1const STICKY_EDGE_TOLERANCE_PX = 1;
2
3/**
4 * Returns the scroll offset that puts a section's natural top where its sticky heading is now.
5 * Collapsing removes the body below that heading; without this adjustment, the unchanged numeric
6 * offset points into the next section and makes its sticky heading appear to replace the one the
7 * reader pressed.
8 */
9export function sectionCollapseAnchorScrollTop(input: {
10 readonly scrollTop: number;
11 readonly viewportTop: number;
12 readonly sectionTop: number;
13 readonly headingTop: number;
14}): number | null {
15 const sectionHasScrolledPastViewport = input.sectionTop < input.viewportTop;
16 const headingIsPinned = input.headingTop <= input.viewportTop + STICKY_EDGE_TOLERANCE_PX;
17 if (!sectionHasScrolledPastViewport || !headingIsPinned) return null;
18
19 return Math.max(0, input.scrollTop + input.sectionTop - input.viewportTop);
20}