1class UnifiedAccessibleMegaMenu { 2 constructor(config = {}) { 3 // ããã©ã«ãè¨å® 4 this.config = { 5 selectors: { 6 globalNav: '.global-nav', 7 header: '.header', 8 mobileMenuToggle: '.mobile-menu-toggle', 9 mobileNavOverlay: '.mobile-nav-overlay', 10 menuOverlay: '.menu-overlay', 11 globalNavSub: '.global-nav-sub', 12 grandchildNav: '.grandchild-nav', 13 megaMenuContent: '.mega-menu-content', 14 megaMenuInner: '.mega-menu-inner', 15 megaMenuSubNav: '.mega-menu-sub-nav', 16 megaMenuGrandchild: '.mega-menu-grandchild', 17 megaMenuMessage: '.mega-menu-message' 18 }, 19 classes: { 20 isOpen: 'is-open', 21 isActive: 'is-active', 22 scrollLock: 'scroll-lock', 23 isIOS: 'is-ios', 24 singleColumn: 'single-column' 25 }, 26 breakpoint: 768, 27 ...config 28 }; 29 30 this.isMobile = false; 31 this.originalScrollPosition = 0; 32 this.isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); 33 this.init(); 34 } 35 36 // ãã«ãã¼ã¡ã½ãã: ã»ã¬ã¯ã¿ãåå¾ 37 getSelector(key) { 38 return this.config.selectors[key]; 39 } 40 41 // ãã«ãã¼ã¡ã½ãã: ã¯ã©ã¹åãåå¾ 42 getClass(key) { 43 return this.config.classes[key]; 44 } 45 46 init() { 47 this.checkDevice(); 48 this.cloneMobileNav(); 49 this.setupEventListeners(); 50 this.setupKeyboardNavigation(); 51 52 window.addEventListener('resize', () => { 53 this.checkDevice(); 54 if (!this.isMobile) { 55 this.closeMobileMenu(); 56 } 57 }); 58 } 59 60 checkDevice() { 61 this.isMobile = window.innerWidth <= this.config.breakpoint; 62 } 63 64 cloneMobileNav() { 65 const originalNav = document.querySelector(this.getSelector('globalNav')); 66 const mobileOverlay = document.querySelector(this.getSelector('mobileNavOverlay')); 67 const clonedNav = originalNav.cloneNode(true); 68 mobileOverlay.appendChild(clonedNav); 69 } 70 71 setupEventListeners() { 72 // Main menu buttons (original and cloned)
73 document.querySelectorAll(`${this.getSelector('globalNav')} > li > button`).forEach(button => { 74 button.addEventListener('click', (e) => { 75 e.preventDefault(); 76 this.handleMainMenuClick(e.target); 77 }); 78 }); 79 80 // Sub menu buttons (original and cloned) 81 document.querySelectorAll(`${this.getSelector('globalNav')} ${this.getSelector('globalNavSub')} > li > button`).forEach(button => { 82 button.addEventListener('click', (e) => { 83 e.preventDefault(); 84 this.handleSubMenuClick(e.target); 85 }); 86 }); 87 88 // Mobile menu toggle 89 const mobileToggle = document.querySelector(this.getSelector('mobileMenuToggle')); 90 if (mobileToggle) { 91 mobileToggle.addEventListener('click', () => { 92 this.toggleMobileMenu(); 93 }); 94 } 95 96 // Close overlays when clicking outside 97 document.querySelector(this.getSelector('menuOverlay')).addEventListener('click', (e) => { 98 if (e.target === e.currentTarget) { 99 this.closeAllPCMenus(); 100 } 101 }); 102 103 // Close menus when clicking outside navigation area 104 document.addEventListener('click', (e) => {
105 const nav = e.target.closest(`${this.getSelector('header')} ${this.getSelector('globalNav')}`); 106 const menuOverlay = e.target.closest(this.getSelector('menuOverlay')); 107 108 if (!nav && !menuOverlay) { 109 this.closeAllPCMenus(); 110 } 111 }); 112 } 113 114 saveScrollPosition() { 115 this.originalScrollPosition = window.pageYOffset || document.documentElement.scrollTop; 116 } 117 118 lockScroll() { 119 this.saveScrollPosition(); 120 document.body.classList.add(this.getClass('scrollLock')); 121 122 if (this.isIOS) { 123 document.body.classList.add(this.getClass('isIOS')); 124 document.body.style.top = `-${this.originalScrollPosition}px`; 125 } 126 127 document.addEventListener('touchmove', this.preventTouchMove, { passive: false }); 128 } 129 130 unlockScroll() { 131 document.body.classList.remove(this.getClass('scrollLock')); 132 133 if (this.isIOS) { 134 document.body.classList.remove(this.getClass('isIOS')); 135 document.body.style.top = ''; 136 window.scrollTo(0, this.originalScrollPosition); 137 } 138 139 document.removeEventListener('touchmove', this.preventTouchMove); 140 } 141 142 preventTouchMove(e) { 143 const scrollableElements = e.target.closest('.mega-menu-content, .mobile-nav-overlay'); 144 if (!scrollableElements) { 145 e.preventDefault(); 146 } 147 } 148 149 handleMainMenuClick(button) { 150 const li = button.parentElement; 151 const isHeaderNav = li.closest(this.getSelector('header')); 152 153 if (this.isMobile || !isHeaderNav) { 154 this.toggleSubMenu(li); 155 } else { 156 this.handlePCMenu(li); 157 } 158 } 159 160 handlePCMenu(li) { 161 const subNav = li.querySelector(this.getSelector('globalNavSub')); 162 if (!subNav) return; 163 164 const isCurrentlyOpen = li.classList.contains(this.getClass('isOpen')); 165 166 if (isCurrentlyOpen) { 167 this.closeAllPCMenus(); 168 } else { 169 this.closeAllPCMenus(); 170 this.openPCMenu(li, subNav); 171 } 172 } 173 174 openPCMenu(li, subNav) { 175 li.classList.add(this.getClass('isOpen')); 176 li.querySelector('button').setAttribute('aria-expanded', 'true'); 177 178 const menuOverlay = document.querySelector(this.getSelector('menuOverlay')); 179 const megaMenuContent = document.querySelector(this.getSelector('megaMenuContent')); 180 const megaMenuSubNav = document.querySelector(this.getSelector('megaMenuSubNav')); 181 const megaMenuGrandchild = document.querySelector(this.getSelector('megaMenuGrandchild')); 182 const megaMenuMessage = document.querySelector(this.getSelector('megaMenuMessage')); 183 184 megaMenuSubNav.innerHTML = ''; 185 megaMenuGrandchild.innerHTML = ''; 186 187 const subItems = subNav.children; 188 let hasGrandchildren = false; 189 let firstItemWithGrandchild = null; 190 let firstGrandchildNav = null; 191 192 Array.from(subItems).forEach((item, index) => { 193 const clonedItem = item.cloneNode(true); 194 const grandchildNav = item.querySelector(this.getSelector('grandchildNav')); 195 196 if (grandchildNav && grandchildNav.children.length > 0) { 197 hasGrandchildren = true; 198 clonedItem.addEventListener('mouseenter', () => { 199 this.showMegaMenuGrandchildren(clonedItem); 200 }); 201 202 if (!firstItemWithGrandchild) { 203 firstItemWithGrandchild = clonedItem; 204 firstGrandchildNav = grandchildNav; 205 } 206 } else { 207 clonedItem.addEventListener('mouseenter', () => { 208 this.clearMegaMenuGrandchildren(); 209 megaMenuSubNav.querySelectorAll('li').forEach(sibling => { 210 sibling.classList.remove(this.getClass('isActive')); 211 }); 212 clonedItem.classList.add(this.getClass('isActive')); 213 }); 214 } 215 216 megaMenuSubNav.appendChild(clonedItem); 217 }); 218 219 if (hasGrandchildren) { 220 megaMenuContent.classList.remove(this.getClass('singleColumn')); 221 megaMenuMessage.style.display = 'none'; 222 megaMenuGrandchild.style.display = 'grid'; 223 224 if (firstItemWithGrandchild && firstGrandchildNav) { 225 firstItemWithGrandchild.classList.add(this.getClass('isActive')); 226 this.populateMegaMenuGrandchildren(firstGrandchildNav); 227 } 228 } else { 229 megaMenuContent.classList.add(this.getClass('singleColumn')); 230 megaMenuGrandchild.style.display = 'none'; 231 megaMenuMessage.style.display = 'none'; 232 } 233 234 menuOverlay.classList.add(this.getClass('isOpen')); 235 } 236 237 showMegaMenuGrandchildren(li) { 238 const grandchildNav = li.querySelector(this.getSelector('grandchildNav')); 239 const megaMenuMessage = document.querySelector(this.getSelector('megaMenuMessage')); 240 const megaMenuGrandchild = document.querySelector(this.getSelector('megaMenuGrandchild')); 241 242 li.parentElement.querySelectorAll('li').forEach(sibling => { 243 sibling.classList.remove(this.getClass('isActive')); 244 }); 245 li.classList.add(this.getClass('isActive')); 246 247 if (grandchildNav && grandchildNav.children.length > 0) { 248 megaMenuMessage.style.display = 'none'; 249 megaMenuGrandchild.style.display = 'grid'; 250 this.populateMegaMenuGrandchildren(grandchildNav); 251 } else { 252 this.clearMegaMenuGrandchildren(); 253 } 254 } 255 256 populateMegaMenuGrandchildren(grandchildNav) { 257 const megaMenuGrandchild = document.querySelector(this.getSelector('megaMenuGrandchild')); 258 megaMenuGrandchild.innerHTML = ''; 259 260 if (!grandchildNav) return; 261
262 Array.from(grandchildNav.children).forEach(item => { 263 const clonedItem = item.cloneNode(true); 264 megaMenuGrandchild.appendChild(clonedItem); 265 }); 266 } 267 268 clearMegaMenuGrandchildren() { 269 const megaMenuGrandchild = document.querySelector(this.getSelector('megaMenuGrandchild')); 270 const megaMenuMessage = document.querySelector(this.getSelector('megaMenuMessage')); 271 272 megaMenuGrandchild.innerHTML = ''; 273 megaMenuGrandchild.style.display = 'none'; 274 megaMenuMessage.style.display = 'flex'; 275 } 276 277 closeAllPCMenus() { 278 document.querySelectorAll(`${this.getSelector('header')} ${this.getSelector('globalNav')} > li.${this.getClass('isOpen')}`).forEach(item => { 279 this.closeMenuItem(item); 280 }); 281 282 const menuOverlay = document.querySelector(this.getSelector('menuOverlay')); 283 if (menuOverlay.classList.contains(this.getClass('isOpen'))) { 284 menuOverlay.classList.remove(this.getClass('isOpen')); 285 } 286 } 287 288 closePCMenu() { 289 this.closeAllPCMenus(); 290 } 291 292 closeSiblingMobileMenus(currentLi) { 293 const isInMobileOverlay = currentLi.closest(this.getSelector('mobileNavOverlay')); 294 if (!isInMobileOverlay) return; 295 296 const parentUl = currentLi.parentElement; 297 298 Array.from(parentUl.children).forEach(siblingLi => { 299 if (siblingLi !== currentLi && siblingLi.classList.contains(this.getClass('isOpen'))) { 300 this.closeMenuItem(siblingLi); 301 } 302 }); 303 } 304 305 closeSiblingMobileSubMenus(currentLi) { 306 const isInMobileOverlay = currentLi.closest(this.getSelector('mobileNavOverlay')); 307 if (!isInMobileOverlay) return; 308 309 const parentUl = currentLi.parentElement; 310 311 Array.from(parentUl.children).forEach(siblingLi => { 312 if (siblingLi !== currentLi && siblingLi.classList.contains(this.getClass('isOpen'))) { 313 this.closeSubMenuItem(siblingLi); 314 } 315 }); 316 } 317 318 toggleSubMenu(li) { 319 const subNav = li.querySelector(this.getSelector('globalNavSub')); 320 const button = li.querySelector('button'); 321 322 if (!subNav || !button) return; 323 324 const isOpen = li.classList.contains(this.getClass('isOpen')); 325 326 if (isOpen) { 327 this.closeMenuItem(li); 328 } else { 329 this.closeSiblingMobileMenus(li); 330 this.openMenuItem(li); 331 } 332 } 333 334 handleSubMenuClick(button) { 335 const li = button.parentElement; 336 const grandchildNav = li.querySelector(this.getSelector('grandchildNav')); 337 338 if (!grandchildNav) return; 339 340 const isOpen = li.classList.contains(this.getClass('isOpen')); 341 342 if (isOpen) { 343 this.closeSubMenuItem(li); 344 } else { 345 this.closeSiblingMobileSubMenus(li); 346 this.openSubMenuItem(li); 347 } 348 } 349 350 openMenuItem(li) { 351 li.classList.add(this.getClass('isOpen')); 352 const subNav = li.querySelector(this.getSelector('globalNavSub')); 353 const button = li.querySelector('button'); 354 355 if (subNav) { 356 subNav.classList.add(this.getClass('isOpen')); 357 358 const overlay = document.querySelector(this.getSelector('mobileNavOverlay')); 359 if (overlay) { 360 const hasGrandchild = subNav.querySelector(this.getSelector('grandchildNav')); 361 if (hasGrandchild) { 362 overlay.classList.remove(this.getClass('singleColumn')); 363 } else { 364 overlay.classList.add(this.getClass('singleColumn')); 365 } 366 } 367 } 368 369 if (button) button.setAttribute('aria-expanded', 'true'); 370 } 371 372 closeMenuItem(li) { 373 li.classList.remove(this.getClass('isOpen')); 374 const subNav = li.querySelector(this.getSelector('globalNavSub')); 375 const button = li.querySelector('button'); 376 377 if (subNav) subNav.classList.remove(this.getClass('isOpen')); 378 if (button) button.setAttribute('aria-expanded', 'false'); 379
380 li.querySelectorAll(`${this.getSelector('globalNavSub')} li.${this.getClass('isOpen')}`).forEach(subItem => { 381 this.closeSubMenuItem(subItem); 382 }); 383 } 384 385 openSubMenuItem(li) { 386 li.classList.add(this.getClass('isOpen')); 387 const grandchildNav = li.querySelector(this.getSelector('grandchildNav')); 388 const button = li.querySelector('button'); 389 390 if (grandchildNav) grandchildNav.classList.add(this.getClass('isOpen')); 391 if (button) button.setAttribute('aria-expanded', 'true'); 392 } 393 394 closeSubMenuItem(li) { 395 li.classList.remove(this.getClass('isOpen')); 396 const grandchildNav = li.querySelector(this.getSelector('grandchildNav')); 397 const button = li.querySelector('button'); 398 399 if (grandchildNav) grandchildNav.classList.remove(this.getClass('isOpen')); 400 if (button) button.setAttribute('aria-expanded', 'false'); 401 } 402 403 toggleMobileMenu() { 404 const mobileToggle = document.querySelector(this.getSelector('mobileMenuToggle')); 405 const mobileOverlay = document.querySelector(this.getSelector('mobileNavOverlay')); 406 407 const isOpen = mobileOverlay.classList.contains(this.getClass('isOpen')); 408 409 if (isOpen) { 410 this.closeMobileMenu(); 411 } else { 412 this.openMobileMenu(); 413 } 414 } 415 416 openMobileMenu() { 417 const mobileToggle = document.querySelector(this.getSelector('mobileMenuToggle')); 418 const mobileOverlay = document.querySelector(this.getSelector('mobileNavOverlay')); 419 420 mobileToggle.classList.add(this.getClass('isOpen')); 421 mobileOverlay.classList.add(this.getClass('isOpen')); 422 mobileToggle.setAttribute('aria-expanded', 'true'); 423 this.lockScroll(); 424 } 425 426 closeMobileMenu() { 427 const mobileToggle = document.querySelector(this.getSelector('mobileMenuToggle')); 428 const mobileOverlay = document.querySelector(this.getSelector('mobileNavOverlay')); 429 430 mobileToggle.classList.remove(this.getClass('isOpen')); 431 mobileOverlay.classList.remove(this.getClass('isOpen')); 432 mobileToggle.setAttribute('aria-expanded', 'false'); 433 this.unlockScroll(); 434 435 mobileOverlay.querySelectorAll(`.${this.getClass('isOpen')}`).forEach(item => { 436 item.classList.remove(this.getClass('isOpen')); 437 const button = item.querySelector('button'); 438 if (button) { 439 button.setAttribute('aria-expanded', 'false'); 440 }
441 const subNav = item.querySelector(`${this.getSelector('globalNavSub')}, ${this.getSelector('grandchildNav')}`); 442 if (subNav) { 443 subNav.classList.remove(this.getClass('isOpen')); 444 } 445 }); 446 } 447 448 setupKeyboardNavigation() { 449 document.addEventListener('keydown', (e) => { 450 if (e.key === 'Escape') { 451 this.closeAllPCMenus(); 452 this.closeMobileMenu(); 453 } 454 }); 455 456 const focusableElements = 'button, a, input, select, textarea, [tabindex]:not([tabindex="-1"])'; 457 458 document.addEventListener('keydown', (e) => { 459 if (e.key === 'Tab') { 460 const menuOverlay = document.querySelector(`${this.getSelector('menuOverlay')}.${this.getClass('isOpen')}`); 461 const mobileOverlay = document.querySelector(`${this.getSelector('mobileNavOverlay')}.${this.getClass('isOpen')}`); 462 463 if (menuOverlay) { 464 this.handleTabNavigation(e, menuOverlay, focusableElements); 465 } else if (mobileOverlay) { 466 this.handleTabNavigation(e, mobileOverlay, focusableElements); 467 } 468 } 469 }); 470 } 471 472 handleTabNavigation(e, container, focusableElements) { 473 const focusable = container.querySelectorAll(focusableElements); 474 const firstFocusable = focusable[0]; 475 const lastFocusable = focusable[focusable.length - 1]; 476 477 if (e.shiftKey) { 478 if (document.activeElement === firstFocusable) { 479 e.preventDefault(); 480 lastFocusable.focus(); 481 } 482 } else { 483 if (document.activeElement === lastFocusable) { 484 e.preventDefault(); 485 firstFocusable.focus(); 486 } 487 } 488 } 489} 490 491// 使ç¨ä¾1: ããã©ã«ãè¨å®ã§åæå 492document.addEventListener('DOMContentLoaded', () => { 493 new UnifiedAccessibleMegaMenu(); 494}); 495 496// 使ç¨ä¾2: ã«ã¹ã¿ã è¨å®ã§åæå 497// document.addEventListener('DOMContentLoaded', () => { 498// new UnifiedAccessibleMegaMenu({ 499// selectors: { 500// globalNav: '.custom-nav', 501// header: '.custom-header', 502// mobileMenuToggle: '.custom-toggle', 503// // ä»ã®ã»ã¬ã¯ã¿ãåæ§ã«ã«ã¹ã¿ãã¤ãºå¯è½ 504// }, 505// classes: { 506// isOpen: 'active', 507// isActive: 'current', 508// // ä»ã®ã¯ã©ã¹åãåæ§ã«ã«ã¹ã¿ãã¤ãºå¯è½ 509// }, 510// breakpoint: 1024 511// }); 512// }); 513 514 515// åè©°ã 516$(function () { 517 const targetFonts = [ 518 'Noto Sans CJK JP', 519 'Noto Sans JP', 520 ]; 521 522 // ã¯ã©ã¹ãã¨ã«å¯¾è±¡æåãå®ç¾© 523 const symbolRules = [ 524 { 525 className: 'tight-full-open', 526 symbols: ['ï¼', 'ã', 'ã', 'ã', 'ï¼»', 'ï½'] 527 }, 528 { 529 className: 'tight-full-close', 530 symbols: ['ï¼', 'ã', 'ã', 'ã', 'ï¼½', 'ï½'] 531 }, 532 { 533 className: 'tight-half-open', 534 symbols: ['ã', 'ã', 'ã'] 535 }, 536 { 537 className: 'tight-half-close', 538 symbols: ['ã', 'ã', 'ã'] 539 }, 540 { 541 className: 'tight-symbol', 542 symbols: ['ã»', 'ã', 'ã', 'ï¼'] 543 }, 544 ]; 545 546 function wrapSymbol(char, className) { 547 if (char.match(/^<span[^>]*>.*<\/span>$/)) { 548 return char.replace(/class="([^"]*)"/, 'class="$1 ' + className + '"'); 549 } 550 return `<span class="${className}">${char}</span>`; 551 } 552 553 $('header *').contents().filter(function () { 554 return this.nodeType === 3 && $.trim(this.nodeValue).length > 0; 555 }).each(function () { 556 const $parent = $(this).parent(); 557 const fontFamily = $parent.css('font-family') || ''; 558 const hasTargetFont = targetFonts.some(font => fontFamily.includes(font)); 559 if (!hasTargetFont) return; 560 561 let text = $(this).text(); 562 let result = ''; 563 564 for (let i = 0; i < text.length; i++) { 565 const char = text[i]; 566 let wrapped = char; 567 568 for (const rule of symbolRules) { 569 if (rule.symbols.includes(char)) { 570 wrapped = wrapSymbol(char, rule.className); 571 break; // æåã«ãããããã«ã¼ã«ãé©ç¨ 572 } 573 } 574 575 result += wrapped; 576 } 577 578 $(this).replaceWith(result); 579 }); 580}); 581
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.