返回 DeepSeek-Reasonix
mobile-nav.js
根目录 / site / src / scripts / mobile-nav.js
1 // Shared responsive navigation behavior for the marketing and community headers.
2 export function initMobileNav() {
3 document.querySelectorAll("[data-mobile-nav]").forEach((menu) => {
4 const header = menu.closest(".nav");
5 const toggle = header?.querySelector("[data-mobile-nav-toggle]");
6 const panel = menu.querySelector(".mobile-nav-panel");
7 if (!toggle || !panel) return;
8
9 let closeTimer;
10 let stateToken = 0;
11 let returnFocus = toggle;
12 const focusables = () => Array.from(panel.querySelectorAll(
13 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])',
14 )).filter((element) => !element.closest("[hidden]"));
15
16 const close = (restore = true) => {
17 stateToken += 1;
18 clearTimeout(closeTimer);
19 toggle.setAttribute("aria-expanded", "false");
20 menu.setAttribute("aria-hidden", "true");
21 menu.classList.remove("is-open");
22 document.body.classList.remove("mobile-nav-open");
23 closeTimer = window.setTimeout(() => { menu.hidden = true; }, 220);
24 if (restore) returnFocus.focus();
25 };
26
27 const open = () => {
28 const token = ++stateToken;
29 clearTimeout(closeTimer);
30 returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : toggle;
31 menu.hidden = false;
32 menu.setAttribute("aria-hidden", "false");
33 toggle.setAttribute("aria-expanded", "true");
34 document.body.classList.add("mobile-nav-open");
35 requestAnimationFrame(() => {
36 if (token === stateToken && !menu.hidden) menu.classList.add("is-open");
37 });
38 requestAnimationFrame(() => {
39 if (token === stateToken && !menu.hidden) focusables()[0]?.focus();
40 });
41 };
42
43 toggle.addEventListener("click", () => (menu.hidden ? open() : close()));
44 menu.querySelectorAll("[data-mobile-nav-close]").forEach((button) => button.addEventListener("click", () => close()));
45 menu.querySelectorAll("a[href]").forEach((link) => link.addEventListener("click", () => close(false)));
46 menu.addEventListener("keydown", (event) => {
47 if (event.key === "Escape") {
48 event.preventDefault();
49 close();
50 return;
51 }
52 if (event.key !== "Tab") return;
53 const items = focusables();
54 if (!items.length) return;
55 const first = items[0];
56 const last = items[items.length - 1];
57 if (event.shiftKey && document.activeElement === first) {
58 event.preventDefault();
59 last.focus();
60 } else if (!event.shiftKey && document.activeElement === last) {
61 event.preventDefault();
62 first.focus();
63 }
64 });
65 menu.addEventListener("transitionend", (event) => {
66 if (event.propertyName === "opacity" && !menu.classList.contains("is-open")) menu.hidden = true;
67 });
68 window.addEventListener("resize", () => {
69 if (window.matchMedia("(min-width: 1101px)").matches && !menu.hidden) close(false);
70 }, { passive: true });
71 });
72 }
73
73 lines JAVASCRIPT