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.