mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
960 lines
27 KiB
JavaScript
960 lines
27 KiB
JavaScript
/**
|
|
* 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 {getChildNamespace} from '../shared/DOMNamespaces';
|
|
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,
|
|
ReactDOMEventResponderInstance,
|
|
ReactDOMFundamentalComponentInstance,
|
|
} from 'shared/ReactDOMTypes';
|
|
import {
|
|
addRootEventTypesForResponderInstance,
|
|
mountEventResponder,
|
|
unmountEventResponder,
|
|
} from '../events/DOMEventResponderSystem';
|
|
|
|
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,
|
|
};
|
|
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,
|
|
enableFlareAPI,
|
|
enableFundamentalAPI,
|
|
} from 'shared/ReactFeatureFlags';
|
|
|
|
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};
|
|
}
|
|
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};
|
|
}
|
|
const parentNamespace = ((parentHostContext: any): HostContextProd);
|
|
return getChildNamespace(parentNamespace, type);
|
|
}
|
|
|
|
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);
|
|
}
|
|
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
|
|
precacheFiberNode(internalInstanceHandle, textNode);
|
|
return textNode;
|
|
}
|
|
|
|
export const isPrimaryRenderer = true;
|
|
export const warnsIfNotActing = 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;
|
|
}
|
|
|
|
function getNextHydratable(node) {
|
|
// Skip non-hydratable nodes.
|
|
for (; node != null; node = node.nextSibling) {
|
|
const nodeType = node.nodeType;
|
|
if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) {
|
|
break;
|
|
}
|
|
if (enableSuspenseServerRenderer) {
|
|
if (nodeType === COMMENT_NODE) {
|
|
const nodeData = (node: any).data;
|
|
if (
|
|
nodeData === SUSPENSE_START_DATA ||
|
|
nodeData === SUSPENSE_FALLBACK_START_DATA ||
|
|
nodeData === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return (node: any);
|
|
}
|
|
|
|
export function getNextHydratableSibling(
|
|
instance: HydratableInstance,
|
|
): null | HydratableInstance {
|
|
return getNextHydratable(instance.nextSibling);
|
|
}
|
|
|
|
export function getFirstHydratableChild(
|
|
parentInstance: Container | Instance,
|
|
): null | HydratableInstance {
|
|
return getNextHydratable(parentInstance.firstChild);
|
|
}
|
|
|
|
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 hydrateSuspenseInstance(
|
|
suspenseInstance: SuspenseInstance,
|
|
internalInstanceHandle: Object,
|
|
) {
|
|
precacheFiberNode(internalInstanceHandle, suspenseInstance);
|
|
}
|
|
|
|
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 ||
|
|
data === SUSPENSE_FALLBACK_START_DATA ||
|
|
data === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
depth++;
|
|
}
|
|
}
|
|
node = node.nextSibling;
|
|
}
|
|
// TODO: Warn, we didn't find the end comment boundary.
|
|
return null;
|
|
}
|
|
|
|
// Returns the SuspenseInstance if this node is a direct child of a
|
|
// SuspenseInstance. I.e. if its previous sibling is a Comment with
|
|
// SUSPENSE_x_START_DATA. Otherwise, null.
|
|
export function getParentSuspenseInstance(
|
|
targetInstance: Instance,
|
|
): null | SuspenseInstance {
|
|
let node = targetInstance.previousSibling;
|
|
// 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_START_DATA ||
|
|
data === SUSPENSE_FALLBACK_START_DATA ||
|
|
data === SUSPENSE_PENDING_START_DATA
|
|
) {
|
|
if (depth === 0) {
|
|
return ((node: any): SuspenseInstance);
|
|
} else {
|
|
depth--;
|
|
}
|
|
} else if (data === SUSPENSE_END_DATA) {
|
|
depth++;
|
|
}
|
|
}
|
|
node = node.previousSibling;
|
|
}
|
|
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 mountResponderInstance(
|
|
responder: ReactDOMEventResponder,
|
|
responderInstance: ReactDOMEventResponderInstance,
|
|
responderProps: Object,
|
|
responderState: Object,
|
|
instance: Instance,
|
|
): ReactDOMEventResponderInstance {
|
|
// Listen to events
|
|
const doc = instance.ownerDocument;
|
|
const {
|
|
rootEventTypes,
|
|
targetEventTypes,
|
|
} = ((responder: any): ReactDOMEventResponder);
|
|
if (targetEventTypes !== null) {
|
|
listenToEventResponderEventTypes(targetEventTypes, doc);
|
|
}
|
|
if (rootEventTypes !== null) {
|
|
addRootEventTypesForResponderInstance(responderInstance, rootEventTypes);
|
|
listenToEventResponderEventTypes(rootEventTypes, doc);
|
|
}
|
|
mountEventResponder(
|
|
responder,
|
|
responderInstance,
|
|
responderProps,
|
|
responderState,
|
|
);
|
|
return responderInstance;
|
|
}
|
|
|
|
export function unmountResponderInstance(
|
|
responderInstance: ReactDOMEventResponderInstance,
|
|
): void {
|
|
if (enableFlareAPI) {
|
|
// TODO stop listening to targetEventTypes
|
|
unmountEventResponder(responderInstance);
|
|
}
|
|
}
|
|
|
|
export function getFundamentalComponentInstance(
|
|
fundamentalInstance: ReactDOMFundamentalComponentInstance,
|
|
): Instance {
|
|
if (enableFundamentalAPI) {
|
|
const {currentFiber, impl, props, state} = fundamentalInstance;
|
|
const instance = impl.getInstance(null, props, state);
|
|
precacheFiberNode(currentFiber, instance);
|
|
return instance;
|
|
}
|
|
// Because of the flag above, this gets around the Flow error;
|
|
return (null: any);
|
|
}
|
|
|
|
export function mountFundamentalComponent(
|
|
fundamentalInstance: ReactDOMFundamentalComponentInstance,
|
|
): void {
|
|
if (enableFundamentalAPI) {
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
const onMount = impl.onMount;
|
|
if (onMount !== undefined) {
|
|
onMount(null, instance, props, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function shouldUpdateFundamentalComponent(
|
|
fundamentalInstance: ReactDOMFundamentalComponentInstance,
|
|
): boolean {
|
|
if (enableFundamentalAPI) {
|
|
const {impl, prevProps, props, state} = fundamentalInstance;
|
|
const shouldUpdate = impl.shouldUpdate;
|
|
if (shouldUpdate !== undefined) {
|
|
return shouldUpdate(null, prevProps, props, state);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function updateFundamentalComponent(
|
|
fundamentalInstance: ReactDOMFundamentalComponentInstance,
|
|
): void {
|
|
if (enableFundamentalAPI) {
|
|
const {impl, instance, prevProps, props, state} = fundamentalInstance;
|
|
const onUpdate = impl.onUpdate;
|
|
if (onUpdate !== undefined) {
|
|
onUpdate(null, instance, prevProps, props, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function unmountFundamentalComponent(
|
|
fundamentalInstance: ReactDOMFundamentalComponentInstance,
|
|
): void {
|
|
if (enableFundamentalAPI) {
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
const onUnmount = impl.onUnmount;
|
|
if (onUnmount !== undefined) {
|
|
onUnmount(null, instance, props, state);
|
|
}
|
|
}
|
|
}
|