UNPKG

@polight/lego

Version:

Tiny Web-Components lib for future-proof HTML mentors

118 lines (99 loc) 2.99 kB
import { h, render } from 'petit-dom' import { toCamelCase } from '../utils' function sanitizeAttribute(attrType, attrValue) { if (attrType === 'object') return sanitizeJsonAttribute(attrValue) if (attrType === 'boolean') return attrValue === "" || !!attrValue if (attrType === 'number') return Number(attrValue) return attrValue } function sanitizeJsonAttribute(attrValue) { try { return JSON.parse(attrValue) } catch (_) { return attrValue } } class Component extends HTMLElement { state = {} useShadowDOM = true #watchProps = [] #isConnected = false #isInitialized = false #customEvents = [] #ready() { this.init?.() this.#watchProps = Object.keys(this.state) this.#syncAttributesToState() this.document = this.useShadowDOM ? this.attachShadow({ mode: "open" }) : this this.#isInitialized = true } #syncAttributesToState() { this.state = Array.from(this.attributes).reduce( (state, attr) => { const camelCaseName = toCamelCase(attr.name) const attrType = typeof this.state[camelCaseName] return { ...state, [camelCaseName]: sanitizeAttribute(attrType, attr.value), } }, this.state ) } get vdom() { return ({ state }) => "" } get vstyle() { return ({ state }) => "" } setAttribute(name, value) { if (name.match(/@[a-z]+(?:-[a-z]+)*/)) return this.#customEvents.push([name.slice(1), value]) super.setAttribute(name, typeof value === 'object' ? JSON.stringify(value) : value) const prop = toCamelCase(name) const attrType = typeof this.state[prop] if (this.#watchProps.includes(prop)) this.render({ [prop]: sanitizeAttribute(attrType, value) }) } removeAttribute(name) { super.removeAttribute(name) const prop = toCamelCase(name) const attrType = typeof this.state[prop] if (this.#watchProps.includes(prop) && prop in this.state) { this.render({ [prop]: sanitizeAttribute(attrType, null) }) } } connectedCallback() { if (!this.#isInitialized) this.#ready() this.#isConnected = true // Load the DOM this.render() this.#customEvents.forEach(([customEvent, listener]) => this.addEventListener(customEvent, listener)) this.connected?.() } disconnectedCallback() { this.#isConnected = false this.#customEvents.forEach(([customEvent, listener]) => this.removeEventListener(customEvent, listener)) this.disconnected?.() } setState(props = {}) { Object.assign(this.state, props) if (this.changed && this.#isConnected) this.changed(props) } set state(value) { this.setState(value) } get state() { return this.state } render(state) { if (state) this.setState(state) if (!this.#isConnected) return render( [this.vdom({ state: this.state }), this.vstyle({ state: this.state })], this.document ) this.rendered?.(state) } } export { h, render, Component }