import {SessionService} from './service/Session.service.js' export abstract class ExComponent extends HTMLElement { protected static styles = ` ` protected static html = `` protected static inheritStyles = true static get observedAttributes() { const map: any = (this as any).exPropertyAttributeMapping if ( Array.isArray(map) ) { return map.map(x => x.attribute) } return [] } protected readonly shadow = this.attachShadow({ mode: 'open' }) private hadFirstRender = false private preFirstRenderDefaultAttributes: any = {} private rendersMap: any = {} private mountListeners: ((el: this) => unknown)[] = [] private didMount = false protected uuid: string constructor() { super() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.uuid = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)) this.setPropertyAttributeMappings() this.setPropertyRendersMappings() this.attachTemplate() this.setPropertyQueryMappings() } dispatchCustom(name: string, detail: any): boolean { return this.dispatchEvent( new CustomEvent(name, { detail }), ) } connectedCallback() { this.mount() this.didMount = true this.mountListeners.map(x => x(this)) this.render() } attachTemplate() { const template = document.createElement('template') let html = ((this as any).constructor as typeof ExComponent).styles + '\n' + ((this as any).constructor as typeof ExComponent).html this.forEachParent(this, ctor => { if ( !ctor.styles ) { return } html = `${ctor.styles}\n${html}` if ( !ctor.inheritStyles ) { return false } }) template.innerHTML = html this.shadow.appendChild(template.content.cloneNode(true)) } setPropertyQueryMappings(): void { const map: any = (this as any).constructor.exPropertyElementMapping if ( Array.isArray(map) ) { for ( const mapping of map ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this[mapping.property] = this.shadow.querySelector(mapping.selector) } } } setPropertyRendersMappings(): void { const map: any = (this as any).constructor.exPropertyRendersMapping if ( Array.isArray(map) ) { for ( const mapping of map ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore delete this[mapping.property] Object.defineProperty(this, mapping.property, { get: () => this.rendersMap[mapping.property], set: value => { this.rendersMap[mapping.property] = value if ( this.hadFirstRender ) { this.render() } }, }) } } } setPropertyAttributeMappings(): void { const map: any = (this as any).constructor.exPropertyAttributeMapping if ( Array.isArray(map) ) { for ( const mapping of map ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore delete this[mapping.property] Object.defineProperty(this, mapping.property, { get: () => this.getOptionalAttribute(mapping.attribute), set: value => { if ( !this.hadFirstRender ) { this.preFirstRenderDefaultAttributes[mapping.attribute] = value return } this.setAttribute(mapping.attribute, value) }, }) } } } attributeChangedCallback(): void { this.render() } onMount(callback: (el: this) => unknown) { if ( this.didMount ) { callback(this) } else { this.mountListeners.push(callback) } } tap(callback: (el: this) => T): T { return callback(this) } // eslint-disable-next-line @typescript-eslint/no-empty-function mount(): void {} render(): void { if ( !this.hadFirstRender ) { for ( const prop of ((this as any).constructor as typeof ExComponent).observedAttributes ) { if ( !this.getAttribute(prop) ) { this.setAttribute(prop, this.preFirstRenderDefaultAttributes[prop]) } } } this.hadFirstRender = true } getOptionalAttribute(name: string): string | undefined { const val = this.getAttribute(name) if ( val && val !== 'undefined' ) { return val } } forEachParent(inst: ExComponent, callback: (ctor: typeof ExComponent) => unknown) { let ctor = inst.constructor as typeof ExComponent do { if ( callback(ctor) === false ) { return } ctor = Object.getPrototypeOf(ctor) } while ( ctor && (ctor.prototype instanceof ExComponent || ctor === ExComponent) ) } session(): SessionService { return SessionService.get() } }