@meom/navigation
Advanced tools
Comparing version 1.0.5 to 1.1.0
# Changelog | ||
## Version 1.1.0 released April 3, 2023 | ||
- Fixed: show only one sub sub menu open at once. | ||
- Updated NPM packages. | ||
## Version 1.0.5 released May 7, 2022 | ||
@@ -3,0 +6,0 @@ - Added `:hover` to sub menus in hover demo. |
@@ -8,15 +8,19 @@ (() => { | ||
elem.classList.add(animation); | ||
elem.addEventListener("animationend", function endAnimation() { | ||
elem.classList.remove(animation); | ||
if (hide) { | ||
elem.classList.remove(hide); | ||
} | ||
elem.removeEventListener("animationend", endAnimation, false); | ||
}, false); | ||
elem.addEventListener( | ||
"animationend", | ||
function endAnimation() { | ||
elem.classList.remove(animation); | ||
if (hide) { | ||
elem.classList.remove(hide); | ||
} | ||
elem.removeEventListener("animationend", endAnimation, false); | ||
}, | ||
false | ||
); | ||
} | ||
function updateAria(el, aria) { | ||
if (typeof el === "undefined" || 0 >= aria.length) { | ||
if ("undefined" === typeof el || 0 >= aria.length) { | ||
return; | ||
} | ||
const hiddenEl = el.getAttribute(`aria-${aria}`) === "true" ? "false" : "true"; | ||
const hiddenEl = "true" === el.getAttribute(`aria-${aria}`) ? "false" : "true"; | ||
el.setAttribute(`aria-${aria}`, hiddenEl); | ||
@@ -55,4 +59,6 @@ } | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind(this); | ||
this._closeAllSubSubMenus = this.closeAllSubSubMenus.bind(this); | ||
this._setSubMenu = this.setSubMenu.bind(this); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind(this); | ||
this._closeAllSubSubMenuToggles = this.closeAllSubSubMenuToggles.bind(this); | ||
this._handleDocClick = this.handleDocClick.bind(this); | ||
@@ -66,3 +72,5 @@ this._handleFocus = this.handleFocus.bind(this); | ||
this.$subNavs = this.$element.querySelectorAll(this.settings.subNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll(this.settings.subSubNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll( | ||
this.settings.subSubNavAnchors | ||
); | ||
this.create(); | ||
@@ -73,3 +81,3 @@ } | ||
this.$element.setAttribute("data-meom-nav", "navigation"); | ||
this.$subNavs.forEach(function(subNav, index) { | ||
this.$subNavs.forEach(function(subNav) { | ||
if (this.settings.action === "click") { | ||
@@ -145,10 +153,20 @@ subNav.setAttribute("hidden", ""); | ||
const closestSubButton = target.closest('[data-meom-nav="sub-toggle"]'); | ||
const closestSubSubButton = target.closest('[data-meom-nav="sub-sub-toggle"]'); | ||
const closestSubSubButton = target.closest( | ||
'[data-meom-nav="sub-sub-toggle"]' | ||
); | ||
if (!closestSubButton && !closestSubSubButton) { | ||
return this; | ||
} | ||
if (!target.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue) && !target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
if (!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && !target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
this._closeAllSubMenus(); | ||
this._closeAllSubMenuToggles(); | ||
} | ||
if (!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
this._closeAllSubSubMenus(); | ||
this._closeAllSubSubMenuToggles(); | ||
} | ||
updateAria(target, "expanded"); | ||
@@ -167,3 +185,5 @@ if (target.nextElementSibling) { | ||
Navigation.prototype.handleCloseSubNav = function(event) { | ||
const openSubMenu = document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const openSubMenu = document.querySelector( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if (openSubMenu) { | ||
@@ -190,3 +210,5 @@ const focusableElements = openSubMenu.querySelectorAll([ | ||
if (ESCAPE_KEY === event.keyCode) { | ||
if (event.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]')) { | ||
if (event.target.matches( | ||
'[data-meom-nav="sub-toggle"][aria-expanded="true"]' | ||
)) { | ||
this._handleSubNav(event); | ||
@@ -197,3 +219,5 @@ this._closeAllSubMenus(); | ||
} | ||
const parentSubMenu = event.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const parentSubMenu = event.target.closest( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if (parentSubMenu) { | ||
@@ -241,3 +265,5 @@ const subMenuToggle = parentSubMenu.previousElementSibling; | ||
Navigation.prototype.closeAllSubMenus = function() { | ||
const openSubMenus = document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const openSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubMenus.forEach(function(openSubMenu) { | ||
@@ -248,2 +274,11 @@ this._setSubMenu(openSubMenu); | ||
}; | ||
Navigation.prototype.closeAllSubSubMenus = function() { | ||
const openSubSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubSubMenus.forEach(function(openSubSubMenu) { | ||
this._setSubMenu(openSubSubMenu); | ||
}, this); | ||
return this; | ||
}; | ||
Navigation.prototype.setSubMenu = function(submenu, event) { | ||
@@ -259,3 +294,8 @@ if (!submenu) { | ||
if (this.settings.onOpenSubNav && typeof this.settings.onOpenSubNav === "function") { | ||
this.settings.onOpenSubNav(this.$element, this.$toggle, submenu, event); | ||
this.settings.onOpenSubNav( | ||
this.$element, | ||
this.$toggle, | ||
submenu, | ||
event | ||
); | ||
} | ||
@@ -265,3 +305,8 @@ } else { | ||
if (this.settings.onCloseSubNav && typeof this.settings.onCloseSubNav === "function") { | ||
this.settings.onCloseSubNav(this.$element, this.$toggle, submenu, event); | ||
this.settings.onCloseSubNav( | ||
this.$element, | ||
this.$toggle, | ||
submenu, | ||
event | ||
); | ||
} | ||
@@ -272,7 +317,11 @@ } | ||
Navigation.prototype.closeAllSubMenuToggles = function() { | ||
const openSubMenuToggles = document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]'); | ||
const openSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubMenuToggles.forEach(function(openSubMenuToggle) { | ||
updateAria(openSubMenuToggle, "expanded"); | ||
}); | ||
const openSubSubMenuToggles = document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]'); | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function(openSubSubMenuToggle) { | ||
@@ -283,2 +332,11 @@ updateAria(openSubSubMenuToggle, "expanded"); | ||
}; | ||
Navigation.prototype.closeAllSubSubMenuToggles = function() { | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function(openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, "expanded"); | ||
}); | ||
return this; | ||
}; | ||
@@ -285,0 +343,0 @@ // src/navigation-multiple.js |
@@ -8,15 +8,19 @@ (() => { | ||
elem.classList.add(animation); | ||
elem.addEventListener("animationend", function endAnimation() { | ||
elem.classList.remove(animation); | ||
if (hide) { | ||
elem.classList.remove(hide); | ||
} | ||
elem.removeEventListener("animationend", endAnimation, false); | ||
}, false); | ||
elem.addEventListener( | ||
"animationend", | ||
function endAnimation() { | ||
elem.classList.remove(animation); | ||
if (hide) { | ||
elem.classList.remove(hide); | ||
} | ||
elem.removeEventListener("animationend", endAnimation, false); | ||
}, | ||
false | ||
); | ||
} | ||
function updateAria(el, aria) { | ||
if (typeof el === "undefined" || 0 >= aria.length) { | ||
if ("undefined" === typeof el || 0 >= aria.length) { | ||
return; | ||
} | ||
const hiddenEl = el.getAttribute(`aria-${aria}`) === "true" ? "false" : "true"; | ||
const hiddenEl = "true" === el.getAttribute(`aria-${aria}`) ? "false" : "true"; | ||
el.setAttribute(`aria-${aria}`, hiddenEl); | ||
@@ -55,4 +59,6 @@ } | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind(this); | ||
this._closeAllSubSubMenus = this.closeAllSubSubMenus.bind(this); | ||
this._setSubMenu = this.setSubMenu.bind(this); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind(this); | ||
this._closeAllSubSubMenuToggles = this.closeAllSubSubMenuToggles.bind(this); | ||
this._handleDocClick = this.handleDocClick.bind(this); | ||
@@ -66,3 +72,5 @@ this._handleFocus = this.handleFocus.bind(this); | ||
this.$subNavs = this.$element.querySelectorAll(this.settings.subNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll(this.settings.subSubNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll( | ||
this.settings.subSubNavAnchors | ||
); | ||
this.create(); | ||
@@ -73,3 +81,3 @@ } | ||
this.$element.setAttribute("data-meom-nav", "navigation"); | ||
this.$subNavs.forEach(function(subNav, index) { | ||
this.$subNavs.forEach(function(subNav) { | ||
if (this.settings.action === "click") { | ||
@@ -145,10 +153,20 @@ subNav.setAttribute("hidden", ""); | ||
const closestSubButton = target.closest('[data-meom-nav="sub-toggle"]'); | ||
const closestSubSubButton = target.closest('[data-meom-nav="sub-sub-toggle"]'); | ||
const closestSubSubButton = target.closest( | ||
'[data-meom-nav="sub-sub-toggle"]' | ||
); | ||
if (!closestSubButton && !closestSubSubButton) { | ||
return this; | ||
} | ||
if (!target.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue) && !target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
if (!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && !target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
this._closeAllSubMenus(); | ||
this._closeAllSubMenuToggles(); | ||
} | ||
if (!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && target.matches('[data-meom-nav="sub-sub-toggle"]')) { | ||
this._closeAllSubSubMenus(); | ||
this._closeAllSubSubMenuToggles(); | ||
} | ||
updateAria(target, "expanded"); | ||
@@ -167,3 +185,5 @@ if (target.nextElementSibling) { | ||
Navigation.prototype.handleCloseSubNav = function(event) { | ||
const openSubMenu = document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const openSubMenu = document.querySelector( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if (openSubMenu) { | ||
@@ -190,3 +210,5 @@ const focusableElements = openSubMenu.querySelectorAll([ | ||
if (ESCAPE_KEY === event.keyCode) { | ||
if (event.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]')) { | ||
if (event.target.matches( | ||
'[data-meom-nav="sub-toggle"][aria-expanded="true"]' | ||
)) { | ||
this._handleSubNav(event); | ||
@@ -197,3 +219,5 @@ this._closeAllSubMenus(); | ||
} | ||
const parentSubMenu = event.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const parentSubMenu = event.target.closest( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if (parentSubMenu) { | ||
@@ -241,3 +265,5 @@ const subMenuToggle = parentSubMenu.previousElementSibling; | ||
Navigation.prototype.closeAllSubMenus = function() { | ||
const openSubMenus = document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`); | ||
const openSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubMenus.forEach(function(openSubMenu) { | ||
@@ -248,2 +274,11 @@ this._setSubMenu(openSubMenu); | ||
}; | ||
Navigation.prototype.closeAllSubSubMenus = function() { | ||
const openSubSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubSubMenus.forEach(function(openSubSubMenu) { | ||
this._setSubMenu(openSubSubMenu); | ||
}, this); | ||
return this; | ||
}; | ||
Navigation.prototype.setSubMenu = function(submenu, event) { | ||
@@ -259,3 +294,8 @@ if (!submenu) { | ||
if (this.settings.onOpenSubNav && typeof this.settings.onOpenSubNav === "function") { | ||
this.settings.onOpenSubNav(this.$element, this.$toggle, submenu, event); | ||
this.settings.onOpenSubNav( | ||
this.$element, | ||
this.$toggle, | ||
submenu, | ||
event | ||
); | ||
} | ||
@@ -265,3 +305,8 @@ } else { | ||
if (this.settings.onCloseSubNav && typeof this.settings.onCloseSubNav === "function") { | ||
this.settings.onCloseSubNav(this.$element, this.$toggle, submenu, event); | ||
this.settings.onCloseSubNav( | ||
this.$element, | ||
this.$toggle, | ||
submenu, | ||
event | ||
); | ||
} | ||
@@ -272,7 +317,11 @@ } | ||
Navigation.prototype.closeAllSubMenuToggles = function() { | ||
const openSubMenuToggles = document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]'); | ||
const openSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubMenuToggles.forEach(function(openSubMenuToggle) { | ||
updateAria(openSubMenuToggle, "expanded"); | ||
}); | ||
const openSubSubMenuToggles = document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]'); | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function(openSubSubMenuToggle) { | ||
@@ -283,2 +332,11 @@ updateAria(openSubSubMenuToggle, "expanded"); | ||
}; | ||
Navigation.prototype.closeAllSubSubMenuToggles = function() { | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function(openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, "expanded"); | ||
}); | ||
return this; | ||
}; | ||
@@ -285,0 +343,0 @@ // src/navigation.js |
@@ -62,6 +62,6 @@ /** | ||
* @param {Object} element Navigation element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} options The settings and options for this instance. | ||
*/ | ||
function Navigation( element, toggle, options = {} ) { | ||
function Navigation(element, toggle, options = {}) { | ||
// Default settings. | ||
@@ -71,3 +71,4 @@ const defaults = { | ||
subNavAnchors: '.js-site-nav-items > .menu-item-has-children > a', | ||
subSubNavAnchors: '.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
subSubNavAnchors: | ||
'.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
toggleNavClass: true, | ||
@@ -85,3 +86,4 @@ toggleNavClassValue: 'is-opened', | ||
expandChildNavText: 'Sub menu', | ||
dropDownIcon: '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
dropDownIcon: | ||
'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
onCreate: null, | ||
@@ -95,11 +97,13 @@ onOpenNav: null, | ||
// Bind methods. | ||
this._handleNav = this.handleNav.bind( this ); | ||
this._handleSubNav = this.handleSubNav.bind( this ); | ||
this._handleCloseNav = this.handleCloseNav.bind( this ); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind( this ); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind( this ); | ||
this._setSubMenu = this.setSubMenu.bind( this ); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind( this ); | ||
this._handleDocClick = this.handleDocClick.bind( this ); | ||
this._handleFocus = this.handleFocus.bind( this ); | ||
this._handleNav = this.handleNav.bind(this); | ||
this._handleSubNav = this.handleSubNav.bind(this); | ||
this._handleCloseNav = this.handleCloseNav.bind(this); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind(this); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind(this); | ||
this._closeAllSubSubMenus = this.closeAllSubSubMenus.bind(this); | ||
this._setSubMenu = this.setSubMenu.bind(this); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind(this); | ||
this._closeAllSubSubMenuToggles = this.closeAllSubSubMenuToggles.bind(this); | ||
this._handleDocClick = this.handleDocClick.bind(this); | ||
this._handleFocus = this.handleFocus.bind(this); | ||
@@ -116,5 +120,3 @@ // Merge options to defaults. | ||
// Set all sub and sub sub navigations. | ||
this.$subNavs = this.$element.querySelectorAll( | ||
this.settings.subNavAnchors | ||
); | ||
this.$subNavs = this.$element.querySelectorAll(this.settings.subNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll( | ||
@@ -133,64 +135,61 @@ this.settings.subSubNavAnchors | ||
// Set ARIA for navigation toggle button. | ||
this.$toggle.setAttribute( 'aria-expanded', 'false' ); | ||
this.$toggle.setAttribute('aria-expanded', 'false'); | ||
// Set data value for nav element. This is for targeting without a class name. | ||
this.$element.setAttribute( 'data-meom-nav', 'navigation' ); | ||
this.$element.setAttribute('data-meom-nav', 'navigation'); | ||
// Setup sub navs toggle buttons. | ||
this.$subNavs.forEach( function ( subNav, index ) { | ||
this.$subNavs.forEach(function (subNav) { | ||
// Hide link in JS to avoid cumulative layout shift (CLS). | ||
if ( this.settings.action === 'click' ) { | ||
subNav.setAttribute( 'hidden', '' ); | ||
if (this.settings.action === 'click') { | ||
subNav.setAttribute('hidden', ''); | ||
} | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.className = `${ this.settings.subToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
if ( this.settings.action === 'click' ) { | ||
subToggleButton.innerHTML = `${ subNav.textContent }${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'click') { | ||
subToggleButton.innerHTML = `${subNav.textContent}${this.settings.dropDownIcon}`; | ||
} | ||
if ( this.settings.action === 'hover' ) { | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'hover') { | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
} | ||
// Add toggle button after anchor. | ||
subNav.after( subToggleButton ); | ||
}, this ); | ||
subNav.after(subToggleButton); | ||
}, this); | ||
// Setup sub sub navs toggle buttons. | ||
this.$subSubNavs.forEach( function ( subSubNav, index ) { | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
this.$subSubNavs.forEach(function (subSubNav, index) { | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.setAttribute( | ||
'aria-controls', | ||
`sub-sub-menu-${ index }` | ||
); | ||
subToggleButton.setAttribute('aria-controls', `sub-sub-menu-${index}`); | ||
// Add matching id for next sub-sub-menu. | ||
if ( subSubNav.nextElementSibling ) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${ index }`; | ||
if (subSubNav.nextElementSibling) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${index}`; | ||
} | ||
subToggleButton.className = `${ this.settings.subSubToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subSubToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
// Add toggle button after anchor. | ||
subSubNav.after( subToggleButton ); | ||
}, this ); | ||
subSubNav.after(subToggleButton); | ||
}, this); | ||
// Set event listeners. | ||
this.$toggle.addEventListener( 'click', this._handleNav, false ); | ||
this.$element.addEventListener( 'click', this._handleSubNav, false ); | ||
document.addEventListener( 'keydown', this._handleCloseNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleCloseSubNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleFocus, false ); | ||
document.addEventListener( 'click', this._handleDocClick, false ); | ||
this.$toggle.addEventListener('click', this._handleNav, false); | ||
this.$element.addEventListener('click', this._handleSubNav, false); | ||
document.addEventListener('keydown', this._handleCloseNav, false); | ||
this.$element.addEventListener('keydown', this._handleCloseSubNav, false); | ||
this.$element.addEventListener('keydown', this._handleFocus, false); | ||
document.addEventListener('click', this._handleDocClick, false); | ||
@@ -206,3 +205,3 @@ /** | ||
) { | ||
this.settings.onCreate( this.$element, this.$toggle ); | ||
this.settings.onCreate(this.$element, this.$toggle); | ||
} | ||
@@ -219,9 +218,9 @@ | ||
*/ | ||
Navigation.prototype.handleNav = function ( event ) { | ||
Navigation.prototype.handleNav = function (event) { | ||
// If navigation is closed and we want to open it. | ||
if ( ! this.navOpened ) { | ||
updateAria( this.$toggle, 'expanded' ); | ||
if (!this.navOpened) { | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.add( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.add(this.settings.toggleNavClassValue); | ||
} | ||
@@ -241,13 +240,13 @@ | ||
) { | ||
this.settings.onOpenNav( this.$element, this.$toggle, event ); | ||
this.settings.onOpenNav(this.$element, this.$toggle, event); | ||
} | ||
} else { | ||
updateAria( this.$toggle, 'expanded' ); | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.remove( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.remove(this.settings.toggleNavClassValue); | ||
} | ||
// Set focus back to toggle button. | ||
if ( this.$toggle ) { | ||
if (this.$toggle) { | ||
this.$toggle.focus(); | ||
@@ -271,3 +270,3 @@ } | ||
) { | ||
this.settings.onCloseNav( this.$element, this.$toggle, event ); | ||
this.settings.onCloseNav(this.$element, this.$toggle, event); | ||
} | ||
@@ -285,6 +284,6 @@ } | ||
*/ | ||
Navigation.prototype.handleSubNav = function ( event ) { | ||
Navigation.prototype.handleSubNav = function (event) { | ||
const target = event.target; | ||
// Use .closest because there can be SVG inside the button. | ||
const closestSubButton = target.closest( '[data-meom-nav="sub-toggle"]' ); | ||
const closestSubButton = target.closest('[data-meom-nav="sub-toggle"]'); | ||
const closestSubSubButton = target.closest( | ||
@@ -295,3 +294,3 @@ '[data-meom-nav="sub-sub-toggle"]' | ||
// If the clicked element doesn't have the correct data attribute, bail. | ||
if ( ! closestSubButton && ! closestSubSubButton ) { | ||
if (!closestSubButton && !closestSubSubButton) { | ||
return this; | ||
@@ -304,4 +303,6 @@ } | ||
if ( | ||
! target.nextElementSibling.classList.contains( this.settings.toggleSubNavClassValue ) && | ||
! target.matches( '[data-meom-nav="sub-sub-toggle"]' ) | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
!target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
@@ -312,8 +313,20 @@ this._closeAllSubMenus(); | ||
// Then again, close all sub sub menus when trying to open any other sub sub menu that is not already open. | ||
// So that only one sub sub menu can be open at current time. | ||
if ( | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
this._closeAllSubSubMenus(); | ||
this._closeAllSubSubMenuToggles(); | ||
} | ||
// Update sub toggle ARIA. | ||
updateAria( target, 'expanded' ); | ||
updateAria(target, 'expanded'); | ||
// Toggle class for next <ul> element (sub-menu). | ||
if ( target.nextElementSibling ) { | ||
this._setSubMenu( target.nextElementSibling, event ); | ||
if (target.nextElementSibling) { | ||
this._setSubMenu(target.nextElementSibling, event); | ||
} | ||
@@ -330,3 +343,3 @@ | ||
*/ | ||
Navigation.prototype.handleCloseNav = function ( event ) { | ||
Navigation.prototype.handleCloseNav = function (event) { | ||
// Close nav on Esc key if nav is open. | ||
@@ -338,3 +351,3 @@ if ( | ||
) { | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -351,11 +364,11 @@ | ||
*/ | ||
Navigation.prototype.handleCloseSubNav = function ( event ) { | ||
Navigation.prototype.handleCloseSubNav = function (event) { | ||
// Set focusable elements inside sub-menu element. | ||
const openSubMenu = document.querySelector( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if ( openSubMenu ) { | ||
if (openSubMenu) { | ||
// Focusable elements. | ||
const focusableElements = openSubMenu.querySelectorAll( [ | ||
const focusableElements = openSubMenu.querySelectorAll([ | ||
'a[href]', | ||
@@ -367,6 +380,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -376,3 +389,3 @@ // Last Tab closes sub-menu. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -399,3 +412,3 @@ ) { | ||
// Close sub-menu on Esc key. | ||
if ( ESCAPE_KEY === event.keyCode ) { | ||
if (ESCAPE_KEY === event.keyCode) { | ||
// If we are on the sub-menu toggle itself. | ||
@@ -407,3 +420,3 @@ if ( | ||
) { | ||
this._handleSubNav( event ); | ||
this._handleSubNav(event); | ||
this._closeAllSubMenus(); | ||
@@ -416,7 +429,7 @@ this._closeAllSubMenuToggles(); | ||
const parentSubMenu = event.target.closest( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
// Set focus to sub menu toggle. | ||
if ( parentSubMenu ) { | ||
if (parentSubMenu) { | ||
// sub-menu toggle. | ||
@@ -426,3 +439,3 @@ const subMenuToggle = parentSubMenu.previousElementSibling; | ||
// Set focus back to sub-menu toggle. | ||
if ( subMenuToggle ) { | ||
if (subMenuToggle) { | ||
subMenuToggle.focus(); | ||
@@ -445,5 +458,5 @@ } | ||
*/ | ||
Navigation.prototype.handleFocus = function ( event ) { | ||
Navigation.prototype.handleFocus = function (event) { | ||
// Bail if menu is not open. | ||
if ( ! this.navOpened ) { | ||
if (!this.navOpened) { | ||
return this; | ||
@@ -453,3 +466,3 @@ } | ||
// Bail if `closeNavOnLastTab` setting is not set to true. | ||
if ( ! this.settings.closeNavOnLastTab ) { | ||
if (!this.settings.closeNavOnLastTab) { | ||
return this; | ||
@@ -459,3 +472,3 @@ } | ||
// Set focusable elements inside element. | ||
const focusableElements = this.$element.querySelectorAll( [ | ||
const focusableElements = this.$element.querySelectorAll([ | ||
'a[href]', | ||
@@ -467,6 +480,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -476,3 +489,3 @@ // Close nav on last Tab. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -482,3 +495,3 @@ ) { | ||
// Close nav. | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -495,5 +508,5 @@ | ||
*/ | ||
Navigation.prototype.handleDocClick = function ( event ) { | ||
Navigation.prototype.handleDocClick = function (event) { | ||
// Bail if clicking inside the nav. | ||
if ( event.target.closest( '[data-meom-nav="navigation"]' ) ) { | ||
if (event.target.closest('[data-meom-nav="navigation"]')) { | ||
return this; | ||
@@ -515,8 +528,8 @@ } | ||
const openSubMenus = document.querySelectorAll( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubMenus.forEach( function ( openSubMenu ) { | ||
this._setSubMenu( openSubMenu ); | ||
}, this ); | ||
openSubMenus.forEach(function (openSubMenu) { | ||
this._setSubMenu(openSubMenu); | ||
}, this); | ||
@@ -527,19 +540,36 @@ return this; | ||
/** | ||
* Close all sub sub menus. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenus = function () { | ||
const openSubSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubSubMenus.forEach(function (openSubSubMenu) { | ||
this._setSubMenu(openSubSubMenu); | ||
}, this); | ||
return this; | ||
}; | ||
/** | ||
* Set classes and animate for sub-menu. | ||
* | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.setSubMenu = function ( submenu, event ) { | ||
if ( ! submenu ) { | ||
Navigation.prototype.setSubMenu = function (submenu, event) { | ||
if (!submenu) { | ||
return this; | ||
} | ||
if ( ! submenu.classList.contains( this.settings.toggleSubNavClassValue ) ) { | ||
submenu.classList.add( this.settings.toggleSubNavClassValue ); | ||
if (!submenu.classList.contains(this.settings.toggleSubNavClassValue)) { | ||
submenu.classList.add(this.settings.toggleSubNavClassValue); | ||
// Base animation with class. | ||
if ( this.settings.animateSubNav ) { | ||
animate( submenu, this.settings.animateSubNavClass ); | ||
if (this.settings.animateSubNav) { | ||
animate(submenu, this.settings.animateSubNavClass); | ||
} | ||
@@ -564,3 +594,3 @@ | ||
} else { | ||
submenu.classList.remove( this.settings.toggleSubNavClassValue ); | ||
submenu.classList.remove(this.settings.toggleSubNavClassValue); | ||
@@ -598,5 +628,5 @@ /** | ||
openSubMenuToggles.forEach( function ( openSubMenuToggle ) { | ||
updateAria( openSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubMenuToggles.forEach(function (openSubMenuToggle) { | ||
updateAria(openSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -607,5 +637,5 @@ const openSubSubMenuToggles = document.querySelectorAll( | ||
openSubSubMenuToggles.forEach( function ( openSubSubMenuToggle ) { | ||
updateAria( openSubSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -615,2 +645,19 @@ return this; | ||
/** | ||
* Close all sub sub menu toggles. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenuToggles = function () { | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
return this; | ||
}; | ||
export { Navigation, animate, updateAria }; |
@@ -1,2 +0,2 @@ | ||
/*! navigation 1.0.5 — © MEOM */ | ||
function t(t,e,s){t&&e&&(t.classList.add(e),t.addEventListener("animationend",(function n(){t.classList.remove(e),s&&t.classList.remove(s),t.removeEventListener("animationend",n,!1)}),!1))}function e(t,e){if(void 0===t||0>=e.length)return;const s="true"===t.getAttribute(`aria-${e}`)?"false":"true";t.setAttribute(`aria-${e}`,s)}function s(t,e,s={}){this._handleNav=this.handleNav.bind(this),this._handleSubNav=this.handleSubNav.bind(this),this._handleCloseNav=this.handleCloseNav.bind(this),this._handleCloseSubNav=this.handleCloseSubNav.bind(this),this._closeAllSubMenus=this.closeAllSubMenus.bind(this),this._setSubMenu=this.setSubMenu.bind(this),this._closeAllSubMenuToggles=this.closeAllSubMenuToggles.bind(this),this._handleDocClick=this.handleDocClick.bind(this),this._handleFocus=this.handleFocus.bind(this);const n={action:"click",subNavAnchors:".js-site-nav-items > .menu-item-has-children > a",subSubNavAnchors:".js-site-nav-items .sub-menu > .menu-item-has-children > a",toggleNavClass:!0,toggleNavClassValue:"is-opened",toggleSubNavClassValue:"is-opened",closeNavOnEscKey:!0,closeNavOnLastTab:!1,subNavClass:".sub-menu",subToggleButtonClasses:"",subSubToggleButtonClasses:"",animateSubNav:!1,animateSubNavClass:"",visuallyHiddenClass:"screen-reader-text",expandChildNavText:"Sub menu",dropDownIcon:'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>',onCreate:null,onOpenNav:null,onCloseNav:null,onOpenSubNav:null,onCloseSubNav:null,...s};this.$element=t,this.$toggle=e,this.settings=n,this.navOpened=!1,this.$subNavs=this.$element.querySelectorAll(this.settings.subNavAnchors),this.$subSubNavs=this.$element.querySelectorAll(this.settings.subSubNavAnchors),this.create()}s.prototype.create=function(){return this.$toggle.setAttribute("aria-expanded","false"),this.$element.setAttribute("data-meom-nav","navigation"),this.$subNavs.forEach((function(t,e){"click"===this.settings.action&&t.setAttribute("hidden","");const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-toggle"),s.setAttribute("aria-expanded","false"),s.className=`${this.settings.subToggleButtonClasses}`,s.type="button","click"===this.settings.action&&(s.innerHTML=`${t.textContent}${this.settings.dropDownIcon}`),"hover"===this.settings.action&&(s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`),t.after(s)}),this),this.$subSubNavs.forEach((function(t,e){const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-sub-toggle"),s.setAttribute("aria-expanded","false"),s.setAttribute("aria-controls",`sub-sub-menu-${e}`),t.nextElementSibling&&(t.nextElementSibling.id=`sub-sub-menu-${e}`),s.className=`${this.settings.subSubToggleButtonClasses}`,s.type="button",s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`,t.after(s)}),this),this.$toggle.addEventListener("click",this._handleNav,!1),this.$element.addEventListener("click",this._handleSubNav,!1),document.addEventListener("keydown",this._handleCloseNav,!1),this.$element.addEventListener("keydown",this._handleCloseSubNav,!1),this.$element.addEventListener("keydown",this._handleFocus,!1),document.addEventListener("click",this._handleDocClick,!1),this.settings.onCreate&&"function"==typeof this.settings.onCreate&&this.settings.onCreate(this.$element,this.$toggle),this},s.prototype.handleNav=function(t){return this.navOpened?(e(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.remove(this.settings.toggleNavClassValue),this.$toggle&&this.$toggle.focus(),this.navOpened=!1,this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this.settings.onCloseNav&&"function"==typeof this.settings.onCloseNav&&this.settings.onCloseNav(this.$element,this.$toggle,t)):(e(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.add(this.settings.toggleNavClassValue),this.navOpened=!0,this.settings.onOpenNav&&"function"==typeof this.settings.onOpenNav&&this.settings.onOpenNav(this.$element,this.$toggle,t)),this},s.prototype.handleSubNav=function(t){const s=t.target,n=s.closest('[data-meom-nav="sub-toggle"]'),i=s.closest('[data-meom-nav="sub-sub-toggle"]');return n||i?(s.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)||s.matches('[data-meom-nav="sub-sub-toggle"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),e(s,"expanded"),s.nextElementSibling&&this._setSubMenu(s.nextElementSibling,t),this):this},s.prototype.handleCloseNav=function(t){return this.navOpened&&this.settings.closeNavOnEscKey&&27===t.keyCode&&this._handleNav(t),this},s.prototype.handleCloseSubNav=function(t){const e=document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const s=e.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),n=s[s.length-1];9!==t.keyCode||t.shiftKey||t.target!==n||(this._closeAllSubMenus(),this._closeAllSubMenuToggles());const i=e.previousElementSibling;i&&9===t.keyCode&&t.shiftKey&&t.target===i&&(this._closeAllSubMenus(),this._closeAllSubMenuToggles())}if(27===t.keyCode){if(t.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]'))return this._handleSubNav(t),this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this;const e=t.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const t=e.previousElementSibling;t&&t.focus()}this._closeAllSubMenus(),this._closeAllSubMenuToggles()}return this},s.prototype.handleFocus=function(t){if(!this.navOpened)return this;if(!this.settings.closeNavOnLastTab)return this;const e=this.$element.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),s=e[e.length-1];return 9!==t.keyCode||t.shiftKey||t.target!==s||(t.preventDefault(),this._handleNav(t)),this},s.prototype.handleDocClick=function(t){return t.target.closest('[data-meom-nav="navigation"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),this},s.prototype.closeAllSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},s.prototype.setSubMenu=function(e,s){return e?(e.classList.contains(this.settings.toggleSubNavClassValue)?(e.classList.remove(this.settings.toggleSubNavClassValue),this.settings.onCloseSubNav&&"function"==typeof this.settings.onCloseSubNav&&this.settings.onCloseSubNav(this.$element,this.$toggle,e,s)):(e.classList.add(this.settings.toggleSubNavClassValue),this.settings.animateSubNav&&t(e,this.settings.animateSubNavClass),this.settings.onOpenSubNav&&"function"==typeof this.settings.onOpenSubNav&&this.settings.onOpenSubNav(this.$element,this.$toggle,e,s)),this):this},s.prototype.closeAllSubMenuToggles=function(){document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]').forEach((function(t){e(t,"expanded")}));return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){e(t,"expanded")})),this};export{s as Navigation,t as animate,e as updateAria}; | ||
/*! navigation 1.1.0 — © MEOM */ | ||
function t(t,e,s){t&&e&&(t.classList.add(e),t.addEventListener("animationend",(function n(){t.classList.remove(e),s&&t.classList.remove(s),t.removeEventListener("animationend",n,!1)}),!1))}function e(t,e){if(void 0===t||0>=e.length)return;const s="true"===t.getAttribute(`aria-${e}`)?"false":"true";t.setAttribute(`aria-${e}`,s)}function s(t,e,s={}){this._handleNav=this.handleNav.bind(this),this._handleSubNav=this.handleSubNav.bind(this),this._handleCloseNav=this.handleCloseNav.bind(this),this._handleCloseSubNav=this.handleCloseSubNav.bind(this),this._closeAllSubMenus=this.closeAllSubMenus.bind(this),this._closeAllSubSubMenus=this.closeAllSubSubMenus.bind(this),this._setSubMenu=this.setSubMenu.bind(this),this._closeAllSubMenuToggles=this.closeAllSubMenuToggles.bind(this),this._closeAllSubSubMenuToggles=this.closeAllSubSubMenuToggles.bind(this),this._handleDocClick=this.handleDocClick.bind(this),this._handleFocus=this.handleFocus.bind(this);const n={action:"click",subNavAnchors:".js-site-nav-items > .menu-item-has-children > a",subSubNavAnchors:".js-site-nav-items .sub-menu > .menu-item-has-children > a",toggleNavClass:!0,toggleNavClassValue:"is-opened",toggleSubNavClassValue:"is-opened",closeNavOnEscKey:!0,closeNavOnLastTab:!1,subNavClass:".sub-menu",subToggleButtonClasses:"",subSubToggleButtonClasses:"",animateSubNav:!1,animateSubNavClass:"",visuallyHiddenClass:"screen-reader-text",expandChildNavText:"Sub menu",dropDownIcon:'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>',onCreate:null,onOpenNav:null,onCloseNav:null,onOpenSubNav:null,onCloseSubNav:null,...s};this.$element=t,this.$toggle=e,this.settings=n,this.navOpened=!1,this.$subNavs=this.$element.querySelectorAll(this.settings.subNavAnchors),this.$subSubNavs=this.$element.querySelectorAll(this.settings.subSubNavAnchors),this.create()}s.prototype.create=function(){return this.$toggle.setAttribute("aria-expanded","false"),this.$element.setAttribute("data-meom-nav","navigation"),this.$subNavs.forEach((function(t){"click"===this.settings.action&&t.setAttribute("hidden","");const e=document.createElement("button");e.setAttribute("data-meom-nav","sub-toggle"),e.setAttribute("aria-expanded","false"),e.className=`${this.settings.subToggleButtonClasses}`,e.type="button","click"===this.settings.action&&(e.innerHTML=`${t.textContent}${this.settings.dropDownIcon}`),"hover"===this.settings.action&&(e.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`),t.after(e)}),this),this.$subSubNavs.forEach((function(t,e){const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-sub-toggle"),s.setAttribute("aria-expanded","false"),s.setAttribute("aria-controls",`sub-sub-menu-${e}`),t.nextElementSibling&&(t.nextElementSibling.id=`sub-sub-menu-${e}`),s.className=`${this.settings.subSubToggleButtonClasses}`,s.type="button",s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`,t.after(s)}),this),this.$toggle.addEventListener("click",this._handleNav,!1),this.$element.addEventListener("click",this._handleSubNav,!1),document.addEventListener("keydown",this._handleCloseNav,!1),this.$element.addEventListener("keydown",this._handleCloseSubNav,!1),this.$element.addEventListener("keydown",this._handleFocus,!1),document.addEventListener("click",this._handleDocClick,!1),this.settings.onCreate&&"function"==typeof this.settings.onCreate&&this.settings.onCreate(this.$element,this.$toggle),this},s.prototype.handleNav=function(t){return this.navOpened?(e(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.remove(this.settings.toggleNavClassValue),this.$toggle&&this.$toggle.focus(),this.navOpened=!1,this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this.settings.onCloseNav&&"function"==typeof this.settings.onCloseNav&&this.settings.onCloseNav(this.$element,this.$toggle,t)):(e(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.add(this.settings.toggleNavClassValue),this.navOpened=!0,this.settings.onOpenNav&&"function"==typeof this.settings.onOpenNav&&this.settings.onOpenNav(this.$element,this.$toggle,t)),this},s.prototype.handleSubNav=function(t){const s=t.target,n=s.closest('[data-meom-nav="sub-toggle"]'),i=s.closest('[data-meom-nav="sub-sub-toggle"]');return n||i?(s.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)||s.matches('[data-meom-nav="sub-sub-toggle"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),!s.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)&&s.matches('[data-meom-nav="sub-sub-toggle"]')&&(this._closeAllSubSubMenus(),this._closeAllSubSubMenuToggles()),e(s,"expanded"),s.nextElementSibling&&this._setSubMenu(s.nextElementSibling,t),this):this},s.prototype.handleCloseNav=function(t){return this.navOpened&&this.settings.closeNavOnEscKey&&27===t.keyCode&&this._handleNav(t),this},s.prototype.handleCloseSubNav=function(t){const e=document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const s=e.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),n=s[s.length-1];9!==t.keyCode||t.shiftKey||t.target!==n||(this._closeAllSubMenus(),this._closeAllSubMenuToggles());const i=e.previousElementSibling;i&&9===t.keyCode&&t.shiftKey&&t.target===i&&(this._closeAllSubMenus(),this._closeAllSubMenuToggles())}if(27===t.keyCode){if(t.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]'))return this._handleSubNav(t),this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this;const e=t.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const t=e.previousElementSibling;t&&t.focus()}this._closeAllSubMenus(),this._closeAllSubMenuToggles()}return this},s.prototype.handleFocus=function(t){if(!this.navOpened)return this;if(!this.settings.closeNavOnLastTab)return this;const e=this.$element.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),s=e[e.length-1];return 9!==t.keyCode||t.shiftKey||t.target!==s||(t.preventDefault(),this._handleNav(t)),this},s.prototype.handleDocClick=function(t){return t.target.closest('[data-meom-nav="navigation"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),this},s.prototype.closeAllSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},s.prototype.closeAllSubSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},s.prototype.setSubMenu=function(e,s){return e?(e.classList.contains(this.settings.toggleSubNavClassValue)?(e.classList.remove(this.settings.toggleSubNavClassValue),this.settings.onCloseSubNav&&"function"==typeof this.settings.onCloseSubNav&&this.settings.onCloseSubNav(this.$element,this.$toggle,e,s)):(e.classList.add(this.settings.toggleSubNavClassValue),this.settings.animateSubNav&&t(e,this.settings.animateSubNavClass),this.settings.onOpenSubNav&&"function"==typeof this.settings.onOpenSubNav&&this.settings.onOpenSubNav(this.$element,this.$toggle,e,s)),this):this},s.prototype.closeAllSubMenuToggles=function(){document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]').forEach((function(t){e(t,"expanded")}));return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){e(t,"expanded")})),this},s.prototype.closeAllSubSubMenuToggles=function(){return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){e(t,"expanded")})),this};export{s as Navigation,t as animate,e as updateAria}; |
@@ -68,6 +68,6 @@ (function (global, factory) { | ||
* @param {Object} element Navigation element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} options The settings and options for this instance. | ||
*/ | ||
function Navigation( element, toggle, options = {} ) { | ||
function Navigation(element, toggle, options = {}) { | ||
// Default settings. | ||
@@ -77,3 +77,4 @@ const defaults = { | ||
subNavAnchors: '.js-site-nav-items > .menu-item-has-children > a', | ||
subSubNavAnchors: '.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
subSubNavAnchors: | ||
'.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
toggleNavClass: true, | ||
@@ -91,3 +92,4 @@ toggleNavClassValue: 'is-opened', | ||
expandChildNavText: 'Sub menu', | ||
dropDownIcon: '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
dropDownIcon: | ||
'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
onCreate: null, | ||
@@ -101,11 +103,13 @@ onOpenNav: null, | ||
// Bind methods. | ||
this._handleNav = this.handleNav.bind( this ); | ||
this._handleSubNav = this.handleSubNav.bind( this ); | ||
this._handleCloseNav = this.handleCloseNav.bind( this ); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind( this ); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind( this ); | ||
this._setSubMenu = this.setSubMenu.bind( this ); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind( this ); | ||
this._handleDocClick = this.handleDocClick.bind( this ); | ||
this._handleFocus = this.handleFocus.bind( this ); | ||
this._handleNav = this.handleNav.bind(this); | ||
this._handleSubNav = this.handleSubNav.bind(this); | ||
this._handleCloseNav = this.handleCloseNav.bind(this); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind(this); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind(this); | ||
this._closeAllSubSubMenus = this.closeAllSubSubMenus.bind(this); | ||
this._setSubMenu = this.setSubMenu.bind(this); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind(this); | ||
this._closeAllSubSubMenuToggles = this.closeAllSubSubMenuToggles.bind(this); | ||
this._handleDocClick = this.handleDocClick.bind(this); | ||
this._handleFocus = this.handleFocus.bind(this); | ||
@@ -122,5 +126,3 @@ // Merge options to defaults. | ||
// Set all sub and sub sub navigations. | ||
this.$subNavs = this.$element.querySelectorAll( | ||
this.settings.subNavAnchors | ||
); | ||
this.$subNavs = this.$element.querySelectorAll(this.settings.subNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll( | ||
@@ -139,64 +141,61 @@ this.settings.subSubNavAnchors | ||
// Set ARIA for navigation toggle button. | ||
this.$toggle.setAttribute( 'aria-expanded', 'false' ); | ||
this.$toggle.setAttribute('aria-expanded', 'false'); | ||
// Set data value for nav element. This is for targeting without a class name. | ||
this.$element.setAttribute( 'data-meom-nav', 'navigation' ); | ||
this.$element.setAttribute('data-meom-nav', 'navigation'); | ||
// Setup sub navs toggle buttons. | ||
this.$subNavs.forEach( function ( subNav, index ) { | ||
this.$subNavs.forEach(function (subNav) { | ||
// Hide link in JS to avoid cumulative layout shift (CLS). | ||
if ( this.settings.action === 'click' ) { | ||
subNav.setAttribute( 'hidden', '' ); | ||
if (this.settings.action === 'click') { | ||
subNav.setAttribute('hidden', ''); | ||
} | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.className = `${ this.settings.subToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
if ( this.settings.action === 'click' ) { | ||
subToggleButton.innerHTML = `${ subNav.textContent }${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'click') { | ||
subToggleButton.innerHTML = `${subNav.textContent}${this.settings.dropDownIcon}`; | ||
} | ||
if ( this.settings.action === 'hover' ) { | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'hover') { | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
} | ||
// Add toggle button after anchor. | ||
subNav.after( subToggleButton ); | ||
}, this ); | ||
subNav.after(subToggleButton); | ||
}, this); | ||
// Setup sub sub navs toggle buttons. | ||
this.$subSubNavs.forEach( function ( subSubNav, index ) { | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
this.$subSubNavs.forEach(function (subSubNav, index) { | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.setAttribute( | ||
'aria-controls', | ||
`sub-sub-menu-${ index }` | ||
); | ||
subToggleButton.setAttribute('aria-controls', `sub-sub-menu-${index}`); | ||
// Add matching id for next sub-sub-menu. | ||
if ( subSubNav.nextElementSibling ) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${ index }`; | ||
if (subSubNav.nextElementSibling) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${index}`; | ||
} | ||
subToggleButton.className = `${ this.settings.subSubToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subSubToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
// Add toggle button after anchor. | ||
subSubNav.after( subToggleButton ); | ||
}, this ); | ||
subSubNav.after(subToggleButton); | ||
}, this); | ||
// Set event listeners. | ||
this.$toggle.addEventListener( 'click', this._handleNav, false ); | ||
this.$element.addEventListener( 'click', this._handleSubNav, false ); | ||
document.addEventListener( 'keydown', this._handleCloseNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleCloseSubNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleFocus, false ); | ||
document.addEventListener( 'click', this._handleDocClick, false ); | ||
this.$toggle.addEventListener('click', this._handleNav, false); | ||
this.$element.addEventListener('click', this._handleSubNav, false); | ||
document.addEventListener('keydown', this._handleCloseNav, false); | ||
this.$element.addEventListener('keydown', this._handleCloseSubNav, false); | ||
this.$element.addEventListener('keydown', this._handleFocus, false); | ||
document.addEventListener('click', this._handleDocClick, false); | ||
@@ -212,3 +211,3 @@ /** | ||
) { | ||
this.settings.onCreate( this.$element, this.$toggle ); | ||
this.settings.onCreate(this.$element, this.$toggle); | ||
} | ||
@@ -225,9 +224,9 @@ | ||
*/ | ||
Navigation.prototype.handleNav = function ( event ) { | ||
Navigation.prototype.handleNav = function (event) { | ||
// If navigation is closed and we want to open it. | ||
if ( ! this.navOpened ) { | ||
updateAria( this.$toggle, 'expanded' ); | ||
if (!this.navOpened) { | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.add( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.add(this.settings.toggleNavClassValue); | ||
} | ||
@@ -247,13 +246,13 @@ | ||
) { | ||
this.settings.onOpenNav( this.$element, this.$toggle, event ); | ||
this.settings.onOpenNav(this.$element, this.$toggle, event); | ||
} | ||
} else { | ||
updateAria( this.$toggle, 'expanded' ); | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.remove( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.remove(this.settings.toggleNavClassValue); | ||
} | ||
// Set focus back to toggle button. | ||
if ( this.$toggle ) { | ||
if (this.$toggle) { | ||
this.$toggle.focus(); | ||
@@ -277,3 +276,3 @@ } | ||
) { | ||
this.settings.onCloseNav( this.$element, this.$toggle, event ); | ||
this.settings.onCloseNav(this.$element, this.$toggle, event); | ||
} | ||
@@ -291,6 +290,6 @@ } | ||
*/ | ||
Navigation.prototype.handleSubNav = function ( event ) { | ||
Navigation.prototype.handleSubNav = function (event) { | ||
const target = event.target; | ||
// Use .closest because there can be SVG inside the button. | ||
const closestSubButton = target.closest( '[data-meom-nav="sub-toggle"]' ); | ||
const closestSubButton = target.closest('[data-meom-nav="sub-toggle"]'); | ||
const closestSubSubButton = target.closest( | ||
@@ -301,3 +300,3 @@ '[data-meom-nav="sub-sub-toggle"]' | ||
// If the clicked element doesn't have the correct data attribute, bail. | ||
if ( ! closestSubButton && ! closestSubSubButton ) { | ||
if (!closestSubButton && !closestSubSubButton) { | ||
return this; | ||
@@ -310,4 +309,6 @@ } | ||
if ( | ||
! target.nextElementSibling.classList.contains( this.settings.toggleSubNavClassValue ) && | ||
! target.matches( '[data-meom-nav="sub-sub-toggle"]' ) | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
!target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
@@ -318,8 +319,20 @@ this._closeAllSubMenus(); | ||
// Then again, close all sub sub menus when trying to open any other sub sub menu that is not already open. | ||
// So that only one sub sub menu can be open at current time. | ||
if ( | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
this._closeAllSubSubMenus(); | ||
this._closeAllSubSubMenuToggles(); | ||
} | ||
// Update sub toggle ARIA. | ||
updateAria( target, 'expanded' ); | ||
updateAria(target, 'expanded'); | ||
// Toggle class for next <ul> element (sub-menu). | ||
if ( target.nextElementSibling ) { | ||
this._setSubMenu( target.nextElementSibling, event ); | ||
if (target.nextElementSibling) { | ||
this._setSubMenu(target.nextElementSibling, event); | ||
} | ||
@@ -336,3 +349,3 @@ | ||
*/ | ||
Navigation.prototype.handleCloseNav = function ( event ) { | ||
Navigation.prototype.handleCloseNav = function (event) { | ||
// Close nav on Esc key if nav is open. | ||
@@ -344,3 +357,3 @@ if ( | ||
) { | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -357,11 +370,11 @@ | ||
*/ | ||
Navigation.prototype.handleCloseSubNav = function ( event ) { | ||
Navigation.prototype.handleCloseSubNav = function (event) { | ||
// Set focusable elements inside sub-menu element. | ||
const openSubMenu = document.querySelector( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if ( openSubMenu ) { | ||
if (openSubMenu) { | ||
// Focusable elements. | ||
const focusableElements = openSubMenu.querySelectorAll( [ | ||
const focusableElements = openSubMenu.querySelectorAll([ | ||
'a[href]', | ||
@@ -373,6 +386,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -382,3 +395,3 @@ // Last Tab closes sub-menu. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -405,3 +418,3 @@ ) { | ||
// Close sub-menu on Esc key. | ||
if ( ESCAPE_KEY === event.keyCode ) { | ||
if (ESCAPE_KEY === event.keyCode) { | ||
// If we are on the sub-menu toggle itself. | ||
@@ -413,3 +426,3 @@ if ( | ||
) { | ||
this._handleSubNav( event ); | ||
this._handleSubNav(event); | ||
this._closeAllSubMenus(); | ||
@@ -422,7 +435,7 @@ this._closeAllSubMenuToggles(); | ||
const parentSubMenu = event.target.closest( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
// Set focus to sub menu toggle. | ||
if ( parentSubMenu ) { | ||
if (parentSubMenu) { | ||
// sub-menu toggle. | ||
@@ -432,3 +445,3 @@ const subMenuToggle = parentSubMenu.previousElementSibling; | ||
// Set focus back to sub-menu toggle. | ||
if ( subMenuToggle ) { | ||
if (subMenuToggle) { | ||
subMenuToggle.focus(); | ||
@@ -451,5 +464,5 @@ } | ||
*/ | ||
Navigation.prototype.handleFocus = function ( event ) { | ||
Navigation.prototype.handleFocus = function (event) { | ||
// Bail if menu is not open. | ||
if ( ! this.navOpened ) { | ||
if (!this.navOpened) { | ||
return this; | ||
@@ -459,3 +472,3 @@ } | ||
// Bail if `closeNavOnLastTab` setting is not set to true. | ||
if ( ! this.settings.closeNavOnLastTab ) { | ||
if (!this.settings.closeNavOnLastTab) { | ||
return this; | ||
@@ -465,3 +478,3 @@ } | ||
// Set focusable elements inside element. | ||
const focusableElements = this.$element.querySelectorAll( [ | ||
const focusableElements = this.$element.querySelectorAll([ | ||
'a[href]', | ||
@@ -473,6 +486,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -482,3 +495,3 @@ // Close nav on last Tab. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -488,3 +501,3 @@ ) { | ||
// Close nav. | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -501,5 +514,5 @@ | ||
*/ | ||
Navigation.prototype.handleDocClick = function ( event ) { | ||
Navigation.prototype.handleDocClick = function (event) { | ||
// Bail if clicking inside the nav. | ||
if ( event.target.closest( '[data-meom-nav="navigation"]' ) ) { | ||
if (event.target.closest('[data-meom-nav="navigation"]')) { | ||
return this; | ||
@@ -521,8 +534,8 @@ } | ||
const openSubMenus = document.querySelectorAll( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubMenus.forEach( function ( openSubMenu ) { | ||
this._setSubMenu( openSubMenu ); | ||
}, this ); | ||
openSubMenus.forEach(function (openSubMenu) { | ||
this._setSubMenu(openSubMenu); | ||
}, this); | ||
@@ -533,19 +546,36 @@ return this; | ||
/** | ||
* Close all sub sub menus. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenus = function () { | ||
const openSubSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubSubMenus.forEach(function (openSubSubMenu) { | ||
this._setSubMenu(openSubSubMenu); | ||
}, this); | ||
return this; | ||
}; | ||
/** | ||
* Set classes and animate for sub-menu. | ||
* | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.setSubMenu = function ( submenu, event ) { | ||
if ( ! submenu ) { | ||
Navigation.prototype.setSubMenu = function (submenu, event) { | ||
if (!submenu) { | ||
return this; | ||
} | ||
if ( ! submenu.classList.contains( this.settings.toggleSubNavClassValue ) ) { | ||
submenu.classList.add( this.settings.toggleSubNavClassValue ); | ||
if (!submenu.classList.contains(this.settings.toggleSubNavClassValue)) { | ||
submenu.classList.add(this.settings.toggleSubNavClassValue); | ||
// Base animation with class. | ||
if ( this.settings.animateSubNav ) { | ||
animate( submenu, this.settings.animateSubNavClass ); | ||
if (this.settings.animateSubNav) { | ||
animate(submenu, this.settings.animateSubNavClass); | ||
} | ||
@@ -570,3 +600,3 @@ | ||
} else { | ||
submenu.classList.remove( this.settings.toggleSubNavClassValue ); | ||
submenu.classList.remove(this.settings.toggleSubNavClassValue); | ||
@@ -604,5 +634,5 @@ /** | ||
openSubMenuToggles.forEach( function ( openSubMenuToggle ) { | ||
updateAria( openSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubMenuToggles.forEach(function (openSubMenuToggle) { | ||
updateAria(openSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -613,5 +643,5 @@ const openSubSubMenuToggles = document.querySelectorAll( | ||
openSubSubMenuToggles.forEach( function ( openSubSubMenuToggle ) { | ||
updateAria( openSubSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -621,2 +651,19 @@ return this; | ||
/** | ||
* Close all sub sub menu toggles. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenuToggles = function () { | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
return this; | ||
}; | ||
exports.Navigation = Navigation; | ||
@@ -623,0 +670,0 @@ exports.animate = animate; |
@@ -1,2 +0,2 @@ | ||
/*! navigation 1.0.5 — © MEOM */ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Navigation={})}(this,(function(t){"use strict";function e(t,e,s){t&&e&&(t.classList.add(e),t.addEventListener("animationend",(function n(){t.classList.remove(e),s&&t.classList.remove(s),t.removeEventListener("animationend",n,!1)}),!1))}function s(t,e){if(void 0===t||0>=e.length)return;const s="true"===t.getAttribute(`aria-${e}`)?"false":"true";t.setAttribute(`aria-${e}`,s)}function n(t,e,s={}){this._handleNav=this.handleNav.bind(this),this._handleSubNav=this.handleSubNav.bind(this),this._handleCloseNav=this.handleCloseNav.bind(this),this._handleCloseSubNav=this.handleCloseSubNav.bind(this),this._closeAllSubMenus=this.closeAllSubMenus.bind(this),this._setSubMenu=this.setSubMenu.bind(this),this._closeAllSubMenuToggles=this.closeAllSubMenuToggles.bind(this),this._handleDocClick=this.handleDocClick.bind(this),this._handleFocus=this.handleFocus.bind(this);const n={action:"click",subNavAnchors:".js-site-nav-items > .menu-item-has-children > a",subSubNavAnchors:".js-site-nav-items .sub-menu > .menu-item-has-children > a",toggleNavClass:!0,toggleNavClassValue:"is-opened",toggleSubNavClassValue:"is-opened",closeNavOnEscKey:!0,closeNavOnLastTab:!1,subNavClass:".sub-menu",subToggleButtonClasses:"",subSubToggleButtonClasses:"",animateSubNav:!1,animateSubNavClass:"",visuallyHiddenClass:"screen-reader-text",expandChildNavText:"Sub menu",dropDownIcon:'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>',onCreate:null,onOpenNav:null,onCloseNav:null,onOpenSubNav:null,onCloseSubNav:null,...s};this.$element=t,this.$toggle=e,this.settings=n,this.navOpened=!1,this.$subNavs=this.$element.querySelectorAll(this.settings.subNavAnchors),this.$subSubNavs=this.$element.querySelectorAll(this.settings.subSubNavAnchors),this.create()}n.prototype.create=function(){return this.$toggle.setAttribute("aria-expanded","false"),this.$element.setAttribute("data-meom-nav","navigation"),this.$subNavs.forEach((function(t,e){"click"===this.settings.action&&t.setAttribute("hidden","");const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-toggle"),s.setAttribute("aria-expanded","false"),s.className=`${this.settings.subToggleButtonClasses}`,s.type="button","click"===this.settings.action&&(s.innerHTML=`${t.textContent}${this.settings.dropDownIcon}`),"hover"===this.settings.action&&(s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`),t.after(s)}),this),this.$subSubNavs.forEach((function(t,e){const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-sub-toggle"),s.setAttribute("aria-expanded","false"),s.setAttribute("aria-controls",`sub-sub-menu-${e}`),t.nextElementSibling&&(t.nextElementSibling.id=`sub-sub-menu-${e}`),s.className=`${this.settings.subSubToggleButtonClasses}`,s.type="button",s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`,t.after(s)}),this),this.$toggle.addEventListener("click",this._handleNav,!1),this.$element.addEventListener("click",this._handleSubNav,!1),document.addEventListener("keydown",this._handleCloseNav,!1),this.$element.addEventListener("keydown",this._handleCloseSubNav,!1),this.$element.addEventListener("keydown",this._handleFocus,!1),document.addEventListener("click",this._handleDocClick,!1),this.settings.onCreate&&"function"==typeof this.settings.onCreate&&this.settings.onCreate(this.$element,this.$toggle),this},n.prototype.handleNav=function(t){return this.navOpened?(s(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.remove(this.settings.toggleNavClassValue),this.$toggle&&this.$toggle.focus(),this.navOpened=!1,this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this.settings.onCloseNav&&"function"==typeof this.settings.onCloseNav&&this.settings.onCloseNav(this.$element,this.$toggle,t)):(s(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.add(this.settings.toggleNavClassValue),this.navOpened=!0,this.settings.onOpenNav&&"function"==typeof this.settings.onOpenNav&&this.settings.onOpenNav(this.$element,this.$toggle,t)),this},n.prototype.handleSubNav=function(t){const e=t.target,n=e.closest('[data-meom-nav="sub-toggle"]'),i=e.closest('[data-meom-nav="sub-sub-toggle"]');return n||i?(e.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)||e.matches('[data-meom-nav="sub-sub-toggle"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),s(e,"expanded"),e.nextElementSibling&&this._setSubMenu(e.nextElementSibling,t),this):this},n.prototype.handleCloseNav=function(t){return this.navOpened&&this.settings.closeNavOnEscKey&&27===t.keyCode&&this._handleNav(t),this},n.prototype.handleCloseSubNav=function(t){const e=document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const s=e.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),n=s[s.length-1];9!==t.keyCode||t.shiftKey||t.target!==n||(this._closeAllSubMenus(),this._closeAllSubMenuToggles());const i=e.previousElementSibling;i&&9===t.keyCode&&t.shiftKey&&t.target===i&&(this._closeAllSubMenus(),this._closeAllSubMenuToggles())}if(27===t.keyCode){if(t.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]'))return this._handleSubNav(t),this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this;const e=t.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const t=e.previousElementSibling;t&&t.focus()}this._closeAllSubMenus(),this._closeAllSubMenuToggles()}return this},n.prototype.handleFocus=function(t){if(!this.navOpened)return this;if(!this.settings.closeNavOnLastTab)return this;const e=this.$element.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),s=e[e.length-1];return 9!==t.keyCode||t.shiftKey||t.target!==s||(t.preventDefault(),this._handleNav(t)),this},n.prototype.handleDocClick=function(t){return t.target.closest('[data-meom-nav="navigation"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),this},n.prototype.closeAllSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},n.prototype.setSubMenu=function(t,s){return t?(t.classList.contains(this.settings.toggleSubNavClassValue)?(t.classList.remove(this.settings.toggleSubNavClassValue),this.settings.onCloseSubNav&&"function"==typeof this.settings.onCloseSubNav&&this.settings.onCloseSubNav(this.$element,this.$toggle,t,s)):(t.classList.add(this.settings.toggleSubNavClassValue),this.settings.animateSubNav&&e(t,this.settings.animateSubNavClass),this.settings.onOpenSubNav&&"function"==typeof this.settings.onOpenSubNav&&this.settings.onOpenSubNav(this.$element,this.$toggle,t,s)),this):this},n.prototype.closeAllSubMenuToggles=function(){document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]').forEach((function(t){s(t,"expanded")}));return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){s(t,"expanded")})),this},t.Navigation=n,t.animate=e,t.updateAria=s,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
/*! navigation 1.1.0 — © MEOM */ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Navigation={})}(this,(function(t){"use strict";function e(t,e,s){t&&e&&(t.classList.add(e),t.addEventListener("animationend",(function n(){t.classList.remove(e),s&&t.classList.remove(s),t.removeEventListener("animationend",n,!1)}),!1))}function s(t,e){if(void 0===t||0>=e.length)return;const s="true"===t.getAttribute(`aria-${e}`)?"false":"true";t.setAttribute(`aria-${e}`,s)}function n(t,e,s={}){this._handleNav=this.handleNav.bind(this),this._handleSubNav=this.handleSubNav.bind(this),this._handleCloseNav=this.handleCloseNav.bind(this),this._handleCloseSubNav=this.handleCloseSubNav.bind(this),this._closeAllSubMenus=this.closeAllSubMenus.bind(this),this._closeAllSubSubMenus=this.closeAllSubSubMenus.bind(this),this._setSubMenu=this.setSubMenu.bind(this),this._closeAllSubMenuToggles=this.closeAllSubMenuToggles.bind(this),this._closeAllSubSubMenuToggles=this.closeAllSubSubMenuToggles.bind(this),this._handleDocClick=this.handleDocClick.bind(this),this._handleFocus=this.handleFocus.bind(this);const n={action:"click",subNavAnchors:".js-site-nav-items > .menu-item-has-children > a",subSubNavAnchors:".js-site-nav-items .sub-menu > .menu-item-has-children > a",toggleNavClass:!0,toggleNavClassValue:"is-opened",toggleSubNavClassValue:"is-opened",closeNavOnEscKey:!0,closeNavOnLastTab:!1,subNavClass:".sub-menu",subToggleButtonClasses:"",subSubToggleButtonClasses:"",animateSubNav:!1,animateSubNavClass:"",visuallyHiddenClass:"screen-reader-text",expandChildNavText:"Sub menu",dropDownIcon:'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>',onCreate:null,onOpenNav:null,onCloseNav:null,onOpenSubNav:null,onCloseSubNav:null,...s};this.$element=t,this.$toggle=e,this.settings=n,this.navOpened=!1,this.$subNavs=this.$element.querySelectorAll(this.settings.subNavAnchors),this.$subSubNavs=this.$element.querySelectorAll(this.settings.subSubNavAnchors),this.create()}n.prototype.create=function(){return this.$toggle.setAttribute("aria-expanded","false"),this.$element.setAttribute("data-meom-nav","navigation"),this.$subNavs.forEach((function(t){"click"===this.settings.action&&t.setAttribute("hidden","");const e=document.createElement("button");e.setAttribute("data-meom-nav","sub-toggle"),e.setAttribute("aria-expanded","false"),e.className=`${this.settings.subToggleButtonClasses}`,e.type="button","click"===this.settings.action&&(e.innerHTML=`${t.textContent}${this.settings.dropDownIcon}`),"hover"===this.settings.action&&(e.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`),t.after(e)}),this),this.$subSubNavs.forEach((function(t,e){const s=document.createElement("button");s.setAttribute("data-meom-nav","sub-sub-toggle"),s.setAttribute("aria-expanded","false"),s.setAttribute("aria-controls",`sub-sub-menu-${e}`),t.nextElementSibling&&(t.nextElementSibling.id=`sub-sub-menu-${e}`),s.className=`${this.settings.subSubToggleButtonClasses}`,s.type="button",s.innerHTML=`<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`,t.after(s)}),this),this.$toggle.addEventListener("click",this._handleNav,!1),this.$element.addEventListener("click",this._handleSubNav,!1),document.addEventListener("keydown",this._handleCloseNav,!1),this.$element.addEventListener("keydown",this._handleCloseSubNav,!1),this.$element.addEventListener("keydown",this._handleFocus,!1),document.addEventListener("click",this._handleDocClick,!1),this.settings.onCreate&&"function"==typeof this.settings.onCreate&&this.settings.onCreate(this.$element,this.$toggle),this},n.prototype.handleNav=function(t){return this.navOpened?(s(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.remove(this.settings.toggleNavClassValue),this.$toggle&&this.$toggle.focus(),this.navOpened=!1,this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this.settings.onCloseNav&&"function"==typeof this.settings.onCloseNav&&this.settings.onCloseNav(this.$element,this.$toggle,t)):(s(this.$toggle,"expanded"),this.settings.toggleNavClass&&this.$element.classList.add(this.settings.toggleNavClassValue),this.navOpened=!0,this.settings.onOpenNav&&"function"==typeof this.settings.onOpenNav&&this.settings.onOpenNav(this.$element,this.$toggle,t)),this},n.prototype.handleSubNav=function(t){const e=t.target,n=e.closest('[data-meom-nav="sub-toggle"]'),i=e.closest('[data-meom-nav="sub-sub-toggle"]');return n||i?(e.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)||e.matches('[data-meom-nav="sub-sub-toggle"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),!e.nextElementSibling.classList.contains(this.settings.toggleSubNavClassValue)&&e.matches('[data-meom-nav="sub-sub-toggle"]')&&(this._closeAllSubSubMenus(),this._closeAllSubSubMenuToggles()),s(e,"expanded"),e.nextElementSibling&&this._setSubMenu(e.nextElementSibling,t),this):this},n.prototype.handleCloseNav=function(t){return this.navOpened&&this.settings.closeNavOnEscKey&&27===t.keyCode&&this._handleNav(t),this},n.prototype.handleCloseSubNav=function(t){const e=document.querySelector(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const s=e.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),n=s[s.length-1];9!==t.keyCode||t.shiftKey||t.target!==n||(this._closeAllSubMenus(),this._closeAllSubMenuToggles());const i=e.previousElementSibling;i&&9===t.keyCode&&t.shiftKey&&t.target===i&&(this._closeAllSubMenus(),this._closeAllSubMenuToggles())}if(27===t.keyCode){if(t.target.matches('[data-meom-nav="sub-toggle"][aria-expanded="true"]'))return this._handleSubNav(t),this._closeAllSubMenus(),this._closeAllSubMenuToggles(),this;const e=t.target.closest(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`);if(e){const t=e.previousElementSibling;t&&t.focus()}this._closeAllSubMenus(),this._closeAllSubMenuToggles()}return this},n.prototype.handleFocus=function(t){if(!this.navOpened)return this;if(!this.settings.closeNavOnLastTab)return this;const e=this.$element.querySelectorAll(["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])"]),s=e[e.length-1];return 9!==t.keyCode||t.shiftKey||t.target!==s||(t.preventDefault(),this._handleNav(t)),this},n.prototype.handleDocClick=function(t){return t.target.closest('[data-meom-nav="navigation"]')||(this._closeAllSubMenus(),this._closeAllSubMenuToggles()),this},n.prototype.closeAllSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},n.prototype.closeAllSubSubMenus=function(){return document.querySelectorAll(`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}`).forEach((function(t){this._setSubMenu(t)}),this),this},n.prototype.setSubMenu=function(t,s){return t?(t.classList.contains(this.settings.toggleSubNavClassValue)?(t.classList.remove(this.settings.toggleSubNavClassValue),this.settings.onCloseSubNav&&"function"==typeof this.settings.onCloseSubNav&&this.settings.onCloseSubNav(this.$element,this.$toggle,t,s)):(t.classList.add(this.settings.toggleSubNavClassValue),this.settings.animateSubNav&&e(t,this.settings.animateSubNavClass),this.settings.onOpenSubNav&&"function"==typeof this.settings.onOpenSubNav&&this.settings.onOpenSubNav(this.$element,this.$toggle,t,s)),this):this},n.prototype.closeAllSubMenuToggles=function(){document.querySelectorAll('[data-meom-nav="sub-toggle"][aria-expanded="true"]').forEach((function(t){s(t,"expanded")}));return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){s(t,"expanded")})),this},n.prototype.closeAllSubSubMenuToggles=function(){return document.querySelectorAll('[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]').forEach((function(t){s(t,"expanded")})),this},t.Navigation=n,t.animate=e,t.updateAria=s,Object.defineProperty(t,"__esModule",{value:!0})})); |
{ | ||
"name": "@meom/navigation", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "MEOM navigation", | ||
@@ -25,10 +25,10 @@ "homepage": "https://github.com/MEOM/navigation", | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^22.0.0", | ||
"@rollup/plugin-node-resolve": "^13.3.0", | ||
"@wordpress/eslint-plugin": "^12.2.0", | ||
"@wordpress/prettier-config": "^1.2.0", | ||
"eslint": "^8.15.0", | ||
"rollup": "^2.72.0", | ||
"@rollup/plugin-commonjs": "^24.0.0", | ||
"@rollup/plugin-node-resolve": "^15.0.0", | ||
"@wordpress/eslint-plugin": "^14.3.0", | ||
"@wordpress/prettier-config": "^2.13.0", | ||
"eslint": "^8.37.0", | ||
"rollup": "^2", | ||
"rollup-plugin-terser": "^7.0.2" | ||
} | ||
} |
@@ -383,2 +383,3 @@ # MEOM navigation | ||
- [In Praise of the Unambiguous Click Menu](https://css-tricks.com/in-praise-of-the-unambiguous-click-menu/) by Mark Root-Wiley. | ||
- [Menu Design: Checklist of 15 UX Guidelines to Help Users ](https://www.nngroup.com/articles/menu-design/) | ||
- [In Finnish: How to build accessible navigation](https://www.eficode.com/fi/blog/miten-navigaatiovalikko-toteutetaan-saavutettavasti). |
@@ -13,6 +13,6 @@ /* Import internal depedencies. */ | ||
* @param {Object} element Navigation element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} toggle Navigation toggle element. | ||
* @param {Object} options The settings and options for this instance. | ||
*/ | ||
function Navigation( element, toggle, options = {} ) { | ||
function Navigation(element, toggle, options = {}) { | ||
// Default settings. | ||
@@ -22,3 +22,4 @@ const defaults = { | ||
subNavAnchors: '.js-site-nav-items > .menu-item-has-children > a', | ||
subSubNavAnchors: '.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
subSubNavAnchors: | ||
'.js-site-nav-items .sub-menu > .menu-item-has-children > a', | ||
toggleNavClass: true, | ||
@@ -36,3 +37,4 @@ toggleNavClassValue: 'is-opened', | ||
expandChildNavText: 'Sub menu', | ||
dropDownIcon: '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
dropDownIcon: | ||
'<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M17.5 11.6L12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z"></path></svg>', | ||
onCreate: null, | ||
@@ -46,11 +48,13 @@ onOpenNav: null, | ||
// Bind methods. | ||
this._handleNav = this.handleNav.bind( this ); | ||
this._handleSubNav = this.handleSubNav.bind( this ); | ||
this._handleCloseNav = this.handleCloseNav.bind( this ); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind( this ); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind( this ); | ||
this._setSubMenu = this.setSubMenu.bind( this ); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind( this ); | ||
this._handleDocClick = this.handleDocClick.bind( this ); | ||
this._handleFocus = this.handleFocus.bind( this ); | ||
this._handleNav = this.handleNav.bind(this); | ||
this._handleSubNav = this.handleSubNav.bind(this); | ||
this._handleCloseNav = this.handleCloseNav.bind(this); | ||
this._handleCloseSubNav = this.handleCloseSubNav.bind(this); | ||
this._closeAllSubMenus = this.closeAllSubMenus.bind(this); | ||
this._closeAllSubSubMenus = this.closeAllSubSubMenus.bind(this); | ||
this._setSubMenu = this.setSubMenu.bind(this); | ||
this._closeAllSubMenuToggles = this.closeAllSubMenuToggles.bind(this); | ||
this._closeAllSubSubMenuToggles = this.closeAllSubSubMenuToggles.bind(this); | ||
this._handleDocClick = this.handleDocClick.bind(this); | ||
this._handleFocus = this.handleFocus.bind(this); | ||
@@ -67,5 +71,3 @@ // Merge options to defaults. | ||
// Set all sub and sub sub navigations. | ||
this.$subNavs = this.$element.querySelectorAll( | ||
this.settings.subNavAnchors | ||
); | ||
this.$subNavs = this.$element.querySelectorAll(this.settings.subNavAnchors); | ||
this.$subSubNavs = this.$element.querySelectorAll( | ||
@@ -84,64 +86,61 @@ this.settings.subSubNavAnchors | ||
// Set ARIA for navigation toggle button. | ||
this.$toggle.setAttribute( 'aria-expanded', 'false' ); | ||
this.$toggle.setAttribute('aria-expanded', 'false'); | ||
// Set data value for nav element. This is for targeting without a class name. | ||
this.$element.setAttribute( 'data-meom-nav', 'navigation' ); | ||
this.$element.setAttribute('data-meom-nav', 'navigation'); | ||
// Setup sub navs toggle buttons. | ||
this.$subNavs.forEach( function ( subNav, index ) { | ||
this.$subNavs.forEach(function (subNav) { | ||
// Hide link in JS to avoid cumulative layout shift (CLS). | ||
if ( this.settings.action === 'click' ) { | ||
subNav.setAttribute( 'hidden', '' ); | ||
if (this.settings.action === 'click') { | ||
subNav.setAttribute('hidden', ''); | ||
} | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.className = `${ this.settings.subToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
if ( this.settings.action === 'click' ) { | ||
subToggleButton.innerHTML = `${ subNav.textContent }${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'click') { | ||
subToggleButton.innerHTML = `${subNav.textContent}${this.settings.dropDownIcon}`; | ||
} | ||
if ( this.settings.action === 'hover' ) { | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
if (this.settings.action === 'hover') { | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
} | ||
// Add toggle button after anchor. | ||
subNav.after( subToggleButton ); | ||
}, this ); | ||
subNav.after(subToggleButton); | ||
}, this); | ||
// Setup sub sub navs toggle buttons. | ||
this.$subSubNavs.forEach( function ( subSubNav, index ) { | ||
const subToggleButton = document.createElement( 'button' ); | ||
subToggleButton.setAttribute( 'data-meom-nav', 'sub-sub-toggle' ); | ||
subToggleButton.setAttribute( 'aria-expanded', 'false' ); | ||
this.$subSubNavs.forEach(function (subSubNav, index) { | ||
const subToggleButton = document.createElement('button'); | ||
subToggleButton.setAttribute('data-meom-nav', 'sub-sub-toggle'); | ||
subToggleButton.setAttribute('aria-expanded', 'false'); | ||
subToggleButton.setAttribute( | ||
'aria-controls', | ||
`sub-sub-menu-${ index }` | ||
); | ||
subToggleButton.setAttribute('aria-controls', `sub-sub-menu-${index}`); | ||
// Add matching id for next sub-sub-menu. | ||
if ( subSubNav.nextElementSibling ) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${ index }`; | ||
if (subSubNav.nextElementSibling) { | ||
subSubNav.nextElementSibling.id = `sub-sub-menu-${index}`; | ||
} | ||
subToggleButton.className = `${ this.settings.subSubToggleButtonClasses }`; | ||
subToggleButton.className = `${this.settings.subSubToggleButtonClasses}`; | ||
subToggleButton.type = 'button'; | ||
subToggleButton.innerHTML = `<span class="${ this.settings.visuallyHiddenClass }">${ this.settings.expandChildNavText }</span>${ this.settings.dropDownIcon }`; | ||
subToggleButton.innerHTML = `<span class="${this.settings.visuallyHiddenClass}">${this.settings.expandChildNavText}</span>${this.settings.dropDownIcon}`; | ||
// Add toggle button after anchor. | ||
subSubNav.after( subToggleButton ); | ||
}, this ); | ||
subSubNav.after(subToggleButton); | ||
}, this); | ||
// Set event listeners. | ||
this.$toggle.addEventListener( 'click', this._handleNav, false ); | ||
this.$element.addEventListener( 'click', this._handleSubNav, false ); | ||
document.addEventListener( 'keydown', this._handleCloseNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleCloseSubNav, false ); | ||
this.$element.addEventListener( 'keydown', this._handleFocus, false ); | ||
document.addEventListener( 'click', this._handleDocClick, false ); | ||
this.$toggle.addEventListener('click', this._handleNav, false); | ||
this.$element.addEventListener('click', this._handleSubNav, false); | ||
document.addEventListener('keydown', this._handleCloseNav, false); | ||
this.$element.addEventListener('keydown', this._handleCloseSubNav, false); | ||
this.$element.addEventListener('keydown', this._handleFocus, false); | ||
document.addEventListener('click', this._handleDocClick, false); | ||
@@ -157,3 +156,3 @@ /** | ||
) { | ||
this.settings.onCreate( this.$element, this.$toggle ); | ||
this.settings.onCreate(this.$element, this.$toggle); | ||
} | ||
@@ -170,9 +169,9 @@ | ||
*/ | ||
Navigation.prototype.handleNav = function ( event ) { | ||
Navigation.prototype.handleNav = function (event) { | ||
// If navigation is closed and we want to open it. | ||
if ( ! this.navOpened ) { | ||
updateAria( this.$toggle, 'expanded' ); | ||
if (!this.navOpened) { | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.add( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.add(this.settings.toggleNavClassValue); | ||
} | ||
@@ -192,13 +191,13 @@ | ||
) { | ||
this.settings.onOpenNav( this.$element, this.$toggle, event ); | ||
this.settings.onOpenNav(this.$element, this.$toggle, event); | ||
} | ||
} else { | ||
updateAria( this.$toggle, 'expanded' ); | ||
updateAria(this.$toggle, 'expanded'); | ||
if ( this.settings.toggleNavClass ) { | ||
this.$element.classList.remove( this.settings.toggleNavClassValue ); | ||
if (this.settings.toggleNavClass) { | ||
this.$element.classList.remove(this.settings.toggleNavClassValue); | ||
} | ||
// Set focus back to toggle button. | ||
if ( this.$toggle ) { | ||
if (this.$toggle) { | ||
this.$toggle.focus(); | ||
@@ -222,3 +221,3 @@ } | ||
) { | ||
this.settings.onCloseNav( this.$element, this.$toggle, event ); | ||
this.settings.onCloseNav(this.$element, this.$toggle, event); | ||
} | ||
@@ -236,6 +235,6 @@ } | ||
*/ | ||
Navigation.prototype.handleSubNav = function ( event ) { | ||
Navigation.prototype.handleSubNav = function (event) { | ||
const target = event.target; | ||
// Use .closest because there can be SVG inside the button. | ||
const closestSubButton = target.closest( '[data-meom-nav="sub-toggle"]' ); | ||
const closestSubButton = target.closest('[data-meom-nav="sub-toggle"]'); | ||
const closestSubSubButton = target.closest( | ||
@@ -246,3 +245,3 @@ '[data-meom-nav="sub-sub-toggle"]' | ||
// If the clicked element doesn't have the correct data attribute, bail. | ||
if ( ! closestSubButton && ! closestSubSubButton ) { | ||
if (!closestSubButton && !closestSubSubButton) { | ||
return this; | ||
@@ -255,4 +254,6 @@ } | ||
if ( | ||
! target.nextElementSibling.classList.contains( this.settings.toggleSubNavClassValue ) && | ||
! target.matches( '[data-meom-nav="sub-sub-toggle"]' ) | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
!target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
@@ -263,8 +264,20 @@ this._closeAllSubMenus(); | ||
// Then again, close all sub sub menus when trying to open any other sub sub menu that is not already open. | ||
// So that only one sub sub menu can be open at current time. | ||
if ( | ||
!target.nextElementSibling.classList.contains( | ||
this.settings.toggleSubNavClassValue | ||
) && | ||
target.matches('[data-meom-nav="sub-sub-toggle"]') | ||
) { | ||
this._closeAllSubSubMenus(); | ||
this._closeAllSubSubMenuToggles(); | ||
} | ||
// Update sub toggle ARIA. | ||
updateAria( target, 'expanded' ); | ||
updateAria(target, 'expanded'); | ||
// Toggle class for next <ul> element (sub-menu). | ||
if ( target.nextElementSibling ) { | ||
this._setSubMenu( target.nextElementSibling, event ); | ||
if (target.nextElementSibling) { | ||
this._setSubMenu(target.nextElementSibling, event); | ||
} | ||
@@ -281,3 +294,3 @@ | ||
*/ | ||
Navigation.prototype.handleCloseNav = function ( event ) { | ||
Navigation.prototype.handleCloseNav = function (event) { | ||
// Close nav on Esc key if nav is open. | ||
@@ -289,3 +302,3 @@ if ( | ||
) { | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -302,11 +315,11 @@ | ||
*/ | ||
Navigation.prototype.handleCloseSubNav = function ( event ) { | ||
Navigation.prototype.handleCloseSubNav = function (event) { | ||
// Set focusable elements inside sub-menu element. | ||
const openSubMenu = document.querySelector( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
if ( openSubMenu ) { | ||
if (openSubMenu) { | ||
// Focusable elements. | ||
const focusableElements = openSubMenu.querySelectorAll( [ | ||
const focusableElements = openSubMenu.querySelectorAll([ | ||
'a[href]', | ||
@@ -318,6 +331,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -327,3 +340,3 @@ // Last Tab closes sub-menu. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -350,3 +363,3 @@ ) { | ||
// Close sub-menu on Esc key. | ||
if ( ESCAPE_KEY === event.keyCode ) { | ||
if (ESCAPE_KEY === event.keyCode) { | ||
// If we are on the sub-menu toggle itself. | ||
@@ -358,3 +371,3 @@ if ( | ||
) { | ||
this._handleSubNav( event ); | ||
this._handleSubNav(event); | ||
this._closeAllSubMenus(); | ||
@@ -367,7 +380,7 @@ this._closeAllSubMenuToggles(); | ||
const parentSubMenu = event.target.closest( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
// Set focus to sub menu toggle. | ||
if ( parentSubMenu ) { | ||
if (parentSubMenu) { | ||
// sub-menu toggle. | ||
@@ -377,3 +390,3 @@ const subMenuToggle = parentSubMenu.previousElementSibling; | ||
// Set focus back to sub-menu toggle. | ||
if ( subMenuToggle ) { | ||
if (subMenuToggle) { | ||
subMenuToggle.focus(); | ||
@@ -396,5 +409,5 @@ } | ||
*/ | ||
Navigation.prototype.handleFocus = function ( event ) { | ||
Navigation.prototype.handleFocus = function (event) { | ||
// Bail if menu is not open. | ||
if ( ! this.navOpened ) { | ||
if (!this.navOpened) { | ||
return this; | ||
@@ -404,3 +417,3 @@ } | ||
// Bail if `closeNavOnLastTab` setting is not set to true. | ||
if ( ! this.settings.closeNavOnLastTab ) { | ||
if (!this.settings.closeNavOnLastTab) { | ||
return this; | ||
@@ -410,3 +423,3 @@ } | ||
// Set focusable elements inside element. | ||
const focusableElements = this.$element.querySelectorAll( [ | ||
const focusableElements = this.$element.querySelectorAll([ | ||
'a[href]', | ||
@@ -418,6 +431,6 @@ 'area[href]', | ||
'button:not([disabled])', | ||
] ); | ||
]); | ||
const lastFocusableElement = | ||
focusableElements[ focusableElements.length - 1 ]; | ||
focusableElements[focusableElements.length - 1]; | ||
@@ -427,3 +440,3 @@ // Close nav on last Tab. | ||
TAB_KEY === event.keyCode && | ||
! event.shiftKey && | ||
!event.shiftKey && | ||
event.target === lastFocusableElement | ||
@@ -433,3 +446,3 @@ ) { | ||
// Close nav. | ||
this._handleNav( event ); | ||
this._handleNav(event); | ||
} | ||
@@ -446,5 +459,5 @@ | ||
*/ | ||
Navigation.prototype.handleDocClick = function ( event ) { | ||
Navigation.prototype.handleDocClick = function (event) { | ||
// Bail if clicking inside the nav. | ||
if ( event.target.closest( '[data-meom-nav="navigation"]' ) ) { | ||
if (event.target.closest('[data-meom-nav="navigation"]')) { | ||
return this; | ||
@@ -466,8 +479,8 @@ } | ||
const openSubMenus = document.querySelectorAll( | ||
`${ this.settings.subNavClass }.${ this.settings.toggleSubNavClassValue }` | ||
`${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubMenus.forEach( function ( openSubMenu ) { | ||
this._setSubMenu( openSubMenu ); | ||
}, this ); | ||
openSubMenus.forEach(function (openSubMenu) { | ||
this._setSubMenu(openSubMenu); | ||
}, this); | ||
@@ -478,19 +491,36 @@ return this; | ||
/** | ||
* Close all sub sub menus. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenus = function () { | ||
const openSubSubMenus = document.querySelectorAll( | ||
`${this.settings.subNavClass} ${this.settings.subNavClass}.${this.settings.toggleSubNavClassValue}` | ||
); | ||
openSubSubMenus.forEach(function (openSubSubMenu) { | ||
this._setSubMenu(openSubSubMenu); | ||
}, this); | ||
return this; | ||
}; | ||
/** | ||
* Set classes and animate for sub-menu. | ||
* | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @param {Node} submenu Sub menu node. | ||
* @param {Object} event Event. | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.setSubMenu = function ( submenu, event ) { | ||
if ( ! submenu ) { | ||
Navigation.prototype.setSubMenu = function (submenu, event) { | ||
if (!submenu) { | ||
return this; | ||
} | ||
if ( ! submenu.classList.contains( this.settings.toggleSubNavClassValue ) ) { | ||
submenu.classList.add( this.settings.toggleSubNavClassValue ); | ||
if (!submenu.classList.contains(this.settings.toggleSubNavClassValue)) { | ||
submenu.classList.add(this.settings.toggleSubNavClassValue); | ||
// Base animation with class. | ||
if ( this.settings.animateSubNav ) { | ||
animate( submenu, this.settings.animateSubNavClass ); | ||
if (this.settings.animateSubNav) { | ||
animate(submenu, this.settings.animateSubNavClass); | ||
} | ||
@@ -515,3 +545,3 @@ | ||
} else { | ||
submenu.classList.remove( this.settings.toggleSubNavClassValue ); | ||
submenu.classList.remove(this.settings.toggleSubNavClassValue); | ||
@@ -549,5 +579,5 @@ /** | ||
openSubMenuToggles.forEach( function ( openSubMenuToggle ) { | ||
updateAria( openSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubMenuToggles.forEach(function (openSubMenuToggle) { | ||
updateAria(openSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -558,5 +588,5 @@ const openSubSubMenuToggles = document.querySelectorAll( | ||
openSubSubMenuToggles.forEach( function ( openSubSubMenuToggle ) { | ||
updateAria( openSubSubMenuToggle, 'expanded' ); | ||
} ); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
@@ -566,2 +596,19 @@ return this; | ||
/** | ||
* Close all sub sub menu toggles. | ||
* | ||
* @return {this} this | ||
*/ | ||
Navigation.prototype.closeAllSubSubMenuToggles = function () { | ||
const openSubSubMenuToggles = document.querySelectorAll( | ||
'[data-meom-nav="sub-sub-toggle"][aria-expanded="true"]' | ||
); | ||
openSubSubMenuToggles.forEach(function (openSubSubMenuToggle) { | ||
updateAria(openSubSubMenuToggle, 'expanded'); | ||
}); | ||
return this; | ||
}; | ||
export default Navigation; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32
385
179981
3591
1