|
|
|
import {SessionService} from './service/Session.service.js'
|
|
|
|
|
|
|
|
export abstract class ExComponent extends HTMLElement {
|
|
|
|
protected static styles = `
|
|
|
|
<style>
|
|
|
|
input {
|
|
|
|
padding: 6px 10px;
|
|
|
|
border: 0;
|
|
|
|
border-bottom: 2px solid var(--color-accent-text);
|
|
|
|
font-size: 1em;
|
|
|
|
background: var(--color-background-darkened);
|
|
|
|
color: var(--color-text);
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
`
|
|
|
|
|
|
|
|
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<T>(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()
|
|
|
|
}
|
|
|
|
}
|