PageSourceSearch

https://www.brotherssupply.com/wp-content/plugins/hozio-studio/com…/quantity-stepper.js?ver=8dc7f3b188b1

js brotherssupply.com collected 2026-10-09 00:30:25 UTC 8,308 bytes, 159 lines download raw bytes

1/* ═════════════════════════════════════════════════════════════════════════
2   QUANTITY STEPPER — widget behavior (Phase-2 widget system; moved out of
3   main.js "Quantity stepper — clamp + bounded buttons + change event").
4
5   Self-contained, no hard dependencies (uses no main.js primitives). As a
6   shortcode widget the stepper ALWAYS loads: quantity-stepper.php un-gates it
7   via the hzostudio_component_markers filter (a no-op — the stepper was never
8   gated — kept for convention uniformity, see toast.php).
9
10   MARKUP CONTRACT (unchanged from main.js — this is public API; commerce.js
11   and integrations depend on it):
12     .hzo-qty[data-hzo-qty] wrapping
13       .hzo-qty__btn--minus / [data-hzo-qty-minus]
14       .hzo-qty__input
15       .hzo-qty__btn--plus  / [data-hzo-qty-plus]
16   Clamps to data-min / data-max on the root (or the input's min / max, which
17   win; min defaults to 1), disables each button at its bound, and dispatches
18   a bubbling `hzo:qty-change` CustomEvent { value, min, max } (value is a
19   Number — commerce.js type-checks it). It NEVER POSTs — a real cart wires
20   onto the event (parity with the add-to-cart surface). Distinct selectors
21   from the mini-cart's data-hzo-qty-up/down so the two never collide.
22   CSS contract: quantity-stepper.css.
23
24   OPTION CONTRACT — settings are data-attributes on `.hzo-qty`, so the
25   authored markup and the [hzo_quantity_stepper] shortcode (quantity-stepper
26   .php generates them) drive the SAME behavior. Every option defaults to the
27   legacy value — attribute-free markup behaves exactly as before:
28
29     data-hzo-quantity-stepper-step   increment per button press / arrow key
30                                      (int ≥ 1; absent/invalid = 1, the legacy
31                                      step)
32
33   A11y (quantity-selector pattern): native text input (inputmode="numeric")
34   with aria-labelled ± buttons — no role="spinbutton" retrofit (a native
35   input already exposes value/edits to AT; the role would demand manual
36   aria-valuenow bookkeeping for no gain). APG spinbutton keyboard support:
37   ArrowUp / ArrowDown on the input step the value (Home/End are left native —
38   they edit the caret in a text input and hijacking them would break text
39   editing). Nothing here animates, so there is no reduced-motion branch; the
40   CSS kills its own transitions under prefers-reduced-motion.
41
42   Public API (the old main.js IIFE exposed no window.hzo names — nothing
43   historical to preserve):
44     hzo.quantityStepper = { init, setup, set }   (init(scope) for AJAX content)
45   ═════════════════════════════════════════════════════════════════════════ */
46(function () {
47    'use strict';
48
49    function bounds(qty, input) {
50        var min = input.hasAttribute('min') ? parseInt(input.getAttribute('min'), 10)
51            : (parseInt(qty.getAttribute('data-min'), 10));
52        if (isNaN(min)) min = 1;
53        var maxRaw = input.hasAttribute('max') ? input.getAttribute('max') : qty.getAttribute('data-max');
54        var max = (maxRaw === null || maxRaw === '') ? null : parseInt(maxRaw, 10);
55        if (max !== null && isNaN(max)) max = null;
56        return { min: min, max: max };
57    }
58
59    // Option: step size (default 1 = the legacy behavior).
60    function stepOf(qty) {
61        var s = parseInt(qty.getAttribute('data-hzo-quantity-stepper-step'), 10);
62        return (isNaN(s) || s < 1) ? 1 : s;
63    }
64
65    function apply(qty, input, val, emit) {
66        var b = bounds(qty, input);
67        if (isNaN(val)) val = b.min;
68        if (val < b.min) val = b.min;
69        if (b.max !== null && val > b.max) val = b.max;
70        input.value = val;
71        var minus = qty.querySelector('.hzo-qty__btn--minus, [data-hzo-qty-minus]');
72        var plus = qty.querySelector('.hzo-qty__btn--plus, [data-hzo-qty-plus]');
73        var disMinus = val <= b.min;
74        var disPlus = (b.max !== null && val >= b.max);
75        // G22: never disable the button that currently holds focus without first
76        // moving focus to the opposite, still-enabled button — a disabled focused
77        // control strands keyboard focus on <body>.
78        var active = document.activeElement;
79        if (disMinus && minus && active === minus && plus && !disPlus) { plus.focus(); }
80        else if (disPlus && plus && active === plus && minus && !disMinus) { minus.focus(); }
81        if (minus) minus.disabled = disMinus;
82        if (plus) plus.disabled = disPlus;
83        if (emit) qty.dispatchEvent(new CustomEvent('hzo:qty-change', { bubbles: true, detail: { value: val, min: b.min, max: b.max } }));
84    }
85
86    function nudge(qty, input, dir) {
87        var current = parseInt(input.value, 10);
88        if (isNaN(current)) current = bounds(qty, input).min;
89        apply(qty, input, current + dir * stepOf(qty), true);
90    }
91
92    // ── Delegated listeners — bound ONCE at module scope, so dynamically-
93    //    injected steppers work without setup() (same as the main.js original).
94    document.addEventListener('click', function (e) {
95        var btn = e.target.closest ? e.target.closest('.hzo-qty__btn--minus, .hzo-qty__btn--plus, [data-hzo-qty-minus], [data-hzo-qty-plus]') : null;
96        if (!btn) return;
97        var qty = btn.closest('.hzo-qty[data-hzo-qty]');
98        if (!qty) return;
99        var input = qty.querySelector('.hzo-qty__input');
100        if (!input) return;
101        nudge(qty, input, (btn.classList.contains('hzo-qty__btn--plus') || btn.hasAttribute('data-hzo-qty-plus')) ? 1 : -1);
102    });
103
104    document.addEventListener('change', function (e) {
105        var input = e.target;
106        if (!input.classList || !input.classList.contains('hzo-qty__input')) return;
107        var qty = input.closest('.hzo-qty[data-hzo-qty]');
108        if (!qty) return;
109        apply(qty, input, parseInt(input.value, 10), true);
110    });
111
112    // APG spinbutton keyboard map on the input (ArrowUp/ArrowDown only — see
113    // header). preventDefault stops any caret movement / page scroll.
114    document.addEventListener('keydown', function (e) {
115        if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
116        var input = e.target;
117        if (!input.classList || !input.classList.contains('hzo-qty__input')) return;
118        var qty = input.closest('.hzo-qty[data-hzo-qty]');
119        if (!qty) return;
120        e.preventDefault();
121        nudge(qty, input, e.key === 'ArrowUp' ? 1 : -1);
122    });
123
124    /* Per-instance seeding: numeric keypad hint + initial clamped value /
125       disabled bounds, WITHOUT emitting (no phantom cart sync on load —
126       identical to the main.js original). Idempotent via the ready guard. */
127    function setup(qty) {
128        if (!qty || qty.getAttribute('data-hzo-quantity-stepper-ready') === 'true') { return; }
129        var input = qty.querySelector('.hzo-qty__input');
130        if (!input) return;
131        qty.setAttribute('data-hzo-quantity-stepper-ready', 'true');
132        if (!input.getAttribute('inputmode')) input.setAttribute('inputmode', 'numeric');
133        apply(qty, input, parseInt(input.value, 10), false);   // seed disabled state, no event on load
134    }
135
136    function init(scope) {
137        var all = (scope || document).querySelectorAll('.hzo-qty[data-hzo-qty]');
138        for (var i = 0; i < all.length; i++) { setup(all[i]); }
139    }
140
141    // Programmatic setter for integrations: clamps + syncs buttons + emits.
142    function set(rootOrId, value) {
143        var qty = typeof rootOrId === 'string' ? document.getElementById(rootOrId) : rootOrId;
144        if (!qty) return;
145        var input = qty.querySelector('.hzo-qty__input');
146        if (!input) return;
147        apply(qty, input, parseInt(value, 10), true);
148    }
149
150    if (document.readyState === 'loading') {
151        document.addEventListener('DOMContentLoaded', function () { init(); });
152    } else {
153        init();
154    }
155
156    // Shared engine namespace (never a new top-level global).
157    window.hzo = window.hzo || {};
158    window.hzo.quantityStepper = { init: init, setup: setup, set: set };
159})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.