PageSourceSearch

https://enterprise.uac.edu.au/assets/scripts/nav-menus.js

js uac.edu.au collected 2026-10-02 03:28:48 UTC 10,596 bytes, 332 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function () {
2  // Always start with menu closed
3  if (document.body.classList.contains("menu-open")) {
4    document.body.classList.remove("menu-open");
5    console.log("[MobileMenu] Removed menu-open from body on load");
6  }
7  console.log(
8    "[MobileMenu] Initial menu-open:",
9    document.body.classList.contains("menu-open"),
10  );
11  //updateNavClass();
12  const menuBtn = document.querySelector(".search-toggle-btn-mobile");
13  const menuCloseBtn = document.querySelector(".mobile-menu-close-btn");
14  const mobileMenu = document.getElementById("mobileMenu");
15
16  function openMobileMenu() {
17    document.body.classList.add("menu-open");
18    menuBtn.setAttribute("aria-expanded", "true");
19
20    // Trigger SVG animation
21    const startSvg = menuBtn.querySelector("#start");
22    if (startSvg && startSvg.beginElement) {
23      startSvg.beginElement();
24    }
25
26    // Move focus to close button after menu opens
27    setTimeout(() => {
28      if (menuCloseBtn) {
29        menuCloseBtn.focus();
30      }
31    }, 100);
32  }
33
34  function closeMobileMenu() {
35    document.body.classList.remove("menu-open");
36    menuBtn.setAttribute("aria-expanded", "false");
37
38    // Trigger SVG reverse animation
39    const reverseSvg = menuBtn.querySelector("#reverse");
40    if (reverseSvg && reverseSvg.beginElement) {
41      reverseSvg.beginElement();
42    }
43
44    // Return focus to toggle button
45    menuBtn.focus();
46  }
47
48  if (menuBtn) {
49    menuBtn.addEventListener("click", function (e) {
50      e.preventDefault();
51      const isOpen = document.body.classList.contains("menu-open");
52
53      if (isOpen) {
54        closeMobileMenu();
55      } else {
56        openMobileMenu();
57      }
58    });
59  }
60
61  if (menuCloseBtn) {
62    menuCloseBtn.addEventListener("click", function (e) {
63      e.preventDefault();
64      closeMobileMenu();
65    });
66  }
67
68  // Focus trap for mobile menu
69  if (mobileMenu && menuBtn) {
70    mobileMenu.addEventListener("keydown", function (e) {
71      if (e.key === "Tab" && document.body.classList.contains("menu-open")) {
72        const focusableElements = mobileMenu.querySelectorAll(
73          'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
74        );
75        const focusableArray = Array.from(focusableElements);
76        const firstFocusable = focusableArray[0];
77        const lastFocusable = focusableArray[focusableArray.length - 1];
78
79        if (e.shiftKey) {
80          // Shift + Tab
81          if (document.activeElement === firstFocusable) {
82            e.preventDefault();
83            lastFocusable.focus();
84          }
85        } else {
86          // Tab
87          if (document.activeElement === lastFocusable) {
88            e.preventDefault();
89            firstFocusable.focus();
90          }
91        }
92      }
93    });
94  }
95
96  document.querySelectorAll(".main-menu-dropdown-toggle").forEach((button) => {
97    button.addEventListener("click", function () {
98      const groupItems = this.nextElementSibling;
99
100      // Close all other open dropdowns
101      document.querySelectorAll(".main-menu-dropdown-menu").forEach((menu) => {
102        if (menu !== groupItems) {
103          menu.classList.remove("show");
104          menu.style.top = "";
105          menu.style.left = "";
106        }
107      });
108
109      document.querySelectorAll(".main-menu-dropdown-toggle").forEach((btn) => {
110        if (btn !== this) {
111          btn.classList.remove("active");
112          btn.setAttribute("aria-expanded", "false");
113        }
114      });
115
116      const isOpen = groupItems.classList.toggle("show");
117      this.classList.toggle("active", isOpen);
118      this.setAttribute("aria-expanded", isOpen);
119
120      if (isOpen) {
121        const button = this;
122        const dropdown = groupItems;
123
124        const parent = button.offsetParent; // positioned ancestor
125        const parentWidth = parent.offsetWidth;
126        const buttonLeft = button.offsetLeft;
127        const dropdownWidth = dropdown.offsetWidth;
128
129        let left;
130
131        // Check if it would overflow the right edge of the parent
132        if (buttonLeft + dropdownWidth > parentWidth) {
133          // Shift left so it doesn't overflow
134          left = parentWidth - dropdownWidth - 10; // 10px padding from edge
135          left = Math.max(0, left); // Make sure it doesn't go off the left side
136        } else {
137          // Align to button
138          left = buttonLeft;
139        }
140
141        // Set vertical position relative to document
142        const rect = button.getBoundingClientRect();
143        const scrollTop =
144          window.pageYOffset || document.documentElement.scrollTop;
145        const top = rect.bottom + scrollTop + 10;
146
147        dropdown.style.top = `${top}px`;
148        dropdown.style.left = `${left}px`;
149
150        console.log(
151          `Dropdown position: top ${dropdown.style.top}, left ${dropdown.style.left}`,
152        );
153      }
154    });
155  });
156
157  // Close dropdown if clicking outside
158  document.addEventListener("click", function (e) {
159    document.querySelectorAll(".main-menu-dropdown-menu").forEach((group) => {
160      const toggleButton = group.previousElementSibling;
161
162      if (!toggleButton.contains(e.target) && !group.contains(e.target)) {
163        group.classList.remove("show");
164        toggleButton.classList.remove("active");
165        group.style.top = "";
166        group.style.left = "";
167        toggleButton.setAttribute("aria-expanded", "false");
168      }
169    });
170  });
171
172  // Keyboard support: Escape to close dropdowns and mobile menu
173  document.addEventListener("keydown", function (e) {
174    if (e.key === "Escape") {
175      // Close mobile menu
176      if (document.body.classList.contains("menu-open")) {
177        closeMobileMenu();
178      }
179
180      // Close dropdown menus
181      const openDropdown = document.querySelector(
182        ".main-menu-dropdown-menu.show",
183      );
184      if (openDropdown) {
185        const toggleButton = openDropdown.previousElementSibling;
186        openDropdown.classList.remove("show");
187        toggleButton.classList.remove("active");
188        openDropdown.style.top = "";
189        openDropdown.style.left = "";
190        toggleButton.setAttribute("aria-expanded", "false");
191        toggleButton.focus();
192      }
193    }
194  });
195
196  // Inline search functionality
197  const searchToggleBtns = document.querySelectorAll(".search-toggle-btn");
198  const searchToggleBtnMobile = document.querySelector(
199    ".search-toggle-btn-mobile",
200  );
201  const inlineSearchForm = document.querySelector(".inline-search-form");
202  const inlineSearchInput = document.getElementById("inline-search-input");
203  const inlineSearchClose = document.querySelector(
204    ".inline-search-form .inline-search-close",
205  );
206  const inlineSearchFormMobile = document.querySelector(
207    ".inline-search-form-mobile",
208  );
209  const inlineSearchInputMobile = document.getElementById(
210    "inline-search-input-mobile",
211  );
212  const inlineSearchCloseMobile = document.querySelector(
213    ".inline-search-form-mobile .inline-search-close",
214  );
215  const mainMenuNav = document.querySelector(".main-menu");
216
217  function openInlineSearch() {
218    if (!inlineSearchForm || !mainMenuNav) return;
219
220    // Remove hidden first, then add active class on next frame for smooth transition
221    inlineSearchForm.removeAttribute("hidden");
222    inlineSearchForm.style.display = "flex";
223
224    requestAnimationFrame(() => {
225      requestAnimationFrame(() => {
226        inlineSearchForm.classList.add("active");
227        searchToggleBtns.forEach((btn) =>
228          btn.setAttribute("aria-expanded", "true"),
229        );
230        mainMenuNav.classList.add("search-active");
231      });
232    });
233
234    // Focus on search input after animation starts
235    setTimeout(() => {
236      inlineSearchInput.focus();
237    }, 150);
238  }
239
240  function closeInlineSearch() {
241    if (!inlineSearchForm || !mainMenuNav) return;
242
243    inlineSearchForm.classList.remove("active");
244    searchToggleBtns.forEach((btn) =>
245      btn.setAttribute("aria-expanded", "false"),
246    );
247    mainMenuNav.classList.remove("search-active");
248
249    // Clear input
250    inlineSearchInput.value = "";
251
252    // Wait for transition to complete before hiding
253    setTimeout(() => {
254      inlineSearchForm.setAttribute("hidden", "");
255      inlineSearchForm.style.display = "";
256    }, 400);
257  }
258
259  function openInlineSearchMobile() {
260    if (!inlineSearchFormMobile) return;
261
262    // Remove hidden first, then add active class on next frame for smooth transition
263    inlineSearchFormMobile.removeAttribute("hidden");
264    inlineSearchFormMobile.style.display = "flex";
265
266    requestAnimationFrame(() => {
267      requestAnimationFrame(() => {
268        inlineSearchFormMobile.classList.add("active");
269        searchToggleBtnMobile.setAttribute("aria-expanded", "true");
270      });
271    });
272
273    // Focus on search input after animation starts
274    setTimeout(() => {
275      inlineSearchInputMobile.focus();
276    }, 150);
277  }
278
279  function closeInlineSearchMobile() {
280    if (!inlineSearchFormMobile) return;
281
282    inlineSearchFormMobile.classList.remove("active");
283    searchToggleBtnMobile.setAttribute("aria-expanded", "false");
284
285    // Clear input
286    inlineSearchInputMobile.value = "";
287
288    // Wait for transition to complete before hiding
289    setTimeout(() => {
290      inlineSearchFormMobile.setAttribute("hidden", "");
291      inlineSearchFormMobile.style.display = "";
292    }, 400);
293
294    // Return focus to toggle button
295    searchToggleBtnMobile.focus();
296  }
297
298  // Open search
299  searchToggleBtns.forEach((btn) => {
300    btn.addEventListener("click", openInlineSearch);
301  });
302
303  // Open mobile search
304  if (searchToggleBtnMobile) {
305    searchToggleBtnMobile.addEventListener("click", openInlineSearchMobile);
306  }
307
308  // Close search
309  if (inlineSearchClose) {
310    inlineSearchClose.addEventListener("click", closeInlineSearch);
311  }
312
313  // Close mobile search
314  if (inlineSearchCloseMobile) {
315    inlineSearchCloseMobile.addEventListener("click", closeInlineSearchMobile);
316  }
317
318  // Close on Escape key
319  document.addEventListener("keydown", function (e) {
320    if (e.key === "Escape") {
321      if (inlineSearchForm && !inlineSearchForm.hasAttribute("hidden")) {
322        closeInlineSearch();
323      }
324      if (
325        inlineSearchFormMobile &&
326        !inlineSearchFormMobile.hasAttribute("hidden")
327      ) {
328        closeInlineSearchMobile();
329      }
330    }
331  });
332});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.