// configuration's list + inspector: one entry shows at a time, picked from the list, the
// prev/next buttons, ↑↓ on the list, or a #hash. the address bar's path grows with the
// selection: configuration ▸ firehose ▸ FIREHOSE_WORKERS, and each ▸ lists that level.
import { tree } from "./tree";
const pane = document.querySelector<HTMLElement>("[data-cfg-pane]");
if (pane) setup(pane);
function setup(pane: HTMLElement) {
const list = pane.querySelector<HTMLElement>("[data-tree]")!;
const outline = tree(list);
const items = [...pane.querySelectorAll<HTMLElement>(".cfg-item")];
const keys = items.map((i) => i.dataset.item!);
const inav = pane.querySelector<HTMLElement>("[data-inav]")!;
const paper = pane.querySelector<HTMLElement>(".paper")!;
let cur = -1;
const sectionOf = (i: number) => {
const it = items[i];
return it.classList.contains("cfg-var") ? it.dataset.sec! : it.classList.contains("cfg-sec") ? it.dataset.item! : null;
};
const nameOf = (i: number) => keys[i] === "" ? list.querySelector(".side-title .k")!.textContent! : items[i].classList.contains("cfg-sec") ? items[i].querySelector("h2")!.textContent! : keys[i];
function select(i: number, how: "push" | "replace" | "none" = "none") {
if (i < 0 || i >= items.length) return;
cur = i;
items.forEach((it, j) => it.classList.toggle("cur", j === i));
outline.select(keys[i]);
// a new entry starts at its top, like turning to a page
if (window.scrollY > 0 && !matchMedia("(max-width: 760px)").matches) window.scrollTo({ top: 0 });
const [prev, next] = [inav.querySelector<HTMLAnchorElement>('[data-step="-1"]')!, inav.querySelector<HTMLAnchorElement>('[data-step="1"]')!];
for (const [a, j, arrow] of [[prev, i - 1, "◀ "], [next, i + 1, " ▶"]] as const) {
const ok = j >= 0 && j < items.length;
a.setAttribute("aria-disabled", String(!ok));
a.href = ok ? `#${keys[j]}` : "#";
a.textContent = ok ? (arrow === "◀ " ? arrow + nameOf(j) : nameOf(j) + arrow) : arrow.trim();
}
inav.hidden = false;
const hash = keys[i] ? `#${keys[i]}` : location.pathname;
if (how === "push") history.pushState(null, "", hash);
if (how === "replace") history.replaceState(null, "", hash);
crumbs(i);
}
const fromHash = () => Math.max(0, keys.indexOf(decodeURIComponent(location.hash.slice(1))));
// the address bar ends at "configuration" in the html; past that the path follows the selection
const addr = document.querySelector<HTMLElement>("[data-addr]");
const trail = addr?.querySelector<HTMLElement>(".crumbs");
const here = trail?.querySelector<HTMLElement>(".crumb.current");
const pageLabel = here?.textContent ?? "";
function crumbs(i: number) {
if (!addr || !trail || !here) return;
trail.querySelectorAll("[data-cfg-crumb]").forEach((n) => n.remove());
addr.querySelectorAll("[data-cfg-menu]").forEach((n) => n.remove());
const sec = sectionOf(i);
here.hidden = !!sec;
here.classList.toggle("current", !sec);
if (!sec) return;
const si = keys.indexOf(sec);
const isVar = items[i].classList.contains("cfg-var");
const sep = (id: string, label: string, entries: number[]) => {
const m = document.createElement("div");
m.className = "menu anchored";
m.popover = "manual";
m.style.setProperty("position-anchor", `--sib-${id}`);
m.dataset.sibmenu = id;
m.dataset.cfgMenu = "";
m.innerHTML = entries.map((j) => `<a href="#${keys[j]}"${j === i || (j === si && !isVar) ? ' aria-current="page"' : ""}><span class="lbl">${nameOf(j)}</span></a>`).join("");
addr.append(m);
return `<button class="crumb-sep" type="button" aria-expanded="false" aria-label="${label}" data-sib="${id}" style="anchor-name: --sib-${id}" data-cfg-crumb></button>`;
};
const sectionIdx = keys.map((_, j) => j).filter((j) => items[j].classList.contains("cfg-sec"));
const inSection = keys.map((_, j) => j).filter((j) => items[j].dataset.sec === sec);
let html = `<a class="crumb" href="#" data-cfg-crumb>${pageLabel}</a>` + sep("cfg-secs", "sections", sectionIdx);
html += isVar
? `<a class="crumb" href="#${sec}" data-cfg-crumb>${nameOf(si)}</a>` + sep("cfg-vars", `settings in ${nameOf(si)}`, inSection) + `<span class="crumb current" data-type data-cfg-crumb>${keys[i]}</span>`
: `<span class="crumb current" data-type data-cfg-crumb>${nameOf(si)}</span>`;
here.insertAdjacentHTML("afterend", html);
}
pane.addEventListener("click", (e) => {
const a = (e.target as HTMLElement).closest<HTMLAnchorElement>("a[href^='#']");
if (!a || a.getAttribute("aria-disabled") === "true") {
if (a) e.preventDefault();
return;
}
const j = keys.indexOf(decodeURIComponent(a.hash.slice(1)));
if (j < 0 && a.getAttribute("href") !== "#") return;
e.preventDefault();
select(Math.max(0, j), "push");
if (list.contains(a)) list.querySelector<HTMLElement>(".tree")!.focus({ preventScroll: true });
if (matchMedia("(max-width: 760px)").matches) paper.scrollIntoView({ behavior: "smooth", block: "start" });
});
// the address bar's own crumbs and sibling menus link into the page too
addr?.addEventListener("click", (e) => {
const a = (e.target as HTMLElement).closest<HTMLAnchorElement>("a[href^='#']");
if (!a || a.pathname !== location.pathname) return;
const j = keys.indexOf(decodeURIComponent(a.hash.slice(1)));
if (j < 0 && a.getAttribute("href") !== "#") return;
e.preventDefault();
select(Math.max(0, j), "push");
});
list.addEventListener("keydown", (e) => {
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
e.preventDefault();
const visible = outline.visible().map((k) => keys.indexOf(k));
const at = visible.indexOf(cur);
const next = visible[Math.max(0, Math.min(visible.length - 1, at + (e.key === "ArrowDown" ? 1 : -1)))];
if (next !== undefined) select(next, "replace");
});
window.addEventListener("popstate", () => select(fromHash()));
window.addEventListener("hashchange", () => select(fromHash()));
select(fromHash());
}
export {};