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.
198 lines
5.6 KiB
198 lines
5.6 KiB
1 year ago
|
/**
|
||
|
* @file
|
||
|
* Provides functionality for second level submenu navigation.
|
||
|
*/
|
||
|
|
||
|
((Drupal) => {
|
||
|
const { isDesktopNav } = Drupal.olivera;
|
||
|
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.olivera.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.olivera.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.olivera.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.olivera.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);
|