@usefy/use-timer 0.0.18 → 0.0.20

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.d.mts CHANGED
@@ -69,18 +69,11 @@ interface UseTimerOptions {
69
69
  * Return type for useTimer hook
70
70
  */
71
71
  interface UseTimerReturn {
72
- /**
73
- * Remaining time in milliseconds
74
- */
75
- time: number;
76
- /**
77
- * Initial time in milliseconds
78
- */
79
- initialTime: number;
80
72
  /**
81
73
  * Formatted time string based on format option
74
+ * @example "05:30", "01:30:00"
82
75
  */
83
- formattedTime: string;
76
+ time: string;
84
77
  /**
85
78
  * Progress percentage (0-100)
86
79
  * 0 at start, 100 when complete
@@ -106,22 +99,6 @@ interface UseTimerReturn {
106
99
  * Whether the timer is idle (never started or after reset)
107
100
  */
108
101
  isIdle: boolean;
109
- /**
110
- * Hours component (0+)
111
- */
112
- hours: number;
113
- /**
114
- * Minutes component (0-59)
115
- */
116
- minutes: number;
117
- /**
118
- * Seconds component (0-59)
119
- */
120
- seconds: number;
121
- /**
122
- * Milliseconds component (0-999)
123
- */
124
- milliseconds: number;
125
102
  /**
126
103
  * Start or resume the timer
127
104
  */
@@ -197,7 +174,7 @@ declare const ms: {
197
174
  *
198
175
  * return (
199
176
  * <div>
200
- * <p>{timer.formattedTime}</p>
177
+ * <p>{timer.time}</p>
201
178
  * <button onClick={timer.toggle}>
202
179
  * {timer.isRunning ? "Pause" : "Start"}
203
180
  * </button>
package/dist/index.d.ts CHANGED
@@ -69,18 +69,11 @@ interface UseTimerOptions {
69
69
  * Return type for useTimer hook
70
70
  */
71
71
  interface UseTimerReturn {
72
- /**
73
- * Remaining time in milliseconds
74
- */
75
- time: number;
76
- /**
77
- * Initial time in milliseconds
78
- */
79
- initialTime: number;
80
72
  /**
81
73
  * Formatted time string based on format option
74
+ * @example "05:30", "01:30:00"
82
75
  */
83
- formattedTime: string;
76
+ time: string;
84
77
  /**
85
78
  * Progress percentage (0-100)
86
79
  * 0 at start, 100 when complete
@@ -106,22 +99,6 @@ interface UseTimerReturn {
106
99
  * Whether the timer is idle (never started or after reset)
107
100
  */
108
101
  isIdle: boolean;
109
- /**
110
- * Hours component (0+)
111
- */
112
- hours: number;
113
- /**
114
- * Minutes component (0-59)
115
- */
116
- minutes: number;
117
- /**
118
- * Seconds component (0-59)
119
- */
120
- seconds: number;
121
- /**
122
- * Milliseconds component (0-999)
123
- */
124
- milliseconds: number;
125
102
  /**
126
103
  * Start or resume the timer
127
104
  */
@@ -197,7 +174,7 @@ declare const ms: {
197
174
  *
198
175
  * return (
199
176
  * <div>
200
- * <p>{timer.formattedTime}</p>
177
+ * <p>{timer.time}</p>
201
178
  * <button onClick={timer.toggle}>
202
179
  * {timer.isRunning ? "Pause" : "Start"}
203
180
  * </button>
package/dist/index.js CHANGED
@@ -178,19 +178,16 @@ function useTimer(initialTimeMs, options = {}) {
178
178
  const isPaused = status === "paused";
179
179
  const isFinished = status === "finished";
180
180
  const isIdle = status === "idle";
181
- const time = timeRef.current;
182
181
  const progress = (0, import_react.useMemo)(() => {
183
182
  if (initialTimeRef.current === 0) return 100;
184
183
  return Math.min(
185
184
  100,
186
185
  Math.max(
187
186
  0,
188
- (initialTimeRef.current - time) / initialTimeRef.current * 100
187
+ (initialTimeRef.current - timeRef.current) / initialTimeRef.current * 100
189
188
  )
190
189
  );
191
190
  }, [formattedTime]);
192
- const decomposedTime = (0, import_react.useMemo)(() => decompose(time), [formattedTime]);
193
- const { hours, minutes, seconds, milliseconds } = decomposedTime;
194
191
  const clearTimer = (0, import_react.useCallback)(() => {
195
192
  if (timerIdRef.current !== null) {
196
193
  clearInterval(timerIdRef.current);
@@ -361,7 +358,7 @@ function useTimer(initialTimeMs, options = {}) {
361
358
  if (status === "running" && loop && timerIdRef.current === null && rafIdRef.current === null) {
362
359
  startTimerLoop();
363
360
  }
364
- }, [status, loop, time, startTimerLoop]);
361
+ }, [status, loop, formattedTime, startTimerLoop]);
365
362
  (0, import_react.useEffect)(() => {
366
363
  let hiddenTime = 0;
367
364
  const handleVisibilityChange = () => {
@@ -396,9 +393,7 @@ function useTimer(initialTimeMs, options = {}) {
396
393
  }, [clearTimer]);
397
394
  return {
398
395
  // State
399
- time,
400
- initialTime: initialTimeRef.current,
401
- formattedTime,
396
+ time: formattedTime,
402
397
  progress,
403
398
  status,
404
399
  // Derived state
@@ -406,11 +401,6 @@ function useTimer(initialTimeMs, options = {}) {
406
401
  isPaused,
407
402
  isFinished,
408
403
  isIdle,
409
- // Decomposed time
410
- hours,
411
- minutes,
412
- seconds,
413
- milliseconds,
414
404
  // Controls
415
405
  start,
416
406
  pause,
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 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 { 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.time}</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 // 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 - timeRef.current) / initialTimeRef.current) *\n 100\n )\n );\n // formattedTime is the trigger for re-render\n }, [formattedTime]);\n\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, formattedTime, 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: formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\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/dist/index.mjs CHANGED
@@ -147,19 +147,16 @@ function useTimer(initialTimeMs, options = {}) {
147
147
  const isPaused = status === "paused";
148
148
  const isFinished = status === "finished";
149
149
  const isIdle = status === "idle";
150
- const time = timeRef.current;
151
150
  const progress = useMemo(() => {
152
151
  if (initialTimeRef.current === 0) return 100;
153
152
  return Math.min(
154
153
  100,
155
154
  Math.max(
156
155
  0,
157
- (initialTimeRef.current - time) / initialTimeRef.current * 100
156
+ (initialTimeRef.current - timeRef.current) / initialTimeRef.current * 100
158
157
  )
159
158
  );
160
159
  }, [formattedTime]);
161
- const decomposedTime = useMemo(() => decompose(time), [formattedTime]);
162
- const { hours, minutes, seconds, milliseconds } = decomposedTime;
163
160
  const clearTimer = useCallback(() => {
164
161
  if (timerIdRef.current !== null) {
165
162
  clearInterval(timerIdRef.current);
@@ -330,7 +327,7 @@ function useTimer(initialTimeMs, options = {}) {
330
327
  if (status === "running" && loop && timerIdRef.current === null && rafIdRef.current === null) {
331
328
  startTimerLoop();
332
329
  }
333
- }, [status, loop, time, startTimerLoop]);
330
+ }, [status, loop, formattedTime, startTimerLoop]);
334
331
  useEffect(() => {
335
332
  let hiddenTime = 0;
336
333
  const handleVisibilityChange = () => {
@@ -365,9 +362,7 @@ function useTimer(initialTimeMs, options = {}) {
365
362
  }, [clearTimer]);
366
363
  return {
367
364
  // State
368
- time,
369
- initialTime: initialTimeRef.current,
370
- formattedTime,
365
+ time: formattedTime,
371
366
  progress,
372
367
  status,
373
368
  // Derived state
@@ -375,11 +370,6 @@ function useTimer(initialTimeMs, options = {}) {
375
370
  isPaused,
376
371
  isFinished,
377
372
  isIdle,
378
- // Decomposed time
379
- hours,
380
- minutes,
381
- seconds,
382
- milliseconds,
383
373
  // Controls
384
374
  start,
385
375
  pause,
@@ -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 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 { 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.time}</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 // 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 - timeRef.current) / initialTimeRef.current) *\n 100\n )\n );\n // formattedTime is the trigger for re-render\n }, [formattedTime]);\n\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, formattedTime, 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: formattedTime,\n progress,\n status,\n\n // Derived state\n isRunning,\n isPaused,\n isFinished,\n isIdle,\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,SAAS,aAAa,WAAW,SAAS,QAAQ,gBAAgB;;;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;;;AF1BO,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,uBAAuB;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,IAAI;AAAA,IAAS,MACjD,qBAAqB,eAAe;AAAA,EACtC;AACA,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAAsB,MAChD,oBAAoB,IAAI,aAAa;AAAA,EACvC;AAIA,QAAM,UAAU,OAAO,eAAe;AACtC,QAAM,uBAAuB;AAAA,IAC3B,qBAAqB,eAAe;AAAA,EACtC;AAGA,QAAM,aAAa,OAAsB,IAAI;AAC7C,QAAM,WAAW,OAAsB,IAAI;AAG3C,QAAM,kBAAkB,OAAe,CAAC;AACxC,QAAM,mBAAmB,OAAO,eAAe;AAG/C,QAAM,iBAAiB,OAAO,eAAe;AAG7C,QAAM,wBAAwB,OAAO,KAAK;AAG1C,QAAM,uBAAuB,OAAO,eAAe;AAGnD,QAAM,gBAAgB,OAAO,UAAU;AACvC,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,YAAY,OAAO,MAAM;AAG/B,QAAM,UAAU,OAAO,IAAI;AAC3B,QAAM,cAAc,OAAO,QAAQ;AACnC,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,YAAY,OAAO,MAAM;AAG/B,YAAU,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,YAAU,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,aAAa;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,WAAW,QAAQ,MAAM;AAC7B,QAAI,eAAe,YAAY,EAAG,QAAO;AACzC,WAAO,KAAK;AAAA,MACV;AAAA,MACA,KAAK;AAAA,QACH;AAAA,SACE,eAAe,UAAU,QAAQ,WAAW,eAAe,UAC3D;AAAA,MACJ;AAAA,IACF;AAAA,EAEF,GAAG,CAAC,aAAa,CAAC;AAIlB,QAAM,aAAa,YAAY,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,iBAAiB,YAAY,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,OAAO,YAAY,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,UAAU;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,iBAAiB,YAAY,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,QAAQ,YAAY,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,QAAQ,YAAY,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,OAAO,YAAY,MAAM;AAC7B,eAAW;AACX,cAAU,MAAM;AAChB,cAAU,UAAU;AAAA,EACtB,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,YAAY,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,UAAU,YAAY,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,SAAS,YAAY,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,UAAU;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,eAAe;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,eAAe;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,YAAU,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,YAAU,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,YAAU,MAAM;AACd,QACE,WAAW,aACX,QACA,WAAW,YAAY,QACvB,SAAS,YAAY,MACrB;AAEA,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,eAAe,cAAc,CAAC;AAGhD,YAAU,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,YAAU,MAAM;AACd,WAAO,MAAM;AACX,iBAAW;AAAA,IACb;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAGf,SAAO;AAAA;AAAA,IAEL,MAAM;AAAA,IACN;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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-timer",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
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",