UNPKG

@adpt/core

Version:
214 lines 8.96 kB
"use strict"; /* * Copyright 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 lodash_1 = tslib_1.__importDefault(require("lodash")); const dom_serialize_1 = require("../dom_serialize"); const jsx_1 = require("../jsx"); const use_async_1 = require("./use_async"); // Function implementation function useMethod(hand, initialOrMethod, method, ...args) { const mName = method || initialOrMethod; const initial = method ? initialOrMethod : undefined; return use_async_1.useAsync(async () => { if (hand == null) return initial; return callInstanceMethod(hand, initial, mName, ...args); }, initial); } exports.useMethod = useMethod; function hasInstanceMethod(name, skip) { return (el) => { if (el === skip) return false; if (!jsx_1.isMountedElement(el)) throw new Error(`Element is not an ElementImpl`); const inst = el.instance; return lodash_1.default.isFunction(inst[name]); }; } exports.hasInstanceMethod = hasInstanceMethod; function notReplacedByStyle() { return (el) => { if (!jsx_1.isMountedElement(el)) throw new Error(`Element is not an ElementImpl`); const succ = el.buildData.successor; if (succ == null) return true; if (!jsx_1.isApplyStyle(succ)) return true; return false; }; } exports.notReplacedByStyle = notReplacedByStyle; function _callInstanceMethod(hand, def, methodName, args, options = {}) { const method = getInstanceValue(hand, () => def, methodName, options); if (!lodash_1.default.isFunction(method)) { const mountedOrig = hand.associated ? hand.mountedOrig : null; throw new Error(`${methodName} exists but is not a function on handle instance:\n` + ((mountedOrig != null) ? dom_serialize_1.serializeDom(mountedOrig) : `mountedOrig is ${mountedOrig}`)); } return method(...args); } /** * Search for the first built Element in the handle chain of `hand` and * immediately execute the instance method `methodName` on that Element's * instance. * * @remarks * If an Element is found that satisfies the search, but method `methodName` * does not exist on the Element's instance, an error is thrown. * * The exact check that is currently used when searching the handle chain is * for mounted Elements that satisfy the predicate {@link notReplacedByStyle}. * In practice, this only selects Elements that are both mounted and built. * * @returns The return value of the called instance method if `hand` is * associated and there is an Element in the handle chain that has not been * replaced by a style sheet rule. Otherwise, returns the default value `def`. * @beta */ function callInstanceMethod(hand, def, methodName, ...args) { return _callInstanceMethod(hand, def, methodName, args); } exports.callInstanceMethod = callInstanceMethod; /** * Search for the first built Element instance in the Handle chain of `hand` that * implements method `methodName` and immediately execute it. * * @remarks * The exact check that is currently used when searching the handle chain is * mounted Elements that have an instance method `methodName`. Because only * built Elements have an Element instance, this only selects Elements that * are mounted and built and will not select Elements that have been replaced * by a style sheet rule. * * @returns The return value of the called instance method if `hand` is * associated and there is an Element in the handle chain that has method * `methodName`. Otherwise, returns the default value `def`. * @beta */ function callFirstInstanceWithMethod(hand, def, methodName, ...args) { // Walk until we find an instance that has the requested method. const options = { pred: hasInstanceMethod(methodName) }; return _callInstanceMethod(hand, def, methodName, args, options); } exports.callFirstInstanceWithMethod = callFirstInstanceWithMethod; /** * Starting with the successor of `hand`, search for a built Element instance in * the handle chain that implements method `methodName` and immediately * execute it. * * @remarks * If `hand` is not associated with an Element, an error is thrown. * * The exact check that is currently used when searching the handle chain is * mounted Elements that have an instance method `methodName`. Because only * built Elements have an Element instance, this only selects Elements that * are mounted and built and will not select Elements that have been replaced * by a style sheet rule. * * * @returns The return value of the called instance method if `hand` is * associated and there is an Element in the handle chain (other than `hand`) * that has method `methodName`. Otherwise, returns the default value `def`. * @beta */ function callNextInstanceWithMethod(hand, def, methodName, ...args) { if (!hand.associated) { // tslint:disable-next-line: max-line-length throw new Error(`Cannot find next instance when calling ${methodName}: handle is not associated with any element`); } // Skip hand.mountedOrig and start with its successor. Walk until we // find an instance that has the requested method. const options = { pred: hasInstanceMethod(methodName, hand.mountedOrig) }; return _callInstanceMethod(hand, def, methodName, args, options); } exports.callNextInstanceWithMethod = callNextInstanceWithMethod; /** * Starting with the successor of `hand`, search for a built Element instance in * the handle chain that implements method `methodName` and immediately * execute it. * * @remarks * If `hand` is not associated with an Element, an error is thrown. * * The exact check that is currently used when searching the handle chain is * mounted Elements that have an instance method `methodName`. Because only * built Elements have an Element instance, this only selects Elements that * are mounted and built and will not select Elements that have been replaced * by a style sheet rule. * * @returns The return value of the called instance method if `hand` is * associated and there is an Element in the handle chain (other than `hand`) * that has method `methodName`. Otherwise, returns the default value `def`. * @deprecated * Renamed to {@link callNextInstanceWithMethod}. */ exports.callNextInstanceMethod = callNextInstanceWithMethod; exports.defaultGetInstanceValueOptions = { pred: notReplacedByStyle(), throwOnNoElem: false }; /** * Get the value of a field on an element instance * * @beta */ function getInstanceValue(hand, def, field, optionsIn) { const options = Object.assign({}, exports.defaultGetInstanceValueOptions, optionsIn); const pred = options.pred; if (!hand.associated) { if (!options.throwOnNoElem) return def; throw new Error(`Cannot get instance field ${field}: Handle is not associated with element`); } const elem = hand.nextMounted(pred); if (!elem) { if (!options.throwOnNoElem) return def; throw new Error(`Cannot get instance field ${field}: Handle does not point to mounted element`); } if (!elem.instance) { throw new Error(`Internal Error: Element is mounted but instance is ${elem.instance}`); } if (!(field in elem.instance)) { throw new Error(`${field} does not exist on handle instance:\n` + dom_serialize_1.serializeDom(elem)); } const val = elem.instance[field]; if (lodash_1.default.isFunction(val)) { return val.bind(elem.instance); } return val; } exports.getInstanceValue = getInstanceValue; /** * Get the value of field from the instance referenced by handled instance. * * @remarks * On first invocation, or if the handle is not associated with an element, or the field is not found, * the value of `initial` will be returned. After the element referenced by handle has been instantiated, * this hook will fetch the actual value of `field`, cause a rebuild, and then return that value * on the next call of the hook. * * @beta */ function useInstanceValue(hand, initial, field) { return use_async_1.useAsync(async () => getInstanceValue(hand, initial, field), initial); } exports.useInstanceValue = useInstanceValue; //# sourceMappingURL=use_method.js.map