Skip to content

Instantly share code, notes, and snippets.

@AdamSoucie
Created December 14, 2019 15:00
Show Gist options
  • Save AdamSoucie/57d9e665e8a966b854d4656f17f4ac84 to your computer and use it in GitHub Desktop.
Save AdamSoucie/57d9e665e8a966b854d4656f17f4ac84 to your computer and use it in GitHub Desktop.
Script that helps support multi-level flyout menus
// Global Variables
const subMenus = document.querySelectorAll( 'li.menu-item-has-children' );
let focasable;
let delayTimer;
let focusTimer;
let currentSubMenu;
// Deal with the sub-menus
if ( subMenus ) {
Array.prototype.forEach.call( subMenus, function( element, iterator ){
element.addEventListener( 'mouseenter', openSubMenu );
element.addEventListener( 'mouseleave', closeSubMenu );
// Get all of the buttons for this sub-menu
let buttons = element.querySelectorAll( 'button' );
let links = element.querySelectorAll( '.sub-menu a' );
// Add the focus and blur listeners to every button & link
Array.prototype.forEach.call( buttons, function( e, i ) {
e.addEventListener( 'click', toggleSubMenu );
e.addEventListener( 'focus', subMenuGainFocus );
e.addEventListener( 'blur', subMenuLostFocus );
} );
Array.prototype.forEach.call( links, function( e, i ) {
e.addEventListener( 'focus', subMenuGainFocus );
e.addEventListener( 'blur', subMenuLostFocus );
} );
} );
}
// Open the sub-menu
function openSubMenu( event ) {
// Close out the currently open submenu if we've moved to a new top-level sub-menu
if( this.classList.contains( 'menu-level-0' ) && currentSubMenu ) {
currentSubMenu.classList.remove( 'open' );
currentSubMenu.setAttribute( 'aria-expanded', 'false' );
}
// Open the menu and set the ARIA attributes
this.classList.add( 'open' );
this.setAttribute( 'aria-expanded', 'true' );
// Set the ARIA on the button as well
this.children[1].setAttribute( 'aria-expanded', 'true' );
// Track the current sub-menu
currentSubMenu = this;
// Clear the timer so the menu doesn't arbitrarily close
clearTimeout( delayTimer );
}
// Close the sub-menu after
function closeSubMenu( event ) {
// Mark the sub-menu as currenet
currentSubMenu = this;
// Set our timer to account for motor disabilities
delayTimer = setTimeout( function( event ){
// Close the menu and sete the ARIA attributes
if ( currentSubMenu ) {
currentSubMenu.classList.remove( 'open' );
currentSubMenu.setAttribute( 'aria-expanded', 'false' );
// Set the ARIA on the button as well
currentSubMenu.children[1].setAttribute( 'aria-expanded', 'false' );
// Clear the current submenu
currentSubMenu = false;
}
}, 1000 );
}
// Handle toggling the sub-menu
function toggleSubMenu( event ) {
let button = event.target;
if ( button.classList.contains( 'fas' ) ) {
button = button.parentNode;
}
if ( button ) {
button.parentNode.classList.toggle( 'open' );
button.parentNode.setAttribute( 'aria-expanded', 'false' === button.parentNode.getAttribute( 'aria-expanded' ) ? 'true' : 'false' );
button.setAttribute( 'aria-expanded', 'false' === button.getAttribute( 'aria-expanded' ) ? 'true' : 'false' );
}
}
// Handle the sub-menu gaining focus
function subMenuGainFocus( event ) {
// If there is an active focus timer, get rid of it because we're still in the sub-menu
if ( focusTimer ) {
clearTimeout( focusTimer );
focusTimer = null;
}
}
// Handle the sub-menu losing focus
function subMenuLostFocus( event ) {
focusTimer = setTimeout( function() {
let openNav = document.querySelector('li.menu-item-has-children.open');
if ( openNav ) {
openNav.classList.remove( 'open' );
openNav.setAttribute( 'aria-expanded', 'false' === event.target.getAttribute( 'aria-expanded' ) ? 'true' : 'false' );
}
}, 10 );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment