UNPKG

be-enhanced

Version:

be-enhanced provides a base class that enables casting spells, or enhancing server-rendered DOM elements based on cross-cutting custom attributes

130 lines (129 loc) 4.42 kB
class InProgressAttachments extends EventTarget { inProgress = new WeakMap(); } // interface AttachedEvent{ // element: Element, // } export class EnhancerRegistry extends EventTarget { #enhancers = new Map(); get enhancers() { return this.#enhancers; } define(emc) { const { enhPropKey } = emc; if (this.#enhancers.has(enhPropKey)) throw 'Only One!'; this.#enhancers.set(enhPropKey, emc); this.dispatchEvent(new Event('register')); } whenDefined(key) { return new Promise(resolve => { if (this.#enhancers.has(key)) { resolve(this.#enhancers.get(key)); return; } const ac = new AbortController(); this.addEventListener('register', e => { if (!this.#enhancers.has(key)) { resolve(this.#enhancers.get(key)); ac.abort(); } }, { signal: ac.signal }); }); } } export const Enhancers = new EnhancerRegistry(); const inProgressAttachments = new InProgressAttachments(); export class BeEnhanced extends EventTarget { self; constructor(self) { super(); this.self = self; } #proxy; get by() { if (this.#proxy === undefined) { const self = this; this.#proxy = new Proxy(self, { get(obj, prop) { if (obj[prop] === undefined) { self.with(prop); obj[prop] = {}; } return obj[prop]; } }); } return this.#proxy; } async with(key) { const emc = await Enhancers.whenDefined(key); return await this.whenResolved(emc, true); } async whenDetached(localName) { let def = customElements.get(localName); if (def === undefined) def = await customElements.whenDefined(localName); const { lispToCamel } = await import('trans-render/lib/lispToCamel.js'); const enhancement = lispToCamel(localName); const { self } = this; const previouslySet = self['beEnhanced'][enhancement]; if (previouslySet instanceof def) { await previouslySet.detach(this, { enhancement, enh: localName, localName }); delete self['beEnhanced'][enhancement]; self.removeAttribute(localName); self.removeAttribute('enh-by-' + localName); self.removeAttribute('data-enh-by-' + localName); } return previouslySet; } async whenAttached(emc) { return await this.whenResolved(emc, true); } async whenResolved(emc, skipResolvedWait = false) { const { importEnh, enhPropKey, base } = emc; if (importEnh === undefined || enhPropKey === undefined) throw 'NI'; const { self } = this; if (base !== undefined) { //TODO: support alt attrs const deferBase = `defer-${base}`; if (self.hasAttribute(deferBase)) { const { wfac } = await import('trans-render/lib/wfac.js'); await wfac(self, deferBase, (mr, el, attrs) => { return !el.hasAttribute(deferBase); }); } } const beEnhanced = self.beEnhanced; const enhancementConstructor = await importEnh(); const initialPropValues = beEnhanced[enhPropKey] || {}; if (initialPropValues instanceof enhancementConstructor) return initialPropValues; const enhancementInstance = new enhancementConstructor(); beEnhanced[enhPropKey] = enhancementInstance; await enhancementInstance.attach(self, { initialPropValues, mountCnfg: emc }); if (!skipResolvedWait) { await enhancementInstance.whenResolved(); } return enhancementInstance; } } const wm = new WeakMap(); Object.defineProperty(Element.prototype, 'beEnhanced', { get() { if (!wm.has(this)) { wm.set(this, new BeEnhanced(this)); } return wm.get(this); }, enumerable: true, configurable: true, }); if (customElements.get('be-enhanced') === undefined) { customElements.define('be-enhanced', class extends HTMLElement { }); }