@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 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 18+
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
- onUpdate: (minutes, seconds) => {
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(); // boolean (false when invalid transition)
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
- const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
152
- const engine = CountdownEngine(5, {
153
- timeProvider: toTimeProvider(fake),
154
- tickIntervalMs: 5,
155
- });
198
+ ### `createFakeTimeProvider(options?)`
156
199
 
157
- engine.start();
158
- fake.advance(3000);
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
- expect(engine.getSnapshot().totalSeconds).toBe(2);
294
+ ### `assertRemainingSeconds(snapshot, expected, tolerance?, message?)`
161
295
 
162
- const snapshot = buildSnapshot({ totalSeconds: 42 });
163
- assertSnapshotState(snapshot, 'RUNNING');
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
- Utilities include:
305
+ Throws if `Math.abs(snapshot.totalSeconds - Math.floor(expected)) > tolerance` or if `expected` is not a finite number.
167
306
 
168
- - `createFakeTimeProvider` / `toTimeProvider` for manual clock control.
169
- - `buildSnapshot`, `assertSnapshotState` helpers for quick snapshot fabrication.
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
- Issues and pull requests are welcome. Please review the [repository guidelines](https://github.com/eagle-head/timekeeper-countdown/blob/main/AGENTS.md) for more details and run:
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
- before submitting changes.
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 b, type CountdownEngineOptions as c, type CountdownParts as d, type TimeProvider as e };
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 b, type CountdownEngineOptions as c, type CountdownParts as d, type TimeProvider as e };
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 u(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}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 e(t,a=2){try{return !Number.isFinite(t)||t<0?"0".repeat(a):Math.floor(t).toString().padStart(a,"0")}catch{return "0".repeat(a)}}function s(t){return Math.floor(t/60)}function i(t){return s(t)%60}function S(t){return Math.floor(t/3600)%24}function p(t){return Math.floor(t/86400)%7}function E(t){return Math.floor(t/604800)%52}function T(t){return Math.floor(t/31536e3)}function f(){let t=r=>c(u(r));return {formatTime:r=>{let o=t(r);return {minutes:e(s(o)),seconds:e(o%60)}},formatMinutes:r=>{let o=t(r);return e(i(o))},formatSeconds:r=>{let o=t(r);return e(o%60)},formatHours:r=>{let o=t(r);return e(S(o))},formatDays:r=>{let o=t(r);return e(p(o))},formatWeeks:r=>{let o=t(r);return e(E(o))},formatYears:r=>{let o=t(r);return e(T(o))}}}var n=f(),F=t=>n.formatTime(t),_=t=>n.formatMinutes(t),d=t=>n.formatSeconds(t),b=t=>n.formatHours(t),M=t=>n.formatDays(t),g=t=>n.formatWeeks(t),R=t=>n.formatYears(t);exports.Formatter=f;exports.defaultFormatter=n;exports.formatDays=M;exports.formatHours=b;exports.formatMinutes=_;exports.formatSeconds=d;exports.formatTime=F;exports.formatWeeks=g;exports.formatYears=R;
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-tLXf4OVD.cjs';
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 const formatTime: (target: FormatTarget) => {
28
+ declare function formatTime(target: number): {
29
29
  minutes: string;
30
30
  seconds: string;
31
31
  };
32
- declare const formatMinutes: (target: FormatTarget) => string;
33
- declare const formatSeconds: (target: FormatTarget) => string;
34
- declare const formatHours: (target: FormatTarget) => string;
35
- declare const formatDays: (target: FormatTarget) => string;
36
- declare const formatWeeks: (target: FormatTarget) => string;
37
- declare const formatYears: (target: FormatTarget) => string;
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-tLXf4OVD.js';
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 const formatTime: (target: FormatTarget) => {
28
+ declare function formatTime(target: number): {
29
29
  minutes: string;
30
30
  seconds: string;
31
31
  };
32
- declare const formatMinutes: (target: FormatTarget) => string;
33
- declare const formatSeconds: (target: FormatTarget) => string;
34
- declare const formatHours: (target: FormatTarget) => string;
35
- declare const formatDays: (target: FormatTarget) => string;
36
- declare const formatWeeks: (target: FormatTarget) => string;
37
- declare const formatYears: (target: FormatTarget) => string;
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 u(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}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 e(t,a=2){try{return !Number.isFinite(t)||t<0?"0".repeat(a):Math.floor(t).toString().padStart(a,"0")}catch{return "0".repeat(a)}}function s(t){return Math.floor(t/60)}function i(t){return s(t)%60}function S(t){return Math.floor(t/3600)%24}function p(t){return Math.floor(t/86400)%7}function E(t){return Math.floor(t/604800)%52}function T(t){return Math.floor(t/31536e3)}function f(){let t=r=>c(u(r));return {formatTime:r=>{let o=t(r);return {minutes:e(s(o)),seconds:e(o%60)}},formatMinutes:r=>{let o=t(r);return e(i(o))},formatSeconds:r=>{let o=t(r);return e(o%60)},formatHours:r=>{let o=t(r);return e(S(o))},formatDays:r=>{let o=t(r);return e(p(o))},formatWeeks:r=>{let o=t(r);return e(E(o))},formatYears:r=>{let o=t(r);return e(T(o))}}}var n=f(),F=t=>n.formatTime(t),_=t=>n.formatMinutes(t),d=t=>n.formatSeconds(t),b=t=>n.formatHours(t),M=t=>n.formatDays(t),g=t=>n.formatWeeks(t),R=t=>n.formatYears(t);export{f as Formatter,n as defaultFormatter,M as formatDays,b as formatHours,_ as formatMinutes,d as formatSeconds,F as formatTime,g as formatWeeks,R as formatYears};
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 Y(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function G(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,e=G();function i(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let o=performance.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0&&o<=Number.MAX_SAFE_INTEGER){t=Y();return}}throw new Error("performance.now() not available or invalid")}catch{t=e;}}return i(),{now:()=>{try{let o=t.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0&&o<=Number.MAX_SAFE_INTEGER)return o;throw new Error("Invalid time value returned")}catch{t!==e&&(t=e);try{let o=e.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0)return o}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}y();var L=10,V=100;function D(t,e,i={}){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");if(!e||typeof e!="object")throw new Error("events must be an object");if(typeof e.onTick!="function")throw new Error("events.onTick must be a function");if(typeof e.onComplete!="function")throw new Error("events.onComplete must be a function");if(typeof e.onError!="function")throw new Error("events.onError must be a function");let o=Math.floor(Math.max(0,t)),c=null,r=null,u=0,S=o,a=o,s=Math.max(L,Math.floor(i.tickIntervalMs??V)),b=y(),d=i.timeProvider??(()=>b.now()),f=()=>setInterval(()=>{try{if(r===null)return;let h=d()-r-u,_=Math.floor(h/1e3),n=Math.max(0,a-_);o=n,n!==S&&(S=n,e.onTick(n)),n===0&&(E(),e.onComplete());}catch(l){E(),e.onError(l instanceof Error?l:new Error(String(l)));}},s),p=()=>{if(c||o<=0)return false;try{if(r!==null&&o<a){let l=(a-o)*1e3;u=d()-r-l;}else r=d(),u=0,S=o;return c=f(),!0}catch(l){return e.onError(l instanceof Error?l:new Error(String(l))),false}},E=()=>{c&&(clearInterval(c),c=null);},C=()=>{E(),o=a,r=null,u=0,S=a;},v=l=>{if(typeof l!="number"||!Number.isFinite(l))return;let h=Math.floor(Math.max(0,Math.min(l,Number.MAX_SAFE_INTEGER)));o=h,r=null,u=0,S=h;};return {start:p,stop:E,reset:C,setSeconds:v,setInitialValue:l=>{if(typeof l!="number"||!Number.isFinite(l))return;let h=Math.floor(Math.max(0,Math.min(l,Number.MAX_SAFE_INTEGER)));a=h,v(h);},getTotalSeconds:()=>o,getInitialValue:()=>a,isRunning:()=>c!==null,destroy:()=>{E(),o=0,r=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),P={[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 x(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 e=m.IDLE;function i(b){let d=P[e]?.[b];if(!d)return false;e=d;try{t?.onStateChange?.(d);}catch{}return true}function o(){return i("start")}function c(){return i("resume")}function r(){return i("pause")}function u(){return i("reset")}function S(){return i("stop")}function a(){return i("complete")}function s(){e===m.IDLE||e===m.STOPPED||i("stop");}return {start:o,resume:c,pause:r,reset:u,stop:S,complete:a,getCurrentState:()=>e,canStart:()=>!!P[e]?.start,canResume:()=>!!P[e]?.resume,canPause:()=>!!P[e]?.pause,isRunning:()=>e===m.RUNNING,destroy:s}}function O(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){if(!t){let e=y();return ()=>e.now()}if(typeof t=="function")return t;if(typeof t.now=="function"){let e=t;return ()=>e.now()}throw new Error("timeProvider must implement a now(): number method")}function Q(t){let e=Math.max(0,Math.floor(t)),i=Math.floor(e/31536e3),o=Math.floor(e/604800)%52,c=Math.floor(e/86400)%7,r=Math.floor(e/3600)%24,u=Math.floor(e/60)%60,S=e%60,a=Math.floor(e/86400),s=Math.floor(e/3600),b=Math.floor(e/60);return {years:i,weeks:o,days:c,hours:r,minutes:u,seconds:S,totalDays:a,totalHours:s,totalMinutes:b}}function I(t,e,i){let o=Q(e);return {initialSeconds:t,totalSeconds:e,parts:o,state:i,isRunning:i===m.RUNNING,isCompleted:e===0&&i===m.STOPPED}}function R(t,e={}){let i=O(t),o=J(e.timeProvider),c=i,r=I(i,i,m.IDLE),u=new Set,S=n=>{try{e.onSnapshot?.(n);}catch{}for(let N of u)try{N(n);}catch{}},a=n=>{r=I(c,r.totalSeconds,n);try{e.onStateChange?.(n,r);}catch{}S(r);},s=n=>{r=I(c,n,d.getCurrentState()),S(r);},b=n=>{try{e.onError?.(n);}catch{}},d=x({onStateChange:n=>{a(n);}}),f=D(i,{onTick:n=>{s(n);},onComplete:()=>{d.complete(),s(0);},onError:n=>{b(n),d.stop();}},{timeProvider:o,tickIntervalMs:e.tickIntervalMs});return {start:()=>{if(!d.canStart())return false;let n=f.start();return n&&(d.start(),s(f.getTotalSeconds())),n},pause:()=>{if(!d.canPause())return false;f.stop();let n=d.pause();return n&&s(f.getTotalSeconds()),n},resume:()=>{if(!d.canResume())return false;let n=f.start();return n&&(d.resume(),s(f.getTotalSeconds())),n},reset:n=>{if(f.stop(),typeof n=="number"){let N=O(n);c=N,f.setInitialValue(N);}else c=i,f.reset();return d.reset(),s(f.getTotalSeconds()),true},stop:()=>{f.stop();let n=d.stop();return n&&(f.setSeconds(0),s(0)),n},setSeconds:n=>{f.setSeconds(n),s(f.getTotalSeconds());},getSnapshot:()=>r,subscribe:n=>{u.add(n);try{n(r);}catch{}return {unsubscribe:()=>{u.delete(n);}}},destroy:()=>{f.destroy(),d.destroy(),u.clear(),r=I(c,0,m.STOPPED);}}}function Z(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function $(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function T(t,e=2){try{return !Number.isFinite(t)||t<0?"0".repeat(e):Math.floor(t).toString().padStart(e,"0")}catch{return "0".repeat(e)}}function H(t){return Math.floor(t/60)}function tt(t){return H(t)%60}function et(t){return Math.floor(t/3600)%24}function ot(t){return Math.floor(t/86400)%7}function nt(t){return Math.floor(t/604800)%52}function rt(t){return Math.floor(t/31536e3)}function M(){let t=a=>$(Z(a));return {formatTime:a=>{let s=t(a);return {minutes:T(H(s)),seconds:T(s%60)}},formatMinutes:a=>{let s=t(a);return T(tt(s))},formatSeconds:a=>{let s=t(a);return T(s%60)},formatHours:a=>{let s=t(a);return T(et(s))},formatDays:a=>{let s=t(a);return T(ot(s))},formatWeeks:a=>{let s=t(a);return T(nt(s))},formatYears:a=>{let s=t(a);return T(rt(s))}}}var g=M(),at=t=>g.formatTime(t),st=t=>g.formatMinutes(t),it=t=>g.formatSeconds(t),ut=t=>g.formatHours(t),ct=t=>g.formatDays(t),mt=t=>g.formatWeeks(t),ft=t=>g.formatYears(t);function dt(t,e={}){let{onUpdate:i,onStateChange:o}=e;if(i!==void 0&&typeof i!="function")throw new Error("onUpdate must be a function");if(o!==void 0&&typeof o!="function")throw new Error("onStateChange must be a function");let c=M(),r=R(t),u=r.getSnapshot(),S=u.state,a=Number.NaN,s=p=>{if(i)try{let{minutes:E,seconds:C}=c.formatTime(p.totalSeconds);i(E,C);}catch{}},b=p=>{if(o)try{o(p);}catch{}},d=r.subscribe(p=>{u=p,p.state!==S&&(S=p.state,b(p.state)),p.totalSeconds!==a&&(a=p.totalSeconds,s(p));}),f=p=>{try{p();}catch{}};return {start:()=>{f(()=>{r.start();});},pause:()=>{f(()=>{r.pause();});},resume:()=>{f(()=>{r.resume();});},reset:p=>{f(()=>{r.reset(p);});},stop:()=>{f(()=>{r.stop();});},getSeconds:()=>{try{return c.formatSeconds(u.totalSeconds)}catch{return "00"}},getMinutes:()=>{try{return c.formatMinutes(u.totalSeconds)}catch{return "00"}},getHours:()=>{try{return c.formatHours(u.totalSeconds)}catch{return "00"}},getDays:()=>{try{return c.formatDays(u.totalSeconds)}catch{return "00"}},getWeeks:()=>{try{return c.formatWeeks(u.totalSeconds)}catch{return "00"}},getYears:()=>{try{return c.formatYears(u.totalSeconds)}catch{return "00"}},getCurrentState:()=>{try{return u.state}catch{return m.IDLE}},getSnapshot:()=>u,destroy:()=>{f(()=>{d.unsubscribe(),r.destroy();});}}}exports.Countdown=dt;exports.CountdownEngine=R;exports.Formatter=M;exports.TimerState=m;exports.defaultFormatter=g;exports.formatDays=ct;exports.formatHours=ut;exports.formatMinutes=st;exports.formatSeconds=it;exports.formatTime=at;exports.formatWeeks=mt;exports.formatYears=ft;
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 { T as TimerState, C as CountdownSnapshot } from './countdown-engine-tLXf4OVD.cjs';
2
- export { a as CountdownEngine, b as CountdownEngineInstance, c as CountdownEngineOptions, d as CountdownParts } from './countdown-engine-tLXf4OVD.cjs';
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
- onUpdate?: (minutes: string, seconds: string) => void;
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 { T as TimerState, C as CountdownSnapshot } from './countdown-engine-tLXf4OVD.js';
2
- export { a as CountdownEngine, b as CountdownEngineInstance, c as CountdownEngineOptions, d as CountdownParts } from './countdown-engine-tLXf4OVD.js';
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
- onUpdate?: (minutes: string, seconds: string) => void;
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 Y(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function G(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,e=G();function i(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let o=performance.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0&&o<=Number.MAX_SAFE_INTEGER){t=Y();return}}throw new Error("performance.now() not available or invalid")}catch{t=e;}}return i(),{now:()=>{try{let o=t.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0&&o<=Number.MAX_SAFE_INTEGER)return o;throw new Error("Invalid time value returned")}catch{t!==e&&(t=e);try{let o=e.now();if(typeof o=="number"&&Number.isFinite(o)&&!isNaN(o)&&o>=0)return o}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}y();var L=10,V=100;function D(t,e,i={}){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");if(!e||typeof e!="object")throw new Error("events must be an object");if(typeof e.onTick!="function")throw new Error("events.onTick must be a function");if(typeof e.onComplete!="function")throw new Error("events.onComplete must be a function");if(typeof e.onError!="function")throw new Error("events.onError must be a function");let o=Math.floor(Math.max(0,t)),c=null,r=null,u=0,S=o,a=o,s=Math.max(L,Math.floor(i.tickIntervalMs??V)),b=y(),d=i.timeProvider??(()=>b.now()),f=()=>setInterval(()=>{try{if(r===null)return;let h=d()-r-u,_=Math.floor(h/1e3),n=Math.max(0,a-_);o=n,n!==S&&(S=n,e.onTick(n)),n===0&&(E(),e.onComplete());}catch(l){E(),e.onError(l instanceof Error?l:new Error(String(l)));}},s),p=()=>{if(c||o<=0)return false;try{if(r!==null&&o<a){let l=(a-o)*1e3;u=d()-r-l;}else r=d(),u=0,S=o;return c=f(),!0}catch(l){return e.onError(l instanceof Error?l:new Error(String(l))),false}},E=()=>{c&&(clearInterval(c),c=null);},C=()=>{E(),o=a,r=null,u=0,S=a;},v=l=>{if(typeof l!="number"||!Number.isFinite(l))return;let h=Math.floor(Math.max(0,Math.min(l,Number.MAX_SAFE_INTEGER)));o=h,r=null,u=0,S=h;};return {start:p,stop:E,reset:C,setSeconds:v,setInitialValue:l=>{if(typeof l!="number"||!Number.isFinite(l))return;let h=Math.floor(Math.max(0,Math.min(l,Number.MAX_SAFE_INTEGER)));a=h,v(h);},getTotalSeconds:()=>o,getInitialValue:()=>a,isRunning:()=>c!==null,destroy:()=>{E(),o=0,r=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),P={[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 x(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 e=m.IDLE;function i(b){let d=P[e]?.[b];if(!d)return false;e=d;try{t?.onStateChange?.(d);}catch{}return true}function o(){return i("start")}function c(){return i("resume")}function r(){return i("pause")}function u(){return i("reset")}function S(){return i("stop")}function a(){return i("complete")}function s(){e===m.IDLE||e===m.STOPPED||i("stop");}return {start:o,resume:c,pause:r,reset:u,stop:S,complete:a,getCurrentState:()=>e,canStart:()=>!!P[e]?.start,canResume:()=>!!P[e]?.resume,canPause:()=>!!P[e]?.pause,isRunning:()=>e===m.RUNNING,destroy:s}}function O(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){if(!t){let e=y();return ()=>e.now()}if(typeof t=="function")return t;if(typeof t.now=="function"){let e=t;return ()=>e.now()}throw new Error("timeProvider must implement a now(): number method")}function Q(t){let e=Math.max(0,Math.floor(t)),i=Math.floor(e/31536e3),o=Math.floor(e/604800)%52,c=Math.floor(e/86400)%7,r=Math.floor(e/3600)%24,u=Math.floor(e/60)%60,S=e%60,a=Math.floor(e/86400),s=Math.floor(e/3600),b=Math.floor(e/60);return {years:i,weeks:o,days:c,hours:r,minutes:u,seconds:S,totalDays:a,totalHours:s,totalMinutes:b}}function I(t,e,i){let o=Q(e);return {initialSeconds:t,totalSeconds:e,parts:o,state:i,isRunning:i===m.RUNNING,isCompleted:e===0&&i===m.STOPPED}}function R(t,e={}){let i=O(t),o=J(e.timeProvider),c=i,r=I(i,i,m.IDLE),u=new Set,S=n=>{try{e.onSnapshot?.(n);}catch{}for(let N of u)try{N(n);}catch{}},a=n=>{r=I(c,r.totalSeconds,n);try{e.onStateChange?.(n,r);}catch{}S(r);},s=n=>{r=I(c,n,d.getCurrentState()),S(r);},b=n=>{try{e.onError?.(n);}catch{}},d=x({onStateChange:n=>{a(n);}}),f=D(i,{onTick:n=>{s(n);},onComplete:()=>{d.complete(),s(0);},onError:n=>{b(n),d.stop();}},{timeProvider:o,tickIntervalMs:e.tickIntervalMs});return {start:()=>{if(!d.canStart())return false;let n=f.start();return n&&(d.start(),s(f.getTotalSeconds())),n},pause:()=>{if(!d.canPause())return false;f.stop();let n=d.pause();return n&&s(f.getTotalSeconds()),n},resume:()=>{if(!d.canResume())return false;let n=f.start();return n&&(d.resume(),s(f.getTotalSeconds())),n},reset:n=>{if(f.stop(),typeof n=="number"){let N=O(n);c=N,f.setInitialValue(N);}else c=i,f.reset();return d.reset(),s(f.getTotalSeconds()),true},stop:()=>{f.stop();let n=d.stop();return n&&(f.setSeconds(0),s(0)),n},setSeconds:n=>{f.setSeconds(n),s(f.getTotalSeconds());},getSnapshot:()=>r,subscribe:n=>{u.add(n);try{n(r);}catch{}return {unsubscribe:()=>{u.delete(n);}}},destroy:()=>{f.destroy(),d.destroy(),u.clear(),r=I(c,0,m.STOPPED);}}}function Z(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function $(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function T(t,e=2){try{return !Number.isFinite(t)||t<0?"0".repeat(e):Math.floor(t).toString().padStart(e,"0")}catch{return "0".repeat(e)}}function H(t){return Math.floor(t/60)}function tt(t){return H(t)%60}function et(t){return Math.floor(t/3600)%24}function ot(t){return Math.floor(t/86400)%7}function nt(t){return Math.floor(t/604800)%52}function rt(t){return Math.floor(t/31536e3)}function M(){let t=a=>$(Z(a));return {formatTime:a=>{let s=t(a);return {minutes:T(H(s)),seconds:T(s%60)}},formatMinutes:a=>{let s=t(a);return T(tt(s))},formatSeconds:a=>{let s=t(a);return T(s%60)},formatHours:a=>{let s=t(a);return T(et(s))},formatDays:a=>{let s=t(a);return T(ot(s))},formatWeeks:a=>{let s=t(a);return T(nt(s))},formatYears:a=>{let s=t(a);return T(rt(s))}}}var g=M(),at=t=>g.formatTime(t),st=t=>g.formatMinutes(t),it=t=>g.formatSeconds(t),ut=t=>g.formatHours(t),ct=t=>g.formatDays(t),mt=t=>g.formatWeeks(t),ft=t=>g.formatYears(t);function dt(t,e={}){let{onUpdate:i,onStateChange:o}=e;if(i!==void 0&&typeof i!="function")throw new Error("onUpdate must be a function");if(o!==void 0&&typeof o!="function")throw new Error("onStateChange must be a function");let c=M(),r=R(t),u=r.getSnapshot(),S=u.state,a=Number.NaN,s=p=>{if(i)try{let{minutes:E,seconds:C}=c.formatTime(p.totalSeconds);i(E,C);}catch{}},b=p=>{if(o)try{o(p);}catch{}},d=r.subscribe(p=>{u=p,p.state!==S&&(S=p.state,b(p.state)),p.totalSeconds!==a&&(a=p.totalSeconds,s(p));}),f=p=>{try{p();}catch{}};return {start:()=>{f(()=>{r.start();});},pause:()=>{f(()=>{r.pause();});},resume:()=>{f(()=>{r.resume();});},reset:p=>{f(()=>{r.reset(p);});},stop:()=>{f(()=>{r.stop();});},getSeconds:()=>{try{return c.formatSeconds(u.totalSeconds)}catch{return "00"}},getMinutes:()=>{try{return c.formatMinutes(u.totalSeconds)}catch{return "00"}},getHours:()=>{try{return c.formatHours(u.totalSeconds)}catch{return "00"}},getDays:()=>{try{return c.formatDays(u.totalSeconds)}catch{return "00"}},getWeeks:()=>{try{return c.formatWeeks(u.totalSeconds)}catch{return "00"}},getYears:()=>{try{return c.formatYears(u.totalSeconds)}catch{return "00"}},getCurrentState:()=>{try{return u.state}catch{return m.IDLE}},getSnapshot:()=>u,destroy:()=>{f(()=>{d.unsubscribe(),r.destroy();});}}}export{dt as Countdown,R as CountdownEngine,M as Formatter,m as TimerState,g as defaultFormatter,ct as formatDays,ut as formatHours,st as formatMinutes,it as formatSeconds,at as formatTime,mt as formatWeeks,ft as formatYears};
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};
@@ -1 +1 @@
1
- 'use strict';var c=t=>!Number.isFinite(t)||t<0?0:t>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t);function h(t={}){let{startMs:e=0,tickMs:r=1e3,highResolution:a=true}=t,o=c(e),i=c(r);return {now:()=>o,advance:(s=i)=>(o=c(o+c(s)),o),set:s=>(o=c(s),o),reset:()=>(o=c(e),o),getTime:()=>o,isHighResolution:a,type:"fake"}}function T(t){return {now:()=>t.now(),isHighResolution:t.isHighResolution,type:t.type}}var n=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[n.IDLE]:{start:n.RUNNING},[n.RUNNING]:{pause:n.PAUSED,reset:n.IDLE,stop:n.STOPPED,complete:n.STOPPED},[n.PAUSED]:{resume:n.RUNNING,reset:n.IDLE,stop:n.STOPPED},[n.STOPPED]:{reset:n.IDLE}});var m=t=>typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t),l=t=>{let e=m(t),r=Math.floor(e/31536e3),a=Math.floor(e/604800)%52,o=Math.floor(e/86400)%7,i=Math.floor(e/3600)%24,S=Math.floor(e/60)%60,d=e%60,p=Math.floor(e/86400),u=Math.floor(e/3600),s=Math.floor(e/60);return {years:r,weeks:a,days:o,hours:i,minutes:S,seconds:d,totalDays:p,totalHours:u,totalMinutes:s}};function b(t={}){let e=m(t.initialSeconds??t.totalSeconds??0),r=m(t.totalSeconds??t.initialSeconds??0),a=t.state??(r>0?n.IDLE:n.STOPPED),o=l(r);return {initialSeconds:e,totalSeconds:r,parts:o,state:a,isRunning:a===n.RUNNING,isCompleted:r===0&&a===n.STOPPED}}function w(t={}){let{totalSeconds:e=0,step:r=1,count:a=1,initialSeconds:o}=t,i=m(e),S=m(r),d=m(a)||1,p=[];for(let u=0;u<d;u+=1){let s=Math.max(i-S*u,0);p.push(b({initialSeconds:o??i,totalSeconds:s,state:s===0?n.STOPPED:n.RUNNING}));}return p}var f=(t,e)=>e?`${t}: ${e}`:t;function x(t,e,r){if(t.state!==e)throw new Error(f(r??"Unexpected countdown state",`expected ${e} but received ${t.state}`))}function I(t,e){if(!t.isCompleted||t.totalSeconds!==0)throw new Error(f(e??"Countdown should be completed"))}function y(t,e,r=0,a){if(typeof e!="number"||!Number.isFinite(e))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(t.totalSeconds-Math.floor(e))>r)throw new Error(f(a??"Unexpected remaining seconds",`expected ${e}\xB1${r} but received ${t.totalSeconds}`))}exports.TimerState=n;exports.assertRemainingSeconds=y;exports.assertSnapshotCompleted=I;exports.assertSnapshotState=x;exports.buildSnapshot=b;exports.buildSnapshotSequence=w;exports.createFakeTimeProvider=h;exports.toTimeProvider=T;
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;
@@ -1,4 +1,4 @@
1
- import { e as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-tLXf4OVD.cjs';
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;
@@ -1,4 +1,4 @@
1
- import { e as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-tLXf4OVD.js';
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;
@@ -1 +1 @@
1
- var c=t=>!Number.isFinite(t)||t<0?0:t>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t);function h(t={}){let{startMs:e=0,tickMs:r=1e3,highResolution:a=true}=t,o=c(e),i=c(r);return {now:()=>o,advance:(s=i)=>(o=c(o+c(s)),o),set:s=>(o=c(s),o),reset:()=>(o=c(e),o),getTime:()=>o,isHighResolution:a,type:"fake"}}function T(t){return {now:()=>t.now(),isHighResolution:t.isHighResolution,type:t.type}}var n=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[n.IDLE]:{start:n.RUNNING},[n.RUNNING]:{pause:n.PAUSED,reset:n.IDLE,stop:n.STOPPED,complete:n.STOPPED},[n.PAUSED]:{resume:n.RUNNING,reset:n.IDLE,stop:n.STOPPED},[n.STOPPED]:{reset:n.IDLE}});var m=t=>typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t),l=t=>{let e=m(t),r=Math.floor(e/31536e3),a=Math.floor(e/604800)%52,o=Math.floor(e/86400)%7,i=Math.floor(e/3600)%24,S=Math.floor(e/60)%60,d=e%60,p=Math.floor(e/86400),u=Math.floor(e/3600),s=Math.floor(e/60);return {years:r,weeks:a,days:o,hours:i,minutes:S,seconds:d,totalDays:p,totalHours:u,totalMinutes:s}};function b(t={}){let e=m(t.initialSeconds??t.totalSeconds??0),r=m(t.totalSeconds??t.initialSeconds??0),a=t.state??(r>0?n.IDLE:n.STOPPED),o=l(r);return {initialSeconds:e,totalSeconds:r,parts:o,state:a,isRunning:a===n.RUNNING,isCompleted:r===0&&a===n.STOPPED}}function w(t={}){let{totalSeconds:e=0,step:r=1,count:a=1,initialSeconds:o}=t,i=m(e),S=m(r),d=m(a)||1,p=[];for(let u=0;u<d;u+=1){let s=Math.max(i-S*u,0);p.push(b({initialSeconds:o??i,totalSeconds:s,state:s===0?n.STOPPED:n.RUNNING}));}return p}var f=(t,e)=>e?`${t}: ${e}`:t;function x(t,e,r){if(t.state!==e)throw new Error(f(r??"Unexpected countdown state",`expected ${e} but received ${t.state}`))}function I(t,e){if(!t.isCompleted||t.totalSeconds!==0)throw new Error(f(e??"Countdown should be completed"))}function y(t,e,r=0,a){if(typeof e!="number"||!Number.isFinite(e))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(t.totalSeconds-Math.floor(e))>r)throw new Error(f(a??"Unexpected remaining seconds",`expected ${e}\xB1${r} but received ${t.totalSeconds}`))}export{n as TimerState,y as assertRemainingSeconds,I as assertSnapshotCompleted,x as assertSnapshotState,b as buildSnapshot,w as buildSnapshotSequence,h as createFakeTimeProvider,T as toTimeProvider};
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.1.4",
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": ">=18"
66
+ "node": ">=22"
66
67
  },
67
68
  "publishConfig": {
68
69
  "registry": "https://registry.npmjs.org",