@adpt/core
Version:
AdaptJS core library
223 lines • 7.99 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 utils_1 = require("@adpt/utils");
const error_1 = require("./error");
const jsx_1 = require("./jsx");
const reanimate_1 = require("./reanimate");
function isHandle(val) {
return isHandleImpl(val);
}
exports.isHandle = isHandle;
function isHandleInternal(val) {
return isHandleImpl(val);
}
exports.isHandleInternal = isHandleInternal;
function getInternalHandle(el) {
const hand = el.props.handle;
if (!isHandleInternal(hand))
throw new error_1.InternalError(`handle is not a HandleImpl`);
return hand;
}
exports.getInternalHandle = getInternalHandle;
let nextId = 0;
const id = Symbol.for("AdaptHandleId");
const origElement = Symbol.for("AdaptHandleOrigElement");
exports.handleSignature = utils_1.sha256hex("This is an Adapt.Handle");
function isHandleObj(val) {
return val.__adaptIsHandle === exports.handleSignature;
}
exports.isHandleObj = isHandleObj;
/**
* Find the Element corresponding to the first handle in the chain for which
* `pred` returns true.
* @remarks
* In the case that the predicate returns true for `hand`, the Element
* associated with `hand` will be returned.
* @returns
* The Element associated to the first handle in the chain for which the
* predicate returns true.
* If no handles in the chain satisfy the predicate, the function returns
* `null` if the chain built to `null`, otherwise `undefined`.
* @internal
*/
function findFirst(start, pred) {
let hand = start;
while (true) {
if (!isHandleImpl(hand)) {
throw new error_1.InternalError(`Handle is not a HandleImpl`);
}
const orig = hand.origTarget;
if (orig === undefined) {
throw new error_1.InternalError(`Handle chain has undefined origTarget`);
}
if (pred(hand))
return orig; // Success. hand satisfies the predicate
if (hand.childElement == null) {
// We've reached the end of the chain without finding a Handle that
// matches the predicate.
return hand.childElement;
}
hand = hand.childElement.props.handle;
if (hand == null) {
throw new error_1.InternalError(`no Handle present on Element in child chain`);
}
}
}
/**
* Finds the Element for the last associated Handle in the chain.
*
* @returns The Element associated with the last Handle in the chain.
* If the chain ends with `null`, returns `null`.
* @internal
*/
function findLast(start) {
let hand = start;
while (true) {
if (!isHandleImpl(hand)) {
throw new error_1.InternalError(`Handle is not a HandleImpl`);
}
const orig = hand.origTarget;
if (orig === undefined) {
throw new error_1.InternalError(`Handle chain has undefined origTarget`);
}
if (hand.childElement === undefined)
return orig;
if (hand.childElement === null)
return null;
hand = hand.childElement.props.handle;
if (hand == null) {
throw new error_1.InternalError(`no Handle present on Element in child chain`);
}
}
}
class HandleImpl {
constructor(opts) {
this.associate = (el) => {
const orig = this[origElement];
if (orig !== undefined) {
const path = jsx_1.isMountedElement(orig) ? orig.path : "<not mounted>";
throw new Error(`Cannot associate a Handle with more than one AdaptElement. ` +
`Original element type ${orig && orig.componentName}, ` +
`path: ${path}, ` +
`second association element type ${el && el.componentName}`);
}
this[origElement] = el;
};
this.replaceTarget = (el, buildId) => {
const orig = this[origElement];
if (orig == null) {
throw new Error(`A Handle must first be associated with an ` +
`Element before replaceTarget can be called`);
}
if (this.buildNum === undefined || buildId.buildNum > this.buildNum) {
this.buildNum = buildId.buildNum;
// Replacing with origElement doesn't modify anything (and importantly,
// doesn't create a loop for target).
if (el === this[origElement])
return;
this.childElement = el;
return; // Success
}
if (this.buildNum === buildId.buildNum) {
throw new Error(`Cannot call replaceTarget on a Handle more than once`);
}
throw new Error(`Cannot call replaceTarget on a Handle with an ` +
`older build iteration. ${this.origDebug()} ` +
`(this.buildNum=${this.buildNum} ` +
`buildId.buildNum=${buildId.buildNum})`);
};
this[id] = nextId++;
if (opts.name)
this.name = opts.name;
if (opts.target !== undefined) {
this.unresolvedTarget = opts.target;
if (opts.target === null)
this.associate(null);
}
}
get associated() {
return this[origElement] !== undefined;
}
targetReplaced(buildId) {
return this.buildNum === buildId.buildNum &&
this.childElement !== undefined;
}
get id() {
return this[id];
}
get origTarget() {
const orig = this[origElement];
return orig;
}
get mountedOrig() {
return this.nextMounted();
}
nextMounted(pred = () => true) {
const elem = findFirst(this, (hand) => {
return jsx_1.isMountedElement(hand.origTarget) && pred(hand.origTarget);
});
if (elem == null)
return elem;
if (!jsx_1.isMountedElement(elem))
return undefined;
return elem;
}
get target() {
if (this.origTarget === undefined)
return undefined;
return findLast(this);
}
toString() {
return `Handle(${this.id})`;
}
toJSON() {
const el = this.target;
const target = jsx_1.isMountedElement(el) ? el.keyPath : null;
return {
__adaptIsHandle: exports.handleSignature,
name: this.name,
target,
urn: exports.handleUrn
};
}
origDebug() {
const orig = this[origElement];
if (orig === undefined)
return "Original element: <unassociated>";
if (orig === null)
return "Original element: <null>";
const path = jsx_1.isMountedElement(orig) ? orig.path : "<not mounted>";
const name = orig.componentName || "<anonymous>";
return `Original element type ${name}, path: ${path}`;
}
}
utils_1.tagConstructor(HandleImpl, "adapt");
function isHandleImpl(val) {
return utils_1.isInstance(val, HandleImpl, "adapt");
}
/**
* User-facing API for creating a Handle
* @param name - Name to associate with the handle for debugging/display purposes
*/
function handle(name) {
return new HandleImpl({ name });
}
exports.handle = handle;
reanimate_1.registerObject(HandleImpl, "HandleImpl", module);
exports.handleUrn = reanimate_1.findMummyUrn(HandleImpl);
//# sourceMappingURL=handle.js.map