mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Adds more scaffolding for experimental event API (#15112)
* Adds more scaffolding for experimental event API
This commit is contained in:
+5
-1
@@ -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,
|
||||
) {
|
||||
|
||||
+82
-5
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
+60
-1
@@ -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
|
||||
|
||||
+63
-2
@@ -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,
|
||||
|
||||
+52
-1
@@ -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,
|
||||
|
||||
+2
-16
@@ -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,
|
||||
|
||||
+16
-5
@@ -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:
|
||||
|
||||
+22
-1
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 = () => (
|
||||
<EventComponent>
|
||||
<div>Hello world</div>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
ReactNoop.render(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput(<div>Hello world</div>);
|
||||
});
|
||||
|
||||
it('should warn when an event component has a direct text child', () => {
|
||||
const Test = () => <EventComponent>Hello world</EventComponent>;
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
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 = () => (
|
||||
<EventComponent>
|
||||
<ChildWrapper />
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<div>Hello world</div>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
ReactNoop.render(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(ReactNoop).toMatchRenderedOutput(<div>Hello world</div>);
|
||||
});
|
||||
|
||||
it('should warn when an event target has a direct text child', () => {
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>Hello world</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
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: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<ChildWrapper />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
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: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>Child 1</span>
|
||||
<span>Child 2</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<div>
|
||||
<EventTarget>
|
||||
<span>Child 1</span>
|
||||
</EventTarget>
|
||||
</div>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactNoop.render(<Test />);
|
||||
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 = () => (
|
||||
<EventComponent>
|
||||
<div>Hello world</div>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(<div>Hello world</div>);
|
||||
});
|
||||
|
||||
it('should warn when an event component has a direct text child', () => {
|
||||
const Test = () => <EventComponent>Hello world</EventComponent>;
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
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 = () => (
|
||||
<EventComponent>
|
||||
<ChildWrapper />
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<div>Hello world</div>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
root.update(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(<div>Hello world</div>);
|
||||
|
||||
const Test2 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>I am now a span</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
root.update(<Test2 />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(<span>I am now a span</span>);
|
||||
});
|
||||
|
||||
it('should warn when an event target has a direct text child', () => {
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>Hello world</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
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: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<ChildWrapper />
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
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: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>Child 1</span>
|
||||
<span>Child 2</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: <TouchHitTarget> 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 = () => (
|
||||
<EventComponent>
|
||||
<EventTarget>
|
||||
<span>Child 1</span>
|
||||
</EventTarget>
|
||||
</EventComponent>
|
||||
);
|
||||
root.update(<Test2 />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(root).toMatchRenderedOutput(<span>Child 1</span>);
|
||||
});
|
||||
|
||||
it('should warn if an event target is not a direct child of an event component', () => {
|
||||
const Test = () => (
|
||||
<EventComponent>
|
||||
<div>
|
||||
<EventTarget>
|
||||
<span>Child 1</span>
|
||||
</EventTarget>
|
||||
</div>
|
||||
</EventComponent>
|
||||
);
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
expect(() => {
|
||||
root.update(<Test />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
}).toWarnDev(
|
||||
'Warning: validateDOMNesting: React event targets must be direct children of event components.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -31,6 +31,9 @@ describe('ReactFiberHostContext', () => {
|
||||
getChildHostContext: function() {
|
||||
return null;
|
||||
},
|
||||
getChildHostContextForEvent: function() {
|
||||
return null;
|
||||
},
|
||||
shouldSetTextContent: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
+47
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -14,7 +14,7 @@ export type ReactNode =
|
||||
| ReactFragment
|
||||
| ReactProvider<any>
|
||||
| ReactConsumer<any>
|
||||
| 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<string>,
|
||||
targetEventTypes: Array<ReactEventResponderEventType>,
|
||||
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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<Instance | TextInstance>,
|
||||
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,
|
||||
'<TouchHitTarget> 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,
|
||||
'<TouchHitTarget> must have a single DOM element as a child. ' +
|
||||
'Found no children.',
|
||||
);
|
||||
}
|
||||
return hostComponent;
|
||||
}
|
||||
+239
-239
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user