@crup/react-timer-hook 0.0.1-alpha.10 → 0.0.1-alpha.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @crup/react-timer-hook
2
2
 
3
- > React timer hooks for countdowns, stopwatches, clocks, polling schedules, and many independent timer lifecycles.
3
+ > A lightweight React hooks library for building timers, stopwatches, and real-time clocks with minimal boilerplate.
4
4
 
5
5
  [![npm alpha](https://img.shields.io/npm/v/%40crup%2Freact-timer-hook/alpha?label=npm%20alpha&color=00b894)](https://www.npmjs.com/package/@crup/react-timer-hook?activeTab=versions)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/%40crup%2Freact-timer-hook?color=0f766e)](https://www.npmjs.com/package/@crup/react-timer-hook)
@@ -17,11 +17,13 @@
17
17
 
18
18
  Timer hooks look simple until real apps need pause/resume semantics, Strict Mode cleanup, async callbacks, polling that does not overlap, and lists with dozens of independent timers.
19
19
 
20
- `@crup/react-timer-hook` keeps the API small and lets your app decide what time means:
20
+ `@crup/react-timer-hook` starts with a ~1.2 kB timer core and lets your app compose the heavier pieces only when it needs them:
21
21
 
22
- - ⏱️ `useTimer()` for one lifecycle: stopwatch, countdown, clock, schedule, or custom flow.
23
- - 🧭 `useTimerGroup()` for many keyed lifecycles with one shared scheduler.
24
- - 🧩 `durationParts()` for display math without locale or timezone opinions.
22
+ - ⏱️ `useTimer()` from the root package for one lifecycle: stopwatch, countdown, clock, or custom flow.
23
+ - 🔋 Batteries are optional: schedules, timer groups, duration helpers, and diagnostics live in subpath imports.
24
+ - 🧭 `useTimerGroup()` from `/group` for many keyed lifecycles with one shared scheduler.
25
+ - 📡 `useScheduledTimer()` from `/schedules` for polling, overdue timing context, and opt-in diagnostics.
26
+ - 🧩 `durationParts()` from `/duration` for display math without locale or timezone opinions.
25
27
  - 🧼 No formatting, timezone, audio, retry, cache, or data-fetching policy baked in.
26
28
  - 🧪 Built for rerenders, Strict Mode, async callbacks, cleanup, and many timers.
27
29
  - 🤖 Agent-friendly docs through hosted `llms.txt`, `llms-full.txt`, and an optional MCP docs helper.
@@ -36,7 +38,10 @@ pnpm add @crup/react-timer-hook@alpha
36
38
  ```
37
39
 
38
40
  ```tsx
39
- import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
41
+ import { useTimer } from '@crup/react-timer-hook';
42
+ import { durationParts } from '@crup/react-timer-hook/duration';
43
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
44
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
40
45
  ```
41
46
 
42
47
  ## Live recipes
@@ -99,7 +104,9 @@ export function AuctionTimer({ auctionId, expiresAt }: {
99
104
  Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
100
105
 
101
106
  ```tsx
102
- const timer = useTimer({
107
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
108
+
109
+ const timer = useScheduledTimer({
103
110
  autoStart: true,
104
111
  updateIntervalMs: 1000,
105
112
  endWhen: snapshot => snapshot.now >= expiresAt,
@@ -123,6 +130,8 @@ const timer = useTimer({
123
130
  Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
124
131
 
125
132
  ```tsx
133
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
134
+
126
135
  const timers = useTimerGroup({
127
136
  updateIntervalMs: 1000,
128
137
  items: auctions.map(auction => ({
@@ -134,15 +143,96 @@ const timers = useTimerGroup({
134
143
  });
135
144
  ```
136
145
 
146
+ ## API tables
147
+
148
+ ### `useTimer()` settings
149
+
150
+ | Key | Type | Required | Description |
151
+ | --- | --- | --- | --- |
152
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
153
+ | `updateIntervalMs` | `number` | No | Render/update cadence in milliseconds. Defaults to `1000`. This does not define elapsed time; elapsed time is calculated from timestamps. Use a smaller value like `100` or `20` when the UI needs finer updates. |
154
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. Use this for countdowns, timeouts, and custom stop conditions. |
155
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. `restart()` creates a new generation. |
156
+
157
+ ### `useScheduledTimer()` settings
158
+
159
+ Import from `@crup/react-timer-hook/schedules` when you need polling or scheduled side effects.
160
+
161
+ | Key | Type | Required | Description |
162
+ | --- | --- | --- | --- |
163
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
164
+ | `updateIntervalMs` | `number` | No | Render/update cadence in milliseconds. Defaults to `1000`. Scheduled callbacks can run on their own cadence. |
165
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. |
166
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. |
167
+ | `schedules` | `TimerSchedule[]` | No | Scheduled side effects that run while the timer is active. Async overlap defaults to `skip`. |
168
+ | `debug` | `TimerDebug` | No | Opt-in semantic diagnostics. No logs are emitted by default. |
169
+
170
+ ### `TimerSchedule`
171
+
172
+ | Key | Type | Required | Description |
173
+ | --- | --- | --- | --- |
174
+ | `id` | `string` | No | Stable identifier used in debug events and schedule context. Falls back to the array index. |
175
+ | `everyMs` | `number` | Yes | Schedule cadence in milliseconds. Must be positive and finite. |
176
+ | `leading` | `boolean` | No | Runs the schedule immediately when the timer starts or resumes into a new generation. Defaults to `false`. |
177
+ | `overlap` | `'skip' \| 'allow'` | No | Controls async overlap. Defaults to `skip`, so a pending callback prevents another run. |
178
+ | `callback` | `(snapshot, controls, context) => void \| Promise<void>` | Yes | Scheduled side effect. Receives timing context with `scheduledAt`, `firedAt`, `nextRunAt`, `overdueCount`, and `effectiveEveryMs`. |
179
+
180
+ ### `useTimerGroup()` settings
181
+
182
+ Import from `@crup/react-timer-hook/group` when many keyed items need independent lifecycle control.
183
+
184
+ | Key | Type | Required | Description |
185
+ | --- | --- | --- | --- |
186
+ | `updateIntervalMs` | `number` | No | Shared scheduler cadence for the group. Defaults to `1000`. |
187
+ | `items` | `TimerGroupItem[]` | No | Initial/synced timer item definitions. Each item has its own lifecycle state. |
188
+ | `debug` | `TimerDebug` | No | Opt-in semantic diagnostics for group lifecycle and schedule events. |
189
+
190
+ ### `TimerGroupItem`
191
+
192
+ | Key | Type | Required | Description |
193
+ | --- | --- | --- | --- |
194
+ | `id` | `string` | Yes | Stable key for the item. Duplicate IDs throw. |
195
+ | `autoStart` | `boolean` | No | Starts the item automatically when it is added or synced. Defaults to `false`. |
196
+ | `endWhen` | `(snapshot) => boolean` | No | Ends that item when it returns `true`. |
197
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per item generation when that item ends naturally. |
198
+ | `schedules` | `TimerSchedule[]` | No | Per-item schedules with the same contract as `useScheduledTimer()`. |
199
+
200
+ ### Values and controls
201
+
202
+ | Key | Type | Description |
203
+ | --- | --- | --- |
204
+ | `status` | `'idle' \| 'running' \| 'paused' \| 'ended' \| 'cancelled'` | Current lifecycle state. |
205
+ | `now` | `number` | Wall-clock timestamp from `Date.now()`. Use for clocks and absolute deadlines. |
206
+ | `tick` | `number` | Number of render/update ticks produced in the current generation. |
207
+ | `startedAt` | `number \| null` | Wall-clock timestamp when the current generation started. |
208
+ | `pausedAt` | `number \| null` | Wall-clock timestamp for the current pause, or `null`. |
209
+ | `endedAt` | `number \| null` | Wall-clock timestamp when `endWhen` ended the lifecycle. |
210
+ | `cancelledAt` | `number \| null` | Wall-clock timestamp when `cancel()` ended the lifecycle early. |
211
+ | `cancelReason` | `string \| null` | Optional reason passed to `cancel(reason)`. |
212
+ | `elapsedMilliseconds` | `number` | Active elapsed duration calculated from monotonic time, excluding paused time. |
213
+ | `isIdle` | `boolean` | Convenience flag for `status === 'idle'`. |
214
+ | `isRunning` | `boolean` | Convenience flag for `status === 'running'`. |
215
+ | `isPaused` | `boolean` | Convenience flag for `status === 'paused'`. |
216
+ | `isEnded` | `boolean` | Convenience flag for `status === 'ended'`. |
217
+ | `isCancelled` | `boolean` | Convenience flag for `status === 'cancelled'`. |
218
+ | `start()` | `function` | Starts an idle timer. No-op if it is already started. |
219
+ | `pause()` | `function` | Pauses a running timer. |
220
+ | `resume()` | `function` | Resumes a paused timer from the paused elapsed value. |
221
+ | `reset(options?)` | `function` | Resets to idle and zero elapsed time. Pass `{ autoStart: true }` to reset directly into running. |
222
+ | `restart()` | `function` | Starts a new running generation from zero elapsed time. |
223
+ | `cancel(reason?)` | `function` | Terminal early stop. Does not call `onEnd`. |
224
+
137
225
  ## Bundle size
138
226
 
139
- Current build:
227
+ The core import stays small. Extra capabilities are opt-in batteries.
140
228
 
141
- | File | Raw | Gzip | Brotli |
229
+ | Entry | Raw | Gzip | Brotli |
142
230
  | --- | ---: | ---: | ---: |
143
- | `dist/index.js` | 12.80 kB | 3.88 kB | 3.47 kB |
144
- | `dist/index.cjs` | 14.04 kB | 4.12 kB | 3.70 kB |
145
- | `dist/index.d.ts` | 4.32 kB | 1.04 kB | 951 B |
231
+ | core | 3.82 kB | 1.31 kB | 1.21 kB |
232
+ | timer group add-on | 8.94 kB | 2.97 kB | 2.70 kB |
233
+ | schedules add-on | 6.88 kB | 2.32 kB | 2.13 kB |
234
+ | duration helper | 318 B | 224 B | 192 B |
235
+ | diagnostics helper | 105 B | 115 B | 99 B |
146
236
 
147
237
  CI writes a size summary to the GitHub Actions UI and posts bundle-size reports on pull requests.
148
238
 
@@ -0,0 +1 @@
1
+ function n(e){return e?e===!0?{enabled:!0,includeTicks:!1,logger:console.debug}:typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger??console.debug}:{enabled:!1,includeTicks:!1}}function r(e,i){let l=n(e);!l.enabled||!l.logger||i.type==="timer:tick"&&!l.includeTicks||l.logger({...i,label:i.label??l.label})}function t(e,i){return{generation:i,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}export{r as a,t as b};
@@ -0,0 +1 @@
1
+ function r(){let n=Date.now(),e=typeof performance<"u"&&typeof performance.now=="function"?performance.now():n;return{wallNow:n,monotonicNow:e}}function a(n,e){if(!Number.isFinite(n)||n<=0)throw new RangeError(`${e} must be a finite number greater than 0`)}function i(n){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:n.wallNow}}function l(n,e){return n.status!=="running"||n.activeStartedAtMonotonic===null?n.baseElapsedMilliseconds:Math.max(0,n.baseElapsedMilliseconds+e.monotonicNow-n.activeStartedAtMonotonic)}function d(n,e){let t=l(n,e);return{status:n.status,now:e.wallNow,tick:n.tick,startedAt:n.startedAt,pausedAt:n.pausedAt,endedAt:n.endedAt,cancelledAt:n.cancelledAt,cancelReason:n.cancelReason,elapsedMilliseconds:t,isIdle:n.status==="idle",isRunning:n.status==="running",isPaused:n.status==="paused",isEnded:n.status==="ended",isCancelled:n.status==="cancelled"}}function c(n,e){return n.status!=="idle"?!1:(n.status="running",n.startedAt=e.wallNow,n.pausedAt=null,n.endedAt=null,n.cancelledAt=null,n.cancelReason=null,n.activeStartedAtMonotonic=e.monotonicNow,n.now=e.wallNow,!0)}function s(n,e){return n.status!=="running"?!1:(n.baseElapsedMilliseconds=l(n,e),n.activeStartedAtMonotonic=null,n.status="paused",n.pausedAt=e.wallNow,n.now=e.wallNow,!0)}function m(n,e){return n.status!=="paused"?!1:(n.status="running",n.pausedAt=null,n.activeStartedAtMonotonic=e.monotonicNow,n.now=e.wallNow,!0)}function o(n,e,t={}){return n.generation+=1,n.tick=0,n.status=t.autoStart?"running":"idle",n.startedAt=t.autoStart?e.wallNow:null,n.pausedAt=null,n.endedAt=null,n.cancelledAt=null,n.cancelReason=null,n.baseElapsedMilliseconds=0,n.activeStartedAtMonotonic=t.autoStart?e.monotonicNow:null,n.now=e.wallNow,!0}function w(n,e){return o(n,e,{autoStart:!0})}function p(n,e,t){return n.status==="ended"||n.status==="cancelled"?!1:(n.baseElapsedMilliseconds=l(n,e),n.activeStartedAtMonotonic=null,n.status="cancelled",n.cancelledAt=e.wallNow,n.cancelReason=t??null,n.now=e.wallNow,!0)}function S(n,e){return n.status!=="running"?!1:(n.baseElapsedMilliseconds=l(n,e),n.activeStartedAtMonotonic=null,n.status="ended",n.endedAt=e.wallNow,n.now=e.wallNow,!0)}function A(n,e){return n.status!=="running"?!1:(n.tick+=1,n.now=e.wallNow,!0)}export{r as a,a as b,i as c,d,c as e,s as f,m as g,o as h,w as i,p as j,S as k,A as l};
@@ -0,0 +1 @@
1
+ "use strict";var t=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var n=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var b=(r,e)=>{for(var o in e)t(r,o,{get:e[o],enumerable:!0})},T=(r,e,o,g)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of n(e))!u.call(r,i)&&i!==o&&t(r,i,{get:()=>e[i],enumerable:!(g=m(e,i))||g.enumerable});return r};var s=r=>T(t({},"__esModule",{value:!0}),r);var l={};b(l,{consoleTimerDiagnostics:()=>D});module.exports=s(l);function D(r={}){return{...r,logger:e=>console.debug("[timer]",e)}}0&&(module.exports={consoleTimerDiagnostics});
@@ -0,0 +1,9 @@
1
+ import { i as TimerDebug } from './types-4uFJF2Fx.cjs';
2
+ export { j as TimerDebugEvent, k as TimerDebugLogger } from './types-4uFJF2Fx.cjs';
3
+
4
+ declare function consoleTimerDiagnostics(options?: {
5
+ includeTicks?: boolean;
6
+ label?: string;
7
+ }): TimerDebug;
8
+
9
+ export { TimerDebug, consoleTimerDiagnostics };
@@ -0,0 +1,9 @@
1
+ import { i as TimerDebug } from './types-4uFJF2Fx.js';
2
+ export { j as TimerDebugEvent, k as TimerDebugLogger } from './types-4uFJF2Fx.js';
3
+
4
+ declare function consoleTimerDiagnostics(options?: {
5
+ includeTicks?: boolean;
6
+ label?: string;
7
+ }): TimerDebug;
8
+
9
+ export { TimerDebug, consoleTimerDiagnostics };
@@ -0,0 +1 @@
1
+ function i(e={}){return{...e,logger:r=>console.debug("[timer]",r)}}export{i as consoleTimerDiagnostics};
@@ -0,0 +1 @@
1
+ "use strict";var a=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var l=(o,t)=>{for(var s in t)a(o,s,{get:t[s],enumerable:!0})},E=(o,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of D(t))!N.call(o,r)&&r!==s&&a(o,r,{get:()=>t[r],enumerable:!(n=f(t,r))||n.enumerable});return o};var O=o=>E(a({},"__esModule",{value:!0}),o);var h={};l(h,{durationParts:()=>i});module.exports=O(h);function i(o){let t=Math.max(0,Math.trunc(Number.isFinite(o)?o:0)),s=Math.floor(t/864e5),n=t%864e5,r=Math.floor(n/36e5),e=n%36e5,u=Math.floor(e/6e4),c=e%6e4,M=Math.floor(c/1e3);return{totalMilliseconds:t,totalSeconds:Math.floor(t/1e3),milliseconds:c%1e3,seconds:M,minutes:u,hours:r,days:s}}0&&(module.exports={durationParts});
@@ -0,0 +1,5 @@
1
+ import { D as DurationParts } from './types-4uFJF2Fx.cjs';
2
+
3
+ declare function durationParts(milliseconds: number): DurationParts;
4
+
5
+ export { DurationParts, durationParts };
@@ -0,0 +1,5 @@
1
+ import { D as DurationParts } from './types-4uFJF2Fx.js';
2
+
3
+ declare function durationParts(milliseconds: number): DurationParts;
4
+
5
+ export { DurationParts, durationParts };
@@ -0,0 +1 @@
1
+ function u(o){let t=Math.max(0,Math.trunc(Number.isFinite(o)?o:0)),a=Math.floor(t/864e5),r=t%864e5,e=Math.floor(r/36e5),s=r%36e5,c=Math.floor(s/6e4),n=s%6e4,i=Math.floor(n/1e3);return{totalMilliseconds:t,totalSeconds:Math.floor(t/1e3),milliseconds:n%1e3,seconds:i,minutes:c,hours:e,days:a}}export{u as durationParts};
package/dist/group.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var O=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var Te=Object.prototype.hasOwnProperty;var Se=(e,o)=>{for(var s in o)O(e,s,{get:o[s],enumerable:!0})},be=(e,o,s,u)=>{if(o&&typeof o=="object"||typeof o=="function")for(let S of ge(o))!Te.call(e,S)&&S!==s&&O(e,S,{get:()=>o[S],enumerable:!(u=pe(o,S))||u.enumerable});return e};var we=e=>be(O({},"__esModule",{value:!0}),e);var ye={};Se(ye,{useTimerGroup:()=>de});module.exports=we(ye);var l=require("react");function Ie(e){return e?e===!0?{enabled:!0,includeTicks:!1,logger:console.debug}:typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger??console.debug}:{enabled:!1,includeTicks:!1}}function I(e,o){let s=Ie(e);!s.enabled||!s.logger||o.type==="timer:tick"&&!s.includeTicks||s.logger({...o,label:o.label??s.label})}function y(e,o){return{generation:o,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function m(){let e=Date.now(),o=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:o}}function F(e,o){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${o} must be a finite number greater than 0`)}function ne(e){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:e.wallNow}}function D(e,o){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+o.monotonicNow-e.activeStartedAtMonotonic)}function f(e,o){let s=D(e,o);return{status:e.status,now:o.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:s,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function P(e,o){return e.status!=="idle"?!1:(e.status="running",e.startedAt=o.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function oe(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=D(e,o),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=o.wallNow,e.now=o.wallNow,!0)}function ae(e,o){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function U(e,o,s={}){return e.generation+=1,e.tick=0,e.status=s.autoStart?"running":"idle",e.startedAt=s.autoStart?o.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=s.autoStart?o.monotonicNow:null,e.now=o.wallNow,!0}function le(e,o){return U(e,o,{autoStart:!0})}function ue(e,o,s){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=D(e,o),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=o.wallNow,e.cancelReason=s??null,e.now=o.wallNow,!0)}function se(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=D(e,o),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=o.wallNow,e.now=o.wallNow,!0)}function ie(e,o){return e.status!=="running"?!1:(e.tick+=1,e.now=o.wallNow,!0)}function de(e={}){let o=e.updateIntervalMs??1e3;F(o,"updateIntervalMs"),z(e.items);let s=(0,l.useRef)(e);s.current=e;let u=(0,l.useRef)(new Map),S=(0,l.useRef)(!1),h=(0,l.useRef)(null),[,p]=(0,l.useReducer)(t=>t+1,0),A=(0,l.useCallback)(()=>{h.current!==null&&(clearTimeout(h.current),h.current=null)},[]),L=(0,l.useCallback)((t,r=m())=>f(t.state,r),[]),c=(0,l.useCallback)((t,r,n,a={})=>{I(s.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...y(n,r?.state.generation??0),...a})},[]),x=(0,l.useCallback)(t=>({start:()=>k(t),pause:()=>v(t),resume:()=>M(t),reset:r=>R(t,r),restart:()=>G(t),cancel:r=>C(t,r)}),[]),j=(0,l.useCallback)((t,r)=>{let n=t.state.generation;if(t.endCalledGeneration!==n){t.endCalledGeneration=n;try{t.definition.onEnd?.(r,x(t.id))}catch(a){I(s.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:a,...y(r,n)})}}},[x]),$=(0,l.useCallback)((t,r,n,a,d,i,b)=>{if(a.pending&&(r.overlap??"skip")==="skip"){a.lastRunAt=b.scheduledAt,I(s.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,reason:"overlap",...b,...y(d,i)});return}a.lastRunAt=b.scheduledAt,a.pending=!0,I(s.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,...b,...y(d,i)}),Promise.resolve().then(()=>r.callback(d,x(t.id),b)).then(()=>{I(s.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,...b,...y(d,i)})},g=>{I(s.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,error:g,...b,...y(d,i)})}).finally(()=>{u.current.get(t.id)?.state.generation===i&&(a.pending=!1)})},[x]),K=(0,l.useCallback)((t,r,n=!1)=>{let a=t.definition.schedules??[],d=new Set;a.forEach((i,b)=>{let g=i.id??String(b);d.add(g);let T=t.schedules.get(g);if(T||(T={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(g,T)),n&&i.leading&&T.leadingGeneration!==t.state.generation){T.leadingGeneration=t.state.generation,$(t,i,g,T,r,t.state.generation,ce(i,g,r.now,r.now,0));return}T.lastRunAt===null&&(T.lastRunAt=t.state.startedAt??r.now);let E=Math.floor((r.now-T.lastRunAt)/i.everyMs);if(E>=1){let fe=T.lastRunAt+E*i.everyMs;$(t,i,g,T,r,t.state.generation,ce(i,g,fe,r.now,E-1))}});for(let i of t.schedules.keys())d.has(i)||t.schedules.delete(i)},[$]),w=(0,l.useCallback)((t,r=m(),n=!1)=>{if(t.state.status!=="running")return;let a=f(t.state,r);if(t.definition.endWhen?.(a)){if(se(t.state,r)){let d=f(t.state,r);c("timer:end",t,d),j(t,d)}return}K(t,a,n)},[j,c,K]),W=(0,l.useCallback)((t=m())=>{let n=s.current.updateIntervalMs??1e3;for(let a of u.current.values()){if(a.state.status!=="running")continue;(a.definition.schedules??[]).forEach((i,b)=>{let g=i.id??String(b),E=a.schedules.get(g)?.lastRunAt??t.wallNow;n=Math.min(n,Math.max(1,E+i.everyMs-t.wallNow))})}return n},[]),N=(0,l.useCallback)(t=>{let r=u.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let n={id:t.id,state:ne(m()),definition:t,schedules:new Map,endCalledGeneration:null};return u.current.set(t.id,n),t.autoStart&&P(n.state,m()),{item:n,added:!0}},[]),q=(0,l.useCallback)(()=>{let t=s.current.items??[],r=new Set,n=!1;t.forEach(a=>{r.add(a.id);let{item:d,added:i}=N(a);n=n||i,a.autoStart&&d.state.status==="idle"&&(n=P(d.state,m())||n)});for(let a of u.current.keys())r.has(a)||(u.current.delete(a),n=!0);return n},[N]);(0,l.useEffect)(()=>{q()&&p()},[q,e.items]);let B=(0,l.useCallback)(t=>{if(z([t]),u.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);N(t),p()},[N]),H=(0,l.useCallback)((t,r)=>{let n=u.current.get(t);if(!n)return;let a={...n.definition,...r,id:t};z([a]),n.definition=a,p()},[]),J=(0,l.useCallback)(t=>{u.current.delete(t),p()},[]),Q=(0,l.useCallback)(()=>{u.current.clear(),A(),p()},[A]),k=(0,l.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=m();P(r.state,n)&&(c("timer:start",r,f(r.state,n)),w(r,n,!0),p())},[c,w]),v=(0,l.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=m();oe(r.state,n)&&(c("timer:pause",r,f(r.state,n)),p())},[c]),M=(0,l.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=m();ae(r.state,n)&&(c("timer:resume",r,f(r.state,n)),w(r,n,!0),p())},[c,w]),R=(0,l.useCallback)((t,r={})=>{let n=u.current.get(t);if(!n)return;let a=m();U(n.state,a,r),n.schedules.clear(),n.endCalledGeneration=null,c("timer:reset",n,f(n.state,a)),r.autoStart&&w(n,a,!0),p()},[c,w]),G=(0,l.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=m();le(r.state,n),r.schedules.clear(),r.endCalledGeneration=null,c("timer:restart",r,f(r.state,n)),w(r,n,!0),p()},[c,w]),C=(0,l.useCallback)((t,r)=>{let n=u.current.get(t);if(!n)return;let a=m();ue(n.state,a,r)&&(c("timer:cancel",n,f(n.state,a),{reason:r}),p())},[c]),me=Array.from(u.current.keys()).map(t=>`${t}:${u.current.get(t).state.status}:${u.current.get(t).state.generation}:${u.current.get(t).state.tick}`).join("|");(0,l.useEffect)(()=>{S.current=!0;let t=Array.from(u.current.values()).filter(n=>n.state.status==="running");if(t.length===0){A();return}A();let r=t[0];return c("scheduler:start",r,f(r.state,m())),h.current=setTimeout(()=>{if(!S.current)return;let n=m();for(let a of u.current.values()){if(a.state.status!=="running")continue;ie(a.state,n);let d=f(a.state,n);c("timer:tick",a,d),w(a,n)}p()},W()),()=>{h.current!==null&&c("scheduler:stop",r,f(r.state,m())),A(),S.current=!1}},[me,A,c,W,w]);let V=(0,l.useCallback)(t=>{let r=u.current.get(t);if(r)return L(r)},[L]),X=m().wallNow,Y=(0,l.useCallback)(()=>Array.from(u.current.keys()).forEach(k),[k]),Z=(0,l.useCallback)(()=>Array.from(u.current.keys()).forEach(v),[v]),_=(0,l.useCallback)(()=>Array.from(u.current.keys()).forEach(M),[M]),ee=(0,l.useCallback)(t=>Array.from(u.current.keys()).forEach(r=>R(r,t)),[R]),te=(0,l.useCallback)(()=>Array.from(u.current.keys()).forEach(G),[G]),re=(0,l.useCallback)(t=>Array.from(u.current.keys()).forEach(r=>C(r,t)),[C]);return(0,l.useMemo)(()=>({now:X,size:u.current.size,ids:Array.from(u.current.keys()),get:V,add:B,update:H,remove:J,clear:Q,start:k,pause:v,resume:M,reset:R,restart:G,cancel:C,startAll:Y,pauseAll:Z,resumeAll:_,resetAll:ee,restartAll:te,cancelAll:re}),[B,C,re,Q,V,X,v,Z,J,R,ee,G,te,M,_,k,Y,H])}function z(e){let o=new Set;e?.forEach(s=>{if(o.has(s.id))throw new Error(`Duplicate timer item id "${s.id}"`);o.add(s.id),s.schedules?.forEach(u=>F(u.everyMs,"schedule.everyMs"))})}function ce(e,o,s,u,S){return{scheduleId:e.id??o,scheduledAt:s,firedAt:u,nextRunAt:s+e.everyMs,overdueCount:S,effectiveEveryMs:e.everyMs}}0&&(module.exports={useTimerGroup});
@@ -0,0 +1,6 @@
1
+ import { d as UseTimerGroupOptions, e as TimerGroupResult } from './types-4uFJF2Fx.cjs';
2
+ export { f as TimerGroupItem, g as TimerGroupItemControls } from './types-4uFJF2Fx.cjs';
3
+
4
+ declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
5
+
6
+ export { TimerGroupResult, UseTimerGroupOptions, useTimerGroup };
@@ -0,0 +1,6 @@
1
+ import { d as UseTimerGroupOptions, e as TimerGroupResult } from './types-4uFJF2Fx.js';
2
+ export { f as TimerGroupItem, g as TimerGroupItemControls } from './types-4uFJF2Fx.js';
3
+
4
+ declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
5
+
6
+ export { TimerGroupResult, UseTimerGroupOptions, useTimerGroup };
package/dist/group.js ADDED
@@ -0,0 +1 @@
1
+ import{a as h,b as T}from"./chunk-65MJVHTI.js";import{a as l,b as O,c as te,d,e as P,f as re,g as ne,h as se,i as ue,j as ae,k as oe,l as ie}from"./chunk-I3PNNG3I.js";import{useCallback as u,useEffect as ce,useMemo as fe,useReducer as pe,useRef as $}from"react";function ge(g={}){let S=g.updateIntervalMs??1e3;O(S,"updateIntervalMs"),U(g.items);let i=$(g);i.current=g;let s=$(new Map),v=$(!1),k=$(null),[,m]=pe(e=>e+1,0),G=u(()=>{k.current!==null&&(clearTimeout(k.current),k.current=null)},[]),z=u((e,t=l())=>d(e.state,t),[]),o=u((e,t,r,n={})=>{h(i.current.debug,{type:e,scope:"timer-group",timerId:t?.id,...T(r,t?.state.generation??0),...n})},[]),x=u(e=>({start:()=>A(e),pause:()=>b(e),resume:()=>M(e),reset:t=>w(e,t),restart:()=>R(e),cancel:t=>C(e,t)}),[]),F=u((e,t)=>{let r=e.state.generation;if(e.endCalledGeneration!==r){e.endCalledGeneration=r;try{e.definition.onEnd?.(t,x(e.id))}catch(n){h(i.current.debug,{type:"callback:error",scope:"timer-group",timerId:e.id,error:n,...T(t,r)})}}},[x]),N=u((e,t,r,n,c,a,y)=>{if(n.pending&&(t.overlap??"skip")==="skip"){n.lastRunAt=y.scheduledAt,h(i.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:e.id,reason:"overlap",...y,...T(c,a)});return}n.lastRunAt=y.scheduledAt,n.pending=!0,h(i.current.debug,{type:"schedule:start",scope:"timer-group",timerId:e.id,...y,...T(c,a)}),Promise.resolve().then(()=>t.callback(c,x(e.id),y)).then(()=>{h(i.current.debug,{type:"schedule:end",scope:"timer-group",timerId:e.id,...y,...T(c,a)})},f=>{h(i.current.debug,{type:"schedule:error",scope:"timer-group",timerId:e.id,error:f,...y,...T(c,a)})}).finally(()=>{s.current.get(e.id)?.state.generation===a&&(n.pending=!1)})},[x]),j=u((e,t,r=!1)=>{let n=e.definition.schedules??[],c=new Set;n.forEach((a,y)=>{let f=a.id??String(y);c.add(f);let p=e.schedules.get(f);if(p||(p={lastRunAt:null,pending:!1,leadingGeneration:null},e.schedules.set(f,p)),r&&a.leading&&p.leadingGeneration!==e.state.generation){p.leadingGeneration=e.state.generation,N(e,a,f,p,t,e.state.generation,le(a,f,t.now,t.now,0));return}p.lastRunAt===null&&(p.lastRunAt=e.state.startedAt??t.now);let E=Math.floor((t.now-p.lastRunAt)/a.everyMs);if(E>=1){let me=p.lastRunAt+E*a.everyMs;N(e,a,f,p,t,e.state.generation,le(a,f,me,t.now,E-1))}});for(let a of e.schedules.keys())c.has(a)||e.schedules.delete(a)},[N]),I=u((e,t=l(),r=!1)=>{if(e.state.status!=="running")return;let n=d(e.state,t);if(e.definition.endWhen?.(n)){if(oe(e.state,t)){let c=d(e.state,t);o("timer:end",e,c),F(e,c)}return}j(e,n,r)},[F,o,j]),K=u((e=l())=>{let r=i.current.updateIntervalMs??1e3;for(let n of s.current.values()){if(n.state.status!=="running")continue;(n.definition.schedules??[]).forEach((a,y)=>{let f=a.id??String(y),E=n.schedules.get(f)?.lastRunAt??e.wallNow;r=Math.min(r,Math.max(1,E+a.everyMs-e.wallNow))})}return r},[]),D=u(e=>{let t=s.current.get(e.id);if(t)return t.definition=e,{item:t,added:!1};let r={id:e.id,state:te(l()),definition:e,schedules:new Map,endCalledGeneration:null};return s.current.set(e.id,r),e.autoStart&&P(r.state,l()),{item:r,added:!0}},[]),W=u(()=>{let e=i.current.items??[],t=new Set,r=!1;e.forEach(n=>{t.add(n.id);let{item:c,added:a}=D(n);r=r||a,n.autoStart&&c.state.status==="idle"&&(r=P(c.state,l())||r)});for(let n of s.current.keys())t.has(n)||(s.current.delete(n),r=!0);return r},[D]);ce(()=>{W()&&m()},[W,g.items]);let q=u(e=>{if(U([e]),s.current.has(e.id))throw new Error(`Timer item "${e.id}" already exists`);D(e),m()},[D]),B=u((e,t)=>{let r=s.current.get(e);if(!r)return;let n={...r.definition,...t,id:e};U([n]),r.definition=n,m()},[]),H=u(e=>{s.current.delete(e),m()},[]),J=u(()=>{s.current.clear(),G(),m()},[G]),A=u(e=>{let t=s.current.get(e);if(!t)return;let r=l();P(t.state,r)&&(o("timer:start",t,d(t.state,r)),I(t,r,!0),m())},[o,I]),b=u(e=>{let t=s.current.get(e);if(!t)return;let r=l();re(t.state,r)&&(o("timer:pause",t,d(t.state,r)),m())},[o]),M=u(e=>{let t=s.current.get(e);if(!t)return;let r=l();ne(t.state,r)&&(o("timer:resume",t,d(t.state,r)),I(t,r,!0),m())},[o,I]),w=u((e,t={})=>{let r=s.current.get(e);if(!r)return;let n=l();se(r.state,n,t),r.schedules.clear(),r.endCalledGeneration=null,o("timer:reset",r,d(r.state,n)),t.autoStart&&I(r,n,!0),m()},[o,I]),R=u(e=>{let t=s.current.get(e);if(!t)return;let r=l();ue(t.state,r),t.schedules.clear(),t.endCalledGeneration=null,o("timer:restart",t,d(t.state,r)),I(t,r,!0),m()},[o,I]),C=u((e,t)=>{let r=s.current.get(e);if(!r)return;let n=l();ae(r.state,n,t)&&(o("timer:cancel",r,d(r.state,n),{reason:t}),m())},[o]),de=Array.from(s.current.keys()).map(e=>`${e}:${s.current.get(e).state.status}:${s.current.get(e).state.generation}:${s.current.get(e).state.tick}`).join("|");ce(()=>{v.current=!0;let e=Array.from(s.current.values()).filter(r=>r.state.status==="running");if(e.length===0){G();return}G();let t=e[0];return o("scheduler:start",t,d(t.state,l())),k.current=setTimeout(()=>{if(!v.current)return;let r=l();for(let n of s.current.values()){if(n.state.status!=="running")continue;ie(n.state,r);let c=d(n.state,r);o("timer:tick",n,c),I(n,r)}m()},K()),()=>{k.current!==null&&o("scheduler:stop",t,d(t.state,l())),G(),v.current=!1}},[de,G,o,K,I]);let L=u(e=>{let t=s.current.get(e);if(t)return z(t)},[z]),Q=l().wallNow,V=u(()=>Array.from(s.current.keys()).forEach(A),[A]),X=u(()=>Array.from(s.current.keys()).forEach(b),[b]),Y=u(()=>Array.from(s.current.keys()).forEach(M),[M]),Z=u(e=>Array.from(s.current.keys()).forEach(t=>w(t,e)),[w]),_=u(()=>Array.from(s.current.keys()).forEach(R),[R]),ee=u(e=>Array.from(s.current.keys()).forEach(t=>C(t,e)),[C]);return fe(()=>({now:Q,size:s.current.size,ids:Array.from(s.current.keys()),get:L,add:q,update:B,remove:H,clear:J,start:A,pause:b,resume:M,reset:w,restart:R,cancel:C,startAll:V,pauseAll:X,resumeAll:Y,resetAll:Z,restartAll:_,cancelAll:ee}),[q,C,ee,J,L,Q,b,X,H,w,Z,R,_,M,Y,A,V,B])}function U(g){let S=new Set;g?.forEach(i=>{if(S.has(i.id))throw new Error(`Duplicate timer item id "${i.id}"`);S.add(i.id),i.schedules?.forEach(s=>O(s.everyMs,"schedule.everyMs"))})}function le(g,S,i,s,v){return{scheduleId:g.id??S,scheduledAt:i,firedAt:s,nextRunAt:i+g.everyMs,overdueCount:v,effectiveEveryMs:g.everyMs}}export{ge as useTimerGroup};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var de=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var Ae=(e,o)=>{for(var a in o)de(e,a,{get:o[a],enumerable:!0})},Ie=(e,o,a,n)=>{if(o&&typeof o=="object"||typeof o=="function")for(let y of ye(o))!we.call(e,y)&&y!==a&&de(e,y,{get:()=>o[y],enumerable:!(n=he(o,y))||n.enumerable});return e};var Me=e=>Ie(de({},"__esModule",{value:!0}),e);var Re={};Ae(Re,{durationParts:()=>fe,useTimer:()=>ge,useTimerGroup:()=>Te});module.exports=Me(Re);function fe(e){let o=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),a=Math.floor(o/864e5),n=o%864e5,y=Math.floor(n/36e5),M=n%36e5,T=Math.floor(M/6e4),v=M%6e4,D=Math.floor(v/1e3);return{totalMilliseconds:o,totalSeconds:Math.floor(o/1e3),milliseconds:v%1e3,seconds:D,minutes:T,hours:y,days:a}}var m=require("react");function ve(e){return e?e===!0?{enabled:!0,includeTicks:!1,logger:console.debug}:typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger??console.debug}:{enabled:!1,includeTicks:!1}}function E(e,o){let a=ve(e);!a.enabled||!a.logger||o.type==="timer:tick"&&!a.includeTicks||a.logger({...o,label:o.label??a.label})}function G(e,o){return{generation:o,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function f(){let e=Date.now(),o=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:o}}function j(e,o){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${o} must be a finite number greater than 0`)}function ne(e){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:e.wallNow}}function oe(e,o){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+o.monotonicNow-e.activeStartedAtMonotonic)}function S(e,o){let a=oe(e,o);return{status:e.status,now:o.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:a,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function q(e,o){return e.status!=="idle"?!1:(e.status="running",e.startedAt=o.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function ue(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=oe(e,o),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=o.wallNow,e.now=o.wallNow,!0)}function se(e,o){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function te(e,o,a={}){return e.generation+=1,e.tick=0,e.status=a.autoStart?"running":"idle",e.startedAt=a.autoStart?o.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=a.autoStart?o.monotonicNow:null,e.now=o.wallNow,!0}function ae(e,o){return te(e,o,{autoStart:!0})}function ie(e,o,a){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=oe(e,o),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=o.wallNow,e.cancelReason=a??null,e.now=o.wallNow,!0)}function le(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=oe(e,o),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=o.wallNow,e.now=o.wallNow,!0)}function ce(e,o){return e.status!=="running"?!1:(e.tick+=1,e.now=o.wallNow,!0)}function ge(e={}){let o=e.updateIntervalMs??1e3;j(o,"updateIntervalMs"),ke(e.schedules);let a=(0,m.useRef)(e);a.current=e;let n=(0,m.useRef)(null);n.current===null&&(n.current=ne(f()));let y=(0,m.useRef)(!1),M=(0,m.useRef)(null),T=(0,m.useRef)(new Map),v=(0,m.useRef)(null),[,D]=(0,m.useReducer)(s=>s+1,0),c=(0,m.useCallback)(()=>{M.current!==null&&(clearTimeout(M.current),M.current=null)},[]),U=(0,m.useCallback)((s=f())=>S(n.current,s),[]),h=(0,m.useCallback)((s,d,g={})=>{E(a.current.debug,{type:s,scope:"timer",...G(d,n.current.generation),...g})},[]),$=(0,m.useRef)(null),B=(0,m.useCallback)(s=>{let d=n.current.generation;if(v.current!==d){v.current=d;try{a.current.onEnd?.(s,$.current)}catch(g){E(a.current.debug,{type:"callback:error",scope:"timer",...G(s,d),error:g})}}},[]),k=(0,m.useCallback)((s,d,g,R,C,p)=>{if(g.pending&&(s.overlap??"skip")==="skip"){g.lastRunAt=p.scheduledAt,E(a.current.debug,{type:"schedule:skip",scope:"timer",reason:"overlap",...p,...G(R,C)});return}g.lastRunAt=p.scheduledAt,g.pending=!0,E(a.current.debug,{type:"schedule:start",scope:"timer",...p,...G(R,C)}),Promise.resolve().then(()=>s.callback(R,$.current,p)).then(()=>{E(a.current.debug,{type:"schedule:end",scope:"timer",...p,...G(R,C)})},K=>{E(a.current.debug,{type:"schedule:error",scope:"timer",error:K,...p,...G(R,C)})}).finally(()=>{n.current?.generation===C&&(g.pending=!1)})},[]),J=(0,m.useCallback)((s,d,g=!1)=>{let R=a.current.schedules??[],C=new Set;R.forEach((p,K)=>{let N=p.id??String(K);C.add(N);let I=T.current.get(N);if(I||(I={lastRunAt:null,pending:!1,leadingGeneration:null},T.current.set(N,I)),g&&p.leading&&I.leadingGeneration!==d){I.leadingGeneration=d,k(p,N,I,s,d,pe(p,N,s.now,s.now,0));return}I.lastRunAt===null&&(I.lastRunAt=n.current.startedAt??s.now);let W=Math.floor((s.now-I.lastRunAt)/p.everyMs);if(W>=1){let re=I.lastRunAt+W*p.everyMs;k(p,N,I,s,d,pe(p,N,re,s.now,W-1))}});for(let p of T.current.keys())C.has(p)||T.current.delete(p)},[k]),A=(0,m.useCallback)((s=f(),d=!1)=>{let g=n.current;if(g.status!=="running")return;let R=S(g,s),C=g.generation;if(a.current.endWhen?.(R)){if(le(g,s)){let p=S(g,s);h("timer:end",p),c(),B(p),D()}return}J(R,C,d)},[B,c,h,J]),Q=(0,m.useCallback)((s=f())=>{let d=a.current.updateIntervalMs??1e3,g=d;return n.current.status!=="running"?d:((a.current.schedules??[]).forEach((p,K)=>{let N=p.id??String(K),W=T.current.get(N)?.lastRunAt??s.wallNow;g=Math.min(g,Math.max(1,W+p.everyMs-s.wallNow))}),g)},[]),V=(0,m.useCallback)(()=>{let s=f();if(!q(n.current,s))return;let d=S(n.current,s);h("timer:start",d),A(s,!0),D()},[h,A]),X=(0,m.useCallback)(()=>{let s=f();if(!ue(n.current,s))return;c();let d=S(n.current,s);h("timer:pause",d),D()},[c,h]),Z=(0,m.useCallback)(()=>{let s=f();if(!se(n.current,s))return;let d=S(n.current,s);h("timer:resume",d),A(s,!0),D()},[h,A]),_=(0,m.useCallback)((s={})=>{let d=f();c(),te(n.current,d,s),T.current.clear(),v.current=null;let g=S(n.current,d);h("timer:reset",g),s.autoStart&&A(d,!0),D()},[c,h,A]),H=(0,m.useCallback)(()=>{let s=f();c(),ae(n.current,s),T.current.clear(),v.current=null;let d=S(n.current,s);h("timer:restart",d),A(s,!0),D()},[c,h,A]),L=(0,m.useCallback)(s=>{let d=f();if(!ie(n.current,d,s))return;c();let g=S(n.current,d);h("timer:cancel",g,{reason:s}),D()},[c,h]);$.current=(0,m.useMemo)(()=>({start:V,pause:X,resume:Z,reset:_,restart:H,cancel:L}),[L,X,_,H,Z,V]),(0,m.useEffect)(()=>(y.current=!0,a.current.autoStart&&n.current.status==="idle"&&$.current.start(),()=>{y.current=!1,c()}),[c]);let F=U(),Y=n.current.generation,z=F.status;return(0,m.useEffect)(()=>{if(!y.current||z!=="running"){c();return}return c(),h("scheduler:start",U()),M.current=setTimeout(()=>{if(!y.current||n.current.generation!==Y||n.current.status!=="running")return;let s=f();ce(n.current,s);let d=S(n.current,s);h("timer:tick",d),A(s),D()},Q()),()=>{M.current!==null&&h("scheduler:stop",U()),c()}},[c,h,Y,Q,U,A,F.tick,z]),{...F,...$.current}}function ke(e){e?.forEach(o=>j(o.everyMs,"schedule.everyMs"))}function pe(e,o,a,n,y){return{scheduleId:e.id??o,scheduledAt:a,firedAt:n,nextRunAt:a+e.everyMs,overdueCount:y,effectiveEveryMs:e.everyMs}}var l=require("react");function Te(e={}){let o=e.updateIntervalMs??1e3;j(o,"updateIntervalMs"),me(e.items);let a=(0,l.useRef)(e);a.current=e;let n=(0,l.useRef)(new Map),y=(0,l.useRef)(!1),M=(0,l.useRef)(null),[,T]=(0,l.useReducer)(t=>t+1,0),v=(0,l.useCallback)(()=>{M.current!==null&&(clearTimeout(M.current),M.current=null)},[]),D=(0,l.useCallback)((t,r=f())=>S(t.state,r),[]),c=(0,l.useCallback)((t,r,u,i={})=>{E(a.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...G(u,r?.state.generation??0),...i})},[]),U=(0,l.useCallback)(t=>({start:()=>H(t),pause:()=>L(t),resume:()=>F(t),reset:r=>Y(t,r),restart:()=>z(t),cancel:r=>s(t,r)}),[]),h=(0,l.useCallback)((t,r)=>{let u=t.state.generation;if(t.endCalledGeneration!==u){t.endCalledGeneration=u;try{t.definition.onEnd?.(r,U(t.id))}catch(i){E(a.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:i,...G(r,u)})}}},[U]),$=(0,l.useCallback)((t,r,u,i,w,b,O)=>{if(i.pending&&(r.overlap??"skip")==="skip"){i.lastRunAt=O.scheduledAt,E(a.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,reason:"overlap",...O,...G(w,b)});return}i.lastRunAt=O.scheduledAt,i.pending=!0,E(a.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,...O,...G(w,b)}),Promise.resolve().then(()=>r.callback(w,U(t.id),O)).then(()=>{E(a.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,...O,...G(w,b)})},x=>{E(a.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,error:x,...O,...G(w,b)})}).finally(()=>{n.current.get(t.id)?.state.generation===b&&(i.pending=!1)})},[U]),B=(0,l.useCallback)((t,r,u=!1)=>{let i=t.definition.schedules??[],w=new Set;i.forEach((b,O)=>{let x=b.id??String(O);w.add(x);let P=t.schedules.get(x);if(P||(P={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(x,P)),u&&b.leading&&P.leadingGeneration!==t.state.generation){P.leadingGeneration=t.state.generation,$(t,b,x,P,r,t.state.generation,Se(b,x,r.now,r.now,0));return}P.lastRunAt===null&&(P.lastRunAt=t.state.startedAt??r.now);let ee=Math.floor((r.now-P.lastRunAt)/b.everyMs);if(ee>=1){let be=P.lastRunAt+ee*b.everyMs;$(t,b,x,P,r,t.state.generation,Se(b,x,be,r.now,ee-1))}});for(let b of t.schedules.keys())w.has(b)||t.schedules.delete(b)},[$]),k=(0,l.useCallback)((t,r=f(),u=!1)=>{if(t.state.status!=="running")return;let i=S(t.state,r);if(t.definition.endWhen?.(i)){if(le(t.state,r)){let w=S(t.state,r);c("timer:end",t,w),h(t,w)}return}B(t,i,u)},[h,c,B]),J=(0,l.useCallback)((t=f())=>{let u=a.current.updateIntervalMs??1e3;for(let i of n.current.values()){if(i.state.status!=="running")continue;(i.definition.schedules??[]).forEach((b,O)=>{let x=b.id??String(O),ee=i.schedules.get(x)?.lastRunAt??t.wallNow;u=Math.min(u,Math.max(1,ee+b.everyMs-t.wallNow))})}return u},[]),A=(0,l.useCallback)(t=>{let r=n.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let u={id:t.id,state:ne(f()),definition:t,schedules:new Map,endCalledGeneration:null};return n.current.set(t.id,u),t.autoStart&&q(u.state,f()),{item:u,added:!0}},[]),Q=(0,l.useCallback)(()=>{let t=a.current.items??[],r=new Set,u=!1;t.forEach(i=>{r.add(i.id);let{item:w,added:b}=A(i);u=u||b,i.autoStart&&w.state.status==="idle"&&(u=q(w.state,f())||u)});for(let i of n.current.keys())r.has(i)||(n.current.delete(i),u=!0);return u},[A]);(0,l.useEffect)(()=>{Q()&&T()},[Q,e.items]);let V=(0,l.useCallback)(t=>{if(me([t]),n.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);A(t),T()},[A]),X=(0,l.useCallback)((t,r)=>{let u=n.current.get(t);if(!u)return;let i={...u.definition,...r,id:t};me([i]),u.definition=i,T()},[]),Z=(0,l.useCallback)(t=>{n.current.delete(t),T()},[]),_=(0,l.useCallback)(()=>{n.current.clear(),v(),T()},[v]),H=(0,l.useCallback)(t=>{let r=n.current.get(t);if(!r)return;let u=f();q(r.state,u)&&(c("timer:start",r,S(r.state,u)),k(r,u,!0),T())},[c,k]),L=(0,l.useCallback)(t=>{let r=n.current.get(t);if(!r)return;let u=f();ue(r.state,u)&&(c("timer:pause",r,S(r.state,u)),T())},[c]),F=(0,l.useCallback)(t=>{let r=n.current.get(t);if(!r)return;let u=f();se(r.state,u)&&(c("timer:resume",r,S(r.state,u)),k(r,u,!0),T())},[c,k]),Y=(0,l.useCallback)((t,r={})=>{let u=n.current.get(t);if(!u)return;let i=f();te(u.state,i,r),u.schedules.clear(),u.endCalledGeneration=null,c("timer:reset",u,S(u.state,i)),r.autoStart&&k(u,i,!0),T()},[c,k]),z=(0,l.useCallback)(t=>{let r=n.current.get(t);if(!r)return;let u=f();ae(r.state,u),r.schedules.clear(),r.endCalledGeneration=null,c("timer:restart",r,S(r.state,u)),k(r,u,!0),T()},[c,k]),s=(0,l.useCallback)((t,r)=>{let u=n.current.get(t);if(!u)return;let i=f();ie(u.state,i,r)&&(c("timer:cancel",u,S(u.state,i),{reason:r}),T())},[c]),g=Array.from(n.current.keys()).map(t=>`${t}:${n.current.get(t).state.status}:${n.current.get(t).state.generation}:${n.current.get(t).state.tick}`).join("|");(0,l.useEffect)(()=>{y.current=!0;let t=Array.from(n.current.values()).filter(u=>u.state.status==="running");if(t.length===0){v();return}v();let r=t[0];return c("scheduler:start",r,S(r.state,f())),M.current=setTimeout(()=>{if(!y.current)return;let u=f();for(let i of n.current.values()){if(i.state.status!=="running")continue;ce(i.state,u);let w=S(i.state,u);c("timer:tick",i,w),k(i,u)}T()},J()),()=>{M.current!==null&&c("scheduler:stop",r,S(r.state,f())),v(),y.current=!1}},[g,v,c,J,k]);let R=(0,l.useCallback)(t=>{let r=n.current.get(t);if(r)return D(r)},[D]),C=f().wallNow,p=(0,l.useCallback)(()=>Array.from(n.current.keys()).forEach(H),[H]),K=(0,l.useCallback)(()=>Array.from(n.current.keys()).forEach(L),[L]),N=(0,l.useCallback)(()=>Array.from(n.current.keys()).forEach(F),[F]),I=(0,l.useCallback)(t=>Array.from(n.current.keys()).forEach(r=>Y(r,t)),[Y]),W=(0,l.useCallback)(()=>Array.from(n.current.keys()).forEach(z),[z]),re=(0,l.useCallback)(t=>Array.from(n.current.keys()).forEach(r=>s(r,t)),[s]);return(0,l.useMemo)(()=>({now:C,size:n.current.size,ids:Array.from(n.current.keys()),get:R,add:V,update:X,remove:Z,clear:_,start:H,pause:L,resume:F,reset:Y,restart:z,cancel:s,startAll:p,pauseAll:K,resumeAll:N,resetAll:I,restartAll:W,cancelAll:re}),[V,s,re,_,R,C,L,K,Z,Y,I,z,W,F,N,H,p,X])}function me(e){let o=new Set;e?.forEach(a=>{if(o.has(a.id))throw new Error(`Duplicate timer item id "${a.id}"`);o.add(a.id),a.schedules?.forEach(n=>j(n.everyMs,"schedule.everyMs"))})}function Se(e,o,a,n,y){return{scheduleId:e.id??o,scheduledAt:a,firedAt:n,nextRunAt:a+e.everyMs,overdueCount:y,effectiveEveryMs:e.everyMs}}0&&(module.exports={durationParts,useTimer,useTimerGroup});
1
+ "use strict";var w=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var P=(e,t)=>{for(var o in t)w(e,o,{get:t[o],enumerable:!0})},F=(e,t,o,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of O(t))!U.call(e,i)&&i!==o&&w(e,i,{get:()=>t[i],enumerable:!(r=E(t,i))||r.enumerable});return e};var D=e=>F(w({},"__esModule",{value:!0}),e);var W={};P(W,{useTimer:()=>x});module.exports=D(W);var d=require("react");function l(){let e=Date.now(),t=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:t}}function b(e,t){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${t} must be a finite number greater than 0`)}function R(e){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:e.wallNow}}function T(e,t){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+t.monotonicNow-e.activeStartedAtMonotonic)}function p(e,t){let o=T(e,t);return{status:e.status,now:t.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:o,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function M(e,t){return e.status!=="idle"?!1:(e.status="running",e.startedAt=t.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function v(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=T(e,t),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=t.wallNow,e.now=t.wallNow,!0)}function C(e,t){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function A(e,t,o={}){return e.generation+=1,e.tick=0,e.status=o.autoStart?"running":"idle",e.startedAt=o.autoStart?t.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=o.autoStart?t.monotonicNow:null,e.now=t.wallNow,!0}function N(e,t){return A(e,t,{autoStart:!0})}function h(e,t,o){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=T(e,t),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=t.wallNow,e.cancelReason=o??null,e.now=t.wallNow,!0)}function y(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=T(e,t),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=t.wallNow,e.now=t.wallNow,!0)}function I(e,t){return e.status!=="running"?!1:(e.tick+=1,e.now=t.wallNow,!0)}function x(e={}){let t=(0,d.useRef)(null);return t.current===null&&(t.current=G(e)),t.current.setOptions(e),(0,d.useEffect)(()=>{let r=t.current;return e.autoStart&&r.controls.start(),()=>r.destroy()},[]),{...(0,d.useSyncExternalStore)(t.current.subscribe,t.current.getSnapshot,t.current.getSnapshot),...t.current.controls}}function G(e){let t=e;b(t.updateIntervalMs??1e3,"updateIntervalMs");let o=new Set,r=R(l()),i=p(r,l()),S=null,f=null,u=(n=l())=>{i=p(r,n),o.forEach(a=>a())},s=()=>{S!==null&&(clearTimeout(S),S=null)},k=n=>{f!==r.generation&&(f=r.generation,t.onEnd?.(n,g))},m=n=>{let a=p(r,n);return!t.endWhen?.(a)||!y(r,n)?!1:(s(),u(n),k(p(r,n)),!0)},c=()=>{s(),r.status==="running"&&(S=setTimeout(()=>{if(r.status!=="running")return;let n=l();I(r,n),!m(n)&&(u(n),c())},t.updateIntervalMs??1e3))},g={start:()=>{let n=l();if(!M(r,n)){r.status==="running"&&c();return}u(n),m(n)||c()},pause:()=>{let n=l();v(r,n)&&(s(),u(n))},resume:()=>{let n=l();C(r,n)&&(u(n),m(n)||c())},reset:(n={})=>{let a=l();s(),A(r,a,n),f=null,u(a),n.autoStart&&!m(a)&&c()},restart:()=>{let n=l();s(),N(r,n),f=null,u(n),m(n)||c()},cancel:n=>{let a=l();h(r,a,n)&&(s(),u(a))}};return{controls:g,destroy:s,getSnapshot:()=>i,setOptions:n=>{b(n.updateIntervalMs??1e3,"updateIntervalMs"),t=n},subscribe:n=>(o.add(n),()=>{o.delete(n)})}}0&&(module.exports={useTimer});
package/dist/index.d.cts CHANGED
@@ -1,142 +1,6 @@
1
- type TimerStatus = 'idle' | 'running' | 'paused' | 'ended' | 'cancelled';
2
- type DurationParts = {
3
- totalMilliseconds: number;
4
- totalSeconds: number;
5
- milliseconds: number;
6
- seconds: number;
7
- minutes: number;
8
- hours: number;
9
- days: number;
10
- };
11
- type TimerSnapshot = {
12
- status: TimerStatus;
13
- now: number;
14
- tick: number;
15
- startedAt: number | null;
16
- pausedAt: number | null;
17
- endedAt: number | null;
18
- cancelledAt: number | null;
19
- cancelReason: string | null;
20
- elapsedMilliseconds: number;
21
- isIdle: boolean;
22
- isRunning: boolean;
23
- isPaused: boolean;
24
- isEnded: boolean;
25
- isCancelled: boolean;
26
- };
27
- type TimerControls = {
28
- start(): void;
29
- pause(): void;
30
- resume(): void;
31
- reset(options?: {
32
- autoStart?: boolean;
33
- }): void;
34
- restart(): void;
35
- cancel(reason?: string): void;
36
- };
37
- type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
38
- type TimerScheduleContext = {
39
- scheduleId: string;
40
- scheduledAt: number;
41
- firedAt: number;
42
- nextRunAt: number;
43
- overdueCount: number;
44
- effectiveEveryMs: number;
45
- };
46
- type TimerSchedule = {
47
- id?: string;
48
- everyMs: number;
49
- leading?: boolean;
50
- overlap?: 'skip' | 'allow';
51
- callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
52
- };
53
- type TimerDebug = boolean | TimerDebugLogger | {
54
- enabled?: boolean;
55
- logger?: TimerDebugLogger;
56
- includeTicks?: boolean;
57
- label?: string;
58
- };
59
- type TimerDebugLogger = (event: TimerDebugEvent) => void;
60
- type TimerDebugEvent = {
61
- type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'scheduler:stop' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
62
- scope: 'timer' | 'timer-group';
63
- label?: string;
64
- timerId?: string;
65
- scheduleId?: string;
66
- generation: number;
67
- tick: number;
68
- now: number;
69
- elapsedMilliseconds: number;
70
- status: TimerStatus;
71
- reason?: string;
72
- error?: unknown;
73
- scheduledAt?: number;
74
- firedAt?: number;
75
- nextRunAt?: number;
76
- overdueCount?: number;
77
- effectiveEveryMs?: number;
78
- };
79
- type UseTimerOptions = {
80
- autoStart?: boolean;
81
- updateIntervalMs?: number;
82
- endWhen?: TimerEndPredicate;
83
- onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
84
- schedules?: TimerSchedule[];
85
- debug?: TimerDebug;
86
- };
87
- type TimerGroupItemControls = {
88
- start(): void;
89
- pause(): void;
90
- resume(): void;
91
- reset(options?: {
92
- autoStart?: boolean;
93
- }): void;
94
- restart(): void;
95
- cancel(reason?: string): void;
96
- };
97
- type TimerGroupItem = {
98
- id: string;
99
- autoStart?: boolean;
100
- endWhen?: TimerEndPredicate;
101
- onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
102
- schedules?: TimerSchedule[];
103
- };
104
- type UseTimerGroupOptions = {
105
- updateIntervalMs?: number;
106
- items?: TimerGroupItem[];
107
- debug?: TimerDebug;
108
- };
109
- type TimerGroupResult = {
110
- now: number;
111
- size: number;
112
- ids: string[];
113
- get(id: string): TimerSnapshot | undefined;
114
- add(item: TimerGroupItem): void;
115
- update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
116
- remove(id: string): void;
117
- clear(): void;
118
- start(id: string): void;
119
- pause(id: string): void;
120
- resume(id: string): void;
121
- reset(id: string, options?: {
122
- autoStart?: boolean;
123
- }): void;
124
- restart(id: string): void;
125
- cancel(id: string, reason?: string): void;
126
- startAll(): void;
127
- pauseAll(): void;
128
- resumeAll(): void;
129
- resetAll(options?: {
130
- autoStart?: boolean;
131
- }): void;
132
- restartAll(): void;
133
- cancelAll(reason?: string): void;
134
- };
135
-
136
- declare function durationParts(milliseconds: number): DurationParts;
1
+ import { U as UseTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-4uFJF2Fx.cjs';
2
+ export { b as TimerEndPredicate, c as TimerStatus } from './types-4uFJF2Fx.cjs';
137
3
 
138
4
  declare function useTimer(options?: UseTimerOptions): TimerSnapshot & TimerControls;
139
5
 
140
- declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
141
-
142
- export { type DurationParts, type TimerControls, type TimerDebug, type TimerDebugEvent, type TimerDebugLogger, type TimerEndPredicate, type TimerGroupItem, type TimerGroupItemControls, type TimerGroupResult, type TimerSchedule, type TimerScheduleContext, type TimerSnapshot, type TimerStatus, type UseTimerGroupOptions, type UseTimerOptions, durationParts, useTimer, useTimerGroup };
6
+ export { TimerControls, TimerSnapshot, UseTimerOptions, useTimer };
package/dist/index.d.ts CHANGED
@@ -1,142 +1,6 @@
1
- type TimerStatus = 'idle' | 'running' | 'paused' | 'ended' | 'cancelled';
2
- type DurationParts = {
3
- totalMilliseconds: number;
4
- totalSeconds: number;
5
- milliseconds: number;
6
- seconds: number;
7
- minutes: number;
8
- hours: number;
9
- days: number;
10
- };
11
- type TimerSnapshot = {
12
- status: TimerStatus;
13
- now: number;
14
- tick: number;
15
- startedAt: number | null;
16
- pausedAt: number | null;
17
- endedAt: number | null;
18
- cancelledAt: number | null;
19
- cancelReason: string | null;
20
- elapsedMilliseconds: number;
21
- isIdle: boolean;
22
- isRunning: boolean;
23
- isPaused: boolean;
24
- isEnded: boolean;
25
- isCancelled: boolean;
26
- };
27
- type TimerControls = {
28
- start(): void;
29
- pause(): void;
30
- resume(): void;
31
- reset(options?: {
32
- autoStart?: boolean;
33
- }): void;
34
- restart(): void;
35
- cancel(reason?: string): void;
36
- };
37
- type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
38
- type TimerScheduleContext = {
39
- scheduleId: string;
40
- scheduledAt: number;
41
- firedAt: number;
42
- nextRunAt: number;
43
- overdueCount: number;
44
- effectiveEveryMs: number;
45
- };
46
- type TimerSchedule = {
47
- id?: string;
48
- everyMs: number;
49
- leading?: boolean;
50
- overlap?: 'skip' | 'allow';
51
- callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
52
- };
53
- type TimerDebug = boolean | TimerDebugLogger | {
54
- enabled?: boolean;
55
- logger?: TimerDebugLogger;
56
- includeTicks?: boolean;
57
- label?: string;
58
- };
59
- type TimerDebugLogger = (event: TimerDebugEvent) => void;
60
- type TimerDebugEvent = {
61
- type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'scheduler:stop' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
62
- scope: 'timer' | 'timer-group';
63
- label?: string;
64
- timerId?: string;
65
- scheduleId?: string;
66
- generation: number;
67
- tick: number;
68
- now: number;
69
- elapsedMilliseconds: number;
70
- status: TimerStatus;
71
- reason?: string;
72
- error?: unknown;
73
- scheduledAt?: number;
74
- firedAt?: number;
75
- nextRunAt?: number;
76
- overdueCount?: number;
77
- effectiveEveryMs?: number;
78
- };
79
- type UseTimerOptions = {
80
- autoStart?: boolean;
81
- updateIntervalMs?: number;
82
- endWhen?: TimerEndPredicate;
83
- onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
84
- schedules?: TimerSchedule[];
85
- debug?: TimerDebug;
86
- };
87
- type TimerGroupItemControls = {
88
- start(): void;
89
- pause(): void;
90
- resume(): void;
91
- reset(options?: {
92
- autoStart?: boolean;
93
- }): void;
94
- restart(): void;
95
- cancel(reason?: string): void;
96
- };
97
- type TimerGroupItem = {
98
- id: string;
99
- autoStart?: boolean;
100
- endWhen?: TimerEndPredicate;
101
- onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
102
- schedules?: TimerSchedule[];
103
- };
104
- type UseTimerGroupOptions = {
105
- updateIntervalMs?: number;
106
- items?: TimerGroupItem[];
107
- debug?: TimerDebug;
108
- };
109
- type TimerGroupResult = {
110
- now: number;
111
- size: number;
112
- ids: string[];
113
- get(id: string): TimerSnapshot | undefined;
114
- add(item: TimerGroupItem): void;
115
- update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
116
- remove(id: string): void;
117
- clear(): void;
118
- start(id: string): void;
119
- pause(id: string): void;
120
- resume(id: string): void;
121
- reset(id: string, options?: {
122
- autoStart?: boolean;
123
- }): void;
124
- restart(id: string): void;
125
- cancel(id: string, reason?: string): void;
126
- startAll(): void;
127
- pauseAll(): void;
128
- resumeAll(): void;
129
- resetAll(options?: {
130
- autoStart?: boolean;
131
- }): void;
132
- restartAll(): void;
133
- cancelAll(reason?: string): void;
134
- };
135
-
136
- declare function durationParts(milliseconds: number): DurationParts;
1
+ import { U as UseTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-4uFJF2Fx.js';
2
+ export { b as TimerEndPredicate, c as TimerStatus } from './types-4uFJF2Fx.js';
137
3
 
138
4
  declare function useTimer(options?: UseTimerOptions): TimerSnapshot & TimerControls;
139
5
 
140
- declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
141
-
142
- export { type DurationParts, type TimerControls, type TimerDebug, type TimerDebugEvent, type TimerDebugLogger, type TimerEndPredicate, type TimerGroupItem, type TimerGroupItemControls, type TimerGroupResult, type TimerSchedule, type TimerScheduleContext, type TimerSnapshot, type TimerStatus, type UseTimerGroupOptions, type UseTimerOptions, durationParts, useTimer, useTimerGroup };
6
+ export { TimerControls, TimerSnapshot, UseTimerOptions, useTimer };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function he(e){let u=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),a=Math.floor(u/864e5),n=u%864e5,A=Math.floor(n/36e5),I=n%36e5,S=Math.floor(I/6e4),M=I%6e4,D=Math.floor(M/1e3);return{totalMilliseconds:u,totalSeconds:Math.floor(u/1e3),milliseconds:M%1e3,seconds:D,minutes:S,hours:A,days:a}}import{useCallback as G,useEffect as pe,useMemo as we,useReducer as Ae,useRef as j}from"react";function ye(e){return e?e===!0?{enabled:!0,includeTicks:!1,logger:console.debug}:typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger??console.debug}:{enabled:!1,includeTicks:!1}}function C(e,u){let a=ye(e);!a.enabled||!a.logger||u.type==="timer:tick"&&!a.includeTicks||a.logger({...u,label:u.label??a.label})}function E(e,u){return{generation:u,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function d(){let e=Date.now(),u=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:u}}function q(e,u){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${u} must be a finite number greater than 0`)}function oe(e){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:e.wallNow}}function ue(e,u){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+u.monotonicNow-e.activeStartedAtMonotonic)}function g(e,u){let a=ue(e,u);return{status:e.status,now:u.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:a,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function B(e,u){return e.status!=="idle"?!1:(e.status="running",e.startedAt=u.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=u.monotonicNow,e.now=u.wallNow,!0)}function se(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=ue(e,u),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=u.wallNow,e.now=u.wallNow,!0)}function ae(e,u){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=u.monotonicNow,e.now=u.wallNow,!0)}function re(e,u,a={}){return e.generation+=1,e.tick=0,e.status=a.autoStart?"running":"idle",e.startedAt=a.autoStart?u.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=a.autoStart?u.monotonicNow:null,e.now=u.wallNow,!0}function ie(e,u){return re(e,u,{autoStart:!0})}function le(e,u,a){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=ue(e,u),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=u.wallNow,e.cancelReason=a??null,e.now=u.wallNow,!0)}function ce(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=ue(e,u),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=u.wallNow,e.now=u.wallNow,!0)}function de(e,u){return e.status!=="running"?!1:(e.tick+=1,e.now=u.wallNow,!0)}function Ie(e={}){let u=e.updateIntervalMs??1e3;q(u,"updateIntervalMs"),Me(e.schedules);let a=j(e);a.current=e;let n=j(null);n.current===null&&(n.current=oe(d()));let A=j(!1),I=j(null),S=j(new Map),M=j(null),[,D]=Ae(s=>s+1,0),l=G(()=>{I.current!==null&&(clearTimeout(I.current),I.current=null)},[]),U=G((s=d())=>g(n.current,s),[]),b=G((s,c,p={})=>{C(a.current.debug,{type:s,scope:"timer",...E(c,n.current.generation),...p})},[]),$=j(null),J=G(s=>{let c=n.current.generation;if(M.current!==c){M.current=c;try{a.current.onEnd?.(s,$.current)}catch(p){C(a.current.debug,{type:"callback:error",scope:"timer",...E(s,c),error:p})}}},[]),v=G((s,c,p,k,R,m)=>{if(p.pending&&(s.overlap??"skip")==="skip"){p.lastRunAt=m.scheduledAt,C(a.current.debug,{type:"schedule:skip",scope:"timer",reason:"overlap",...m,...E(k,R)});return}p.lastRunAt=m.scheduledAt,p.pending=!0,C(a.current.debug,{type:"schedule:start",scope:"timer",...m,...E(k,R)}),Promise.resolve().then(()=>s.callback(k,$.current,m)).then(()=>{C(a.current.debug,{type:"schedule:end",scope:"timer",...m,...E(k,R)})},K=>{C(a.current.debug,{type:"schedule:error",scope:"timer",error:K,...m,...E(k,R)})}).finally(()=>{n.current?.generation===R&&(p.pending=!1)})},[]),Q=G((s,c,p=!1)=>{let k=a.current.schedules??[],R=new Set;k.forEach((m,K)=>{let N=m.id??String(K);R.add(N);let w=S.current.get(N);if(w||(w={lastRunAt:null,pending:!1,leadingGeneration:null},S.current.set(N,w)),p&&m.leading&&w.leadingGeneration!==c){w.leadingGeneration=c,v(m,N,w,s,c,ge(m,N,s.now,s.now,0));return}w.lastRunAt===null&&(w.lastRunAt=n.current.startedAt??s.now);let W=Math.floor((s.now-w.lastRunAt)/m.everyMs);if(W>=1){let ne=w.lastRunAt+W*m.everyMs;v(m,N,w,s,c,ge(m,N,ne,s.now,W-1))}});for(let m of S.current.keys())R.has(m)||S.current.delete(m)},[v]),y=G((s=d(),c=!1)=>{let p=n.current;if(p.status!=="running")return;let k=g(p,s),R=p.generation;if(a.current.endWhen?.(k)){if(ce(p,s)){let m=g(p,s);b("timer:end",m),l(),J(m),D()}return}Q(k,R,c)},[J,l,b,Q]),V=G((s=d())=>{let c=a.current.updateIntervalMs??1e3,p=c;return n.current.status!=="running"?c:((a.current.schedules??[]).forEach((m,K)=>{let N=m.id??String(K),W=S.current.get(N)?.lastRunAt??s.wallNow;p=Math.min(p,Math.max(1,W+m.everyMs-s.wallNow))}),p)},[]),X=G(()=>{let s=d();if(!B(n.current,s))return;let c=g(n.current,s);b("timer:start",c),y(s,!0),D()},[b,y]),Z=G(()=>{let s=d();if(!se(n.current,s))return;l();let c=g(n.current,s);b("timer:pause",c),D()},[l,b]),_=G(()=>{let s=d();if(!ae(n.current,s))return;let c=g(n.current,s);b("timer:resume",c),y(s,!0),D()},[b,y]),ee=G((s={})=>{let c=d();l(),re(n.current,c,s),S.current.clear(),M.current=null;let p=g(n.current,c);b("timer:reset",p),s.autoStart&&y(c,!0),D()},[l,b,y]),H=G(()=>{let s=d();l(),ie(n.current,s),S.current.clear(),M.current=null;let c=g(n.current,s);b("timer:restart",c),y(s,!0),D()},[l,b,y]),L=G(s=>{let c=d();if(!le(n.current,c,s))return;l();let p=g(n.current,c);b("timer:cancel",p,{reason:s}),D()},[l,b]);$.current=we(()=>({start:X,pause:Z,resume:_,reset:ee,restart:H,cancel:L}),[L,Z,ee,H,_,X]),pe(()=>(A.current=!0,a.current.autoStart&&n.current.status==="idle"&&$.current.start(),()=>{A.current=!1,l()}),[l]);let F=U(),Y=n.current.generation,z=F.status;return pe(()=>{if(!A.current||z!=="running"){l();return}return l(),b("scheduler:start",U()),I.current=setTimeout(()=>{if(!A.current||n.current.generation!==Y||n.current.status!=="running")return;let s=d();de(n.current,s);let c=g(n.current,s);b("timer:tick",c),y(s),D()},V()),()=>{I.current!==null&&b("scheduler:stop",U()),l()}},[l,b,Y,V,U,y,F.tick,z]),{...F,...$.current}}function Me(e){e?.forEach(u=>q(u.everyMs,"schedule.everyMs"))}function ge(e,u,a,n,A){return{scheduleId:e.id??u,scheduledAt:a,firedAt:n,nextRunAt:a+e.everyMs,overdueCount:A,effectiveEveryMs:e.everyMs}}import{useCallback as f,useEffect as Se,useMemo as ve,useReducer as ke,useRef as me}from"react";function Re(e={}){let u=e.updateIntervalMs??1e3;q(u,"updateIntervalMs"),fe(e.items);let a=me(e);a.current=e;let n=me(new Map),A=me(!1),I=me(null),[,S]=ke(t=>t+1,0),M=f(()=>{I.current!==null&&(clearTimeout(I.current),I.current=null)},[]),D=f((t,r=d())=>g(t.state,r),[]),l=f((t,r,o,i={})=>{C(a.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...E(o,r?.state.generation??0),...i})},[]),U=f(t=>({start:()=>H(t),pause:()=>L(t),resume:()=>F(t),reset:r=>Y(t,r),restart:()=>z(t),cancel:r=>s(t,r)}),[]),b=f((t,r)=>{let o=t.state.generation;if(t.endCalledGeneration!==o){t.endCalledGeneration=o;try{t.definition.onEnd?.(r,U(t.id))}catch(i){C(a.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:i,...E(r,o)})}}},[U]),$=f((t,r,o,i,h,T,O)=>{if(i.pending&&(r.overlap??"skip")==="skip"){i.lastRunAt=O.scheduledAt,C(a.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,reason:"overlap",...O,...E(h,T)});return}i.lastRunAt=O.scheduledAt,i.pending=!0,C(a.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,...O,...E(h,T)}),Promise.resolve().then(()=>r.callback(h,U(t.id),O)).then(()=>{C(a.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,...O,...E(h,T)})},x=>{C(a.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,error:x,...O,...E(h,T)})}).finally(()=>{n.current.get(t.id)?.state.generation===T&&(i.pending=!1)})},[U]),J=f((t,r,o=!1)=>{let i=t.definition.schedules??[],h=new Set;i.forEach((T,O)=>{let x=T.id??String(O);h.add(x);let P=t.schedules.get(x);if(P||(P={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(x,P)),o&&T.leading&&P.leadingGeneration!==t.state.generation){P.leadingGeneration=t.state.generation,$(t,T,x,P,r,t.state.generation,Te(T,x,r.now,r.now,0));return}P.lastRunAt===null&&(P.lastRunAt=t.state.startedAt??r.now);let te=Math.floor((r.now-P.lastRunAt)/T.everyMs);if(te>=1){let be=P.lastRunAt+te*T.everyMs;$(t,T,x,P,r,t.state.generation,Te(T,x,be,r.now,te-1))}});for(let T of t.schedules.keys())h.has(T)||t.schedules.delete(T)},[$]),v=f((t,r=d(),o=!1)=>{if(t.state.status!=="running")return;let i=g(t.state,r);if(t.definition.endWhen?.(i)){if(ce(t.state,r)){let h=g(t.state,r);l("timer:end",t,h),b(t,h)}return}J(t,i,o)},[b,l,J]),Q=f((t=d())=>{let o=a.current.updateIntervalMs??1e3;for(let i of n.current.values()){if(i.state.status!=="running")continue;(i.definition.schedules??[]).forEach((T,O)=>{let x=T.id??String(O),te=i.schedules.get(x)?.lastRunAt??t.wallNow;o=Math.min(o,Math.max(1,te+T.everyMs-t.wallNow))})}return o},[]),y=f(t=>{let r=n.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let o={id:t.id,state:oe(d()),definition:t,schedules:new Map,endCalledGeneration:null};return n.current.set(t.id,o),t.autoStart&&B(o.state,d()),{item:o,added:!0}},[]),V=f(()=>{let t=a.current.items??[],r=new Set,o=!1;t.forEach(i=>{r.add(i.id);let{item:h,added:T}=y(i);o=o||T,i.autoStart&&h.state.status==="idle"&&(o=B(h.state,d())||o)});for(let i of n.current.keys())r.has(i)||(n.current.delete(i),o=!0);return o},[y]);Se(()=>{V()&&S()},[V,e.items]);let X=f(t=>{if(fe([t]),n.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);y(t),S()},[y]),Z=f((t,r)=>{let o=n.current.get(t);if(!o)return;let i={...o.definition,...r,id:t};fe([i]),o.definition=i,S()},[]),_=f(t=>{n.current.delete(t),S()},[]),ee=f(()=>{n.current.clear(),M(),S()},[M]),H=f(t=>{let r=n.current.get(t);if(!r)return;let o=d();B(r.state,o)&&(l("timer:start",r,g(r.state,o)),v(r,o,!0),S())},[l,v]),L=f(t=>{let r=n.current.get(t);if(!r)return;let o=d();se(r.state,o)&&(l("timer:pause",r,g(r.state,o)),S())},[l]),F=f(t=>{let r=n.current.get(t);if(!r)return;let o=d();ae(r.state,o)&&(l("timer:resume",r,g(r.state,o)),v(r,o,!0),S())},[l,v]),Y=f((t,r={})=>{let o=n.current.get(t);if(!o)return;let i=d();re(o.state,i,r),o.schedules.clear(),o.endCalledGeneration=null,l("timer:reset",o,g(o.state,i)),r.autoStart&&v(o,i,!0),S()},[l,v]),z=f(t=>{let r=n.current.get(t);if(!r)return;let o=d();ie(r.state,o),r.schedules.clear(),r.endCalledGeneration=null,l("timer:restart",r,g(r.state,o)),v(r,o,!0),S()},[l,v]),s=f((t,r)=>{let o=n.current.get(t);if(!o)return;let i=d();le(o.state,i,r)&&(l("timer:cancel",o,g(o.state,i),{reason:r}),S())},[l]),p=Array.from(n.current.keys()).map(t=>`${t}:${n.current.get(t).state.status}:${n.current.get(t).state.generation}:${n.current.get(t).state.tick}`).join("|");Se(()=>{A.current=!0;let t=Array.from(n.current.values()).filter(o=>o.state.status==="running");if(t.length===0){M();return}M();let r=t[0];return l("scheduler:start",r,g(r.state,d())),I.current=setTimeout(()=>{if(!A.current)return;let o=d();for(let i of n.current.values()){if(i.state.status!=="running")continue;de(i.state,o);let h=g(i.state,o);l("timer:tick",i,h),v(i,o)}S()},Q()),()=>{I.current!==null&&l("scheduler:stop",r,g(r.state,d())),M(),A.current=!1}},[p,M,l,Q,v]);let k=f(t=>{let r=n.current.get(t);if(r)return D(r)},[D]),R=d().wallNow,m=f(()=>Array.from(n.current.keys()).forEach(H),[H]),K=f(()=>Array.from(n.current.keys()).forEach(L),[L]),N=f(()=>Array.from(n.current.keys()).forEach(F),[F]),w=f(t=>Array.from(n.current.keys()).forEach(r=>Y(r,t)),[Y]),W=f(()=>Array.from(n.current.keys()).forEach(z),[z]),ne=f(t=>Array.from(n.current.keys()).forEach(r=>s(r,t)),[s]);return ve(()=>({now:R,size:n.current.size,ids:Array.from(n.current.keys()),get:k,add:X,update:Z,remove:_,clear:ee,start:H,pause:L,resume:F,reset:Y,restart:z,cancel:s,startAll:m,pauseAll:K,resumeAll:N,resetAll:w,restartAll:W,cancelAll:ne}),[X,s,ne,ee,k,R,L,K,_,Y,w,z,W,F,N,H,m,Z])}function fe(e){let u=new Set;e?.forEach(a=>{if(u.has(a.id))throw new Error(`Duplicate timer item id "${a.id}"`);u.add(a.id),a.schedules?.forEach(n=>q(n.everyMs,"schedule.everyMs"))})}function Te(e,u,a,n,A){return{scheduleId:e.id??u,scheduledAt:a,firedAt:n,nextRunAt:a+e.everyMs,overdueCount:A,effectiveEveryMs:e.everyMs}}export{he as durationParts,Ie as useTimer,Re as useTimerGroup};
1
+ import{a as n,b as f,c as h,d as l,e as y,f as v,g,h as b,i as C,j as I,k,l as O}from"./chunk-I3PNNG3I.js";import{useEffect as M,useRef as U,useSyncExternalStore as R}from"react";function x(u={}){let r=U(null);return r.current===null&&(r.current=P(u)),r.current.setOptions(u),M(()=>{let e=r.current;return u.autoStart&&e.controls.start(),()=>e.destroy()},[]),{...R(r.current.subscribe,r.current.getSnapshot,r.current.getSnapshot),...r.current.controls}}function P(u){let r=u;f(r.updateIntervalMs??1e3,"updateIntervalMs");let m=new Set,e=h(n()),S=l(e,n()),p=null,T=null,s=(t=n())=>{S=l(e,t),m.forEach(o=>o())},i=()=>{p!==null&&(clearTimeout(p),p=null)},E=t=>{T!==e.generation&&(T=e.generation,r.onEnd?.(t,d))},c=t=>{let o=l(e,t);return!r.endWhen?.(o)||!k(e,t)?!1:(i(),s(t),E(l(e,t)),!0)},a=()=>{i(),e.status==="running"&&(p=setTimeout(()=>{if(e.status!=="running")return;let t=n();O(e,t),!c(t)&&(s(t),a())},r.updateIntervalMs??1e3))},d={start:()=>{let t=n();if(!y(e,t)){e.status==="running"&&a();return}s(t),c(t)||a()},pause:()=>{let t=n();v(e,t)&&(i(),s(t))},resume:()=>{let t=n();g(e,t)&&(s(t),c(t)||a())},reset:(t={})=>{let o=n();i(),b(e,o,t),T=null,s(o),t.autoStart&&!c(o)&&a()},restart:()=>{let t=n();i(),C(e,t),T=null,s(t),c(t)||a()},cancel:t=>{let o=n();I(e,o,t)&&(i(),s(o))}};return{controls:d,destroy:i,getSnapshot:()=>S,setOptions:t=>{f(t.updateIntervalMs??1e3,"updateIntervalMs"),r=t},subscribe:t=>(m.add(t),()=>{m.delete(t)})}}export{x as useTimer};
@@ -0,0 +1 @@
1
+ "use strict";var I=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ue=Object.getOwnPropertyNames;var le=Object.prototype.hasOwnProperty;var oe=(e,t)=>{for(var r in t)I(e,r,{get:t[r],enumerable:!0})},ie=(e,t,r,l)=>{if(t&&typeof t=="object"||typeof t=="function")for(let d of ue(t))!le.call(e,d)&&d!==r&&I(e,d,{get:()=>t[d],enumerable:!(l=re(t,d))||l.enumerable});return e};var ae=e=>ie(I({},"__esModule",{value:!0}),e);var de={};oe(de,{useScheduledTimer:()=>te});module.exports=ae(de);var o=require("react");function ce(e){return e?e===!0?{enabled:!0,includeTicks:!1,logger:console.debug}:typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger??console.debug}:{enabled:!1,includeTicks:!1}}function h(e,t){let r=ce(e);!r.enabled||!r.logger||t.type==="timer:tick"&&!r.includeTicks||r.logger({...t,label:t.label??r.label})}function y(e,t){return{generation:t,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function f(){let e=Date.now(),t=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:t}}function P(e,t){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${t} must be a finite number greater than 0`)}function H(e){return{status:"idle",generation:0,tick:0,startedAt:null,pausedAt:null,endedAt:null,cancelledAt:null,cancelReason:null,baseElapsedMilliseconds:0,activeStartedAtMonotonic:null,now:e.wallNow}}function x(e,t){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+t.monotonicNow-e.activeStartedAtMonotonic)}function g(e,t){let r=x(e,t);return{status:e.status,now:t.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:r,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function J(e,t){return e.status!=="idle"?!1:(e.status="running",e.startedAt=t.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function Q(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=x(e,t),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=t.wallNow,e.now=t.wallNow,!0)}function V(e,t){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function G(e,t,r={}){return e.generation+=1,e.tick=0,e.status=r.autoStart?"running":"idle",e.startedAt=r.autoStart?t.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=r.autoStart?t.monotonicNow:null,e.now=t.wallNow,!0}function X(e,t){return G(e,t,{autoStart:!0})}function Y(e,t,r){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=x(e,t),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=t.wallNow,e.cancelReason=r??null,e.now=t.wallNow,!0)}function Z(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=x(e,t),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=t.wallNow,e.now=t.wallNow,!0)}function _(e,t){return e.status!=="running"?!1:(e.tick+=1,e.now=t.wallNow,!0)}function te(e={}){let t=e.updateIntervalMs??1e3;P(t,"updateIntervalMs"),se(e.schedules);let r=(0,o.useRef)(e);r.current=e;let l=(0,o.useRef)(null);l.current===null&&(l.current=H(f()));let d=(0,o.useRef)(!1),R=(0,o.useRef)(null),A=(0,o.useRef)(new Map),C=(0,o.useRef)(null),[,w]=(0,o.useReducer)(n=>n+1,0),s=(0,o.useCallback)(()=>{R.current!==null&&(clearTimeout(R.current),R.current=null)},[]),N=(0,o.useCallback)((n=f())=>g(l.current,n),[]),c=(0,o.useCallback)((n,u,i={})=>{h(r.current.debug,{type:n,scope:"timer",...y(u,l.current.generation),...i})},[]),k=(0,o.useRef)(null),O=(0,o.useCallback)(n=>{let u=l.current.generation;if(C.current!==u){C.current=u;try{r.current.onEnd?.(n,k.current)}catch(i){h(r.current.debug,{type:"callback:error",scope:"timer",...y(n,u),error:i})}}},[]),E=(0,o.useCallback)((n,u,i,p,S,a)=>{if(i.pending&&(n.overlap??"skip")==="skip"){i.lastRunAt=a.scheduledAt,h(r.current.debug,{type:"schedule:skip",scope:"timer",reason:"overlap",...a,...y(p,S)});return}i.lastRunAt=a.scheduledAt,i.pending=!0,h(r.current.debug,{type:"schedule:start",scope:"timer",...a,...y(p,S)}),Promise.resolve().then(()=>n.callback(p,k.current,a)).then(()=>{h(r.current.debug,{type:"schedule:end",scope:"timer",...a,...y(p,S)})},M=>{h(r.current.debug,{type:"schedule:error",scope:"timer",error:M,...a,...y(p,S)})}).finally(()=>{l.current?.generation===S&&(i.pending=!1)})},[]),F=(0,o.useCallback)((n,u,i=!1)=>{let p=r.current.schedules??[],S=new Set;p.forEach((a,M)=>{let T=a.id??String(M);S.add(T);let m=A.current.get(T);if(m||(m={lastRunAt:null,pending:!1,leadingGeneration:null},A.current.set(T,m)),i&&a.leading&&m.leadingGeneration!==u){m.leadingGeneration=u,E(a,T,m,n,u,ee(a,T,n.now,n.now,0));return}m.lastRunAt===null&&(m.lastRunAt=l.current.startedAt??n.now);let v=Math.floor((n.now-m.lastRunAt)/a.everyMs);if(v>=1){let ne=m.lastRunAt+v*a.everyMs;E(a,T,m,n,u,ee(a,T,ne,n.now,v-1))}});for(let a of A.current.keys())S.has(a)||A.current.delete(a)},[E]),b=(0,o.useCallback)((n=f(),u=!1)=>{let i=l.current;if(i.status!=="running")return;let p=g(i,n),S=i.generation;if(r.current.endWhen?.(p)){if(Z(i,n)){let a=g(i,n);c("timer:end",a),s(),O(a),w()}return}F(p,S,u)},[O,s,c,F]),L=(0,o.useCallback)((n=f())=>{let u=r.current.updateIntervalMs??1e3,i=u;return l.current.status!=="running"?u:((r.current.schedules??[]).forEach((a,M)=>{let T=a.id??String(M),v=A.current.get(T)?.lastRunAt??n.wallNow;i=Math.min(i,Math.max(1,v+a.everyMs-n.wallNow))}),i)},[]),U=(0,o.useCallback)(()=>{let n=f();if(!J(l.current,n))return;let u=g(l.current,n);c("timer:start",u),b(n,!0),w()},[c,b]),K=(0,o.useCallback)(()=>{let n=f();if(!Q(l.current,n))return;s();let u=g(l.current,n);c("timer:pause",u),w()},[s,c]),W=(0,o.useCallback)(()=>{let n=f();if(!V(l.current,n))return;let u=g(l.current,n);c("timer:resume",u),b(n,!0),w()},[c,b]),$=(0,o.useCallback)((n={})=>{let u=f();s(),G(l.current,u,n),A.current.clear(),C.current=null;let i=g(l.current,u);c("timer:reset",i),n.autoStart&&b(u,!0),w()},[s,c,b]),j=(0,o.useCallback)(()=>{let n=f();s(),X(l.current,n),A.current.clear(),C.current=null;let u=g(l.current,n);c("timer:restart",u),b(n,!0),w()},[s,c,b]),q=(0,o.useCallback)(n=>{let u=f();if(!Y(l.current,u,n))return;s();let i=g(l.current,u);c("timer:cancel",i,{reason:n}),w()},[s,c]);k.current=(0,o.useMemo)(()=>({start:U,pause:K,resume:W,reset:$,restart:j,cancel:q}),[q,K,$,j,W,U]),(0,o.useEffect)(()=>(d.current=!0,r.current.autoStart&&l.current.status==="idle"&&k.current.start(),()=>{d.current=!1,s()}),[s]);let D=N(),z=l.current.generation,B=D.status;return(0,o.useEffect)(()=>{if(!d.current||B!=="running"){s();return}return s(),c("scheduler:start",N()),R.current=setTimeout(()=>{if(!d.current||l.current.generation!==z||l.current.status!=="running")return;let n=f();_(l.current,n);let u=g(l.current,n);c("timer:tick",u),b(n),w()},L()),()=>{R.current!==null&&c("scheduler:stop",N()),s()}},[s,c,z,L,N,b,D.tick,B]),{...D,...k.current}}function se(e){e?.forEach(t=>P(t.everyMs,"schedule.everyMs"))}function ee(e,t,r,l,d){return{scheduleId:e.id??t,scheduledAt:r,firedAt:l,nextRunAt:r+e.everyMs,overdueCount:d,effectiveEveryMs:e.everyMs}}0&&(module.exports={useScheduledTimer});
@@ -0,0 +1,6 @@
1
+ import { h as UseScheduledTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-4uFJF2Fx.cjs';
2
+ export { i as TimerDebug, j as TimerDebugEvent, k as TimerDebugLogger, l as TimerSchedule, m as TimerScheduleContext } from './types-4uFJF2Fx.cjs';
3
+
4
+ declare function useScheduledTimer(options?: UseScheduledTimerOptions): TimerSnapshot & TimerControls;
5
+
6
+ export { UseScheduledTimerOptions, useScheduledTimer };
@@ -0,0 +1,6 @@
1
+ import { h as UseScheduledTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-4uFJF2Fx.js';
2
+ export { i as TimerDebug, j as TimerDebugEvent, k as TimerDebugLogger, l as TimerSchedule, m as TimerScheduleContext } from './types-4uFJF2Fx.js';
3
+
4
+ declare function useScheduledTimer(options?: UseScheduledTimerOptions): TimerSnapshot & TimerControls;
5
+
6
+ export { UseScheduledTimerOptions, useScheduledTimer };
@@ -0,0 +1 @@
1
+ import{a as y,b}from"./chunk-65MJVHTI.js";import{a as l,b as G,c as B,d as f,e as H,f as J,g as Q,h as V,i as X,j as Y,k as Z,l as _}from"./chunk-I3PNNG3I.js";import{useCallback as i,useEffect as $,useMemo as re,useReducer as ne,useRef as v}from"react";function ue(p={}){let k=p.updateIntervalMs??1e3;G(k,"updateIntervalMs"),ce(p.schedules);let s=v(p);s.current=p;let r=v(null);r.current===null&&(r.current=B(l()));let R=v(!1),M=v(null),T=v(new Map),D=v(null),[,g]=ne(e=>e+1,0),o=i(()=>{M.current!==null&&(clearTimeout(M.current),M.current=null)},[]),E=i((e=l())=>f(r.current,e),[]),c=i((e,t,n={})=>{y(s.current.debug,{type:e,scope:"timer",...b(t,r.current.generation),...n})},[]),A=v(null),P=i(e=>{let t=r.current.generation;if(D.current!==t){D.current=t;try{s.current.onEnd?.(e,A.current)}catch(n){y(s.current.debug,{type:"callback:error",scope:"timer",...b(e,t),error:n})}}},[]),I=i((e,t,n,m,d,u)=>{if(n.pending&&(e.overlap??"skip")==="skip"){n.lastRunAt=u.scheduledAt,y(s.current.debug,{type:"schedule:skip",scope:"timer",reason:"overlap",...u,...b(m,d)});return}n.lastRunAt=u.scheduledAt,n.pending=!0,y(s.current.debug,{type:"schedule:start",scope:"timer",...u,...b(m,d)}),Promise.resolve().then(()=>e.callback(m,A.current,u)).then(()=>{y(s.current.debug,{type:"schedule:end",scope:"timer",...u,...b(m,d)})},C=>{y(s.current.debug,{type:"schedule:error",scope:"timer",error:C,...u,...b(m,d)})}).finally(()=>{r.current?.generation===d&&(n.pending=!1)})},[]),N=i((e,t,n=!1)=>{let m=s.current.schedules??[],d=new Set;m.forEach((u,C)=>{let h=u.id??String(C);d.add(h);let a=T.current.get(h);if(a||(a={lastRunAt:null,pending:!1,leadingGeneration:null},T.current.set(h,a)),n&&u.leading&&a.leadingGeneration!==t){a.leadingGeneration=t,I(u,h,a,e,t,ee(u,h,e.now,e.now,0));return}a.lastRunAt===null&&(a.lastRunAt=r.current.startedAt??e.now);let w=Math.floor((e.now-a.lastRunAt)/u.everyMs);if(w>=1){let te=a.lastRunAt+w*u.everyMs;I(u,h,a,e,t,ee(u,h,te,e.now,w-1))}});for(let u of T.current.keys())d.has(u)||T.current.delete(u)},[I]),S=i((e=l(),t=!1)=>{let n=r.current;if(n.status!=="running")return;let m=f(n,e),d=n.generation;if(s.current.endWhen?.(m)){if(Z(n,e)){let u=f(n,e);c("timer:end",u),o(),P(u),g()}return}N(m,d,t)},[P,o,c,N]),O=i((e=l())=>{let t=s.current.updateIntervalMs??1e3,n=t;return r.current.status!=="running"?t:((s.current.schedules??[]).forEach((u,C)=>{let h=u.id??String(C),w=T.current.get(h)?.lastRunAt??e.wallNow;n=Math.min(n,Math.max(1,w+u.everyMs-e.wallNow))}),n)},[]),U=i(()=>{let e=l();if(!H(r.current,e))return;let t=f(r.current,e);c("timer:start",t),S(e,!0),g()},[c,S]),F=i(()=>{let e=l();if(!J(r.current,e))return;o();let t=f(r.current,e);c("timer:pause",t),g()},[o,c]),K=i(()=>{let e=l();if(!Q(r.current,e))return;let t=f(r.current,e);c("timer:resume",t),S(e,!0),g()},[c,S]),L=i((e={})=>{let t=l();o(),V(r.current,t,e),T.current.clear(),D.current=null;let n=f(r.current,t);c("timer:reset",n),e.autoStart&&S(t,!0),g()},[o,c,S]),W=i(()=>{let e=l();o(),X(r.current,e),T.current.clear(),D.current=null;let t=f(r.current,e);c("timer:restart",t),S(e,!0),g()},[o,c,S]),j=i(e=>{let t=l();if(!Y(r.current,t,e))return;o();let n=f(r.current,t);c("timer:cancel",n,{reason:e}),g()},[o,c]);A.current=re(()=>({start:U,pause:F,resume:K,reset:L,restart:W,cancel:j}),[j,F,L,W,K,U]),$(()=>(R.current=!0,s.current.autoStart&&r.current.status==="idle"&&A.current.start(),()=>{R.current=!1,o()}),[o]);let x=E(),q=r.current.generation,z=x.status;return $(()=>{if(!R.current||z!=="running"){o();return}return o(),c("scheduler:start",E()),M.current=setTimeout(()=>{if(!R.current||r.current.generation!==q||r.current.status!=="running")return;let e=l();_(r.current,e);let t=f(r.current,e);c("timer:tick",t),S(e),g()},O()),()=>{M.current!==null&&c("scheduler:stop",E()),o()}},[o,c,q,O,E,S,x.tick,z]),{...x,...A.current}}function ce(p){p?.forEach(k=>G(k.everyMs,"schedule.everyMs"))}function ee(p,k,s,r,R){return{scheduleId:p.id??k,scheduledAt:s,firedAt:r,nextRunAt:s+p.everyMs,overdueCount:R,effectiveEveryMs:p.everyMs}}export{ue as useScheduledTimer};
@@ -0,0 +1,138 @@
1
+ type TimerStatus = 'idle' | 'running' | 'paused' | 'ended' | 'cancelled';
2
+ type DurationParts = {
3
+ totalMilliseconds: number;
4
+ totalSeconds: number;
5
+ milliseconds: number;
6
+ seconds: number;
7
+ minutes: number;
8
+ hours: number;
9
+ days: number;
10
+ };
11
+ type TimerSnapshot = {
12
+ status: TimerStatus;
13
+ now: number;
14
+ tick: number;
15
+ startedAt: number | null;
16
+ pausedAt: number | null;
17
+ endedAt: number | null;
18
+ cancelledAt: number | null;
19
+ cancelReason: string | null;
20
+ elapsedMilliseconds: number;
21
+ isIdle: boolean;
22
+ isRunning: boolean;
23
+ isPaused: boolean;
24
+ isEnded: boolean;
25
+ isCancelled: boolean;
26
+ };
27
+ type TimerControls = {
28
+ start(): void;
29
+ pause(): void;
30
+ resume(): void;
31
+ reset(options?: {
32
+ autoStart?: boolean;
33
+ }): void;
34
+ restart(): void;
35
+ cancel(reason?: string): void;
36
+ };
37
+ type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
38
+ type TimerScheduleContext = {
39
+ scheduleId: string;
40
+ scheduledAt: number;
41
+ firedAt: number;
42
+ nextRunAt: number;
43
+ overdueCount: number;
44
+ effectiveEveryMs: number;
45
+ };
46
+ type TimerSchedule = {
47
+ id?: string;
48
+ everyMs: number;
49
+ leading?: boolean;
50
+ overlap?: 'skip' | 'allow';
51
+ callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
52
+ };
53
+ type TimerDebug = boolean | TimerDebugLogger | {
54
+ enabled?: boolean;
55
+ logger?: TimerDebugLogger;
56
+ includeTicks?: boolean;
57
+ label?: string;
58
+ };
59
+ type TimerDebugLogger = (event: TimerDebugEvent) => void;
60
+ type TimerDebugEvent = {
61
+ type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'scheduler:stop' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
62
+ scope: 'timer' | 'timer-group';
63
+ label?: string;
64
+ timerId?: string;
65
+ scheduleId?: string;
66
+ generation: number;
67
+ tick: number;
68
+ now: number;
69
+ elapsedMilliseconds: number;
70
+ status: TimerStatus;
71
+ reason?: string;
72
+ error?: unknown;
73
+ scheduledAt?: number;
74
+ firedAt?: number;
75
+ nextRunAt?: number;
76
+ overdueCount?: number;
77
+ effectiveEveryMs?: number;
78
+ };
79
+ type UseTimerOptions = {
80
+ autoStart?: boolean;
81
+ updateIntervalMs?: number;
82
+ endWhen?: TimerEndPredicate;
83
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
84
+ };
85
+ type UseScheduledTimerOptions = UseTimerOptions & {
86
+ schedules?: TimerSchedule[];
87
+ debug?: TimerDebug;
88
+ };
89
+ type TimerGroupItemControls = {
90
+ start(): void;
91
+ pause(): void;
92
+ resume(): void;
93
+ reset(options?: {
94
+ autoStart?: boolean;
95
+ }): void;
96
+ restart(): void;
97
+ cancel(reason?: string): void;
98
+ };
99
+ type TimerGroupItem = {
100
+ id: string;
101
+ autoStart?: boolean;
102
+ endWhen?: TimerEndPredicate;
103
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
104
+ schedules?: TimerSchedule[];
105
+ };
106
+ type UseTimerGroupOptions = {
107
+ updateIntervalMs?: number;
108
+ items?: TimerGroupItem[];
109
+ debug?: TimerDebug;
110
+ };
111
+ type TimerGroupResult = {
112
+ now: number;
113
+ size: number;
114
+ ids: string[];
115
+ get(id: string): TimerSnapshot | undefined;
116
+ add(item: TimerGroupItem): void;
117
+ update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
118
+ remove(id: string): void;
119
+ clear(): void;
120
+ start(id: string): void;
121
+ pause(id: string): void;
122
+ resume(id: string): void;
123
+ reset(id: string, options?: {
124
+ autoStart?: boolean;
125
+ }): void;
126
+ restart(id: string): void;
127
+ cancel(id: string, reason?: string): void;
128
+ startAll(): void;
129
+ pauseAll(): void;
130
+ resumeAll(): void;
131
+ resetAll(options?: {
132
+ autoStart?: boolean;
133
+ }): void;
134
+ restartAll(): void;
135
+ cancelAll(reason?: string): void;
136
+ };
137
+
138
+ export type { DurationParts as D, TimerSnapshot as T, UseTimerOptions as U, TimerControls as a, TimerEndPredicate as b, TimerStatus as c, UseTimerGroupOptions as d, TimerGroupResult as e, TimerGroupItem as f, TimerGroupItemControls as g, UseScheduledTimerOptions as h, TimerDebug as i, TimerDebugEvent as j, TimerDebugLogger as k, TimerSchedule as l, TimerScheduleContext as m };
@@ -0,0 +1,138 @@
1
+ type TimerStatus = 'idle' | 'running' | 'paused' | 'ended' | 'cancelled';
2
+ type DurationParts = {
3
+ totalMilliseconds: number;
4
+ totalSeconds: number;
5
+ milliseconds: number;
6
+ seconds: number;
7
+ minutes: number;
8
+ hours: number;
9
+ days: number;
10
+ };
11
+ type TimerSnapshot = {
12
+ status: TimerStatus;
13
+ now: number;
14
+ tick: number;
15
+ startedAt: number | null;
16
+ pausedAt: number | null;
17
+ endedAt: number | null;
18
+ cancelledAt: number | null;
19
+ cancelReason: string | null;
20
+ elapsedMilliseconds: number;
21
+ isIdle: boolean;
22
+ isRunning: boolean;
23
+ isPaused: boolean;
24
+ isEnded: boolean;
25
+ isCancelled: boolean;
26
+ };
27
+ type TimerControls = {
28
+ start(): void;
29
+ pause(): void;
30
+ resume(): void;
31
+ reset(options?: {
32
+ autoStart?: boolean;
33
+ }): void;
34
+ restart(): void;
35
+ cancel(reason?: string): void;
36
+ };
37
+ type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
38
+ type TimerScheduleContext = {
39
+ scheduleId: string;
40
+ scheduledAt: number;
41
+ firedAt: number;
42
+ nextRunAt: number;
43
+ overdueCount: number;
44
+ effectiveEveryMs: number;
45
+ };
46
+ type TimerSchedule = {
47
+ id?: string;
48
+ everyMs: number;
49
+ leading?: boolean;
50
+ overlap?: 'skip' | 'allow';
51
+ callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
52
+ };
53
+ type TimerDebug = boolean | TimerDebugLogger | {
54
+ enabled?: boolean;
55
+ logger?: TimerDebugLogger;
56
+ includeTicks?: boolean;
57
+ label?: string;
58
+ };
59
+ type TimerDebugLogger = (event: TimerDebugEvent) => void;
60
+ type TimerDebugEvent = {
61
+ type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'scheduler:stop' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
62
+ scope: 'timer' | 'timer-group';
63
+ label?: string;
64
+ timerId?: string;
65
+ scheduleId?: string;
66
+ generation: number;
67
+ tick: number;
68
+ now: number;
69
+ elapsedMilliseconds: number;
70
+ status: TimerStatus;
71
+ reason?: string;
72
+ error?: unknown;
73
+ scheduledAt?: number;
74
+ firedAt?: number;
75
+ nextRunAt?: number;
76
+ overdueCount?: number;
77
+ effectiveEveryMs?: number;
78
+ };
79
+ type UseTimerOptions = {
80
+ autoStart?: boolean;
81
+ updateIntervalMs?: number;
82
+ endWhen?: TimerEndPredicate;
83
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
84
+ };
85
+ type UseScheduledTimerOptions = UseTimerOptions & {
86
+ schedules?: TimerSchedule[];
87
+ debug?: TimerDebug;
88
+ };
89
+ type TimerGroupItemControls = {
90
+ start(): void;
91
+ pause(): void;
92
+ resume(): void;
93
+ reset(options?: {
94
+ autoStart?: boolean;
95
+ }): void;
96
+ restart(): void;
97
+ cancel(reason?: string): void;
98
+ };
99
+ type TimerGroupItem = {
100
+ id: string;
101
+ autoStart?: boolean;
102
+ endWhen?: TimerEndPredicate;
103
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
104
+ schedules?: TimerSchedule[];
105
+ };
106
+ type UseTimerGroupOptions = {
107
+ updateIntervalMs?: number;
108
+ items?: TimerGroupItem[];
109
+ debug?: TimerDebug;
110
+ };
111
+ type TimerGroupResult = {
112
+ now: number;
113
+ size: number;
114
+ ids: string[];
115
+ get(id: string): TimerSnapshot | undefined;
116
+ add(item: TimerGroupItem): void;
117
+ update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
118
+ remove(id: string): void;
119
+ clear(): void;
120
+ start(id: string): void;
121
+ pause(id: string): void;
122
+ resume(id: string): void;
123
+ reset(id: string, options?: {
124
+ autoStart?: boolean;
125
+ }): void;
126
+ restart(id: string): void;
127
+ cancel(id: string, reason?: string): void;
128
+ startAll(): void;
129
+ pauseAll(): void;
130
+ resumeAll(): void;
131
+ resetAll(options?: {
132
+ autoStart?: boolean;
133
+ }): void;
134
+ restartAll(): void;
135
+ cancelAll(reason?: string): void;
136
+ };
137
+
138
+ export type { DurationParts as D, TimerSnapshot as T, UseTimerOptions as U, TimerControls as a, TimerEndPredicate as b, TimerStatus as c, UseTimerGroupOptions as d, TimerGroupResult as e, TimerGroupItem as f, TimerGroupItemControls as g, UseScheduledTimerOptions as h, TimerDebug as i, TimerDebugEvent as j, TimerDebugLogger as k, TimerSchedule as l, TimerScheduleContext as m };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crup/react-timer-hook",
3
- "version": "0.0.1-alpha.10",
4
- "description": "React timer lifecycle hooks for countdowns, stopwatches, schedules, and many independent timers.",
3
+ "version": "0.0.1-alpha.12",
4
+ "description": "A lightweight React hooks library for building timers, stopwatches, and real-time clocks with minimal boilerplate.",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -10,6 +10,26 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js",
12
12
  "require": "./dist/index.cjs"
13
+ },
14
+ "./group": {
15
+ "types": "./dist/group.d.ts",
16
+ "import": "./dist/group.js",
17
+ "require": "./dist/group.cjs"
18
+ },
19
+ "./duration": {
20
+ "types": "./dist/duration.d.ts",
21
+ "import": "./dist/duration.js",
22
+ "require": "./dist/duration.cjs"
23
+ },
24
+ "./schedules": {
25
+ "types": "./dist/schedules.d.ts",
26
+ "import": "./dist/schedules.js",
27
+ "require": "./dist/schedules.cjs"
28
+ },
29
+ "./diagnostics": {
30
+ "types": "./dist/diagnostics.d.ts",
31
+ "import": "./dist/diagnostics.js",
32
+ "require": "./dist/diagnostics.cjs"
13
33
  }
14
34
  },
15
35
  "files": [