/** * Brings what `selector` finds inside `container` into view once it is drawn, whether it is already there or arrives * with the next change. FullCalendar draws a "+N" popover after the click handler returns, and fills in its rows after * that, so the popover is kept in view as it grows until the allowance ends. */ export function revealWhenDrawn(container: HTMLElement | null, selector: string, waitMs = 1000): void { if (!container) return; let shown: Element | undefined; let sized: ResizeObserver | undefined; let ended = false; const reveal = () => { if (ended) return; const drawn = container.querySelector(selector); if (!drawn) return; if (drawn !== shown) { shown = drawn; drawn.scrollIntoView({ block: 'nearest' }); sized?.disconnect(); if (typeof ResizeObserver !== 'undefined') { sized = new ResizeObserver(() => { reveal(); }); sized.observe(drawn); } } // Whatever still runs past the foot of the window is scrolled there directly, the page being the scroller. const overflow = drawn.getBoundingClientRect().bottom - window.innerHeight; if (overflow > 0) window.scrollBy({ top: Math.ceil(overflow) + 8 }); }; // Measured again after the browser lays the change out, since rows and padding can land a frame later. const revealAfterLayout = () => { reveal(); if (typeof window.requestAnimationFrame === 'function') window.requestAnimationFrame(() => { reveal(); }); }; if (typeof MutationObserver === 'undefined') { revealAfterLayout(); return; } const observer = new MutationObserver(revealAfterLayout); observer.observe(container, { childList: true, subtree: true }); window.setTimeout(() => { ended = true; observer.disconnect(); sized?.disconnect(); }, waitMs); revealAfterLayout(); }