https://www.balharbourshops.com/wp-content/plugins/bhs-algolia-search/assets/bhs-search.js?ver=1.1.6
1/** 2 * BHS Algolia Search â header opener + overlay controller. 3 * 4 * Vanilla JS, no jQuery. Everything is null-checked so a missing element 5 * (theme change, plugin disabled, autocomplete not initialised) degrades 6 * to the plain fallback form or to no-op. 7 */ 8(function (window, document) { 9 'use strict'; 10 11 if (!document || !document.querySelector) { 12 return; 13 } 14 15 var CONFIG = window.bhsSearchConfig || {}; 16 var SEARCH_URL = typeof CONFIG.searchUrl === 'string' && CONFIG.searchUrl ? CONFIG.searchUrl : '/'; 17 var QUERY_VAR = typeof CONFIG.queryVar === 'string' && CONFIG.queryVar ? CONFIG.queryVar : 's'; 18 var OPEN_CLASS = 'is-open'; 19 var HTML_CLASS = 'bhs-search-open'; 20 var FALLBACK_CLASS = 'bhs-search--fallback'; 21 var TRANSITION_MS = 260; 22 23 var state = { 24 open: false, 25 lastFocus: null, 26 scrollY: 0, 27 closeTimer: null, 28 fallbackTimer: null, 29 bound: false 30 }; 31 32 /* ------------------------------------------------------------------ */ 33 /* Helpers */ 34 /* ------------------------------------------------------------------ */ 35 36 function qs(selector, context) { 37 try { 38 return (context || document).querySelector(selector); 39 } catch (e) { 40 return null; 41 } 42 } 43 44 function getOverlay() { 45 return document.getElementById('bhs-search'); 46 } 47 48 function getAutocompleteInput() { 49 return qs('#bhs-autocomplete .aa-Input'); 50 } 51 52 function getFallbackForm() { 53 return qs('#bhs-search .bhs-search__fallback'); 54 } 55 56 function getFallbackInput() { 57 return qs('#bhs-search .bhs-search__fallback-input'); 58 } 59 60 function getActiveInput() { 61 return getAutocompleteInput() || getFallbackInput(); 62 } 63 64 function resultsUrl(query) { 65 var joiner = SEARCH_URL.indexOf('?') === -1 ? '?' : '&'; 66 return SEARCH_URL + joiner + QUERY_VAR + '=' + encodeURIComponent(query || ''); 67 } 68 69 function goToResults(query) { 70 var q = (query || '').replace(/^\s+|\s+$/g, ''); 71 if (!q) { 72 return; 73 } 74 window.location.assign(resultsUrl(q)); 75 } 76 77 function dispatch(name, detail) { 78 try { 79 document.dispatchEvent(new CustomEvent(name, { detail: detail || {} })); 80 } catch (e) { 81 /* older browsers: ignore */ 82 } 83 } 84 85 /* ------------------------------------------------------------------ */ 86 /* Opener placement */ 87 /* ------------------------------------------------------------------ */ 88 89 function placeOpener() { 90 var tpl = document.getElementById('bhs-search-opener-tpl'); 91 if (!tpl) { 92 return; 93 } 94 var li = qs('li.bhs-search-li', tpl); 95 if (!li) { 96 return; 97 } 98 99 // Already placed (e.g. script ran twice). 100 if (qs('ul.header-links li.bhs-search-li')) { 101 if (tpl.parentNode) { 102 tpl.parentNode.removeChild(tpl); 103 } 104 return; 105 } 106 107 var headerLinks = qs('#header ul.header-links') || qs('ul.header-links'); 108 109 if (!headerLinks) { 110 var host = qs('#header .header-top .container') || qs('#header .header-top') || qs('#header .container') || qs('#header'); 111 if (!host) { 112 return; 113 } 114 headerLinks = document.createElement('ul'); 115 headerLinks.className = 'header-links bhs-header-links--generated'; 116 host.insertBefore(headerLinks, host.firstChild); 117 } 118 119 var cartLink = qs('.wps-btn-cart', headerLinks); 120 var cartLi = null; 121 if (cartLink) { 122 var node = cartLink; 123 while (node && node !== headerLinks) { 124 if (node.tagName && node.tagName.toLowerCase() === 'li') { 125 cartLi = node; 126 break; 127 } 128 node = node.parentNode; 129 } 130 } 131 132 if (cartLi && cartLi.parentNode === headerLinks) { 133 headerLinks.insertBefore(li, cartLi); 134 } else { 135 headerLinks.appendChild(li); 136 } 137 138 if (tpl.parentNode) { 139 tpl.parentNode.removeChild(tpl); 140 } 141 } 142 143 /* ------------------------------------------------------------------ */ 144 /* Fallback detection */ 145 /* ------------------------------------------------------------------ */ 146 147 function updateFallback() { 148 var overlay = getOverlay(); 149 var form = getFallbackForm(); 150 if (!overlay || !form) { 151 return; 152 } 153 var hasAutocomplete = !!getAutocompleteInput(); 154 if (hasAutocomplete) { 155 form.hidden = true; 156 form.setAttribute('hidden', ''); 157 overlay.classList.remove(FALLBACK_CLASS); 158 } else { 159 form.hidden = false;
160 form.removeAttribute('hidden'); 161 overlay.classList.add(FALLBACK_CLASS); 162 } 163 } 164 165 function scheduleFallbackCheck() { 166 if (state.fallbackTimer) { 167 window.clearTimeout(state.fallbackTimer); 168 } 169 state.fallbackTimer = window.setTimeout(updateFallback, 1500); 170 } 171 172 /* ------------------------------------------------------------------ */ 173 /* Scroll lock */ 174 /* ------------------------------------------------------------------ */ 175 176 function lockScroll() { 177 var html = document.documentElement; 178 var body = document.body; 179 if (!html || !body) { 180 return; 181 } 182 state.scrollY = window.pageYOffset || html.scrollTop || body.scrollTop || 0; 183 html.classList.add(HTML_CLASS); 184 body.style.position = 'fixed'; 185 body.style.top = '-' + state.scrollY + 'px'; 186 body.style.left = '0'; 187 body.style.right = '0'; 188 body.style.width = '100%'; 189 } 190 191 function unlockScroll() { 192 var html = document.documentElement; 193 var body = document.body; 194 if (!html || !body) { 195 return; 196 } 197 html.classList.remove(HTML_CLASS); 198 body.style.position = ''; 199 body.style.top = ''; 200 body.style.left = ''; 201 body.style.right = ''; 202 body.style.width = ''; 203 window.scrollTo(0, state.scrollY || 0); 204 } 205 206 /* ------------------------------------------------------------------ */ 207 /* Open / close */ 208 /* ------------------------------------------------------------------ */ 209 210 function focusInput() { 211 var input = getActiveInput(); 212 if (!input) { 213 return; 214 } 215 try { 216 input.focus({ preventScroll: true }); 217 } catch (e) { 218 input.focus(); 219 } 220 try { 221 var len = input.value ? input.value.length : 0; 222 if (len && typeof input.setSelectionRange === 'function') { 223 input.setSelectionRange(0, len); 224 } 225 } catch (e) { 226 /* type=search on some browsers throws; ignore */ 227 } 228 } 229 230 function open(trigger) { 231 var overlay = getOverlay(); 232 if (!overlay || state.open) { 233 return; 234 } 235 if (state.closeTimer) { 236 window.clearTimeout(state.closeTimer); 237 state.closeTimer = null; 238 } 239 240 state.open = true; 241 state.lastFocus = trigger && trigger.focus ? trigger : document.activeElement; 242 243 updateFallback(); 244 lockScroll(); 245 246 overlay.hidden = false; 247 overlay.removeAttribute('hidden'); 248 overlay.setAttribute('aria-hidden', 'false'); 249 250 // Focus immediately (works while opacity is still 0), then again once shown. 251 focusInput(); 252 253 // Add the open class on the next frame so the opacity transition runs; 254 // the timeout covers throttled requestAnimationFrame (hidden tabs). 255 var shown = false; 256 var show = function () { 257 if (shown || !state.open) { 258 return; 259 } 260 shown = true; 261 overlay.classList.add(OPEN_CLASS); 262 focusInput(); 263 }; 264 var raf = window.requestAnimationFrame || function (cb) { return window.setTimeout(cb, 16); }; 265 raf(function () { 266 raf(show); 267 }); 268 window.setTimeout(show, 60); 269 270 var openers = document.querySelectorAll('.bhs-search-opener'); 271 for (var i = 0; i < openers.length; i++) { 272 openers[i].setAttribute('aria-expanded', 'true'); 273 } 274 275 dispatch('bhs-search:open'); 276 } 277 278 function close() { 279 var overlay = getOverlay(); 280 if (!overlay || !state.open) { 281 return; 282 } 283 state.open = false; 284 285 // Close the autocomplete panel if the instance exposes setIsOpen. 286 var ac = window.bhsAutocomplete; 287 if (ac && typeof ac.setIsOpen === 'function') { 288 try { 289 ac.setIsOpen(false); 290 } catch (e) { 291 /* ignore */ 292 } 293 } 294 295 overlay.classList.remove(OPEN_CLASS); 296 overlay.setAttribute('aria-hidden', 'true'); 297 298 var openers = document.querySelectorAll('.bhs-search-opener'); 299 for (var i = 0; i < openers.length; i++) { 300 openers[i].setAttribute('aria-expanded', 'false'); 301 } 302 303 state.closeTimer = window.setTimeout(function () { 304 state.closeTimer = null; 305 if (!state.open) { 306 overlay.hidden = true; 307 overlay.setAttribute('hidden', ''); 308 } 309 }, TRANSITION_MS); 310 311 unlockScroll(); 312 313 var target = state.lastFocus; 314 state.lastFocus = null; 315 if (target && typeof target.focus === 'function' && document.contains(target)) { 316 try {
317 target.focus({ preventScroll: true }); 318 } catch (e) { 319 target.focus(); 320 } 321 } 322 323 dispatch('bhs-search:close'); 324 } 325 326 function toggle(trigger) { 327 if (state.open) { 328 close(); 329 } else { 330 open(trigger); 331 } 332 } 333 334 /* ------------------------------------------------------------------ */ 335 /* Events */ 336 /* ------------------------------------------------------------------ */ 337 338 function closest(el, selector) { 339 while (el && el.nodeType === 1) { 340 if (el.matches && el.matches(selector)) { 341 return el; 342 } 343 el = el.parentNode; 344 } 345 return null; 346 } 347 348 function onDocumentClick(event) { 349 var target = event.target; 350 if (!target) { 351 return; 352 } 353 354 var opener = closest(target, '.bhs-search-opener, [data-bhs-search-open], a[href="#bhs-search"], a[href="#search"]'); 355 if (opener) { 356 event.preventDefault(); 357 toggle(opener); 358 return; 359 } 360 361 if (!state.open) { 362 return; 363 } 364 365 var closer = closest(target, '[data-bhs-search-close], .bhs-search__close'); 366 if (closer) { 367 event.preventDefault(); 368 close(); 369 } 370 } 371 372 function onDocumentKeydown(event) { 373 if (!state.open) { 374 return; 375 } 376 var key = event.key || event.keyCode; 377 378 if (key === 'Escape' || key === 'Esc' || key === 27) { 379 event.preventDefault(); 380 close(); 381 return; 382 } 383 384 if (key === 'Enter' || key === 13) { 385 var input = getAutocompleteInput(); 386 if (!input || event.target !== input) { 387 return; 388 } 389 // An item is highlighted: autocomplete-js navigates to it itself. 390 if (input.getAttribute('aria-activedescendant')) { 391 return; 392 } 393 var q = (input.value || '').replace(/^\s+|\s+$/g, ''); 394 if (!q) { 395 return; 396 } 397 event.preventDefault(); 398 goToResults(q); 399 } 400 } 401 402 function onFallbackSubmit(event) { 403 var form = getFallbackForm(); 404 if (!form || event.target !== form) { 405 return; 406 } 407 var input = getFallbackInput(); 408 var q = input ? (input.value || '').replace(/^\s+|\s+$/g, '') : ''; 409 if (!q) { 410 event.preventDefault(); 411 if (input) { 412 input.focus(); 413 } 414 } 415 // Otherwise let the native GET to /?s= proceed. 416 } 417 418 function onAutocompleteReady() { 419 updateFallback(); 420 if (state.open) { 421 focusInput(); 422 } 423 } 424 425 function onHashOpen() { 426 if (window.location.hash === '#search' || window.location.hash === '#bhs-search') { 427 open(null); 428 } 429 } 430 431 function bind() { 432 if (state.bound) { 433 return; 434 } 435 state.bound = true; 436 437 document.addEventListener('click', onDocumentClick, false); 438 document.addEventListener('keydown', onDocumentKeydown, false); 439 document.addEventListener('submit', onFallbackSubmit, false); 440 document.addEventListener('bhs-search:autocomplete-ready', onAutocompleteReady, false); 441 document.addEventListener('bhs-search:request-open', function () { open(null); }, false); 442 document.addEventListener('bhs-search:request-close', close, false); 443 window.addEventListener('hashchange', onHashOpen, false); 444 window.addEventListener('load', scheduleFallbackCheck, false); 445 } 446 447 /* ------------------------------------------------------------------ */ 448 /* Init */ 449 /* ------------------------------------------------------------------ */ 450 451 function init() { 452 placeOpener(); 453 bind(); 454 onHashOpen(); 455 if (document.readyState === 'complete') { 456 scheduleFallbackCheck(); 457 } 458 } 459 460 461 /* ------------------------------------------------------------------ */ 462 /* Broken thumbnails: swap in the placeholder styling */ 463 /* ------------------------------------------------------------------ */ 464 465 function onImageError(event) { 466 var img = event && event.target; 467 if (!img || img.tagName !== 'IMG') { return; } 468 var scope = img.closest ? img.closest('#bhs-search, #algolia-hits, .bhs-results') : null; 469 if (!scope) { return; } 470 img.classList.add('is-broken'); 471 var box = img.closest('.bhs-aa-item__media, .bhs-card__img'); 472 if (box) { box.classList.add('is-broken'); } 473 } 474 document.addEventListener('error', onImageError, true); 475 476 // Public API for the theme or other scripts. 477 window.bhsSearch = { 478 open: function () { open(null); }, 479 close: close, 480 toggle: function () { toggle(null); }, 481 isOpen: function () { return state.open; }, 482 placeOpener: placeOpener 483 }; 484 485 if (document.readyState === 'loading') { 486 document.addEventListener('DOMContentLoaded', init, false); 487 } else { 488 init(); 489 } 490})(window, document);
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.