← 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
M DW-Websites/DesignerWallcoverings-product.liquidA DW-Websites/variant-min-quantity.liquid
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 →