123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- /**
- * @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);
- if (value) {
- props.body.classList.add('is-overlay-active');
- props.body.classList.add('is-fixed');
- props.navWrapper.classList.add('is-active');
- } else {
- props.body.classList.remove('is-overlay-active');
- props.body.classList.remove('is-fixed');
- props.navWrapper.classList.remove('is-active');
- }
- }
- /**
- * 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' || e.key === 'Esc') {
- 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);
|