PageSourceSearch

https://iqlandia.cz/packages/iqlandia/blocks/autonav/view.js?ccm_n…=65306614fea9262a62c7c9f03b4c34c5d4966e1b

js iqlandia.cz collected 2026-09-24 22:28:20 UTC 20,937 bytes, 627 lines download raw bytes

1(function () {
2  const autonavBP = matchMedia("(min-width: 1440px)");
3  const autonavSemiBP = matchMedia("(min-width: 840px)");
4  let layout = "desktop";
5  let hamburgerActive = false;
6  let $cartLink = null;
7  let $cartCount = null;
8  let $hamburger = null;
9  let $searchToggle = null;
10  let $searchDropdown = null;
11  let trapFocus = false;
12  let $autonav, $topBar, $hamburgerMenu, $dropdownToggles, $main;
13
14  window.addEventListener("DOMContentLoaded", init);
15
16  function init() {
17    // Cache DOM queries
18    const $wrapper = document.querySelector(".autonav-wrapper");
19    $autonav = $wrapper.querySelector(".autonav");
20    $topBar = $autonav.querySelector(".autonav-top-bar");
21    $hamburgerMenu = $autonav.querySelector("#autonav-page-list");
22    $dropdownToggles = $hamburgerMenu.querySelectorAll(
23      ".autonav-dropdown-toggle",
24    );
25    $main = document.querySelector("#main");
26
27    // Use requestAnimationFrame for layout-related operations
28    requestAnimationFrame(() => {
29      initMarkup();
30      initHamburger();
31      initDropdowns();
32      initDropdownSubemnus();
33      trapFocusInit($autonav);
34      buildLayout();
35      forceSubDropdownsOpen();
36    });
37
38    // Use passive event listeners where possible
39    autonavBP.addEventListener("change", buildLayout, { passive: true });
40    autonavSemiBP.addEventListener("change", buildLayout, { passive: true });
41    autonavSemiBP.addEventListener("change", forceSubDropdownsOpen, {
42      passive: true,
43    });
44
45    document.querySelector("#autonav-page-list")?.classList.remove("hidden");
46  }
47
48  function initMarkup() {
49    const $home = $autonav.querySelector(".autonav-home");
50    const $homeLink = $home.querySelector("a");
51    $homeLink.classList.add(..."inline-block mr-16 pl-0".split(" "));
52    $topBar.append($homeLink);
53    $home.remove();
54
55    const $tickets = $autonav.querySelector(".autonav-tickets");
56    if ($tickets !== null) {
57      const $ticketsLink = $tickets.querySelector("a");
58      // Store the original text content immediately
59      window.__ticketsText = $tickets.innerText;
60      
61      $ticketsLink.setAttribute(
62        "class",
63        "autonav-tickets-link button button--primary max-[420px]:hidden pl-20",
64      );
65      $ticketsLink.setAttribute("title", window.__ticketsText);
66      const icon = $ticketsLink.querySelector("svg");
67      
68      $ticketsLink.innerHTML = `${
69        icon !== null ? icon.outerHTML : ""
70      }<span class="max-[580px]:sr-only">${window.__ticketsText}</span>`;
71      // Store tickets link for later use
72      window.__ticketsLink = $ticketsLink;
73      appendToTopBar($tickets);
74    }
75
76    const $cart = $autonav.querySelector(".autonav-cart");
77    if ($cart !== null) {
78      $cartLink = $cart.querySelector("a");
79      $cartLink.setAttribute(
80        "class",
81        "autonav-cart-link inline-flex gap-8 py-12 pl-16 pr-20 font-bold no-underline bg-white text-black hover:bg-white/90 focus:bg-white/90",
82      );
83      $cartLink.hidden = true;
84      $cartLink.setAttribute("title", $cartLink.innerText);
85      $cartLink.innerHTML =
86        `<span class="sr-only">${$cartLink.innerHTML}</span>` +
87        '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32"><circle cx="10" cy="28" r="2" fill="currentColor"/><circle cx="24" cy="28" r="2" fill="currentColor"/><path fill="currentColor" d="M28 7H5.82L5 2.8A1 1 0 0 0 4 2H0v2h3.18L7 23.2a1 1 0 0 0 1 .8h18v-2H8.82L8 18h18a1 1 0 0 0 1-.78l2-9A1 1 0 0 0 28 7m-2.8 9H7.62l-1.4-7h20.53Z"/></svg>'; // carbon:cart
88      appendToTopBar($cart);
89      initCartCount();
90    }
91
92    initSearchDropdown();
93  }
94
95  function initCartCount() {
96    $cartCount = document.createElement("span");
97    $cartLink.append($cartCount);
98
99    window.__updateCartCount = (count = null) => {
100      const displayCount =
101        count !== null
102          ? count
103          : (JSON.parse(localStorage.getItem("tickets")) ?? []).length;
104
105      if (displayCount) {
106        $cartLink.hidden = false;
107        $cartCount.innerHTML = `${displayCount} <abbr class="no-underline" title="${window.__i18n.autonav.pieces}">${window.__i18n.autonav.pcs}</abbr>`;
108        
109        // Hide tickets label when cart is visible (desktop only)
110        if (window.__ticketsLink && autonavBP.matches) {
111          const $textSpan = window.__ticketsLink.querySelector("span");
112          if ($textSpan) {
113            $textSpan.classList.add("cart-visible-hide"); // Custom class for cart-based hiding
114          }
115        }
116      } else {
117        $cartLink.hidden = true;
118        $cartCount.innerHTML = "";
119        
120        // Show tickets label when cart is hidden (desktop only)
121        if (window.__ticketsLink && autonavBP.matches) {
122          const $textSpan = window.__ticketsLink.querySelector("span");
123          if ($textSpan) {
124            $textSpan.classList.remove("cart-visible-hide"); // Remove our custom class
125          }
126        }
127      }
128      return displayCount;
129    };
130    window.__updateCartCount();
131  }
132
133  function initDropdowns() {
134    const handleDropdownToggle = (e) => {
135      e.preventDefault();
136      toggleDropdown(e.currentTarget);
137    };
138
139    $dropdownToggles.forEach(($toggle) => {
140      $toggle.addEventListener("click", handleDropdownToggle);
141      setDropdownExpanded($toggle, false);
142      $toggle.disabled = false;
143    });
144
145    // Use event delegation for body click and keydown events
146    document.body.addEventListener("click", handleBodyClick);
147    document.body.addEventListener("keydown", handleBodyKeydown);
148  }
149
150  function handleBodyClick(e) {
151    if (e.target.closest(".autonav") === null) {
152      collapseAllTopBarDropdowns();
153    }
154  }
155
156  function handleBodyKeydown(e) {
157    if (e.key == "Escape") {
158      if (hamburgerActive) {
159        if (document.activeElement.closest("#autonav-page-list") !== null) {
160          $hamburger.focus();
161        }
162        setHamburgerExpanded(false);
163      } else {
164        const closestDropdown = document.activeElement.closest(
165          '[id^="autonav-dropdown-"][data-level="1"]',
166        );
167        if (closestDropdown !== null) {
168          closestDropdown.previousElementSibling.focus();
169        }
170        collapseAllTopBarDropdowns();
171      }
172    }
173  }
174
175  function toggleDropdown($toggle) {
176    const expand = $toggle.getAttribute("aria-expanded") == "false";
177    if (
178      $toggle.getAttribute("data-level") == "1" &&
179      $toggle.closest(".autonav-top-bar") !== null
180    ) {
181      collapseAllTopBarDropdowns();
182
183      if (expand) {
184        trapFocus = true;
185        blurBackground(true);
186      }
187    }
188
189    // Mobile hamburger: only one top-level section open at a time (accordion).
190    if (
191      !autonavSemiBP.matches &&
192      expand &&
193      $toggle.getAttribute("data-level") == "1" &&
194      $hamburgerMenu.contains($toggle)
195    ) {
196      collapseMobileHamburgerLevel1DropdownsExcept($toggle);
197    }
198
199    setDropdownExpanded($toggle, expand);
200  }
201
202  function setDropdownExpanded($toggle, expanded) {
203    $toggle.setAttribute("aria-expanded", expanded ? "true" : "false");
204    document.querySelector(`#${$toggle.getAttribute("aria-controls")}`).hidden =
205      !expanded;
206  }
207
208  function collapseAllTopBarDropdowns() {
209    for (const $toggle of $dropdownToggles) {
210      if (
211        $toggle.closest(".autonav-top-bar") !== null &&
212        $toggle.getAttribute("data-level") == "1"
213      ) {
214        setDropdownExpanded($toggle, false);
215      }
216    }
217
218    const $search = $autonav.querySelector(".autonav-top-bar .autonav-search");
219    if ($search !== null) {
220      $searchToggle.setAttribute("aria-expanded", "false");
221      $searchDropdown.hidden = true;
222    }
223
224    setHamburgerExpanded(false);
225
226    trapFocus = false;
227    blurBackground(false);
228  }
229
230  function blurBackground(blur) {
231    const $footer = document.querySelector("#footer-areas");
232    if (blur) {
233      $main.classList.add("blur", "pointer-events-none");
234      if ($footer !== null) {
235        $footer.classList.add("blur", "pointer-events-none");
236      }
237    } else {
238      $main.classList.remove("blur", "pointer-events-none");
239      if ($footer !== null) {
240        $footer.classList.remove("blur", "pointer-events-none");
241      }
242    }
243  }
244
245  function initDropdownSubemnus() {
246    for (const $submenu of $hamburgerMenu.querySelectorAll(
247      ".autonav-dropdown-submenu",
248    ) ?? []) {
249      const $list = $submenu.querySelector(".autonav-dropdown-submenu-list");
250      const items = $list.children.length;
251      $submenu.classList.add(
252        ...`autonav:row-span-${
253          items + 1
254        } autonav:grid autonav:grid-cols-subgrid autonav:grid-rows-subgrid`.split(
255          " ",
256        ),
257      );
258      $list.classList.add(
259        ...`autonav:row-span-${items} autonav:grid autonav:grid-cols-subgrid autonav:grid-rows-subgrid`.split(
260          " ",
261        ),
262      );
263    }
264  }
265
266  function buildLayout() {
267    const $topLevelMenuItems = $autonav.querySelectorAll(
268      ".autonav-top-level-menu-item",
269    );
270    const $langSwitch = $autonav.querySelector(".autonav-switch-language");
271    const $search = $autonav.querySelector(".autonav-search");
272    const $tickets = $autonav.querySelector(".autonav-tickets");
273    const $userAccount = $autonav.querySelector(".autonav-user-account");
274
275    // Clean up any existing tickets in hamburger menu
276    const $existingTickets = $hamburgerMenu.querySelector(".autonav-tickets");
277    if ($existingTickets) {
278      $existingTickets.remove();
279    }
280
281    // Clean up any existing user account in hamburger menu
282    const $existingUserAccount = $hamburgerMenu.querySelector(".autonav-user-account");
283    if ($existingUserAccount) {
284      $existingUserAccount.remove();
285    }
286
287    if (document.activeElement.closest(".autonav") !== null) {
288      $autonav.focus();
289    }
290
291    if (autonavBP.matches) {
292      for (const $item of $topLevelMenuItems) {
293        appendToTopBar($item);
294      }
295      appendToTopBar($tickets);
296      appendToTopBar($autonav.querySelector(".autonav-cart"));
297      appendToTopBar($langSwitch);
298      appendToTopBar($search);
299      appendToTopBar($userAccount);
300      displaySearchToggle(true);
301      $hamburger.hidden = true;
302    } else {
303      // For both semi-desktop and mobile layouts
304      if (autonavSemiBP.matches) {
305        appendToTopBar($langSwitch);
306        appendToTopBar($search);
307        appendToTopBar($userAccount);
308        displaySearchToggle(true);
309      } else {
310        appendToHamburgerMenu($search);
311        displaySearchToggle(false);
312      }
313
314      for (const $item of $topLevelMenuItems) {
315        appendToHamburgerMenu($item);
316      }
317
318      if (!autonavSemiBP.matches) {
319        appendToHamburgerMenu($langSwitch);
320        appendToHamburgerMenu($userAccount);
321      }
322
323
324      // Add tickets button to hamburger menu
325      if ($tickets) {
326        const $ticketsClone = $tickets.cloneNode(true);
327        $ticketsClone.classList.remove("ml-auto");
328        const $ticketsLink = $ticketsClone.querySelector("a");
329        if ($ticketsLink) {
330          $ticketsLink.classList.remove("max-[420px]:hidden");
331          $ticketsLink.classList.add("w-full", "justify-center", "my-16");
332          const $textSpan = $ticketsLink.querySelector("span");
333          if ($textSpan) {
334            $textSpan.classList.remove("max-[580px]:sr-only");
335            // Use the stored text content
336            $textSpan.innerHTML = window.__ticketsText;
337          }
338        }
339        appendToHamburgerMenu($ticketsClone);
340      }
341
342      $topBar.append($hamburger);
343      $hamburger.hidden = false;
344    }
345
346    collapseAllTopBarDropdowns();
347
348    // Keep the first mobile section (visit) expanded by default.
349    ensureMobileVisitDefaultOpen();
350    
351    // Update tickets label visibility based on cart state and screen size
352    updateTicketsLabelVisibility();
353  }
354
355  function setHamburgerExpanded(expanded) {
356    $hamburger.setAttribute("aria-expanded", expanded);
357    $hamburgerMenu.hidden = !expanded;
358    if (expanded) {
359      ensureMobileVisitDefaultOpen();
360    }
361  }
362
363  function initHamburger() {
364    const $label = $autonav.querySelector("#autonav-label");
365    $hamburger = document.createElement("button");
366    $hamburger.classList.add("autonav-hamburger");
367    $hamburger.setAttribute("type", "button");
368    $hamburger.setAttribute("aria-expanded", "false");
369    $hamburger.setAttribute("aria-controls", "autonav-page-list");
370    $hamburger.setAttribute("title", $label.innerText);
371    $hamburger.innerHTML =
372      `<span>${$label.innerHTML}</span>` +
373      '<svg class="autonav-hamburger-icon" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 32 32"><path fill="currentColor" d="M4 6h24v2H4zm0 18h24v2H4zm0-12h24v2H4zm0 6h24v2H4z"/></svg>' + // carbon:menu
374      '<svg class="autonav-hamburger-icon is-close" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 32 32"><path fill="currentColor" d="M17.414 16L24 9.414L22.586 8L16 14.586L9.414 8L8 9.414L14.586 16L8 22.586L9.414 24L16 17.414L22.586 24L24 22.586z"/></svg>'; // carbon:close
375    $hamburger.addEventListener("click", (e) => {
376      e.preventDefault();
377      const expand = $hamburger.getAttribute("aria-expanded") == "false";
378      collapseAllTopBarDropdowns();
379      setHamburgerExpanded(expand);
380    });
381
382    $topBar.append($hamburger);
383    $hamburgerMenu.hidden = true;
384  }
385
386  function initSearchDropdown() {
387    $searchDropdown = $autonav.querySelector(".autonav-search search");
388    $searchToggle = document.createElement("button");
389    $searchToggle.classList.add(
390      ..."mb-1 flex items-center px-12 py-28 aria-expanded:bg-gray-10 hover:bg-gray-10 focus:bg-gray-10".split(
391        " ",
392      ),
393    );
394    $searchToggle.setAttribute("type", "button");
395    $searchToggle.setAttribute("aria-expanded", "false");
396    $searchToggle.setAttribute(
397      "aria-controls",
398      $searchDropdown.getAttribute("id"),
399    );
400    $searchToggle.setAttribute("title", window.__i18n?.autonav.search);
401    $searchToggle.innerHTML =
402      `<span class="sr-only">${window.__i18n?.autonav.search}</span>` +
403      '<svg aria-hidden="true" width="24" height="24" viewBox="0 0 32 32">
403<path fill="currentColor" d="m29 27.586l-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9a9.01 9.01 0 0 1-9-9"/></svg>'; // carbon:search
404    $searchToggle.addEventListener("click", (e) => {
405      e.preventDefault();
406      const expand = $searchDropdown.hidden;
407      if (expand) {
408        collapseAllTopBarDropdowns();
409      }
410      trapFocus = expand;
411      blurBackground(expand);
412      $searchToggle.setAttribute("aria-expanded", expand ? "true" : "false");
413      $searchDropdown.hidden = !expand;
414    });
415    $searchDropdown.hidden = true;
416    $searchDropdown.before($searchToggle);
417  }
418
419  function displaySearchToggle(displayToggle) {
420    $searchToggle.hidden = !displayToggle;
421    if (!displayToggle) {
422      $searchDropdown.hidden = false;
423    }
424  }
425
426  function appendToTopBar($old) {
427    const $new = document.createElement("div");
428    if ($old.hasAttribute("class")) {
429      $new.setAttribute("class", $old.getAttribute("class"));
430    }
431    while ($old.children.length > 0) {
432      $new.insertBefore($old.children[0], null);
433    }
434    $topBar.append($new);
435    $old.remove();
436  }
437
438  function appendToHamburgerMenu($old) {
439    const $new = document.createElement("li");
440    if ($old.hasAttribute("class")) {
441      $new.setAttribute("class", $old.getAttribute("class"));
442    }
443    while ($old.children.length > 0) {
444      $new.insertBefore($old.children[0], null);
445    }
446    $hamburgerMenu.append($new);
447    $old.remove();
448  }
449
450  function updateTicketsLabelVisibility() {
451    if (window.__ticketsLink) {
452      const $textSpan = window.__ticketsLink.querySelector("span");
453      if ($textSpan) {
454        // Only manage our cart-based hiding on desktop
455        const cartVisible = !$cartLink.hidden;
456        const isDesktop = autonavBP.matches;
457        
458        if (cartVisible && isDesktop) {
459          $textSpan.classList.add("cart-visible-hide"); // Custom class for cart-based hiding
460        } else if (isDesktop) {
461          $textSpan.classList.remove("cart-visible-hide"); // Remove our custom class
462        }
463        // On mobile, do nothing - let the existing responsive behavior work
464      }
465    }
466  }
467
468  function forceSubDropdownsOpen() {
469    for (const $toggle of $dropdownToggles) {
470      if (autonavSemiBP.matches && $toggle.getAttribute("data-level") != "1") {
471        setDropdownExpanded($toggle, true);
472        $toggle.disabled = true;
473      } else {
474        setDropdownExpanded($toggle, false);
475        $toggle.disabled = false;
476      }
477    }
478  }
479
480  function collapseMobileHamburgerLevel1DropdownsExcept($exceptToggle) {
481    if (autonavSemiBP.matches) {
482      return;
483    }
484    for (const $t of $dropdownToggles) {
485      if (
486        $t.getAttribute("data-level") == "1" &&
487        $hamburgerMenu.contains($t) &&
488        $t !== $exceptToggle
489      ) {
490        setDropdownExpanded($t, false);
491      }
492    }
493  }
494
495  function ensureMobileVisitDefaultOpen() {
496    if (autonavSemiBP.matches) {
497      return;
498    }
499
500    const $visitDropdown = $hamburgerMenu.querySelector(
501      ".autonav-dropdown.is-visit",
502    );
503    if (!$visitDropdown) {
504      return;
505    }
506
507    const visitDropdownId = $visitDropdown.getAttribute("id");
508    if (!visitDropdownId) {
509      return;
510    }
511
512    const $visitToggle = $hamburgerMenu.querySelector(
513      `.autonav-dropdown-toggle[data-level="1"][aria-controls="${visitDropdownId}"]`,
514    );
515    if (!$visitToggle) {
516      return;
517    }
518
519    collapseMobileHamburgerLevel1DropdownsExcept($visitToggle);
520    setDropdownExpanded($visitToggle, true);
521  }
522
523  /**
524   * https://github.com/jashkenas/underscore/blob/master/modules/debounce.js
525   */
526  function debounce(func, wait, immediate) {
527    function restArguments(func, startIndex) {
528      startIndex = startIndex == null ? func.length - 1 : +startIndex;
529      return function () {
530        var length = Math.max(arguments.length - startIndex, 0),
531          rest = Array(length),
532          index = 0;
533        for (; index < length; index++) {
534          rest[index] = arguments[index + startIndex];
535        }
536        switch (startIndex) {
537          case 0:
538            return func.call(this, rest);
539          case 1:
540            return func.call(this, arguments[0], rest);
541          case 2:
542            return func.call(this, arguments[0], arguments[1], rest);
543        }
544        var args = Array(startIndex + 1);
545        for (index = 0; index < startIndex; index++) {
546          args[index] = arguments[index];
547        }
548        args[startIndex] = rest;
549        return func.apply(this, args);
550      };
551    }
552
553    var timeout, previous, args, result, context;
554
555    var later = function () {
556      var passed = Date.now() - previous;
557      if (wait > passed) {
558        timeout = setTimeout(later, wait - passed);
559      } else {
560        timeout = null;
561        if (!immediate) result = func.apply(context, args);
562        // This check is needed because `func` can recursively invoke `debounced`.
563        if (!timeout) args = context = null;
564      }
565    };
566
567    var debounced = restArguments(function (_args) {
568      context = this;
569      args = _args;
570      previous = Date.now();
571      if (!timeout) {
572        timeout = setTimeout(later, wait);
573        if (immediate) result = func.apply(context, args);
574      }
575      return result;
576    });
577
578    debounced.cancel = function () {
579      clearTimeout(timeout);
580      timeout = args = context = null;
581    };
582
583    return debounced;
584  }
585
586  /**
587   * Trap focus (pressing tab) inside `$element`.
588   */
589  function trapFocusInit($element) {
590    // all focusable elements (https://classroom.udacity.com/courses/ud891/lessons/7962031279/concepts/79621414230923)
591    const focusableSelector =
592      'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';
593
594    function focusOverride(e) {
595      if (!trapFocus) return;
596
597      if (e.keyCode === 9) {
598        // Tab
599        // DOM could be updated, check new elements every time
600        let $focusable = $element.querySelectorAll(focusableSelector);
601        $focusable = Array.from($focusable).filter((node) => {
602          if (node.closest("[hidden]") !== null) return false;
603          return (
604            node.getAttribute("tabindex") !== null ||
605            node.getAttribute("tabindex") >= 0
606          );
607        });
608
609        // last -> first
610        if (e.target == $focusable[$focusable.length - 1] && !e.shiftKey) {
611          e.preventDefault();
612          $focusable[0].focus();
613        }
614        // first -> last
615        else if (e.target == $focusable[0] && e.shiftKey) {
616          e.preventDefault();
617          $focusable[$focusable.length - 1].focus();
618        }
619      }
620    }
621
622    $element.addEventListener("keydown", focusOverride);
623    return () => {
624      $element.removeEventListener("keydown", focusOverride);
625    };
626  }
627})();

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.