@webqit/oohtml
Version:
A suite of new DOM features that brings language support for modern UI development paradigms: a component-based architecture, data binding, and reactivity.
152 lines (141 loc) • 6.01 kB
JavaScript
/**
* @imports
*/
import DOMContext from '../context-api/DOMContext.js';
import { getDefs } from './index.js';
import { env } from '../util.js';
export default class HTMLImportsContext extends DOMContext {
/**
* @kind
*/
static kind = 'html-imports';
/**
* @createRequest
*/
static createRequest(detail = null) {
const request = super.createRequest();
if (detail?.startsWith('/')) {
request.detail = detail;
request.targetContext = Infinity;
} else if (detail?.startsWith('@')) {
const [targetContext, ..._detail] = detail.slice(1).split(/(?<=\w)(?=\/|#)/).map(s => s.trim());
request.targetContext = targetContext;
request.detail = _detail.join('');
} else { request.detail = detail; }
return request;
}
/**
* @localModules
*/
get localModules() { return getDefs(this.host); }
get inheritedModules() { return this.#inheritedModules; }
#inheritedModules = {};
/**
* @handle()
*/
handle(event) {
const { window: { webqit: { Observer } } } = env;
// Any existing event.meta.controller? Abort!
event.meta.controller?.abort();
// Parse and translate detail
let path = (event.detail || '').split(/\/|(?<=\w)(?=#)/g).map(x => x.trim()).filter(x => x);
if (!path.length) return event.respondWith();
path = path.join(`/${this.configs.HTML_IMPORTS.api.defs}/`)?.split('/').map(x => x === '*' ? Infinity : x) || [];
// We'll now fulfill request
const options = { live: event.live, sig_nal: event.signal, descripted: true };
event.meta.controller = Observer.reduce(this.#modules, path, Observer.get, (m) => {
if (Array.isArray(m)) {
if (!m.length) {
event.respondWith();
return;
}
// Paths with wildcard
for (const n of m) {
event.respondWith(n);
}
} else {
event.respondWith(m.value);
}
}, options);
}
/**
* @unsubscribed()
*/
unsubscribed(event) { event.meta.controller?.abort(); }
/**
* @initialize()
*/
#modules;
#controller1;
#controller2;
initialize(host) {
this.host = host;
const { window: { webqit: { Observer } } } = env;
// ----------------
// Resolve
const resolve = () => {
for (const key of new Set([...Object.keys(this.localModules), ...Object.keys(this.inheritedModules), ...Object.keys(this.#modules)])) {
if (!Observer.has(this.localModules, key) && !Observer.has(this.inheritedModules, key)) {
Observer.deleteProperty(this.#modules, key);
} else if (key === '#' && Observer.has(this.localModules, key) && Observer.has(this.inheritedModules, key)) {
Observer.set(this.#modules, key, [...Observer.get(this.localModules, key), ...Observer.get(this.inheritedModules, key)]);
} else {
const _module = Observer.get(this.localModules, key) || Observer.get(this.inheritedModules, key);
if (Observer.get(this.#modules, key) !== _module) {
Observer.set(this.#modules, key, _module);
}
}
}
};
// ----------------
// Observe local
this.#modules = { ...this.localModules };
this.#controller1?.abort();
this.#controller1 = Observer.observe(this.localModules, () => resolve('local'), { timing: 'sync' });
// ----------------
// If host has importscontext attr, compute that
const $config = this.configs.HTML_IMPORTS;
if (this.host.matches && $config.attr.importscontext) {
const realdom = this.host.ownerDocument.defaultView.webqit.realdom;
let prevRef;
this.#controller2?.disconnect();
this.#controller2 = realdom.realtime(this.host).attr($config.attr.importscontext, (record, { signal }) => {
const moduleRef = (record.value || '').trim();
prevRef = moduleRef;
// This superModules contextrequest is automatically aborted by the injected signal below
this.#inheritedModules = {};
const request = { ...this.constructor.createRequest(moduleRef ? `${moduleRef}/*` : '*'), live: true, signal, diff: true };
this.host.parentNode[this.configs.CONTEXT_API.api.contexts].request(request, (m) => {
if (!m) {
this.#inheritedModules = {};
resolve('inherited');
} else if (m.type === 'delete') {
delete this.#inheritedModules[m.key];
if (!Reflect.has(this.localModules, m.key)) {
Observer.deleteProperty(this.#modules, m.key);
}
} else {
this.#inheritedModules[m.key] = m.value;
if (!Reflect.has(this.localModules, m.key) && Reflect.get(this.#modules, m.key) !== m.value) {
Observer.set(this.#modules, m.key, m.value);
}
}
});
resolve('inherited');
}, { live: true, timing: 'sync', oldValue: true, lifecycleSignals: true });
}
// ----------------
return super.initialize(host);
}
/**
* @dispose()
*/
dispose(host) {
// Stop listening for sources
this.#controller1?.abort();
this.#controller2?.disconnect();
// Now, stop listening for contextrequest and contextclaim events
// And relinquish own subscribers to owner context
return super.dispose(host);
}
}