@timekeeper-countdown/core 0.1.4 → 0.3.0
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 +176 -24
- package/dist/{countdown-engine-tLXf4OVD.d.cts → countdown-engine-DEKF_8iT.d.cts} +30 -1
- package/dist/{countdown-engine-tLXf4OVD.d.ts → countdown-engine-DEKF_8iT.d.ts} +30 -1
- package/dist/format.cjs +1 -1
- package/dist/format.d.cts +18 -8
- package/dist/format.d.ts +18 -8
- package/dist/format.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/testing-utils.cjs +1 -1
- package/dist/testing-utils.d.cts +1 -1
- package/dist/testing-utils.d.ts +1 -1
- package/dist/testing-utils.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ The published bundle is pure ESM. When targeting CommonJS environments use a bun
|
|
|
21
21
|
|
|
22
22
|
Supported runtimes:
|
|
23
23
|
|
|
24
|
-
- Node.js
|
|
24
|
+
- Node.js 22+
|
|
25
25
|
- Modern browsers (ES2022 modules)
|
|
26
26
|
|
|
27
27
|
---
|
|
@@ -32,9 +32,11 @@ Supported runtimes:
|
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
import { Countdown, TimerState } from '@timekeeper-countdown/core';
|
|
35
|
+
import { formatTime } from '@timekeeper-countdown/core/format';
|
|
35
36
|
|
|
36
37
|
const countdown = Countdown(300, {
|
|
37
|
-
|
|
38
|
+
onSnapshot: snapshot => {
|
|
39
|
+
const { minutes, seconds } = formatTime(snapshot);
|
|
38
40
|
timerElement.textContent = `${minutes}:${seconds}`;
|
|
39
41
|
},
|
|
40
42
|
onStateChange: state => {
|
|
@@ -42,6 +44,9 @@ const countdown = Countdown(300, {
|
|
|
42
44
|
console.log('Finished!');
|
|
43
45
|
}
|
|
44
46
|
},
|
|
47
|
+
onError: error => {
|
|
48
|
+
console.error('Countdown error:', error);
|
|
49
|
+
},
|
|
45
50
|
});
|
|
46
51
|
|
|
47
52
|
countdown.start(); // Begin the countdown
|
|
@@ -50,7 +55,7 @@ countdown.start(); // Begin the countdown
|
|
|
50
55
|
`Countdown` wraps the lower-level engine and returns convenient methods:
|
|
51
56
|
|
|
52
57
|
```ts
|
|
53
|
-
countdown.start(); //
|
|
58
|
+
countdown.start(); // void (the low-level CountdownEngine returns boolean)
|
|
54
59
|
countdown.pause();
|
|
55
60
|
countdown.resume();
|
|
56
61
|
countdown.reset(nextInitialSeconds?);
|
|
@@ -88,9 +93,12 @@ engine.start();
|
|
|
88
93
|
`CountdownEngine` exposes fine-grained control:
|
|
89
94
|
|
|
90
95
|
- `start`, `pause`, `resume`, `reset(nextInitialSeconds?)`, `stop`, `setSeconds(value)`, `destroy`
|
|
96
|
+
- The constructor, `reset(value)`, and `setSeconds(value)` require a non-negative integer (≤ `Number.MAX_SAFE_INTEGER`) and **throw** on an invalid value (negative, non-integer, `NaN`, or `Infinity`).
|
|
91
97
|
- `getSnapshot()` returns the latest snapshot.
|
|
92
98
|
- `subscribe(listener)` emits the current snapshot immediately and on every tick.
|
|
93
99
|
|
|
100
|
+
Exceptions thrown by your own callbacks — `onSnapshot`, `onStateChange`, and `subscribe` listeners — are **swallowed** (fail-soft): one throwing listener never crashes the timer or stops the other listeners. These callback errors are **not** delivered to `onError`; `onError` is reserved for internal/timer errors only.
|
|
101
|
+
|
|
94
102
|
Snapshot structure:
|
|
95
103
|
|
|
96
104
|
```ts
|
|
@@ -116,6 +124,40 @@ interface CountdownSnapshot {
|
|
|
116
124
|
|
|
117
125
|
---
|
|
118
126
|
|
|
127
|
+
## State Transitions
|
|
128
|
+
|
|
129
|
+
The engine enforces a strict state machine. Invalid transitions are silently ignored and return `false`.
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
start()
|
|
133
|
+
IDLE ──────────────► RUNNING
|
|
134
|
+
▲ │ │
|
|
135
|
+
│ │ │
|
|
136
|
+
│ reset() pause() stop() / complete()
|
|
137
|
+
│ │ │
|
|
138
|
+
│ ▼ │
|
|
139
|
+
│ PAUSED │
|
|
140
|
+
│ │ │
|
|
141
|
+
│ reset() │ │
|
|
142
|
+
│◄───────────────────┘ │
|
|
143
|
+
│ │
|
|
144
|
+
│ reset() ▼
|
|
145
|
+
│◄──────────────────── STOPPED
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| From | Action | To |
|
|
149
|
+
| --------- | ---------- | --------- |
|
|
150
|
+
| `IDLE` | `start()` | `RUNNING` |
|
|
151
|
+
| `RUNNING` | `pause()` | `PAUSED` |
|
|
152
|
+
| `RUNNING` | `reset()` | `IDLE` |
|
|
153
|
+
| `RUNNING` | `stop()` | `STOPPED` |
|
|
154
|
+
| `PAUSED` | `resume()` | `RUNNING` |
|
|
155
|
+
| `PAUSED` | `reset()` | `IDLE` |
|
|
156
|
+
| `PAUSED` | `stop()` | `STOPPED` |
|
|
157
|
+
| `STOPPED` | `reset()` | `IDLE` |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
119
161
|
## Formatting Helpers
|
|
120
162
|
|
|
121
163
|
Use the helpers exported at `@timekeeper-countdown/core/format` to avoid reimplementing `padStart` logic.
|
|
@@ -145,28 +187,140 @@ import {
|
|
|
145
187
|
createFakeTimeProvider,
|
|
146
188
|
toTimeProvider,
|
|
147
189
|
buildSnapshot,
|
|
190
|
+
buildSnapshotSequence,
|
|
148
191
|
assertSnapshotState,
|
|
192
|
+
assertSnapshotCompleted,
|
|
193
|
+
assertRemainingSeconds,
|
|
194
|
+
TimerState,
|
|
149
195
|
} from '@timekeeper-countdown/core/testing-utils';
|
|
196
|
+
```
|
|
150
197
|
|
|
151
|
-
|
|
152
|
-
const engine = CountdownEngine(5, {
|
|
153
|
-
timeProvider: toTimeProvider(fake),
|
|
154
|
-
tickIntervalMs: 5,
|
|
155
|
-
});
|
|
198
|
+
### `createFakeTimeProvider(options?)`
|
|
156
199
|
|
|
157
|
-
|
|
158
|
-
|
|
200
|
+
Provides a controllable clock for deterministic testing.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
interface FakeTimeOptions {
|
|
204
|
+
startMs?: number; // default: 0
|
|
205
|
+
tickMs?: number; // default: 1000 — default step for advance()
|
|
206
|
+
highResolution?: boolean; // default: true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface FakeTimeProvider extends TimeProvider {
|
|
210
|
+
advance(ms?: number): number; // advances by ms (or tickMs if omitted), returns current time
|
|
211
|
+
set(ms: number): number; // sets clock to absolute value, returns current time
|
|
212
|
+
reset(): number; // resets to startMs, returns current time
|
|
213
|
+
getTime(): number; // returns current time without advancing
|
|
214
|
+
now(): number; // same as getTime (inherited from TimeProvider)
|
|
215
|
+
isHighResolution: boolean;
|
|
216
|
+
type: 'fake';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createFakeTimeProvider(options?: FakeTimeOptions): FakeTimeProvider;
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- Negative or non-finite values are clamped to `0`.
|
|
223
|
+
- Values above `Number.MAX_SAFE_INTEGER` are clamped to `Number.MAX_SAFE_INTEGER`.
|
|
224
|
+
- `advance()` without a parameter uses `tickMs` as the default step.
|
|
225
|
+
|
|
226
|
+
### `toTimeProvider(fake)`
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
function toTimeProvider(fake: FakeTimeProvider): TimeProvider;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Converts a `FakeTimeProvider` to a read-only `TimeProvider`, used to pass to `CountdownEngine` or `useCountdown`.
|
|
233
|
+
|
|
234
|
+
### `buildSnapshot(options?)`
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
interface SnapshotOptions {
|
|
238
|
+
initialSeconds?: number; // fallback: totalSeconds, then 0
|
|
239
|
+
totalSeconds?: number; // fallback: initialSeconds, then 0
|
|
240
|
+
state?: TimerState; // fallback: IDLE if totalSeconds > 0, else STOPPED
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function buildSnapshot(options?: SnapshotOptions): CountdownSnapshot;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const snapshot = buildSnapshot({ totalSeconds: 90, state: TimerState.RUNNING });
|
|
248
|
+
// snapshot.parts.minutes === 1
|
|
249
|
+
// snapshot.parts.seconds === 30
|
|
250
|
+
// snapshot.isRunning === true
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `buildSnapshotSequence(options?)`
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
interface SequenceOptions extends SnapshotOptions {
|
|
257
|
+
step?: number; // default: 1 — decrement per snapshot
|
|
258
|
+
count?: number; // default: 1 — number of snapshots
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildSnapshotSequence(options?: SequenceOptions): CountdownSnapshot[];
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Generates `count` snapshots, decrementing `totalSeconds` by `step` each iteration. The last snapshot with `remaining === 0` gets `state: STOPPED`; all others get `state: RUNNING`.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const sequence = buildSnapshotSequence({ totalSeconds: 4, step: 2, count: 3 });
|
|
268
|
+
// sequence[0].totalSeconds === 4 (RUNNING)
|
|
269
|
+
// sequence[1].totalSeconds === 2 (RUNNING)
|
|
270
|
+
// sequence[2].totalSeconds === 0 (STOPPED)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `assertSnapshotState(snapshot, expected, message?)`
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
function assertSnapshotState(
|
|
277
|
+
snapshot: CountdownSnapshot,
|
|
278
|
+
expected: TimerState,
|
|
279
|
+
message?: string // default: "Unexpected countdown state"
|
|
280
|
+
): void;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `assertSnapshotCompleted(snapshot, message?)`
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
function assertSnapshotCompleted(
|
|
287
|
+
snapshot: CountdownSnapshot,
|
|
288
|
+
message?: string // default: "Countdown should be completed"
|
|
289
|
+
): void;
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Throws if `snapshot.isCompleted === false` OR `snapshot.totalSeconds !== 0`.
|
|
159
293
|
|
|
160
|
-
|
|
294
|
+
### `assertRemainingSeconds(snapshot, expected, tolerance?, message?)`
|
|
161
295
|
|
|
162
|
-
|
|
163
|
-
|
|
296
|
+
```ts
|
|
297
|
+
function assertRemainingSeconds(
|
|
298
|
+
snapshot: CountdownSnapshot,
|
|
299
|
+
expected: number,
|
|
300
|
+
tolerance?: number, // default: 0
|
|
301
|
+
message?: string // default: "Unexpected remaining seconds"
|
|
302
|
+
): void;
|
|
164
303
|
```
|
|
165
304
|
|
|
166
|
-
|
|
305
|
+
Throws if `Math.abs(snapshot.totalSeconds - Math.floor(expected)) > tolerance` or if `expected` is not a finite number.
|
|
167
306
|
|
|
168
|
-
|
|
169
|
-
|
|
307
|
+
```ts
|
|
308
|
+
assertRemainingSeconds(snapshot, 5); // exact
|
|
309
|
+
assertRemainingSeconds(snapshot, 5, 0.5); // accepts 4.5–5.5
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `TimerState` re-export
|
|
313
|
+
|
|
314
|
+
`TimerState` is re-exported via `testing-utils`, avoiding a double import:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
// Instead of two separate imports:
|
|
318
|
+
import { TimerState } from '@timekeeper-countdown/core';
|
|
319
|
+
import { buildSnapshot } from '@timekeeper-countdown/core/testing-utils';
|
|
320
|
+
|
|
321
|
+
// You can import everything from one place:
|
|
322
|
+
import { buildSnapshot, TimerState } from '@timekeeper-countdown/core/testing-utils';
|
|
323
|
+
```
|
|
170
324
|
|
|
171
325
|
---
|
|
172
326
|
|
|
@@ -188,6 +342,10 @@ const provider = {
|
|
|
188
342
|
CountdownEngine(60, { timeProvider: provider });
|
|
189
343
|
```
|
|
190
344
|
|
|
345
|
+
Constructing with an invalid `timeProvider` — anything that is neither a function nor an object exposing a `now(): number` method — **throws** at construction (and that error propagates from `useCountdown`'s effect).
|
|
346
|
+
|
|
347
|
+
Every time provider — the default one and any custom provider you supply — is wrapped in a finite, non-decreasing guard, so a `NaN`/`Infinity`/backward clock reading (from NTP, DST, or sleep-wake, or a buggy custom provider) is repaired to the last good value, and remaining seconds is always clamped to `[0, initialValue]`. A provider whose `now()` *throws* is not repaired; the error routes to `onError` instead.
|
|
348
|
+
|
|
191
349
|
---
|
|
192
350
|
|
|
193
351
|
## TypeScript Support
|
|
@@ -215,15 +373,9 @@ import type {
|
|
|
215
373
|
|
|
216
374
|
## Contributing
|
|
217
375
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
npm run lint --workspaces
|
|
222
|
-
npm run test --workspaces
|
|
223
|
-
npm run typecheck --workspaces
|
|
224
|
-
```
|
|
376
|
+
We welcome contributions! Please read our [Contributing Guide](https://github.com/eagle-head/timekeeper-countdown/blob/main/CONTRIBUTING.md) to get started.
|
|
225
377
|
|
|
226
|
-
|
|
378
|
+
By participating in this project, you agree to abide by our [Code of Conduct](https://github.com/eagle-head/timekeeper-countdown/blob/main/CODE_OF_CONDUCT.md).
|
|
227
379
|
|
|
228
380
|
---
|
|
229
381
|
|
|
@@ -16,6 +16,33 @@ declare const TimerState: Readonly<{
|
|
|
16
16
|
}>;
|
|
17
17
|
type TimerState = (typeof TimerState)[keyof typeof TimerState];
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A countdown duration broken down into calendar-style units.
|
|
21
|
+
*
|
|
22
|
+
* The unit ladder is fixed-length and internally consistent — a year is
|
|
23
|
+
* {@link SECONDS_PER_YEAR} (365 days), a week is {@link SECONDS_PER_WEEK}
|
|
24
|
+
* (7 days), and so on. Because the breakdown is computed by successive
|
|
25
|
+
* subtraction over that single ladder (not independent modular arithmetic),
|
|
26
|
+
* the parts always reconstruct the total exactly:
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* years*SECONDS_PER_YEAR + weeks*SECONDS_PER_WEEK + days*SECONDS_PER_DAY +
|
|
30
|
+
* hours*SECONDS_PER_HOUR + minutes*SECONDS_PER_MINUTE + seconds === totalSeconds
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Field ranges:
|
|
34
|
+
* - `years` — 0+ (unbounded; one year is 365 days)
|
|
35
|
+
* - `weeks` — 0–52 (one week is 7 days, so the 365-day remainder reaches 52 weeks)
|
|
36
|
+
* - `days` — 0–6
|
|
37
|
+
* - `hours` — 0–23
|
|
38
|
+
* - `minutes` / `seconds` — 0–59
|
|
39
|
+
*
|
|
40
|
+
* The `total*` fields are cumulative floors of the whole duration, NOT modular
|
|
41
|
+
* remainders of the calendar ladder:
|
|
42
|
+
* - `totalDays` — `Math.floor(totalSeconds / SECONDS_PER_DAY)`
|
|
43
|
+
* - `totalHours` — `Math.floor(totalSeconds / SECONDS_PER_HOUR)`
|
|
44
|
+
* - `totalMinutes` — `Math.floor(totalSeconds / SECONDS_PER_MINUTE)`
|
|
45
|
+
*/
|
|
19
46
|
interface CountdownParts {
|
|
20
47
|
years: number;
|
|
21
48
|
weeks: number;
|
|
@@ -27,6 +54,7 @@ interface CountdownParts {
|
|
|
27
54
|
totalHours: number;
|
|
28
55
|
totalMinutes: number;
|
|
29
56
|
}
|
|
57
|
+
|
|
30
58
|
interface CountdownSnapshot {
|
|
31
59
|
initialSeconds: number;
|
|
32
60
|
totalSeconds: number;
|
|
@@ -56,6 +84,7 @@ interface CountdownEngineInstance {
|
|
|
56
84
|
subscribe: (listener: (snapshot: CountdownSnapshot) => void) => CountdownSubscription;
|
|
57
85
|
destroy: () => void;
|
|
58
86
|
}
|
|
87
|
+
declare function buildSnapshot(initialSeconds: number, totalSeconds: number, state: TimerState): CountdownSnapshot;
|
|
59
88
|
declare function CountdownEngine(initialSecondsInput: number, options?: CountdownEngineOptions): CountdownEngineInstance;
|
|
60
89
|
|
|
61
|
-
export { type CountdownSnapshot as C, TimerState as T, CountdownEngine as a, type CountdownEngineInstance as
|
|
90
|
+
export { type CountdownSnapshot as C, TimerState as T, CountdownEngine as a, buildSnapshot as b, type CountdownEngineInstance as c, type CountdownEngineOptions as d, type CountdownParts as e, type TimeProvider as f };
|
|
@@ -16,6 +16,33 @@ declare const TimerState: Readonly<{
|
|
|
16
16
|
}>;
|
|
17
17
|
type TimerState = (typeof TimerState)[keyof typeof TimerState];
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A countdown duration broken down into calendar-style units.
|
|
21
|
+
*
|
|
22
|
+
* The unit ladder is fixed-length and internally consistent — a year is
|
|
23
|
+
* {@link SECONDS_PER_YEAR} (365 days), a week is {@link SECONDS_PER_WEEK}
|
|
24
|
+
* (7 days), and so on. Because the breakdown is computed by successive
|
|
25
|
+
* subtraction over that single ladder (not independent modular arithmetic),
|
|
26
|
+
* the parts always reconstruct the total exactly:
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* years*SECONDS_PER_YEAR + weeks*SECONDS_PER_WEEK + days*SECONDS_PER_DAY +
|
|
30
|
+
* hours*SECONDS_PER_HOUR + minutes*SECONDS_PER_MINUTE + seconds === totalSeconds
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Field ranges:
|
|
34
|
+
* - `years` — 0+ (unbounded; one year is 365 days)
|
|
35
|
+
* - `weeks` — 0–52 (one week is 7 days, so the 365-day remainder reaches 52 weeks)
|
|
36
|
+
* - `days` — 0–6
|
|
37
|
+
* - `hours` — 0–23
|
|
38
|
+
* - `minutes` / `seconds` — 0–59
|
|
39
|
+
*
|
|
40
|
+
* The `total*` fields are cumulative floors of the whole duration, NOT modular
|
|
41
|
+
* remainders of the calendar ladder:
|
|
42
|
+
* - `totalDays` — `Math.floor(totalSeconds / SECONDS_PER_DAY)`
|
|
43
|
+
* - `totalHours` — `Math.floor(totalSeconds / SECONDS_PER_HOUR)`
|
|
44
|
+
* - `totalMinutes` — `Math.floor(totalSeconds / SECONDS_PER_MINUTE)`
|
|
45
|
+
*/
|
|
19
46
|
interface CountdownParts {
|
|
20
47
|
years: number;
|
|
21
48
|
weeks: number;
|
|
@@ -27,6 +54,7 @@ interface CountdownParts {
|
|
|
27
54
|
totalHours: number;
|
|
28
55
|
totalMinutes: number;
|
|
29
56
|
}
|
|
57
|
+
|
|
30
58
|
interface CountdownSnapshot {
|
|
31
59
|
initialSeconds: number;
|
|
32
60
|
totalSeconds: number;
|
|
@@ -56,6 +84,7 @@ interface CountdownEngineInstance {
|
|
|
56
84
|
subscribe: (listener: (snapshot: CountdownSnapshot) => void) => CountdownSubscription;
|
|
57
85
|
destroy: () => void;
|
|
58
86
|
}
|
|
87
|
+
declare function buildSnapshot(initialSeconds: number, totalSeconds: number, state: TimerState): CountdownSnapshot;
|
|
59
88
|
declare function CountdownEngine(initialSecondsInput: number, options?: CountdownEngineOptions): CountdownEngineInstance;
|
|
60
89
|
|
|
61
|
-
export { type CountdownSnapshot as C, TimerState as T, CountdownEngine as a, type CountdownEngineInstance as
|
|
90
|
+
export { type CountdownSnapshot as C, TimerState as T, CountdownEngine as a, buildSnapshot as b, type CountdownEngineInstance as c, type CountdownEngineOptions as d, type CountdownParts as e, type TimeProvider as f };
|
package/dist/format.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function l(t){let a=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,o=a,s=Math.floor(o/31536e3);o-=s*31536e3;let m=Math.floor(o/604800);o-=m*604800;let u=Math.floor(o/86400);o-=u*86400;let f=Math.floor(o/3600);o-=f*3600;let i=Math.floor(o/60);return o-=i*60,{years:s,weeks:m,days:u,hours:f,minutes:i,seconds:o,totalDays:Math.floor(a/86400),totalHours:Math.floor(a/3600),totalMinutes:Math.floor(a/60)}}function S(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function F(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function n(t,a=2){return t.toString().padStart(a,"0")}function x(){let t=r=>l(S(F(r)));return {formatTime:r=>{let c=t(r);return {minutes:n(c.totalMinutes),seconds:n(c.seconds)}},formatMinutes:r=>n(t(r).minutes),formatSeconds:r=>n(t(r).seconds),formatHours:r=>n(t(r).hours),formatDays:r=>n(t(r).days),formatWeeks:r=>n(t(r).weeks),formatYears:r=>n(t(r).years)}}var e=x();function M(t){return e.formatTime(t)}function h(t){return e.formatMinutes(t)}function y(t){return e.formatSeconds(t)}function E(t){return e.formatHours(t)}function k(t){return e.formatDays(t)}function C(t){return e.formatWeeks(t)}function w(t){return e.formatYears(t)}exports.Formatter=x;exports.defaultFormatter=e;exports.formatDays=k;exports.formatHours=E;exports.formatMinutes=h;exports.formatSeconds=y;exports.formatTime=M;exports.formatWeeks=C;exports.formatYears=w;
|
package/dist/format.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.cjs';
|
|
2
2
|
|
|
3
3
|
type FormatTarget = number | Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined;
|
|
4
4
|
declare function Formatter(): {
|
|
@@ -25,15 +25,25 @@ declare const defaultFormatter: {
|
|
|
25
25
|
formatWeeks: (target: FormatTarget) => string;
|
|
26
26
|
formatYears: (target: FormatTarget) => string;
|
|
27
27
|
};
|
|
28
|
-
declare
|
|
28
|
+
declare function formatTime(target: number): {
|
|
29
29
|
minutes: string;
|
|
30
30
|
seconds: string;
|
|
31
31
|
};
|
|
32
|
-
declare
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
declare
|
|
37
|
-
declare
|
|
32
|
+
declare function formatTime(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): {
|
|
33
|
+
minutes: string;
|
|
34
|
+
seconds: string;
|
|
35
|
+
};
|
|
36
|
+
declare function formatMinutes(target: number): string;
|
|
37
|
+
declare function formatMinutes(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
38
|
+
declare function formatSeconds(target: number): string;
|
|
39
|
+
declare function formatSeconds(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
40
|
+
declare function formatHours(target: number): string;
|
|
41
|
+
declare function formatHours(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
42
|
+
declare function formatDays(target: number): string;
|
|
43
|
+
declare function formatDays(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
44
|
+
declare function formatWeeks(target: number): string;
|
|
45
|
+
declare function formatWeeks(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
46
|
+
declare function formatYears(target: number): string;
|
|
47
|
+
declare function formatYears(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
38
48
|
|
|
39
49
|
export { type FormatTarget, Formatter, defaultFormatter, formatDays, formatHours, formatMinutes, formatSeconds, formatTime, formatWeeks, formatYears };
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.js';
|
|
2
2
|
|
|
3
3
|
type FormatTarget = number | Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined;
|
|
4
4
|
declare function Formatter(): {
|
|
@@ -25,15 +25,25 @@ declare const defaultFormatter: {
|
|
|
25
25
|
formatWeeks: (target: FormatTarget) => string;
|
|
26
26
|
formatYears: (target: FormatTarget) => string;
|
|
27
27
|
};
|
|
28
|
-
declare
|
|
28
|
+
declare function formatTime(target: number): {
|
|
29
29
|
minutes: string;
|
|
30
30
|
seconds: string;
|
|
31
31
|
};
|
|
32
|
-
declare
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
declare
|
|
37
|
-
declare
|
|
32
|
+
declare function formatTime(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): {
|
|
33
|
+
minutes: string;
|
|
34
|
+
seconds: string;
|
|
35
|
+
};
|
|
36
|
+
declare function formatMinutes(target: number): string;
|
|
37
|
+
declare function formatMinutes(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
38
|
+
declare function formatSeconds(target: number): string;
|
|
39
|
+
declare function formatSeconds(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
40
|
+
declare function formatHours(target: number): string;
|
|
41
|
+
declare function formatHours(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
42
|
+
declare function formatDays(target: number): string;
|
|
43
|
+
declare function formatDays(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
44
|
+
declare function formatWeeks(target: number): string;
|
|
45
|
+
declare function formatWeeks(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
46
|
+
declare function formatYears(target: number): string;
|
|
47
|
+
declare function formatYears(target: Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined): string;
|
|
38
48
|
|
|
39
49
|
export { type FormatTarget, Formatter, defaultFormatter, formatDays, formatHours, formatMinutes, formatSeconds, formatTime, formatWeeks, formatYears };
|
package/dist/format.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function l(t){let a=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,o=a,s=Math.floor(o/31536e3);o-=s*31536e3;let m=Math.floor(o/604800);o-=m*604800;let u=Math.floor(o/86400);o-=u*86400;let f=Math.floor(o/3600);o-=f*3600;let i=Math.floor(o/60);return o-=i*60,{years:s,weeks:m,days:u,hours:f,minutes:i,seconds:o,totalDays:Math.floor(a/86400),totalHours:Math.floor(a/3600),totalMinutes:Math.floor(a/60)}}function S(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function F(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function n(t,a=2){return t.toString().padStart(a,"0")}function x(){let t=r=>l(S(F(r)));return {formatTime:r=>{let c=t(r);return {minutes:n(c.totalMinutes),seconds:n(c.seconds)}},formatMinutes:r=>n(t(r).minutes),formatSeconds:r=>n(t(r).seconds),formatHours:r=>n(t(r).hours),formatDays:r=>n(t(r).days),formatWeeks:r=>n(t(r).weeks),formatYears:r=>n(t(r).years)}}var e=x();function M(t){return e.formatTime(t)}function h(t){return e.formatMinutes(t)}function y(t){return e.formatSeconds(t)}function E(t){return e.formatHours(t)}function k(t){return e.formatDays(t)}function C(t){return e.formatWeeks(t)}function w(t){return e.formatYears(t)}export{x as Formatter,e as defaultFormatter,k as formatDays,E as formatHours,h as formatMinutes,y as formatSeconds,M as formatTime,C as formatWeeks,w as formatYears};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function W(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function X(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,o=X();function n(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let e=performance.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER){t=W();return}}throw new Error("performance.now() not available or invalid")}catch{t=o;}}return n(),{now:()=>{try{let e=t.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER)return e;throw new Error("Invalid time value returned")}catch{t!==o&&(t=o);try{let e=o.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0)return e}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}function k(t,o=0){let n=typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0;return ()=>{let e=t();return typeof e!="number"||!Number.isFinite(e)||e<n||(n=e),n}}y();var z=10,j=100;function B(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?j:Math.max(z,Math.floor(t))}function A(t,o,n={}){if(typeof t!="number"||!Number.isFinite(t))throw new Error("initialSeconds must be a finite number");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");let e=Math.floor(Math.max(0,t)),s=null,a=null,u=0,c=e,i=e,S=B(n.tickIntervalMs),g=y(),f=n.timeProvider??(()=>g.now()),l=()=>setInterval(()=>{try{if(a===null)return;let h=f()-a-u,D=Math.floor(h/1e3),r=Number.isFinite(D)?Math.min(i,Math.max(0,i-D)):i;e=r,r!==c&&(c=r,o.onTick(r)),r===0&&(d(),o.onComplete());}catch(p){d(),o.onError(p instanceof Error?p:new Error(String(p)));}},S),N=()=>{if(s||e<=0)return false;try{if(a!==null&&e<i){let p=(i-e)*1e3;u=f()-a-p;}else a=f(),u=0,c=e;return s=l(),!0}catch(p){return o.onError(p instanceof Error?p:new Error(String(p))),false}},d=()=>{s&&(clearInterval(s),s=null);},b=()=>{d(),e=i,a=null,u=0,c=i;},v=p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));e=h,a=null,u=0,c=h;};return {start:N,stop:d,reset:b,setSeconds:v,setInitialValue:p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));i=h,v(h);},getTotalSeconds:()=>e,getInitialValue:()=>i,isRunning:()=>s!==null,destroy:()=>{d(),e=0,a=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),I={[m.IDLE]:{start:m.RUNNING},[m.RUNNING]:{pause:m.PAUSED,reset:m.IDLE,stop:m.STOPPED,complete:m.STOPPED},[m.PAUSED]:{resume:m.RUNNING,reset:m.IDLE,stop:m.STOPPED},[m.STOPPED]:{reset:m.IDLE}};function O(t){if(t!==void 0){if(typeof t!="object"||t===null)throw new Error("events must be an object");if(t.onStateChange!==void 0&&typeof t.onStateChange!="function")throw new Error("events.onStateChange must be a function")}let o=m.IDLE;function n(g){let f=I[o]?.[g];if(!f)return false;o=f;try{t?.onStateChange?.(f);}catch{}return true}function e(){return n("start")}function s(){return n("resume")}function a(){return n("pause")}function u(){return n("reset")}function c(){return n("stop")}function i(){return n("complete")}function S(){o===m.IDLE||o===m.STOPPED||n("stop");}return {start:e,resume:s,pause:a,reset:u,stop:c,complete:i,getCurrentState:()=>o,canStart:()=>!!I[o]?.start,canResume:()=>!!I[o]?.resume,canPause:()=>!!I[o]?.pause,isRunning:()=>o===m.RUNNING,destroy:S}}function x(t){let o=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,n=o,e=Math.floor(n/31536e3);n-=e*31536e3;let s=Math.floor(n/604800);n-=s*604800;let a=Math.floor(n/86400);n-=a*86400;let u=Math.floor(n/3600);n-=u*3600;let c=Math.floor(n/60);return n-=c*60,{years:e,weeks:s,days:a,hours:u,minutes:c,seconds:n,totalDays:Math.floor(o/86400),totalHours:Math.floor(o/3600),totalMinutes:Math.floor(o/60)}}function C(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function R(t){if(typeof t!="number"||!Number.isFinite(t)||!Number.isInteger(t))throw new Error("initialSeconds must be a finite, non-negative integer");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");return t}function J(t){let o;if(t)if(typeof t=="function")o=t;else if(typeof t.now=="function"){let n=t;o=()=>n.now();}else throw new Error("timeProvider must implement a now(): number method");else {let n=y();o=()=>n.now();}return k(o)}function P(t,o,n){let e=C(t),s=C(o),a=x(s);return {initialSeconds:e,totalSeconds:s,parts:a,state:n,isRunning:n===m.RUNNING,isCompleted:s===0&&n===m.STOPPED}}function F(t,o={}){let n=R(t),e=J(o.timeProvider),s=n,a=P(n,n,m.IDLE),u=new Set,c=r=>{try{o.onSnapshot?.(r);}catch{}for(let w of u)try{w(r);}catch{}},i=r=>{let w=r===m.STOPPED?0:l.getTotalSeconds();a=P(s,w,r);try{o.onStateChange?.(r,a);}catch{}c(a);},S=r=>{a=P(s,r,f.getCurrentState()),c(a);},g=r=>{try{o.onError?.(r);}catch{}},f=O({onStateChange:r=>{i(r);}}),l=A(n,{onTick:r=>{S(r);},onComplete:()=>{f.complete();},onError:r=>{g(r),f.stop();}},{timeProvider:e,tickIntervalMs:o.tickIntervalMs});return {start:()=>{if(!f.canStart())return false;let r=l.start();return r&&f.start(),r},pause:()=>f.canPause()?(l.stop(),f.pause()):false,resume:()=>{if(!f.canResume())return false;let r=l.start();return r&&f.resume(),r},reset:r=>{if(l.stop(),typeof r=="number"){let _=R(r);s=_,l.setInitialValue(_);}else s=n,l.reset();return f.reset()||S(l.getTotalSeconds()),true},stop:()=>{l.stop();let r=f.stop();return r&&l.setSeconds(0),r},setSeconds:r=>{let w=R(r);l.setSeconds(w),S(l.getTotalSeconds());},getSnapshot:()=>a,subscribe:r=>{u.add(r);try{r(a);}catch{}return {unsubscribe:()=>{u.delete(r);}}},destroy:()=>{l.destroy(),f.destroy(),u.clear(),a=P(s,0,m.STOPPED);}}}function Q(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function T(t,o=2){return t.toString().padStart(o,"0")}function M(){let t=i=>x(C(Q(i)));return {formatTime:i=>{let S=t(i);return {minutes:T(S.totalMinutes),seconds:T(S.seconds)}},formatMinutes:i=>T(t(i).minutes),formatSeconds:i=>T(t(i).seconds),formatHours:i=>T(t(i).hours),formatDays:i=>T(t(i).days),formatWeeks:i=>T(t(i).weeks),formatYears:i=>T(t(i).years)}}var E=M();function Z(t){return E.formatTime(t)}function $(t){return E.formatMinutes(t)}function tt(t){return E.formatSeconds(t)}function et(t){return E.formatHours(t)}function nt(t){return E.formatDays(t)}function ot(t){return E.formatWeeks(t)}function rt(t){return E.formatYears(t)}function it(t,o={}){let{onSnapshot:n,onStateChange:e,onError:s}=o;if(n!==void 0&&typeof n!="function")throw new Error("onSnapshot must be a function");if(e!==void 0&&typeof e!="function")throw new Error("onStateChange must be a function");if(s!==void 0&&typeof s!="function")throw new Error("onError must be a function");let a=M(),u=F(t,{onError:s}),c=u.getSnapshot(),i=c.state,S=Number.NaN,g=d=>{if(s)try{s(d);}catch{}},f=d=>{if(n)try{n(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},l=d=>{if(e)try{e(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},N=u.subscribe(d=>{c=d,d.state!==i&&(i=d.state,l(d.state)),d.totalSeconds!==S&&(S=d.totalSeconds,f(d));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:d=>{u.reset(d);},stop:()=>{u.stop();},getSeconds:()=>a.formatSeconds(c.totalSeconds),getMinutes:()=>a.formatMinutes(c.totalSeconds),getHours:()=>a.formatHours(c.totalSeconds),getDays:()=>a.formatDays(c.totalSeconds),getWeeks:()=>a.formatWeeks(c.totalSeconds),getYears:()=>a.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{N.unsubscribe();}finally{u.destroy();}}}}exports.Countdown=it;exports.CountdownEngine=F;exports.Formatter=M;exports.TimerState=m;exports.buildSnapshot=P;exports.defaultFormatter=E;exports.formatDays=nt;exports.formatHours=et;exports.formatMinutes=$;exports.formatSeconds=tt;exports.formatTime=Z;exports.formatWeeks=ot;exports.formatYears=rt;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as CountdownEngine,
|
|
1
|
+
import { C as CountdownSnapshot, T as TimerState } from './countdown-engine-DEKF_8iT.cjs';
|
|
2
|
+
export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-DEKF_8iT.cjs';
|
|
3
3
|
export { FormatTarget, Formatter, defaultFormatter, formatDays, formatHours, formatMinutes, formatSeconds, formatTime, formatWeeks, formatYears } from './format.cjs';
|
|
4
4
|
|
|
5
5
|
interface CountdownOptions {
|
|
6
|
-
|
|
6
|
+
onSnapshot?: (snapshot: CountdownSnapshot) => void;
|
|
7
7
|
onStateChange?: (state: TimerState) => void;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
8
9
|
}
|
|
9
10
|
interface CountdownInstance {
|
|
10
11
|
start: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { a as CountdownEngine,
|
|
1
|
+
import { C as CountdownSnapshot, T as TimerState } from './countdown-engine-DEKF_8iT.js';
|
|
2
|
+
export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-DEKF_8iT.js';
|
|
3
3
|
export { FormatTarget, Formatter, defaultFormatter, formatDays, formatHours, formatMinutes, formatSeconds, formatTime, formatWeeks, formatYears } from './format.js';
|
|
4
4
|
|
|
5
5
|
interface CountdownOptions {
|
|
6
|
-
|
|
6
|
+
onSnapshot?: (snapshot: CountdownSnapshot) => void;
|
|
7
7
|
onStateChange?: (state: TimerState) => void;
|
|
8
|
+
onError?: (error: Error) => void;
|
|
8
9
|
}
|
|
9
10
|
interface CountdownInstance {
|
|
10
11
|
start: () => void;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function W(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function X(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,o=X();function n(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let e=performance.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER){t=W();return}}throw new Error("performance.now() not available or invalid")}catch{t=o;}}return n(),{now:()=>{try{let e=t.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER)return e;throw new Error("Invalid time value returned")}catch{t!==o&&(t=o);try{let e=o.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0)return e}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}function k(t,o=0){let n=typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0;return ()=>{let e=t();return typeof e!="number"||!Number.isFinite(e)||e<n||(n=e),n}}y();var z=10,j=100;function B(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?j:Math.max(z,Math.floor(t))}function A(t,o,n={}){if(typeof t!="number"||!Number.isFinite(t))throw new Error("initialSeconds must be a finite number");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");let e=Math.floor(Math.max(0,t)),s=null,a=null,u=0,c=e,i=e,S=B(n.tickIntervalMs),g=y(),f=n.timeProvider??(()=>g.now()),l=()=>setInterval(()=>{try{if(a===null)return;let h=f()-a-u,D=Math.floor(h/1e3),r=Number.isFinite(D)?Math.min(i,Math.max(0,i-D)):i;e=r,r!==c&&(c=r,o.onTick(r)),r===0&&(d(),o.onComplete());}catch(p){d(),o.onError(p instanceof Error?p:new Error(String(p)));}},S),N=()=>{if(s||e<=0)return false;try{if(a!==null&&e<i){let p=(i-e)*1e3;u=f()-a-p;}else a=f(),u=0,c=e;return s=l(),!0}catch(p){return o.onError(p instanceof Error?p:new Error(String(p))),false}},d=()=>{s&&(clearInterval(s),s=null);},b=()=>{d(),e=i,a=null,u=0,c=i;},v=p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));e=h,a=null,u=0,c=h;};return {start:N,stop:d,reset:b,setSeconds:v,setInitialValue:p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));i=h,v(h);},getTotalSeconds:()=>e,getInitialValue:()=>i,isRunning:()=>s!==null,destroy:()=>{d(),e=0,a=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),I={[m.IDLE]:{start:m.RUNNING},[m.RUNNING]:{pause:m.PAUSED,reset:m.IDLE,stop:m.STOPPED,complete:m.STOPPED},[m.PAUSED]:{resume:m.RUNNING,reset:m.IDLE,stop:m.STOPPED},[m.STOPPED]:{reset:m.IDLE}};function O(t){if(t!==void 0){if(typeof t!="object"||t===null)throw new Error("events must be an object");if(t.onStateChange!==void 0&&typeof t.onStateChange!="function")throw new Error("events.onStateChange must be a function")}let o=m.IDLE;function n(g){let f=I[o]?.[g];if(!f)return false;o=f;try{t?.onStateChange?.(f);}catch{}return true}function e(){return n("start")}function s(){return n("resume")}function a(){return n("pause")}function u(){return n("reset")}function c(){return n("stop")}function i(){return n("complete")}function S(){o===m.IDLE||o===m.STOPPED||n("stop");}return {start:e,resume:s,pause:a,reset:u,stop:c,complete:i,getCurrentState:()=>o,canStart:()=>!!I[o]?.start,canResume:()=>!!I[o]?.resume,canPause:()=>!!I[o]?.pause,isRunning:()=>o===m.RUNNING,destroy:S}}function x(t){let o=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,n=o,e=Math.floor(n/31536e3);n-=e*31536e3;let s=Math.floor(n/604800);n-=s*604800;let a=Math.floor(n/86400);n-=a*86400;let u=Math.floor(n/3600);n-=u*3600;let c=Math.floor(n/60);return n-=c*60,{years:e,weeks:s,days:a,hours:u,minutes:c,seconds:n,totalDays:Math.floor(o/86400),totalHours:Math.floor(o/3600),totalMinutes:Math.floor(o/60)}}function C(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function R(t){if(typeof t!="number"||!Number.isFinite(t)||!Number.isInteger(t))throw new Error("initialSeconds must be a finite, non-negative integer");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");return t}function J(t){let o;if(t)if(typeof t=="function")o=t;else if(typeof t.now=="function"){let n=t;o=()=>n.now();}else throw new Error("timeProvider must implement a now(): number method");else {let n=y();o=()=>n.now();}return k(o)}function P(t,o,n){let e=C(t),s=C(o),a=x(s);return {initialSeconds:e,totalSeconds:s,parts:a,state:n,isRunning:n===m.RUNNING,isCompleted:s===0&&n===m.STOPPED}}function F(t,o={}){let n=R(t),e=J(o.timeProvider),s=n,a=P(n,n,m.IDLE),u=new Set,c=r=>{try{o.onSnapshot?.(r);}catch{}for(let w of u)try{w(r);}catch{}},i=r=>{let w=r===m.STOPPED?0:l.getTotalSeconds();a=P(s,w,r);try{o.onStateChange?.(r,a);}catch{}c(a);},S=r=>{a=P(s,r,f.getCurrentState()),c(a);},g=r=>{try{o.onError?.(r);}catch{}},f=O({onStateChange:r=>{i(r);}}),l=A(n,{onTick:r=>{S(r);},onComplete:()=>{f.complete();},onError:r=>{g(r),f.stop();}},{timeProvider:e,tickIntervalMs:o.tickIntervalMs});return {start:()=>{if(!f.canStart())return false;let r=l.start();return r&&f.start(),r},pause:()=>f.canPause()?(l.stop(),f.pause()):false,resume:()=>{if(!f.canResume())return false;let r=l.start();return r&&f.resume(),r},reset:r=>{if(l.stop(),typeof r=="number"){let _=R(r);s=_,l.setInitialValue(_);}else s=n,l.reset();return f.reset()||S(l.getTotalSeconds()),true},stop:()=>{l.stop();let r=f.stop();return r&&l.setSeconds(0),r},setSeconds:r=>{let w=R(r);l.setSeconds(w),S(l.getTotalSeconds());},getSnapshot:()=>a,subscribe:r=>{u.add(r);try{r(a);}catch{}return {unsubscribe:()=>{u.delete(r);}}},destroy:()=>{l.destroy(),f.destroy(),u.clear(),a=P(s,0,m.STOPPED);}}}function Q(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function T(t,o=2){return t.toString().padStart(o,"0")}function M(){let t=i=>x(C(Q(i)));return {formatTime:i=>{let S=t(i);return {minutes:T(S.totalMinutes),seconds:T(S.seconds)}},formatMinutes:i=>T(t(i).minutes),formatSeconds:i=>T(t(i).seconds),formatHours:i=>T(t(i).hours),formatDays:i=>T(t(i).days),formatWeeks:i=>T(t(i).weeks),formatYears:i=>T(t(i).years)}}var E=M();function Z(t){return E.formatTime(t)}function $(t){return E.formatMinutes(t)}function tt(t){return E.formatSeconds(t)}function et(t){return E.formatHours(t)}function nt(t){return E.formatDays(t)}function ot(t){return E.formatWeeks(t)}function rt(t){return E.formatYears(t)}function it(t,o={}){let{onSnapshot:n,onStateChange:e,onError:s}=o;if(n!==void 0&&typeof n!="function")throw new Error("onSnapshot must be a function");if(e!==void 0&&typeof e!="function")throw new Error("onStateChange must be a function");if(s!==void 0&&typeof s!="function")throw new Error("onError must be a function");let a=M(),u=F(t,{onError:s}),c=u.getSnapshot(),i=c.state,S=Number.NaN,g=d=>{if(s)try{s(d);}catch{}},f=d=>{if(n)try{n(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},l=d=>{if(e)try{e(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},N=u.subscribe(d=>{c=d,d.state!==i&&(i=d.state,l(d.state)),d.totalSeconds!==S&&(S=d.totalSeconds,f(d));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:d=>{u.reset(d);},stop:()=>{u.stop();},getSeconds:()=>a.formatSeconds(c.totalSeconds),getMinutes:()=>a.formatMinutes(c.totalSeconds),getHours:()=>a.formatHours(c.totalSeconds),getDays:()=>a.formatDays(c.totalSeconds),getWeeks:()=>a.formatWeeks(c.totalSeconds),getYears:()=>a.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{N.unsubscribe();}finally{u.destroy();}}}}export{it as Countdown,F as CountdownEngine,M as Formatter,m as TimerState,P as buildSnapshot,E as defaultFormatter,nt as formatDays,et as formatHours,$ as formatMinutes,tt as formatSeconds,Z as formatTime,ot as formatWeeks,rt as formatYears};
|
package/dist/testing-utils.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var u=e=>!Number.isFinite(e)||e<0?0:e>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e);function M(e={}){let{startMs:n=0,tickMs:t=1e3,highResolution:a=true}=e,r=u(n),s=u(t);return {now:()=>r,advance:(i=s)=>(r=u(r+u(i)),r),set:i=>(r=u(i),r),reset:()=>(r=u(n),r),getTime:()=>r,isHighResolution:a,type:"fake"}}function R(e){return {now:()=>e.now(),isHighResolution:e.isHighResolution,type:e.type}}function h(e){let n=Number.isFinite(e)?Math.max(0,Math.floor(e)):0,t=n,a=Math.floor(t/31536e3);t-=a*31536e3;let r=Math.floor(t/604800);t-=r*604800;let s=Math.floor(t/86400);t-=s*86400;let m=Math.floor(t/3600);t-=m*3600;let p=Math.floor(t/60);return t-=p*60,{years:a,weeks:r,days:s,hours:m,minutes:p,seconds:t,totalDays:Math.floor(n/86400),totalHours:Math.floor(n/3600),totalMinutes:Math.floor(n/60)}}function c(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?0:e>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e)}var o=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[o.IDLE]:{start:o.RUNNING},[o.RUNNING]:{pause:o.PAUSED,reset:o.IDLE,stop:o.STOPPED,complete:o.STOPPED},[o.PAUSED]:{resume:o.RUNNING,reset:o.IDLE,stop:o.STOPPED},[o.STOPPED]:{reset:o.IDLE}});function P(e={}){let n=c(e.initialSeconds??e.totalSeconds??0),t=c(e.totalSeconds??e.initialSeconds??0),a=e.state??(t>0?o.IDLE:o.STOPPED),r=h(t);return {initialSeconds:n,totalSeconds:t,parts:r,state:a,isRunning:a===o.RUNNING,isCompleted:t===0&&a===o.STOPPED}}function A(e={}){let{totalSeconds:n=0,step:t=1,count:a=1,initialSeconds:r}=e,s=c(n),m=c(t),p=c(a)||1,S=[];for(let f=0;f<p;f+=1){let i=Math.max(s-m*f,0);S.push(P({initialSeconds:r??s,totalSeconds:i,state:i===0?o.STOPPED:o.RUNNING}));}return S}var d=(e,n)=>n?`${e}: ${n}`:e;function U(e,n,t){if(e.state!==n)throw new Error(d(t??"Unexpected countdown state",`expected ${n} but received ${e.state}`))}function F(e,n){if(!e.isCompleted||e.totalSeconds!==0)throw new Error(d(n??"Countdown should be completed"))}function v(e,n,t=0,a){if(typeof n!="number"||!Number.isFinite(n))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(e.totalSeconds-Math.floor(n))>t)throw new Error(d(a??"Unexpected remaining seconds",`expected ${n}\xB1${t} but received ${e.totalSeconds}`))}exports.TimerState=o;exports.assertRemainingSeconds=v;exports.assertSnapshotCompleted=F;exports.assertSnapshotState=U;exports.buildSnapshot=P;exports.buildSnapshotSequence=A;exports.createFakeTimeProvider=M;exports.toTimeProvider=R;
|
package/dist/testing-utils.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.cjs';
|
|
2
2
|
|
|
3
3
|
interface FakeTimeProvider extends TimeProvider {
|
|
4
4
|
advance: (ms?: number) => number;
|
package/dist/testing-utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.js';
|
|
2
2
|
|
|
3
3
|
interface FakeTimeProvider extends TimeProvider {
|
|
4
4
|
advance: (ms?: number) => number;
|
package/dist/testing-utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=e=>!Number.isFinite(e)||e<0?0:e>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e);function M(e={}){let{startMs:n=0,tickMs:t=1e3,highResolution:a=true}=e,r=u(n),s=u(t);return {now:()=>r,advance:(i=s)=>(r=u(r+u(i)),r),set:i=>(r=u(i),r),reset:()=>(r=u(n),r),getTime:()=>r,isHighResolution:a,type:"fake"}}function R(e){return {now:()=>e.now(),isHighResolution:e.isHighResolution,type:e.type}}function h(e){let n=Number.isFinite(e)?Math.max(0,Math.floor(e)):0,t=n,a=Math.floor(t/31536e3);t-=a*31536e3;let r=Math.floor(t/604800);t-=r*604800;let s=Math.floor(t/86400);t-=s*86400;let m=Math.floor(t/3600);t-=m*3600;let p=Math.floor(t/60);return t-=p*60,{years:a,weeks:r,days:s,hours:m,minutes:p,seconds:t,totalDays:Math.floor(n/86400),totalHours:Math.floor(n/3600),totalMinutes:Math.floor(n/60)}}function c(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?0:e>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e)}var o=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[o.IDLE]:{start:o.RUNNING},[o.RUNNING]:{pause:o.PAUSED,reset:o.IDLE,stop:o.STOPPED,complete:o.STOPPED},[o.PAUSED]:{resume:o.RUNNING,reset:o.IDLE,stop:o.STOPPED},[o.STOPPED]:{reset:o.IDLE}});function P(e={}){let n=c(e.initialSeconds??e.totalSeconds??0),t=c(e.totalSeconds??e.initialSeconds??0),a=e.state??(t>0?o.IDLE:o.STOPPED),r=h(t);return {initialSeconds:n,totalSeconds:t,parts:r,state:a,isRunning:a===o.RUNNING,isCompleted:t===0&&a===o.STOPPED}}function A(e={}){let{totalSeconds:n=0,step:t=1,count:a=1,initialSeconds:r}=e,s=c(n),m=c(t),p=c(a)||1,S=[];for(let f=0;f<p;f+=1){let i=Math.max(s-m*f,0);S.push(P({initialSeconds:r??s,totalSeconds:i,state:i===0?o.STOPPED:o.RUNNING}));}return S}var d=(e,n)=>n?`${e}: ${n}`:e;function U(e,n,t){if(e.state!==n)throw new Error(d(t??"Unexpected countdown state",`expected ${n} but received ${e.state}`))}function F(e,n){if(!e.isCompleted||e.totalSeconds!==0)throw new Error(d(n??"Countdown should be completed"))}function v(e,n,t=0,a){if(typeof n!="number"||!Number.isFinite(n))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(e.totalSeconds-Math.floor(n))>t)throw new Error(d(a??"Unexpected remaining seconds",`expected ${n}\xB1${t} but received ${e.totalSeconds}`))}export{o as TimerState,v as assertRemainingSeconds,F as assertSnapshotCompleted,U as assertSnapshotState,P as buildSnapshot,A as buildSnapshotSequence,M as createFakeTimeProvider,R as toTimeProvider};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timekeeper-countdown/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Lightweight countdown engine that powers the Timekeeper packages. Pure TypeScript with no runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"countdown",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"test": "vitest run",
|
|
55
55
|
"test:ui": "vitest --ui",
|
|
56
56
|
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"test:mutation": "stryker run",
|
|
57
58
|
"typecheck": "tsc --noEmit",
|
|
58
59
|
"lint": "eslint .",
|
|
59
60
|
"lint:fix": "eslint . --fix",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"prepublishOnly": "npm run build && npm run test && npm run typecheck"
|
|
63
64
|
},
|
|
64
65
|
"engines": {
|
|
65
|
-
"node": ">=
|
|
66
|
+
"node": ">=22"
|
|
66
67
|
},
|
|
67
68
|
"publishConfig": {
|
|
68
69
|
"registry": "https://registry.npmjs.org",
|