UNPKG

stencil-quantum

Version:

Experience the quantum realm of stencil.

527 lines (515 loc) 17.4 kB
'use strict'; const index = require('./index-059fa27d.js'); //#region Logging const _log = (...args) => _log.debug && console.log(...args); _log.debug = false; const log = _log; const nop = () => { }; function hookComponent(prototype, key, cb) { const _original = prototype[key] || nop; prototype[key] = async function (...args) { log(key, this); const cb2 = await cb(this); let result = await _original.apply(this, args); if (cb2 instanceof Function) await cb2(this); return result; }; return () => { prototype[key] = _original; }; } //#endregion const $providers = Symbol.for("stencil-quantum-providers"); class Provider { constructor(key, value, debug = false) { this.key = key; this.value = value; this.debug = debug; this.listeners = []; this.hooks = new Map(); this.mutable = false; this.paused = false; this.retrieve = () => { return this.value; }; this.provide = (value) => { if (this.debug) log(`(${String(this.key)}) `, "PROVIDING", value, "to", this.listeners); let oldvalue = this.value; this.value = value; if (!this.paused) { this.listeners .filter(listener => !listener.paused) .forEach(listener => listener.action(value, oldvalue)); } return this; }; this.update = (fn) => { return this.provide(fn(this.retrieve())); }; this.listen = (cb, updateImmediately = true, el) => { if (this.debug) log(`(${String(this.key)}) `, "LISTEN", updateImmediately, this, cb); const listener = { action: cb, unlisten: () => this.unlisten(cb), paused: false }; this.listeners = [...this.listeners, listener]; if (updateImmediately) cb(this.value); return listener; }; this.unlisten = (cb) => { this.listeners = this.listeners.filter(listener => listener.action !== cb); }; this.attach = (el, noHook) => { if (this.debug) log(`(${String(this.key)}) `, "Add Provider", el, this); const providers = Provider.getAttached(el); if (!providers.includes(this)) providers.push(this); if (this.debug) log(`(${String(this.key)}) `, "Total Providers", el, providers); return noHook ? this : this.hook(el); }; this.isHooked = (el) => { return this.hooks.has(el); }; this.getHook = (el) => this.hooks.get(el); this.hook = (el) => { if (this.debug) log(`(${String(this.key)}) `, "Hook Provider", el, this); this.hooks.set(el, this.listen(() => index.forceUpdate(el))); return this; }; this.pauseHook = (el, paused = true) => { if (this.debug) log(`(${String(this.key)}) `, "Pausing Hook", paused); if (this.isHooked(el)) this.getHook(el).paused = paused; }; this.unhook = (el) => { var _a; if (this.debug) log(`(${String(this.key)}) `, "Unhook Provider", el, this); if (this.isHooked(el)) { (_a = this.hooks.get(el)) === null || _a === void 0 ? void 0 : _a.unlisten(); this.hooks.delete(el); } return this; }; this.destroy = () => { this.listeners = []; this.hooks = new Map(); }; } pause(paused = true) { this.paused = paused; } /** * Creates a predicate, that filters providers matching a key and having no namespace or being * @param key * @param namespace */ static makeFilter(key, namespace) { return (p) => p.key === key || (namespace && typeof key === "string" && p.key === namespace + "__" + key); } static find(el, key, namespace, debug) { var _a, _b, _c, _d; if (debug) log(`(${String(key)}) `, "Searching Provider", key, namespace, el); const providers = Provider.getAttached(el).filter(Provider.makeFilter(key, namespace)); if (providers.length > 1) { throw new QuantumError(`Found multiple "${String(key)}" providers on the same object!`); } else if (providers.length === 1) { return providers[0]; } // No provider found on this el, check parent let parent = ((_c = (_a = el.parentElement) !== null && _a !== void 0 ? _a : (_b = el.shadowRoot) === null || _b === void 0 ? void 0 : _b.host) !== null && _c !== void 0 ? _c : (_d = el.parentNode) === null || _d === void 0 ? void 0 : _d.host); // parentElement or shadowRoot.host if (!parent) { throw new QuantumError(`No provider in hierarchy found that matches "${String(key)}"!`); } return Provider.find(parent, key, namespace, debug).attach(el, true); // Attach reference to this el to make lookup for children shorter } static create(el, key, value, namespace, debug) { if (typeof key === "string") key = (namespace ? namespace + "__" : "") + key; if (debug) log(`(${String(key)}) `, "Create Provider", el, key, value); return new Provider(key, value, debug).attach(el); } static getAttached(el) { if (!(el[$providers] instanceof Array)) { el[$providers] = []; } return el[$providers]; } } const $error = Symbol.for("stencil-quantum-error"); class QuantumError extends Error { constructor(err, target) { super(err instanceof Error ? err.message : err); this.target = target; if (err instanceof Error) Object.assign(this, err); } } function throwQuantum(el, error) { const err = new QuantumError(error, el); console.error(err); try { const provider = Provider.find(el, $error); provider.provide(err); } catch (e) { throw err; } } function Throw(opts) { return function (prototype, propertyName) { let provider; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); try { provider = Provider.find(el, $error, opts === null || opts === void 0 ? void 0 : opts.namespace); provider.attach(el); provider.hook(el); } catch (err) { throwQuantum(el, err); } }); if (delete prototype[propertyName]) { Object.defineProperty(prototype, propertyName, { get: () => provider === null || provider === void 0 ? void 0 : provider.retrieve(), set: (v) => provider === null || provider === void 0 ? void 0 : provider.provide(v), enumerable: true, configurable: true }); } }; } function ContextError(opts) { return function (prototype, propertyName) { let provider; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); provider = Provider.create(el, $error, prototype[propertyName], opts === null || opts === void 0 ? void 0 : opts.namespace); try { provider.attach(el); provider.hook(el); } catch (err) { throwQuantum(el, err); } }); if (delete prototype[propertyName]) { Object.defineProperty(prototype, propertyName, { get: () => provider === null || provider === void 0 ? void 0 : provider.retrieve(), set: (v) => provider === null || provider === void 0 ? void 0 : provider.provide(v), enumerable: true, configurable: true }); } }; } function Catch(opts) { return function (prototype, propertyName, propertyDesciptor) { hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); const provider = Provider.create(el, $error, undefined, opts === null || opts === void 0 ? void 0 : opts.namespace); const responseProvider = (opts === null || opts === void 0 ? void 0 : opts.provide) ? Provider.create(el, opts.provide, undefined, opts.namespace) : undefined; try { provider.attach(el); responseProvider === null || responseProvider === void 0 ? void 0 : responseProvider.attach(el); provider.listen(async (v) => { var _a; try { const response = await ((_a = propertyDesciptor.value) === null || _a === void 0 ? void 0 : _a.apply(obj, [v])); responseProvider === null || responseProvider === void 0 ? void 0 : responseProvider.provide(response); } catch (err) { throwQuantum(el, err); } }); } catch (err) { throwQuantum(el, err); } }); return propertyDesciptor; }; } function Context(config, key) { return function (prototype, propertyName) { const opts = config === null || config === void 0 ? void 0 : config.get(key); let defaultValue = opts === null || opts === void 0 ? void 0 : opts.default; delete prototype[propertyName]; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); let provider; try { provider = Provider.find(el, key, opts === null || opts === void 0 ? void 0 : opts.namespace, opts === null || opts === void 0 ? void 0 : opts.debug); provider.hook(el); } catch (err) { } hookComponent(prototype, "disconnectedCallback", o => { if (o === obj) { const el = index.getElement(obj); provider === null || provider === void 0 ? void 0 : provider.pauseHook(el, true); } }); hookComponent(prototype, "connectedCallback", o => { if (o === obj) { const el = index.getElement(obj); provider === null || provider === void 0 ? void 0 : provider.pauseHook(el, false); } }); Object.defineProperty(obj, propertyName, { get: () => { var _a; return (_a = provider === null || provider === void 0 ? void 0 : provider.retrieve()) !== null && _a !== void 0 ? _a : defaultValue; }, set: v => { if ((opts === null || opts === void 0 ? void 0 : opts.mutable) && (provider === null || provider === void 0 ? void 0 : provider.mutable)) { provider.provide(v); } else { defaultValue = v; } }, enumerable: true, configurable: true }); return () => { if (provider) return; try { provider = Provider.find(el, key, opts === null || opts === void 0 ? void 0 : opts.namespace, opts === null || opts === void 0 ? void 0 : opts.debug); provider.hook(el); } catch (err) { throwQuantum(el, err); } }; }); }; } function Observe(config, key) { return function (prototype, propertyName, propertyDesciptor) { const opts = config === null || config === void 0 ? void 0 : config.get(key); const method = propertyDesciptor.value; hookComponent(prototype, "componentWillLoad", obj => { let provider; let listener; const el = index.getElement(obj); const hookProvider = () => { provider = Provider.find(el, key, opts === null || opts === void 0 ? void 0 : opts.namespace); listener = provider.listen(v => { try { method.apply(obj, [v]); } catch (err) { throwQuantum(el, err); } }); }; hookComponent(prototype, "disconnectedCallback", o => { if (o === obj && listener) listener.paused = true; }); hookComponent(prototype, "connectedCallback", o => { if (o === obj && listener) listener.paused = false; }); try { hookProvider(); } catch (err) { } return () => { if (provider) return; listener === null || listener === void 0 ? void 0 : listener.unlisten(); try { hookProvider(); } catch (err) { throwQuantum(el, err); } }; }); return propertyDesciptor; }; } function Provide(config, key) { return function (prototype, propertyName) { const opts = config === null || config === void 0 ? void 0 : config.get(key); let defaultValue = prototype[propertyName] || (opts === null || opts === void 0 ? void 0 : opts.default); delete prototype[propertyName]; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); const provider = Provider.create(el, key, defaultValue !== null && defaultValue !== void 0 ? defaultValue : obj[propertyName], opts === null || opts === void 0 ? void 0 : opts.namespace, opts === null || opts === void 0 ? void 0 : opts.debug); provider.mutable = !!(opts === null || opts === void 0 ? void 0 : opts.mutable); try { provider.attach(el); provider.hook(el); } catch (err) { throwQuantum(el, err); } Object.defineProperty(obj, propertyName, { get: () => provider.retrieve(), set: (v) => provider.provide(v), enumerable: true, configurable: true }); hookComponent(prototype, "disconnectedCallback", o => { if (o === obj) provider.pause(); }); hookComponent(prototype, "connectedCallback", o => { if (o === obj) provider.pause(false); }); }); }; } function React(from, fromKey, to, toKey) { return function (prototype, propertyName, propertyDesciptor) { const fromOpts = from.get(fromKey); const toOpts = to.get(toKey); const method = propertyDesciptor.value; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); let listener; let inputProvider; const resultProvider = Provider.create(el, toOpts.name, undefined, toOpts.namespace); resultProvider.mutable = !!toOpts.mutable; const hookProvider = () => { inputProvider = Provider.find(el, fromOpts.name, fromOpts.namespace); listener = inputProvider.listen(async (v) => { try { const result = await method.apply(obj, [v]); if (resultProvider) resultProvider.provide(result); } catch (err) { throwQuantum(el, err); } }); }; hookComponent(prototype, "disconnectedCallback", o => { if (o === obj && listener) listener.paused = true; }); hookComponent(prototype, "connectedCallback", o => { if (o === obj && listener) listener.paused = false; }); try { hookProvider(); return nop; } catch (err) { return () => { listener === null || listener === void 0 ? void 0 : listener.unlisten(); try { hookProvider(); } catch (err) { throwQuantum(el, err); } }; } }); return propertyDesciptor; }; } function Implement(config, key) { return function (prototype, propertyName) { const opts = config === null || config === void 0 ? void 0 : config.get(key); let defaultValue = prototype[propertyName] || (opts === null || opts === void 0 ? void 0 : opts.default); delete prototype[propertyName]; hookComponent(prototype, "componentWillLoad", obj => { const el = index.getElement(obj); const provider = Provider.create(el, key, defaultValue !== null && defaultValue !== void 0 ? defaultValue : obj[propertyName], opts === null || opts === void 0 ? void 0 : opts.namespace, opts === null || opts === void 0 ? void 0 : opts.debug); try { provider.attach(el); provider.hook(el); } catch (err) { throwQuantum(el, err); } Object.defineProperty(obj, propertyName, { get: () => provider.retrieve(), set: (v) => provider.provide((...args) => v.apply(obj, args)), enumerable: true, configurable: true }); hookComponent(prototype, "disconnectedCallback", o => { if (o === obj) provider.pause(); }); hookComponent(prototype, "connectedCallback", o => { if (o === obj) provider.pause(false); }); }); }; } // TODO: factory function that adds all keys to the config; class Entanglement { constructor(schema, opts) { this.keys = Object.entries(schema) .map(([key, value]) => { var _a; return ({ name: key, namespace: (_a = opts === null || opts === void 0 ? void 0 : opts.namespace) !== null && _a !== void 0 ? _a : `$${Entanglement.uid++}`, default: value.default, mutable: value.mutable, debug: value.debug, config: this }); }) .reduce((a, b) => (Object.assign(Object.assign({}, a), { [b.name]: b })), {}); } get(key) { return this.keys[key]; } Provide(key) { return Provide(this, key); } Context(key) { return Context(this, key); } Observe(key) { return Observe(this, key); } React(key, target, targetKey) { return React(this, key, target, targetKey); } Implement(key) { return Implement(this, key); } Action(key) { return Context(this, key); } } Entanglement.uid = 0; exports.Catch = Catch; exports.Context = Context; exports.ContextError = ContextError; exports.Entanglement = Entanglement; exports.Implement = Implement; exports.Observe = Observe; exports.Provide = Provide; exports.Provider = Provider; exports.QuantumError = QuantumError; exports.React = React; exports.Throw = Throw; exports.hookComponent = hookComponent; exports.log = log; exports.nop = nop; exports.throwQuantum = throwQuantum;