UNPKG

@joist/element

Version:

Intelligently apply styles to WebComponents

118 lines 5.04 kB
import { define } from "./define.js"; import { metadataStore } from "./metadata.js"; export function element(opts) { return function elementDecorator(Base, ctx) { const meta = metadataStore.read(ctx.metadata); ctx.addInitializer(function () { if (opts?.tagName) { define({ tagName: opts.tagName, dependsOn: opts.dependsOn }, this); } }); const def = { [Base.name]: class extends Base { static observedAttributes = Array.from(meta.attrs.keys()); #abortController = null; constructor(...args) { super(...args); if (opts?.shadowDom) { if (!this.shadowRoot) { this.attachShadow(opts.shadowDomOpts ?? { mode: "open" }); } for (const res of opts.shadowDom) { res.apply(this); } } for (const cb of meta.onReady) { cb.call(this); } } attributeChangedCallback(name, oldValue, newValue) { const attr = meta.attrs.get(name); const cbs = meta.attrChanges.get(name); if (attr) { if (oldValue !== newValue) { const sourceValue = attr.access.get.call(this); let value = newValue; if (typeof sourceValue === "boolean") { // treat as boolean value = newValue !== null; } else if (typeof sourceValue === "number") { // treat as number value = Number(newValue); } attr.access.set.call(this, value); } if (cbs) { for (const cb of cbs) { cb.call(this, name, oldValue, newValue); } } if (attr.observe) { if (super.attributeChangedCallback) { super.attributeChangedCallback(name, oldValue, newValue); } } } } connectedCallback() { if (!this.#abortController) { this.#abortController = new AbortController(); for (const { event, cb, selector } of meta.listeners) { const root = selector(this); if (root) { root.addEventListener(event, cb.bind(this), { signal: this.#abortController.signal, }); } else { throw new Error(`could not add listener to ${root}`); } } } reflectAttributeValues(this, meta.attrs); if (super.connectedCallback) { super.connectedCallback(); } } disconnectedCallback() { if (this.#abortController) { this.#abortController.abort(); this.#abortController = null; } if (super.disconnectedCallback) { super.disconnectedCallback(); } } }, }; return def[Base.name]; }; } function reflectAttributeValues(el, attrs) { for (const [attrName, { access, reflect }] of attrs) { if (reflect) { const value = access.get.call(el); // reflect values back to attributes if (value !== null && value !== undefined && value !== "") { if (typeof value === "boolean") { if (value === true) { // set boolean attribute if (!el.hasAttribute(attrName)) { el.setAttribute(attrName, ""); } } } else if (!el.hasAttribute(attrName)) { // only set parent attribute if it doesn't exist // set key/value attribute const strValue = String(value); if (el.getAttribute(attrName) !== strValue) { el.setAttribute(attrName, strValue); } } } } } } //# sourceMappingURL=element.js.map