@adpt/core
Version:
AdaptJS core library
826 lines • 31.4 kB
JavaScript
;
/*
* Copyright 2018-2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const debug_1 = tslib_1.__importDefault(require("debug"));
const util = tslib_1.__importStar(require("util"));
const ld = tslib_1.__importStar(require("lodash"));
const css = tslib_1.__importStar(require("./css"));
const jsx_1 = require("./jsx");
const state_1 = require("./state");
const observers_1 = require("./observers");
const utils_1 = require("@adpt/utils");
const builtin_components_1 = require("./builtin_components");
const error_1 = require("./error");
const handle_1 = require("./handle");
const hooks_1 = require("./hooks");
const keys_1 = require("./keys");
const debugBuild = debug_1.default("adapt:dom:build");
const debugState = debug_1.default("adapt:state");
class BuildResults {
constructor(recorder, mountedOrig, contents, other) {
this.recorder = recorder;
// These accumulate across build passes
this.buildErr = false;
this.messages = [];
this.buildPassReset();
if (contents !== undefined) {
this.contents = contents;
}
if (mountedOrig !== undefined) {
this.mountedOrig = mountedOrig;
}
if (other !== undefined) {
this.combine(other);
}
}
buildPassReset() {
this.mountedOrig = null;
this.contents = null;
this.cleanups = [];
this.mountedElements = [];
this.builtElements = [];
this.stateChanged = false;
this.partialBuild = false;
}
// Terminology is a little confusing here. Anything that allows the
// build to keep progressing should be MessageType.warning.
// MessageType.error should only be for catastrophic things where
// the build cannot keep running (i.e. an exception that can't be
// handled within build).
// However, either MessageType.warning or MessageType.error indicates
// an unsuccessful build, therefore buildErr = true.
/**
* Record an error in build data recorder and log a message and mark
* the build as errored.
* This is the primary interface for most build errors.
*/
error(err, from) {
const error = ld.isError(err) ? err : new Error(err);
this.recorder({ type: "error", error });
this.message({ type: utils_1.MessageType.warning, from }, error);
}
message(msg, err) {
const content = err ? err.message : msg.content;
if (!content)
throw new error_1.InternalError(`build message doesn't have content or err`);
const copy = Object.assign({}, msg, { content, timestamp: msg.timestamp ? msg.timestamp : Date.now(), from: msg.from ? msg.from : "DOM build" });
this.messages.push(copy);
switch (copy.type) {
case utils_1.MessageType.warning:
case utils_1.MessageType.error:
this.buildErr = true;
this.partialBuild = true;
}
}
combine(other) {
this.messages.push(...other.messages);
this.cleanups.push(...other.cleanups);
this.mountedElements.push(...other.mountedElements);
this.builtElements.push(...other.builtElements);
this.buildErr = this.buildErr || other.buildErr;
this.partialBuild = this.partialBuild || other.partialBuild;
this.stateChanged = this.stateChanged || other.stateChanged;
other.messages = [];
other.cleanups = [];
other.builtElements = [];
other.mountedElements = [];
return this;
}
cleanup() {
let clean;
do {
clean = this.cleanups.pop();
if (clean)
clean();
} while (clean);
}
toBuildOutput(stateStore) {
if (this.buildErr && this.messages.length === 0) {
throw new error_1.InternalError(`buildErr is true, but there are ` +
`no messages to describe why`);
}
if (this.partialBuild) {
if (this.contents !== null && !jsx_1.isPartialFinalDomElement(this.contents)) {
throw new error_1.InternalError(`contents is not a mounted element: ${this.contents}`);
}
return {
partialBuild: true,
buildErr: this.buildErr,
messages: this.messages,
contents: this.contents,
mountedOrig: this.mountedOrig,
};
}
if (this.contents !== null && !jsx_1.isFinalDomElement(this.contents)) {
throw new error_1.InternalError(`contents is not a valid built DOM element: ${this.contents}`);
}
const builtElements = this.builtElements;
return {
partialBuild: false,
buildErr: false,
messages: this.messages,
contents: this.contents,
mountedOrig: this.mountedOrig,
builtElements: this.builtElements,
processStateUpdates: () => processStateUpdates(builtElements, stateStore),
};
}
}
function isClassConstructorError(err) {
return err instanceof TypeError && typeof err.message === "string" &&
/Class constructor .* cannot be invoked/.test(err.message);
}
function recordDomError(cc, element, err) {
let message;
if (ld.isError(err)) {
message = `Component ${element.componentName} cannot be built ` +
`with current props` + (err.message ? ": " + err.message : "");
cc.error(message);
}
else {
message = err.content;
cc.message(err);
}
const domError = jsx_1.createElement(builtin_components_1.DomError, {}, message);
const kids = jsx_1.childrenToArray(element.props.children);
kids.unshift(domError);
replaceChildren(element, kids);
return { domError, message };
}
function makeElementStatus(observerManager) {
return async function elementStatus(handle) {
const elem = handle.mountedOrig;
if (elem == null)
return { noStatus: true };
if (!jsx_1.isElementImpl(elem))
throw new error_1.InternalError("Element is not ElementImpl");
try {
return await (observerManager ?
elem.statusWithMgr(observerManager) :
elem.status());
}
catch (e) {
if (!observers_1.isObserverNeedsData(e))
throw e;
return undefined;
}
};
}
exports.makeElementStatus = makeElementStatus;
function buildHelpers(options) {
const { buildNum, deployID, deployOpID } = options;
const elementStatus = makeElementStatus(options.observerManager);
return {
buildNum,
deployID,
deployOpID,
elementStatus,
};
}
async function computeContentsFromElement(element, options) {
const ret = new BuildResults(options.recorder, element);
const helpers = buildHelpers(options);
try {
hooks_1.startHooks({ element, options, helpers });
ret.contents =
element.componentType(element.props);
return ret;
}
catch (e) {
if (e instanceof error_1.BuildNotImplemented)
return buildDone(e);
if (!isClassConstructorError(e)) {
if (error_1.isError(e)) {
return buildDone(new Error(`SFC build failed: ${e.message}`));
}
throw e;
}
// element.componentType is a class, not a function. Fall through.
}
finally {
hooks_1.finishHooks();
}
if (!jsx_1.isComponentElement(element)) {
throw new error_1.InternalError(`trying to construct non-component`);
}
let component;
try {
component = constructComponent(element, options);
}
catch (e) {
if (e instanceof error_1.BuildNotImplemented)
return buildDone(e);
if (error_1.isError(e)) {
return buildDone(new Error(`Component construction failed: ${e.message}`));
}
throw e;
}
try {
if (!ld.isFunction(component.build)) {
throw new error_1.BuildNotImplemented(`build is not a function, build = ${util.inspect(component.build)}`);
}
ret.contents = await component.build(helpers);
if (component.cleanup) {
ret.cleanups.push(component.cleanup.bind(component));
}
return ret;
}
catch (e) {
if (e instanceof error_1.BuildNotImplemented)
return buildDone(e);
if (error_1.isError(e)) {
return buildDone(new Error(`Component build failed: ${e.message}`));
}
throw e;
}
function buildDone(err) {
ret.contents = element;
if (err)
recordDomError(ret, element, err);
return ret;
}
}
function findOverride(styles, path, options) {
const element = path[path.length - 1];
const reg = options.matchInfoReg;
for (const style of styles.reverse()) {
if (css.canMatch(reg, element) &&
!css.ruleHasMatched(reg, element, style) &&
style.match(path)) {
css.ruleMatches(reg, element, style);
return { style, override: style.sfc };
}
}
return null;
}
async function computeContents(path, options) {
const element = ld.last(path);
if (element == null) {
const ret = new BuildResults(options.recorder);
return ret;
}
if (!jsx_1.isMountedElement(element))
throw new error_1.InternalError(`computeContents for umounted element: ${element}`);
const hand = handle_1.getInternalHandle(element);
const out = await computeContentsFromElement(element, options);
// Default behavior if the component doesn't explicitly call
// handle.replaceTarget is to do the replace for them.
if (!hand.targetReplaced(options))
hand.replaceTarget(out.contents, options);
options.recorder({
type: "step",
oldElem: element,
newElem: out.contents
});
return out;
}
function ApplyStyle(props) {
const origBuild = () => {
return props.element;
};
const hand = handle_1.getInternalHandle(props.element);
const opts = {
buildNum: props.buildNum,
origBuild,
origElement: props.element,
[css.$matchInfoReg]: props.matchInfoReg,
};
const ret = props.override(props.element.props, opts);
// Default behavior if they don't explicitly call
// handle.replaceTarget is to do the replace for them.
if (ret !== props.element && !hand.targetReplaced(props)) {
hand.replaceTarget(ret, props);
}
return ret;
}
//Gross, but we need to provide ApplyStyle to jsx.ts like this to avoid a circular require
// tslint:disable-next-line:no-var-requires
require("./jsx").ApplyStyle = ApplyStyle;
function doOverride(path, key, styles, options) {
let element = ld.last(path);
if (element == null) {
throw new Error("Cannot match null element to style rules for empty path");
}
const overrideFound = findOverride(styles, path, options);
if (overrideFound != null) {
const matchInfoReg = options.matchInfoReg;
if (jsx_1.isComponentElement(element)) {
if (!jsx_1.isMountedElement(element))
throw new error_1.InternalError(`Element should be mounted`);
if (!jsx_1.isElementImpl(element))
throw new error_1.InternalError(`Element should be ElementImpl`);
if (element.component == null) {
element.component = constructComponent(element, options);
}
}
const hand = handle_1.getInternalHandle(element);
const oldEl = element;
element = jsx_1.cloneElement(element, key, element.props.children);
css.copyRuleMatches(matchInfoReg, oldEl, element);
hand.replaceTarget(element, options);
const { style, override } = overrideFound;
const props = Object.assign({}, key, { override,
element,
matchInfoReg, buildNum: options.buildNum });
const newElem = jsx_1.createElement(ApplyStyle, props);
// The ApplyStyle element should never match any CSS rule
css.neverMatch(matchInfoReg, newElem);
options.recorder({
type: "step",
oldElem: element,
newElem,
style
});
return newElem;
}
else {
return element;
}
}
function mountElement(path, parentStateNamespace, options) {
let elem = ld.last(path);
if (elem === undefined) {
throw new error_1.InternalError("Attempt to mount empty path");
}
if (elem === null)
return new BuildResults(options.recorder, elem, elem);
if (jsx_1.isMountedElement(elem)) {
throw new Error("Attempt to remount element: " + util.inspect(elem));
}
const newKey = keys_1.computeMountKey(elem, parentStateNamespace);
const hand = handle_1.getInternalHandle(elem);
const oldEl = elem;
elem = jsx_1.cloneElement(elem, newKey, elem.props.children);
css.copyRuleMatches(options.matchInfoReg, oldEl, elem);
if (!hand.targetReplaced(options))
hand.replaceTarget(elem, options);
if (!jsx_1.isElementImpl(elem)) {
throw new Error("Elements must derive from ElementImpl");
}
const finalPath = subLastPathElem(path, elem);
elem.mount(parentStateNamespace, domPathToString(finalPath), domPathToKeyPath(finalPath), options.deployID, options.deployOpID);
if (!jsx_1.isMountedElement(elem))
throw new error_1.InternalError(`just mounted element is not mounted ${elem}`);
const out = new BuildResults(options.recorder, elem, elem);
out.mountedElements.push(elem);
return out;
}
function subLastPathElem(path, elem) {
const ret = path.slice(0, -1);
ret.push(elem);
return ret;
}
async function buildElement(path, parentStateNamespace, styles, options) {
const elem = ld.last(path);
if (elem === undefined) {
throw new error_1.InternalError("buildElement called with empty path");
}
if (elem === null)
return new BuildResults(options.recorder, null, null);
if (!jsx_1.isMountedElement(elem))
throw new error_1.InternalError(`attempt to build unmounted element ${elem}`);
if (!jsx_1.isElementImpl(elem))
throw new Error(`Elements must derive from ElementImpl ${elem}`);
const override = doOverride(path, keys_1.computeMountKey(elem, parentStateNamespace), styles, options);
if (override !== elem) {
return new BuildResults(options.recorder, elem, override);
}
if (jsx_1.isPrimitiveElement(elem)) {
const res = new BuildResults(options.recorder, elem, elem);
try {
constructComponent(elem, options);
res.builtElements.push(elem);
elem.setBuilt();
}
catch (err) {
if (!error_1.isError(err))
throw err;
recordDomError(res, elem, new Error(`Component construction failed: ${err.message}`));
}
return res;
}
const out = await computeContents(path, options);
if (out.contents != null) {
if (Array.isArray(out.contents)) {
const name = elem.componentName;
throw new Error(`Component build for ${name} returned an ` +
`array. Components must return a single root element when ` +
`built.`);
}
}
out.builtElements.push(elem);
elem.setBuilt();
return out;
}
function constructComponent(elem, options) {
const { deployID, deployOpID, observerManager, stateStore } = options;
if (!jsx_1.isElementImpl(elem)) {
throw new error_1.InternalError(`Element is not an ElementImpl`);
}
jsx_1.pushComponentConstructorData({
deployInfo: {
deployID,
deployOpID,
},
getState: () => stateStore.elementState(elem.stateNamespace),
setInitialState: (init) => stateStore.setElementState(elem.stateNamespace, init),
stateUpdates: elem.stateUpdates,
observerManager
});
try {
const component = new elem.componentType(elem.props);
elem.component = component;
return component;
}
finally {
jsx_1.popComponentConstructorData();
}
}
function computeOptions(optionsIn) {
if (optionsIn != null)
optionsIn = utils_1.removeUndef(optionsIn);
const defaultBuildOptions = {
depth: -1,
shallow: false,
// Next line shouldn't be needed. VSCode tslint is ok, CLI is not.
// tslint:disable-next-line:object-literal-sort-keys
recorder: (_op) => { return; },
stateStore: state_1.createStateStore(),
observerManager: observers_1.createObserverManagerDeployment(),
maxBuildPasses: 200,
buildOnce: false,
deployID: "<none>",
deployOpID: 0,
matchInfoReg: css.createMatchInfoReg(),
hookInfo: hooks_1.createHookInfo(),
};
return Object.assign({}, defaultBuildOptions, optionsIn, { buildPass: 0 });
}
// Simultaneous builds
let buildCount = 0;
function isBuildOutputPartial(v) {
return (utils_1.isObject(v) &&
v.partialBuild === true &&
(v.contents === null || jsx_1.isPartialFinalDomElement(v.contents)));
}
exports.isBuildOutputPartial = isBuildOutputPartial;
function isBuildOutputError(v) {
return isBuildOutputPartial(v) && v.buildErr === true;
}
exports.isBuildOutputError = isBuildOutputError;
exports.noStateUpdates = () => Promise.resolve({ stateChanged: false });
function isBuildOutputSuccess(v) {
return (utils_1.isObject(v) &&
v.partialBuild === false &&
v.buildErr !== true &&
(v.contents === null || jsx_1.isFinalDomElement(v.contents)));
}
exports.isBuildOutputSuccess = isBuildOutputSuccess;
async function build(root, styles, options) {
const debugBuildBuild = debugBuild.extend("build");
debugBuildBuild(`start`);
if (buildCount !== 0) {
throw new error_1.InternalError(`Attempt to build multiple DOMs concurrently not supported`);
}
try {
buildCount++;
const optionsReq = computeOptions(options);
const results = new BuildResults(optionsReq.recorder);
const styleList = css.buildStyles(styles);
if (optionsReq.depth === 0)
throw new Error(`build depth cannot be 0: ${options}`);
await pathBuild([root], styleList, optionsReq, results);
return results.toBuildOutput(optionsReq.stateStore);
}
finally {
debugBuildBuild(`done`);
buildCount--;
}
}
exports.build = build;
async function buildOnce(root, styles, options) {
return build(root, styles, Object.assign({}, options, { buildOnce: true }));
}
exports.buildOnce = buildOnce;
function atDepth(options, depth) {
if (options.shallow)
return true;
if (options.depth === -1)
return false;
return depth >= options.depth;
}
async function nextTick() {
await new Promise((res) => {
process.nextTick(res);
});
}
async function pathBuild(path, styles, options, results) {
options.matchInfoReg = css.createMatchInfoReg();
await pathBuildOnceGuts(path, styles, options, results);
if (results.buildErr || options.buildOnce)
return;
if (results.stateChanged) {
await nextTick();
return pathBuild(path, styles, options, results);
}
}
// Unique identifier for a build pass
let nextBuildNum = 1;
async function pathBuildOnceGuts(path, styles, options, results) {
const root = path[path.length - 1];
const buildNum = nextBuildNum++;
const buildPass = ++options.buildPass;
if (buildPass > options.maxBuildPasses) {
results.error(`DOM build exceeded maximum number of build iterations ` +
`(${options.maxBuildPasses})`);
return;
}
const debug = debugBuild.extend(`pathBuildOnceGuts:${buildPass}`);
debug(`start (pass ${buildPass})`);
options.recorder({ type: "start", root, buildPass });
results.buildPassReset();
try {
const once = await realBuildOnce(path, null, styles, Object.assign({}, options, { buildNum }), null);
debug(`build finished`);
once.cleanup();
results.combine(once);
results.mountedOrig = once.mountedOrig;
results.contents = once.contents;
}
catch (error) {
options.recorder({ type: "error", error });
debug(`error: ${error} `);
throw error;
}
if (results.buildErr)
return;
debug(`validating`);
results.builtElements.map((elem) => {
if (jsx_1.isMountedPrimitiveElement(elem)) {
let msgs;
try {
msgs = elem.validate();
}
catch (err) {
if (!ld.isError(err))
err = new error_1.ThrewNonError(err);
msgs = [err];
}
for (const m of msgs)
recordDomError(results, elem, m);
}
});
if (results.buildErr)
return;
debug(`postBuild`);
options.recorder({ type: "done", root: results.contents });
const { stateChanged } = await processStateUpdates(results.builtElements, options.stateStore);
if (stateChanged)
results.stateChanged = true;
debug(`done (stateChanged=${results.stateChanged})`);
}
async function processStateUpdates(builtElements, stateStore) {
let stateChanged = false;
debugState(`State updates: start`);
const updates = builtElements.map(async (elem) => {
if (jsx_1.isElementImpl(elem)) {
const ret = await elem.postBuild(stateStore);
if (ret.stateChanged)
stateChanged = true;
}
});
await Promise.all(updates);
debugState(`State updates: complete (stateChanged=${stateChanged})`);
return { stateChanged };
}
exports.processStateUpdates = processStateUpdates;
function setOrigChildren(predecessor, origChildren) {
predecessor.buildData.origChildren = origChildren;
}
async function buildChildren(newRoot, workingPath, styles, options) {
if (!jsx_1.isElementImpl(newRoot))
throw new Error(`Elements must inherit from ElementImpl ${util.inspect(newRoot)}`);
const out = new BuildResults(options.recorder);
const children = newRoot.props.children;
let newChildren = null;
if (children == null) {
return { newChildren: null, childBldResults: out };
}
//FIXME(manishv) Make this use an explicit stack
//instead of recursion to avoid blowing the call stack
//For deep DOMs
let childList = [];
if (jsx_1.isElement(children)) {
childList = [children];
}
else if (ld.isArray(children)) {
childList = children;
}
keys_1.assignKeysAtPlacement(childList);
newChildren = [];
const mountedOrigChildren = [];
for (const child of childList) {
if (jsx_1.isElementImpl(child)) {
if (jsx_1.isMountedElement(child) && child.built()) {
newChildren.push(child); //Must be from a deferred build
mountedOrigChildren.push(child);
continue;
}
options.recorder({ type: "descend", descendFrom: newRoot, descendTo: child });
const ret = await realBuildOnce([...workingPath, child], newRoot.stateNamespace, styles, options, null, child);
options.recorder({ type: "ascend", ascendTo: newRoot, ascendFrom: child });
ret.cleanup(); // Do lower level cleanups before combining msgs
out.combine(ret);
newChildren.push(ret.contents);
mountedOrigChildren.push(ret.mountedOrig);
continue;
}
else {
newChildren.push(child);
mountedOrigChildren.push(child);
continue;
}
}
setOrigChildren(newRoot, mountedOrigChildren);
newChildren = newChildren.filter(utils_1.notNull);
return { newChildren, childBldResults: out };
}
function setSuccessor(predecessor, succ) {
if (predecessor === null)
return;
if (!jsx_1.isElementImpl(predecessor))
throw new error_1.InternalError(`Element is not ElementImpl: ${predecessor}`);
predecessor.buildData.successor = succ;
}
let realBuildId = 0;
async function realBuildOnce(pathIn, parentStateNamespace, styles, options, predecessor, workingElem) {
const buildId = ++realBuildId;
const debug = debugBuild.extend(`realBuildOnce:${buildId}`);
debug(`start (id: ${buildId})`);
try {
let deferring = false;
const atDepthFlag = atDepth(options, pathIn.length);
if (options.depth === 0)
throw new error_1.InternalError("build depth 0 not supported");
if (parentStateNamespace == null) {
parentStateNamespace = state_1.stateNamespaceForPath(pathIn.slice(0, -1));
}
const oldElem = ld.last(pathIn);
if (oldElem === undefined)
throw new error_1.InternalError("realBuild called with empty path");
if (oldElem === null)
return new BuildResults(options.recorder, null);
if (workingElem === undefined) {
workingElem = oldElem;
}
const out = new BuildResults(options.recorder);
let mountedElem = oldElem;
if (!jsx_1.isMountedElement(oldElem)) {
const mountOut = mountElement(pathIn, parentStateNamespace, options);
if (mountOut.buildErr)
return mountOut;
out.contents = mountedElem = mountOut.contents;
out.combine(mountOut);
}
if (!jsx_1.isMountedElement(mountedElem))
throw new error_1.InternalError("element not mounted after mount");
out.mountedOrig = mountedElem;
setSuccessor(predecessor, mountedElem);
if (mountedElem === null) {
options.recorder({ type: "elementBuilt", oldElem: workingElem, newElem: out.contents });
return out;
}
//Element is mounted
const mountedPath = subLastPathElem(pathIn, mountedElem);
let newRoot;
let newPath = mountedPath;
if (!jsx_1.isElementImpl(mountedElem)) {
throw new Error("Elements must inherit from ElementImpl:" + util.inspect(newRoot));
}
if (!jsx_1.isDeferredElementImpl(mountedElem) || mountedElem.shouldBuild()) {
const computeOut = await buildElement(mountedPath, parentStateNamespace, styles, options);
out.combine(computeOut);
out.contents = newRoot = computeOut.contents;
if (computeOut.buildErr)
return out;
if (newRoot !== null) {
if (newRoot !== mountedElem) {
newPath = subLastPathElem(mountedPath, newRoot);
const ret = (await realBuildOnce(newPath, mountedElem.stateNamespace, styles, options, mountedElem, workingElem)).combine(out);
ret.mountedOrig = out.mountedOrig;
return ret;
}
else {
options.recorder({ type: "elementBuilt", oldElem: workingElem, newElem: newRoot });
return out;
}
}
}
else {
options.recorder({ type: "defer", elem: mountedElem });
deferring = true;
mountedElem.setDeferred();
newRoot = mountedElem;
out.contents = newRoot;
}
if (newRoot === undefined) {
out.error(`Root element undefined after build`);
out.contents = null;
return out;
}
if (newRoot === null) {
setSuccessor(mountedElem, newRoot);
options.recorder({ type: "elementBuilt", oldElem: workingElem, newElem: null });
return out;
}
//Do not process children of DomError nodes in case they result in more DomError children
if (!builtin_components_1.isDomErrorElement(newRoot)) {
if (!atDepthFlag) {
const { newChildren, childBldResults } = await buildChildren(newRoot, mountedPath, styles, options);
out.combine(childBldResults);
replaceChildren(newRoot, newChildren);
}
}
else {
if (!out.buildErr) {
// This could happen if a user instantiates a DomError element.
// Treat that as a build error too.
out.error("User-created DomError component present in the DOM tree");
}
}
if (atDepthFlag)
out.partialBuild = true;
//We are here either because mountedElem was deferred, or because mountedElem === newRoot
if (!deferring || atDepthFlag) {
options.recorder({ type: "elementBuilt", oldElem: workingElem, newElem: newRoot });
return out;
}
//FIXME(manishv)? Should this check be if there were no element children instead of just no children?
//No built event in this case since we've exited early
if (atDepthFlag && newRoot.props.children === undefined)
return out;
//We must have deferred to get here
options.recorder({ type: "buildDeferred", elem: mountedElem });
const deferredRet = (await realBuildOnce(newPath, mountedElem.stateNamespace, styles, options, predecessor, workingElem)).combine(out);
deferredRet.mountedOrig = out.mountedOrig;
return deferredRet;
}
catch (err) {
debug(`error (id: ${buildId}): ${err}`);
throw err;
}
finally {
debug(`done (id: ${buildId})`);
}
}
function replaceChildren(elem, children) {
children = jsx_1.simplifyChildren(children);
if (Object.isFrozen(elem.props)) {
const newProps = Object.assign({}, elem.props);
if (children == null) {
delete newProps.children;
}
else {
newProps.children = children;
}
elem.props = newProps;
Object.freeze(elem.props);
}
else {
if (children == null) {
delete elem.props.children;
}
else {
elem.props.children = children;
}
}
}
function domPathToString(domPath) {
return "/" + domPath.map((el) => el.componentType.name).join("/");
}
exports.domPathToString = domPathToString;
function domPathToKeyPath(domPath) {
return domPath.map((el) => {
const key = el.props.key;
if (typeof key !== "string") {
throw new error_1.InternalError(`element has no key`);
}
return key;
});
}
//# sourceMappingURL=dom.js.map