136 lines
3.9 KiB
JavaScript
136 lines
3.9 KiB
JavaScript
((Drupal) => {
|
|
/**
|
|
* Checks if navWrapper contains "is-active" class.
|
|
* @param {object} 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('js-overlay-active');
|
|
props.body.classList.add('js-fixed');
|
|
props.navWrapper.classList.add('is-active');
|
|
} else {
|
|
props.body.classList.remove('js-overlay-active');
|
|
props.body.classList.remove('js-fixed');
|
|
props.navWrapper.classList.remove('is-active');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Init function for 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));
|
|
});
|
|
|
|
// Closes any open sub navigation first, then close 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.
|
|
props.navWrapper.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Tab') {
|
|
if (e.shiftKey) {
|
|
if (
|
|
document.activeElement === props.firstFocusableEl &&
|
|
!props.olivero.isDesktopNav()
|
|
) {
|
|
props.navButton.focus();
|
|
e.preventDefault();
|
|
}
|
|
} else if (
|
|
document.activeElement === props.lastFocusableEl &&
|
|
!props.olivero.isDesktopNav()
|
|
) {
|
|
props.navButton.focus();
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Remove overlays when browser is resized and desktop nav appears.
|
|
// @todo Use core/drupal.debounce library to throttle when we move into theming.
|
|
window.addEventListener('resize', () => {
|
|
if (props.olivero.isDesktopNav()) {
|
|
toggleNav(props, false);
|
|
props.body.classList.remove('js-overlay-active');
|
|
props.body.classList.remove('js-fixed');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize the navigation JS.
|
|
*/
|
|
Drupal.behaviors.oliveroNavigation = {
|
|
attach(context, settings) {
|
|
const navWrapperId = 'header-nav';
|
|
const navWrapper = context.querySelector(
|
|
`#${navWrapperId}:not(.${navWrapperId}-processed)`,
|
|
);
|
|
if (navWrapper) {
|
|
navWrapper.classList.add(`${navWrapperId}-processed`);
|
|
const { olivero } = Drupal;
|
|
const navButton = context.querySelector('.mobile-nav-button');
|
|
const body = context.querySelector('body');
|
|
const overlay = context.querySelector('.overlay');
|
|
const focusableNavElements = navWrapper.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
);
|
|
const firstFocusableEl = focusableNavElements[0];
|
|
const lastFocusableEl =
|
|
focusableNavElements[focusableNavElements.length - 1];
|
|
|
|
init({
|
|
settings,
|
|
olivero,
|
|
navWrapperId,
|
|
navWrapper,
|
|
navButton,
|
|
body,
|
|
overlay,
|
|
firstFocusableEl,
|
|
lastFocusableEl,
|
|
});
|
|
}
|
|
},
|
|
};
|
|
})(Drupal);
|