1export default function GDMFiltersAccordion( 2 containerSelector = ".insights_filters", 3) { 4 const container = document.querySelector(containerSelector); 5 if (!container) return; 6 7 const headers = Array.from(container.querySelectorAll(".fancytree-header")); 8 9 // When the "Apply filters" button is clicked, close the filters panel by 10 // removing the classes that open it (mobile drawer behaviour): "open" (used 11 // by the accordion/filter-groups)and "insights-filters-open" (used by the 12 // filters-toggle modal drawer on mobile). 13 const applyFiltersButton = container.querySelector(".apply-filters"); 14 15 if (applyFiltersButton) { 16 applyFiltersButton.addEventListener("click", () => { 17 container.classList.remove("open", "insights-filters-open"); 18 document.body.classList.remove("freeze-element"); 19 }); 20 } 21 22 if (headers.length === 0) return; 23 24 const getPaneForHeader = (header) => { 25 const toggleId = header.getAttribute("data-toggle"); 26 27 if (toggleId) { 28 const normalizedId = toggleId.replace(/^#/, ""); 29 const paneById = document.getElementById(normalizedId); 30 if (paneById && paneById.classList.contains("dropdown-pane")) { 31 return paneById; 32 } 33 } 34 35 const siblingPane = header.nextElementSibling; 36 if (siblingPane && siblingPane.classList.contains("dropdown-pane")) { 37 return siblingPane; 38 } 39 40 return null; 41 }; 42 43 const getOrCreateSummary = (pane) => { 44 let summary = pane.nextElementSibling; 45 46 if (!summary || !summary.classList.contains("gdm-filters-summary")) { 47 summary = document.createElement("div"); 48 summary.className = "gdm-filters-summary"; 49 pane.insertAdjacentElement("afterend", summary); 50 } 51 52 return summary; 53 }; 54 55 // Render the collapsed group's summary. When filters are checked we show the 56 // checked items exactly as they appear when the group is open (via the 57 // `ul.selected-filters.show` styling applied by the theme CSS: checkbox + 58 // checked background + check icon), and each item can be clicked to uncheck 59 // the filter without opening the group. With nothing selected we show the 60 // "No filters selected" text. 61 const renderSummary = (pane, summary) => { 62 summary.innerHTML = ""; 63 64 const selectedLis = Array.from( 65 pane.querySelectorAll('li[aria-selected="true"]'), 66 ).filter((li) => li.getAttribute("data-os-facet-data") !== "any"); 67 68 if (selectedLis.length === 0) { 69 summary.innerHTML = ""; 70 return; 71 } 72 73 // "show" is required so the theme's ul.selected-filters.show rules style 74 // these checkboxes exactly like the open tree (checked box + check icon). 75 const list = document.createElement("ul"); 76 list.className = "selected-filters show gdm-selected-filters-summary"; 77 78 selectedLis.forEach((li) => { 79 const liClone = li.cloneNode(true); 80 liClone.setAttribute("aria-selected", "true"); 81 82 liClone.querySelectorAll("ul").forEach((list) => list.remove()); 83 84 const node = liClone.querySelector(".fancytree-node"); 85 if (node) { 86 node.classList.add("fancytree-selected"); 87 } 88 89 const checkbox = liClone.querySelector(".fancytree-checkbox"); 90 if (checkbox) { 91 checkbox.setAttribute("aria-checked", "true"); 92 } 93 94 // Clicking anywhere on the inserted item unchecks the filter. We dispatch 95 // a bubbling click on the ORIGINAL tree item so the plugin's own delegated 96 // handler runs (it unchecks, re-sorts, refreshes counts and re-applies the 97 // filters). `MouseEvent` defaults to non-bubbling, which would never reach 98 // the container-bound handler, so bubbles must be set explicitly. 99 const uncheck = (event) => { 100 event.preventDefault(); 101 event.stopPropagation(); 102 li.dispatchEvent( 103 new MouseEvent("click", { bubbles: true, cancelable: true }), 104 ); 105 }; 106 107 // One handler on the row handles clicks on the checkbox OR the label â 108 // clicks on the checkbox bubble up to the li and are not stopped, so the 109 // checkbox itself is clickable to uncheck. 110 liClone.addEventListener("click", uncheck); 111 112 list.appendChild(liClone); 113 }); 114 115 summary.appendChild(list); 116 }; 117 118 const syncFiltersSummary = (pane) => { 119 const summary = getOrCreateSummary(pane); 120 const precedingPane = summary.previousElementSibling; 121 const precedingPaneIsOpen = 122 precedingPane && 123 precedingPane.classList.contains("dropdown-pane") && 124 precedingPane.classList.contains("open"); 125 126 if (precedingPaneIsOpen) { 127 // Group is open: the real tree is visible and interactive, hide the summary. 128 summary.style.display = "none"; 129 return; 130 } 131 132 renderSummary(pane, summary); 133 }; 134 135 const canOpenPane = (pane) => { 136 if (!pane) return false;
137 return pane.style.display !== "none"; 138 }; 139 140 headers.forEach((header) => { 141 const pane = getPaneForHeader(header); 142 if (pane) syncFiltersSummary(pane); 143 }); 144 145 // Re-sync the summaries whenever a selection changes or the tree is 146 // re-rendered (AJAX filter updates rebuild the tree markup). 147 headers.forEach((header) => { 148 const pane = getPaneForHeader(header); 149 if (!pane) return; 150 151 const observer = new MutationObserver(() => syncFiltersSummary(pane)); 152 observer.observe(pane, { 153 childList: true, 154 subtree: true, 155 attributes: true, 156 attributeFilter: ["aria-selected", "class"], 157 }); 158 }); 159 160 headers.forEach((header) => { 161 header.addEventListener("click", () => { 162 const pane = getPaneForHeader(header); 163 164 if (!pane) return; 165 166 if (!canOpenPane(pane)) { 167 header.classList.remove("open"); 168 pane.classList.remove("open"); 169 syncFiltersSummary(pane); 170 return; 171 } 172 173 const isOpen = pane.classList.contains("open"); 174 175 if (isOpen) { 176 header.classList.remove("open"); 177 pane.classList.remove("open"); 178 syncFiltersSummary(pane); 179 return; 180 } 181 182 header.classList.add("open"); 183 pane.classList.add("open"); 184 syncFiltersSummary(pane); 185 }); 186 }); 187}
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.