← back to Designer Wallcoverings
Port MOQ enforcement to Steves Version 5.1 (Archetype): metafield min/step + sample exemption + clamp script; originals backed up
d8930599a0ae8a0d5125c3363acc3a1a976b0b67 · 2026-06-22 17:15:19 -0700 · Steve Abrams
Files touched
A shopify/theme-moq/v51/element-quantity-selector.FIXED.liquidA shopify/theme-moq/v51/element-quantity-selector.ORIGINAL.liquidA shopify/theme-moq/v51/quantity-picker.FIXED.liquidA shopify/theme-moq/v51/quantity-picker.ORIGINAL.liquid
Diff
commit d8930599a0ae8a0d5125c3363acc3a1a976b0b67
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 22 17:15:19 2026 -0700
Port MOQ enforcement to Steves Version 5.1 (Archetype): metafield min/step + sample exemption + clamp script; originals backed up
---
.../v51/element-quantity-selector.FIXED.liquid | 184 +++++++++++++++++++++
.../v51/element-quantity-selector.ORIGINAL.liquid | 181 ++++++++++++++++++++
shopify/theme-moq/v51/quantity-picker.FIXED.liquid | 118 +++++++++++++
.../theme-moq/v51/quantity-picker.ORIGINAL.liquid | 70 ++++++++
4 files changed, 553 insertions(+)
diff --git a/shopify/theme-moq/v51/element-quantity-selector.FIXED.liquid b/shopify/theme-moq/v51/element-quantity-selector.FIXED.liquid
new file mode 100644
index 00000000..5c2790f1
--- /dev/null
+++ b/shopify/theme-moq/v51/element-quantity-selector.FIXED.liquid
@@ -0,0 +1,184 @@
+{% doc %}
+ Copyright © 2025 Archetype Themes LP. All rights reserved.
+
+ Interactive quantity selector with plus/minus buttons and numeric input.
+
+ @param {string} [id] - ID of the quantity selector
+ @param {string} [name] - Name of the quantity selector
+ @param {number} [value] - Initial value of the quantity selector
+ @param {number} [min] - Minimum value of the quantity selector
+ @param {number} [max] - Maximum value of the quantity selector
+ @param {string} [form_id] - ID of the form the quantity selector is associated with
+ @param {string} [cart_item_key] - Key of the cart item the quantity selector is associated with
+ @param {string} [style] - Custom styles for the quantity selector
+ @param {boolean} [inverted] - Whether to invert the color scheme
+ @param {boolean} [disabled] - Whether to disable the quantity selector
+ @param {string} [label] - Label of the quantity selector
+
+ @example
+ {% render 'element.quantity-selector', value: 1, min: 1, max: 10 %}
+{% enddoc %}
+
+{%- liquid
+ capture default_id
+ render 'utility.id', name: product.id
+ endcapture
+
+ assign id = id | default: default_id
+ assign name = name | default: 'quantity'
+ assign value = value | default: 1
+ assign min = min | default: 1
+ assign step = step | default: 1
+ assign form_id = form_id | default: ''
+ assign cart_item_key = cart_item_key | default: ''
+ assign inverted = inverted | default: false
+ assign style = style | default: ''
+ assign disabled = disabled | default: false, allow_false: true
+ assign label = label | default: ''
+
+ assign inverted_input = false
+ assign inverted_buttons = true
+ if inverted
+ assign inverted_input = true
+ assign inverted_buttons = false
+ endif
+
+ assign value_string = value | append: ''
+ assign value_string_size = value_string.size
+
+ if label == blank
+ capture label
+ render 'utility.translate', key: 'labels.quantity', fallback: 'Quantity'
+ endcapture
+ endif
+-%}
+
+{%- capture custom_properties -%}
+ --digit-count: {{ value_string_size }}ch;
+{%- endcapture -%}
+
+{%- assign style = custom_properties | append: ' ' | append: style | strip -%}
+
+{%- capture input_attributes -%}
+ data-initial-value="{{ value }}"
+ min="{{ min }}"
+ step="{{ step }}"
+ data-step="{{ step }}"
+ max="{{ max }}"
+ pattern="[0-9]*"
+ form="{{ form_id }}"
+{%- endcapture -%}
+
+{%- capture button_minus_attributes -%}
+ aria-label="{% render 'utility.translate', key: 'actions.reduce_item_quantity', fallback: 'Reduce item quantity by one' -%}"
+{%- endcapture -%}
+
+{%- capture button_plus_attributes -%}
+ aria-label="{% render 'utility.translate', key: 'actions.increase_item_quantity', fallback: 'Increase item quantity by one' -%}"
+{%- endcapture -%}
+
+<quantity-selector
+ class="element-quantity-selector"
+ {% if cart_item_key != blank -%}
+ key="{{ cart_item_key }}"
+ {%- endif %}
+ style="{{ style }}"
+>
+ {%- render 'element.button',
+ classlist: 'element-quantity-selector__button element-quantity-selector__button--minus',
+ icon: 'minus',
+ attributes: button_minus_attributes,
+ inverted: inverted_buttons,
+ transparent: true,
+ type: 'button',
+ disabled: disabled,
+ style: '--element-button-color-primary: var(--color-primary); --element-button-color-secondary: var(--color-secondary);'
+ -%}
+
+ {%- render 'element.input',
+ type: 'text',
+ classlist: 'element-quantity-selector__input',
+ id: id,
+ name: name,
+ value: value,
+ attributes: input_attributes,
+ inverted: inverted_input,
+ disabled: disabled
+ -%}
+
+ {%- render 'element.button',
+ classlist: 'element-quantity-selector__button element-quantity-selector__button--plus',
+ icon: 'plus',
+ attributes: button_plus_attributes,
+ inverted: inverted_buttons,
+ transparent: true,
+ type: 'button',
+ disabled: disabled,
+ style: '--element-button-color-primary: var(--color-primary); --element-button-color-secondary: var(--color-secondary);'
+ -%}
+</quantity-selector>
+
+<script type="module">
+ import 'element.quantity-selector'
+</script>
+
+{% render 'style.primitive-tokens' %}
+{% render 'style.normalize' %}
+
+{% stylesheet %}
+ .element-quantity-selector {
+ --element-input-color-primary: var(--color-primary, #000);
+ --element-input-color-secondary: var(--color-secondary, #fff);
+
+ width: fit-content;
+ display: inline-block;
+ position: relative;
+ overflow: visible;
+ pointer-events: auto;
+
+ &.is-loading {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ }
+
+ .element-quantity-selector__input {
+ --element-input-radius: var(--element-button-radius);
+ --element-input-padding-inline: calc(
+ var(--element-input-line-height) * var(--element-input-font-size) + 2 * var(--element-input-padding-block)
+ );
+ --element-input-width: max(calc(var(--digit-count, 1ch) + 2 * var(--size-1)), 32px);
+ box-sizing: content-box;
+ text-align: center;
+ }
+
+ .element-quantity-selector__button {
+ --element-button-border-width: 0;
+ --element-button-color-primary: var(--element-input-color-primary, var(--root-color-primary, #000));
+ --element-button-color-secondary: var(--element-input-color-secondary, var(--root-color-secondary, #fff));
+
+ position: absolute;
+ top: 0;
+ height: 100%;
+ aspect-ratio: 1;
+ border: 1px solid transparent;
+ background-clip: padding-box;
+ z-index: 1;
+
+ &:has(+ .element-quantity-selector__input:focus),
+ .element-quantity-selector__input:focus + & {
+ border-width: var(--element-input-box-shadow-spread-radius--hover);
+ }
+
+ &:is(.element-quantity-selector__button--minus) {
+ border-radius: var(--element-button-radius) 0 0 var(--element-button-radius);
+ left: 0;
+ }
+
+ &:is(.element-quantity-selector__button--plus) {
+ border-radius: 0 var(--element-button-radius) var(--element-button-radius) 0;
+ right: 0;
+ }
+ }
+{% endstylesheet %}
+
diff --git a/shopify/theme-moq/v51/element-quantity-selector.ORIGINAL.liquid b/shopify/theme-moq/v51/element-quantity-selector.ORIGINAL.liquid
new file mode 100644
index 00000000..b8e00933
--- /dev/null
+++ b/shopify/theme-moq/v51/element-quantity-selector.ORIGINAL.liquid
@@ -0,0 +1,181 @@
+{% doc %}
+ Copyright © 2025 Archetype Themes LP. All rights reserved.
+
+ Interactive quantity selector with plus/minus buttons and numeric input.
+
+ @param {string} [id] - ID of the quantity selector
+ @param {string} [name] - Name of the quantity selector
+ @param {number} [value] - Initial value of the quantity selector
+ @param {number} [min] - Minimum value of the quantity selector
+ @param {number} [max] - Maximum value of the quantity selector
+ @param {string} [form_id] - ID of the form the quantity selector is associated with
+ @param {string} [cart_item_key] - Key of the cart item the quantity selector is associated with
+ @param {string} [style] - Custom styles for the quantity selector
+ @param {boolean} [inverted] - Whether to invert the color scheme
+ @param {boolean} [disabled] - Whether to disable the quantity selector
+ @param {string} [label] - Label of the quantity selector
+
+ @example
+ {% render 'element.quantity-selector', value: 1, min: 1, max: 10 %}
+{% enddoc %}
+
+{%- liquid
+ capture default_id
+ render 'utility.id', name: product.id
+ endcapture
+
+ assign id = id | default: default_id
+ assign name = name | default: 'quantity'
+ assign value = value | default: 1
+ assign min = min | default: 1
+ assign form_id = form_id | default: ''
+ assign cart_item_key = cart_item_key | default: ''
+ assign inverted = inverted | default: false
+ assign style = style | default: ''
+ assign disabled = disabled | default: false, allow_false: true
+ assign label = label | default: ''
+
+ assign inverted_input = false
+ assign inverted_buttons = true
+ if inverted
+ assign inverted_input = true
+ assign inverted_buttons = false
+ endif
+
+ assign value_string = value | append: ''
+ assign value_string_size = value_string.size
+
+ if label == blank
+ capture label
+ render 'utility.translate', key: 'labels.quantity', fallback: 'Quantity'
+ endcapture
+ endif
+-%}
+
+{%- capture custom_properties -%}
+ --digit-count: {{ value_string_size }}ch;
+{%- endcapture -%}
+
+{%- assign style = custom_properties | append: ' ' | append: style | strip -%}
+
+{%- capture input_attributes -%}
+ data-initial-value="{{ value }}"
+ min="{{ min }}"
+ max="{{ max }}"
+ pattern="[0-9]*"
+ form="{{ form_id }}"
+{%- endcapture -%}
+
+{%- capture button_minus_attributes -%}
+ aria-label="{% render 'utility.translate', key: 'actions.reduce_item_quantity', fallback: 'Reduce item quantity by one' -%}"
+{%- endcapture -%}
+
+{%- capture button_plus_attributes -%}
+ aria-label="{% render 'utility.translate', key: 'actions.increase_item_quantity', fallback: 'Increase item quantity by one' -%}"
+{%- endcapture -%}
+
+<quantity-selector
+ class="element-quantity-selector"
+ {% if cart_item_key != blank -%}
+ key="{{ cart_item_key }}"
+ {%- endif %}
+ style="{{ style }}"
+>
+ {%- render 'element.button',
+ classlist: 'element-quantity-selector__button element-quantity-selector__button--minus',
+ icon: 'minus',
+ attributes: button_minus_attributes,
+ inverted: inverted_buttons,
+ transparent: true,
+ type: 'button',
+ disabled: disabled,
+ style: '--element-button-color-primary: var(--color-primary); --element-button-color-secondary: var(--color-secondary);'
+ -%}
+
+ {%- render 'element.input',
+ type: 'text',
+ classlist: 'element-quantity-selector__input',
+ id: id,
+ name: name,
+ value: value,
+ attributes: input_attributes,
+ inverted: inverted_input,
+ disabled: disabled
+ -%}
+
+ {%- render 'element.button',
+ classlist: 'element-quantity-selector__button element-quantity-selector__button--plus',
+ icon: 'plus',
+ attributes: button_plus_attributes,
+ inverted: inverted_buttons,
+ transparent: true,
+ type: 'button',
+ disabled: disabled,
+ style: '--element-button-color-primary: var(--color-primary); --element-button-color-secondary: var(--color-secondary);'
+ -%}
+</quantity-selector>
+
+<script type="module">
+ import 'element.quantity-selector'
+</script>
+
+{% render 'style.primitive-tokens' %}
+{% render 'style.normalize' %}
+
+{% stylesheet %}
+ .element-quantity-selector {
+ --element-input-color-primary: var(--color-primary, #000);
+ --element-input-color-secondary: var(--color-secondary, #fff);
+
+ width: fit-content;
+ display: inline-block;
+ position: relative;
+ overflow: visible;
+ pointer-events: auto;
+
+ &.is-loading {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ }
+
+ .element-quantity-selector__input {
+ --element-input-radius: var(--element-button-radius);
+ --element-input-padding-inline: calc(
+ var(--element-input-line-height) * var(--element-input-font-size) + 2 * var(--element-input-padding-block)
+ );
+ --element-input-width: max(calc(var(--digit-count, 1ch) + 2 * var(--size-1)), 32px);
+ box-sizing: content-box;
+ text-align: center;
+ }
+
+ .element-quantity-selector__button {
+ --element-button-border-width: 0;
+ --element-button-color-primary: var(--element-input-color-primary, var(--root-color-primary, #000));
+ --element-button-color-secondary: var(--element-input-color-secondary, var(--root-color-secondary, #fff));
+
+ position: absolute;
+ top: 0;
+ height: 100%;
+ aspect-ratio: 1;
+ border: 1px solid transparent;
+ background-clip: padding-box;
+ z-index: 1;
+
+ &:has(+ .element-quantity-selector__input:focus),
+ .element-quantity-selector__input:focus + & {
+ border-width: var(--element-input-box-shadow-spread-radius--hover);
+ }
+
+ &:is(.element-quantity-selector__button--minus) {
+ border-radius: var(--element-button-radius) 0 0 var(--element-button-radius);
+ left: 0;
+ }
+
+ &:is(.element-quantity-selector__button--plus) {
+ border-radius: 0 var(--element-button-radius) var(--element-button-radius) 0;
+ right: 0;
+ }
+ }
+{% endstylesheet %}
+
diff --git a/shopify/theme-moq/v51/quantity-picker.FIXED.liquid b/shopify/theme-moq/v51/quantity-picker.FIXED.liquid
new file mode 100644
index 00000000..8d74fb6b
--- /dev/null
+++ b/shopify/theme-moq/v51/quantity-picker.FIXED.liquid
@@ -0,0 +1,118 @@
+{% 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 %}
+ {%- liquid
+ # DW MOQ 2026-06-22: read order-min metafields (robust append coercion)
+ assign moq_min = product.metafields.global.v_prods_quantity_order_min | append: '' | plus: 0
+ assign moq_step = product.metafields.global.v_prods_quantity_order_units | append: '' | plus: 0
+ if moq_min < 1
+ assign moq_min = 1
+ endif
+ if moq_step < 1
+ assign moq_step = 1
+ endif
+ # Sample variant is exempt from the minimum (always orderable at qty 1)
+ assign sel_t = current_variant.title | downcase
+ assign eff_min = moq_min
+ assign eff_step = moq_step
+ if sel_t contains 'sample'
+ assign eff_min = 1
+ assign eff_step = 1
+ endif
+ assign value = eff_min
+ assign max = ''
+ assign min = eff_min
+ assign step = eff_step
+ 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, step: step, disabled: disabled %}
+{% endcapture %}
+
+{% render 'layout.stack', gap: 'xs', slot: quantity_selector_stack %}
+
+
+{%- comment -%} DW MOQ 2026-06-22: enforce min + increment on the Archetype qty input (delegated, idempotent, defensive) {%- endcomment -%}
+<script>
+(function(){
+ if (window.__dwMoqBound) return; window.__dwMoqBound = true;
+ function snap(q){
+ try{
+ var min = parseInt(q.getAttribute('min'),10); if(isNaN(min)||min<1) min=1;
+ var step = parseInt(q.getAttribute('data-step')||q.getAttribute('step'),10); if(isNaN(step)||step<1) step=1;
+ var v = parseInt(q.value,10);
+ if(isNaN(v)||v<min) v=min;
+ var r=(v-min)%step; if(r!==0) v=v-r+step;
+ if(String(v)!==q.value) q.value=v;
+ }catch(e){}
+ }
+ document.addEventListener('change', function(e){
+ var q=e.target; if(q&&q.matches&&q.matches('input[name="quantity"], .element-quantity-selector__input')) snap(q);
+ }, true);
+ document.addEventListener('click', function(e){
+ var b=e.target.closest&&e.target.closest('.element-quantity-selector__button'); if(!b) return;
+ var sel=b.closest('quantity-selector, .element-quantity-selector'); var q=sel&&sel.querySelector('input');
+ if(q) setTimeout(function(){snap(q);},0);
+ }, true);
+ document.addEventListener('submit', function(e){
+ var f=e.target; var q=f&&f.querySelector&&f.querySelector('input[name="quantity"]'); if(q) snap(q);
+ }, true);
+})();
+</script>
diff --git a/shopify/theme-moq/v51/quantity-picker.ORIGINAL.liquid b/shopify/theme-moq/v51/quantity-picker.ORIGINAL.liquid
new file mode 100644
index 00000000..c4848b56
--- /dev/null
+++ b/shopify/theme-moq/v51/quantity-picker.ORIGINAL.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 %}
+
← 8e45fc21 5.1 dev: horizontal collapsible Boost filter bar (dw-filter-
·
back to Designer Wallcoverings
·
5.1 PDP: hide theme's styled .select-wrapper shell when size 89778d14 →