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("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("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("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); } }