diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0fdf601653..99ff28b955 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,8 +27,7 @@ jobs:
- name: Lighthouse
uses: shopify/lighthouse-ci-action@v1
with:
- app_id: ${{ secrets.SHOP_APP_ID }}
- app_password: ${{ secrets.SHOP_APP_PASSWORD }}
+ access_token: ${{ secrets.SHOP_ACCESS_TOKEN }}
store: ${{ secrets.SHOP_STORE }}
password: ${{ secrets.SHOP_PASSWORD }}
lhci_github_app_token: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0689f6914b..90abaf1f39 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,12 +1,15 @@
{
"editor.formatOnSave": false,
"[javascript]": {
- "editor.formatOnSave": true
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.formatOnSave": true
},
"[liquid]": {
+ "editor.defaultFormatter": "Shopify.theme-check-vscode",
"editor.formatOnSave": true
- }
+ },
+ "themeCheck.checkOnSave": true
}
diff --git a/README.md b/README.md
index 522c1f0e49..664b09fc2b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
Brought to you and maintained by [Trellis Commerce](https://trellis.co/) - A full-service eCommerce agency based in Boston, MA
-Latest merged code from [Dawn v13.0.1](https://github.com/Shopify/dawn/releases/tag/v13.0.1)
+Latest merged code from [Dawn v15.0.1](https://github.com/Shopify/dawn/releases/tag/v15.0.1)
# Dawn + Tailwind CSS + Prettier Shopify Starter Theme
@@ -20,6 +20,7 @@ The starter theme includes an integration of:
## Other Noted Modifications
- Set the default page width to 1440px and tweaked the desktop page width range to be 1200px to 1600px with a step adjustment of 10px (standard desktop width used at Trellis and allows for more fine tuning)
+- There is a page template called `noindexnofollow` with the meta tag `noindex, nofollow` for any pages that need to be hidden from search engine site crawlers
## Steps to Start Using this Starter Theme
@@ -54,16 +55,9 @@ First, make sure your `Workflow permissions` are set like below in order for the
In your GitHub repo, navigate to Settings > Secrets > Actions and add the following repository secrets:
-`SHOP_APP_ID` & `SHOP_APP_PASSWORD`
+`SHOP_ACCESS_TOKEN`
-- Get values by navigating to https://mystore.myshopify.com/admin/apps/development, select the theme kit app, and copy the API key value for `SHOP_APP_ID` & Admin API access token value for `SHOP_APP_PASSWORD` (value starts with shpat)
-- Notes on how to get these values:
-
-1. Navigate to the Apps section in your Shopify admin and click the Develop apps button in the top right
-2. Then Allow custom app development
-3. You should be able to click the Create an app button
-4. In the configuration tab of your app, go ahead and check all the boxes for the Admin and Storefront API permissions
-5. The API key & Admin API access token will be in the API credentials tab
+- Settings > Apps and sales channels > Develop Apps > Create an app. Name it something like `Lighthouse` and give the app permissions of `read_products,write_themes`. Install the app and use the token value that will start with `shpat_`.
`SHOP_STORE`
@@ -88,7 +82,7 @@ In your GitHub repo, navigate to Settings > Secrets > Actions and add the follow
These secret values are used in the `ci.yml` GitHub workflow:
-
+
## Install [Shopify Liquid VSCode extension](https://marketplace.visualstudio.com/items?itemName=Shopify.theme-check-vscode)
diff --git a/assets/base.css b/assets/base.css
index 4b99b461df..90da2e8bf7 100644
--- a/assets/base.css
+++ b/assets/base.css
@@ -3,6 +3,10 @@
--alpha-button-border: 1;
--alpha-link: 0.85;
--alpha-badge-border: 0.1;
+ --focused-base-outline: 0.2rem solid rgba(var(--color-foreground), 0.5);
+ --focused-base-outline-offset: 0.3rem;
+ --focused-base-box-shadow: 0 0 0 0.3rem rgb(var(--color-background)),
+ 0 0 0.5rem 0.4rem rgba(var(--color-foreground), 0.3);
}
.product-card-wrapper .card,
@@ -264,8 +268,18 @@ h5,
word-break: break-word;
}
+.hxxl {
+ font-size: clamp(
+ calc(var(--font-heading-scale) * 5.6rem),
+ 14vw,
+ calc(var(--font-heading-scale) * 7.2rem)
+ );
+ line-height: 1.1;
+}
+
.hxl {
font-size: calc(var(--font-heading-scale) * 5rem);
+ line-height: calc(1 + 0.3 / max(1, var(--font-heading-scale)));
}
@media only screen and (min-width: 750px) {
@@ -690,18 +704,16 @@ summary::-webkit-details-marker {
}
*:focus-visible {
- outline: 0.2rem solid rgba(var(--color-foreground), 0.5);
- outline-offset: 0.3rem;
- box-shadow: 0 0 0 0.3rem rgb(var(--color-background)),
- 0 0 0.5rem 0.4rem rgba(var(--color-foreground), 0.3);
+ outline: var(--focused-base-outline);
+ outline-offset: var(--focused-base-outline-offset);
+ box-shadow: var(--focused-base-box-shadow);
}
/* Fallback - for browsers that don't support :focus-visible, a fallback is set for :focus */
.focused {
- outline: 0.2rem solid rgba(var(--color-foreground), 0.5);
- outline-offset: 0.3rem;
- box-shadow: 0 0 0 0.3rem rgb(var(--color-background)),
- 0 0 0.5rem 0.4rem rgba(var(--color-foreground), 0.3);
+ outline: var(--focused-base-outline);
+ outline-offset: var(--focused-base-outline-offset);
+ box-shadow: var(--focused-base-box-shadow);
}
/*
@@ -2012,7 +2024,7 @@ input[type='checkbox'] {
position: relative;
}
-product-info .loading__spinner:not(.hidden) ~ *,
+.product__info-container .loading__spinner:not(.hidden) ~ *,
.quantity__rules-cart .loading__spinner:not(.hidden) ~ * {
visibility: hidden;
}
@@ -2571,6 +2583,14 @@ product-info .loading__spinner:not(.hidden) ~ *,
background: rgba(var(--color-foreground), 0.5);
}
+.header__icon--account shop-user-avatar {
+ --shop-avatar-size: 2.8rem;
+}
+
+account-icon {
+ display: flex;
+}
+
/* Search */
menu-drawer + .header__search {
display: none;
@@ -3525,3 +3545,102 @@ details-disclosure > details {
--border-offset: 0px; /* Prevent the border from growing on buttons when this effect is on. */
}
}
+
+/* Loading spinner */
+.loading__spinner {
+ position: absolute;
+ z-index: 1;
+ width: 1.8rem;
+}
+
+.loading__spinner {
+ width: 1.8rem;
+ display: inline-block;
+}
+
+.spinner {
+ animation: rotator 1.4s linear infinite;
+}
+
+@keyframes rotator {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(270deg);
+ }
+}
+
+.path {
+ stroke-dasharray: 280;
+ stroke-dashoffset: 0;
+ transform-origin: center;
+ stroke: rgb(var(--color-foreground));
+ animation: dash 1.4s ease-in-out infinite;
+}
+
+@media screen and (forced-colors: active) {
+ .path {
+ stroke: CanvasText;
+ }
+}
+
+@keyframes dash {
+ 0% {
+ stroke-dashoffset: 280;
+ }
+ 50% {
+ stroke-dashoffset: 75;
+ transform: rotate(135deg);
+ }
+ 100% {
+ stroke-dashoffset: 280;
+ transform: rotate(450deg);
+ }
+}
+
+.loading__spinner:not(.hidden) + .cart-item__price-wrapper,
+.loading__spinner:not(.hidden) ~ cart-remove-button {
+ opacity: 50%;
+}
+
+.loading__spinner:not(.hidden) ~ cart-remove-button {
+ pointer-events: none;
+ cursor: default;
+}
+
+/* Progress bar */
+.progress-bar-container {
+ width: 100%;
+ margin: auto;
+}
+
+.progress-bar {
+ height: 0.13rem;
+ width: 100%;
+}
+
+.progress-bar-value {
+ width: 100%;
+ height: 100%;
+ background-color: rgb(var(--color-foreground));
+ animation: indeterminateAnimation var(--duration-extra-longer) infinite
+ ease-in-out;
+ transform-origin: 0;
+}
+
+.progress-bar .progress-bar-value {
+ display: block;
+}
+
+@keyframes indeterminateAnimation {
+ 0% {
+ transform: translateX(-20%) scaleX(0);
+ }
+ 40% {
+ transform: translateX(30%) scaleX(0.7);
+ }
+ 100% {
+ transform: translateX(100%) scaleX(0);
+ }
+}
diff --git a/assets/cart-drawer.js b/assets/cart-drawer.js
index 11ec600d04..f19060725c 100644
--- a/assets/cart-drawer.js
+++ b/assets/cart-drawer.js
@@ -15,6 +15,8 @@ class CartDrawer extends HTMLElement {
setHeaderCartIconAccessibility() {
const cartLink = document.querySelector('#cart-icon-bubble');
+ if (!cartLink) return;
+
cartLink.setAttribute('role', 'button');
cartLink.setAttribute('aria-haspopup', 'dialog');
cartLink.addEventListener('click', (event) => {
@@ -91,6 +93,8 @@ class CartDrawer extends HTMLElement {
const sectionElement = section.selector
? document.querySelector(section.selector)
: document.getElementById(section.id);
+
+ if (!sectionElement) return;
sectionElement.innerHTML = this.getSectionInnerHTML(
parsedState.sections[section.id],
section.selector,
diff --git a/assets/cart.js b/assets/cart.js
index 58cd8aa823..5b08e6e4a7 100644
--- a/assets/cart.js
+++ b/assets/cart.js
@@ -47,13 +47,57 @@ class CartItems extends HTMLElement {
}
}
+ 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;
+ let message = '';
+
+ if (inputValue < event.target.dataset.min) {
+ message = window.quickOrderListStrings.min_error.replace(
+ '[min]',
+ event.target.dataset.min,
+ );
+ } else if (inputValue > parseInt(event.target.max)) {
+ message = window.quickOrderListStrings.max_error.replace(
+ '[max]',
+ event.target.max,
+ );
+ } else if (inputValue % parseInt(event.target.step) !== 0) {
+ message = window.quickOrderListStrings.step_error.replace(
+ '[step]',
+ event.target.step,
+ );
+ }
+
+ if (message) {
+ this.setValidity(event, index, message);
+ } else {
+ event.target.setCustomValidity('');
+ event.target.reportValidity();
+ this.updateQuantity(
+ index,
+ inputValue,
+ document.activeElement.getAttribute('name'),
+ event.target.dataset.quantityVariantId,
+ );
+ }
+ }
+
onChange(event) {
- this.updateQuantity(
- event.target.dataset.index,
- event.target.value,
- document.activeElement.getAttribute('name'),
- event.target.dataset.quantityVariantId,
- );
+ this.validateQuantity(event);
}
onCartUpdate() {
@@ -235,7 +279,8 @@ class CartItems extends HTMLElement {
document.getElementById(`Line-item-error-${line}`) ||
document.getElementById(`CartDrawer-LineItemError-${line}`);
if (lineItemError)
- lineItemError.querySelector('.cart-item__error-text').innerHTML = message;
+ lineItemError.querySelector('.cart-item__error-text').textContent =
+ message;
this.lineItemStatusElement.setAttribute('aria-hidden', true);
diff --git a/assets/component-card.css b/assets/component-card.css
index 7ced47241c..aa41a4f800 100644
--- a/assets/component-card.css
+++ b/assets/component-card.css
@@ -152,6 +152,129 @@
color: rgba(var(--color-foreground), 0.75);
}
+.card__information-volume-pricing-note--button,
+.card__information-volume-pricing-note--button.quantity-popover__info-button--icon-with-label {
+ position: relative;
+ z-index: 1;
+ cursor: pointer;
+ padding: 0;
+ margin: 0;
+ text-align: var(--text-alignment);
+ min-width: auto;
+}
+
+.card__information-volume-pricing-note--button:hover {
+ text-decoration: underline;
+}
+
+.card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ transform: initial;
+ top: auto;
+ bottom: 4rem;
+ max-width: 20rem;
+ width: calc(95% + 2rem);
+}
+
+.card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info
+ span:first-of-type {
+ padding-right: 0.3rem;
+}
+
+.card__information-volume-pricing-note--button-right
+ + .global-settings-popup.quantity-popover__info {
+ right: 0;
+ left: auto;
+}
+
+.card__information-volume-pricing-note--button-center
+ + .global-settings-popup.quantity-popover__info {
+ left: 50%;
+ transform: translate(-50%);
+}
+
+.card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info
+ .quantity__rules {
+ text-align: left;
+}
+
+@media screen and (min-width: 990px) {
+ .grid--6-col-desktop .card__content quick-add-bulk .quantity {
+ width: auto;
+ }
+
+ .grid--6-col-desktop .card__content quick-add-bulk .quantity__button {
+ width: calc(3rem / var(--font-body-scale));
+ }
+
+ .grid--6-col-desktop
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ left: 50%;
+ transform: translate(-50%);
+ width: calc(100% + var(--border-width) + 3.5rem);
+ }
+
+ .grid--6-col-desktop
+ .card--standard
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ width: calc(100% + var(--border-width) + 1rem);
+ }
+}
+
+@media screen and (max-width: 749px) {
+ .grid--2-col-tablet-down .card__content quick-add-bulk .quantity__button {
+ width: calc(3.5rem / var(--font-body-scale));
+ }
+
+ .grid--2-col-tablet-down
+ .card--card
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info,
+ .grid--2-col-tablet-down
+ .card--standard
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ left: 50%;
+ transform: translate(-50%);
+ }
+
+ .grid--2-col-tablet-down
+ .card--standard
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ width: 100%;
+ }
+
+ .grid--2-col-tablet-down
+ .card--card
+ .card__information-volume-pricing-note--button
+ + .global-settings-popup.quantity-popover__info {
+ width: calc(100% + var(--border-width) + 4rem);
+ }
+
+ .grid--2-col-tablet-down .card__content quick-add-bulk .quantity {
+ width: auto;
+ }
+}
+
+.card-information quantity-popover volume-pricing {
+ margin-top: 0;
+}
+
+@media screen and (max-width: 989px) {
+ .card-information quantity-popover .quantity__rules ~ volume-pricing {
+ margin-top: 0;
+ }
+
+ .card-information quantity-popover volume-pricing {
+ margin-top: 4.2rem;
+ }
+}
+
@media screen and (min-width: 750px) {
.card__information {
padding-bottom: 1.7rem;
@@ -381,6 +504,15 @@
margin-top: 0.4rem;
}
+/* Specificity needed due to the changes below */
+.card-information
+ > *:not(.visually-hidden:first-child)
+ + quantity-popover:not(.rating):not(.card__information-volume-pricing-note),
+.card-information
+ .card__information-volume-pricing-note.card__information-volume-pricing-note--button {
+ margin-top: 0;
+}
+
.card-information
> *:not(.visually-hidden:first-child)
+ *:not(.rating):not(.card__information-volume-pricing-note) {
diff --git a/assets/component-cart-items.css b/assets/component-cart-items.css
index 19a34bcb7f..d2294b4e96 100644
--- a/assets/component-cart-items.css
+++ b/assets/component-cart-items.css
@@ -348,8 +348,8 @@ cart-remove-button .icon-remove {
}
}
-@media screen and (min-width: 749px) and (max-width: 990px) {
+@media screen and (max-width: 989px) {
.cart-items .quantity-popover__info-button {
- padding-left: 1.5rem;
+ padding-left: 0;
}
}
diff --git a/assets/component-loading-spinner.css b/assets/component-loading-spinner.css
deleted file mode 100644
index 6cc341a3ed..0000000000
--- a/assets/component-loading-spinner.css
+++ /dev/null
@@ -1,61 +0,0 @@
-.loading__spinner {
- position: absolute;
- z-index: 1;
- width: 1.8rem;
-}
-
-.loading__spinner {
- width: 1.8rem;
- display: inline-block;
-}
-
-.spinner {
- animation: rotator 1.4s linear infinite;
-}
-
-@keyframes rotator {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(270deg);
- }
-}
-
-.path {
- stroke-dasharray: 280;
- stroke-dashoffset: 0;
- transform-origin: center;
- stroke: rgb(var(--color-foreground));
- animation: dash 1.4s ease-in-out infinite;
-}
-
-@media screen and (forced-colors: active) {
- .path {
- stroke: CanvasText;
- }
-}
-
-@keyframes dash {
- 0% {
- stroke-dashoffset: 280;
- }
- 50% {
- stroke-dashoffset: 75;
- transform: rotate(135deg);
- }
- 100% {
- stroke-dashoffset: 280;
- transform: rotate(450deg);
- }
-}
-
-.loading__spinner:not(.hidden) + .cart-item__price-wrapper,
-.loading__spinner:not(.hidden) ~ cart-remove-button {
- opacity: 50%;
-}
-
-.loading__spinner:not(.hidden) ~ cart-remove-button {
- pointer-events: none;
- cursor: default;
-}
diff --git a/assets/component-menu-drawer.css b/assets/component-menu-drawer.css
index 8eba49f6e0..608c4ce2ec 100644
--- a/assets/component-menu-drawer.css
+++ b/assets/component-menu-drawer.css
@@ -219,6 +219,12 @@ details[open].menu-opening > .menu-drawer__submenu {
margin-right: 1rem;
}
+.menu-drawer__account shop-user-avatar {
+ --shop-avatar-size: 2.4rem;
+ margin-right: 0.55rem;
+ margin-left: -0.45rem;
+}
+
.menu-drawer__account:hover .icon-account {
transform: scale(1.07);
}
diff --git a/assets/component-slideshow.css b/assets/component-slideshow.css
index 40fdcfcab5..57aef76923 100644
--- a/assets/component-slideshow.css
+++ b/assets/component-slideshow.css
@@ -9,6 +9,7 @@ slideshow-component .slideshow.banner {
flex-wrap: nowrap;
margin: 0;
gap: 0;
+ overflow-y: hidden;
}
.slideshow__slide {
diff --git a/assets/component-volume-pricing.css b/assets/component-volume-pricing.css
index 2729866c34..f23c9b21d6 100644
--- a/assets/component-volume-pricing.css
+++ b/assets/component-volume-pricing.css
@@ -19,7 +19,7 @@ volume-pricing li {
justify-content: space-between;
}
-.volume-pricing-note {
+div.volume-pricing-note {
margin-top: -2.6rem;
}
diff --git a/assets/constants.js b/assets/constants.js
index 1b016f6fc0..8c405e63e6 100644
--- a/assets/constants.js
+++ b/assets/constants.js
@@ -3,11 +3,7 @@ const ON_CHANGE_DEBOUNCE_TIMER = 300;
const PUB_SUB_EVENTS = {
cartUpdate: 'cart-update',
quantityUpdate: 'quantity-update',
+ optionValueSelectionChange: 'option-value-selection-change',
variantChange: 'variant-change',
cartError: 'cart-error',
- sectionRefreshed: 'section-refreshed',
-};
-
-const SECTION_REFRESH_RESOURCE_TYPE = {
- product: 'product',
};
diff --git a/assets/global.js b/assets/global.js
index 4a231c5365..f73058be7a 100644
--- a/assets/global.js
+++ b/assets/global.js
@@ -6,37 +6,61 @@ function getFocusableElements(container) {
);
}
-class HTMLUpdateUtility {
- #preProcessCallbacks = [];
- #postProcessCallbacks = [];
+class SectionId {
+ static #separator = '__';
- constructor() {}
+ // 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];
+ }
- addPreProcessCallback(callback) {
- this.#preProcessCallbacks.push(callback);
+ // 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];
}
- addPostProcessCallback(callback) {
- this.#postProcessCallbacks.push(callback);
+ // 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
*/
- viewTransition(oldNode, newContent) {
- this.#preProcessCallbacks.forEach((callback) => callback(newContent));
+ 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}`,
+ );
+ });
- const newNode = oldNode.cloneNode();
- HTMLUpdateUtility.setInnerHTML(newNode, newContent.innerHTML);
oldNode.parentNode.insertBefore(newNode, oldNode);
oldNode.style.display = 'none';
- this.#postProcessCallbacks.forEach((callback) => callback(newNode));
+ postProcessCallbacks?.forEach((callback) => callback(newNode));
- setTimeout(() => oldNode.remove(), 1000);
+ setTimeout(() => oldNode.remove(), 500);
}
// Sets inner HTML and reinjects the script tags to allow execution. By default, scripts are disabled when using element.innerHTML.
@@ -722,6 +746,45 @@ class ModalDialog extends HTMLElement {
}
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();
@@ -1220,74 +1283,32 @@ customElements.define('slideshow-component', SlideshowComponent);
class VariantSelects extends HTMLElement {
constructor() {
super();
- this.addEventListener('change', this.handleProductUpdate);
- this.initializeProductSwapUtility();
}
- initializeProductSwapUtility() {
- this.swapProductUtility = new HTMLUpdateUtility();
- this.swapProductUtility.addPreProcessCallback((html) => {
- html
- .querySelectorAll('.scroll-trigger')
- .forEach((element) => element.classList.add('scroll-trigger--cancel'));
- return html;
- });
- this.swapProductUtility.addPostProcessCallback((newNode) => {
- window?.Shopify?.PaymentButton?.init();
- window?.ProductModel?.loadShopifyXR();
- publish(PUB_SUB_EVENTS.sectionRefreshed, {
+ connectedCallback() {
+ this.addEventListener('change', (event) => {
+ const target = this.getInputForEventTarget(event.target);
+ this.updateSelectionMetadata(event);
+
+ publish(PUB_SUB_EVENTS.optionValueSelectionChange, {
data: {
- sectionId: this.dataset.section,
- resource: {
- type: SECTION_REFRESH_RESOURCE_TYPE.product,
- id: newNode.querySelector('variant-selects').dataset.productId,
- },
+ event,
+ target,
+ selectedOptionValues: this.selectedOptionValues,
},
});
});
}
- handleProductUpdate(event) {
- const input = this.getInputForEventTarget(event.target);
- const targetId = input.id;
- const targetUrl = input.dataset.productUrl;
- this.currentVariant = this.getVariantData(targetId);
- const sectionId = this.dataset.originalSection || this.dataset.section;
- this.updateSelectedSwatchValue(event);
- this.toggleAddButton(true, '', false);
- this.removeErrorMessage();
-
- let callback = () => {};
- if (this.dataset.url !== targetUrl) {
- this.updateURL(targetUrl);
- this.updateShareUrl(targetUrl);
- callback = this.handleSwapProduct(sectionId);
- } else if (!this.currentVariant) {
- this.setUnavailable();
- callback = (html) => {
- this.updatePickupAvailability();
- this.updateOptionValues(html);
- };
- } else {
- this.updateURL(targetUrl);
- this.updateVariantInput();
- this.updateShareUrl(targetUrl);
- callback = this.handleUpdateProductInfo(sectionId);
- }
-
- this.renderProductInfo(sectionId, targetUrl, targetId, callback);
- }
-
- getSelectedOptionValues() {
- return Array.from(
- this.querySelectorAll('select, fieldset input:checked'),
- ).map((element) => element.dataset.optionValueId);
- }
-
- updateSelectedSwatchValue({ 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')
@@ -1306,6 +1327,11 @@ class VariantSelects extends HTMLElement {
);
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`)
@@ -1314,419 +1340,46 @@ class VariantSelects extends HTMLElement {
}
}
- updateMedia(html) {
- const mediaGallerySource = document.querySelector(
- `[id^="MediaGallery-${this.dataset.section}"] ul`,
- );
- const mediaGalleryDestination = html.querySelector(
- `[id^="MediaGallery-${this.dataset.section}"] ul`,
- );
-
- const refreshSourceData = () => {
- 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();
- }
- },
- );
- }
-
- document
- .querySelector(`[id^="MediaGallery-${this.dataset.section}"]`)
- .setActiveMedia(
- `${this.dataset.section}-${this.currentVariant.featured_media?.id}`,
- );
-
- // update media modal
- const modalContent = document.querySelector(
- `#ProductModal-${this.dataset.section} .product-media-modal__content`,
- );
- const newModalContent = html.querySelector(`product-modal`);
- if (modalContent && newModalContent)
- modalContent.innerHTML = newModalContent.innerHTML;
- }
-
- updateURL(url) {
- if (this.dataset.updateUrl === 'false') return;
- window.history.replaceState(
- {},
- '',
- `${url}${
- this.currentVariant?.id ? `?variant=${this.currentVariant.id}` : ''
- }`,
- );
- }
-
- updateShareUrl(url) {
- const shareButton = document.getElementById(
- `Share-${this.dataset.section}`,
- );
- if (!shareButton || !shareButton.updateUrl) return;
- shareButton.updateUrl(
- `${window.shopUrl}${url}${
- this.currentVariant?.id ? `?variant=${this.currentVariant.id}` : ''
- }`,
- );
- }
-
- updateVariantInput() {
- const productForms = document.querySelectorAll(
- `#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}`,
- );
- 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');
- if (!pickUpAvailability) return;
-
- if (this.currentVariant && this.currentVariant.available) {
- pickUpAvailability.fetchAvailability(this.currentVariant.id);
- } else {
- pickUpAvailability.removeAttribute('available');
- pickUpAvailability.innerHTML = '';
- }
- }
-
getInputForEventTarget(target) {
return target.tagName === 'SELECT' ? target.selectedOptions[0] : target;
}
- getVariantData(inputId) {
- return JSON.parse(this.getVariantDataElement(inputId).textContent);
- }
-
- getVariantDataElement(inputId) {
- return this.querySelector(
- `script[type="application/json"][data-resource="${inputId}"]`,
- );
- }
-
- removeErrorMessage() {
- const section = this.closest('section');
- if (!section) return;
-
- const productForm = section.querySelector('product-form');
- if (productForm) productForm.handleErrorMessage();
- }
-
- getWrappingSection(sectionId) {
- return (
- this.closest(`section[data-section="${sectionId}"]`) || // main-product
- this.closest(`quick-add-modal`)?.modalContent || // quick-add
- this.closest(`#shopify-section-${sectionId}`) || // featured-product
- null
- );
- }
-
- handleSwapProduct(sectionId) {
- return (html) => {
- const oldContent = this.getWrappingSection(sectionId);
- if (!oldContent) {
- return;
- }
-
- document.getElementById(`ProductModal-${sectionId}`)?.remove();
-
- const response =
- html.querySelector(
- `section[data-section="${sectionId}"]`,
- ) /* main/quick-add */ ||
- html.getElementById(
- `shopify-section-${sectionId}`,
- ); /* featured product*/
-
- this.swapProductUtility.viewTransition(oldContent, response);
- };
- }
-
- handleUpdateProductInfo(sectionId) {
- return (html) => {
- this.updatePickupAvailability();
- this.updateMedia(html);
- const priceDestination = document.getElementById(
- `price-${this.dataset.section}`,
- );
- const priceSource = html.getElementById(`price-${sectionId}`);
- const skuSource = html.getElementById(`Sku-${sectionId}`);
- const skuDestination = document.getElementById(
- `Sku-${this.dataset.section}`,
- );
- const inventorySource = html.getElementById(`Inventory-${sectionId}`);
- const inventoryDestination = document.getElementById(
- `Inventory-${this.dataset.section}`,
- );
-
- const volumePricingSource = html.getElementById(`Volume-${sectionId}`);
-
- const pricePerItemDestination = document.getElementById(
- `Price-Per-Item-${this.dataset.section}`,
- );
- const pricePerItemSource = html.getElementById(
- `Price-Per-Item-${sectionId}`,
- );
-
- const volumePricingDestination = document.getElementById(
- `Volume-${this.dataset.section}`,
- );
- const qtyRules = document.getElementById(
- `Quantity-Rules-${this.dataset.section}`,
- );
- const volumeNote = document.getElementById(
- `Volume-Note-${this.dataset.section}`,
- );
-
- if (volumeNote) volumeNote.classList.remove('hidden');
- if (volumePricingDestination)
- volumePricingDestination.classList.remove('hidden');
- if (qtyRules) qtyRules.classList.remove('hidden');
- if (priceSource && priceDestination)
- priceDestination.innerHTML = priceSource.innerHTML;
- if (inventorySource && inventoryDestination)
- inventoryDestination.innerHTML = inventorySource.innerHTML;
- if (skuSource && skuDestination) {
- skuDestination.innerHTML = skuSource.innerHTML;
- skuDestination.classList.toggle(
- 'hidden',
- skuSource.classList.contains('hidden'),
- );
- }
- if (volumePricingSource && volumePricingDestination) {
- volumePricingDestination.innerHTML = volumePricingSource.innerHTML;
- }
- if (pricePerItemSource && pricePerItemDestination) {
- pricePerItemDestination.innerHTML = pricePerItemSource.innerHTML;
- pricePerItemDestination.classList.toggle(
- 'hidden',
- pricePerItemSource.classList.contains('hidden'),
- );
- }
-
- const price = document.getElementById(`price-${this.dataset.section}`);
- if (price) price.classList.remove('hidden');
-
- if (inventoryDestination)
- inventoryDestination.classList.toggle(
- 'hidden',
- inventorySource.innerText === '',
- );
-
- const addButtonUpdated = html.getElementById(
- `ProductSubmitButton-${sectionId}`,
- );
- this.toggleAddButton(
- addButtonUpdated ? addButtonUpdated.hasAttribute('disabled') : true,
- window.variantStrings.soldOut,
- );
-
- this.updateOptionValues(html);
-
- publish(PUB_SUB_EVENTS.variantChange, {
- data: {
- sectionId,
- html,
- variant: this.currentVariant,
- },
- });
- };
- }
-
- updateOptionValues(html) {
- const variantSelects = html.querySelector('variant-selects');
- if (variantSelects) this.innerHTML = variantSelects.innerHTML;
- }
-
- renderProductInfo(sectionId, url, targetId, callback) {
- const params = this.currentVariant
- ? `variant=${this.currentVariant?.id}`
- : `option_values=${this.getSelectedOptionValues().join(',')}`;
-
- this.abortController?.abort();
- this.abortController = new AbortController();
-
- fetch(`${url}?section_id=${sectionId}&${params}`, {
- signal: this.abortController.signal,
- })
- .then((response) => response.text())
- .then((responseText) => {
- const html = new DOMParser().parseFromString(responseText, 'text/html');
- callback(html);
- })
- .then(() => {
- // set focus to last clicked option value
- document.getElementById(targetId).focus();
- });
- }
-
- toggleAddButton(disable = true, text, modifyClass = true) {
- const productForm = document.getElementById(
- `product-form-${this.dataset.section}`,
- );
- if (!productForm) return;
- const addButton = productForm.querySelector('[name="add"]');
- const addButtonText = productForm.querySelector('[name="add"] > span');
- if (!addButton) return;
-
- if (disable) {
- addButton.setAttribute('disabled', 'disabled');
- if (text) addButtonText.textContent = text;
- } else {
- addButton.removeAttribute('disabled');
- addButtonText.textContent = window.variantStrings.addToCart;
- }
- }
-
- setUnavailable() {
- this.toggleAddButton(true, '', true);
- const button = document.getElementById(
- `product-form-${this.dataset.section}`,
- );
- const addButton = button.querySelector('[name="add"]');
- const addButtonText = button.querySelector('[name="add"] > span');
- const price = document.getElementById(`price-${this.dataset.section}`);
- const inventory = document.getElementById(
- `Inventory-${this.dataset.section}`,
- );
- const sku = document.getElementById(`Sku-${this.dataset.section}`);
- const pricePerItem = document.getElementById(
- `Price-Per-Item-${this.dataset.section}`,
- );
- const volumeNote = document.getElementById(
- `Volume-Note-${this.dataset.section}`,
- );
- const volumeTable = document.getElementById(
- `Volume-${this.dataset.section}`,
- );
- const qtyRules = document.getElementById(
- `Quantity-Rules-${this.dataset.section}`,
- );
-
- if (!addButton) return;
- addButtonText.textContent = window.variantStrings.unavailable;
- if (price) price.classList.add('hidden');
- if (inventory) inventory.classList.add('hidden');
- if (sku) sku.classList.add('hidden');
- if (pricePerItem) pricePerItem.classList.add('hidden');
- if (volumeNote) volumeNote.classList.add('hidden');
- if (volumeTable) volumeTable.classList.add('hidden');
- if (qtyRules) qtyRules.classList.add('hidden');
- }
-
- getInputSelector() {
- return 'variant-selects fieldset input[type="radio"], variant-selects select option';
+ 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();
- this.observer = null;
}
connectedCallback() {
- this.initializeRecommendations();
-
- this.unsubscribeFromSectionRefresh = subscribe(
- PUB_SUB_EVENTS.sectionRefreshed,
- (event) => {
- const sectionId = this.dataset.sectionId;
- const isRelatedProduct = this.classList.contains('related-products');
- const isParentSectionUpdated =
- sectionId &&
- (event.data?.sectionId ?? '') === `${sectionId.split('__')[0]}__main`;
-
- if (isRelatedProduct && isParentSectionUpdated) {
- this.dataset.productId = event.data.resource.id;
- this.initializeRecommendations();
- }
- },
- );
- }
-
- disconnectedCallback() {
- this.unsubscribeFromSectionRefresh();
+ this.initializeRecommendations(this.dataset.productId);
}
- initializeRecommendations() {
+ initializeRecommendations(productId) {
this.observer?.unobserve(this);
this.observer = new IntersectionObserver(
(entries, observer) => {
if (!entries[0].isIntersecting) return;
observer.unobserve(this);
- this.loadRecommendations();
+ this.loadRecommendations(productId);
},
{ rootMargin: '0px 0px 400px 0px' },
);
this.observer.observe(this);
}
- loadRecommendations() {
+ loadRecommendations(productId) {
fetch(
- `${this.dataset.url}&product_id=${this.dataset.productId}§ion_id=${this.dataset.sectionId}`,
+ `${this.dataset.url}&product_id=${productId}§ion_id=${this.dataset.sectionId}`,
)
.then((response) => response.text())
.then((text) => {
@@ -1756,3 +1409,131 @@ class ProductRecommendations extends HTMLElement {
}
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);
+}
diff --git a/assets/localization-form.js b/assets/localization-form.js
index f8fb5e2de9..da24bcc353 100644
--- a/assets/localization-form.js
+++ b/assets/localization-form.js
@@ -184,8 +184,15 @@ if (!customElements.get('localization-form')) {
}
}
+ normalizeString(str) {
+ return str
+ .normalize('NFD')
+ .replace(/\p{Diacritic}/gu, '')
+ .toLowerCase();
+ }
+
filterCountries() {
- const searchValue = this.elements.search.value.toLowerCase();
+ const searchValue = this.normalizeString(this.elements.search.value);
const popularCountries = this.querySelector('.popular-countries');
const allCountries = this.querySelectorAll('a');
let visibleCountries = allCountries.length;
@@ -197,9 +204,9 @@ if (!customElements.get('localization-form')) {
}
allCountries.forEach((item) => {
- const countryName = item
- .querySelector('.country')
- .textContent.toLowerCase();
+ const countryName = this.normalizeString(
+ item.querySelector('.country').textContent,
+ );
if (countryName.indexOf(searchValue) > -1) {
item.parentElement.classList.remove('hidden');
visibleCountries++;
diff --git a/assets/media-gallery.js b/assets/media-gallery.js
index f89c340f01..5682ce7f4f 100644
--- a/assets/media-gallery.js
+++ b/assets/media-gallery.js
@@ -23,7 +23,11 @@ if (!customElements.get('media-gallery')) {
.querySelector('button')
.addEventListener(
'click',
- this.setActiveMedia.bind(this, mediaToSwitch.dataset.target),
+ this.setActiveMedia.bind(
+ this,
+ mediaToSwitch.dataset.target,
+ false,
+ ),
);
});
if (
@@ -40,7 +44,7 @@ if (!customElements.get('media-gallery')) {
this.setActiveThumbnail(thumbnail);
}
- setActiveMedia(mediaId) {
+ setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
@@ -54,6 +58,21 @@ if (!customElements.get('media-gallery')) {
});
activeMedia?.classList?.add('is-active');
+ if (prepend) {
+ activeMedia.parentElement.firstChild !== activeMedia &&
+ activeMedia.parentElement.prepend(activeMedia);
+
+ if (this.elements.thumbnails) {
+ const activeThumbnail = this.elements.thumbnails.querySelector(
+ `[data-target="${mediaId}"]`,
+ );
+ activeThumbnail.parentElement.firstChild !== activeThumbnail &&
+ activeThumbnail.parentElement.prepend(activeThumbnail);
+ }
+
+ if (this.elements.viewer.slider) this.elements.viewer.resetPages();
+ }
+
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
diff --git a/assets/pickup-availability.js b/assets/pickup-availability.js
index d0ec161c39..08c2ec4ef2 100644
--- a/assets/pickup-availability.js
+++ b/assets/pickup-availability.js
@@ -40,10 +40,19 @@ if (!customElements.get('pickup-availability')) {
});
}
- onClickRefreshList(evt) {
+ onClickRefreshList() {
this.fetchAvailability(this.dataset.variantId);
}
+ update(variant) {
+ if (variant?.available) {
+ this.fetchAvailability(variant.id);
+ } else {
+ this.removeAttribute('available');
+ this.innerHTML = '';
+ }
+ }
+
renderError() {
this.innerHTML = '';
this.appendChild(this.errorHtml);
diff --git a/assets/predictive-search.js b/assets/predictive-search.js
index 0d45ce5a33..229acdea9a 100644
--- a/assets/predictive-search.js
+++ b/assets/predictive-search.js
@@ -268,7 +268,7 @@ class PredictiveSearch extends SearchForm {
getResultsMaxHeight() {
this.resultsMaxHeight =
window.innerHeight -
- document.querySelector('.section-header').getBoundingClientRect().bottom;
+ document.querySelector('.section-header')?.getBoundingClientRect().bottom;
return this.resultsMaxHeight;
}
diff --git a/assets/product-form.js b/assets/product-form.js
index f77d36ceef..c3e39bcb42 100644
--- a/assets/product-form.js
+++ b/assets/product-form.js
@@ -6,12 +6,13 @@ if (!customElements.get('product-form')) {
super();
this.form = this.querySelector('form');
- this.form.querySelector('[name=id]').disabled = false;
+ this.variantIdInput.disabled = false;
this.form.addEventListener('submit', this.onSubmitHandler.bind(this));
this.cart =
document.querySelector('cart-notification') ||
document.querySelector('cart-drawer');
this.submitButton = this.querySelector('[type="submit"]');
+ this.submitButtonText = this.submitButton.querySelector('span');
if (document.querySelector('cart-drawer'))
this.submitButton.setAttribute('aria-haspopup', 'dialog');
@@ -60,7 +61,7 @@ if (!customElements.get('product-form')) {
this.submitButton.querySelector('.sold-out-message');
if (!soldOutMessage) return;
this.submitButton.setAttribute('aria-disabled', true);
- this.submitButton.querySelector('span').classList.add('hidden');
+ this.submitButtonText.classList.add('hidden');
soldOutMessage.classList.remove('hidden');
this.error = true;
return;
@@ -121,6 +122,20 @@ if (!customElements.get('product-form')) {
this.errorMessage.textContent = errorMessage;
}
}
+
+ toggleSubmitButton(disable = true, text) {
+ if (disable) {
+ this.submitButton.setAttribute('disabled', 'disabled');
+ if (text) this.submitButtonText.textContent = text;
+ } else {
+ this.submitButton.removeAttribute('disabled');
+ this.submitButtonText.textContent = window.variantStrings.addToCart;
+ }
+ }
+
+ get variantIdInput() {
+ return this.form.querySelector('[name=id]');
+ }
},
);
}
diff --git a/assets/product-info.js b/assets/product-info.js
index aca8624930..f02506a284 100644
--- a/assets/product-info.js
+++ b/assets/product-info.js
@@ -2,20 +2,45 @@ if (!customElements.get('product-info')) {
customElements.define(
'product-info',
class ProductInfo extends HTMLElement {
+ quantityInput = undefined;
+ quantityForm = undefined;
+ onVariantChangeUnsubscriber = undefined;
+ cartUpdateUnsubscriber = undefined;
+ abortController = undefined;
+ pendingRequestUrl = null;
+ preProcessHtmlCallbacks = [];
+ postProcessHtmlCallbacks = [];
+
constructor() {
super();
- this.input = this.querySelector('.quantity__input');
- this.currentVariant = this.querySelector('.product-variant-id');
- this.submitButton = this.querySelector('[type="submit"]');
- }
- cartUpdateUnsubscriber = undefined;
- variantChangeUnsubscriber = undefined;
+ this.quantityInput = this.querySelector('.quantity__input');
+ }
connectedCallback() {
- if (!this.input) return;
+ this.initializeProductSwapUtility();
+
+ this.onVariantChangeUnsubscriber = subscribe(
+ PUB_SUB_EVENTS.optionValueSelectionChange,
+ this.handleOptionValueChange.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(
@@ -23,83 +48,425 @@ if (!customElements.get('product-info')) {
this.fetchQuantityRules.bind(this),
);
}
- this.variantChangeUnsubscriber = subscribe(
- PUB_SUB_EVENTS.variantChange,
- (event) => {
- const sectionId = this.dataset.originalSection
- ? this.dataset.originalSection
- : this.dataset.section;
- if (event.data.sectionId !== sectionId) return;
- this.updateQuantityRules(event.data.sectionId, event.data.html);
- this.setQuantityBoundries();
- },
- );
}
disconnectedCallback() {
- if (this.cartUpdateUnsubscriber) {
- this.cartUpdateUnsubscriber();
+ 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 },
+ }) {
+ 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),
+ });
+ }
+
+ 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(',')}`);
}
- if (this.variantChangeUnsubscriber) {
- this.variantChangeUnsubscriber();
+
+ 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);
+
+ 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,
+ },
+ });
+ };
+ }
+
+ 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.input.dataset.cartQuantity
- ? parseInt(this.input.dataset.cartQuantity)
+ cartQuantity: this.quantityInput.dataset.cartQuantity
+ ? parseInt(this.quantityInput.dataset.cartQuantity)
: 0,
- min: this.input.dataset.min ? parseInt(this.input.dataset.min) : 1,
- max: this.input.dataset.max ? parseInt(this.input.dataset.max) : null,
- step: this.input.step ? parseInt(this.input.step) : 1,
+ 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.input.min = min;
+
+ this.quantityInput.min = min;
if (max) {
- this.input.max = max;
+ this.quantityInput.max = max;
} else {
- this.input.removeAttribute('max');
+ this.quantityInput.removeAttribute('max');
}
- this.input.value = min;
+ this.quantityInput.value = min;
+
publish(PUB_SUB_EVENTS.quantityUpdate, undefined);
}
fetchQuantityRules() {
- if (!this.currentVariant || !this.currentVariant.value) return;
+ const currentVariantId = this.productForm?.variantIdInput?.value;
+ if (!currentVariantId) return;
+
this.querySelector(
'.quantity__rules-cart .loading__spinner',
).classList.remove('hidden');
fetch(
- `${this.dataset.url}?variant=${this.currentVariant.value}§ion_id=${this.dataset.section}`,
+ `${this.dataset.url}?variant=${currentVariantId}§ion_id=${this.dataset.section}`,
)
- .then((response) => {
- return response.text();
- })
+ .then((response) => response.text())
.then((responseText) => {
const html = new DOMParser().parseFromString(
responseText,
'text/html',
);
this.updateQuantityRules(this.dataset.section, html);
- this.setQuantityBoundries();
- })
- .catch((e) => {
- console.error(e);
})
- .finally(() => {
+ .catch((e) => console.error(e))
+ .finally(() =>
this.querySelector(
'.quantity__rules-cart .loading__spinner',
- ).classList.add('hidden');
- });
+ ).classList.add('hidden'),
+ );
}
updateQuantityRules(sectionId, html) {
+ if (!this.quantityInput) return;
+ this.setQuantityBoundries();
+
const quantityFormUpdated = html.getElementById(
`Quantity-Form-${sectionId}`,
);
@@ -132,6 +499,46 @@ if (!customElements.get('product-info')) {
}
}
}
+
+ 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;
+ }
},
);
}
diff --git a/assets/quick-add-bulk.js b/assets/quick-add-bulk.js
index c96a40a462..552efb7efe 100644
--- a/assets/quick-add-bulk.js
+++ b/assets/quick-add-bulk.js
@@ -1,14 +1,19 @@
if (!customElements.get('quick-add-bulk')) {
customElements.define(
'quick-add-bulk',
- class QuickAddBulk extends HTMLElement {
+ class QuickAddBulk extends BulkAdd {
constructor() {
super();
+ this.quantity = this.querySelector('quantity-input');
+
const debouncedOnChange = debounce((event) => {
- if (parseInt(event.target.dataset.cartQuantity) === 0) {
- this.addToCart(event);
+ if (parseInt(event.target.value) === 0) {
+ this.startQueue(
+ event.target.dataset.index,
+ parseInt(event.target.value),
+ );
} else {
- this.updateCart(event);
+ this.validateQuantity(event);
}
}, ON_CHANGE_DEBOUNCE_TIMER);
@@ -16,13 +21,23 @@ if (!customElements.get('quick-add-bulk')) {
this.listenForActiveInput();
this.listenForKeydown();
this.lastActiveInputId = null;
+ const pageParams = new URLSearchParams(window.location.search);
+ window.pageNumber = decodeURIComponent(pageParams.get('page') || '');
}
connectedCallback() {
this.cartUpdateUnsubscriber = subscribe(
PUB_SUB_EVENTS.cartUpdate,
(event) => {
- if (event.source === 'quick-add') {
+ if (
+ event.source === 'quick-add' ||
+ (event.cartData.items &&
+ !event.cartData.items.some(
+ (item) => item.id === parseInt(this.dataset.index),
+ )) ||
+ (event.cartData.variant_id &&
+ !(event.cartData.variant_id === parseInt(this.dataset.index)))
+ ) {
return;
}
// If its another section that made the update
@@ -66,12 +81,6 @@ if (!customElements.get('quick-add-bulk')) {
});
}
- resetQuantityInput(id) {
- const input = document.getElementById(id);
- input.value = input.getAttribute('value');
- this.isEnterPressed = false;
- }
-
cleanErrorMessageOnType(event) {
event.target.addEventListener(
'keypress',
@@ -85,7 +94,7 @@ if (!customElements.get('quick-add-bulk')) {
onCartUpdate() {
return new Promise((resolve, reject) => {
fetch(
- `${window.location.pathname}?section_id=${
+ `${this.getSectionsUrl()}?section_id=${
this.closest('.collection').dataset.id
}`,
)
@@ -100,7 +109,9 @@ if (!customElements.get('quick-add-bulk')) {
this.closest('.collection').dataset.id
}`,
);
- this.innerHTML = sourceQty.innerHTML;
+ if (sourceQty) {
+ this.innerHTML = sourceQty.innerHTML;
+ }
resolve();
})
.catch((e) => {
@@ -110,95 +121,42 @@ if (!customElements.get('quick-add-bulk')) {
});
}
- updateCart(event) {
- this.lastActiveInputId = event.target.getAttribute('data-index');
- this.quantity = this.querySelector('quantity-input');
- // this.quantity.classList.add('loading');
+ updateMultipleQty(items) {
this.selectProgressBar().classList.remove('hidden');
- const body = JSON.stringify({
- quantity: event.target.value,
- id: event.target.getAttribute('data-index'),
- sections: this.getSectionsToRender().map(
- (section) => section.section,
- ),
- });
-
- fetch(`${routes.cart_change_url}`, {
- ...fetchConfig('javascript'),
- ...{ body },
- })
- .then((response) => {
- return response.text();
- })
- .then((state) => {
- const parsedState = JSON.parse(state);
-
- if (parsedState.description || parsedState.errors) {
- event.target.setCustomValidity(parsedState.description);
- event.target.reportValidity();
- this.resetQuantityInput(event.target.id);
- this.selectProgressBar().classList.add('hidden');
- event.target.select();
- this.cleanErrorMessageOnType(event);
- return;
- }
- this.renderSections(parsedState);
-
- publish(PUB_SUB_EVENTS.cartUpdate, {
- source: 'quick-add',
- cartData: parsedState,
- });
- })
- .catch((error) => {
- console.log(error, 'error');
- });
- }
-
- addToCart(event) {
- this.selectProgressBar().classList.remove('hidden');
- this.lastActiveInputId = event.target.getAttribute('data-index');
+ const ids = Object.keys(items);
const body = JSON.stringify({
- items: [
- {
- quantity: parseInt(event.target.value),
- id: parseInt(this.dataset.id),
- },
- ],
+ updates: items,
sections: this.getSectionsToRender().map(
(section) => section.section,
),
+ sections_url: this.getSectionsUrl(),
});
- fetch(`${routes.cart_add_url}`, {
- ...fetchConfig('javascript'),
- ...{ body },
- })
+ fetch(`${routes.cart_update_url}`, { ...fetchConfig(), ...{ body } })
.then((response) => {
return response.text();
})
.then((state) => {
const parsedState = JSON.parse(state);
- if (parsedState.description || parsedState.errors) {
- event.target.setCustomValidity(parsedState.description);
- event.target.reportValidity();
- this.resetQuantityInput(event.target.id);
- this.selectProgressBar().classList.add('hidden');
- event.target.select();
- this.cleanErrorMessageOnType(event);
- // Error handling
- return;
- }
-
- this.renderSections(parsedState);
-
+ this.renderSections(parsedState, ids);
publish(PUB_SUB_EVENTS.cartUpdate, {
source: 'quick-add',
cartData: parsedState,
});
})
- .catch((error) => {
- console.error(error);
+ .catch(() => {
+ // Commented out for now and will be fixed when BE issue is done https://github.com/Shopify/shopify/issues/440605
+ // e.target.setCustomValidity(error);
+ // e.target.reportValidity();
+ // this.resetQuantityInput(ids[index]);
+ // this.selectProgressBar().classList.add('hidden');
+ // e.target.select();
+ // this.cleanErrorMessageOnType(e);
+ })
+ .finally(() => {
+ this.selectProgressBar().classList.add('hidden');
+ this.requestStarted = false;
});
}
@@ -206,11 +164,11 @@ if (!customElements.get('quick-add-bulk')) {
return [
{
id: `quick-add-bulk-${this.dataset.id}-${
- this.closest('.collection').dataset.id
+ this.closest('.collection-quick-add-bulk').dataset.id
}`,
- section: this.closest('.collection').dataset.id,
+ section: this.closest('.collection-quick-add-bulk').dataset.id,
selector: `#quick-add-bulk-${this.dataset.id}-${
- this.closest('.collection').dataset.id
+ this.closest('.collection-quick-add-bulk').dataset.id
}`,
},
{
@@ -226,13 +184,11 @@ if (!customElements.get('quick-add-bulk')) {
];
}
- getSectionInnerHTML(html, selector) {
- return new DOMParser()
- .parseFromString(html, 'text/html')
- .querySelector(selector).innerHTML;
- }
-
- renderSections(parsedState) {
+ renderSections(parsedState, ids) {
+ const intersection = this.queue.filter((element) =>
+ ids.includes(element.id),
+ );
+ if (intersection.length !== 0) return;
this.getSectionsToRender().forEach((section) => {
const sectionElement = document.getElementById(section.id);
if (
diff --git a/assets/quick-add.css b/assets/quick-add.css
index c4239d7c6b..93c5e94350 100644
--- a/assets/quick-add.css
+++ b/assets/quick-add.css
@@ -35,6 +35,10 @@
transform: none;
}
+.quick-add-modal .quick-order-list__container {
+ padding-bottom: 1.5rem;
+}
+
.quick-add-modal__content.quick-add-modal__content--bulk {
width: 90%;
}
@@ -78,6 +82,10 @@
max-width: 100%;
}
+.quick-add-modal__content-info.quick-add-modal__content-info--bulk {
+ padding-bottom: 0;
+}
+
.quick-add-modal__content-info--bulk h3 {
margin-bottom: 0.5rem;
margin-top: 0;
@@ -88,7 +96,17 @@
display: inline-block;
}
+.section-bulk-quick-order-list-padding {
+ padding-top: 2.7rem;
+ padding-bottom: 2.7rem;
+}
+
@media screen and (min-width: 750px) {
+ .section-bulk-quick-order-list-padding {
+ padding-top: 3.6rem;
+ padding-bottom: 3.6rem;
+ }
+
.quick-add-modal__content-info--bulk .card__information-volume-pricing-note {
padding-left: 1.6rem;
}
@@ -122,6 +140,11 @@
padding-left: 1rem;
}
+.quick-add-modal__content-info--bulk-details > a:hover {
+ text-decoration: underline;
+ text-underline-offset: 0.3rem;
+}
+
@media screen and (min-width: 990px) {
.quick-add-modal__content-info--bulk .quick-add__product-media,
.quick-add-modal__content-info--bulk .quick-add__product-container,
@@ -154,7 +177,7 @@
width: auto;
}
-@media screen and (max-width: 990px) {
+@media screen and (max-width: 989px) {
.quick-add-modal__content-info--bulk .quick-add__content-info__media {
display: flex;
margin: 0;
@@ -170,7 +193,7 @@
}
}
-@media screen and (min-width: 989px) {
+@media screen and (min-width: 990px) {
.quick-add-modal__content-info--bulk .quick-add__info {
flex-direction: column;
position: sticky;
@@ -180,7 +203,7 @@
}
}
-@media screen and (max-width: 990px) {
+@media screen and (max-width: 989px) {
.quick-add-modal__content-info--bulk {
flex-direction: column;
}
@@ -202,6 +225,10 @@
width: 100%;
}
+.quick-add-modal__content-info > product-info {
+ padding: 0;
+}
+
@media screen and (max-width: 749px) {
quick-add-modal .slider .product__media-item.grid__item {
margin-left: 1.5rem;
@@ -252,6 +279,10 @@ quick-add-modal .product:not(.featured-product) .product__view-details {
display: block;
}
+.quick-add-modal__content--bulk .product__view-details .icon {
+ margin-left: 1.2rem;
+}
+
quick-add-modal .quick-add-hidden,
quick-add-modal .product__modal-opener:not(.product__modal-opener--image),
quick-add-modal .product__media-item:not(:first-child) {
@@ -371,6 +402,7 @@ quick-add-bulk .progress-bar-container {
overflow: hidden;
border-radius: var(--inputs-radius-outset);
border: var(--inputs-border-width) solid transparent;
+ z-index: -1;
}
quick-add-bulk quantity-input {
@@ -378,6 +410,10 @@ quick-add-bulk quantity-input {
}
quick-add-bulk .quantity__input {
- width: calc(6.5rem / var(--font-body-scale));
+ max-width: calc(6.5rem / var(--font-body-scale));
flex-grow: 0;
}
+
+.quantity__input-disabled {
+ pointer-events: none;
+}
diff --git a/assets/quick-add.js b/assets/quick-add.js
index f63c95ac78..d0c3808aa9 100644
--- a/assets/quick-add.js
+++ b/assets/quick-add.js
@@ -5,6 +5,10 @@ if (!customElements.get('quick-add-modal')) {
constructor() {
super();
this.modalContent = this.querySelector('[id^="QuickAddInfo-"]');
+
+ this.addEventListener('product-info:loaded', ({ target }) => {
+ target.addPreProcessCallback(this.preprocessHTML.bind(this));
+ });
}
hide(preventFocus = false) {
@@ -30,14 +34,12 @@ if (!customElements.get('quick-add-modal')) {
responseText,
'text/html',
);
- const productElement = responseHTML.querySelector(
- 'section[id^="MainProduct-"]',
- );
+ const productElement = responseHTML.querySelector('product-info');
this.preprocessHTML(productElement);
HTMLUpdateUtility.setInnerHTML(
this.modalContent,
- productElement.innerHTML,
+ productElement.outerHTML,
);
if (window.Shopify && Shopify.PaymentButton) {
@@ -48,27 +50,12 @@ if (!customElements.get('quick-add-modal')) {
super.show(opener);
})
.finally(() => {
- this.bindProductChangeCallbacks();
opener.removeAttribute('aria-disabled');
opener.classList.remove('loading');
opener.querySelector('.loading__spinner').classList.add('hidden');
});
}
- bindProductChangeCallbacks() {
- const swapProductUtility =
- this.querySelector('variant-selects')?.swapProductUtility;
- if (swapProductUtility) {
- swapProductUtility.addPreProcessCallback(
- this.preprocessHTML.bind(this),
- );
- swapProductUtility.addPostProcessCallback(() => {
- this.modalContent = this.querySelector('[id^="QuickAddInfo-"]');
- this.bindProductChangeCallbacks();
- });
- }
- }
-
preprocessHTML(productElement) {
productElement.classList.forEach((classApplied) => {
if (classApplied.startsWith('color-') || classApplied === 'gradient')
@@ -82,10 +69,7 @@ if (!customElements.get('quick-add-modal')) {
}
preventVariantURLSwitching(productElement) {
- const variantPicker = productElement.querySelector('variant-selects');
- if (!variantPicker) return;
-
- variantPicker.setAttribute('data-update-url', 'false');
+ productElement.setAttribute('data-update-url', 'false');
}
removeDOMElements(productElement) {
@@ -103,15 +87,23 @@ if (!customElements.get('quick-add-modal')) {
preventDuplicatedIDs(productElement) {
const sectionId = productElement.dataset.section;
+
+ const oldId = sectionId;
+ const newId = `quickadd-${sectionId}`;
productElement.innerHTML = productElement.innerHTML.replaceAll(
- sectionId,
- `quickadd-${sectionId}`,
+ oldId,
+ newId,
);
- productElement
- .querySelectorAll('variant-selects, product-info')
- .forEach((element) => {
- element.dataset.originalSection = sectionId;
- });
+ Array.from(productElement.attributes).forEach((attribute) => {
+ if (attribute.value.includes(oldId)) {
+ productElement.setAttribute(
+ attribute.name,
+ attribute.value.replace(oldId, newId),
+ );
+ }
+ });
+
+ productElement.dataset.originalSection = sectionId;
}
removeGalleryListSemantic(productElement) {
@@ -128,7 +120,7 @@ if (!customElements.get('quick-add-modal')) {
updateImageSizes(productElement) {
const product = productElement.querySelector('.product');
- const desktopColumns = product.classList.contains('product--columns');
+ const desktopColumns = product?.classList.contains('product--columns');
if (!desktopColumns) return;
const mediaImages = product.querySelectorAll('.product__media img');
diff --git a/assets/quick-order-list.css b/assets/quick-order-list.css
index b8a43f04a4..c260997754 100644
--- a/assets/quick-order-list.css
+++ b/assets/quick-order-list.css
@@ -36,6 +36,10 @@ quick-order-list .quantity__button {
z-index: 1;
}
+.variant-item__image-container.global-media-settings::after {
+ content: none;
+}
+
@media screen and (min-width: 990px) {
.quick-order-list__total {
position: sticky;
@@ -305,6 +309,10 @@ quick-order-list-remove-button .icon-remove {
left: 2rem;
top: 1.2rem;
}
+
+ .variant-remove-total--empty .loading__spinner {
+ top: -1rem;
+ }
}
quick-order-list-remove-button:hover .icon-remove {
@@ -442,6 +450,10 @@ quick-order-list-remove-button:hover .icon-remove {
}
}
+.quick-order-list__button-text {
+ text-align: center;
+}
+
.quick-order-list-total__confirmation {
display: flex;
justify-content: center;
diff --git a/assets/quick-order-list.js b/assets/quick-order-list.js
index d8f146bf14..eaf4941d3f 100644
--- a/assets/quick-order-list.js
+++ b/assets/quick-order-list.js
@@ -1,13 +1,12 @@
if (!customElements.get('quick-order-list-remove-button')) {
customElements.define(
'quick-order-list-remove-button',
- class QuickOrderListRemoveButton extends HTMLElement {
+ class QuickOrderListRemoveButton extends BulkAdd {
constructor() {
super();
this.addEventListener('click', (event) => {
event.preventDefault();
- const quickOrderList = this.closest('quick-order-list');
- quickOrderList.updateQuantity(this.dataset.index, 0);
+ this.startQueue(this.dataset.index, 0);
});
}
},
@@ -73,16 +72,11 @@ if (!customElements.get('quick-order-list-remove-all-button')) {
if (!customElements.get('quick-order-list')) {
customElements.define(
'quick-order-list',
- class QuickOrderList extends HTMLElement {
+ class QuickOrderList extends BulkAdd {
constructor() {
super();
this.cart = document.querySelector('cart-drawer');
- this.actions = {
- add: 'ADD',
- update: 'UPDATE',
- };
-
- this.quickOrderListId = `quick-order-list-${this.dataset.productId}`;
+ this.quickOrderListId = `${this.dataset.section}-${this.dataset.productId}`;
this.defineInputsAndQuickOrderTable();
this.variantItemStatusElement = document.getElementById(
@@ -119,15 +113,13 @@ if (!customElements.get('quick-order-list')) {
});
}
+ const pageParams = new URLSearchParams(window.location.search);
+ window.pageNumber = decodeURIComponent(pageParams.get('page') || '');
form.addEventListener('submit', this.onSubmit.bind(this));
- const debouncedOnChange = debounce((event) => {
- this.onChange(event);
- }, ON_CHANGE_DEBOUNCE_TIMER);
- this.addEventListener('change', debouncedOnChange.bind(this));
+ this.addMultipleDebounce();
}
cartUpdateUnsubscriber = undefined;
- sectionRefreshUnsubscriber = undefined;
onSubmit(event) {
event.preventDefault();
@@ -137,34 +129,30 @@ if (!customElements.get('quick-order-list')) {
this.cartUpdateUnsubscriber = subscribe(
PUB_SUB_EVENTS.cartUpdate,
(event) => {
- if (event.source === this.quickOrderListId) {
+ const variantIds = [];
+ this.querySelectorAll('.variant-item').forEach((item) => {
+ variantIds.push(parseInt(item.dataset.variantId));
+ });
+ if (
+ event.source === this.quickOrderListId ||
+ !event.cartData.items?.some((element) =>
+ variantIds.includes(element.variant_id),
+ )
+ ) {
return;
}
// If its another section that made the update
this.refresh().then(() => {
this.defineInputsAndQuickOrderTable();
+ this.addMultipleDebounce();
});
},
);
- this.sectionRefreshUnsubscriber = subscribe(
- PUB_SUB_EVENTS.sectionRefreshed,
- (event) => {
- const isParentSectionUpdated =
- this.sectionId &&
- (event.data?.sectionId ?? '') ===
- `${this.sectionId.split('__')[0]}__main`;
-
- if (isParentSectionUpdated) {
- this.refresh();
- }
- },
- );
- this.sectionId = this.dataset.id;
+ this.sectionId = this.dataset.section;
}
disconnectedCallback() {
this.cartUpdateUnsubscriber?.();
- this.sectionRefreshUnsubscriber?.();
}
defineInputsAndQuickOrderTable() {
@@ -182,23 +170,11 @@ if (!customElements.get('quick-order-list')) {
onChange(event) {
const inputValue = parseInt(event.target.value);
- const cartQuantity = parseInt(event.target.dataset.cartQuantity);
- const index = event.target.dataset.index;
- const name = document.activeElement.getAttribute('name');
-
- const quantity = inputValue - cartQuantity;
this.cleanErrorMessageOnType(event);
if (inputValue == 0) {
- this.updateQuantity(index, inputValue, name, this.actions.update);
+ this.startQueue(event.target.dataset.index, inputValue);
} else {
- this.validateQuantity(
- event,
- name,
- index,
- inputValue,
- cartQuantity,
- quantity,
- );
+ this.validateQuantity(event);
}
}
@@ -209,52 +185,6 @@ if (!customElements.get('quick-order-list')) {
});
}
- validateQuantity(event, name, index, inputValue, cartQuantity, quantity) {
- 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();
- if (cartQuantity > 0) {
- this.updateQuantity(index, inputValue, name, this.actions.update);
- } else {
- this.updateQuantity(index, quantity, name, this.actions.add);
- }
- }
- }
-
- setValidity(event, index, message) {
- event.target.setCustomValidity(message);
- event.target.reportValidity();
- this.resetQuantityInput(index);
- event.target.select();
- }
-
validateInput(target) {
if (target.max) {
return (
@@ -274,7 +204,7 @@ if (!customElements.get('quick-order-list')) {
refresh() {
return new Promise((resolve, reject) => {
- fetch(`${window.location.pathname}?section_id=${this.sectionId}`)
+ fetch(`${this.getSectionsUrl()}?section_id=${this.sectionId}`)
.then((response) => response.text())
.then((responseText) => {
const html = new DOMParser().parseFromString(
@@ -282,7 +212,9 @@ if (!customElements.get('quick-order-list')) {
'text/html',
);
const sourceQty = html.querySelector(`#${this.quickOrderListId}`);
- this.innerHTML = sourceQty.innerHTML;
+ if (sourceQty) {
+ this.innerHTML = sourceQty.innerHTML;
+ }
resolve();
})
.catch((e) => {
@@ -296,7 +228,8 @@ if (!customElements.get('quick-order-list')) {
return [
{
id: this.quickOrderListId,
- section: document.getElementById(this.quickOrderListId).dataset.id,
+ section: document.getElementById(this.quickOrderListId).dataset
+ .section,
selector: `#${this.quickOrderListId} .js-contents`,
},
{
@@ -310,8 +243,9 @@ if (!customElements.get('quick-order-list')) {
selector: '.shopify-section',
},
{
- id: `quick-order-list-total-${this.dataset.productId}`,
- section: document.getElementById(this.quickOrderListId).dataset.id,
+ id: `quick-order-list-total-${this.dataset.productId}-${this.dataset.section}`,
+ section: document.getElementById(this.quickOrderListId).dataset
+ .section,
selector: `#${this.quickOrderListId} .quick-order-list__total`,
},
{
@@ -322,7 +256,22 @@ if (!customElements.get('quick-order-list')) {
];
}
- renderSections(parsedState, id) {
+ addMultipleDebounce() {
+ this.querySelectorAll('quantity-input').forEach((qty) => {
+ const debouncedOnChange = debounce((event) => {
+ this.onChange(event);
+ }, 100);
+ qty.addEventListener('change', debouncedOnChange.bind(this));
+ });
+ }
+
+ renderSections(parsedState, ids) {
+ this.ids.push(ids);
+ const intersection = this.queue.filter((element) =>
+ ids.includes(element.id),
+ );
+ if (intersection.length !== 0) return;
+
this.getSectionsToRender().forEach((section) => {
const sectionElement = document.getElementById(section.id);
if (
@@ -346,13 +295,15 @@ if (!customElements.get('quick-order-list')) {
if (elementToReplace) {
if (
section.selector === `#${this.quickOrderListId} .js-contents` &&
- id !== undefined
+ this.ids.length > 0
) {
- elementToReplace.querySelector(`#Variant-${id}`).innerHTML =
- this.getSectionInnerHTML(
- parsedState.sections[section.section],
- `#Variant-${id}`,
- );
+ this.ids.flat().forEach((i) => {
+ elementToReplace.querySelector(`#Variant-${i}`).innerHTML =
+ this.getSectionInnerHTML(
+ parsedState.sections[section.section],
+ `#Variant-${i}`,
+ );
+ });
} else {
elementToReplace.innerHTML = this.getSectionInnerHTML(
parsedState.sections[section.section],
@@ -362,6 +313,8 @@ if (!customElements.get('quick-order-list')) {
}
});
this.defineInputsAndQuickOrderTable();
+ this.addMultipleDebounce();
+ this.ids = [];
}
getTableHead() {
@@ -437,6 +390,7 @@ if (!customElements.get('quick-order-list')) {
e.target.blur();
if (this.validateInput(e.target)) {
const currentIndex = this.allInputsArray.indexOf(e.target);
+ this.lastKey = e.shiftKey;
if (!e.shiftKey) {
const nextIndex = currentIndex + 1;
const nextVariant =
@@ -447,6 +401,7 @@ if (!customElements.get('quick-order-list')) {
const previousVariant =
this.allInputsArray[previousIndex] ||
this.allInputsArray[this.allInputsArray.length - 1];
+ this.lastElement = previousVariant.dataset.index;
previousVariant.select();
}
}
@@ -467,14 +422,15 @@ if (!customElements.get('quick-order-list')) {
updateMultipleQty(items) {
this.querySelector(
'.variant-remove-total .loading__spinner',
- ).classList.remove('hidden');
+ )?.classList.remove('hidden');
+ const ids = Object.keys(items);
const body = JSON.stringify({
updates: items,
sections: this.getSectionsToRender().map(
(section) => section.section,
),
- sections_url: window.location.pathname,
+ sections_url: this.dataset.url,
});
this.updateMessage();
@@ -486,128 +442,23 @@ if (!customElements.get('quick-order-list')) {
})
.then((state) => {
const parsedState = JSON.parse(state);
- this.renderSections(parsedState);
- })
- .catch(() => {
- this.setErrorMessage(window.cartStrings.error);
- })
- .finally(() => {
- this.querySelector(
- '.variant-remove-total .loading__spinner',
- ).classList.add('hidden');
- });
- }
-
- updateQuantity(id, quantity, name, action) {
- this.toggleLoading(id, true);
- this.cleanErrors();
-
- let routeUrl = routes.cart_change_url;
- let body = JSON.stringify({
- quantity,
- id,
- sections: this.getSectionsToRender().map(
- (section) => section.section,
- ),
- sections_url: window.location.pathname,
- });
- let fetchConfigType;
- if (action === this.actions.add) {
- fetchConfigType = 'javascript';
- routeUrl = routes.cart_add_url;
- body = JSON.stringify({
- items: [
- {
- quantity: parseInt(quantity),
- id: parseInt(id),
- },
- ],
- sections: this.getSectionsToRender().map(
- (section) => section.section,
- ),
- sections_url: window.location.pathname,
- });
- }
-
- this.updateMessage();
- this.setErrorMessage();
-
- fetch(`${routeUrl}`, { ...fetchConfig(fetchConfigType), ...{ body } })
- .then((response) => {
- return response.text();
- })
- .then((state) => {
- const parsedState = JSON.parse(state);
- const quantityElement = document.getElementById(`Quantity-${id}`);
- const items = document.querySelectorAll('.variant-item');
-
- if (parsedState.description || parsedState.errors) {
- const variantItem = document.querySelector(
- `[id^="Variant-${id}"] .variant-item__totals.small-hide .loading__spinner`,
- );
- variantItem.classList.add('loading__spinner--error');
- this.resetQuantityInput(id, quantityElement);
- if (parsedState.errors) {
- this.updateLiveRegions(id, parsedState.errors);
- } else {
- this.updateLiveRegions(id, parsedState.description);
- }
- return;
- }
-
- this.classList.toggle('is-empty', parsedState.item_count === 0);
-
- this.renderSections(parsedState, id);
-
- let hasError = false;
-
- const currentItem = parsedState.items.find(
- (item) => item.variant_id === parseInt(id),
- );
- const updatedValue = currentItem ? currentItem.quantity : undefined;
- if (updatedValue && updatedValue !== quantity) {
- this.updateError(updatedValue, id);
- hasError = true;
- }
-
+ this.renderSections(parsedState, ids);
publish(PUB_SUB_EVENTS.cartUpdate, {
source: this.quickOrderListId,
cartData: parsedState,
});
-
- if (hasError) {
- this.updateMessage();
- } else if (action === this.actions.add) {
- this.updateMessage(parseInt(quantity));
- } else if (action === this.actions.update) {
- this.updateMessage(
- parseInt(quantity - quantityElement.dataset.cartQuantity),
- );
- } else {
- this.updateMessage(
- -parseInt(quantityElement.dataset.cartQuantity),
- );
- }
})
- .catch((error) => {
- this.querySelectorAll('.loading__spinner').forEach((overlay) =>
- overlay.classList.add('hidden'),
- );
- this.resetQuantityInput(id);
- console.error(error);
+ .catch(() => {
this.setErrorMessage(window.cartStrings.error);
})
.finally(() => {
- this.toggleLoading(id);
+ this.querySelector(
+ '.variant-remove-total .loading__spinner',
+ )?.classList.add('hidden');
+ this.requestStarted = false;
});
}
- resetQuantityInput(id, quantityElement) {
- const input =
- quantityElement ?? document.getElementById(`Quantity-${id}`);
- input.value = input.getAttribute('value');
- }
-
setErrorMessage(message = null) {
this.errorMessageTemplate =
this.errorMessageTemplate ??
@@ -678,13 +529,9 @@ if (!customElements.get('quick-order-list')) {
this.updateLiveRegions(id, message);
}
- cleanErrors() {
- this.querySelectorAll('.desktop-row-error').forEach((error) =>
- error.classList.add('hidden'),
- );
- this.querySelectorAll(`.variant-item__error-text`).forEach(
- (error) => (error.innerHTML = ''),
- );
+ cleanErrors(id) {
+ // this.querySelectorAll('.desktop-row-error').forEach((error) => error.classList.add('hidden'));
+ // this.querySelectorAll(`.variant-item__error-text`).forEach((error) => error.innerHTML = '');
}
updateLiveRegions(id, message) {
@@ -717,26 +564,22 @@ if (!customElements.get('quick-order-list')) {
}, 1000);
}
- getSectionInnerHTML(html, selector) {
- return new DOMParser()
- .parseFromString(html, 'text/html')
- .querySelector(selector).innerHTML;
- }
-
toggleLoading(id, enable) {
- const quickOrderList = document.getElementById(this.quickOrderListId);
const quickOrderListItems = this.querySelectorAll(
`#Variant-${id} .loading__spinner`,
);
+ const quickOrderListItem = this.querySelector(`#Variant-${id}`);
if (enable) {
- quickOrderList.classList.add('quick-order-list__container--disabled');
+ quickOrderListItem.classList.add(
+ 'quick-order-list__container--disabled',
+ );
[...quickOrderListItems].forEach((overlay) =>
overlay.classList.remove('hidden'),
);
this.variantItemStatusElement.setAttribute('aria-hidden', false);
} else {
- quickOrderList.classList.remove(
+ quickOrderListItem.classList.remove(
'quick-order-list__container--disabled',
);
quickOrderListItems.forEach((overlay) =>
diff --git a/assets/section-image-banner.css b/assets/section-image-banner.css
index c4a3e23e61..965a2c3119 100644
--- a/assets/section-image-banner.css
+++ b/assets/section-image-banner.css
@@ -348,7 +348,7 @@
}
.banner__box {
- padding: 4rem 3.5rem;
+ padding: 4rem 1.5rem;
position: relative;
height: fit-content;
align-items: center;
@@ -358,8 +358,17 @@
z-index: 1;
}
+.banner--mobile-bottom .banner__box {
+ padding: 4rem 3.5rem;
+}
+
@media screen and (min-width: 750px) {
+ .banner__box {
+ padding: 4rem 3.5rem;
+ }
+
.banner--desktop-transparent .banner__box {
+ padding: 4rem 0;
background: transparent;
max-width: 89rem;
border: none;
diff --git a/assets/section-main-product.css b/assets/section-main-product.css
index 296cb350fe..5a31969b70 100644
--- a/assets/section-main-product.css
+++ b/assets/section-main-product.css
@@ -1,3 +1,7 @@
+product-info {
+ display: block;
+}
+
.product {
margin: 0;
}
diff --git a/assets/section-multicolumn.css b/assets/section-multicolumn.css
index 47ae6b70f6..a35c38cf06 100644
--- a/assets/section-multicolumn.css
+++ b/assets/section-multicolumn.css
@@ -16,6 +16,13 @@
}
}
+@media screen and (min-width: 750px) and (max-width: 989px) {
+ .multicolumn__title {
+ padding-left: 5rem;
+ padding-right: 5rem;
+ }
+}
+
@media screen and (max-width: 989px) {
.multicolumn .page-width {
padding-left: 0;
@@ -124,8 +131,7 @@
}
@media screen and (min-width: 750px) {
- .multicolumn-list.slider,
- .multicolumn-list.grid--4-col-desktop {
+ .multicolumn-list.slider {
padding: 0;
}
diff --git a/config/settings_schema.json b/config/settings_schema.json
index 2b0f365204..e7b203c381 100644
--- a/config/settings_schema.json
+++ b/config/settings_schema.json
@@ -2,7 +2,7 @@
{
"name": "theme_info",
"theme_name": "Dawn",
- "theme_version": "13.0.0",
+ "theme_version": "15.0.0",
"theme_author": "Shopify",
"theme_documentation_url": "https://help.shopify.com/manual/online-store/themes",
"theme_support_url": "https://support.shopify.com/"
diff --git a/layout/theme.liquid b/layout/theme.liquid
index c4d684a2ae..a177c6ff42 100644
--- a/layout/theme.liquid
+++ b/layout/theme.liquid
@@ -26,12 +26,19 @@
{% endif %}
+ {% if template contains 'noindexnofollow' %}
+
+ {% endif %}
+
{% render 'meta-tags' %}
-
+
+
+
+
{%- if settings.animations_reveal_on_scroll -%}
{%- endif -%}
@@ -254,6 +261,15 @@
{{ 'app.css' | asset_url | stylesheet_tag }}
{{ 'base.css' | asset_url | stylesheet_tag }}
+
+
+ {%- if settings.cart_type == 'drawer' -%}
+ {{ 'component-cart-drawer.css' | asset_url | stylesheet_tag }}
+ {{ 'component-cart.css' | asset_url | stylesheet_tag }}
+ {{ 'component-totals.css' | asset_url | stylesheet_tag }}
+ {{ 'component-price.css' | asset_url | stylesheet_tag }}
+ {{ 'component-discounts.css' | asset_url | stylesheet_tag }}
+ {%- endif -%}
{%- unless settings.type_body_font.system? -%}
{% comment %}theme-check-disable AssetPreload{% endcomment %}
@@ -361,5 +377,9 @@
{%- if settings.predictive_search_enabled -%}
{%- endif -%}
+
+ {%- if settings.cart_type == 'drawer' -%}
+
+ {%- endif -%}