fix: prevent focus from shifting away from visible entries #49

Merged
eleith merged 1 commits from gemini/fix/focus-shifting into main 2025-08-19 18:34:55 +00:00
+43 -32
View File
@@ -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));