You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
4.6 KiB
160 lines
4.6 KiB
/** |
|
* @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.olives.areAnySubNavsOpen()) { |
|
props.olives.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.olives.isDesktopNav() |
|
) { |
|
lastTabbableEl.focus(); |
|
e.preventDefault(); |
|
} |
|
} else if ( |
|
document.activeElement === lastTabbableEl && |
|
!props.olives.isDesktopNav() |
|
) { |
|
firstTabbableEl.focus(); |
|
e.preventDefault(); |
|
} |
|
} |
|
}); |
|
|
|
// Remove overlays when browser is resized and desktop nav appears. |
|
window.addEventListener('resize', () => { |
|
if (props.olives.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.olives.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.olivesNavigation = { |
|
attach(context) { |
|
const headerId = 'header'; |
|
const header = once('navigation', `#${headerId}`, context).shift(); |
|
const navWrapperId = 'header-nav'; |
|
|
|
if (header) { |
|
const navWrapper = header.querySelector(`#${navWrapperId}`); |
|
const { olives } = 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({ |
|
olives, |
|
header, |
|
navWrapperId, |
|
navWrapper, |
|
navButton, |
|
body, |
|
overlay, |
|
}); |
|
} |
|
}, |
|
}; |
|
})(Drupal, once, tabbable);
|
|
|