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