1/** 2 * AI Translate Language Switcher Shortcode JavaScript 3 * Handles dropdown toggle and accessibility features 4 */ 5(function() { 6 'use strict'; 7 8 // Initialize when DOM is ready 9 document.addEventListener('DOMContentLoaded', function() { 10 initLanguageSwitchers(); 11 }); 12 13 /** 14 * Initialize all language switcher dropdowns 15 */ 16 function initLanguageSwitchers() { 17 const switchers = document.querySelectorAll('.ai-language-switcher-dropdown'); 18 19 switchers.forEach(function(switcher) { 20 const button = switcher.querySelector('.ai-language-switcher-btn'); 21 const menu = switcher.querySelector('.ai-language-switcher-menu'); 22 23 if (!button || !menu) { 24 return; 25 } 26 27 // Toggle dropdown on button click 28 button.addEventListener('click', function(event) { 29 event.preventDefault(); 30 event.stopPropagation(); 31 toggleDropdown(switcher); 32 }); 33 34 // Handle keyboard navigation 35 button.addEventListener('keydown', function(event) { 36 handleButtonKeydown(event, switcher); 37 }); 38 39 // Handle menu item keydown 40 const menuItems = menu.querySelectorAll('.ai-language-item'); 41 menuItems.forEach(function(item, index) { 42 item.addEventListener('keydown', function(event) { 43 handleMenuItemKeydown(event, switcher, index, menuItems); 44 }); 45 }); 46 }); 47 48 // Close dropdowns when clicking outside 49 document.addEventListener('click', function(event) { 50 closeAllDropdowns(); 51 }); 52 53 // Close dropdowns on escape key 54 document.addEventListener('keydown', function(event) { 55 if (event.key === 'Escape') { 56 closeAllDropdowns(); 57 } 58 }); 59 } 60 61 /** 62 * Toggle dropdown visibility 63 */ 64 function toggleDropdown(switcher) { 65 const button = switcher.querySelector('.ai-language-switcher-btn'); 66 const menu = switcher.querySelector('.ai-language-switcher-menu'); 67 const isExpanded = button.getAttribute('aria-expanded') === 'true'; 68 69 if (isExpanded) { 70 closeDropdown(switcher); 71 } else { 72 // Close other dropdowns first 73 closeAllDropdowns(); 74 openDropdown(switcher); 75 } 76 } 77 78 /** 79 * Open a dropdown 80 */ 81 function openDropdown(switcher) { 82 const button = switcher.querySelector('.ai-language-switcher-btn'); 83 const menu = switcher.querySelector('.ai-language-switcher-menu'); 84 85 button.setAttribute('aria-expanded', 'true'); 86 menu.removeAttribute('hidden'); 87 88 // Focus first menu item for keyboard navigation 89 const firstItem = menu.querySelector('.ai-language-item'); 90 if (firstItem) { 91 firstItem.focus(); 92 } 93 } 94 95 /** 96 * Close a dropdown 97 */ 98 function closeDropdown(switcher) { 99 const button = switcher.querySelector('.ai-language-switcher-btn'); 100 const menu = switcher.querySelector('.ai-language-switcher-menu'); 101 102 button.setAttribute('aria-expanded', 'false'); 103 menu.setAttribute('hidden', ''); 104 105 // Return focus to button 106 button.focus(); 107 } 108 109 /** 110 * Close all dropdowns 111 */ 112 function closeAllDropdowns() { 113 const openSwitchers = document.querySelectorAll('.ai-language-switcher-dropdown .ai-language-switcher-btn[aria-expanded="true"]'); 114 115 openSwitchers.forEach(function(button) { 116 const switcher = button.closest('.ai-language-switcher-dropdown'); 117 if (switcher) { 118 closeDropdown(switcher); 119 } 120 }); 121 } 122 123 /** 124 * Handle button keyboard events 125 */ 126 function handleButtonKeydown(event, switcher) { 127 const button = switcher.querySelector('.ai-language-switcher-btn'); 128 129 switch (event.key) { 130 case 'ArrowDown': 131 case 'Enter': 132 case ' ': 133 event.preventDefault(); 134 toggleDropdown(switcher); 135 break; 136 case 'ArrowUp': 137 event.preventDefault(); 138 // Open dropdown and focus last item 139 closeAllDropdowns(); 140 openDropdown(switcher); 141 const menu = switcher.querySelector('.ai-language-switcher-menu'); 142 const lastItem = menu.querySelector('.ai-language-item:last-child'); 143 if (lastItem) { 144 lastItem.focus(); 145 } 146 break; 147 } 148 } 149 150 /** 151 * Handle menu item keyboard events 152 */ 153 function handleMenuItemKeydown(event, switcher, currentIndex, menuItems) { 154 const menu = switcher.querySelector('.ai-language-switcher-menu'); 155 156 switch (event.key) { 157 case 'ArrowDown': 158 event.preventDefault(); 159 const nextIndex = Math.min(currentIndex + 1, menuItems.length - 1); 160 menuItems[nextIndex].focus(); 161 break; 162 case 'ArrowUp': 163 event.preventDefault(); 164 const prevIndex = Math.max(currentIndex - 1, 0); 165 menuItems[prevIndex].focus(); 166 break; 167 case 'Home': 168 event.preventDefault(); 169 menuItems[0].focus(); 170 break; 171 case 'End': 172 event.preventDefault(); 173 menuItems[menuItems.length - 1].focus(); 174 break; 175 case 'Escape': 176 event.preventDefault(); 177 closeDropdown(switcher); 178 break; 179 case 'Enter': 180 case ' ': 181 // Let the link handle the navigation 182 break; 183 } 184 } 185 186 // Mobile menu support - toggle submenus on click/tap
187 initMobileMenuSupport(); 188})(); 189 190/** 191 * Initialize mobile menu support for language switcher submenus 192 */ 193function initMobileMenuSupport() { 194 let attempts = 0; 195 const maxAttempts = 10; 196 197 function bindMobileSwitchers() { 198 // Find language switcher menu items 199 const languageItems = document.querySelectorAll('.menu-item-language-switcher'); 200 let boundCount = 0; 201 202 languageItems.forEach(function(item) { 203 const link = item.querySelector('.ai-menu-language-current'); 204 const submenu = item.querySelector('.sub-menu'); 205 206 // Only handle dropdown items (with submenu), inline items work without JS 207 if (link && submenu) { 208 if (link.getAttribute('data-ai-mobile-bound') === '1') { 209 return; 210 } 211 link.setAttribute('data-ai-mobile-bound', '1'); 212 boundCount++; 213 214 // Add click handler for mobile 215 link.addEventListener('click', function(event) { 216 // Prevent hash-jumps and theme anchor handlers from hijacking this toggle. 217 event.preventDefault(); 218 219 // Use click-to-expand when: (a) touch device (no hover) or (b) narrow viewport (no hover rule <769px). 220 var needsClick = window.matchMedia('(hover: none)').matches || window.innerWidth <= 768; 221 if (needsClick) { 222 event.stopPropagation(); 223 224 // Toggle submenu visibility 225 const isVisible = submenu.classList.contains('ai-force-show'); 226 227 if (isVisible) { 228 submenu.style.display = ''; 229 submenu.style.removeProperty('max-height'); 230 submenu.classList.remove('ai-force-show'); 231 link.setAttribute('aria-expanded', 'false'); 232 } else { 233 // Close other language switcher submenus first 234 document.querySelectorAll('.menu-item-language-switcher .sub-menu').forEach(function(otherSubmenu) { 235 if (otherSubmenu !== submenu) { 236 otherSubmenu.style.display = ''; 237 otherSubmenu.style.removeProperty('max-height'); 238 otherSubmenu.classList.remove('ai-force-show'); 239 const otherLink = otherSubmenu.closest('.menu-item-language-switcher').querySelector('.ai-menu-language-current'); 240 if (otherLink) { 241 otherLink.setAttribute('aria-expanded', 'false'); 242 } 243 } 244 }); 245 246 submenu.style.display = 'block'; 247 submenu.classList.add('ai-force-show'); 248 link.setAttribute('aria-expanded', 'true'); 249 // Override theme's max-height:0 on submenus (many mobile themes use inline max-height:0 !important) 250 submenu.style.setProperty('max-height', '500px', 'important'); 251 } 252 } 253 }); 254 255 // Add ARIA attributes for accessibility 256 link.setAttribute('aria-expanded', 'false'); 257 link.setAttribute('aria-haspopup', 'true'); 258 } 259 }); 260 261 // Retry shortly for themes that render/replace menus after initial load. 262 if (boundCount === 0 && attempts < maxAttempts) { 263 attempts++; 264 setTimeout(bindMobileSwitchers, 300); 265 } 266 } 267 268 bindMobileSwitchers(); 269}
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.