import { useEffect, useRef, useState, type ReactNode } from "react"; import { useOpenGeniText } from "../../messages/runtime"; /** * Reserve the chat slot before starting expensive media work. Activation is * one-way: scrolling away must not discard an interactive preview's state. * The timeline parks at its initial tip in layout effects before this observer * is attached, so historical previews above the entry viewport stay dormant. */ export function DeferredChatMedia({ children, height, label, actionLabel, }: { children: ReactNode; height: number; /** What is loading, as in "Load {label}". */ label?: string | undefined; /** The complete (translated) activation label; overrides `label`. */ actionLabel?: string | undefined; }) { const t = useOpenGeniText(); const host = useRef(null); const [active, setActive] = useState(false); useEffect(() => { const node = host.current; if (active || !node) return; if (typeof IntersectionObserver === "undefined") { setActive(true); return; } let disposed = false; const observer = new IntersectionObserver( (entries) => { if (disposed || !entries.some((entry) => entry.isIntersecting)) return; setActive(true); observer.disconnect(); }, { root: node.closest("[data-og-timeline-scroller]"), rootMargin: "200px 0px", }, ); observer.observe(node); return () => { disposed = true; observer.disconnect(); }; }, [active]); return (
{active ? ( children ) : (
)}
); }