@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.
153 lines (142 loc) • 6.22 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, signal: 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();
if ( moduleRef === prevRef ) return;
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 );
}
}