Files
react/packages/react-dom/src/client/ReactDOMHostConfig.js
T
Andrew Clark 6568a79931 [Scheduler] requestPaint (#15960)
* [Scheduler] requestPaint

Signals to Scheduler that the browser needs to paint the screen. React
will call it in the commit phase. Scheduler will yield at the end of
the current frame, even if there is no pending input.

When `isInputPending` is not available, this has no effect, because we
yield at the end of every frame regardless.

React will call `requestPaint` in the commit phase as long as there's at
least one effect. We could choose not to call it if none of the effects
are DOM mutations, but this is so rare that it doesn't seem worthwhile
to bother checking.

* Fall back gracefully if requestPaint is missing
2019-06-22 00:15:09 -07:00

1012 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 {precacheFiberNode, updateFiberProps} from './ReactDOMComponentTree';
import {
createElement,
createTextNode,
setInitialProperties,
diffProperties,
updateProperties,
diffHydratedProperties,
diffHydratedText,
trapClickOnNonInteractiveElement,
warnForUnmatchedText,
warnForDeletedHydratableElement,
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
listenToEventResponderEventTypes,
} from './ReactDOMComponent';
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
import setTextContent from './setTextContent';
import {validateDOMNesting, updatedAncestorInfo} from './validateDOMNesting';
import {
isEnabled as ReactBrowserEventEmitterIsEnabled,
setEnabled as ReactBrowserEventEmitterSetEnabled,
} from '../events/ReactBrowserEventEmitter';
import {Namespaces, getChildNamespace} from '../shared/DOMNamespaces';
import {addRootEventTypesForComponentInstance} from '../events/DOMEventResponderSystem';
import {
ELEMENT_NODE,
TEXT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
import dangerousStyleValue from '../shared/dangerousStyleValue';
import type {DOMContainer} from './ReactDOM';
import type {
ReactDOMEventResponder,
ReactDOMEventComponentInstance,
} from 'shared/ReactDOMTypes';
import {
mountEventResponder,
unmountEventResponder,
} from '../events/DOMEventResponderSystem';
import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
import {canUseDOM} from 'shared/ExecutionEnvironment';
export type Type = string;
export type Props = {
autoFocus?: boolean,
children?: mixed,
hidden?: boolean,
suppressHydrationWarning?: boolean,
dangerouslySetInnerHTML?: mixed,
style?: {
display?: string,
},
bottom?: null | number,
left?: null | number,
right?: null | number,
top?: null | number,
};
export type EventTargetChildElement = {
type: string,
props: null | {
style?: {
position?: string,
zIndex?: number,
bottom?: string,
left?: string,
right?: string,
top?: string,
},
},
};
export type Container = Element | Document;
export type Instance = Element;
export type TextInstance = Text;
export type SuspenseInstance = Comment & {_reactRetry?: () => void};
export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
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;
export type UpdatePayload = Array<mixed>;
export type ChildSet = void; // Unused
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
import {
enableSuspenseServerRenderer,
enableEventAPI,
} from 'shared/ReactFeatureFlags';
import warning from 'shared/warning';
const {html: HTML_NAMESPACE} = Namespaces;
let SUPPRESS_HYDRATION_WARNING;
if (__DEV__) {
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
}
const SUSPENSE_START_DATA = '$';
const SUSPENSE_END_DATA = '/$';
const SUSPENSE_PENDING_START_DATA = '$?';
const SUSPENSE_FALLBACK_START_DATA = '$!';
const STYLE = 'style';
let eventsEnabled: ?boolean = null;
let selectionInformation: ?mixed = null;
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
}
return false;
}
export * from 'shared/HostConfigWithNoPersistence';
export function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType;
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
let root = (rootContainerInstance: any).documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
break;
}
default: {
const container: any =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null;
type = container.tagName;
namespace = getChildNamespace(ownNamespace, type);
break;
}
}
if (__DEV__) {
const validatedTag = type.toLowerCase();
const ancestorInfo = updatedAncestorInfo(null, validatedTag);
return {namespace, ancestorInfo, eventData: null};
}
return namespace;
}
export function getChildHostContext(
parentHostContext: HostContext,
type: string,
rootContainerInstance: Container,
): HostContext {
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
const ancestorInfo = updatedAncestorInfo(
parentHostContextDev.ancestorInfo,
type,
);
return {namespace, ancestorInfo, eventData: null};
}
const parentNamespace = ((parentHostContext: any): HostContextProd);
return getChildNamespace(parentNamespace, type);
}
export function getChildHostContextForEventComponent(
parentHostContext: HostContext,
): HostContext {
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const {namespace, ancestorInfo} = parentHostContextDev;
warning(
parentHostContextDev.eventData === null ||
!parentHostContextDev.eventData.isEventTarget,
'validateDOMNesting: React event targets must not have event components as children.',
);
const eventData = {
isEventComponent: true,
isEventTarget: false,
};
return {namespace, ancestorInfo, eventData};
}
return parentHostContext;
}
export function getChildHostContextForEventTarget(
parentHostContext: HostContext,
type: Symbol | number,
): HostContext {
if (__DEV__) {
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
const {namespace, ancestorInfo} = parentHostContextDev;
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
warning(
parentHostContextDev.eventData === null ||
!parentHostContextDev.eventData.isEventComponent,
'validateDOMNesting: <TouchHitTarget> cannot not be a direct child of an event component. ' +
'Ensure <TouchHitTarget> is a direct child of a DOM element.',
);
const parentNamespace = parentHostContextDev.namespace;
if (parentNamespace !== HTML_NAMESPACE) {
throw new Error(
'<TouchHitTarget> was used in an unsupported DOM namespace. ' +
'Ensure the <TouchHitTarget> is used in an HTML namespace.',
);
}
}
const eventData = {
isEventComponent: false,
isEventTarget: true,
};
return {namespace, ancestorInfo, eventData};
}
return parentHostContext;
}
export function getPublicInstance(instance: Instance): * {
return instance;
}
export function prepareForCommit(containerInfo: Container): void {
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
selectionInformation = getSelectionInformation();
ReactBrowserEventEmitterSetEnabled(false);
}
export function resetAfterCommit(containerInfo: Container): void {
restoreSelection(selectionInformation);
selectionInformation = null;
ReactBrowserEventEmitterSetEnabled(eventsEnabled);
eventsEnabled = null;
}
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
export function finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
}
export function prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
if (
typeof newProps.children !== typeof oldProps.children &&
(typeof newProps.children === 'string' ||
typeof newProps.children === 'number')
) {
const string = '' + newProps.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
}
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
}
export function shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
type === 'option' ||
type === 'noscript' ||
typeof props.children === 'string' ||
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
props.dangerouslySetInnerHTML.__html != null)
);
}
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
}
export function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
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,
);
}
}
}
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
export const isPrimaryRenderer = true;
// This initialization code may run even on server environments
// if a component just imports ReactDOM (e.g. for findDOMNode).
// Some environments might not have setTimeout or clearTimeout.
export const scheduleTimeout =
typeof setTimeout === 'function' ? setTimeout : (undefined: any);
export const cancelTimeout =
typeof clearTimeout === 'function' ? clearTimeout : (undefined: any);
export const noTimeout = -1;
// -------------------
// Mutation
// -------------------
export const supportsMutation = true;
export function commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Despite the naming that might imply otherwise, this method only
// fires if there is an `Update` effect scheduled during mounting.
// This happens if `finalizeInitialChildren` returns `true` (which it
// does to implement the `autoFocus` attribute on the client). But
// there are also other cases when this might happen (such as patching
// up text content during hydration mismatch). So we'll check this again.
if (shouldAutoFocusHostComponent(type, newProps)) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
}
}
export function commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}
export function resetTextContent(domElement: Instance): void {
setTextContent(domElement, '');
}
export function commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
}
export function appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
export function appendChildToContainer(
container: DOMContainer,
child: Instance | TextInstance,
): void {
let parentNode;
if (container.nodeType === COMMENT_NODE) {
parentNode = (container.parentNode: any);
parentNode.insertBefore(child, container);
} else {
parentNode = container;
parentNode.appendChild(child);
}
// This container might be used for a portal.
// If something inside a portal is clicked, that click should bubble
// through the React tree. However, on Mobile Safari the click would
// never bubble through the *DOM* tree unless an ancestor with onclick
// event exists. So we wouldn't see it and dispatch it.
// This is why we ensure that non React root containers have inline onclick
// defined.
// https://github.com/facebook/react/issues/11918
const reactRootContainer = container._reactRootContainer;
if (
(reactRootContainer === null || reactRootContainer === undefined) &&
parentNode.onclick === null
) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement));
}
}
export function insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance | SuspenseInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
}
export function insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance | SuspenseInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
}
export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance | SuspenseInstance,
): void {
parentInstance.removeChild(child);
}
export function removeChildFromContainer(
container: Container,
child: Instance | TextInstance | SuspenseInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
}
export function clearSuspenseBoundary(
parentInstance: Instance,
suspenseInstance: SuspenseInstance,
): void {
let node = suspenseInstance;
// Delete all nodes within this suspense boundary.
// There might be nested nodes so we need to keep track of how
// deep we are and only break out when we're back on top.
let depth = 0;
do {
let nextNode = node.nextSibling;
parentInstance.removeChild(node);
if (nextNode && nextNode.nodeType === COMMENT_NODE) {
let data = ((nextNode: any).data: string);
if (data === SUSPENSE_END_DATA) {
if (depth === 0) {
parentInstance.removeChild(nextNode);
return;
} else {
depth--;
}
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA
) {
depth++;
}
}
node = nextNode;
} while (node);
// TODO: Warn, we didn't find the end comment boundary.
}
export function clearSuspenseBoundaryFromContainer(
container: Container,
suspenseInstance: SuspenseInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
clearSuspenseBoundary((container.parentNode: any), suspenseInstance);
} else if (container.nodeType === ELEMENT_NODE) {
clearSuspenseBoundary((container: any), suspenseInstance);
} else {
// Document nodes should never contain suspense boundaries.
}
}
export function hideInstance(instance: Instance): void {
// TODO: Does this work for all element types? What about MathML? Should we
// pass host context to this method?
instance = ((instance: any): HTMLElement);
const style = instance.style;
if (typeof style.setProperty === 'function') {
style.setProperty('display', 'none', 'important');
} else {
style.display = 'none';
}
}
export function hideTextInstance(textInstance: TextInstance): void {
textInstance.nodeValue = '';
}
export function unhideInstance(instance: Instance, props: Props): void {
instance = ((instance: any): HTMLElement);
const styleProp = props[STYLE];
const display =
styleProp !== undefined &&
styleProp !== null &&
styleProp.hasOwnProperty('display')
? styleProp.display
: null;
instance.style.display = dangerousStyleValue('display', display);
}
export function unhideTextInstance(
textInstance: TextInstance,
text: string,
): void {
textInstance.nodeValue = text;
}
// -------------------
// Hydration
// -------------------
export const supportsHydration = true;
export function canHydrateInstance(
instance: HydratableInstance,
type: string,
props: Props,
): null | Instance {
if (
instance.nodeType !== ELEMENT_NODE ||
type.toLowerCase() !== instance.nodeName.toLowerCase()
) {
return null;
}
// This has now been refined to an element node.
return ((instance: any): Instance);
}
export function canHydrateTextInstance(
instance: HydratableInstance,
text: string,
): null | TextInstance {
if (text === '' || instance.nodeType !== TEXT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return null;
}
// This has now been refined to a text node.
return ((instance: any): TextInstance);
}
export function canHydrateSuspenseInstance(
instance: HydratableInstance,
): null | SuspenseInstance {
if (instance.nodeType !== COMMENT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return null;
}
// This has now been refined to a suspense node.
return ((instance: any): SuspenseInstance);
}
export function isSuspenseInstancePending(instance: SuspenseInstance) {
return instance.data === SUSPENSE_PENDING_START_DATA;
}
export function isSuspenseInstanceFallback(instance: SuspenseInstance) {
return instance.data === SUSPENSE_FALLBACK_START_DATA;
}
export function registerSuspenseInstanceRetry(
instance: SuspenseInstance,
callback: () => void,
) {
instance._reactRetry = callback;
}
export function getNextHydratableSibling(
instance: HydratableInstance,
): null | HydratableInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE &&
(!enableSuspenseServerRenderer ||
node.nodeType !== COMMENT_NODE ||
((node: any).data !== SUSPENSE_START_DATA &&
(node: any).data !== SUSPENSE_PENDING_START_DATA &&
(node: any).data !== SUSPENSE_FALLBACK_START_DATA))
) {
node = node.nextSibling;
}
return (node: any);
}
export function getFirstHydratableChild(
parentInstance: Container | Instance,
): null | HydratableInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE &&
(!enableSuspenseServerRenderer ||
next.nodeType !== COMMENT_NODE ||
((next: any).data !== SUSPENSE_START_DATA &&
(next: any).data !== SUSPENSE_FALLBACK_START_DATA &&
(next: any).data !== SUSPENSE_PENDING_START_DATA))
) {
next = next.nextSibling;
}
return (next: any);
}
export function hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
}
export function hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
}
export function getNextHydratableInstanceAfterSuspenseInstance(
suspenseInstance: SuspenseInstance,
): null | HydratableInstance {
let node = suspenseInstance.nextSibling;
// Skip past all nodes within this suspense boundary.
// There might be nested nodes so we need to keep track of how
// deep we are and only break out when we're back on top.
let depth = 0;
while (node) {
if (node.nodeType === COMMENT_NODE) {
let data = ((node: any).data: string);
if (data === SUSPENSE_END_DATA) {
if (depth === 0) {
return getNextHydratableSibling((node: any));
} else {
depth--;
}
} else if (data === SUSPENSE_START_DATA) {
depth++;
}
}
node = node.nextSibling;
}
// TODO: Warn, we didn't find the end comment boundary.
return null;
}
export function didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
}
export function didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
}
export function didNotHydrateContainerInstance(
parentContainer: Container,
instance: HydratableInstance,
) {
if (__DEV__) {
if (instance.nodeType === ELEMENT_NODE) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else if (instance.nodeType === COMMENT_NODE) {
// TODO: warnForDeletedHydratableSuspenseBoundary
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
}
export function didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: HydratableInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === ELEMENT_NODE) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else if (instance.nodeType === COMMENT_NODE) {
// TODO: warnForDeletedHydratableSuspenseBoundary
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
}
export function didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
}
export function didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
}
export function didNotFindHydratableContainerSuspenseInstance(
parentContainer: Container,
) {
if (__DEV__) {
// TODO: warnForInsertedHydratedSupsense(parentContainer);
}
}
export function didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
}
export function didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
}
export function didNotFindHydratableSuspenseInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
// TODO: warnForInsertedHydratedSuspense(parentInstance);
}
}
export function mountEventComponent(
eventComponentInstance: ReactDOMEventComponentInstance,
): void {
if (enableEventAPI) {
const rootContainerInstance = ((eventComponentInstance.rootInstance: any): Container);
const doc = rootContainerInstance.ownerDocument;
const documentBody = doc.body || doc;
const responder = eventComponentInstance.responder;
const {
rootEventTypes,
targetEventTypes,
} = ((responder: any): ReactDOMEventResponder);
if (targetEventTypes !== undefined) {
listenToEventResponderEventTypes(targetEventTypes, documentBody);
}
if (rootEventTypes !== undefined) {
addRootEventTypesForComponentInstance(
eventComponentInstance,
rootEventTypes,
);
listenToEventResponderEventTypes(rootEventTypes, documentBody);
}
mountEventResponder(eventComponentInstance);
}
}
export function updateEventComponent(
eventComponentInstance: ReactDOMEventComponentInstance,
): void {
// NO-OP, why might use this in the future
}
export function unmountEventComponent(
eventComponentInstance: ReactDOMEventComponentInstance,
): void {
if (enableEventAPI) {
// TODO stop listening to targetEventTypes
unmountEventResponder(eventComponentInstance);
}
}
export function getEventTargetChildElement(
type: Symbol | number,
props: Props,
): null | EventTargetChildElement {
if (enableEventAPI) {
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
const {bottom, left, right, top} = props;
if (!bottom && !left && !right && !top) {
return null;
}
return {
type: 'div',
props: {
style: {
position: 'absolute',
zIndex: -1,
pointerEvents: null,
bottom: bottom ? `-${bottom}px` : '0px',
left: left ? `-${left}px` : '0px',
right: right ? `-${right}px` : '0px',
top: top ? `-${top}px` : '0px',
},
hydrateTouchHitTarget: true,
suppressHydrationWarning: true,
},
};
}
}
return null;
}
export function handleEventTarget(
type: Symbol | number,
props: Props,
rootContainerInstance: Container,
internalInstanceHandle: Object,
): boolean {
if (
__DEV__ &&
type === REACT_EVENT_TARGET_TOUCH_HIT &&
(props.left || props.right || props.top || props.bottom)
) {
return true;
}
return false;
}
export function commitEventTarget(
type: Symbol | number,
props: Props,
instance: Instance,
parentInstance: Instance,
): void {
if (enableEventAPI) {
if (type === REACT_EVENT_TARGET_TOUCH_HIT) {
if (__DEV__ && canUseDOM) {
// This is done at DEV time because getComputedStyle will
// typically force a style recalculation and force a layout,
// reflow -– both of which are sync are expensive.
const computedStyles = window.getComputedStyle(parentInstance);
const position = computedStyles.getPropertyValue('position');
warning(
position !== '' && position !== 'static',
'<TouchHitTarget> inserts an empty absolutely positioned <div>. ' +
'This requires its parent DOM node to be positioned too, but the ' +
'parent DOM node was found to have the style "position" set to ' +
'either no value, or a value of "static". Try using a "position" ' +
'value of "relative".',
);
warning(
computedStyles.getPropertyValue('z-index') !== '',
'<TouchHitTarget> inserts an empty <div> with "z-index" of "-1". ' +
'This requires its parent DOM node to have a "z-index" greater than "-1",' +
'but the parent DOM node was found to no "z-index" value set.' +
' Try using a "z-index" value of "0" or greater.',
);
}
}
}
}