output-reveal.ts (1837B)
1 // Reveal only the start of a foreground result, never its whole (possibly huge) 2 // box. Bounds are visual-viewport coordinates, accounting for the sticky toolbar. 3 export function revealDelta(top: number, height: number, toolbarHeight: number, viewportTop: number, viewportHeight: number): number { 4 const safeTop = viewportTop + toolbarHeight + 12; 5 const bottom = viewportTop + viewportHeight - 12; 6 if (bottom <= safeTop) return 0; 7 const visibleStart = Math.min(64, height, bottom - safeTop); 8 return top >= safeTop && top + visibleStart <= bottom ? 0 : top - safeTop; 9 } 10 11 export function outputReveal(toolbar: HTMLElement) { 12 let armed = false, revision = 0; 13 const cancel = (): void => { armed = false; revision++; }; 14 // A user who scrolls elsewhere during a slow run has chosen what to read. 15 window.addEventListener('pagehide', cancel); 16 window.addEventListener('wheel', cancel, { passive: true }); 17 window.addEventListener('touchmove', cancel, { passive: true }); 18 window.addEventListener('keydown', event => { 19 if (['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown', ' '].includes(event.key)) cancel(); 20 }); 21 function reveal(target: HTMLElement): void { 22 if (!armed) return; 23 armed = false; const current = revision; 24 requestAnimationFrame(() => { 25 if (current !== revision || !matchMedia('(max-width:740px)').matches || target.closest('[inert]') || !target.getClientRects().length) return; 26 const rect = target.getBoundingClientRect(), viewport = window.visualViewport; 27 const delta = revealDelta(rect.top, rect.height, toolbar.getBoundingClientRect().height, viewport?.offsetTop ?? 0, viewport?.height ?? innerHeight); 28 if (delta) window.scrollBy({ top: delta, behavior: 'instant' }); 29 }); 30 } 31 return { arm(): void { cancel(); armed = true; }, cancel, reveal }; 32 }