1document.addEventListener('DOMContentLoaded', () => { 2 const searchFilters = document.querySelectorAll('.autex-search-filter'); 3 4 /** 5 * Global registry of taxonomy slugs used as URL filter params. 6 * Populated by the tab/checkbox filter widgets on DOMContentLoaded. 7 * Allows this widget to read only filter params, ignoring unrelated ones like `page`. 8 */ 9 window.__autexFilterTaxonomies = window.__autexFilterTaxonomies || new Set(); 10 11 /** 12 * Builds filter groups from the URL using only registered taxonomies. 13 * Within a group: OR (e.g. blue OR red). Between groups: AND (e.g. cat AND color). 14 * e.g. ?cat=absorption&color=blue,red â [['cat-absorption'], ['color-blue', 'color-red']] 15 * Other filters (tabs, checkboxes) are responsible for keeping the URL in sync 16 * and dispatching a 'filterchange' CustomEvent on window after each update. 17 */ 18 const getFilterGroups = () => { 19 const params = new URLSearchParams(window.location.search); 20 const groups = []; 21 for (const [key, value] of params) { 22 if (!window.__autexFilterTaxonomies.has(key) || !value) continue; 23 const classes = value.split(',').filter(Boolean).map((slug) => `${key}-${slug}`); 24 if (classes.length) groups.push(classes); 25 } 26 return groups; 27 }; 28 29 /** 30 * Returns true if the item passes all active URL filter groups. 31 * When no filter groups are active, all items pass. 32 */ 33 const itemPassesUrlFilters = (item, filterGroups) => { 34 if (!filterGroups.length) return true; 35 return filterGroups.every((group) => group.some((cls) => item.classList.contains(cls))); 36 }; 37 38 const ITEM_FADE_MS = 200; 39 40 const showItem = (item) => { 41 clearTimeout(item._autexHideTimer); 42 item.style.opacity = '0'; 43 item.style.display = ''; 44 requestAnimationFrame(() => { 45 requestAnimationFrame(() => { 46 item.style.opacity = ''; 47 }); 48 }); 49 }; 50 51 const hideItem = (item) => { 52 clearTimeout(item._autexHideTimer); 53 item.style.opacity = '0'; 54 item._autexHideTimer = setTimeout(() => { 55 item.style.display = 'none'; 56 }, ITEM_FADE_MS);
57 }; 58 59 // ââ Active filter chips (shared with checkbox filter) âââââââââââââââââââ 60 window.__autexChipSources = window.__autexChipSources || new Map(); 61 window.__autexChipContainers = window.__autexChipContainers || new Map(); 62 window.__autexSearchInputs = window.__autexSearchInputs || new Map(); 63 64 if (!window.__autexRenderChipsForTarget) { 65 window.__autexRenderChipsForTarget = (targetKey) => { 66 const container = window.__autexChipContainers.get(targetKey); 67 if (!container) { 68 return; 69 } 70 // Deduplicate by taxonomy: only the first source per taxonomy that returns chips is used 71 const seenTaxonomies = new Set(); 72 const chips = (window.__autexChipSources.get(targetKey) || []).flatMap((src) => { 73 if (seenTaxonomies.has(src.taxonomy)) return []; 74 const srcChips = src.getChips(); 75 if (srcChips.length) seenTaxonomies.add(src.taxonomy); 76 return srcChips; 77 }); 78 container.innerHTML = ''; 79 chips.forEach(({ label, onClick }) => { 80 const chip = document.createElement('button'); 81 chip.type = 'button'; 82 chip.className = 'autex-active-filter-chip'; 83 chip.innerHTML = `<span class="autex-active-filter-chip__label">${label}</span><span class="autex-active-filter-chip__remove" aria-hidden="true">×</span>`; 84 chip.addEventListener('click', onClick); 85 container.appendChild(chip); 86 }); 87 container.hidden = chips.length === 0; 88 }; 89 } 90 91 searchFilters.forEach((searchFilter) => { 92 const input = searchFilter.querySelector('.autex-search-filter__input'); 93 const target = searchFilter.getAttribute('data-target'); 94 const targetElement = target ? document.querySelector(target) : null; 95 96 if (!input || !targetElement) { 97 return; 98 } 99 100 const loopItems = Array.from(targetElement.querySelectorAll('[data-elementor-type="loop-item"]')); 101 const items = loopItems.length ? loopItems : Array.from(targetElement.children); 102 103 if (!items.length) { 104 return; 105 } 106 107 const normalizedTextMap = new Map( 108 items.map((item) => [ 109 item, 110 (item.textContent || '').toLowerCase().replace(/\s+/g, ' ').trim(), 111 ]) 112 ); 113 114 // Register search chip source for this target 115 if (!window.__autexChipContainers.has(target)) { 116 const chipContainer = document.createElement('div'); 117 chipContainer.className = 'autex-active-filters'; 118 chipContainer.hidden = true; 119 targetElement.prepend(chipContainer); 120 window.__autexChipContainers.set(target, chipContainer); 121 } 122 if (!window.__autexChipSources.has(target)) { 123 window.__autexChipSources.set(target, []); 124 } 125 window.__autexChipSources.get(target).push({ 126 taxonomy: 'search', 127 getChips: () => { 128 const query = input.value.trim(); 129 if (!query || query.length < 3) return []; 130 return [ 131 { 132 label: query, 133 onClick: () => { 134 input.value = ''; 135 applySearch(); 136 }, 137 }, 138 ]; 139 }, 140 }); 141 142 const renderChips = () => window.__autexRenderChipsForTarget(target); 143 144 // Register this input for cross-instance sync (desktop + mobile modal pairs) 145 if (!window.__autexSearchInputs.has(target)) { 146 window.__autexSearchInputs.set(target, []); 147 } 148 window.__autexSearchInputs.get(target).push(input); 149 150 const syncPeerInputs = () => { 151 (window.__autexSearchInputs.get(target) || []).forEac
151h((peer) => { 152 if (peer !== input && peer.value !== input.value) { 153 peer.value = input.value; 154 } 155 }); 156 }; 157 158 const applySearch = () => { 159 const rawQuery = input.value.toLowerCase().replace(/\s+/g, ' ').trim(); 160 const keywords = rawQuery ? rawQuery.split(' ') : []; 161 const filterGroups = getFilterGroups(); 162 163 // Only search if we have 3 or more characters, or none 164 if (rawQuery.length > 0 && rawQuery.length < 3) { 165 return; 166 } 167 168 // Unhide all loop grids before re-filtering so previously hidden ones can reappear 169 targetElement.querySelectorAll('.elementor-widget-loop-grid').forEach((grid) => { 170 grid.style.display = ''; 171 }); 172 173 // Keep all instances for this target in sync 174 syncPeerInputs(); 175 176 items.forEach((item) => { 177 // URL-based filters take priority â never un-hide what they've excluded 178 if (!itemPassesUrlFilters(item, filterGroups)) { 179 hideItem(item); 180 return; 181 } 182 183 // No search query â item is visible (URL filters already passed above) 184 if (!keywords.length) { 185 showItem(item); 186 return; 187 } 188 189 // Apply text search on top 190 const itemText = normalizedTextMap.get(item) || ''; 191 const matchesSearch = keywords.every((keyword) => itemText.includes(keyword)); 192 if (matchesSearch) { 193 showItem(item); 194 } else { 195 hideItem(item); 196 } 197 }); 198 199 renderChips(); 200 201 setTimeout(() => { 202 const noResults = targetElement.querySelector('.no-results'); 203 if (noResults) { 204 const hasVisible = items.some((item) => item.style.display !== 'none'); 205 noResults.style.display = hasVisible ? 'none' : ''; 206 } 207 targetElement.querySelectorAll('.elementor-loop-container').forEach((container) => { 208 const hasVisible = Array.from( 209 container.querySelectorAll('[data-elementor-type="loop-item"]') 210 ).some((item) => item.style.display !== 'none'); 211 if (!hasVisible) { 212 const grid = container.closest('.elementor-widget-loop-grid'); 213 if (grid) grid.style.display = 'none'; 214 } 215 }); 216 }, ITEM_FADE_MS);
217 }; 218 219 input.addEventListener('input', applySearch); 220 input.addEventListener('change', applySearch); 221 input.addEventListener('keyup', applySearch); 222 223 // Re-apply when other filters update the URL. 224 // Other filters call: window.dispatchEvent(new CustomEvent('filterchange')) 225 // after updating the URL with history.replaceState. 226 window.addEventListener('filterchange', applySearch); 227 228 // Also handle browser back/forward navigation 229 window.addEventListener('popstate', applySearch); 230 }); 231});
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.