1/** 2 * Governance Listing â dropdown filter UX 3 * 4 * Mirrors the Library block's filter dropdown (open/close, in-panel category 5 * search, checkbox state, Reset/Cancel/Apply) but submits the form natively to 6 * a URL with `governance_category[]` params instead of going through AJAX. 7 */ 8(function () { 9 'use strict'; 10 11 function initForm(form) { 12 if (!form || form.dataset.governanceFilterInit === '1') return; 13 form.dataset.governanceFilterInit = '1'; 14 15 const toggle = form.querySelector('[data-filter-toggle]'); 16 const panel = form.querySelector('[data-filter-panel]'); 17 const chevron = form.querySelector('[data-filter-chevron]'); 18 const search = form.querySelector('[data-filter-search]'); 19 const list = form.querySelector('[data-filter-list]'); 20 const noMatch = form.querySelector('.filter-no-match'); 21 const resetBtn = form.querySelector('[data-filter-reset]'); 22 const cancelBtn = form.querySelector('[data-filter-cancel]'); 23 const checkboxes = form.querySelectorAll('[data-filter-checkbox]'); 24 25 if (!toggle || !panel) return; 26 27 // Snapshot initial checked state for cancel/restore 28 let snapshot = Array.from(checkboxes).map(cb => cb.checked); 29 30 function setOpen(open) { 31 if (open) { 32 panel.hidden = false; 33 toggle.setAttribute('aria-expanded', 'true'); 34 if (chevron) chevron.style.transform = 'rotate(180deg)'; 35 snapshot = Array.from(checkboxes).map(cb => cb.checked); 36 } else { 37 panel.hidden = true; 38 toggle.setAttribute('aria-expanded', 'false'); 39 if (chevron) chevron.style.transform = ''; 40 if (search) { 41 search.value = ''; 42 filterOptions(''); 43 } 44 } 45 } 46 47 function filterOptions(term) { 48 term = (term || '').toLowerCase().trim(); 49 let visibleCount = 0; 50 if (list) { 51 list.querySelectorAll('.filter-option').forEach(li => { 52 const text = li.getAttribute('data-filter-option-text') || ''; 53 const match = term === '' || text.indexOf(term) !== -1; 54 li.style.display = match ? '' : 'none'; 55 if (match) visibleCount++; 56 }); 57 } 58 if (noMatch) noMatch.classList.toggle('hidden', visibleCount !== 0); 59 } 60 61 toggle.addEventListener('click', function (e) { 62 e.preventDefault(); 63 setOpen(panel.hidden); 64 }); 65 66 document.addEventListener('click', function (e) { 67 if (panel.hidden) return; 68 if (form.contains(e.target)) return; 69 setOpen(false); 70 }); 71 72 document.addEventListener('keydown', function (e) { 73 if (e.key === 'Escape' && !panel.hidden) setOpen(false); 74 }); 75 76 if (search) { 77 search.addEventListener('input', function () { 78 filterOptions(search.value); 79 }); 80 } 81 82 if (resetBtn) { 83 resetBtn.addEventListener('click', function (e) { 84 e.preventDefault(); 85 checkboxes.forEach(cb => { cb.checked = false; }); 86 }); 87 } 88 89 if (cancelBtn) { 90 cancelBtn.addEventListener('click', function (e) { 91 e.preventDefault(); 92 checkboxes.forEach((cb, i) => { cb.checked = !!snapshot[i]; }); 93 setOpen(false); 94 }); 95 } 96 } 97 98 // Scroll the active pill inside each .pill-strip into view on load. 99 // Active = first pill with the "bg-asphalt" class (matches selected state). 100 function scrollActivePillIntoView() { 101 document.querySelectorAll('.pill-strip').forEach(strip => { 102 const active = strip.querySelector('.bg-asphalt'); 103 if (!active) return; 104 const stripRect = strip.getBoundingClientRect(); 105 const activeRect = active.getBoundingClientRect(); 106 // Skip if already fully visible. 107 if (activeRect.left >= stripRect.left && activeRect.right <= stripRect.right) return; 108 // Center the active pill within the strip without affecting page scroll. 109 const offset = (active.offsetLeft) - (strip.clientWidth / 2) + (active.offsetWidth / 2);
110 strip.scrollTo({ left: Math.max(0, offset), behavior: 'auto' }); 111 }); 112 } 113 114 function init() { 115 document.querySelectorAll('[data-governance-filter-form]').forEach(initForm); 116 scrollActivePillIntoView(); 117 } 118 119 if (document.readyState === 'loading') { 120 document.addEventListener('DOMContentLoaded', init); 121 } else { 122 init(); 123 } 124})();
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.