161 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			161 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @file
							 | 
						||
| 
								 | 
							
								 * Customization of navigation.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								((Drupal, once, tabbable) => {
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Checks if navWrapper contains "is-active" class.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {Element} navWrapper
							 | 
						||
| 
								 | 
							
								   *   Header navigation.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return {boolean}
							 | 
						||
| 
								 | 
							
								   *   True if navWrapper contains "is-active" class, false if not.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function isNavOpen(navWrapper) {
							 | 
						||
| 
								 | 
							
								    return navWrapper.classList.contains('is-active');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Opens or closes the header navigation.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {object} props
							 | 
						||
| 
								 | 
							
								   *   Navigation props.
							 | 
						||
| 
								 | 
							
								   * @param {boolean} state
							 | 
						||
| 
								 | 
							
								   *   State which to transition the header navigation menu into.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function toggleNav(props, state) {
							 | 
						||
| 
								 | 
							
								    const value = !!state;
							 | 
						||
| 
								 | 
							
								    props.navButton.setAttribute('aria-expanded', value);
							 | 
						||
| 
								 | 
							
								    props.body.classList.toggle('is-overlay-active', value);
							 | 
						||
| 
								 | 
							
								    props.body.classList.toggle('is-fixed', value);
							 | 
						||
| 
								 | 
							
								    props.navWrapper.classList.toggle('is-active', value);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Initialize the header navigation.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param {object} props
							 | 
						||
| 
								 | 
							
								   *   Navigation props.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  function init(props) {
							 | 
						||
| 
								 | 
							
								    props.navButton.setAttribute('aria-controls', props.navWrapperId);
							 | 
						||
| 
								 | 
							
								    props.navButton.setAttribute('aria-expanded', 'false');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    props.navButton.addEventListener('click', () => {
							 | 
						||
| 
								 | 
							
								      toggleNav(props, !isNavOpen(props.navWrapper));
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Close any open sub-navigation first, then close the header navigation.
							 | 
						||
| 
								 | 
							
								    document.addEventListener('keyup', (e) => {
							 | 
						||
| 
								 | 
							
								      if (e.key === 'Escape') {
							 | 
						||
| 
								 | 
							
								        if (props.olivero.areAnySubNavsOpen()) {
							 | 
						||
| 
								 | 
							
								          props.olivero.closeAllSubNav();
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          toggleNav(props, false);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    props.overlay.addEventListener('click', () => {
							 | 
						||
| 
								 | 
							
								      toggleNav(props, false);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    props.overlay.addEventListener('touchstart', () => {
							 | 
						||
| 
								 | 
							
								      toggleNav(props, false);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Focus trap. This is added to the header element because the navButton
							 | 
						||
| 
								 | 
							
								    // element is not a child element of the navWrapper element, and the keydown
							 | 
						||
| 
								 | 
							
								    // event would not fire if focus is on the navButton element.
							 | 
						||
| 
								 | 
							
								    props.header.addEventListener('keydown', (e) => {
							 | 
						||
| 
								 | 
							
								      if (e.key === 'Tab' && isNavOpen(props.navWrapper)) {
							 | 
						||
| 
								 | 
							
								        const tabbableNavElements = tabbable.tabbable(props.navWrapper);
							 | 
						||
| 
								 | 
							
								        tabbableNavElements.unshift(props.navButton);
							 | 
						||
| 
								 | 
							
								        const firstTabbableEl = tabbableNavElements[0];
							 | 
						||
| 
								 | 
							
								        const lastTabbableEl =
							 | 
						||
| 
								 | 
							
								          tabbableNavElements[tabbableNavElements.length - 1];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (e.shiftKey) {
							 | 
						||
| 
								 | 
							
								          if (
							 | 
						||
| 
								 | 
							
								            document.activeElement === firstTabbableEl &&
							 | 
						||
| 
								 | 
							
								            !props.olivero.isDesktopNav()
							 | 
						||
| 
								 | 
							
								          ) {
							 | 
						||
| 
								 | 
							
								            lastTabbableEl.focus();
							 | 
						||
| 
								 | 
							
								            e.preventDefault();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else if (
							 | 
						||
| 
								 | 
							
								          document.activeElement === lastTabbableEl &&
							 | 
						||
| 
								 | 
							
								          !props.olivero.isDesktopNav()
							 | 
						||
| 
								 | 
							
								        ) {
							 | 
						||
| 
								 | 
							
								          firstTabbableEl.focus();
							 | 
						||
| 
								 | 
							
								          e.preventDefault();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Remove overlays when browser is resized and desktop nav appears.
							 | 
						||
| 
								 | 
							
								    window.addEventListener('resize', () => {
							 | 
						||
| 
								 | 
							
								      if (props.olivero.isDesktopNav()) {
							 | 
						||
| 
								 | 
							
								        toggleNav(props, false);
							 | 
						||
| 
								 | 
							
								        props.body.classList.remove('is-overlay-active');
							 | 
						||
| 
								 | 
							
								        props.body.classList.remove('is-fixed');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Ensure that all sub-navigation menus close when the browser is resized.
							 | 
						||
| 
								 | 
							
								      Drupal.olivero.closeAllSubNav();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // If hyperlink links to an anchor in the current page, close the
							 | 
						||
| 
								 | 
							
								    // mobile menu after the click.
							 | 
						||
| 
								 | 
							
								    props.navWrapper.addEventListener('click', (e) => {
							 | 
						||
| 
								 | 
							
								      if (
							 | 
						||
| 
								 | 
							
								        e.target.matches(
							 | 
						||
| 
								 | 
							
								          `[href*="${window.location.pathname}#"], [href*="${window.location.pathname}#"] *, [href^="#"], [href^="#"] *`,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								      ) {
							 | 
						||
| 
								 | 
							
								        toggleNav(props, false);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Initialize the navigation.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @type {Drupal~behavior}
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @prop {Drupal~behaviorAttach} attach
							 | 
						||
| 
								 | 
							
								   *   Attach context and settings for navigation.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  Drupal.behaviors.oliveroNavigation = {
							 | 
						||
| 
								 | 
							
								    attach(context) {
							 | 
						||
| 
								 | 
							
								      const headerId = 'header';
							 | 
						||
| 
								 | 
							
								      const header = once('navigation', `#${headerId}`, context).shift();
							 | 
						||
| 
								 | 
							
								      const navWrapperId = 'header-nav';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (header) {
							 | 
						||
| 
								 | 
							
								        const navWrapper = header.querySelector(`#${navWrapperId}`);
							 | 
						||
| 
								 | 
							
								        const { olivero } = Drupal;
							 | 
						||
| 
								 | 
							
								        const navButton = context.querySelector(
							 | 
						||
| 
								 | 
							
								          '[data-drupal-selector="mobile-nav-button"]',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								        const body = document.body;
							 | 
						||
| 
								 | 
							
								        const overlay = context.querySelector(
							 | 
						||
| 
								 | 
							
								          '[data-drupal-selector="header-nav-overlay"]',
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        init({
							 | 
						||
| 
								 | 
							
								          olivero,
							 | 
						||
| 
								 | 
							
								          header,
							 | 
						||
| 
								 | 
							
								          navWrapperId,
							 | 
						||
| 
								 | 
							
								          navWrapper,
							 | 
						||
| 
								 | 
							
								          navButton,
							 | 
						||
| 
								 | 
							
								          body,
							 | 
						||
| 
								 | 
							
								          overlay,
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								})(Drupal, once, tabbable);
							 |