1 20
if (!customElements.get("variant-selects")) { /** * @class * @function VariantSelects */ class VariantSelects extends HTMLElement { constructor() { super(); this.sticky = this.dataset.sticky; this.updateUrl = this.dataset.updateUrl === "true"; this.isDisabledFeature = this.dataset.isDisabled; this.addEventListener("change", this.onVariantChange); this.other = Array.from( document.querySelectorAll("variant-selects") ).filter((selector) => { return selector != this; }); this.productWrapper = this.closest(".thb-product-detail"); if (this.productWrapper) { this.productSlider = this.productWrapper.querySelector( `#MediaGallery-${this.dataset.section}` ); this.hideVariants = this.productSlider.dataset.hideVariants === "true"; } } connectedCallback() { if (this.updateUrl) { this.updateOptions(); this.updateMasterId(); this.setDisabled(); this.setImageSet(); this.toggleAddButton(!this.currentVariant?.available || false); } } onVariantChange() { this.updateOptions(); this.updateMasterId(); this.toggleAddButton(!this.currentVariant?.available || false); this.updatePickupAvailability(); this.removeErrorMessage(); this.updateVariantText(); this.setDisabled(); if (!this.currentVariant) { this.toggleAddButton(true); this.setUnavailable(); } else { this.updateMedia(); if (this.updateUrl) { this.updateURL(); } this.updateVariantInput(); this.renderProductInfo().then(() => { // Update Afterpay after product info is rendered if (window.Afterpay && this.currentVariant) { window.afterpay_current_variant = this.currentVariant; window.afterpay_cart_total_price = this.currentVariant.price; window.afterpay_product = { ...window.afterpay_product, selected_or_first_available_variant: this.currentVariant }; // Update square-placement attributes const placement = document.querySelector('square-placement'); if (placement) { placement.setAttribute('data-amount', this.currentVariant.price / 100); placement.setAttribute('data-item-skus', this.currentVariant.sku); // Force a refresh of the placement const event = new Event('change', { bubbles: true }); placement.dispatchEvent(event); } } }); // Call the function to update SKU this.updateSku(); } this.updateOther(); dispatchCustomEvent("product:variant-change", { variant: this.currentVariant, sectionId: this.dataset.section, }); } updateOptions() { this.fieldsets = Array.from(this.querySelectorAll("fieldset")); this.options = []; this.option_keys = []; this.fieldsets.forEach((fieldset, i) => { if (fieldset.querySelector("select")) { this.options.push(fieldset.querySelector("select").value); this.option_keys.push(fieldset.querySelector("select").name); } else if (fieldset.querySelectorAll("input").length) { this.options.push(fieldset.querySelector("input:checked").value); this.option_keys.push(fieldset.querySelector("input").name); } }); this.dataset.options = this.options; } updateVariantText() { const fieldsets = Array.from(this.querySelectorAll("fieldset")); fieldsets.forEach((item, i) => { let label = item.querySelector(".form__label__value"); if (label) { label.innerHTML = this.options[i]; } }); } updateSku() { const skuElement = document.querySelector(".product-page-sku"); if (skuElement && this.currentVariant) { skuElement.textContent = `REF: ${this.currentVariant.sku}`; } } updateMasterId() { this.currentVariant = this.getVariantData().find((variant) => { return !variant.options .map((option, index) => { return this.options[index] === option; }) .includes(false); }); } updateOther() { if (this.dataset.updateUrl === "false") { return; } if (this.other.length) { let fieldsets = this.other[0].querySelectorAll("fieldset"), fieldsets_array = Array.from(fieldsets); this.options.forEach((option, i) => { if (fieldsets_array[i].querySelector("select")) { fieldsets_array[i].querySelector(`select`).value = option; } else if (fieldsets_array[i].querySelectorAll("input").length) { fieldsets_array[i].querySelector( `input[value="${option}"]` ).checked = true; } }); this.other[0].updateOptions(); this.other[0].updateMasterId(); this.other[0].updateVariantText(); this.other[0].setDisabled(); } } updateMedia() { if (!this.currentVariant) return; if (!this.currentVariant.featured_media) return; if (!this.productSlider) return; let mediaId = `${this.dataset.section}-${this.currentVariant.featured_media.id}`; let activeMedia = this.productSlider.querySelector( `[data-media-id="${mediaId}"]` ); this.productSlider .querySelectorAll("[data-media-id]") .forEach((element) => { element.classList.remove("is-active"); }); this.setImageSetMedia(); activeMedia.classList.add("is-active"); activeMedia.parentElement.prepend(activeMedia); if (!this.sticky) { window.setTimeout(() => { if (window.innerWidth > 1068) { let header_h = parseInt( getComputedStyle(document.documentElement).getPropertyValue( "--header-height" ) ); // window.scrollTo({ // left: 0, // top: activeMedia.parentElement.offsetTop - header_h, // behavior: "instant", // }); this.productWrapper.querySelector(`#Product-Slider`).scrollTo({ left: 0, behavior: "instant", }); } }); } } updateURL() { if (!this.currentVariant || this.dataset.updateUrl === "false") return; window.history.replaceState( {}, "", `${this.dataset.url}?variant=${this.currentVariant.id}` ); } updateShareUrl() { const shareButton = document.getElementById( `Share-${this.dataset.section}` ); if (!shareButton) return; shareButton.updateUrl( `${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}` ); } updateVariantInput() { const productForms = document.querySelectorAll( `#product-form-${this.dataset.section}, #product-form-installment` ); productForms.forEach((productForm) => { const input = productForm.querySelector('input[name="id"]'); input.value = this.currentVariant.id; input.dispatchEvent( new Event("change", { bubbles: true, }) ); }); } updatePickupAvailability() { const pickUpAvailability = document.querySelector( ".pickup-availability-wrapper" ); if (!pickUpAvailability) return; if (this.currentVariant && this.currentVariant.available) { pickUpAvailability.fetchAvailability(this.currentVariant.id); } else { pickUpAvailability.removeAttribute("available"); pickUpAvailability.innerHTML = ""; } } removeErrorMessage() { const section = this.closest("section"); if (!section) return; const productForm = section.querySelector("product-form"); if (productForm) productForm.handleErrorMessage(); } getSectionsToRender() { return [ `product-title-${this.dataset.section}`, `price-${this.dataset.section}`, `price-${this.dataset.section}--sticky`, `product-image-${this.dataset.section}--sticky`, `inventory-${this.dataset.section}`, `sku-${this.dataset.section}`, `quantity-${this.dataset.section}`, `afterpay-${this.dataset.section}` ]; } renderProductInfo() { let sections = this.getSectionsToRender(); return fetch( `${this.dataset.url}?variant=${this.currentVariant.id}§ion_id=${this.dataset.section}` ) .then((response) => response.text()) .then((responseText) => { const html = new DOMParser().parseFromString(responseText, "text/html"); sections.forEach((id) => { const destination = document.getElementById(id); const source = html.getElementById(id); if (source && destination) { // Find and temporarily remove square-placement const squarePlacement = document.querySelector('square-placement'); let placementContainer = null; if (squarePlacement) { // Store the exact container that holds the placement placementContainer = squarePlacement.parentElement; squarePlacement.remove(); } // Update the section HTML destination.innerHTML = source.innerHTML; // If we had a placement and know where it was, put it back if (squarePlacement && placementContainer) { // Find the same container using its ID const newContainer = document.getElementById(placementContainer.id); if (newContainer) { newContainer.appendChild(squarePlacement); } else { // Fallback to the destination if we can't find the exact container destination.appendChild(squarePlacement); } } } const price = document.getElementById(id); const price_fixed = document.getElementById(id + "--sticky"); if (price) price.classList.remove("visibility-hidden"); if (price_fixed) price_fixed.classList.remove("visibility-hidden"); }); this.toggleAddButton( !this.currentVariant.available, window.theme.variantStrings.soldOut ); }); } getVariantStateMap(formWrapper) { try { return JSON.parse(formWrapper.dataset.variantPreorderMap || "{}"); } catch (error) { console.error("Invalid variant preorder map", error, formWrapper.dataset.variantPreorderMap); return {}; } } updateProductTitlePrefix(shouldPrefix) { const titleEls = document.querySelectorAll( `#product-title-${this.dataset.section} .product-title, .product-title` ); titleEls.forEach((titleEl) => { const rawTitle = titleEl.dataset.rawTitle || titleEl.dataset.baseTitle || titleEl.textContent.replace(/^(?:pre[\s-]*order:\s*)+/i, "").trim(); titleEl.dataset.baseTitle = rawTitle; titleEl.textContent = shouldPrefix ? `Pre-Order: ${rawTitle}` : rawTitle; }); } toggleAddButton(disable = true, text = false, modifyClass = true) { const productForm = document.getElementById( `product-form-${this.dataset.section}` ); if (!productForm) return; const formWrapper = productForm.querySelector('[data-type="add-to-cart-form"]'); if (!formWrapper) return; const submitButtons = document.querySelectorAll(".single-add-to-cart-button"); if (!submitButtons.length) return; const isInstoreOnly = formWrapper.dataset.instoreOnly === "true"; const isComingSoon = formWrapper.dataset.comingSoon === "true"; const variantStateMap = this.getVariantStateMap(formWrapper); const currentVariantId = String(this.currentVariant?.id || ""); const currentState = variantStateMap[currentVariantId] || {}; const hasPreorder = currentState.hasPreorder === true; const isOutOfStock = currentState.isOutOfStock === true; const canSellOOS = currentState.canSellOOS === true; const isPreorderActive = hasPreorder && isOutOfStock && canSellOOS; const isNotifyMePreorder = hasPreorder && isOutOfStock && !canSellOOS; const strings = { notifyMe: window.theme?.variantStrings?.notifyMe || "Notify when available", inStoreOnly: window.theme?.variantStrings?.inStoreOnly || "Available In-Store Only", comingSoon: window.theme?.variantStrings?.comingSoon || "Coming Soon", preOrder: window.theme?.variantStrings?.preOrder || "Pre-Order", preorderInStoreOnly: window.theme?.variantStrings?.preorderinStoreOnly || "Pre-Order In Store", addToCart: window.theme?.variantStrings?.addToCart || "Add to Cart", unavailable: window.theme?.variantStrings?.unavailable || "Unavailable", }; const shouldPrefixTitle = isPreorderActive; this.updateProductTitlePrefix(shouldPrefixTitle); submitButtons.forEach((submitButton) => { const submitButtonText = submitButton.querySelector( ".single-add-to-cart-button--text" ); if (!submitButtonText) return; submitButton.classList.remove("klaviyo-bis-trigger", "sold-out", "loading"); submitButton.removeAttribute("disabled"); if (!this.currentVariant) { submitButton.classList.add("sold-out"); submitButtonText.textContent = strings.unavailable; return; } if (isComingSoon) { submitButton.setAttribute("disabled", "disabled"); submitButtonText.textContent = strings.comingSoon; return; } // Preorder product, OOS, continue selling unchecked -> BIS if (isNotifyMePreorder) { submitButton.classList.add("klaviyo-bis-trigger", "sold-out"); submitButtonText.textContent = strings.notifyMe; return; } // Preorder product, OOS, continue selling checked, instore-only -> Pre-Order In Store if (isPreorderActive && isInstoreOnly) { submitButton.setAttribute("disabled", "disabled"); submitButtonText.textContent = strings.preorderInStoreOnly; return; } // Preorder product, OOS, continue selling checked -> Pre-Order if (isPreorderActive) { submitButtonText.textContent = strings.preOrder; return; } // In stock + instore-only -> Available In-Store Only if (isInstoreOnly) { submitButton.setAttribute("disabled", "disabled"); submitButtonText.textContent = strings.inStoreOnly; return; } // Regular sold out non-preorder case -> BIS if (!this.currentVariant.available) { submitButton.classList.add("klaviyo-bis-trigger", "sold-out"); submitButtonText.textContent = strings.notifyMe; return; } // Everything else -> Add to Cart submitButtonText.textContent = strings.addToCart; }); const existingKlaviyoButtons = productForm.querySelectorAll( "a.notifybutton.klaviyo-bis-trigger" ); existingKlaviyoButtons.forEach((button) => button.remove()); } setUnavailable() { const submitButtons = document.querySelectorAll( ".single-add-to-cart-button" ); const price = document.getElementById(`price-${this.dataset.section}`); const price_fixed = document.getElementById( `price-${this.dataset.section}--sticky` ); submitButtons.forEach((submitButton) => { const submitButtonText = submitButton.querySelector( ".single-add-to-cart-button--text" ); if (!submitButton) return; submitButtonText.textContent = window.theme.variantStrings.unavailable; submitButton.classList.add("sold-out"); }); if (price) price.classList.add("visibility-hidden"); if (price_fixed) price_fixed.classList.add("visibility-hidden"); } setDisabled() { if (this.isDisabledFeature != "true") { return; } const variant_data = this.getVariantData(); if (variant_data && this.currentVariant) { const selected_options = this.currentVariant.options.map( (value, index) => { return { value, index: `option${index + 1}`, }; } ); const available_options = this.createAvailableOptionsTree( variant_data, selected_options ); this.fieldsets.forEach((fieldset, i) => { const fieldset_options = Object.values(available_options)[i]; if (fieldset_options) { if (fieldset.querySelector("select")) { fieldset_options.forEach((option, option_i) => { if (option.isUnavailable) { fieldset.querySelector( "option[value=" + JSON.stringify(option.value) + "]" ).disabled = true; } else { fieldset.querySelector( "option[value=" + JSON.stringify(option.value) + "]" ).disabled = false; } }); } else if (fieldset.querySelectorAll("input").length) { fieldset.querySelectorAll("input").forEach((input, input_i) => { input.classList.toggle( "is-disabled", fieldset_options[input_i].isUnavailable ); }); } } }); } return true; } getImageSetName(variant_name) { return variant_name .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/-$/, "") .replace(/^-/, ""); } setImageSet() { if (!this.productSlider) return; let dataSetEl = this.productSlider.querySelector("[data-set-name]"); if (dataSetEl) { this.imageSetName = dataSetEl.dataset.setName; this.imageSetIndex = this.querySelector( '.product-form__input[data-handle="' + this.imageSetName + '"]' ).dataset.index; this.setImageSetMedia(); } } setImageSetMedia() { if (!this.imageSetIndex) { return; } let setValue = this.getImageSetName( this.currentVariant[this.imageSetIndex] ); let group = this.imageSetName + "_" + setValue; let selected_set_images = this.productSlider.querySelectorAll( `[data-set-name="${this.imageSetName}"]` ); if (this.hideVariants) { selected_set_images.forEach((thumb) => { thumb.classList.toggle("is-active", thumb.dataset.group === group); }); } else { let set_images = Array.from(selected_set_images).filter(function ( element ) { return element.dataset.group === group; }); set_images.forEach((thumb) => { thumb.parentElement.prepend(thumb); }); } setTimeout(() => { this.productSlider.querySelector("product-slider").onPaginationResize(); }, 100); } createAvailableOptionsTree(variant_data, selected_options) { // Reduce variant array into option availability tree return variant_data.reduce( (options, variant) => { // Check each option group (e.g. option1, option2, option3) of the variant Object.keys(options).forEach((index) => { if (variant[index] === null) return; let entry = options[index].find( (option) => option.value === variant[index] ); if (typeof entry === "undefined") { // If option has yet to be added to the options tree, add it entry = { value: variant[index], isUnavailable: true, }; options[index].push(entry); } // Check how many selected option values match a variant const countVariantOptionsThatMatchCurrent = selected_options.reduce( (count, { value, index }) => { return variant[index] === value ? count + 1 : count; }, 0 ); // Only enable an option if an available variant matches all but one current selected value if ( countVariantOptionsThatMatchCurrent >= selected_options.length - 1 ) { entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable; } // Make sure if a variant is unavailable, disable currently selected option if ( (!this.currentVariant || !this.currentVariant.available) && selected_options.find( (option) => option.value === entry.value && index === option.index ) ) { entry.isUnavailable = true; } // First option is always enabled if (index === "option1") { entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable; } }); return options; }, { option1: [], option2: [], option3: [], } ); } getVariantData() { this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent); return this.variantData; } } customElements.define("variant-selects", VariantSelects); /** * @class * @function VariantRadios */ class VariantRadios extends VariantSelects { constructor() { super(); } updateOptions() { const fieldsets = Array.from(this.querySelectorAll("fieldset")); this.options = fieldsets.map((fieldset) => { return Array.from(fieldset.querySelectorAll("input")).find( (radio) => radio.checked ).value; }); } } customElements.define("variant-radios", VariantRadios); } if (!customElements.get("product-slider")) { /** * @class * @function ProductSlider */ class ProductSlider extends HTMLElement { constructor() { super(); } connectedCallback() { this.pagination = this.parentElement.querySelector( ".product-images-buttons" ); this.sliderItems = this.querySelectorAll('[id^="Slide-"]'); // Start Gallery let observer = new MutationObserver(() => { this.setupProductGallery(); }); observer.observe(this, { attributes: true, attributeFilter: ["class"], childList: true, characterData: false, }); this.setupProductGallery(); // Start Pagination if (this.pagination) { this.setupPagination(); this.resizeObserver = new ResizeObserver((entries) => this.onPaginationResize() ); this.resizeObserver.observe(this); this.addEventListener("scroll", this.updatePagination.bind(this)); } } setupProductGallery() { if (!this.querySelectorAll(".product-single__media-zoom").length) { return; } this.setEventListeners(); } buildItems(activeImages) { let images = activeImages.map((item) => { let activelink = item.querySelector(".product-single__media-zoom"); return { src: activelink.getAttribute("href"), msrc: activelink.dataset.msrc, w: activelink.dataset.w, h: activelink.dataset.h, title: activelink.getAttribute("title"), }; }); return images; } setEventListeners() { let activeImages = Array.from( this.querySelectorAll(".product-images__slide--image") ).filter((element) => element.clientWidth > 0), items = this.buildItems(activeImages), captionEl = this.dataset.captions, pswpElement = document.querySelectorAll(".pswp")[0], options = { maxSpreadZoom: 2, loop: false, allowPanToNext: false, closeOnScroll: false, showHideOpacity: false, arrowKeys: true, history: false, captionEl: captionEl, fullscreenEl: false, zoomEl: false, shareEl: false, counterEl: true, arrowEl: true, preloaderEl: true, }; let openPswp = function (e, link, options, pswpElement, items) { let parent = link.closest(".product-images__slide"); let i = activeImages.indexOf(parent); options.index = parseInt(i, 10); options.getThumbBoundsFn = () => { const thumbnail = link.closest(".product-single__media"), pageYScroll = window.scrollY || document.documentElement.scrollTop, rect = thumbnail.getBoundingClientRect(); return { x: rect.left, y: rect.top + pageYScroll, w: rect.width, }; }; if (typeof PhotoSwipe !== "undefined") { let pswp = new PhotoSwipe( pswpElement, PhotoSwipeUI_Default, items, options ); pswp.listen("firstUpdate", () => { pswp.listen("parseVerticalMargin", function (item) { item.vGap = { top: 50, bottom: 50, }; }); }); pswp.init(); } e.preventDefault(); }; this.querySelectorAll(".product-single__media-zoom").forEach(function ( link ) { let thumbnail = link.closest(".product-single__media"); let clone = link.cloneNode(true); thumbnail.append(clone); link.remove(); clone.addEventListener("click", (e) => openPswp(e, clone, options, pswpElement, items) ); }); } setupPagination() { this.sliderItemsToShow = Array.from(this.sliderItems).filter( (element) => element.clientWidth > 0 ); if (this.sliderItemsToShow.length < 2) return; this.sliderItemOffset = this.sliderItemsToShow[1].offsetLeft - this.sliderItemsToShow[0].offsetLeft; this.currentPageElement = this.pagination.querySelector( ".slider-counter--current" ); this.pageTotalElement = this.pagination.querySelector( ".slider-counter--total" ); this.prevButton = this.pagination.querySelector( 'button[name="previous"]' ); this.nextButton = this.pagination.querySelector('button[name="next"]'); this.prevButton.addEventListener( "click", this.onPaginationButtonClick.bind(this) ); this.nextButton.addEventListener( "click", this.onPaginationButtonClick.bind(this) ); this.updatePagination(); } onPaginationResize() { this.sliderItemsToShow = Array.from(this.sliderItems).filter( (element) => element.clientWidth > 0 ); if (this.sliderItemsToShow.length < 2) return; this.sliderItemOffset = this.sliderItemsToShow[1].offsetLeft - this.sliderItemsToShow[0].offsetLeft; this.updatePagination(); } onPaginationButtonClick(event) { event.preventDefault(); this.slideScrollPosition = event.currentTarget.name === "next" ? this.scrollLeft + 1 * this.sliderItemOffset : this.scrollLeft - 1 * this.sliderItemOffset; this.scrollTo({ left: this.slideScrollPosition, }); } updatePagination() { if (!this.nextButton) return; const previousPage = this.currentPage; this.currentPage = Math.round(this.scrollLeft / this.sliderItemOffset) + 1; if (this.currentPageElement) { this.currentPageElement.textContent = this.currentPage; } if (this.pageTotalElement) { this.pageTotalElement.textContent = this.sliderItemsToShow.length; } if (this.currentPage != previousPage) { this.dispatchEvent( new CustomEvent("slideChanged", { detail: { currentPage: this.currentPage, currentElement: this.sliderItemsToShow[this.currentPage - 1], }, }) ); } if ( this.isSlideVisible(this.sliderItemsToShow[0]) && this.scrollLeft === 0 ) { this.prevButton.setAttribute("disabled", "disabled"); } else { this.prevButton.removeAttribute("disabled"); } if ( this.isSlideVisible( this.sliderItemsToShow[this.sliderItemsToShow.length - 1] ) ) { this.nextButton.setAttribute("disabled", "disabled"); } else { this.nextButton.removeAttribute("disabled"); } } isSlideVisible(element, offset = 0) { const lastVisibleSlide = this.clientWidth + this.scrollLeft - offset; return ( element.offsetLeft + element.clientWidth <= lastVisibleSlide && element.offsetLeft >= this.scrollLeft ); } } customElements.define("product-slider", ProductSlider); } /** * @class * @function ProductForm */ if (!customElements.get("product-form")) { customElements.define( "product-form", class ProductForm extends HTMLElement { constructor() { super(); } connectedCallback() { this.sticky = this.dataset.sticky; this.form = document.getElementById( `product-form-${this.dataset.section}` ); this.form.querySelector("[name=id]").disabled = false; if (!this.sticky) { this.form.addEventListener("submit", this.onSubmitHandler.bind(this)); } this.cartNotification = document.querySelector("cart-notification"); this.body = document.body; this.hideErrors = this.dataset.hideErrors === "true"; const hasVariantSelectors = document.querySelector("variant-selects, variant-radios"); if (!hasVariantSelectors) { const formWrapper = this.form.querySelector('[data-type="add-to-cart-form"]'); const titleElement = document.querySelector(`#product-title-${this.dataset.section} .product-title`) || document.querySelector(".product-title"); if (formWrapper && titleElement) { let preorderMap = {}; try { preorderMap = JSON.parse(formWrapper.dataset.variantPreorderMap || "{}"); } catch (error) { console.error("Invalid variant preorder map", error); } const variantId = String(this.form.querySelector('[name="id"]')?.value || ""); const currentState = preorderMap[variantId] || {}; const hasPreorder = currentState.hasPreorder === true; const isOutOfStock = currentState.isOutOfStock === true; const canSellOOS = currentState.canSellOOS === true; const isPreorderOnlineOnly = currentState.isPreorderOnlineOnly === true; // Match the same logic used in VariantSelects const shouldPrefixTitle = (hasPreorder && isOutOfStock && canSellOOS) || isPreorderOnlineOnly; const rawTitle = titleElement.dataset.rawTitle || titleElement.dataset.baseTitle || titleElement.textContent.replace(/^(?:pre[\s-]*order:\s*)+/i, "").trim(); titleElement.dataset.baseTitle = rawTitle; titleElement.textContent = shouldPrefixTitle ? `Pre-Order: ${rawTitle}` : rawTitle; } } } onSubmitHandler(evt) { evt.preventDefault(); if (!this.form.reportValidity()) { return; } const submitButtons = document.querySelectorAll( ".single-add-to-cart-button" ); submitButtons.forEach((submitButton) => { if (submitButton.classList.contains("loading")) return; submitButton.setAttribute("aria-disabled", true); submitButton.classList.add("loading"); }); this.handleErrorMessage(); const config = { method: "POST", headers: { "X-Requested-With": "XMLHttpRequest", Accept: "application/javascript", }, }; let formData = new FormData(this.form); formData.append( "sections", this.getSectionsToRender().map((section) => section.section) ); formData.append("sections_url", window.location.pathname); config.body = formData; fetch(`${theme.routes.cart_add_url}`, config) .then((response) => response.json()) .then((response) => { if (response.status) { dispatchCustomEvent("product:variant-error", { source: "product-form", productVariantId: formData.get("id"), errors: response.description, message: response.message, }); this.handleErrorMessage(response.description); return; } this.renderContents(response); dispatchCustomEvent("cart:item-added", { product: response.hasOwnProperty("items") ? response.items[0] : response, }); }) .catch((e) => { console.error(e); }) .finally(() => { submitButtons.forEach((submitButton) => { submitButton.classList.remove("loading"); submitButton.removeAttribute("aria-disabled"); }); }); } getSectionsToRender() { return [ { id: "Cart", section: "main-cart", selector: ".thb-cart-form", }, { id: "Cart-Drawer", section: "cart-drawer", selector: ".cart-drawer", }, { id: "cart-drawer-toggle", section: "cart-bubble", selector: ".thb-item-count", }, ]; } renderContents(parsedState) { this.getSectionsToRender().forEach((section) => { if (!document.getElementById(section.id)) { return; } const elementToReplace = document .getElementById(section.id) .querySelector(section.selector) || document.getElementById(section.id); elementToReplace.innerHTML = this.getSectionInnerHTML( parsedState.sections[section.section], section.selector ); if (typeof CartDrawer !== "undefined") { new CartDrawer(); } if (typeof Cart !== "undefined") { new Cart().renderContents(parsedState); } }); let product_drawer = document.getElementById("Product-Drawer"); if (product_drawer && product_drawer.contains(this)) { product_drawer .querySelector(".product-quick-images--container") .classList.remove("active"); document.body.classList.remove("open-quick-view"); if (window.innerWidth < 1069) { product_drawer.classList.remove("active"); if (document.getElementById("Cart-Drawer")) { document.getElementById("Cart-Drawer").classList.add("active"); document.body.classList.add("open-cart"); document .getElementById("Cart-Drawer") .querySelector(".product-recommendations--full") .classList.add("active"); dispatchCustomEvent("cart-drawer:open"); } } else { product_drawer .querySelector(".product-quick-images--container") .addEventListener("transitionend", function () { product_drawer.classList.remove("active"); if (document.getElementById("Cart-Drawer")) { document .getElementById("Cart-Drawer") .classList.add("active"); document.body.classList.add("open-cart"); document .getElementById("Cart-Drawer") .querySelector(".product-recommendations--full") .classList.add("active"); dispatchCustomEvent("cart-drawer:open"); } }); } if (!document.getElementById("Cart-Drawer")) { document.body.classList.remove("open-cc"); } } else if (document.getElementById("Cart-Drawer")) { document.body.classList.add("open-cc"); document.body.classList.add("open-cart"); document.getElementById("Cart-Drawer").classList.add("active"); dispatchCustomEvent("cart-drawer:open"); } } getSectionInnerHTML(html, selector = ".shopify-section") { return new DOMParser() .parseFromString(html, "text/html") .querySelector(selector).innerHTML; } handleErrorMessage(errorMessage = false) { if (this.hideErrors) return; this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector(".product-form__error-message-wrapper"); this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector( ".product-form__error-message" ); this.errorMessageWrapper.toggleAttribute("hidden", !errorMessage); if (errorMessage) { this.errorMessage.textContent = errorMessage; } } } ); } /** * @class * @function ProductAddToCartSticky */ if (!customElements.get("product-add-to-cart-sticky")) { class ProductAddToCartSticky extends HTMLElement { constructor() { super(); this.animations_enabled = document.body.classList.contains("animations-true") && typeof gsap !== "undefined"; } connectedCallback() { this.setupObservers(); this.setupToggle(); } setupToggle() { const button = this.querySelector(".product-add-to-cart-sticky--inner"), content = this.querySelector(".product-add-to-cart-sticky--content"); if (this.animations_enabled) { const tl = gsap.timeline({ reversed: true, paused: true, onStart: () => { button.classList.add("sticky-open"); }, onReverseComplete: () => { button.classList.remove("sticky-open"); }, }); tl.set( content, { display: "block", height: "auto", }, "start" ).from( content, { height: 0, duration: 0.25, }, "start+=0.001" ); button.addEventListener("click", function () { tl.reversed() ? tl.play() : tl.reverse(); return false; }); } else { button.addEventListener("click", function () { content.classList.toggle("active"); return false; }); } } setupObservers() { let _this = this, observer = new IntersectionObserver( function (entries) { entries.forEach((entry) => { if (entry.target === footer) { if (entry.intersectionRatio > 0) { _this.classList.remove("sticky--visible"); } else if (entry.intersectionRatio == 0 && _this.formPassed) { _this.classList.add("sticky--visible"); } } if (entry.target === form) { let boundingRect = form.getBoundingClientRect(); if ( entry.intersectionRatio === 0 && window.scrollY > boundingRect.top + boundingRect.height ) { _this.formPassed = true; _this.classList.add("sticky--visible"); } else if (entry.intersectionRatio === 1) { _this.formPassed = false; _this.classList.remove("sticky--visible"); } } }); }, { threshold: [0, 1], } ), form = document.getElementById(`product-form-${this.dataset.section}`), footer = document.getElementById("footer"); _this.formPassed = false; observer.observe(form); observer.observe(footer); } } customElements.define("product-add-to-cart-sticky", ProductAddToCartSticky); } /** * @class * @function ProductSidePanelLinks */ if (!customElements.get("side-panel-links")) { class ProductSidePanelLinks extends HTMLElement { constructor() { super(); this.links = this.querySelectorAll("button"); this.drawer = document.getElementById("Product-Information-Drawer"); this.buttons = this.drawer.querySelector(".side-panel-content--tabs"); this.panels = this.drawer .querySelector(".side-panel-content--inner") .querySelectorAll(".side-panel-content--tab-panel"); this.body = document.body; } connectedCallback() { this.setupObservers(); } disconnectedCallback() {} setupObservers() { this.links.forEach((item, i) => { item.addEventListener("click", (e) => { this.body.classList.add("open-cc"); this.buttons.toggleActiveClass(i); this.drawer.classList.add("active"); }); }); } } customElements.define("side-panel-links", ProductSidePanelLinks); } if (typeof addIdToRecentlyViewed !== "undefined") { addIdToRecentlyViewed(); }

FREQUENTLY ASKED QUESTIONS

WATCH STORIES TOLD DIFFERENT
•
RARE, EXCLUSIVE, LIMITED
•
WATCH STORIES TOLD DIFFERENT
•
RARE, EXCLUSIVE, LIMITED
•
WATCH STORIES TOLD DIFFERENT
•
RARE, EXCLUSIVE, LIMITED
•