PageSourceSearch

https://www.internationalrivers.org/wp-content/themes/pioneer/js/main.js?ver=2.11

js internationalrivers.org collected 2026-09-24 08:47:43 UTC 13,800 bytes, 381 lines download raw bytes

1jQuery(document).ready(function ($) {
2
3/*	==========================================================================
4	Toggle CSS classes for focused navigation items to improve accessibility
5	========================================================================== */
6	jQuery( '#masthead' ).on( 'focusin focusout', '#menu-primary-menu .sub-menu a', function(){
7		jQuery( this )
8			.parent( 'li' ).toggleClass( 'focused' )
9			.closest( '#menu-primary-menu > li.menu-item-has-children' ).toggleClass( 'focused' );
10	});
11
12/*	==========================================================================
13	Escape closes an open desktop sub-menu
14	========================================================================== */
15	/*
16	 * A sub-menu opens on focus and covers the page, so Escape has to dismiss it
17	 * without leaving the menu (WCAG 1.4.13). The `dismissed` class holds it closed
18	 * while the parent link keeps focus, since the stylesheet otherwise reopens it;
19	 * the class clears when focus leaves the item or the pointer returns.
20	 */
21	jQuery( '#masthead' ).on( 'keydown', '#menu-primary-menu > li.menu-item-has-children', function( e ) {
22
23		if ( e.key !== 'Escape' || ! window.matchMedia( '(min-width: 768px)' ).matches ) {
24			return;
25		}
26
27		const $item    = jQuery( this );
28		const $subMenu = $item.children( '.sub-menu' );
29
30		if ( ! $subMenu.length || getComputedStyle( $subMenu[0] ).visibility !== 'visible' ) {
31			return;
32		}
33
34		e.preventDefault();
35		e.stopPropagation();
36
37		// Focus first, so the focused-class toggles above settle before the classes are cleared.
38		$item.children( 'a' )[0].focus();
39		$item.addClass( 'dismissed' ).find( '.focused' ).addBack().removeClass( 'focused' );
40	} );
41
42	jQuery( '#masthead' ).on( 'focusout mouseenter', '#menu-primary-menu > li.dismissed', function( e ) {
43
44		if ( e.type === 'focusout' && e.relatedTarget && this.contains( e.relatedTarget ) ) {
45			return;
46		}
47
48		jQuery( this ).removeClass( 'dismissed' );
49	} );
50
51/*	==========================================================================
52	MeanMenu init and options
53	========================================================================== */
54	// Shared with the expand toggle handler below so the two glyphs can't drift.
55	const expandGlyph   = '+';
56	const contractGlyph = '–';
57
58	jQuery('header nav').meanmenu( {
59		meanScreenWidth: '767',
60		meanMenuCloseSize: '2rem',
61		meanMenuContainer: '.mobile-nav',
62		meanExpand: expandGlyph,
63		meanContract: contractGlyph
64	});
65
66/*	==========================================================================
67	Give MeanMenu's generated toggle a name, role and state
68	MeanMenu builds an empty anchor, so screen readers announce it as an
69	unnamed link with no indication of whether the menu is open. The names come
70	from PHP through wp_localize_script so they can be translated.
71	========================================================================== */
72	( function () {
73
74		const phpLabels = window.pioneerMenuLabels || {};
75		const labels    = {
76			menu: phpLabels.menu || 'Menu',
77			toggleSubmenu: phpLabels.toggleSubmenu || 'Toggle submenu'
78		};
79
80		var $container = jQuery( '.mobile-nav' );
81
82		if ( ! $container.length ) {
83			return;
84		}
85
86		/*
87		 * MeanMenu rebuilds its bar whenever the window is resized across the
88		 * breakpoint, which throws away anything set on the generated toggle, so
89		 * the attributes are re-applied whenever a new toggle appears.
90		 */
91		function enhance() {
92
93			var $reveal = $container.find( '.meanmenu-reveal' );
94
95			if ( $reveal.length && $reveal.attr( 'role' ) !== 'button' ) {
96
97				$container.find( '.mean-nav' ).attr( 'id', 'mobile-nav-menu' );
98
99				$reveal.attr( {
100					'role': 'button',
101					'aria-label': labels.menu,
102					'aria-controls': 'mobile-nav-menu',
103					'aria-expanded': 'false'
104				} );
105			}
106
107			moveExpandToggles();
108		}
109
110		/*
111		 * MeanMenu appends each expand toggle after the sub-menu it opens, which
112		 * puts it after every sub-item in the tab order, so move it up to follow
113		 * the parent link. MeanMenu's own click handler opens prev( 'ul' ), which
114		 * finds nothing after the move, so the delegated handler below takes over.
115		 */
116		function moveExpandToggles() {
117
118			$container.find( 'a.mean-expand' ).each( function () {
119
120				var $toggle  = jQuery( this );
121				var $submenu = $toggle.prev( 'ul' );
122
123				if ( ! $submenu.length ) {
124					return;
125				}
126
127				$toggle
128					.off( 'click' )
129					.attr( {
130						'aria-label': labels.toggleSubmenu,
131						'aria-expanded': 'false'
132					} )
133					.insertBefore( $submenu );
134			} );
135		}
136
137		enhance();
138
139		if ( window.MutationObserver ) {
140			new window.MutationObserver( enhance ).observe( $container[0], { childList: true, subtree: true } );
141		}
142
143		/*
144		 * Delegated so it survives the toggle being replaced. MeanMenu marks the
145		 * open state with the meanclose class and sets it in its own click
146		 * handler, so read it back on the next tick.
147		 */
148		$container.on( 'click', '.meanmenu-reveal', function () {
149
150			var $self = jQuery( this );
151
152			setTimeout( function () {
153				$self.attr( 'aria-expanded', $self.hasClass( 'meanclose' ) ? 'true' : 'false' );
154			}, 0 );
155		} );
156
157		// It announces as a button, so Space has to work as well as Enter. The
158		// generated control is a link, which only reacts to Enter on its own.
159		$container.on( 'keydown', '.meanmenu-reveal', function ( e ) {
160
161			if ( e.key === ' ' || e.key === 'Spacebar' ) {
162				e.preventDefault();
163				jQuery( this ).trigger( 'click' );
164			}
165		} );
166
167		/*
168		 * Replaces the per-toggle handler removed above, doing what MeanMenu did
169		 * but against next( 'ul' ) now that the toggle comes first. Delegated so
170		 * it survives MeanMenu rebuilding the bar on resize.
171		 */
172		$container.on( 'click', 'a.mean-expand', function ( e ) {
173
174			var $toggle  = jQuery( this );
175			var $submenu = $toggle.next( 'ul' );
176
177			e.preventDefault();
178
179			if ( $toggle.hasClass( 'mean-clicked' ) ) {
180
181				$toggle.text( expandGlyph ).attr( 'aria-expanded', 'false' );
182				$submenu.slideUp( 300 );
183
184			} else {
185
186				$toggle.text( contractGlyph ).attr( 'aria-expanded', 'true' );
187				$submenu.slideDown( 300 );
188			}
189
190			$toggle.toggleClass( 'mean-clicked' );
191		} );
192	} )();
193
194
195
196/*	==========================================================================
197	Toggle Search Bar
198	========================================================================== */
199	/*
200	 * Remember which toggle opened the form. There is a desktop toggle and a
201	 * copy inside the mobile menu bar, and Escape hands focus back to the one
202	 * the person used.
203	 */
204	let $searchToggleInUse = jQuery('.search-toggle').first();
205
206	/*
207	 * The toggle shows a magnifying glass while the form is closed and an X
208	 * while it is open, so its name changes with the icon. The names come from
209	 * PHP through wp_localize_script so they can be translated.
210	 */
211	const $searchPhpLabels = window.pioneerMenuLabels || {};
212	const $searchOpenLabel = $searchPhpLabels.search || 'Search';
213	const $searchCloseLabel = $searchPhpLabels.closeSearch || 'Close search';
214
215	jQuery('.search-toggle').on('click', function(e) {
216
217		$searchToggleInUse = jQuery( this );
218
219		// Open or close the search form
220		jQuery('#masthead .search-form').toggleClass('search-open');
221
222		// The class drives the icon swap and the panel's slide, so it flips at once.
223		jQuery( '.search-toggle' ).toggleClass( 'search-open' );
224
225		// The name and expanded state flip with the icon. On close they change right
226		// away, since focus returns to the toggle. On open they wait until focus has
227		// moved into the field, because a screen reader starts reading a change on
228		// the focused control and the move to the field cuts it off mid-word.
229		const searchIsOpen = jQuery( '.search-toggle' ).hasClass( 'search-open' );
230
231		if ( searchIsOpen === false ) {
232			jQuery( '.search-toggle' ).attr( 'aria-expanded', 'false' ).attr( 'aria-label', $searchOpenLabel );
233		}
234
235		// Once the search form transitions to display place the cursor inside it
236		jQuery( '#masthead .search-form' ).one( 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function(){
237			var $this = $( this );
238
239			if( $this.hasClass( 'search-open' ) === true ){
240				$this.find( 'input[type="search"]' ).focus();
241				jQuery( '.search-toggle' ).attr( 'aria-expanded', 'true' ).attr( 'aria-label', $searchCloseLabel );
242			}
243		});
244		
245		e.preventDefault();
246	});
247
248	// It announces as a button, so Space has to work as well as Enter. The
249	// toggle is a link, which only reacts to Enter on its own.
250	jQuery( '.search-toggle' ).on( 'keydown', function ( e ) {
251
252		if ( e.key === ' ' || e.key === 'Spacebar' ) {
253			e.preventDefault();
254			jQuery( this ).trigger( 'click' );
255		}
256	} );
257
258	/*
259	 * Escape closes the form and returns focus to the toggle, matching the other
260	 * themes. Bound to the form and the toggles so it only fires while one of
261	 * them holds focus.
262	 */
263	jQuery( '#masthead .search-form, .search-toggle' ).on( 'keydown', function(e) {
264
265		const $searchForm = jQuery( '#masthead .search-form' );
266
267		if ( e.key !== 'Escape' || ! $searchForm.hasClass( 'search-open' ) ) {
268			return;
269		}
270
271		$searchForm.removeClass( 'search-open' );
272		jQuery( '.search-toggle' ).removeClass( 'search-open' ).attr( 'aria-expanded', 'false' ).attr( 'aria-label', $searchOpenLabel );
273		$searchToggleInUse.focus();
274	});
275
276
277/*	==========================================================================
278	iOS body class
279	========================================================================== */
280	if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
281		jQuery('body').addClass('agent-ios');
282	}
283
284
285/*	==========================================================================
286	Wrap <address> tags with Google Maps links
287	========================================================================== */
288	jQuery('address').each(function () {
289		var link = "<a href='http://maps.google.com/maps?q=" + encodeURIComponent( jQuery(this).text() ) + "' class='map-link' target='_blank'>" + jQuery(this).html() + "</a>";
290		jQuery(this).html(link);
291	});
292
293	/*  ==========================================================================
294	Remove Bottom Border from Anchors that contain Images
295
296	If the anchor links are designed out to have bottom borders this will remove the bottom border
297	when a user adds a linked image within the content editor.
298	========================================================================== */
299	$('img').parent('a').css('border-bottom', 'none');
300
301
302	/*  ==========================================================================
303	Open Default WordPress Gallery Pictures in New Tab
304	========================================================================== */
305	$('.gallery .gallery-item a').attr('target', '_blank');
306
307
308	/*	==========================================================================
309	Double Tap and Go makes primary navigation dropdowns work on tablets
310	========================================================================== */
311	$( '#site-navigation #menu-primary-menu' ).doubleTapToGo({
312		selectorChain: '> li:has(ul)'
313	});
314
315
316	/* ============================================================================
317	Homepage Statistics Block - Numbers Count Up (See https://github.com/inorganik/countUp.js)
318	============================================================================ */
319	const numberElements = document.querySelectorAll( '.homepage-statistics .stat span.number' );
320	numberElements.forEach( function( numberElement ) {
321		const endNumber      = parseFloat( numberElement.getAttribute( 'data-end-number' ) );
322		const countAnimation = new countUp.CountUp(
323			numberElement,
324			endNumber,
325			{
326				duration: 2.5, // Duration in seconds
327				enableScrollSpy: true, // Animate when the element enters view
328				scrollSpyOnce: true, // Only animate once
329				decimalPlaces: parseInt( numberElement.getAttribute( 'data-num-decimal-places' ), 10 ),
330			}
331		);
332		countAnimation.start();
333	});
334
335	/* ============================================================================
336	Homepage Blocks Background Colors
337	Alternate colors but skip Tiles blocks and combine single Text Callouts that have right-aligned buttons
338	============================================================================ */
339	const homepageBlocks = document.querySelectorAll( 'body.home .site-main > section' );
340	if ( homepageBlocks && homepageBlocks.length > 0 ) {
341
342		let sectionCounter               = 1;
343		let previousBlockClasses         = [];
344		const requiredTextCalloutClasses = [ 'text-callouts', 'callout-num-1', 'has-buttons-on-the-right' ];
345
346		homepageBlocks.forEach( ( block ) => {
347
348			// Skip Tiles blocks so they aren't factored into the alternating background colors.
349			if ( block.classList.contains( 'homepage-tiles' ) ) {
350
351				return;
352			}
353
354			const blockClasses                            = block.classList.value.split( ' ' );
355			const isSingleTextCalloutButtonRight          = requiredTextCalloutClasses.every( requiredClass => blockClasses.includes( requiredClass ) );
356			const wasPreviousSingleTextCalloutButtonRight = requiredTextCalloutClasses.every( requiredClass => previousBlockClasses.includes( requiredClass ) );
357			const previousBlockHasColoredBackground       = previousBlockClasses.includes( 'colored-background' );
358			
359			// If this block and the previous block are Text Callouts with a single callout and right aligned buttons, treat them as one block.
360			if ( isSingleTextCalloutButtonRight && wasPreviousSingleTextCalloutButtonRight ) {
361
362				if ( previousBlockHasColoredBackground ) {
363
364					block.classList.add( 'colored-background' );
365				}
366
367				return;
368			}
369
370			// Add colored-background class to alternating blocks.
371			if ( sectionCounter % 2 === 0 ) {
372
373				block.classList.add( 'colored-background' );
374			}
375
376			previousBlockClasses = block.classList.value.split( ' ' );
377
378			sectionCounter++;
379		});
380	}
381});

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.