@use-raf/timer 0.0.2

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/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @use-raf/timer
2
+
3
+ A React hook for creating frame-synchronized timers using `requestAnimationFrame`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @use-raf/timer
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { useRafTimer } from '@use-raf/timer'
15
+
16
+ const Demo = () => {
17
+ const { isActive, elapsed, start, stop, reset } = useRafTimer({
18
+ duration: 5000, // 5 seconds
19
+ onComplete: () => console.log('Timer completed!'),
20
+ })
21
+
22
+ return (
23
+ <div>
24
+ <p>Elapsed: {Math.floor(elapsed)}ms</p>
25
+ <p>Status: {isActive ? 'Running' : 'Stopped'}</p>
26
+ <button onClick={start} disabled={isActive}>Start</button>
27
+ <button onClick={stop} disabled={!isActive}>Stop</button>
28
+ <button onClick={reset}>Reset</button>
29
+ </div>
30
+ )
31
+ }
32
+ ```
33
+
34
+ With throttled updates:
35
+
36
+ ```tsx
37
+ const Demo = () => {
38
+ const { elapsed } = useRafTimer({
39
+ duration: 10000,
40
+ throttle: 100, // Update every 100ms
41
+ onUpdate: (elapsed) => {
42
+ console.log(`Elapsed: ${elapsed}ms`)
43
+ },
44
+ })
45
+ }
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### `useRafTimer(options)`
51
+
52
+ **Parameters:**
53
+
54
+ - `duration: number` - Timer duration in milliseconds
55
+ - `immediate?: boolean` - Start timer on mount (default: `false`)
56
+ - `throttle?: number` - Minimum milliseconds between updates (default: `0`)
57
+ - `onUpdate?: (elapsed: number) => void` - Called on each frame with elapsed time
58
+ - `onComplete?: () => void` - Called when timer completes
59
+
60
+ **Returns:**
61
+
62
+ - `isActive: boolean` - Whether the timer is running
63
+ - `elapsed: number` - Current elapsed time in milliseconds
64
+ - `start: () => void` - Start the timer
65
+ - `stop: () => void` - Pause the timer
66
+ - `reset: () => void` - Stop and reset elapsed time to 0
67
+ - `sink: () => void` - Stop the timer and trigger `onComplete`
68
+
69
+ ## Combining with `useProgress`
70
+
71
+ The `useProgress` hook calculates normalized progress (0-1) from elapsed time:
72
+
73
+ ```tsx
74
+ import { useRafTimer, useProgress } from '@use-raf/timer'
75
+
76
+ const ProgressDemo = () => {
77
+ const duration = 5000
78
+
79
+ const { elapsed, start, stop, reset } = useRafTimer({ duration })
80
+ const { progress } = useProgress({
81
+ elapsed,
82
+ duration,
83
+ precision: 2, // Round to 2 decimal places
84
+ })
85
+
86
+ return (
87
+ <div>
88
+ <div style={{ width: `${progress * 100}%`, height: 20, background: 'blue' }} />
89
+ <p>Progress: {(progress * 100).toFixed(0)}%</p>
90
+ <button onClick={start}>Start</button>
91
+ <button onClick={stop}>Stop</button>
92
+ <button onClick={reset}>Reset</button>
93
+ </div>
94
+ )
95
+ }
96
+ ```
97
+
98
+ ### `useProgress(options)`
99
+
100
+ **Parameters:**
101
+
102
+ - `elapsed: number` - Current elapsed time in milliseconds
103
+ - `duration: number` - Total duration in milliseconds
104
+ - `precision?: number` - Number of decimal places to round to (default: `4`)
105
+
106
+ **Returns:**
107
+
108
+ - `progress: number` - Progress value between 0 and 1
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Options for configuring the progress calculation.
3
+ */
4
+ interface ProgressProps {
5
+ /** Current elapsed time in milliseconds */
6
+ elapsed: number;
7
+ /** Total duration in milliseconds */
8
+ duration: number;
9
+ /** Number of decimal places to round to (default: 4) */
10
+ precision?: number;
11
+ }
12
+ /**
13
+ * Return value of the useProgress hook.
14
+ */
15
+ interface ProgressReturn {
16
+ /** Progress value between 0 and 1 */
17
+ progress: number;
18
+ }
19
+ /**
20
+ * A React hook that calculates normalized progress (0-1) from elapsed time and duration.
21
+ *
22
+ * @param options - Configuration options for progress calculation
23
+ * @returns Object containing the normalized progress value
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * const { progress } = useProgress({
28
+ * elapsed: 2500,
29
+ * duration: 5000,
30
+ * })
31
+ * // progress === 0.5
32
+ * ```
33
+ *
34
+ * @example
35
+ * With custom precision:
36
+ * ```tsx
37
+ * const { progress } = useProgress({
38
+ * elapsed: 1234,
39
+ * duration: 5000,
40
+ * precision: 2,
41
+ * })
42
+ * // progress === 0.24
43
+ * ```
44
+ */
45
+ declare const useProgress: ({ elapsed, duration, precision, }: ProgressProps) => ProgressReturn;
46
+
47
+ interface RafTimerProps {
48
+ /**
49
+ * Timer duration in milliseconds.
50
+ */
51
+ duration: number;
52
+ /**
53
+ * Whether to start the timer immediately on mount.
54
+ * @default false
55
+ */
56
+ immediate?: boolean;
57
+ /**
58
+ * Minimum time in milliseconds between updates.
59
+ * Use 0 for no throttling (updates every frame).
60
+ * @default 0
61
+ */
62
+ throttle?: number;
63
+ /**
64
+ * Optional callback invoked on each frame with elapsed time.
65
+ */
66
+ onUpdate?: (elapsed: number) => void;
67
+ /**
68
+ * Optional callback invoked when the timer completes.
69
+ */
70
+ onComplete?: () => void;
71
+ }
72
+ interface RafTimerReturn {
73
+ /**
74
+ * Whether the timer is currently running.
75
+ */
76
+ isActive: boolean;
77
+ /**
78
+ * Current elapsed time in milliseconds.
79
+ */
80
+ elapsed: number;
81
+ /**
82
+ * Starts or resumes the timer.
83
+ * Safe to call multiple times.
84
+ */
85
+ start: () => void;
86
+ /**
87
+ * Pauses the timer.
88
+ * Safe to call multiple times.
89
+ */
90
+ stop: () => void;
91
+ /**
92
+ * Stops the timer and resets elapsed time to 0.
93
+ */
94
+ reset: () => void;
95
+ /**
96
+ * Stops the timer and triggers the onComplete callback.
97
+ */
98
+ sink: () => void;
99
+ }
100
+ /**
101
+ * A React hook for creating a frame-synchronized timer.
102
+ *
103
+ * Provides manual control over a timer with optional throttling and completion callbacks.
104
+ * The timer tracks elapsed time and can be started, stopped, or reset at any time.
105
+ *
106
+ * @param options - Configuration options for the timer behavior
107
+ * @returns Object with timer state and control functions
108
+ *
109
+ * @example
110
+ * ```tsx
111
+ * const { isActive, elapsed, start, stop, reset } = useRafTimer({
112
+ * duration: 5000,
113
+ * onComplete: () => console.log('Timer completed!')
114
+ * })
115
+ *
116
+ * // Start the timer
117
+ * start()
118
+ *
119
+ * // Stop when needed
120
+ * stop()
121
+ *
122
+ * // Reset to 0
123
+ * reset()
124
+ * ```
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * // With throttled updates
129
+ * useRafTimer({
130
+ * duration: 10000,
131
+ * throttle: 100, // Update every 100ms
132
+ * onUpdate: (elapsed) => {
133
+ * console.log(`Elapsed: ${elapsed}ms`)
134
+ * }
135
+ * })
136
+ * ```
137
+ */
138
+ declare const useRafTimer: ({ duration, immediate, throttle, onUpdate, onComplete, }: RafTimerProps) => RafTimerReturn;
139
+
140
+ export { type ProgressProps, type ProgressReturn, type RafTimerProps, type RafTimerReturn, useProgress, useRafTimer };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{useRafLoop as N}from"@use-raf/loop";import{setTimeoutFrame as C}from"@use-raf/skd";import{useCallback as f,useEffect as g,useRef as b,useState as x}from"react";var F=({duration:r,immediate:e=!1,throttle:t=0,onUpdate:s,onComplete:m})=>{let n=b({onUpdate:s,onComplete:m});g(()=>{n.current={onUpdate:s,onComplete:m}});let u=b(0),[T,E]=x(u.current),p=f(o=>{u.current=o,E(o),n.current.onUpdate?.(o)},[]),d=b(),i=f((o,P)=>{let c=u.current+P;p(c),d.current=o},[p]),[l,M]=x(!1),{stop:a,start:k}=N(i,{immediate:e,throttle:t,setActive:M}),R=f(()=>{a(),n.current.onComplete?.()},[a]);g(()=>{if(!l)return;let o=r-u.current;return C(c=>{let v=d.current||c,A=c-v;i(c,A),R()},o)},[l,r,R,i]);let h=f(()=>{a(),p(0)},[a,p]);return{isActive:l,elapsed:T,start:k,stop:a,reset:h,sink:R}};import{useMemo as I}from"react";var[S,_]=[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],y=(r=S,e=_)=>t=>Math.max(r,Math.min(t,e)),L=y(0,1),z=(r,e=0)=>{let s=10**Math.max(0,e-e%1);return Math.floor(r*s)/s},G=({elapsed:r,duration:e,precision:t=4})=>({progress:I(()=>{if(e===0)return 1;let m=r/e,n=L(m);return z(n,t)},[r,e,t])});export{G as useProgress,F as useRafTimer};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/raf-timer.hook.ts","../src/progress.hook.ts"],"sourcesContent":["import type { RafLoopCallback } from '@use-raf/loop'\nimport { useRafLoop } from '@use-raf/loop'\nimport { setTimeoutFrame } from '@use-raf/skd'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nexport interface RafTimerProps {\n /**\n * Timer duration in milliseconds.\n */\n duration: number\n /**\n * Whether to start the timer immediately on mount.\n * @default false\n */\n immediate?: boolean\n /**\n * Minimum time in milliseconds between updates.\n * Use 0 for no throttling (updates every frame).\n * @default 0\n */\n throttle?: number\n /**\n * Optional callback invoked on each frame with elapsed time.\n */\n onUpdate?: (elapsed: number) => void\n /**\n * Optional callback invoked when the timer completes.\n */\n onComplete?: () => void\n}\n\nexport interface RafTimerReturn {\n /**\n * Whether the timer is currently running.\n */\n isActive: boolean\n /**\n * Current elapsed time in milliseconds.\n */\n elapsed: number\n /**\n * Starts or resumes the timer.\n * Safe to call multiple times.\n */\n start: () => void\n /**\n * Pauses the timer.\n * Safe to call multiple times.\n */\n stop: () => void\n /**\n * Stops the timer and resets elapsed time to 0.\n */\n reset: () => void\n /**\n * Stops the timer and triggers the onComplete callback.\n */\n sink: () => void\n}\n\n/**\n * A React hook for creating a frame-synchronized timer.\n *\n * Provides manual control over a timer with optional throttling and completion callbacks.\n * The timer tracks elapsed time and can be started, stopped, or reset at any time.\n *\n * @param options - Configuration options for the timer behavior\n * @returns Object with timer state and control functions\n *\n * @example\n * ```tsx\n * const { isActive, elapsed, start, stop, reset } = useRafTimer({\n * duration: 5000,\n * onComplete: () => console.log('Timer completed!')\n * })\n *\n * // Start the timer\n * start()\n *\n * // Stop when needed\n * stop()\n *\n * // Reset to 0\n * reset()\n * ```\n *\n * @example\n * ```tsx\n * // With throttled updates\n * useRafTimer({\n * duration: 10000,\n * throttle: 100, // Update every 100ms\n * onUpdate: (elapsed) => {\n * console.log(`Elapsed: ${elapsed}ms`)\n * }\n * })\n * ```\n */\nexport const useRafTimer = ({\n duration,\n immediate = false,\n throttle = 0,\n onUpdate,\n onComplete,\n}: RafTimerProps): RafTimerReturn => {\n const callbacksRef = useRef({ onUpdate, onComplete })\n useEffect(() => {\n callbacksRef.current = { onUpdate, onComplete }\n })\n\n const elapsedRef = useRef(0)\n const [elapsed, setElapsedState] = useState(elapsedRef.current)\n const setElapsed = useCallback((v: number) => {\n elapsedRef.current = v\n setElapsedState(v)\n callbacksRef.current.onUpdate?.(v)\n }, [])\n\n const frameTimestamp = useRef<number>()\n const onFrame = useCallback<RafLoopCallback>(\n (timestamp, delta) => {\n const elapsed = elapsedRef.current + delta\n setElapsed(elapsed)\n frameTimestamp.current = timestamp\n },\n [setElapsed],\n )\n const [isActive, setActive] = useState(false)\n const { stop, start } = useRafLoop(onFrame, { immediate, throttle, setActive })\n\n const finalize = useCallback(() => {\n stop()\n callbacksRef.current.onComplete?.()\n }, [stop])\n\n useEffect(() => {\n if (!isActive) {\n return\n }\n\n const left = duration - elapsedRef.current\n const cancel = setTimeoutFrame((timestamp) => {\n const last = frameTimestamp.current || timestamp\n const delta = timestamp - last\n onFrame(timestamp, delta)\n finalize()\n }, left)\n\n return cancel\n }, [isActive, duration, finalize, onFrame])\n\n const reset = useCallback(() => {\n stop()\n setElapsed(0)\n }, [stop, setElapsed])\n\n return {\n isActive,\n elapsed,\n start,\n stop,\n reset,\n sink: finalize,\n }\n}\n","import { useMemo } from 'react'\n\nconst [MIN, MAX] = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]\n\nconst clamp =\n (left = MIN, right = MAX) =>\n (x: number) =>\n Math.max(left, Math.min(x, right))\nconst normalize = clamp(0, 1)\n\nconst trim = (x: number, precision = 0) => {\n const p = Math.max(0, precision - (precision % 1))\n const mult = 10 ** p\n return Math.floor(x * mult) / mult\n}\n\n/**\n * Options for configuring the progress calculation.\n */\nexport interface ProgressProps {\n /** Current elapsed time in milliseconds */\n elapsed: number\n /** Total duration in milliseconds */\n duration: number\n /** Number of decimal places to round to (default: 4) */\n precision?: number\n}\n\n/**\n * Return value of the useProgress hook.\n */\nexport interface ProgressReturn {\n /** Progress value between 0 and 1 */\n progress: number\n}\n\n/**\n * A React hook that calculates normalized progress (0-1) from elapsed time and duration.\n *\n * @param options - Configuration options for progress calculation\n * @returns Object containing the normalized progress value\n *\n * @example\n * ```tsx\n * const { progress } = useProgress({\n * elapsed: 2500,\n * duration: 5000,\n * })\n * // progress === 0.5\n * ```\n *\n * @example\n * With custom precision:\n * ```tsx\n * const { progress } = useProgress({\n * elapsed: 1234,\n * duration: 5000,\n * precision: 2,\n * })\n * // progress === 0.24\n * ```\n */\nexport const useProgress = ({\n elapsed,\n duration,\n precision = 4,\n}: ProgressProps): ProgressReturn => {\n const progress = useMemo(() => {\n if (duration === 0) {\n return 1\n }\n const t = elapsed / duration\n const p = normalize(t)\n\n return trim(p, precision)\n }, [elapsed, duration, precision])\n\n return { progress }\n}\n"],"mappings":"AACA,OAAS,cAAAA,MAAkB,gBAC3B,OAAS,mBAAAC,MAAuB,eAChC,OAAS,eAAAC,EAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QA+FlD,IAAMC,EAAc,CAAC,CAC1B,SAAAC,EACA,UAAAC,EAAY,GACZ,SAAAC,EAAW,EACX,SAAAC,EACA,WAAAC,CACF,IAAqC,CACnC,IAAMC,EAAeR,EAAO,CAAE,SAAAM,EAAU,WAAAC,CAAW,CAAC,EACpDR,EAAU,IAAM,CACdS,EAAa,QAAU,CAAE,SAAAF,EAAU,WAAAC,CAAW,CAChD,CAAC,EAED,IAAME,EAAaT,EAAO,CAAC,EACrB,CAACU,EAASC,CAAe,EAAIV,EAASQ,EAAW,OAAO,EACxDG,EAAad,EAAae,GAAc,CAC5CJ,EAAW,QAAUI,EACrBF,EAAgBE,CAAC,EACjBL,EAAa,QAAQ,WAAWK,CAAC,CACnC,EAAG,CAAC,CAAC,EAECC,EAAiBd,EAAe,EAChCe,EAAUjB,EACd,CAACkB,EAAWC,IAAU,CACpB,IAAMP,EAAUD,EAAW,QAAUQ,EACrCL,EAAWF,CAAO,EAClBI,EAAe,QAAUE,CAC3B,EACA,CAACJ,CAAU,CACb,EACM,CAACM,EAAUC,CAAS,EAAIlB,EAAS,EAAK,EACtC,CAAE,KAAAmB,EAAM,MAAAC,CAAM,EAAIzB,EAAWmB,EAAS,CAAE,UAAAX,EAAW,SAAAC,EAAU,UAAAc,CAAU,CAAC,EAExEG,EAAWxB,EAAY,IAAM,CACjCsB,EAAK,EACLZ,EAAa,QAAQ,aAAa,CACpC,EAAG,CAACY,CAAI,CAAC,EAETrB,EAAU,IAAM,CACd,GAAI,CAACmB,EACH,OAGF,IAAMK,EAAOpB,EAAWM,EAAW,QAQnC,OAPeZ,EAAiBmB,GAAc,CAC5C,IAAMQ,EAAOV,EAAe,SAAWE,EACjCC,EAAQD,EAAYQ,EAC1BT,EAAQC,EAAWC,CAAK,EACxBK,EAAS,CACX,EAAGC,CAAI,CAGT,EAAG,CAACL,EAAUf,EAAUmB,EAAUP,CAAO,CAAC,EAE1C,IAAMU,EAAQ3B,EAAY,IAAM,CAC9BsB,EAAK,EACLR,EAAW,CAAC,CACd,EAAG,CAACQ,EAAMR,CAAU,CAAC,EAErB,MAAO,CACL,SAAAM,EACA,QAAAR,EACA,MAAAW,EACA,KAAAD,EACA,MAAAK,EACA,KAAMH,CACR,CACF,ECpKA,OAAS,WAAAI,MAAe,QAExB,GAAM,CAACC,EAAKC,CAAG,EAAI,CAAC,OAAO,iBAAkB,OAAO,gBAAgB,EAE9DC,EACJ,CAACC,EAAOH,EAAKI,EAAQH,IACpBI,GACC,KAAK,IAAIF,EAAM,KAAK,IAAIE,EAAGD,CAAK,CAAC,EAC/BE,EAAYJ,EAAM,EAAG,CAAC,EAEtBK,EAAO,CAACF,EAAWG,EAAY,IAAM,CAEzC,IAAMC,EAAO,IADH,KAAK,IAAI,EAAGD,EAAaA,EAAY,CAAE,EAEjD,OAAO,KAAK,MAAMH,EAAII,CAAI,EAAIA,CAChC,EAgDaC,EAAc,CAAC,CAC1B,QAAAC,EACA,SAAAC,EACA,UAAAJ,EAAY,CACd,KAWS,CAAE,SAVQT,EAAQ,IAAM,CAC7B,GAAIa,IAAa,EACf,MAAO,GAET,IAAMC,EAAIF,EAAUC,EACdE,EAAIR,EAAUO,CAAC,EAErB,OAAON,EAAKO,EAAGN,CAAS,CAC1B,EAAG,CAACG,EAASC,EAAUJ,CAAS,CAAC,CAEf","names":["useRafLoop","setTimeoutFrame","useCallback","useEffect","useRef","useState","useRafTimer","duration","immediate","throttle","onUpdate","onComplete","callbacksRef","elapsedRef","elapsed","setElapsedState","setElapsed","v","frameTimestamp","onFrame","timestamp","delta","isActive","setActive","stop","start","finalize","left","last","reset","useMemo","MIN","MAX","clamp","left","right","x","normalize","trim","precision","mult","useProgress","elapsed","duration","t","p"]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@use-raf/timer",
3
+ "version": "0.0.2",
4
+ "description": "React frame-synchronized timer hook",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts"
10
+ },
11
+ "publishConfig": {
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "sideEffects": false,
26
+ "keywords": [
27
+ "react",
28
+ "hook",
29
+ "requestAnimationFrame",
30
+ "raf",
31
+ "animation",
32
+ "interval",
33
+ "timer",
34
+ "schedule",
35
+ "setTimer",
36
+ "progress",
37
+ "countdown"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "lint": "biome check",
44
+ "lint:fix": "biome check --write"
45
+ },
46
+ "peerDependencies": {
47
+ "react": ">=16.14"
48
+ },
49
+ "dependencies": {
50
+ "@use-raf/loop": "workspace:*",
51
+ "@use-raf/skd": "workspace:*"
52
+ },
53
+ "author": "einouqo",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/einouqo/use-raf"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/einouqo/use-raf/issues"
60
+ },
61
+ "license": "MIT"
62
+ }