@crup/react-timer-hook 0.0.1-alpha.9 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
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
- [![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)
5
+ [![npm](https://img.shields.io/npm/v/%40crup%2Freact-timer-hook?label=npm&color=00b894)](https://www.npmjs.com/package/@crup/react-timer-hook)
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)
7
7
  [![CI](https://github.com/crup/react-timer-hook/actions/workflows/ci.yml/badge.svg)](https://github.com/crup/react-timer-hook/actions/workflows/ci.yml)
8
8
  [![Docs](https://github.com/crup/react-timer-hook/actions/workflows/docs.yml/badge.svg)](https://github.com/crup/react-timer-hook/actions/workflows/docs.yml)
@@ -15,37 +15,62 @@
15
15
 
16
16
  ## Why this exists
17
17
 
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.
18
+ Timers get messy when a product needs pause and resume, countdowns tied to server time, async work, or a screen full of independent rows.
19
19
 
20
- `@crup/react-timer-hook` keeps the API small and lets your app decide what time means:
20
+ `@crup/react-timer-hook` keeps the default import small and lets you add only the pieces your screen needs:
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.
25
- - 🧼 No formatting, timezone, audio, retry, cache, or data-fetching policy baked in.
26
- - 🧪 Built for rerenders, Strict Mode, async callbacks, cleanup, and many timers.
27
- - 🤖 Agent-friendly docs through hosted `llms.txt`, `llms-full.txt`, and an optional MCP docs helper.
22
+ - ⏱️ `useTimer()` from the root package for one lifecycle: stopwatch, countdown, clock, or custom flow.
23
+ - 🔋 Add schedules, timer groups, duration helpers, and diagnostics only when a screen needs them.
24
+ - 🧭 `useTimerGroup()` from `/group` for many keyed lifecycles with one shared scheduler.
25
+ - 📡 `useScheduledTimer()` from `/schedules` for polling and timing context.
26
+ - 🧩 `durationParts()` from `/duration` for common display math.
27
+ - 🧪 Tested against rerenders, React Strict Mode, async callbacks, cleanup, and multi-timer screens.
28
+ - 🤖 AI-ready docs are available through hosted `llms.txt`, `llms-full.txt`, and an optional MCP docs helper.
28
29
 
29
30
  ## Install
30
31
 
31
- The project is currently in alpha while the API receives feedback.
32
-
33
32
  ```sh
34
- npm install @crup/react-timer-hook@alpha
35
- pnpm add @crup/react-timer-hook@alpha
33
+ npm install @crup/react-timer-hook@latest
34
+ pnpm add @crup/react-timer-hook@latest
36
35
  ```
37
36
 
37
+ Runtime requirements: Node 18+ and React 18+.
38
+
38
39
  ```tsx
39
- import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
40
+ import { useTimer } from '@crup/react-timer-hook';
41
+ import { durationParts } from '@crup/react-timer-hook/duration';
42
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
43
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
40
44
  ```
41
45
 
42
46
  ## Live recipes
43
47
 
44
48
  Each recipe has a live playground and a focused code sample:
45
49
 
46
- - Basic: [wall clock](https://crup.github.io/react-timer-hook/recipes/basic/wall-clock/), [stopwatch](https://crup.github.io/react-timer-hook/recipes/basic/stopwatch/), [absolute countdown](https://crup.github.io/react-timer-hook/recipes/basic/absolute-countdown/), [pausable countdown](https://crup.github.io/react-timer-hook/recipes/basic/pausable-countdown/), [manual controls](https://crup.github.io/react-timer-hook/recipes/basic/manual-controls/)
47
- - Intermediate: [once-only onEnd](https://crup.github.io/react-timer-hook/recipes/intermediate/once-only-on-end/), [polling schedule](https://crup.github.io/react-timer-hook/recipes/intermediate/polling-schedule/), [poll and cancel](https://crup.github.io/react-timer-hook/recipes/intermediate/poll-and-cancel/), [backend event stop](https://crup.github.io/react-timer-hook/recipes/intermediate/backend-event-stop/), [debug logs](https://crup.github.io/react-timer-hook/recipes/intermediate/debug-logs/)
48
- - Advanced: [many display countdowns](https://crup.github.io/react-timer-hook/recipes/advanced/many-display-countdowns/), [timer group](https://crup.github.io/react-timer-hook/recipes/advanced/timer-group/), [group controls](https://crup.github.io/react-timer-hook/recipes/advanced/group-controls/), [per-item polling](https://crup.github.io/react-timer-hook/recipes/advanced/per-item-polling/), [dynamic items](https://crup.github.io/react-timer-hook/recipes/advanced/dynamic-items/)
50
+ - Basic: [wall clock](https://crup.github.io/react-timer-hook/recipes/basic/wall-clock/), [stopwatch](https://crup.github.io/react-timer-hook/recipes/basic/stopwatch/), [absolute countdown](https://crup.github.io/react-timer-hook/recipes/basic/absolute-countdown/), [pausable countdown](https://crup.github.io/react-timer-hook/recipes/basic/pausable-countdown/), [OTP resend cooldown](https://crup.github.io/react-timer-hook/recipes/basic/otp-resend/), [manual controls](https://crup.github.io/react-timer-hook/recipes/basic/manual-controls/)
51
+ - Intermediate: [once-only onEnd](https://crup.github.io/react-timer-hook/recipes/intermediate/once-only-on-end/), [polling schedule](https://crup.github.io/react-timer-hook/recipes/intermediate/polling-schedule/), [autosave heartbeat](https://crup.github.io/react-timer-hook/recipes/intermediate/autosave-heartbeat/), [poll and cancel](https://crup.github.io/react-timer-hook/recipes/intermediate/poll-and-cancel/), [backend event stop](https://crup.github.io/react-timer-hook/recipes/intermediate/backend-event-stop/), [diagnostics](https://crup.github.io/react-timer-hook/recipes/intermediate/debug-logs/)
52
+ - Advanced: [many display countdowns](https://crup.github.io/react-timer-hook/recipes/advanced/many-display-countdowns/), [timer group](https://crup.github.io/react-timer-hook/recipes/advanced/timer-group/), [group controls](https://crup.github.io/react-timer-hook/recipes/advanced/group-controls/), [checkout holds](https://crup.github.io/react-timer-hook/recipes/advanced/checkout-holds/), [per-item polling](https://crup.github.io/react-timer-hook/recipes/advanced/per-item-polling/), [dynamic items](https://crup.github.io/react-timer-hook/recipes/advanced/dynamic-items/), [toast auto-dismiss](https://crup.github.io/react-timer-hook/recipes/advanced/toast-auto-dismiss/)
53
+
54
+ ## Use cases
55
+
56
+ | Product case | Use | Import | Recipe |
57
+ | --- | --- | --- | --- |
58
+ | Stopwatch, call timer, workout timer | Core | `@crup/react-timer-hook` | [Stopwatch](https://crup.github.io/react-timer-hook/recipes/basic/stopwatch/) |
59
+ | Wall clock or "last updated" display | Core | `@crup/react-timer-hook` | [Wall clock](https://crup.github.io/react-timer-hook/recipes/basic/wall-clock/) |
60
+ | Auction, reservation, or job deadline | Core | `@crup/react-timer-hook` | [Absolute countdown](https://crup.github.io/react-timer-hook/recipes/basic/absolute-countdown/) |
61
+ | Focus timer or checkout hold that pauses | Core + duration | `@crup/react-timer-hook` + `/duration` | [Pausable countdown](https://crup.github.io/react-timer-hook/recipes/basic/pausable-countdown/) |
62
+ | OTP resend or retry cooldown | Core + duration | `@crup/react-timer-hook` + `/duration` | [OTP resend cooldown](https://crup.github.io/react-timer-hook/recipes/basic/otp-resend/) |
63
+ | Backend status polling | Schedules | `@crup/react-timer-hook/schedules` | [Polling schedule](https://crup.github.io/react-timer-hook/recipes/intermediate/polling-schedule/) |
64
+ | Draft autosave or presence heartbeat | Schedules | `@crup/react-timer-hook/schedules` | [Autosave heartbeat](https://crup.github.io/react-timer-hook/recipes/intermediate/autosave-heartbeat/) |
65
+ | Polling that can close early | Schedules | `@crup/react-timer-hook/schedules` | [Poll and cancel](https://crup.github.io/react-timer-hook/recipes/intermediate/poll-and-cancel/) |
66
+ | Auction list with independent row controls | Timer group | `@crup/react-timer-hook/group` | [Timer group](https://crup.github.io/react-timer-hook/recipes/advanced/timer-group/) |
67
+ | Checkout holds with independent controls | Timer group | `@crup/react-timer-hook/group` | [Checkout holds](https://crup.github.io/react-timer-hook/recipes/advanced/checkout-holds/) |
68
+ | Upload/job dashboard with per-row polling | Timer group + schedules | `@crup/react-timer-hook/group` | [Per-item polling](https://crup.github.io/react-timer-hook/recipes/advanced/per-item-polling/) |
69
+ | Toast expiry or runtime item timers | Timer group | `@crup/react-timer-hook/group` | [Toast auto-dismiss](https://crup.github.io/react-timer-hook/recipes/advanced/toast-auto-dismiss/) |
70
+
71
+ See the full use-case guide: https://crup.github.io/react-timer-hook/use-cases/
72
+
73
+ Design assumptions and runtime limits: https://crup.github.io/react-timer-hook/project/caveats/
49
74
 
50
75
  ## Quick examples
51
76
 
@@ -99,7 +124,9 @@ export function AuctionTimer({ auctionId, expiresAt }: {
99
124
  Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
100
125
 
101
126
  ```tsx
102
- const timer = useTimer({
127
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
128
+
129
+ const timer = useScheduledTimer({
103
130
  autoStart: true,
104
131
  updateIntervalMs: 1000,
105
132
  endWhen: snapshot => snapshot.now >= expiresAt,
@@ -108,7 +135,8 @@ const timer = useTimer({
108
135
  id: 'auction-poll',
109
136
  everyMs: 5000,
110
137
  overlap: 'skip',
111
- callback: async (_snapshot, controls) => {
138
+ callback: async (_snapshot, controls, context) => {
139
+ console.log(`auction poll fired ${context.firedAt - context.scheduledAt}ms late`);
112
140
  const auction = await api.getAuction(auctionId);
113
141
  if (auction.status === 'sold') controls.cancel('sold');
114
142
  },
@@ -122,6 +150,8 @@ const timer = useTimer({
122
150
  Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
123
151
 
124
152
  ```tsx
153
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
154
+
125
155
  const timers = useTimerGroup({
126
156
  updateIntervalMs: 1000,
127
157
  items: auctions.map(auction => ({
@@ -133,15 +163,100 @@ const timers = useTimerGroup({
133
163
  });
134
164
  ```
135
165
 
166
+ ## API reference
167
+
168
+ ### `useTimer()` settings
169
+
170
+ | Key | Type | Required | Description |
171
+ | --- | --- | --- | --- |
172
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
173
+ | `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. |
174
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. Use this for countdowns, timeouts, and custom stop conditions. |
175
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. `restart()` creates a new generation. |
176
+ | `onError` | `(error, snapshot, controls) => void` | No | Handles sync throws and async rejections from `onEnd`. Also used as the fallback for schedule callback failures when a schedule does not define `onError`. |
177
+
178
+ ### `useScheduledTimer()` settings
179
+
180
+ Import from `@crup/react-timer-hook/schedules` when you need polling or scheduled side effects.
181
+
182
+ | Key | Type | Required | Description |
183
+ | --- | --- | --- | --- |
184
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
185
+ | `updateIntervalMs` | `number` | No | Render/update cadence in milliseconds. Defaults to `1000`. Scheduled callbacks can run on their own cadence. |
186
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. |
187
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. |
188
+ | `onError` | `(error, snapshot, controls) => void` | No | Handles sync throws and async rejections from `onEnd`. |
189
+ | `schedules` | `TimerSchedule[]` | No | Scheduled side effects that run while the timer is active. Async overlap defaults to `skip`. |
190
+ | `diagnostics` | `TimerDiagnostics` | No | Optional lifecycle and schedule events. No logs are emitted unless you pass a logger. |
191
+
192
+ ### `TimerSchedule`
193
+
194
+ | Key | Type | Required | Description |
195
+ | --- | --- | --- | --- |
196
+ | `id` | `string` | No | Stable identifier used in diagnostics events and schedule context. Falls back to the array index. |
197
+ | `everyMs` | `number` | Yes | Schedule cadence in milliseconds. Must be positive and finite. |
198
+ | `leading` | `boolean` | No | Runs the schedule immediately when the timer starts or resumes into a new generation. Defaults to `false`. |
199
+ | `overlap` | `'skip' \| 'allow'` | No | Controls async overlap. Defaults to `skip`, so a pending callback prevents another run. |
200
+ | `callback` | `(snapshot, controls, context) => void \| Promise<void>` | Yes | Scheduled side effect. Receives timing context with `scheduledAt`, `firedAt`, `nextRunAt`, `overdueCount`, and `effectiveEveryMs`. |
201
+ | `onError` | `(error, snapshot, controls, context) => void` | No | Handles sync throws and async rejections from that schedule's `callback`. Falls back to the timer or item `onError` when omitted. |
202
+
203
+ ### `useTimerGroup()` settings
204
+
205
+ Import from `@crup/react-timer-hook/group` when many keyed items need independent lifecycle control.
206
+
207
+ | Key | Type | Required | Description |
208
+ | --- | --- | --- | --- |
209
+ | `updateIntervalMs` | `number` | No | Shared scheduler cadence for the group. Defaults to `1000`. |
210
+ | `items` | `TimerGroupItem[]` | No | Initial/synced timer item definitions. Each item has its own lifecycle state. |
211
+ | `diagnostics` | `TimerDiagnostics` | No | Optional lifecycle and schedule events for group timers. |
212
+
213
+ ### `TimerGroupItem`
214
+
215
+ | Key | Type | Required | Description |
216
+ | --- | --- | --- | --- |
217
+ | `id` | `string` | Yes | Stable key for the item. Duplicate IDs throw. |
218
+ | `autoStart` | `boolean` | No | Starts the item automatically when it is added or synced. Defaults to `false`. |
219
+ | `endWhen` | `(snapshot) => boolean` | No | Ends that item when it returns `true`. |
220
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per item generation when that item ends naturally. |
221
+ | `onError` | `(error, snapshot, controls) => void` | No | Handles sync throws and async rejections from that item's `onEnd`. Also used as the fallback for that item's schedule callback failures. |
222
+ | `schedules` | `TimerSchedule[]` | No | Per-item schedules with the same contract as `useScheduledTimer()`. |
223
+
224
+ ### Values and controls
225
+
226
+ | Key | Type | Description |
227
+ | --- | --- | --- |
228
+ | `status` | `'idle' \| 'running' \| 'paused' \| 'ended' \| 'cancelled'` | Current lifecycle state. |
229
+ | `now` | `number` | Wall-clock timestamp from `Date.now()`. Use for clocks and absolute deadlines. |
230
+ | `tick` | `number` | Number of render/update ticks produced in the current generation. |
231
+ | `startedAt` | `number \| null` | Wall-clock timestamp when the current generation started. |
232
+ | `pausedAt` | `number \| null` | Wall-clock timestamp for the current pause, or `null`. |
233
+ | `endedAt` | `number \| null` | Wall-clock timestamp when `endWhen` ended the lifecycle. |
234
+ | `cancelledAt` | `number \| null` | Wall-clock timestamp when `cancel()` ended the lifecycle early. |
235
+ | `cancelReason` | `string \| null` | Optional reason passed to `cancel(reason)`. |
236
+ | `elapsedMilliseconds` | `number` | Active elapsed duration calculated from monotonic time, excluding paused time. |
237
+ | `isIdle` | `boolean` | Convenience flag for `status === 'idle'`. |
238
+ | `isRunning` | `boolean` | Convenience flag for `status === 'running'`. |
239
+ | `isPaused` | `boolean` | Convenience flag for `status === 'paused'`. |
240
+ | `isEnded` | `boolean` | Convenience flag for `status === 'ended'`. |
241
+ | `isCancelled` | `boolean` | Convenience flag for `status === 'cancelled'`. |
242
+ | `start()` | `function` | Starts an idle timer. No-op if it is already started. |
243
+ | `pause()` | `function` | Pauses a running timer. |
244
+ | `resume()` | `function` | Resumes a paused timer from the paused elapsed value. |
245
+ | `reset(options?)` | `function` | Resets to idle and zero elapsed time. Pass `{ autoStart: true }` to reset directly into running. |
246
+ | `restart()` | `function` | Starts a new running generation from zero elapsed time. |
247
+ | `cancel(reason?)` | `function` | Terminal early stop. Does not call `onEnd`. |
248
+
136
249
  ## Bundle size
137
250
 
138
- Current build:
251
+ The default import stays small. Add the other pieces only when that screen needs them.
139
252
 
140
- | File | Raw | Gzip | Brotli |
141
- | --- | ---: | ---: | ---: |
142
- | `dist/index.js` | 12.45 kB | 3.75 kB | 3.36 kB |
143
- | `dist/index.cjs` | 13.69 kB | 4.01 kB | 3.60 kB |
144
- | `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
253
+ | Piece | Import | Best for | Raw | Gzip | Brotli |
254
+ | --- | --- | --- | ---: | ---: | ---: |
255
+ | ⏱️ Core | `@crup/react-timer-hook` | Stopwatch, countdown, clock, custom lifecycle | 4.44 kB | 1.52 kB | 1.40 kB |
256
+ | 🧭 Timer group | `@crup/react-timer-hook/group` | Many independent row/item timers | 10.93 kB | 3.83 kB | 3.50 kB |
257
+ | 📡 Schedules | `@crup/react-timer-hook/schedules` | Polling, cadence callbacks, overdue timing context | 8.62 kB | 3.02 kB | 2.78 kB |
258
+ | 🧩 Duration | `@crup/react-timer-hook/duration` | `days`, `hours`, `minutes`, `seconds`, `milliseconds` | 318 B | 224 B | 192 B |
259
+ | 🔎 Diagnostics | `@crup/react-timer-hook/diagnostics` | Optional lifecycle and schedule event logging | 105 B | 115 B | 90 B |
145
260
 
146
261
  CI writes a size summary to the GitHub Actions UI and posts bundle-size reports on pull requests.
147
262
 
@@ -180,5 +295,6 @@ Issues, recipes, docs improvements, and focused bug reports are welcome.
180
295
  - Read the docs: https://crup.github.io/react-timer-hook/
181
296
  - Open an issue: https://github.com/crup/react-timer-hook/issues
182
297
  - See the contributing guide: ./CONTRIBUTING.md
298
+ - Release policy: https://crup.github.io/react-timer-hook/project/release-channels/
183
299
 
184
- The package targets Node 24 for development and React 18+ as a peer dependency.
300
+ The package targets Node 18+ and React 18+.
@@ -0,0 +1 @@
1
+ import{c as C,d as y,e as T,l as b}from"./chunk-XXJAJJJ6.js";function E(e){return e?typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger}:{enabled:!1,includeTicks:!1}}function M(e,n){let t=E(e);!t.enabled||!t.logger||n.type==="timer:tick"&&!t.includeTicks||t.logger({...n,label:n.label??t.label})}function R(e,n){return{generation:n,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function k(){return{lastRunAt:null,pendingCount:0,leadingGeneration:null,signature:""}}function G({schedules:e=[],states:n,snapshot:t,generation:i,controls:r,activation:o=!1,isLive:s,onError:a,onEvent:m}){let u=new Set;e.forEach((l,h)=>{let c=f(l,h);u.add(c);let d=n.get(c);if(d||(d=k(),d.signature=g(l,h),n.set(c,d)),o&&l.leading&&d.leadingGeneration!==i){d.leadingGeneration=i,x(l,c,d,t,i,r,v(l,c,t.now,t.now,0),s,a,m);return}d.lastRunAt===null&&(d.lastRunAt=t.now);let p=Math.floor((t.now-d.lastRunAt)/l.everyMs);if(p>=1){let D=d.lastRunAt+p*l.everyMs;x(l,c,d,t,i,r,v(l,c,D,t.now,p-1),s,a,m)}});for(let l of n.keys())u.has(l)||n.delete(l)}function P(e,n,t,i){let r=i;return e?.forEach((o,s)=>{let a=f(o,s),u=n.get(a)?.lastRunAt??t;r=Math.min(r,Math.max(1,u+o.everyMs-t))}),r}function O(e,n,t,i){let r=new Set;e?.forEach((o,s)=>{let a=f(o,s),m=g(o,s);r.add(a);let u=n.get(a);if(u)u.signature!==m&&(u.lastRunAt=i?t:null,u.leadingGeneration=null,u.signature=m);else{let l=k();l.lastRunAt=i?t:null,l.signature=m,n.set(a,l)}});for(let o of n.keys())r.has(o)||n.delete(o)}function N(e){return JSON.stringify((e??[]).map((n,t)=>g(n,t)))}function H(e){let n=new Set;e?.forEach((t,i)=>{C(t.everyMs,"schedule.everyMs");let r=f(t,i);if(n.has(r))throw new Error(`Duplicate schedule id "${r}"`);n.add(r)})}function v(e,n,t,i,r){return{scheduleId:e.id??n,scheduledAt:t,firedAt:i,nextRunAt:t+e.everyMs,overdueCount:r,effectiveEveryMs:e.everyMs}}function x(e,n,t,i,r,o,s,a,m,u){if(t.pendingCount>0&&(e.overlap??"skip")==="skip"){t.lastRunAt=s.scheduledAt,S(a,r,u,{type:"schedule:skip",context:s,reason:"overlap"});return}t.lastRunAt=s.scheduledAt,t.pendingCount+=1,S(a,r,u,{type:"schedule:start",context:s}),Promise.resolve().then(()=>e.callback(i,o,s)).then(()=>S(a,r,u,{type:"schedule:end",context:s}),l=>{if(a(r))try{e.onError?e.onError(l,i,o,s):m?.(l,i,o,s)}finally{u?.({type:"schedule:error",context:s,error:l})}}).finally(()=>{a(r)&&(t.pendingCount=Math.max(0,t.pendingCount-1))})}function f(e,n){return e.id??String(n)}function g(e,n){return JSON.stringify([e.id??n,e.everyMs,e.leading??!1,e.overlap??"skip"])}function S(e,n,t,i){e(n)&&t?.(i)}function W(e,n){return{state:y(n),definition:e,endCalledGeneration:null}}function F(e){e.endCalledGeneration=null}function $(e,n){return T(e.state,n)}function j(e,n,t,i){let r=T(e.state,n);if(!e.definition.endWhen?.(r)||!b(e.state,n))return null;let o=T(e.state,n);return I(e,o,t,i),o}function I(e,n,t,i){let r=e.state.generation;if(e.endCalledGeneration!==r){e.endCalledGeneration=r;try{let o=e.definition.onEnd?.(n,t);o&&i&&Promise.resolve(o).catch(s=>i?.(s,n,r))}catch(o){if(i){i(o,n,r);return}throw o}}}export{M as a,R as b,G as c,P as d,O as e,N as f,H as g,W as h,F as i,$ as j,j as k};
@@ -0,0 +1 @@
1
+ function o(n,e){return{start:()=>{e()&&n.start()},pause:()=>{e()&&n.pause()},resume:()=>{e()&&n.resume()},reset:t=>{e()&&n.reset(t)},restart:()=>{e()&&n.restart()},cancel:t=>{e()&&n.cancel(t)}}}function u(){let n=Date.now(),e=typeof performance<"u"&&typeof performance.now=="function"?performance.now():n;return{wallNow:n,monotonicNow:e}}function i(n,e){if(!Number.isFinite(n)||n<=0)throw new RangeError(`${e} must be a finite number greater than 0`)}function c(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 r(n,e){return n.status!=="running"||n.activeStartedAtMonotonic===null?n.baseElapsedMilliseconds:Math.max(0,n.baseElapsedMilliseconds+e.monotonicNow-n.activeStartedAtMonotonic)}function s(n,e){let t=r(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 m(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 p(n,e){return n.status!=="running"?!1:(n.baseElapsedMilliseconds=r(n,e),n.activeStartedAtMonotonic=null,n.status="paused",n.pausedAt=e.wallNow,n.now=e.wallNow,!0)}function w(n,e){return n.status!=="paused"?!1:(n.status="running",n.pausedAt=null,n.activeStartedAtMonotonic=e.monotonicNow,n.now=e.wallNow,!0)}function l(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 f(n,e){return l(n,e,{autoStart:!0})}function S(n,e,t){return n.status!=="running"&&n.status!=="paused"?!1:(n.baseElapsedMilliseconds=r(n,e),n.activeStartedAtMonotonic=null,n.status="cancelled",n.cancelledAt=e.wallNow,n.cancelReason=t??null,n.now=e.wallNow,!0)}function A(n,e){return n.status!=="running"?!1:(n.baseElapsedMilliseconds=r(n,e),n.activeStartedAtMonotonic=null,n.status="ended",n.endedAt=e.wallNow,n.now=e.wallNow,!0)}function b(n,e){return n.status!=="running"?!1:(n.tick+=1,n.now=e.wallNow,!0)}export{o as a,u as b,i as c,c as d,s as e,m as f,p as g,w as h,l as i,f as j,S as k,A as l,b as m};
@@ -0,0 +1 @@
1
+ "use strict";var t=Object.defineProperty;var r=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var c=Object.prototype.hasOwnProperty;var m=(e,i)=>{for(var s in i)t(e,s,{get:i[s],enumerable:!0})},a=(e,i,s,n)=>{if(i&&typeof i=="object"||typeof i=="function")for(let o of g(i))!c.call(e,o)&&o!==s&&t(e,o,{get:()=>i[o],enumerable:!(n=r(i,o))||n.enumerable});return e};var T=e=>a(t({},"__esModule",{value:!0}),e);var l={};m(l,{consoleTimerDiagnostics:()=>D});module.exports=T(l);function D(e={}){return{...e,logger:i=>console.debug("[timer]",i)}}0&&(module.exports={consoleTimerDiagnostics});
@@ -0,0 +1,9 @@
1
+ import { i as TimerDiagnostics } from './types-D-Vzr-PF.cjs';
2
+ export { j as TimerDiagnosticsEvent, k as TimerDiagnosticsLogger } from './types-D-Vzr-PF.cjs';
3
+
4
+ declare function consoleTimerDiagnostics(options?: {
5
+ includeTicks?: boolean;
6
+ label?: string;
7
+ }): TimerDiagnostics;
8
+
9
+ export { TimerDiagnostics, consoleTimerDiagnostics };
@@ -0,0 +1,9 @@
1
+ import { i as TimerDiagnostics } from './types-D-Vzr-PF.js';
2
+ export { j as TimerDiagnosticsEvent, k as TimerDiagnosticsLogger } from './types-D-Vzr-PF.js';
3
+
4
+ declare function consoleTimerDiagnostics(options?: {
5
+ includeTicks?: boolean;
6
+ label?: string;
7
+ }): TimerDiagnostics;
8
+
9
+ export { TimerDiagnostics, consoleTimerDiagnostics };
@@ -0,0 +1 @@
1
+ function o(i={}){return{...i,logger:e=>console.debug("[timer]",e)}}export{o 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-D-Vzr-PF.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-D-Vzr-PF.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 B=Object.defineProperty;var Ee=Object.getOwnPropertyDescriptor;var Ge=Object.getOwnPropertyNames;var Re=Object.prototype.hasOwnProperty;var Me=(e,t)=>{for(var o in t)B(e,o,{get:t[o],enumerable:!0})},Ne=(e,t,o,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of Ge(t))!Re.call(e,l)&&l!==o&&B(e,l,{get:()=>t[l],enumerable:!(i=Ee(t,l))||i.enumerable});return e};var De=e=>Ne(B({},"__esModule",{value:!0}),e);var ze={};Me(ze,{useTimerGroup:()=>we});module.exports=De(ze);var x=require("react");function T(){let e=Date.now(),t=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:t}}function N(e,t){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${t} must be a finite number greater than 0`)}function Q(e,t){return{start:()=>{t()&&e.start()},pause:()=>{t()&&e.pause()},resume:()=>{t()&&e.resume()},reset:o=>{t()&&e.reset(o)},restart:()=>{t()&&e.restart()},cancel:o=>{t()&&e.cancel(o)}}}function Oe(e){return e?typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger}:{enabled:!1,includeTicks:!1}}function D(e,t){let o=Oe(e);!o.enabled||!o.logger||t.type==="timer:tick"&&!o.includeTicks||o.logger({...t,label:t.label??o.label})}function O(e,t){return{generation:t,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function ie(){return{lastRunAt:null,pendingCount:0,leadingGeneration:null,signature:""}}function se({schedules:e=[],states:t,snapshot:o,generation:i,controls:l,activation:c=!1,isLive:d,onError:p,onEvent:h}){let m=new Set;e.forEach((u,A)=>{let f=P(u,A);m.add(f);let y=t.get(f);if(y||(y=ie(),y.signature=X(u,A),t.set(f,y)),c&&u.leading&&y.leadingGeneration!==i){y.leadingGeneration=i,oe(u,f,y,o,i,l,re(u,f,o.now,o.now,0),d,p,h);return}y.lastRunAt===null&&(y.lastRunAt=o.now);let G=Math.floor((o.now-y.lastRunAt)/u.everyMs);if(G>=1){let M=y.lastRunAt+G*u.everyMs;oe(u,f,y,o,i,l,re(u,f,M,o.now,G-1),d,p,h)}});for(let u of t.keys())m.has(u)||t.delete(u)}function le(e,t,o,i){let l=i;return e?.forEach((c,d)=>{let p=P(c,d),m=t.get(p)?.lastRunAt??o;l=Math.min(l,Math.max(1,m+c.everyMs-o))}),l}function ae(e,t,o,i){let l=new Set;e?.forEach((c,d)=>{let p=P(c,d),h=X(c,d);l.add(p);let m=t.get(p);if(m)m.signature!==h&&(m.lastRunAt=i?o:null,m.leadingGeneration=null,m.signature=h);else{let u=ie();u.lastRunAt=i?o:null,u.signature=h,t.set(p,u)}});for(let c of t.keys())l.has(c)||t.delete(c)}function ue(e){return JSON.stringify((e??[]).map((t,o)=>X(t,o)))}function ce(e){let t=new Set;e?.forEach((o,i)=>{N(o.everyMs,"schedule.everyMs");let l=P(o,i);if(t.has(l))throw new Error(`Duplicate schedule id "${l}"`);t.add(l)})}function re(e,t,o,i,l){return{scheduleId:e.id??t,scheduledAt:o,firedAt:i,nextRunAt:o+e.everyMs,overdueCount:l,effectiveEveryMs:e.everyMs}}function oe(e,t,o,i,l,c,d,p,h,m){if(o.pendingCount>0&&(e.overlap??"skip")==="skip"){o.lastRunAt=d.scheduledAt,V(p,l,m,{type:"schedule:skip",context:d,reason:"overlap"});return}o.lastRunAt=d.scheduledAt,o.pendingCount+=1,V(p,l,m,{type:"schedule:start",context:d}),Promise.resolve().then(()=>e.callback(i,c,d)).then(()=>V(p,l,m,{type:"schedule:end",context:d}),u=>{if(p(l))try{e.onError?e.onError(u,i,c,d):h?.(u,i,c,d)}finally{m?.({type:"schedule:error",context:d,error:u})}}).finally(()=>{p(l)&&(o.pendingCount=Math.max(0,o.pendingCount-1))})}function P(e,t){return e.id??String(t)}function X(e,t){return JSON.stringify([e.id??t,e.everyMs,e.leading??!1,e.overlap??"skip"])}function V(e,t,o,i){e(t)&&o?.(i)}function de(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 U(e,t){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+t.monotonicNow-e.activeStartedAtMonotonic)}function z(e,t){let o=U(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 F(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 me(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=U(e,t),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=t.wallNow,e.now=t.wallNow,!0)}function pe(e,t){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function Y(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 fe(e,t){return Y(e,t,{autoStart:!0})}function Te(e,t,o){return e.status!=="running"&&e.status!=="paused"?!1:(e.baseElapsedMilliseconds=U(e,t),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=t.wallNow,e.cancelReason=o??null,e.now=t.wallNow,!0)}function Se(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=U(e,t),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=t.wallNow,e.now=t.wallNow,!0)}function ge(e,t){return e.status!=="running"?!1:(e.tick+=1,e.now=t.wallNow,!0)}function he(e,t){return{state:de(t),definition:e,endCalledGeneration:null}}function Z(e){e.endCalledGeneration=null}function C(e,t){return z(e.state,t)}function ye(e,t,o,i){let l=z(e.state,t);if(!e.definition.endWhen?.(l)||!Se(e.state,t))return null;let c=z(e.state,t);return Pe(e,c,o,i),c}function Pe(e,t,o,i){let l=e.state.generation;if(e.endCalledGeneration!==l){e.endCalledGeneration=l;try{let c=e.definition.onEnd?.(t,o);c&&i&&Promise.resolve(c).catch(d=>i?.(d,t,l))}catch(c){if(i){i(c,t,l);return}throw c}}}function we(e={}){let t=(0,x.useRef)(null);return t.current===null&&(t.current=Ue(e)),t.current.setOptions(e),(0,x.useEffect)(()=>{t.current?.commitOptions()}),(0,x.useEffect)(()=>{let i=t.current;return()=>i.destroy()},[]),{...(0,x.useSyncExternalStore)(t.current.subscribe,t.current.getSnapshot,t.current.getServerSnapshot),get:t.current.getTimer,...t.current.controls}}function Ue(e){let t=e;L(t);let o=new Set,i=new Map,l=T(),c=_(i,l.wallNow),d=c,p=null,h=$(t),m=!0,u=(n=T())=>{l=n,c=_(i,n.wallNow),o.forEach(r=>r())},A=()=>{p!==null&&(clearTimeout(p),p=null)},f=(n,r,s,a={})=>{D(t.diagnostics,{type:n,scope:"timer-group",timerId:r?.id,...O(s,r?.state.generation??0),...a})},y=n=>(r,s,a)=>{let S=Q(M(n.id),()=>i.get(n.id)===n&&n.state.generation===a);n.definition.onError?.(r,s,S),D(t.diagnostics,{type:"callback:error",scope:"timer-group",timerId:n.id,error:r,...O(s,a)})},G=(n,r,s)=>a=>{D(t.diagnostics,{type:a.type,scope:"timer-group",timerId:n.id,...a.context,..."reason"in a?{reason:a.reason}:{},..."error"in a?{error:a.error}:{},...O(r,s)})},M=n=>({start:()=>H(n),pause:()=>J(n),resume:()=>K(n),reset:r=>W(n,r),restart:()=>q(n),cancel:r=>j(n,r)}),v=(n,r=T(),s=!1)=>{if(n.state.status!=="running")return!1;let a=n.state.generation,S=Q(M(n.id),()=>i.get(n.id)===n&&n.state.generation===a),b=ye(n,r,S,y(n));if(b)return f("timer:end",n,b),!0;let g=C(n,r);return se({schedules:n.definition.schedules,states:n.schedules,snapshot:g,generation:n.state.generation,controls:S,activation:s,isLive:I=>i.get(n.id)===n&&n.state.generation===I,onError:(I,k,E)=>n.definition.onError?.(I,k,E),onEvent:G(n,g,n.state.generation)}),!1},w=(n=!0)=>{A();let r=Array.from(i.values()).filter(S=>S.state.status==="running");if(r.length===0)return;let s=T(),a=t.updateIntervalMs??1e3;for(let S of r)a=Math.min(a,le(S.definition.schedules,S.schedules,s.wallNow,a));n&&f("scheduler:start",r[0],C(r[0],s)),p=setTimeout(()=>{let S=T();for(let b of i.values()){if(b.state.status!=="running")continue;ge(b.state,S);let g=C(b,S);f("timer:tick",b,g),v(b,S)}u(S),w()},a)},te=n=>{let r=i.get(n.id);if(r)return r.definition=n,R(r,T().wallNow),{item:r,added:!1};let s=T(),a={...he(n,s),id:n.id,schedules:new Map};return i.set(n.id,a),R(a,s.wallNow),{item:a,added:!0}},ne=(n={})=>{let r=n.notify??!0,s=n.process??!0,a=n.reschedule??!0,S=n.autoStart??!0;L(t);let b=new Set,g=!1,I=T();if(t.items){for(let k of t.items){b.add(k.id);let{item:E,added:Ae}=te(k);g=g||Ae,S&&k.autoStart&&E.state.status==="idle"&&F(E.state,I)&&(g=!0,s&&(g=v(E,I,!0)||g)),s&&(g=v(E,I)||g)}for(let k of i.keys())b.has(k)||(i.delete(k),g=!0)}g&&(r?u(I):(l=I,c=_(i,I.wallNow))),(g||a)&&w(r)},be=n=>{if(ee([n]),i.has(n.id))throw new Error(`Timer item "${n.id}" already exists`);let{item:r}=te(n),s=T();n.autoStart&&F(r.state,s)&&v(r,s,!0),h=$(t),u(s),w()},Ce=(n,r)=>{let s=i.get(n);if(!s)return;let a={...s.definition,...r,id:n};ee([a]),s.definition=a,R(s,T().wallNow),v(s),h=$(t),u(),w()},Ie=n=>{i.delete(n)&&(u(),w())},ve=()=>{i.clear(),A(),u()},H=n=>{let r=i.get(n);if(!r)return;let s=T();F(r.state,s)&&(f("timer:start",r,C(r,s)),v(r,s,!0),u(s),w())},J=n=>{let r=i.get(n);if(!r)return;let s=T();me(r.state,s)&&(f("timer:pause",r,C(r,s)),u(s),w())},K=n=>{let r=i.get(n);if(!r)return;let s=T();pe(r.state,s)&&(f("timer:resume",r,C(r,s)),v(r,s,!0),u(s),w())},W=(n,r={})=>{let s=i.get(n);if(!s)return;let a=T();Y(s.state,a,r),s.schedules.clear(),R(s,a.wallNow),Z(s),f("timer:reset",s,C(s,a)),v(s,a,r.autoStart),u(a),w()},q=n=>{let r=i.get(n);if(!r)return;let s=T();fe(r.state,s),r.schedules.clear(),R(r,s.wallNow),Z(r),f("timer:restart",r,C(r,s)),v(r,s,!0),u(s),w()},j=(n,r)=>{let s=i.get(n);if(!s)return;let a=T();Te(s.state,a,r)&&(f("timer:cancel",s,C(s,a),{reason:r}),u(a),w())},ke=n=>{let r=i.get(n);return r?C(r,l):void 0},xe={add:be,update:Ce,remove:Ie,clear:ve,start:H,pause:J,resume:K,reset:W,restart:q,cancel:j,startAll:()=>Array.from(i.keys()).forEach(H),pauseAll:()=>Array.from(i.keys()).forEach(J),resumeAll:()=>Array.from(i.keys()).forEach(K),resetAll:n=>Array.from(i.keys()).forEach(r=>W(r,n)),restartAll:()=>Array.from(i.keys()).forEach(q),cancelAll:n=>Array.from(i.keys()).forEach(r=>j(r,n))};return ne({notify:!1,process:!1,reschedule:!1,autoStart:!1}),{commitOptions:()=>{m&&(m=!1,ne({notify:!0,process:!0,reschedule:!0,autoStart:!0}))},controls:xe,destroy:A,getSnapshot:()=>c,getServerSnapshot:()=>d,getTimer:ke,setOptions:n=>{L(n);let r=$(n);m=m||n.items!==t.items||r!==h,t=n,h=r},subscribe:n=>(o.add(n),()=>o.delete(n))}}function _(e,t){return{now:t,size:e.size,ids:Array.from(e.keys())}}function R(e,t){ae(e.definition.schedules,e.schedules,t,e.state.status==="running")}function $(e){return JSON.stringify([e.updateIntervalMs??1e3,...(e.items??[]).map(t=>{let o=ue(t.schedules);return[t.id,t.autoStart??!1,o??""]})])}function L(e){N(e.updateIntervalMs??1e3,"updateIntervalMs"),ee(e.items)}function ee(e){let t=new Set;e?.forEach(o=>{if(!o.id)throw new Error("Timer item id is required");if(t.has(o.id))throw new Error(`Duplicate timer item id "${o.id}"`);t.add(o.id),ce(o.schedules)})}0&&(module.exports={useTimerGroup});
@@ -0,0 +1,6 @@
1
+ import { d as UseTimerGroupOptions, e as TimerGroupResult } from './types-D-Vzr-PF.cjs';
2
+ export { f as TimerGroupItem, g as TimerGroupItemControls } from './types-D-Vzr-PF.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-D-Vzr-PF.js';
2
+ export { f as TimerGroupItem, g as TimerGroupItemControls } from './types-D-Vzr-PF.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 A,b as N,c as Z,d as _,e as ee,f as te,g as re,h as se,i as L,j as f,k as oe}from"./chunk-OYRPLLEV.js";import{a as D,b as a,c as H,f as b,g as K,h as Q,i as V,j as W,k as X,m as Y}from"./chunk-XXJAJJJ6.js";import{useEffect as ne,useRef as he,useSyncExternalStore as Se}from"react";function Te(i={}){let o=he(null);return o.current===null&&(o.current=ye(i)),o.current.setOptions(i),ne(()=>{o.current?.commitOptions()}),ne(()=>{let n=o.current;return()=>n.destroy()},[]),{...Se(o.current.subscribe,o.current.getSnapshot,o.current.getServerSnapshot),get:o.current.getTimer,...o.current.controls}}function ye(i){let o=i;q(o);let l=new Set,n=new Map,G=a(),w=$(n,G.wallNow),ie=w,k=null,v=R(o),E=!0,m=(e=a())=>{G=e,w=$(n,e.wallNow),l.forEach(t=>t())},C=()=>{k!==null&&(clearTimeout(k),k=null)},h=(e,t,r,s={})=>{A(o.diagnostics,{type:e,scope:"timer-group",timerId:t?.id,...N(r,t?.state.generation??0),...s})},ae=e=>(t,r,s)=>{let u=D(J(e.id),()=>n.get(e.id)===e&&e.state.generation===s);e.definition.onError?.(t,r,u),A(o.diagnostics,{type:"callback:error",scope:"timer-group",timerId:e.id,error:t,...N(r,s)})},ue=(e,t,r)=>s=>{A(o.diagnostics,{type:s.type,scope:"timer-group",timerId:e.id,...s.context,..."reason"in s?{reason:s.reason}:{},..."error"in s?{error:s.error}:{},...N(t,r)})},J=e=>({start:()=>O(e),pause:()=>M(e),resume:()=>P(e),reset:t=>x(e,t),restart:()=>U(e),cancel:t=>z(e,t)}),S=(e,t=a(),r=!1)=>{if(e.state.status!=="running")return!1;let s=e.state.generation,u=D(J(e.id),()=>n.get(e.id)===e&&e.state.generation===s),p=oe(e,t,u,ae(e));if(p)return h("timer:end",e,p),!0;let c=f(e,t);return Z({schedules:e.definition.schedules,states:e.schedules,snapshot:c,generation:e.state.generation,controls:u,activation:r,isLive:g=>n.get(e.id)===e&&e.state.generation===g,onError:(g,T,y)=>e.definition.onError?.(g,T,y),onEvent:ue(e,c,e.state.generation)}),!1},d=(e=!0)=>{C();let t=Array.from(n.values()).filter(u=>u.state.status==="running");if(t.length===0)return;let r=a(),s=o.updateIntervalMs??1e3;for(let u of t)s=Math.min(s,_(u.definition.schedules,u.schedules,r.wallNow,s));e&&h("scheduler:start",t[0],f(t[0],r)),k=setTimeout(()=>{let u=a();for(let p of n.values()){if(p.state.status!=="running")continue;Y(p.state,u);let c=f(p,u);h("timer:tick",p,c),S(p,u)}m(u),d()},s)},j=e=>{let t=n.get(e.id);if(t)return t.definition=e,I(t,a().wallNow),{item:t,added:!1};let r=a(),s={...se(e,r),id:e.id,schedules:new Map};return n.set(e.id,s),I(s,r.wallNow),{item:s,added:!0}},B=(e={})=>{let t=e.notify??!0,r=e.process??!0,s=e.reschedule??!0,u=e.autoStart??!0;q(o);let p=new Set,c=!1,g=a();if(o.items){for(let T of o.items){p.add(T.id);let{item:y,added:ge}=j(T);c=c||ge,u&&T.autoStart&&y.state.status==="idle"&&b(y.state,g)&&(c=!0,r&&(c=S(y,g,!0)||c)),r&&(c=S(y,g)||c)}for(let T of n.keys())p.has(T)||(n.delete(T),c=!0)}c&&(t?m(g):(G=g,w=$(n,g.wallNow))),(c||s)&&d(t)},ce=e=>{if(F([e]),n.has(e.id))throw new Error(`Timer item "${e.id}" already exists`);let{item:t}=j(e),r=a();e.autoStart&&b(t.state,r)&&S(t,r,!0),v=R(o),m(r),d()},le=(e,t)=>{let r=n.get(e);if(!r)return;let s={...r.definition,...t,id:e};F([s]),r.definition=s,I(r,a().wallNow),S(r),v=R(o),m(),d()},me=e=>{n.delete(e)&&(m(),d())},de=()=>{n.clear(),C(),m()},O=e=>{let t=n.get(e);if(!t)return;let r=a();b(t.state,r)&&(h("timer:start",t,f(t,r)),S(t,r,!0),m(r),d())},M=e=>{let t=n.get(e);if(!t)return;let r=a();K(t.state,r)&&(h("timer:pause",t,f(t,r)),m(r),d())},P=e=>{let t=n.get(e);if(!t)return;let r=a();Q(t.state,r)&&(h("timer:resume",t,f(t,r)),S(t,r,!0),m(r),d())},x=(e,t={})=>{let r=n.get(e);if(!r)return;let s=a();V(r.state,s,t),r.schedules.clear(),I(r,s.wallNow),L(r),h("timer:reset",r,f(r,s)),S(r,s,t.autoStart),m(s),d()},U=e=>{let t=n.get(e);if(!t)return;let r=a();W(t.state,r),t.schedules.clear(),I(t,r.wallNow),L(t),h("timer:restart",t,f(t,r)),S(t,r,!0),m(r),d()},z=(e,t)=>{let r=n.get(e);if(!r)return;let s=a();X(r.state,s,t)&&(h("timer:cancel",r,f(r,s),{reason:t}),m(s),d())},pe=e=>{let t=n.get(e);return t?f(t,G):void 0},fe={add:ce,update:le,remove:me,clear:de,start:O,pause:M,resume:P,reset:x,restart:U,cancel:z,startAll:()=>Array.from(n.keys()).forEach(O),pauseAll:()=>Array.from(n.keys()).forEach(M),resumeAll:()=>Array.from(n.keys()).forEach(P),resetAll:e=>Array.from(n.keys()).forEach(t=>x(t,e)),restartAll:()=>Array.from(n.keys()).forEach(U),cancelAll:e=>Array.from(n.keys()).forEach(t=>z(t,e))};return B({notify:!1,process:!1,reschedule:!1,autoStart:!1}),{commitOptions:()=>{E&&(E=!1,B({notify:!0,process:!0,reschedule:!0,autoStart:!0}))},controls:fe,destroy:C,getSnapshot:()=>w,getServerSnapshot:()=>ie,getTimer:pe,setOptions:e=>{q(e);let t=R(e);E=E||e.items!==o.items||t!==v,o=e,v=t},subscribe:e=>(l.add(e),()=>l.delete(e))}}function $(i,o){return{now:o,size:i.size,ids:Array.from(i.keys())}}function I(i,o){ee(i.definition.schedules,i.schedules,o,i.state.status==="running")}function R(i){return JSON.stringify([i.updateIntervalMs??1e3,...(i.items??[]).map(o=>{let l=te(o.schedules);return[o.id,o.autoStart??!1,l??""]})])}function q(i){H(i.updateIntervalMs??1e3,"updateIntervalMs"),F(i.items)}function F(i){let o=new Set;i?.forEach(l=>{if(!l.id)throw new Error("Timer item id is required");if(o.has(l.id))throw new Error(`Duplicate timer item id "${l.id}"`);o.add(l.id),re(l.schedules)})}export{Te as useTimerGroup};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var le=Object.defineProperty;var Se=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var he=(e,u)=>{for(var a in u)le(e,a,{get:u[a],enumerable:!0})},ye=(e,u,a,o)=>{if(u&&typeof u=="object"||typeof u=="function")for(let w of Te(u))!be.call(e,w)&&w!==a&&le(e,w,{get:()=>u[w],enumerable:!(o=Se(u,w))||o.enumerable});return e};var Ie=e=>ye(le({},"__esModule",{value:!0}),e);var Me={};he(Me,{durationParts:()=>me,useTimer:()=>fe,useTimerGroup:()=>pe});module.exports=Ie(Me);function me(e){let u=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),a=Math.floor(u/864e5),o=u%864e5,w=Math.floor(o/36e5),M=o%36e5,S=Math.floor(M/6e4),v=M%6e4,D=Math.floor(v/1e3);return{totalMilliseconds:u,totalSeconds:Math.floor(u/1e3),milliseconds:v%1e3,seconds:D,minutes:S,hours:w,days:a}}var m=require("react");function we(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 G(e,u){let a=we(e);!a.enabled||!a.logger||u.type==="timer:tick"&&!a.includeTicks||a.logger({...u,label:u.label??a.label})}function C(e,u){return{generation:u,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function f(){let e=Date.now(),u=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:u}}function W(e,u){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${u} must be a finite number greater than 0`)}function te(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 re(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=re(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 j(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 ne(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=re(e,u),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=u.wallNow,e.now=u.wallNow,!0)}function oe(e,u){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=u.monotonicNow,e.now=u.wallNow,!0)}function _(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 ue(e,u){return _(e,u,{autoStart:!0})}function se(e,u,a){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=re(e,u),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=u.wallNow,e.cancelReason=a??null,e.now=u.wallNow,!0)}function ae(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=re(e,u),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=u.wallNow,e.now=u.wallNow,!0)}function ie(e,u){return e.status!=="running"?!1:(e.tick+=1,e.now=u.wallNow,!0)}function fe(e={}){let u=e.updateIntervalMs??1e3;W(u,"updateIntervalMs"),Ae(e.schedules);let a=(0,m.useRef)(e);a.current=e;let o=(0,m.useRef)(null);o.current===null&&(o.current=te(f()));let w=(0,m.useRef)(!1),M=(0,m.useRef)(null),S=(0,m.useRef)(new Map),v=(0,m.useRef)(null),[,D]=(0,m.useReducer)(s=>s+1,0),d=(0,m.useCallback)(()=>{M.current!==null&&(clearTimeout(M.current),M.current=null)},[]),O=(0,m.useCallback)((s=f())=>g(o.current,s),[]),T=(0,m.useCallback)((s,c,p={})=>{G(a.current.debug,{type:s,scope:"timer",...C(c,o.current.generation),...p})},[]),x=(0,m.useRef)(null),q=(0,m.useCallback)(s=>{let c=o.current.generation;if(v.current!==c){v.current=c;try{a.current.onEnd?.(s,x.current)}catch(p){G(a.current.debug,{type:"callback:error",scope:"timer",...C(s,c),error:p})}}},[]),k=(0,m.useCallback)((s,c,p,I,R)=>{if(p.pending&&(s.overlap??"skip")==="skip"){G(a.current.debug,{type:"schedule:skip",scope:"timer",scheduleId:s.id??c,reason:"overlap",...C(I,R)});return}p.lastRunAt=I.now,p.pending=!0,G(a.current.debug,{type:"schedule:start",scope:"timer",scheduleId:s.id??c,...C(I,R)}),Promise.resolve().then(()=>s.callback(I,x.current)).then(()=>{G(a.current.debug,{type:"schedule:end",scope:"timer",scheduleId:s.id??c,...C(I,R)})},h=>{G(a.current.debug,{type:"schedule:error",scope:"timer",scheduleId:s.id??c,error:h,...C(I,R)})}).finally(()=>{o.current?.generation===R&&(p.pending=!1)})},[]),B=(0,m.useCallback)((s,c,p=!1)=>{let I=a.current.schedules??[],R=new Set;I.forEach((h,z)=>{let P=h.id??String(z);R.add(P);let E=S.current.get(P);if(E||(E={lastRunAt:null,pending:!1,leadingGeneration:null},S.current.set(P,E)),p&&h.leading&&E.leadingGeneration!==c){E.leadingGeneration=c,k(h,P,E,s,c);return}if(E.lastRunAt===null){E.lastRunAt=s.now;return}s.now-E.lastRunAt>=h.everyMs&&k(h,P,E,s,c)});for(let h of S.current.keys())R.has(h)||S.current.delete(h)},[k]),A=(0,m.useCallback)((s=f(),c=!1)=>{let p=o.current;if(p.status!=="running")return;let I=g(p,s),R=p.generation;if(a.current.endWhen?.(I)){if(ae(p,s)){let h=g(p,s);T("timer:end",h),d(),q(h),D()}return}B(I,R,c)},[q,d,T,B]),J=(0,m.useCallback)((s=f())=>{let c=a.current.updateIntervalMs??1e3,p=c,I=o.current;return I.status!=="running"?c:((a.current.schedules??[]).forEach((h,z)=>{let P=h.id??String(z),ee=S.current.get(P)?.lastRunAt??I.startedAt??s.wallNow;p=Math.min(p,Math.max(1,ee+h.everyMs-s.wallNow))}),p)},[]),Q=(0,m.useCallback)(()=>{let s=f();if(!j(o.current,s))return;let c=g(o.current,s);T("timer:start",c),A(s,!0),D()},[T,A]),V=(0,m.useCallback)(()=>{let s=f();if(!ne(o.current,s))return;d();let c=g(o.current,s);T("timer:pause",c),D()},[d,T]),X=(0,m.useCallback)(()=>{let s=f();if(!oe(o.current,s))return;let c=g(o.current,s);T("timer:resume",c),A(s,!0),D()},[T,A]),Z=(0,m.useCallback)((s={})=>{let c=f();d(),_(o.current,c,s),S.current.clear(),v.current=null;let p=g(o.current,c);T("timer:reset",p),s.autoStart&&A(c,!0),D()},[d,T,A]),$=(0,m.useCallback)(()=>{let s=f();d(),ue(o.current,s),S.current.clear(),v.current=null;let c=g(o.current,s);T("timer:restart",c),A(s,!0),D()},[d,T,A]),F=(0,m.useCallback)(s=>{let c=f();if(!se(o.current,c,s))return;d();let p=g(o.current,c);T("timer:cancel",p,{reason:s}),D()},[d,T]);x.current=(0,m.useMemo)(()=>({start:Q,pause:V,resume:X,reset:Z,restart:$,cancel:F}),[F,V,Z,$,X,Q]),(0,m.useEffect)(()=>(w.current=!0,a.current.autoStart&&o.current.status==="idle"&&x.current.start(),()=>{w.current=!1,d()}),[d]);let U=O(),H=o.current.generation,L=U.status;return(0,m.useEffect)(()=>{if(!w.current||L!=="running"){d();return}return d(),T("scheduler:start",O()),M.current=setTimeout(()=>{if(!w.current||o.current.generation!==H||o.current.status!=="running")return;let s=f();ie(o.current,s);let c=g(o.current,s);T("timer:tick",c),A(s),D()},J()),()=>{M.current!==null&&T("scheduler:stop",O()),d()}},[d,T,H,J,O,A,U.tick,L]),{...U,...x.current}}function Ae(e){e?.forEach(u=>W(u.everyMs,"schedule.everyMs"))}var l=require("react");function pe(e={}){let u=e.updateIntervalMs??1e3;W(u,"updateIntervalMs"),ce(e.items);let a=(0,l.useRef)(e);a.current=e;let o=(0,l.useRef)(new Map),w=(0,l.useRef)(!1),M=(0,l.useRef)(null),[,S]=(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())=>g(t.state,r),[]),d=(0,l.useCallback)((t,r,n,i={})=>{G(a.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...C(n,r?.state.generation??0),...i})},[]),O=(0,l.useCallback)(t=>({start:()=>$(t),pause:()=>F(t),resume:()=>U(t),reset:r=>H(t,r),restart:()=>L(t),cancel:r=>s(t,r)}),[]),T=(0,l.useCallback)((t,r)=>{let n=t.state.generation;if(t.endCalledGeneration!==n){t.endCalledGeneration=n;try{t.definition.onEnd?.(r,O(t.id))}catch(i){G(a.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:i,...C(r,n)})}}},[O]),x=(0,l.useCallback)((t,r,n,i,y,b)=>{if(i.pending&&(r.overlap??"skip")==="skip"){G(a.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,reason:"overlap",...C(y,b)});return}i.lastRunAt=y.now,i.pending=!0,G(a.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...C(y,b)}),Promise.resolve().then(()=>r.callback(y,O(t.id))).then(()=>{G(a.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...C(y,b)})},K=>{G(a.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,error:K,...C(y,b)})}).finally(()=>{o.current.get(t.id)?.state.generation===b&&(i.pending=!1)})},[O]),q=(0,l.useCallback)((t,r,n=!1)=>{let i=t.definition.schedules??[],y=new Set;i.forEach((b,K)=>{let Y=b.id??String(K);y.add(Y);let N=t.schedules.get(Y);if(N||(N={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(Y,N)),n&&b.leading&&N.leadingGeneration!==t.state.generation){N.leadingGeneration=t.state.generation,x(t,b,Y,N,r,t.state.generation);return}if(N.lastRunAt===null){N.lastRunAt=t.state.startedAt??r.now,r.now-N.lastRunAt>=b.everyMs&&x(t,b,Y,N,r,t.state.generation);return}r.now-N.lastRunAt>=b.everyMs&&x(t,b,Y,N,r,t.state.generation)});for(let b of t.schedules.keys())y.has(b)||t.schedules.delete(b)},[x]),k=(0,l.useCallback)((t,r=f(),n=!1)=>{if(t.state.status!=="running")return;let i=g(t.state,r);if(t.definition.endWhen?.(i)){if(ae(t.state,r)){let y=g(t.state,r);d("timer:end",t,y),T(t,y)}return}q(t,i,n)},[T,d,q]),B=(0,l.useCallback)((t=f())=>{let n=a.current.updateIntervalMs??1e3;for(let i of o.current.values()){if(i.state.status!=="running")continue;(i.definition.schedules??[]).forEach((b,K)=>{let Y=b.id??String(K),ge=i.schedules.get(Y)?.lastRunAt??i.state.startedAt??t.wallNow;n=Math.min(n,Math.max(1,ge+b.everyMs-t.wallNow))})}return n},[]),A=(0,l.useCallback)(t=>{let r=o.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let n={id:t.id,state:te(f()),definition:t,schedules:new Map,endCalledGeneration:null};return o.current.set(t.id,n),t.autoStart&&j(n.state,f()),{item:n,added:!0}},[]),J=(0,l.useCallback)(()=>{let t=a.current.items??[],r=new Set,n=!1;t.forEach(i=>{r.add(i.id);let{item:y,added:b}=A(i);n=n||b,i.autoStart&&y.state.status==="idle"&&(n=j(y.state,f())||n)});for(let i of o.current.keys())r.has(i)||(o.current.delete(i),n=!0);return n},[A]);(0,l.useEffect)(()=>{J()&&S()},[J,e.items]);let Q=(0,l.useCallback)(t=>{if(ce([t]),o.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);A(t),S()},[A]),V=(0,l.useCallback)((t,r)=>{let n=o.current.get(t);if(!n)return;let i={...n.definition,...r,id:t};ce([i]),n.definition=i,S()},[]),X=(0,l.useCallback)(t=>{o.current.delete(t),S()},[]),Z=(0,l.useCallback)(()=>{o.current.clear(),v(),S()},[v]),$=(0,l.useCallback)(t=>{let r=o.current.get(t);if(!r)return;let n=f();j(r.state,n)&&(d("timer:start",r,g(r.state,n)),k(r,n,!0),S())},[d,k]),F=(0,l.useCallback)(t=>{let r=o.current.get(t);if(!r)return;let n=f();ne(r.state,n)&&(d("timer:pause",r,g(r.state,n)),S())},[d]),U=(0,l.useCallback)(t=>{let r=o.current.get(t);if(!r)return;let n=f();oe(r.state,n)&&(d("timer:resume",r,g(r.state,n)),k(r,n,!0),S())},[d,k]),H=(0,l.useCallback)((t,r={})=>{let n=o.current.get(t);if(!n)return;let i=f();_(n.state,i,r),n.schedules.clear(),n.endCalledGeneration=null,d("timer:reset",n,g(n.state,i)),r.autoStart&&k(n,i,!0),S()},[d,k]),L=(0,l.useCallback)(t=>{let r=o.current.get(t);if(!r)return;let n=f();ue(r.state,n),r.schedules.clear(),r.endCalledGeneration=null,d("timer:restart",r,g(r.state,n)),k(r,n,!0),S()},[d,k]),s=(0,l.useCallback)((t,r)=>{let n=o.current.get(t);if(!n)return;let i=f();se(n.state,i,r)&&(d("timer:cancel",n,g(n.state,i),{reason:r}),S())},[d]),p=Array.from(o.current.keys()).map(t=>`${t}:${o.current.get(t).state.status}:${o.current.get(t).state.generation}:${o.current.get(t).state.tick}`).join("|");(0,l.useEffect)(()=>{w.current=!0;let t=Array.from(o.current.values()).filter(n=>n.state.status==="running");if(t.length===0){v();return}v();let r=t[0];return d("scheduler:start",r,g(r.state,f())),M.current=setTimeout(()=>{if(!w.current)return;let n=f();for(let i of o.current.values()){if(i.state.status!=="running")continue;ie(i.state,n);let y=g(i.state,n);d("timer:tick",i,y),k(i,n)}S()},B()),()=>{M.current!==null&&d("scheduler:stop",r,g(r.state,f())),v(),w.current=!1}},[p,v,d,B,k]);let I=(0,l.useCallback)(t=>{let r=o.current.get(t);if(r)return D(r)},[D]),R=f().wallNow,h=(0,l.useCallback)(()=>Array.from(o.current.keys()).forEach($),[$]),z=(0,l.useCallback)(()=>Array.from(o.current.keys()).forEach(F),[F]),P=(0,l.useCallback)(()=>Array.from(o.current.keys()).forEach(U),[U]),E=(0,l.useCallback)(t=>Array.from(o.current.keys()).forEach(r=>H(r,t)),[H]),ee=(0,l.useCallback)(()=>Array.from(o.current.keys()).forEach(L),[L]),de=(0,l.useCallback)(t=>Array.from(o.current.keys()).forEach(r=>s(r,t)),[s]);return(0,l.useMemo)(()=>({now:R,size:o.current.size,ids:Array.from(o.current.keys()),get:I,add:Q,update:V,remove:X,clear:Z,start:$,pause:F,resume:U,reset:H,restart:L,cancel:s,startAll:h,pauseAll:z,resumeAll:P,resetAll:E,restartAll:ee,cancelAll:de}),[Q,s,de,Z,I,R,F,z,X,H,E,L,ee,U,P,$,h,V])}function ce(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(o=>W(o.everyMs,"schedule.everyMs"))})}0&&(module.exports={durationParts,useTimer,useTimerGroup});
1
+ "use strict";var g=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var j=(e,t)=>{for(var o in t)g(e,o,{get:t[o],enumerable:!0})},q=(e,t,o,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of G(t))!$.call(e,i)&&i!==o&&g(e,i,{get:()=>t[i],enumerable:!(r=D(t,i))||r.enumerable});return e};var z=e=>q(g({},"__esModule",{value:!0}),e);var H={};j(H,{useTimer:()=>U});module.exports=z(H);var d=require("react");function R(e,t){return{start:()=>{t()&&e.start()},pause:()=>{t()&&e.pause()},resume:()=>{t()&&e.resume()},reset:o=>{t()&&e.reset(o)},restart:()=>{t()&&e.restart()},cancel:o=>{t()&&e.cancel(o)}}}function l(){let e=Date.now(),t=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:t}}function A(e,t){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${t} must be a finite number greater than 0`)}function y(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 b(e,t){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+t.monotonicNow-e.activeStartedAtMonotonic)}function f(e,t){let o=b(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 N(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 I(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=b(e,t),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=t.wallNow,e.now=t.wallNow,!0)}function k(e,t){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function C(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 E(e,t){return C(e,t,{autoStart:!0})}function x(e,t,o){return e.status!=="running"&&e.status!=="paused"?!1:(e.baseElapsedMilliseconds=b(e,t),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=t.wallNow,e.cancelReason=o??null,e.now=t.wallNow,!0)}function O(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=b(e,t),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=t.wallNow,e.now=t.wallNow,!0)}function P(e,t){return e.status!=="running"?!1:(e.tick+=1,e.now=t.wallNow,!0)}function U(e={}){let t=(0,d.useRef)(null);return t.current===null&&(t.current=B(e)),t.current.setOptions(e),(0,d.useEffect)(()=>{t.current?.commitOptions()}),(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.getServerSnapshot),...t.current.controls}}function B(e){let t=e;A(t.updateIntervalMs??1e3,"updateIntervalMs");let o=new Set,r=y(l()),i=f(r,l()),F=i,S=null,T=null,w=!1,s=(n=l())=>{i=f(r,n),o.forEach(a=>a())},u=()=>{S!==null&&(clearTimeout(S),S=null)},W=n=>{if(T===r.generation)return;T=r.generation;let a=r.generation,M=R(h,()=>r.generation===a),v=p=>{if(t.onError){t.onError(p,n,M);return}setTimeout(()=>{throw p},0)};try{Promise.resolve(t.onEnd?.(n,M)).catch(p=>{v(p)})}catch(p){v(p)}},m=n=>{let a=f(r,n);return!t.endWhen?.(a)||!O(r,n)?!1:(u(),s(n),W(f(r,n)),!0)},c=()=>{u(),r.status==="running"&&(S=setTimeout(()=>{if(r.status!=="running")return;let n=l();P(r,n),!m(n)&&(s(n),c())},t.updateIntervalMs??1e3))},h={start:()=>{let n=l();if(!N(r,n)){r.status==="running"&&c();return}s(n),m(n)||c()},pause:()=>{let n=l();I(r,n)&&(u(),s(n))},resume:()=>{let n=l();k(r,n)&&(s(n),m(n)||c())},reset:(n={})=>{let a=l();u(),C(r,a,n),T=null,s(a),n.autoStart&&!m(a)&&c()},restart:()=>{let n=l();u(),E(r,n),T=null,s(n),m(n)||c()},cancel:n=>{let a=l();x(r,a,n)&&(u(),s(a))}};return{commitOptions:()=>{if(w&&(w=!1,r.status==="running")){let n=l();m(n)||c()}},controls:h,destroy:u,getSnapshot:()=>i,getServerSnapshot:()=>F,setOptions:n=>{A(n.updateIntervalMs??1e3,"updateIntervalMs"),w=w||(n.updateIntervalMs??1e3)!==(t.updateIntervalMs??1e3)||n.endWhen!==t.endWhen,t=n},subscribe:n=>(o.add(n),()=>o.delete(n))}}0&&(module.exports={useTimer});
package/dist/index.d.cts CHANGED
@@ -1,129 +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 TimerSchedule = {
39
- id?: string;
40
- everyMs: number;
41
- leading?: boolean;
42
- overlap?: 'skip' | 'allow';
43
- callback: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
44
- };
45
- type TimerDebug = boolean | TimerDebugLogger | {
46
- enabled?: boolean;
47
- logger?: TimerDebugLogger;
48
- includeTicks?: boolean;
49
- label?: string;
50
- };
51
- type TimerDebugLogger = (event: TimerDebugEvent) => void;
52
- type TimerDebugEvent = {
53
- 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';
54
- scope: 'timer' | 'timer-group';
55
- label?: string;
56
- timerId?: string;
57
- scheduleId?: string;
58
- generation: number;
59
- tick: number;
60
- now: number;
61
- elapsedMilliseconds: number;
62
- status: TimerStatus;
63
- reason?: string;
64
- error?: unknown;
65
- };
66
- type UseTimerOptions = {
67
- autoStart?: boolean;
68
- updateIntervalMs?: number;
69
- endWhen?: TimerEndPredicate;
70
- onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
71
- schedules?: TimerSchedule[];
72
- debug?: TimerDebug;
73
- };
74
- type TimerGroupItemControls = {
75
- start(): void;
76
- pause(): void;
77
- resume(): void;
78
- reset(options?: {
79
- autoStart?: boolean;
80
- }): void;
81
- restart(): void;
82
- cancel(reason?: string): void;
83
- };
84
- type TimerGroupItem = {
85
- id: string;
86
- autoStart?: boolean;
87
- endWhen?: TimerEndPredicate;
88
- onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
89
- schedules?: TimerSchedule[];
90
- };
91
- type UseTimerGroupOptions = {
92
- updateIntervalMs?: number;
93
- items?: TimerGroupItem[];
94
- debug?: TimerDebug;
95
- };
96
- type TimerGroupResult = {
97
- now: number;
98
- size: number;
99
- ids: string[];
100
- get(id: string): TimerSnapshot | undefined;
101
- add(item: TimerGroupItem): void;
102
- update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
103
- remove(id: string): void;
104
- clear(): void;
105
- start(id: string): void;
106
- pause(id: string): void;
107
- resume(id: string): void;
108
- reset(id: string, options?: {
109
- autoStart?: boolean;
110
- }): void;
111
- restart(id: string): void;
112
- cancel(id: string, reason?: string): void;
113
- startAll(): void;
114
- pauseAll(): void;
115
- resumeAll(): void;
116
- resetAll(options?: {
117
- autoStart?: boolean;
118
- }): void;
119
- restartAll(): void;
120
- cancelAll(reason?: string): void;
121
- };
122
-
123
- declare function durationParts(milliseconds: number): DurationParts;
1
+ import { U as UseTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-D-Vzr-PF.cjs';
2
+ export { b as TimerEndPredicate, c as TimerStatus } from './types-D-Vzr-PF.cjs';
124
3
 
125
4
  declare function useTimer(options?: UseTimerOptions): TimerSnapshot & TimerControls;
126
5
 
127
- declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
128
-
129
- export { type DurationParts, type TimerControls, type TimerDebug, type TimerDebugEvent, type TimerDebugLogger, type TimerEndPredicate, type TimerGroupItem, type TimerGroupItemControls, type TimerGroupResult, type TimerSchedule, type TimerSnapshot, type TimerStatus, type UseTimerGroupOptions, type UseTimerOptions, durationParts, useTimer, useTimerGroup };
6
+ export { TimerControls, TimerSnapshot, UseTimerOptions, useTimer };
package/dist/index.d.ts CHANGED
@@ -1,129 +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 TimerSchedule = {
39
- id?: string;
40
- everyMs: number;
41
- leading?: boolean;
42
- overlap?: 'skip' | 'allow';
43
- callback: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
44
- };
45
- type TimerDebug = boolean | TimerDebugLogger | {
46
- enabled?: boolean;
47
- logger?: TimerDebugLogger;
48
- includeTicks?: boolean;
49
- label?: string;
50
- };
51
- type TimerDebugLogger = (event: TimerDebugEvent) => void;
52
- type TimerDebugEvent = {
53
- 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';
54
- scope: 'timer' | 'timer-group';
55
- label?: string;
56
- timerId?: string;
57
- scheduleId?: string;
58
- generation: number;
59
- tick: number;
60
- now: number;
61
- elapsedMilliseconds: number;
62
- status: TimerStatus;
63
- reason?: string;
64
- error?: unknown;
65
- };
66
- type UseTimerOptions = {
67
- autoStart?: boolean;
68
- updateIntervalMs?: number;
69
- endWhen?: TimerEndPredicate;
70
- onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
71
- schedules?: TimerSchedule[];
72
- debug?: TimerDebug;
73
- };
74
- type TimerGroupItemControls = {
75
- start(): void;
76
- pause(): void;
77
- resume(): void;
78
- reset(options?: {
79
- autoStart?: boolean;
80
- }): void;
81
- restart(): void;
82
- cancel(reason?: string): void;
83
- };
84
- type TimerGroupItem = {
85
- id: string;
86
- autoStart?: boolean;
87
- endWhen?: TimerEndPredicate;
88
- onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
89
- schedules?: TimerSchedule[];
90
- };
91
- type UseTimerGroupOptions = {
92
- updateIntervalMs?: number;
93
- items?: TimerGroupItem[];
94
- debug?: TimerDebug;
95
- };
96
- type TimerGroupResult = {
97
- now: number;
98
- size: number;
99
- ids: string[];
100
- get(id: string): TimerSnapshot | undefined;
101
- add(item: TimerGroupItem): void;
102
- update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
103
- remove(id: string): void;
104
- clear(): void;
105
- start(id: string): void;
106
- pause(id: string): void;
107
- resume(id: string): void;
108
- reset(id: string, options?: {
109
- autoStart?: boolean;
110
- }): void;
111
- restart(id: string): void;
112
- cancel(id: string, reason?: string): void;
113
- startAll(): void;
114
- pauseAll(): void;
115
- resumeAll(): void;
116
- resetAll(options?: {
117
- autoStart?: boolean;
118
- }): void;
119
- restartAll(): void;
120
- cancelAll(reason?: string): void;
121
- };
122
-
123
- declare function durationParts(milliseconds: number): DurationParts;
1
+ import { U as UseTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-D-Vzr-PF.js';
2
+ export { b as TimerEndPredicate, c as TimerStatus } from './types-D-Vzr-PF.js';
124
3
 
125
4
  declare function useTimer(options?: UseTimerOptions): TimerSnapshot & TimerControls;
126
5
 
127
- declare function useTimerGroup(options?: UseTimerGroupOptions): TimerGroupResult;
128
-
129
- export { type DurationParts, type TimerControls, type TimerDebug, type TimerDebugEvent, type TimerDebugLogger, type TimerEndPredicate, type TimerGroupItem, type TimerGroupItemControls, type TimerGroupResult, type TimerSchedule, 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 Se(e){let s=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),a=Math.floor(s/864e5),o=s%864e5,N=Math.floor(o/36e5),w=o%36e5,g=Math.floor(w/6e4),A=w%6e4,C=Math.floor(A/1e3);return{totalMilliseconds:s,totalSeconds:Math.floor(s/1e3),milliseconds:A%1e3,seconds:C,minutes:g,hours:N,days:a}}import{useCallback as G,useEffect as fe,useMemo as be,useReducer as he,useRef as z}from"react";function Te(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,s){let a=Te(e);!a.enabled||!a.logger||s.type==="timer:tick"&&!a.includeTicks||a.logger({...s,label:s.label??a.label})}function E(e,s){return{generation:s,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function d(){let e=Date.now(),s=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:s}}function j(e,s){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${s} must be a finite number greater than 0`)}function re(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 ne(e,s){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+s.monotonicNow-e.activeStartedAtMonotonic)}function p(e,s){let a=ne(e,s);return{status:e.status,now:s.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,s){return e.status!=="idle"?!1:(e.status="running",e.startedAt=s.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=s.monotonicNow,e.now=s.wallNow,!0)}function oe(e,s){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=ne(e,s),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=s.wallNow,e.now=s.wallNow,!0)}function ue(e,s){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=s.monotonicNow,e.now=s.wallNow,!0)}function ee(e,s,a={}){return e.generation+=1,e.tick=0,e.status=a.autoStart?"running":"idle",e.startedAt=a.autoStart?s.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=a.autoStart?s.monotonicNow:null,e.now=s.wallNow,!0}function se(e,s){return ee(e,s,{autoStart:!0})}function ae(e,s,a){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=ne(e,s),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=s.wallNow,e.cancelReason=a??null,e.now=s.wallNow,!0)}function ie(e,s){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=ne(e,s),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=s.wallNow,e.now=s.wallNow,!0)}function le(e,s){return e.status!=="running"?!1:(e.tick+=1,e.now=s.wallNow,!0)}function ye(e={}){let s=e.updateIntervalMs??1e3;j(s,"updateIntervalMs"),Ie(e.schedules);let a=z(e);a.current=e;let o=z(null);o.current===null&&(o.current=re(d()));let N=z(!1),w=z(null),g=z(new Map),A=z(null),[,C]=he(u=>u+1,0),c=G(()=>{w.current!==null&&(clearTimeout(w.current),w.current=null)},[]),O=G((u=d())=>p(o.current,u),[]),S=G((u,l,f={})=>{R(a.current.debug,{type:u,scope:"timer",...E(l,o.current.generation),...f})},[]),x=z(null),B=G(u=>{let l=o.current.generation;if(A.current!==l){A.current=l;try{a.current.onEnd?.(u,x.current)}catch(f){R(a.current.debug,{type:"callback:error",scope:"timer",...E(u,l),error:f})}}},[]),M=G((u,l,f,y,v)=>{if(f.pending&&(u.overlap??"skip")==="skip"){R(a.current.debug,{type:"schedule:skip",scope:"timer",scheduleId:u.id??l,reason:"overlap",...E(y,v)});return}f.lastRunAt=y.now,f.pending=!0,R(a.current.debug,{type:"schedule:start",scope:"timer",scheduleId:u.id??l,...E(y,v)}),Promise.resolve().then(()=>u.callback(y,x.current)).then(()=>{R(a.current.debug,{type:"schedule:end",scope:"timer",scheduleId:u.id??l,...E(y,v)})},b=>{R(a.current.debug,{type:"schedule:error",scope:"timer",scheduleId:u.id??l,error:b,...E(y,v)})}).finally(()=>{o.current?.generation===v&&(f.pending=!1)})},[]),J=G((u,l,f=!1)=>{let y=a.current.schedules??[],v=new Set;y.forEach((b,K)=>{let P=b.id??String(K);v.add(P);let k=g.current.get(P);if(k||(k={lastRunAt:null,pending:!1,leadingGeneration:null},g.current.set(P,k)),f&&b.leading&&k.leadingGeneration!==l){k.leadingGeneration=l,M(b,P,k,u,l);return}if(k.lastRunAt===null){k.lastRunAt=u.now;return}u.now-k.lastRunAt>=b.everyMs&&M(b,P,k,u,l)});for(let b of g.current.keys())v.has(b)||g.current.delete(b)},[M]),I=G((u=d(),l=!1)=>{let f=o.current;if(f.status!=="running")return;let y=p(f,u),v=f.generation;if(a.current.endWhen?.(y)){if(ie(f,u)){let b=p(f,u);S("timer:end",b),c(),B(b),C()}return}J(y,v,l)},[B,c,S,J]),Q=G((u=d())=>{let l=a.current.updateIntervalMs??1e3,f=l,y=o.current;return y.status!=="running"?l:((a.current.schedules??[]).forEach((b,K)=>{let P=b.id??String(K),te=g.current.get(P)?.lastRunAt??y.startedAt??u.wallNow;f=Math.min(f,Math.max(1,te+b.everyMs-u.wallNow))}),f)},[]),V=G(()=>{let u=d();if(!q(o.current,u))return;let l=p(o.current,u);S("timer:start",l),I(u,!0),C()},[S,I]),X=G(()=>{let u=d();if(!oe(o.current,u))return;c();let l=p(o.current,u);S("timer:pause",l),C()},[c,S]),Z=G(()=>{let u=d();if(!ue(o.current,u))return;let l=p(o.current,u);S("timer:resume",l),I(u,!0),C()},[S,I]),_=G((u={})=>{let l=d();c(),ee(o.current,l,u),g.current.clear(),A.current=null;let f=p(o.current,l);S("timer:reset",f),u.autoStart&&I(l,!0),C()},[c,S,I]),$=G(()=>{let u=d();c(),se(o.current,u),g.current.clear(),A.current=null;let l=p(o.current,u);S("timer:restart",l),I(u,!0),C()},[c,S,I]),F=G(u=>{let l=d();if(!ae(o.current,l,u))return;c();let f=p(o.current,l);S("timer:cancel",f,{reason:u}),C()},[c,S]);x.current=be(()=>({start:V,pause:X,resume:Z,reset:_,restart:$,cancel:F}),[F,X,_,$,Z,V]),fe(()=>(N.current=!0,a.current.autoStart&&o.current.status==="idle"&&x.current.start(),()=>{N.current=!1,c()}),[c]);let U=O(),H=o.current.generation,L=U.status;return fe(()=>{if(!N.current||L!=="running"){c();return}return c(),S("scheduler:start",O()),w.current=setTimeout(()=>{if(!N.current||o.current.generation!==H||o.current.status!=="running")return;let u=d();le(o.current,u);let l=p(o.current,u);S("timer:tick",l),I(u),C()},Q()),()=>{w.current!==null&&S("scheduler:stop",O()),c()}},[c,S,H,Q,O,I,U.tick,L]),{...U,...x.current}}function Ie(e){e?.forEach(s=>j(s.everyMs,"schedule.everyMs"))}import{useCallback as m,useEffect as pe,useMemo as we,useReducer as Ae,useRef as ce}from"react";function Me(e={}){let s=e.updateIntervalMs??1e3;j(s,"updateIntervalMs"),de(e.items);let a=ce(e);a.current=e;let o=ce(new Map),N=ce(!1),w=ce(null),[,g]=Ae(t=>t+1,0),A=m(()=>{w.current!==null&&(clearTimeout(w.current),w.current=null)},[]),C=m((t,r=d())=>p(t.state,r),[]),c=m((t,r,n,i={})=>{R(a.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...E(n,r?.state.generation??0),...i})},[]),O=m(t=>({start:()=>$(t),pause:()=>F(t),resume:()=>U(t),reset:r=>H(t,r),restart:()=>L(t),cancel:r=>u(t,r)}),[]),S=m((t,r)=>{let n=t.state.generation;if(t.endCalledGeneration!==n){t.endCalledGeneration=n;try{t.definition.onEnd?.(r,O(t.id))}catch(i){R(a.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:i,...E(r,n)})}}},[O]),x=m((t,r,n,i,h,T)=>{if(i.pending&&(r.overlap??"skip")==="skip"){R(a.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,reason:"overlap",...E(h,T)});return}i.lastRunAt=h.now,i.pending=!0,R(a.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...E(h,T)}),Promise.resolve().then(()=>r.callback(h,O(t.id))).then(()=>{R(a.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...E(h,T)})},W=>{R(a.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,error:W,...E(h,T)})}).finally(()=>{o.current.get(t.id)?.state.generation===T&&(i.pending=!1)})},[O]),B=m((t,r,n=!1)=>{let i=t.definition.schedules??[],h=new Set;i.forEach((T,W)=>{let Y=T.id??String(W);h.add(Y);let D=t.schedules.get(Y);if(D||(D={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(Y,D)),n&&T.leading&&D.leadingGeneration!==t.state.generation){D.leadingGeneration=t.state.generation,x(t,T,Y,D,r,t.state.generation);return}if(D.lastRunAt===null){D.lastRunAt=t.state.startedAt??r.now,r.now-D.lastRunAt>=T.everyMs&&x(t,T,Y,D,r,t.state.generation);return}r.now-D.lastRunAt>=T.everyMs&&x(t,T,Y,D,r,t.state.generation)});for(let T of t.schedules.keys())h.has(T)||t.schedules.delete(T)},[x]),M=m((t,r=d(),n=!1)=>{if(t.state.status!=="running")return;let i=p(t.state,r);if(t.definition.endWhen?.(i)){if(ie(t.state,r)){let h=p(t.state,r);c("timer:end",t,h),S(t,h)}return}B(t,i,n)},[S,c,B]),J=m((t=d())=>{let n=a.current.updateIntervalMs??1e3;for(let i of o.current.values()){if(i.state.status!=="running")continue;(i.definition.schedules??[]).forEach((T,W)=>{let Y=T.id??String(W),ge=i.schedules.get(Y)?.lastRunAt??i.state.startedAt??t.wallNow;n=Math.min(n,Math.max(1,ge+T.everyMs-t.wallNow))})}return n},[]),I=m(t=>{let r=o.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let n={id:t.id,state:re(d()),definition:t,schedules:new Map,endCalledGeneration:null};return o.current.set(t.id,n),t.autoStart&&q(n.state,d()),{item:n,added:!0}},[]),Q=m(()=>{let t=a.current.items??[],r=new Set,n=!1;t.forEach(i=>{r.add(i.id);let{item:h,added:T}=I(i);n=n||T,i.autoStart&&h.state.status==="idle"&&(n=q(h.state,d())||n)});for(let i of o.current.keys())r.has(i)||(o.current.delete(i),n=!0);return n},[I]);pe(()=>{Q()&&g()},[Q,e.items]);let V=m(t=>{if(de([t]),o.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);I(t),g()},[I]),X=m((t,r)=>{let n=o.current.get(t);if(!n)return;let i={...n.definition,...r,id:t};de([i]),n.definition=i,g()},[]),Z=m(t=>{o.current.delete(t),g()},[]),_=m(()=>{o.current.clear(),A(),g()},[A]),$=m(t=>{let r=o.current.get(t);if(!r)return;let n=d();q(r.state,n)&&(c("timer:start",r,p(r.state,n)),M(r,n,!0),g())},[c,M]),F=m(t=>{let r=o.current.get(t);if(!r)return;let n=d();oe(r.state,n)&&(c("timer:pause",r,p(r.state,n)),g())},[c]),U=m(t=>{let r=o.current.get(t);if(!r)return;let n=d();ue(r.state,n)&&(c("timer:resume",r,p(r.state,n)),M(r,n,!0),g())},[c,M]),H=m((t,r={})=>{let n=o.current.get(t);if(!n)return;let i=d();ee(n.state,i,r),n.schedules.clear(),n.endCalledGeneration=null,c("timer:reset",n,p(n.state,i)),r.autoStart&&M(n,i,!0),g()},[c,M]),L=m(t=>{let r=o.current.get(t);if(!r)return;let n=d();se(r.state,n),r.schedules.clear(),r.endCalledGeneration=null,c("timer:restart",r,p(r.state,n)),M(r,n,!0),g()},[c,M]),u=m((t,r)=>{let n=o.current.get(t);if(!n)return;let i=d();ae(n.state,i,r)&&(c("timer:cancel",n,p(n.state,i),{reason:r}),g())},[c]),f=Array.from(o.current.keys()).map(t=>`${t}:${o.current.get(t).state.status}:${o.current.get(t).state.generation}:${o.current.get(t).state.tick}`).join("|");pe(()=>{N.current=!0;let t=Array.from(o.current.values()).filter(n=>n.state.status==="running");if(t.length===0){A();return}A();let r=t[0];return c("scheduler:start",r,p(r.state,d())),w.current=setTimeout(()=>{if(!N.current)return;let n=d();for(let i of o.current.values()){if(i.state.status!=="running")continue;le(i.state,n);let h=p(i.state,n);c("timer:tick",i,h),M(i,n)}g()},J()),()=>{w.current!==null&&c("scheduler:stop",r,p(r.state,d())),A(),N.current=!1}},[f,A,c,J,M]);let y=m(t=>{let r=o.current.get(t);if(r)return C(r)},[C]),v=d().wallNow,b=m(()=>Array.from(o.current.keys()).forEach($),[$]),K=m(()=>Array.from(o.current.keys()).forEach(F),[F]),P=m(()=>Array.from(o.current.keys()).forEach(U),[U]),k=m(t=>Array.from(o.current.keys()).forEach(r=>H(r,t)),[H]),te=m(()=>Array.from(o.current.keys()).forEach(L),[L]),me=m(t=>Array.from(o.current.keys()).forEach(r=>u(r,t)),[u]);return we(()=>({now:v,size:o.current.size,ids:Array.from(o.current.keys()),get:y,add:V,update:X,remove:Z,clear:_,start:$,pause:F,resume:U,reset:H,restart:L,cancel:u,startAll:b,pauseAll:K,resumeAll:P,resetAll:k,restartAll:te,cancelAll:me}),[V,u,me,_,y,v,F,K,Z,H,k,L,te,U,P,$,b,X])}function de(e){let s=new Set;e?.forEach(a=>{if(s.has(a.id))throw new Error(`Duplicate timer item id "${a.id}"`);s.add(a.id),a.schedules?.forEach(o=>j(o.everyMs,"schedule.everyMs"))})}export{Se as durationParts,ye as useTimer,Me as useTimerGroup};
1
+ import{a as C,b as o,c as h,d as k,e as m,f as I,g as b,h as E,i as O,j as M,k as U,l as R,m as w}from"./chunk-XXJAJJJ6.js";import{useEffect as P,useRef as F,useSyncExternalStore as G}from"react";function N(l={}){let r=F(null);return r.current===null&&(r.current=j(l)),r.current.setOptions(l),P(()=>{r.current?.commitOptions()}),P(()=>{let e=r.current;return l.autoStart&&e.controls.start(),()=>e.destroy()},[]),{...G(r.current.subscribe,r.current.getSnapshot,r.current.getServerSnapshot),...r.current.controls}}function j(l){let r=l;h(r.updateIntervalMs??1e3,"updateIntervalMs");let p=new Set,e=k(o()),d=m(e,o()),W=d,f=null,T=null,S=!1,s=(t=o())=>{d=m(e,t),p.forEach(n=>n())},i=()=>{f!==null&&(clearTimeout(f),f=null)},x=t=>{if(T===e.generation)return;T=e.generation;let n=e.generation,g=C(v,()=>e.generation===n),y=u=>{if(r.onError){r.onError(u,t,g);return}setTimeout(()=>{throw u},0)};try{Promise.resolve(r.onEnd?.(t,g)).catch(u=>{y(u)})}catch(u){y(u)}},c=t=>{let n=m(e,t);return!r.endWhen?.(n)||!R(e,t)?!1:(i(),s(t),x(m(e,t)),!0)},a=()=>{i(),e.status==="running"&&(f=setTimeout(()=>{if(e.status!=="running")return;let t=o();w(e,t),!c(t)&&(s(t),a())},r.updateIntervalMs??1e3))},v={start:()=>{let t=o();if(!I(e,t)){e.status==="running"&&a();return}s(t),c(t)||a()},pause:()=>{let t=o();b(e,t)&&(i(),s(t))},resume:()=>{let t=o();E(e,t)&&(s(t),c(t)||a())},reset:(t={})=>{let n=o();i(),O(e,n,t),T=null,s(n),t.autoStart&&!c(n)&&a()},restart:()=>{let t=o();i(),M(e,t),T=null,s(t),c(t)||a()},cancel:t=>{let n=o();U(e,n,t)&&(i(),s(n))}};return{commitOptions:()=>{if(S&&(S=!1,e.status==="running")){let t=o();c(t)||a()}},controls:v,destroy:i,getSnapshot:()=>d,getServerSnapshot:()=>W,setOptions:t=>{h(t.updateIntervalMs??1e3,"updateIntervalMs"),S=S||(t.updateIntervalMs??1e3)!==(r.updateIntervalMs??1e3)||t.endWhen!==r.endWhen,r=t},subscribe:t=>(p.add(t),()=>p.delete(t))}}export{N as useTimer};
@@ -0,0 +1 @@
1
+ "use strict";var N=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var ue=Object.getOwnPropertyNames;var ce=Object.prototype.hasOwnProperty;var de=(e,t)=>{for(var r in t)N(e,r,{get:t[r],enumerable:!0})},me=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ue(t))!ce.call(e,i)&&i!==r&&N(e,i,{get:()=>t[i],enumerable:!(n=ae(t,i))||n.enumerable});return e};var pe=e=>me(N({},"__esModule",{value:!0}),e);var ge={};de(ge,{useScheduledTimer:()=>ie});module.exports=pe(ge);var y=require("react");function f(){let e=Date.now(),t=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:t}}function x(e,t){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${t} must be a finite number greater than 0`)}function O(e,t){return{start:()=>{t()&&e.start()},pause:()=>{t()&&e.pause()},resume:()=>{t()&&e.resume()},reset:r=>{t()&&e.reset(r)},restart:()=>{t()&&e.restart()},cancel:r=>{t()&&e.cancel(r)}}}function fe(e){return e?typeof e=="function"?{enabled:!0,includeTicks:!1,logger:e}:{enabled:e.enabled!==!1,includeTicks:e.includeTicks??!1,label:e.label,logger:e.logger}:{enabled:!1,includeTicks:!1}}function k(e,t){let r=fe(e);!r.enabled||!r.logger||t.type==="timer:tick"&&!r.includeTicks||r.logger({...t,label:t.label??r.label})}function A(e,t){return{generation:t,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function j(){return{lastRunAt:null,pendingCount:0,leadingGeneration:null,signature:""}}function q({schedules:e=[],states:t,snapshot:r,generation:n,controls:i,activation:s=!1,isLive:u,onError:m,onEvent:T}){let d=new Set;e.forEach((l,h)=>{let p=E(l,h);d.add(p);let S=t.get(p);if(S||(S=j(),S.signature=U(l,h),t.set(p,S)),s&&l.leading&&S.leadingGeneration!==n){S.leadingGeneration=n,$(l,p,S,r,n,i,J(l,p,r.now,r.now,0),u,m,T);return}S.lastRunAt===null&&(S.lastRunAt=r.now);let v=Math.floor((r.now-S.lastRunAt)/l.everyMs);if(v>=1){let C=S.lastRunAt+v*l.everyMs;$(l,p,S,r,n,i,J(l,p,C,r.now,v-1),u,m,T)}});for(let l of t.keys())d.has(l)||t.delete(l)}function z(e,t,r,n){let i=n;return e?.forEach((s,u)=>{let m=E(s,u),d=t.get(m)?.lastRunAt??r;i=Math.min(i,Math.max(1,d+s.everyMs-r))}),i}function B(e,t,r,n){let i=new Set;e?.forEach((s,u)=>{let m=E(s,u),T=U(s,u);i.add(m);let d=t.get(m);if(d)d.signature!==T&&(d.lastRunAt=n?r:null,d.leadingGeneration=null,d.signature=T);else{let l=j();l.lastRunAt=n?r:null,l.signature=T,t.set(m,l)}});for(let s of t.keys())i.has(s)||t.delete(s)}function G(e){return JSON.stringify((e??[]).map((t,r)=>U(t,r)))}function Q(e){let t=new Set;e?.forEach((r,n)=>{x(r.everyMs,"schedule.everyMs");let i=E(r,n);if(t.has(i))throw new Error(`Duplicate schedule id "${i}"`);t.add(i)})}function J(e,t,r,n,i){return{scheduleId:e.id??t,scheduledAt:r,firedAt:n,nextRunAt:r+e.everyMs,overdueCount:i,effectiveEveryMs:e.everyMs}}function $(e,t,r,n,i,s,u,m,T,d){if(r.pendingCount>0&&(e.overlap??"skip")==="skip"){r.lastRunAt=u.scheduledAt,P(m,i,d,{type:"schedule:skip",context:u,reason:"overlap"});return}r.lastRunAt=u.scheduledAt,r.pendingCount+=1,P(m,i,d,{type:"schedule:start",context:u}),Promise.resolve().then(()=>e.callback(n,s,u)).then(()=>P(m,i,d,{type:"schedule:end",context:u}),l=>{if(m(i))try{e.onError?e.onError(l,n,s,u):T?.(l,n,s,u)}finally{d?.({type:"schedule:error",context:u,error:l})}}).finally(()=>{m(i)&&(r.pendingCount=Math.max(0,r.pendingCount-1))})}function E(e,t){return e.id??String(t)}function U(e,t){return JSON.stringify([e.id??t,e.everyMs,e.leading??!1,e.overlap??"skip"])}function P(e,t,r,n){e(t)&&r?.(n)}function V(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 M(e,t){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+t.monotonicNow-e.activeStartedAtMonotonic)}function I(e,t){let r=M(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 X(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 Y(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=M(e,t),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=t.wallNow,e.now=t.wallNow,!0)}function Z(e,t){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=t.monotonicNow,e.now=t.wallNow,!0)}function F(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 _(e,t){return F(e,t,{autoStart:!0})}function L(e,t,r){return e.status!=="running"&&e.status!=="paused"?!1:(e.baseElapsedMilliseconds=M(e,t),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=t.wallNow,e.cancelReason=r??null,e.now=t.wallNow,!0)}function ee(e,t){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=M(e,t),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=t.wallNow,e.now=t.wallNow,!0)}function te(e,t){return e.status!=="running"?!1:(e.tick+=1,e.now=t.wallNow,!0)}function ne(e,t){return{state:V(t),definition:e,endCalledGeneration:null}}function W(e){e.endCalledGeneration=null}function g(e,t){return I(e.state,t)}function re(e,t,r,n){let i=I(e.state,t);if(!e.definition.endWhen?.(i)||!ee(e.state,t))return null;let s=I(e.state,t);return Se(e,s,r,n),s}function Se(e,t,r,n){let i=e.state.generation;if(e.endCalledGeneration!==i){e.endCalledGeneration=i;try{let s=e.definition.onEnd?.(t,r);s&&n&&Promise.resolve(s).catch(u=>n?.(u,t,i))}catch(s){if(n){n(s,t,i);return}throw s}}}function ie(e={}){let t=(0,y.useRef)(null);return t.current===null&&(t.current=Te(e)),t.current.setOptions(e),(0,y.useEffect)(()=>{t.current?.commitOptions()}),(0,y.useEffect)(()=>{let n=t.current;return e.autoStart&&n.controls.start(),()=>n.destroy()},[]),{...(0,y.useSyncExternalStore)(t.current.subscribe,t.current.getSnapshot,t.current.getServerSnapshot),...t.current.controls}}function Te(e){let t=e;oe(t);let r=new Set,n=ne(t,f()),i=new Map,s=g(n,f()),u=s,m=null,T=G(t.schedules),d=!1,l=(o=f())=>{s=g(n,o),r.forEach(a=>a())},h=()=>{m!==null&&(clearTimeout(m),m=null)},p=(o,a,c={})=>{k(t.diagnostics,{type:o,scope:"timer",...A(a,n.state.generation),...c})},S=(o,a,c)=>{let w=O(R,()=>n.state.generation===c);t.onError?.(o,a,w),k(t.diagnostics,{type:"callback:error",scope:"timer",error:o,...A(a,c)})},v=(o,a)=>c=>{k(t.diagnostics,{type:c.type,scope:"timer",...c.context,..."reason"in c?{reason:c.reason}:{},..."error"in c?{error:c.error}:{},...A(o,a)})},C=(o=f(),a=!1)=>{if(n.state.status!=="running")return!1;let c=n.state.generation,w=O(R,()=>n.state.generation===c),H=re(n,o,w,S);if(H)return h(),p("timer:end",H),l(o),!0;let K=g(n,o);return q({schedules:t.schedules,states:i,snapshot:K,generation:n.state.generation,controls:w,activation:a,isLive:D=>n.state.generation===D,onError:(D,le,se)=>t.onError?.(D,le,se),onEvent:v(K,n.state.generation)}),!1},b=(o=!0)=>{if(h(),n.state.status!=="running")return;let a=z(t.schedules,i,f().wallNow,t.updateIntervalMs??1e3);o&&p("scheduler:start",g(n,f())),m=setTimeout(()=>{if(n.state.status!=="running")return;let c=f();te(n.state,c);let w=g(n,c);p("timer:tick",w),C(c)||(l(c),b())},a)},R={start:()=>{let o=f();if(!X(n.state,o)){n.state.status==="running"&&b();return}p("timer:start",g(n,o)),C(o,!0),l(o),b()},pause:()=>{let o=f();Y(n.state,o)&&(h(),p("timer:pause",g(n,o)),l(o))},resume:()=>{let o=f();Z(n.state,o)&&(p("timer:resume",g(n,o)),C(o,!0),l(o),b())},reset:(o={})=>{let a=f();h(),F(n.state,a,o),i.clear(),W(n),p("timer:reset",g(n,a)),C(a,o.autoStart),l(a),b()},restart:()=>{let o=f();h(),_(n.state,o),i.clear(),W(n),p("timer:restart",g(n,o)),C(o,!0),l(o),b()},cancel:o=>{let a=f();L(n.state,a,o)&&(h(),p("timer:cancel",g(n,a),{reason:o}),l(a))}};return{commitOptions:()=>{if(d&&(d=!1,B(t.schedules,i,f().wallNow,n.state.status==="running"),n.state.status==="running")){let o=f();C(o)||b(!1)}},controls:R,destroy:h,getSnapshot:()=>s,getServerSnapshot:()=>u,setOptions:o=>{oe(o);let a=G(o.schedules),c=a!==T;d=d||c||(o.updateIntervalMs??1e3)!==(t.updateIntervalMs??1e3)||o.endWhen!==t.endWhen,t=o,n.definition=o,c&&(T=a)},subscribe:o=>(r.add(o),()=>r.delete(o))}}function oe(e){x(e.updateIntervalMs??1e3,"updateIntervalMs"),Q(e.schedules)}0&&(module.exports={useScheduledTimer});
@@ -0,0 +1,6 @@
1
+ import { h as UseScheduledTimerOptions, T as TimerSnapshot, a as TimerControls } from './types-D-Vzr-PF.cjs';
2
+ export { i as TimerDiagnostics, j as TimerDiagnosticsEvent, k as TimerDiagnosticsLogger, l as TimerSchedule, m as TimerScheduleContext } from './types-D-Vzr-PF.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-D-Vzr-PF.js';
2
+ export { i as TimerDiagnostics, j as TimerDiagnosticsEvent, k as TimerDiagnosticsLogger, l as TimerSchedule, m as TimerScheduleContext } from './types-D-Vzr-PF.js';
3
+
4
+ declare function useScheduledTimer(options?: UseScheduledTimerOptions): TimerSnapshot & TimerControls;
5
+
6
+ export { UseScheduledTimerOptions, useScheduledTimer };
@@ -0,0 +1 @@
1
+ import{a as g,b as y,c as F,d as G,e as j,f as C,g as q,h as z,i as I,j as i,k as A}from"./chunk-OYRPLLEV.js";import{a as E,b as o,c as U,f as M,g as N,h as P,i as x,j as R,k as L,m as W}from"./chunk-XXJAJJJ6.js";import{useEffect as B,useRef as Y,useSyncExternalStore as Z}from"react";function _(c={}){let r=Y(null);return r.current===null&&(r.current=$(c)),r.current.setOptions(c),B(()=>{r.current?.commitOptions()}),B(()=>{let t=r.current;return c.autoStart&&t.controls.start(),()=>t.destroy()},[]),{...Z(r.current.subscribe,r.current.getSnapshot,r.current.getServerSnapshot),...r.current.controls}}function $(c){let r=c;H(r);let h=new Set,t=z(r,o()),S=new Map,v=i(t,o()),J=v,f=null,O=C(r.schedules),T=!1,u=(e=o())=>{v=i(t,e),h.forEach(s=>s())},l=()=>{f!==null&&(clearTimeout(f),f=null)},a=(e,s,n={})=>{g(r.diagnostics,{type:e,scope:"timer",...y(s,t.state.generation),...n})},K=(e,s,n)=>{let p=E(b,()=>t.state.generation===n);r.onError?.(e,s,p),g(r.diagnostics,{type:"callback:error",scope:"timer",error:e,...y(s,n)})},Q=(e,s)=>n=>{g(r.diagnostics,{type:n.type,scope:"timer",...n.context,..."reason"in n?{reason:n.reason}:{},..."error"in n?{error:n.error}:{},...y(e,s)})},d=(e=o(),s=!1)=>{if(t.state.status!=="running")return!1;let n=t.state.generation,p=E(b,()=>t.state.generation===n),D=A(t,e,p,K);if(D)return l(),a("timer:end",D),u(e),!0;let w=i(t,e);return F({schedules:r.schedules,states:S,snapshot:w,generation:t.state.generation,controls:p,activation:s,isLive:k=>t.state.generation===k,onError:(k,V,X)=>r.onError?.(k,V,X),onEvent:Q(w,t.state.generation)}),!1},m=(e=!0)=>{if(l(),t.state.status!=="running")return;let s=G(r.schedules,S,o().wallNow,r.updateIntervalMs??1e3);e&&a("scheduler:start",i(t,o())),f=setTimeout(()=>{if(t.state.status!=="running")return;let n=o();W(t.state,n);let p=i(t,n);a("timer:tick",p),d(n)||(u(n),m())},s)},b={start:()=>{let e=o();if(!M(t.state,e)){t.state.status==="running"&&m();return}a("timer:start",i(t,e)),d(e,!0),u(e),m()},pause:()=>{let e=o();N(t.state,e)&&(l(),a("timer:pause",i(t,e)),u(e))},resume:()=>{let e=o();P(t.state,e)&&(a("timer:resume",i(t,e)),d(e,!0),u(e),m())},reset:(e={})=>{let s=o();l(),x(t.state,s,e),S.clear(),I(t),a("timer:reset",i(t,s)),d(s,e.autoStart),u(s),m()},restart:()=>{let e=o();l(),R(t.state,e),S.clear(),I(t),a("timer:restart",i(t,e)),d(e,!0),u(e),m()},cancel:e=>{let s=o();L(t.state,s,e)&&(l(),a("timer:cancel",i(t,s),{reason:e}),u(s))}};return{commitOptions:()=>{if(T&&(T=!1,j(r.schedules,S,o().wallNow,t.state.status==="running"),t.state.status==="running")){let e=o();d(e)||m(!1)}},controls:b,destroy:l,getSnapshot:()=>v,getServerSnapshot:()=>J,setOptions:e=>{H(e);let s=C(e.schedules),n=s!==O;T=T||n||(e.updateIntervalMs??1e3)!==(r.updateIntervalMs??1e3)||e.endWhen!==r.endWhen,r=e,t.definition=e,n&&(O=s)},subscribe:e=>(h.add(e),()=>h.delete(e))}}function H(c){U(c.updateIntervalMs??1e3,"updateIntervalMs"),q(c.schedules)}export{_ as useScheduledTimer};
@@ -0,0 +1,133 @@
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 TimerLifecycleControls = {
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 TimerControls = TimerLifecycleControls;
38
+ type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
39
+ type TimerScheduleContext = {
40
+ scheduleId: string;
41
+ scheduledAt: number;
42
+ firedAt: number;
43
+ nextRunAt: number;
44
+ overdueCount: number;
45
+ effectiveEveryMs: number;
46
+ };
47
+ type TimerSchedule = {
48
+ id?: string;
49
+ everyMs: number;
50
+ leading?: boolean;
51
+ overlap?: 'skip' | 'allow';
52
+ callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
53
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void;
54
+ };
55
+ type TimerDiagnostics = TimerDiagnosticsLogger | {
56
+ enabled?: boolean;
57
+ logger: TimerDiagnosticsLogger;
58
+ includeTicks?: boolean;
59
+ label?: string;
60
+ };
61
+ type TimerDiagnosticsLogger = (event: TimerDiagnosticsEvent) => void;
62
+ type TimerDiagnosticsEvent = {
63
+ type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
64
+ scope: 'timer' | 'timer-group';
65
+ label?: string;
66
+ timerId?: string;
67
+ scheduleId?: string;
68
+ generation: number;
69
+ tick: number;
70
+ now: number;
71
+ elapsedMilliseconds: number;
72
+ status: TimerStatus;
73
+ reason?: string;
74
+ error?: unknown;
75
+ scheduledAt?: number;
76
+ firedAt?: number;
77
+ nextRunAt?: number;
78
+ overdueCount?: number;
79
+ effectiveEveryMs?: number;
80
+ };
81
+ type UseTimerOptions = {
82
+ autoStart?: boolean;
83
+ updateIntervalMs?: number;
84
+ endWhen?: TimerEndPredicate;
85
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
86
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerControls) => void;
87
+ };
88
+ type UseScheduledTimerOptions = UseTimerOptions & {
89
+ schedules?: TimerSchedule[];
90
+ diagnostics?: TimerDiagnostics;
91
+ };
92
+ type TimerGroupItemControls = TimerLifecycleControls;
93
+ type TimerGroupItem = {
94
+ id: string;
95
+ autoStart?: boolean;
96
+ endWhen?: TimerEndPredicate;
97
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
98
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void;
99
+ schedules?: TimerSchedule[];
100
+ };
101
+ type UseTimerGroupOptions = {
102
+ updateIntervalMs?: number;
103
+ items?: TimerGroupItem[];
104
+ diagnostics?: TimerDiagnostics;
105
+ };
106
+ type TimerGroupResult = {
107
+ now: number;
108
+ size: number;
109
+ ids: string[];
110
+ get(id: string): TimerSnapshot | undefined;
111
+ add(item: TimerGroupItem): void;
112
+ update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
113
+ remove(id: string): void;
114
+ clear(): void;
115
+ start(id: string): void;
116
+ pause(id: string): void;
117
+ resume(id: string): void;
118
+ reset(id: string, options?: {
119
+ autoStart?: boolean;
120
+ }): void;
121
+ restart(id: string): void;
122
+ cancel(id: string, reason?: string): void;
123
+ startAll(): void;
124
+ pauseAll(): void;
125
+ resumeAll(): void;
126
+ resetAll(options?: {
127
+ autoStart?: boolean;
128
+ }): void;
129
+ restartAll(): void;
130
+ cancelAll(reason?: string): void;
131
+ };
132
+
133
+ 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, TimerDiagnostics as i, TimerDiagnosticsEvent as j, TimerDiagnosticsLogger as k, TimerSchedule as l, TimerScheduleContext as m };
@@ -0,0 +1,133 @@
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 TimerLifecycleControls = {
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 TimerControls = TimerLifecycleControls;
38
+ type TimerEndPredicate = (snapshot: TimerSnapshot) => boolean;
39
+ type TimerScheduleContext = {
40
+ scheduleId: string;
41
+ scheduledAt: number;
42
+ firedAt: number;
43
+ nextRunAt: number;
44
+ overdueCount: number;
45
+ effectiveEveryMs: number;
46
+ };
47
+ type TimerSchedule = {
48
+ id?: string;
49
+ everyMs: number;
50
+ leading?: boolean;
51
+ overlap?: 'skip' | 'allow';
52
+ callback: (snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void | Promise<void>;
53
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerControls, context: TimerScheduleContext) => void;
54
+ };
55
+ type TimerDiagnostics = TimerDiagnosticsLogger | {
56
+ enabled?: boolean;
57
+ logger: TimerDiagnosticsLogger;
58
+ includeTicks?: boolean;
59
+ label?: string;
60
+ };
61
+ type TimerDiagnosticsLogger = (event: TimerDiagnosticsEvent) => void;
62
+ type TimerDiagnosticsEvent = {
63
+ type: 'timer:start' | 'timer:pause' | 'timer:resume' | 'timer:reset' | 'timer:restart' | 'timer:cancel' | 'timer:end' | 'timer:tick' | 'scheduler:start' | 'schedule:start' | 'schedule:skip' | 'schedule:end' | 'schedule:error' | 'callback:error';
64
+ scope: 'timer' | 'timer-group';
65
+ label?: string;
66
+ timerId?: string;
67
+ scheduleId?: string;
68
+ generation: number;
69
+ tick: number;
70
+ now: number;
71
+ elapsedMilliseconds: number;
72
+ status: TimerStatus;
73
+ reason?: string;
74
+ error?: unknown;
75
+ scheduledAt?: number;
76
+ firedAt?: number;
77
+ nextRunAt?: number;
78
+ overdueCount?: number;
79
+ effectiveEveryMs?: number;
80
+ };
81
+ type UseTimerOptions = {
82
+ autoStart?: boolean;
83
+ updateIntervalMs?: number;
84
+ endWhen?: TimerEndPredicate;
85
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerControls) => void | Promise<void>;
86
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerControls) => void;
87
+ };
88
+ type UseScheduledTimerOptions = UseTimerOptions & {
89
+ schedules?: TimerSchedule[];
90
+ diagnostics?: TimerDiagnostics;
91
+ };
92
+ type TimerGroupItemControls = TimerLifecycleControls;
93
+ type TimerGroupItem = {
94
+ id: string;
95
+ autoStart?: boolean;
96
+ endWhen?: TimerEndPredicate;
97
+ onEnd?: (snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void | Promise<void>;
98
+ onError?: (error: unknown, snapshot: TimerSnapshot, controls: TimerGroupItemControls) => void;
99
+ schedules?: TimerSchedule[];
100
+ };
101
+ type UseTimerGroupOptions = {
102
+ updateIntervalMs?: number;
103
+ items?: TimerGroupItem[];
104
+ diagnostics?: TimerDiagnostics;
105
+ };
106
+ type TimerGroupResult = {
107
+ now: number;
108
+ size: number;
109
+ ids: string[];
110
+ get(id: string): TimerSnapshot | undefined;
111
+ add(item: TimerGroupItem): void;
112
+ update(id: string, item: Partial<Omit<TimerGroupItem, 'id'>>): void;
113
+ remove(id: string): void;
114
+ clear(): void;
115
+ start(id: string): void;
116
+ pause(id: string): void;
117
+ resume(id: string): void;
118
+ reset(id: string, options?: {
119
+ autoStart?: boolean;
120
+ }): void;
121
+ restart(id: string): void;
122
+ cancel(id: string, reason?: string): void;
123
+ startAll(): void;
124
+ pauseAll(): void;
125
+ resumeAll(): void;
126
+ resetAll(options?: {
127
+ autoStart?: boolean;
128
+ }): void;
129
+ restartAll(): void;
130
+ cancelAll(reason?: string): void;
131
+ };
132
+
133
+ 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, TimerDiagnostics as i, TimerDiagnosticsEvent as j, TimerDiagnosticsLogger as k, TimerSchedule as l, TimerScheduleContext as m };
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@crup/react-timer-hook",
3
- "version": "0.0.1-alpha.9",
4
- "description": "React timer lifecycle hooks for countdowns, stopwatches, schedules, and many independent timers.",
3
+ "version": "0.0.2",
4
+ "description": "A lightweight React hooks library for building timers, stopwatches, and real-time clocks with minimal boilerplate.",
5
+ "homepage": "https://crup.github.io/react-timer-hook/",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/crup/react-timer-hook.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/crup/react-timer-hook/issues"
12
+ },
5
13
  "main": "./dist/index.cjs",
6
14
  "module": "./dist/index.js",
7
15
  "types": "./dist/index.d.ts",
@@ -10,6 +18,26 @@
10
18
  "types": "./dist/index.d.ts",
11
19
  "import": "./dist/index.js",
12
20
  "require": "./dist/index.cjs"
21
+ },
22
+ "./group": {
23
+ "types": "./dist/group.d.ts",
24
+ "import": "./dist/group.js",
25
+ "require": "./dist/group.cjs"
26
+ },
27
+ "./duration": {
28
+ "types": "./dist/duration.d.ts",
29
+ "import": "./dist/duration.js",
30
+ "require": "./dist/duration.cjs"
31
+ },
32
+ "./schedules": {
33
+ "types": "./dist/schedules.d.ts",
34
+ "import": "./dist/schedules.js",
35
+ "require": "./dist/schedules.cjs"
36
+ },
37
+ "./diagnostics": {
38
+ "types": "./dist/diagnostics.d.ts",
39
+ "import": "./dist/diagnostics.js",
40
+ "require": "./dist/diagnostics.cjs"
13
41
  }
14
42
  },
15
43
  "files": [
@@ -19,20 +47,33 @@
19
47
  ],
20
48
  "sideEffects": false,
21
49
  "publishConfig": {
22
- "access": "public",
23
- "tag": "alpha"
50
+ "access": "public"
24
51
  },
25
52
  "engines": {
26
- "node": ">=24.0.0"
53
+ "node": ">=18.0.0"
27
54
  },
28
55
  "keywords": [
29
56
  "react",
30
57
  "hook",
58
+ "hooks",
31
59
  "timer",
60
+ "react-timer",
61
+ "react-timer-hook",
62
+ "timer-hook",
32
63
  "stopwatch",
64
+ "react-stopwatch",
65
+ "stopwatch-hook",
33
66
  "time",
34
67
  "countdown",
68
+ "countdown-timer",
69
+ "react-countdown",
70
+ "clock",
71
+ "real-time",
35
72
  "scheduler",
73
+ "polling",
74
+ "duration",
75
+ "timer-group",
76
+ "typescript",
36
77
  "react-hooks"
37
78
  ],
38
79
  "author": "Rajender Joshi <connect@rajender.pro>",
@@ -67,7 +108,7 @@
67
108
  "ai:context": "node scripts/ai-context.mjs",
68
109
  "build": "tsup",
69
110
  "changeset": "changeset",
70
- "docs:build": "NO_UPDATE_NOTIFIER=1 docusaurus build docs-site",
111
+ "docs:build": "node scripts/build-docs.mjs",
71
112
  "docs:dev": "NO_UPDATE_NOTIFIER=1 docusaurus start docs-site",
72
113
  "docs:preview": "NO_UPDATE_NOTIFIER=1 docusaurus serve docs-site/build",
73
114
  "mcp:docs": "node mcp/server.mjs",