"use client"; import { useReducedMotion } from "./Preferences.tsx"; import { createContext, useCallback, useContext, useEffect, useRef, useState, type ReactNode, } from "react"; import { AnimatePresence, motion } from "motion/react"; import { EASE_WIPE } from "./easings.ts"; const WIPE_MS = 1050; const MIDPOINT_MS = 500; type WipeContextValue = { wipe: (label?: string) => Promise; }; const PageWipeContext = createContext(null); export function usePageWipe(): WipeContextValue { const ctx = useContext(PageWipeContext); if (!ctx) throw new Error("usePageWipe must be used within "); return ctx; } export function PageWipeProvider({ children }: { children: ReactNode }) { const reduce = useReducedMotion(); const [active, setActive] = useState<{ label: string; id: number } | null>( null, ); const resolver = useRef<(() => void) | null>(null); const pending = useRef | null>(null); const timers = useRef([]); useEffect( () => () => { timers.current.forEach(window.clearTimeout); resolver.current?.(); }, [], ); const wipe = useCallback( (label = "") => { if (reduce) return Promise.resolve(); if (pending.current) return pending.current; pending.current = new Promise((resolve) => { resolver.current = () => { resolve(); resolver.current = null; }; setActive({ label, id: Date.now() }); timers.current.push( window.setTimeout(() => resolver.current?.(), MIDPOINT_MS), ); }); return pending.current; }, [reduce], ); const finish = useCallback(() => { timers.current.push( window.setTimeout(() => { setActive(null); pending.current = null; }, 60), ); }, []); return ( {children} {active && ( {active.label && ( {active.label} )} )} ); }