You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
5.8 KiB

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()
}
}