import { Computed } from "native-signal/weak"; import { own } from "./own"; /** * Bind a record of signals (or plain values) to an element's attributes reactively. * Each key maps to an attribute name; each value may be a signal, a Computed, * or a raw value. Signal values are unwrapped and kept live with a Computed effect. * * @example * bindAttrs(btn, { * disabled: this.is_readonly, // NativeSignal * "aria-pressed": this.show_versions, // NativeSignal * class: new Computed(() => // Computed * `version-item ${active.get() === v.id ? "active" : ""}`), * ["class.open"]:this.is_open, // NativeSignal * value: "static-value", // plain string — set once, not tracked * }); */ type SignalLike = { get(): T }; export type AttrValue = SignalLike | string | boolean | number | null; const PROPERTY_ONLY_KEYS = new Set(["value", "checked", "selected", "indeterminate"]); const usesLiveProperty = (el : Element, key:string) => PROPERTY_ONLY_KEYS.has(key) && key in el; export function bind_attrs( el: Element, attrs: Record, ) { for (const [key, value] of Object.entries(attrs)) { const isSignal = value !== null && typeof value === "object" && typeof (value as any).get === "function"; // ── class.foo mode ─────────────────────────────────────────────────── if (key.startsWith("class.")) { const className = key.slice("class.".length); const apply = (v: unknown) => { el.classList.toggle(className, Boolean(v)); } if (isSignal) { const sig = value as SignalLike; let effect = new Computed(() => apply(sig.get()), undefined, true); own(effect, el); } else apply(value); continue; } // ── normal attribute mode ──────────────────────────────────────────── const apply = (v: string | boolean | number | null) => { if(usesLiveProperty(el, key)) { // Live form-control state must go through the IDL property; // setAttribute only touches the initial/default value once // the user (or code) has interacted with the control. if(typeof (el as any)[key] === "boolean") (el as any)[key] = v; (el as any)[key] = v === false || v === null || v === undefined ? "" : String(v === true ? "" : v); return; } if (v === false || v === null || v === undefined) el.removeAttribute(key); else if (v === true) el.setAttribute(key, ""); else el.setAttribute(key, String(v)); }; if (isSignal) { const sig = value as SignalLike; let effect = new Computed(() => apply(sig.get()), undefined, true); own(effect, el); } else apply(value as any); } }