[object Object]

← back to Designer Wallcoverings

product: per-variant min quantity (Sold Per Yard=5, Wallcovering=10, Sample=1) + clamp so qty can't go below min

ab77a98ef451a78735e89e772821e7f8c90745b2 · 2026-06-29 11:04:06 -0700 · Steve Abrams

Files touched

Diff

commit ab77a98ef451a78735e89e772821e7f8c90745b2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 29 11:04:06 2026 -0700

    product: per-variant min quantity (Sold Per Yard=5, Wallcovering=10, Sample=1) + clamp so qty can't go below min
---
 DW-Websites/DesignerWallcoverings-product.liquid |  3 +
 DW-Websites/variant-min-quantity.liquid          | 90 ++++++++++++++++++++++++
 2 files changed, 93 insertions(+)

diff --git a/DW-Websites/DesignerWallcoverings-product.liquid b/DW-Websites/DesignerWallcoverings-product.liquid
index 0af8cb0f..e4bd3286 100644
--- a/DW-Websites/DesignerWallcoverings-product.liquid
+++ b/DW-Websites/DesignerWallcoverings-product.liquid
@@ -1318,3 +1318,6 @@ document.addEventListener("DOMContentLoaded", async () => {
     {{ line }}
   </div>
 </div>
+
+{%- comment -%} per-variant minimum quantity (5 yd / 10 yd) + below-min clamp {%- endcomment -%}
+{%- include 'variant-min-quantity' -%}
diff --git a/DW-Websites/variant-min-quantity.liquid b/DW-Websites/variant-min-quantity.liquid
new file mode 100644
index 00000000..684b129f
--- /dev/null
+++ b/DW-Websites/variant-min-quantity.liquid
@@ -0,0 +1,90 @@
+{%- comment -%}
+  variant-min-quantity — make the Quantity field's minimum follow the selected Size
+  variant. The Size option text already encodes the minimum, e.g.
+  "Sold Per Yard (5 yd min)" -> 5, "Wallcovering (10 yd min)" -> 10, Sample -> 1.
+  Pure client-side, no metafield changes. Include near the end of the product
+  template:  {%- include 'variant-min-quantity' -%}
+{%- endcomment -%}
+<script>
+(function () {
+  // pull the integer minimum out of an option label like "...(10 yd min)"
+  function minFromText(s) {
+    var m = String(s || '').match(/(\d+)\s*yd\s*min/i);
+    return m ? parseInt(m[1], 10) : null;
+  }
+  function qtyInput() {
+    return document.querySelector(
+      'input[name="quantity"], input#Quantity, .quantity__input, input.quantity-selector, [data-quantity-input]'
+    );
+  }
+  function selectedSizeText() {
+    // Shopify <select name="options[Size]"> (or generic single-option-selector)
+    var sels = document.querySelectorAll(
+      'select[name="options[Size]"], select.single-option-selector'
+    );
+    for (var i = 0; i < sels.length; i++) {
+      var sel = sels[i];
+      var label = (sel.getAttribute('data-option-name') || sel.name || '').toLowerCase();
+      if (sels.length === 1 || label.indexOf('size') !== -1) {
+        if (sel.selectedIndex >= 0) return sel.options[sel.selectedIndex].textContent;
+      }
+    }
+    // radio-style variant pickers
+    var r = document.querySelector('input[type="radio"][name*="Size"]:checked, .variant-input input:checked');
+    if (r) {
+      var lbl = r.id && document.querySelector('label[for="' + r.id + '"]');
+      return (lbl ? lbl.textContent : r.value);
+    }
+    return '';
+  }
+  // never let the field sit below its minimum (typing, blur, or a minus button)
+  function clampUp(qi) {
+    var min = parseInt(qi.getAttribute('min'), 10) || 1;
+    var v = parseInt(qi.value, 10);
+    if (isNaN(v) || v < min) { if (String(qi.value) !== String(min)) qi.value = min; }
+  }
+  function wireClamp(qi) {
+    if (qi.__minClamp) return; qi.__minClamp = true;
+    ['input', 'change', 'blur', 'keyup'].forEach(function (ev) {
+      qi.addEventListener(ev, function () { clampUp(qi); });
+    });
+  }
+  function applyMin(forceValue) {
+    var qi = qtyInput();
+    if (!qi) return;
+    wireClamp(qi);
+    var txt = selectedSizeText();
+    // a Sample is always a single unit, even if its label includes a "yd min" phrase
+    var n = /sample/i.test(txt) ? 1 : minFromText(txt);
+    var min = n || 1;
+    if (String(qi.min) !== String(min)) { qi.min = min; qi.setAttribute('min', min); }
+    if (n) { qi.step = min; qi.setAttribute('step', min); }      // order in multiples of the min
+    var cur = parseInt(qi.value, 10);
+    if (forceValue || isNaN(cur) || cur < min) {
+      if (String(qi.value) !== String(min)) {
+        qi.value = min;
+        qi.dispatchEvent(new Event('change', { bubbles: true }));
+      }
+    } else {
+      clampUp(qi);
+    }
+  }
+  function wire() {
+    document
+      .querySelectorAll('select[name="options[Size]"], select.single-option-selector, input[type="radio"][name*="Size"]')
+      .forEach(function (el) {
+        if (el.__minWired) return; el.__minWired = true;
+        el.addEventListener('change', function () { setTimeout(function () { applyMin(true); }, 30); });
+      });
+    applyMin(true);
+  }
+  if (document.readyState !== 'loading') wire();
+  else document.addEventListener('DOMContentLoaded', wire);
+  // re-wire + keep min synced when the theme re-renders the form (debounced; never forces value)
+  var t;
+  new MutationObserver(function () {
+    clearTimeout(t);
+    t = setTimeout(function () { wire(); applyMin(false); }, 120);
+  }).observe(document.documentElement, { subtree: true, childList: true });
+})();
+</script>

← 9a74adab appointments: FINISH-on-kamatera one-paste (mount+reload, vh  ·  back to Designer Wallcoverings  ·  appointments: Google OAuth setup guide (Cloud Console + secr a2ad63bc →