// id-classlist.ts import { IdApi, ClassApi } from "../../../types/dom.types.js"; import { LiveTree } from "../livetree.js"; import { getAttrImpl, setAttrsImpl, removeAttrImpl } from "./attr-handle.js"; import { document_binding_for_node } from "../lifecycle/document-binding-state.js"; import type { CanonicalPublicAttrs } from "../../../core/types.js"; export function make_id_api(tree: TTree): IdApi { return { // read from underlying attr impl to avoid calling tree.id.get() (recursion) get: () => { const v = getAttrImpl(tree, "id"); return typeof v === "string" ? v : undefined; }, // write via attr impl (id is just an attribute) set: (id: string) => { setAttrsImpl(tree, "id", id); return tree; }, // clear via remove impl clear: () => { removeAttrImpl(tree, "id"); return tree; }, }; } export function make_class_api(tree: TTree): ClassApi { const transform = ( operation: (current: Set) => Set, ): TTree | undefined => { const binding = document_binding_for_node(tree.node); if (binding === undefined) return undefined; binding.delegateAttrs({ kind: "transform", apply: (attrs: CanonicalPublicAttrs) => { const raw = typeof attrs.class === "string" ? attrs.class : ""; const next = [...operation(new Set(raw.split(/\s+/).filter(Boolean)))].filter(Boolean).join(" ").trim(); return next.length === 0 ? { kind: "drop", name: "class" } : { kind: "set", name: "class", value: next }; }, }); return tree; }; // read from attrs, not tree.classlist.get() (avoids self-recursion) const getRaw = (): string | undefined => { const v = tree.attrs.get("class"); return (typeof v === "string" && v.trim().length > 0) ? v : undefined; }; // keep parsing centralized; uses getRaw() once per op const getSet = (): Set => { const s = getRaw() ?? ""; return new Set(s.split(/\s+/).filter(Boolean)); }; // centralize write semantics (empty => drop) const write = (names: Iterable): TTree => { const next = Array.from(names).filter(Boolean).join(" ").trim(); if (!next) tree.attrs.drop("class"); else tree.attrs.set("class", next); return tree; }; return { get: () => getRaw(), has: (name: string) => getSet().has(name), set: (cls) => { const next = Array.isArray(cls) ? cls.filter(Boolean).join(" ").trim() : (cls ?? "").trim(); // write via attrs (no tree.classlist.*) if (!next) tree.attrs.drop("class"); else tree.attrs.set("class", next); return tree; }, add: (...names) => { const delegated = transform((set) => { for (const n of names) if (n) set.add(n); return set; }); if (delegated !== undefined) return delegated; const set = getSet(); for (const n of names) if (n) set.add(n); return write(set); }, remove: (...names) => { const delegated = transform((set) => { for (const n of names) if (n) set.delete(n); return set; }); if (delegated !== undefined) return delegated; const set = getSet(); for (const n of names) if (n) set.delete(n); return write(set); }, toggle: (name, force) => { const delegated = transform((set) => { const shouldHave = force === undefined ? !set.has(name) : force; if (shouldHave) set.add(name); else set.delete(name); return set; }); if (delegated !== undefined) return delegated; const set = getSet(); const has = set.has(name); const shouldHave = (force === undefined) ? !has : force; if (shouldHave) set.add(name); else set.delete(name); return write(set); }, clear: () => { // drop via attrs tree.attrs.drop("class"); return tree; }, }; }