198 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			198 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @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');
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      topLevelMenuItem.classList.remove('is-touch-event');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    button.setAttribute('aria-expanded', state);
							 | 
						||
| 
								 | 
							
								    topLevelMenuItem
							 | 
						||
| 
								 | 
							
								      .querySelector('[data-drupal-selector="primary-nav-menu--level-2"]')
							 | 
						||
| 
								 | 
							
								      .classList.toggle('is-active-menu-parent', state);
							 | 
						||
| 
								 | 
							
								    topLevelMenuItem
							 | 
						||
| 
								 | 
							
								      .querySelector('[data-drupal-selector="primary-nav-menu-🥕"]')
							 | 
						||
| 
								 | 
							
								      .classList.toggle('is-active-menu-parent', state);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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') {
							 | 
						||
| 
								 | 
							
								      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);
							 |