PageSourceSearch

https://unboundia.com/wp-content/themes/unboundia/js/navigation.js?ver=1790945812

js unboundia.com collected 2026-10-02 15:25:27 UTC 5,799 bytes, 233 lines download raw bytes

1/**
2 * File navigation.js.
3 *
4 * Handles toggling the navigation menu for small screens and enables TAB key
5 * navigation support for dropdown menus.
6 */
7( function() {
8	const siteNavigation = document.getElementById( 'masthead' );
9
10	// Return early if the navigation doesn't exist.
11	if ( ! siteNavigation ) {
12		return;
13	}
14
15	const button = siteNavigation.getElementsByTagName( 'button' )[ 0 ];
16
17	// Return early if the button doesn't exist.
18	if ( 'undefined' === typeof button ) {
19		return;
20	}
21
22	const menu = siteNavigation.getElementsByClassName( 'header-menu-wrapper' )[ 0 ];
23
24	// Hide menu toggle button if menu is empty and return early.
25	if ( 'undefined' === typeof menu ) {
26		button.style.display = 'none';
27		return;
28	}
29
30	if ( ! menu.classList.contains( 'nav-menu' ) ) {
31		menu.classList.add( 'nav-menu' );
32	}
33
34	const mobileQuery = window.matchMedia( '(max-width: 991px)' );
35
36	function isMobile() {
37		return mobileQuery.matches;
38	}
39
40
41	/* --------------------------------------------------------
42	   OPEN / CLOSE HELPERS — explicit state, never a blind toggle
43	   -------------------------------------------------------- */
44
45	function setExpanded( item, open ) {
46		item.classList.toggle( 'focus', open );
47
48		const link = item.querySelector( ':scope > a' );
49
50		if ( link ) {
51			link.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
52		}
53	}
54
55	function closeDescendants( item ) {
56		item.querySelectorAll( 'li.focus' ).forEach( function( child ) {
57			setExpanded( child, false );
58		} );
59	}
60
61	function closeSiblings( item ) {
62		Array.prototype.forEach.call( item.parentNode.children, function( sibling ) {
63			if ( sibling !== item && sibling.classList.contains( 'focus' ) ) {
64				closeDescendants( sibling );
65				setExpanded( sibling, false );
66			}
67		} );
68	}
69
70	function openItem( item ) {
71		closeSiblings( item );
72		setExpanded( item, true );
73	}
74
75	function closeItem( item ) {
76		closeDescendants( item );
77		setExpanded( item, false );
78	}
79
80	function closeAll() {
81		menu.querySelectorAll( 'li.focus' ).forEach( function( item ) {
82			setExpanded( item, false );
83		} );
84	}
85
86
87	/* --------------------------------------------------------
88	   HAMBURGER
89	   -------------------------------------------------------- */
90
91	button.addEventListener( 'click', function() {
92		siteNavigation.classList.toggle( 'toggled' );
93		menuIconAnimation();
94
95		const isOpen = siteNavigation.classList.contains( 'toggled' );
96
97		button.setAttribute( 'aria-expanded', isOpen ? 'true' : 'false' );
98
99		// Reset submenu state whenever the drawer closes.
100		if ( ! isOpen ) {
101			closeAll();
102		}
103	} );
104
105	// Close when clicking outside the navigation.
106	document.addEventListener( 'click', function( event ) {
107		const isClickInside = siteNavigation.contains( event.target );
108
109		if ( ! isClickInside ) {
110			siteNavigation.classList.remove( 'toggled' );
111			button.setAttribute( 'aria-expanded', 'false' );
112			closeAll();
113		}
114	} );
115
116
117	/* --------------------------------------------------------
118	   SUBMENUS
119	   -------------------------------------------------------- */
120
121	const links = menu.getElementsByTagName( 'a' );
122	const linksWithChildren = menu.querySelectorAll( '.menu-item-has-children > a, .page_item_has_children > a' );
123
124	// Desktop keyboard navigation only.
125	for ( const link of links ) {
126		link.addEventListener( 'focus', toggleFocus, true );
127		link.addEventListener( 'blur', toggleFocus, true );
128	}
129
130	// Mobile: click owns the submenu, so touchstart is not registered at all.
131	for ( const link of linksWithChildren ) {
132		link.addEventListener( 'click', function( event ) {
133			if ( ! isMobile() ) {
134				return;
135			}
136
137			event.preventDefault();
138			event.stopPropagation();
139
140			const item = this.parentNode;
141
142			if ( item.classList.contains( 'focus' ) ) {
143				closeItem( item );
144			} else {
145				openItem( item );
146			}
147		} );
148	}
149
150	/**
151	 * Sets or removes .focus on ancestors of a focused link (desktop only).
152	 */
153	function toggleFocus( event ) {
154		if ( isMobile() ) {
155			return;
156		}
157
158		const open = ( event.type === 'focus' );
159		let self = this;
160
161		while ( self && ! self.classList.contains( 'nav-menu' ) ) {
162			if ( 'li' === self.tagName.toLowerCase() ) {
163				self.classList.toggle( 'focus', open );
164			}
165			self = self.parentNode;
166		}
167	}
168
169	// Clear stale state when crossing the breakpoint.
170	mobileQuery.addEventListener( 'change', closeAll );
171
172
173	/* --------------------------------------------------------
174	   ICON
175	   -------------------------------------------------------- */
176
177	function menuIconAnimation() {
178		if ( typeof gsap === 'undefined' ) {
179			return;
180		}
181
182		const isOpen = siteNavigation.classList.contains( 'toggled' );
183
184		const hamburger = {
185			top: 'M30 25L10 25',
186			bottom: 'M30 15L10 15',
187		};
188
189		const close = {
190			top: 'M28.333 11.6666L11.6663 28.3333',
191			bottom: 'M28.333 28.3334L11.6663 11.6667',
192		};
193
194		gsap.to( '.line-top', {
195			attr: { d: isOpen ? close.top : hamburger.top },
196			duration: 0.4,
197			ease: 'power3.inOut',
198		} );
199
200		gsap.to( '.line-bottom', {
201			attr: { d: isOpen ? close.bottom : hamburger.bottom },
202			duration: 0.4,
203			ease: 'power3.inOut',
204		} );
205	}
206}() );
207
208// Hide and show navbar on scroll
209jQuery(function ($) {
210  let lastScroll = 0;
211  const $nav = $("#masthead");
212
213  $(window).on("scroll", function () {
214    let currentScroll = $(this).scrollTop();
215
216    // Always show navbar at the top
217    if (currentScroll <= 0) {
218      $nav.removeClass("nav-hide nav-show").addClass("nav-top");
219      lastScroll = 0;
220      return;
221    }
222	$nav.removeClass("nav-top");
223    if (currentScroll > lastScroll && currentScroll > 20) {
224      // Scrolling Down
225      $nav.removeClass("nav-show").addClass("nav-hide");
226    } else if (currentScroll < lastScroll) {
227      // Scrolling Up
228      $nav.removeClass("nav-hide").addClass("nav-show");
229    }
230
231    lastScroll = currentScroll;
232  });
233});

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.