UNPKG

arkit

Version:

Visualises JavaScript, TypeScript and Flow codebases as meaningful and committable architecture diagrams

795 lines (730 loc) 22.9 kB
/** * 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, } 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'; export type Type = string; export type Props = { autoFocus?: boolean, children?: mixed, hidden?: boolean, suppressHydrationWarning?: boolean, dangerouslySetInnerHTML?: mixed, style?: { display?: 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 { unstable_scheduleCallback as scheduleDeferredCallback, unstable_cancelCallback as cancelDeferredCallback, } from 'scheduler'; import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags'; export { unstable_now as now, unstable_scheduleCallback as scheduleDeferredCallback, unstable_shouldYield as shouldYield, unstable_cancelCallback as cancelDeferredCallback, } from 'scheduler'; 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; // 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; export const schedulePassiveEffects = scheduleDeferredCallback; export const cancelPassiveEffects = cancelDeferredCallback; // ------------------- // 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); instance.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); } }