| /** | |
| * @license | |
| * Copyright 2021 The Go Authors. All rights reserved. | |
| * Use of this source code is governed by a BSD-style | |
| * license that can be found in the LICENSE file. | |
| */ | |
| export function registerHeaderListeners(): void { | |
| const header = document.querySelector('.js-header') as HTMLElement; | |
| // Desktop menu hover state | |
| const menuItemHovers = document.querySelectorAll('.js-desktop-menu-hover'); | |
| menuItemHovers.forEach(menuItemHover => { | |
| // when user clicks on the dropdown menu item on desktop or mobile, | |
| // force the menu to stay open until the user clicks off of it. | |
| menuItemHover.addEventListener('mouseenter', e => { | |
| const target = e.target as HTMLElement; | |
| const forced = document.querySelector('.forced-open') as HTMLElement; | |
| if (forced && forced !== menuItemHover) { | |
| forced.blur(); | |
| forced.classList.remove('forced-open'); | |
| } | |
| // prevents menus that have been tabbed into from staying open | |
| // when you hover over another menu | |
| target.classList.remove('forced-closed'); | |
| target.classList.add('forced-open'); | |
| }); | |
| const toggleForcedOpen = (e: Event) => { | |
| const target = e.target as HTMLElement; | |
| const isForced = target?.classList.contains('forced-open'); | |
| const currentTarget = e.currentTarget as HTMLElement; | |
| if (isForced) { | |
| currentTarget.removeEventListener('blur', () => | |
| currentTarget.classList.remove('forced-open'), | |
| ); | |
| currentTarget.classList.remove('forced-open'); | |
| currentTarget.classList.add('forced-closed'); | |
| currentTarget.blur(); | |
| currentTarget?.parentNode?.addEventListener('mouseout', () => { | |
| currentTarget.classList.remove('forced-closed'); | |
| }); | |
| } else { | |
| currentTarget.classList.remove('forced-closed'); | |
| currentTarget.classList.add('forced-open'); | |
| currentTarget.focus(); | |
| currentTarget.addEventListener('blur', () => currentTarget.classList.remove('forced-open')); | |
| currentTarget?.parentNode?.removeEventListener('mouseout', () => { | |
| currentTarget.classList.remove('forced-closed'); | |
| }); | |
| } | |
| currentTarget.focus(); | |
| }; | |
| menuItemHover.addEventListener('click', toggleForcedOpen); | |
| menuItemHover.addEventListener('focus', e => { | |
| const target = e.target as HTMLElement; | |
| target.classList.add('forced-closed'); | |
| target.classList.remove('forced-open'); | |
| }); | |
| // ensure desktop submenus are closed when esc is pressed | |
| const closeSubmenuOnEsc = (e: Event) => { | |
| const event = e as KeyboardEvent; | |
| const target = e.target as HTMLElement; | |
| if (event.key === 'Escape') { | |
| const forcedOpenItem = document.querySelector('.forced-open') as HTMLElement; | |
| if (forcedOpenItem) { | |
| forcedOpenItem.classList.remove('forced-open'); | |
| forcedOpenItem.blur(); | |
| forcedOpenItem.classList.add('forced-closed'); | |
| target?.focus(); | |
| } | |
| } | |
| }; | |
| document.addEventListener('keydown', closeSubmenuOnEsc); | |
| }); | |
| // Mobile menu subnav menus | |
| const headerbuttons = document.querySelectorAll('.js-headerMenuButton'); | |
| headerbuttons.forEach(button => { | |
| button.addEventListener('click', e => { | |
| e.preventDefault(); | |
| const isActive = header?.classList.contains('is-active'); | |
| if (isActive) { | |
| handleNavigationDrawerInactive(header); | |
| } else { | |
| handleNavigationDrawerActive(header); | |
| } | |
| button.setAttribute('aria-expanded', isActive ? 'true' : 'false'); | |
| }); | |
| }); | |
| const scrim = document.querySelector('.js-scrim'); | |
| scrim?.addEventListener('click', e => { | |
| e.preventDefault(); | |
| // find any active submenus and close them | |
| const activeSubnavs = document.querySelectorAll('.go-NavigationDrawer-submenuItem.is-active'); | |
| activeSubnavs.forEach(subnav => handleNavigationDrawerInactive(subnav as HTMLElement)); | |
| handleNavigationDrawerInactive(header); | |
| headerbuttons.forEach(button => { | |
| button.setAttribute( | |
| 'aria-expanded', | |
| header?.classList.contains('is-active') ? 'true' : 'false', | |
| ); | |
| }); | |
| }); | |
| const getNavigationDrawerMenuItems = (navigationDrawer: HTMLElement): HTMLElement[] => { | |
| if (!navigationDrawer) { | |
| return []; | |
| } | |
| const menuItems = Array.from( | |
| navigationDrawer.querySelectorAll( | |
| ':scope > .go-NavigationDrawer-nav > .go-NavigationDrawer-list > .go-NavigationDrawer-listItem > a, :scope > .go-NavigationDrawer-nav > .go-NavigationDrawer-list > .go-NavigationDrawer-listItem > .go-Header-socialIcons > a', | |
| ) || [], | |
| ); | |
| const anchorEl = navigationDrawer.querySelector('.go-NavigationDrawer-header > a'); | |
| if (anchorEl) { | |
| menuItems.unshift(anchorEl); | |
| } | |
| return menuItems as HTMLElement[]; | |
| }; | |
| const getNavigationDrawerIsSubnav = (navigationDrawer: HTMLElement) => { | |
| if (!navigationDrawer) { | |
| return; | |
| } | |
| return navigationDrawer.classList.contains('go-NavigationDrawer-submenuItem'); | |
| }; | |
| const handleNavigationDrawerInactive = (navigationDrawer: HTMLElement) => { | |
| if (!navigationDrawer) { | |
| return; | |
| } | |
| const menuItems = getNavigationDrawerMenuItems(navigationDrawer); | |
| navigationDrawer.classList.remove('is-active'); | |
| const parentMenuItem = navigationDrawer | |
| .closest('.go-NavigationDrawer-listItem') | |
| ?.querySelector(':scope > a') as HTMLElement; | |
| parentMenuItem?.focus(); | |
| menuItems?.forEach(item => item?.setAttribute('tabindex', '-1')); | |
| if (menuItems && menuItems[0]) { | |
| menuItems[0].removeEventListener('keydown', handleMenuItemTabLeftFactory(navigationDrawer)); | |
| menuItems[menuItems.length - 1].removeEventListener( | |
| 'keydown', | |
| handleMenuItemTabRightFactory(navigationDrawer), | |
| ); | |
| } | |
| if (navigationDrawer === header) { | |
| if (headerbuttons) (headerbuttons[0] as HTMLElement)?.focus(); | |
| } | |
| }; | |
| const handleNavigationDrawerActive = (navigationDrawer: HTMLElement) => { | |
| const menuItems = getNavigationDrawerMenuItems(navigationDrawer); | |
| navigationDrawer.classList.add('is-active'); | |
| menuItems.forEach(item => item.setAttribute('tabindex', '0')); | |
| menuItems[0].focus(); | |
| menuItems[0].addEventListener('keydown', handleMenuItemTabLeftFactory(navigationDrawer)); | |
| menuItems[menuItems.length - 1].addEventListener( | |
| 'keydown', | |
| handleMenuItemTabRightFactory(navigationDrawer), | |
| ); | |
| }; | |
| const handleMenuItemTabLeftFactory = (navigationDrawer: HTMLElement) => { | |
| return (e: KeyboardEvent) => { | |
| if (e.key === 'Tab' && e.shiftKey) { | |
| e.preventDefault(); | |
| handleNavigationDrawerInactive(navigationDrawer); | |
| } | |
| }; | |
| }; | |
| const handleMenuItemTabRightFactory = (navigationDrawer: HTMLElement) => { | |
| return (e: KeyboardEvent) => { | |
| if (e.key === 'Tab' && !e.shiftKey) { | |
| e.preventDefault(); | |
| handleNavigationDrawerInactive(navigationDrawer); | |
| } | |
| }; | |
| }; | |
| const prepMobileNavigationDrawer = (navigationDrawer: HTMLElement) => { | |
| const isSubnav = getNavigationDrawerIsSubnav(navigationDrawer); | |
| const menuItems = getNavigationDrawerMenuItems(navigationDrawer); | |
| navigationDrawer.addEventListener('keyup', e => { | |
| if (e.key === 'Escape') { | |
| handleNavigationDrawerInactive(navigationDrawer); | |
| } | |
| }); | |
| menuItems.forEach(item => { | |
| const parentLi = item.closest('li'); | |
| if (parentLi && parentLi.classList.contains('js-mobile-subnav-trigger')) { | |
| const submenu = parentLi.querySelector('.go-NavigationDrawer-submenuItem') as HTMLElement; | |
| item.addEventListener('click', () => { | |
| handleNavigationDrawerActive(submenu); | |
| }); | |
| } | |
| }); | |
| if (isSubnav) { | |
| handleNavigationDrawerInactive(navigationDrawer); | |
| navigationDrawer | |
| ?.querySelector('.go-NavigationDrawer-header') | |
| ?.addEventListener('click', e => { | |
| e.preventDefault(); | |
| handleNavigationDrawerInactive(navigationDrawer); | |
| }); | |
| } | |
| }; | |
| document | |
| .querySelectorAll('.go-NavigationDrawer') | |
| .forEach(drawer => prepMobileNavigationDrawer(drawer as HTMLElement)); | |
| handleNavigationDrawerInactive(header); | |
| } | |
| export function registerSearchFormListeners(): void { | |
| const searchForm = document.querySelector('.js-searchForm'); | |
| const expandSearch = document.querySelector('.js-expandSearch'); | |
| const input = searchForm?.querySelector('input'); | |
| const headerLogo = document.querySelector('.js-headerLogo'); | |
| const menuButton = document.querySelector('.js-headerMenuButton'); | |
| expandSearch?.addEventListener('click', () => { | |
| searchForm?.classList.add('go-SearchForm--expanded'); | |
| headerLogo?.classList.add('go-Header-logo--hidden'); | |
| menuButton?.classList.add('go-Header-navOpen--hidden'); | |
| input?.focus(); | |
| }); | |
| document?.addEventListener('click', e => { | |
| if (!searchForm?.contains(e.target as Node)) { | |
| searchForm?.classList.remove('go-SearchForm--expanded'); | |
| headerLogo?.classList.remove('go-Header-logo--hidden'); | |
| menuButton?.classList.remove('go-Header-navOpen--hidden'); | |
| } | |
| }); | |
| } |