From b83e01cade6f86b9a127bdfdda60f625fd14fdcd Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 20 Mar 2019 11:20:17 +0000 Subject: [PATCH] Adds more scaffolding for experimental event API (#15112) * Adds more scaffolding for experimental event API --- packages/react-art/src/ReactARTHostConfig.js | 6 +- .../src/client/ReactDOMHostConfig.js | 87 +++- .../src/ReactFabricHostConfig.js | 10 +- .../src/ReactNativeHostConfig.js | 10 +- .../src/createReactNoop.js | 61 ++- packages/react-reconciler/src/ReactFiber.js | 65 ++- .../src/ReactFiberBeginWork.js | 53 +- .../src/ReactFiberCommitWork.js | 18 +- .../src/ReactFiberCompleteWork.js | 21 +- .../src/ReactFiberHostContext.js | 23 +- .../ReactFiberEvents-test-internal.js | 361 +++++++++++++ .../__tests__/ReactFiberHostContext-test.js | 3 + .../src/forks/ReactFiberHostConfig.custom.js | 2 + .../src/ReactTestHostConfig.js | 49 +- packages/shared/ReactSymbols.js | 2 +- packages/shared/ReactTypes.js | 10 +- .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../shared/getElementFromTouchHitTarget.js | 85 ++++ scripts/rollup/results.json | 478 +++++++++--------- 19 files changed, 1066 insertions(+), 279 deletions(-) create mode 100644 packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js create mode 100644 packages/shared/getElementFromTouchHitTarget.js diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js index 187c49e0d3d91..caf2d5da18aae 100644 --- a/packages/react-art/src/ReactARTHostConfig.js +++ b/packages/react-art/src/ReactARTHostConfig.js @@ -340,6 +340,10 @@ export function getChildHostContext() { return NO_CONTEXT; } +export function getChildHostContextForEvent() { + return NO_CONTEXT; +} + export const scheduleTimeout = setTimeout; export const cancelTimeout = clearTimeout; export const noTimeout = -1; @@ -440,7 +444,7 @@ export function handleEventComponent( } export function handleEventTarget( - type: string, + type: Symbol | number, props: Props, internalInstanceHandle: Object, ) { diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 60808a8336e79..6f84f0c6fc91f 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -44,6 +44,12 @@ import dangerousStyleValue from '../shared/dangerousStyleValue'; import type {DOMContainer} from './ReactDOM'; import type {ReactEventResponder} from 'shared/ReactTypes'; +import { + REACT_EVENT_COMPONENT_TYPE, + REACT_EVENT_TARGET_TYPE, + REACT_EVENT_TARGET_TOUCH_HIT, +} from 'shared/ReactSymbols'; +import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget'; export type Type = string; export type Props = { @@ -65,6 +71,10 @@ export type PublicInstance = Element | Text; type HostContextDev = { namespace: string, ancestorInfo: mixed, + eventData: null | {| + isEventComponent?: boolean, + isEventTarget?: boolean, + |}, }; type HostContextProd = string; export type HostContext = HostContextDev | HostContextProd; @@ -73,7 +83,11 @@ export type ChildSet = void; // Unused export type TimeoutHandle = TimeoutID; export type NoTimeout = -1; -import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags'; +import { + enableSuspenseServerRenderer, + enableEventAPI, +} from 'shared/ReactFeatureFlags'; +import warning from 'shared/warning'; // Intentionally not named imports because Rollup would // use dynamic dispatch for CommonJS interop named imports. @@ -142,7 +156,7 @@ export function getRootHostContext( if (__DEV__) { const validatedTag = type.toLowerCase(); const ancestorInfo = updatedAncestorInfo(null, validatedTag); - return {namespace, ancestorInfo}; + return {namespace, ancestorInfo, eventData: null}; } return namespace; } @@ -159,12 +173,42 @@ export function getChildHostContext( parentHostContextDev.ancestorInfo, type, ); - return {namespace, ancestorInfo}; + return {namespace, ancestorInfo, eventData: null}; } const parentNamespace = ((parentHostContext: any): HostContextProd); return getChildNamespace(parentNamespace, type); } +export function getChildHostContextForEvent( + parentHostContext: HostContext, + type: Symbol | number, +): HostContext { + if (__DEV__) { + const parentHostContextDev = ((parentHostContext: any): HostContextDev); + const {namespace, ancestorInfo} = parentHostContextDev; + let eventData = null; + + if (type === REACT_EVENT_COMPONENT_TYPE) { + eventData = { + isEventComponent: true, + isEventTarget: false, + }; + } else if (type === REACT_EVENT_TARGET_TYPE) { + warning( + parentHostContextDev.eventData !== null && + parentHostContextDev.eventData.isEventComponent, + 'validateDOMNesting: React event targets must be direct children of event components.', + ); + eventData = { + isEventComponent: false, + isEventTarget: true, + }; + } + return {namespace, ancestorInfo, eventData}; + } + return parentHostContext; +} + export function getPublicInstance(instance: Instance): * { return instance; } @@ -296,6 +340,23 @@ export function createTextInstance( if (__DEV__) { const hostContextDev = ((hostContext: any): HostContextDev); validateDOMNesting(null, text, hostContextDev.ancestorInfo); + if (enableEventAPI) { + const eventData = hostContextDev.eventData; + if (eventData !== null) { + warning( + !eventData.isEventComponent, + 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + warning( + !eventData.isEventTarget, + 'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + } + } } const textNode: TextInstance = createTextNode(text, rootContainerInstance); precacheFiberNode(internalInstanceHandle, textNode); @@ -804,9 +865,25 @@ export function handleEventComponent( } export function handleEventTarget( - type: string, + type: Symbol | number, props: Props, internalInstanceHandle: Object, ) { - // TODO: add handleEventTarget implementation + // Touch target hit slop handling + if (type === REACT_EVENT_TARGET_TOUCH_HIT) { + // Validates that there is a single element + const element = getElementFromTouchHitTarget(internalInstanceHandle); + if (element !== null) { + // We update the event target state node to be that of the element. + // We can then diff this entry to determine if we need to add the + // hit slop element, or change the dimensions of the hit slop. + const lastElement = internalInstanceHandle.stateNode; + if (lastElement !== element) { + internalInstanceHandle.stateNode = element; + // TODO: Create the hit slop element and attach it to the element + } else { + // TODO: Diff the left, top, right, bottom props + } + } + } } diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 2c62f43b4344a..945f528de00b1 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -283,6 +283,14 @@ export function getChildHostContext( } } +export function getChildHostContextForEvent( + parentHostContext: HostContext, + type: Symbol | number, +) { + // TODO: add getChildHostContextForEvent implementation + return parentHostContext; +} + export function getPublicInstance(instance: Instance): * { return instance.canonical; } @@ -428,7 +436,7 @@ export function handleEventComponent( } export function handleEventTarget( - type: string, + type: Symbol | number, props: Props, internalInstanceHandle: Object, ) { diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js index d1fff312ccfa7..27044ef54df2f 100644 --- a/packages/react-native-renderer/src/ReactNativeHostConfig.js +++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js @@ -206,6 +206,14 @@ export function getChildHostContext( } } +export function getChildHostContextForEvent( + parentHostContext: HostContext, + type: Symbol | number, +) { + // TODO: add getChildHostContextForEvent implementation + return parentHostContext; +} + export function getPublicInstance(instance: Instance): * { return instance; } @@ -487,7 +495,7 @@ export function handleEventComponent( } export function handleEventTarget( - type: string, + type: Symbol | number, props: Props, internalInstanceHandle: Object, ) { diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index d7cfef9cc7648..1d7fa86fbc91e 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -21,8 +21,18 @@ import type {ReactNodeList} from 'shared/ReactTypes'; import * as Scheduler from 'scheduler/unstable_mock'; import {createPortal} from 'shared/ReactPortal'; import expect from 'expect'; -import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; +import { + REACT_FRAGMENT_TYPE, + REACT_ELEMENT_TYPE, + REACT_EVENT_COMPONENT_TYPE, + REACT_EVENT_TARGET_TYPE, + REACT_EVENT_TARGET_TOUCH_HIT, +} from 'shared/ReactSymbols'; import warningWithoutStack from 'shared/warningWithoutStack'; +import warning from 'shared/warning'; +import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget'; + +import {enableEventAPI} from 'shared/ReactFeatureFlags'; // for .act's return value type Thenable = { @@ -54,6 +64,8 @@ type HostContext = Object; const NO_CONTEXT = {}; const UPPERCASE_CONTEXT = {}; +const EVENT_COMPONENT_CONTEXT = {}; +const EVENT_TARGET_CONTEXT = {}; const UPDATE_SIGNAL = {}; if (__DEV__) { Object.freeze(NO_CONTEXT); @@ -250,6 +262,24 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { return NO_CONTEXT; }, + getChildHostContextForEvent( + parentHostContext: HostContext, + type: Symbol | number, + ) { + if (__DEV__ && enableEventAPI) { + if (type === REACT_EVENT_COMPONENT_TYPE) { + return EVENT_COMPONENT_CONTEXT; + } else if (type === REACT_EVENT_TARGET_TYPE) { + warning( + parentHostContext === EVENT_COMPONENT_CONTEXT, + 'validateDOMNesting: React event targets must be direct children of event components.', + ); + return EVENT_TARGET_CONTEXT; + } + } + return parentHostContext; + }, + getPublicInstance(instance) { return instance; }, @@ -333,6 +363,20 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { hostContext: Object, internalInstanceHandle: Object, ): TextInstance { + if (__DEV__ && enableEventAPI) { + warning( + hostContext !== EVENT_COMPONENT_CONTEXT, + 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + warning( + hostContext !== EVENT_TARGET_CONTEXT, + 'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + } if (hostContext === UPPERCASE_CONTEXT) { text = text.toUpperCase(); } @@ -363,6 +407,21 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { isPrimaryRenderer: true, supportsHydration: false, + + handleEventComponent() { + // NO-OP + }, + + handleEventTarget( + type: Symbol | number, + props: Props, + internalInstanceHandle: Object, + ) { + if (type === REACT_EVENT_TARGET_TOUCH_HIT) { + // Validates that there is a single element + getElementFromTouchHitTarget(internalInstanceHandle); + } + }, }; const hostConfig = useMutation diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 08bff4899e9ed..b5a8b249a54c3 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -8,7 +8,13 @@ */ import type {ReactElement, Source} from 'shared/ReactElementType'; -import type {ReactFragment, ReactPortal, RefObject} from 'shared/ReactTypes'; +import type { + ReactFragment, + ReactPortal, + RefObject, + ReactEventComponent, + ReactEventTarget, +} from 'shared/ReactTypes'; import type {WorkTag} from 'shared/ReactWorkTags'; import type {TypeOfMode} from './ReactTypeOfMode'; import type {SideEffectTag} from 'shared/ReactSideEffectTags'; @@ -19,7 +25,7 @@ import type {HookType} from './ReactFiberHooks'; import invariant from 'shared/invariant'; import warningWithoutStack from 'shared/warningWithoutStack'; -import {enableProfilerTimer} from 'shared/ReactFeatureFlags'; +import {enableProfilerTimer, enableEventAPI} from 'shared/ReactFeatureFlags'; import {NoEffect} from 'shared/ReactSideEffectTags'; import { IndeterminateComponent, @@ -38,6 +44,8 @@ import { FunctionComponent, MemoComponent, LazyComponent, + EventComponent, + EventTarget, } from 'shared/ReactWorkTags'; import getComponentName from 'shared/getComponentName'; @@ -60,6 +68,8 @@ import { REACT_SUSPENSE_TYPE, REACT_MEMO_TYPE, REACT_LAZY_TYPE, + REACT_EVENT_COMPONENT_TYPE, + REACT_EVENT_TARGET_TYPE, } from 'shared/ReactSymbols'; let hasBadMapPolyfill; @@ -503,6 +513,28 @@ export function createFiberFromTypeAndProps( fiberTag = LazyComponent; resolvedType = null; break getTag; + case REACT_EVENT_COMPONENT_TYPE: + if (enableEventAPI) { + return createFiberFromEventComponent( + type, + pendingProps, + mode, + expirationTime, + key, + ); + } + break; + case REACT_EVENT_TARGET_TYPE: + if (enableEventAPI) { + return createFiberFromEventTarget( + type, + pendingProps, + mode, + expirationTime, + key, + ); + } + break; } } let info = ''; @@ -581,6 +613,35 @@ export function createFiberFromFragment( return fiber; } +export function createFiberFromEventComponent( + eventComponent: ReactEventComponent, + pendingProps: any, + mode: TypeOfMode, + expirationTime: ExpirationTime, + key: null | string, +): Fiber { + const fiber = createFiber(EventComponent, pendingProps, key, mode); + fiber.elementType = eventComponent; + fiber.type = eventComponent; + fiber.stateNode = new Map(); + fiber.expirationTime = expirationTime; + return fiber; +} + +export function createFiberFromEventTarget( + eventTarget: ReactEventTarget, + pendingProps: any, + mode: TypeOfMode, + expirationTime: ExpirationTime, + key: null | string, +): Fiber { + const fiber = createFiber(EventTarget, pendingProps, key, mode); + fiber.elementType = eventTarget; + fiber.type = eventTarget; + fiber.expirationTime = expirationTime; + return fiber; +} + function createFiberFromProfiler( pendingProps: any, mode: TypeOfMode, diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 6cc20994c266d..3c5e0fddae0e1 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -35,6 +35,8 @@ import { SimpleMemoComponent, LazyComponent, IncompleteClassComponent, + EventComponent, + EventTarget, } from 'shared/ReactWorkTags'; import { NoEffect, @@ -52,6 +54,7 @@ import { debugRenderPhaseSideEffectsForStrictMode, enableProfilerTimer, enableSuspenseServerRenderer, + enableEventAPI, } from 'shared/ReactFeatureFlags'; import invariant from 'shared/invariant'; import shallowEqual from 'shared/shallowEqual'; @@ -93,7 +96,11 @@ import { registerSuspenseInstanceRetry, } from './ReactFiberHostConfig'; import type {SuspenseInstance} from './ReactFiberHostConfig'; -import {pushHostContext, pushHostContainer} from './ReactFiberHostContext'; +import { + pushHostContext, + pushHostContainer, + pushHostContextForEvent, +} from './ReactFiberHostContext'; import { pushProvider, propagateContextChange, @@ -1943,6 +1950,34 @@ function updateContextConsumer( return workInProgress.child; } +function updateEventComponent(current, workInProgress, renderExpirationTime) { + const nextProps = workInProgress.pendingProps; + let nextChildren = nextProps.children; + + reconcileChildren( + current, + workInProgress, + nextChildren, + renderExpirationTime, + ); + pushHostContextForEvent(workInProgress); + return workInProgress.child; +} + +function updateEventTarget(current, workInProgress, renderExpirationTime) { + const nextProps = workInProgress.pendingProps; + let nextChildren = nextProps.children; + + reconcileChildren( + current, + workInProgress, + nextChildren, + renderExpirationTime, + ); + pushHostContextForEvent(workInProgress); + return workInProgress.child; +} + export function markWorkInProgressReceivedUpdate() { didReceiveUpdate = true; } @@ -2259,6 +2294,22 @@ function beginWork( } break; } + case EventComponent: { + if (enableEventAPI) { + return updateEventComponent( + current, + workInProgress, + renderExpirationTime, + ); + } + break; + } + case EventTarget: { + if (enableEventAPI) { + return updateEventTarget(current, workInProgress, renderExpirationTime); + } + break; + } } invariant( false, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 544dccfd449a9..a3e479c20ad62 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -43,7 +43,6 @@ import { IncompleteClassComponent, MemoComponent, SimpleMemoComponent, - EventTarget, } from 'shared/ReactWorkTags'; import { invokeGuardedCallback, @@ -91,7 +90,6 @@ import { hideTextInstance, unhideInstance, unhideTextInstance, - handleEventTarget, } from './ReactFiberHostConfig'; import { captureCommitPhaseError, @@ -301,7 +299,6 @@ function commitBeforeMutationLifeCycles( case HostText: case HostPortal: case IncompleteClassComponent: - case EventTarget: // Nothing to do for these component types return; default: { @@ -588,7 +585,6 @@ function commitLifeCycles( } case SuspenseComponent: case IncompleteClassComponent: - case EventTarget: break; default: { invariant( @@ -819,12 +815,8 @@ function commitContainer(finishedWork: Fiber) { } switch (finishedWork.tag) { - case ClassComponent: { - return; - } - case HostComponent: { - return; - } + case ClassComponent: + case HostComponent: case HostText: { return; } @@ -1216,12 +1208,6 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { case IncompleteClassComponent: { return; } - case EventTarget: { - const newProps = finishedWork.memoizedProps; - const type = finishedWork.type.type; - handleEventTarget(type, newProps, finishedWork); - return; - } default: { invariant( false, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 00535beffe2b6..8a16dbe43befd 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -66,6 +66,7 @@ import { appendChildToContainerChildSet, finalizeContainerChildren, handleEventComponent, + handleEventTarget, } from './ReactFiberHostConfig'; import { getRootHostContainer, @@ -85,7 +86,10 @@ import { skipPastDehydratedSuspenseInstance, popHydrationState, } from './ReactFiberHydrationContext'; -import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags'; +import { + enableSuspenseServerRenderer, + enableEventAPI, +} from 'shared/ReactFeatureFlags'; function markUpdate(workInProgress: Fiber) { // Tag the fiber with an update effect. This turns a Placement into @@ -766,13 +770,20 @@ function completeWork( break; } case EventComponent: { - const rootContainerInstance = getRootHostContainer(); - const responder = workInProgress.type.responder; - handleEventComponent(responder, rootContainerInstance, workInProgress); + if (enableEventAPI) { + popHostContext(workInProgress); + const rootContainerInstance = getRootHostContainer(); + const responder = workInProgress.type.responder; + handleEventComponent(responder, rootContainerInstance, workInProgress); + } break; } case EventTarget: { - markUpdate(workInProgress); + if (enableEventAPI) { + popHostContext(workInProgress); + const type = workInProgress.type.type; + handleEventTarget(type, newProps, workInProgress); + } break; } default: diff --git a/packages/react-reconciler/src/ReactFiberHostContext.js b/packages/react-reconciler/src/ReactFiberHostContext.js index 6b7eed3dd52db..54598c6eed6f3 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.js @@ -13,7 +13,11 @@ import type {Container, HostContext} from './ReactFiberHostConfig'; import invariant from 'shared/invariant'; -import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig'; +import { + getChildHostContext, + getRootHostContext, + getChildHostContextForEvent, +} from './ReactFiberHostConfig'; import {createCursor, push, pop} from './ReactFiberStack'; declare class NoContextT {} @@ -92,6 +96,22 @@ function pushHostContext(fiber: Fiber): void { push(contextStackCursor, nextContext, fiber); } +function pushHostContextForEvent(fiber: Fiber): void { + const context: HostContext = requiredContext(contextStackCursor.current); + const eventTypeof = fiber.type.$$typeof; + const nextContext = getChildHostContextForEvent(context, eventTypeof); + + // Don't push this Fiber's context unless it's unique. + if (context === nextContext) { + return; + } + + // Track the context and the Fiber that provided it. + // This enables us to pop only Fibers that provide unique contexts. + push(contextFiberStackCursor, fiber, fiber); + push(contextStackCursor, nextContext, fiber); +} + function popHostContext(fiber: Fiber): void { // Do not pop unless this Fiber provided the current context. // pushHostContext() only pushes Fibers that provide unique contexts. @@ -110,4 +130,5 @@ export { popHostContext, pushHostContainer, pushHostContext, + pushHostContextForEvent, }; diff --git a/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js new file mode 100644 index 0000000000000..9e7bcf180471f --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactFiberEvents-test-internal.js @@ -0,0 +1,361 @@ +/** + * 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 + * @jest-environment node + */ + +'use strict'; + +let React; +let ReactNoop; +let Scheduler; +let ReactFeatureFlags; +let EventComponent; +let ReactTestRenderer; +let EventTarget; +let ReactEvents; + +const noOpResponder = { + targetEventTypes: [], + handleEvent() {}, +}; + +function createReactEventComponent() { + return { + $$typeof: Symbol.for('react.event_component'), + props: null, + responder: noOpResponder, + }; +} + +function init() { + jest.resetModules(); + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableEventAPI = true; + React = require('react'); + Scheduler = require('scheduler'); + ReactEvents = require('react-events'); +} + +function initNoopRenderer() { + init(); + ReactNoop = require('react-noop-renderer'); +} + +function initTestRenderer() { + init(); + ReactTestRenderer = require('react-test-renderer'); +} + +// This is a new feature in Fiber so I put it in its own test file. It could +// probably move to one of the other test files once it is official. +describe('ReactTopLevelText', () => { + describe('NoopRenderer', () => { + beforeEach(() => { + initNoopRenderer(); + EventComponent = createReactEventComponent(); + EventTarget = ReactEvents.TouchHitTarget; + }); + + it('should render a simple event component with a single child', () => { + const Test = () => ( + +
Hello world
+
+ ); + + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + expect(ReactNoop).toMatchRenderedOutput(
Hello world
); + }); + + it('should warn when an event component has a direct text child', () => { + const Test = () => Hello world; + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should warn when an event component has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + ); + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should render a simple event component with a single event target', () => { + const Test = () => ( + + +
Hello world
+
+
+ ); + + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + expect(ReactNoop).toMatchRenderedOutput(
Hello world
); + }); + + it('should warn when an event target has a direct text child', () => { + const Test = () => ( + + Hello world + + ); + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + + + ); + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has more than one child', () => { + const Test = () => ( + + + Child 1 + Child 2 + + + ); + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: must only have a single DOM element as a child. Found many children.', + ); + }); + + it('should warn if an event target is not a direct child of an event component', () => { + const Test = () => ( + +
+ + Child 1 + +
+
+ ); + + expect(() => { + ReactNoop.render(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event targets must be direct children of event components.', + ); + }); + }); + + describe('TestRenderer', () => { + beforeEach(() => { + initTestRenderer(); + EventComponent = createReactEventComponent(); + EventTarget = ReactEvents.TouchHitTarget; + }); + + it('should render a simple event component with a single child', () => { + const Test = () => ( + +
Hello world
+
+ ); + + const root = ReactTestRenderer.create(null); + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + expect(root).toMatchRenderedOutput(
Hello world
); + }); + + it('should warn when an event component has a direct text child', () => { + const Test = () => Hello world; + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should warn when an event component has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + ); + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + ); + }); + + it('should render a simple event component with a single event target', () => { + const Test = () => ( + + +
Hello world
+
+
+ ); + + const root = ReactTestRenderer.create(null); + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + expect(root).toMatchRenderedOutput(
Hello world
); + + const Test2 = () => ( + + + I am now a span + + + ); + + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + expect(root).toMatchRenderedOutput(I am now a span); + }); + + it('should warn when an event target has a direct text child', () => { + const Test = () => ( + + Hello world + + ); + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has a direct text child #2', () => { + const ChildWrapper = () => 'Hello world'; + const Test = () => ( + + + + + + ); + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev([ + 'Warning: validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "Hello world" in an element.', + 'Warning: must have a single DOM element as a child. Found no children.', + ]); + }); + + it('should warn when an event target has more than one child', () => { + const Test = () => ( + + + Child 1 + Child 2 + + + ); + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: must only have a single DOM element as a child. Found many children.', + ); + // This should not fire a warning, as this is now valid. + const Test2 = () => ( + + + Child 1 + + + ); + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + expect(root).toMatchRenderedOutput(Child 1); + }); + + it('should warn if an event target is not a direct child of an event component', () => { + const Test = () => ( + +
+ + Child 1 + +
+
+ ); + + const root = ReactTestRenderer.create(null); + expect(() => { + root.update(); + expect(Scheduler).toFlushWithoutYielding(); + }).toWarnDev( + 'Warning: validateDOMNesting: React event targets must be direct children of event components.', + ); + }); + }); +}); diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js index 4dbc13bd8148c..4efb059a28e2a 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.js @@ -31,6 +31,9 @@ describe('ReactFiberHostContext', () => { getChildHostContext: function() { return null; }, + getChildHostContextForEvent: function() { + return null; + }, shouldSetTextContent: function() { return false; }, diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index 67a6ed333bec0..0926eb8cfbd1e 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -41,6 +41,8 @@ export opaque type NoTimeout = mixed; // eslint-disable-line no-undef export const getPublicInstance = $$$hostConfig.getPublicInstance; export const getRootHostContext = $$$hostConfig.getRootHostContext; export const getChildHostContext = $$$hostConfig.getChildHostContext; +export const getChildHostContextForEvent = + $$$hostConfig.getChildHostContextForEvent; export const prepareForCommit = $$$hostConfig.prepareForCommit; export const resetAfterCommit = $$$hostConfig.resetAfterCommit; export const createInstance = $$$hostConfig.createInstance; diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js index 27d1f0f57d28f..a86fc4b266517 100644 --- a/packages/react-test-renderer/src/ReactTestHostConfig.js +++ b/packages/react-test-renderer/src/ReactTestHostConfig.js @@ -10,6 +10,14 @@ import warning from 'shared/warning'; import type {ReactEventResponder} from 'shared/ReactTypes'; +import { + REACT_EVENT_COMPONENT_TYPE, + REACT_EVENT_TARGET_TYPE, + REACT_EVENT_TARGET_TOUCH_HIT, +} from 'shared/ReactSymbols'; +import getElementFromTouchHitTarget from 'shared/getElementFromTouchHitTarget'; + +import {enableEventAPI} from 'shared/ReactFeatureFlags'; export type Type = string; export type Props = Object; @@ -42,6 +50,8 @@ export type NoTimeout = -1; export * from 'shared/HostConfigWithNoPersistence'; export * from 'shared/HostConfigWithNoHydration'; +const EVENT_COMPONENT_CONTEXT = {}; +const EVENT_TARGET_CONTEXT = {}; const NO_CONTEXT = {}; const UPDATE_SIGNAL = {}; if (__DEV__) { @@ -117,6 +127,24 @@ export function getChildHostContext( return NO_CONTEXT; } +export function getChildHostContextForEvent( + parentHostContext: HostContext, + type: Symbol | number, +): HostContext { + if (__DEV__ && enableEventAPI) { + if (type === REACT_EVENT_COMPONENT_TYPE) { + return EVENT_COMPONENT_CONTEXT; + } else if (type === REACT_EVENT_TARGET_TYPE) { + warning( + parentHostContext === EVENT_COMPONENT_CONTEXT, + 'validateDOMNesting: React event targets must be direct children of event components.', + ); + return EVENT_TARGET_CONTEXT; + } + } + return NO_CONTEXT; +} + export function prepareForCommit(containerInfo: Container): void { // noop } @@ -188,6 +216,20 @@ export function createTextInstance( hostContext: Object, internalInstanceHandle: Object, ): TextInstance { + if (__DEV__ && enableEventAPI) { + warning( + hostContext !== EVENT_COMPONENT_CONTEXT, + 'validateDOMNesting: React event components cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + warning( + hostContext !== EVENT_TARGET_CONTEXT, + 'validateDOMNesting: React event targets cannot have text DOM nodes as children. ' + + 'Wrap the child text "%s" in an element.', + text, + ); + } return { text, isHidden: false, @@ -272,9 +314,12 @@ export function handleEventComponent( } export function handleEventTarget( - type: string, + type: Symbol | number, props: Props, internalInstanceHandle: Object, ) { - // TODO: add handleEventTarget implementation + if (type === REACT_EVENT_TARGET_TOUCH_HIT) { + // Validates that there is a single element + getElementFromTouchHitTarget(internalInstanceHandle); + } } diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 45e8a57e04e43..cde9f89c5b463 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -47,7 +47,7 @@ export const REACT_SUSPENSE_TYPE = hasSymbol export const REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3; export const REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4; export const REACT_EVENT_COMPONENT_TYPE = hasSymbol - ? Symbol.for('react.event') + ? Symbol.for('react.event_component') : 0xead5; export const REACT_EVENT_TARGET_TYPE = hasSymbol ? Symbol.for('react.event_target') diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 41d9503ec7545..cedd17d170a2a 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -14,7 +14,7 @@ export type ReactNode = | ReactFragment | ReactProvider | ReactConsumer - | ReactEvent + | ReactEventComponent | ReactEventTarget; export type ReactEmpty = null | void | boolean; @@ -81,13 +81,17 @@ export type RefObject = {| current: any, |}; +export type ReactEventResponderEventType = + | string + | {name: string, passive?: boolean, capture?: boolean}; + export type ReactEventResponder = { - targetEventTypes: Array, + targetEventTypes: Array, createInitialState?: (props: Object) => Object, handleEvent: (context: Object, props: Object, state: Object) => void, }; -export type ReactEvent = {| +export type ReactEventComponent = {| $$typeof: Symbol | number, props: null | Object, responder: ReactEventResponder, diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 7239941035bc5..bd9aa64dd4764 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -27,6 +27,7 @@ export const enableStableConcurrentModeAPIs = false; export const warnAboutShorthandPropertyCollision = false; export const enableSchedulerDebugging = false; export const warnAboutDeprecatedSetNativeProps = false; +export const enableEventAPI = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/getElementFromTouchHitTarget.js b/packages/shared/getElementFromTouchHitTarget.js new file mode 100644 index 0000000000000..dea4fd7f31884 --- /dev/null +++ b/packages/shared/getElementFromTouchHitTarget.js @@ -0,0 +1,85 @@ +/** + * 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 {Fiber} from 'react-reconciler/src/ReactFiber'; + +import {HostComponent} from 'shared/ReactWorkTags'; +import warning from 'shared/warning'; + +type HostContext = Object; + +type TextInstance = + | Text + | {| + text: string, + id: number, + hidden: boolean, + context: HostContext, + |}; + +type Instance = + | Element + | {| + type: string, + id: number, + children: Array, + text: string | null, + prop: any, + hidden: boolean, + context: HostContext, + |}; + +export default function getElementFromTouchHitTarget( + targetFiber: Fiber, +): null | Instance { + // Traverse through child fibers and find the first host components + let node = targetFiber.child; + let hostComponent = null; + + while (node !== null) { + if (node.tag === HostComponent) { + if (__DEV__) { + if (hostComponent === null) { + hostComponent = node.stateNode; + } else { + warning( + false, + ' must only have a single DOM element as a child. ' + + 'Found many children.', + ); + } + while (node !== null) { + if (node === targetFiber) { + return hostComponent; + } else if (node.sibling !== null) { + node = node.sibling; + break; + } + node = node.return; + } + } else { + return node.stateNode; + } + } else if (node.child !== null) { + node = node.child; + } else if (node.sibling !== null) { + node = node.sibling; + } else { + break; + } + } + if (__DEV__) { + warning( + hostComponent !== null, + ' must have a single DOM element as a child. ' + + 'Found no children.', + ); + } + return hostComponent; +} diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 1e19f2a2581fa..ad8460e020670 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -4,29 +4,29 @@ "filename": "react.development.js", "bundleType": "UMD_DEV", "packageName": "react", - "size": 102032, - "gzip": 26457 + "size": 102492, + "gzip": 26625 }, { "filename": "react.production.min.js", "bundleType": "UMD_PROD", "packageName": "react", - "size": 12564, - "gzip": 4826 + "size": 12609, + "gzip": 4834 }, { "filename": "react.development.js", "bundleType": "NODE_DEV", "packageName": "react", - "size": 63704, - "gzip": 17181 + "size": 64139, + "gzip": 17318 }, { "filename": "react.production.min.js", "bundleType": "NODE_PROD", "packageName": "react", - "size": 6831, - "gzip": 2817 + "size": 6834, + "gzip": 2814 }, { "filename": "React-dev.js", @@ -46,29 +46,29 @@ "filename": "react-dom.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 791682, - "gzip": 179962 + "size": 803793, + "gzip": 183116 }, { "filename": "react-dom.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 107808, - "gzip": 34704 + "size": 107752, + "gzip": 34911 }, { "filename": "react-dom.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 786187, - "gzip": 178415 + "size": 798048, + "gzip": 181482 }, { "filename": "react-dom.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108031, - "gzip": 34186 + "size": 107733, + "gzip": 34431 }, { "filename": "ReactDOM-dev.js", @@ -88,29 +88,29 @@ "filename": "react-dom-test-utils.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 48181, - "gzip": 13291 + "size": 48274, + "gzip": 13318 }, { "filename": "react-dom-test-utils.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 10504, - "gzip": 3882 + "size": 10511, + "gzip": 3880 }, { "filename": "react-dom-test-utils.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 47895, - "gzip": 13220 + "size": 47988, + "gzip": 13245 }, { "filename": "react-dom-test-utils.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 10281, - "gzip": 3814 + "size": 10288, + "gzip": 3812 }, { "filename": "ReactTestUtils-dev.js", @@ -123,29 +123,29 @@ "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 62058, - "gzip": 16289 + "size": 62061, + "gzip": 16285 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 11263, - "gzip": 3892 + "size": 11266, + "gzip": 3889 }, { "filename": "react-dom-unstable-native-dependencies.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 61722, - "gzip": 16156 + "size": 61725, + "gzip": 16151 }, { "filename": "react-dom-unstable-native-dependencies.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 10998, - "gzip": 3785 + "size": 11001, + "gzip": 3783 }, { "filename": "ReactDOMUnstableNativeDependencies-dev.js", @@ -165,29 +165,29 @@ "filename": "react-dom-server.browser.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 130425, - "gzip": 34746 + "size": 133264, + "gzip": 35517 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 19319, - "gzip": 7379 + "size": 19666, + "gzip": 7443 }, { "filename": "react-dom-server.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 126463, - "gzip": 33795 + "size": 129302, + "gzip": 34571 }, { "filename": "react-dom-server.browser.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 19239, - "gzip": 7368 + "size": 19590, + "gzip": 7447 }, { "filename": "ReactDOMServer-dev.js", @@ -207,43 +207,43 @@ "filename": "react-dom-server.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 128570, - "gzip": 34348 + "size": 131409, + "gzip": 35129 }, { "filename": "react-dom-server.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 20132, - "gzip": 7685 + "size": 20483, + "gzip": 7757 }, { "filename": "react-art.development.js", "bundleType": "UMD_DEV", "packageName": "react-art", - "size": 562387, - "gzip": 121860 + "size": 570224, + "gzip": 123819 }, { "filename": "react-art.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-art", - "size": 99616, - "gzip": 30538 + "size": 99370, + "gzip": 30598 }, { "filename": "react-art.development.js", "bundleType": "NODE_DEV", "packageName": "react-art", - "size": 491818, - "gzip": 104175 + "size": 499479, + "gzip": 106084 }, { "filename": "react-art.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-art", - "size": 63873, - "gzip": 19446 + "size": 63450, + "gzip": 19455 }, { "filename": "ReactART-dev.js", @@ -291,29 +291,29 @@ "filename": "react-test-renderer.development.js", "bundleType": "UMD_DEV", "packageName": "react-test-renderer", - "size": 503952, - "gzip": 106548 + "size": 508633, + "gzip": 107760 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-test-renderer", - "size": 65222, - "gzip": 19962 + "size": 64479, + "gzip": 19791 }, { "filename": "react-test-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 498258, - "gzip": 105207 + "size": 504043, + "gzip": 106587 }, { "filename": "react-test-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 64873, - "gzip": 19614 + "size": 64119, + "gzip": 19538 }, { "filename": "ReactTestRenderer-dev.js", @@ -326,29 +326,29 @@ "filename": "react-test-renderer-shallow.development.js", "bundleType": "UMD_DEV", "packageName": "react-test-renderer", - "size": 38084, - "gzip": 9724 + "size": 38113, + "gzip": 9733 }, { "filename": "react-test-renderer-shallow.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-test-renderer", - "size": 11382, - "gzip": 3422 + "size": 11385, + "gzip": 3419 }, { "filename": "react-test-renderer-shallow.development.js", "bundleType": "NODE_DEV", "packageName": "react-test-renderer", - "size": 32246, - "gzip": 8323 + "size": 32275, + "gzip": 8333 }, { "filename": "react-test-renderer-shallow.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-test-renderer", - "size": 12043, - "gzip": 3734 + "size": 12046, + "gzip": 3733 }, { "filename": "ReactShallowRenderer-dev.js", @@ -361,57 +361,57 @@ "filename": "react-noop-renderer.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 28240, - "gzip": 6740 + "size": 26949, + "gzip": 6362 }, { "filename": "react-noop-renderer.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 10081, - "gzip": 3367 + "size": 9933, + "gzip": 3165 }, { "filename": "react-reconciler.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 489120, - "gzip": 102507 + "size": 497879, + "gzip": 104645 }, { "filename": "react-reconciler.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 65080, - "gzip": 19234 + "size": 64551, + "gzip": 19325 }, { "filename": "react-reconciler-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 487276, - "gzip": 101780 + "size": 495898, + "gzip": 103848 }, { "filename": "react-reconciler-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 65091, - "gzip": 19239 + "size": 64562, + "gzip": 19330 }, { "filename": "react-reconciler-reflection.development.js", "bundleType": "NODE_DEV", "packageName": "react-reconciler", - "size": 16132, - "gzip": 5091 + "size": 16161, + "gzip": 5096 }, { "filename": "react-reconciler-reflection.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-reconciler", - "size": 2757, - "gzip": 1247 + "size": 2760, + "gzip": 1244 }, { "filename": "react-call-return.development.js", @@ -431,29 +431,29 @@ "filename": "react-is.development.js", "bundleType": "UMD_DEV", "packageName": "react-is", - "size": 8255, - "gzip": 2495 + "size": 8551, + "gzip": 2554 }, { "filename": "react-is.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-is", - "size": 2342, - "gzip": 898 + "size": 2466, + "gzip": 928 }, { "filename": "react-is.development.js", "bundleType": "NODE_DEV", "packageName": "react-is", - "size": 8066, - "gzip": 2445 + "size": 8362, + "gzip": 2502 }, { "filename": "react-is.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-is", - "size": 2339, - "gzip": 838 + "size": 2463, + "gzip": 870 }, { "filename": "ReactIs-dev.js", @@ -487,22 +487,22 @@ "filename": "create-subscription.development.js", "bundleType": "NODE_DEV", "packageName": "create-subscription", - "size": 8535, - "gzip": 2957 + "size": 8538, + "gzip": 2952 }, { "filename": "create-subscription.production.min.js", "bundleType": "NODE_PROD", "packageName": "create-subscription", - "size": 2886, - "gzip": 1346 + "size": 2889, + "gzip": 1344 }, { "filename": "React-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react", - "size": 60786, - "gzip": 16140 + "size": 61163, + "gzip": 16239 }, { "filename": "React-prod.js", @@ -515,22 +515,22 @@ "filename": "ReactDOM-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 809740, - "gzip": 179616 + "size": 821918, + "gzip": 182691 }, { "filename": "ReactDOM-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 329960, - "gzip": 60112 + "size": 333267, + "gzip": 61003 }, { "filename": "ReactTestUtils-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 44795, - "gzip": 12159 + "size": 44870, + "gzip": 12188 }, { "filename": "ReactDOMUnstableNativeDependencies-dev.js", @@ -550,113 +550,113 @@ "filename": "ReactDOMServer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 127337, - "gzip": 33235 + "size": 130310, + "gzip": 33947 }, { "filename": "ReactDOMServer-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 46343, - "gzip": 10662 + "size": 46994, + "gzip": 10956 }, { "filename": "ReactART-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-art", - "size": 501297, - "gzip": 103384 + "size": 509037, + "gzip": 105217 }, { "filename": "ReactART-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-art", - "size": 200132, - "gzip": 33817 + "size": 201874, + "gzip": 34245 }, { "filename": "ReactNativeRenderer-dev.js", "bundleType": "RN_FB_DEV", "packageName": "react-native-renderer", - "size": 631255, - "gzip": 134845 + "size": 637829, + "gzip": 136280 }, { "filename": "ReactNativeRenderer-prod.js", "bundleType": "RN_FB_PROD", "packageName": "react-native-renderer", - "size": 252735, - "gzip": 44084 + "size": 251569, + "gzip": 43973 }, { "filename": "ReactNativeRenderer-dev.js", "bundleType": "RN_OSS_DEV", "packageName": "react-native-renderer", - "size": 631168, - "gzip": 134810 + "size": 637742, + "gzip": 136246 }, { "filename": "ReactNativeRenderer-prod.js", "bundleType": "RN_OSS_PROD", "packageName": "react-native-renderer", - "size": 252749, - "gzip": 44079 + "size": 251583, + "gzip": 43970 }, { "filename": "ReactFabric-dev.js", "bundleType": "RN_FB_DEV", "packageName": "react-native-renderer", - "size": 621889, - "gzip": 132504 + "size": 628028, + "gzip": 133874 }, { "filename": "ReactFabric-prod.js", "bundleType": "RN_FB_PROD", "packageName": "react-native-renderer", - "size": 244896, - "gzip": 42571 + "size": 245497, + "gzip": 42746 }, { "filename": "ReactFabric-dev.js", "bundleType": "RN_OSS_DEV", "packageName": "react-native-renderer", - "size": 621794, - "gzip": 132455 + "size": 627933, + "gzip": 133834 }, { "filename": "ReactFabric-prod.js", "bundleType": "RN_OSS_PROD", "packageName": "react-native-renderer", - "size": 244902, - "gzip": 42561 + "size": 245503, + "gzip": 42741 }, { "filename": "ReactTestRenderer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-test-renderer", - "size": 508573, - "gzip": 104823 + "size": 514514, + "gzip": 106185 }, { "filename": "ReactShallowRenderer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-test-renderer", - "size": 30628, - "gzip": 7749 + "size": 30652, + "gzip": 7761 }, { "filename": "ReactIs-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-is", - "size": 6437, - "gzip": 1724 + "size": 6754, + "gzip": 1788 }, { "filename": "ReactIs-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-is", - "size": 4838, - "gzip": 1202 + "size": 5133, + "gzip": 1256 }, { "filename": "scheduler.development.js", @@ -676,15 +676,15 @@ "filename": "scheduler.development.js", "bundleType": "NODE_DEV", "packageName": "scheduler", - "size": 23505, - "gzip": 6019 + "size": 23508, + "gzip": 6017 }, { "filename": "scheduler.production.min.js", "bundleType": "NODE_PROD", "packageName": "scheduler", - "size": 4888, - "gzip": 1819 + "size": 4891, + "gzip": 1813 }, { "filename": "SimpleCacheProvider-dev.js", @@ -704,36 +704,36 @@ "filename": "react-noop-renderer-persistent.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 28359, - "gzip": 6753 + "size": 27068, + "gzip": 6375 }, { "filename": "react-noop-renderer-persistent.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 10103, - "gzip": 3373 + "size": 9955, + "gzip": 3170 }, { "filename": "react-dom.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111178, - "gzip": 35023 + "size": 110923, + "gzip": 35060 }, { "filename": "ReactNativeRenderer-profiling.js", "bundleType": "RN_OSS_PROFILING", "packageName": "react-native-renderer", - "size": 259254, - "gzip": 45666 + "size": 257964, + "gzip": 45359 }, { "filename": "ReactFabric-profiling.js", "bundleType": "RN_OSS_PROFILING", "packageName": "react-native-renderer", - "size": 251284, - "gzip": 44137 + "size": 250954, + "gzip": 44143 }, { "filename": "Scheduler-dev.js", @@ -767,64 +767,64 @@ "filename": "ReactDOM-profiling.js", "bundleType": "FB_WWW_PROFILING", "packageName": "react-dom", - "size": 336694, - "gzip": 61627 + "size": 339339, + "gzip": 62379 }, { "filename": "ReactNativeRenderer-profiling.js", "bundleType": "RN_FB_PROFILING", "packageName": "react-native-renderer", - "size": 259235, - "gzip": 45675 + "size": 257945, + "gzip": 45363 }, { "filename": "ReactFabric-profiling.js", "bundleType": "RN_FB_PROFILING", "packageName": "react-native-renderer", - "size": 251273, - "gzip": 44141 + "size": 250943, + "gzip": 44145 }, { "filename": "react.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react", - "size": 14773, - "gzip": 5356 + "size": 14818, + "gzip": 5369 }, { "filename": "react-dom.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react-dom", - "size": 110796, - "gzip": 35607 + "size": 110829, + "gzip": 35621 }, { "filename": "scheduler-tracing.development.js", "bundleType": "NODE_DEV", "packageName": "scheduler", - "size": 10737, - "gzip": 2535 + "size": 10878, + "gzip": 2594 }, { "filename": "scheduler-tracing.production.min.js", "bundleType": "NODE_PROD", "packageName": "scheduler", - "size": 719, - "gzip": 374 + "size": 722, + "gzip": 372 }, { "filename": "scheduler-tracing.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "scheduler", - "size": 3334, - "gzip": 991 + "size": 3337, + "gzip": 987 }, { "filename": "SchedulerTracing-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "scheduler", - "size": 10207, - "gzip": 2134 + "size": 10267, + "gzip": 2151 }, { "filename": "SchedulerTracing-prod.js", @@ -844,106 +844,106 @@ "filename": "react-cache.development.js", "bundleType": "NODE_DEV", "packageName": "react-cache", - "size": 9030, - "gzip": 3016 + "size": 9192, + "gzip": 3076 }, { "filename": "react-cache.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-cache", - "size": 2199, - "gzip": 1125 + "size": 2202, + "gzip": 1121 }, { "filename": "ReactCache-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-cache", - "size": 7429, - "gzip": 2370 + "size": 7587, + "gzip": 2444 }, { "filename": "ReactCache-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-cache", - "size": 5200, - "gzip": 1641 + "size": 5235, + "gzip": 1648 }, { "filename": "react-cache.development.js", "bundleType": "UMD_DEV", "packageName": "react-cache", - "size": 9261, - "gzip": 3090 + "size": 9423, + "gzip": 3152 }, { "filename": "react-cache.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-cache", - "size": 2398, - "gzip": 1215 + "size": 2405, + "gzip": 1214 }, { "filename": "jest-react.development.js", "bundleType": "NODE_DEV", "packageName": "jest-react", - "size": 9286, - "gzip": 3053 + "size": 7455, + "gzip": 2737 }, { "filename": "jest-react.production.min.js", "bundleType": "NODE_PROD", "packageName": "jest-react", - "size": 3716, - "gzip": 1655 + "size": 2930, + "gzip": 1442 }, { "filename": "JestReact-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "jest-react", - "size": 6125, - "gzip": 1827 + "size": 4208, + "gzip": 1490 }, { "filename": "JestReact-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "jest-react", - "size": 5201, - "gzip": 1623 + "size": 3592, + "gzip": 1315 }, { "filename": "react-debug-tools.development.js", "bundleType": "NODE_DEV", "packageName": "react-debug-tools", - "size": 19024, - "gzip": 5638 + "size": 19625, + "gzip": 5835 }, { "filename": "react-debug-tools.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-debug-tools", - "size": 5800, - "gzip": 2343 + "size": 5929, + "gzip": 2395 }, { "filename": "eslint-plugin-react-hooks.development.js", "bundleType": "NODE_DEV", "packageName": "eslint-plugin-react-hooks", - "size": 50458, - "gzip": 11887 + "size": 75099, + "gzip": 17223 }, { "filename": "eslint-plugin-react-hooks.production.min.js", "bundleType": "NODE_PROD", "packageName": "eslint-plugin-react-hooks", - "size": 12598, - "gzip": 4566 + "size": 19731, + "gzip": 6811 }, { "filename": "ReactDOMFizzServer-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 3772, - "gzip": 1432 + "size": 3796, + "gzip": 1444 }, { "filename": "ReactDOMFizzServer-prod.js", @@ -956,141 +956,141 @@ "filename": "react-noop-renderer-server.development.js", "bundleType": "NODE_DEV", "packageName": "react-noop-renderer", - "size": 1861, - "gzip": 869 + "size": 1864, + "gzip": 868 }, { "filename": "react-noop-renderer-server.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-noop-renderer", - "size": 804, - "gzip": 482 + "size": 807, + "gzip": 480 }, { "filename": "react-stream.development.js", "bundleType": "NODE_DEV", "packageName": "react-stream", - "size": 4633, - "gzip": 1683 + "size": 4674, + "gzip": 1693 }, { "filename": "react-stream.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-stream", - "size": 1224, + "size": 1227, "gzip": 655 }, { "filename": "react-dom-unstable-fizz.browser.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 3704, - "gzip": 1463 + "size": 3745, + "gzip": 1476 }, { "filename": "react-dom-unstable-fizz.browser.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 1227, - "gzip": 697 + "size": 1230, + "gzip": 694 }, { "filename": "react-dom-unstable-fizz.browser.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 3528, - "gzip": 1416 + "size": 3569, + "gzip": 1433 }, { "filename": "react-dom-unstable-fizz.browser.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 1063, + "size": 1066, "gzip": 628 }, { "filename": "react-dom-unstable-fizz.node.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 3780, - "gzip": 1443 + "size": 3821, + "gzip": 1459 }, { "filename": "react-dom-unstable-fizz.node.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 1122, + "size": 1125, "gzip": 659 }, { "filename": "ESLintPluginReactHooks-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "eslint-plugin-react-hooks", - "size": 54073, - "gzip": 12239 + "size": 80483, + "gzip": 17729 }, { "filename": "react-dom-unstable-fire.development.js", "bundleType": "UMD_DEV", "packageName": "react-dom", - "size": 792036, - "gzip": 180102 + "size": 804147, + "gzip": 183253 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "UMD_PROD", "packageName": "react-dom", - "size": 107823, - "gzip": 34713 + "size": 107767, + "gzip": 34920 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "UMD_PROFILING", "packageName": "react-dom", - "size": 110811, - "gzip": 35616 + "size": 110844, + "gzip": 35630 }, { "filename": "react-dom-unstable-fire.development.js", "bundleType": "NODE_DEV", "packageName": "react-dom", - "size": 786540, - "gzip": 178557 + "size": 798401, + "gzip": 181622 }, { "filename": "react-dom-unstable-fire.production.min.js", "bundleType": "NODE_PROD", "packageName": "react-dom", - "size": 108045, - "gzip": 34197 + "size": 107747, + "gzip": 34441 }, { "filename": "react-dom-unstable-fire.profiling.min.js", "bundleType": "NODE_PROFILING", "packageName": "react-dom", - "size": 111192, - "gzip": 35033 + "size": 110937, + "gzip": 35069 }, { "filename": "ReactFire-dev.js", "bundleType": "FB_WWW_DEV", "packageName": "react-dom", - "size": 808931, - "gzip": 179569 + "size": 821109, + "gzip": 182607 }, { "filename": "ReactFire-prod.js", "bundleType": "FB_WWW_PROD", "packageName": "react-dom", - "size": 318110, - "gzip": 57721 + "size": 321531, + "gzip": 58583 }, { "filename": "ReactFire-profiling.js", "bundleType": "FB_WWW_PROFILING", "packageName": "react-dom", - "size": 324881, - "gzip": 59172 + "size": 327694, + "gzip": 59916 }, { "filename": "jest-mock-scheduler.development.js", @@ -1124,15 +1124,15 @@ "filename": "scheduler-unstable_mock.development.js", "bundleType": "NODE_DEV", "packageName": "scheduler", - "size": 17926, - "gzip": 4128 + "size": 17929, + "gzip": 4125 }, { "filename": "scheduler-unstable_mock.production.min.js", "bundleType": "NODE_PROD", "packageName": "scheduler", - "size": 4173, - "gzip": 1606 + "size": 4176, + "gzip": 1603 }, { "filename": "SchedulerMock-dev.js",