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

55 lines
2.1 KiB
TypeScript

import { emit } from "../../shared/helpers";
/** PettyDataTable — sortable table with click-to-sort column headers. */
export class PettyDataTable extends HTMLElement {
connectedCallback(): void {
const headers = this.querySelectorAll<HTMLElement>("th[data-sort]");
for (const th of headers) {
th.setAttribute("role", "columnheader");
th.setAttribute("aria-sort", "none");
th.style.cursor = "pointer";
th.addEventListener("click", this.#onHeaderClick);
}
}
disconnectedCallback(): void {
const headers = this.querySelectorAll<HTMLElement>("th[data-sort]");
for (const th of headers) th.removeEventListener("click", this.#onHeaderClick);
}
#onHeaderClick = (e: Event): void => {
const th = e.currentTarget as HTMLElement;
const column = th.dataset.sort ?? "";
const current = th.getAttribute("aria-sort") ?? "none";
const next = current === "ascending" ? "descending" : "ascending";
this.#clearSorts();
th.setAttribute("aria-sort", next);
th.dataset.state = next;
this.#sortBy(th, next === "ascending" ? 1 : -1);
emit(this, "sort", { column, direction: next });
};
#clearSorts(): void {
const headers = this.querySelectorAll<HTMLElement>("th[data-sort]");
for (const th of headers) { th.setAttribute("aria-sort", "none"); th.dataset.state = "none"; }
}
#sortBy(th: HTMLElement, dir: number): void {
const tbody = this.querySelector("tbody[data-part=body]") ?? this.querySelector("tbody");
if (!tbody) return;
const headerRow = th.parentElement;
if (!headerRow) return;
const colIdx = Array.from(headerRow.children).indexOf(th);
const rows = Array.from(tbody.querySelectorAll("tr"));
rows.sort((a, b) => {
const aText = a.children[colIdx]?.textContent?.trim() ?? "";
const bText = b.children[colIdx]?.textContent?.trim() ?? "";
const aNum = Number(aText);
const bNum = Number(bText);
if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) return (aNum - bNum) * dir;
return aText.localeCompare(bText) * dir;
});
for (const row of rows) tbody.appendChild(row);
}
}