'use client'; import { useRef, useState } from 'react'; const HOLD_DURATION_MS = 1000; const BOOST_DURATION_MS = 50; const mapProgress = (elapsedMs: number) => { if (elapsedMs <= 0) { return 0; } if (elapsedMs <= BOOST_DURATION_MS) { return 0.2 * (elapsedMs / BOOST_DURATION_MS); } const tailElapsedMs = Math.min(elapsedMs - BOOST_DURATION_MS, HOLD_DURATION_MS - BOOST_DURATION_MS); return 0.2 + 0.8 * (tailElapsedMs / (HOLD_DURATION_MS - BOOST_DURATION_MS)); }; export const useHoldToConfirm = (onConfirm: () => void) => { const frameRef = useRef(null); const startRef = useRef(null); const confirmedRef = useRef(false); const [progress, setProgress] = useState(0); const [isHolding, setHolding] = useState(false); const clearFrame = () => { if (frameRef.current !== null) { window.cancelAnimationFrame(frameRef.current); frameRef.current = null; } }; const reset = () => { clearFrame(); startRef.current = null; confirmedRef.current = false; setHolding(false); setProgress(0); }; const step = () => { if (startRef.current === null) { return; } const elapsedMs = performance.now() - startRef.current; const nextProgress = mapProgress(elapsedMs); const clampedProgress = Math.min(nextProgress, 1); setProgress(clampedProgress); if (clampedProgress >= 1 && !confirmedRef.current) { confirmedRef.current = true; onConfirm(); window.setTimeout(() => { reset(); }, 120); return; } frameRef.current = window.requestAnimationFrame(step); }; const start = () => { if (isHolding) { return; } clearFrame(); confirmedRef.current = false; startRef.current = performance.now(); setHolding(true); frameRef.current = window.requestAnimationFrame(step); }; const cancel = () => { if (!isHolding) { return; } reset(); }; return { progress, isHolding, start, cancel, }; };