diff --git a/profiles/w3c.js b/profiles/w3c.js index 50537315c9..464490caf4 100644 --- a/profiles/w3c.js +++ b/profiles/w3c.js @@ -54,6 +54,8 @@ const modules = [ import("../src/core/data-tests.js"), import("../src/core/list-sorter.js"), import("../src/core/highlight-vars.js"), + import("../src/core/dfn-panel.js"), + import("../src/core/sortable-table.js"), import("../src/core/data-type.js"), import("../src/core/algorithms.js"), import("../src/core/anchor-expander.js"), diff --git a/src/core/sortable-table.js b/src/core/sortable-table.js new file mode 100644 index 0000000000..ed9587078a --- /dev/null +++ b/src/core/sortable-table.js @@ -0,0 +1,28 @@ +// @ts-check +import css from "../styles/sortable-table.css.js"; +import { fetchBase } from "./text-loader.js"; + +export const name = "core/sortable-table"; + +export async function run() { + if (!document.querySelector("table.sortable")) { + return; + } + + const style = document.createElement("style"); + style.textContent = css; + document.head.insertBefore(style, document.querySelector("link")); + + const script = document.createElement("script"); + script.id = "respec-dfn-panel"; + script.textContent = await loadScript(); + document.body.append(script); +} + +async function loadScript() { + try { + return (await import("text!./sortable-table.runtime.js")).default; + } catch { + return fetchBase("./src/core/sortable-table.runtime.js"); + } +} diff --git a/src/core/sortable-table.runtime.js b/src/core/sortable-table.runtime.js new file mode 100644 index 0000000000..f5ae4b68cb --- /dev/null +++ b/src/core/sortable-table.runtime.js @@ -0,0 +1,82 @@ +if (document.respec) { + document.respec.ready.then(setupSortableTable); +} else { + setupSortableTable(); +} + +function setupSortableTable() { + /** @type {WeakMap>} */ + const STATE = new WeakMap(); + + /** + * @param {HTMLTableCellElement} th + * @param {-1 | 0 | 1} dir + */ + const createOrUpdateButton = (th, dir) => { + const textValue = [" descending", "", " ascending"][dir + 1]; + const iconValue = ["▼", "▲/▼", "▲"][dir + 1]; // TODO: fix these mappings + const label = th.dataset.text || th.textContent; + if (!th.dataset.text) th.dataset.text = label; + + const button = + th.querySelector("button") || document.createElement("button"); + const text = `Sort${textValue} by ${label}`; + button.title = text; + button.setAttribute("aria-label", text); + button.textContent = iconValue; + return button; + }; + + /** @type {NodeListOf} */ + const tables = document.querySelectorAll("table.sortable"); + for (const table of tables) { + for (const th of table.tHead.querySelectorAll("th")) { + th.append(createOrUpdateButton(th, 0)); + } + } + + /** @type {(el: MouseEvent)=> HTMLTableCellElement | null} */ + const getTrigger = ev => { + if (!(ev.target instanceof HTMLElement)) return null; + const el = ev.target; + if (el.localName === "th") { + return el; + } + if (el.localName === "button" && el.parentElement.localName === "th") { + return el.parentElement; + } + return null; + }; + + document.addEventListener("click", ev => { + const th = getTrigger(ev); + if (!th) return; + + /** @type {HTMLTableElement | null} */ + const table = th.closest("table.sortable"); + if (!table) return; + + ev.preventDefault(); + + const state = + STATE.get(table) || STATE.set(table, new WeakMap()).get(table); + const direction = state.get(th) === 1 ? -1 : 1; // -1: ascending, 1: descending + state.set(th, direction); + + createOrUpdateButton(th, direction); + + const tbody = table.tBodies[0]; + const headers = th.closest("tr").cells; + const colIndex = Array.from(headers).findIndex(node => node === th); + const rows = Array.from(tbody.rows).sort((a, b) => { + const x = a.cells[colIndex].textContent.trimStart(); + const y = b.cells[colIndex].textContent.trimStart(); + return direction * x.localeCompare(y); + }); + + /** @type {typeof tbody} */ + const tbodyCone = tbody.cloneNode(false); + tbodyCone.append(...rows); + tbody.replaceWith(tbodyCone); + }); +} diff --git a/src/styles/sortable-table.css.js b/src/styles/sortable-table.css.js new file mode 100644 index 0000000000..e29e64ddf7 --- /dev/null +++ b/src/styles/sortable-table.css.js @@ -0,0 +1,24 @@ +const css = String.raw; + +export default css` + .sortable th { + vertical-align: middle; + white-space: nowrap; + } + + .sortable th button { + background: inherit; + color: inherit; + border: none; + font-size: 0.6em; + padding: 0.5em; + } + + .sortable th button:hover { + cursor: pointer; + } + + .sortable th button:focus { + outline: 1px solid; + } +`;