import { useEffect, useMemo, useRef, useState } from "react"; import { useCommand } from "./useCommand.js"; export type UseScrollableTextOptions = { textLength: number; viewportHeight: number; signature: string; }; export type UseScrollableTextResult = { scrollOffset: number; canScroll: boolean; maxScrollOffset: number; pageStep: number; }; export function useScrollableText({ textLength, viewportHeight, signature, }: UseScrollableTextOptions): UseScrollableTextResult { let [scrollOffset, setScrollOffset] = useState(0); let lastSignatureRef = useRef(signature); useEffect(() => { if (lastSignatureRef.current !== signature) { lastSignatureRef.current = signature; setScrollOffset(0); } }, [signature]); let maxScrollOffset = useMemo(() => Math.max((textLength || 1) - viewportHeight, 0), [textLength, viewportHeight]); useEffect(() => { setScrollOffset((offset) => Math.min(offset, maxScrollOffset)); }, [maxScrollOffset]); let canScroll = textLength > viewportHeight && viewportHeight > 0; let pageStep = useMemo(() => Math.max(viewportHeight - 1, 1), [viewportHeight]); useCommand("up", () => setScrollOffset((offset) => Math.max(offset - 1, 0)), canScroll); useCommand("down", () => setScrollOffset((offset) => Math.min(offset + 1, maxScrollOffset)), canScroll); useCommand("pageup", () => setScrollOffset((offset) => Math.max(offset - pageStep, 0)), canScroll); useCommand("pagedown", () => setScrollOffset((offset) => Math.min(offset + pageStep, maxScrollOffset)), canScroll); return { scrollOffset, canScroll, maxScrollOffset, pageStep }; }