Some checks are pending
CI / check (push) Waiting to run
- 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
63 lines
2.2 KiB
TypeScript
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 });
|
|
};
|
|
}
|