@crup/react-timer-hook 0.0.1-alpha.3 โ 0.0.1-alpha.5
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 +123 -191
- package/dist/index.cjs +1 -841
- package/dist/index.js +1 -812
- package/package.json +17 -6
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,79 +1,81 @@
|
|
|
1
1
|
# @crup/react-timer-hook
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Deterministic React timer primitives for countdowns, stopwatches, clocks, schedules, and many independent timers.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@crup/react-timer-hook?activeTab=versions)
|
|
6
|
+
[](https://www.npmjs.com/package/@crup/react-timer-hook)
|
|
7
|
+
[](https://github.com/crup/react-timer-hook/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/crup/react-timer-hook/actions/workflows/docs.yml)
|
|
9
|
+
[](https://github.com/crup/react-timer-hook/actions/workflows/size.yml)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](./dist/index.d.ts)
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
๐ Docs: https://crup.github.io/react-timer-hook/
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
## Docs and live examples
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
pnpm add @crup/react-timer-hook
|
|
15
|
-
npm install @crup/react-timer-hook
|
|
16
|
-
```
|
|
17
|
+
The documentation site is built with Docusaurus and includes live React playgrounds for 15 recipes:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
- Basic: clock, stopwatch, absolute countdown, pausable countdown, manual controls
|
|
20
|
+
- Intermediate: once-only `onEnd`, polling, poll-and-cancel, backend events, debug logs
|
|
21
|
+
- Advanced: many display timers, timer groups, global controls, per-item polling, dynamic items
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
import { useTimer, useTimerGroup, durationParts } from '@crup/react-timer-hook';
|
|
22
|
-
```
|
|
23
|
+
Open: https://crup.github.io/react-timer-hook/
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
## Why it is different
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
- `useTimerGroup()` for many keyed independent timer lifecycles.
|
|
28
|
-
- `durationParts()` as a pure numeric helper.
|
|
27
|
+
Most timer libraries mix scheduling, lifecycle, formatting, and app behavior. This package keeps the core small:
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
- โฑ๏ธ `useTimer()` for one lifecycle.
|
|
30
|
+
- ๐งญ `useTimerGroup()` for many keyed lifecycles with one shared scheduler.
|
|
31
|
+
- ๐งฉ `durationParts()` for display-friendly duration math.
|
|
32
|
+
- ๐งผ No timezone, locale, or formatting opinions.
|
|
33
|
+
- ๐งช Built around React Strict Mode, rerenders, async callbacks, and cleanup.
|
|
34
|
+
- ๐ค AI-friendly docs via `llms.txt`, `llms-full.txt`, and a tiny local MCP docs utility.
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
## Install
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
- lifecycle state
|
|
36
|
-
- presentation formatting
|
|
38
|
+
Alpha is the only intended release channel until stable publishing is explicitly unlocked.
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
```sh
|
|
41
|
+
npm install @crup/react-timer-hook@alpha
|
|
42
|
+
pnpm add @crup/react-timer-hook@alpha
|
|
43
|
+
```
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
```ts
|
|
46
|
+
import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
|
|
47
|
+
```
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
- stopwatch display
|
|
44
|
-
- clock formatting
|
|
45
|
-
- timezone and locale behavior
|
|
46
|
-
- API polling behavior
|
|
47
|
-
- audio or notification side effects
|
|
49
|
+
## Quick examples
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
### Stopwatch
|
|
50
52
|
|
|
51
53
|
```tsx
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
54
|
+
import { useTimer } from '@crup/react-timer-hook';
|
|
55
|
+
|
|
56
|
+
export function Stopwatch() {
|
|
57
|
+
const timer = useTimer({ updateIntervalMs: 100 });
|
|
57
58
|
|
|
58
59
|
return (
|
|
59
60
|
<>
|
|
60
|
-
<
|
|
61
|
+
<output>{Math.floor(timer.elapsedMilliseconds / 1000)}s</output>
|
|
61
62
|
<button onClick={timer.start}>Start</button>
|
|
62
63
|
<button onClick={timer.pause}>Pause</button>
|
|
63
64
|
<button onClick={timer.resume}>Resume</button>
|
|
64
65
|
<button onClick={timer.restart}>Restart</button>
|
|
65
|
-
<button onClick={() => timer.reset()}>Reset</button>
|
|
66
66
|
</>
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
### Absolute countdown
|
|
72
72
|
|
|
73
|
-
Use
|
|
73
|
+
Use `now` for wall-clock deadlines from a server, auction, reservation, or job expiry.
|
|
74
74
|
|
|
75
75
|
```tsx
|
|
76
|
-
|
|
76
|
+
import { useTimer } from '@crup/react-timer-hook';
|
|
77
|
+
|
|
78
|
+
export function AuctionTimer({ auctionId, expiresAt }: {
|
|
77
79
|
auctionId: string;
|
|
78
80
|
expiresAt: number;
|
|
79
81
|
}) {
|
|
@@ -86,188 +88,118 @@ function AuctionTimer({ auctionId, expiresAt }: {
|
|
|
86
88
|
|
|
87
89
|
const remainingMs = Math.max(0, expiresAt - timer.now);
|
|
88
90
|
|
|
89
|
-
if (timer.isEnded)
|
|
90
|
-
return <span>Auction ended</span>;
|
|
91
|
-
}
|
|
92
|
-
|
|
91
|
+
if (timer.isEnded) return <span>Auction ended</span>;
|
|
93
92
|
return <span>{Math.ceil(remainingMs / 1000)}s left</span>;
|
|
94
93
|
}
|
|
95
94
|
```
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
### Polling with early cancel
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Use this when pausing should freeze the remaining duration.
|
|
98
|
+
Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
|
|
102
99
|
|
|
103
100
|
```tsx
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
101
|
+
const timer = useTimer({
|
|
102
|
+
autoStart: true,
|
|
103
|
+
updateIntervalMs: 1000,
|
|
104
|
+
endWhen: snapshot => snapshot.now >= expiresAt,
|
|
105
|
+
schedules: [
|
|
106
|
+
{
|
|
107
|
+
id: 'auction-poll',
|
|
108
|
+
everyMs: 5000,
|
|
109
|
+
overlap: 'skip',
|
|
110
|
+
callback: async (_snapshot, controls) => {
|
|
111
|
+
const auction = await api.getAuction(auctionId);
|
|
112
|
+
if (auction.status === 'sold') controls.cancel('sold');
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
### Many independent timers
|
|
120
|
+
|
|
121
|
+
Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
|
|
120
122
|
|
|
121
123
|
```tsx
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
const timers = useTimerGroup({
|
|
125
|
+
updateIntervalMs: 1000,
|
|
126
|
+
items: auctions.map(auction => ({
|
|
127
|
+
id: auction.id,
|
|
124
128
|
autoStart: true,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
129
|
+
endWhen: snapshot => snapshot.now >= auction.expiresAt,
|
|
130
|
+
onEnd: () => api.closeAuction(auction.id),
|
|
131
|
+
})),
|
|
132
|
+
});
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Schedules and Polling
|
|
135
|
+
## Bundle size
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
Current local build:
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
endWhen: snapshot => snapshot.now >= expiresAt,
|
|
144
|
-
onEnd: () => api.closeAuction(auctionId),
|
|
145
|
-
schedules: [
|
|
146
|
-
{
|
|
147
|
-
id: 'poll-auction',
|
|
148
|
-
everyMs: 5000,
|
|
149
|
-
overlap: 'skip',
|
|
150
|
-
callback: async (_snapshot, controls) => {
|
|
151
|
-
const auction = await api.getAuction(auctionId);
|
|
152
|
-
|
|
153
|
-
if (auction.status === 'sold') {
|
|
154
|
-
controls.cancel('sold');
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
});
|
|
139
|
+
| File | Raw | Gzip | Brotli |
|
|
140
|
+
| --- | ---: | ---: | ---: |
|
|
141
|
+
| `dist/index.js` | 27.32 kB | 4.69 kB | 4.18 kB |
|
|
142
|
+
| `dist/index.cjs` | 29.18 kB | 5.08 kB | 4.50 kB |
|
|
143
|
+
| `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
|
|
160
144
|
|
|
161
|
-
|
|
162
|
-
return <span>Auction closed</span>;
|
|
163
|
-
}
|
|
145
|
+
CI writes a size summary to the GitHub Actions UI and posts a bundle-size comment on pull requests.
|
|
164
146
|
|
|
165
|
-
|
|
166
|
-
return <span>{Math.ceil(remainingMs / 1000)}s left</span>;
|
|
167
|
-
}
|
|
168
|
-
```
|
|
147
|
+
## AI-friendly
|
|
169
148
|
|
|
170
|
-
|
|
149
|
+
End users do not need these files. They are for coding agents, docs-aware IDEs, and MCP clients.
|
|
171
150
|
|
|
172
|
-
|
|
151
|
+
### MCP setup
|
|
173
152
|
|
|
174
|
-
|
|
153
|
+
Clone the repo, install dependencies, and point your MCP client at the local stdio server:
|
|
175
154
|
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
id: auction.id,
|
|
182
|
-
autoStart: true,
|
|
183
|
-
endWhen: snapshot => snapshot.now >= auction.expiresAt,
|
|
184
|
-
onEnd: () => api.closeAuction(auction.id),
|
|
185
|
-
})),
|
|
186
|
-
});
|
|
155
|
+
```sh
|
|
156
|
+
git clone https://github.com/crup/react-timer-hook.git
|
|
157
|
+
cd react-timer-hook
|
|
158
|
+
pnpm install
|
|
159
|
+
```
|
|
187
160
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
key={auction.id}
|
|
197
|
-
auction={auction}
|
|
198
|
-
remainingMs={remainingMs}
|
|
199
|
-
isPaused={timer?.isPaused ?? false}
|
|
200
|
-
isEnded={timer?.isEnded ?? false}
|
|
201
|
-
onPause={() => timers.pause(auction.id)}
|
|
202
|
-
onResume={() => timers.resume(auction.id)}
|
|
203
|
-
onCancel={() => timers.cancel(auction.id, 'sold')}
|
|
204
|
-
/>
|
|
205
|
-
);
|
|
206
|
-
})}
|
|
207
|
-
</>
|
|
208
|
-
);
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"mcpServers": {
|
|
164
|
+
"react-timer-hook-docs": {
|
|
165
|
+
"command": "node",
|
|
166
|
+
"args": ["/absolute/path/to/react-timer-hook/mcp/server.mjs"]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
209
169
|
}
|
|
210
170
|
```
|
|
211
171
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
## Debug Logs
|
|
215
|
-
|
|
216
|
-
Debug logging is planned for v1, but it is opt-in.
|
|
172
|
+
The MCP server exposes:
|
|
217
173
|
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
debug: event => {
|
|
223
|
-
console.debug('[timer]', event);
|
|
224
|
-
},
|
|
225
|
-
});
|
|
174
|
+
```txt
|
|
175
|
+
react-timer-hook://package
|
|
176
|
+
react-timer-hook://api
|
|
177
|
+
react-timer-hook://recipes
|
|
226
178
|
```
|
|
227
179
|
|
|
228
|
-
|
|
180
|
+
Agents can use hosted context:
|
|
229
181
|
|
|
230
|
-
|
|
182
|
+
- https://crup.github.io/react-timer-hook/llms.txt
|
|
183
|
+
- https://crup.github.io/react-timer-hook/llms-full.txt
|
|
231
184
|
|
|
232
|
-
|
|
185
|
+
Local MCP/docs helpers:
|
|
233
186
|
|
|
234
|
-
|
|
187
|
+
```sh
|
|
188
|
+
pnpm ai:context
|
|
189
|
+
pnpm mcp:docs
|
|
190
|
+
```
|
|
235
191
|
|
|
236
|
-
|
|
237
|
-
| --- | ---: | ---: | ---: |
|
|
238
|
-
| `dist/index.js` | 27.32 kB | 4.69 kB | 4.18 kB |
|
|
239
|
-
| `dist/index.cjs` | 29.18 kB | 5.08 kB | 4.50 kB |
|
|
240
|
-
| `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
|
|
192
|
+
The MCP utility is repo-local and excluded from the published npm package.
|
|
241
193
|
|
|
242
|
-
|
|
194
|
+
## Release policy
|
|
243
195
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
196
|
+
- Published versions must stay `0.0.1-alpha.x` until stable release is explicitly unlocked.
|
|
197
|
+
- `@alpha` is the documented install tag right now.
|
|
198
|
+
- Npm requires a `latest` dist-tag, so the workflow keeps `latest` pointing at the current alpha until stable publishing is unlocked.
|
|
199
|
+
|
|
200
|
+
## Links
|
|
247
201
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
- Use recursive `setTimeout`, not `setInterval`.
|
|
253
|
-
- Never schedule timers during render.
|
|
254
|
-
- Use `Date.now()` for wall-clock `now`.
|
|
255
|
-
- Use `performance.now()` internally for active elapsed duration, with a `Date.now()` fallback.
|
|
256
|
-
- Keep controls stable for React dependency arrays.
|
|
257
|
-
- Keep latest callbacks and options in refs so rerenders do not restart the scheduler unnecessarily.
|
|
258
|
-
- Guard async work with generation IDs.
|
|
259
|
-
- Clean up on unmount.
|
|
260
|
-
- Test with fake timers and React Strict Mode.
|
|
261
|
-
|
|
262
|
-
See:
|
|
263
|
-
|
|
264
|
-
- [Requirements](./REQUIREMENTS.md)
|
|
265
|
-
- [API Specification](./docs/API.md)
|
|
266
|
-
- [Design Decisions](./docs/DECISIONS.md)
|
|
267
|
-
- [Recipes](./docs/RECIPES.md)
|
|
268
|
-
- [Branching and Commits](./docs/BRANCHING_AND_COMMITS.md)
|
|
269
|
-
- [Implementation Plan](./docs/IMPLEMENTATION.md)
|
|
270
|
-
- [Task Plan](./docs/TASKS.md)
|
|
271
|
-
- [OSS and GTM Plan](./docs/OSS_GTM.md)
|
|
272
|
-
- [Release and Docs Plan](./docs/RELEASE_AND_DOCS.md)
|
|
273
|
-
- [Agent Task Cards](./docs/AGENT_TASKS.md)
|
|
202
|
+
- ๐ Docs: https://crup.github.io/react-timer-hook/
|
|
203
|
+
- ๐ฆ npm: https://www.npmjs.com/package/@crup/react-timer-hook
|
|
204
|
+
- ๐งต Issues: https://github.com/crup/react-timer-hook/issues
|
|
205
|
+
- ๐ค Contributing: ./CONTRIBUTING.md
|