PageSourceSearch

https://environment-analyst.com/_/clicky-menus/1.5.2/clicky-menus.js

js environment-analyst.com collected 2026-10-02 07:04:53 UTC 5,573 bytes, 188 lines download raw bytes

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.