PageSourceSearch

https://www.vcommission.com/wp-content/themes/twentytwenty/assets/js/index.js?ver=1.0.22

js vcommission.com collected 2026-09-25 08:38:42 UTC 27,908 bytes, 904 lines download raw bytes

1/*	-----------------------------------------------------------------------------------------------
2	Namespace
3--------------------------------------------------------------------------------------------------- */
4
5var twentytwenty = twentytwenty || {};
6
7// Set a default value for scrolled.
8twentytwenty.scrolled = 0;
9
10// polyfill closest
11// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
12if ( ! Element.prototype.closest ) {
13	Element.prototype.closest = function( s ) {
14		var el = this;
15
16		do {
17			if ( el.matches( s ) ) {
18				return el;
19			}
20
21			el = el.parentElement || el.parentNode;
22		} while ( el !== null && el.nodeType === 1 );
23
24		return null;
25	};
26}
27
28// polyfill forEach
29// https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
30if ( window.NodeList && ! NodeList.prototype.forEach ) {
31	NodeList.prototype.forEach = function( callback, thisArg ) {
32		var i;
33		var len = this.length;
34
35		thisArg = thisArg || window;
36
37		for ( i = 0; i < len; i++ ) {
38			callback.call( thisArg, this[ i ], i, this );
39		}
40	};
41}
42
43// event "polyfill"
44twentytwenty.createEvent = function( eventName ) {
45	var event;
46	if ( typeof window.Event === 'function' ) {
47		event = new Event( eventName );
48	} else {
49		event = document.createEvent( 'Event' );
50		event.initEvent( eventName, true, false );
51	}
52	return event;
53};
54
55// matches "polyfill"
56// https://developer.mozilla.org/es/docs/Web/API/Element/matches
57if ( ! Element.prototype.matches ) {
58	Element.prototype.matches =
59		Element.prototype.matchesSelector ||
60		Element.prototype.mozMatchesSelector ||
61		Element.prototype.msMatchesSelector ||
62		Element.prototype.oMatchesSelector ||
63		Element.prototype.webkitMatchesSelector ||
64		function( s ) {
65			var matches = ( this.document || this.ownerDocument ).querySelectorAll( s ),
66				i = matches.length;
67			while ( --i >= 0 && matches.item( i ) !== this ) {}
68			return i > -1;
69		};
70}
71
72// Add a class to the body for when touch is enabled for browsers that don't support media queries
73// for interaction media features. Adapted from <https://codepen.io/Ferie/pen/vQOMmO>.
74twentytwenty.touchEnabled = {
75
76	init: function() {
77		var matchMedia = function() {
78			// Include the 'heartz' as a way to have a non-matching MQ to help terminate the join. See <https://git.io/vznFH>.
79			var prefixes = [ '-webkit-', '-moz-', '-o-', '-ms-' ];
80			var query = [ '(', prefixes.join( 'touch-enabled),(' ), 'heartz', ')' ].join( '' );
81			return window.matchMedia && window.matchMedia( query ).matches;
82		};
83
84		if ( ( 'ontouchstart' in window ) || ( window.DocumentTouch && document instanceof window.DocumentTouch ) || matchMedia() ) {
85			document.body.classList.add( 'touch-enabled' );
86		}
87	}
88}; // twentytwenty.touchEnabled
89
90/*	-----------------------------------------------------------------------------------------------
91	Cover Modals
92--------------------------------------------------------------------------------------------------- */
93
94twentytwenty.coverModals = {
95
96	init: function() {
97		if ( document.querySelector( '.cover-modal' ) ) {
98			// Handle cover modals when they're toggled.
99			this.onToggle();
100
101			// When toggled, untoggle if visitor clicks on the wrapping element of the modal.
102			this.outsideUntoggle();
103
104			// Close on escape key press.
105			this.closeOnEscape();
106
107			// Hide and show modals before and after their animations have played out.
108			this.hideAndShowModals();
109		}
110	},
111
112	// Handle cover modals when they're toggled.
113	onToggle: function() {
114		document.querySelectorAll( '.cover-modal' ).forEach( function( element ) {
115			element.addEventListener( 'toggled', function( event ) {
116				var modal = event.target,
117					body = document.body;
118
119				if ( modal.classList.contains( 'active' ) ) {
120					body.classList.add( 'showing-modal' );
121				} else {
122					body.classList.remove( 'showing-modal' );
123					body.classList.add( 'hiding-modal' );
124
125					// Remove the hiding class after a delay, when animations have been run.
126					setTimeout( function() {
127						body.classList.remove( 'hiding-modal' );
128					}, 500 );
129				}
130			} );
131		} );
132	},
133
134	// Close modal on outside click.
135	outsideUntoggle: function() {
136		document.addEventListener( 'click', function( event ) {
137			var target = event.target;
138			var modal = document.querySelector( '.cover-modal.active' );
139
140			// if target onclick is <a> with # within the href attribute
141			if ( event.target.tagName.toLowerCase() === 'a' && event.target.hash.includes( '#' ) && modal !== null ) {
142				// untoggle the modal
143				this.untoggleModal( modal );
144				// wait 550 and scroll to the anchor
145				setTimeout( function() {
146					var anchor = document.getElementById( event.target.hash.slice( 1 ) );
147					anchor.scrollIntoView();
148				}, 550 );
149			}
150
151			if ( target === modal ) {
152				this.untoggleModal( target );
153			}
154		}.bind( this ) );
155	},
156
157	// Close modal on escape key press.
158	closeOnEscape: function() {
159		document.addEventListener( 'keydown', function( event ) {
160			if ( event.keyCode === 27 ) {
161				event.preventDefault();
162				document.querySelectorAll( '.cover-modal.active' ).forEach( function( element ) {
163					this.untoggleModal( element );
164				}.bind( this ) );
165			}
166		}.bind( this ) );
167	},
168
169	// Hide and show modals before and after their animations have played out.
170	hideAndShowModals: function() {
171		var _doc = document,
172			_win = window,
173			modals = _doc.querySelectorAll( '.cover-modal' ),
174			htmlStyle = _doc.documentElement.style,
175			adminBar = _doc.querySelector( '#wpadminbar' );
176
177		function getAdminBarHeight( negativeValue ) {
178			var height,
179				currentScroll = _win.pageYOffset;
180
181			if ( adminBar ) {
182				height = currentScroll + adminBar.getBoundingClientRect().height;
183
184				return negativeValue ? -height : height;
185			}
186
187			return currentScroll === 0 ? 0 : -currentScroll;
188		}
189
190		function htmlStyles() {
191			var overflow = _win.innerHeight > _doc.documentElement.getBoundingClientRect().height;
192
193			return {
194				'overflow-y': overflow ? 'hidden' : 'scroll',
195				position: 'fixed',
196				width: '100%',
197				top: getAdminBarHeight( true ) + 'px',
198				left: 0
199			};
200		}
201
202		// Show the modal.
203		modals.forEach( function( modal ) {
204			modal.addEventListener( 'toggle-target-before-inactive', function( event ) {
205				var styles = htmlStyles(),
206					offsetY = _win.pageYOffset,
207					paddingTop = ( Math.abs( getAdminBarHeight() ) - offsetY ) + 'px',
208					mQuery = _win.matchMedia( '(max-width: 600px)' );
209
210				if ( event.target !== modal ) {
211					return;
212				}
213
214				Object.keys( styles ).forEach( function( styleKey ) {
215					htmlStyle.setProperty( styleKey, styles[ styleKey ] );
216				} );
217
218				_win.twentytwenty.scrolled = parseInt( styles.top, 10 );
219
220				if ( adminBar ) {
221					_doc.body.style.setProperty( 'padding-top', paddingTop );
222
223					if ( mQuery.matches ) {
224						if ( offsetY >= getAdminBarHeight() ) {
225							modal.style.setProperty( 'top', 0 );
226						} else {
227							modal.style.setProperty( 'top', ( getAdminBarHeight() - offsetY ) + 'px' );
228						}
229					}
230				}
231
232				modal.classList.add( 'show-modal' );
233			} );
234
235			// Hide the modal after a delay, so animations have time to play out.
236			modal.addEventListener( 'toggle-target-after-inactive', function( event ) {
237				if ( event.target !== modal ) {
238					return;
239				}
240
241				setTimeout( function() {
242					var clickedEl = twentytwenty.toggles.clickedEl;
243
244					modal.classList.remove( 'show-modal' );
245
246					Object.keys( htmlStyles() ).forEach( function( styleKey ) {
247						htmlStyle.removeProperty( styleKey );
248					} );
249
250					if ( adminBar ) {
251						_doc.body.style.removeProperty( 'padding-top' );
252						modal.style.removeProperty( 'top' );
253					}
254
255					if ( clickedEl !== false ) {
256						clickedEl.focus();
257						clickedEl = false;
258					}
259
260					_win.scrollTo( 0, Math.abs( _win.twentytwenty.scrolled + getAdminBarHeight() ) );
261
262					_win.twentytwenty.scrolled = 0;
263				}, 500 );
264			} );
265		} );
266	},
267
268	// Untoggle a modal.
269	untoggleModal: function( modal ) {
270		var modalTargetClass,
271			modalToggle = false;
272
273		// If the modal has specified the string (ID or class) used by toggles to target it, untoggle the toggles with that target string.
274		// The modal-target-string must match the string toggles use to target the modal.
275		if ( modal.dataset.modalTargetString ) {
276			modalTargetClass = modal.dataset.modalTargetString;
277
278			modalToggle = document.querySelector( '*[data-toggle-target="' + modalTargetClass + '"]' );
279		}
280
281		// If a modal toggle exists, trigger it so all of the toggle options are included.
282		if ( modalToggle ) {
283			modalToggle.click();
284
285			// If one doesn't exist, just hide the modal.
286		} else {
287			modal.classList.remove( 'active' );
288		}
289	}
290
291}; // twentytwenty.coverModals
292
293/*	-----------------------------------------------------------------------------------------------
294	Intrinsic Ratio Embeds
295--------------------------------------------------------------------------------------------------- */
296
297twentytwenty.intrinsicRatioVideos = {
298
299	init: function() {
300		this.makeFit();
301
302		window.addEventListener( 'resize', function() {
303			this.makeFit();
304		}.bind( this ) );
305	},
306
307	makeFit: function() {
308		document.querySelectorAll( 'iframe, object, video' ).forEach( function( video ) {
309			var ratio, iTargetWidth,
310				container = video.parentNode;
311
312			// Skip videos we want to ignore.
313			if ( video.classList.contains( 'intrinsic-ignore' ) || video.parentNode.classList.contains( 'intrinsic-ignore' ) ) {
314				return true;
315			}
316
317			if ( ! video.dataset.origwidth ) {
318				// Get the video element proportions.
319				video.setAttribute( 'data-origwidth', video.width );
320				video.setAttribute( 'data-origheight', video.height );
321			}
322
323			iTargetWidth = container.offsetWidth;
324
325			// Get ratio from proportions.
326			ratio = iTargetWidth / video.dataset.origwidth;
327
328			// Scale based on ratio, thus retaining proportions.
329			video.style.width = iTargetWidth + 'px';
330			video.style.height = ( video.dataset.origheight * ratio ) + 'px';
331		} );
332	}
333
334}; // twentytwenty.intrinsicRatioVideos
335
336/*	-----------------------------------------------------------------------------------------------
337	Modal Menu
338--------------------------------------------------------------------------------------------------- */
339twentytwenty.modalMenu = {
340
341	init: function() {
342		// If the current menu item is in a sub level, expand all the levels higher up on load.
343		this.expandLevel();
344		this.keepFocusInModal();
345	},
346
347	expandLevel: function() {
348		var modalMenus = document.querySelectorAll( '.modal-menu' );
349
350		modalMenus.forEach( function( modalMenu ) {
351			var activeMenuItem = modalMenu.querySelector( '.current-menu-item' );
352
353			if ( activeMenuItem ) {
354				twentytwentyFindParents( activeMenuItem, 'li' ).forEach( function( element ) {
355					var subMenuToggle = element.querySelector( '.sub-menu-toggle' );
356					if ( subMenuToggle ) {
357						twentytwenty.toggles.performToggle( subMenuToggle, true );
358					}
359				} );
360			}
361		} );
362	},
363
364	keepFocusInModal: function() {
365		var _doc = document;
366
367		_doc.addEventListener( 'keydown', function( event ) {
368			var toggleTarget, modal, selectors, elements, menuType, bottomMenu, activeEl, lastEl, firstEl, tabKey, shiftKey,
369				clickedEl = twentytwenty.toggles.clickedEl;
370
371			if ( clickedEl && _doc.body.classList.contains( 'showing-modal' ) ) {
372				toggleTarget = clickedEl.dataset.toggleTarget;
373				selectors = 'input, a, button';
374				modal = _doc.querySelector( toggleTarget );
375
376				elements = modal.querySelectorAll( selectors );
377				elements = Array.prototype.slice.call( elements );
378
379				if ( '.menu-modal' === toggleTarget ) {
380					menuType = window.matchMedia( '(min-width: 1000px)' ).matches;
381					menuType = menuType ? '.expanded-menu' : '.mobile-menu';
382
383					elements = elements.filter( function( element ) {
384						return null !== element.closest( menuType ) && null !== element.offsetParent;
385					} );
386
387					elements.unshift( _doc.querySelector( '.close-nav-toggle' ) );
388
389					bottomMenu = _doc.querySelector( '.menu-bottom > nav' );
390
391					if ( bottomMenu ) {
392						bottomMenu.querySelectorAll( selectors ).forEach( function( element ) {
393							elements.push( element );
394						} );
395					}
396				}
397
398				lastEl = elements[ elements.length - 1 ];
399				firstEl = elements[0];
400				activeEl = _doc.activeElement;
401				tabKey = event.keyCode === 9;
402				shiftKey = event.shiftKey;
403
404				if ( ! shiftKey && tabKey && lastEl === activeEl ) {
405					event.preventDefault();
406					firstEl.focus();
407				}
408
409				if ( shiftKey && tabKey && firstEl === activeEl ) {
410					event.preventDefault();
411					lastEl.focus();
412				}
413			}
414		} );
415	}
416}; // twentytwenty.modalMenu
417
418/*	-----------------------------------------------------------------------------------------------
419	Primary Menu
420--------------------------------------------------------------------------------------------------- */
421
422twentytwenty.primaryMenu = {
423
424	init: function() {
425		this.focusMenuWithChildren();
426	},
427
428	// The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu
429	// by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element.
430	focusMenuWithChildren: function() {
431		// Get all the link elements within the primary menu.
432		var links, i, len,
433			menu = document.querySelector( '.primary-menu-wrapper' );
434
435		if ( ! menu ) {
436			return false;
437		}
438
439		links = menu.getElementsByTagName( 'a' );
440
441		// Each time a menu link is focused, update focus.
442		for ( i = 0, len = links.length; i < len; i++ ) {
443			links[i].addEventListener( 'focus', updateFocus, true );
444		}
445
446		menu.addEventListener( 'focusout', removeFocus, true );
447
448		// Remove focus classes from menu.
449		function removeFocus(e){
450			const leavingMenu = ! menu.contains( e.relatedTarget );
451
452			if ( leavingMenu ) {
453				// Remove focus from all li elements of primary-menu.
454				menu.querySelectorAll( 'li' ).forEach( function( el ) {
455					if ( el.classList.contains( 'focus' ) ) {
456						el.classList.remove( 'focus', 'closed' );
457					}
458				});
459			}
460		}
461
462		// Update focus class on an element.
463		function updateFocus() {
464			var self = this;
465
466			// Remove focus from all li elements of primary-menu.
467			menu.querySelectorAll( 'li' ).forEach( function( el ){
468				if ( el.classList.contains( 'closed' ) ) {
469					el.classList.remove( 'closed' );
470				}
471				if ( el.classList.contains( 'focus' ) ) {
472					el.classList.remove( 'focus' );
473				}
474			});
475			
476			// Set focus on current `a` element's parent `li`.
477			self.parentElement.classList.add( 'focus' );
478			// If current element is inside sub-menu find main parent li and add focus.
479			if ( self.closest( '.menu-item-has-children' ) ) {
480				twentytwentyFindParents( self, 'li.menu-item-has-children' ).forEach( function( element ) {
481					element.classList.add( 'focus' );
482				});
483			}
484		}
485
486		// When the `esc` key is pressed while in menu, move focus up one level.
487		menu.addEventListener( 'keydown', removeFocusEsc, true );
488
489		// Remove focus when `esc` key pressed.
490		function removeFocusEsc( e ) {
491			e = e || window.event;
492			var isEscape = false,
493				focusedElement = e.target;
494
495			// Find if pressed key is `esc`.
496			if ( 'key' in e ) {
497				isEscape = ( e.key === 'Escape' || e.key === 'Esc' );
498			} else {
499				isEscape = ( e.keyCode === 27 );
500			}
501
502			// If pressed key is esc, remove focus class from parent menu li.
503			if ( isEscape ) {
504				var parentLi = focusedElement.closest( 'li' ),
505					nestedParent = closestExcludingSelf( parentLi, 'li.menu-item-has-children' ),
506					focusPosition = nestedParent ? nestedParent.querySelector('a') : false;
507
508					if ( null !== nestedParent ) {
509						nestedParent.classList.add( 'focus' );
510						focusPosition.focus();
511					} else {
512						parentLi.classList.remove( 'focus' );
513						parentLi.classList.add( 'closed' );
514					}
515			}
516		}
517
518		function closestExcludingSelf(element, selector) {
519			if ( ! element || ! selector ) {
520				return null;
521			}
522			const parent = element.parentElement;
523
524			return parent ? parent.closest(selector) : null;
525		}
526	}
527}; // twentytwenty.primaryMenu
528
529/*	-----------------------------------------------------------------------------------------------
530	Toggles
531--------------------------------------------------------------------------------------------------- */
532
533twentytwenty.toggles = {
534
535	clickedEl: false,
536
537	init: function() {
538		// Do the toggle.
539		this.toggle();
540
541		// Check for toggle/untoggle on resize.
542		this.resizeCheck();
543
544		// Check for untoggle on escape key press.
545		this.untoggleOnEscapeKeyPress();
546	},
547
548	performToggle: function( element, instantly ) {
549		var target, timeOutTime, classToToggle,
550			self = this,
551			_doc = document,
552			// Get our targets.
553			toggle = element,
554			targetString = toggle.dataset.toggleTarget,
555			activeClass = 'active';
556
557		// Elements to focus after modals are closed.
558		if ( ! _doc.querySelectorAll( '.show-modal' ).length ) {
559			self.clickedEl = _doc.activeElement;
560		}
561
562		if ( targetString === 'next' ) {
563			target = toggle.nextSibling;
564		} else {
565			target = _doc.querySelector( targetString );
566		}
567
568		// Trigger events on the toggle targets before they are toggled.
569		if ( target.classList.contains( activeClass ) ) {
570			target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-before-active' ) );
571		} else {
572			target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-before-inactive' ) );
573		}
574
575		// Get the class to toggle, if specified.
576		classToToggle = toggle.dataset.classToToggle ? toggle.dataset.classToToggle : activeClass;
577
578		// For cover modals, set a short timeout duration so the class animations have time to play out.
579		timeOutTime = 0;
580
581		if ( target.classList.contains( 'cover-modal' ) ) {
582			timeOutTime = 10;
583		}
584
585		setTimeout( function() {
586			var focusElement,
587				subMenued = target.classList.contains( 'sub-menu' ),
588				newTarget = subMenued ? toggle.closest( '.menu-item' ).querySelector( '.sub-menu' ) : target,
589				duration = toggle.dataset.toggleDuration;
590
591			// Toggle the target of the clicked toggle.
592			if ( toggle.dataset.toggleType === 'slidetoggle' && ! instantly && duration !== '0' ) {
593				twentytwentyMenuToggle( newTarget, duration );
594			} else {
595				newTarget.classList.toggle( classToToggle );
596			}
597
598			// If the toggle target is 'next', only give the clicked toggle the active class.
599			if ( targetString === 'next' ) {
600				toggle.classList.toggle( activeClass );
601			} else if ( target.classList.contains( 'sub-menu' ) ) {
602				toggle.classList.toggle( activeClass );
603			} else {
604				// If not, toggle all toggles with this toggle target.
605				_doc.querySelector( '*[data-toggle-target="' + targetString + '"]' ).classList.toggle( activeClass );
606			}
607
608			// Toggle aria-expanded on the toggle.
609			twentytwentyToggleAttribute( toggle, 'aria-expanded', 'true', 'false' );
610
611			if ( self.clickedEl && -1 !== toggle.getAttribute( 'class' ).indexOf( 'close-' ) ) {
612				twentytwentyToggleAttribute( self.clickedEl, 'aria-expanded', 'true', 'false' );
613			}
614
615			// Toggle body class.
616			if ( toggle.dataset.toggleBodyClass ) {
617				_doc.body.classList.toggle( toggle.dataset.toggleBodyClass );
618			}
619
620			// Check whether to set focus.
621			if ( toggle.dataset.setFocus ) {
622				focusElement = _doc.querySelector( toggle.dataset.setFocus );
623
624				if ( focusElement ) {
625					if ( target.classList.contains( activeClass ) ) {
626						focusElement.focus();
627					} else {
628						focusElement.blur();
629					}
630				}
631			}
632
633			// Trigger the toggled event on the toggle target.
634			target.dispatchEvent( twentytwenty.createEvent( 'toggled' ) );
635
636			// Trigger events on the toggle targets after they are toggled.
637			if ( target.classList.contains( activeClass ) ) {
638				target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-after-active' ) );
639			} else {
640				target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-after-inactive' ) );
641			}
642		}, timeOutTime );
643	},
644
645	// Do the toggle.
646	toggle: function() {
647		var self = this;
648
649		document.querySelectorAll( '*[data-toggle-target]' ).forEach( function( element ) {
650			element.addEventListener( 'click', function( event ) {
651				event.preventDefault();
652				self.performToggle( element );
653			} );
654		} );
655	},
656
657	// Check for toggle/untoggle on screen resize.
658	resizeCheck: function() {
659		if ( document.querySelectorAll( '*[data-untoggle-above], *[data-untoggle-below], *[data-toggle-above], *[data-toggle-below]' ).length ) {
660			window.addEventListener( 'resize', function() {
661				var winWidth = window.innerWidth,
662					toggles = document.querySelectorAll( '.toggle' );
663
664				toggles.forEach( function( toggle ) {
665					var unToggleAbove = toggle.dataset.untoggleAbove,
666						unToggleBelow = toggle.dataset.untoggleBelow,
667						toggleAbove = toggle.dataset.toggleAbove,
668						toggleBelow = toggle.dataset.toggleBelow;
669
670					// If no width comparison is set, continue.
671					if ( ! unToggleAbove && ! unToggleBelow && ! toggleAbove && ! toggleBelow ) {
672						return;
673					}
674
675					// If the toggle width comparison is true, toggle the toggle.
676					if (
677						( ( ( unToggleAbove && winWidth > unToggleAbove ) ||
678							( unToggleBelow && winWidth < unToggleBelow ) ) &&
679							toggle.classList.contains( 'active' ) ) ||
680						( ( ( toggleAbove && winWidth > toggleAbove ) ||
681							( toggleBelow && winWidth < toggleBelow ) ) &&
682							! toggle.classList.contains( 'active' ) )
683					) {
684						toggle.click();
685					}
686				} );
687			} );
688		}
689	},
690
691	// Close toggle on escape key press.
692	untoggleOnEscapeKeyPress: function() {
693		document.addEventListener( 'keyup', function( event ) {
694			if ( event.key === 'Escape' ) {
695				document.querySelectorAll( '*[data-untoggle-on-escape].active' ).forEach( function( element ) {
696					if ( element.classList.contains( 'active' ) ) {
697						element.click();
698					}
699				} );
700			}
701		} );
702	}
703
704}; // twentytwenty.toggles
705
706/**
707 * Is the DOM ready?
708 *
709 * This implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/
710 *
711 * @since Twenty Twenty 1.0
712 *
713 * @param {Function} fn Callback function to run.
714 */
715function twentytwentyDomReady( fn ) {
716	if ( typeof fn !== 'function' ) {
717		return;
718	}
719
720	if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
721		return fn();
722	}
723
724	document.addEventListener( 'DOMContentLoaded', fn, false );
725}
726
727twentytwentyDomReady( function() {
728	twentytwenty.toggles.init();              // Handle toggles.
729	twentytwenty.coverModals.init();          // Handle cover modals.
730	twentytwenty.intrinsicRatioVideos.init(); // Retain aspect ratio of videos on window resize.
731	twentytwenty.modalMenu.init();            // Modal Menu.
732	twentytwenty.primaryMenu.init();          // Primary Menu.
733	twentytwenty.touchEnabled.init();         // Add class to body if device is touch-enabled.
734} );
735
736/*	-----------------------------------------------------------------------------------------------
737	Helper functions
738--------------------------------------------------------------------------------------------------- */
739
740/* Toggle an attribute ----------------------- */
741
742function twentytwentyToggleAttribute( element, attribute, trueVal, falseVal ) {
743	var toggles;
744
745	if ( ! element.hasAttribute( attribute ) ) {
746		return;
747	}
748
749	if ( trueVal === undefined ) {
750		trueVal = true;
751	}
752	if ( falseVal === undefined ) {
753		falseVal = false;
754	}
755
756	/*
757	 * Take into account multiple toggle elements that need their state to be
758	 * synced. For example: the Search toggle buttons for desktop and mobile.
759	 */
760	toggles = document.querySelectorAll( '[data-toggle-target="' + element.dataset.toggleTarget + '"]' );
761
762	toggles.forEach( function( toggle ) {
763		if ( ! toggle.hasAttribute( attribute ) ) {
764			return;
765		}
766
767		if ( toggle.getAttribute( attribute ) !== trueVal ) {
768			toggle.setAttribute( attribute, trueVal );
769		} else {
770			toggle.setAttribute( attribute, falseVal );
771		}
772	} );
773}
774
775/**
776 * Toggle a menu item on or off.
777 *
778 * @since Twenty Twenty 1.0
779 *
780 * @param {HTMLElement} target
781 * @param {number} duration
782 */
783function twentytwentyMenuToggle( target, duration ) {
784	var initialParentHeight, finalParentHeight, menu, menuItems, transitionListener,
785		initialPositions = [],
786		finalPositions = [];
787
788	if ( ! target ) {
789		return;
790	}
791
792	menu = target.closest( '.menu-wrapper' );
793
794	// Step 1: look at the initial positions of every menu item.
795	menuItems = menu.querySelectorAll( '.menu-item' );
796
797	menuItems.forEach( function( menuItem, index ) {
798		initialPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
799	} );
800	initialParentHeight = target.parentElement.offsetHeight;
801
802	target.classList.add( 'toggling-target' );
803
804	// Step 2: toggle target menu item and look at the final positions of every menu item.
805	target.classList.toggle( 'active' );
806
807	menuItems.forEach( function( menuItem, index ) {
808		finalPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
809	} );
810	finalParentHeight = target.parentElement.offsetHeight;
811
812	// Step 3: close target menu item again.
813	// The whole process happens without giving the browser a chance to render, so it's invisible.
814	target.classList.toggle( 'active' );
815
816	/*
817	 * Step 4: prepare animation.
818	 * Position all the items with absolute offsets, at the same starting position.
819	 * Shouldn't result in any visual changes if done right.
820	 */
821	menu.classList.add( 'is-toggling' );
822	target.classList.toggle( 'active' );
823	menuItems.forEach( function( menuItem, index ) {
824		var initialPosition = initialPositions[ index ];
825		if ( initialPosition.y === 0 && menuItem.parentElement === target ) {
826			initialPosition.y = initialParentHeight;
827		}
828		menuItem.style.transform = 'translate(' + initialPosition.x + 'px, ' + initialPosition.y + 'px)';
829	} );
830
831	/*
832	 * The double rAF is unfortunately needed, since we're toggling CSS classes, and
833	 * the only way to ensure layout completion here across browsers is to wait twice.
834	 * This just delays the start of the animation by 2 frames and is thus not an issue.
835	 */
836	requestAnimationFrame( function() {
837		requestAnimationFrame( function() {
838			/*
839			 * Step 5: start animation by moving everything to final position.
840			 * All the layout work has already happened, while we were preparing for the animation.
841			 * The animation now runs entirely in CSS, using cheap CSS properties (opacity and transform)
842			 * that don't trigger the layout or paint stages.
843			 */
844			menu.classList.add( 'is-animating' );
845			menuItems.forEach( function( menuItem, index ) {
846				var finalPosition = finalPositions[ index ];
847				if ( finalPosition.y === 0 && menuItem.parentElement === target ) {
848					finalPosition.y = finalParentHeight;
849				}
850				if ( duration !== undefined ) {
851					menuItem.style.transitionDuration = duration + 'ms';
852				}
853				menuItem.style.transform = 'translate(' + finalPosition.x + 'px, ' + finalPosition.y + 'px)';
854			} );
855			if ( duration !== undefined ) {
856				target.style.transitionDuration = duration + 'ms';
857			}
858		} );
859
860		// Step 6: finish toggling.
861		// Remove all transient classes when the animation ends.
862		transitionListener = function() {
863			menu.classList.remove( 'is-animating' );
864			menu.classList.remove( 'is-toggling' );
865			target.classList.remove( 'toggling-target' );
866			menuItems.forEach( function( menuItem ) {
867				menuItem.style.transform = '';
868				menuItem.style.transitionDuration = '';
869			} );
870			target.style.transitionDuration = '';
871			target.removeEventListener( 'transitionend', transitionListener );
872		};
873
874		target.addEventListener( 'transitionend', transitionListener );
875	} );
876}
877
878/**
879 * Traverses the DOM up to find elements matching the query.
880 *
881 * @since Twenty Twenty 1.0
882 *
883 * @param {HTMLElement} target
884 * @param {string} query
885 * @return {NodeList} parents matching query
886 */
887function twentytwentyFindParents( target, query ) {
888	var parents = [];
889
890	// Recursively go up the DOM adding matches to the parents array.
891	function traverse( item ) {
892		var parent = item.parentNode;
893		if ( parent instanceof HTMLElement ) {
894			if ( parent.matches( query ) ) {
895				parents.push( parent );
896			}
897			traverse( parent );
898		}
899	}
900
901	traverse( target );
902
903	return parents;
904}

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.