PageSourceSearch

https://www.brotherssupply.com/wp-content/plugins/hozio-studio/com…/accordion/accordion.js?ver=380c2b3cdb6f

js brotherssupply.com collected 2026-10-09 00:30:56 UTC 10,546 bytes, 199 lines download raw bytes

1/* ═════════════════════════════════════════════════════════════════════════
2   ACCORDION — widget behavior (Phase-2 widget system; moved out of main.js)
3
4   Self-contained, no dependencies (no shared hzo.* primitives used). The
5   accordion is NOT in the loader's gated-markers map, so it already
6   always-loads; accordion.php still un-gates per the widget convention (a
7   harmless no-op today, correct if the accordion is ever gated).
8
9   The base .hzo-accordion is native <details>/<summary> and stays fully
10   JS-FREE by default — this file only powers the two opt-in enhancements
11   (the exact legacy main.js contract; nothing here binds unless the
12   data-attributes below are authored):
13
14   1. Exclusive open: a wrapper with [data-hzo-accordion-exclusive] gets a
15      shared `name` injected into every child <details>, activating the
16      browser-native single-open group (Chrome 120+/Safari 17.2+/FF 130+;
17      harmless attribute elsewhere). Attribute value (optional) = the group
18      name; otherwise one is generated per wrapper. Grouped details allow at
19      most ONE open member, so any authored `open` after the first is cleared.
20
21   2. Expand/collapse all: a <button data-hzo-accordion-expand-all> toggles
22      every child <details> of its accordion (attribute value = accordion id;
23      empty = the nearest .hzo-accordion in the same .hzo-section). The label
24      flips Expand all/Collapse all (override via data-hzo-expand-label /
25      data-hzo-collapse-label) and aria-expanded tracks state — including
26      when the user opens/closes rows by hand (toggle listener; the event
27      doesn't bubble, so it's captured). Expanding all inside an exclusive
28      group temporarily lifts the shared `name` (a grouped set can't hold
29      more than one open) and restores it on collapse; the lifted name is
30      stashed in data-hzo-acc-name (internal, not an authoring surface).
31
32   OPTION CONTRACT — every setting is a data-attribute, so the block pattern
33   (author writes attributes by hand) and the [hzo_accordion] shortcode
34   (accordion.php generates them from settings) drive the SAME behavior.
35   Every option defaults OFF/legacy — attribute-free markup is untouched
36   (pure native JS-free accordion, byte-for-byte the pre-widget behavior):
37
38     data-hzo-accordion-exclusive   (wrapper) single-open group; value = the
39                                    shared group name (optional — empty value
40                                    gets a generated per-wrapper name)
41     data-hzo-accordion-expand-all  (button)  value = target accordion id
42                                    (empty → nearest .hzo-accordion in the
43                                    button's own .hzo-section)
44     data-hzo-expand-label          (button)  label while collapsed
45                                    (default "Expand all")
46     data-hzo-collapse-label        (button)  label while expanded
47                                    (default "Collapse all")
48
49   Reduced motion: this file animates nothing — toggling `open` is native and
50   instant; the open/close height/opacity motion lives in accordion-motion.css
51   (::details-content transitions) behind its own prefers-reduced-motion kill.
52
53   A11y: the disclosure itself is the native <details>/<summary> element
54   (keyboard operation + expanded semantics for free — the built-in equivalent
55   of the WAI-ARIA APG disclosure pattern; no ARIA to bolt on). The
56   expand/collapse-all button carries aria-expanded, kept honest across manual
57   row toggles via the captured toggle listener.
58
59   Idempotent: each enhanced root (exclusive wrapper / expand-all button) is
60   guarded with data-hzo-accordion-ready. The two roles are disjoint element
61   kinds (a container vs. a <button>), so one guard attribute serves both.
62
63   Public API (widget-convention surface, for AJAX-injected instances):
64     hzo.accordion = { init }     — init(scope) scans a subtree (default:
65                                    document) and wires new instances.
66   ═════════════════════════════════════════════════════════════════════════ */
67(function () {
68    'use strict';
69
70    // Generated exclusive-group names ("hzo-accordion-x0", "-x1", …).
71    // Module-level so repeated init() calls (AJAX scopes) can never mint a
72    // duplicate; the initial whole-document pass numbers wrappers 0..n in
73    // DOM order — exactly the names the legacy main.js loop produced.
74    var groupSeq = 0;
75
76    /* 1 ── exclusive-open groups */
77    function setupExclusive(wrap) {
78        if (wrap.getAttribute('data-hzo-accordion-ready') === 'true') { return; }
79        wrap.setAttribute('data-hzo-accordion-ready', 'true');
80        // Counter advances for EVERY wrapper (even explicitly-named ones) to
81        // match the legacy index-based numbering byte-for-byte.
82        var name = wrap.getAttribute('data-hzo-accordion-exclusive') || ('hzo-accordion-x' + groupSeq);
83        groupSeq++;
84        var items = wrap.querySelectorAll('details');
85        var seenOpen = false;
86        for (var i = 0; i < items.length; i++) {
87            if (items[i].open) {
88                if (seenOpen) { items[i].open = false; }   // group allows one open member
89                else { seenOpen = true; }
90            }
91            items[i].setAttribute('name', name);
92        }
93    }
94
95    /* 2 ── expand/collapse all */
96    function accordionFor(btn) {
97        var id = btn.getAttribute('data-hzo-accordion-expand-all');
98        if (id) {
99            var el = document.getElementById(id);
100            if (el) { return el; }
101        }
102        var scope = btn.closest('.hzo-section') || document;
103        return scope.querySelector('.hzo-accordion');
104    }
105
106    function setupButton(btn) {
107        if (btn.getAttribute('data-hzo-accordion-ready') === 'true') { return; }
108        var acc = accordionFor(btn);
109        // No target yet → leave the ready flag UNSET so a later init() (after
110        // the accordion is injected) can still wire this button. On a static
111        // page this is identical to legacy: an orphan button stays inert.
112        if (!acc) { return; }
113        btn.setAttribute('data-hzo-accordion-ready', 'true');
114
115        var expandLabel = btn.getAttribute('data-hzo-expand-label') || 'Expand all';
116        var collapseLabel = btn.getAttribute('data-hzo-collapse-label') || 'Collapse all';
117
118        function items() { return acc.querySelectorAll('details'); }
119        function sync() {
120            var list = items();
121            var allOpen = list.length > 0;
122            for (var i = 0; i < list.length; i++) {
123                if (!list[i].open) { allOpen = false; break; }
124            }
125            btn.setAttribute('aria-expanded', allOpen ? 'true' : 'false');
126            btn.textContent = allOpen ? collapseLabel : expandLabel;
127        }
128        // An exclusive group (<details name>) can hold only one open item,
129        // which blocks expand-all. Drive the name purely by open-count:
130        // lift the shared name whenever >1 is open, restore it once ≤1
131        // remains. Count-driven (not tied to the button's branch) so it's
132        // correct no matter how rows opened/closed — the fix for a manual
133        // close-after-expand-all leaving the group permanently nameless.
134        function reconcileNames() {
135            var list = items(), open = 0, j;
136            for (j = 0; j < list.length; j++) { if (list[j].open) { open++; } }
137            for (j = 0; j < list.length; j++) {
138                var d = list[j];
139                if (open > 1) {
140                    if (d.hasAttribute('name')) {
141                        d.setAttribute('data-hzo-acc-name', d.getAttribute('name'));
142                        d.removeAttribute('name');
143                    }
144                } else if (d.hasAttribute('data-hzo-acc-name')) {
145                    d.setAttribute('name', d.getAttribute('data-hzo-acc-name'));
146                    d.removeAttribute('data-hzo-acc-name');
147                }
148            }
149        }
150
151        btn.addEventListener('click', function () {
152            var list = items();
153            var opening = btn.getAttribute('aria-expanded') !== 'true';
154            // Opening all: lift every name FIRST (so setting open=true can't
155            // trip the exclusive auto-close), then open.
156            if (opening) {
157                for (var k = 0; k < list.length; k++) {
158                    if (list[k].hasAttribute('name')) {
159                        list[k].setAttribute('data-hzo-acc-name', list[k].getAttribute('name'));
160                        list[k].removeAttribute('name');
161                    }
162                }
163            }
164            for (var i = 0; i < list.length; i++) { list[i].open = opening; }
165            reconcileNames();
165   // collapse-all settles back to ≤1 → names restored
166            sync();
167        });
168
169        // Manual row toggles keep the button + exclusivity honest. `toggle`
170        // doesn't bubble — capture phase catches it from every child <details>.
171        // Bound per button (two buttons targeting one accordion each bind their
172        // own listener, exactly like legacy); the ready guard above prevents
173        // the same button ever double-binding.
174        acc.addEventListener('toggle', function () { reconcileNames(); sync(); }, true);
175
176        sync();   // seed label/aria from the authored open state
177    }
178
179    function init(scope) {
180        var root = scope || document;
181        // Exclusive wrappers FIRST (legacy order): each button's initial
182        // sync() then sees the post-grouping open state (extra authored
183        // `open` rows already cleared by the group).
184        var exclusives = root.querySelectorAll('[data-hzo-accordion-exclusive]');
185        for (var x = 0; x < exclusives.length; x++) { setupExclusive(exclusives[x]); }
186        var btns = root.querySelectorAll('[data-hzo-accordion-expand-all]');
187        for (var b = 0; b < btns.length; b++) { setupButton(btns[b]); }
188    }
189
190    if (document.readyState === 'loading') {
191        document.addEventListener('DOMContentLoaded', function () { init(); });
192    } else {
193        init();
194    }
195
196    // Shared engine namespace — never a new top-level global.
197    window.hzo = window.hzo || {};
198    window.hzo.accordion = { init: init };
199})();

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.