'use client'; import { useEffect, useRef } from 'react'; /** Anything with a `.focus()` method. */ export interface Focusable { focus: () => void; } /** * Core stream-end focus effect — context-free on purpose so it can run * *inside* `ChatProvider` without an import cycle (`context` → * `hooks/useAutoFocusOnStreamEnd` → `context`). * * Fires `resolveTarget().focus()` on the streaming true → false edge. * Both `ChatProvider` and the public `useAutoFocusOnStreamEnd` hook * delegate here. */ export function useStreamEndFocus(params: { isStreaming: boolean; enabled: boolean; delayMs: number; /** Resolves the focus target lazily, at fire time — so it always * sees the freshest registered composer handle. */ resolveTarget: () => Focusable | null; }): void { const { isStreaming, enabled, delayMs, resolveTarget } = params; // Keep the resolver in a ref so the effect doesn't re-fire when the // caller passes a fresh closure each render. const resolveRef = useRef(resolveTarget); resolveRef.current = resolveTarget; const prevStreamingRef = useRef(isStreaming); useEffect(() => { const wasStreaming = prevStreamingRef.current; prevStreamingRef.current = isStreaming; if (!enabled) return; // Only the true → false transition fires focus — toggling `enabled` // mid-stream won't steal focus while the user is reading. if (!(wasStreaming && !isStreaming)) return; const focusNow = () => resolveRef.current()?.focus(); if (delayMs > 0) { const id = window.setTimeout(focusNow, delayMs); return () => window.clearTimeout(id); } const raf = requestAnimationFrame(focusNow); return () => cancelAnimationFrame(raf); }, [isStreaming, enabled, delayMs]); }