@usefy/use-timer 0.0.1 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useTimer.ts","../src/utils/timeUtils.ts","../src/utils/formatUtils.ts"],"sourcesContent":["// Main hook\nexport { useTimer, ms } from \"./useTimer\";\n\n// Types\nexport type {\n TimeFormat,\n TimeFormatter,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\n\n// Utilities (for advanced usage)\nexport { decompose, toMs, fromMs } from \"./utils/timeUtils\";\nexport type { DecomposedTime, TimeUnit } from \"./utils/timeUtils\";\nexport { formatTime } from \"./utils/formatUtils\";\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n TimeFormat,\n TimeFormatter,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\nimport { decompose } from \"./utils/timeUtils\";\nimport { formatTime } from \"./utils/formatUtils\";\n\n/**\n * Helper object to create milliseconds from various time units\n *\n * @example\n * ```tsx\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * // 5 minute timer\n * const timer = useTimer(ms.minutes(5));\n *\n * // 1 hour 30 minutes timer\n * const timer = useTimer(ms.hours(1) + ms.minutes(30));\n * ```\n */\nexport const ms = {\n /** Convert seconds to milliseconds */\n seconds: (n: number): number => n * 1000,\n /** Convert minutes to milliseconds */\n minutes: (n: number): number => n * 60 * 1000,\n /** Convert hours to milliseconds */\n hours: (n: number): number => n * 60 * 60 * 1000,\n} as const;\n\n/**\n * A powerful countdown timer hook with comprehensive controls and features.\n *\n * @param initialTimeMs - Initial time in milliseconds\n * @param options - Timer configuration options\n * @returns Timer state and control functions\n *\n * @example\n * ```tsx\n * // Basic usage\n * function Timer() {\n * const timer = useTimer(60000); // 60 seconds\n *\n * return (\n * <div>\n * <p>{timer.formattedTime}</p>\n * <button onClick={timer.toggle}>\n * {timer.isRunning ? \"Pause\" : \"Start\"}\n * </button>\n * <button onClick={timer.reset}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks and auto-start\n * const timer = useTimer(30000, {\n * autoStart: true,\n * onComplete: () => alert(\"Time's up!\"),\n * onTick: (remaining) => console.log(`${remaining}ms left`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With time helpers\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * const timer = useTimer(ms.minutes(5), {\n * format: \"MM:SS\",\n * loop: true,\n * });\n * ```\n */\nexport function useTimer(\n initialTimeMs: number,\n options: UseTimerOptions = {}\n): UseTimerReturn {\n // ============ Parse Options ============\n const {\n interval = 1,\n format = \"MM:SS\",\n autoStart = false,\n loop = false,\n useRAF = false,\n onComplete,\n onTick,\n onStart,\n onPause,\n onReset,\n onStop,\n } = options;\n\n // Ensure initialTime is non-negative\n const safeInitialTime = Math.max(0, initialTimeMs);\n\n // ============ Helper to compute formatted time ============\n const computeFormattedTime = useCallback(\n (timeMs: number): string => {\n if (typeof format === \"function\") {\n return format(timeMs);\n }\n return formatTime(timeMs, format as TimeFormat);\n },\n [format]\n );\n\n // ============ State ============\n // Only formattedTime triggers re-renders (optimization)\n const [formattedTime, setFormattedTime] = useState(() =>\n computeFormattedTime(safeInitialTime)\n );\n const [status, setStatus] = useState<TimerStatus>(() =>\n safeInitialTime === 0 ? \"finished\" : \"idle\"\n );\n\n // ============ Refs ============\n // Time is stored in ref for performance - only formattedTime changes trigger re-renders\n const timeRef = useRef(safeInitialTime);\n const prevFormattedTimeRef = useRef<string>(\n computeFormattedTime(safeInitialTime)\n );\n\n // Timer ID for cleanup\n const timerIdRef = useRef<number | null>(null);\n const rafIdRef = useRef<number | null>(null);\n\n // For accurate time tracking\n const lastTickTimeRef = useRef<number>(0);\n const remainingTimeRef = useRef(safeInitialTime);\n\n // Track initial time for reset\n const initialTimeRef = useRef(safeInitialTime);\n\n // Store if autoStart has been triggered\n const autoStartTriggeredRef = useRef(false);\n\n // Track previous initialTimeMs to detect actual prop changes\n const prevInitialTimeMsRef = useRef(safeInitialTime);\n\n // Callback refs for latest values without causing re-renders\n const onCompleteRef = useRef(onComplete);\n const onTickRef = useRef(onTick);\n const onStartRef = useRef(onStart);\n const onPauseRef = useRef(onPause);\n const onResetRef = useRef(onReset);\n const onStopRef = useRef(onStop);\n\n // Options refs\n const loopRef = useRef(loop);\n const intervalRef = useRef(interval);\n const useRAFRef = useRef(useRAF);\n const formatRef = useRef(format);\n\n // ============ Update Refs ============\n useEffect(() => {\n onCompleteRef.current = onComplete;\n onTickRef.current = onTick;\n onStartRef.current = onStart;\n onPauseRef.current = onPause;\n onResetRef.current = onReset;\n onStopRef.current = onStop;\n loopRef.current = loop;\n intervalRef.current = interval;\n useRAFRef.current = useRAF;\n formatRef.current = format;\n });\n\n // Re-compute formattedTime when format option changes\n useEffect(() => {\n const newFormatted = computeFormattedTime(timeRef.current);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n }, [format, computeFormattedTime]);\n\n // ============ Helper to update time (only re-renders if formatted value changes) ============\n const updateTime = useCallback(\n (newTimeMs: number) => {\n timeRef.current = newTimeMs;\n const newFormatted = computeFormattedTime(newTimeMs);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n },\n [computeFormattedTime]\n );\n\n // ============ Derived State ============\n const isRunning = status === \"running\";\n const isPaused = status === \"paused\";\n const isFinished = status === \"finished\";\n const isIdle = status === \"idle\";\n\n // Current time value (read from ref)\n const time = timeRef.current;\n\n // Progress: 0 at start, 100 at completion\n const progress = useMemo(() => {\n if (initialTimeRef.current === 0) return 100;\n return Math.min(\n 100,\n Math.max(\n 0,\n ((initialTimeRef.current - time) / initialTimeRef.current) * 100\n )\n );\n // formattedTime is the trigger for re-render, time is derived from ref\n }, [formattedTime]);\n\n // Decomposed time (recalculated on re-render)\n const decomposedTime = useMemo(() => decompose(time), [formattedTime]);\n const { hours, minutes, seconds, milliseconds } = decomposedTime;\n\n // ============ Clear Timer ============\n const clearTimer = useCallback(() => {\n if (timerIdRef.current !== null) {\n clearInterval(timerIdRef.current);\n timerIdRef.current = null;\n }\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // ============ Handle Complete ============\n const handleComplete = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = 0;\n updateTime(0);\n\n if (loopRef.current) {\n // Loop: reset and restart\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n onCompleteRef.current?.();\n // Status stays running, will schedule next tick in effect\n } else {\n setStatus(\"finished\");\n onCompleteRef.current?.();\n }\n }, [clearTimer, updateTime]);\n\n // ============ Tick Function ============\n const tick = useCallback(() => {\n const now = performance.now();\n const elapsed = now - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = now;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n }, [handleComplete, updateTime]);\n\n // ============ RAF Tick ============\n const rafTick = useCallback(\n (timestamp: number) => {\n if (lastTickTimeRef.current === 0) {\n lastTickTimeRef.current = timestamp;\n }\n\n const elapsed = timestamp - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = timestamp;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n } else {\n rafIdRef.current = requestAnimationFrame(rafTick);\n }\n },\n [handleComplete, updateTime]\n );\n\n // ============ Start Timer Loop ============\n const startTimerLoop = useCallback(() => {\n clearTimer();\n lastTickTimeRef.current = performance.now();\n\n if (useRAFRef.current) {\n rafIdRef.current = requestAnimationFrame(rafTick);\n } else {\n timerIdRef.current = window.setInterval(tick, intervalRef.current);\n }\n }, [clearTimer, tick, rafTick]);\n\n // ============ Control Functions ============\n const start = useCallback(() => {\n // Don't start if already running or finished\n if (status === \"running\" || status === \"finished\") {\n return;\n }\n\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }, [status, startTimerLoop]);\n\n const pause = useCallback(() => {\n // Only pause if running\n if (status !== \"running\") {\n return;\n }\n\n clearTimer();\n setStatus(\"paused\");\n onPauseRef.current?.();\n }, [status, clearTimer]);\n\n const stop = useCallback(() => {\n clearTimer();\n setStatus(\"idle\");\n onStopRef.current?.();\n }, [clearTimer]);\n\n const reset = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n setStatus(initialTimeRef.current === 0 ? \"finished\" : \"idle\");\n onResetRef.current?.();\n }, [clearTimer, updateTime]);\n\n const restart = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n\n if (initialTimeRef.current === 0) {\n setStatus(\"finished\");\n return;\n }\n\n setStatus(\"running\");\n onResetRef.current?.();\n onStartRef.current?.();\n startTimerLoop();\n }, [clearTimer, updateTime, startTimerLoop]);\n\n const toggle = useCallback(() => {\n if (isRunning) {\n pause();\n } else if (isPaused || isIdle) {\n start();\n }\n }, [isRunning, isPaused, isIdle, start, pause]);\n\n // ============ Time Manipulation ============\n const addTime = useCallback(\n (addMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current + addMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If timer was finished and we add time, it's no longer finished\n if (status === \"finished\" && newTimeMs > 0) {\n setStatus(\"idle\");\n }\n },\n [status, updateTime]\n );\n\n const subtractTime = useCallback(\n (subtractMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current - subtractMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If time reaches 0, mark as finished\n if (newTimeMs <= 0 && status === \"running\") {\n handleComplete();\n }\n },\n [status, handleComplete, updateTime]\n );\n\n const setTimeValue = useCallback(\n (newTimeMs: number) => {\n const safeTime = Math.max(0, newTimeMs);\n remainingTimeRef.current = safeTime;\n updateTime(safeTime);\n\n // Update finished state based on new time\n if (safeTime === 0 && status === \"running\") {\n handleComplete();\n } else if (safeTime > 0 && status === \"finished\") {\n setStatus(\"idle\");\n }\n },\n [status, handleComplete, updateTime]\n );\n\n // ============ Effects ============\n // Handle autoStart\n useEffect(() => {\n if (autoStart && !autoStartTriggeredRef.current && safeInitialTime > 0) {\n autoStartTriggeredRef.current = true;\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }\n }, [autoStart, safeInitialTime, startTimerLoop]);\n\n // Handle initialTimeMs changes when not running\n // Only react to actual prop changes, not status changes\n useEffect(() => {\n const newSafeTime = Math.max(0, initialTimeMs);\n\n // Only update if initialTimeMs actually changed\n if (prevInitialTimeMsRef.current !== newSafeTime) {\n prevInitialTimeMsRef.current = newSafeTime;\n\n // Only sync time when not running or paused\n if (status !== \"running\" && status !== \"paused\") {\n initialTimeRef.current = newSafeTime;\n remainingTimeRef.current = newSafeTime;\n updateTime(newSafeTime);\n setStatus(newSafeTime === 0 ? \"finished\" : \"idle\");\n }\n }\n }, [initialTimeMs, status, updateTime]);\n\n // Handle loop restart when running\n useEffect(() => {\n if (\n status === \"running\" &&\n loop &&\n timerIdRef.current === null &&\n rafIdRef.current === null\n ) {\n // Timer was cleared for loop completion, restart it\n startTimerLoop();\n }\n }, [status, loop, time, startTimerLoop]);\n\n // Visibility API: compensate for time drift when tab is inactive\n useEffect(() => {\n let hiddenTime = 0;\n\n const handleVisibilityChange = () => {\n if (status !== \"running\") return;\n\n if (document.hidden) {\n // Tab became hidden, record the time\n hiddenTime = performance.now();\n } else if (hiddenTime > 0) {\n // Tab became visible, calculate elapsed time\n const elapsed = performance.now() - hiddenTime;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n remainingTimeRef.current = newRemaining;\n lastTickTimeRef.current = performance.now();\n\n // Force update formatted time\n const newFormatted = formatRef.current\n ? typeof formatRef.current === \"function\"\n ? formatRef.current(newRemaining)\n : formatTime(newRemaining, formatRef.current as TimeFormat)\n : formatTime(newRemaining, \"MM:SS\");\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n hiddenTime = 0;\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [status, handleComplete]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, [clearTimer]);\n\n // ============ Return ============\n return {\n // State\n time,\n initialTime: initialTimeRef.current,\n formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\n\n // Decomposed time\n hours,\n minutes,\n seconds,\n milliseconds,\n\n // Controls\n start,\n pause,\n stop,\n reset,\n restart,\n toggle,\n\n // Time manipulation\n addTime,\n subtractTime,\n setTime: setTimeValue,\n };\n}\n","/**\n * Supported time units for utility functions\n */\nexport type TimeUnit = \"ms\" | \"seconds\" | \"minutes\" | \"hours\";\n\n/**\n * Millisecond multipliers for each time unit\n */\nconst MS_MULTIPLIERS: Record<TimeUnit, number> = {\n ms: 1,\n seconds: 1000,\n minutes: 60 * 1000,\n hours: 60 * 60 * 1000,\n};\n\n/**\n * Convert a time value to milliseconds\n * @param time - The time value to convert\n * @param unit - The unit of the time value\n * @returns Time in milliseconds (minimum 0)\n */\nexport function toMs(time: number, unit: TimeUnit): number {\n return Math.max(0, Math.floor(time * MS_MULTIPLIERS[unit]));\n}\n\n/**\n * Convert milliseconds to a specific time unit\n * @param ms - Time in milliseconds\n * @param unit - Target time unit\n * @returns Time in the specified unit\n */\nexport function fromMs(ms: number, unit: TimeUnit): number {\n return ms / MS_MULTIPLIERS[unit];\n}\n\n/**\n * Decomposed time object\n */\nexport interface DecomposedTime {\n hours: number;\n minutes: number;\n seconds: number;\n milliseconds: number;\n}\n\n/**\n * Decompose milliseconds into hours, minutes, seconds, and milliseconds\n * @param ms - Time in milliseconds\n * @returns Decomposed time object\n */\nexport function decompose(ms: number): DecomposedTime {\n const safeMs = Math.max(0, ms);\n\n // Use floor for consistent calculation across all components\n const totalSeconds = Math.floor(safeMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n // Milliseconds are the fractional part\n const milliseconds = Math.floor(safeMs % 1000);\n\n return { hours, minutes, seconds, milliseconds };\n}\n","import type { TimeFormat } from \"../types\";\nimport { decompose } from \"./timeUtils\";\n\n/**\n * Pad a number with leading zeros\n * @param num - Number to pad\n * @param length - Desired string length\n * @returns Padded string\n */\nfunction padNumber(num: number, length: number): string {\n return num.toString().padStart(length, \"0\");\n}\n\n/**\n * Format milliseconds into a time string\n * @param ms - Time in milliseconds\n * @param format - Desired format\n * @returns Formatted time string\n */\nexport function formatTime(ms: number, format: TimeFormat): string {\n const { hours, minutes, seconds, milliseconds } = decompose(Math.max(0, ms));\n\n switch (format) {\n case \"HH:MM:SS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`;\n\n case \"MM:SS\": {\n const totalMinutes = hours * 60 + minutes;\n return `${padNumber(totalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n\n case \"SS\": {\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds.toString();\n }\n\n case \"mm:ss.SSS\": {\n const totalMins = hours * 60 + minutes;\n return `${padNumber(totalMins, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n }\n\n case \"HH:MM:SS.SSS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n\n default:\n // Default to MM:SS format\n const defaultTotalMinutes = hours * 60 + minutes;\n return `${padNumber(defaultTotalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n}\n"],"mappings":"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","names":["ms","ms"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useTimer.ts","../src/utils/timeUtils.ts","../src/utils/formatUtils.ts"],"sourcesContent":["// Main hook\nexport { useTimer, ms } from \"./useTimer\";\n\n// Types\nexport type {\n TimeFormat,\n TimeFormatter,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\n\n// Utilities (for advanced usage)\nexport { decompose, toMs, fromMs } from \"./utils/timeUtils\";\nexport type { DecomposedTime, TimeUnit } from \"./utils/timeUtils\";\nexport { formatTime } from \"./utils/formatUtils\";\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n TimeFormat,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\nimport { decompose } from \"./utils/timeUtils\";\nimport { formatTime } from \"./utils/formatUtils\";\n\n/**\n * Helper object to create milliseconds from various time units\n *\n * @example\n * ```tsx\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * // 5 minute timer\n * const timer = useTimer(ms.minutes(5));\n *\n * // 1 hour 30 minutes timer\n * const timer = useTimer(ms.hours(1) + ms.minutes(30));\n * ```\n */\nexport const ms = {\n /** Convert seconds to milliseconds */\n seconds: (n: number): number => n * 1000,\n /** Convert minutes to milliseconds */\n minutes: (n: number): number => n * 60 * 1000,\n /** Convert hours to milliseconds */\n hours: (n: number): number => n * 60 * 60 * 1000,\n} as const;\n\n/**\n * A powerful countdown timer hook with comprehensive controls and features.\n *\n * @param initialTimeMs - Initial time in milliseconds\n * @param options - Timer configuration options\n * @returns Timer state and control functions\n *\n * @example\n * ```tsx\n * // Basic usage\n * function Timer() {\n * const timer = useTimer(60000); // 60 seconds\n *\n * return (\n * <div>\n * <p>{timer.formattedTime}</p>\n * <button onClick={timer.toggle}>\n * {timer.isRunning ? \"Pause\" : \"Start\"}\n * </button>\n * <button onClick={timer.reset}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks and auto-start\n * const timer = useTimer(30000, {\n * autoStart: true,\n * onComplete: () => alert(\"Time's up!\"),\n * onTick: (remaining) => console.log(`${remaining}ms left`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With time helpers\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * const timer = useTimer(ms.minutes(5), {\n * format: \"MM:SS\",\n * loop: true,\n * });\n * ```\n */\nexport function useTimer(\n initialTimeMs: number,\n options: UseTimerOptions = {}\n): UseTimerReturn {\n // ============ Parse Options ============\n const {\n interval = 1,\n format = \"MM:SS\",\n autoStart = false,\n loop = false,\n useRAF = false,\n onComplete,\n onTick,\n onStart,\n onPause,\n onReset,\n onStop,\n } = options;\n\n // Ensure initialTime is non-negative\n const safeInitialTime = Math.max(0, initialTimeMs);\n\n // ============ Helper to compute formatted time ============\n const computeFormattedTime = useCallback(\n (timeMs: number): string => {\n if (typeof format === \"function\") {\n return format(timeMs);\n }\n return formatTime(timeMs, format as TimeFormat);\n },\n [format]\n );\n\n // ============ State ============\n // Only formattedTime triggers re-renders (optimization)\n const [formattedTime, setFormattedTime] = useState(() =>\n computeFormattedTime(safeInitialTime)\n );\n const [status, setStatus] = useState<TimerStatus>(() =>\n safeInitialTime === 0 ? \"finished\" : \"idle\"\n );\n\n // ============ Refs ============\n // Time is stored in ref for performance - only formattedTime changes trigger re-renders\n const timeRef = useRef(safeInitialTime);\n const prevFormattedTimeRef = useRef<string>(\n computeFormattedTime(safeInitialTime)\n );\n\n // Timer ID for cleanup\n const timerIdRef = useRef<number | null>(null);\n const rafIdRef = useRef<number | null>(null);\n\n // For accurate time tracking\n const lastTickTimeRef = useRef<number>(0);\n const remainingTimeRef = useRef(safeInitialTime);\n\n // Track initial time for reset\n const initialTimeRef = useRef(safeInitialTime);\n\n // Store if autoStart has been triggered\n const autoStartTriggeredRef = useRef(false);\n\n // Track previous initialTimeMs to detect actual prop changes\n const prevInitialTimeMsRef = useRef(safeInitialTime);\n\n // Callback refs for latest values without causing re-renders\n const onCompleteRef = useRef(onComplete);\n const onTickRef = useRef(onTick);\n const onStartRef = useRef(onStart);\n const onPauseRef = useRef(onPause);\n const onResetRef = useRef(onReset);\n const onStopRef = useRef(onStop);\n\n // Options refs\n const loopRef = useRef(loop);\n const intervalRef = useRef(interval);\n const useRAFRef = useRef(useRAF);\n const formatRef = useRef(format);\n\n // ============ Update Refs ============\n useEffect(() => {\n onCompleteRef.current = onComplete;\n onTickRef.current = onTick;\n onStartRef.current = onStart;\n onPauseRef.current = onPause;\n onResetRef.current = onReset;\n onStopRef.current = onStop;\n loopRef.current = loop;\n intervalRef.current = interval;\n useRAFRef.current = useRAF;\n formatRef.current = format;\n });\n\n // Re-compute formattedTime when format option changes\n useEffect(() => {\n const newFormatted = computeFormattedTime(timeRef.current);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n }, [format, computeFormattedTime]);\n\n // ============ Helper to update time (only re-renders if formatted value changes) ============\n const updateTime = useCallback(\n (newTimeMs: number) => {\n timeRef.current = newTimeMs;\n const newFormatted = computeFormattedTime(newTimeMs);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n },\n [computeFormattedTime]\n );\n\n // ============ Derived State ============\n const isRunning = status === \"running\";\n const isPaused = status === \"paused\";\n const isFinished = status === \"finished\";\n const isIdle = status === \"idle\";\n\n // Current time value (read from ref)\n const time = timeRef.current;\n\n // Progress: 0 at start, 100 at completion\n const progress = useMemo(() => {\n if (initialTimeRef.current === 0) return 100;\n return Math.min(\n 100,\n Math.max(\n 0,\n ((initialTimeRef.current - time) / initialTimeRef.current) * 100\n )\n );\n // formattedTime is the trigger for re-render, time is derived from ref\n }, [formattedTime]);\n\n // Decomposed time (recalculated on re-render)\n const decomposedTime = useMemo(() => decompose(time), [formattedTime]);\n const { hours, minutes, seconds, milliseconds } = decomposedTime;\n\n // ============ Clear Timer ============\n const clearTimer = useCallback(() => {\n if (timerIdRef.current !== null) {\n clearInterval(timerIdRef.current);\n timerIdRef.current = null;\n }\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // ============ Handle Complete ============\n const handleComplete = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = 0;\n updateTime(0);\n\n if (loopRef.current) {\n // Loop: reset and restart\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n onCompleteRef.current?.();\n // Status stays running, will schedule next tick in effect\n } else {\n setStatus(\"finished\");\n onCompleteRef.current?.();\n }\n }, [clearTimer, updateTime]);\n\n // ============ Tick Function ============\n const tick = useCallback(() => {\n const now = performance.now();\n const elapsed = now - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = now;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n }, [handleComplete, updateTime]);\n\n // ============ RAF Tick ============\n const rafTick = useCallback(\n (timestamp: number) => {\n if (lastTickTimeRef.current === 0) {\n lastTickTimeRef.current = timestamp;\n }\n\n const elapsed = timestamp - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = timestamp;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n } else {\n rafIdRef.current = requestAnimationFrame(rafTick);\n }\n },\n [handleComplete, updateTime]\n );\n\n // ============ Start Timer Loop ============\n const startTimerLoop = useCallback(() => {\n clearTimer();\n lastTickTimeRef.current = performance.now();\n\n if (useRAFRef.current) {\n rafIdRef.current = requestAnimationFrame(rafTick);\n } else {\n timerIdRef.current = window.setInterval(tick, intervalRef.current);\n }\n }, [clearTimer, tick, rafTick]);\n\n // ============ Control Functions ============\n const start = useCallback(() => {\n // Don't start if already running or finished\n if (status === \"running\" || status === \"finished\") {\n return;\n }\n\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }, [status, startTimerLoop]);\n\n const pause = useCallback(() => {\n // Only pause if running\n if (status !== \"running\") {\n return;\n }\n\n clearTimer();\n setStatus(\"paused\");\n onPauseRef.current?.();\n }, [status, clearTimer]);\n\n const stop = useCallback(() => {\n clearTimer();\n setStatus(\"idle\");\n onStopRef.current?.();\n }, [clearTimer]);\n\n const reset = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n setStatus(initialTimeRef.current === 0 ? \"finished\" : \"idle\");\n onResetRef.current?.();\n }, [clearTimer, updateTime]);\n\n const restart = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n\n if (initialTimeRef.current === 0) {\n setStatus(\"finished\");\n return;\n }\n\n setStatus(\"running\");\n onResetRef.current?.();\n onStartRef.current?.();\n startTimerLoop();\n }, [clearTimer, updateTime, startTimerLoop]);\n\n const toggle = useCallback(() => {\n if (isRunning) {\n pause();\n } else if (isPaused || isIdle) {\n start();\n }\n }, [isRunning, isPaused, isIdle, start, pause]);\n\n // ============ Time Manipulation ============\n const addTime = useCallback(\n (addMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current + addMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If timer was finished and we add time, it's no longer finished\n if (status === \"finished\" && newTimeMs > 0) {\n setStatus(\"idle\");\n }\n },\n [status, updateTime]\n );\n\n const subtractTime = useCallback(\n (subtractMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current - subtractMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If time reaches 0, mark as finished\n if (newTimeMs <= 0 && status === \"running\") {\n handleComplete();\n }\n },\n [status, handleComplete, updateTime]\n );\n\n const setTimeValue = useCallback(\n (newTimeMs: number) => {\n const safeTime = Math.max(0, newTimeMs);\n remainingTimeRef.current = safeTime;\n updateTime(safeTime);\n\n // Update finished state based on new time\n if (safeTime === 0 && status === \"running\") {\n handleComplete();\n } else if (safeTime > 0 && status === \"finished\") {\n setStatus(\"idle\");\n }\n },\n [status, handleComplete, updateTime]\n );\n\n // ============ Effects ============\n // Handle autoStart\n useEffect(() => {\n if (autoStart && !autoStartTriggeredRef.current && safeInitialTime > 0) {\n autoStartTriggeredRef.current = true;\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }\n }, [autoStart, safeInitialTime, startTimerLoop]);\n\n // Handle initialTimeMs changes when not running\n // Only react to actual prop changes, not status changes\n useEffect(() => {\n const newSafeTime = Math.max(0, initialTimeMs);\n\n // Only update if initialTimeMs actually changed\n if (prevInitialTimeMsRef.current !== newSafeTime) {\n prevInitialTimeMsRef.current = newSafeTime;\n\n // Only sync time when not running or paused\n if (status !== \"running\" && status !== \"paused\") {\n initialTimeRef.current = newSafeTime;\n remainingTimeRef.current = newSafeTime;\n updateTime(newSafeTime);\n setStatus(newSafeTime === 0 ? \"finished\" : \"idle\");\n }\n }\n }, [initialTimeMs, status, updateTime]);\n\n // Handle loop restart when running\n useEffect(() => {\n if (\n status === \"running\" &&\n loop &&\n timerIdRef.current === null &&\n rafIdRef.current === null\n ) {\n // Timer was cleared for loop completion, restart it\n startTimerLoop();\n }\n }, [status, loop, time, startTimerLoop]);\n\n // Visibility API: compensate for time drift when tab is inactive\n useEffect(() => {\n let hiddenTime = 0;\n\n const handleVisibilityChange = () => {\n if (status !== \"running\") return;\n\n if (document.hidden) {\n // Tab became hidden, record the time\n hiddenTime = performance.now();\n } else if (hiddenTime > 0) {\n // Tab became visible, calculate elapsed time\n const elapsed = performance.now() - hiddenTime;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n remainingTimeRef.current = newRemaining;\n lastTickTimeRef.current = performance.now();\n\n // Force update formatted time\n const newFormatted = formatRef.current\n ? typeof formatRef.current === \"function\"\n ? formatRef.current(newRemaining)\n : formatTime(newRemaining, formatRef.current as TimeFormat)\n : formatTime(newRemaining, \"MM:SS\");\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n hiddenTime = 0;\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [status, handleComplete]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, [clearTimer]);\n\n // ============ Return ============\n return {\n // State\n time,\n initialTime: initialTimeRef.current,\n formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\n\n // Decomposed time\n hours,\n minutes,\n seconds,\n milliseconds,\n\n // Controls\n start,\n pause,\n stop,\n reset,\n restart,\n toggle,\n\n // Time manipulation\n addTime,\n subtractTime,\n setTime: setTimeValue,\n };\n}\n","/**\n * Supported time units for utility functions\n */\nexport type TimeUnit = \"ms\" | \"seconds\" | \"minutes\" | \"hours\";\n\n/**\n * Millisecond multipliers for each time unit\n */\nconst MS_MULTIPLIERS: Record<TimeUnit, number> = {\n ms: 1,\n seconds: 1000,\n minutes: 60 * 1000,\n hours: 60 * 60 * 1000,\n};\n\n/**\n * Convert a time value to milliseconds\n * @param time - The time value to convert\n * @param unit - The unit of the time value\n * @returns Time in milliseconds (minimum 0)\n */\nexport function toMs(time: number, unit: TimeUnit): number {\n return Math.max(0, Math.floor(time * MS_MULTIPLIERS[unit]));\n}\n\n/**\n * Convert milliseconds to a specific time unit\n * @param ms - Time in milliseconds\n * @param unit - Target time unit\n * @returns Time in the specified unit\n */\nexport function fromMs(ms: number, unit: TimeUnit): number {\n return ms / MS_MULTIPLIERS[unit];\n}\n\n/**\n * Decomposed time object\n */\nexport interface DecomposedTime {\n hours: number;\n minutes: number;\n seconds: number;\n milliseconds: number;\n}\n\n/**\n * Decompose milliseconds into hours, minutes, seconds, and milliseconds\n * @param ms - Time in milliseconds\n * @returns Decomposed time object\n */\nexport function decompose(ms: number): DecomposedTime {\n const safeMs = Math.max(0, ms);\n\n // Use floor for consistent calculation across all components\n const totalSeconds = Math.floor(safeMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n // Milliseconds are the fractional part\n const milliseconds = Math.floor(safeMs % 1000);\n\n return { hours, minutes, seconds, milliseconds };\n}\n","import type { TimeFormat } from \"../types\";\nimport { decompose } from \"./timeUtils\";\n\n/**\n * Pad a number with leading zeros\n * @param num - Number to pad\n * @param length - Desired string length\n * @returns Padded string\n */\nfunction padNumber(num: number, length: number): string {\n return num.toString().padStart(length, \"0\");\n}\n\n/**\n * Format milliseconds into a time string\n * @param ms - Time in milliseconds\n * @param format - Desired format\n * @returns Formatted time string\n */\nexport function formatTime(ms: number, format: TimeFormat): string {\n const { hours, minutes, seconds, milliseconds } = decompose(Math.max(0, ms));\n\n switch (format) {\n case \"HH:MM:SS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`;\n\n case \"MM:SS\": {\n const totalMinutes = hours * 60 + minutes;\n return `${padNumber(totalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n\n case \"SS\": {\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds.toString();\n }\n\n case \"mm:ss.SSS\": {\n const totalMins = hours * 60 + minutes;\n return `${padNumber(totalMins, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n }\n\n case \"HH:MM:SS.SSS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n\n default:\n // Default to MM:SS format\n const defaultTotalMinutes = hours * 60 + minutes;\n return `${padNumber(defaultTotalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAkE;;;ACQlE,IAAM,iBAA2C;AAAA,EAC/C,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,SAAS,KAAK;AAAA,EACd,OAAO,KAAK,KAAK;AACnB;AAQO,SAAS,KAAK,MAAc,MAAwB;AACzD,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,eAAe,IAAI,CAAC,CAAC;AAC5D;AAQO,SAAS,OAAOA,KAAY,MAAwB;AACzD,SAAOA,MAAK,eAAe,IAAI;AACjC;AAiBO,SAAS,UAAUA,KAA4B;AACpD,QAAM,SAAS,KAAK,IAAI,GAAGA,GAAE;AAG7B,QAAM,eAAe,KAAK,MAAM,SAAS,GAAI;AAC7C,QAAM,QAAQ,KAAK,MAAM,eAAe,IAAI;AAC5C,QAAM,UAAU,KAAK,MAAO,eAAe,OAAQ,EAAE;AACrD,QAAM,UAAU,eAAe;AAE/B,QAAM,eAAe,KAAK,MAAM,SAAS,GAAI;AAE7C,SAAO,EAAE,OAAO,SAAS,SAAS,aAAa;AACjD;;;ACrDA,SAAS,UAAU,KAAa,QAAwB;AACtD,SAAO,IAAI,SAAS,EAAE,SAAS,QAAQ,GAAG;AAC5C;AAQO,SAAS,WAAWC,KAAY,QAA4B;AACjE,QAAM,EAAE,OAAO,SAAS,SAAS,aAAa,IAAI,UAAU,KAAK,IAAI,GAAGA,GAAE,CAAC;AAE3E,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,GAAG,UAAU,OAAO,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC;AAAA,IAEjF,KAAK,SAAS;AACZ,YAAM,eAAe,QAAQ,KAAK;AAClC,aAAO,GAAG,UAAU,cAAc,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC;AAAA,IAC/D;AAAA,IAEA,KAAK,MAAM;AACT,YAAM,eAAe,QAAQ,OAAO,UAAU,KAAK;AACnD,aAAO,aAAa,SAAS;AAAA,IAC/B;AAAA,IAEA,KAAK,aAAa;AAChB,YAAM,YAAY,QAAQ,KAAK;AAC/B,aAAO,GAAG,UAAU,WAAW,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC,IAAI,UAAU,cAAc,CAAC,CAAC;AAAA,IAC1F;AAAA,IAEA,KAAK;AACH,aAAO,GAAG,UAAU,OAAO,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC,IAAI,UAAU,cAAc,CAAC,CAAC;AAAA,IAE/G;AAEE,YAAM,sBAAsB,QAAQ,KAAK;AACzC,aAAO,GAAG,UAAU,qBAAqB,CAAC,CAAC,IAAI,UAAU,SAAS,CAAC,CAAC;AAAA,EACxE;AACF;;;AFzBO,IAAM,KAAK;AAAA;AAAA,EAEhB,SAAS,CAAC,MAAsB,IAAI;AAAA;AAAA,EAEpC,SAAS,CAAC,MAAsB,IAAI,KAAK;AAAA;AAAA,EAEzC,OAAO,CAAC,MAAsB,IAAI,KAAK,KAAK;AAC9C;AAgDO,SAAS,SACd,eACA,UAA2B,CAAC,GACZ;AAEhB,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,kBAAkB,KAAK,IAAI,GAAG,aAAa;AAGjD,QAAM,2BAAuB;AAAA,IAC3B,CAAC,WAA2B;AAC1B,UAAI,OAAO,WAAW,YAAY;AAChC,eAAO,OAAO,MAAM;AAAA,MACtB;AACA,aAAO,WAAW,QAAQ,MAAoB;AAAA,IAChD;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAIA,QAAM,CAAC,eAAe,gBAAgB,QAAI;AAAA,IAAS,MACjD,qBAAqB,eAAe;AAAA,EACtC;AACA,QAAM,CAAC,QAAQ,SAAS,QAAI;AAAA,IAAsB,MAChD,oBAAoB,IAAI,aAAa;AAAA,EACvC;AAIA,QAAM,cAAU,qBAAO,eAAe;AACtC,QAAM,2BAAuB;AAAA,IAC3B,qBAAqB,eAAe;AAAA,EACtC;AAGA,QAAM,iBAAa,qBAAsB,IAAI;AAC7C,QAAM,eAAW,qBAAsB,IAAI;AAG3C,QAAM,sBAAkB,qBAAe,CAAC;AACxC,QAAM,uBAAmB,qBAAO,eAAe;AAG/C,QAAM,qBAAiB,qBAAO,eAAe;AAG7C,QAAM,4BAAwB,qBAAO,KAAK;AAG1C,QAAM,2BAAuB,qBAAO,eAAe;AAGnD,QAAM,oBAAgB,qBAAO,UAAU;AACvC,QAAM,gBAAY,qBAAO,MAAM;AAC/B,QAAM,iBAAa,qBAAO,OAAO;AACjC,QAAM,iBAAa,qBAAO,OAAO;AACjC,QAAM,iBAAa,qBAAO,OAAO;AACjC,QAAM,gBAAY,qBAAO,MAAM;AAG/B,QAAM,cAAU,qBAAO,IAAI;AAC3B,QAAM,kBAAc,qBAAO,QAAQ;AACnC,QAAM,gBAAY,qBAAO,MAAM;AAC/B,QAAM,gBAAY,qBAAO,MAAM;AAG/B,8BAAU,MAAM;AACd,kBAAc,UAAU;AACxB,cAAU,UAAU;AACpB,eAAW,UAAU;AACrB,eAAW,UAAU;AACrB,eAAW,UAAU;AACrB,cAAU,UAAU;AACpB,YAAQ,UAAU;AAClB,gBAAY,UAAU;AACtB,cAAU,UAAU;AACpB,cAAU,UAAU;AAAA,EACtB,CAAC;AAGD,8BAAU,MAAM;AACd,UAAM,eAAe,qBAAqB,QAAQ,OAAO;AACzD,QAAI,iBAAiB,qBAAqB,SAAS;AACjD,2BAAqB,UAAU;AAC/B,uBAAiB,YAAY;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,QAAQ,oBAAoB,CAAC;AAGjC,QAAM,iBAAa;AAAA,IACjB,CAAC,cAAsB;AACrB,cAAQ,UAAU;AAClB,YAAM,eAAe,qBAAqB,SAAS;AACnD,UAAI,iBAAiB,qBAAqB,SAAS;AACjD,6BAAqB,UAAU;AAC/B,yBAAiB,YAAY;AAAA,MAC/B;AAAA,IACF;AAAA,IACA,CAAC,oBAAoB;AAAA,EACvB;AAGA,QAAM,YAAY,WAAW;AAC7B,QAAM,WAAW,WAAW;AAC5B,QAAM,aAAa,WAAW;AAC9B,QAAM,SAAS,WAAW;AAG1B,QAAM,OAAO,QAAQ;AAGrB,QAAM,eAAW,sBAAQ,MAAM;AAC7B,QAAI,eAAe,YAAY,EAAG,QAAO;AACzC,WAAO,KAAK;AAAA,MACV;AAAA,MACA,KAAK;AAAA,QACH;AAAA,SACE,eAAe,UAAU,QAAQ,eAAe,UAAW;AAAA,MAC/D;AAAA,IACF;AAAA,EAEF,GAAG,CAAC,aAAa,CAAC;AAGlB,QAAM,qBAAiB,sBAAQ,MAAM,UAAU,IAAI,GAAG,CAAC,aAAa,CAAC;AACrE,QAAM,EAAE,OAAO,SAAS,SAAS,aAAa,IAAI;AAGlD,QAAM,iBAAa,0BAAY,MAAM;AACnC,QAAI,WAAW,YAAY,MAAM;AAC/B,oBAAc,WAAW,OAAO;AAChC,iBAAW,UAAU;AAAA,IACvB;AACA,QAAI,SAAS,YAAY,MAAM;AAC7B,2BAAqB,SAAS,OAAO;AACrC,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,qBAAiB,0BAAY,MAAM;AACvC,eAAW;AACX,qBAAiB,UAAU;AAC3B,eAAW,CAAC;AAEZ,QAAI,QAAQ,SAAS;AAEnB,uBAAiB,UAAU,eAAe;AAC1C,iBAAW,eAAe,OAAO;AACjC,oBAAc,UAAU;AAAA,IAE1B,OAAO;AACL,gBAAU,UAAU;AACpB,oBAAc,UAAU;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,YAAY,UAAU,CAAC;AAG3B,QAAM,WAAO,0BAAY,MAAM;AAC7B,UAAM,MAAM,YAAY,IAAI;AAC5B,UAAM,UAAU,MAAM,gBAAgB;AACtC,UAAM,eAAe,KAAK,IAAI,GAAG,iBAAiB,UAAU,OAAO;AAEnE,oBAAgB,UAAU;AAC1B,qBAAiB,UAAU;AAC3B,eAAW,YAAY;AACvB,cAAU,UAAU,YAAY;AAEhC,QAAI,gBAAgB,GAAG;AACrB,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,gBAAgB,UAAU,CAAC;AAG/B,QAAM,cAAU;AAAA,IACd,CAAC,cAAsB;AACrB,UAAI,gBAAgB,YAAY,GAAG;AACjC,wBAAgB,UAAU;AAAA,MAC5B;AAEA,YAAM,UAAU,YAAY,gBAAgB;AAC5C,YAAM,eAAe,KAAK,IAAI,GAAG,iBAAiB,UAAU,OAAO;AAEnE,sBAAgB,UAAU;AAC1B,uBAAiB,UAAU;AAC3B,iBAAW,YAAY;AACvB,gBAAU,UAAU,YAAY;AAEhC,UAAI,gBAAgB,GAAG;AACrB,uBAAe;AAAA,MACjB,OAAO;AACL,iBAAS,UAAU,sBAAsB,OAAO;AAAA,MAClD;AAAA,IACF;AAAA,IACA,CAAC,gBAAgB,UAAU;AAAA,EAC7B;AAGA,QAAM,qBAAiB,0BAAY,MAAM;AACvC,eAAW;AACX,oBAAgB,UAAU,YAAY,IAAI;AAE1C,QAAI,UAAU,SAAS;AACrB,eAAS,UAAU,sBAAsB,OAAO;AAAA,IAClD,OAAO;AACL,iBAAW,UAAU,OAAO,YAAY,MAAM,YAAY,OAAO;AAAA,IACnE;AAAA,EACF,GAAG,CAAC,YAAY,MAAM,OAAO,CAAC;AAG9B,QAAM,YAAQ,0BAAY,MAAM;AAE9B,QAAI,WAAW,aAAa,WAAW,YAAY;AACjD;AAAA,IACF;AAEA,cAAU,SAAS;AACnB,eAAW,UAAU;AACrB,mBAAe;AAAA,EACjB,GAAG,CAAC,QAAQ,cAAc,CAAC;AAE3B,QAAM,YAAQ,0BAAY,MAAM;AAE9B,QAAI,WAAW,WAAW;AACxB;AAAA,IACF;AAEA,eAAW;AACX,cAAU,QAAQ;AAClB,eAAW,UAAU;AAAA,EACvB,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAM,WAAO,0BAAY,MAAM;AAC7B,eAAW;AACX,cAAU,MAAM;AAChB,cAAU,UAAU;AAAA,EACtB,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,YAAQ,0BAAY,MAAM;AAC9B,eAAW;AACX,qBAAiB,UAAU,eAAe;AAC1C,eAAW,eAAe,OAAO;AACjC,cAAU,eAAe,YAAY,IAAI,aAAa,MAAM;AAC5D,eAAW,UAAU;AAAA,EACvB,GAAG,CAAC,YAAY,UAAU,CAAC;AAE3B,QAAM,cAAU,0BAAY,MAAM;AAChC,eAAW;AACX,qBAAiB,UAAU,eAAe;AAC1C,eAAW,eAAe,OAAO;AAEjC,QAAI,eAAe,YAAY,GAAG;AAChC,gBAAU,UAAU;AACpB;AAAA,IACF;AAEA,cAAU,SAAS;AACnB,eAAW,UAAU;AACrB,eAAW,UAAU;AACrB,mBAAe;AAAA,EACjB,GAAG,CAAC,YAAY,YAAY,cAAc,CAAC;AAE3C,QAAM,aAAS,0BAAY,MAAM;AAC/B,QAAI,WAAW;AACb,YAAM;AAAA,IACR,WAAW,YAAY,QAAQ;AAC7B,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,WAAW,UAAU,QAAQ,OAAO,KAAK,CAAC;AAG9C,QAAM,cAAU;AAAA,IACd,CAAC,UAAkB;AACjB,YAAM,YAAY,KAAK,IAAI,GAAG,iBAAiB,UAAU,KAAK;AAC9D,uBAAiB,UAAU;AAC3B,iBAAW,SAAS;AAGpB,UAAI,WAAW,cAAc,YAAY,GAAG;AAC1C,kBAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,UAAU;AAAA,EACrB;AAEA,QAAM,mBAAe;AAAA,IACnB,CAAC,eAAuB;AACtB,YAAM,YAAY,KAAK,IAAI,GAAG,iBAAiB,UAAU,UAAU;AACnE,uBAAiB,UAAU;AAC3B,iBAAW,SAAS;AAGpB,UAAI,aAAa,KAAK,WAAW,WAAW;AAC1C,uBAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,gBAAgB,UAAU;AAAA,EACrC;AAEA,QAAM,mBAAe;AAAA,IACnB,CAAC,cAAsB;AACrB,YAAM,WAAW,KAAK,IAAI,GAAG,SAAS;AACtC,uBAAiB,UAAU;AAC3B,iBAAW,QAAQ;AAGnB,UAAI,aAAa,KAAK,WAAW,WAAW;AAC1C,uBAAe;AAAA,MACjB,WAAW,WAAW,KAAK,WAAW,YAAY;AAChD,kBAAU,MAAM;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,gBAAgB,UAAU;AAAA,EACrC;AAIA,8BAAU,MAAM;AACd,QAAI,aAAa,CAAC,sBAAsB,WAAW,kBAAkB,GAAG;AACtE,4BAAsB,UAAU;AAChC,gBAAU,SAAS;AACnB,iBAAW,UAAU;AACrB,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,WAAW,iBAAiB,cAAc,CAAC;AAI/C,8BAAU,MAAM;AACd,UAAM,cAAc,KAAK,IAAI,GAAG,aAAa;AAG7C,QAAI,qBAAqB,YAAY,aAAa;AAChD,2BAAqB,UAAU;AAG/B,UAAI,WAAW,aAAa,WAAW,UAAU;AAC/C,uBAAe,UAAU;AACzB,yBAAiB,UAAU;AAC3B,mBAAW,WAAW;AACtB,kBAAU,gBAAgB,IAAI,aAAa,MAAM;AAAA,MACnD;AAAA,IACF;AAAA,EACF,GAAG,CAAC,eAAe,QAAQ,UAAU,CAAC;AAGtC,8BAAU,MAAM;AACd,QACE,WAAW,aACX,QACA,WAAW,YAAY,QACvB,SAAS,YAAY,MACrB;AAEA,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,MAAM,cAAc,CAAC;AAGvC,8BAAU,MAAM;AACd,QAAI,aAAa;AAEjB,UAAM,yBAAyB,MAAM;AACnC,UAAI,WAAW,UAAW;AAE1B,UAAI,SAAS,QAAQ;AAEnB,qBAAa,YAAY,IAAI;AAAA,MAC/B,WAAW,aAAa,GAAG;AAEzB,cAAM,UAAU,YAAY,IAAI,IAAI;AACpC,cAAM,eAAe,KAAK,IAAI,GAAG,iBAAiB,UAAU,OAAO;AACnE,yBAAiB,UAAU;AAC3B,wBAAgB,UAAU,YAAY,IAAI;AAG1C,cAAM,eAAe,UAAU,UAC3B,OAAO,UAAU,YAAY,aAC3B,UAAU,QAAQ,YAAY,IAC9B,WAAW,cAAc,UAAU,OAAqB,IAC1D,WAAW,cAAc,OAAO;AACpC,YAAI,iBAAiB,qBAAqB,SAAS;AACjD,+BAAqB,UAAU;AAC/B,2BAAiB,YAAY;AAAA,QAC/B;AAEA,YAAI,gBAAgB,GAAG;AACrB,yBAAe;AAAA,QACjB;AACA,qBAAa;AAAA,MACf;AAAA,IACF;AAEA,aAAS,iBAAiB,oBAAoB,sBAAsB;AACpE,WAAO,MAAM;AACX,eAAS,oBAAoB,oBAAoB,sBAAsB;AAAA,IACzE;AAAA,EACF,GAAG,CAAC,QAAQ,cAAc,CAAC;AAG3B,8BAAU,MAAM;AACd,WAAO,MAAM;AACX,iBAAW;AAAA,IACb;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAGf,SAAO;AAAA;AAAA,IAEL;AAAA,IACA,aAAa,eAAe;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;","names":["ms","ms"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useTimer.ts","../src/utils/timeUtils.ts","../src/utils/formatUtils.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n TimeFormat,\n TimeFormatter,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\nimport { decompose } from \"./utils/timeUtils\";\nimport { formatTime } from \"./utils/formatUtils\";\n\n/**\n * Helper object to create milliseconds from various time units\n *\n * @example\n * ```tsx\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * // 5 minute timer\n * const timer = useTimer(ms.minutes(5));\n *\n * // 1 hour 30 minutes timer\n * const timer = useTimer(ms.hours(1) + ms.minutes(30));\n * ```\n */\nexport const ms = {\n /** Convert seconds to milliseconds */\n seconds: (n: number): number => n * 1000,\n /** Convert minutes to milliseconds */\n minutes: (n: number): number => n * 60 * 1000,\n /** Convert hours to milliseconds */\n hours: (n: number): number => n * 60 * 60 * 1000,\n} as const;\n\n/**\n * A powerful countdown timer hook with comprehensive controls and features.\n *\n * @param initialTimeMs - Initial time in milliseconds\n * @param options - Timer configuration options\n * @returns Timer state and control functions\n *\n * @example\n * ```tsx\n * // Basic usage\n * function Timer() {\n * const timer = useTimer(60000); // 60 seconds\n *\n * return (\n * <div>\n * <p>{timer.formattedTime}</p>\n * <button onClick={timer.toggle}>\n * {timer.isRunning ? \"Pause\" : \"Start\"}\n * </button>\n * <button onClick={timer.reset}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks and auto-start\n * const timer = useTimer(30000, {\n * autoStart: true,\n * onComplete: () => alert(\"Time's up!\"),\n * onTick: (remaining) => console.log(`${remaining}ms left`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With time helpers\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * const timer = useTimer(ms.minutes(5), {\n * format: \"MM:SS\",\n * loop: true,\n * });\n * ```\n */\nexport function useTimer(\n initialTimeMs: number,\n options: UseTimerOptions = {}\n): UseTimerReturn {\n // ============ Parse Options ============\n const {\n interval = 1,\n format = \"MM:SS\",\n autoStart = false,\n loop = false,\n useRAF = false,\n onComplete,\n onTick,\n onStart,\n onPause,\n onReset,\n onStop,\n } = options;\n\n // Ensure initialTime is non-negative\n const safeInitialTime = Math.max(0, initialTimeMs);\n\n // ============ Helper to compute formatted time ============\n const computeFormattedTime = useCallback(\n (timeMs: number): string => {\n if (typeof format === \"function\") {\n return format(timeMs);\n }\n return formatTime(timeMs, format as TimeFormat);\n },\n [format]\n );\n\n // ============ State ============\n // Only formattedTime triggers re-renders (optimization)\n const [formattedTime, setFormattedTime] = useState(() =>\n computeFormattedTime(safeInitialTime)\n );\n const [status, setStatus] = useState<TimerStatus>(() =>\n safeInitialTime === 0 ? \"finished\" : \"idle\"\n );\n\n // ============ Refs ============\n // Time is stored in ref for performance - only formattedTime changes trigger re-renders\n const timeRef = useRef(safeInitialTime);\n const prevFormattedTimeRef = useRef<string>(\n computeFormattedTime(safeInitialTime)\n );\n\n // Timer ID for cleanup\n const timerIdRef = useRef<number | null>(null);\n const rafIdRef = useRef<number | null>(null);\n\n // For accurate time tracking\n const lastTickTimeRef = useRef<number>(0);\n const remainingTimeRef = useRef(safeInitialTime);\n\n // Track initial time for reset\n const initialTimeRef = useRef(safeInitialTime);\n\n // Store if autoStart has been triggered\n const autoStartTriggeredRef = useRef(false);\n\n // Track previous initialTimeMs to detect actual prop changes\n const prevInitialTimeMsRef = useRef(safeInitialTime);\n\n // Callback refs for latest values without causing re-renders\n const onCompleteRef = useRef(onComplete);\n const onTickRef = useRef(onTick);\n const onStartRef = useRef(onStart);\n const onPauseRef = useRef(onPause);\n const onResetRef = useRef(onReset);\n const onStopRef = useRef(onStop);\n\n // Options refs\n const loopRef = useRef(loop);\n const intervalRef = useRef(interval);\n const useRAFRef = useRef(useRAF);\n const formatRef = useRef(format);\n\n // ============ Update Refs ============\n useEffect(() => {\n onCompleteRef.current = onComplete;\n onTickRef.current = onTick;\n onStartRef.current = onStart;\n onPauseRef.current = onPause;\n onResetRef.current = onReset;\n onStopRef.current = onStop;\n loopRef.current = loop;\n intervalRef.current = interval;\n useRAFRef.current = useRAF;\n formatRef.current = format;\n });\n\n // Re-compute formattedTime when format option changes\n useEffect(() => {\n const newFormatted = computeFormattedTime(timeRef.current);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n }, [format, computeFormattedTime]);\n\n // ============ Helper to update time (only re-renders if formatted value changes) ============\n const updateTime = useCallback(\n (newTimeMs: number) => {\n timeRef.current = newTimeMs;\n const newFormatted = computeFormattedTime(newTimeMs);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n },\n [computeFormattedTime]\n );\n\n // ============ Derived State ============\n const isRunning = status === \"running\";\n const isPaused = status === \"paused\";\n const isFinished = status === \"finished\";\n const isIdle = status === \"idle\";\n\n // Current time value (read from ref)\n const time = timeRef.current;\n\n // Progress: 0 at start, 100 at completion\n const progress = useMemo(() => {\n if (initialTimeRef.current === 0) return 100;\n return Math.min(\n 100,\n Math.max(\n 0,\n ((initialTimeRef.current - time) / initialTimeRef.current) * 100\n )\n );\n // formattedTime is the trigger for re-render, time is derived from ref\n }, [formattedTime]);\n\n // Decomposed time (recalculated on re-render)\n const decomposedTime = useMemo(() => decompose(time), [formattedTime]);\n const { hours, minutes, seconds, milliseconds } = decomposedTime;\n\n // ============ Clear Timer ============\n const clearTimer = useCallback(() => {\n if (timerIdRef.current !== null) {\n clearInterval(timerIdRef.current);\n timerIdRef.current = null;\n }\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // ============ Handle Complete ============\n const handleComplete = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = 0;\n updateTime(0);\n\n if (loopRef.current) {\n // Loop: reset and restart\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n onCompleteRef.current?.();\n // Status stays running, will schedule next tick in effect\n } else {\n setStatus(\"finished\");\n onCompleteRef.current?.();\n }\n }, [clearTimer, updateTime]);\n\n // ============ Tick Function ============\n const tick = useCallback(() => {\n const now = performance.now();\n const elapsed = now - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = now;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n }, [handleComplete, updateTime]);\n\n // ============ RAF Tick ============\n const rafTick = useCallback(\n (timestamp: number) => {\n if (lastTickTimeRef.current === 0) {\n lastTickTimeRef.current = timestamp;\n }\n\n const elapsed = timestamp - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = timestamp;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n } else {\n rafIdRef.current = requestAnimationFrame(rafTick);\n }\n },\n [handleComplete, updateTime]\n );\n\n // ============ Start Timer Loop ============\n const startTimerLoop = useCallback(() => {\n clearTimer();\n lastTickTimeRef.current = performance.now();\n\n if (useRAFRef.current) {\n rafIdRef.current = requestAnimationFrame(rafTick);\n } else {\n timerIdRef.current = window.setInterval(tick, intervalRef.current);\n }\n }, [clearTimer, tick, rafTick]);\n\n // ============ Control Functions ============\n const start = useCallback(() => {\n // Don't start if already running or finished\n if (status === \"running\" || status === \"finished\") {\n return;\n }\n\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }, [status, startTimerLoop]);\n\n const pause = useCallback(() => {\n // Only pause if running\n if (status !== \"running\") {\n return;\n }\n\n clearTimer();\n setStatus(\"paused\");\n onPauseRef.current?.();\n }, [status, clearTimer]);\n\n const stop = useCallback(() => {\n clearTimer();\n setStatus(\"idle\");\n onStopRef.current?.();\n }, [clearTimer]);\n\n const reset = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n setStatus(initialTimeRef.current === 0 ? \"finished\" : \"idle\");\n onResetRef.current?.();\n }, [clearTimer, updateTime]);\n\n const restart = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n\n if (initialTimeRef.current === 0) {\n setStatus(\"finished\");\n return;\n }\n\n setStatus(\"running\");\n onResetRef.current?.();\n onStartRef.current?.();\n startTimerLoop();\n }, [clearTimer, updateTime, startTimerLoop]);\n\n const toggle = useCallback(() => {\n if (isRunning) {\n pause();\n } else if (isPaused || isIdle) {\n start();\n }\n }, [isRunning, isPaused, isIdle, start, pause]);\n\n // ============ Time Manipulation ============\n const addTime = useCallback(\n (addMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current + addMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If timer was finished and we add time, it's no longer finished\n if (status === \"finished\" && newTimeMs > 0) {\n setStatus(\"idle\");\n }\n },\n [status, updateTime]\n );\n\n const subtractTime = useCallback(\n (subtractMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current - subtractMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If time reaches 0, mark as finished\n if (newTimeMs <= 0 && status === \"running\") {\n handleComplete();\n }\n },\n [status, handleComplete, updateTime]\n );\n\n const setTimeValue = useCallback(\n (newTimeMs: number) => {\n const safeTime = Math.max(0, newTimeMs);\n remainingTimeRef.current = safeTime;\n updateTime(safeTime);\n\n // Update finished state based on new time\n if (safeTime === 0 && status === \"running\") {\n handleComplete();\n } else if (safeTime > 0 && status === \"finished\") {\n setStatus(\"idle\");\n }\n },\n [status, handleComplete, updateTime]\n );\n\n // ============ Effects ============\n // Handle autoStart\n useEffect(() => {\n if (autoStart && !autoStartTriggeredRef.current && safeInitialTime > 0) {\n autoStartTriggeredRef.current = true;\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }\n }, [autoStart, safeInitialTime, startTimerLoop]);\n\n // Handle initialTimeMs changes when not running\n // Only react to actual prop changes, not status changes\n useEffect(() => {\n const newSafeTime = Math.max(0, initialTimeMs);\n\n // Only update if initialTimeMs actually changed\n if (prevInitialTimeMsRef.current !== newSafeTime) {\n prevInitialTimeMsRef.current = newSafeTime;\n\n // Only sync time when not running or paused\n if (status !== \"running\" && status !== \"paused\") {\n initialTimeRef.current = newSafeTime;\n remainingTimeRef.current = newSafeTime;\n updateTime(newSafeTime);\n setStatus(newSafeTime === 0 ? \"finished\" : \"idle\");\n }\n }\n }, [initialTimeMs, status, updateTime]);\n\n // Handle loop restart when running\n useEffect(() => {\n if (\n status === \"running\" &&\n loop &&\n timerIdRef.current === null &&\n rafIdRef.current === null\n ) {\n // Timer was cleared for loop completion, restart it\n startTimerLoop();\n }\n }, [status, loop, time, startTimerLoop]);\n\n // Visibility API: compensate for time drift when tab is inactive\n useEffect(() => {\n let hiddenTime = 0;\n\n const handleVisibilityChange = () => {\n if (status !== \"running\") return;\n\n if (document.hidden) {\n // Tab became hidden, record the time\n hiddenTime = performance.now();\n } else if (hiddenTime > 0) {\n // Tab became visible, calculate elapsed time\n const elapsed = performance.now() - hiddenTime;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n remainingTimeRef.current = newRemaining;\n lastTickTimeRef.current = performance.now();\n\n // Force update formatted time\n const newFormatted = formatRef.current\n ? typeof formatRef.current === \"function\"\n ? formatRef.current(newRemaining)\n : formatTime(newRemaining, formatRef.current as TimeFormat)\n : formatTime(newRemaining, \"MM:SS\");\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n hiddenTime = 0;\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [status, handleComplete]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, [clearTimer]);\n\n // ============ Return ============\n return {\n // State\n time,\n initialTime: initialTimeRef.current,\n formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\n\n // Decomposed time\n hours,\n minutes,\n seconds,\n milliseconds,\n\n // Controls\n start,\n pause,\n stop,\n reset,\n restart,\n toggle,\n\n // Time manipulation\n addTime,\n subtractTime,\n setTime: setTimeValue,\n };\n}\n","/**\n * Supported time units for utility functions\n */\nexport type TimeUnit = \"ms\" | \"seconds\" | \"minutes\" | \"hours\";\n\n/**\n * Millisecond multipliers for each time unit\n */\nconst MS_MULTIPLIERS: Record<TimeUnit, number> = {\n ms: 1,\n seconds: 1000,\n minutes: 60 * 1000,\n hours: 60 * 60 * 1000,\n};\n\n/**\n * Convert a time value to milliseconds\n * @param time - The time value to convert\n * @param unit - The unit of the time value\n * @returns Time in milliseconds (minimum 0)\n */\nexport function toMs(time: number, unit: TimeUnit): number {\n return Math.max(0, Math.floor(time * MS_MULTIPLIERS[unit]));\n}\n\n/**\n * Convert milliseconds to a specific time unit\n * @param ms - Time in milliseconds\n * @param unit - Target time unit\n * @returns Time in the specified unit\n */\nexport function fromMs(ms: number, unit: TimeUnit): number {\n return ms / MS_MULTIPLIERS[unit];\n}\n\n/**\n * Decomposed time object\n */\nexport interface DecomposedTime {\n hours: number;\n minutes: number;\n seconds: number;\n milliseconds: number;\n}\n\n/**\n * Decompose milliseconds into hours, minutes, seconds, and milliseconds\n * @param ms - Time in milliseconds\n * @returns Decomposed time object\n */\nexport function decompose(ms: number): DecomposedTime {\n const safeMs = Math.max(0, ms);\n\n // Use floor for consistent calculation across all components\n const totalSeconds = Math.floor(safeMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n // Milliseconds are the fractional part\n const milliseconds = Math.floor(safeMs % 1000);\n\n return { hours, minutes, seconds, milliseconds };\n}\n","import type { TimeFormat } from \"../types\";\nimport { decompose } from \"./timeUtils\";\n\n/**\n * Pad a number with leading zeros\n * @param num - Number to pad\n * @param length - Desired string length\n * @returns Padded string\n */\nfunction padNumber(num: number, length: number): string {\n return num.toString().padStart(length, \"0\");\n}\n\n/**\n * Format milliseconds into a time string\n * @param ms - Time in milliseconds\n * @param format - Desired format\n * @returns Formatted time string\n */\nexport function formatTime(ms: number, format: TimeFormat): string {\n const { hours, minutes, seconds, milliseconds } = decompose(Math.max(0, ms));\n\n switch (format) {\n case \"HH:MM:SS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`;\n\n case \"MM:SS\": {\n const totalMinutes = hours * 60 + minutes;\n return `${padNumber(totalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n\n case \"SS\": {\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds.toString();\n }\n\n case \"mm:ss.SSS\": {\n const totalMins = hours * 60 + minutes;\n return `${padNumber(totalMins, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n }\n\n case \"HH:MM:SS.SSS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n\n default:\n // Default to MM:SS format\n const defaultTotalMinutes = hours * 60 + minutes;\n return `${padNumber(defaultTotalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n}\n"],"mappings":"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","names":["ms","ms"]}
1
+ {"version":3,"sources":["../src/useTimer.ts","../src/utils/timeUtils.ts","../src/utils/formatUtils.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n TimeFormat,\n TimerStatus,\n UseTimerOptions,\n UseTimerReturn,\n} from \"./types\";\nimport { decompose } from \"./utils/timeUtils\";\nimport { formatTime } from \"./utils/formatUtils\";\n\n/**\n * Helper object to create milliseconds from various time units\n *\n * @example\n * ```tsx\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * // 5 minute timer\n * const timer = useTimer(ms.minutes(5));\n *\n * // 1 hour 30 minutes timer\n * const timer = useTimer(ms.hours(1) + ms.minutes(30));\n * ```\n */\nexport const ms = {\n /** Convert seconds to milliseconds */\n seconds: (n: number): number => n * 1000,\n /** Convert minutes to milliseconds */\n minutes: (n: number): number => n * 60 * 1000,\n /** Convert hours to milliseconds */\n hours: (n: number): number => n * 60 * 60 * 1000,\n} as const;\n\n/**\n * A powerful countdown timer hook with comprehensive controls and features.\n *\n * @param initialTimeMs - Initial time in milliseconds\n * @param options - Timer configuration options\n * @returns Timer state and control functions\n *\n * @example\n * ```tsx\n * // Basic usage\n * function Timer() {\n * const timer = useTimer(60000); // 60 seconds\n *\n * return (\n * <div>\n * <p>{timer.formattedTime}</p>\n * <button onClick={timer.toggle}>\n * {timer.isRunning ? \"Pause\" : \"Start\"}\n * </button>\n * <button onClick={timer.reset}>Reset</button>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks and auto-start\n * const timer = useTimer(30000, {\n * autoStart: true,\n * onComplete: () => alert(\"Time's up!\"),\n * onTick: (remaining) => console.log(`${remaining}ms left`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // With time helpers\n * import { useTimer, ms } from \"@usefy/use-timer\";\n *\n * const timer = useTimer(ms.minutes(5), {\n * format: \"MM:SS\",\n * loop: true,\n * });\n * ```\n */\nexport function useTimer(\n initialTimeMs: number,\n options: UseTimerOptions = {}\n): UseTimerReturn {\n // ============ Parse Options ============\n const {\n interval = 1,\n format = \"MM:SS\",\n autoStart = false,\n loop = false,\n useRAF = false,\n onComplete,\n onTick,\n onStart,\n onPause,\n onReset,\n onStop,\n } = options;\n\n // Ensure initialTime is non-negative\n const safeInitialTime = Math.max(0, initialTimeMs);\n\n // ============ Helper to compute formatted time ============\n const computeFormattedTime = useCallback(\n (timeMs: number): string => {\n if (typeof format === \"function\") {\n return format(timeMs);\n }\n return formatTime(timeMs, format as TimeFormat);\n },\n [format]\n );\n\n // ============ State ============\n // Only formattedTime triggers re-renders (optimization)\n const [formattedTime, setFormattedTime] = useState(() =>\n computeFormattedTime(safeInitialTime)\n );\n const [status, setStatus] = useState<TimerStatus>(() =>\n safeInitialTime === 0 ? \"finished\" : \"idle\"\n );\n\n // ============ Refs ============\n // Time is stored in ref for performance - only formattedTime changes trigger re-renders\n const timeRef = useRef(safeInitialTime);\n const prevFormattedTimeRef = useRef<string>(\n computeFormattedTime(safeInitialTime)\n );\n\n // Timer ID for cleanup\n const timerIdRef = useRef<number | null>(null);\n const rafIdRef = useRef<number | null>(null);\n\n // For accurate time tracking\n const lastTickTimeRef = useRef<number>(0);\n const remainingTimeRef = useRef(safeInitialTime);\n\n // Track initial time for reset\n const initialTimeRef = useRef(safeInitialTime);\n\n // Store if autoStart has been triggered\n const autoStartTriggeredRef = useRef(false);\n\n // Track previous initialTimeMs to detect actual prop changes\n const prevInitialTimeMsRef = useRef(safeInitialTime);\n\n // Callback refs for latest values without causing re-renders\n const onCompleteRef = useRef(onComplete);\n const onTickRef = useRef(onTick);\n const onStartRef = useRef(onStart);\n const onPauseRef = useRef(onPause);\n const onResetRef = useRef(onReset);\n const onStopRef = useRef(onStop);\n\n // Options refs\n const loopRef = useRef(loop);\n const intervalRef = useRef(interval);\n const useRAFRef = useRef(useRAF);\n const formatRef = useRef(format);\n\n // ============ Update Refs ============\n useEffect(() => {\n onCompleteRef.current = onComplete;\n onTickRef.current = onTick;\n onStartRef.current = onStart;\n onPauseRef.current = onPause;\n onResetRef.current = onReset;\n onStopRef.current = onStop;\n loopRef.current = loop;\n intervalRef.current = interval;\n useRAFRef.current = useRAF;\n formatRef.current = format;\n });\n\n // Re-compute formattedTime when format option changes\n useEffect(() => {\n const newFormatted = computeFormattedTime(timeRef.current);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n }, [format, computeFormattedTime]);\n\n // ============ Helper to update time (only re-renders if formatted value changes) ============\n const updateTime = useCallback(\n (newTimeMs: number) => {\n timeRef.current = newTimeMs;\n const newFormatted = computeFormattedTime(newTimeMs);\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n },\n [computeFormattedTime]\n );\n\n // ============ Derived State ============\n const isRunning = status === \"running\";\n const isPaused = status === \"paused\";\n const isFinished = status === \"finished\";\n const isIdle = status === \"idle\";\n\n // Current time value (read from ref)\n const time = timeRef.current;\n\n // Progress: 0 at start, 100 at completion\n const progress = useMemo(() => {\n if (initialTimeRef.current === 0) return 100;\n return Math.min(\n 100,\n Math.max(\n 0,\n ((initialTimeRef.current - time) / initialTimeRef.current) * 100\n )\n );\n // formattedTime is the trigger for re-render, time is derived from ref\n }, [formattedTime]);\n\n // Decomposed time (recalculated on re-render)\n const decomposedTime = useMemo(() => decompose(time), [formattedTime]);\n const { hours, minutes, seconds, milliseconds } = decomposedTime;\n\n // ============ Clear Timer ============\n const clearTimer = useCallback(() => {\n if (timerIdRef.current !== null) {\n clearInterval(timerIdRef.current);\n timerIdRef.current = null;\n }\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n }, []);\n\n // ============ Handle Complete ============\n const handleComplete = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = 0;\n updateTime(0);\n\n if (loopRef.current) {\n // Loop: reset and restart\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n onCompleteRef.current?.();\n // Status stays running, will schedule next tick in effect\n } else {\n setStatus(\"finished\");\n onCompleteRef.current?.();\n }\n }, [clearTimer, updateTime]);\n\n // ============ Tick Function ============\n const tick = useCallback(() => {\n const now = performance.now();\n const elapsed = now - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = now;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n }, [handleComplete, updateTime]);\n\n // ============ RAF Tick ============\n const rafTick = useCallback(\n (timestamp: number) => {\n if (lastTickTimeRef.current === 0) {\n lastTickTimeRef.current = timestamp;\n }\n\n const elapsed = timestamp - lastTickTimeRef.current;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n\n lastTickTimeRef.current = timestamp;\n remainingTimeRef.current = newRemaining;\n updateTime(newRemaining);\n onTickRef.current?.(newRemaining);\n\n if (newRemaining <= 0) {\n handleComplete();\n } else {\n rafIdRef.current = requestAnimationFrame(rafTick);\n }\n },\n [handleComplete, updateTime]\n );\n\n // ============ Start Timer Loop ============\n const startTimerLoop = useCallback(() => {\n clearTimer();\n lastTickTimeRef.current = performance.now();\n\n if (useRAFRef.current) {\n rafIdRef.current = requestAnimationFrame(rafTick);\n } else {\n timerIdRef.current = window.setInterval(tick, intervalRef.current);\n }\n }, [clearTimer, tick, rafTick]);\n\n // ============ Control Functions ============\n const start = useCallback(() => {\n // Don't start if already running or finished\n if (status === \"running\" || status === \"finished\") {\n return;\n }\n\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }, [status, startTimerLoop]);\n\n const pause = useCallback(() => {\n // Only pause if running\n if (status !== \"running\") {\n return;\n }\n\n clearTimer();\n setStatus(\"paused\");\n onPauseRef.current?.();\n }, [status, clearTimer]);\n\n const stop = useCallback(() => {\n clearTimer();\n setStatus(\"idle\");\n onStopRef.current?.();\n }, [clearTimer]);\n\n const reset = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n setStatus(initialTimeRef.current === 0 ? \"finished\" : \"idle\");\n onResetRef.current?.();\n }, [clearTimer, updateTime]);\n\n const restart = useCallback(() => {\n clearTimer();\n remainingTimeRef.current = initialTimeRef.current;\n updateTime(initialTimeRef.current);\n\n if (initialTimeRef.current === 0) {\n setStatus(\"finished\");\n return;\n }\n\n setStatus(\"running\");\n onResetRef.current?.();\n onStartRef.current?.();\n startTimerLoop();\n }, [clearTimer, updateTime, startTimerLoop]);\n\n const toggle = useCallback(() => {\n if (isRunning) {\n pause();\n } else if (isPaused || isIdle) {\n start();\n }\n }, [isRunning, isPaused, isIdle, start, pause]);\n\n // ============ Time Manipulation ============\n const addTime = useCallback(\n (addMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current + addMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If timer was finished and we add time, it's no longer finished\n if (status === \"finished\" && newTimeMs > 0) {\n setStatus(\"idle\");\n }\n },\n [status, updateTime]\n );\n\n const subtractTime = useCallback(\n (subtractMs: number) => {\n const newTimeMs = Math.max(0, remainingTimeRef.current - subtractMs);\n remainingTimeRef.current = newTimeMs;\n updateTime(newTimeMs);\n\n // If time reaches 0, mark as finished\n if (newTimeMs <= 0 && status === \"running\") {\n handleComplete();\n }\n },\n [status, handleComplete, updateTime]\n );\n\n const setTimeValue = useCallback(\n (newTimeMs: number) => {\n const safeTime = Math.max(0, newTimeMs);\n remainingTimeRef.current = safeTime;\n updateTime(safeTime);\n\n // Update finished state based on new time\n if (safeTime === 0 && status === \"running\") {\n handleComplete();\n } else if (safeTime > 0 && status === \"finished\") {\n setStatus(\"idle\");\n }\n },\n [status, handleComplete, updateTime]\n );\n\n // ============ Effects ============\n // Handle autoStart\n useEffect(() => {\n if (autoStart && !autoStartTriggeredRef.current && safeInitialTime > 0) {\n autoStartTriggeredRef.current = true;\n setStatus(\"running\");\n onStartRef.current?.();\n startTimerLoop();\n }\n }, [autoStart, safeInitialTime, startTimerLoop]);\n\n // Handle initialTimeMs changes when not running\n // Only react to actual prop changes, not status changes\n useEffect(() => {\n const newSafeTime = Math.max(0, initialTimeMs);\n\n // Only update if initialTimeMs actually changed\n if (prevInitialTimeMsRef.current !== newSafeTime) {\n prevInitialTimeMsRef.current = newSafeTime;\n\n // Only sync time when not running or paused\n if (status !== \"running\" && status !== \"paused\") {\n initialTimeRef.current = newSafeTime;\n remainingTimeRef.current = newSafeTime;\n updateTime(newSafeTime);\n setStatus(newSafeTime === 0 ? \"finished\" : \"idle\");\n }\n }\n }, [initialTimeMs, status, updateTime]);\n\n // Handle loop restart when running\n useEffect(() => {\n if (\n status === \"running\" &&\n loop &&\n timerIdRef.current === null &&\n rafIdRef.current === null\n ) {\n // Timer was cleared for loop completion, restart it\n startTimerLoop();\n }\n }, [status, loop, time, startTimerLoop]);\n\n // Visibility API: compensate for time drift when tab is inactive\n useEffect(() => {\n let hiddenTime = 0;\n\n const handleVisibilityChange = () => {\n if (status !== \"running\") return;\n\n if (document.hidden) {\n // Tab became hidden, record the time\n hiddenTime = performance.now();\n } else if (hiddenTime > 0) {\n // Tab became visible, calculate elapsed time\n const elapsed = performance.now() - hiddenTime;\n const newRemaining = Math.max(0, remainingTimeRef.current - elapsed);\n remainingTimeRef.current = newRemaining;\n lastTickTimeRef.current = performance.now();\n\n // Force update formatted time\n const newFormatted = formatRef.current\n ? typeof formatRef.current === \"function\"\n ? formatRef.current(newRemaining)\n : formatTime(newRemaining, formatRef.current as TimeFormat)\n : formatTime(newRemaining, \"MM:SS\");\n if (newFormatted !== prevFormattedTimeRef.current) {\n prevFormattedTimeRef.current = newFormatted;\n setFormattedTime(newFormatted);\n }\n\n if (newRemaining <= 0) {\n handleComplete();\n }\n hiddenTime = 0;\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [status, handleComplete]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n clearTimer();\n };\n }, [clearTimer]);\n\n // ============ Return ============\n return {\n // State\n time,\n initialTime: initialTimeRef.current,\n formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\n\n // Decomposed time\n hours,\n minutes,\n seconds,\n milliseconds,\n\n // Controls\n start,\n pause,\n stop,\n reset,\n restart,\n toggle,\n\n // Time manipulation\n addTime,\n subtractTime,\n setTime: setTimeValue,\n };\n}\n","/**\n * Supported time units for utility functions\n */\nexport type TimeUnit = \"ms\" | \"seconds\" | \"minutes\" | \"hours\";\n\n/**\n * Millisecond multipliers for each time unit\n */\nconst MS_MULTIPLIERS: Record<TimeUnit, number> = {\n ms: 1,\n seconds: 1000,\n minutes: 60 * 1000,\n hours: 60 * 60 * 1000,\n};\n\n/**\n * Convert a time value to milliseconds\n * @param time - The time value to convert\n * @param unit - The unit of the time value\n * @returns Time in milliseconds (minimum 0)\n */\nexport function toMs(time: number, unit: TimeUnit): number {\n return Math.max(0, Math.floor(time * MS_MULTIPLIERS[unit]));\n}\n\n/**\n * Convert milliseconds to a specific time unit\n * @param ms - Time in milliseconds\n * @param unit - Target time unit\n * @returns Time in the specified unit\n */\nexport function fromMs(ms: number, unit: TimeUnit): number {\n return ms / MS_MULTIPLIERS[unit];\n}\n\n/**\n * Decomposed time object\n */\nexport interface DecomposedTime {\n hours: number;\n minutes: number;\n seconds: number;\n milliseconds: number;\n}\n\n/**\n * Decompose milliseconds into hours, minutes, seconds, and milliseconds\n * @param ms - Time in milliseconds\n * @returns Decomposed time object\n */\nexport function decompose(ms: number): DecomposedTime {\n const safeMs = Math.max(0, ms);\n\n // Use floor for consistent calculation across all components\n const totalSeconds = Math.floor(safeMs / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n // Milliseconds are the fractional part\n const milliseconds = Math.floor(safeMs % 1000);\n\n return { hours, minutes, seconds, milliseconds };\n}\n","import type { TimeFormat } from \"../types\";\nimport { decompose } from \"./timeUtils\";\n\n/**\n * Pad a number with leading zeros\n * @param num - Number to pad\n * @param length - Desired string length\n * @returns Padded string\n */\nfunction padNumber(num: number, length: number): string {\n return num.toString().padStart(length, \"0\");\n}\n\n/**\n * Format milliseconds into a time string\n * @param ms - Time in milliseconds\n * @param format - Desired format\n * @returns Formatted time string\n */\nexport function formatTime(ms: number, format: TimeFormat): string {\n const { hours, minutes, seconds, milliseconds } = decompose(Math.max(0, ms));\n\n switch (format) {\n case \"HH:MM:SS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}`;\n\n case \"MM:SS\": {\n const totalMinutes = hours * 60 + minutes;\n return `${padNumber(totalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n\n case \"SS\": {\n const totalSeconds = hours * 3600 + minutes * 60 + seconds;\n return totalSeconds.toString();\n }\n\n case \"mm:ss.SSS\": {\n const totalMins = hours * 60 + minutes;\n return `${padNumber(totalMins, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n }\n\n case \"HH:MM:SS.SSS\":\n return `${padNumber(hours, 2)}:${padNumber(minutes, 2)}:${padNumber(seconds, 2)}.${padNumber(milliseconds, 3)}`;\n\n default:\n // Default to MM:SS format\n const defaultTotalMinutes = hours * 60 + minutes;\n return `${padNumber(defaultTotalMinutes, 2)}:${padNumber(seconds, 2)}`;\n }\n}\n"],"mappings":"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","names":["ms","ms"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-timer",
3
- "version": "0.0.1",
3
+ "version": "0.0.19",
4
4
  "description": "A React hook for managing countdown timers with precision and control",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",