diff --git a/packages/react-art/src/ReactART.js b/packages/react-art/src/ReactART.js
index a7f9a3064ce1..d24395c46226 100644
--- a/packages/react-art/src/ReactART.js
+++ b/packages/react-art/src/ReactART.js
@@ -7,267 +7,18 @@
import React from 'react';
import ReactFiberReconciler from 'react-reconciler';
-import * as ReactScheduler from 'react-scheduler';
+import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
-import Transform from 'art/core/transform';
-import invariant from 'fbjs/lib/invariant';
-import emptyObject from 'fbjs/lib/emptyObject';
+
+import ReactARTHostConfig from './ReactARTHostConfig';
+import {TYPES, childrenAsString} from './ReactARTInternals';
Mode.setCurrent(
// Change to 'art/modes/dom' for easier debugging via SVG
FastNoSideEffects,
);
-const pooledTransform = new Transform();
-
-const EVENT_TYPES = {
- onClick: 'click',
- onMouseMove: 'mousemove',
- onMouseOver: 'mouseover',
- onMouseOut: 'mouseout',
- onMouseUp: 'mouseup',
- onMouseDown: 'mousedown',
-};
-
-const TYPES = {
- CLIPPING_RECTANGLE: 'ClippingRectangle',
- GROUP: 'Group',
- SHAPE: 'Shape',
- TEXT: 'Text',
-};
-
-const UPDATE_SIGNAL = {};
-
-/** Helper Methods */
-
-function addEventListeners(instance, type, listener) {
- // We need to explicitly unregister before unmount.
- // For this reason we need to track subscriptions.
- if (!instance._listeners) {
- instance._listeners = {};
- instance._subscriptions = {};
- }
-
- instance._listeners[type] = listener;
-
- if (listener) {
- if (!instance._subscriptions[type]) {
- instance._subscriptions[type] = instance.subscribe(
- type,
- createEventHandler(instance),
- instance,
- );
- }
- } else {
- if (instance._subscriptions[type]) {
- instance._subscriptions[type]();
- delete instance._subscriptions[type];
- }
- }
-}
-
-function childrenAsString(children) {
- if (!children) {
- return '';
- } else if (typeof children === 'string') {
- return children;
- } else if (children.length) {
- return children.join('');
- } else {
- return '';
- }
-}
-
-function createEventHandler(instance) {
- return function handleEvent(event) {
- const listener = instance._listeners[event.type];
-
- if (!listener) {
- // Noop
- } else if (typeof listener === 'function') {
- listener.call(instance, event);
- } else if (listener.handleEvent) {
- listener.handleEvent(event);
- }
- };
-}
-
-function destroyEventListeners(instance) {
- if (instance._subscriptions) {
- for (let type in instance._subscriptions) {
- instance._subscriptions[type]();
- }
- }
-
- instance._subscriptions = null;
- instance._listeners = null;
-}
-
-function getScaleX(props) {
- if (props.scaleX != null) {
- return props.scaleX;
- } else if (props.scale != null) {
- return props.scale;
- } else {
- return 1;
- }
-}
-
-function getScaleY(props) {
- if (props.scaleY != null) {
- return props.scaleY;
- } else if (props.scale != null) {
- return props.scale;
- } else {
- return 1;
- }
-}
-
-function isSameFont(oldFont, newFont) {
- if (oldFont === newFont) {
- return true;
- } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
- return false;
- } else {
- return (
- newFont.fontSize === oldFont.fontSize &&
- newFont.fontStyle === oldFont.fontStyle &&
- newFont.fontVariant === oldFont.fontVariant &&
- newFont.fontWeight === oldFont.fontWeight &&
- newFont.fontFamily === oldFont.fontFamily
- );
- }
-}
-
-/** Render Methods */
-
-function applyClippingRectangleProps(instance, props, prevProps = {}) {
- applyNodeProps(instance, props, prevProps);
-
- instance.width = props.width;
- instance.height = props.height;
-}
-
-function applyGroupProps(instance, props, prevProps = {}) {
- applyNodeProps(instance, props, prevProps);
-
- instance.width = props.width;
- instance.height = props.height;
-}
-
-function applyNodeProps(instance, props, prevProps = {}) {
- const scaleX = getScaleX(props);
- const scaleY = getScaleY(props);
-
- pooledTransform
- .transformTo(1, 0, 0, 1, 0, 0)
- .move(props.x || 0, props.y || 0)
- .rotate(props.rotation || 0, props.originX, props.originY)
- .scale(scaleX, scaleY, props.originX, props.originY);
-
- if (props.transform != null) {
- pooledTransform.transform(props.transform);
- }
-
- if (
- instance.xx !== pooledTransform.xx ||
- instance.yx !== pooledTransform.yx ||
- instance.xy !== pooledTransform.xy ||
- instance.yy !== pooledTransform.yy ||
- instance.x !== pooledTransform.x ||
- instance.y !== pooledTransform.y
- ) {
- instance.transformTo(pooledTransform);
- }
-
- if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
- instance.indicate(props.cursor, props.title);
- }
-
- if (instance.blend && props.opacity !== prevProps.opacity) {
- instance.blend(props.opacity == null ? 1 : props.opacity);
- }
-
- if (props.visible !== prevProps.visible) {
- if (props.visible == null || props.visible) {
- instance.show();
- } else {
- instance.hide();
- }
- }
-
- for (let type in EVENT_TYPES) {
- addEventListeners(instance, EVENT_TYPES[type], props[type]);
- }
-}
-
-function applyRenderableNodeProps(instance, props, prevProps = {}) {
- applyNodeProps(instance, props, prevProps);
-
- if (prevProps.fill !== props.fill) {
- if (props.fill && props.fill.applyFill) {
- props.fill.applyFill(instance);
- } else {
- instance.fill(props.fill);
- }
- }
- if (
- prevProps.stroke !== props.stroke ||
- prevProps.strokeWidth !== props.strokeWidth ||
- prevProps.strokeCap !== props.strokeCap ||
- prevProps.strokeJoin !== props.strokeJoin ||
- // TODO: Consider deep check of stokeDash; may benefit VML in IE.
- prevProps.strokeDash !== props.strokeDash
- ) {
- instance.stroke(
- props.stroke,
- props.strokeWidth,
- props.strokeCap,
- props.strokeJoin,
- props.strokeDash,
- );
- }
-}
-
-function applyShapeProps(instance, props, prevProps = {}) {
- applyRenderableNodeProps(instance, props, prevProps);
-
- const path = props.d || childrenAsString(props.children);
-
- const prevDelta = instance._prevDelta;
- const prevPath = instance._prevPath;
-
- if (
- path !== prevPath ||
- path.delta !== prevDelta ||
- prevProps.height !== props.height ||
- prevProps.width !== props.width
- ) {
- instance.draw(path, props.width, props.height);
-
- instance._prevDelta = path.delta;
- instance._prevPath = path;
- }
-}
-
-function applyTextProps(instance, props, prevProps = {}) {
- applyRenderableNodeProps(instance, props, prevProps);
-
- const string = props.children;
-
- if (
- instance._currentString !== string ||
- !isSameFont(props.font, prevProps.font) ||
- props.alignment !== prevProps.alignment ||
- props.path !== prevProps.path
- ) {
- instance.draw(string, props.font, props.alignment, props.path);
-
- instance._currentString = string;
- }
-}
-
/** Declarative fill-type objects; API design not finalized */
const slice = Array.prototype.slice;
@@ -383,158 +134,7 @@ class Text extends React.Component {
/** ART Renderer */
-const ARTRenderer = ReactFiberReconciler({
- appendInitialChild(parentInstance, child) {
- if (typeof child === 'string') {
- // Noop for string children of Text (eg {'foo'}{'bar'})
- invariant(false, 'Text children should already be flattened.');
- return;
- }
-
- child.inject(parentInstance);
- },
-
- createInstance(type, props, internalInstanceHandle) {
- let instance;
-
- switch (type) {
- case TYPES.CLIPPING_RECTANGLE:
- instance = Mode.ClippingRectangle();
- instance._applyProps = applyClippingRectangleProps;
- break;
- case TYPES.GROUP:
- instance = Mode.Group();
- instance._applyProps = applyGroupProps;
- break;
- case TYPES.SHAPE:
- instance = Mode.Shape();
- instance._applyProps = applyShapeProps;
- break;
- case TYPES.TEXT:
- instance = Mode.Text(
- props.children,
- props.font,
- props.alignment,
- props.path,
- );
- instance._applyProps = applyTextProps;
- break;
- }
-
- invariant(instance, 'ReactART does not support the type "%s"', type);
-
- instance._applyProps(instance, props);
-
- return instance;
- },
-
- createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
- return text;
- },
-
- finalizeInitialChildren(domElement, type, props) {
- return false;
- },
-
- getPublicInstance(instance) {
- return instance;
- },
-
- prepareForCommit() {
- // Noop
- },
-
- prepareUpdate(domElement, type, oldProps, newProps) {
- return UPDATE_SIGNAL;
- },
-
- resetAfterCommit() {
- // Noop
- },
-
- resetTextContent(domElement) {
- // Noop
- },
-
- shouldDeprioritizeSubtree(type, props) {
- return false;
- },
-
- getRootHostContext() {
- return emptyObject;
- },
-
- getChildHostContext() {
- return emptyObject;
- },
-
- scheduleDeferredCallback: ReactScheduler.rIC,
-
- shouldSetTextContent(type, props) {
- return (
- typeof props.children === 'string' || typeof props.children === 'number'
- );
- },
-
- now: ReactScheduler.now,
-
- // The ART renderer is secondary to the React DOM renderer.
- isPrimaryRenderer: false,
-
- mutation: {
- appendChild(parentInstance, child) {
- if (child.parentNode === parentInstance) {
- child.eject();
- }
- child.inject(parentInstance);
- },
-
- appendChildToContainer(parentInstance, child) {
- if (child.parentNode === parentInstance) {
- child.eject();
- }
- child.inject(parentInstance);
- },
-
- insertBefore(parentInstance, child, beforeChild) {
- invariant(
- child !== beforeChild,
- 'ReactART: Can not insert node before itself',
- );
- child.injectBefore(beforeChild);
- },
-
- insertInContainerBefore(parentInstance, child, beforeChild) {
- invariant(
- child !== beforeChild,
- 'ReactART: Can not insert node before itself',
- );
- child.injectBefore(beforeChild);
- },
-
- removeChild(parentInstance, child) {
- destroyEventListeners(child);
- child.eject();
- },
-
- removeChildFromContainer(parentInstance, child) {
- destroyEventListeners(child);
- child.eject();
- },
-
- commitTextUpdate(textInstance, oldText, newText) {
- // Noop
- },
-
- commitMount(instance, type, newProps) {
- // Noop
- },
-
- commitUpdate(instance, updatePayload, type, oldProps, newProps) {
- instance._applyProps(instance, newProps, oldProps);
- },
- },
-});
+const ARTRenderer = ReactFiberReconciler(ReactARTHostConfig);
/** API */
diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js
new file mode 100644
index 000000000000..cd059537cccb
--- /dev/null
+++ b/packages/react-art/src/ReactARTHostConfig.js
@@ -0,0 +1,390 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import * as ReactScheduler from 'react-scheduler';
+import Transform from 'art/core/transform';
+import Mode from 'art/modes/current';
+import invariant from 'fbjs/lib/invariant';
+import emptyObject from 'fbjs/lib/emptyObject';
+
+import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
+
+const pooledTransform = new Transform();
+
+const UPDATE_SIGNAL = {};
+
+/** Helper Methods */
+
+function addEventListeners(instance, type, listener) {
+ // We need to explicitly unregister before unmount.
+ // For this reason we need to track subscriptions.
+ if (!instance._listeners) {
+ instance._listeners = {};
+ instance._subscriptions = {};
+ }
+
+ instance._listeners[type] = listener;
+
+ if (listener) {
+ if (!instance._subscriptions[type]) {
+ instance._subscriptions[type] = instance.subscribe(
+ type,
+ createEventHandler(instance),
+ instance,
+ );
+ }
+ } else {
+ if (instance._subscriptions[type]) {
+ instance._subscriptions[type]();
+ delete instance._subscriptions[type];
+ }
+ }
+}
+
+function createEventHandler(instance) {
+ return function handleEvent(event) {
+ const listener = instance._listeners[event.type];
+
+ if (!listener) {
+ // Noop
+ } else if (typeof listener === 'function') {
+ listener.call(instance, event);
+ } else if (listener.handleEvent) {
+ listener.handleEvent(event);
+ }
+ };
+}
+
+function destroyEventListeners(instance) {
+ if (instance._subscriptions) {
+ for (let type in instance._subscriptions) {
+ instance._subscriptions[type]();
+ }
+ }
+
+ instance._subscriptions = null;
+ instance._listeners = null;
+}
+
+function getScaleX(props) {
+ if (props.scaleX != null) {
+ return props.scaleX;
+ } else if (props.scale != null) {
+ return props.scale;
+ } else {
+ return 1;
+ }
+}
+
+function getScaleY(props) {
+ if (props.scaleY != null) {
+ return props.scaleY;
+ } else if (props.scale != null) {
+ return props.scale;
+ } else {
+ return 1;
+ }
+}
+
+function isSameFont(oldFont, newFont) {
+ if (oldFont === newFont) {
+ return true;
+ } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
+ return false;
+ } else {
+ return (
+ newFont.fontSize === oldFont.fontSize &&
+ newFont.fontStyle === oldFont.fontStyle &&
+ newFont.fontVariant === oldFont.fontVariant &&
+ newFont.fontWeight === oldFont.fontWeight &&
+ newFont.fontFamily === oldFont.fontFamily
+ );
+ }
+}
+
+/** Render Methods */
+
+function applyClippingRectangleProps(instance, props, prevProps = {}) {
+ applyNodeProps(instance, props, prevProps);
+
+ instance.width = props.width;
+ instance.height = props.height;
+}
+
+function applyGroupProps(instance, props, prevProps = {}) {
+ applyNodeProps(instance, props, prevProps);
+
+ instance.width = props.width;
+ instance.height = props.height;
+}
+
+function applyNodeProps(instance, props, prevProps = {}) {
+ const scaleX = getScaleX(props);
+ const scaleY = getScaleY(props);
+
+ pooledTransform
+ .transformTo(1, 0, 0, 1, 0, 0)
+ .move(props.x || 0, props.y || 0)
+ .rotate(props.rotation || 0, props.originX, props.originY)
+ .scale(scaleX, scaleY, props.originX, props.originY);
+
+ if (props.transform != null) {
+ pooledTransform.transform(props.transform);
+ }
+
+ if (
+ instance.xx !== pooledTransform.xx ||
+ instance.yx !== pooledTransform.yx ||
+ instance.xy !== pooledTransform.xy ||
+ instance.yy !== pooledTransform.yy ||
+ instance.x !== pooledTransform.x ||
+ instance.y !== pooledTransform.y
+ ) {
+ instance.transformTo(pooledTransform);
+ }
+
+ if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
+ instance.indicate(props.cursor, props.title);
+ }
+
+ if (instance.blend && props.opacity !== prevProps.opacity) {
+ instance.blend(props.opacity == null ? 1 : props.opacity);
+ }
+
+ if (props.visible !== prevProps.visible) {
+ if (props.visible == null || props.visible) {
+ instance.show();
+ } else {
+ instance.hide();
+ }
+ }
+
+ for (let type in EVENT_TYPES) {
+ addEventListeners(instance, EVENT_TYPES[type], props[type]);
+ }
+}
+
+function applyRenderableNodeProps(instance, props, prevProps = {}) {
+ applyNodeProps(instance, props, prevProps);
+
+ if (prevProps.fill !== props.fill) {
+ if (props.fill && props.fill.applyFill) {
+ props.fill.applyFill(instance);
+ } else {
+ instance.fill(props.fill);
+ }
+ }
+ if (
+ prevProps.stroke !== props.stroke ||
+ prevProps.strokeWidth !== props.strokeWidth ||
+ prevProps.strokeCap !== props.strokeCap ||
+ prevProps.strokeJoin !== props.strokeJoin ||
+ // TODO: Consider deep check of stokeDash; may benefit VML in IE.
+ prevProps.strokeDash !== props.strokeDash
+ ) {
+ instance.stroke(
+ props.stroke,
+ props.strokeWidth,
+ props.strokeCap,
+ props.strokeJoin,
+ props.strokeDash,
+ );
+ }
+}
+
+function applyShapeProps(instance, props, prevProps = {}) {
+ applyRenderableNodeProps(instance, props, prevProps);
+
+ const path = props.d || childrenAsString(props.children);
+
+ const prevDelta = instance._prevDelta;
+ const prevPath = instance._prevPath;
+
+ if (
+ path !== prevPath ||
+ path.delta !== prevDelta ||
+ prevProps.height !== props.height ||
+ prevProps.width !== props.width
+ ) {
+ instance.draw(path, props.width, props.height);
+
+ instance._prevDelta = path.delta;
+ instance._prevPath = path;
+ }
+}
+
+function applyTextProps(instance, props, prevProps = {}) {
+ applyRenderableNodeProps(instance, props, prevProps);
+
+ const string = props.children;
+
+ if (
+ instance._currentString !== string ||
+ !isSameFont(props.font, prevProps.font) ||
+ props.alignment !== prevProps.alignment ||
+ props.path !== prevProps.path
+ ) {
+ instance.draw(string, props.font, props.alignment, props.path);
+
+ instance._currentString = string;
+ }
+}
+
+const ReactARTHostConfig = {
+ appendInitialChild(parentInstance, child) {
+ if (typeof child === 'string') {
+ // Noop for string children of Text (eg {'foo'}{'bar'})
+ invariant(false, 'Text children should already be flattened.');
+ return;
+ }
+
+ child.inject(parentInstance);
+ },
+
+ createInstance(type, props, internalInstanceHandle) {
+ let instance;
+
+ switch (type) {
+ case TYPES.CLIPPING_RECTANGLE:
+ instance = Mode.ClippingRectangle();
+ instance._applyProps = applyClippingRectangleProps;
+ break;
+ case TYPES.GROUP:
+ instance = Mode.Group();
+ instance._applyProps = applyGroupProps;
+ break;
+ case TYPES.SHAPE:
+ instance = Mode.Shape();
+ instance._applyProps = applyShapeProps;
+ break;
+ case TYPES.TEXT:
+ instance = Mode.Text(
+ props.children,
+ props.font,
+ props.alignment,
+ props.path,
+ );
+ instance._applyProps = applyTextProps;
+ break;
+ }
+
+ invariant(instance, 'ReactART does not support the type "%s"', type);
+
+ instance._applyProps(instance, props);
+
+ return instance;
+ },
+
+ createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
+ return text;
+ },
+
+ finalizeInitialChildren(domElement, type, props) {
+ return false;
+ },
+
+ getPublicInstance(instance) {
+ return instance;
+ },
+
+ prepareForCommit() {
+ // Noop
+ },
+
+ prepareUpdate(domElement, type, oldProps, newProps) {
+ return UPDATE_SIGNAL;
+ },
+
+ resetAfterCommit() {
+ // Noop
+ },
+
+ resetTextContent(domElement) {
+ // Noop
+ },
+
+ shouldDeprioritizeSubtree(type, props) {
+ return false;
+ },
+
+ getRootHostContext() {
+ return emptyObject;
+ },
+
+ getChildHostContext() {
+ return emptyObject;
+ },
+
+ scheduleDeferredCallback: ReactScheduler.rIC,
+
+ shouldSetTextContent(type, props) {
+ return (
+ typeof props.children === 'string' || typeof props.children === 'number'
+ );
+ },
+
+ now: ReactScheduler.now,
+
+ // The ART renderer is secondary to the React DOM renderer.
+ isPrimaryRenderer: false,
+
+ mutation: {
+ appendChild(parentInstance, child) {
+ if (child.parentNode === parentInstance) {
+ child.eject();
+ }
+ child.inject(parentInstance);
+ },
+
+ appendChildToContainer(parentInstance, child) {
+ if (child.parentNode === parentInstance) {
+ child.eject();
+ }
+ child.inject(parentInstance);
+ },
+
+ insertBefore(parentInstance, child, beforeChild) {
+ invariant(
+ child !== beforeChild,
+ 'ReactART: Can not insert node before itself',
+ );
+ child.injectBefore(beforeChild);
+ },
+
+ insertInContainerBefore(parentInstance, child, beforeChild) {
+ invariant(
+ child !== beforeChild,
+ 'ReactART: Can not insert node before itself',
+ );
+ child.injectBefore(beforeChild);
+ },
+
+ removeChild(parentInstance, child) {
+ destroyEventListeners(child);
+ child.eject();
+ },
+
+ removeChildFromContainer(parentInstance, child) {
+ destroyEventListeners(child);
+ child.eject();
+ },
+
+ commitTextUpdate(textInstance, oldText, newText) {
+ // Noop
+ },
+
+ commitMount(instance, type, newProps) {
+ // Noop
+ },
+
+ commitUpdate(instance, updatePayload, type, oldProps, newProps) {
+ instance._applyProps(instance, newProps, oldProps);
+ },
+ },
+};
+
+export default ReactARTHostConfig;
diff --git a/packages/react-art/src/ReactARTInternals.js b/packages/react-art/src/ReactARTInternals.js
new file mode 100644
index 000000000000..b31c9c896496
--- /dev/null
+++ b/packages/react-art/src/ReactARTInternals.js
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+export const TYPES = {
+ CLIPPING_RECTANGLE: 'ClippingRectangle',
+ GROUP: 'Group',
+ SHAPE: 'Shape',
+ TEXT: 'Text',
+};
+
+export const EVENT_TYPES = {
+ onClick: 'click',
+ onMouseMove: 'mousemove',
+ onMouseOver: 'mouseover',
+ onMouseOut: 'mouseout',
+ onMouseUp: 'mouseup',
+ onMouseDown: 'mousedown',
+};
+
+export function childrenAsString(children) {
+ if (!children) {
+ return '';
+ } else if (typeof children === 'string') {
+ return children;
+ } else if (children.length) {
+ return children.join('');
+ } else {
+ return '';
+ }
+}
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index 81683c47748e..40450e5f5eef 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -14,12 +14,12 @@ import type {
FiberRoot,
Batch as FiberRootBatch,
} from 'react-reconciler/src/ReactFiberRoot';
+import type {Container} from './ReactDOMHostConfig';
import '../shared/checkReact';
import './ReactDOMClientInjection';
import ReactFiberReconciler from 'react-reconciler';
-// TODO: direct imports like some-package/src/* are bad. Fix me.
import * as ReactPortal from 'shared/ReactPortal';
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
@@ -29,53 +29,29 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
import * as EventPropagators from 'events/EventPropagators';
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
import ReactVersion from 'shared/ReactVersion';
-import * as ReactScheduler from 'react-scheduler';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import getComponentName from 'shared/getComponentName';
import invariant from 'fbjs/lib/invariant';
import lowPriorityWarning from 'shared/lowPriorityWarning';
import warning from 'fbjs/lib/warning';
+import ReactDOMHostConfig from './ReactDOMHostConfig';
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
-import * as ReactInputSelection from './ReactInputSelection';
-import setTextContent from './setTextContent';
-import validateDOMNesting from './validateDOMNesting';
-import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
-import {getChildNamespace} from '../shared/DOMNamespaces';
import {
ELEMENT_NODE,
- TEXT_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
} from '../shared/HTMLNodeType';
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
-const {
- createElement,
- createTextNode,
- setInitialProperties,
- diffProperties,
- updateProperties,
- diffHydratedProperties,
- diffHydratedText,
- warnForUnmatchedText,
- warnForDeletedHydratableElement,
- warnForDeletedHydratableText,
- warnForInsertedHydratedElement,
- warnForInsertedHydratedText,
-} = ReactDOMFiberComponent;
-const {updatedAncestorInfo} = validateDOMNesting;
-const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
-
-let SUPPRESS_HYDRATION_WARNING;
+
let topLevelUpdateWarnings;
let warnOnInvalidCallback;
let didWarnAboutUnstableCreatePortal = false;
if (__DEV__) {
- SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
if (
typeof Map !== 'function' ||
Map.prototype == null ||
@@ -157,26 +133,6 @@ type DOMContainer =
_reactRootContainer: ?Root,
});
-type Container = Element | Document;
-type Props = {
- autoFocus?: boolean,
- children?: mixed,
- hidden?: boolean,
- suppressHydrationWarning?: boolean,
-};
-type Instance = Element;
-type TextInstance = Text;
-
-type HostContextDev = {
- namespace: string,
- ancestorInfo: mixed,
-};
-type HostContextProd = string;
-type HostContext = HostContextDev | HostContextProd;
-
-let eventsEnabled: ?boolean = null;
-let selectionInformation: ?mixed = null;
-
type Batch = FiberRootBatch & {
render(children: ReactNodeList): Work,
then(onComplete: () => mixed): void,
@@ -491,504 +447,7 @@ function shouldHydrateDueToLegacyHeuristic(container) {
);
}
-function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
- switch (type) {
- case 'button':
- case 'input':
- case 'select':
- case 'textarea':
- return !!props.autoFocus;
- }
- return false;
-}
-
-const DOMRenderer = ReactFiberReconciler({
- 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, null);
- return {namespace, ancestorInfo};
- }
- return namespace;
- },
-
- getChildHostContext(
- parentHostContext: HostContext,
- type: string,
- ): HostContext {
- if (__DEV__) {
- const parentHostContextDev = ((parentHostContext: any): HostContextDev);
- const namespace = getChildNamespace(parentHostContextDev.namespace, type);
- const ancestorInfo = updatedAncestorInfo(
- parentHostContextDev.ancestorInfo,
- type,
- null,
- );
- return {namespace, ancestorInfo};
- }
- const parentNamespace = ((parentHostContext: any): HostContextProd);
- return getChildNamespace(parentNamespace, type);
- },
-
- getPublicInstance(instance) {
- return instance;
- },
-
- prepareForCommit(): void {
- eventsEnabled = ReactBrowserEventEmitter.isEnabled();
- selectionInformation = ReactInputSelection.getSelectionInformation();
- ReactBrowserEventEmitter.setEnabled(false);
- },
-
- resetAfterCommit(): void {
- ReactInputSelection.restoreSelection(selectionInformation);
- selectionInformation = null;
- ReactBrowserEventEmitter.setEnabled(eventsEnabled);
- eventsEnabled = null;
- },
-
- 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,
- null,
- );
- 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;
- },
-
- appendInitialChild(
- parentInstance: Instance,
- child: Instance | TextInstance,
- ): void {
- parentInstance.appendChild(child);
- },
-
- finalizeInitialChildren(
- domElement: Instance,
- type: string,
- props: Props,
- rootContainerInstance: Container,
- ): boolean {
- setInitialProperties(domElement, type, props, rootContainerInstance);
- return shouldAutoFocusHostComponent(type, props);
- },
-
- prepareUpdate(
- domElement: Instance,
- type: string,
- oldProps: Props,
- newProps: Props,
- rootContainerInstance: Container,
- hostContext: HostContext,
- ): null | Array {
- 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,
- null,
- );
- validateDOMNesting(null, string, ownAncestorInfo);
- }
- }
- return diffProperties(
- domElement,
- type,
- oldProps,
- newProps,
- rootContainerInstance,
- );
- },
-
- shouldSetTextContent(type: string, props: Props): boolean {
- return (
- type === 'textarea' ||
- typeof props.children === 'string' ||
- typeof props.children === 'number' ||
- (typeof props.dangerouslySetInnerHTML === 'object' &&
- props.dangerouslySetInnerHTML !== null &&
- typeof props.dangerouslySetInnerHTML.__html === 'string')
- );
- },
-
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
- return !!props.hidden;
- },
-
- 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;
- },
-
- now: ReactScheduler.now,
-
- isPrimaryRenderer: true,
-
- mutation: {
- 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();
- }
- },
-
- commitUpdate(
- domElement: Instance,
- updatePayload: Array,
- 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);
- },
-
- resetTextContent(domElement: Instance): void {
- setTextContent(domElement, '');
- },
-
- commitTextUpdate(
- textInstance: TextInstance,
- oldText: string,
- newText: string,
- ): void {
- textInstance.nodeValue = newText;
- },
-
- appendChild(
- parentInstance: Instance,
- child: Instance | TextInstance,
- ): void {
- parentInstance.appendChild(child);
- },
-
- appendChildToContainer(
- container: Container,
- child: Instance | TextInstance,
- ): void {
- if (container.nodeType === COMMENT_NODE) {
- (container.parentNode: any).insertBefore(child, container);
- } else {
- container.appendChild(child);
- }
- },
-
- insertBefore(
- parentInstance: Instance,
- child: Instance | TextInstance,
- beforeChild: Instance | TextInstance,
- ): void {
- parentInstance.insertBefore(child, beforeChild);
- },
-
- insertInContainerBefore(
- container: Container,
- child: Instance | TextInstance,
- beforeChild: Instance | TextInstance,
- ): void {
- if (container.nodeType === COMMENT_NODE) {
- (container.parentNode: any).insertBefore(child, beforeChild);
- } else {
- container.insertBefore(child, beforeChild);
- }
- },
-
- removeChild(
- parentInstance: Instance,
- child: Instance | TextInstance,
- ): void {
- parentInstance.removeChild(child);
- },
-
- removeChildFromContainer(
- container: Container,
- child: Instance | TextInstance,
- ): void {
- if (container.nodeType === COMMENT_NODE) {
- (container.parentNode: any).removeChild(child);
- } else {
- container.removeChild(child);
- }
- },
- },
-
- hydration: {
- canHydrateInstance(
- instance: Instance | TextInstance,
- 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);
- },
-
- canHydrateTextInstance(
- instance: Instance | TextInstance,
- 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);
- },
-
- getNextHydratableSibling(
- instance: Instance | TextInstance,
- ): null | Instance | TextInstance {
- let node = instance.nextSibling;
- // Skip non-hydratable nodes.
- while (
- node &&
- node.nodeType !== ELEMENT_NODE &&
- node.nodeType !== TEXT_NODE
- ) {
- node = node.nextSibling;
- }
- return (node: any);
- },
-
- getFirstHydratableChild(
- parentInstance: Container | Instance,
- ): null | Instance | TextInstance {
- let next = parentInstance.firstChild;
- // Skip non-hydratable nodes.
- while (
- next &&
- next.nodeType !== ELEMENT_NODE &&
- next.nodeType !== TEXT_NODE
- ) {
- next = next.nextSibling;
- }
- return (next: any);
- },
-
- hydrateInstance(
- instance: Instance,
- type: string,
- props: Props,
- rootContainerInstance: Container,
- hostContext: HostContext,
- internalInstanceHandle: Object,
- ): null | Array {
- 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,
- );
- },
-
- hydrateTextInstance(
- textInstance: TextInstance,
- text: string,
- internalInstanceHandle: Object,
- ): boolean {
- precacheFiberNode(internalInstanceHandle, textInstance);
- return diffHydratedText(textInstance, text);
- },
-
- didNotMatchHydratedContainerTextInstance(
- parentContainer: Container,
- textInstance: TextInstance,
- text: string,
- ) {
- if (__DEV__) {
- warnForUnmatchedText(textInstance, text);
- }
- },
-
- didNotMatchHydratedTextInstance(
- parentType: string,
- parentProps: Props,
- parentInstance: Instance,
- textInstance: TextInstance,
- text: string,
- ) {
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
- warnForUnmatchedText(textInstance, text);
- }
- },
-
- didNotHydrateContainerInstance(
- parentContainer: Container,
- instance: Instance | TextInstance,
- ) {
- if (__DEV__) {
- if (instance.nodeType === 1) {
- warnForDeletedHydratableElement(parentContainer, (instance: any));
- } else {
- warnForDeletedHydratableText(parentContainer, (instance: any));
- }
- }
- },
-
- didNotHydrateInstance(
- parentType: string,
- parentProps: Props,
- parentInstance: Instance,
- instance: Instance | TextInstance,
- ) {
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
- if (instance.nodeType === 1) {
- warnForDeletedHydratableElement(parentInstance, (instance: any));
- } else {
- warnForDeletedHydratableText(parentInstance, (instance: any));
- }
- }
- },
-
- didNotFindHydratableContainerInstance(
- parentContainer: Container,
- type: string,
- props: Props,
- ) {
- if (__DEV__) {
- warnForInsertedHydratedElement(parentContainer, type, props);
- }
- },
-
- didNotFindHydratableContainerTextInstance(
- parentContainer: Container,
- text: string,
- ) {
- if (__DEV__) {
- warnForInsertedHydratedText(parentContainer, text);
- }
- },
-
- didNotFindHydratableInstance(
- parentType: string,
- parentProps: Props,
- parentInstance: Instance,
- type: string,
- props: Props,
- ) {
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
- warnForInsertedHydratedElement(parentInstance, type, props);
- }
- },
-
- didNotFindHydratableTextInstance(
- parentType: string,
- parentProps: Props,
- parentInstance: Instance,
- text: string,
- ) {
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
- warnForInsertedHydratedText(parentInstance, text);
- }
- },
- },
-
- scheduleDeferredCallback: ReactScheduler.rIC,
- cancelDeferredCallback: ReactScheduler.cIC,
-});
+const DOMRenderer = ReactFiberReconciler(ReactDOMHostConfig);
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
new file mode 100644
index 000000000000..95471f2279da
--- /dev/null
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -0,0 +1,568 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import * as ReactScheduler from 'react-scheduler';
+
+import * as ReactDOMComponentTree from './ReactDOMComponentTree';
+import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
+import * as ReactInputSelection from './ReactInputSelection';
+import setTextContent from './setTextContent';
+import validateDOMNesting from './validateDOMNesting';
+import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
+import {getChildNamespace} from '../shared/DOMNamespaces';
+import {
+ ELEMENT_NODE,
+ TEXT_NODE,
+ COMMENT_NODE,
+ DOCUMENT_NODE,
+ DOCUMENT_FRAGMENT_NODE,
+} from '../shared/HTMLNodeType';
+
+export type Container = Element | Document;
+type Props = {
+ autoFocus?: boolean,
+ children?: mixed,
+ hidden?: boolean,
+ suppressHydrationWarning?: boolean,
+};
+type Instance = Element;
+type TextInstance = Text;
+
+type HostContextDev = {
+ namespace: string,
+ ancestorInfo: mixed,
+};
+type HostContextProd = string;
+type HostContext = HostContextDev | HostContextProd;
+
+const {
+ createElement,
+ createTextNode,
+ setInitialProperties,
+ diffProperties,
+ updateProperties,
+ diffHydratedProperties,
+ diffHydratedText,
+ warnForUnmatchedText,
+ warnForDeletedHydratableElement,
+ warnForDeletedHydratableText,
+ warnForInsertedHydratedElement,
+ warnForInsertedHydratedText,
+} = ReactDOMFiberComponent;
+const {updatedAncestorInfo} = validateDOMNesting;
+const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
+
+let SUPPRESS_HYDRATION_WARNING;
+if (__DEV__) {
+ SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
+}
+
+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;
+}
+
+const ReactDOMHostConfig = {
+ 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, null);
+ return {namespace, ancestorInfo};
+ }
+ return namespace;
+ },
+
+ getChildHostContext(
+ parentHostContext: HostContext,
+ type: string,
+ ): HostContext {
+ if (__DEV__) {
+ const parentHostContextDev = ((parentHostContext: any): HostContextDev);
+ const namespace = getChildNamespace(parentHostContextDev.namespace, type);
+ const ancestorInfo = updatedAncestorInfo(
+ parentHostContextDev.ancestorInfo,
+ type,
+ null,
+ );
+ return {namespace, ancestorInfo};
+ }
+ const parentNamespace = ((parentHostContext: any): HostContextProd);
+ return getChildNamespace(parentNamespace, type);
+ },
+
+ getPublicInstance(instance: Instance | TextInstance): * {
+ return instance;
+ },
+
+ prepareForCommit(): void {
+ eventsEnabled = ReactBrowserEventEmitter.isEnabled();
+ selectionInformation = ReactInputSelection.getSelectionInformation();
+ ReactBrowserEventEmitter.setEnabled(false);
+ },
+
+ resetAfterCommit(): void {
+ ReactInputSelection.restoreSelection(selectionInformation);
+ selectionInformation = null;
+ ReactBrowserEventEmitter.setEnabled(eventsEnabled);
+ eventsEnabled = null;
+ },
+
+ 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,
+ null,
+ );
+ 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;
+ },
+
+ appendInitialChild(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ ): void {
+ parentInstance.appendChild(child);
+ },
+
+ finalizeInitialChildren(
+ domElement: Instance,
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ ): boolean {
+ setInitialProperties(domElement, type, props, rootContainerInstance);
+ return shouldAutoFocusHostComponent(type, props);
+ },
+
+ prepareUpdate(
+ domElement: Instance,
+ type: string,
+ oldProps: Props,
+ newProps: Props,
+ rootContainerInstance: Container,
+ hostContext: HostContext,
+ ): null | Array {
+ 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,
+ null,
+ );
+ validateDOMNesting(null, string, ownAncestorInfo);
+ }
+ }
+ return diffProperties(
+ domElement,
+ type,
+ oldProps,
+ newProps,
+ rootContainerInstance,
+ );
+ },
+
+ shouldSetTextContent(type: string, props: Props): boolean {
+ return (
+ type === 'textarea' ||
+ typeof props.children === 'string' ||
+ typeof props.children === 'number' ||
+ (typeof props.dangerouslySetInnerHTML === 'object' &&
+ props.dangerouslySetInnerHTML !== null &&
+ typeof props.dangerouslySetInnerHTML.__html === 'string')
+ );
+ },
+
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
+ return !!props.hidden;
+ },
+
+ 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;
+ },
+
+ now: ReactScheduler.now,
+
+ isPrimaryRenderer: true,
+
+ mutation: {
+ 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();
+ }
+ },
+
+ commitUpdate(
+ domElement: Instance,
+ updatePayload: Array,
+ 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);
+ },
+
+ resetTextContent(domElement: Instance): void {
+ setTextContent(domElement, '');
+ },
+
+ commitTextUpdate(
+ textInstance: TextInstance,
+ oldText: string,
+ newText: string,
+ ): void {
+ textInstance.nodeValue = newText;
+ },
+
+ appendChild(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ ): void {
+ parentInstance.appendChild(child);
+ },
+
+ appendChildToContainer(
+ container: Container,
+ child: Instance | TextInstance,
+ ): void {
+ if (container.nodeType === COMMENT_NODE) {
+ (container.parentNode: any).insertBefore(child, container);
+ } else {
+ container.appendChild(child);
+ }
+ },
+
+ insertBefore(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ beforeChild: Instance | TextInstance,
+ ): void {
+ parentInstance.insertBefore(child, beforeChild);
+ },
+
+ insertInContainerBefore(
+ container: Container,
+ child: Instance | TextInstance,
+ beforeChild: Instance | TextInstance,
+ ): void {
+ if (container.nodeType === COMMENT_NODE) {
+ (container.parentNode: any).insertBefore(child, beforeChild);
+ } else {
+ container.insertBefore(child, beforeChild);
+ }
+ },
+
+ removeChild(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ ): void {
+ parentInstance.removeChild(child);
+ },
+
+ removeChildFromContainer(
+ container: Container,
+ child: Instance | TextInstance,
+ ): void {
+ if (container.nodeType === COMMENT_NODE) {
+ (container.parentNode: any).removeChild(child);
+ } else {
+ container.removeChild(child);
+ }
+ },
+ },
+
+ hydration: {
+ canHydrateInstance(
+ instance: Instance | TextInstance,
+ 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);
+ },
+
+ canHydrateTextInstance(
+ instance: Instance | TextInstance,
+ 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);
+ },
+
+ getNextHydratableSibling(
+ instance: Instance | TextInstance,
+ ): null | Instance | TextInstance {
+ let node = instance.nextSibling;
+ // Skip non-hydratable nodes.
+ while (
+ node &&
+ node.nodeType !== ELEMENT_NODE &&
+ node.nodeType !== TEXT_NODE
+ ) {
+ node = node.nextSibling;
+ }
+ return (node: any);
+ },
+
+ getFirstHydratableChild(
+ parentInstance: Container | Instance,
+ ): null | Instance | TextInstance {
+ let next = parentInstance.firstChild;
+ // Skip non-hydratable nodes.
+ while (
+ next &&
+ next.nodeType !== ELEMENT_NODE &&
+ next.nodeType !== TEXT_NODE
+ ) {
+ next = next.nextSibling;
+ }
+ return (next: any);
+ },
+
+ hydrateInstance(
+ instance: Instance,
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ hostContext: HostContext,
+ internalInstanceHandle: Object,
+ ): null | Array {
+ 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,
+ );
+ },
+
+ hydrateTextInstance(
+ textInstance: TextInstance,
+ text: string,
+ internalInstanceHandle: Object,
+ ): boolean {
+ precacheFiberNode(internalInstanceHandle, textInstance);
+ return diffHydratedText(textInstance, text);
+ },
+
+ didNotMatchHydratedContainerTextInstance(
+ parentContainer: Container,
+ textInstance: TextInstance,
+ text: string,
+ ) {
+ if (__DEV__) {
+ warnForUnmatchedText(textInstance, text);
+ }
+ },
+
+ didNotMatchHydratedTextInstance(
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
+ textInstance: TextInstance,
+ text: string,
+ ) {
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForUnmatchedText(textInstance, text);
+ }
+ },
+
+ didNotHydrateContainerInstance(
+ parentContainer: Container,
+ instance: Instance | TextInstance,
+ ) {
+ if (__DEV__) {
+ if (instance.nodeType === 1) {
+ warnForDeletedHydratableElement(parentContainer, (instance: any));
+ } else {
+ warnForDeletedHydratableText(parentContainer, (instance: any));
+ }
+ }
+ },
+
+ didNotHydrateInstance(
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
+ instance: Instance | TextInstance,
+ ) {
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ if (instance.nodeType === 1) {
+ warnForDeletedHydratableElement(parentInstance, (instance: any));
+ } else {
+ warnForDeletedHydratableText(parentInstance, (instance: any));
+ }
+ }
+ },
+
+ didNotFindHydratableContainerInstance(
+ parentContainer: Container,
+ type: string,
+ props: Props,
+ ) {
+ if (__DEV__) {
+ warnForInsertedHydratedElement(parentContainer, type, props);
+ }
+ },
+
+ didNotFindHydratableContainerTextInstance(
+ parentContainer: Container,
+ text: string,
+ ) {
+ if (__DEV__) {
+ warnForInsertedHydratedText(parentContainer, text);
+ }
+ },
+
+ didNotFindHydratableInstance(
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
+ type: string,
+ props: Props,
+ ) {
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForInsertedHydratedElement(parentInstance, type, props);
+ }
+ },
+
+ didNotFindHydratableTextInstance(
+ parentType: string,
+ parentProps: Props,
+ parentInstance: Instance,
+ text: string,
+ ) {
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
+ warnForInsertedHydratedText(parentInstance, text);
+ }
+ },
+ },
+
+ scheduleDeferredCallback: ReactScheduler.rIC,
+ cancelDeferredCallback: ReactScheduler.cIC,
+};
+
+export default ReactDOMHostConfig;
diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js
new file mode 100644
index 000000000000..921067e5164f
--- /dev/null
+++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js
@@ -0,0 +1,355 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * 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 {
+ MeasureInWindowOnSuccessCallback,
+ MeasureLayoutOnSuccessCallback,
+ MeasureOnSuccessCallback,
+ NativeMethodsMixinType,
+ ReactNativeBaseComponentViewConfig,
+} from './ReactNativeTypes';
+
+import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
+import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
+import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
+import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
+
+import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
+import invariant from 'fbjs/lib/invariant';
+
+// Modules provided by RN:
+import TextInputState from 'TextInputState';
+import FabricUIManager from 'FabricUIManager';
+import UIManager from 'UIManager';
+
+// Counter for uniquely identifying views.
+// % 10 === 1 means it is a rootTag.
+// % 2 === 0 means it is a Fabric tag.
+// This means that they never overlap.
+let nextReactTag = 2;
+
+type HostContext = $ReadOnly<{|
+ isInAParentText: boolean,
+|}>;
+
+/**
+ * This is used for refs on host components.
+ */
+class ReactFabricHostComponent {
+ _nativeTag: number;
+ viewConfig: ReactNativeBaseComponentViewConfig;
+ currentProps: Props;
+
+ constructor(
+ tag: number,
+ viewConfig: ReactNativeBaseComponentViewConfig,
+ props: Props,
+ ) {
+ this._nativeTag = tag;
+ this.viewConfig = viewConfig;
+ this.currentProps = props;
+ }
+
+ blur() {
+ TextInputState.blurTextInput(this._nativeTag);
+ }
+
+ focus() {
+ TextInputState.focusTextInput(this._nativeTag);
+ }
+
+ measure(callback: MeasureOnSuccessCallback) {
+ UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
+ }
+
+ measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
+ UIManager.measureInWindow(
+ this._nativeTag,
+ mountSafeCallback(this, callback),
+ );
+ }
+
+ measureLayout(
+ relativeToNativeNode: number,
+ onSuccess: MeasureLayoutOnSuccessCallback,
+ onFail: () => void /* currently unused */,
+ ) {
+ UIManager.measureLayout(
+ this._nativeTag,
+ relativeToNativeNode,
+ mountSafeCallback(this, onFail),
+ mountSafeCallback(this, onSuccess),
+ );
+ }
+
+ setNativeProps(nativeProps: Object) {
+ if (__DEV__) {
+ warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
+ }
+
+ const updatePayload = ReactNativeAttributePayload.create(
+ nativeProps,
+ this.viewConfig.validAttributes,
+ );
+
+ // Avoid the overhead of bridge calls if there's no update.
+ // This is an expensive no-op for Android, and causes an unnecessary
+ // view invalidation for certain components (eg RCTTextInput) on iOS.
+ if (updatePayload != null) {
+ UIManager.updateView(
+ this._nativeTag,
+ this.viewConfig.uiViewClassName,
+ updatePayload,
+ );
+ }
+ }
+}
+
+// eslint-disable-next-line no-unused-expressions
+(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
+
+type Node = Object;
+type ChildSet = Object;
+type Container = number;
+type Instance = {
+ node: Node,
+ canonical: ReactFabricHostComponent,
+};
+type Props = Object;
+type TextInstance = {
+ node: Node,
+};
+
+const ReacFabricHostConfig = {
+ appendInitialChild(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ ): void {
+ FabricUIManager.appendChild(parentInstance.node, child.node);
+ },
+
+ createInstance(
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ hostContext: HostContext,
+ internalInstanceHandle: Object,
+ ): Instance {
+ const tag = nextReactTag;
+ nextReactTag += 2;
+
+ const viewConfig = ReactNativeViewConfigRegistry.get(type);
+
+ if (__DEV__) {
+ for (const key in viewConfig.validAttributes) {
+ if (props.hasOwnProperty(key)) {
+ deepFreezeAndThrowOnMutationInDev(props[key]);
+ }
+ }
+ }
+
+ invariant(
+ type !== 'RCTView' || !hostContext.isInAParentText,
+ 'Nesting of within is not currently supported.',
+ );
+
+ const updatePayload = ReactNativeAttributePayload.create(
+ props,
+ viewConfig.validAttributes,
+ );
+
+ const node = FabricUIManager.createNode(
+ tag, // reactTag
+ viewConfig.uiViewClassName, // viewName
+ rootContainerInstance, // rootTag
+ updatePayload, // props
+ internalInstanceHandle, // internalInstanceHandle
+ );
+
+ const component = new ReactFabricHostComponent(tag, viewConfig, props);
+
+ return {
+ node: node,
+ canonical: component,
+ };
+ },
+
+ createTextInstance(
+ text: string,
+ rootContainerInstance: Container,
+ hostContext: HostContext,
+ internalInstanceHandle: Object,
+ ): TextInstance {
+ invariant(
+ hostContext.isInAParentText,
+ 'Text strings must be rendered within a component.',
+ );
+
+ const tag = nextReactTag;
+ nextReactTag += 2;
+
+ const node = FabricUIManager.createNode(
+ tag, // reactTag
+ 'RCTRawText', // viewName
+ rootContainerInstance, // rootTag
+ {text: text}, // props
+ internalInstanceHandle, // instance handle
+ );
+
+ return {
+ node: node,
+ };
+ },
+
+ finalizeInitialChildren(
+ parentInstance: Instance,
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ ): boolean {
+ return false;
+ },
+
+ getRootHostContext(rootContainerInstance: Container): HostContext {
+ return {isInAParentText: false};
+ },
+
+ getChildHostContext(
+ parentHostContext: HostContext,
+ type: string,
+ ): HostContext {
+ const prevIsInAParentText = parentHostContext.isInAParentText;
+ const isInAParentText =
+ type === 'AndroidTextInput' || // Android
+ type === 'RCTMultilineTextInputView' || // iOS
+ type === 'RCTSinglelineTextInputView' || // iOS
+ type === 'RCTText' ||
+ type === 'RCTVirtualText';
+
+ if (prevIsInAParentText !== isInAParentText) {
+ return {isInAParentText};
+ } else {
+ return parentHostContext;
+ }
+ },
+
+ getPublicInstance(instance: Instance): * {
+ return instance.canonical;
+ },
+
+ now: ReactNativeFrameScheduling.now,
+
+ // The Fabric renderer is secondary to the existing React Native renderer.
+ isPrimaryRenderer: false,
+
+ prepareForCommit(): void {
+ // Noop
+ },
+
+ prepareUpdate(
+ instance: Instance,
+ type: string,
+ oldProps: Props,
+ newProps: Props,
+ rootContainerInstance: Container,
+ hostContext: HostContext,
+ ): null | Object {
+ const viewConfig = instance.canonical.viewConfig;
+ const updatePayload = ReactNativeAttributePayload.diff(
+ oldProps,
+ newProps,
+ viewConfig.validAttributes,
+ );
+ // TODO: If the event handlers have changed, we need to update the current props
+ // in the commit phase but there is no host config hook to do it yet.
+ return updatePayload;
+ },
+
+ resetAfterCommit(): void {
+ // Noop
+ },
+
+ scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
+ cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
+
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
+ return false;
+ },
+
+ shouldSetTextContent(type: string, props: Props): boolean {
+ // TODO (bvaughn) Revisit this decision.
+ // Always returning false simplifies the createInstance() implementation,
+ // But creates an additional child Fiber for raw text children.
+ // No additional native views are created though.
+ // It's not clear to me which is better so I'm deferring for now.
+ // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
+ return false;
+ },
+
+ persistence: {
+ cloneInstance(
+ instance: Instance,
+ updatePayload: null | Object,
+ type: string,
+ oldProps: Props,
+ newProps: Props,
+ internalInstanceHandle: Object,
+ keepChildren: boolean,
+ recyclableInstance: null | Instance,
+ ): Instance {
+ const node = instance.node;
+ let clone;
+ if (keepChildren) {
+ if (updatePayload !== null) {
+ clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
+ } else {
+ clone = FabricUIManager.cloneNode(node);
+ }
+ } else {
+ if (updatePayload !== null) {
+ clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
+ node,
+ updatePayload,
+ );
+ } else {
+ clone = FabricUIManager.cloneNodeWithNewChildren(node);
+ }
+ }
+ return {
+ node: clone,
+ canonical: instance.canonical,
+ };
+ },
+
+ createContainerChildSet(container: Container): ChildSet {
+ return FabricUIManager.createChildSet(container);
+ },
+
+ appendChildToContainerChildSet(
+ childSet: ChildSet,
+ child: Instance | TextInstance,
+ ): void {
+ FabricUIManager.appendChildToSet(childSet, child.node);
+ },
+
+ finalizeContainerChildren(
+ container: Container,
+ newChildren: ChildSet,
+ ): void {
+ FabricUIManager.completeRoot(container, newChildren);
+ },
+
+ replaceContainerChildren(
+ container: Container,
+ newChildren: ChildSet,
+ ): void {},
+ },
+};
+
+export default ReacFabricHostConfig;
diff --git a/packages/react-native-renderer/src/ReactFabricRenderer.js b/packages/react-native-renderer/src/ReactFabricRenderer.js
index 9c597c2e5320..8f77d283c9a2 100644
--- a/packages/react-native-renderer/src/ReactFabricRenderer.js
+++ b/packages/react-native-renderer/src/ReactFabricRenderer.js
@@ -7,350 +7,9 @@
* @flow
*/
-import type {
- MeasureInWindowOnSuccessCallback,
- MeasureLayoutOnSuccessCallback,
- MeasureOnSuccessCallback,
- NativeMethodsMixinType,
- ReactNativeBaseComponentViewConfig,
-} from './ReactNativeTypes';
-
-import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
-import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
-import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
-import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
import ReactFiberReconciler from 'react-reconciler';
+import ReactFabricHostConfig from './ReactFabricHostConfig';
-import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
-import invariant from 'fbjs/lib/invariant';
-
-// Modules provided by RN:
-import TextInputState from 'TextInputState';
-import FabricUIManager from 'FabricUIManager';
-import UIManager from 'UIManager';
-
-// Counter for uniquely identifying views.
-// % 10 === 1 means it is a rootTag.
-// % 2 === 0 means it is a Fabric tag.
-// This means that they never overlap.
-let nextReactTag = 2;
-
-type HostContext = $ReadOnly<{|
- isInAParentText: boolean,
-|}>;
-
-/**
- * This is used for refs on host components.
- */
-class ReactFabricHostComponent {
- _nativeTag: number;
- viewConfig: ReactNativeBaseComponentViewConfig;
- currentProps: Props;
-
- constructor(
- tag: number,
- viewConfig: ReactNativeBaseComponentViewConfig,
- props: Props,
- ) {
- this._nativeTag = tag;
- this.viewConfig = viewConfig;
- this.currentProps = props;
- }
-
- blur() {
- TextInputState.blurTextInput(this._nativeTag);
- }
-
- focus() {
- TextInputState.focusTextInput(this._nativeTag);
- }
-
- measure(callback: MeasureOnSuccessCallback) {
- UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
- }
-
- measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
- UIManager.measureInWindow(
- this._nativeTag,
- mountSafeCallback(this, callback),
- );
- }
-
- measureLayout(
- relativeToNativeNode: number,
- onSuccess: MeasureLayoutOnSuccessCallback,
- onFail: () => void /* currently unused */,
- ) {
- UIManager.measureLayout(
- this._nativeTag,
- relativeToNativeNode,
- mountSafeCallback(this, onFail),
- mountSafeCallback(this, onSuccess),
- );
- }
-
- setNativeProps(nativeProps: Object) {
- if (__DEV__) {
- warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
- }
-
- const updatePayload = ReactNativeAttributePayload.create(
- nativeProps,
- this.viewConfig.validAttributes,
- );
-
- // Avoid the overhead of bridge calls if there's no update.
- // This is an expensive no-op for Android, and causes an unnecessary
- // view invalidation for certain components (eg RCTTextInput) on iOS.
- if (updatePayload != null) {
- UIManager.updateView(
- this._nativeTag,
- this.viewConfig.uiViewClassName,
- updatePayload,
- );
- }
- }
-}
-
-// eslint-disable-next-line no-unused-expressions
-(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
-
-type Node = Object;
-type ChildSet = Object;
-type Container = number;
-type Instance = {
- node: Node,
- canonical: ReactFabricHostComponent,
-};
-type Props = Object;
-type TextInstance = {
- node: Node,
-};
-
-const ReactFabricRenderer = ReactFiberReconciler({
- appendInitialChild(
- parentInstance: Instance,
- child: Instance | TextInstance,
- ): void {
- FabricUIManager.appendChild(parentInstance.node, child.node);
- },
-
- createInstance(
- type: string,
- props: Props,
- rootContainerInstance: Container,
- hostContext: HostContext,
- internalInstanceHandle: Object,
- ): Instance {
- const tag = nextReactTag;
- nextReactTag += 2;
-
- const viewConfig = ReactNativeViewConfigRegistry.get(type);
-
- if (__DEV__) {
- for (const key in viewConfig.validAttributes) {
- if (props.hasOwnProperty(key)) {
- deepFreezeAndThrowOnMutationInDev(props[key]);
- }
- }
- }
-
- invariant(
- type !== 'RCTView' || !hostContext.isInAParentText,
- 'Nesting of within is not currently supported.',
- );
-
- const updatePayload = ReactNativeAttributePayload.create(
- props,
- viewConfig.validAttributes,
- );
-
- const node = FabricUIManager.createNode(
- tag, // reactTag
- viewConfig.uiViewClassName, // viewName
- rootContainerInstance, // rootTag
- updatePayload, // props
- internalInstanceHandle, // internalInstanceHandle
- );
-
- const component = new ReactFabricHostComponent(tag, viewConfig, props);
-
- return {
- node: node,
- canonical: component,
- };
- },
-
- createTextInstance(
- text: string,
- rootContainerInstance: Container,
- hostContext: HostContext,
- internalInstanceHandle: Object,
- ): TextInstance {
- invariant(
- hostContext.isInAParentText,
- 'Text strings must be rendered within a component.',
- );
-
- const tag = nextReactTag;
- nextReactTag += 2;
-
- const node = FabricUIManager.createNode(
- tag, // reactTag
- 'RCTRawText', // viewName
- rootContainerInstance, // rootTag
- {text: text}, // props
- internalInstanceHandle, // instance handle
- );
-
- return {
- node: node,
- };
- },
-
- finalizeInitialChildren(
- parentInstance: Instance,
- type: string,
- props: Props,
- rootContainerInstance: Container,
- ): boolean {
- return false;
- },
-
- getRootHostContext(rootContainerInstance: Container): HostContext {
- return {isInAParentText: false};
- },
-
- getChildHostContext(
- parentHostContext: HostContext,
- type: string,
- ): HostContext {
- const prevIsInAParentText = parentHostContext.isInAParentText;
- const isInAParentText =
- type === 'AndroidTextInput' || // Android
- type === 'RCTMultilineTextInputView' || // iOS
- type === 'RCTSinglelineTextInputView' || // iOS
- type === 'RCTText' ||
- type === 'RCTVirtualText';
-
- if (prevIsInAParentText !== isInAParentText) {
- return {isInAParentText};
- } else {
- return parentHostContext;
- }
- },
-
- getPublicInstance(instance) {
- return instance.canonical;
- },
-
- now: ReactNativeFrameScheduling.now,
-
- // The Fabric renderer is secondary to the existing React Native renderer.
- isPrimaryRenderer: false,
-
- prepareForCommit(): void {
- // Noop
- },
-
- prepareUpdate(
- instance: Instance,
- type: string,
- oldProps: Props,
- newProps: Props,
- rootContainerInstance: Container,
- hostContext: HostContext,
- ): null | Object {
- const viewConfig = instance.canonical.viewConfig;
- const updatePayload = ReactNativeAttributePayload.diff(
- oldProps,
- newProps,
- viewConfig.validAttributes,
- );
- // TODO: If the event handlers have changed, we need to update the current props
- // in the commit phase but there is no host config hook to do it yet.
- return updatePayload;
- },
-
- resetAfterCommit(): void {
- // Noop
- },
-
- scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
- cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
-
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
- return false;
- },
-
- shouldSetTextContent(type: string, props: Props): boolean {
- // TODO (bvaughn) Revisit this decision.
- // Always returning false simplifies the createInstance() implementation,
- // But creates an additional child Fiber for raw text children.
- // No additional native views are created though.
- // It's not clear to me which is better so I'm deferring for now.
- // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
- return false;
- },
-
- persistence: {
- cloneInstance(
- instance: Instance,
- updatePayload: null | Object,
- type: string,
- oldProps: Props,
- newProps: Props,
- internalInstanceHandle: Object,
- keepChildren: boolean,
- recyclableInstance: null | Instance,
- ): Instance {
- const node = instance.node;
- let clone;
- if (keepChildren) {
- if (updatePayload !== null) {
- clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
- } else {
- clone = FabricUIManager.cloneNode(node);
- }
- } else {
- if (updatePayload !== null) {
- clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
- node,
- updatePayload,
- );
- } else {
- clone = FabricUIManager.cloneNodeWithNewChildren(node);
- }
- }
- return {
- node: clone,
- canonical: instance.canonical,
- };
- },
-
- createContainerChildSet(container: Container): ChildSet {
- return FabricUIManager.createChildSet(container);
- },
-
- appendChildToContainerChildSet(
- childSet: ChildSet,
- child: Instance | TextInstance,
- ): void {
- FabricUIManager.appendChildToSet(childSet, child.node);
- },
-
- finalizeContainerChildren(
- container: Container,
- newChildren: ChildSet,
- ): void {
- FabricUIManager.completeRoot(container, newChildren);
- },
-
- replaceContainerChildren(
- container: Container,
- newChildren: ChildSet,
- ): void {},
- },
-});
+const ReactFabricRenderer = ReactFiberReconciler(ReactFabricHostConfig);
export default ReactFabricRenderer;
diff --git a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
index 86c0d3ad707d..3d5bf5bbc993 100644
--- a/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+++ b/packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
@@ -14,7 +14,7 @@ import type {
NativeMethodsMixinType,
ReactNativeBaseComponentViewConfig,
} from './ReactNativeTypes';
-import type {Instance} from './ReactNativeFiberRenderer';
+import type {Instance} from './ReactNativeHostConfig';
// Modules provided by RN:
import TextInputState from 'TextInputState';
diff --git a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js
similarity index 98%
rename from packages/react-native-renderer/src/ReactNativeFiberRenderer.js
rename to packages/react-native-renderer/src/ReactNativeHostConfig.js
index 01fe4816b4ba..b766d1e6554f 100644
--- a/packages/react-native-renderer/src/ReactNativeFiberRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js
@@ -9,7 +9,6 @@
import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
-import ReactFiberReconciler from 'react-reconciler';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'fbjs/lib/invariant';
@@ -64,7 +63,7 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
}
}
-const NativeRenderer = ReactFiberReconciler({
+const ReactNativeHostConfig = {
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
@@ -193,7 +192,7 @@ const NativeRenderer = ReactFiberReconciler({
}
},
- getPublicInstance(instance) {
+ getPublicInstance(instance: Instance): * {
return instance;
},
@@ -427,6 +426,6 @@ const NativeRenderer = ReactFiberReconciler({
// Noop
},
},
-});
+};
-export default NativeRenderer;
+export default ReactNativeHostConfig;
diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js
index f8e984d0e63f..ce55def9e679 100644
--- a/packages/react-native-renderer/src/ReactNativeRenderer.js
+++ b/packages/react-native-renderer/src/ReactNativeRenderer.js
@@ -12,6 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
import './ReactNativeInjection';
+import ReactFiberReconciler from 'react-reconciler';
import * as ReactPortal from 'shared/ReactPortal';
import * as ReactGenericBatching from 'events/ReactGenericBatching';
import ReactVersion from 'shared/ReactVersion';
@@ -20,16 +21,18 @@ import UIManager from 'UIManager';
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
+import ReactNativeHostConfig from './ReactNativeHostConfig';
import NativeMethodsMixin from './NativeMethodsMixin';
import ReactNativeComponent from './ReactNativeComponent';
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
-import ReactNativeFiberRenderer from './ReactNativeFiberRenderer';
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
import getComponentName from 'shared/getComponentName';
import warning from 'fbjs/lib/warning';
+const ReactNativeFiberRenderer = ReactFiberReconciler(ReactNativeHostConfig);
+
const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
function findNodeHandle(componentOrHandle: any): ?number {
diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js
new file mode 100644
index 000000000000..ef7c6c927c4b
--- /dev/null
+++ b/packages/react-test-renderer/src/ReactTestHostConfig.js
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import emptyObject from 'fbjs/lib/emptyObject';
+
+import * as TestRendererScheduling from './ReactTestRendererScheduling';
+
+export type Instance = {|
+ type: string,
+ props: Object,
+ children: Array,
+ rootContainerInstance: Container,
+ tag: 'INSTANCE',
+|};
+
+export type TextInstance = {|
+ text: string,
+ tag: 'TEXT',
+|};
+
+type Container = {|
+ children: Array,
+ createNodeMock: Function,
+ tag: 'CONTAINER',
+|};
+
+type Props = Object;
+
+const UPDATE_SIGNAL = {};
+
+function getPublicInstance(inst: Instance | TextInstance): * {
+ switch (inst.tag) {
+ case 'INSTANCE':
+ const createNodeMock = inst.rootContainerInstance.createNodeMock;
+ return createNodeMock({
+ type: inst.type,
+ props: inst.props,
+ });
+ default:
+ return inst;
+ }
+}
+
+function appendChild(
+ parentInstance: Instance | Container,
+ child: Instance | TextInstance,
+): void {
+ const index = parentInstance.children.indexOf(child);
+ if (index !== -1) {
+ parentInstance.children.splice(index, 1);
+ }
+ parentInstance.children.push(child);
+}
+
+function insertBefore(
+ parentInstance: Instance | Container,
+ child: Instance | TextInstance,
+ beforeChild: Instance | TextInstance,
+): void {
+ const index = parentInstance.children.indexOf(child);
+ if (index !== -1) {
+ parentInstance.children.splice(index, 1);
+ }
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
+ parentInstance.children.splice(beforeIndex, 0, child);
+}
+
+function removeChild(
+ parentInstance: Instance | Container,
+ child: Instance | TextInstance,
+): void {
+ const index = parentInstance.children.indexOf(child);
+ parentInstance.children.splice(index, 1);
+}
+
+const ReactTestHostConfig = {
+ getRootHostContext() {
+ return emptyObject;
+ },
+
+ getChildHostContext() {
+ return emptyObject;
+ },
+
+ prepareForCommit(): void {
+ // noop
+ },
+
+ resetAfterCommit(): void {
+ // noop
+ },
+
+ createInstance(
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ hostContext: Object,
+ internalInstanceHandle: Object,
+ ): Instance {
+ return {
+ type,
+ props,
+ children: [],
+ rootContainerInstance,
+ tag: 'INSTANCE',
+ };
+ },
+
+ appendInitialChild(
+ parentInstance: Instance,
+ child: Instance | TextInstance,
+ ): void {
+ const index = parentInstance.children.indexOf(child);
+ if (index !== -1) {
+ parentInstance.children.splice(index, 1);
+ }
+ parentInstance.children.push(child);
+ },
+
+ finalizeInitialChildren(
+ testElement: Instance,
+ type: string,
+ props: Props,
+ rootContainerInstance: Container,
+ ): boolean {
+ return false;
+ },
+
+ prepareUpdate(
+ testElement: Instance,
+ type: string,
+ oldProps: Props,
+ newProps: Props,
+ rootContainerInstance: Container,
+ hostContext: Object,
+ ): null | {} {
+ return UPDATE_SIGNAL;
+ },
+
+ shouldSetTextContent(type: string, props: Props): boolean {
+ return false;
+ },
+
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
+ return false;
+ },
+
+ createTextInstance(
+ text: string,
+ rootContainerInstance: Container,
+ hostContext: Object,
+ internalInstanceHandle: Object,
+ ): TextInstance {
+ return {
+ text,
+ tag: 'TEXT',
+ };
+ },
+
+ getPublicInstance,
+
+ scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
+ cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
+ // This approach enables `now` to be mocked by tests,
+ // Even after the reconciler has initialized and read host config values.
+ now: () => TestRendererScheduling.nowImplementation(),
+
+ isPrimaryRenderer: true,
+
+ mutation: {
+ commitUpdate(
+ instance: Instance,
+ updatePayload: {},
+ type: string,
+ oldProps: Props,
+ newProps: Props,
+ internalInstanceHandle: Object,
+ ): void {
+ instance.type = type;
+ instance.props = newProps;
+ },
+
+ commitMount(
+ instance: Instance,
+ type: string,
+ newProps: Props,
+ internalInstanceHandle: Object,
+ ): void {
+ // noop
+ },
+
+ commitTextUpdate(
+ textInstance: TextInstance,
+ oldText: string,
+ newText: string,
+ ): void {
+ textInstance.text = newText;
+ },
+ resetTextContent(testElement: Instance): void {
+ // noop
+ },
+
+ appendChild: appendChild,
+ appendChildToContainer: appendChild,
+ insertBefore: insertBefore,
+ insertInContainerBefore: insertBefore,
+ removeChild: removeChild,
+ removeChildFromContainer: removeChild,
+ },
+};
+
+export default ReactTestHostConfig;
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index 7fa54534d310..5fab9af606fb 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -9,12 +9,11 @@
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
-import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
+import type {Instance, TextInstance} from './ReactTestHostConfig';
import ReactFiberReconciler from 'react-reconciler';
import {batchedUpdates} from 'events/ReactGenericBatching';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
-import emptyObject from 'fbjs/lib/emptyObject';
import {
Fragment,
FunctionalComponent,
@@ -31,6 +30,9 @@ import {
} from 'shared/ReactTypeOfWork';
import invariant from 'fbjs/lib/invariant';
+import ReactTestHostConfig from './ReactTestHostConfig';
+import * as TestRendererScheduling from './ReactTestRendererScheduling';
+
type TestRendererOptions = {
createNodeMock: (element: React$Element) => any,
unstable_isAsync: boolean,
@@ -44,26 +46,6 @@ type ReactTestRendererJSON = {|
|};
type ReactTestRendererNode = ReactTestRendererJSON | string;
-type Container = {|
- children: Array,
- createNodeMock: Function,
- tag: 'CONTAINER',
-|};
-
-type Props = Object;
-type Instance = {|
- type: string,
- props: Object,
- children: Array,
- rootContainerInstance: Container,
- tag: 'INSTANCE',
-|};
-
-type TextInstance = {|
- text: string,
- tag: 'TEXT',
-|};
-
type FindOptions = $Shape<{
// performs a "greedy" search: if a matching node is found, will continue
// to search within the matching node's children. (default: true)
@@ -72,203 +54,7 @@ type FindOptions = $Shape<{
export type Predicate = (node: ReactTestInstance) => ?boolean;
-const UPDATE_SIGNAL = {};
-
-function getPublicInstance(inst: Instance | TextInstance): * {
- switch (inst.tag) {
- case 'INSTANCE':
- const createNodeMock = inst.rootContainerInstance.createNodeMock;
- return createNodeMock({
- type: inst.type,
- props: inst.props,
- });
- default:
- return inst;
- }
-}
-
-function appendChild(
- parentInstance: Instance | Container,
- child: Instance | TextInstance,
-): void {
- const index = parentInstance.children.indexOf(child);
- if (index !== -1) {
- parentInstance.children.splice(index, 1);
- }
- parentInstance.children.push(child);
-}
-
-function insertBefore(
- parentInstance: Instance | Container,
- child: Instance | TextInstance,
- beforeChild: Instance | TextInstance,
-): void {
- const index = parentInstance.children.indexOf(child);
- if (index !== -1) {
- parentInstance.children.splice(index, 1);
- }
- const beforeIndex = parentInstance.children.indexOf(beforeChild);
- parentInstance.children.splice(beforeIndex, 0, child);
-}
-
-function removeChild(
- parentInstance: Instance | Container,
- child: Instance | TextInstance,
-): void {
- const index = parentInstance.children.indexOf(child);
- parentInstance.children.splice(index, 1);
-}
-
-// Current virtual time
-let nowImplementation = () => 0;
-let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
-let yieldedValues: Array | null = null;
-
-const TestRenderer = ReactFiberReconciler({
- getRootHostContext() {
- return emptyObject;
- },
-
- getChildHostContext() {
- return emptyObject;
- },
-
- prepareForCommit(): void {
- // noop
- },
-
- resetAfterCommit(): void {
- // noop
- },
-
- createInstance(
- type: string,
- props: Props,
- rootContainerInstance: Container,
- hostContext: Object,
- internalInstanceHandle: Object,
- ): Instance {
- return {
- type,
- props,
- children: [],
- rootContainerInstance,
- tag: 'INSTANCE',
- };
- },
-
- appendInitialChild(
- parentInstance: Instance,
- child: Instance | TextInstance,
- ): void {
- const index = parentInstance.children.indexOf(child);
- if (index !== -1) {
- parentInstance.children.splice(index, 1);
- }
- parentInstance.children.push(child);
- },
-
- finalizeInitialChildren(
- testElement: Instance,
- type: string,
- props: Props,
- rootContainerInstance: Container,
- ): boolean {
- return false;
- },
-
- prepareUpdate(
- testElement: Instance,
- type: string,
- oldProps: Props,
- newProps: Props,
- rootContainerInstance: Container,
- hostContext: Object,
- ): null | {} {
- return UPDATE_SIGNAL;
- },
-
- shouldSetTextContent(type: string, props: Props): boolean {
- return false;
- },
-
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
- return false;
- },
-
- createTextInstance(
- text: string,
- rootContainerInstance: Container,
- hostContext: Object,
- internalInstanceHandle: Object,
- ): TextInstance {
- return {
- text,
- tag: 'TEXT',
- };
- },
-
- scheduleDeferredCallback(
- callback: (deadline: Deadline) => mixed,
- options?: {timeout: number},
- ): number {
- scheduledCallback = callback;
- return 0;
- },
-
- cancelDeferredCallback(timeoutID: number): void {
- scheduledCallback = null;
- },
-
- getPublicInstance,
-
- // This approach enables `now` to be mocked by tests,
- // Even after the reconciler has initialized and read host config values.
- now: () => nowImplementation(),
-
- isPrimaryRenderer: true,
-
- mutation: {
- commitUpdate(
- instance: Instance,
- updatePayload: {},
- type: string,
- oldProps: Props,
- newProps: Props,
- internalInstanceHandle: Object,
- ): void {
- instance.type = type;
- instance.props = newProps;
- },
-
- commitMount(
- instance: Instance,
- type: string,
- newProps: Props,
- internalInstanceHandle: Object,
- ): void {
- // noop
- },
-
- commitTextUpdate(
- textInstance: TextInstance,
- oldText: string,
- newText: string,
- ): void {
- textInstance.text = newText;
- },
- resetTextContent(testElement: Instance): void {
- // noop
- },
-
- appendChild: appendChild,
- appendChildToContainer: appendChild,
- insertBefore: insertBefore,
- insertInContainerBefore: insertBefore,
- removeChild: removeChild,
- removeChildFromContainer: removeChild,
- },
-});
+const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);
const defaultTestOptions = {
createNodeMock: function() {
@@ -444,7 +230,7 @@ class ReactTestInstance {
get instance() {
if (this._fiber.tag === HostComponent) {
- return getPublicInstance(this._fiber.stateNode);
+ return ReactTestHostConfig.getPublicInstance(this._fiber.stateNode);
} else {
return this._fiber.stateNode;
}
@@ -676,77 +462,20 @@ const ReactTestRendererFiber = {
container = null;
root = null;
},
- unstable_flushAll(): Array {
- yieldedValues = null;
- while (scheduledCallback !== null) {
- const cb = scheduledCallback;
- scheduledCallback = null;
- cb({
- timeRemaining() {
- // Keep rendering until there's no more work
- return 999;
- },
- // React's scheduler has its own way of keeping track of expired
- // work and doesn't read this, so don't bother setting it to the
- // correct value.
- didTimeout: false,
- });
- }
- if (yieldedValues === null) {
- // Always return an array.
- return [];
- }
- return yieldedValues;
- },
- unstable_flushThrough(expectedValues: Array): Array {
- let didStop = false;
- yieldedValues = null;
- while (scheduledCallback !== null && !didStop) {
- const cb = scheduledCallback;
- scheduledCallback = null;
- cb({
- timeRemaining() {
- if (
- yieldedValues !== null &&
- yieldedValues.length >= expectedValues.length
- ) {
- // We at least as many values as expected. Stop rendering.
- didStop = true;
- return 0;
- }
- // Keep rendering.
- return 999;
- },
- // React's scheduler has its own way of keeping track of expired
- // work and doesn't read this, so don't bother setting it to the
- // correct value.
- didTimeout: false,
- });
- }
- if (yieldedValues === null) {
- // Always return an array.
- return [];
- }
- return yieldedValues;
- },
- unstable_yield(value: mixed): void {
- if (yieldedValues === null) {
- yieldedValues = [value];
- } else {
- yieldedValues.push(value);
- }
- },
getInstance() {
if (root == null || root.current == null) {
return null;
}
return TestRenderer.getPublicRootInstance(root);
},
+ unstable_flushAll: TestRendererScheduling.flushAll,
unstable_flushSync(fn: Function) {
- yieldedValues = [];
- TestRenderer.flushSync(fn);
- return yieldedValues;
+ return TestRendererScheduling.withCleanYields(() => {
+ TestRenderer.flushSync(fn);
+ });
},
+ unstable_flushThrough: TestRendererScheduling.flushThrough,
+ unstable_yield: TestRendererScheduling.yieldValue,
};
Object.defineProperty(
@@ -771,9 +500,7 @@ const ReactTestRendererFiber = {
unstable_batchedUpdates: batchedUpdates,
/* eslint-enable camelcase */
- unstable_setNowImplementation(implementation: () => number): void {
- nowImplementation = implementation;
- },
+ unstable_setNowImplementation: TestRendererScheduling.setNowImplementation,
};
export default ReactTestRendererFiber;
diff --git a/packages/react-test-renderer/src/ReactTestRendererScheduling.js b/packages/react-test-renderer/src/ReactTestRendererScheduling.js
new file mode 100644
index 000000000000..dfa7616a3b36
--- /dev/null
+++ b/packages/react-test-renderer/src/ReactTestRendererScheduling.js
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2013-present, Facebook, Inc.
+ *
+ * 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 {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
+
+// Current virtual time
+export let nowImplementation = () => 0;
+export let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
+export let yieldedValues: Array | null = null;
+
+export function scheduleDeferredCallback(
+ callback: (deadline: Deadline) => mixed,
+ options?: {timeout: number},
+): number {
+ scheduledCallback = callback;
+ return 0;
+}
+
+export function cancelDeferredCallback(timeoutID: number): void {
+ scheduledCallback = null;
+}
+
+export function setNowImplementation(implementation: () => number): void {
+ nowImplementation = implementation;
+}
+
+export function flushAll(): Array {
+ yieldedValues = null;
+ while (scheduledCallback !== null) {
+ const cb = scheduledCallback;
+ scheduledCallback = null;
+ cb({
+ timeRemaining() {
+ // Keep rendering until there's no more work
+ return 999;
+ },
+ // React's scheduler has its own way of keeping track of expired
+ // work and doesn't read this, so don't bother setting it to the
+ // correct value.
+ didTimeout: false,
+ });
+ }
+ if (yieldedValues === null) {
+ // Always return an array.
+ return [];
+ }
+ return yieldedValues;
+}
+
+export function flushThrough(expectedValues: Array): Array {
+ let didStop = false;
+ yieldedValues = null;
+ while (scheduledCallback !== null && !didStop) {
+ const cb = scheduledCallback;
+ scheduledCallback = null;
+ cb({
+ timeRemaining() {
+ if (
+ yieldedValues !== null &&
+ yieldedValues.length >= expectedValues.length
+ ) {
+ // We at least as many values as expected. Stop rendering.
+ didStop = true;
+ return 0;
+ }
+ // Keep rendering.
+ return 999;
+ },
+ // React's scheduler has its own way of keeping track of expired
+ // work and doesn't read this, so don't bother setting it to the
+ // correct value.
+ didTimeout: false,
+ });
+ }
+ if (yieldedValues === null) {
+ // Always return an array.
+ return [];
+ }
+ return yieldedValues;
+}
+
+export function yieldValue(value: mixed): void {
+ if (yieldedValues === null) {
+ yieldedValues = [value];
+ } else {
+ yieldedValues.push(value);
+ }
+}
+
+export function withCleanYields(fn: Function) {
+ yieldedValues = [];
+ fn();
+ return yieldedValues;
+}