← back to Designer Wallcoverings
auto-save: 2026-06-30T20:13:04 (3 files) — pending-approval/boost-filter-consolidation-2026-06-25 vendor-scrapers/china-seas-refresh shopify/theme-fixes/custom-js-qty-2026-06-30/
67ffd7848b1b13c0fe9aa1ebe6c8b1a0566fba57 · 2026-06-30 20:13:10 -0700 · Steve Abrams
Files touched
A shopify/theme-fixes/custom-js-qty-2026-06-30/custom.jsA shopify/theme-fixes/custom-js-qty-2026-06-30/diag.cjsA shopify/theme-fixes/custom-js-qty-2026-06-30/element.quantity-selector.jsA shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/blocks___section-flex-pdp-quantity-picker.liquidA shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquidA shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__section.flex-pdp.quantity-picker.liquidA shopify/theme-fixes/custom-js-qty-2026-06-30/probe.cjsA shopify/theme-fixes/custom-js-qty-2026-06-30/trace.cjs
Diff
commit 67ffd7848b1b13c0fe9aa1ebe6c8b1a0566fba57
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 30 20:13:10 2026 -0700
auto-save: 2026-06-30T20:13:04 (3 files) — pending-approval/boost-filter-consolidation-2026-06-25 vendor-scrapers/china-seas-refresh shopify/theme-fixes/custom-js-qty-2026-06-30/
---
.../theme-fixes/custom-js-qty-2026-06-30/custom.js | 511 +++++++++++++
.../theme-fixes/custom-js-qty-2026-06-30/diag.cjs | 52 ++
.../element.quantity-selector.js | 93 +++
...locks___section-flex-pdp-quantity-picker.liquid | 15 +
.../liquid/snippets__product-form-content.liquid | 800 +++++++++++++++++++++
...ippets__section.flex-pdp.quantity-picker.liquid | 70 ++
.../theme-fixes/custom-js-qty-2026-06-30/probe.cjs | 72 ++
.../theme-fixes/custom-js-qty-2026-06-30/trace.cjs | 45 ++
8 files changed, 1658 insertions(+)
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js
new file mode 100644
index 00000000..64d9005e
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js
@@ -0,0 +1,511 @@
+/*========== 020 Custom js for quantity ===================*/
+
+function mm_refresh_quantity_increments() {
+ jQuery("div.mm_quantity:not(.mm_buttons_added), td.mm_quantity:not(.mm_buttons_added)").each(function(a, b) {
+ var c = jQuery(b);
+ c.addClass("mm_buttons_added"), c.children().first().before('<input type="button" value="-" class="minus" />'), c.children().last().after('<input type="button" value="+" class="plus" />')
+ })
+}
+String.prototype.getDecimals || (String.prototype.getDecimals = function() {
+ var a = this,
+ b = ("" + a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+ return b ? Math.max(0, (b[1] ? b[1].length : 0) - (b[2] ? +b[2] : 0)) : 0
+}), jQuery(document).ready(function() {
+ mm_refresh_quantity_increments()
+
+ // Custom popup contact form (moved from snippets/product.liquid)
+ if (jQuery('.custom-popup-contact-form').length > 0) {
+ jQuery('.custom-popup-contact-form').on('click', function (e) {
+ e.preventDefault();
+ jQuery('#custom-popup-overlay').show();
+ });
+ }
+
+ if (jQuery('#custom-popup-overlay .custom-close').length > 0) {
+ jQuery('#custom-popup-overlay .custom-close').on('click', function () {
+ jQuery('#custom-popup-overlay').hide();
+ });
+ }
+
+ if (jQuery('#custom-popup-overlay .success-message').length > 0) {
+ jQuery('.custom-contact-message').html('Thank you. We will respond to your message shortly.');
+ }
+
+ if (jQuery('#custom-popup-overlay .error-message').length > 0) {
+ jQuery('.custom-contact-message').html(jQuery('#custom-popup-overlay .error-message').html());
+ }
+}), jQuery(document).on("updated_wc_div", function() {
+ mm_refresh_quantity_increments()
+}), jQuery(document).on("click", ".plus, .minus", async function(event) {
+ event.preventDefault();
+
+ var a = jQuery(this).closest(".mm_quantity").find(".qty"),
+ b = parseFloat(a.val()),
+ c = parseFloat(a.attr("max")),
+ d = parseFloat(a.attr("min")),
+ e = a.attr("step");
+ //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
+ b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
+
+ //alert();
+ const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
+ jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
+
+ if($(this).hasClass('cart-page-quantity-value')){
+ const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
+ console.log('-----', variantId)
+ await updateItem(variantId, quantity)
+ window.location.href="/cart"
+ }
+
+
+});
+
+async function updateItem(variantId, quantity, properties={}){
+ const result = await fetch("/cart/change.json", {
+ method:"post",
+ headers: {
+ "Content-type": "application/json",
+ "Accept": "application/json"
+ },
+ body: JSON.stringify({
+ id: variantId,
+ quantity: quantity,
+ properties: properties
+ })
+ })
+ return result.json();
+}
+
+function mm_validate(evt) {
+ var theEvent = evt || window.event;
+ theEvent.preventDefault()
+ /*
+ if (theEvent.type === 'paste') {
+ key = event.clipboardData.getData('text/plain');
+ } else {
+ var key = theEvent.keyCode || theEvent.which;
+ key = String.fromCharCode(key);
+ }
+ var regex = /[0-9]|\./;
+
+ if( !regex.test(key) ) {
+ theEvent.returnValue = false;
+ if(theEvent.preventDefault) theEvent.preventDefault();
+ }*/
+}
+
+
+/* for variant sample */
+jQuery(document).on('change', '.shopify-product-form .single-option-selector, .shopify-product-form .product-select', function () {
+ var $form = jQuery(this).closest('.shopify-product-form');
+
+ var isOptionSelector = jQuery(this).hasClass('single-option-selector');
+
+ var run = function () {
+ var $variantSelect = $form.find('.product-select');
+ var $selected;
+ var selectedSku = '';
+ var optionText = '';
+
+ if ($variantSelect.length && $variantSelect.is('select')) {
+ $selected = $variantSelect.find(':selected');
+ selectedSku = ($selected.attr('data-sku') || '').toString();
+ optionText = ($selected.text() || '').toString();
+ } else if ($variantSelect.length && $variantSelect.is('input')) {
+ // 1-variant products: .product-select is a hidden input
+ selectedSku = ($variantSelect.attr('data-sku') || '').toString();
+ optionText = ($variantSelect.attr('data-variant-title') || '').toString();
+ } else {
+ $selected = jQuery(this).find(':selected');
+ selectedSku = ($selected.attr('data-sku') || '').toString();
+ optionText = ($selected.text() || '').toString();
+ }
+
+ // If we can't determine anything, don't flip the UI.
+ if (!selectedSku && !optionText) return;
+
+ var isSkuSample = /-+sample$/i.test(selectedSku);
+ var isTextSample = /\bsample\b/i.test(optionText);
+ var isPureSample = /^sample(\b|[^a-z])/i.test(optionText.trim());
+ var isSample = isSkuSample || isTextSample;
+
+ var $qty = $form.find('.mm_quantity .qty');
+ var $qtyWrapper = $form.find('.mm_quantity');
+ var originalMin = $qty.data('min');
+ var originalStep = $qty.data('step');
+
+ if (!originalMin) {
+ originalMin = parseFloat($qty.attr('min')) || 1;
+ $qty.data('min', originalMin);
+ }
+ if (!originalStep) {
+ originalStep = parseFloat($qty.attr('step')) || 1;
+ $qty.data('step', originalStep);
+ }
+
+ // Sample request buttons are always available (hide/show is variant-driven only)
+ $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
+
+ if (isSample) {
+ $qty.val(1);
+ $qty.attr({ step: 1, min: 1, max: 1 });
+ $qtyWrapper.find('.plus, .minus').prop('disabled', true);
+ $qtyWrapper.find('.hquantity').attr('value', 1);
+
+ $form.find('.product-option-quantity-label').addClass('hidden').hide();
+ $qtyWrapper.addClass('hidden').hide();
+ $form.find('.add-to-cart').addClass('hidden').hide();
+
+ if (!isPureSample) {
+ $form.find('.dl-second-sample-btn').removeClass('unavailable');
+ } else if (!$form.find('.dl-second-sample-btn').hasClass('unavailable')) {
+ $form.find('.dl-second-sample-btn').addClass('unavailable');
+ }
+ } else {
+ $form.find('.product-option-quantity-label').removeClass('hidden').show();
+ $qtyWrapper.removeClass('hidden').show();
+ $form.find('.add-to-cart').removeClass('hidden').show();
+
+ var minValue = originalMin;
+ var currentValue = parseFloat($qty.val()) || 0;
+ if (currentValue < minValue) {
+ $qty.val(minValue);
+ $qtyWrapper.find('.hquantity').attr('value', minValue);
+ }
+ $qty.attr({ step: originalStep, min: minValue });
+ $qty.removeAttr('max');
+ $qtyWrapper.find('.plus, .minus').prop('disabled', false);
+ }
+
+ // Update the SKU display
+ var displaySku = selectedSku
+ .replace(/-+sample$/i, '')
+ .replace(/-+$/g, '');
+ jQuery('.product-sku-value').text(displaySku);
+
+ // Trade discount text
+ if (jQuery('#customer_tag').val() === 'trade') {
+ requestAnimationFrame(function () {
+ requestAnimationFrame(function () {
+ if (isSample) {
+ jQuery('.product-price-discount').text('Complimentary Sample');
+ } else {
+ // Use the currently displayed currency (symbol/code) from the UI, not hardcoded USD.
+ // Also support both "298,95" and "298.95" style decimals.
+ function dlParseDisplayedNumber(numStr) {
+ var s = (numStr || '').toString().replace(/\s/g, '');
+ var lastComma = s.lastIndexOf(',');
+ var lastDot = s.lastIndexOf('.');
+
+ // No separators: plain number
+ if (lastComma === -1 && lastDot === -1) {
+ var plain = parseFloat(s.replace(/[^\d.-]/g, ''));
+ return isFinite(plain) ? plain : NaN;
+ }
+
+ // Decide decimal separator by whichever comes last
+ var decimalSep = lastComma > lastDot ? ',' : '.';
+ var otherSep = decimalSep === ',' ? '.' : ',';
+
+ var parts = s.split(decimalSep);
+ if (!parts.length) return NaN;
+ var intPart = (parts[0] || '').replace(new RegExp('[\\' + otherSep + ']', 'g'), '').replace(/[^\d-]/g, '');
+ var fracPart = (parts[1] || '').replace(/[^\d]/g, '');
+ var normalized = intPart + (fracPart ? ('.' + fracPart) : '');
+ var val = parseFloat(normalized);
+ return isFinite(val) ? val : NaN;
+ }
+
+ function dlFormatLike(numStr, value) {
+ var s = (numStr || '').toString().replace(/\s/g, '');
+ var lastComma = s.lastIndexOf(',');
+ var lastDot = s.lastIndexOf('.');
+ var decimalSep = lastComma === -1 && lastDot === -1 ? '.' : (lastComma > lastDot ? ',' : '.');
+ var thousandsSep = decimalSep === ',' ? '.' : ',';
+
+ var hasThousands = false;
+ if (lastComma !== -1 || lastDot !== -1) {
+ var decIndex = decimalSep === ',' ? lastComma : lastDot;
+ var intStr = s.slice(0, decIndex);
+ hasThousands = intStr.indexOf(thousandsSep) !== -1;
+ }
+
+ var fixed = (Math.round(value * 100) / 100).toFixed(2);
+ var fixedParts = fixed.split('.');
+ var intPart = fixedParts[0];
+ var fracPart = fixedParts[1] || '00';
+
+ if (hasThousands) {
+ intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
+ }
+
+ return intPart + decimalSep + fracPart;
+ }
+
+ var priceText = (jQuery('.product-price-minimum').first().text() || '').trim();
+ var re = /[\d.,]+/;
+ var m = re.exec(priceText);
+ if (!m) return;
+
+ var numberStr = m[0];
+ var prefix = priceText.slice(0, m.index);
+ var suffix = priceText.slice(m.index + numberStr.length);
+
+ var priceValue = dlParseDisplayedNumber(numberStr);
+ if (!isFinite(priceValue)) return;
+
+ var discountedValue = priceValue * 0.85;
+ var formattedNumber = dlFormatLike(numberStr, discountedValue);
+ jQuery('.product-price-discount').text(prefix + formattedNumber + suffix);
+ }
+ });
+ });
+ }
+ }.bind(this);
+
+ var schedule = function () {
+ requestAnimationFrame(function () {
+ requestAnimationFrame(run);
+ });
+ };
+
+ if (isOptionSelector) {
+ setTimeout(schedule, 60);
+ } else {
+ schedule();
+ }
+});
+
+
+//hide ddl and show last variant
+
+if($('#has_display_variant').length>0 && $('#has_display_variant').val() == "no"){
+ $('.last_variant').hide();
+ if ( $( ".shopify-product-form .single-option-selector" ).length ) {
+
+ $('.shopify-product-form .single-option-selector').hide();
+
+ setTimeout(function(){
+ $('.product-options .select-wrapper').hide();
+ }, 500);
+
+ $('.last_variant').show();
+
+ var single_opt_ind=0;
+ $('.shopify-product-form .single-option-selector option').each(function(index, option){
+ single_opt_ind=index;
+ });
+
+
+ /* DW deep-link honor (vp-dw-commerce 2026-06-24) */
+ // Honor an explicit ?variant=<id> deep-link: if the URL names a variant that
+ // matches one of the options, select THAT option instead of the computed default,
+ // so a link to the Sample renders $4.25 and a link to the Roll renders the roll price.
+ try {
+ var dlWanted = new URLSearchParams(window.location.search).get('variant');
+ if (dlWanted) {
+ $dlOpts.each(function(index, option){
+ var ov = (option.value || '').toString();
+ var dv = (option.getAttribute && option.getAttribute('data-variant-id') || '').toString();
+ if (ov === dlWanted || dv === dlWanted) { single_opt_ind = index; }
+ });
+ }
+ } catch (e) {}
+ /* /DW deep-link honor */
+ $(".shopify-product-form .single-option-selector").prop('selectedIndex', single_opt_ind).trigger('change');
+ }
+}
+//--------------------------------
+
+// Prevent rapid multi-clicks from adding multiple samples before mini-cart UI updates
+function dlGetSampleBtns(variantId) {
+ return $(".dl-sample-btn[data-dlvid='" + variantId + "'], .dl-second-sample-btn[data-dlvid='" + variantId + "']");
+}
+function dlIsSampleLocked(variantId) {
+ return dlGetSampleBtns(variantId).first().attr("data-dl-locked") === "1";
+}
+function dlLockSampleBtns(variantId) {
+ var $btns = dlGetSampleBtns(variantId);
+ $btns.attr("data-dl-locked", "1").addClass("dl-click-locked").attr("aria-disabled", "true");
+ $btns.each(function() {
+ var $b = $(this);
+ if ($b.is("button, input")) $b.prop("disabled", true);
+ });
+}
+function dlUnlockSampleBtns(variantId) {
+ var $btns = dlGetSampleBtns(variantId);
+ $btns.removeAttr("data-dl-locked").removeClass("dl-click-locked").removeAttr("aria-disabled");
+ $btns.each(function() {
+ var $b = $(this);
+ if ($b.is("button, input")) $b.prop("disabled", false);
+ });
+}
+function dlUnlockSampleBtnsWhenInCart(variantId, maxMs) {
+ var start = Date.now();
+ var timer = setInterval(function() {
+ if ($(".mini-cart-item-wrapper [data-variant='" + variantId + "']").length > 0) {
+ clearInterval(timer);
+ dlUnlockSampleBtns(variantId);
+ return;
+ }
+ if (Date.now() - start >= (maxMs || 20000)) {
+ clearInterval(timer);
+ dlUnlockSampleBtns(variantId);
+ }
+ }, 250);
+}
+
+
+$(document).on("click", ".dl-sample-btn", function(e) {
+ e.preventDefault()
+ var $btn = $(this);
+ var $form = $btn.closest("form");
+ var id = $btn.attr("data-dlvid");
+ //check if the sample was added already
+ if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+ if (dlIsSampleLocked(id)) return;
+ dlLockSampleBtns(id);
+ dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+ var product_id = $(this).attr("data-product-id");
+ var $productSelect = $form.find("#product-select-"+product_id);
+ if (!$productSelect.length) $productSelect = $form.find(".product-select");
+ var first_selected_id = $productSelect.is("select") ? $productSelect.find(":selected").val() : $productSelect.val();
+ $productSelect.val(id).trigger('change');
+ //
+ var $qtyInput = $form.find(".input-text.qty");
+ var default_quantity = $qtyInput.val();
+ $qtyInput.val(1);
+ $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+ //go back to original selected status
+
+ $qtyInput.val(default_quantity);
+ if (first_selected_id && first_selected_id !== id) {
+ $productSelect.val(first_selected_id).trigger('change');
+ }
+ } else {
+ //alert("1 sample only");
+ if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+ $(".product-message").removeClass("success-message").addClass("error-message");
+ $(".product-message").text("1 Sample per SKU");
+ }
+ }
+});
+$(document).on("click", ".dl-second-sample-btn", function(e) {
+ e.preventDefault()
+ var $btn = $(this);
+ var $form = $btn.closest("form");
+ var id = $btn.attr("data-dlvid");
+ //check if the sample was added already
+ if($(".mini-cart-item-wrapper [data-variant='"+id+"']").length == 0) {
+ if (dlIsSampleLocked(id)) return;
+ dlLockSampleBtns(id);
+ dlUnlockSampleBtnsWhenInCart(id, 20000);
+
+ //
+ var $qtyInput = $form.find(".input-text.qty");
+ var default_quantity = $qtyInput.val();
+ $qtyInput.val(1);
+ $form.find(".add-to-cart.cowlendar-add-to-cart").trigger("click");
+
+ //go back to original selected status
+
+ $qtyInput.val(default_quantity);
+ } else {
+ //alert("1 sample only");
+ if($(".product-message").hasClass("success-message") || $(".mini-cart-item-wrapper [data-variant='"+id+"']").length > 0) {
+ $(".product-message").removeClass("success-message").addClass("error-message");
+ $(".product-message").text("1 Sample per SKU");
+ }
+ }
+});
+
+//add the remove functionality on the mini-cart
+$(document).on("click", ".remove-item", function(e) {
+ e.preventDefault();
+ var variantId = $(this).closest('.mini-cart-item').attr("data-variant");
+ var quantity = parseInt($(this).closest(".mini-cart-item").find(".mini-cart-item-quantity").find("span").text());
+ var cart_num = parseInt($(".cart-count .cart-count-number").text());
+ var reminder_num = cart_num - quantity;
+ $.ajax({
+ type: "POST",
+ url: "/cart/change.js",
+ dataType: "json",
+ data: {
+ id: parseFloat(variantId),
+ quantity: 0,
+ },
+ }).then((data) => {
+ console.log("success");
+ $(this).closest('.mini-cart-item').remove();
+ dlUnlockSampleBtns(variantId);
+ $(".cart-count .cart-count-number").text(parseInt(reminder_num));
+ if(reminder_num == 0) {
+ $(".mini-cart").hide();
+ }
+ });
+});
+
+$(document).on("click", ".upsell-add-to-cart", function() {
+ var id = $(this).attr("data-item-id");
+ var num = parseInt($(this).attr("data-num"));
+ $.ajax({
+ type: 'POST',
+ url: '/cart/add.js',
+ dataType: 'json',
+ data: {
+ items: [
+ {
+ quantity: num,
+ id: id
+ }
+ ]
+ },
+ success: function (data){
+ location.href= "/cart";
+ }
+ });
+});
+
+/* Custom code for sascha */
+function showMiniCartIfReady() {
+ if (window.innerWidth <= 767) {
+ return;
+ }
+ var $miniCart = $(".mini-cart");
+ if ($miniCart.length && !$miniCart.hasClass("empty")) {
+ $(".mini-cart-wrapper").addClass("is-open");
+ }
+}
+
+function watchMiniCartUpdates() {
+ var target = document.querySelector(".mini-cart-item-wrapper");
+ if (!target || !window.MutationObserver) {
+ return;
+ }
+ var observer = new MutationObserver(function() {
+ showMiniCartIfReady();
+ });
+ observer.observe(target, { childList: true, subtree: true });
+}
+
+$(document).ready(function() {
+ watchMiniCartUpdates();
+});
+
+$(document).on("click", ".add-to-cart.cowlendar-add-to-cart", function() {
+ showMiniCartIfReady();
+});
+
+$(document).on("click", function(event) {
+ var $target = $(event.target);
+ if ($target.closest(".mini-cart, .mini-cart-wrapper").length === 0) {
+ $(".mini-cart-wrapper").removeClass("is-open");
+ }
+});
+
+$(document).on("mouseleave", ".mini-cart-wrapper", function() {
+ $(this).removeClass("is-open");
+});
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/diag.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/diag.cjs
new file mode 100644
index 00000000..b80c1c17
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/diag.cjs
@@ -0,0 +1,52 @@
+// Diagnose the DOM structure around the qty picker on a theme.
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME = process.argv[2];
+const HANDLE = process.argv[3] || 'dwtt-80064-designer-wallcoverings-los-angeles';
+const URL = `https://designer-laboratory-sandbox.myshopify.com/products/${HANDLE}?preview_theme_id=${THEME}`;
+
+(async () => {
+ const browser = await chromium.launch();
+ const page = await (await browser.newContext()).newPage();
+ await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+ await page.waitForSelector('.mm_quantity input[name="quantity"]', { state: 'attached', timeout: 30000 });
+ await page.waitForTimeout(1800);
+
+ // select roll variant
+ await page.evaluate(() => {
+ const sel = document.querySelector('.product-select');
+ if (sel && sel.tagName === 'SELECT') {
+ const opts = Array.from(sel.options);
+ const roll = opts.find(o => !/sample/i.test(o.textContent) && !/-sample$/i.test(o.getAttribute('data-sku')||'')) || opts[0];
+ sel.value = roll.value; sel.dispatchEvent(new Event('change', { bubbles: true }));
+ if (window.jQuery) window.jQuery(sel).trigger('change');
+ }
+ });
+ await page.waitForTimeout(1500);
+
+ const info = await page.evaluate(() => {
+ const blocks = Array.from(document.querySelectorAll('.mm_quantity')).map((b, i) => {
+ const q = b.querySelector('input[name="quantity"]');
+ const h = b.querySelector('.hquantity');
+ const rect = b.getBoundingClientRect();
+ const style = getComputedStyle(b);
+ return {
+ i,
+ className: b.className,
+ visible: style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0,
+ display: style.display,
+ qtyVal: q ? q.value : null,
+ qtyStep: q ? q.getAttribute('step') : null,
+ qtyMin: q ? q.getAttribute('min') : null,
+ qtyDataStep: q ? q.getAttribute('data-step') : null,
+ qtyDataMin: q ? q.getAttribute('data-min') : null,
+ hVal: h ? h.value : null,
+ plus: b.querySelectorAll('.plus').length,
+ minus: b.querySelectorAll('.minus').length,
+ plusVisible: Array.from(b.querySelectorAll('.plus')).map(p => getComputedStyle(p).display !== 'none' && p.getBoundingClientRect().width>0),
+ };
+ });
+ return { blockCount: blocks.length, blocks };
+ });
+ console.log(JSON.stringify(info, null, 2));
+ await browser.close();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/element.quantity-selector.js b/shopify/theme-fixes/custom-js-qty-2026-06-30/element.quantity-selector.js
new file mode 100644
index 00000000..efb77b0b
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/element.quantity-selector.js
@@ -0,0 +1,93 @@
+const selectors = {
+ input: '.element-quantity-selector__input',
+ plus: '.element-quantity-selector__button--plus',
+ minus: '.element-quantity-selector__button--minus'
+}
+
+class QuantitySelector extends HTMLElement {
+ connectedCallback() {
+ this.abortController = new AbortController()
+
+ this.plus = this.querySelector(selectors.plus)
+ this.minus = this.querySelector(selectors.minus)
+ this.input = this.querySelector(selectors.input)
+ this.minValue = this.input.getAttribute('min') || 1
+ this.maxValue = this.input.getAttribute('max')
+
+ this.plus.addEventListener(
+ 'click',
+ function () {
+ let qty = this._getQty()
+ this._change(qty + 1)
+ }.bind(this),
+ { signal: this.abortController.signal }
+ )
+
+ this.minus.addEventListener(
+ 'click',
+ function () {
+ let qty = this._getQty()
+ this._change(qty - 1)
+ }.bind(this),
+ { signal: this.abortController.signal }
+ )
+
+ this.input.addEventListener(
+ 'input',
+ function (evt) {
+ this._change(this._getQty())
+ }.bind(this),
+ { signal: this.abortController.signal }
+ )
+ }
+
+ disconnectedCallback() {
+ this.abortController.abort()
+ }
+
+ _getQty() {
+ let qty = this.input.value
+ if (parseFloat(qty) == parseInt(qty) && !isNaN(qty)) {
+ // We have a valid number!
+ } else {
+ // Not a number. Default to 1.
+ qty = 1
+ }
+ return parseInt(qty)
+ }
+
+ _change(qty) {
+ if (qty <= this.minValue) {
+ qty = this.minValue
+ }
+
+ if (this.maxValue && qty > this.maxValue) {
+ qty = this.maxValue
+ }
+
+ this.input.value = qty
+ this.style.setProperty('--digit-count', `${qty.toString().length}ch`)
+
+ if (this.key && this.key !== '') {
+ this.dispatchEvent(
+ new CustomEvent('cart:quantity', {
+ bubbles: true,
+ detail: [this.key, qty, this]
+ })
+ )
+ } else {
+ this.dispatchEvent(
+ new CustomEvent('quantity:change', {
+ detail: {
+ qty: qty
+ }
+ })
+ )
+ }
+ }
+ get key() {
+ return this.getAttribute('key')
+ }
+}
+
+customElements.define('quantity-selector', QuantitySelector)
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/blocks___section-flex-pdp-quantity-picker.liquid b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/blocks___section-flex-pdp-quantity-picker.liquid
new file mode 100644
index 00000000..8fd3130b
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/blocks___section-flex-pdp-quantity-picker.liquid
@@ -0,0 +1,15 @@
+{% render 'section.flex-pdp.quantity-picker',
+ product: closest.product,
+ show_inventory_status: false
+%}
+
+{% schema %}
+{
+ "name": "t:flex_pdp.blocks.quantity_picker.name",
+ "presets": [
+ {
+ "name": "t:flex_pdp.blocks.quantity_picker.name"
+ }
+ ]
+}
+{% endschema %}
\ No newline at end of file
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid
new file mode 100644
index 00000000..598116d0
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__product-form-content.liquid
@@ -0,0 +1,800 @@
+{% comment %}
+ @param product {Product}
+
+ @param show_social_media_icons {Boolean}
+
+ @param show_payment_button {Boolean}
+ Show the DPBs in the product form
+{% endcomment %}
+
+{% assign product_id = product.id %}
+{% assign selected_variant = product.selected_or_first_available_variant %}
+
+{%- liquid
+ assign _selected_variant_sku = selected_variant.sku | default: '' | downcase
+ assign _selected_variant_title = selected_variant.title | default: '' | downcase
+
+ assign _selected_variant_sku_suffix = _selected_variant_sku | slice: -7, 7
+
+ assign is_sample_sku = false
+ if _selected_variant_sku != blank and _selected_variant_sku_suffix == '-sample'
+ assign is_sample_sku = true
+ endif
+
+ assign is_sample_title = false
+ if _selected_variant_title contains 'sample'
+ assign is_sample_title = true
+ endif
+
+ assign is_sample_like = false
+ if is_sample_sku
+ assign is_sample_like = true
+ elsif is_sample_title and selected_variant.price == 0
+ assign is_sample_like = true
+ endif
+-%}
+
+<!-- -----------020------------ -->
+{% assign prods_quantity_order_min = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_min'] %}
+ {% if product.metafields['global']['v_prods_quantity_order_min'] != '' %}
+ {% assign prods_quantity_order_min = product.metafields['global']['v_prods_quantity_order_min'] %}
+ {% endif %}
+{% endif %}
+
+{% assign prods_quantity_order_units = 1 %}
+
+{% if product.metafields['global']['v_prods_quantity_order_units'] %}
+ {% if product.metafields['global']['v_prods_quantity_order_units'] != '' %}
+ {% assign prods_quantity_order_units = product.metafields['global']['v_prods_quantity_order_units'] %}
+ {% endif %}
+{% endif %}
+
+<!-- Minimum order quantity from custom metafields (e.g. Ralph Lauren fabrics) -->
+{% assign min_order_qty = product.metafields.custom.minimum_order_quantity | default: nil %}
+{% assign min_order_unit = product.metafields.custom.minimum_order_unit | default: '' %}
+{% assign has_min_order = false %}
+{% if min_order_qty != nil and min_order_qty != blank and min_order_qty > 1 %}
+ {% assign has_min_order = true %}
+ {% assign min_order_qty_num = min_order_qty | plus: 0 %}
+ {% if min_order_qty_num > prods_quantity_order_min %}
+ {% assign prods_quantity_order_min = min_order_qty_num %}
+ {% endif %}
+{% endif %}
+
+<!-- -------------------------- -->
+<script>
+ setTimeout(function () {
+ const qtySelector = document.querySelector('.mm_quantity .input-text.qty');
+ if (qtySelector) {
+ qtySelector.value = {{ prods_quantity_order_min }};
+ }
+ }, 1000);
+</script>
+
+{% if has_min_order %}
+<!-- Minimum order notice and validation -->
+<style>
+ .min-order-notice {
+ background: #fff3cd;
+ border: 1px solid #ffc107;
+ border-radius: 4px;
+ padding: 8px 12px;
+ margin: 10px 0;
+ font-size: 14px;
+ font-weight: 600;
+ color: #856404;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+ .min-order-notice svg {
+ flex-shrink: 0;
+ }
+ .min-order-error {
+ background: #f8d7da;
+ border: 1px solid #f5c6cb;
+ color: #721c24;
+ border-radius: 4px;
+ padding: 8px 12px;
+ margin: 8px 0 0;
+ font-size: 13px;
+ display: none;
+ }
+</style>
+<div class="min-order-notice" data-min-order-notice>
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 100 14A7 7 0 008 1zm0 2.5a.75.75 0 01.75.75v4a.75.75 0 01-1.5 0v-4A.75.75 0 018 3.5zm0 8a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
+ Minimum order: {{ min_order_qty }} {{ min_order_unit }}
+</div>
+<script>
+ (function() {
+ var MIN_QTY = {{ min_order_qty }};
+ var MIN_UNIT = {{ min_order_unit | json }};
+
+ document.addEventListener('DOMContentLoaded', function() {
+ // Find the product form (could be either .shopify-product-form or form with data-product-form)
+ var forms = document.querySelectorAll('form[action*="/cart/add"], form[data-product-form]');
+ forms.forEach(function(form) {
+ form.addEventListener('submit', function(e) {
+ var qtyInput = form.querySelector('.mm_quantity .input-text.qty') || form.querySelector('input[name="quantity"]');
+ if (!qtyInput) return;
+
+ var qty = parseInt(qtyInput.value, 10);
+ if (isNaN(qty) || qty < MIN_QTY) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+
+ // Show error message
+ var errorDiv = form.querySelector('.min-order-error');
+ if (!errorDiv) {
+ errorDiv = document.createElement('div');
+ errorDiv.className = 'min-order-error';
+ var addToCartBtn = form.querySelector('.product-add-to-cart');
+ if (addToCartBtn) {
+ addToCartBtn.appendChild(errorDiv);
+ }
+ }
+ errorDiv.textContent = 'Please enter a quantity of at least ' + MIN_QTY + ' ' + MIN_UNIT + '.';
+ errorDiv.style.display = 'block';
+
+ // Also fix the value
+ qtyInput.value = MIN_QTY;
+ var hqty = form.querySelector('.hquantity');
+ if (hqty) hqty.value = MIN_QTY;
+
+ // Hide error after 5s
+ setTimeout(function() { errorDiv.style.display = 'none'; }, 5000);
+ return false;
+ }
+ });
+ });
+
+ // Also enforce min on the minus button - prevent going below minimum
+ jQuery(document).on('click', '.minus', function() {
+ var $qty = jQuery(this).closest('.mm_quantity').find('.qty');
+ var currentVal = parseInt($qty.val(), 10);
+ if (currentVal < MIN_QTY) {
+ $qty.val(MIN_QTY);
+ jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+ }
+ });
+
+ // Enforce min on blur (manual typing)
+ jQuery(document).on('blur', '.mm_quantity .input-text.qty', function() {
+ var currentVal = parseInt(jQuery(this).val(), 10);
+ if (isNaN(currentVal) || currentVal < MIN_QTY) {
+ jQuery(this).val(MIN_QTY);
+ jQuery(this).closest('.mm_quantity').find('.hquantity').attr('value', MIN_QTY);
+ }
+ });
+
+ // Hide notice when sample variant is selected
+ jQuery(document).on('change', '.product-select, .single-option-selector', function() {
+ var $form = jQuery(this).closest('form, [data-product-form]');
+ var $selected = $form.find('.product-select option:selected, .product-select[type="hidden"]');
+ var selectedText = ($selected.text() || $selected.attr('data-variant-title') || '').toLowerCase();
+ var selectedSku = ($selected.attr('data-sku') || '').toLowerCase();
+ var isSample = /sample/i.test(selectedText) || /-sample$/i.test(selectedSku);
+
+ var $notice = jQuery('.min-order-notice[data-min-order-notice]');
+ if (isSample) {
+ $notice.hide();
+ } else {
+ $notice.show();
+ }
+ });
+ });
+ })();
+</script>
+{% endif %}
+
+{% for block in section.blocks %}
+ {% case block.type %}
+ {% when 'vendor' %}
+ <a class="product-vendor" href="{{ product.vendor | url_for_vendor }}" {{ block.shopify_attributes }}>{{ product.vendor }}</a>
+
+ {% when 'title' %}
+ <h1 class="product-title" {{ block.shopify_attributes }}>
+ {% if product != blank %}
+ {% if link_to %}
+ <a href="{{ product.url }}">
+ {{ product.title }}
+ </a>
+ {% else %}
+ {% comment %}{{ product.title }} {% endcomment %}
+ <a href="https://designer-laboratory-sandbox.myshopify.com/search?q={{ product.title }}">
+ {{ product.title }}
+ </a>
+ {% endif %}
+ {% else %}
+ {{ 'products.product.onboarding.title' | t }}
+ {% endif %}
+ </h1>
+ <span class="product-sku-label">SKU: </span>
+ {%- liquid
+ assign sku_raw = product.selected_or_first_available_variant.sku | default: ''
+ assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+ assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+ assign sku_last_char = sku_display | slice: -1, 1
+ if sku_display != blank and sku_last_char == '-'
+ assign sku_display = sku_display | remove_last: '-'
+ endif
+ -%}
+ <span class="product-sku-value">{{ sku_display }}</span>
+ {% when '@app' %}
+ <div class="product-app" {{ block.shopify_attributes }}>
+ {% render block %}
+ </div>
+
+ {% when 'custom-liquid' %}
+ {% if block.settings.custom_liquid != blank %}
+ <div class="product__custom-liquid" {{ block.shopify_attributes }}>
+ {{ block.settings.custom_liquid }}
+ </div>
+ {% endif %}
+
+ {% when 'collapsible-tab' %}
+ {% if block.settings.collapsible_tab_heading != blank %}
+ <details class="collapsible-tab" {{ block.shopify_attributes }}>
+ <summary class="collapsible-tab__heading">
+ <span>{{ block.settings.collapsible_tab_heading | escape }}</span>
+ {%
+ render 'icons',
+ id: 'chevron-right',
+ %}
+ </summary>
+
+ {% if block.settings.collapsible_tab_text != blank %}
+ <div class="collapsible-tab__text rte">
+ {{ block.settings.collapsible_tab_text }}
+ </div>
+ {% endif %}
+ </details>
+ {% endif %}
+
+ {% when 'tabs' %}
+ {% assign tab_counter = 1 %}
+ <div class="product-tabs" {{ block.shopify_attributes }}>
+ {% if block.settings.show_product_description %}
+ <input
+ class="product-tabs__radio"
+ id="product-tabs__radio-{{ section.id }}"
+ name="{{ section.id }}"
+ tabindex="0"
+ type="radio"
+ checked="checked"
+ >
+
+ <label class="product-tabs__label" for="product-tabs__radio-{{ section.id }}">
+ {{ 'onboarding.tab_title'| t }}
+ </label>
+
+ <div class="product-tabs__panel rte" tabindex="0">
+ {% if onboarding %}
+ {{ 'onboarding.tab_content'| t }}
+ {% else %}
+ {% if product.description != blank %}
+ {{ product.description }}
+ {% else %}
+ {{ 'onboarding.tab_content'| t }}
+ {% endif %}
+ {% endif %}
+ </div>
+ {% endif %}
+
+ {% for i in (1..3) %}
+ {% assign tab_text_key = 'tab_text_' | append: i %}
+ {% assign tab_heading_key = 'tab_heading_' | append: i %}
+ {% assign tab_heading_value = block.settings[tab_heading_key] %}
+ {% assign tab_text_value = block.settings[tab_text_key] %}
+
+ {% if tab_heading_value != blank %}
+ <input
+ class="product-tabs__radio"
+ id="product-tabs__radio-{{ i }}-{{ section.id }}"
+ name="{{ section.id }}"
+ tabindex="0"
+ type="radio"
+ {% if tab_counter == 1 and block.settings.show_product_description == false %}
+ checked
+ {% endif %}
+ >
+
+ <label class="product-tabs__label" for="product-tabs__radio-{{ i }}-{{ section.id }}">
+ {{ tab_heading_value | escape }}
+ </label>
+
+ <div class="product-tabs__panel rte" tabindex="0">
+ {{ tab_text_value }}
+ </div>
+
+ {% assign tab_counter = tab_counter | plus: 1 %}
+ {% endif %}
+ {% endfor %}
+ </div>
+
+ {% when 'price' %}
+ <div class="product__price" {{ block.shopify_attributes }}>
+ {%- comment -%}
+ DW trade/designer pricing tiers (2026-06-11): any customer carrying one of these
+ professional tags sees the trade price display (15% off, ×0.85). This restores the
+ pre-app behavior where every designer/trade tier got the discount — not just 'trade'.
+ Matches *any* tag containing 'designer' (interior_designer, commercial_designer, …)
+ plus the explicit tiers below. Add new tiers to the explicit list as needed.
+ {%- endcomment -%}
+ {%- assign dw_is_trade = false -%}
+ {%- for dw_t in customer.tags -%}
+ {%- assign dw_tl = dw_t | downcase | strip -%}
+ {%- if dw_tl contains 'designer' or dw_tl == 'trade' or dw_tl == 'architect' or dw_tl == 'stager' or dw_tl == 'hospitality' or dw_tl == 'retailer' -%}
+ {%- assign dw_is_trade = true -%}
+ {%- break -%}
+ {%- endif -%}
+ {%- endfor -%}
+ <input type="hidden" id="customer_tag" value="{% if dw_is_trade %}trade{% endif %}">
+ <p class="product-price">
+ {% comment %} SA Wholesale (saw_/wbuyx/"WHO") app removed 2026-06-11 — it left the price span blank. Render native variant price unconditionally. {% endcomment %}
+ <!-- native price markup -->
+ <meta itemprop="price" content="{{ selected_variant.price | money_without_currency }}">
+ <span class="product-price-minimum money {% if dw_is_trade %}original{% endif %}">
+ {% if product != blank %}
+ {{ selected_variant.price | money }}
+ {% else %}
+ {{ 'products.product.onboarding.price' | t }}
+ {% endif %}
+ </span>
+ {% if dw_is_trade %}
+ <span
+ class="product-price-discount"
+ >
+ {% assign target_product = selected_variant %}
+ {% assign com_selected_variant_title = selected_variant.title | capitalize %}
+ {% assign com_selected_variant_sku = selected_variant.sku | default: '' | downcase %}
+ {% if com_selected_variant_title == "Sample" or com_selected_variant_sku contains "sample" %}
+ {% comment %} {% render 'money', value: 0 %} {% endcomment %}
+ Complimentary Sample
+ {% else %}
+ {% assign com_variant_first_title = product.variants[0].title | capitalize %}
+ {% if com_variant_first_title == "Sample" and product.variants[1] != blank %}
+ {% assign target_product = product.variants[1] %}
+ {% endif %}
+ {% assign discounted_price = target_product.price | times:0.85 | round: 2 %}
+ {% render 'money', value: discounted_price %}
+ {% endif %}
+
+ </span>
+ {% endif %}
+ {% comment %}
+ <span
+ class="product-price-compare money original {% if selected_variant.compare_at_price > selected_variant.price %}visible{% endif %}"
+ >
+ {{ selected_variant.compare_at_price | money }}
+ </span>
+ {% endcomment %}
+ </p>
+
+ {% assign variant_for_unit_price = selected_variant %}
+ {% comment %}Inject unit-price begin{% endcomment %}
+ {% comment %}
+ @param variant_for_unit_price
+ Product variant for price
+ @param tax_text
+ String containing 'tax included' text
+ {% endcomment %}
+
+ {% capture total_quantity %}
+ <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+ {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+ </span>
+ {% endcapture %}
+
+
+ {% capture unit_price %}
+ <span class="product-price__unit-price-amount money" data-unit-price-amount>
+ {{ variant_for_unit_price.unit_price | money }}
+ </span>
+ {% endcapture %}
+ {% capture unit_measure %}
+ <span class="product-price__unit-price-measure" data-unit-price-measure>
+ {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+ {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+ {%- endif %}
+ {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+ </span>
+ {% endcapture %}
+
+ <div
+ class="
+ product-price__unit-price
+ {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+ "
+ data-unit-price
+ >
+ {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+ </div>
+
+ {% assign variant_for_unit_price = blank %}
+ {% comment %}Inject unit-price end{% endcomment %}
+
+
+ {%- capture tax_text -%}
+ {{ 'products.product.tax_line_html' | t }}
+ {%- endcapture -%}
+
+ {%- if tax_text != blank -%}
+ <div class="
+ product-price__tax
+ {% unless selected_variant.taxable %}hidden{% endunless %}
+ "
+ data-tax-line
+ >
+ {{ tax_text }}
+ </div>
+ {%- endif -%}
+
+ {{ form | payment_terms }}
+ </div>
+
+ {% when 'form' %}
+
+ <div class="product__form" {{ block.shopify_attributes }}>
+
+ {% unless product.has_only_default_variant %}
+ <div class="product-options">
+ {%-
+ render 'product-options-dropdown',
+ product: product,
+ form_id: product_id
+ -%}
+
+ <label
+ class="
+ product-option-column-1
+ product-option-row-4
+ product-option-quantity-label
+ {% if is_sample_like %}hidden{% endif %}
+ "
+ for="default-variant-quantity-input"
+ >
+ <strong>{{ 'general.general.quantity' | t }}:</strong>
+ </label>
+ {% comment %}
+ <input
+ class="product-option-quantity"
+ id="default-variant-quantity-input"
+ data-product-quantity-input
+ type="number"
+ inputmode="numeric"
+ name="quantity"
+ value="1"
+ >
+ {% endcomment %}
+ <!-- 020 -->
+ <div class="mm_quantity mm_buttons_added product-option-row-4 {% if is_sample_like %}hidden{% endif %}">
+ <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+ <input type="button" value="-" class="minus">
+ <input
+ type="text"
+ step="{{ prods_quantity_order_units }}"
+ min="{{ prods_quantity_order_min }}"
+ max=""
+ name="quantity"
+ value="{{ prods_quantity_order_min }}"
+ title="Qty"
+ class="input-text qty text"
+ size="4"
+ pattern=""
+ inputmode=""
+ onkeypress="mm_validate(event)"
+ >
+ <input type="button" value="+" class="plus">
+ </div>
+ <!-- -->
+
+ <div class="selector-wrapper no-js-required">
+ <label for="product-select-{{ product_id }}"></label>
+ <select
+ class="product-select"
+ name="id"
+ id="product-select-{{ product_id }}"
+ data-select-ignore
+ >
+ {% for variant in product.variants %}
+ {% if variant.available %}
+ {% assign com_variant_title = variant.title | capitalize %}
+ {% if com_variant_title == "Sample" %}
+ {% assign dl_sample_variant = variant.id %}
+ {% assign dl_sample_variant_title = product.title | append: ' - ' | append: variant.title %}
+ {% assign dl_sample = 'small_sample' %}
+ {% endif %}
+
+ {% if com_variant_title == "Large Sample" %}
+ {% assign dl_sample_variant2 = variant.id %}
+ {% assign dl_sample_variant_title2 = product.title | append: ' - ' | append: variant.title %}
+ {% assign dl_sample2 = 'large_sample' %}
+ {% endif %}
+
+ <option
+ {% if variant == selected_variant %}selected="selected"{% endif %}
+ value="{{ variant.id }}"
+ data-variant-id="{{ variant.id }}"
+ data-sku="{{ variant.sku }}"
+ data-vprice="{{ variant.price }}"
+ >
+ {{ variant.title }} - {{ variant.price | money }}
+ </option>
+ {% else %}
+ <option
+ disabled="disabled"
+ data-variant-id="{{ variant.id }}"
+ value="{{ variant.id }}"
+ data-sku="{{ variant.sku }}">
+ {{ variant.title }} - {{ 'products.product.sold_out' | t }}
+ </option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ {% else %}
+ <div class="product-options product-options-default-only">
+ <input
+ class="product-select"
+ name="id"
+ value="{{ product.variants[0].id }}"
+ type="hidden"
+ data-variant-title="{{ product.variants[0].title }}"
+ data-sku="{{ product.variants[0].sku }}"
+ data-vprice="{{ product.variants[0].price }}"
+ />
+
+ <label
+ class="
+ product-option-column-1
+ product-option-row-1
+ product-option-quantity-label
+ {% if is_sample_like %}hidden{% endif %}
+ "
+ for="quantity-input"
+ >
+ <strong>{{ 'general.general.quantity' | t }}:</strong>
+ </label>
+
+<!-- <input
+ class="product-option-quantity"
+ id="quantity-input"
+ data-product-quantity-input
+ type="number"
+ inputmode="numeric"
+ name="quantity"
+ value="1"
+ > -->
+ <!-- 020 -->
+ <div class="mm_quantity mm_buttons_added product-option-row-1 {% if is_sample_like %}hidden{% endif %}">
+ <input type="hidden" class="hquantity" name="hquantity" value="{{ prods_quantity_order_min }}">
+ <input type="button" value="-" class="minus">
+ <input
+ type="text"
+ step="{{ prods_quantity_order_units }}"
+ min="{{ prods_quantity_order_min }}"
+ max=""
+ name="quantity"
+ value="{{ prods_quantity_order_min }}"
+ title="Qty"
+ class="input-text qty text"
+ size="4"
+ pattern=""
+ inputmode=""
+ onkeypress="mm_validate(event)"
+ >
+ <input type="button" value="+" class="plus">
+ </div>
+ <!-- -->
+ </div>
+ {% endunless %}
+
+ {% for tag in product.tags %}
+ {% if tag contains 'custom mural' %}
+ <!-- Custom Price Calculator -->
+ <div id="custom-calculator" style="display:none" data-calculatorid="62388fabd48c8"></div>
+ <script>
+ document.querySelector(".product-options .last_variant").innerHTML = "Custom Sizes";
+
+ var appendCalc = setInterval(moveCalc, 300);
+
+ function moveCalc() {
+ var calc = document.getElementById("calculator");
+ if (calc) {
+ document
+ .querySelector(".product-options")
+ .insertBefore(
+ document.getElementById("calculator"),
+ document.querySelector(".product-options").children[2]
+ );
+ clearInterval(appendCalc);
+ }
+ }
+ </script>
+ <style>
+ .product-options {
+ display: flex;
+ flex-direction: column;
+ align-items: baseline;
+ margin-left: 0;
+ }
+ .product-options .product-option-quantity-label,
+ .mm_quantity.mm_buttons_added {
+ display: none !important;
+ }
+ </style>
+ {% endif %}
+ {% endfor %}
+
+ {% if product.available %}
+ <div id="infiniteoptions-container"></div>
+ <div id="uploadery-container"></div>
+ {% endif %}
+
+ <!-- Print PDF button -->
+ {% render 'button-product-pdf-print' %}
+
+ <div
+ class="
+ product-add-to-cart
+ {% if product != blank and show_payment_button %}
+ product-smart-payments
+ {% endif %}
+ "
+ >
+ {% if selected_variant.available and template.name != 'password' %}
+ <!-- 020 -->
+ <div class="custom-contact-message"></div>
+
+ <a
+ href="#"
+ class="dl-contact-btn custom-popup-contact-form"
+ {% if variant.price == 0 %}
+ style="display:inline-block;"
+ {% else %}
+ style="display:none;"
+ {% endif %}
+ >
+ Contact Us
+ </a>
+
+ {% assign title = 'Request Sample' %}
+ {% assign showonly = blank %}
+ {% if product.tags contains "Showroom only" and product.price == 0 %}
+ {% assign showonly = 'showonly' %}
+ {% endif %}
+ {% if product.tags contains "Showroom Only" and product.price == 0 %}
+ {% assign showonly = 'showonly' %}
+ {% endif %}
+
+ {% if is_sample_sku and dl_sample == blank and dl_sample2 == blank %}
+ <a
+ href=""
+ data-dlvid="{{ selected_variant.id }}"
+ data-dlvtitle="{{ product.title | append: ' - Sample' }}"
+ class="dl-sample-btn {{ showonly }}"
+ data-product-id="{{ product.id }}"
+ >
+ {{ title }}
+ </a>
+ {% endif %}
+
+ {% if dl_sample != blank and dl_sample2 != blank and dl_sample == 'small_sample' and dl_sample2 == 'large_sample' %}
+ {% assign title = 'Small Sample' %}
+ {% endif %}
+
+ {% if dl_sample == 'small_sample' and dl_sample_variant !='' and dl_sample_variant_title != '' %}
+ <a
+ href=""
+ data-dlvid="{{ dl_sample_variant }}"
+ data-dlvtitle="{{ dl_sample_variant_title }}"
+ class="dl-sample-btn {{ showonly }}"
+ data-product-id="{{ product.id }}"
+ >
+ {{ title }}
+ </a>
+ {% endif %}
+
+ {% if dl_sample2 == 'large_sample' and dl_sample_variant2 !='' and dl_sample_variant_title2 != '' %}
+ <a
+ href=""
+ data-dlvid="{{ dl_sample_variant2 }}"
+ data-dlvtitle="{{ dl_sample_variant_title2 }}"
+ class="dl-sample-btn {{ showonly }}"
+ data-product-id="{{ product.id }}"
+ >
+ Large Sample
+ </a>
+ {% endif %}
+ {% for variant_item in product.variants %}
+ {% if variant_item.price == 0 and variant_item.title contains "Sample" and variant_item.title != "Sample" %}
+ {% assign showing_title = variant_item.title %}
+ {% if showing_title == "Textured Vinyl Sample" %}
+ {% assign showing_title = "Vinyl Sample" %}
+ {% endif %}
+ <a
+ href=""
+ data-dlvid="{{ variant_item.id }}"
+ data-dlvtitle="{{ variant_item.title }}"
+ class="dl-second-sample-btn {% if variant_item.id != product.selected_or_first_available_variant.id %}unavailable{% endif %} {{ showonly }}"
+ data-product-id="{{ product.id }}"
+ >
+ Request {{ showing_title }}
+ </a>
+ {% endif %}
+ {% endfor %}
+
+ {% render 'spec-sheet-button' %}
+
+<!-- <input class="add-to-cart" type="submit" value="{{ 'products.product.add_to_cart' | t }}" /> -->
+ {% assign com_first_variant_title = product.selected_or_first_available_variant.title | capitalize %}
+ <input class="add-to-cart {% if is_sample_like %}hidden{% endif %} {% if product.variants.size == 1 and com_first_variant_title == "Sample" %}hidden{% endif %} {% if product.selected_or_first_available_variant.price == 0 %}unavailable{% endif %}" type="submit" value="{{ 'products.product.add_to_cart' | t }}"/>
+ {% else %}
+ <input class="add-to-cart disabled" type="submit" value="{{ 'products.product.sold_out' | t }}" disabled/>
+ {% endif %}
+
+ <!--020 -->
+ {% for t in product.tags %}
+ {% if t == 'Customize' %}
+ <a href="#" class="dl-customize-btn custom-popup-contact-form">Customize this Pattern</a>
+ {% endif %}
+ {% endfor %}
+
+ {% if show_payment_button %}
+ {{ form | payment_button }}
+ {% endif %}
+
+ {% capture the_snippet_fave %}{% include 'socialshopwave-widget-fave' %}{% endcapture %}
+ {% unless the_snippet_fave contains 'Liquid error' %}
+ {{ the_snippet_fave }}
+ {% endunless %}
+ </div>
+ <div class="product-message"></div>
+
+ <div data-surface-pick-up></div>
+ {%-
+ render 'modal',
+ modal_id: 'surface-pick-up',
+ modal_class: 'surface-pick-up-modal',
+ -%}
+ </div>
+
+ {% when 'social' %}
+ <div class="product__social" {{ block.shopify_attributes }}>
+ {% render 'share-buttons' %}
+ </div>
+
+ {% when 'description' %}
+ {% if product != blank and product.description != blank %}
+ <div class="product-description rte" {{ block.shopify_attributes }}>
+ {{ product.description }}
+ </div>
+ {% elsif product == blank %}
+ <div class="product-description rte" {{ block.shopify_attributes }}>
+ {{ 'products.product.onboarding.description' | t }}
+ </div>
+ {% endif %}
+
+ {%- when 'rating' -%}
+ {%- if product.metafields.reviews.rating.value != blank -%}
+ <div class="product__rating rating" {{ block.shopify_attributes }}>
+ {%
+ render 'rating-stars',
+ value: product.metafields.reviews.rating.value.rating,
+ scale_max: product.metafields.reviews.rating.value.scale_max,
+ %}
+ <p class="rating__text">
+ <span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
+ </p>
+ <p class="rating__count">
+ <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
+ <span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "general.accessibility.total_reviews" | t }}</span>
+ </p>
+ </div>
+ {%- endif -%}
+ {% endcase %}
+{% endfor %}
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__section.flex-pdp.quantity-picker.liquid b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__section.flex-pdp.quantity-picker.liquid
new file mode 100644
index 00000000..c4848b56
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/liquid/snippets__section.flex-pdp.quantity-picker.liquid
@@ -0,0 +1,70 @@
+{% doc %}
+ Copyright © 2025 Archetype Themes LP. All rights reserved.
+
+ Renders a quantity selector with optional inventory status display.
+
+ This component provides a quantity input field with increment/decrement buttons
+ and can optionally display inventory status information. It respects inventory
+ limits when inventory management is enabled and shows appropriate maximum values.
+
+ @param {boolean} [show_inventory_status] - Whether to show inventory status below quantity selector (default: true)
+ @param {number} [inventory_threshold] - The inventory level at which to show low stock warning (default: 10)
+
+ @example
+ {% render 'section.flex-pdp.quantity-picker', show_inventory_status: true, inventory_threshold: 5, product: closest.product %}
+{% enddoc %}
+
+{%- liquid
+ assign form_id = 'section.flex-pdp.form-' | append: section.id
+ assign show_inventory_status = show_inventory_status | default: block.settings.show_inventory_status, allow_false: true | default: true, allow_false: true
+ assign inventory_threshold = inventory_threshold | default: block.settings.inventory_threshold | default: 10
+ assign current_variant = product.selected_or_first_available_variant
+-%}
+
+{% capture product_inventory %}
+ {% render 'section.flex-pdp._inventory-text', product: product, inventory_threshold: inventory_threshold %}
+{% endcapture %}
+
+{% capture quantity_selector_label %}
+ {% capture quantity_selector_attributes %}
+ for="{% render 'utility.id', name: product.id %}"
+ {% endcapture -%}
+
+ {% if show_inventory_status %}
+ {% capture quantity_selector_label_beginning %}
+ {%- render 'utility.translate', key: 'labels.quantity', fallback: 'Quantity' -%}
+ {% endcapture %}
+ {% assign quantity_selector_label_beginning = quantity_selector_label_beginning | strip | append: ':' %}
+ {% capture quantity_selector_label_content %}
+ {% render 'element.text', as: 'span', text: quantity_selector_label_beginning, style: 'font-weight: 600;' %}
+ {% render 'element.text', as: 'span', text: product_inventory, style: 'opacity: 0.75;' %}
+ {% endcapture %}
+ {% render 'element.text', as: 'label', variant: 'body-sm', text: quantity_selector_label_content, attributes: quantity_selector_attributes %}
+ {% else %}
+ {% render 'element.text', as: 'label', variant: 'body-sm', locale: 'labels.quantity', text: 'Quantity', style: 'font-weight: 600;', attributes: quantity_selector_attributes %}
+ {% endif %}
+{% endcapture %}
+
+{% capture quantity_selector_stack %}
+ {% assign value = 1 %}
+ {% assign max = '' %}
+ {% assign min = 1 %}
+ {% assign disabled = false %}
+
+ {% if current_variant.inventory_management == 'shopify' and current_variant.inventory_policy != 'continue' and show_inventory_status %}
+ {% assign max = current_variant.inventory_quantity %}
+ {% endif %}
+
+ {% unless current_variant.available %}
+ {% assign max = 0 %}
+ {% assign min = 0 %}
+ {% assign value = 0 %}
+ {% assign disabled = true %}
+ {% endunless %}
+
+ {% render 'layout.stack', gap: 'xs', horizontal: true, slot: quantity_selector_label %}
+ {% render 'element.quantity-selector', product: product, form_id: form_id, max: max, value: value, min: min, disabled: disabled %}
+{% endcapture %}
+
+{% render 'layout.stack', gap: 'xs', slot: quantity_selector_stack %}
+
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/probe.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/probe.cjs
new file mode 100644
index 00000000..5318ca83
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/probe.cjs
@@ -0,0 +1,72 @@
+// Probe the +/- stepper on a given theme. Usage: node probe.cjs <themeId> <label>
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+
+const THEME = process.argv[2];
+const LABEL = process.argv[3] || THEME;
+const HANDLE = process.argv[4] || 'dwtt-80064-designer-wallcoverings-los-angeles';
+const BASE = 'https://designer-laboratory-sandbox.myshopify.com/products/' + HANDLE;
+const URL = THEME ? `${BASE}?preview_theme_id=${THEME}` : BASE;
+
+(async () => {
+ const browser = await chromium.launch();
+ const ctx = await browser.newContext();
+ const page = await ctx.newPage();
+ let out_pick = null;
+ const errors = [];
+ page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+ page.on('console', m => { if (m.type() === 'error') errors.push('CONSOLE.error: ' + m.text()); });
+
+ await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+ await page.waitForSelector('.mm_quantity input[name="quantity"]', { state: 'attached', timeout: 30000 });
+ await page.waitForTimeout(1800);
+
+ // Select the ROLL (non-sample) variant so the qty picker is shown.
+ const picked = await page.evaluate(() => {
+ const sel = document.querySelector('.product-select');
+ if (!sel || sel.tagName !== 'SELECT') return { ok: false, reason: 'no <select> (single-variant?)' };
+ const opts = Array.from(sel.options);
+ // pick first option whose text/sku is NOT a sample
+ const roll = opts.find(o => {
+ const t = (o.textContent || '').toLowerCase();
+ const sku = (o.getAttribute('data-sku') || '').toLowerCase();
+ return !/sample/.test(t) && !/-sample$/.test(sku);
+ }) || opts[0];
+ sel.value = roll.value;
+ sel.dispatchEvent(new Event('change', { bubbles: true }));
+ // jQuery change too
+ if (window.jQuery) window.jQuery(sel).trigger('change');
+ return { ok: true, chosen: roll.textContent.trim(), sku: roll.getAttribute('data-sku') };
+ });
+ out_pick = picked;
+ await page.waitForTimeout(1500);
+
+ const getState = async () => page.evaluate(() => {
+ const q = document.querySelector('.mm_quantity .input-text.qty, .mm_quantity input[name="quantity"]');
+ const h = document.querySelector('.mm_quantity .hquantity, input[name="hquantity"]');
+ return {
+ qtyVal: q ? q.value : null,
+ qtyStep: q ? q.getAttribute('step') : null,
+ qtyMin: q ? q.getAttribute('min') : null,
+ qtyMax: q ? q.getAttribute('max') : null,
+ hVal: h ? h.value : null,
+ hAttr: h ? h.getAttribute('value') : null,
+ plusCount: document.querySelectorAll('.mm_quantity .plus').length,
+ minusCount: document.querySelectorAll('.mm_quantity .minus').length,
+ };
+ });
+
+ const clickPlus = async () => { await page.click('.mm_quantity .plus'); await page.waitForTimeout(300); };
+ const clickMinus = async () => { await page.click('.mm_quantity .minus'); await page.waitForTimeout(300); };
+
+ const out = { label: LABEL, url: URL, variantPicked: out_pick, steps: [] };
+ out.steps.push(['initial', await getState()]);
+ await clickPlus(); out.steps.push(['after +1', await getState()]);
+ await clickPlus(); out.steps.push(['after +2', await getState()]);
+ await clickMinus(); out.steps.push(['after -1', await getState()]);
+ await clickMinus(); out.steps.push(['after -2', await getState()]);
+ await clickMinus(); out.steps.push(['after -3 (floor test)', await getState()]);
+ out.errors = errors;
+
+ console.log(JSON.stringify(out, null, 2));
+ await browser.close();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/trace.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/trace.cjs
new file mode 100644
index 00000000..41115718
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/trace.cjs
@@ -0,0 +1,45 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const THEME = process.argv[2];
+const HANDLE = 'dwtt-80064-designer-wallcoverings-los-angeles';
+const URL = `https://designer-laboratory-sandbox.myshopify.com/products/${HANDLE}?preview_theme_id=${THEME}`;
+(async () => {
+ const browser = await chromium.launch();
+ const page = await (await browser.newContext()).newPage();
+ const errs = [];
+ page.on('pageerror', e => errs.push('PAGEERROR: ' + e.message));
+ await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+ await page.waitForSelector('.mm_quantity input[name="quantity"]', { state: 'attached', timeout: 30000 });
+ await page.waitForTimeout(1800);
+ await page.evaluate(() => {
+ const sel = document.querySelector('.product-select');
+ if (sel && sel.tagName === 'SELECT') {
+ const opts = Array.from(sel.options);
+ const roll = opts.find(o => !/sample/i.test(o.textContent) && !/-sample$/i.test(o.getAttribute('data-sku')||'')) || opts[0];
+ sel.value = roll.value; sel.dispatchEvent(new Event('change', { bubbles: true }));
+ if (window.jQuery) window.jQuery(sel).trigger('change');
+ }
+ });
+ await page.waitForTimeout(1500);
+
+ // Manually replicate the plus-handler math and report each intermediate.
+ const trace = await page.evaluate(() => {
+ const $ = window.jQuery;
+ const btn = document.querySelector('.mm_quantity .plus');
+ const a = $(btn).closest('.mm_quantity').find('.qty');
+ const before = a.val();
+ const b = parseFloat(a.val());
+ const c = parseFloat(a.attr('max'));
+ const d = parseFloat(a.attr('min'));
+ const e = a.attr('step');
+ // compute what the handler would set
+ let target;
+ const eNum = ('any'!==e && ''!==e && e!==undefined && !isNaN(parseFloat(e))) ? e : '1';
+ const decimals = (function(s){var m=(''+s).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return m?Math.max(0,(m[1]?m[1].length:0)-(m[2]?+m[2]:0)):0;})(eNum);
+ target = (b + parseFloat(eNum)).toFixed(decimals);
+ a.val(target);
+ const afterSet = a.val();
+ return { before, b, c: isNaN(c)?'NaN':c, d, e, eNum, decimals, target, afterSet, elemCount: a.length };
+ });
+ console.log(JSON.stringify({ trace, errs }, null, 2));
+ await browser.close();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });
← a1e11baf Theme fix: color-dots makeDot 'c is not defined' scoping bug
·
back to Designer Wallcoverings
·
DWTT qty stepper fix: repair +/- increment/decrement in cust b77fe370 →