1/** 2 * Travelify theme behaviour. 3 * 4 * Plain DOM APIs, no jQuery. Loaded in the footer. 5 */ 6( function () { 7 'use strict'; 8 9 function prefersReducedMotion() { 10 return window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; 11 } 12 13 /** 14 * Back to top. 15 * 16 * Fades in past 1000px, as the jQuery version did, and scrolls to the top 17 * of the document when activated. 18 */ 19 function initBackToTop() { 20 var widget = document.querySelector( '.back-to-top' ); 21 22 if ( ! widget ) { 23 return; 24 } 25 26 var link = widget.querySelector( 'a' ); 27 28 widget.classList.add( 'back-to-top--ready' ); 29 30 var visible = false; 31 var ticking = false; 32 33 function update() { 34 var shouldShow = ( window.pageYOffset || document.documentElement.scrollTop ) > 1000; 35 36 if ( shouldShow !== visible ) { 37 visible = shouldShow; 38 widget.classList.toggle( 'is-visible', visible ); 39 } 40 41 ticking = false; 42 } 43 44 window.addEventListener( 'scroll', function () { 45 if ( ! ticking ) { 46 window.requestAnimationFrame( update ); 47 ticking = true; 48 } 49 }, { passive: true } ); 50 51 update(); 52 53 if ( ! link ) { 54 return; 55 } 56 57 link.addEventListener( 'click', function ( event ) { 58 event.preventDefault(); 59 60 window.scrollTo( { 61 top: 0, 62 behavior: prefersReducedMotion() ? 'auto' : 'smooth' 63 } ); 64 65 // Send focus somewhere sensible rather than leaving it at the bottom. 66 var target = document.getElementById( 'branding' ); 67 68 if ( target ) { 69 target.setAttribute( 'tabindex', '-1' ); 70 target.focus( { preventScroll: true } ); 71 } 72 } ); 73 } 74 75 /** 76 * Mobile navigation. 77 * 78 * Replaces the <select> that TinyNav built below 768px. TinyNav shipped 79 * bundled inside the old functions.min.js -- the standalone tinynav.js in 80 * library/js was dead code, which is easy to misread as the menu being 81 * absent. It worked; it was just a <select> with no sub-menu structure and 82 * no accessible semantics. 83 */ 84 function initMobileMenu() { 85 var nav = document.getElementById( 'main-nav' ); 86 87 if ( ! nav ) { 88 return; 89 } 90 91 var menu = nav.querySelector( 'ul' ); 92 93 if ( ! menu ) { 94 return; 95 } 96 97 var strings = window.travelifyScreenReaderText || {}; 98 99 var toggle = document.createElement( 'button' ); 100 toggle.className = 'menu-toggle'; 101 toggle.type = 'button'; 102 toggle.setAttribute( 'aria-expanded', 'false' ); 103 toggle.setAttribute( 'aria-controls', menu.id || 'travelify-primary-menu' ); 104 var label = document.createElement( 'span' ); 105 label.className = 'menu-toggle-text'; 106 label.textContent = strings.menu || 'Menu'; 107 108 var icon = document.createElement( 'span' ); 109 icon.className = 'menu-toggle-icon'; 110 icon.setAttribute( 'aria-hidden', 'true' ); 111 112 toggle.appendChild( icon ); 113 toggle.appendChild( label ); 114 115 if ( ! menu.id ) { 116 menu.id = 'travelify-primary-menu'; 117 } 118 119 menu.parentNode.insertBefore( toggle, menu ); 120 121 /* 122 * Whether the menu is on screen is decided here, not in the stylesheet. 123 * 124 * The CSS rules that hide the list and reveal it again on .is-open only 125 * exist in this version of style.css. Where that file is out of step -- 126 * a child theme carrying its own copy, a minify or CDN layer serving a 127 * cached one -- the class still toggles but nothing moves, and the 128 * button is dead. Setting display from here means the menu opens 129 * regardless of which stylesheet arrived. 130 * 131 * MOBILE_BREAKPOINT mirrors the max-width in style.css; keep them equal. 132 */ 133 var MOBILE_BREAKPOINT = 767; 134 135 function isMobile() { 136 return window.innerWidth <= MOBILE_BREAKPOINT; 137 } 138 139 function applyMenuState( open ) { 140 if ( isMobile() ) { 141 toggle.style.display = 'flex'; 142 menu.style.display = open ? 'block' : 'none'; 143 } else { 144 // Desktop: hand both back to the stylesheet. 145 toggle.style.display = 'none'; 146 menu.style.display = ''; 147 } 148 } 149 150 function setOpen( open ) { 151 nav.classList.toggle( 'is-open', open ); 152 toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); 153 applyMenuState( open ); 154 } 155 156 setOpen( false ); 157 158 toggle.addEventListener( 'click', function () { 159 setOpen( ! nav.classList.contains( 'is-open' ) ); 160 } ); 161 162 // Sub-menu toggles: hover cannot open a dropdown on a touch screen. 163 Array.prototype.forEach.call( menu.querySelectorAll( 'li' ), function ( item ) { 164 var submenu = item.querySelector( 'ul' ); 165 166 if ( ! submenu ) { 167 return; 168 } 169 170 item.classList.add( 'has-submenu' ); 171 172 var link = item.querySelector( 'a' ); 173 var button = document.createElement( 'button' ); 174 175 button.className = 'submenu-toggle'; 176 button.type = 'button'; 177 button.setAttribute( 'aria-expanded', 'false' ); 178 button.setAttribute( 179 'aria-label', 180 ( strings.expand || 'Open sub-menu of' ) + ' ' + ( link ? link.textContent.trim() : '' ) 181 ); 182 var chevron = document.createElement( 'span' ); 183 chevron.setAttribute( 'aria-hidden', 'true' ); 184 button.appendChild( chevron ); 185 186 button.addEventListener( 'click', function () { 187 var open = item.classList.toggle( 'submenu-open' ); 188 button.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); 189 190 // As above: do not depend on the stylesheet to reveal it. 191 if ( isMobile() ) { 192 submenu.style.display = open ? 'block' : 'none'; 193 } 194 button.setAttribute( 195 'aria-label', 196 ( open ? ( strings.collapse || 'Close sub-menu of' ) : ( strings.expand || 'Open sub-menu of' ) ) + ' ' + ( link ? link.textContent.trim() : '' ) 197 ); 198 } ); 199 200 if ( link ) { 201 link.parentNode.insertBefore( button, link.nextSibling ); 202 } else { 203 item.insertBefore( button, item.firstChild ); 204 } 205 } ); 206 207 // Escape closes whatever is open and returns focus to the toggle. 208 nav.addEventListener( 'keydown', function ( event ) { 209 if ( event.key !== 'Escape' ) { 210 return; 211 } 212
213 if ( nav.classList.contains( 'is-open' ) ) { 214 setOpen( false ); 215 toggle.focus(); 216 } 217 } ); 218 219 // Collapse when the layout goes back to the desktop menu. 220 if ( window.matchMedia ) { 221 var desktop = window.matchMedia( '(min-width: 768px)' ); 222 223 var reset = function () { 224 setOpen( false ); 225 226 Array.prototype.forEach.call( menu.querySelectorAll( '.has-submenu' ), function ( item ) { 227 item.classList.remove( 'submenu-open' ); 228 229 var button = item.querySelector( '.submenu-toggle' ); 230 var sub = item.querySelector( 'ul' ); 231 232 if ( button ) { 233 button.setAttribute( 'aria-expanded', 'false' ); 234 } 235 236 // Clear the inline display so the stylesheet governs again. 237 if ( sub ) { 238 sub.style.display = ''; 239 } 240 } ); 241 }; 242 243 if ( desktop.addEventListener ) { 244 desktop.addEventListener( 'change', reset ); 245 } else if ( desktop.addListener ) { 246 desktop.addListener( reset ); 247 } 248 249 window.addEventListener( 'resize', function () { 250 applyMenuState( nav.classList.contains( 'is-open' ) ); 251 } ); 252 } 253 } 254 255 /** 256 * Keyboard support for the desktop dropdowns. 257 * 258 * The stylesheet opens sub-menus on :focus-within, which covers tabbing. 259 * This adds the class as well so older engines behave the same way. 260 */ 261 function initDesktopMenuFocus() { 262 var nav = document.getElementById( 'main-nav' ); 263 264 if ( ! nav ) { 265 return; 266 } 267 268 Array.prototype.forEach.call( nav.querySelectorAll( 'a' ), function ( link ) { 269 link.addEventListener( 'focus', function () { 270 var item = link.parentNode; 271 272 while ( item && item !== nav ) { 273 if ( item.tagName === 'LI' ) { 274 item.classList.add( 'focus' ); 275 } 276 item = item.parentNode; 277 } 278 } ); 279 280 link.addEventListener( 'blur', function () { 281 var item = link.parentNode; 282 283 while ( item && item !== nav ) { 284 if ( item.tagName === 'LI' ) { 285 item.classList.remove( 'focus' ); 286 } 287 item = item.parentNode; 288 } 289 } ); 290 } ); 291 } 292 293 function init() { 294 initBackToTop(); 295 initMobileMenu(); 296 initDesktopMenuFocus(); 297 } 298 299 if ( document.readyState !== 'loading' ) { 300 init(); 301 } else { 302 document.addEventListener( 'DOMContentLoaded', init ); 303 } 304}() );
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.