{"version":3,"file":"use-typewriter.cjs","names":[],"sources":["../../src/hooks/use-typewriter.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport { useInterval } from \"./use-interval\";\n\n/** State returned by {@link useTypewriter}. */\nexport interface UseTypewriterResult {\n    /** The prefix of `text` revealed so far. */\n    displayedText: string;\n    /** `true` once the whole string is on screen. */\n    isComplete: boolean;\n    /** Reveal the rest immediately — wire it to a tap or a key press. */\n    skip: () => void;\n}\n\n/**\n * Reveals a string one character at a time.\n *\n * Changing `text` restarts the reveal. The reset happens during render rather\n * than in an effect, so the new string never flashes in full for one frame\n * before the animation takes over.\n *\n * Always give the reader a way out: an animation that cannot be skipped is a\n * tax on anyone re-reading or moving fast, which is what `skip` is for.\n *\n * How far the reveal got is derived rather than stored: a `speedMs` of `0` reads\n * as \"all of it\" without a state write, and `useInterval` pausing on a `null`\n * delay is what stops the timer at the end. Both used to need an effect that\n * cleared its own interval from inside a state updater.\n *\n * @param text - The full string to reveal. Nullish is treated as empty.\n * @param speedMs - Delay between characters. `0` or less renders instantly,\n *   which is the hook-safe way to honour `prefers-reduced-motion`.\n * @returns The revealed prefix, whether it finished, and a `skip` action.\n *\n * @example\n * const reduced = useMediaQuery(\"(prefers-reduced-motion: reduce)\");\n * const { displayedText, isComplete, skip } = useTypewriter(line, reduced ? 0 : 30);\n *\n * return <p onClick={skip}>{displayedText}{isComplete ? \"\" : \"▌\"}</p>;\n */\nexport function useTypewriter(text: string, speedMs: number): UseTypewriterResult {\n    const safeText = text ?? \"\";\n    const initialCount = speedMs > 0 ? 0 : safeText.length;\n\n    const [renderedText, setRenderedText] = useState(safeText);\n    const [count, setCount] = useState(initialCount);\n\n    if (renderedText !== safeText) {\n        setRenderedText(safeText);\n        setCount(initialCount);\n    }\n\n    const revealed = speedMs > 0 ? Math.min(count, safeText.length) : safeText.length;\n\n    useInterval(\n        () => setCount((current) => current + 1),\n        speedMs > 0 && revealed < safeText.length ? speedMs : null,\n    );\n\n    const skip = useCallback(() => setCount(safeText.length), [safeText.length]);\n\n    return {\n        displayedText: safeText.slice(0, revealed),\n        isComplete: revealed >= safeText.length,\n        skip,\n    };\n}\n"],"mappings":"6DAuCA,SAAgB,EAAc,EAAc,EAAsC,CAC9E,IAAM,EAAW,GAAQ,GACnB,EAAe,EAAU,EAAI,EAAI,EAAS,OAE1C,CAAC,EAAc,IAAA,EAAmB,EAAA,SAAA,CAAS,CAAQ,EACnD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAS,CAAY,EAE3C,IAAiB,IACjB,EAAgB,CAAQ,EACxB,EAAS,CAAY,GAGzB,IAAM,EAAW,EAAU,EAAI,KAAK,IAAI,EAAO,EAAS,MAAM,EAAI,EAAS,OAE3E,EAAA,gBACU,EAAU,GAAY,EAAU,CAAC,EACvC,EAAU,GAAK,EAAW,EAAS,OAAS,EAAU,IAC1D,EAEA,IAAM,GAAA,EAAO,EAAA,YAAA,KAAkB,EAAS,EAAS,MAAM,EAAG,CAAC,EAAS,MAAM,CAAC,EAE3E,MAAO,CACH,cAAe,EAAS,MAAM,EAAG,CAAQ,EACzC,WAAY,GAAY,EAAS,OACjC,MACJ,CACJ"}