123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- /**
- * @file
- * Provides functionality for second level submenu navigation.
- */
- ((Drupal) => {
- const { isDesktopNav } = Drupal.olivero;
- const secondLevelNavMenus = document.querySelectorAll(
- '[data-drupal-selector="primary-nav-menu-item-has-children"]',
- );
- /**
- * Shows and hides the specified menu item's second level submenu.
- *
- * @param {Element} topLevelMenuItem
- * The <li> element that is the container for the menu and submenus.
- * @param {boolean} [toState]
- * Optional state where we want the submenu to end up.
- */
- function toggleSubNav(topLevelMenuItem, toState) {
- const buttonSelector =
- '[data-drupal-selector="primary-nav-submenu-toggle-button"]';
- const button = topLevelMenuItem.querySelector(buttonSelector);
- const state =
- toState !== undefined
- ? toState
- : button.getAttribute('aria-expanded') !== 'true';
- if (state) {
- // If desktop nav, ensure all menus close before expanding new one.
- if (isDesktopNav()) {
- secondLevelNavMenus.forEach((el) => {
- el.querySelector(buttonSelector).setAttribute(
- 'aria-expanded',
- 'false',
- );
- el.querySelector(
- '[data-drupal-selector="primary-nav-menu--level-2"]',
- ).classList.remove('is-active-menu-parent');
- el.querySelector(
- '[data-drupal-selector="primary-nav-menu-🥕"]',
- ).classList.remove('is-active-menu-parent');
- });
- }
- button.setAttribute('aria-expanded', 'true');
- topLevelMenuItem
- .querySelector('[data-drupal-selector="primary-nav-menu--level-2"]')
- .classList.add('is-active-menu-parent');
- topLevelMenuItem
- .querySelector('[data-drupal-selector="primary-nav-menu-🥕"]')
- .classList.add('is-active-menu-parent');
- } else {
- button.setAttribute('aria-expanded', 'false');
- topLevelMenuItem.classList.remove('is-touch-event');
- topLevelMenuItem
- .querySelector('[data-drupal-selector="primary-nav-menu--level-2"]')
- .classList.remove('is-active-menu-parent');
- topLevelMenuItem
- .querySelector('[data-drupal-selector="primary-nav-menu-🥕"]')
- .classList.remove('is-active-menu-parent');
- }
- }
- Drupal.olivero.toggleSubNav = toggleSubNav;
- /**
- * Sets a timeout and closes current desktop navigation submenu if it
- * does not contain the focused element.
- *
- * @param {Event} e
- * The event object.
- */
- function handleBlur(e) {
- if (!Drupal.olivero.isDesktopNav()) return;
- setTimeout(() => {
- const menuParentItem = e.target.closest(
- '[data-drupal-selector="primary-nav-menu-item-has-children"]',
- );
- if (!menuParentItem.contains(document.activeElement)) {
- toggleSubNav(menuParentItem, false);
- }
- }, 200);
- }
- // Add event listeners onto each sub navigation parent and button.
- secondLevelNavMenus.forEach((el) => {
- const button = el.querySelector(
- '[data-drupal-selector="primary-nav-submenu-toggle-button"]',
- );
- button.removeAttribute('aria-hidden');
- button.removeAttribute('tabindex');
- // If touch event, prevent mouseover event from triggering the submenu.
- el.addEventListener(
- 'touchstart',
- () => {
- el.classList.add('is-touch-event');
- },
- { passive: true },
- );
- el.addEventListener('mouseover', () => {
- if (isDesktopNav() && !el.classList.contains('is-touch-event')) {
- el.classList.add('is-active-mouseover-event');
- toggleSubNav(el, true);
- // Timeout is added to ensure that users of assistive devices (such as
- // mouse grid tools) do not simultaneously trigger both the mouseover
- // and click events. When these events are triggered together, the
- // submenu to appear to not open.
- setTimeout(() => {
- el.classList.remove('is-active-mouseover-event');
- }, 500);
- }
- });
- button.addEventListener('click', () => {
- if (!el.classList.contains('is-active-mouseover-event')) {
- toggleSubNav(el);
- }
- });
- el.addEventListener('mouseout', () => {
- if (
- isDesktopNav() &&
- !document.activeElement.matches(
- '[aria-expanded="true"], .is-active-menu-parent *',
- )
- ) {
- toggleSubNav(el, false);
- }
- });
- el.addEventListener('blur', handleBlur, true);
- });
- /**
- * Close all second level sub navigation menus.
- */
- function closeAllSubNav() {
- secondLevelNavMenus.forEach((el) => {
- // Return focus to the toggle button if the submenu contains focus.
- if (el.contains(document.activeElement)) {
- el.querySelector(
- '[data-drupal-selector="primary-nav-submenu-toggle-button"]',
- ).focus();
- }
- toggleSubNav(el, false);
- });
- }
- Drupal.olivero.closeAllSubNav = closeAllSubNav;
- /**
- * Checks if any sub navigation items are currently active.
- *
- * @return {boolean}
- * If sub navigation is currently open.
- */
- function areAnySubNavsOpen() {
- let subNavsAreOpen = false;
- secondLevelNavMenus.forEach((el) => {
- const button = el.querySelector(
- '[data-drupal-selector="primary-nav-submenu-toggle-button"]',
- );
- const state = button.getAttribute('aria-expanded') === 'true';
- if (state) {
- subNavsAreOpen = true;
- }
- });
- return subNavsAreOpen;
- }
- Drupal.olivero.areAnySubNavsOpen = areAnySubNavsOpen;
- // Ensure that desktop submenus close when escape key is pressed.
- document.addEventListener('keyup', (e) => {
- if (e.key === 'Escape' || e.key === 'Esc') {
- if (isDesktopNav()) closeAllSubNav();
- }
- });
- // If user taps outside of menu, close all menus.
- document.addEventListener(
- 'touchstart',
- (e) => {
- if (
- areAnySubNavsOpen() &&
- !e.target.matches(
- '[data-drupal-selector="header-nav"], [data-drupal-selector="header-nav"] *',
- )
- ) {
- closeAllSubNav();
- }
- },
- { passive: true },
- );
- })(Drupal);
|