@kpsclothing Alright So i am pretty much done. This took a lot of looking up and workarounds, i will admit that. Let me explain the steps. There are four steps please look carefully and copy paste code. the part of naming of alt images will be the same as before. Just make these changes.
/////////// First Step ///////////
Open product-media-gallery.liquid file and replace with this code
@media(min-width:767px){ .product__media-list li:not(.is-active){ display: none !important; } }{% comment %}
Renders a product media gallery. Should be used with âmedia-gallery.jsâ
Also see âproduct-media-modalâ
Accepts:
- product: {Object} Product liquid object
- variant_images: {Array} Product images associated with a variant
- limit: {Number} (optional) When passed, limits the number of media items to render
Usage:
{% render âproduct-media-galleryâ %}
{% endcomment %}
{%- liquid
if section.settings.hide_variants and variant_images.size == product.media.size
assign single_media_visible = true
endif
if limit == 1
assign single_media_visible = true
endif
assign media_count = product.media.size
if section.settings.hide_variants and media_count > 1 and variant_images.size > 0
assign media_count = media_count | minus: variant_images.size | plus: 1
endif
if media_count == 1 or single_media_visible
assign single_media_visible_mobile = true
endif
if media_count == 0 or single_media_visible_mobile or section.settings.mobile_thumbnails == âshowâ or section.settings.mobile_thumbnails == âcolumnsâ and media_count < 3
assign hide_mobile_slider = true
endif
if section.settings.media_size == âlargeâ
assign media_width = 0.65
elsif section.settings.media_size == âmediumâ
assign media_width = 0.55
elsif section.settings.media_size == âsmallâ
assign media_width = 0.45
endif
-%}
<media-gallery
id=âMediaGallery-{{ section.id }}â
role=âregionâ
{% if section.settings.enable_sticky_info %}
class=âproduct__column-stickyâ
{% endif %}
aria-label=â{{ âproducts.product.media.gallery_viewerâ | t }}â
data-desktop-layout=â{{ section.settings.gallery_layout }}â
-
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign featured_media = product.selected_or_first_available_variant.featured_media -%}
- {%- assign media_position = 1 -%} {% render 'product-thumbnail', media: featured_media, media_count: media_count, position: media_position, desktop_layout: section.settings.gallery_layout, mobile_layout: section.settings.mobile_thumbnails, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true, media_width: media_width, media_fit: section.settings.media_fit, constrain_to_viewport: section.settings.constrain_to_viewport, lazy_load: false %} {%- endif -%} {%- for media in product.media -%} {% if media_position >= limit or media_position >= 1 and section.settings.hide_variants and variant_images contains media.src %} {% continue %} {% endif %} {%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%}
- {%- liquid assign media_position = media_position | default: 0 | plus: 1 assign lazy_load = false if media_position > 1 assign lazy_load = true endif -%} {% render 'product-thumbnail', media: media, media_count: media_count, position: media_position, desktop_layout: section.settings.gallery_layout, mobile_layout: section.settings.mobile_thumbnails, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true, media_width: media_width, media_fit: section.settings.media_fit, constrain_to_viewport: section.settings.constrain_to_viewport, lazy_load: lazy_load %} {%- endunless -%} {%- endfor -%}
-
{%- capture sizes -%}
(min-width: {{ settings.page_width }}px) calc(({{ settings.page_width | minus: 100 | times: media_width | round }} - 4rem) / 4),
(min-width: 990px) calc(({{ media_width | times: 100 }}vw - 4rem) / 4),
(min-width: 750px) calc((100vw - 15rem) / 8),
calc((100vw - 8rem) / 3)
{%- endcapture -%}
- {%- capture thumbnail_id -%} Thumbnail-{{ section.id }}-0 {%- endcapture -%} {{ featured_media.preview_image | image_url: width: 416 | image_tag: loading: 'lazy', sizes: sizes, widths: '54, 74, 104, 162, 208, 324, 416', id: thumbnail_id, alt: featured_media.alt | escape }} {%- endif -%} {%- for media in product.media -%} {%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%} {%- liquid capture media_index if media.media_type == 'model' increment model_index elsif media.media_type == 'video' or media.media_type == 'external_video' increment video_index elsif media.media_type == 'image' increment image_index endif endcapture assign media_index = media_index | plus: 1 -%}
- {%- if media.media_type == 'model' -%} {%- render 'icon-3d-model' -%} {%- elsif media.media_type == 'video' or media.media_type == 'external_video' -%} {%- render 'icon-play' -%} {%- endif -%} {%- capture thumbnail_id -%} Thumbnail-{{ section.id }}-{{ forloop.index }} {%- endcapture -%} {{ media.preview_image | image_url: width: 416 | image_tag: loading: 'lazy', sizes: sizes, widths: '54, 74, 104, 162, 208, 324, 416', id: thumbnail_id, alt: media.alt | escape }} {%- endunless -%} {%- endfor -%}
{%- if featured_media != null -%}
{%- liquid
capture media_index
if featured_media.media_type == âmodelâ
increment model_index
elsif featured_media.media_type == âvideoâ or featured_media.media_type == âexternal_videoâ
increment video_index
elsif featured_media.media_type == âimageâ
increment image_index
endif
endcapture
assign media_index = media_index | plus: 1
-%}
///////// Second step ///////////
Open product-info.js and replace with this code
if (!customElements.get(âproduct-infoâ)) {
customElements.define(
âproduct-infoâ,
class ProductInfo extends HTMLElement {
quantityInput = undefined;
quantityForm = undefined;
onVariantChangeUnsubscriber = undefined;
onVariantLoadSubscriber = undefined;
cartUpdateUnsubscriber = undefined;
abortController = undefined;
pendingRequestUrl = null;
preProcessHtmlCallbacks = ;
postProcessHtmlCallbacks = ;
constructor() {
super();
this.quantityInput = this.querySelector(â.quantity__inputâ);
}
connectedCallback() {
this.initializeProductSwapUtility();
this.onVariantChangeUnsubscriber = subscribe(
PUB_SUB_EVENTS.optionValueSelectionChange,
this.handleOptionValueChange.bind(this)
);
this.onVariantLoadSubscriber = subscribe(
PUB_SUB_EVENTS.onVariantLoadChange,
this.handleOptionValueLoad.bind(this)
);
this.initQuantityHandlers();
this.dispatchEvent(new CustomEvent(âproduct-info:loadedâ, { bubbles: true }));
}
addPreProcessCallback(callback) {
this.preProcessHtmlCallbacks.push(callback);
}
initQuantityHandlers() {
if (!this.quantityInput) return;
this.quantityForm = this.querySelector(â.product-form__quantityâ);
if (!this.quantityForm) return;
this.setQuantityBoundries();
if (!this.dataset.originalSection) {
this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, this.fetchQuantityRules.bind(this));
}
}
disconnectedCallback() {
this.onVariantChangeUnsubscriber();
this.cartUpdateUnsubscriber?.();
}
initializeProductSwapUtility() {
this.preProcessHtmlCallbacks.push((html) =>
html.querySelectorAll(â.scroll-triggerâ).forEach((element) => element.classList.add(âscroll-triggerâcancelâ))
);
this.postProcessHtmlCallbacks.push((newNode) => {
window?.Shopify?.PaymentButton?.init();
window?.ProductModel?.loadShopifyXR();
});
}
handleOptionValueChange({ data: { event, target, selectedOptionValues } }) {
console.log(event,target,selectedOptionValues)
if (!this.contains(event.target)) return;
this.resetProductFormState();
const productUrl = target.dataset.productUrl || this.pendingRequestUrl || this.dataset.url;
this.pendingRequestUrl = productUrl;
const shouldSwapProduct = this.dataset.url !== productUrl;
const shouldFetchFullPage = this.dataset.updateUrl === âtrueâ && shouldSwapProduct;
this.renderProductInfo({
requestUrl: this.buildRequestUrlWithParams(productUrl, selectedOptionValues, shouldFetchFullPage),
targetId: target.id,
callback: shouldSwapProduct
? this.handleSwapProduct(productUrl, shouldFetchFullPage)
: this.handleUpdateProductInfo(productUrl),
});
}
handleOptionValueLoad({ data: { target } }) {
const selectedVariant = target.querySelector(âvariant-selects [data-selected-variant]â)?.innerHTML;
const variant = !!selectedVariant ? JSON.parse(selectedVariant) : null;
console.log(variant);
if(variant.featured_image && variant.featured_image.alt){
document.querySelectorAll(â[thumbnail-alt]â).forEach(img => img.style.display = ânoneâ);
const currentImageAlt = variant.featured_image.alt;
const thumbnailSelector= [thumbnail-alt= '${currentImageAlt}'];
document.querySelectorAll(thumbnailSelector).forEach((img) => {
img.style.display = âblockâ;
})
} else{
document.querySelectorAll(â[thumbnail-alt]â).forEach(img => img.style.display = âblockâ);
}
}
resetProductFormState() {
const productForm = this.productForm;
productForm?.toggleSubmitButton(true);
productForm?.handleErrorMessage();
}
handleSwapProduct(productUrl, updateFullPage) {
return (html) => {
this.productModal?.remove();
const selector = updateFullPage ? âproduct-info[id^=âMainProductâ]â : âproduct-infoâ;
const variant = this.getSelectedVariant(html.querySelector(selector));
this.updateURL(productUrl, variant?.id);
if (updateFullPage) {
document.querySelector(âhead titleâ).innerHTML = html.querySelector(âhead titleâ).innerHTML;
HTMLUpdateUtility.viewTransition(
document.querySelector(âmainâ),
html.querySelector(âmainâ),
this.preProcessHtmlCallbacks,
this.postProcessHtmlCallbacks
);
} else {
HTMLUpdateUtility.viewTransition(
this,
html.querySelector(âproduct-infoâ),
this.preProcessHtmlCallbacks,
this.postProcessHtmlCallbacks
);
}
};
}
renderProductInfo({ requestUrl, targetId, callback }) {
this.abortController?.abort();
this.abortController = new AbortController();
fetch(requestUrl, { signal: this.abortController.signal })
.then((response) => response.text())
.then((responseText) => {
this.pendingRequestUrl = null;
const html = new DOMParser().parseFromString(responseText, âtext/htmlâ);
callback(html);
})
.then(() => {
// set focus to last clicked option value
document.querySelector(#${targetId})?.focus();
})
.catch((error) => {
if (error.name === âAbortErrorâ) {
console.log(âFetch aborted by userâ);
} else {
console.error(error);
}
});
}
getSelectedVariant(productInfoNode) {
const selectedVariant = productInfoNode.querySelector(âvariant-selects [data-selected-variant]â)?.innerHTML;
return !!selectedVariant ? JSON.parse(selectedVariant) : null;
}
buildRequestUrlWithParams(url, optionValues, shouldFetchFullPage = false) {
const params = ;
!shouldFetchFullPage && params.push(section_id=${this.sectionId});
if (optionValues.length) {
params.push(option_values=${optionValues.join(',')});
}
return ${url}?${params.join('&')};
}
updateOptionValues(html) {
const variantSelects = html.querySelector(âvariant-selectsâ);
if (variantSelects) {
HTMLUpdateUtility.viewTransition(this.variantSelectors, variantSelects, this.preProcessHtmlCallbacks);
}
}
handleUpdateProductInfo(productUrl) {
return (html) => {
const variant = this.getSelectedVariant(html);
this.pickupAvailability?.update(variant);
this.updateOptionValues(html);
this.updateURL(productUrl, variant?.id);
this.updateVariantInputs(variant?.id);
if (!variant) {
this.setUnavailable();
return;
}
this.updateMedia(html, variant?.featured_media?.id);
this.filterVariantImages(variant);
const updateSourceFromDestination = (id, shouldHide = (source) => false) => {
const source = html.getElementById(${id}-${this.sectionId});
const destination = this.querySelector(#${id}-${this.dataset.section});
if (source && destination) {
destination.innerHTML = source.innerHTML;
destination.classList.toggle(âhiddenâ, shouldHide(source));
}
};
updateSourceFromDestination(âpriceâ);
updateSourceFromDestination(âSkuâ, ({ classList }) => classList.contains(âhiddenâ));
updateSourceFromDestination(âInventoryâ, ({ innerText }) => innerText === ââ);
updateSourceFromDestination(âVolumeâ);
updateSourceFromDestination(âPrice-Per-Itemâ, ({ classList }) => classList.contains(âhiddenâ));
this.updateQuantityRules(this.sectionId, html);
this.querySelector(#Quantity-Rules-${this.dataset.section})?.classList.remove(âhiddenâ);
this.querySelector(#Volume-Note-${this.dataset.section})?.classList.remove(âhiddenâ);
this.productForm?.toggleSubmitButton(
html.getElementById(ProductSubmitButton-${this.sectionId})?.hasAttribute(âdisabledâ) ?? true,
window.variantStrings.soldOut
);
publish(PUB_SUB_EVENTS.variantChange, {
data: {
sectionId: this.sectionId,
html,
variant,
},
});
};
}
filterVariantImages(varObj){
console.log(varObj)
if(varObj.featured_image && varObj.featured_image.alt){
document.querySelectorAll(â[thumbnail-alt]â).forEach(img => img.style.display = ânoneâ);
const currentImageAlt = varObj.featured_image.alt;
const thumbnailSelector= [thumbnail-alt= '${currentImageAlt}'];
document.querySelectorAll(thumbnailSelector).forEach((img) => {
img.style.display = âblockâ;
})
} else{
document.querySelectorAll(â[thumbnail-alt]â).forEach(img => img.style.display = âblockâ);
}
}
updateVariantInputs(variantId) {
this.querySelectorAll(
#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}
).forEach((productForm) => {
const input = productForm.querySelector(âinput[name=âidâ]â);
input.value = variantId ?? ââ;
input.dispatchEvent(new Event(âchangeâ, { bubbles: true }));
});
}
updateURL(url, variantId) {
this.querySelector(âshare-buttonâ)?.updateUrl(
${window.shopUrl}${url}${variantId ? ?variant=${variantId} : ''}
);
if (this.dataset.updateUrl === âfalseâ) return;
window.history.replaceState({}, ââ, ${url}${variantId ? ?variant=${variantId} : ''});
}
setUnavailable() {
this.productForm?.toggleSubmitButton(true, window.variantStrings.unavailable);
const selectors = [âpriceâ, âInventoryâ, âSkuâ, âPrice-Per-Itemâ, âVolume-Noteâ, âVolumeâ, âQuantity-Rulesâ]
.map((id) => #${id}-${this.dataset.section})
.join(', ');
document.querySelectorAll(selectors).forEach(({ classList }) => classList.add(âhiddenâ));
}
updateMedia(html, variantFeaturedMediaId) {
if (!variantFeaturedMediaId) return;
const mediaGallerySource = this.querySelector(âmedia-gallery ulâ);
const mediaGalleryDestination = html.querySelector(media-gallery ul);
const refreshSourceData = () => {
if (this.hasAttribute(âdata-zoom-on-hoverâ)) enableZoomOnHover(2);
const mediaGallerySourceItems = Array.from(mediaGallerySource.querySelectorAll(âli[data-media-id]â));
const sourceSet = new Set(mediaGallerySourceItems.map((item) => item.dataset.mediaId));
const sourceMap = new Map(
mediaGallerySourceItems.map((item, index) => [item.dataset.mediaId, { item, index }])
);
return [mediaGallerySourceItems, sourceSet, sourceMap];
};
if (mediaGallerySource && mediaGalleryDestination) {
let [mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
const mediaGalleryDestinationItems = Array.from(
mediaGalleryDestination.querySelectorAll(âli[data-media-id]â)
);
const destinationSet = new Set(mediaGalleryDestinationItems.map(({ dataset }) => dataset.mediaId));
let shouldRefresh = false;
// add items from new data not present in DOM
for (let i = mediaGalleryDestinationItems.length - 1; i >= 0; iâ) {
if (!sourceSet.has(mediaGalleryDestinationItems[i].dataset.mediaId)) {
mediaGallerySource.prepend(mediaGalleryDestinationItems[i]);
shouldRefresh = true;
}
}
// remove items from DOM not present in new data
for (let i = 0; i < mediaGallerySourceItems.length; i++) {
if (!destinationSet.has(mediaGallerySourceItems[i].dataset.mediaId)) {
mediaGallerySourceItems[i].remove();
shouldRefresh = true;
}
}
// refresh
if (shouldRefresh) [mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
// if media galleries donât match, sort to match new data order
mediaGalleryDestinationItems.forEach((destinationItem, destinationIndex) => {
const sourceData = sourceMap.get(destinationItem.dataset.mediaId);
if (sourceData && sourceData.index !== destinationIndex) {
mediaGallerySource.insertBefore(
sourceData.item,
mediaGallerySource.querySelector(li:nth-of-type(${destinationIndex + 1}))
);
// refresh source now that it has been modified
[mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
}
});
}
// set featured media as active in the media gallery
this.querySelector(media-gallery)?.setActiveMedia?.(
${this.dataset.section}-${variantFeaturedMediaId},
true
);
// update media modal
const modalContent = this.productModal?.querySelector(.product-media-modal__content);
const newModalContent = html.querySelector(product-modal .product-media-modal__content);
if (modalContent && newModalContent) modalContent.innerHTML = newModalContent.innerHTML;
}
setQuantityBoundries() {
const data = {
cartQuantity: this.quantityInput.dataset.cartQuantity ? parseInt(this.quantityInput.dataset.cartQuantity) : 0,
min: this.quantityInput.dataset.min ? parseInt(this.quantityInput.dataset.min) : 1,
max: this.quantityInput.dataset.max ? parseInt(this.quantityInput.dataset.max) : null,
step: this.quantityInput.step ? parseInt(this.quantityInput.step) : 1,
};
let min = data.min;
const max = data.max === null ? data.max : data.max - data.cartQuantity;
if (max !== null) min = Math.min(min, max);
if (data.cartQuantity >= data.min) min = Math.min(min, data.step);
this.quantityInput.min = min;
if (max) {
this.quantityInput.max = max;
} else {
this.quantityInput.removeAttribute(âmaxâ);
}
this.quantityInput.value = min;
publish(PUB_SUB_EVENTS.quantityUpdate, undefined);
}
fetchQuantityRules() {
const currentVariantId = this.productForm?.variantIdInput?.value;
if (!currentVariantId) return;
this.querySelector(â.quantity__rules-cart .loading__spinnerâ).classList.remove(âhiddenâ);
fetch(${this.dataset.url}?variant=${currentVariantId}§ion_id=${this.dataset.section})
.then((response) => response.text())
.then((responseText) => {
const html = new DOMParser().parseFromString(responseText, âtext/htmlâ);
this.updateQuantityRules(this.dataset.section, html);
})
.catch((e) => console.error(e))
.finally(() => this.querySelector(â.quantity__rules-cart .loading__spinnerâ).classList.add(âhiddenâ));
}
updateQuantityRules(sectionId, html) {
if (!this.quantityInput) return;
this.setQuantityBoundries();
const quantityFormUpdated = html.getElementById(Quantity-Form-${sectionId});
const selectors = [â.quantity__inputâ, â.quantity__rulesâ, â.quantity__labelâ];
for (let selector of selectors) {
const current = this.quantityForm.querySelector(selector);
const updated = quantityFormUpdated.querySelector(selector);
if (!current || !updated) continue;
if (selector === â.quantity__inputâ) {
const attributes = [âdata-cart-quantityâ, âdata-minâ, âdata-maxâ, âstepâ];
for (let attribute of attributes) {
const valueUpdated = updated.getAttribute(attribute);
if (valueUpdated !== null) {
current.setAttribute(attribute, valueUpdated);
} else {
current.removeAttribute(attribute);
}
}
} else {
current.innerHTML = updated.innerHTML;
}
}
}
get productForm() {
return this.querySelector(product-form);
}
get productModal() {
return document.querySelector(#ProductModal-${this.dataset.section});
}
get pickupAvailability() {
return this.querySelector(pickup-availability);
}
get variantSelectors() {
return this.querySelector(âvariant-selectsâ);
}
get relatedProducts() {
const relatedProductsSectionId = SectionId.getIdForSection(
SectionId.parseId(this.sectionId),
ârelated-productsâ
);
return document.querySelector(product-recommendations[data-section-id^="${relatedProductsSectionId}"]);
}
get quickOrderList() {
const quickOrderListSectionId = SectionId.getIdForSection(
SectionId.parseId(this.sectionId),
âquick_order_listâ
);
return document.querySelector(quick-order-list[data-id^="${quickOrderListSectionId}"]);
}
get sectionId() {
return this.dataset.originalSection || this.dataset.section;
}
}
);
}
////////// Third step /////////////////
Open global.js and replace with this code
function getFocusableElements(container) {
return Array.from(
container.querySelectorAll(
âsummary, a[href], button:enabled, [tabindex]:not([tabindex^=â-â]), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframeâ
)
);
}
class SectionId {
static #separator = â__â;
// for a qualified section id (e.g. âtemplateâ22224696705326__mainâ), return just the section id (e.g. âtemplateâ22224696705326â)
static parseId(qualifiedSectionId) {
return qualifiedSectionId.split(SectionId.#separator)[0];
}
// for a qualified section id (e.g. âtemplateâ22224696705326__mainâ), return just the section name (e.g. âmainâ)
static parseSectionName(qualifiedSectionId) {
return qualifiedSectionId.split(SectionId.#separator)[1];
}
// for a section id (e.g. âtemplateâ22224696705326â) and a section name (e.g. ârecommended-productsâ), return a qualified section id (e.g. âtemplateâ22224696705326__recommended-productsâ)
static getIdForSection(sectionId, sectionName) {
return ${sectionId}${SectionId.#separator}${sectionName};
}
}
class HTMLUpdateUtility {
/**
- Used to swap an HTML node with a new node.
- The new node is inserted as a previous sibling to the old node, the old node is hidden, and then the old node is removed.
- The function currently uses a double buffer approach, but this should be replaced by a view transition once it is more widely supported https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
*/
static viewTransition(oldNode, newContent, preProcessCallbacks = , postProcessCallbacks = ) {
preProcessCallbacks?.forEach((callback) => callback(newContent));
const newNodeWrapper = document.createElement(âdivâ);
HTMLUpdateUtility.setInnerHTML(newNodeWrapper, newContent.outerHTML);
const newNode = newNodeWrapper.firstChild;
// dedupe IDs
const uniqueKey = Date.now();
oldNode.querySelectorAll(â[id], [form]â).forEach((element) => {
element.id && (element.id = ${element.id}-${uniqueKey});
element.form && element.setAttribute(âformâ, ${element.form.getAttribute('id')}-${uniqueKey});
});
oldNode.parentNode.insertBefore(newNode, oldNode);
oldNode.style.display = ânoneâ;
postProcessCallbacks?.forEach((callback) => callback(newNode));
setTimeout(() => oldNode.remove(), 500);
}
// Sets inner HTML and reinjects the script tags to allow execution. By default, scripts are disabled when using element.innerHTML.
static setInnerHTML(element, html) {
element.innerHTML = html;
element.querySelectorAll(âscriptâ).forEach((oldScriptTag) => {
const newScriptTag = document.createElement(âscriptâ);
Array.from(oldScriptTag.attributes).forEach((attribute) => {
newScriptTag.setAttribute(attribute.name, attribute.value);
});
newScriptTag.appendChild(document.createTextNode(oldScriptTag.innerHTML));
oldScriptTag.parentNode.replaceChild(newScriptTag, oldScriptTag);
});
}
}
document.querySelectorAll(â[id^=âDetails-â] summaryâ).forEach((summary) => {
summary.setAttribute(âroleâ, âbuttonâ);
summary.setAttribute(âaria-expandedâ, summary.parentNode.hasAttribute(âopenâ));
if (summary.nextElementSibling.getAttribute(âidâ)) {
summary.setAttribute(âaria-controlsâ, summary.nextElementSibling.id);
}
summary.addEventListener(âclickâ, (event) => {
event.currentTarget.setAttribute(âaria-expandedâ, !event.currentTarget.closest(âdetailsâ).hasAttribute(âopenâ));
});
if (summary.closest(âheader-drawer, menu-drawerâ)) return;
summary.parentElement.addEventListener(âkeyupâ, onKeyUpEscape);
});
const trapFocusHandlers = {};
function trapFocus(container, elementToFocus = container) {
var elements = getFocusableElements(container);
var first = elements[0];
var last = elements[elements.length - 1];
removeTrapFocus();
trapFocusHandlers.focusin = (event) => {
if (event.target !== container && event.target !== last && event.target !== first) return;
document.addEventListener(âkeydownâ, trapFocusHandlers.keydown);
};
trapFocusHandlers.focusout = function () {
document.removeEventListener(âkeydownâ, trapFocusHandlers.keydown);
};
trapFocusHandlers.keydown = function (event) {
if (event.code.toUpperCase() !== âTABâ) return; // If not TAB key
// On the last focusable element and tab forward, focus the first element.
if (event.target === last && !event.shiftKey) {
event.preventDefault();
first.focus();
}
// On the first focusable element and tab backward, focus the last element.
if ((event.target === container || event.target === first) && event.shiftKey) {
event.preventDefault();
last.focus();
}
};
document.addEventListener(âfocusoutâ, trapFocusHandlers.focusout);
document.addEventListener(âfocusinâ, trapFocusHandlers.focusin);
elementToFocus.focus();
if (
elementToFocus.tagName === âINPUTâ &&
[âsearchâ, âtextâ, âemailâ, âurlâ].includes(elementToFocus.type) &&
elementToFocus.value
) {
elementToFocus.setSelectionRange(0, elementToFocus.value.length);
}
}
// Here run the querySelector to figure out if the browser supports :focus-visible or not and run code based on it.
try {
document.querySelector(â:focus-visibleâ);
} catch (e) {
focusVisiblePolyfill();
}
function focusVisiblePolyfill() {
const navKeys = [
âARROWUPâ,
âARROWDOWNâ,
âARROWLEFTâ,
âARROWRIGHTâ,
âTABâ,
âENTERâ,
âSPACEâ,
âESCAPEâ,
âHOMEâ,
âENDâ,
âPAGEUPâ,
âPAGEDOWNâ,
];
let currentFocusedElement = null;
let mouseClick = null;
window.addEventListener(âkeydownâ, (event) => {
if (navKeys.includes(event.code.toUpperCase())) {
mouseClick = false;
}
});
window.addEventListener(âmousedownâ, (event) => {
mouseClick = true;
});
window.addEventListener(
âfocusâ,
() => {
if (currentFocusedElement) currentFocusedElement.classList.remove(âfocusedâ);
if (mouseClick) return;
currentFocusedElement = document.activeElement;
currentFocusedElement.classList.add(âfocusedâ);
},
true
);
}
function pauseAllMedia() {
document.querySelectorAll(â.js-youtubeâ).forEach((video) => {
video.contentWindow.postMessage(â{âeventâ:âcommandâ,âfuncâ:"â + âpauseVideoâ + ââ,âargsâ:â"}â, ââ);
});
document.querySelectorAll(â.js-vimeoâ).forEach((video) => {
video.contentWindow.postMessage(â{âmethodâ:âpauseâ}â, 'â);
});
document.querySelectorAll(âvideoâ).forEach((video) => video.pause());
document.querySelectorAll(âproduct-modelâ).forEach((model) => {
if (model.modelViewerUI) model.modelViewerUI.pause();
});
}
function removeTrapFocus(elementToFocus = null) {
document.removeEventListener(âfocusinâ, trapFocusHandlers.focusin);
document.removeEventListener(âfocusoutâ, trapFocusHandlers.focusout);
document.removeEventListener(âkeydownâ, trapFocusHandlers.keydown);
if (elementToFocus) elementToFocus.focus();
}
function onKeyUpEscape(event) {
if (event.code.toUpperCase() !== âESCAPEâ) return;
const openDetailsElement = event.target.closest(âdetails[open]â);
if (!openDetailsElement) return;
const summaryElement = openDetailsElement.querySelector(âsummaryâ);
openDetailsElement.removeAttribute(âopenâ);
summaryElement.setAttribute(âaria-expandedâ, false);
summaryElement.focus();
}
class QuantityInput extends HTMLElement {
constructor() {
super();
this.input = this.querySelector(âinputâ);
this.changeEvent = new Event(âchangeâ, { bubbles: true });
this.input.addEventListener(âchangeâ, this.onInputChange.bind(this));
this.querySelectorAll(âbuttonâ).forEach((button) =>
button.addEventListener(âclickâ, this.onButtonClick.bind(this))
);
}
quantityUpdateUnsubscriber = undefined;
connectedCallback() {
this.validateQtyRules();
this.quantityUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.quantityUpdate, this.validateQtyRules.bind(this));
}
disconnectedCallback() {
if (this.quantityUpdateUnsubscriber) {
this.quantityUpdateUnsubscriber();
}
}
onInputChange(event) {
this.validateQtyRules();
}
onButtonClick(event) {
event.preventDefault();
const previousValue = this.input.value;
if (event.target.name === âplusâ) {
if (parseInt(this.input.dataset.min) > parseInt(this.input.step) && this.input.value == 0) {
this.input.value = this.input.dataset.min;
} else {
this.input.stepUp();
}
} else {
this.input.stepDown();
}
if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent);
if (this.input.dataset.min === previousValue && event.target.name === âminusâ) {
this.input.value = parseInt(this.input.min);
}
}
validateQtyRules() {
const value = parseInt(this.input.value);
if (this.input.min) {
const buttonMinus = this.querySelector(â.quantity__button[name=âminusâ]â);
buttonMinus.classList.toggle(âdisabledâ, parseInt(value) <= parseInt(this.input.min));
}
if (this.input.max) {
const max = parseInt(this.input.max);
const buttonPlus = this.querySelector(â.quantity__button[name=âplusâ]â);
buttonPlus.classList.toggle(âdisabledâ, value >= max);
}
}
}
customElements.define(âquantity-inputâ, QuantityInput);
function debounce(fn, wait) {
let t;
return (âŚargs) => {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
function throttle(fn, delay) {
let lastCall = 0;
return function (âŚargs) {
const now = new Date().getTime();
if (now - lastCall < delay) {
return;
}
lastCall = now;
return fn(âŚargs);
};
}
function fetchConfig(type = âjsonâ) {
return {
method: âPOSTâ,
headers: { âContent-Typeâ: âapplication/jsonâ, Accept: application/${type} },
};
}
/*
- Shopify Common JS
*/
if (typeof window.Shopify == âundefinedâ) {
window.Shopify = {};
}
Shopify.bind = function (fn, scope) {
return function () {
return fn.apply(scope, arguments);
};
};
Shopify.setSelectorByValue = function (selector, value) {
for (var i = 0, count = selector.options.length; i < count; i++) {
var option = selector.options[i];
if (value == option.value || value == option.innerHTML) {
selector.selectedIndex = i;
return i;
}
}
};
Shopify.addListener = function (target, eventName, callback) {
target.addEventListener
? target.addEventListener(eventName, callback, false)
: target.attachEvent(âonâ + eventName, callback);
};
Shopify.postLink = function (path, options) {
options = options || {};
var method = options[âmethodâ] || âpostâ;
var params = options[âparametersâ] || {};
var form = document.createElement(âformâ);
form.setAttribute(âmethodâ, method);
form.setAttribute(âactionâ, path);
for (var key in params) {
var hiddenField = document.createElement(âinputâ);
hiddenField.setAttribute(âtypeâ, âhiddenâ);
hiddenField.setAttribute(ânameâ, key);
hiddenField.setAttribute(âvalueâ, params[key]);
form.appendChild(hiddenField);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
};
Shopify.CountryProvinceSelector = function (country_domid, province_domid, options) {
this.countryEl = document.getElementById(country_domid);
this.provinceEl = document.getElementById(province_domid);
this.provinceContainer = document.getElementById(options[âhideElementâ] || province_domid);
Shopify.addListener(this.countryEl, âchangeâ, Shopify.bind(this.countryHandler, this));
this.initCountry();
this.initProvince();
};
Shopify.CountryProvinceSelector.prototype = {
initCountry: function () {
var value = this.countryEl.getAttribute(âdata-defaultâ);
Shopify.setSelectorByValue(this.countryEl, value);
this.countryHandler();
},
initProvince: function () {
var value = this.provinceEl.getAttribute(âdata-defaultâ);
if (value && this.provinceEl.options.length > 0) {
Shopify.setSelectorByValue(this.provinceEl, value);
}
},
countryHandler: function (e) {
var opt = this.countryEl.options[this.countryEl.selectedIndex];
var raw = opt.getAttribute(âdata-provincesâ);
var provinces = JSON.parse(raw);
this.clearOptions(this.provinceEl);
if (provinces && provinces.length == 0) {
this.provinceContainer.style.display = ânoneâ;
} else {
for (var i = 0; i < provinces.length; i++) {
var opt = document.createElement(âoptionâ);
opt.value = provinces[i][0];
opt.innerHTML = provinces[i][1];
this.provinceEl.appendChild(opt);
}
this.provinceContainer.style.display = ââ;
}
},
clearOptions: function (selector) {
while (selector.firstChild) {
selector.removeChild(selector.firstChild);
}
},
setOptions: function (selector, values) {
for (var i = 0, count = values.length; i < values.length; i++) {
var opt = document.createElement(âoptionâ);
opt.value = values[i];
opt.innerHTML = values[i];
selector.appendChild(opt);
}
},
};
class MenuDrawer extends HTMLElement {
constructor() {
super();
this.mainDetailsToggle = this.querySelector(âdetailsâ);
this.addEventListener(âkeyupâ, this.onKeyUp.bind(this));
this.addEventListener(âfocusoutâ, this.onFocusOut.bind(this));
this.bindEvents();
}
bindEvents() {
this.querySelectorAll(âsummaryâ).forEach((summary) =>
summary.addEventListener(âclickâ, this.onSummaryClick.bind(this))
);
this.querySelectorAll(
âbutton:not(.localization-selector):not(.country-selector__close-button):not(.country-filter__reset-button)â
).forEach((button) => button.addEventListener(âclickâ, this.onCloseButtonClick.bind(this)));
}
onKeyUp(event) {
if (event.code.toUpperCase() !== âESCAPEâ) return;
const openDetailsElement = event.target.closest(âdetails[open]â);
if (!openDetailsElement) return;
openDetailsElement === this.mainDetailsToggle
? this.closeMenuDrawer(event, this.mainDetailsToggle.querySelector(âsummaryâ))
: this.closeSubmenu(openDetailsElement);
}
onSummaryClick(event) {
const summaryElement = event.currentTarget;
const detailsElement = summaryElement.parentNode;
const parentMenuElement = detailsElement.closest(â.has-submenuâ);
const isOpen = detailsElement.hasAttribute(âopenâ);
const reducedMotion = window.matchMedia(â(prefers-reduced-motion: reduce)â);
function addTrapFocus() {
trapFocus(summaryElement.nextElementSibling, detailsElement.querySelector(âbuttonâ));
summaryElement.nextElementSibling.removeEventListener(âtransitionendâ, addTrapFocus);
}
if (detailsElement === this.mainDetailsToggle) {
if (isOpen) event.preventDefault();
isOpen ? this.closeMenuDrawer(event, summaryElement) : this.openMenuDrawer(summaryElement);
if (window.matchMedia(â(max-width: 990px)â)) {
document.documentElement.style.setProperty(ââviewport-heightâ, ${window.innerHeight}px);
}
} else {
setTimeout(() => {
detailsElement.classList.add(âmenu-openingâ);
summaryElement.setAttribute(âaria-expandedâ, true);
parentMenuElement && parentMenuElement.classList.add(âsubmenu-openâ);
!reducedMotion || reducedMotion.matches
? addTrapFocus()
: summaryElement.nextElementSibling.addEventListener(âtransitionendâ, addTrapFocus);
}, 100);
}
}
openMenuDrawer(summaryElement) {
setTimeout(() => {
this.mainDetailsToggle.classList.add(âmenu-openingâ);
});
summaryElement.setAttribute(âaria-expandedâ, true);
trapFocus(this.mainDetailsToggle, summaryElement);
document.body.classList.add(overflow-hidden-${this.dataset.breakpoint});
}
closeMenuDrawer(event, elementToFocus = false) {
if (event === undefined) return;
this.mainDetailsToggle.classList.remove(âmenu-openingâ);
this.mainDetailsToggle.querySelectorAll(âdetailsâ).forEach((details) => {
details.removeAttribute(âopenâ);
details.classList.remove(âmenu-openingâ);
});
this.mainDetailsToggle.querySelectorAll(â.submenu-openâ).forEach((submenu) => {
submenu.classList.remove(âsubmenu-openâ);
});
document.body.classList.remove(overflow-hidden-${this.dataset.breakpoint});
removeTrapFocus(elementToFocus);
this.closeAnimation(this.mainDetailsToggle);
if (event instanceof KeyboardEvent) elementToFocus?.setAttribute(âaria-expandedâ, false);
}
onFocusOut() {
setTimeout(() => {
if (this.mainDetailsToggle.hasAttribute(âopenâ) && !this.mainDetailsToggle.contains(document.activeElement))
this.closeMenuDrawer();
});
}
onCloseButtonClick(event) {
const detailsElement = event.currentTarget.closest(âdetailsâ);
this.closeSubmenu(detailsElement);
}
closeSubmenu(detailsElement) {
const parentMenuElement = detailsElement.closest(â.submenu-openâ);
parentMenuElement && parentMenuElement.classList.remove(âsubmenu-openâ);
detailsElement.classList.remove(âmenu-openingâ);
detailsElement.querySelector(âsummaryâ).setAttribute(âaria-expandedâ, false);
removeTrapFocus(detailsElement.querySelector(âsummaryâ));
this.closeAnimation(detailsElement);
}
closeAnimation(detailsElement) {
let animationStart;
const handleAnimation = (time) => {
if (animationStart === undefined) {
animationStart = time;
}
const elapsedTime = time - animationStart;
if (elapsedTime < 400) {
window.requestAnimationFrame(handleAnimation);
} else {
detailsElement.removeAttribute(âopenâ);
if (detailsElement.closest(âdetails[open]â)) {
trapFocus(detailsElement.closest(âdetails[open]â), detailsElement.querySelector(âsummaryâ));
}
}
};
window.requestAnimationFrame(handleAnimation);
}
}
customElements.define(âmenu-drawerâ, MenuDrawer);
class HeaderDrawer extends MenuDrawer {
constructor() {
super();
}
openMenuDrawer(summaryElement) {
this.header = this.header || document.querySelector(â.section-headerâ);
this.borderOffset =
this.borderOffset || this.closest(â.header-wrapperâ).classList.contains(âheader-wrapperâborder-bottomâ) ? 1 : 0;
document.documentElement.style.setProperty(
ââheader-bottom-positionâ,
${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px
);
this.header.classList.add(âmenu-openâ);
setTimeout(() => {
this.mainDetailsToggle.classList.add(âmenu-openingâ);
});
summaryElement.setAttribute(âaria-expandedâ, true);
window.addEventListener(âresizeâ, this.onResize);
trapFocus(this.mainDetailsToggle, summaryElement);
document.body.classList.add(overflow-hidden-${this.dataset.breakpoint});
}
closeMenuDrawer(event, elementToFocus) {
if (!elementToFocus) return;
super.closeMenuDrawer(event, elementToFocus);
this.header.classList.remove(âmenu-openâ);
window.removeEventListener(âresizeâ, this.onResize);
}
onResize = () => {
this.header &&
document.documentElement.style.setProperty(
ââheader-bottom-positionâ,
${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px
);
document.documentElement.style.setProperty(ââviewport-heightâ, ${window.innerHeight}px);
};
}
customElements.define(âheader-drawerâ, HeaderDrawer);
class ModalDialog extends HTMLElement {
constructor() {
super();
this.querySelector(â[id^=âModalClose-â]â).addEventListener(âclickâ, this.hide.bind(this, false));
this.addEventListener(âkeyupâ, (event) => {
if (event.code.toUpperCase() === âESCAPEâ) this.hide();
});
if (this.classList.contains(âmedia-modalâ)) {
this.addEventListener(âpointerupâ, (event) => {
if (event.pointerType === âmouseâ && !event.target.closest(âdeferred-media, product-modelâ)) this.hide();
});
} else {
this.addEventListener(âclickâ, (event) => {
if (event.target === this) this.hide();
});
}
}
connectedCallback() {
if (this.moved) return;
this.moved = true;
document.body.appendChild(this);
}
show(opener) {
this.openedBy = opener;
const popup = this.querySelector(â.template-popupâ);
document.body.classList.add(âoverflow-hiddenâ);
this.setAttribute(âopenâ, ââ);
if (popup) popup.loadContent();
trapFocus(this, this.querySelector(â[role=âdialogâ]â));
window.pauseAllMedia();
}
hide() {
document.body.classList.remove(âoverflow-hiddenâ);
document.body.dispatchEvent(new CustomEvent(âmodalClosedâ));
this.removeAttribute(âopenâ);
removeTrapFocus(this.openedBy);
window.pauseAllMedia();
}
}
customElements.define(âmodal-dialogâ, ModalDialog);
class BulkModal extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const handleIntersection = (entries, observer) => {
if (!entries[0].isIntersecting) return;
observer.unobserve(this);
if (this.innerHTML.trim() === ââ) {
const productUrl = this.dataset.url.split(â?â)[0];
fetch(${productUrl}?section_id=bulk-quick-order-list)
.then((response) => response.text())
.then((responseText) => {
const html = new DOMParser().parseFromString(responseText, âtext/htmlâ);
const sourceQty = html.querySelector(â.quick-order-list-containerâ).parentNode;
this.innerHTML = sourceQty.innerHTML;
})
.catch((e) => {
console.error(e);
});
}
};
new IntersectionObserver(handleIntersection.bind(this)).observe(
document.querySelector(#QuickBulk-${this.dataset.productId}-${this.dataset.sectionId})
);
}
}
customElements.define(âbulk-modalâ, BulkModal);
class ModalOpener extends HTMLElement {
constructor() {
super();
const button = this.querySelector(âbuttonâ);
if (!button) return;
button.addEventListener(âclickâ, () => {
const modal = document.querySelector(this.getAttribute(âdata-modalâ));
if (modal) modal.show(button);
});
}
}
customElements.define(âmodal-openerâ, ModalOpener);
class DeferredMedia extends HTMLElement {
constructor() {
super();
const poster = this.querySelector(â[id^=âDeferred-Poster-â]â);
if (!poster) return;
poster.addEventListener(âclickâ, this.loadContent.bind(this));
}
loadContent(focus = true) {
window.pauseAllMedia();
if (!this.getAttribute(âloadedâ)) {
const content = document.createElement(âdivâ);
content.appendChild(this.querySelector(âtemplateâ).content.firstElementChild.cloneNode(true));
this.setAttribute(âloadedâ, true);
const deferredElement = this.appendChild(content.querySelector(âvideo, model-viewer, iframeâ));
if (focus) deferredElement.focus();
if (deferredElement.nodeName == âVIDEOâ && deferredElement.getAttribute(âautoplayâ)) {
// force autoplay for safari
deferredElement.play();
}
}
}
}
customElements.define(âdeferred-mediaâ, DeferredMedia);
class SliderComponent extends HTMLElement {
constructor() {
super();
this.slider = this.querySelector(â[id^=âSlider-â]â);
this.sliderItems = this.querySelectorAll(â[id^=âSlide-â]â);
this.enableSliderLooping = false;
this.currentPageElement = this.querySelector(â.slider-counterâcurrentâ);
this.pageTotalElement = this.querySelector(â.slider-counterâtotalâ);
this.prevButton = this.querySelector(âbutton[name=âpreviousâ]â);
this.nextButton = this.querySelector(âbutton[name=ânextâ]â);
if (!this.slider || !this.nextButton) return;
this.initPages();
const resizeObserver = new ResizeObserver((entries) => this.initPages());
resizeObserver.observe(this.slider);
this.slider.addEventListener(âscrollâ, this.update.bind(this));
this.prevButton.addEventListener(âclickâ, this.onButtonClick.bind(this));
this.nextButton.addEventListener(âclickâ, this.onButtonClick.bind(this));
}
initPages() {
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.slidesPerPage = Math.floor(
(this.slider.clientWidth - this.sliderItemsToShow[0].offsetLeft) / this.sliderItemOffset
);
this.totalPages = this.sliderItemsToShow.length - this.slidesPerPage + 1;
this.update();
}
resetPages() {
this.sliderItems = this.querySelectorAll(â[id^=âSlide-â]â);
this.initPages();
}
update() {
// Temporarily prevents unneeded updates resulting from variant changes
// This should be refactored as part of https://github.com/Shopify/dawn/issues/2057
if (!this.slider || !this.nextButton) return;
const previousPage = this.currentPage;
this.currentPage = Math.round(this.slider.scrollLeft / this.sliderItemOffset) + 1;
if (this.currentPageElement && this.pageTotalElement) {
this.currentPageElement.textContent = this.currentPage;
this.pageTotalElement.textContent = this.totalPages;
}
if (this.currentPage != previousPage) {
this.dispatchEvent(
new CustomEvent(âslideChangedâ, {
detail: {
currentPage: this.currentPage,
currentElement: this.sliderItemsToShow[this.currentPage - 1],
},
})
);
}
if (this.enableSliderLooping) return;
if (this.isSlideVisible(this.sliderItemsToShow[0]) && this.slider.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.slider.clientWidth + this.slider.scrollLeft - offset;
return element.offsetLeft + element.clientWidth <= lastVisibleSlide && element.offsetLeft >= this.slider.scrollLeft;
}
onButtonClick(event) {
event.preventDefault();
const step = event.currentTarget.dataset.step || 1;
this.slideScrollPosition =
event.currentTarget.name === ânextâ
? this.slider.scrollLeft + step * this.sliderItemOffset
: this.slider.scrollLeft - step * this.sliderItemOffset;
this.setSlidePosition(this.slideScrollPosition);
}
setSlidePosition(position) {
this.slider.scrollTo({
left: position,
});
}
}
customElements.define(âslider-componentâ, SliderComponent);
class SlideshowComponent extends SliderComponent {
constructor() {
super();
this.sliderControlWrapper = this.querySelector(â.slider-buttonsâ);
this.enableSliderLooping = true;
if (!this.sliderControlWrapper) return;
this.sliderFirstItemNode = this.slider.querySelector(â.slideshow__slideâ);
if (this.sliderItemsToShow.length > 0) this.currentPage = 1;
this.announcementBarSlider = this.querySelector(â.announcement-bar-sliderâ);
// Value below should match --duration-announcement-bar CSS value
this.announcerBarAnimationDelay = this.announcementBarSlider ? 250 : 0;
this.sliderControlLinksArray = Array.from(this.sliderControlWrapper.querySelectorAll(â.slider-counter__linkâ));
this.sliderControlLinksArray.forEach((link) => link.addEventListener(âclickâ, this.linkToSlide.bind(this)));
this.slider.addEventListener(âscrollâ, this.setSlideVisibility.bind(this));
this.setSlideVisibility();
if (this.announcementBarSlider) {
this.announcementBarArrowButtonWasClicked = false;
this.reducedMotion = window.matchMedia(â(prefers-reduced-motion: reduce)â);
this.reducedMotion.addEventListener(âchangeâ, () => {
if (this.slider.getAttribute(âdata-autoplayâ) === âtrueâ) this.setAutoPlay();
});
[this.prevButton, this.nextButton].forEach((button) => {
button.addEventListener(
âclickâ,
() => {
this.announcementBarArrowButtonWasClicked = true;
},
{ once: true }
);
});
}
if (this.slider.getAttribute(âdata-autoplayâ) === âtrueâ) this.setAutoPlay();
}
setAutoPlay() {
this.autoplaySpeed = this.slider.dataset.speed * 1000;
this.addEventListener(âmouseoverâ, this.focusInHandling.bind(this));
this.addEventListener(âmouseleaveâ, this.focusOutHandling.bind(this));
this.addEventListener(âfocusinâ, this.focusInHandling.bind(this));
this.addEventListener(âfocusoutâ, this.focusOutHandling.bind(this));
if (this.querySelector(â.slideshow__autoplayâ)) {
this.sliderAutoplayButton = this.querySelector(â.slideshow__autoplayâ);
this.sliderAutoplayButton.addEventListener(âclickâ, this.autoPlayToggle.bind(this));
this.autoplayButtonIsSetToPlay = true;
this.play();
} else {
this.reducedMotion.matches || this.announcementBarArrowButtonWasClicked ? this.pause() : this.play();
}
}
onButtonClick(event) {
super.onButtonClick(event);
this.wasClicked = true;
const isFirstSlide = this.currentPage === 1;
const isLastSlide = this.currentPage === this.sliderItemsToShow.length;
if (!isFirstSlide && !isLastSlide) {
this.applyAnimationToAnnouncementBar(event.currentTarget.name);
return;
}
if (isFirstSlide && event.currentTarget.name === âpreviousâ) {
this.slideScrollPosition =
this.slider.scrollLeft + this.sliderFirstItemNode.clientWidth * this.sliderItemsToShow.length;
} else if (isLastSlide && event.currentTarget.name === ânextâ) {
this.slideScrollPosition = 0;
}
this.setSlidePosition(this.slideScrollPosition);
this.applyAnimationToAnnouncementBar(event.currentTarget.name);
}
setSlidePosition(position) {
if (this.setPositionTimeout) clearTimeout(this.setPositionTimeout);
this.setPositionTimeout = setTimeout(() => {
this.slider.scrollTo({
left: position,
});
}, this.announcerBarAnimationDelay);
}
update() {
super.update();
this.sliderControlButtons = this.querySelectorAll(â.slider-counter__linkâ);
this.prevButton.removeAttribute(âdisabledâ);
if (!this.sliderControlButtons.length) return;
this.sliderControlButtons.forEach((link) => {
link.classList.remove(âslider-counter__linkâactiveâ);
link.removeAttribute(âaria-currentâ);
});
this.sliderControlButtons[this.currentPage - 1].classList.add(âslider-counter__linkâactiveâ);
this.sliderControlButtons[this.currentPage - 1].setAttribute(âaria-currentâ, true);
}
autoPlayToggle() {
this.togglePlayButtonState(this.autoplayButtonIsSetToPlay);
this.autoplayButtonIsSetToPlay ? this.pause() : this.play();
this.autoplayButtonIsSetToPlay = !this.autoplayButtonIsSetToPlay;
}
focusOutHandling(event) {
if (this.sliderAutoplayButton) {
const focusedOnAutoplayButton =
event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target);
if (!this.autoplayButtonIsSetToPlay || focusedOnAutoplayButton) return;
this.play();
} else if (!this.reducedMotion.matches && !this.announcementBarArrowButtonWasClicked) {
this.play();
}
}
focusInHandling(event) {
if (this.sliderAutoplayButton) {
const focusedOnAutoplayButton =
event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target);
if (focusedOnAutoplayButton && this.autoplayButtonIsSetToPlay) {
this.play();
} else if (this.autoplayButtonIsSetToPlay) {
this.pause();
}
} else if (this.announcementBarSlider.contains(event.target)) {
this.pause();
}
}
play() {
this.slider.setAttribute(âaria-liveâ, âoffâ);
clearInterval(this.autoplay);
this.autoplay = setInterval(this.autoRotateSlides.bind(this), this.autoplaySpeed);
}
pause() {
this.slider.setAttribute(âaria-liveâ, âpoliteâ);
clearInterval(this.autoplay);
}
togglePlayButtonState(pauseAutoplay) {
if (pauseAutoplay) {
this.sliderAutoplayButton.classList.add(âslideshow__autoplayâpausedâ);
this.sliderAutoplayButton.setAttribute(âaria-labelâ, window.accessibilityStrings.playSlideshow);
} else {
this.sliderAutoplayButton.classList.remove(âslideshow__autoplayâpausedâ);
this.sliderAutoplayButton.setAttribute(âaria-labelâ, window.accessibilityStrings.pauseSlideshow);
}
}
autoRotateSlides() {
const slideScrollPosition =
this.currentPage === this.sliderItems.length ? 0 : this.slider.scrollLeft + this.sliderItemOffset;
this.setSlidePosition(slideScrollPosition);
this.applyAnimationToAnnouncementBar();
}
setSlideVisibility(event) {
this.sliderItemsToShow.forEach((item, index) => {
const linkElements = item.querySelectorAll(âaâ);
if (index === this.currentPage - 1) {
if (linkElements.length)
linkElements.forEach((button) => {
button.removeAttribute(âtabindexâ);
});
item.setAttribute(âaria-hiddenâ, âfalseâ);
item.removeAttribute(âtabindexâ);
} else {
if (linkElements.length)
linkElements.forEach((button) => {
button.setAttribute(âtabindexâ, â-1â);
});
item.setAttribute(âaria-hiddenâ, âtrueâ);
item.setAttribute(âtabindexâ, â-1â);
}
});
this.wasClicked = false;
}
applyAnimationToAnnouncementBar(button = ânextâ) {
if (!this.announcementBarSlider) return;
const itemsCount = this.sliderItems.length;
const increment = button === ânextâ ? 1 : -1;
const currentIndex = this.currentPage - 1;
let nextIndex = (currentIndex + increment) % itemsCount;
nextIndex = nextIndex === -1 ? itemsCount - 1 : nextIndex;
const nextSlide = this.sliderItems[nextIndex];
const currentSlide = this.sliderItems[currentIndex];
const animationClassIn = âannouncement-bar-sliderâfade-inâ;
const animationClassOut = âannouncement-bar-sliderâfade-outâ;
const isFirstSlide = currentIndex === 0;
const isLastSlide = currentIndex === itemsCount - 1;
const shouldMoveNext = (button === ânextâ && !isLastSlide) || (button === âpreviousâ && isFirstSlide);
const direction = shouldMoveNext ? ânextâ : âpreviousâ;
currentSlide.classList.add(${animationClassOut}-${direction});
nextSlide.classList.add(${animationClassIn}-${direction});
setTimeout(() => {
currentSlide.classList.remove(${animationClassOut}-${direction});
nextSlide.classList.remove(${animationClassIn}-${direction});
}, this.announcerBarAnimationDelay * 2);
}
linkToSlide(event) {
event.preventDefault();
const slideScrollPosition =
this.slider.scrollLeft +
this.sliderFirstItemNode.clientWidth *
(this.sliderControlLinksArray.indexOf(event.currentTarget) + 1 - this.currentPage);
this.slider.scrollTo({
left: slideScrollPosition,
});
}
}
customElements.define(âslideshow-componentâ, SlideshowComponent);
class VariantSelects extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.addEventListener(âchangeâ, (event) => {
const target = this.getInputForEventTarget(event.target);
this.updateSelectionMetadata(event);
publish(PUB_SUB_EVENTS.optionValueSelectionChange, {
data: {
event,
target,
selectedOptionValues: this.selectedOptionValues,
},
});
});
document.addEventListener(âDOMContentLoadedâ, (event) => {
const target = this.getInputForEventTarget(event.target);
// this.updateSelectionMetadata(event);
publish(PUB_SUB_EVENTS.onVariantLoadChange, {
data: {
target
},
});
});
}
updateSelectionMetadata({ target }) {
const { value, tagName } = target;
if (tagName === âSELECTâ && target.selectedOptions.length) {
Array.from(target.options)
.find((option) => option.getAttribute(âselectedâ))
.removeAttribute(âselectedâ);
target.selectedOptions[0].setAttribute(âselectedâ, âselectedâ);
const swatchValue = target.selectedOptions[0].dataset.optionSwatchValue;
const selectedDropdownSwatchValue = target
.closest(â.product-form__inputâ)
.querySelector(â[data-selected-value] > .swatchâ);
if (!selectedDropdownSwatchValue) return;
if (swatchValue) {
selectedDropdownSwatchValue.style.setProperty(ââswatchâbackgroundâ, swatchValue);
selectedDropdownSwatchValue.classList.remove(âswatchâunavailableâ);
} else {
selectedDropdownSwatchValue.style.setProperty(ââswatchâbackgroundâ, âunsetâ);
selectedDropdownSwatchValue.classList.add(âswatchâunavailableâ);
}
selectedDropdownSwatchValue.style.setProperty(
ââswatch-focal-pointâ,
target.selectedOptions[0].dataset.optionSwatchFocalPoint || âunsetâ
);
} else if (tagName === âINPUTâ && target.type === âradioâ) {
const selectedSwatchValue = target.closest(.product-form__input).querySelector(â[data-selected-value]â);
if (selectedSwatchValue) selectedSwatchValue.innerHTML = value;
}
}
getInputForEventTarget(target) {
return target.tagName === âSELECTâ ? target.selectedOptions[0] : target;
}
get selectedOptionValues() {
return Array.from(this.querySelectorAll(âselect option[selected], fieldset input:checkedâ)).map(
({ dataset }) => dataset.optionValueId
);
}
}
customElements.define(âvariant-selectsâ, VariantSelects);
class ProductRecommendations extends HTMLElement {
observer = undefined;
constructor() {
super();
}
connectedCallback() {
this.initializeRecommendations(this.dataset.productId);
}
initializeRecommendations(productId) {
this.observer?.unobserve(this);
this.observer = new IntersectionObserver(
(entries, observer) => {
if (!entries[0].isIntersecting) return;
observer.unobserve(this);
this.loadRecommendations(productId);
},
{ rootMargin: â0px 0px 400px 0pxâ }
);
this.observer.observe(this);
}
loadRecommendations(productId) {
fetch(${this.dataset.url}&product_id=${productId}§ion_id=${this.dataset.sectionId})
.then((response) => response.text())
.then((text) => {
const html = document.createElement(âdivâ);
html.innerHTML = text;
const recommendations = html.querySelector(âproduct-recommendationsâ);
if (recommendations?.innerHTML.trim().length) {
this.innerHTML = recommendations.innerHTML;
}
if (!this.querySelector(âslideshow-componentâ) && this.classList.contains(âcomplementary-productsâ)) {
this.remove();
}
if (html.querySelector(â.grid__itemâ)) {
this.classList.add(âproduct-recommendationsâloadedâ);
}
})
.catch((e) => {
console.error(e);
});
}
}
customElements.define(âproduct-recommendationsâ, ProductRecommendations);
class AccountIcon extends HTMLElement {
constructor() {
super();
this.icon = this.querySelector(â.iconâ);
}
connectedCallback() {
document.addEventListener(âstorefront:signincompletedâ, this.handleStorefrontSignInCompleted.bind(this));
}
handleStorefrontSignInCompleted(event) {
if (event?.detail?.avatar) {
this.icon?.replaceWith(event.detail.avatar.cloneNode());
}
}
}
customElements.define(âaccount-iconâ, AccountIcon);
class BulkAdd extends HTMLElement {
constructor() {
super();
this.queue = ;
this.requestStarted = false;
this.ids = ;
}
startQueue(id, quantity) {
this.queue.push({ id, quantity });
const interval = setInterval(() => {
if (this.queue.length > 0) {
if (!this.requestStarted) {
this.sendRequest(this.queue);
}
} else {
clearInterval(interval);
}
}, 250);
}
sendRequest(queue) {
this.requestStarted = true;
const items = {};
queue.forEach((queueItem) => {
items[parseInt(queueItem.id)] = queueItem.quantity;
});
this.queue = this.queue.filter((queueElement) => !queue.includes(queueElement));
const quickBulkElement = this.closest(âquick-order-listâ) || this.closest(âquick-add-bulkâ);
quickBulkElement.updateMultipleQty(items);
}
resetQuantityInput(id) {
const input = this.querySelector(#Quantity-${id});
input.value = input.getAttribute(âvalueâ);
this.isEnterPressed = false;
}
setValidity(event, index, message) {
event.target.setCustomValidity(message);
event.target.reportValidity();
this.resetQuantityInput(index);
event.target.select();
}
validateQuantity(event) {
const inputValue = parseInt(event.target.value);
const index = event.target.dataset.index;
if (inputValue < event.target.dataset.min) {
this.setValidity(event, index, window.quickOrderListStrings.min_error.replace(â[min]â, event.target.dataset.min));
} else if (inputValue > parseInt(event.target.max)) {
this.setValidity(event, index, window.quickOrderListStrings.max_error.replace(â[max]â, event.target.max));
} else if (inputValue % parseInt(event.target.step) != 0) {
this.setValidity(event, index, window.quickOrderListStrings.step_error.replace(â[step]â, event.target.step));
} else {
event.target.setCustomValidity(ââ);
event.target.reportValidity();
this.startQueue(index, inputValue);
}
}
getSectionsUrl() {
if (window.pageNumber) {
return ${window.location.pathname}?page=${window.pageNumber};
} else {
return ${window.location.pathname};
}
}
getSectionInnerHTML(html, selector) {
return new DOMParser().parseFromString(html, âtext/htmlâ).querySelector(selector).innerHTML;
}
}
if (!customElements.get(âbulk-addâ)) {
customElements.define(âbulk-addâ, BulkAdd);
}
//////// Fourth Step ////////
Open product-variant-options.liquid and replace this code. This is for the images inside color variants
{% comment %}
Renders product variant options
Accepts:
- product: {Object} product object.
- option: {Object} current product_option object.
- block: {Object} block object.
- picker_type: {String} type of picker to dispay
Usage:
{% render âproduct-variant-optionsâ,
product: product,
option: option,
block: block
picker_type: picker_type
%}
{% endcomment %}
{%- liquid
assign product_form_id = âproduct-form-â | append: section.id
-%}
{%- for value in option.values -%}
{%- liquid
assign swatch_focal_point = null
if value.swatch.image
assign image_url = value.swatch.image | image_url: width: 50
assign swatch_value = âurl(â | append: image_url | append: â)â
assign swatch_focal_point = value.swatch.image.presentation.focal_point
elsif value.swatch.color
assign swatch_value = ârgb(â | append: value.swatch.color.rgb | append: â)â
else
assign swatch_value = null
endif
assign option_disabled = true
if value.available
assign option_disabled = false
endif
-%}
{%- capture input_id -%}
{{ section.id }}-{{ option.position }}-{{ forloop.index0 -}}
{%- endcapture -%}
{%- capture input_name -%}
{{ option.name }}-{{ option.position }}
{%- endcapture -%}
{%- capture input_dataset -%}
data-product-url=â{{ value.product_url }}â
data-option-value-id=â{{ value.id }}â
{%- endcapture -%}
{%- capture label_unavailable -%}
{{- âproducts.product.variant_sold_out_or_unavailableâ | t -}}
{%- endcapture -%}
{%- if picker_type == âswatchâ -%}
{%- capture help_text -%}
{{ value | escape }}
{{ label_unavailable }}
{%- endcapture -%}
{%
render âswatch-inputâ,
id: input_id,
name: input_name,
value: value | escape,
swatch: value.swatch,
product_form_id: product_form_id,
checked: value.selected,
visually_disabled: option_disabled,
shape: block.settings.swatch_shape,
help_text: help_text,
additional_props: input_dataset
%}
{%- elsif picker_type == âbuttonâ -%}
<input
type=âradioâ
id=â{{ input_id }}â
name=â{{ input_name }}â
value=â{{ value | escape }}â
form=â{{ product_form_id }}â
{% if value.selected %}
checked
{% endif %}
{% if option_disabled %}
class=âdisabledâ
{% endif %}
{{ input_dataset }}
{% if product.variants[forloop.index0].featured_image != blank and option.name == âColorâ %}
{{ label_unavailable }}
{% else %}
{{ value -}}
{{ label_unavailable }}
{% endif %}
{%- elsif picker_type == âdropdownâ or picker_type == âswatch_dropdownâ -%}




