mirror of
https://github.com/nunocoracao/blowfish.git
synced 2026-10-10 22:09:31 +00:00
The dropdown panel only closed through its own X button. Escape already dismissed it, so the missing pointer equivalent left the panel stuck open over the page after a stray tap. Close it on any click landing outside the panel and outside the labels that toggle it. Both early returns are load-bearing. A label forwards a synthetic click to its checkbox, so without skipping the input the menu closes in the same gesture that opened it. And without skipping the opener, opening then closing with the hamburger leaves the panel open, because this handler and the label's default action cancel each other out. The fullscreen panel needs no special case. It covers the viewport, so every click lands inside it and the handler stays inert.
104 lines
3.3 KiB
JavaScript
104 lines
3.3 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
function syncCheckboxLabels(checkbox) {
|
|
var expanded = checkbox.checked ? "true" : "false";
|
|
document.querySelectorAll('label[role="button"][for="' + checkbox.id + '"]').forEach(function (label) {
|
|
label.setAttribute("aria-expanded", expanded);
|
|
});
|
|
}
|
|
|
|
function setupCheckboxLabels() {
|
|
var seen = {};
|
|
document.querySelectorAll('label[role="button"][for]').forEach(function (label) {
|
|
var checkbox = document.getElementById(label.getAttribute("for"));
|
|
if (!checkbox || checkbox.type !== "checkbox") return;
|
|
|
|
// Enter/Space activates the label like a real button
|
|
label.addEventListener("keydown", function (event) {
|
|
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
event.preventDefault();
|
|
label.click();
|
|
}
|
|
});
|
|
|
|
if (!seen[checkbox.id]) {
|
|
seen[checkbox.id] = true;
|
|
checkbox.addEventListener("change", function () {
|
|
syncCheckboxLabels(checkbox);
|
|
});
|
|
syncCheckboxLabels(checkbox);
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupMobileMenuEscape() {
|
|
var toggle = document.getElementById("mobile-menu-toggle");
|
|
if (!toggle) return;
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape" && toggle.checked) {
|
|
toggle.checked = false;
|
|
syncCheckboxLabels(toggle);
|
|
var opener = document.querySelector('label[role="button"][for="mobile-menu-toggle"]');
|
|
if (opener) opener.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupMobileMenuOutsideClick() {
|
|
var toggle = document.getElementById("mobile-menu-toggle");
|
|
var dialog = document.getElementById("mobile-menu-dialog");
|
|
if (!toggle || !dialog) return;
|
|
document.addEventListener("click", function (event) {
|
|
if (!toggle.checked || event.target === toggle) return;
|
|
if (dialog.contains(event.target)) return;
|
|
if (event.target.closest('label[for="mobile-menu-toggle"]')) return;
|
|
toggle.checked = false;
|
|
syncCheckboxLabels(toggle);
|
|
});
|
|
}
|
|
|
|
function setupDesktopDropdowns() {
|
|
document.querySelectorAll(".nested-menu").forEach(function (menu) {
|
|
var trigger = menu.querySelector("[aria-haspopup]");
|
|
if (!trigger) return;
|
|
var setExpanded = function (expanded) {
|
|
trigger.setAttribute("aria-expanded", expanded ? "true" : "false");
|
|
};
|
|
menu.addEventListener("mouseenter", function () {
|
|
setExpanded(true);
|
|
});
|
|
menu.addEventListener("mouseleave", function () {
|
|
setExpanded(false);
|
|
});
|
|
menu.addEventListener("focusin", function () {
|
|
setExpanded(true);
|
|
});
|
|
menu.addEventListener("focusout", function (event) {
|
|
if (!menu.contains(event.relatedTarget)) setExpanded(false);
|
|
});
|
|
menu.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape") {
|
|
if (document.activeElement && menu.contains(document.activeElement)) {
|
|
document.activeElement.blur();
|
|
}
|
|
setExpanded(false);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
setupCheckboxLabels();
|
|
setupMobileMenuEscape();
|
|
setupMobileMenuOutsideClick();
|
|
setupDesktopDropdowns();
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|