mirror of
https://ghproxy.net/https://github.com/AlxMedia/featureon.git
synced 2025-08-26 05:49:06 +08:00
319 lines
8.6 KiB
JavaScript
319 lines
8.6 KiB
JavaScript
/**
|
|
* Polyfill for IE11 - adds NodeList.foreach().
|
|
*
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
|
|
*/
|
|
if ( window.NodeList && ! NodeList.prototype.forEach ) {
|
|
NodeList.prototype.forEach = function( callback, thisArg ) {
|
|
thisArg = thisArg || window;
|
|
for ( var i = 0; i < this.length; i++ ) { // eslint-disable-line vars-on-top
|
|
callback.call( thisArg, this[ i ], i, this );
|
|
}
|
|
};
|
|
}
|
|
|
|
window.alxMediaMenu = {
|
|
|
|
/**
|
|
*
|
|
* @param {Object} args - The arguments.
|
|
* @param {string} args.selector - The navigation selector.
|
|
* @param {int} args.breakpoint - The breakpoint in pixels.
|
|
*/
|
|
init: function( args ) {
|
|
var self = this,
|
|
navs = document.querySelectorAll( args.selector );
|
|
|
|
if ( ! navs.length ) {
|
|
return;
|
|
}
|
|
|
|
navs.forEach( function( nav ) {
|
|
var menuToggler = nav.querySelector( '.menu-toggle' );
|
|
|
|
// Hide menu toggle button if menu is empty and return early.
|
|
if ( ! nav.querySelector( 'ul' ) && nav.querySelector( '.menu-toggle' ) ) {
|
|
nav.querySelector( '.menu-toggle' ).style.display = 'none';
|
|
}
|
|
|
|
// Add nav-menu class.
|
|
if ( ! nav.classList.contains( 'nav-menu' ) ) {
|
|
nav.classList.add( 'nav-menu' );
|
|
}
|
|
|
|
// Toggle the hover event listeners.
|
|
self.toggleHoverEventListeners( nav );
|
|
|
|
// Toggle focus classes on links.
|
|
nav.querySelectorAll( 'a,button' ).forEach( function( link ) {
|
|
link.addEventListener( 'focus', window.alxMediaMenu.toggleFocus, true );
|
|
link.addEventListener( 'blur', window.alxMediaMenu.toggleFocus, true );
|
|
});
|
|
|
|
menuToggler.addEventListener( 'click', function() {
|
|
if ( nav.classList.contains( 'toggled' ) ) {
|
|
menuToggler.setAttribute( 'aria-expanded', 'false' );
|
|
nav.classList.remove( 'toggled' );
|
|
} else {
|
|
menuToggler.setAttribute( 'aria-expanded', 'true' );
|
|
nav.classList.add( 'toggled' );
|
|
}
|
|
});
|
|
|
|
// If on mobile nav, close it when clicking outside.
|
|
// If on desktop, close expanded submenus when clicking outside.
|
|
document.addEventListener( 'click', function( event ) {
|
|
if ( ! nav.contains( event.target ) ) {
|
|
|
|
// Mobile.
|
|
nav.classList.remove( 'toggled' );
|
|
|
|
// Desktop.
|
|
nav.querySelectorAll( 'button.active,.sub-menu.active' ).forEach( function( el ) {
|
|
el.classList.remove( 'active' );
|
|
});
|
|
|
|
menuToggler.setAttribute( 'aria-expanded', 'false' );
|
|
}
|
|
});
|
|
});
|
|
|
|
// Toggle mobile classes on initial load.
|
|
window.alxMediaMenu.toggleMobile( args.selector, args.breakpoint );
|
|
|
|
// Toggle mobile classes on resize.
|
|
window.addEventListener( 'resize', function() {
|
|
|
|
// If timer is null, reset it to our bounceDelay and run, otherwise wait until timer is cleared.
|
|
if ( ! window.resizeDebouncedTimeout ) {
|
|
window.resizeDebouncedTimeout = setTimeout( function() {
|
|
window.resizeDebouncedTimeout = null;
|
|
window.alxMediaMenu.toggleMobile( args.selector, args.breakpoint );
|
|
}, 250 );
|
|
}
|
|
});
|
|
|
|
// Toggle focus classes to allow submenu access on tables.
|
|
document.querySelectorAll( args.selector ).forEach( function( el ) {
|
|
window.alxMediaMenu.toggleFocusTouch( el );
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Expand a menu item.
|
|
*
|
|
* @param {Element} - The menu item (DOM element).
|
|
* @return {void}
|
|
*/
|
|
toggleItem: function( el ) {
|
|
var parentLi = this.helper.firstAncestorMatch( el, 'li' ),
|
|
parentUl = this.helper.firstAncestorMatch( el, 'ul' ),
|
|
ul = parentLi.querySelector( 'ul.sub-menu' );
|
|
|
|
parentLi.classList.remove( 'hover' );
|
|
|
|
ul.setAttribute( 'tabindex', '-1' );
|
|
this.helper.toggleClass( ul, 'active' );
|
|
this.helper.toggleClass( el, 'active' );
|
|
|
|
// Go one level up in the list, and close other items that are already open.
|
|
parentUl.querySelectorAll( 'ul.sub-menu' ).forEach( function( subMenu ) {
|
|
var subMenuButton;
|
|
if ( ! parentLi.contains( subMenu ) ) {
|
|
subMenu.classList.remove( 'active' );
|
|
subMenuButton = subMenu.parentNode.querySelector( 'button.active' );
|
|
if ( subMenuButton ) {
|
|
subMenuButton.classList.remove( 'active' );
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Toggles a mobile class to elements matching our selector,
|
|
* depending on the defined breakpoint.
|
|
*
|
|
* @param {string} selector - The elements where we want to toggle our mobile class.
|
|
* @param {string} className - The class-name we want to toggle.
|
|
* @param {int} breakpoint - The breakpoint.
|
|
* @return {void}
|
|
*/
|
|
toggleMobile: function( selector, breakpoint ) {
|
|
var self = this,
|
|
screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
|
|
navs = document.body.querySelectorAll( selector ),
|
|
isMobile;
|
|
|
|
breakpoint = breakpoint || 720;
|
|
isMobile = breakpoint > screenWidth;
|
|
|
|
if ( isMobile ) {
|
|
navs.forEach( function( nav ) {
|
|
if ( ! nav.classList.contains( 'mobile' ) ) {
|
|
nav.classList.add( 'mobile' );
|
|
self.toggleHoverEventListeners( nav );
|
|
}
|
|
});
|
|
} else {
|
|
navs.forEach( function( nav ) {
|
|
if ( nav.classList.contains( 'mobile' ) ) {
|
|
nav.classList.remove( 'mobile' );
|
|
self.toggleHoverEventListeners( nav );
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add a "hover" class.
|
|
*
|
|
* @return {void}
|
|
*/
|
|
liMouseEnterEvent: function() {
|
|
this.classList.add( 'hover' );
|
|
},
|
|
|
|
/**
|
|
* Remove the "hover" class.
|
|
*
|
|
* @return {void}
|
|
*/
|
|
liMouseLeaveEvent: function() {
|
|
this.classList.remove( 'hover' );
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {Element} nav - The nav element.
|
|
* @return {void}
|
|
*/
|
|
toggleHoverEventListeners: function( nav ) {
|
|
if ( nav.classList.contains( 'mobile' ) ) {
|
|
this.removeHoverEventListeners( nav );
|
|
} else {
|
|
this.addHoverEventListeners( nav );
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add event-listeners for hover events.
|
|
*
|
|
* @param {Element} nav - The nav element.
|
|
* @return {void}
|
|
*/
|
|
addHoverEventListeners: function( nav ) {
|
|
nav.querySelectorAll( 'li' ).forEach( function( li ) {
|
|
li.addEventListener( 'mouseenter', window.alxMediaMenu.liMouseEnterEvent );
|
|
li.addEventListener( 'mouseleave', window.alxMediaMenu.liMouseLeaveEvent );
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Remove event-listeners for hover events.
|
|
*
|
|
* @param {Element} nav - The nav element.
|
|
* @return {void}
|
|
*/
|
|
removeHoverEventListeners: function( nav ) {
|
|
nav.querySelectorAll( 'li' ).forEach( function( li ) {
|
|
li.removeEventListener( 'mouseenter', window.alxMediaMenu.liMouseEnterEvent );
|
|
li.removeEventListener( 'mouseleave', window.alxMediaMenu.liMouseLeaveEvent );
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sets or removes .focus class on an element.
|
|
*
|
|
* @return {void}
|
|
*/
|
|
toggleFocus: function() {
|
|
var self = this;
|
|
|
|
// Move up through the ancestors of the current link until we hit .nav-menu.
|
|
while ( -1 === self.className.indexOf( 'nav-menu' ) ) {
|
|
// On li elements toggle the class .focus.
|
|
if ( 'li' === self.tagName.toLowerCase() ) {
|
|
if ( -1 !== self.className.indexOf( 'focus' ) ) {
|
|
self.className = self.className.replace( ' focus', '' );
|
|
} else {
|
|
self.className += ' focus';
|
|
}
|
|
}
|
|
|
|
self = self.parentElement;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Toggle focus classes to allow submenu access on tables.
|
|
*
|
|
* @param {Element} el - The menu element.
|
|
* @return {void}
|
|
*/
|
|
toggleFocusTouch: function( el ) {
|
|
var touchStartFn,
|
|
parentLinks = el.querySelectorAll( '.menu-item-has-children > a, .page_item_has_children > a' );
|
|
|
|
if ( 'ontouchstart' in window ) {
|
|
touchStartFn = function( e ) {
|
|
var menuItem = this.parentNode;
|
|
|
|
if ( ! menuItem.classList.contains( 'focus' ) ) {
|
|
e.preventDefault();
|
|
menuItem.parentNode.children.forEach( function( child ) {
|
|
if ( menuItem !== child ) {
|
|
child.classList.remove( 'focus' );
|
|
}
|
|
});
|
|
menuItem.classList.add( 'focus' );
|
|
} else {
|
|
menuItem.classList.remove( 'focus' );
|
|
}
|
|
};
|
|
|
|
parentLinks.forEach( function( parentLink ) {
|
|
parentLink.addEventListener( 'touchstart', touchStartFn, false );
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Helper methods.
|
|
*/
|
|
helper: {
|
|
|
|
/**
|
|
* Toggle a class to an element.
|
|
*
|
|
* @param {Element} el - The element.
|
|
* @param {string} className - The class we want to toggle.
|
|
* @return {void}
|
|
*/
|
|
toggleClass: function( el, className ) {
|
|
if ( el.classList.contains( className ) ) {
|
|
el.classList.remove( className );
|
|
} else {
|
|
el.classList.add( className );
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the 1st ancestor of an element that matches our selector.
|
|
*
|
|
* @param {Element} el - The element.
|
|
* @param {string} selector - The class we want to toggle.
|
|
* @return {Element}
|
|
*/
|
|
firstAncestorMatch: function( el, selector ) {
|
|
if ( el.parentNode.matches( selector ) ) {
|
|
return el.parentNode;
|
|
}
|
|
return this.firstAncestorMatch( el.parentNode, selector );
|
|
}
|
|
}
|
|
};
|
|
|
|
window.alxMediaMenu.init({
|
|
selector: '.main-navigation.nav-menu',
|
|
breakpoint: 720
|
|
});
|