1/** Shopify CDN: Minification failed 2 3Line 40:0 Transforming const to the configured target environment ("es5") is not supported yet 4Line 42:45 Transforming default arguments to the configured target environment ("es5") is not supported yet 5Line 102:2 Transforming const to the configured target environment ("es5") is not supported yet 6Line 103:2 Transforming let to the configured target environment ("es5") is not supported yet 7Line 104:2 Transforming let to the configured target environment ("es5") is not supported yet 8Line 140:40 Transforming default arguments to the configured target environment ("es5") is not supported yet 9Line 151:2 Transforming const to the configured target environment ("es5") is not supported yet 10Line 154:2 Transforming const to the configured target environment ("es5") is not supported yet 11Line 160:0 Transforming class syntax to the configured target environment ("es5") is not supported yet 12Line 161:13 Transforming object literal extensions to the configured target environment ("es5") is not supported yet 13... and 157 more hidden warnings 14 15**/ 16function getFocusableElements(container) { 17 return Array.from( 18 container.querySelectorAll( 19 "summary, a[href], button:enabled, [tabindex]:not([tabindex^='-']), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe" 20 ) 21 ); 22} 23 24document.querySelectorAll('[id^="Details-"] summary').forEach((summary) => { 25 summary.setAttribute('role', 'button'); 26 summary.setAttribute('aria-expanded', summary.parentNode.hasAttribute('open')); 27 28 if(summary.nextElementSibling.getAttribute('id')) { 29 summary.setAttribute('aria-controls', summary.nextElementSibling.id); 30 } 31 32 summary.addEventListener('click', (event) => { 33 event.currentTarget.setAttribute('aria-expanded', !event.currentTarget.closest('details').hasAttribute('open')); 34 }); 35 36 if (summary.closest('header-drawer')) return; 37 summary.parentElement.addEventListener('keyup', onKeyUpEscape); 38}); 39 40const trapFocusHandlers = {}; 41 42function trapFocus(container, elementToFocus = container) { 43 var elements = getFocusableElements(container); 44 var first = elements[0]; 45 var last = elements[elements.length - 1]; 46 47 removeTrapFocus(); 48 49 trapFocusHandlers.focusin = (event) => { 50 if ( 51 event.target !== container && 52 event.target !== last && 53 event.target !== first 54 ) 55 return; 56 57 document.addEventListener('keydown', trapFocusHandlers.keydown); 58 }; 59 60 trapFocusHandlers.focusout = function() { 61 document.removeEventListener('keydown', trapFocusHandlers.keydown); 62 }; 63 64 trapFocusHandlers.keydown = function(event) { 65 if (event.code.toUpperCase() !== 'TAB') return; // If not TAB key 66 // On the last focusable element and tab forward, focus the first element. 67 if (event.target === last && !event.shiftKey) { 68 event.preventDefault(); 69 first.focus(); 70 } 71 72 // On the first focusable element and tab backward, focus the last element. 73 if ( 74 (event.target === container || event.target === first) && 75 event.shiftKey 76 ) { 77 event.preventDefault(); 78 last.focus(); 79 } 80 }; 81 82 document.addEventListener('focusout', trapFocusHandlers.focusout); 83 document.addEventListener('focusin', trapFocusHandlers.focusin); 84 85 elementToFocus.focus(); 86 87 if (elementToFocus.tagName === 'INPUT' && 88 ['search', 'text', 'email', 'url'].includes(elementToFocus.type) && 89 elementToFocus.value) { 90 elementToFocus.setSelectionRange(0, elementToFocus.value.length); 91 } 92} 93 94// Here run the querySelector to figure out if the browser supports :focus-visible or not and run code based on it. 95try { 96 document.querySelector(":focus-visible"); 97} catch(e) { 98 focusVisiblePolyfill(); 99} 100 101function focusVisiblePolyfill() { 102 const navKeys = ['ARROWUP', 'ARROWDOWN', 'ARROWLEFT', 'ARROWRIGHT', 'TAB', 'ENTER', 'SPACE', 'ESCAPE', 'HOME', 'END', 'PAGEUP', 'PAGEDOWN'] 103 let currentFocusedElement = null; 104 let mouseClick = null; 105 106 window.addEventListener('keydown', (event) => { 107 if(navKeys.includes(event.code.toUpperCase())) { 108 mouseClick = false;
109 } 110 }); 111 112 window.addEventListener('mousedown', (event) => { 113 mouseClick = true; 114 }); 115 116 window.addEventListener('focus', () => { 117 if (currentFocusedElement) currentFocusedElement.classList.remove('focused'); 118 119 if (mouseClick) return; 120 121 currentFocusedElement = document.activeElement; 122 currentFocusedElement.classList.add('focused'); 123 124 }, true); 125} 126 127function pauseAllMedia() { 128 document.querySelectorAll('.js-youtube').forEach((video) => { 129 video.contentWindow.postMessage('{"event":"command","func":"' + 'pauseVideo' + '","args":""}', '*'); 130 }); 131 document.querySelectorAll('.js-vimeo').forEach((video) => { 132 video.contentWindow.postMessage('{"method":"pause"}', '*'); 133 }); 134 document.querySelectorAll('video').forEach((video) => video.pause()); 135 document.querySelectorAll('product-model').forEach((model) => { 136 if (model.modelViewerUI) model.modelViewerUI.pause(); 137 }); 138} 139 140function removeTrapFocus(elementToFocus = null) { 141 document.removeEventListener('focusin', trapFocusHandlers.focusin); 142 document.removeEventListener('focusout', trapFocusHandlers.focusout); 143 document.removeEventListener('keydown', trapFocusHandlers.keydown); 144 145 if (elementToFocus) elementToFocus.focus(); 146} 147 148function onKeyUpEscape(event) { 149 if (event.code.toUpperCase() !== 'ESCAPE') return; 150 151 const openDetailsElement = event.target.closest('details[open]'); 152 if (!openDetailsElement) return; 153 154 const summaryElement = openDetailsElement.querySelector('summary'); 155 openDetailsElement.removeAttribute('open'); 156 summaryElement.setAttribute('aria-expanded', false); 157 summaryElement.focus(); 158} 159 160class QuantityInput extends HTMLElement { 161 constructor() { 162 super(); 163 this.input = this.querySelector('input'); 164 this.changeEvent = new Event('change', { bubbles: true }); 165 166 this.input.addEventListener('change', this.onInputChange.bind(this)); 167 this.querySelectorAll('button').forEach( 168 (button) => button.addEventListener('click', this.onButtonClick.bind(this)) 169 ); 170 } 171 172 quantityUpdateUnsubscriber = undefined; 173 174 connectedCallback() { 175 this.validateQtyRules(); 176 this.quantityUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.quantityUpdate, this.validateQtyRules.bind(this)); 177 } 178 179 disconnectedCallback() { 180 if (this.quantityUpdateUnsubscriber) { 181 this.quantityUpdateUnsubscriber(); 182 } 183 } 184 185 onInputChange(event) { 186 this.validateQtyRules(); 187 } 188 189 onButtonClick(event) { 190 event.preventDefault(); 191 const previousValue = this.input.value; 192 193 event.target.name === 'plus' ? this.input.stepUp() : this.input.stepDown(); 194 if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent); 195 } 196 197 validateQtyRules() { 198 const value = parseInt(this.input.value); 199 if (this.input.min) { 200 const min = parseInt(this.input.min); 201 const buttonMinus = this.querySelector(".quantity__button[name='minus']"); 202 buttonMinus.classList.toggle('disabled', value <= min); 203 } 204 if (this.input.max) { 205 const max = parseInt(this.input.max); 206 const buttonPlus = this.querySelector(".quantity__button[name='plus']"); 207 buttonPlus.classList.toggle('disabled', value >= max); 208 } 209 } 210} 211 212customElements.define('quantity-input', QuantityInput); 213 214function debounce(fn, wait) { 215 let t; 216 return (...args) => { 217 clearTimeout(t); 218 t = setTimeout(() => fn.apply(this, args), wait); 219 }; 220} 221 222function fetchConfig(type = 'json') { 223 return { 224 method: 'POST', 225 headers: { 'Content-Type': 'application/json', 'Accept': `application/${type}` } 226 }; 227} 228 229/* 230 * Shopify Common JS 231 * 232 */ 233if ((typeof window.Shopify) == 'undefined') { 234 window.Shopify = {}; 235} 236 237Shopify.bind = function(fn, scope) { 238 return function() { 239 return fn.apply(scope, arguments); 240 } 241}; 242 243Shopify.setSelectorByValue = function(selector, value) { 244 for (var i = 0, count = selector.options.length; i < count; i++) { 245 var option = selector.options[i]; 246 if (value == option.value || value == option.innerHTML) { 247 selector.selectedIndex = i; 248 return i; 249 } 250 } 251}; 252 253Shopify.addListener = function(target, eventName, callback) { 254 target.addEventListener ? target.addEventListener(eventName, callback, false) : target.attachEvent('on'+eventName, callback); 255}; 256 257Shopify.postLink = function(path, options) { 258 options = options || {}; 259 var method = options['method'] || 'post'; 260 var params = options['parameters'] || {}; 261 262 var form = document.createElement("form"); 263 form.setAttribute("method", method); 264 form.setAttribute("action", path); 265 266 for(var key in params) { 267 var hiddenField = document.createElement("input"); 268 hiddenField.setAttribute("type", "hidden"); 269 hiddenField.setAttribute("name", key); 270 hiddenField.setAttribute("value", params[key]); 271 form.appendChild(hiddenField); 272 } 273 document.body.appendChild(form); 274 form.submit(); 275 document.body.removeChild(form); 276}; 277 278Shopify.CountryProvinceSelector = function(country_domid, province_domid, options) { 279 this.countryEl = document.getElementById(country_domid); 280 this.provinceEl = document.getElementById(province_domid); 281 this.provinceContainer = document.getElementById(options['hideElement'] || province_domid); 282 283 Shopify.addListener(this.countryEl, 'change', Shopify.bind(this.countryHandler,this)); 284 285 this.initCountry(); 286 this.initProvince(); 287}; 288 289Shopify.CountryProvinceSelector.prototype = { 290 initCountry: function() { 291 var value = this.countryEl.getAttribute('data-default'); 292 Shopify.setSelectorByValue(this.countryEl, value); 293 this.countryHandler(); 294 }, 295 296 initProvince: function() { 297 var value = this.provinceEl.getAttribute('data-default'); 298 if (value && this.provinceEl.options.length > 0) { 299 Shopify.setSelectorByValue(this.provinceEl, value); 300 } 301 }, 302 303 countryHandler: function(e) { 304 var opt = this.countryEl.options[this.countryEl.selectedIndex]; 305 var raw = opt.getAttribute('data-provinces'); 306 var provinces = JSON.parse(raw); 307 308 this.clearOptions(this.provinceEl); 309 if (provinces && provinces.length == 0) { 310 this.provinceContainer.style.display = 'none'; 311 } else { 312 for (var i = 0; i < provinces.length; i++) { 313 var opt = document.createElement('option'); 314 opt.value = provinces[i][0]; 315 opt.innerHTML = provinces[i][1]; 316 this.provinceEl.appendChild(opt); 317 } 318 319 this.provinceContainer.style.display = ""; 320 } 321 }, 322 323 clearOptions: function(selector) { 324 while (selector.firstChild) { 325 selector.removeChild(selector.firstChild); 326 } 327 }, 328 329 setOptions: function(selector, values) { 330 for (var i = 0, count = values.length; i < values.length; i++) { 331 var opt = document.createElement('option'); 332 opt.value = values[i]; 333 opt.innerHTML = values[i]; 334 selector.appendChild(opt); 335 } 336 } 337}; 338 339class MenuDrawer extends HTMLElement { 340 constructor() { 341 super(); 342 343 this.mainDetailsToggle = this.querySelector('details'); 344 345 this.addEventListener('keyup', this.onKeyUp.bind(this)); 346 this.addEventListener('focusout', this.onFocusOut.bind(this)); 347 this.bindEvents(); 348 } 349 350 bindEvents() { 351 this.querySelectorAll('summary').forEach(summary => summary.addEventListener('click', this.onSummaryClick.bind(this))); 352 this.querySelectorAll('button').forEach(button => button.addEventListener('click', this.onCloseButtonClick.bind(this))); 353 } 354 355 onKeyUp(event) { 356 if(event.code.toUpperCase() !== 'ESCAPE') return; 357 358 const openDetailsElement = event.target.closest('details[open]'); 359 if(!openDetailsElement) return; 360 361 openDetailsElement === this.mainDetailsToggle ? this.closeMenuDrawer(event, this.mainDetailsToggle.querySelector('summary')) : this.closeSubmenu(openDetailsElement); 362 } 363 364 onSummaryClick(event) { 365 const summaryElement = event.currentTarget; 366 const detailsElement = summaryElement.parentNode; 367 const parentMenuElement = detailsElement.closest('.has-submenu'); 368 const isOpen = detailsElement.hasAttribute('open'); 369 const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); 370 371 function addTrapFocus() { 372 trapFocus(summaryElement.nextElementSibling, detailsElement.querySelector('button')); 373 summaryElement.nextElementSibling.removeEventListener('transitionend', addTrapFocus); 374 } 375 376 if (detailsElement === this.mainDetailsToggle) { 377 if(isOpen) event.preventDefault(); 378 isOpen ? this.closeMenuDrawer(event, summaryElement) : this.openMenuDrawer(summaryElement); 379 380 if (window.matchMedia('(max-width: 990px)')) { 381 document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`); 382 } 383 } else { 384 setTimeout(() => { 385 detailsElement.classList.add('menu-opening'); 386 summaryElement.setAttribute('aria-expanded', true); 387 parentMenuElement && parentMenuElement.classList.add('submenu-open'); 388 !reducedMotion || reducedMotion.matches ? addTrapFocus() : summaryElement.nextElementSibling.addEventListener('transitionend', addTrapFocus); 389 }, 100); 390 } 391 } 392 393 openMenuDrawer(summaryElement) { 394 setTimeout(() => { 395 this.mainDetailsToggle.classList.add('menu-opening'); 396 }); 397 summaryElement.setAttribute('aria-expanded', true); 398 trapFocus(this.mainDetailsToggle, summaryElement); 399 document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`); 400 } 401 402 closeMenuDrawer(event, elementToFocus = false) { 403 if (event === undefined) return; 404 405 this.mainDetailsToggle.classList.remove('menu-opening'); 406 this.mainDetailsToggle.querySelectorAll('details').forEach(detail
406s => { 407 details.removeAttribute('open'); 408 details.classList.remove('menu-opening'); 409 }); 410 this.mainDetailsToggle.querySelectorAll('.submenu-open').forEach(submenu => { 411 submenu.classList.remove('submenu-open'); 412 }); 413 document.body.classList.remove(`overflow-hidden-${this.dataset.breakpoint}`); 414 removeTrapFocus(elementToFocus); 415 this.closeAnimation(this.mainDetailsToggle); 416 } 417 418 onFocusOut(event) { 419 setTimeout(() => { 420 if (this.mainDetailsToggle.hasAttribute('open') && !this.mainDetailsToggle.contains(document.activeElement)) this.closeMenuDrawer(); 421 }); 422 } 423 424 onCloseButtonClick(event) { 425 const detailsElement = event.currentTarget.closest('details'); 426 this.closeSubmenu(detailsElement); 427 } 428 429 closeSubmenu(detailsElement) { 430 const parentMenuElement = detailsElement.closest('.submenu-open'); 431 parentMenuElement && parentMenuElement.classList.remove('submenu-open'); 432 detailsElement.classList.remove('menu-opening'); 433 detailsElement.querySelector('summary').setAttribute('aria-expanded', false);
434 removeTrapFocus(detailsElement.querySelector('summary')); 435 this.closeAnimation(detailsElement); 436 } 437 438 closeAnimation(detailsElement) { 439 let animationStart; 440 441 const handleAnimation = (time) => { 442 if (animationStart === undefined) { 443 animationStart = time; 444 } 445 446 const elapsedTime = time - animationStart; 447 448 if (elapsedTime < 400) { 449 window.requestAnimationFrame(handleAnimation); 450 } else { 451 detailsElement.removeAttribute('open'); 452 if (detailsElement.closest('details[open]')) { 453 trapFocus(detailsElement.closest('details[open]'), detailsElement.querySelector('summary')); 454 } 455 } 456 } 457 458 window.requestAnimationFrame(handleAnimation); 459 } 460} 461 462customElements.define('menu-drawer', MenuDrawer); 463 464class HeaderDrawer extends MenuDrawer { 465 constructor() { 466 super(); 467 } 468 469 openMenuDrawer(summaryElement) { 470 this.header = this.header || document.querySelector('.section-header'); 471 this.borderOffset = this.borderOffset || this.closest('.header-wrapper').classList.contains('header-wrapper--border-bottom') ? 1 : 0; 472 document.documentElement.style.setProperty('--header-bottom-position', `${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px`); 473 this.header.classList.add('menu-open'); 474 475 setTimeout(() => { 476 this.mainDetailsToggle.classList.add('menu-opening'); 477 }); 478 479 summaryElement.setAttribute('aria-expanded', true); 480 trapFocus(this.mainDetailsToggle, summaryElement); 481 document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`); 482 } 483 484 closeMenuDrawer(event, elementToFocus) { 485 super.closeMenuDrawer(event, elementToFocus); 486 this.header.classList.remove('menu-open'); 487 } 488} 489 490customElements.define('header-drawer', HeaderDrawer); 491 492class ModalDialog extends HTMLElement { 493 constructor() { 494 super(); 495 this.querySelector('[id^="ModalClose-"]').addEventListener( 496 'click', 497 this.hide.bind(this, false) 498 ); 499 this.addEventListener('keyup', (event) => { 500 if (event.code.toUpperCase() === 'ESCAPE') this.hide(); 501 }); 502 if (this.classList.contains('media-modal')) { 503 this.addEventListener('pointerup', (event) => { 504 if (event.pointerType === 'mouse' && !event.target.closest('deferred-media, product-model')) this.hide(); 505 }); 506 } else { 507 this.addEventListener('click', (event) => { 508 if (event.target === this) this.hide(); 509 }); 510 } 511 } 512 513 connectedCallback() { 514 if (this.moved) return; 515 this.moved = true; 516 document.body.appendChild(this); 517 } 518 519 show(opener) { 520 this.openedBy = opener; 521 const popup = this.querySelector('.template-popup'); 522 document.body.classList.add('overflow-hidden'); 523 this.setAttribute('open', ''); 524 if (popup) popup.loadContent(); 525 trapFocus(this, this.querySelector('[role="dialog"]')); 526 window.pauseAllMedia(); 527 } 528 529 hide() { 530 document.body.classList.remove('overflow-hidden'); 531 document.body.dispatchEvent(new CustomEvent('modalClosed')); 532 this.removeAttribute('open'); 533 removeTrapFocus(this.openedBy); 534 window.pauseAllMedia(); 535 } 536} 537customElements.define('modal-dialog', ModalDialog); 538 539class ModalOpener extends HTMLElement { 540 constructor() { 541 super(); 542 543 const button = this.querySelector('button'); 544 545 if (!button) return; 546 button.addEventListener('click', () => { 547 const modal = document.querySelector(this.getAttribute('data-modal')); 548 if (modal) modal.show(button); 549 }); 550 } 551} 552customElements.define('modal-opener', ModalOpener); 553 554class DeferredMedia extends HTMLElement { 555 constructor() { 556 super(); 557 const poster = this.querySelector('[id^="Deferred-Poster-"]'); 558 if (!poster) return; 559 poster.addEventListener('click', this.loadContent.bind(this)); 560 } 561 562 loadContent(focus = true) { 563 window.pauseAllMedia(); 564 if (!this.getAttribute('loaded')) { 565 const content = document.createElement('div'); 566 content.appendChild(this.querySelector('template').content.firstElementChild.cloneNode(true)); 567 568 this.setAttribute('loaded', true); 569 const deferredElement = this.appendChild(content.querySelector('video, model-viewer, iframe')); 570 if (focus) deferredElement.focus(); 571 } 572 } 573} 574 575customElements.define('deferred-media', DeferredMedia); 576 577class SliderComponent extends HTMLElement { 578 constructor() { 579 super(); 580 this.slider = this.querySelector('[id^="Slider-"]'); 581 this.sliderItems = this.querySelectorAll('[id^="Slide-"]'); 582 this.enableSliderLooping = false;
583 this.currentPageElement = this.querySelector('.slider-counter--current'); 584 this.pageTotalElement = this.querySelector('.slider-counter--total'); 585 this.prevButton = this.querySelector('button[name="previous"]'); 586 this.nextButton = this.querySelector('button[name="next"]'); 587 588 if (!this.slider || !this.nextButton) return; 589 590 this.initPages(); 591 const resizeObserver = new ResizeObserver(entries => this.initPages()); 592 resizeObserver.observe(this.slider); 593 594 this.slider.addEventListener('scroll', this.update.bind(this)); 595 this.prevButton.addEventListener('click', this.onButtonClick.bind(this)); 596 this.nextButton.addEventListener('click', this.onButtonClick.bind(this)); 597 } 598 599 initPages() { 600 this.sliderItemsToShow = Array.from(this.sliderItems).filter(element => element.clientWidth > 0); 601 if (this.sliderItemsToShow.length < 2) return; 602 this.sliderItemOffset = this.sliderItemsToShow[1].offsetLeft - this.sliderItemsToShow[0].offsetLeft; 603 this.slidesPerPage = Math.floor((this.slider.clientWidth - this.sliderItemsToShow[0].offsetLeft) / this.sliderItemOffset); 604 this.totalPages = this.sliderItemsToShow.length - this.slidesPerPage + 1; 605 this.update(); 606 } 607 608 resetPages() { 609 this.sliderItems = this.querySelectorAll('[id^="Slide-"]'); 610 this.initPages(); 611 } 612 613 update() { 614 // Temporarily prevents unneeded updates resulting from variant changes 615 // This should be refactored as part of https://github.com/Shopify/dawn/issues/2057 616 if (!this.slider || !this.nextButton) return; 617 618 const previousPage = this.currentPage; 619 this.currentPage = Math.round(this.slider.scrollLeft / this.sliderItemOffset) + 1; 620 621 if (this.currentPageElement && this.pageTotalElement) { 622 this.currentPageElement.textContent = this.currentPage; 623 this.pageTotalElement.textContent = this.totalPages; 624 } 625 626 if (this.currentPage != previousPage) { 627 this.dispatchEvent(new CustomEvent('slideChanged', { detail: { 628 currentPage: this.currentPage, 629 currentElement: this.sliderItemsToShow[this.currentPage - 1] 630 }})); 631 } 632 633 if (this.enableSliderLooping) return; 634 635 if (this.isSlideVisible(this.sliderItemsToShow[0]) && this.slider.scrollLeft === 0) { 636 this.prevButton.setAttribute('disabled', 'disabled'); 637 } else { 638 this.prevButton.removeAttribute('disabled'); 639 } 640 641 if (this.isSlideVisible(this.sliderItemsToShow[this.sliderItemsToShow.length - 1])) { 642 this.nextButton.setAttribute('disabled', 'disabled'); 643 } else { 644 this.nextButton.removeAttribute('disabled'); 645 } 646 } 647 648 isSlideVisible(element, offset = 0) { 649 const lastVisibleSlide = this.slider.clientWidth + this.slider.scrollLeft - offset; 650 return (element.offsetLeft + element.clientWidth) <= lastVisibleSlide && element.offsetLeft >= this.slider.scrollLeft; 651 } 652 653 onButtonClick(event) { 654 event.preventDefault(); 655 const step = event.currentTarget.dataset.step || 1; 656 this.slideScrollPosition = event.currentTarget.name === 'next' ? this.slider.scrollLeft + (step * this.sliderItemOffset) : this.slider.scrollLeft - (step * this.sliderItemOffset); 657 this.slider.scrollTo({ 658 left: this.slideScrollPosition 659 }); 660 } 661} 662 663customElements.define('slider-component', SliderComponent); 664 665class SlideshowComponent extends SliderComponent { 666 constructor() { 667 super(); 668 this.sliderControlWrapper = this.querySelector('.slider-buttons'); 669 this.enableSliderLooping = true; 670 671 if (!this.sliderControlWrapper) return; 672 673 this.sliderFirstItemNode = this.slider.querySelector('.slideshow__slide'); 674 if (this.sliderItemsToShow.length > 0) this.currentPage = 1; 675 676 this.sliderControlLinksArray = Array.from(this.sliderControlWrapper.querySelectorAll('.slider-counter__link')); 677 this.sliderControlLinksArray.forEach(link => link.addEventListener('click', this.linkToSlide.bind(this))); 678 this.slider.addEventListener('scroll', this.setSlideVisibility.bind(this)); 679 this.setSlideVisibility(); 680 681 if (this.slider.getAttribute('data-autoplay') === 'true') this.setAutoPlay(); 682 } 683 684 setAutoPlay() { 685 this.sliderAutoplayButton = this.querySelector('.slideshow__autoplay'); 686 this.autoplaySpeed = this.slider.dataset.speed * 1000; 687 688 this.sliderAutoplayButton.addEventListener('click', this.autoPlayToggle.bind(this)); 689 this.addEventListener('mouseover', this.focusInHandling.bind(this)); 690 this.addEventListener('mouseleave', this.focusOutHandling.bind(this)); 691 this.addEventListener('focusin', this.focusInHandling.bind(this)); 692 this.addEventListener('focusout', this.focusOutHandling.bind(this)); 693 694 this.play(); 695 this.autoplayButtonIsSetToPlay = true; 696 } 697 698 onButtonClick(event) { 699 super.onButtonClick(event); 700 const isFirstSlide = this.currentPage === 1; 701 const isLastSlide = this.currentPage === this.sliderItemsToShow.length; 702 703 if (!isFirstSlide && !isLastSlide) return; 704 705 if (isFirstSlide && event.currentTarget.name === 'previous') { 706 this.slideScrollPosition = this.slider.scrollLeft + this.sliderFirstItemNode.clientWidth * this.sliderItemsToShow.length; 707 } else if (isLastSlide && event.currentTarget.name === 'next') { 708 this.slideScrollPosition = 0; 709 } 710 this.slider.scrollTo({ 711 left: this.slideScrollPosition 712 }); 713 } 714 715 update() {
716 super.update(); 717 this.sliderControlButtons = this.querySelectorAll('.slider-counter__link'); 718 this.prevButton.removeAttribute('disabled'); 719 720 if (!this.sliderControlButtons.length) return; 721 722 this.sliderControlButtons.forEach(link => { 723 link.classList.remove('slider-counter__link--active'); 724 link.removeAttribute('aria-current'); 725 }); 726 this.sliderControlButtons[this.currentPage - 1].classList.add('slider-counter__link--active'); 727 this.sliderControlButtons[this.currentPage - 1].setAttribute('aria-current', true); 728 } 729 730 autoPlayToggle() { 731 this.togglePlayButtonState(this.autoplayButtonIsSetToPlay); 732 this.autoplayButtonIsSetToPlay ? this.pause() : this.play(); 733 this.autoplayButtonIsSetToPlay = !this.autoplayButtonIsSetToPlay; 734 } 735 736 focusOutHandling(event) { 737 const focusedOnAutoplayButton = event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target); 738 if (!this.autoplayButtonIsSetToPlay || focusedOnAutoplayButton) return; 739 this.play(); 740 } 741 742 focusInHandling(event) { 743 const focusedOnAutoplayButton = event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target); 744 if (focusedOnAutoplayButton && this.autoplayButtonIsSetToPlay) { 745 this.play(); 746 } else if (this.autoplayButtonIsSetToPlay) { 747 this.pause(); 748 } 749 } 750 751 play() { 752 this.slider.setAttribute('aria-live', 'off'); 753 clearInterval(this.autoplay); 754 this.autoplay = setInterval(this.autoRotateSlides.bind(this), this.autoplaySpeed); 755 } 756 757 pause() { 758 this.slider.setAttribute('aria-live', 'polite'); 759 clearInterval(this.autoplay); 760 } 761 762 togglePlayButtonState(pauseAutoplay) { 763 if (pauseAutoplay) { 764 this.sliderAutoplayButton.classList.add('slideshow__autoplay--paused'); 765 this.sliderAutoplayButton.setAttribute('aria-label', window.accessibilityStrings.playSlideshow); 766 } else { 767 this.sliderAutoplayButton.classList.remove('slideshow__autoplay--paused'); 768 this.sliderAutoplayButton.setAttribute('aria-label', window.accessibilityStrings.pauseSlideshow); 769 } 770 } 771 772 autoRotateSlides() { 773 const slideScrollPosition = this.currentPage === this.sliderItems.length ? 0 : this.slider.scrollLeft + this.slider.querySelector('.slideshow__slide').clientWidth; 774 this.slider.scrollTo({ 775 left: slideScrollPosition 776 }); 777 } 778 779 setSlideVisibility() { 780 this.sliderItemsToShow.forEach((item, index) => { 781 const linkElements = item.querySelectorAll('a'); 782 if (index === this.currentPage - 1) { 783 if (linkElements.length) linkElements.forEach(button => { 784 button.removeAttribute('tabindex'); 785 }); 786 item.setAttribute('aria-hidden', 'false'); 787 item.removeAttribute('tabindex'); 788 } else { 789 if (linkElements.length) linkElements.forEach(button => { 790 button.setAttribute('tabindex', '-1'); 791 }); 792 item.setAttribute('aria-hidden', 'true'); 793 item.setAttribute('tabindex', '-1'); 794 } 795 }); 796 } 797 798 linkToSlide(event) { 799 event.preventDefault(); 800 const slideScrollPosition = this.slider.scrollLeft + this.sliderFirstItemNode.clientWidth * (this.sliderControlLinksArray.indexOf(event.currentTarget) + 1 - this.currentPage); 801 this.slider.scrollTo({ 802 left: slideScrollPosition 803 }); 804 } 805} 806 807customElements.define('slideshow-component', SlideshowComponent); 808 809class VariantSelects extends HTMLElement { 810 constructor() { 811 super(); 812 this.addEventListener('change', this.onVariantChange); 813 } 814 815 onVariantChange() { 816 this.updateOptions(); 817 this.updateMasterId(); 818 this.toggleAddButton(true, '', false); 819 this.updatePickupAvailability(); 820 this.removeErrorMessage(); 821 this.updateVariantStatuses(); 822 823 if (!this.currentVariant) { 824 this.toggleAddButton(true, '', true); 825 this.setUnavailable(); 826 } else { 827 this.updateMedia(); 828 this.updateURL(); 829 this.updateVariantInput(); 830 this.renderProductInfo(); 831 this.updateShareUrl(); 832 } 833 } 834 835 updateOptions() { 836 this.options = Array.from(this.querySelectorAll('select'), (select) => select.value); 837 } 838 839 updateMasterId() { 840 this.currentVariant = this.getVariantData().find((variant) => { 841 return !variant.options.map((option, index) => { 842 return this.options[index] === option; 843 }).includes(false); 844 }); 845 } 846 847 updateMedia() { 848 if (!this.currentVariant) return; 849 if (!this.currentVariant.featured_media) return; 850 851 const mediaGalleries = document.querySelectorAll(`[id^="MediaGallery-${this.dataset.section}"]`);
852 mediaGalleries.forEach(mediaGallery => mediaGallery.setActiveMedia(`${this.dataset.section}-${this.currentVariant.featured_media.id}`, true)); 853 854 const modalContent = document.querySelector(`#ProductModal-${this.dataset.section} .product-media-modal__content`); 855 if (!modalContent) return; 856 const newMediaModal = modalContent.querySelector( `[data-media-id="${this.currentVariant.featured_media.id}"]`); 857 modalContent.prepend(newMediaModal); 858 } 859 860 updateURL() { 861 if (!this.currentVariant || this.dataset.updateUrl === 'false') return; 862 window.history.propertyIsEnumerable({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`); 863 } 864 865 updateShareUrl() { 866 const shareButton = document.getElementById(`Share-${this.dataset.section}`); 867 if (!shareButton || !shareButton.updateUrl) return; 868 shareButton.updateUrl(`${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}`); 869 } 870 871 updateVariantInput() { 872 const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}`); 873 productForms.forEach((productForm) => { 874 const input = productForm.querySelector('input[name="id"]'); 875 input.value = this.currentVariant.id; 876 input.dispatchEvent(new Event('change', { bubbles: true })); 877 }); 878 } 879 880 updateVariantStatuses() { 881 const selectedOptionOneVariants = this.variantData.filter(variant => this.querySelector(':checked').value === variant.option1); 882 const inputWrappers = [...this.querySelectorAll('.product-form__input')]; 883 inputWrappers.forEach((option, index) => { 884 if (index === 0) return; 885 const optionInputs = [...option.querySelectorAll('input[type="radio"], option')] 886 const previousOptionSelected = inputWrappers[index - 1].querySelector(':checked').value; 887 const availableOptionInputsValue = selectedOptionOneVariants.filter(variant => variant.available && variant[`option${ index }`] === previousOptionSelected).map(variantOption => variantOption[`option${ index + 1 }`]); 888 this.setInputAvailability(optionInputs, availableOptionInputsValue) 889 }); 890 } 891 892 setInputAvailability(listOfOptions, listOfAvailableOptions) { 893 listOfOptions.forEach(input => { 894 if (listOfAvailableOptions.includes(input.getAttribute('value'))) { 895 input.innerText = input.getAttribute('value'); 896 } else { 897 input.innerText = window.variantStrings.unavailable_with_option.replace('[value]', input.getAttribute('value')); 898 } 899 }); 900 } 901 902 updatePickupAvailability() { 903 const pickUpAvailability = document.querySelector('pickup-availability'); 904 if (!pickUpAvailability) return; 905 906 if (this.currentVariant && this.currentVariant.available) { 907 pickUpAvailability.fetchAvailability(this.currentVariant.id); 908 } else { 909 pickUpAvailability.removeAttribute('available'); 910 pickUpAvailability.innerHTML = ''; 911 } 912 } 913 914 removeErrorMessage() { 915 const section = this.closest('section'); 916 if (!section) return; 917 918 const productForm = section.querySelector('product-form'); 919 if (productForm) productForm.handleErrorMessage(); 920 } 921 922 renderProductInfo() { 923 const requestedVariantId = this.currentVariant.id; 924 const sectionId = this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section; 925 926 fetch(`${this.dataset.url}?variant=${requestedVariantId}§ion_id=${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`) 927 .then((response) => response.text()) 928 .then((responseText) => { 929 // prevent unnecessary ui changes from abandoned selections 930 if (this.currentVariant.id !== requestedVariantId) return; 931 932 const html = new DOMParser().parseFromString(responseText, 'text/html') 933 const destination = document.getElementById(`price-${this.dataset.section}`); 934 const source = html.getElementById(`price-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`); 935 const skuSource = html.getElementById(`Sku-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`); 936 const skuDestination = document.getElementById(`Sku-${this.dataset.section}`); 937 const inventorySource = html.getElementById(`Inventory-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`); 938 const inventoryDestination = document.getElementById(`Inventory-${this.dataset.section}`); 939 940 if (source && destination) destination.innerHTML = source.innerHTML; 941 if (inventorySource && inventoryDestination) inventoryDestination.innerHTML = inventorySource.innerHTML; 942 if (skuSource && skuDestination) { 943 skuDestination.innerHTML = skuSource.innerHTML; 944 skuDestination.classList.toggle('visibility-hidden', skuSource.classList.contains('visibility-hidden')); 945 } 946 947 const price = document.getElementById(`price-${this.dataset.section}`); 948 949 if (price) price.classList.remove('visibility-hidden'); 950 951 if (inventoryDestination) inventoryDestination.classList.toggle('visibility-hidden', inventorySource.innerText === ''); 952 953 const addButtonUpdated = html.getElementById(`ProductSubmitButton-${sectionId}`); 954 this.toggleAddButton(addButtonUpdated ? addButtonUpdated.hasAttribute('disabled') : true, window.variantStrings.soldOut); 955 956 publish(PUB_SUB_EVENTS.variantChange, {data: { 957 sectionId, 958 html, 959 variant: this.currentVariant 960 }}); 961 }); 962 } 963 964 toggleAddButton(disable = true, text, modifyClass = true) { 965 const productForm = document.getElementById(`product-form-${this.dataset.section}`); 966 if (!productForm) return; 967 const addButton = productForm.querySelector('[name="add"]'); 968 const addButtonText = productForm.querySelector('[name="add"] > span'); 969 if (!addButton) return; 970 971 if (disable) { 972 addButton.setAttribute('disabled', 'disabled'); 973 if (text) addButtonText.textContent = text; 974 } else { 975 addButton.removeAttribute('disabled'); 976 addButtonText.textContent = window.variantStrings.addToCart; 977 } 978 979 if (!modifyClass) return; 980 } 981 982 setUnavailable() { 983 const button = document.getElementById(`product-form-${this.dataset.section}`); 984 const addButton = button.querySelector('[name="add"]'); 985 const addButtonText = button.querySelector('[name="add"] > span'); 986 const price = document.getElementById(`price-${this.dataset.section}`); 987 const inventory = document.getElementById(`Inventory-${this.dataset.section}`); 988 const sku = document.getElementById(`Sku-${this.dataset.section}`); 989 990 if (!addButton) return; 991 addButtonText.textContent = window.variantStrings.unavailable; 992 if (price) price.classList.add('visibility-hidden'); 993 if (inventory) inventory.classList.add('visibility-hidden');
994 if (sku) sku.classList.add('visibility-hidden'); 995 } 996 997 getVariantData() { 998 this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent); 999 return this.variantData; 1000 } 1001} 1002 1003customElements.define('variant-selects', VariantSelects); 1004 1005class VariantRadios extends VariantSelects { 1006 constructor() { 1007 super(); 1008 } 1009 1010 setInputAvailability(listOfOptions, listOfAvailableOptions) { 1011 listOfOptions.forEach(input => { 1012 if (listOfAvailableOptions.includes(input.getAttribute('value'))) { 1013 input.classList.remove('disabled'); 1014 } else { 1015 input.classList.add('disabled'); 1016 } 1017 }); 1018 } 1019 1020 updateOptions() { 1021 const fieldsets = Array.from(this.querySelectorAll('fieldset')); 1022 this.options = fieldsets.map((fieldset) => { 1023 return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value; 1024 }); 1025 } 1026} 1027 1028customElements.define('variant-radios', VariantRadios); 1029 1030class ProductRecommendations extends HTMLElement { 1031 constructor() { 1032 super(); 1033 } 1034 1035 connectedCallback() { 1036 const handleIntersection = (entries, observer) => { 1037 if (!entries[0].isIntersecting) return; 1038 observer.unobserve(this); 1039 1040 fetch(this.dataset.url) 1041 .then(response => response.text()) 1042 .then(text => { 1043 const html = document.createElement('div'); 1044 html.innerHTML = text; 1045 const recommendations = html.querySelector('product-recommendations'); 1046 1047 if (recommendations && recommendations.innerHTML.trim().length) { 1048 this.innerHTML = recommendations.innerHTML; 1049 } 1050 1051 if (!this.querySelector('slideshow-component') && this.classList.contains('complementary-products')) { 1052 this.remove(); 1053 } 1054 1055 if (html.querySelector('.grid__item')) { 1056 this.classList.add('product-recommendations--loaded'); 1057 } 1058 }) 1059 .catch(e => { 1060 console.error(e); 1061 }); 1062 } 1063 1064 new IntersectionObserver(handleIntersection.bind(this), {rootMargin: '0px 0px 400px 0px'}).observe(this); 1065 } 1066} 1067 1068customElements.define('product-recommendations', ProductRecommendations);
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.