From c9ccbce3f7eb19a3d5a14adc4ab1928c02d276e0 Mon Sep 17 00:00:00 2001 From: eleith Date: Tue, 19 Aug 2025 18:32:56 +0000 Subject: [PATCH] fix: prevent focus from shifting away from visible entries The previous auto-focus logic was too aggressive and would shift focus away from large, open entries that were still visible on screen. This change refines the `IntersectionObserver` logic to: - Only trigger auto-focus when scrolling down. - Prevent focus from changing if the currently active entry is still visible in the viewport. - Only move focus to entries that appear after the previously focused one. --- internal/templates/static/script.js | 75 +++++++++++++++++------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/internal/templates/static/script.js b/internal/templates/static/script.js index 32f9e09..0e3a5dd 100644 --- a/internal/templates/static/script.js +++ b/internal/templates/static/script.js @@ -85,41 +85,52 @@ document.addEventListener('DOMContentLoaded', () => { } const observer = new IntersectionObserver((intersectionEntries) => { - const scrollDirection = window.scrollY > lastScrollY ? 'down' : 'up'; - lastScrollY = window.scrollY; + const scrollDirection = window.scrollY > lastScrollY ? 'down' : 'up'; + lastScrollY = window.scrollY; - intersectionEntries.forEach(intersectionEntry => { - const { - target, - isIntersecting, - boundingClientRect - } = intersectionEntry; + // Mark entries as viewed when they scroll out of view + intersectionEntries.forEach(intersectionEntry => { + const { + target, + isIntersecting, + boundingClientRect + } = intersectionEntry; + if (scrollDirection === 'down' && !isIntersecting && boundingClientRect.top < 0) { + target.classList.add('viewed'); + } + }); - // If an entry is no longer intersecting and we are scrolling down, - // mark it as viewed. - if (scrollDirection === 'down' && !isIntersecting && boundingClientRect.top < 0) { - target.classList.add('viewed'); - } + // If we are scrolling up, do nothing with focus. + if (scrollDirection === 'up') { + return; + } - // If there's no active entry, or the active entry is no longer visible, - // try to set the first visible entry as active. - if (!activeEntry || (activeEntry && !intersectionEntries.some(e => e.target === activeEntry && e.isIntersecting))) { - for (const entry of getVisibleEntries()) { - const rect = entry.getBoundingClientRect(); - // Set the first entry that is at or above the top of the viewport as active. - // Only set active if it's not already the active entry and it's not behind the current active entry. - if (rect.top >= 0) { - if (!activeEntry || entries.indexOf(entry) >= entries.indexOf(activeEntry)) { - setActiveEntry(entry, true); // Prevent scroll on auto-focus - } - return; - } - } - } - }); - }, { - threshold: 0.01 // A very small threshold to detect entry entering/leaving the viewport - }); + // Check if the active entry is still visible. If so, do nothing. + if (activeEntry) { + const rect = activeEntry.getBoundingClientRect(); + if (rect.bottom > 0 && rect.top < window.innerHeight) { + return; + } + } + + // Find the next entry to focus. + // It should be the first one that is at least partially visible. + for (const entry of getVisibleEntries()) { + const rect = entry.getBoundingClientRect(); + if (rect.top >= 0 && rect.top < window.innerHeight) { + // Only set active if it's after the current active entry. + const activeEntryIndex = activeEntry ? entries.indexOf(activeEntry) : -1; + const newEntryIndex = entries.indexOf(entry); + if (newEntryIndex > activeEntryIndex) { + setActiveEntry(entry, true); + return; + } + } + } + + }, { + threshold: 0.0, + }); entries.forEach(entry => observer.observe(entry)); -- 2.54.0