Ó£ÌÒÊÓÆµ

All Search Results Hidden Title

Refine your results

, outside every landmark and visible. Relocate it into main and hide it visually — it still announces from there. */ const srStatus = document.getElementById('searchAccessibilityContainer'); const mainEl = document.querySelector('main'); if (srStatus && mainEl) { srStatus.classList.add('sr-only'); if (srStatus.parentElement !== mainEl) mainEl.appendChild(srStatus); } document.addEventListener('click', (e) => { const btn = e.target.closest('[data-facet-toggle]'); if (!btn) return; const group = btn.closest('[data-facet-group]')?.dataset.facetGroup; if (!group) return; expandedFacetGroups[group] = !expandedFacetGroups[group]; syncFacetGroups(); }); /* ---------------------------------------------------------------- Mobile filter toggle ---------------------------------------------------------------- */ document.addEventListener('click', (e) => { const filterBtn = e.target.closest('#mobile-filter-toggle'); if (!filterBtn) return; const container = document.getElementById('searchstax-facets-container'); if (!container) return; const isOpen = container.classList.toggle('filters-open'); filterBtn.setAttribute('aria-expanded', String(isOpen)); filterBtn.classList.toggle('collapsed', !isOpen); }); /* ---------------------------------------------------------------- Move the facets container between the sidebar (desktop) and below the Filter button (mobile). The widget re-renders into the element by ID, so relocating the element itself is safe. ---------------------------------------------------------------- */ const facetsContainer = document.getElementById('searchstax-facets-container'); const facetsMobileSlot = document.getElementById('facets-mobile-slot'); const facetsDesktopSlot = document.getElementById('facets-desktop-slot'); const isDesktop = window.matchMedia('(min-width: 992px)'); function placeFacets(mq) { const target = mq.matches ? facetsDesktopSlot : facetsMobileSlot; if (facetsContainer && target && facetsContainer.parentElement !== target) { target.appendChild(facetsContainer); } } placeFacets(isDesktop); isDesktop.addEventListener('change', placeFacets); /* Facet markup is replaced on every search, and the re-rendered panel has no `show` class. Restore state synchronously — debouncing this paints the collapsed state first. Scoped to the facets container so it isn't competing with the results burst. */ const facetsObserver = new MutationObserver(() => syncFacetGroups(true)); if (facetsContainer) { facetsObserver.observe(facetsContainer, {childList: true, subtree: true}); } /* ---------------------------------------------------------------- Which tab is showing. The "all results" tab is synthetic, so the widget never checks or marks it. The URL is the source of truth, and the body class it sets drives both the tab's active state and hiding the Filter button (there are no facets to filter on the all-results view). ---------------------------------------------------------------- */ function activeTabFromUrl() { const params = new URLSearchParams(window.location.search); for (const [key, value] of params) { if (key.startsWith('searchstax[facets]') && value.includes('sectionType_s')) { return decodeHtmlEntities(value.split(':').pop()).toLowerCase(); } } return ''; } function syncTabView() { const active = activeTabFromUrl() || 'all results'; document.body.classList.toggle('all-results-view', active === 'all results'); document.body.classList.toggle('tabbed-view', active !== 'all results'); document.querySelectorAll('#searchstax-tabs-container .searchstax-facet-input').forEach(item => { const cb = item.querySelector('.searchstax-facet-input-checkbox'); const link = item.querySelector('.searchstax-facet-value-label'); if (!cb || !link) return; const isActive = (cb.getAttribute('aria-label') || '').toLowerCase() === active; cb.checked = isActive; link.classList.toggle('active', isActive); link.setAttribute('aria-pressed', String(isActive)); }); } // Landing page: no search has run yet, so no facets exist to filter. syncTabView(); /* ---------------------------------------------------------------- Applied filters row Rendered from the facet checkbox state rather than the widget's own pill markup, so it can live in the refine bar. Removal is delegated back to the widget by clicking its checkboxes. ---------------------------------------------------------------- */ const appliedRoot = document.getElementById('applied-filters'); let appliedFilters = []; const escapeHtml = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); function readAppliedFilters() { const boxes = document.querySelectorAll( '#searchstax-facets-container .searchstax-facet-input-checkbox:checked' ); const seen = new Set(); const out = []; Array.from(boxes).forEach(cb => { const group = cb.closest('fieldset')?.querySelector('legend')?.textContent.trim() || ''; if (/section type/i.test(group)) return; // owned by the tabs, not a user filter const label = cb.closest('.form-check')?.querySelector('label'); let text = cb.getAttribute('aria-label') || ''; if (label) { const clone = label.cloneNode(true); clone.querySelectorAll('span').forEach(s => s.remove()); // drop the "(18)" text = clone.textContent.trim(); } const key = `${group}|${text}`; if (seen.has(key)) return; // desktop + mobile markup both hold checkboxes seen.add(key); out.push({checkbox: cb, label: text, key}); }); return out; } function renderAppliedFilters() { if (!appliedRoot) return; appliedFilters = readAppliedFilters(); const signature = appliedFilters.map(f => f.key).join('|'); if (signature === appliedRoot.dataset.signature) return; // no change, skip appliedRoot.dataset.signature = signature; if (!appliedFilters.length) { appliedRoot.hidden = true; appliedRoot.innerHTML = ''; return; } appliedRoot.hidden = false; appliedRoot.innerHTML = ` Applied filters: `; } function clearAppliedFilters() { if (!appliedRoot) return; appliedFilters = []; appliedRoot.dataset.signature = ''; appliedRoot.innerHTML = ''; appliedRoot.hidden = true; } // Fallback only: used if the widget's clear-all button is absent from // the DOM. Fires one search per filter, so the clearAll path wins. function clearFiltersSequentially(guard = 0) { if (guard > 20) return; // safety valve const remaining = readAppliedFilters(); // re-read after every re-render if (!remaining.length) return; remaining[0].checkbox.click(); setTimeout(() => clearFiltersSequentially(guard + 1), 400); } appliedRoot?.addEventListener('click', (e) => { const pill = e.target.closest('.applied-filters__pill'); if (pill) { appliedFilters[Number(pill.dataset.filterIndex)]?.checkbox.click(); return; } if (e.target.closest('.applied-filters__clear')) { const clearAll = document.querySelector( '#searchstax-facets-container .searchstax-facets-pill-clear-all' ); clearAppliedFilters(); // hide immediately if (clearAll) { clearAll.click(); } else { clearFiltersSequentially(); } setTimeout(renderAppliedFilters, 300); // reconcile once the widget settles } }); /* ---------------------------------------------------------------- Re-apply our DOM tweaks after every widget re-render. Debounced: the widget fires a burst of mutations while rendering results, and every pass here is idempotent anyway. ---------------------------------------------------------------- */ let syncTimer = null; const observer = new MutationObserver(() => { clearTimeout(syncTimer); syncTimer = setTimeout(() => { const relevanceOption = document.querySelector('#searchstax-search-order-select option[value=""]'); if (relevanceOption && !relevanceOption.dataset.translated) { relevanceOption.textContent = 'Relevance'; relevanceOption.dataset.translated = "true"; } syncTabView(); renderAppliedFilters(); }, 50); }); observer.observe(document.body, { childList: true, subtree: true }); }; document.head.appendChild(script); };