| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
(() => { |
| 19 |
"use strict"; |
| 20 |
|
| 21 |
const DISCLOSE = "[data-disclose]"; |
| 22 |
const ROW = "[data-row]"; |
| 23 |
|
| 24 |
|
| 25 |
const depth = (row) => Number(row.getAttribute("data-depth") ?? 0); |
| 26 |
|
| 27 |
|
| 28 |
const rowOf = (el) => el.closest(ROW); |
| 29 |
|
| 30 |
|
| 31 |
* The rows under `row`: the ones after it that are deeper, up to the first |
| 32 |
* one no deeper than it. |
| 33 |
* |
| 34 |
* The same reading `quasi_router::folded` makes on the other side, and it |
| 35 |
* has to be: a reader who folds a branch here and reloads the page must see |
| 36 |
* the list the server would have folded. |
| 37 |
|
| 38 |
const under = (row) => { |
| 39 |
const rows = [...row.parentElement.querySelectorAll(`:scope > ${ROW}`)]; |
| 40 |
const start = rows.indexOf(row); |
| 41 |
const level = depth(row); |
| 42 |
const kin = []; |
| 43 |
for (const next of rows.slice(start + 1)) { |
| 44 |
if (depth(next) <= level) break; |
| 45 |
kin.push(next); |
| 46 |
} |
| 47 |
return kin; |
| 48 |
}; |
| 49 |
|
| 50 |
|
| 51 |
* Draw the outline under `row` as its chevrons say it should be. |
| 52 |
* |
| 53 |
* Walked rather than toggled row by row: a branch inside a shut branch |
| 54 |
* keeps its own state and stays shut when the outer one opens, which is |
| 55 |
* what a reader who left it shut expects to come back to. `folded` is the |
| 56 |
* same one-variable walk the Rust side makes. |
| 57 |
|
| 58 |
const settle = (row) => { |
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
|
| 64 |
let shut = row.querySelector(DISCLOSE)?.getAttribute("aria-expanded") === "false" |
| 65 |
? depth(row) |
| 66 |
: null; |
| 67 |
for (const next of under(row)) { |
| 68 |
const level = depth(next); |
| 69 |
if (shut !== null && level <= shut) shut = null; |
| 70 |
const hidden = shut !== null; |
| 71 |
next.hidden = hidden; |
| 72 |
const chevron = next.querySelector(DISCLOSE); |
| 73 |
if (!hidden && chevron?.getAttribute("aria-expanded") === "false") { |
| 74 |
shut = level; |
| 75 |
} |
| 76 |
} |
| 77 |
}; |
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
|
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
|
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
document.addEventListener("click", (event) => { |
| 95 |
const chevron = event.target.closest(DISCLOSE); |
| 96 |
if (!chevron) return; |
| 97 |
const row = rowOf(chevron); |
| 98 |
if (!row) return; |
| 99 |
|
| 100 |
|
| 101 |
|
| 102 |
|
| 103 |
event.preventDefault(); |
| 104 |
event.stopPropagation(); |
| 105 |
const open = chevron.getAttribute("aria-expanded") === "true"; |
| 106 |
chevron.setAttribute("aria-expanded", String(!open)); |
| 107 |
chevron.setAttribute("aria-label", open ? "Expand" : "Collapse"); |
| 108 |
settle(row); |
| 109 |
}, true); |
| 110 |
})(); |
| 111 |
|