// Restore focus across the out-of-band sidebar swap. // // Under htmx 2 this was the whole mechanism: htmx saved focus only around // the MAIN swap target, so a focused filter control destroyed by the // out-of-band sidebar swap dropped a keyboard user to on every // filter change. htmx 4 saves and restores focus per swap task, the // out-of-band ones included, so this is a backstop rather than the fix. // // It is kept because it costs nothing when htmx has already restored, and // because the two now disagree about ordering: htmx 4 swaps the main target // first and the out-of-band elements after, where 2 did the reverse. So the // settle that fires first is the results one, when the sidebar has not been // replaced yet -- which is why `pending` is cleared on a restore rather // than on the first settle after the request. (function () { var pending = null; document.body.addEventListener('htmx:before:request', function () { var el = document.activeElement; pending = el && el.id && el.closest('#discover-sidebar') ? el.id : null; }); document.body.addEventListener('htmx:after:settle', function () { if (!pending) return; var el = document.getElementById(pending); // Only if it actually went away and came back; a surviving element // kept its focus already. if (!el || el === document.activeElement) return; el.focus({ preventScroll: true }); pending = null; }); })(); // View toggle (list vs grid) with localStorage persistence. // Re-applied after HTMX swaps because new content replaces the container. (function() { function applyView(view) { var container = document.getElementById('results-container-inner'); if (container) { container.className = 'results-container results-' + view; } document.querySelectorAll('.view-btn').forEach(function(btn) { btn.classList.toggle('is-selected', btn.dataset.view === view); }); // The column headers label the list layout; in grid view they label nothing. var header = document.querySelector('.table-header'); if (header) { header.classList.toggle('is-hidden', view !== 'list'); } } // Load saved preference on page load document.addEventListener('DOMContentLoaded', function() { var saved = safeStorageGet('discoverViewPref') || 'grid'; applyView(saved); }); // Handle view button clicks document.querySelectorAll('.view-btn').forEach(function(btn) { btn.addEventListener('click', function() { var view = btn.dataset.view; applyView(view); safeStorageSet('discoverViewPref', view); }); }); // Re-apply view preference after HTMX swaps new content. // // Settle rather than swap: htmx 4 fires `htmx:after:swap` on the // element that made the request, and a sidebar filter link is inside // the region the out-of-band sidebar swap replaces, so the event has // nothing to bubble through. // // Asked as "did the results region just arrive" rather than "is the // settled element #results-container". The region is swapped whole now // (hx-swap="outerHTML", which is what a described control emits), so // the element the event names is not necessarily the one that was // targeted, and the id test silently stopped firing under an outer // swap: the grid preference reverted to list on every filter click. document.body.addEventListener('htmx:after:settle', function(evt) { var el = evt.target; if (!el || !el.querySelector) return; var arrived = el.id === 'results-container' || el.id === 'results-container-inner' || el.querySelector('#results-container-inner'); if (arrived) { var saved = safeStorageGet('discoverViewPref') || 'grid'; applyView(saved); } }); })();