PageSourceSearch

https://www.brotherssupply.com/wp-content/plugins/hozio-studio/com…/mini-cart/mini-cart.js?ver=bcf5290e79f5

js brotherssupply.com collected 2026-10-09 00:30:20 UTC 9,733 bytes, 183 lines download raw bytes

1/* ═════════════════════════════════════════════════════════════════════════
2   MINI-CART — widget behavior (Phase-2 widget system; moved out of main.js)
3
4   The mini-cart IS a .hzo-drawer composition — open/close/trap/lock/Esc/
5   focus-return machinery all lives in components/drawer/drawer.js (open it
6   via data-hzo-drawer-open="hzo-mini-cart" or hzo.openDrawer; commerce.js
7   auto-opens it after add-to-cart). THIS file only owns the line-item
8   behavior:
9
10     · qty steppers (±, min 1 — use remove to go lower) recompute the line
11       total + subtotal from each item's data-unit-price;
12     · remove deletes the line, moves focus (tag-removal pattern: next →
13       previous → empty-state CTA/close), recomputes, and shows the
14       .hzo-empty state + hides the footer when the last item goes.
15
16   UI surface only — real cart wiring is assets/js/commerce.js + the
17   fragment pipeline in includes/wc-ajax.php. Contract: component-patterns §46.
18
19   COMMERCE COUPLING (every touchpoint below is frozen public API):
20     · Live WooCommerce carts: rows carry data-cart-item-key and their prices
21       are server-rendered wc_price HTML (correct currency/format).
22       commerce.js + the cart-fragments filter own those numbers — writing
23       our client-side money here would corrupt them for ~600 ms until the
24       sync lands. Only the demo/static drawer (data-unit-price, no item key)
25       gets the client-side money math; the empty/footer/list visibility sync
26       runs either way.
27     · REGISTRATION ORDER IS API: commerce.js's own delegated click handler
28       reads the settled count off the row, expecting THIS handler to have
29       already stepped it (optimistic update first). This script's listener
30       must register before commerce.js runs — components enqueue before
31       hozio-studio-commerce in hozio-studio.php's priority-999 closure.
32     · commerce.js focus-restores through the same selectors used here
33       (.hzo-minicart__item[data-cart-item-key], [data-hzo-qty-up/down],
34       [data-hzo-minicart-remove], .hzo-minicart .hzo-drawer__close) — all
35       byte-frozen.
36
37   OPTION CONTRACT — data-attributes on the `.hzo-minicart` root; the block
38   pattern (hand-authored) and the [hzo_mini_cart] shortcode (mini-cart.php
39   generates them) drive the SAME behavior. Every option defaults to the
40   legacy value — attribute-free markup behaves exactly as before:
41
42     data-hzo-mini-cart-currency   money prefix for the DEMO/static math
43                                   (default "$", the legacy hardcoded format;
44                                   live carts never touch this — their money
45                                   is server-rendered wc_price HTML)
46     data-hzo-mini-cart-max-qty    upper clamp for the ± stepper (int ≥ 1;
47                                   absent/invalid = unclamped, the legacy
48                                   behavior). A count already above the clamp
49                                   is left alone — the stepper just refuses
50                                   to go higher.
51
52   A11y: the drawer supplies the WAI-ARIA APG Dialog semantics (trap, Esc,
53   focus return); in here the counts are aria-live="polite" (authored markup
54   and wc-ajax fragments both carry it), and remove moves focus per the APG
55   deletion pattern (next control → previous → empty-state CTA → drawer
56   close) so focus never drops to <body> inside the trap. Nothing here
57   animates — no reduced-motion branch needed (mini-cart.css kills its own
58   transitions under prefers-reduced-motion).
59
60   Self-contained: uses NO shared hzo.* primitives, so there is no
61   hozio-studio-main dependency to guard.
62
63   Public API (the old main.js IIFE exposed no window.hzo names — nothing
64   historical to preserve):
65     hzo.miniCart = { init, setup, recompute }   (init(scope) for AJAX content)
66   ═════════════════════════════════════════════════════════════════════════ */
67(function () {
68    'use strict';
69
70    // Money for the DEMO/static path only (live carts keep server-rendered
71    // wc_price HTML). Default '$' prefix = the legacy hardcoded format.
72    function money(n, symbol) {
73        return (symbol || '$') + (Math.round(n * 100) / 100).toFixed(2);
74    }
75
76    function recompute(cart) {
77        var items = cart.querySelectorAll('.hzo-minicart__item');
78        // Live WooCommerce cart? Its rows carry data-cart-item-key and their
79        // prices are server-rendered wc_price HTML (correct currency/format).
80        // commerce.js + the cart-fragments filter own those numbers — writing
81        // our client-side money here would corrupt them for ~600ms until the
82        // sync lands. Only the demo/static drawer (data-unit-price) gets the
83        // client-side money math; either way the visibility sync runs.
84        var live = !!cart.querySelector('.hzo-minicart__item[data-cart-item-key]');
85        if (!live) {
86            var symbol = cart.getAttribute('data-hzo-mini-cart-currency') || '$';
87            var subtotal = 0;
88            for (var i = 0; i < items.length; i++) {
89                var price = parseFloat(items[i].getAttribute('data-unit-price')) || 0;
90                var countEl = items[i].querySelector('.hzo-minicart__count');
91                var qty = countEl ? (parseInt(countEl.textContent, 10) || 0) : 0;
92                var lineEl = items[i].querySelector('.hzo-minicart__linetotal');
93                if (lineEl) lineEl.textContent = money(price * qty, symbol);
94                subtotal += price * qty;
95            }
96            var sv = cart.querySelector('.hzo-minicart__subtotal-value');
97            if (sv) sv.textContent = money(subtotal, symbol);
98        }
99        var isEmpty = items.length === 0;
100        var empty = cart.querySelector('.hzo-minicart__empty');
101        var footer = cart.querySelector('.hzo-minicart__footer');
102        var list = cart.querySelector('.hzo-minicart__items');
103        if (empty) empty.hidden = !isEmpty;
104        if (footer) footer.hidden = isEmpty;
105        if (list) list.hidden = isEmpty;
106    }
107
108    // Option: stepper upper clamp (null = unclamped, the legacy behavior).
109    function maxQtyOf(cart) {
110        var v = parseInt(cart.getAttribute('data-hzo-mini-cart-max-qty') || '', 10);
111        return (isNaN(v) || v < 1) ? null : v;
112    }
113
114    // Delegated once at script scope (exactly like the legacy IIFE — carts and
115    // fragment-swapped lists injected later keep working). Registered BEFORE
116    // commerce.js's own click handler (see REGISTRATION ORDER in the header).
117    document.addEventListener('click', function (e) {
118        if (!e.target.closest) return;
119        var cart = e.target.closest('.hzo-minicart');
120        if (!cart) return;
121
122        var step = e.target.closest('[data-hzo-qty-up], [data-hzo-qty-down]');
123        if (step) {
124            var item = step.closest('.hzo-minicart__item');
125            var count = item && item.querySelector('.hzo-minicart__count');
126            if (!count) return;
127            var q = parseInt(count.textContent, 10) || 1;
128            if (step.hasAttribute('data-hzo-qty-up')) {
129                var max = maxQtyOf(cart);
130                if (max === null || q < max) q = q + 1;   // clamp only blocks going higher
131            } else {
132                q = Math.max(1, q - 1);
133            }
134            count.textContent = q;
135            recompute(cart);
136            return;
137        }
138
139        var rm = e.target.closest('[data-hzo-minicart-remove]');
140        if (rm) {
141            var row = rm.closest('.hzo-minicart__item');
142            var list = row && row.parentElement;
143            if (!list) return;
144            var removes = Array.prototype.slice.call(list.querySelectorAll('[data-hzo-minicart-remove]'));
145            var idx = removes.indexOf(rm);
146            list.removeChild(row);
147            recompute(cart);   // toggle empty/footer/list visibility FIRST, so the
148                               // empty-state CTA is focusable before we move focus to it
149            var remaining = list.querySelectorAll('[data-hzo-minicart-remove]');
150            if (remaining.length) {
151                remaining[Math.min(idx, remaining.length - 1)].focus();   // next, or previous if last
152            } else {
153                var fallback = cart.querySelector('.hzo-minicart__empty a, .hzo-minicart__empty button') ||
154                               cart.querySelector('.hzo-drawer__close');
155                if (fallback) fallback.focus();
156            }
157        }
158    });
159
160    /* Per-root init — idempotent via the ready guard. Setup is just the
161       initial recompute (line totals + empty/footer/list visibility), the
162       same pass the legacy IIFE ran over every cart at load. */
163    function setup(cart) {
164        if (!cart || cart.getAttribute('data-hzo-mini-cart-ready') === 'true') return;
165        cart.setAttribute('data-hzo-mini-cart-ready', 'true');
166        recompute(cart);
167    }
168
169    function init(scope) {
170        var carts = (scope || document).querySelectorAll('.hzo-m
170inicart');
171        for (var c = 0; c < carts.length; c++) setup(carts[c]);
172    }
173
174    if (document.readyState === 'loading') {
175        document.addEventListener('DOMContentLoaded', function () { init(); });
176    } else {
177        init();
178    }
179
180    // Shared engine namespace (never a new top-level global).
181    window.hzo = window.hzo || {};
182    window.hzo.miniCart = { init: init, setup: setup, recompute: recompute };
183})();

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.