@crup/react-timer-hook 0.0.1-alpha.8 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @crup/react-timer-hook
2
2
 
3
- > React timer hooks for countdowns, stopwatches, clocks, polling schedules, and many independent timer lifecycles.
3
+ > A lightweight React hooks library for building timers, stopwatches, and real-time clocks with minimal boilerplate.
4
4
 
5
5
  [![npm alpha](https://img.shields.io/npm/v/%40crup%2Freact-timer-hook/alpha?label=npm%20alpha&color=00b894)](https://www.npmjs.com/package/@crup/react-timer-hook?activeTab=versions)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/%40crup%2Freact-timer-hook?color=0f766e)](https://www.npmjs.com/package/@crup/react-timer-hook)
@@ -15,16 +15,17 @@
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
 
@@ -35,17 +36,43 @@ npm install @crup/react-timer-hook@alpha
35
36
  pnpm add @crup/react-timer-hook@alpha
36
37
  ```
37
38
 
39
+ Runtime requirements: Node 18+ and React 18+.
40
+
38
41
  ```tsx
39
- import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
42
+ import { useTimer } from '@crup/react-timer-hook';
43
+ import { durationParts } from '@crup/react-timer-hook/duration';
44
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
45
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
40
46
  ```
41
47
 
42
48
  ## Live recipes
43
49
 
44
50
  Each recipe has a live playground and a focused code sample:
45
51
 
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/)
52
+ - 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/)
53
+ - 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/)
54
+ - 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/)
55
+
56
+ ## Use cases
57
+
58
+ | Product case | Use | Import | Recipe |
59
+ | --- | --- | --- | --- |
60
+ | Stopwatch, call timer, workout timer | Core | `@crup/react-timer-hook` | [Stopwatch](https://crup.github.io/react-timer-hook/recipes/basic/stopwatch/) |
61
+ | Wall clock or "last updated" display | Core | `@crup/react-timer-hook` | [Wall clock](https://crup.github.io/react-timer-hook/recipes/basic/wall-clock/) |
62
+ | Auction, reservation, or job deadline | Core | `@crup/react-timer-hook` | [Absolute countdown](https://crup.github.io/react-timer-hook/recipes/basic/absolute-countdown/) |
63
+ | 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/) |
64
+ | 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/) |
65
+ | Backend status polling | Schedules | `@crup/react-timer-hook/schedules` | [Polling schedule](https://crup.github.io/react-timer-hook/recipes/intermediate/polling-schedule/) |
66
+ | Draft autosave or presence heartbeat | Schedules | `@crup/react-timer-hook/schedules` | [Autosave heartbeat](https://crup.github.io/react-timer-hook/recipes/intermediate/autosave-heartbeat/) |
67
+ | 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/) |
68
+ | 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/) |
69
+ | 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/) |
70
+ | 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/) |
71
+ | 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/) |
72
+
73
+ See the full use-case guide: https://crup.github.io/react-timer-hook/use-cases/
74
+
75
+ Design assumptions and runtime limits: https://crup.github.io/react-timer-hook/project/caveats/
49
76
 
50
77
  ## Quick examples
51
78
 
@@ -99,7 +126,9 @@ export function AuctionTimer({ auctionId, expiresAt }: {
99
126
  Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
100
127
 
101
128
  ```tsx
102
- const timer = useTimer({
129
+ import { useScheduledTimer } from '@crup/react-timer-hook/schedules';
130
+
131
+ const timer = useScheduledTimer({
103
132
  autoStart: true,
104
133
  updateIntervalMs: 1000,
105
134
  endWhen: snapshot => snapshot.now >= expiresAt,
@@ -108,7 +137,8 @@ const timer = useTimer({
108
137
  id: 'auction-poll',
109
138
  everyMs: 5000,
110
139
  overlap: 'skip',
111
- callback: async (_snapshot, controls) => {
140
+ callback: async (_snapshot, controls, context) => {
141
+ console.log(`auction poll fired ${context.firedAt - context.scheduledAt}ms late`);
112
142
  const auction = await api.getAuction(auctionId);
113
143
  if (auction.status === 'sold') controls.cancel('sold');
114
144
  },
@@ -122,6 +152,8 @@ const timer = useTimer({
122
152
  Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
123
153
 
124
154
  ```tsx
155
+ import { useTimerGroup } from '@crup/react-timer-hook/group';
156
+
125
157
  const timers = useTimerGroup({
126
158
  updateIntervalMs: 1000,
127
159
  items: auctions.map(auction => ({
@@ -133,15 +165,100 @@ const timers = useTimerGroup({
133
165
  });
134
166
  ```
135
167
 
168
+ ## API reference
169
+
170
+ ### `useTimer()` settings
171
+
172
+ | Key | Type | Required | Description |
173
+ | --- | --- | --- | --- |
174
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
175
+ | `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. |
176
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. Use this for countdowns, timeouts, and custom stop conditions. |
177
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. `restart()` creates a new generation. |
178
+ | `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`. |
179
+
180
+ ### `useScheduledTimer()` settings
181
+
182
+ Import from `@crup/react-timer-hook/schedules` when you need polling or scheduled side effects.
183
+
184
+ | Key | Type | Required | Description |
185
+ | --- | --- | --- | --- |
186
+ | `autoStart` | `boolean` | No | Starts the lifecycle after mount. Defaults to `false`. |
187
+ | `updateIntervalMs` | `number` | No | Render/update cadence in milliseconds. Defaults to `1000`. Scheduled callbacks can run on their own cadence. |
188
+ | `endWhen` | `(snapshot) => boolean` | No | Ends the lifecycle when it returns `true`. |
189
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per generation when `endWhen` ends the lifecycle. |
190
+ | `onError` | `(error, snapshot, controls) => void` | No | Handles sync throws and async rejections from `onEnd`. |
191
+ | `schedules` | `TimerSchedule[]` | No | Scheduled side effects that run while the timer is active. Async overlap defaults to `skip`. |
192
+ | `diagnostics` | `TimerDiagnostics` | No | Optional lifecycle and schedule events. No logs are emitted unless you pass a logger. |
193
+
194
+ ### `TimerSchedule`
195
+
196
+ | Key | Type | Required | Description |
197
+ | --- | --- | --- | --- |
198
+ | `id` | `string` | No | Stable identifier used in diagnostics events and schedule context. Falls back to the array index. |
199
+ | `everyMs` | `number` | Yes | Schedule cadence in milliseconds. Must be positive and finite. |
200
+ | `leading` | `boolean` | No | Runs the schedule immediately when the timer starts or resumes into a new generation. Defaults to `false`. |
201
+ | `overlap` | `'skip' \| 'allow'` | No | Controls async overlap. Defaults to `skip`, so a pending callback prevents another run. |
202
+ | `callback` | `(snapshot, controls, context) => void \| Promise<void>` | Yes | Scheduled side effect. Receives timing context with `scheduledAt`, `firedAt`, `nextRunAt`, `overdueCount`, and `effectiveEveryMs`. |
203
+ | `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. |
204
+
205
+ ### `useTimerGroup()` settings
206
+
207
+ Import from `@crup/react-timer-hook/group` when many keyed items need independent lifecycle control.
208
+
209
+ | Key | Type | Required | Description |
210
+ | --- | --- | --- | --- |
211
+ | `updateIntervalMs` | `number` | No | Shared scheduler cadence for the group. Defaults to `1000`. |
212
+ | `items` | `TimerGroupItem[]` | No | Initial/synced timer item definitions. Each item has its own lifecycle state. |
213
+ | `diagnostics` | `TimerDiagnostics` | No | Optional lifecycle and schedule events for group timers. |
214
+
215
+ ### `TimerGroupItem`
216
+
217
+ | Key | Type | Required | Description |
218
+ | --- | --- | --- | --- |
219
+ | `id` | `string` | Yes | Stable key for the item. Duplicate IDs throw. |
220
+ | `autoStart` | `boolean` | No | Starts the item automatically when it is added or synced. Defaults to `false`. |
221
+ | `endWhen` | `(snapshot) => boolean` | No | Ends that item when it returns `true`. |
222
+ | `onEnd` | `(snapshot, controls) => void \| Promise<void>` | No | Called once per item generation when that item ends naturally. |
223
+ | `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. |
224
+ | `schedules` | `TimerSchedule[]` | No | Per-item schedules with the same contract as `useScheduledTimer()`. |
225
+
226
+ ### Values and controls
227
+
228
+ | Key | Type | Description |
229
+ | --- | --- | --- |
230
+ | `status` | `'idle' \| 'running' \| 'paused' \| 'ended' \| 'cancelled'` | Current lifecycle state. |
231
+ | `now` | `number` | Wall-clock timestamp from `Date.now()`. Use for clocks and absolute deadlines. |
232
+ | `tick` | `number` | Number of render/update ticks produced in the current generation. |
233
+ | `startedAt` | `number \| null` | Wall-clock timestamp when the current generation started. |
234
+ | `pausedAt` | `number \| null` | Wall-clock timestamp for the current pause, or `null`. |
235
+ | `endedAt` | `number \| null` | Wall-clock timestamp when `endWhen` ended the lifecycle. |
236
+ | `cancelledAt` | `number \| null` | Wall-clock timestamp when `cancel()` ended the lifecycle early. |
237
+ | `cancelReason` | `string \| null` | Optional reason passed to `cancel(reason)`. |
238
+ | `elapsedMilliseconds` | `number` | Active elapsed duration calculated from monotonic time, excluding paused time. |
239
+ | `isIdle` | `boolean` | Convenience flag for `status === 'idle'`. |
240
+ | `isRunning` | `boolean` | Convenience flag for `status === 'running'`. |
241
+ | `isPaused` | `boolean` | Convenience flag for `status === 'paused'`. |
242
+ | `isEnded` | `boolean` | Convenience flag for `status === 'ended'`. |
243
+ | `isCancelled` | `boolean` | Convenience flag for `status === 'cancelled'`. |
244
+ | `start()` | `function` | Starts an idle timer. No-op if it is already started. |
245
+ | `pause()` | `function` | Pauses a running timer. |
246
+ | `resume()` | `function` | Resumes a paused timer from the paused elapsed value. |
247
+ | `reset(options?)` | `function` | Resets to idle and zero elapsed time. Pass `{ autoStart: true }` to reset directly into running. |
248
+ | `restart()` | `function` | Starts a new running generation from zero elapsed time. |
249
+ | `cancel(reason?)` | `function` | Terminal early stop. Does not call `onEnd`. |
250
+
136
251
  ## Bundle size
137
252
 
138
- Current build:
253
+ The default import stays small. Add the other pieces only when that screen needs them.
139
254
 
140
- | File | Raw | Gzip | Brotli |
141
- | --- | ---: | ---: | ---: |
142
- | `dist/index.js` | 11.82 kB | 3.55 kB | 3.20 kB |
143
- | `dist/index.cjs` | 12.94 kB | 3.79 kB | 3.42 kB |
144
- | `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
255
+ | Piece | Import | Best for | Raw | Gzip | Brotli |
256
+ | --- | --- | --- | ---: | ---: | ---: |
257
+ | ⏱️ Core | `@crup/react-timer-hook` | Stopwatch, countdown, clock, custom lifecycle | 4.44 kB | 1.52 kB | 1.40 kB |
258
+ | 🧭 Timer group | `@crup/react-timer-hook/group` | Many independent row/item timers | 10.93 kB | 3.83 kB | 3.50 kB |
259
+ | 📡 Schedules | `@crup/react-timer-hook/schedules` | Polling, cadence callbacks, overdue timing context | 8.62 kB | 3.02 kB | 2.78 kB |
260
+ | 🧩 Duration | `@crup/react-timer-hook/duration` | `days`, `hours`, `minutes`, `seconds`, `milliseconds` | 318 B | 224 B | 192 B |
261
+ | 🔎 Diagnostics | `@crup/react-timer-hook/diagnostics` | Optional lifecycle and schedule event logging | 105 B | 115 B | 90 B |
145
262
 
146
263
  CI writes a size summary to the GitHub Actions UI and posts bundle-size reports on pull requests.
147
264
 
@@ -180,5 +297,6 @@ Issues, recipes, docs improvements, and focused bug reports are welcome.
180
297
  - Read the docs: https://crup.github.io/react-timer-hook/
181
298
  - Open an issue: https://github.com/crup/react-timer-hook/issues
182
299
  - See the contributing guide: ./CONTRIBUTING.md
300
+ - Release policy: https://crup.github.io/react-timer-hook/project/release-channels/
183
301
 
184
- The package targets Node 24 for development and React 18+ as a peer dependency.
302
+ 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 ne=Object.defineProperty;var ie=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ce=Object.prototype.hasOwnProperty;var de=(e,o)=>{for(var a in o)ne(e,a,{get:o[a],enumerable:!0})},me=(e,o,a,u)=>{if(o&&typeof o=="object"||typeof o=="function")for(let I of le(o))!ce.call(e,I)&&I!==a&&ne(e,I,{get:()=>o[I],enumerable:!(u=ie(o,I))||u.enumerable});return e};var fe=e=>me(ne({},"__esModule",{value:!0}),e);var Te={};de(Te,{durationParts:()=>ue,useTimer:()=>se,useTimerGroup:()=>ae});module.exports=fe(Te);function ue(e){let o=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),a=Math.floor(o/864e5),u=o%864e5,I=Math.floor(u/36e5),y=u%36e5,T=Math.floor(y/6e4),w=y%6e4,M=Math.floor(w/1e3);return{totalMilliseconds:o,totalSeconds:Math.floor(o/1e3),milliseconds:w%1e3,seconds:M,minutes:T,hours:I,days:a}}var m=require("react");function pe(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 v(e,o){let a=pe(e);!a.enabled||!a.logger||o.type==="timer:tick"&&!a.includeTicks||a.logger({...o,label:o.label??a.label})}function R(e,o){return{generation:o,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function f(){let e=Date.now(),o=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:o}}function L(e,o){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${o} must be a finite number greater than 0`)}function Q(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 V(e,o){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+o.monotonicNow-e.activeStartedAtMonotonic)}function p(e,o){let a=V(e,o);return{status:e.status,now:o.wallNow,tick:e.tick,startedAt:e.startedAt,pausedAt:e.pausedAt,endedAt:e.endedAt,cancelledAt:e.cancelledAt,cancelReason:e.cancelReason,elapsedMilliseconds:a,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function Y(e,o){return e.status!=="idle"?!1:(e.status="running",e.startedAt=o.wallNow,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function X(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=V(e,o),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=o.wallNow,e.now=o.wallNow,!0)}function Z(e,o){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=o.monotonicNow,e.now=o.wallNow,!0)}function B(e,o,a={}){return e.generation+=1,e.tick=0,e.status=a.autoStart?"running":"idle",e.startedAt=a.autoStart?o.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=a.autoStart?o.monotonicNow:null,e.now=o.wallNow,!0}function _(e,o){return B(e,o,{autoStart:!0})}function ee(e,o,a){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=V(e,o),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=o.wallNow,e.cancelReason=a??null,e.now=o.wallNow,!0)}function te(e,o){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=V(e,o),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=o.wallNow,e.now=o.wallNow,!0)}function re(e,o){return e.status!=="running"?!1:(e.tick+=1,e.now=o.wallNow,!0)}function se(e={}){let o=e.updateIntervalMs??1e3;L(o,"updateIntervalMs"),ge(e.schedules);let a=(0,m.useRef)(e);a.current=e;let u=(0,m.useRef)(null);u.current===null&&(u.current=Q(f()));let I=(0,m.useRef)(!1),y=(0,m.useRef)(null),T=(0,m.useRef)(new Map),w=(0,m.useRef)(null),[,M]=(0,m.useReducer)(s=>s+1,0),l=(0,m.useCallback)(()=>{y.current!==null&&(clearTimeout(y.current),y.current=null)},[]),N=(0,m.useCallback)((s=f())=>p(u.current,s),[]),S=(0,m.useCallback)((s,c,g={})=>{v(a.current.debug,{type:s,scope:"timer",...R(c,u.current.generation),...g})},[]),D=(0,m.useRef)(null),z=(0,m.useCallback)(s=>{let c=u.current.generation;if(w.current!==c){w.current=c;try{a.current.onEnd?.(s,D.current)}catch(g){v(a.current.debug,{type:"callback:error",scope:"timer",...R(s,c),error:g})}}},[]),A=(0,m.useCallback)((s,c,g,k,G)=>{if(g.pending&&(s.overlap??"skip")==="skip"){v(a.current.debug,{type:"schedule:skip",scope:"timer",scheduleId:s.id??c,reason:"overlap",...R(k,G)});return}g.lastRunAt=k.now,g.pending=!0,v(a.current.debug,{type:"schedule:start",scope:"timer",scheduleId:s.id??c,...R(k,G)}),Promise.resolve().then(()=>s.callback(k,D.current)).then(()=>{v(a.current.debug,{type:"schedule:end",scope:"timer",scheduleId:s.id??c,...R(k,G)})},t=>{v(a.current.debug,{type:"schedule:error",scope:"timer",scheduleId:s.id??c,error:t,...R(k,G)})}).finally(()=>{u.current?.generation===G&&(g.pending=!1)})},[]),P=(0,m.useCallback)((s,c,g=!1)=>{let k=a.current.schedules??[],G=new Set;k.forEach((t,r)=>{let n=t.id??String(r);G.add(n);let i=T.current.get(n);if(i||(i={lastRunAt:null,pending:!1,leadingGeneration:null},T.current.set(n,i)),g&&t.leading&&i.leadingGeneration!==c){i.leadingGeneration=c,A(t,n,i,s,c);return}if(i.lastRunAt===null){i.lastRunAt=s.now;return}s.now-i.lastRunAt>=t.everyMs&&A(t,n,i,s,c)});for(let t of T.current.keys())G.has(t)||T.current.delete(t)},[A]),E=(0,m.useCallback)((s=f(),c=!1)=>{let g=u.current;if(g.status!=="running")return;let k=p(g,s),G=g.generation;if(a.current.endWhen?.(k)){if(te(g,s)){let t=p(g,s);S("timer:end",t),l(),z(t),M()}return}P(k,G,c)},[z,l,S,P]),K=(0,m.useCallback)(()=>{let s=f();if(!Y(u.current,s))return;let c=p(u.current,s);S("timer:start",c),E(s,!0),M()},[S,E]),W=(0,m.useCallback)(()=>{let s=f();if(!X(u.current,s))return;l();let c=p(u.current,s);S("timer:pause",c),M()},[l,S]),j=(0,m.useCallback)(()=>{let s=f();if(!Z(u.current,s))return;let c=p(u.current,s);S("timer:resume",c),E(s,!0),M()},[S,E]),q=(0,m.useCallback)((s={})=>{let c=f();l(),B(u.current,c,s),T.current.clear(),w.current=null;let g=p(u.current,c);S("timer:reset",g),s.autoStart&&E(c,!0),M()},[l,S,E]),O=(0,m.useCallback)(()=>{let s=f();l(),_(u.current,s),T.current.clear(),w.current=null;let c=p(u.current,s);S("timer:restart",c),E(s,!0),M()},[l,S,E]),U=(0,m.useCallback)(s=>{let c=f();if(!ee(u.current,c,s))return;l();let g=p(u.current,c);S("timer:cancel",g,{reason:s}),M()},[l,S]);D.current=(0,m.useMemo)(()=>({start:K,pause:W,resume:j,reset:q,restart:O,cancel:U}),[U,W,q,O,j,K]),(0,m.useEffect)(()=>(I.current=!0,a.current.autoStart&&u.current.status==="idle"&&D.current.start(),()=>{I.current=!1,l()}),[l]);let x=N(),$=u.current.generation,F=x.status;return(0,m.useEffect)(()=>{if(!I.current||F!=="running"){l();return}return l(),S("scheduler:start",N()),y.current=setTimeout(()=>{if(!I.current||u.current.generation!==$||u.current.status!=="running")return;let s=f();re(u.current,s);let c=p(u.current,s);S("timer:tick",c),E(s),M()},a.current.updateIntervalMs??1e3),()=>{y.current!==null&&S("scheduler:stop",N()),l()}},[l,S,$,N,E,x.tick,F]),{...x,...D.current}}function ge(e){e?.forEach(o=>L(o.everyMs,"schedule.everyMs"))}var d=require("react");function ae(e={}){let o=e.updateIntervalMs??1e3;L(o,"updateIntervalMs"),oe(e.items);let a=(0,d.useRef)(e);a.current=e;let u=(0,d.useRef)(new Map),I=(0,d.useRef)(!1),y=(0,d.useRef)(null),[,T]=(0,d.useReducer)(t=>t+1,0),w=(0,d.useCallback)(()=>{y.current!==null&&(clearTimeout(y.current),y.current=null)},[]),M=(0,d.useCallback)((t,r=f())=>p(t.state,r),[]),l=(0,d.useCallback)((t,r,n,i={})=>{v(a.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...R(n,r?.state.generation??0),...i})},[]),N=(0,d.useCallback)(t=>({start:()=>O(t),pause:()=>U(t),resume:()=>x(t),reset:r=>$(t,r),restart:()=>F(t),cancel:r=>s(t,r)}),[]),S=(0,d.useCallback)((t,r)=>{let n=t.state.generation;if(t.endCalledGeneration!==n){t.endCalledGeneration=n;try{t.definition.onEnd?.(r,N(t.id))}catch(i){v(a.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:i,...R(r,n)})}}},[N]),D=(0,d.useCallback)((t,r,n,i,h,b)=>{if(i.pending&&(r.overlap??"skip")==="skip"){v(a.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,reason:"overlap",...R(h,b)});return}i.lastRunAt=h.now,i.pending=!0,v(a.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...R(h,b)}),Promise.resolve().then(()=>r.callback(h,N(t.id))).then(()=>{v(a.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...R(h,b)})},J=>{v(a.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,error:J,...R(h,b)})}).finally(()=>{u.current.get(t.id)?.state.generation===b&&(i.pending=!1)})},[N]),z=(0,d.useCallback)((t,r,n=!1)=>{let i=t.definition.schedules??[],h=new Set;i.forEach((b,J)=>{let H=b.id??String(J);h.add(H);let C=t.schedules.get(H);if(C||(C={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(H,C)),n&&b.leading&&C.leadingGeneration!==t.state.generation){C.leadingGeneration=t.state.generation,D(t,b,H,C,r,t.state.generation);return}if(C.lastRunAt===null){C.lastRunAt=t.state.startedAt??r.now,r.now-C.lastRunAt>=b.everyMs&&D(t,b,H,C,r,t.state.generation);return}r.now-C.lastRunAt>=b.everyMs&&D(t,b,H,C,r,t.state.generation)});for(let b of t.schedules.keys())h.has(b)||t.schedules.delete(b)},[D]),A=(0,d.useCallback)((t,r=f(),n=!1)=>{if(t.state.status!=="running")return;let i=p(t.state,r);if(t.definition.endWhen?.(i)){if(te(t.state,r)){let h=p(t.state,r);l("timer:end",t,h),S(t,h)}return}z(t,i,n)},[S,l,z]),P=(0,d.useCallback)(t=>{let r=u.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let n={id:t.id,state:Q(f()),definition:t,schedules:new Map,endCalledGeneration:null};return u.current.set(t.id,n),t.autoStart&&Y(n.state,f()),{item:n,added:!0}},[]),E=(0,d.useCallback)(()=>{let t=a.current.items??[],r=new Set,n=!1;t.forEach(i=>{r.add(i.id);let{item:h,added:b}=P(i);n=n||b,i.autoStart&&h.state.status==="idle"&&(n=Y(h.state,f())||n)});for(let i of u.current.keys())r.has(i)||(u.current.delete(i),n=!0);return n},[P]);(0,d.useEffect)(()=>{E()&&T()},[E,e.items]);let K=(0,d.useCallback)(t=>{if(oe([t]),u.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);P(t),T()},[P]),W=(0,d.useCallback)((t,r)=>{let n=u.current.get(t);if(!n)return;let i={...n.definition,...r,id:t};oe([i]),n.definition=i,T()},[]),j=(0,d.useCallback)(t=>{u.current.delete(t),T()},[]),q=(0,d.useCallback)(()=>{u.current.clear(),w(),T()},[w]),O=(0,d.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=f();Y(r.state,n)&&(l("timer:start",r,p(r.state,n)),A(r,n,!0),T())},[l,A]),U=(0,d.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=f();X(r.state,n)&&(l("timer:pause",r,p(r.state,n)),T())},[l]),x=(0,d.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=f();Z(r.state,n)&&(l("timer:resume",r,p(r.state,n)),A(r,n,!0),T())},[l,A]),$=(0,d.useCallback)((t,r={})=>{let n=u.current.get(t);if(!n)return;let i=f();B(n.state,i,r),n.schedules.clear(),n.endCalledGeneration=null,l("timer:reset",n,p(n.state,i)),r.autoStart&&A(n,i,!0),T()},[l,A]),F=(0,d.useCallback)(t=>{let r=u.current.get(t);if(!r)return;let n=f();_(r.state,n),r.schedules.clear(),r.endCalledGeneration=null,l("timer:restart",r,p(r.state,n)),A(r,n,!0),T()},[l,A]),s=(0,d.useCallback)((t,r)=>{let n=u.current.get(t);if(!n)return;let i=f();ee(n.state,i,r)&&(l("timer:cancel",n,p(n.state,i),{reason:r}),T())},[l]),g=Array.from(u.current.keys()).map(t=>`${t}:${u.current.get(t).state.status}:${u.current.get(t).state.generation}:${u.current.get(t).state.tick}`).join("|");(0,d.useEffect)(()=>{I.current=!0;let t=Array.from(u.current.values()).filter(n=>n.state.status==="running");if(t.length===0){w();return}w();let r=t[0];return l("scheduler:start",r,p(r.state,f())),y.current=setTimeout(()=>{if(!I.current)return;let n=f();for(let i of u.current.values()){if(i.state.status!=="running")continue;re(i.state,n);let h=p(i.state,n);l("timer:tick",i,h),A(i,n)}T()},a.current.updateIntervalMs??1e3),()=>{y.current!==null&&l("scheduler:stop",r,p(r.state,f())),w(),I.current=!1}},[g,w,l,A]);let k=(0,d.useCallback)(t=>{let r=u.current.get(t);if(r)return M(r)},[M]),G=f().wallNow;return(0,d.useMemo)(()=>({now:G,size:u.current.size,ids:Array.from(u.current.keys()),get:k,add:K,update:W,remove:j,clear:q,start:O,pause:U,resume:x,reset:$,restart:F,cancel:s,startAll:()=>Array.from(u.current.keys()).forEach(O),pauseAll:()=>Array.from(u.current.keys()).forEach(U),resumeAll:()=>Array.from(u.current.keys()).forEach(x),resetAll:t=>Array.from(u.current.keys()).forEach(r=>$(r,t)),restartAll:()=>Array.from(u.current.keys()).forEach(F),cancelAll:t=>Array.from(u.current.keys()).forEach(r=>s(r,t))}),[K,s,q,k,G,U,j,$,F,x,O,W])}function oe(e){let o=new Set;e?.forEach(a=>{if(o.has(a.id))throw new Error(`Duplicate timer item id "${a.id}"`);o.add(a.id),a.schedules?.forEach(u=>L(u.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 ie(e){let u=Math.max(0,Math.trunc(Number.isFinite(e)?e:0)),i=Math.floor(u/864e5),o=u%864e5,C=Math.floor(o/36e5),h=o%36e5,p=Math.floor(h/6e4),I=h%6e4,v=Math.floor(I/1e3);return{totalMilliseconds:u,totalSeconds:Math.floor(u/1e3),milliseconds:I%1e3,seconds:v,minutes:p,hours:C,days:i}}import{useCallback as E,useEffect as se,useMemo as ce,useReducer as de,useRef as H}from"react";function le(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 A(e,u){let i=le(e);!i.enabled||!i.logger||u.type==="timer:tick"&&!i.includeTicks||i.logger({...u,label:u.label??i.label})}function k(e,u){return{generation:u,tick:e.tick,now:e.now,elapsedMilliseconds:e.elapsedMilliseconds,status:e.status}}function d(){let e=Date.now(),u=typeof performance<"u"&&typeof performance.now=="function"?performance.now():e;return{wallNow:e,monotonicNow:u}}function Y(e,u){if(!Number.isFinite(e)||e<=0)throw new RangeError(`${u} must be a finite number greater than 0`)}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 X(e,u){return e.status!=="running"||e.activeStartedAtMonotonic===null?e.baseElapsedMilliseconds:Math.max(0,e.baseElapsedMilliseconds+u.monotonicNow-e.activeStartedAtMonotonic)}function m(e,u){let i=X(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:i,isIdle:e.status==="idle",isRunning:e.status==="running",isPaused:e.status==="paused",isEnded:e.status==="ended",isCancelled:e.status==="cancelled"}}function z(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 Z(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=X(e,u),e.activeStartedAtMonotonic=null,e.status="paused",e.pausedAt=u.wallNow,e.now=u.wallNow,!0)}function _(e,u){return e.status!=="paused"?!1:(e.status="running",e.pausedAt=null,e.activeStartedAtMonotonic=u.monotonicNow,e.now=u.wallNow,!0)}function J(e,u,i={}){return e.generation+=1,e.tick=0,e.status=i.autoStart?"running":"idle",e.startedAt=i.autoStart?u.wallNow:null,e.pausedAt=null,e.endedAt=null,e.cancelledAt=null,e.cancelReason=null,e.baseElapsedMilliseconds=0,e.activeStartedAtMonotonic=i.autoStart?u.monotonicNow:null,e.now=u.wallNow,!0}function ee(e,u){return J(e,u,{autoStart:!0})}function te(e,u,i){return e.status==="ended"||e.status==="cancelled"?!1:(e.baseElapsedMilliseconds=X(e,u),e.activeStartedAtMonotonic=null,e.status="cancelled",e.cancelledAt=u.wallNow,e.cancelReason=i??null,e.now=u.wallNow,!0)}function re(e,u){return e.status!=="running"?!1:(e.baseElapsedMilliseconds=X(e,u),e.activeStartedAtMonotonic=null,e.status="ended",e.endedAt=u.wallNow,e.now=u.wallNow,!0)}function ne(e,u){return e.status!=="running"?!1:(e.tick+=1,e.now=u.wallNow,!0)}function me(e={}){let u=e.updateIntervalMs??1e3;Y(u,"updateIntervalMs"),fe(e.schedules);let i=H(e);i.current=e;let o=H(null);o.current===null&&(o.current=V(d()));let C=H(!1),h=H(null),p=H(new Map),I=H(null),[,v]=de(s=>s+1,0),l=E(()=>{h.current!==null&&(clearTimeout(h.current),h.current=null)},[]),N=E((s=d())=>m(o.current,s),[]),g=E((s,c,f={})=>{A(i.current.debug,{type:s,scope:"timer",...k(c,o.current.generation),...f})},[]),D=H(null),K=E(s=>{let c=o.current.generation;if(I.current!==c){I.current=c;try{i.current.onEnd?.(s,D.current)}catch(f){A(i.current.debug,{type:"callback:error",scope:"timer",...k(s,c),error:f})}}},[]),y=E((s,c,f,w,M)=>{if(f.pending&&(s.overlap??"skip")==="skip"){A(i.current.debug,{type:"schedule:skip",scope:"timer",scheduleId:s.id??c,reason:"overlap",...k(w,M)});return}f.lastRunAt=w.now,f.pending=!0,A(i.current.debug,{type:"schedule:start",scope:"timer",scheduleId:s.id??c,...k(w,M)}),Promise.resolve().then(()=>s.callback(w,D.current)).then(()=>{A(i.current.debug,{type:"schedule:end",scope:"timer",scheduleId:s.id??c,...k(w,M)})},t=>{A(i.current.debug,{type:"schedule:error",scope:"timer",scheduleId:s.id??c,error:t,...k(w,M)})}).finally(()=>{o.current?.generation===M&&(f.pending=!1)})},[]),P=E((s,c,f=!1)=>{let w=i.current.schedules??[],M=new Set;w.forEach((t,r)=>{let n=t.id??String(r);M.add(n);let a=p.current.get(n);if(a||(a={lastRunAt:null,pending:!1,leadingGeneration:null},p.current.set(n,a)),f&&t.leading&&a.leadingGeneration!==c){a.leadingGeneration=c,y(t,n,a,s,c);return}if(a.lastRunAt===null){a.lastRunAt=s.now;return}s.now-a.lastRunAt>=t.everyMs&&y(t,n,a,s,c)});for(let t of p.current.keys())M.has(t)||p.current.delete(t)},[y]),R=E((s=d(),c=!1)=>{let f=o.current;if(f.status!=="running")return;let w=m(f,s),M=f.generation;if(i.current.endWhen?.(w)){if(re(f,s)){let t=m(f,s);g("timer:end",t),l(),K(t),v()}return}P(w,M,c)},[K,l,g,P]),W=E(()=>{let s=d();if(!z(o.current,s))return;let c=m(o.current,s);g("timer:start",c),R(s,!0),v()},[g,R]),j=E(()=>{let s=d();if(!Z(o.current,s))return;l();let c=m(o.current,s);g("timer:pause",c),v()},[l,g]),q=E(()=>{let s=d();if(!_(o.current,s))return;let c=m(o.current,s);g("timer:resume",c),R(s,!0),v()},[g,R]),B=E((s={})=>{let c=d();l(),J(o.current,c,s),p.current.clear(),I.current=null;let f=m(o.current,c);g("timer:reset",f),s.autoStart&&R(c,!0),v()},[l,g,R]),O=E(()=>{let s=d();l(),ee(o.current,s),p.current.clear(),I.current=null;let c=m(o.current,s);g("timer:restart",c),R(s,!0),v()},[l,g,R]),U=E(s=>{let c=d();if(!te(o.current,c,s))return;l();let f=m(o.current,c);g("timer:cancel",f,{reason:s}),v()},[l,g]);D.current=ce(()=>({start:W,pause:j,resume:q,reset:B,restart:O,cancel:U}),[U,j,B,O,q,W]),se(()=>(C.current=!0,i.current.autoStart&&o.current.status==="idle"&&D.current.start(),()=>{C.current=!1,l()}),[l]);let x=N(),$=o.current.generation,F=x.status;return se(()=>{if(!C.current||F!=="running"){l();return}return l(),g("scheduler:start",N()),h.current=setTimeout(()=>{if(!C.current||o.current.generation!==$||o.current.status!=="running")return;let s=d();ne(o.current,s);let c=m(o.current,s);g("timer:tick",c),R(s),v()},i.current.updateIntervalMs??1e3),()=>{h.current!==null&&g("scheduler:stop",N()),l()}},[l,g,$,N,R,x.tick,F]),{...x,...D.current}}function fe(e){e?.forEach(u=>Y(u.everyMs,"schedule.everyMs"))}import{useCallback as T,useEffect as ae,useMemo as pe,useReducer as ge,useRef as oe}from"react";function Te(e={}){let u=e.updateIntervalMs??1e3;Y(u,"updateIntervalMs"),ue(e.items);let i=oe(e);i.current=e;let o=oe(new Map),C=oe(!1),h=oe(null),[,p]=ge(t=>t+1,0),I=T(()=>{h.current!==null&&(clearTimeout(h.current),h.current=null)},[]),v=T((t,r=d())=>m(t.state,r),[]),l=T((t,r,n,a={})=>{A(i.current.debug,{type:t,scope:"timer-group",timerId:r?.id,...k(n,r?.state.generation??0),...a})},[]),N=T(t=>({start:()=>O(t),pause:()=>U(t),resume:()=>x(t),reset:r=>$(t,r),restart:()=>F(t),cancel:r=>s(t,r)}),[]),g=T((t,r)=>{let n=t.state.generation;if(t.endCalledGeneration!==n){t.endCalledGeneration=n;try{t.definition.onEnd?.(r,N(t.id))}catch(a){A(i.current.debug,{type:"callback:error",scope:"timer-group",timerId:t.id,error:a,...k(r,n)})}}},[N]),D=T((t,r,n,a,b,S)=>{if(a.pending&&(r.overlap??"skip")==="skip"){A(i.current.debug,{type:"schedule:skip",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,reason:"overlap",...k(b,S)});return}a.lastRunAt=b.now,a.pending=!0,A(i.current.debug,{type:"schedule:start",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...k(b,S)}),Promise.resolve().then(()=>r.callback(b,N(t.id))).then(()=>{A(i.current.debug,{type:"schedule:end",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,...k(b,S)})},Q=>{A(i.current.debug,{type:"schedule:error",scope:"timer-group",timerId:t.id,scheduleId:r.id??n,error:Q,...k(b,S)})}).finally(()=>{o.current.get(t.id)?.state.generation===S&&(a.pending=!1)})},[N]),K=T((t,r,n=!1)=>{let a=t.definition.schedules??[],b=new Set;a.forEach((S,Q)=>{let L=S.id??String(Q);b.add(L);let G=t.schedules.get(L);if(G||(G={lastRunAt:null,pending:!1,leadingGeneration:null},t.schedules.set(L,G)),n&&S.leading&&G.leadingGeneration!==t.state.generation){G.leadingGeneration=t.state.generation,D(t,S,L,G,r,t.state.generation);return}if(G.lastRunAt===null){G.lastRunAt=t.state.startedAt??r.now,r.now-G.lastRunAt>=S.everyMs&&D(t,S,L,G,r,t.state.generation);return}r.now-G.lastRunAt>=S.everyMs&&D(t,S,L,G,r,t.state.generation)});for(let S of t.schedules.keys())b.has(S)||t.schedules.delete(S)},[D]),y=T((t,r=d(),n=!1)=>{if(t.state.status!=="running")return;let a=m(t.state,r);if(t.definition.endWhen?.(a)){if(re(t.state,r)){let b=m(t.state,r);l("timer:end",t,b),g(t,b)}return}K(t,a,n)},[g,l,K]),P=T(t=>{let r=o.current.get(t.id);if(r)return r.definition=t,{item:r,added:!1};let n={id:t.id,state:V(d()),definition:t,schedules:new Map,endCalledGeneration:null};return o.current.set(t.id,n),t.autoStart&&z(n.state,d()),{item:n,added:!0}},[]),R=T(()=>{let t=i.current.items??[],r=new Set,n=!1;t.forEach(a=>{r.add(a.id);let{item:b,added:S}=P(a);n=n||S,a.autoStart&&b.state.status==="idle"&&(n=z(b.state,d())||n)});for(let a of o.current.keys())r.has(a)||(o.current.delete(a),n=!0);return n},[P]);ae(()=>{R()&&p()},[R,e.items]);let W=T(t=>{if(ue([t]),o.current.has(t.id))throw new Error(`Timer item "${t.id}" already exists`);P(t),p()},[P]),j=T((t,r)=>{let n=o.current.get(t);if(!n)return;let a={...n.definition,...r,id:t};ue([a]),n.definition=a,p()},[]),q=T(t=>{o.current.delete(t),p()},[]),B=T(()=>{o.current.clear(),I(),p()},[I]),O=T(t=>{let r=o.current.get(t);if(!r)return;let n=d();z(r.state,n)&&(l("timer:start",r,m(r.state,n)),y(r,n,!0),p())},[l,y]),U=T(t=>{let r=o.current.get(t);if(!r)return;let n=d();Z(r.state,n)&&(l("timer:pause",r,m(r.state,n)),p())},[l]),x=T(t=>{let r=o.current.get(t);if(!r)return;let n=d();_(r.state,n)&&(l("timer:resume",r,m(r.state,n)),y(r,n,!0),p())},[l,y]),$=T((t,r={})=>{let n=o.current.get(t);if(!n)return;let a=d();J(n.state,a,r),n.schedules.clear(),n.endCalledGeneration=null,l("timer:reset",n,m(n.state,a)),r.autoStart&&y(n,a,!0),p()},[l,y]),F=T(t=>{let r=o.current.get(t);if(!r)return;let n=d();ee(r.state,n),r.schedules.clear(),r.endCalledGeneration=null,l("timer:restart",r,m(r.state,n)),y(r,n,!0),p()},[l,y]),s=T((t,r)=>{let n=o.current.get(t);if(!n)return;let a=d();te(n.state,a,r)&&(l("timer:cancel",n,m(n.state,a),{reason:r}),p())},[l]),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("|");ae(()=>{C.current=!0;let t=Array.from(o.current.values()).filter(n=>n.state.status==="running");if(t.length===0){I();return}I();let r=t[0];return l("scheduler:start",r,m(r.state,d())),h.current=setTimeout(()=>{if(!C.current)return;let n=d();for(let a of o.current.values()){if(a.state.status!=="running")continue;ne(a.state,n);let b=m(a.state,n);l("timer:tick",a,b),y(a,n)}p()},i.current.updateIntervalMs??1e3),()=>{h.current!==null&&l("scheduler:stop",r,m(r.state,d())),I(),C.current=!1}},[f,I,l,y]);let w=T(t=>{let r=o.current.get(t);if(r)return v(r)},[v]),M=d().wallNow;return pe(()=>({now:M,size:o.current.size,ids:Array.from(o.current.keys()),get:w,add:W,update:j,remove:q,clear:B,start:O,pause:U,resume:x,reset:$,restart:F,cancel:s,startAll:()=>Array.from(o.current.keys()).forEach(O),pauseAll:()=>Array.from(o.current.keys()).forEach(U),resumeAll:()=>Array.from(o.current.keys()).forEach(x),resetAll:t=>Array.from(o.current.keys()).forEach(r=>$(r,t)),restartAll:()=>Array.from(o.current.keys()).forEach(F),cancelAll:t=>Array.from(o.current.keys()).forEach(r=>s(r,t))}),[W,s,B,w,M,U,q,$,F,x,O,j])}function ue(e){let u=new Set;e?.forEach(i=>{if(u.has(i.id))throw new Error(`Duplicate timer item id "${i.id}"`);u.add(i.id),i.schedules?.forEach(o=>Y(o.everyMs,"schedule.everyMs"))})}export{ie as durationParts,me as useTimer,Te 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.8",
4
- "description": "React timer lifecycle hooks for countdowns, stopwatches, schedules, and many independent timers.",
3
+ "version": "0.0.1",
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",