Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds more scaffolding for experimental event API #15112

Merged
merged 17 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function getRootHostContext(

export function getChildHostContext(
trueadm marked this conversation as resolved.
Show resolved Hide resolved
parentHostContext: HostContext,
type: string,
type: string | Symbol,
rootContainerInstance: Container,
): HostContext {
if (__DEV__) {
Expand Down
21 changes: 20 additions & 1 deletion packages/react-dom/src/client/validateDOMNesting.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/

import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
import warningWithoutStack from 'shared/warningWithoutStack';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import {getCurrentFiberStackInDev} from 'react-reconciler/src/ReactCurrentFiber';

import {enableEventAPI} from 'shared/ReactFeatureFlags';

let validateDOMNesting = () => {};
let updatedAncestorInfo = () => {};

Expand Down Expand Up @@ -159,10 +162,18 @@ if (__DEV__) {
dlItemTagAutoclosing: null,
};

updatedAncestorInfo = function(oldInfo, tag) {
updatedAncestorInfo = function(oldInfo, tag: string | Symbol) {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
let ancestorInfo = {...(oldInfo || emptyAncestorInfo)};
let info = {tag};

if (enableEventAPI) {
if (tag === REACT_EVENT_COMPONENT_TYPE) {
ancestorInfo.inEventComponent = true;
return ancestorInfo;
} else {
ancestorInfo.inEventComponent = false;
}
}
if (inScopeTags.indexOf(tag) !== -1) {
ancestorInfo.aTagInScope = null;
ancestorInfo.buttonTagInScope = null;
Expand Down Expand Up @@ -411,6 +422,14 @@ if (__DEV__) {
const parentTag = parentInfo && parentInfo.tag;

if (childText != null) {
if (enableEventAPI) {
warningWithoutStack(
!ancestorInfo.inEventComponent,
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
'Wrap the child text "%s" in an element.',
childText,
);
}
warningWithoutStack(
childTag == null,
'validateDOMNesting: when childText is passed, childTag should be null',
Expand Down
9 changes: 8 additions & 1 deletion packages/react-dom/src/shared/DOMNamespaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/

import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';

import {enableEventAPI} from 'shared/ReactFeatureFlags';

const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
Expand All @@ -29,8 +33,11 @@ export function getIntrinsicNamespace(type: string): string {

export function getChildNamespace(
parentNamespace: string | null,
type: string,
type: string | Symbol,
trueadm marked this conversation as resolved.
Show resolved Hide resolved
): string {
if (enableEventAPI && type === REACT_EVENT_COMPONENT_TYPE) {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
trueadm marked this conversation as resolved.
Show resolved Hide resolved
return parentNamespace;
}
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
// No (or default) parent namespace: potential entry point.
return getIntrinsicNamespace(type);
Expand Down
65 changes: 63 additions & 2 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
*/

import type {ReactElement, Source} from 'shared/ReactElementType';
import type {ReactFragment, ReactPortal, RefObject} from 'shared/ReactTypes';
import type {
ReactFragment,
ReactPortal,
RefObject,
ReactEvent,
ReactEventTarget,
} from 'shared/ReactTypes';
import type {WorkTag} from 'shared/ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {SideEffectTag} from 'shared/ReactSideEffectTags';
Expand All @@ -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,
Expand All @@ -38,6 +44,8 @@ import {
FunctionComponent,
MemoComponent,
LazyComponent,
EventComponent,
EventTarget,
} from 'shared/ReactWorkTags';
import getComponentName from 'shared/getComponentName';

Expand All @@ -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;
Expand Down Expand Up @@ -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 = '';
Expand Down Expand Up @@ -581,6 +613,35 @@ export function createFiberFromFragment(
return fiber;
}

export function createFiberFromEventComponent(
event: ReactEvent,
pendingProps: any,
mode: TypeOfMode,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
const fiber = createFiber(EventComponent, pendingProps, key, mode);
fiber.elementType = event;
fiber.type = event;
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,
Expand Down
76 changes: 76 additions & 0 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
SimpleMemoComponent,
LazyComponent,
IncompleteClassComponent,
EventComponent,
EventTarget,
} from 'shared/ReactWorkTags';
import {
NoEffect,
Expand All @@ -52,6 +54,7 @@ import {
debugRenderPhaseSideEffectsForStrictMode,
enableProfilerTimer,
enableSuspenseServerRenderer,
enableEventAPI,
} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
import shallowEqual from 'shared/shallowEqual';
Expand Down Expand Up @@ -1923,6 +1926,63 @@ function updateContextConsumer(
return workInProgress.child;
}

function updateEventComponent(current, workInProgress, renderExpirationTime) {
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;

reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
if (__DEV__) {
// We need to validate the event component does not have any direct children
// that are text host nodes (once resolved). So we use host context in the
// renderer and validate this inside the renderer. We only do this in DEV.
pushHostContext(workInProgress);
}
return workInProgress.child;
}

function updateEventTarget(current, workInProgress, renderExpirationTime) {
const nextProps = workInProgress.pendingProps;
let nextChildren = nextProps.children;

reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
const parent = workInProgress.return;
invariant(
parent !== null && parent.tag === Event,
'Event target components must be direct children of event components',
);
trueadm marked this conversation as resolved.
Show resolved Hide resolved
// These warnings only occur in DEV to reduce overhead in production
if (__DEV__ && nextProps.type === 'touch-hit') {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
let childrenCount = 0;
let child = workInProgress.child;
while (child !== null) {
if (child.tag === HostText) {
childrenCount++;
warning(
false,
'<TouchHitTarget> cannot have text nodes as direct children',
);
} else if (child.tag === HostComponent) {
childrenCount++;
}
child = child.sibling;
}
if (childrenCount !== 1) {
warning(false, '<TouchHitTarget> must only have a single child.');
}
}
return workInProgress.child;
}

export function markWorkInProgressReceivedUpdate() {
didReceiveUpdate = true;
}
Expand Down Expand Up @@ -2239,6 +2299,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,
Expand Down
9 changes: 6 additions & 3 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
enableSchedulerTracing,
enableProfilerTimer,
enableSuspenseServerRenderer,
enableEventAPI,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
Expand Down Expand Up @@ -1217,9 +1218,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
return;
}
case EventTarget: {
const newProps = finishedWork.memoizedProps;
const type = finishedWork.type.type;
handleEventTarget(type, newProps, finishedWork);
if (enableEventAPI) {
const newProps = finishedWork.memoizedProps;
const type = finishedWork.type.type;
handleEventTarget(type, newProps, finishedWork);
}
return;
}
default: {
Expand Down
22 changes: 17 additions & 5 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,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
Expand Down Expand Up @@ -766,13 +769,22 @@ function completeWork(
break;
}
case EventComponent: {
const rootContainerInstance = getRootHostContainer();
const responder = workInProgress.type.responder;
handleEventComponent(responder, rootContainerInstance, workInProgress);
if (enableEventAPI) {
if (__DEV__) {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
// We validate that event components do not have text nodes as direct children
// in the renderer using host context. We only do this in DEV.
popHostContext(workInProgress);
}
const rootContainerInstance = getRootHostContainer();
const responder = workInProgress.type.responder;
handleEventComponent(responder, rootContainerInstance, workInProgress);
}
break;
}
case EventTarget: {
markUpdate(workInProgress);
if (enableEventAPI) {
markUpdate(workInProgress);
trueadm marked this conversation as resolved.
Show resolved Hide resolved
}
break;
}
default:
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/ReactSymbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,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() {
Expand Down