@timekeeper-countdown/core 0.1.4 → 0.2.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
@@ -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
@@ -116,6 +121,40 @@ interface CountdownSnapshot {
116
121
 
117
122
  ---
118
123
 
124
+ ## State Transitions
125
+
126
+ The engine enforces a strict state machine. Invalid transitions are silently ignored and return `false`.
127
+
128
+ ```text
129
+ start()
130
+ IDLE ──────────────► RUNNING
131
+ ▲ │ │
132
+ │ │ │
133
+ │ reset() pause() stop() / complete()
134
+ │ │ │
135
+ │ ▼ │
136
+ │ PAUSED │
137
+ │ │ │
138
+ │ reset() │ │
139
+ │◄───────────────────┘ │
140
+ │ │
141
+ │ reset() ▼
142
+ │◄──────────────────── STOPPED
143
+ ```
144
+
145
+ | From | Action | To |
146
+ | --------- | ---------- | --------- |
147
+ | `IDLE` | `start()` | `RUNNING` |
148
+ | `RUNNING` | `pause()` | `PAUSED` |
149
+ | `RUNNING` | `reset()` | `IDLE` |
150
+ | `RUNNING` | `stop()` | `STOPPED` |
151
+ | `PAUSED` | `resume()` | `RUNNING` |
152
+ | `PAUSED` | `reset()` | `IDLE` |
153
+ | `PAUSED` | `stop()` | `STOPPED` |
154
+ | `STOPPED` | `reset()` | `IDLE` |
155
+
156
+ ---
157
+
119
158
  ## Formatting Helpers
120
159
 
121
160
  Use the helpers exported at `@timekeeper-countdown/core/format` to avoid reimplementing `padStart` logic.
@@ -145,28 +184,140 @@ import {
145
184
  createFakeTimeProvider,
146
185
  toTimeProvider,
147
186
  buildSnapshot,
187
+ buildSnapshotSequence,
148
188
  assertSnapshotState,
189
+ assertSnapshotCompleted,
190
+ assertRemainingSeconds,
191
+ TimerState,
149
192
  } from '@timekeeper-countdown/core/testing-utils';
193
+ ```
150
194
 
151
- const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
152
- const engine = CountdownEngine(5, {
153
- timeProvider: toTimeProvider(fake),
154
- tickIntervalMs: 5,
155
- });
195
+ ### `createFakeTimeProvider(options?)`
156
196
 
157
- engine.start();
158
- fake.advance(3000);
197
+ Provides a controllable clock for deterministic testing.
198
+
199
+ ```ts
200
+ interface FakeTimeOptions {
201
+ startMs?: number; // default: 0
202
+ tickMs?: number; // default: 1000 — default step for advance()
203
+ highResolution?: boolean; // default: true
204
+ }
205
+
206
+ interface FakeTimeProvider extends TimeProvider {
207
+ advance(ms?: number): number; // advances by ms (or tickMs if omitted), returns current time
208
+ set(ms: number): number; // sets clock to absolute value, returns current time
209
+ reset(): number; // resets to startMs, returns current time
210
+ getTime(): number; // returns current time without advancing
211
+ now(): number; // same as getTime (inherited from TimeProvider)
212
+ isHighResolution: boolean;
213
+ type: 'fake';
214
+ }
215
+
216
+ function createFakeTimeProvider(options?: FakeTimeOptions): FakeTimeProvider;
217
+ ```
218
+
219
+ - Negative or non-finite values are clamped to `0`.
220
+ - Values above `Number.MAX_SAFE_INTEGER` are clamped to `Number.MAX_SAFE_INTEGER`.
221
+ - `advance()` without a parameter uses `tickMs` as the default step.
222
+
223
+ ### `toTimeProvider(fake)`
224
+
225
+ ```ts
226
+ function toTimeProvider(fake: FakeTimeProvider): TimeProvider;
227
+ ```
228
+
229
+ Converts a `FakeTimeProvider` to a read-only `TimeProvider`, used to pass to `CountdownEngine` or `useCountdown`.
230
+
231
+ ### `buildSnapshot(options?)`
232
+
233
+ ```ts
234
+ interface SnapshotOptions {
235
+ initialSeconds?: number; // fallback: totalSeconds, then 0
236
+ totalSeconds?: number; // fallback: initialSeconds, then 0
237
+ state?: TimerState; // fallback: IDLE if totalSeconds > 0, else STOPPED
238
+ }
239
+
240
+ function buildSnapshot(options?: SnapshotOptions): CountdownSnapshot;
241
+ ```
159
242
 
160
- expect(engine.getSnapshot().totalSeconds).toBe(2);
243
+ ```ts
244
+ const snapshot = buildSnapshot({ totalSeconds: 90, state: TimerState.RUNNING });
245
+ // snapshot.parts.minutes === 1
246
+ // snapshot.parts.seconds === 30
247
+ // snapshot.isRunning === true
248
+ ```
249
+
250
+ ### `buildSnapshotSequence(options?)`
251
+
252
+ ```ts
253
+ interface SequenceOptions extends SnapshotOptions {
254
+ step?: number; // default: 1 — decrement per snapshot
255
+ count?: number; // default: 1 — number of snapshots
256
+ }
257
+
258
+ function buildSnapshotSequence(options?: SequenceOptions): CountdownSnapshot[];
259
+ ```
161
260
 
162
- const snapshot = buildSnapshot({ totalSeconds: 42 });
163
- assertSnapshotState(snapshot, 'RUNNING');
261
+ Generates `count` snapshots, decrementing `totalSeconds` by `step` each iteration. The last snapshot with `remaining === 0` gets `state: STOPPED`; all others get `state: RUNNING`.
262
+
263
+ ```ts
264
+ const sequence = buildSnapshotSequence({ totalSeconds: 4, step: 2, count: 3 });
265
+ // sequence[0].totalSeconds === 4 (RUNNING)
266
+ // sequence[1].totalSeconds === 2 (RUNNING)
267
+ // sequence[2].totalSeconds === 0 (STOPPED)
268
+ ```
269
+
270
+ ### `assertSnapshotState(snapshot, expected, message?)`
271
+
272
+ ```ts
273
+ function assertSnapshotState(
274
+ snapshot: CountdownSnapshot,
275
+ expected: TimerState,
276
+ message?: string // default: "Unexpected countdown state"
277
+ ): void;
164
278
  ```
165
279
 
166
- Utilities include:
280
+ ### `assertSnapshotCompleted(snapshot, message?)`
167
281
 
168
- - `createFakeTimeProvider` / `toTimeProvider` for manual clock control.
169
- - `buildSnapshot`, `assertSnapshotState` helpers for quick snapshot fabrication.
282
+ ```ts
283
+ function assertSnapshotCompleted(
284
+ snapshot: CountdownSnapshot,
285
+ message?: string // default: "Countdown should be completed"
286
+ ): void;
287
+ ```
288
+
289
+ Throws if `snapshot.isCompleted === false` OR `snapshot.totalSeconds !== 0`.
290
+
291
+ ### `assertRemainingSeconds(snapshot, expected, tolerance?, message?)`
292
+
293
+ ```ts
294
+ function assertRemainingSeconds(
295
+ snapshot: CountdownSnapshot,
296
+ expected: number,
297
+ tolerance?: number, // default: 0
298
+ message?: string // default: "Unexpected remaining seconds"
299
+ ): void;
300
+ ```
301
+
302
+ Throws if `Math.abs(snapshot.totalSeconds - Math.floor(expected)) > tolerance` or if `expected` is not a finite number.
303
+
304
+ ```ts
305
+ assertRemainingSeconds(snapshot, 5); // exact
306
+ assertRemainingSeconds(snapshot, 5, 0.5); // accepts 4.5–5.5
307
+ ```
308
+
309
+ ### `TimerState` re-export
310
+
311
+ `TimerState` is re-exported via `testing-utils`, avoiding a double import:
312
+
313
+ ```ts
314
+ // Instead of two separate imports:
315
+ import { TimerState } from '@timekeeper-countdown/core';
316
+ import { buildSnapshot } from '@timekeeper-countdown/core/testing-utils';
317
+
318
+ // You can import everything from one place:
319
+ import { buildSnapshot, TimerState } from '@timekeeper-countdown/core/testing-utils';
320
+ ```
170
321
 
171
322
  ---
172
323
 
@@ -56,6 +56,7 @@ interface CountdownEngineInstance {
56
56
  subscribe: (listener: (snapshot: CountdownSnapshot) => void) => CountdownSubscription;
57
57
  destroy: () => void;
58
58
  }
59
+ declare function buildSnapshot(initialSeconds: number, totalSeconds: number, state: TimerState): CountdownSnapshot;
59
60
  declare function CountdownEngine(initialSecondsInput: number, options?: CountdownEngineOptions): CountdownEngineInstance;
60
61
 
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 };
62
+ 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 };
@@ -56,6 +56,7 @@ interface CountdownEngineInstance {
56
56
  subscribe: (listener: (snapshot: CountdownSnapshot) => void) => CountdownSubscription;
57
57
  destroy: () => void;
58
58
  }
59
+ declare function buildSnapshot(initialSeconds: number, totalSeconds: number, state: TimerState): CountdownSnapshot;
59
60
  declare function CountdownEngine(initialSecondsInput: number, options?: CountdownEngineOptions): CountdownEngineInstance;
60
61
 
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 };
62
+ 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 f(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function i(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function o(t,a=2){return !Number.isFinite(t)||t<0?"0".repeat(a):Math.floor(t).toString().padStart(a,"0")}function s(t){return Math.floor(t/60)}function c(t){return s(t)%60}function d(t){return Math.floor(t/3600)%24}function g(t){return Math.floor(t/86400)%7}function p(t){return Math.floor(t/604800)%52}function S(t){return Math.floor(t/31536e3)}function m(){let t=r=>i(f(r));return {formatTime:r=>{let n=t(r);return {minutes:o(s(n)),seconds:o(n%60)}},formatMinutes:r=>{let n=t(r);return o(c(n))},formatSeconds:r=>{let n=t(r);return o(n%60)},formatHours:r=>{let n=t(r);return o(d(n))},formatDays:r=>{let n=t(r);return o(g(n))},formatWeeks:r=>{let n=t(r);return o(p(n))},formatYears:r=>{let n=t(r);return o(S(n))}}}var e=m();function l(t){return e.formatTime(t)}function E(t){return e.formatMinutes(t)}function T(t){return e.formatSeconds(t)}function x(t){return e.formatHours(t)}function F(t){return e.formatDays(t)}function b(t){return e.formatWeeks(t)}function _(t){return e.formatYears(t)}exports.Formatter=m;exports.defaultFormatter=e;exports.formatDays=F;exports.formatHours=x;exports.formatMinutes=E;exports.formatSeconds=T;exports.formatTime=l;exports.formatWeeks=b;exports.formatYears=_;
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-qnWxt-ed.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-qnWxt-ed.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 f(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function i(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function o(t,a=2){return !Number.isFinite(t)||t<0?"0".repeat(a):Math.floor(t).toString().padStart(a,"0")}function s(t){return Math.floor(t/60)}function c(t){return s(t)%60}function d(t){return Math.floor(t/3600)%24}function g(t){return Math.floor(t/86400)%7}function p(t){return Math.floor(t/604800)%52}function S(t){return Math.floor(t/31536e3)}function m(){let t=r=>i(f(r));return {formatTime:r=>{let n=t(r);return {minutes:o(s(n)),seconds:o(n%60)}},formatMinutes:r=>{let n=t(r);return o(c(n))},formatSeconds:r=>{let n=t(r);return o(n%60)},formatHours:r=>{let n=t(r);return o(d(n))},formatDays:r=>{let n=t(r);return o(g(n))},formatWeeks:r=>{let n=t(r);return o(p(n))},formatYears:r=>{let n=t(r);return o(S(n))}}}var e=m();function l(t){return e.formatTime(t)}function E(t){return e.formatMinutes(t)}function T(t){return e.formatSeconds(t)}function x(t){return e.formatHours(t)}function F(t){return e.formatDays(t)}function b(t){return e.formatWeeks(t)}function _(t){return e.formatYears(t)}export{m as Formatter,e as defaultFormatter,F as formatDays,x as formatHours,E as formatMinutes,T as formatSeconds,l as formatTime,b as formatWeeks,_ 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 Y(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function W(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function w(){let t,e=W();function s(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let n=performance.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0&&n<=Number.MAX_SAFE_INTEGER){t=Y();return}}throw new Error("performance.now() not available or invalid")}catch{t=e;}}return s(),{now:()=>{try{let n=t.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0&&n<=Number.MAX_SAFE_INTEGER)return n;throw new Error("Invalid time value returned")}catch{t!==e&&(t=e);try{let n=e.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0)return n}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}w();var L=10,V=100;function D(t,e,s={}){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 n=Math.floor(Math.max(0,t)),m=null,i=null,u=0,c=n,r=n,a=Math.max(L,Math.floor(s.tickIntervalMs??V)),b=w(),d=s.timeProvider??(()=>b.now()),p=()=>setInterval(()=>{try{if(i===null)return;let E=d()-i-u,x=Math.floor(E/1e3),o=Math.max(0,r-x);n=o,o!==c&&(c=o,e.onTick(o)),o===0&&(l(),e.onComplete());}catch(S){l(),e.onError(S instanceof Error?S:new Error(String(S)));}},a),P=()=>{if(m||n<=0)return false;try{if(i!==null&&n<r){let S=(r-n)*1e3;u=d()-i-S;}else i=d(),u=0,c=n;return m=p(),!0}catch(S){return e.onError(S instanceof Error?S:new Error(String(S))),false}},l=()=>{m&&(clearInterval(m),m=null);},g=()=>{l(),n=r,i=null,u=0,c=r;},v=S=>{if(typeof S!="number"||!Number.isFinite(S))return;let E=Math.floor(Math.max(0,Math.min(S,Number.MAX_SAFE_INTEGER)));n=E,i=null,u=0,c=E;};return {start:P,stop:l,reset:g,setSeconds:v,setInitialValue:S=>{if(typeof S!="number"||!Number.isFinite(S))return;let E=Math.floor(Math.max(0,Math.min(S,Number.MAX_SAFE_INTEGER)));r=E,v(E);},getTotalSeconds:()=>n,getInitialValue:()=>r,isRunning:()=>m!==null,destroy:()=>{l(),n=0,i=null,u=0;}}}var f=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),M={[f.IDLE]:{start:f.RUNNING},[f.RUNNING]:{pause:f.PAUSED,reset:f.IDLE,stop:f.STOPPED,complete:f.STOPPED},[f.PAUSED]:{resume:f.RUNNING,reset:f.IDLE,stop:f.STOPPED},[f.STOPPED]:{reset:f.IDLE}};function _(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=f.IDLE;function s(b){let d=M[e]?.[b];if(!d)return false;e=d;try{t?.onStateChange?.(d);}catch{}return true}function n(){return s("start")}function m(){return s("resume")}function i(){return s("pause")}function u(){return s("reset")}function c(){return s("stop")}function r(){return s("complete")}function a(){e===f.IDLE||e===f.STOPPED||s("stop");}return {start:n,resume:m,pause:i,reset:u,stop:c,complete:r,getCurrentState:()=>e,canStart:()=>!!M[e]?.start,canResume:()=>!!M[e]?.resume,canPause:()=>!!M[e]?.pause,isRunning:()=>e===f.RUNNING,destroy:a}}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=w();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)),s=Math.floor(e/31536e3),n=Math.floor(e/604800)%52,m=Math.floor(e/86400)%7,i=Math.floor(e/3600)%24,u=Math.floor(e/60)%60,c=e%60,r=Math.floor(e/86400),a=Math.floor(e/3600),b=Math.floor(e/60);return {years:s,weeks:n,days:m,hours:i,minutes:u,seconds:c,totalDays:r,totalHours:a,totalMinutes:b}}function y(t,e,s){let n=Q(e);return {initialSeconds:t,totalSeconds:e,parts:n,state:s,isRunning:s===f.RUNNING,isCompleted:e===0&&s===f.STOPPED}}function R(t,e={}){let s=O(t),n=J(e.timeProvider),m=s,i=y(s,s,f.IDLE),u=new Set,c=o=>{try{e.onSnapshot?.(o);}catch{}for(let N of u)try{N(o);}catch{}},r=o=>{i=y(m,i.totalSeconds,o);try{e.onStateChange?.(o,i);}catch{}c(i);},a=o=>{i=y(m,o,d.getCurrentState()),c(i);},b=o=>{try{e.onError?.(o);}catch{}},d=_({onStateChange:o=>{r(o);}}),p=D(s,{onTick:o=>{a(o);},onComplete:()=>{d.complete(),a(0);},onError:o=>{b(o),d.stop();}},{timeProvider:n,tickIntervalMs:e.tickIntervalMs});return {start:()=>{if(!d.canStart())return false;let o=p.start();return o&&(d.start(),a(p.getTotalSeconds())),o},pause:()=>{if(!d.canPause())return false;p.stop();let o=d.pause();return o&&a(p.getTotalSeconds()),o},resume:()=>{if(!d.canResume())return false;let o=p.start();return o&&(d.resume(),a(p.getTotalSeconds())),o},reset:o=>{if(p.stop(),typeof o=="number"){let N=O(o);m=N,p.setInitialValue(N);}else m=s,p.reset();return d.reset(),a(p.getTotalSeconds()),true},stop:()=>{p.stop();let o=d.stop();return o&&(p.setSeconds(0),a(0)),o},setSeconds:o=>{p.setSeconds(o),a(p.getTotalSeconds());},getSnapshot:()=>i,subscribe:o=>{u.add(o);try{o(i);}catch{}return {unsubscribe:()=>{u.delete(o);}}},destroy:()=>{p.destroy(),d.destroy(),u.clear(),i=y(m,0,f.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 h(t,e=2){return !Number.isFinite(t)||t<0?"0".repeat(e):Math.floor(t).toString().padStart(e,"0")}function k(t){return Math.floor(t/60)}function tt(t){return k(t)%60}function et(t){return Math.floor(t/3600)%24}function nt(t){return Math.floor(t/86400)%7}function ot(t){return Math.floor(t/604800)%52}function rt(t){return Math.floor(t/31536e3)}function I(){let t=r=>$(Z(r));return {formatTime:r=>{let a=t(r);return {minutes:h(k(a)),seconds:h(a%60)}},formatMinutes:r=>{let a=t(r);return h(tt(a))},formatSeconds:r=>{let a=t(r);return h(a%60)},formatHours:r=>{let a=t(r);return h(et(a))},formatDays:r=>{let a=t(r);return h(nt(a))},formatWeeks:r=>{let a=t(r);return h(ot(a))},formatYears:r=>{let a=t(r);return h(rt(a))}}}var T=I();function at(t){return T.formatTime(t)}function st(t){return T.formatMinutes(t)}function it(t){return T.formatSeconds(t)}function ut(t){return T.formatHours(t)}function ct(t){return T.formatDays(t)}function mt(t){return T.formatWeeks(t)}function ft(t){return T.formatYears(t)}function dt(t,e={}){let{onSnapshot:s,onStateChange:n,onError:m}=e;if(s!==void 0&&typeof s!="function")throw new Error("onSnapshot must be a function");if(n!==void 0&&typeof n!="function")throw new Error("onStateChange must be a function");if(m!==void 0&&typeof m!="function")throw new Error("onError must be a function");let i=I(),u=R(t,{onError:m}),c=u.getSnapshot(),r=c.state,a=Number.NaN,b=l=>{if(m)try{m(l);}catch{}},d=l=>{if(s)try{s(l);}catch(g){b(g instanceof Error?g:new Error(String(g)));}},p=l=>{if(n)try{n(l);}catch(g){b(g instanceof Error?g:new Error(String(g)));}},P=u.subscribe(l=>{c=l,l.state!==r&&(r=l.state,p(l.state)),l.totalSeconds!==a&&(a=l.totalSeconds,d(l));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:l=>{u.reset(l);},stop:()=>{u.stop();},getSeconds:()=>i.formatSeconds(c.totalSeconds),getMinutes:()=>i.formatMinutes(c.totalSeconds),getHours:()=>i.formatHours(c.totalSeconds),getDays:()=>i.formatDays(c.totalSeconds),getWeeks:()=>i.formatWeeks(c.totalSeconds),getYears:()=>i.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{P.unsubscribe();}finally{u.destroy();}}}}exports.Countdown=dt;exports.CountdownEngine=R;exports.Formatter=I;exports.TimerState=f;exports.buildSnapshot=y;exports.defaultFormatter=T;exports.formatDays=ct;exports.formatHours=ut;exports.formatMinutes=st;exports.formatSeconds=it;exports.formatTime=at;exports.formatWeeks=mt;exports.formatYears=ft;
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-qnWxt-ed.cjs';
2
+ export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-qnWxt-ed.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-qnWxt-ed.js';
2
+ export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-qnWxt-ed.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 Y(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function W(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function w(){let t,e=W();function s(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let n=performance.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0&&n<=Number.MAX_SAFE_INTEGER){t=Y();return}}throw new Error("performance.now() not available or invalid")}catch{t=e;}}return s(),{now:()=>{try{let n=t.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0&&n<=Number.MAX_SAFE_INTEGER)return n;throw new Error("Invalid time value returned")}catch{t!==e&&(t=e);try{let n=e.now();if(typeof n=="number"&&Number.isFinite(n)&&!isNaN(n)&&n>=0)return n}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}w();var L=10,V=100;function D(t,e,s={}){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 n=Math.floor(Math.max(0,t)),m=null,i=null,u=0,c=n,r=n,a=Math.max(L,Math.floor(s.tickIntervalMs??V)),b=w(),d=s.timeProvider??(()=>b.now()),p=()=>setInterval(()=>{try{if(i===null)return;let E=d()-i-u,x=Math.floor(E/1e3),o=Math.max(0,r-x);n=o,o!==c&&(c=o,e.onTick(o)),o===0&&(l(),e.onComplete());}catch(S){l(),e.onError(S instanceof Error?S:new Error(String(S)));}},a),P=()=>{if(m||n<=0)return false;try{if(i!==null&&n<r){let S=(r-n)*1e3;u=d()-i-S;}else i=d(),u=0,c=n;return m=p(),!0}catch(S){return e.onError(S instanceof Error?S:new Error(String(S))),false}},l=()=>{m&&(clearInterval(m),m=null);},g=()=>{l(),n=r,i=null,u=0,c=r;},v=S=>{if(typeof S!="number"||!Number.isFinite(S))return;let E=Math.floor(Math.max(0,Math.min(S,Number.MAX_SAFE_INTEGER)));n=E,i=null,u=0,c=E;};return {start:P,stop:l,reset:g,setSeconds:v,setInitialValue:S=>{if(typeof S!="number"||!Number.isFinite(S))return;let E=Math.floor(Math.max(0,Math.min(S,Number.MAX_SAFE_INTEGER)));r=E,v(E);},getTotalSeconds:()=>n,getInitialValue:()=>r,isRunning:()=>m!==null,destroy:()=>{l(),n=0,i=null,u=0;}}}var f=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),M={[f.IDLE]:{start:f.RUNNING},[f.RUNNING]:{pause:f.PAUSED,reset:f.IDLE,stop:f.STOPPED,complete:f.STOPPED},[f.PAUSED]:{resume:f.RUNNING,reset:f.IDLE,stop:f.STOPPED},[f.STOPPED]:{reset:f.IDLE}};function _(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=f.IDLE;function s(b){let d=M[e]?.[b];if(!d)return false;e=d;try{t?.onStateChange?.(d);}catch{}return true}function n(){return s("start")}function m(){return s("resume")}function i(){return s("pause")}function u(){return s("reset")}function c(){return s("stop")}function r(){return s("complete")}function a(){e===f.IDLE||e===f.STOPPED||s("stop");}return {start:n,resume:m,pause:i,reset:u,stop:c,complete:r,getCurrentState:()=>e,canStart:()=>!!M[e]?.start,canResume:()=>!!M[e]?.resume,canPause:()=>!!M[e]?.pause,isRunning:()=>e===f.RUNNING,destroy:a}}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=w();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)),s=Math.floor(e/31536e3),n=Math.floor(e/604800)%52,m=Math.floor(e/86400)%7,i=Math.floor(e/3600)%24,u=Math.floor(e/60)%60,c=e%60,r=Math.floor(e/86400),a=Math.floor(e/3600),b=Math.floor(e/60);return {years:s,weeks:n,days:m,hours:i,minutes:u,seconds:c,totalDays:r,totalHours:a,totalMinutes:b}}function y(t,e,s){let n=Q(e);return {initialSeconds:t,totalSeconds:e,parts:n,state:s,isRunning:s===f.RUNNING,isCompleted:e===0&&s===f.STOPPED}}function R(t,e={}){let s=O(t),n=J(e.timeProvider),m=s,i=y(s,s,f.IDLE),u=new Set,c=o=>{try{e.onSnapshot?.(o);}catch{}for(let N of u)try{N(o);}catch{}},r=o=>{i=y(m,i.totalSeconds,o);try{e.onStateChange?.(o,i);}catch{}c(i);},a=o=>{i=y(m,o,d.getCurrentState()),c(i);},b=o=>{try{e.onError?.(o);}catch{}},d=_({onStateChange:o=>{r(o);}}),p=D(s,{onTick:o=>{a(o);},onComplete:()=>{d.complete(),a(0);},onError:o=>{b(o),d.stop();}},{timeProvider:n,tickIntervalMs:e.tickIntervalMs});return {start:()=>{if(!d.canStart())return false;let o=p.start();return o&&(d.start(),a(p.getTotalSeconds())),o},pause:()=>{if(!d.canPause())return false;p.stop();let o=d.pause();return o&&a(p.getTotalSeconds()),o},resume:()=>{if(!d.canResume())return false;let o=p.start();return o&&(d.resume(),a(p.getTotalSeconds())),o},reset:o=>{if(p.stop(),typeof o=="number"){let N=O(o);m=N,p.setInitialValue(N);}else m=s,p.reset();return d.reset(),a(p.getTotalSeconds()),true},stop:()=>{p.stop();let o=d.stop();return o&&(p.setSeconds(0),a(0)),o},setSeconds:o=>{p.setSeconds(o),a(p.getTotalSeconds());},getSnapshot:()=>i,subscribe:o=>{u.add(o);try{o(i);}catch{}return {unsubscribe:()=>{u.delete(o);}}},destroy:()=>{p.destroy(),d.destroy(),u.clear(),i=y(m,0,f.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 h(t,e=2){return !Number.isFinite(t)||t<0?"0".repeat(e):Math.floor(t).toString().padStart(e,"0")}function k(t){return Math.floor(t/60)}function tt(t){return k(t)%60}function et(t){return Math.floor(t/3600)%24}function nt(t){return Math.floor(t/86400)%7}function ot(t){return Math.floor(t/604800)%52}function rt(t){return Math.floor(t/31536e3)}function I(){let t=r=>$(Z(r));return {formatTime:r=>{let a=t(r);return {minutes:h(k(a)),seconds:h(a%60)}},formatMinutes:r=>{let a=t(r);return h(tt(a))},formatSeconds:r=>{let a=t(r);return h(a%60)},formatHours:r=>{let a=t(r);return h(et(a))},formatDays:r=>{let a=t(r);return h(nt(a))},formatWeeks:r=>{let a=t(r);return h(ot(a))},formatYears:r=>{let a=t(r);return h(rt(a))}}}var T=I();function at(t){return T.formatTime(t)}function st(t){return T.formatMinutes(t)}function it(t){return T.formatSeconds(t)}function ut(t){return T.formatHours(t)}function ct(t){return T.formatDays(t)}function mt(t){return T.formatWeeks(t)}function ft(t){return T.formatYears(t)}function dt(t,e={}){let{onSnapshot:s,onStateChange:n,onError:m}=e;if(s!==void 0&&typeof s!="function")throw new Error("onSnapshot must be a function");if(n!==void 0&&typeof n!="function")throw new Error("onStateChange must be a function");if(m!==void 0&&typeof m!="function")throw new Error("onError must be a function");let i=I(),u=R(t,{onError:m}),c=u.getSnapshot(),r=c.state,a=Number.NaN,b=l=>{if(m)try{m(l);}catch{}},d=l=>{if(s)try{s(l);}catch(g){b(g instanceof Error?g:new Error(String(g)));}},p=l=>{if(n)try{n(l);}catch(g){b(g instanceof Error?g:new Error(String(g)));}},P=u.subscribe(l=>{c=l,l.state!==r&&(r=l.state,p(l.state)),l.totalSeconds!==a&&(a=l.totalSeconds,d(l));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:l=>{u.reset(l);},stop:()=>{u.stop();},getSeconds:()=>i.formatSeconds(c.totalSeconds),getMinutes:()=>i.formatMinutes(c.totalSeconds),getHours:()=>i.formatHours(c.totalSeconds),getDays:()=>i.formatDays(c.totalSeconds),getWeeks:()=>i.formatWeeks(c.totalSeconds),getYears:()=>i.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{P.unsubscribe();}finally{u.destroy();}}}}export{dt as Countdown,R as CountdownEngine,I as Formatter,f as TimerState,y as buildSnapshot,T as defaultFormatter,ct as formatDays,ut as formatHours,st as formatMinutes,it as formatSeconds,at as formatTime,mt as formatWeeks,ft as formatYears};
@@ -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-qnWxt-ed.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-qnWxt-ed.js';
2
2
 
3
3
  interface FakeTimeProvider extends TimeProvider {
4
4
  advance: (ms?: number) => number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekeeper-countdown/core",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight countdown engine that powers the Timekeeper packages. Pure TypeScript with no runtime dependencies.",
5
5
  "keywords": [
6
6
  "countdown",