import { useEffect, useRef, useState, type RefObject } from "react"; import { CONTENT_ID } from "./landmarks"; // A tall page header that folds to one row once the page's content scrolls (the owner's pick for the // channel page: the banner and the identity block took ~40% of a 133% screen before the first video). // // The header is FIXED (it sits in the shell's toolbar band, outside the scroller), so folding it makes the // scroller taller, which shortens how far the content can scroll. Two rules keep that from flickering: // - fold only when the page can still scroll past the fold point once the header has given its height // back (`room - saving > FOLD_AT`): a page barely taller than the window would otherwise fold, lose its // scroll, land at the top, unfold, and so on, with every wheel tick; // - unfold only at the very top, not at the fold point: between the two the header keeps whatever it was. export const FOLD_AT = 32; interface ScrollPosition { // The scroller's scrollTop. top: number; // How far it can scroll at all: scrollHeight − clientHeight. room: number; } // `saving` = the height the header gives back when it folds (measured while it is unfolded). export function nextFolded( folded: boolean, { top, room }: ScrollPosition, saving: number, ): boolean { if (folded) return top > 0; return top > FOLD_AT && room - saving > FOLD_AT; } // Whether the header is folded, following the page's one scroller (PageScroller's
). `foldable` is // the part of the header that folds away; its height, read while it is shown, is the `saving` above. export function useScrollFold(foldable: RefObject): boolean { const [folded, setFolded] = useState(false); const saving = useRef(0); useEffect(() => { const update = (el: HTMLElement) => { if (foldable.current) saving.current = foldable.current.offsetHeight; setFolded((was) => nextFolded( was, { top: el.scrollTop, room: el.scrollHeight - el.clientHeight }, saving.current, ), ); }; // Scroll events do not bubble but they do capture, so one document listener sees the scroller even // when it mounts after this page (as BackToTop does). A Back that restores the position scrolls it too. const onScroll = (e: Event) => { const el = e.target; if (el instanceof HTMLElement && el.id === CONTENT_ID) update(el); }; document.addEventListener("scroll", onScroll, { passive: true, capture: true }); const main = document.getElementById(CONTENT_ID); if (main) update(main); return () => document.removeEventListener("scroll", onScroll, { capture: true }); }, [foldable]); return folded; }