UNPKG

@adpt/core

Version:
223 lines 7.99 kB
"use strict"; /* * 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