PageSourceSearch

https://zebrock.org/wp-content/themes/twentytwentyone/assets/js/primary-navigation.js?ver=2.1

js zebrock.org collected 2026-10-02 17:54:01 UTC 6,046 bytes, 204 lines download raw bytes

1/**
2 * File primary-navigation.js.
3 *
4 * Required to open and close the mobile navigation.
5 */
6
7/**
8 * Toggle an attribute's value
9 *
10 * @since Twenty Twenty-One 1.0
11 *
12 * @param {Element} el - The element.
13 * @param {boolean} withListeners - Whether we want to add/remove listeners or not.
14 */
15function twentytwentyoneToggleAriaExpanded( el, withListeners ) {
16	if ( 'true' !== el.getAttribute( 'aria-expanded' ) ) {
17		el.setAttribute( 'aria-expanded', 'true' );
18		twentytwentyoneSubmenuPosition( el.parentElement );
19		if ( withListeners ) {
20			document.addEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside );
21		}
22	} else {
23		el.setAttribute( 'aria-expanded', 'false' );
24		if ( withListeners ) {
25			document.removeEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside );
26		}
27	}
28}
29
30function twentytwentyoneCollapseMenuOnClickOutside( event ) {
31	if ( ! document.getElementById( 'site-navigation' ).contains( event.target ) ) {
32		document.getElementById( 'site-navigation' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) {
33			button.setAttribute( 'aria-expanded', 'false' );
34		} );
35	}
36}
37
38/**
39 * Changes the position of submenus so they always fit the screen horizontally.
40 *
41 * @since Twenty Twenty-One 1.0
42 *
43 * @param {Element} li - The li element.
44 */
45function twentytwentyoneSubmenuPosition( li ) {
46	var subMenu = li.querySelector( 'ul.sub-menu' ),
47		rect,
48		right,
49		left,
50		windowWidth;
51
52	if ( ! subMenu ) {
53		return;
54	}
55
56	rect = subMenu.getBoundingClientRect();
57	right = Math.round( rect.right );
58	left = Math.round( rect.left );
59	windowWidth = Math.round( window.innerWidth );
60
61	if ( right > windowWidth ) {
62		subMenu.classList.add( 'submenu-reposition-right' );
63	} else if ( document.body.classList.contains( 'rtl' ) && left < 0 ) {
64		subMenu.classList.add( 'submenu-reposition-left' );
65	}
66}
67
68/**
69 * Handle clicks on submenu toggles.
70 *
71 * @since Twenty Twenty-One 1.0
72 *
73 * @param {Element} el - The element.
74 */
75function twentytwentyoneExpandSubMenu( el ) { // jshint ignore:line
76	// Close other expanded items.
77	el.closest( 'nav' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) {
78		if ( button !== el ) {
79			button.setAttribute( 'aria-expanded', 'false' );
80		}
81	} );
82
83	// Toggle aria-expanded on the button.
84	twentytwentyoneToggleAriaExpanded( el, true );
85
86	// On tab-away collapse the menu.
87	el.parentNode.querySelectorAll( 'ul > li:last-child > a' ).forEach( function( linkEl ) {
88		linkEl.addEventListener( 'blur', function( event ) {
89			if ( ! el.parentNode.contains( event.relatedTarget ) ) {
90				el.setAttribute( 'aria-expanded', 'false' );
91			}
92		} );
93	} );
94}
95
96( function() {
97	/**
98	 * Menu Toggle Behaviors
99	 *
100	 * @since Twenty Twenty-One 1.0
101	 *
102	 * @param {string} id - The ID.
103	 */
104	var navMenu = function( id ) {
105		var wrapper = document.body, // this is the element to which a CSS class is added when a mobile nav menu is open
106			mobileButton = document.getElementById( id + '-mobile-menu' ),
107			navMenuEl = document.getElementById( 'site-navigation' );
108
109		// If there's no nav menu, none of this is necessary.
110		if ( ! navMenuEl ) {
111			return;
112		}
113
114		if ( mobileButton ) {
115			mobileButton.onclick = function() {
116				wrapper.classList.toggle( id + '-navigation-open' );
117				wrapper.classList.toggle( 'lock-scrolling' );
118				twentytwentyoneToggleAriaExpanded( mobileButton );
119				mobileButton.focus();
120			};
121		}
122
123		/**
124		 * Trap keyboard navigation in the menu modal.
125		 * Adapted from Twenty Twenty.
126		 *
127		 * @since Twenty Twenty-One 1.0
128		 */
129		document.addEventListener( 'keydown', function( event ) {
130			var modal, elements, selectors, lastEl, firstEl, activeEl, tabKey, shift
130Key, escKey;
131			if ( ! wrapper.classList.contains( id + '-navigation-open' ) ) {
132				return;
133			}
134
135			modal = document.querySelector( '.' + id + '-navigation' );
136			selectors = 'input, a, button';
137			elements = modal.querySelectorAll( selectors );
138			elements = Array.prototype.slice.call( elements );
139			tabKey = event.keyCode === 9;
140			shiftKey = event.shiftKey;
141			escKey = event.keyCode === 27;
142			activeEl = document.activeElement; // eslint-disable-line @wordpress/no-global-active-element
143			lastEl = elements[ elements.length - 1 ];
144			firstEl = elements[0];
145
146			if ( escKey ) {
147				event.preventDefault();
148				wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' );
149				twentytwentyoneToggleAriaExpanded( mobileButton );
150				mobileButton.focus();
151			}
152
153			if ( ! shiftKey && tabKey && lastEl === activeEl ) {
154				event.preventDefault();
155				firstEl.focus();
156			}
157
158			if ( shiftKey && tabKey && firstEl === activeEl ) {
159				event.preventDefault();
160				lastEl.focus();
161			}
162
163			// If there are no elements in the menu, don't move the focus
164			if ( tabKey && firstEl === lastEl ) {
165				event.preventDefault();
166			}
167		} );
168
169		/**
170		 * Close menu and scroll to anchor when an anchor link is clicked.
171		 * Adapted from Twenty Twenty.
172		 *
173		 * @since Twenty Twenty-One 1.1
174		 */
175		document.getElementById( 'site-navigation' ).addEventListener( 'click', function( event ) {
176			// If target onclick is <a> with # within the href attribute
177			if ( event.target.hash ) {
178				wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' );
179				twentytwentyoneToggleAriaExpanded( mobileButton );
180				// Wait 550 and scroll to the anchor.
181				setTimeout(function () {
182					var anchor = document.getElementById(event.target.hash.slice(1));
183					if ( anchor ) {
184						anchor.scrollIntoView();
185					}
186				}, 550);
187			}
188		} );
189
190		navMenuEl.querySelectorAll( '.menu-wrapper > .menu-item-has-children' ).forEach( function( li ) {
191			li.addEventListener( 'mouseenter', function() {
192				this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'true' );
193				twentytwentyoneSubmenuPosition( li );
194			} );
195			li.addEventListener( 'mouseleave', function() {
196				this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'false' );
197			} );
198		} );
199	};
200
201	window.addEventListener( 'load', function() {
202		new navMenu( 'primary' );
203	} );
204}() );

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.