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.