Cookbook auto-fold: use IntersectionObserver to catch any scroll source

The scroll listener on .cookbook-body never fired — the user is
likely scrolling inside the nested .hwfit-list (max-height:52vh)
which doesn't bubble to its parent. IntersectionObserver fires
whenever the Direct Download header crosses the viewport edge
regardless of which container moved.

Folds only when boundingClientRect.top < 0 (header pushed up past
the top) so modal close / detach doesn't trigger it.
This commit is contained in:
pewdiepie-archdaemon
2026-06-13 20:07:32 +09:00
parent ae0b29af3d
commit 74e563dabc
+21 -17
View File
@@ -1416,24 +1416,28 @@ function _wireTabEvents(body) {
_setFolded(!folded); _setFolded(!folded);
}); });
// Auto-fold when the user scrolls past the Direct Download header. // Auto-fold when the user scrolls past the Direct Download header.
// Desktop scroll container is .cookbook-body; mobile is the modal // Use IntersectionObserver — fires whenever the header element
// .modal-content. Walk up via .closest() to find whichever is // crosses the viewport edge, regardless of which scroll container
// actually scrollable. Doesn't auto-unfold — chevron ▸ still // moved (cookbook-body, modal-content, hwfit-list, viewport, etc).
// expands manually. // Doesn't auto-unfold; the chevron ▸ still expands manually.
const _scrollHost = dlFold.closest('.cookbook-body') let _ioPrimed = false;
|| dlFold.closest('#cookbook-modal .modal-content') const _io = new IntersectionObserver((entries) => {
|| dlFold.closest('.modal-content') const entry = entries[0];
|| document.scrollingElement if (!entry) return;
|| document.body; // Skip the initial firing where the header is already in view.
const _onScroll = () => { if (!_ioPrimed) {
if (dlFoldBody.style.display === 'none') return; _ioPrimed = true;
const r = dlFold.getBoundingClientRect(); if (entry.isIntersecting) return;
const top = (_scrollHost && _scrollHost.getBoundingClientRect) ? _scrollHost.getBoundingClientRect().top : 0;
if (r.bottom < top + 4) {
_setFolded(true, /* persist */ false);
} }
}; if (entry.isIntersecting) return;
_scrollHost.addEventListener('scroll', _onScroll, { passive: true }); // Only fold when scrolled ABOVE the viewport (not when the modal
// closes / detaches under it). boundingClientRect.top < 0 means
// the header was pushed off the top.
if (entry.boundingClientRect.top >= 0) return;
if (dlFoldBody.style.display === 'none') return;
_setFolded(true, /* persist */ false);
}, { threshold: 0 });
_io.observe(dlFold);
} }
const hfToggle = document.getElementById('cookbook-hf-latest-toggle'); const hfToggle = document.getElementById('cookbook-hf-latest-toggle');
const hfArrow = document.getElementById('cookbook-hf-latest-arrow'); const hfArrow = document.getElementById('cookbook-hf-latest-arrow');