Mats Bosson bf576905a7
Some checks are pending
CI / check (push) Waiting to run
All components, schemas, tests, MCP, and showcase
- 51 headless Web Components (45 core + 6 animation)
- Shared helpers: emit(), part(), listen(), wireLabel(), initialValue()
- Zero `new CustomEvent` or `static #counter` — all use shared utils
- Zod schemas for all 44 core components
- MCP package with discover, inspect, compose, validate tools
- Showcase with Aperture Science theme, M3 Expressive motion
- 81 tests passing, TypeScript strict mode clean
- Signals (~500B), SPA router (~400B), zero dependencies
2026-03-31 20:21:41 +07:00

63 lines
2.2 KiB
TypeScript

import { signal } from "../../signals";
import { emit, listen, part, wireLabel } from "../../shared/helpers";
/** PettyCheckbox — tri-state checkbox with label wiring and change events. */
export class PettyCheckbox extends HTMLElement {
static observedAttributes = ["checked", "indeterminate", "disabled", "name", "value"];
readonly #checked = signal(false);
readonly #indeterminate = signal(false);
#cleanup = (): void => {};
get checked(): boolean { return this.#checked.get(); }
set checked(v: boolean) { this.#checked.set(v); this.#sync(); }
get indeterminate(): boolean { return this.#indeterminate.get(); }
set indeterminate(v: boolean) { this.#indeterminate.set(v); this.#sync(); }
connectedCallback(): void {
const input = this.#input();
if (!input) return;
if (this.hasAttribute("checked")) this.#checked.set(true);
if (this.hasAttribute("indeterminate")) this.#indeterminate.set(true);
wireLabel(input, part(this, "label"), "petty-cb");
this.#sync();
this.#cleanup = listen(input, [["change", this.#handleChange]]);
}
disconnectedCallback(): void {
this.#cleanup();
}
attributeChangedCallback(name: string, _old: string | null, next: string | null): void {
if (name === "checked") this.#checked.set(next !== null);
if (name === "indeterminate") this.#indeterminate.set(next !== null);
this.#sync();
}
#input(): HTMLInputElement | null {
return part<HTMLInputElement>(this, "control");
}
#sync(): void {
const input = this.#input();
if (!input) return;
input.checked = this.#checked.get();
input.indeterminate = this.#indeterminate.get();
input.disabled = this.hasAttribute("disabled");
if (this.hasAttribute("name")) input.name = this.getAttribute("name") ?? "";
if (this.hasAttribute("value")) input.value = this.getAttribute("value") ?? "on";
const state = this.#indeterminate.get() ? "indeterminate" : this.#checked.get() ? "checked" : "unchecked";
this.dataset.state = state;
}
#handleChange = (): void => {
const input = this.#input();
if (!input) return;
this.#checked.set(input.checked);
this.#indeterminate.set(false);
this.#sync();
emit(this, "change", { checked: input.checked, indeterminate: false });
};
}