1/** 2 * Clicky Menus v1.5.2 3 */ 4 5( function() { 6 'use strict'; 7 8 const ClickyMenus = function( menu, menuIndex ) { 9 // DOM element(s) 10 const container = menu.parentElement; 11 let currentMenuItem, 12 i, 13 len; 14 15 this.init = function() { 16 menuSetup(); 17 document.addEventListener( 'click', closeIfClickOutsideMenu ); 18 // custom event to allow outside scripts to close submenus 19 menu.addEventListener( 'clickyMenusClose', closeOpenSubmenu ); 20 }; 21 22 /*=================================================== 23 = Menu Open / Close Functions = 24 ===================================================*/ 25 function toggleOnMenuClick( e ) { 26 const button = e.currentTarget; 27 28 // close open menu if there is one 29 if ( currentMenuItem && button !== currentMenuItem ) { 30 toggleSubmenu( currentMenuItem ); 31 } 32 33 toggleSubmenu( button ); 34 } 35 36 function toggleSubmenu( button ) { 37 const submenu = document.getElementById( button.getAttribute( 'aria-controls' ) ); 38 39 if ( 'true' === button.getAttribute( 'aria-expanded' ) ) { 40 button.setAttribute( 'aria-expanded', false ); 41 submenu.setAttribute( 'aria-hidden', true ); 42 currentMenuItem = false; 43 } else { 44 button.setAttribute( 'aria-expanded', true ); 45 submenu.setAttribute( 'aria-hidden', false ); 46 preventOffScreenSubmenu( submenu ); 47 currentMenuItem = button; 48 } 49 } 50 51 function preventOffScreenSubmenu( submenu ) { 52 const screenWidth = window.innerWidth || 53 document.documentElement.clientWidth || 54 document.body.clientWidth, 55 parent = submenu.offsetParent, 56 menuLeftEdge = parent.getBoundingClientRect().left, 57 menuRightEdge = menuLeftEdge + submenu.offsetWidth; 58 59 if ( menuRightEdge + 32 > screenWidth ) { // adding 32 so it's not too close 60 submenu.classList.add( 'sub-menu--right' ); 61 } 62 } 63 64 function closeOnEscKey( e ) { 65 if ( 27 === e.keyCode ) { 66 // we're in a submenu item 67 if ( null !== e.target.closest( 'ul[aria-hidden="false"]' ) ) { 68 currentMenuItem.focus(); 69 toggleSubmenu( currentMenuItem ); 70 e.preventDefault(); 71 // we're on a parent item 72 } else if ( 'true' === e.target.getAttribute( 'aria-expanded' ) ) { 73 toggleSubmenu( currentMenuItem ); 74 e.preventDefault(); 75 } 76 } 77 } 78 79 function closeIfClickOutsideMenu( e ) { 80 if ( currentMenuItem && ! e.target.closest( '#' + container.id ) ) { 81 toggleSubmenu( currentMenuItem ); 82 } 83 } 84 85 function closeOpenSubmenu() { 86 if( currentMenuItem ) { 87 toggleSubmenu( currentMenuItem ); 88 } 89 } 90 91 /*=========================================================== 92 = Modify Menu Markup & Bind Listeners = 93 =============================================================*/ 94 function menuSetup() { 95 menu.classList.remove( 'no-js' ); 96 const submenuSelector = 'clickySubmenuSelector' in menu.dataset ? menu.dataset.clickySubmenuSelector : 'ul'; 97 98 menu.querySelectorAll( submenuSelector ).forEach( ( submenu ) => { 99 const menuItem = submenu.parentElement; 100 101 if ( 'undefined' !== typeof submenu ) { 102 const button = convertLinkToButton( menuItem ); 103 104 setUpAria( submenu, button ); 105 106 // bind event listener to button 107 button.addEventListener( 'click', toggleOnMenuClick ); 108 menu.addEventListener( 'keydown', closeOnEscKey ); 109 } 110 } ); 111 } 112 113 /** 114 * Why do this? See https://justmarkup.com/articles/2019-01-21-the-link-to-button-enhancement/ 115 * 116 * @param {HTMLElement} menuItem An element representing a link to be converted to a button 117 */ 118 function convertLinkToButton( menuItem ) { 119 const link = menuItem.getElementsByTagName( 'a' )[ 0 ], 120 linkHTML = link.innerHTML, 121 linkAtts = link.attributes, 122 button = document.createElement( 'button' ); 123 124 if ( null !== link ) { 125 // copy button attributes and content from link 126 button.innerHTML = linkHTML.trim(); 127 for ( i = 0, len = linkAtts.length; i < len; i++ ) { 128 const attr = linkAtts[ i ]; 129 if ( 'href' !== attr.name ) { 130 button.setAttribute( attr.name, attr.value ); 131 } 132 } 133 134 menuItem.replaceChild( button, link ); 135 } 136 137 return button; 138 } 139 140 function setUpAria( submenu, button ) { 141 const submenuId = submenu.getAttribute( 'id' ); 142 143 let id; 144 const idSuffix = `-submenu-${menuIndex}-${i}`; 145 if ( null === submenuId ) { 146 id = button.textContent.trim().replace( /\s+/g, '-' ).replace(/^[^a-zA-Z]+|[^\w:.-]+/g, "").toLowerCase() + idSuffix; 147 i++; 148 } else { 149 id = submenuId + idSuffix; 150 i++; 151 } 152 153 // set button ARIA 154 button.setAttribute( 'aria-controls', id ); 155 button.setAttribute( 'aria-expanded', false ); 156 157 // set submenu ARIA 158 submenu.setAttribute( 'id', id ); 159 submenu.setAttribute( 'aria-hidden', true ); 160 } 161 }; 162 163 function dispatchMenuClose(e) { 164 const menuId = e.currentTarget.getAttribute('data-clicky-menus-close'); 165 const menu = document.getElementById( menuId ); 166 if( menu ) { 167 menu.dispatchEvent( new Event( 'clickyMenusClose' ) ); 168 } 169 } 170 171 /* Create a ClickMenus object and initiate menu for any menu with .clicky-menu class */ 172 document.addEventListener( 'DOMContentLoaded', function() { 173 const menus = document.querySelectorAll( '.clicky-menu' ); 174 let i = 1;
175 menus.forEach( ( menu ) => { 176 const clickyMenu = new ClickyMenus( menu, i ); 177 clickyMenu.init(); 178 i++; 179 } ); 180 181 const menuClosers = document.querySelectorAll( '[data-clicky-menus-close]' ); 182 if( menuClosers ) { 183 menuClosers.forEach( ( menuCloser ) => { 184 menuCloser.addEventListener( 'click', dispatchMenuClose ); 185 } ); 186 } 187 } ); 188}() );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.