@joist/element
Version:
Intelligently apply styles to WebComponents
118 lines • 5.04 kB
JavaScript
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