| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
(() => { |
| 25 |
"use strict"; |
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
const TIERS = ["cell-drops-first", "cell-drops-next"]; |
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
const STRIP = '[role="tablist"]'; |
| 39 |
|
| 40 |
|
| 41 |
* The element whose children fold, and the children in shedding order. |
| 42 |
* |
| 43 |
* Order is by priority tier and then last-first within a tier. Last-first |
| 44 |
* is what "shed by priority, not by document order" leaves once priority is |
| 45 |
* spent: among members worth the same, the ones at the end are the ones a |
| 46 |
* reader reaches for least. For a strip, where no tab carries a priority at |
| 47 |
* all, that is the whole rule and it is the one the description means -- |
| 48 |
* the last tabs fold away and the first stay. |
| 49 |
|
| 50 |
const shedding = (run) => { |
| 51 |
const strip = run.querySelector(STRIP); |
| 52 |
const host = strip ?? run; |
| 53 |
const members = [...host.children].filter( |
| 54 |
(el) => !el.classList.contains("run-overflow"), |
| 55 |
); |
| 56 |
const order = []; |
| 57 |
for (const tier of TIERS) { |
| 58 |
order.push(...members.filter((el) => el.classList.contains(tier)).reverse()); |
| 59 |
} |
| 60 |
order.push( |
| 61 |
...members |
| 62 |
.filter((el) => !TIERS.some((tier) => el.classList.contains(tier))) |
| 63 |
.reverse(), |
| 64 |
); |
| 65 |
return { host, order }; |
| 66 |
}; |
| 67 |
|
| 68 |
|
| 69 |
const control = (host) => { |
| 70 |
const existing = host.querySelector(":scope > .run-overflow"); |
| 71 |
if (existing) return existing; |
| 72 |
|
| 73 |
const wrap = document.createElement("div"); |
| 74 |
wrap.className = "run-overflow"; |
| 75 |
|
| 76 |
const button = document.createElement("button"); |
| 77 |
button.type = "button"; |
| 78 |
button.className = "button"; |
| 79 |
button.setAttribute("aria-expanded", "false"); |
| 80 |
button.setAttribute("aria-haspopup", "true"); |
| 81 |
|
| 82 |
|
| 83 |
button.textContent = "More"; |
| 84 |
|
| 85 |
const items = document.createElement("div"); |
| 86 |
items.className = "run-overflow-items"; |
| 87 |
items.hidden = true; |
| 88 |
|
| 89 |
button.addEventListener("click", () => { |
| 90 |
const open = items.hidden; |
| 91 |
items.hidden = !open; |
| 92 |
button.setAttribute("aria-expanded", String(open)); |
| 93 |
}); |
| 94 |
|
| 95 |
wrap.append(button, items); |
| 96 |
host.append(wrap); |
| 97 |
return wrap; |
| 98 |
}; |
| 99 |
|
| 100 |
|
| 101 |
const restore = (host, wrap) => { |
| 102 |
const items = wrap.querySelector(".run-overflow-items"); |
| 103 |
while (items.firstChild) host.insertBefore(items.firstChild, wrap); |
| 104 |
wrap.hidden = true; |
| 105 |
items.hidden = true; |
| 106 |
wrap.querySelector("button")?.setAttribute("aria-expanded", "false"); |
| 107 |
}; |
| 108 |
|
| 109 |
|
| 110 |
|
| 111 |
|
| 112 |
|
| 113 |
const tight = (host) => host.scrollWidth > host.clientWidth + 1; |
| 114 |
|
| 115 |
|
| 116 |
const fold = (run) => { |
| 117 |
const { host, order } = shedding(run); |
| 118 |
const wrap = control(host); |
| 119 |
restore(host, wrap); |
| 120 |
|
| 121 |
|
| 122 |
|
| 123 |
run.setAttribute("data-menu", ""); |
| 124 |
|
| 125 |
const items = wrap.querySelector(".run-overflow-items"); |
| 126 |
let shed = 0; |
| 127 |
|
| 128 |
|
| 129 |
|
| 130 |
while (tight(host) && shed < order.length - 1) { |
| 131 |
items.prepend(order[shed]); |
| 132 |
shed += 1; |
| 133 |
} |
| 134 |
wrap.hidden = shed === 0; |
| 135 |
}; |
| 136 |
|
| 137 |
const all = () => { |
| 138 |
for (const run of document.querySelectorAll(".run-menu")) fold(run); |
| 139 |
}; |
| 140 |
|
| 141 |
|
| 142 |
|
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
if (typeof ResizeObserver === "function") { |
| 147 |
const observer = new ResizeObserver(() => all()); |
| 148 |
observer.observe(document.documentElement); |
| 149 |
} else { |
| 150 |
window.addEventListener("resize", all); |
| 151 |
} |
| 152 |
document.addEventListener("htmx:after:settle", all); |
| 153 |
if (document.readyState === "loading") { |
| 154 |
document.addEventListener("DOMContentLoaded", all); |
| 155 |
} else { |
| 156 |
all(); |
| 157 |
} |
| 158 |
})(); |
| 159 |
|