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.

140 lines
4.1 KiB

export abstract class ExComponent extends HTMLElement {
protected static html = ``
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
constructor() {
super()
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')
template.innerHTML = ((this as any).constructor as typeof ExComponent).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.getAttribute(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
}
}