PageSourceSearch

https://inravision.gov.co/themes/custom/rtvc/js/header.js?tlqa2m

js inravision.gov.co collected 2026-10-02 21:36:33 UTC 6,416 bytes, 168 lines download raw bytes

1/**
2 * @file
3 * Header RTVC — menú hamburguesa mobile.
4 */
5
6(function (Drupal, once) {
7  'use strict';
8
9  Drupal.behaviors.headerRTVC = {
10    attach: function (context) {
11
12      var elements = once('header-rtvc-init', '#hamburgerBtn', context);
13      if (!elements.length) return;
14
15      var hamburgerBtn = elements[0];
16      var mobilePanel  = document.getElementById('mobileMenuPanel');
17      var overlay      = document.getElementById('mobileMenuOverlay');
18
19      if (!mobilePanel || !overlay) return;
20
21      /* ── Abrir / cerrar ─────────────────────────────────────── */
22      function openMenu() {
23        mobilePanel.classList.add('is-open');
24        overlay.classList.add('is-open');
25        hamburgerBtn.classList.add('is-open');
26        hamburgerBtn.setAttribute('aria-expanded', 'true');
27        mobilePanel.setAttribute('aria-hidden', 'false');
28        document.body.style.overflow = 'hidden';
29        // WCAG: mover focus al primer enlace del menú
30        var firstLink = mobilePanel.querySelector('.nav-link, .dropdown-item, a');
31        if (firstLink) firstLink.focus();
32      }
33
34      function closeMenu() {
35        mobilePanel.classList.remove('is-open');
36        overlay.classList.remove('is-open');
37        hamburgerBtn.classList.remove('is-open');
38        hamburgerBtn.setAttribute('aria-expanded', 'false');
39        mobilePanel.setAttribute('aria-hidden', 'true');
40        document.body.style.overflow = '';
41      }
42
43      /* Clic en hamburguesa */
44      hamburgerBtn.addEventListener('click', function (e) {
45        e.stopPropagation();
46        mobilePanel.classList.contains('is-open') ? closeMenu() : openMenu();
47      });
48
49      /* Clic en overlay */
50      overlay.addEventListener('click', closeMenu);
51
52      /* Tecla Escape */
53      document.addEventListener('keydown', function (e) {
54        if (e.key === 'Escape' && mobilePanel.classList.contains('is-open')) {
55          closeMenu();
56          hamburgerBtn.focus();
57        }
58      });
59
60      /* ── Inyectar botones toggle separados para submenús ──── */
61      mobilePanel.querySelectorAll('.dropdown-toggle').forEach(function (link) {
62        // Quitar clase dropdown-toggle del <a> para que funcione como link normal
63        link.classList.remove('dropdown-toggle');
64        link.removeAttribute('aria-expanded');
65
66        // Crear botón chevron separado
67        var toggleBtn = document.createElement('button');
68        toggleBtn.className = 'mobile-submenu-toggle';
69        toggleBtn.setAttribute('aria-label', 'Expandir submenú');
70        toggleBtn.setAttribute('aria-expanded', 'false');
71        toggleBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>';
72
73        // Insertar el botón después del link
74        link.parentNode.insertBefore(toggleBtn, link.nextSibling);
75      });
76
77      /* ── Marcar ítem activo + auto-expandir dropdowns ──── */
78      var currentPath = window.location.pathname;
79      mobilePanel.querySelectorAll('.navbar-nav > li').forEach(function (li) {
80        // Buscar el link más específico (más largo) que coincida con la URL
81        var matchedLink = null;
82        var matchLength = 0;
83        li.querySelectorAll('a').forEach(function (link) {
84          var href = link.getAttribute('href') || '';
85          if (!href || href === '/' || href === '#') return;
86          if ((currentPath === href || currentPath.startsWith(href + '/')) && href.length > matchLength) {
87            matchedLink = link;
88            matchLength = href.length;
89          }
90        });
91
92        if (!matchedLink) return;
93
94        // Marcar el li de primer nivel como activo
95        li.classList.add('is-active');
96
97        // Marcar el sub-ítem coincidente
98        var matchedLi = matchedLink.closest('li');
99        if (matchedLi && matchedLi !== li) {
100          matchedLi.classList.add('is-active-item');
101        }
102
103        // Subir por el árbol y abrir todos los dropdowns en el camino (botones ya inyectados)
104        var node = matchedLink.parentElement;
105        while (node && node !== li) {
106          if (node.classList.contains('dropdown-menu')) {
107            node.classList.add('show');
108            var prev = node.previousElementSibling;
109            if (prev && prev.classList.contains('mobile-submenu-toggle')) {
110              prev.setAttribute('aria-expanded', 'true');
111            }
112          }
113          node = node.parentElement;
114        }
115      });
116
117      /* ── Submenús: click solo en el botón toggle ──── */
118      mobilePanel.addEventListener('click', function (e) {
119        var toggleBtn = e.target.closest('.mobile-submenu-toggle');
120        if (!toggleBtn) return;
121
122        e.preventDefault();
123        e.stopPropagation();
124
125        var dropdownMenu = toggleBtn.nextElementSibling;
126        if (!dropdownMenu || !dropdownMenu.classList.contains('dropdown-menu')) return;
127
128        var isOpen = dropdownMenu.classList.contains('show');
129
130        // Cerrar hermanos abiertos al mismo nivel
131        var parentUl = toggleBtn.closest('ul');
132        if (parentUl) {
133          parentUl.querySelectorAll(':scope > li > .dropdown-menu.show').forEach(function (m) {
134            if (m !== dropdownMenu) {
135              m.classList.remove('show');
136              var siblingBtn = m.previousElementSibling;
137              if (siblingBtn && siblingBtn.classList.contains('mobile-submenu-toggle')) {
138                siblingBtn.setAttribute('aria-expanded', 'false');
139              }
140            }
141          });
142        }
143
144        dropdownMenu.classList.toggle('show', !isOpen);
145        toggleBtn.setAttribute('aria-expanded', String(!isOpen));
146      });
147
148      /* Cerrar panel al navegar a un enlace */
149      mobilePanel.addEventListener('click', function (e) {
150        var link = e.target.closest('a');
151        if (link) closeMenu();
152      });
153
154      /* Cerrar al redimensionar a desktop */
155      var resizeTimer;
156      window.addEventListener('resize', function () {
157        clearTimeout(resizeTimer);
158        resizeTimer = setTimeout(function () {
159          if (window.innerWidth >= 992 && mobilePanel.classList.contains('is-open')) {
160            closeMenu();
161          }
162        }, 150);
163      });
164
165    }
166  };
167
168})(Drupal, once);

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.