@timekeeper-countdown/core 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -9
- package/dist/{countdown-engine-qnWxt-ed.d.cts → countdown-engine-DEKF_8iT.d.cts} +28 -0
- package/dist/{countdown-engine-qnWxt-ed.d.ts → countdown-engine-DEKF_8iT.d.ts} +28 -0
- package/dist/format.cjs +1 -1
- package/dist/format.d.cts +1 -1
- package/dist/format.d.ts +1 -1
- package/dist/format.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/testing-utils.cjs +1 -1
- package/dist/testing-utils.d.cts +1 -1
- package/dist/testing-utils.d.ts +1 -1
- package/dist/testing-utils.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ countdown.start(); // Begin the countdown
|
|
|
55
55
|
`Countdown` wraps the lower-level engine and returns convenient methods:
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
|
-
countdown.start(); //
|
|
58
|
+
countdown.start(); // void (the low-level CountdownEngine returns boolean)
|
|
59
59
|
countdown.pause();
|
|
60
60
|
countdown.resume();
|
|
61
61
|
countdown.reset(nextInitialSeconds?);
|
|
@@ -93,9 +93,12 @@ engine.start();
|
|
|
93
93
|
`CountdownEngine` exposes fine-grained control:
|
|
94
94
|
|
|
95
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`).
|
|
96
97
|
- `getSnapshot()` returns the latest snapshot.
|
|
97
98
|
- `subscribe(listener)` emits the current snapshot immediately and on every tick.
|
|
98
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
|
+
|
|
99
102
|
Snapshot structure:
|
|
100
103
|
|
|
101
104
|
```ts
|
|
@@ -339,6 +342,10 @@ const provider = {
|
|
|
339
342
|
CountdownEngine(60, { timeProvider: provider });
|
|
340
343
|
```
|
|
341
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
|
+
|
|
342
349
|
---
|
|
343
350
|
|
|
344
351
|
## TypeScript Support
|
|
@@ -366,15 +373,9 @@ import type {
|
|
|
366
373
|
|
|
367
374
|
## Contributing
|
|
368
375
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
```bash
|
|
372
|
-
npm run lint --workspaces
|
|
373
|
-
npm run test --workspaces
|
|
374
|
-
npm run typecheck --workspaces
|
|
375
|
-
```
|
|
376
|
+
We welcome contributions! Please read our [Contributing Guide](https://github.com/eagle-head/timekeeper-countdown/blob/main/CONTRIBUTING.md) to get started.
|
|
376
377
|
|
|
377
|
-
|
|
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).
|
|
378
379
|
|
|
379
380
|
---
|
|
380
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;
|
|
@@ -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;
|
package/dist/format.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function l(t){let a=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,o=a,s=Math.floor(o/31536e3);o-=s*31536e3;let m=Math.floor(o/604800);o-=m*604800;let u=Math.floor(o/86400);o-=u*86400;let f=Math.floor(o/3600);o-=f*3600;let i=Math.floor(o/60);return o-=i*60,{years:s,weeks:m,days:u,hours:f,minutes:i,seconds:o,totalDays:Math.floor(a/86400),totalHours:Math.floor(a/3600),totalMinutes:Math.floor(a/60)}}function S(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function F(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function n(t,a=2){return t.toString().padStart(a,"0")}function x(){let t=r=>l(S(F(r)));return {formatTime:r=>{let c=t(r);return {minutes:n(c.totalMinutes),seconds:n(c.seconds)}},formatMinutes:r=>n(t(r).minutes),formatSeconds:r=>n(t(r).seconds),formatHours:r=>n(t(r).hours),formatDays:r=>n(t(r).days),formatWeeks:r=>n(t(r).weeks),formatYears:r=>n(t(r).years)}}var e=x();function M(t){return e.formatTime(t)}function h(t){return e.formatMinutes(t)}function y(t){return e.formatSeconds(t)}function E(t){return e.formatHours(t)}function k(t){return e.formatDays(t)}function C(t){return e.formatWeeks(t)}function w(t){return e.formatYears(t)}exports.Formatter=x;exports.defaultFormatter=e;exports.formatDays=k;exports.formatHours=E;exports.formatMinutes=h;exports.formatSeconds=y;exports.formatTime=M;exports.formatWeeks=C;exports.formatYears=w;
|
package/dist/format.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.cjs';
|
|
2
2
|
|
|
3
3
|
type FormatTarget = number | Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined;
|
|
4
4
|
declare function Formatter(): {
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.js';
|
|
2
2
|
|
|
3
3
|
type FormatTarget = number | Pick<CountdownSnapshot, 'totalSeconds'> | null | undefined;
|
|
4
4
|
declare function Formatter(): {
|
package/dist/format.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function l(t){let a=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,o=a,s=Math.floor(o/31536e3);o-=s*31536e3;let m=Math.floor(o/604800);o-=m*604800;let u=Math.floor(o/86400);o-=u*86400;let f=Math.floor(o/3600);o-=f*3600;let i=Math.floor(o/60);return o-=i*60,{years:s,weeks:m,days:u,hours:f,minutes:i,seconds:o,totalDays:Math.floor(a/86400),totalHours:Math.floor(a/3600),totalMinutes:Math.floor(a/60)}}function S(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function F(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function n(t,a=2){return t.toString().padStart(a,"0")}function x(){let t=r=>l(S(F(r)));return {formatTime:r=>{let c=t(r);return {minutes:n(c.totalMinutes),seconds:n(c.seconds)}},formatMinutes:r=>n(t(r).minutes),formatSeconds:r=>n(t(r).seconds),formatHours:r=>n(t(r).hours),formatDays:r=>n(t(r).days),formatWeeks:r=>n(t(r).weeks),formatYears:r=>n(t(r).years)}}var e=x();function M(t){return e.formatTime(t)}function h(t){return e.formatMinutes(t)}function y(t){return e.formatSeconds(t)}function E(t){return e.formatHours(t)}function k(t){return e.formatDays(t)}function C(t){return e.formatWeeks(t)}function w(t){return e.formatYears(t)}export{x as Formatter,e as defaultFormatter,k as formatDays,E as formatHours,h as formatMinutes,y as formatSeconds,M as formatTime,C as formatWeeks,w as formatYears};
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function W(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function X(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,o=X();function n(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let e=performance.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER){t=W();return}}throw new Error("performance.now() not available or invalid")}catch{t=o;}}return n(),{now:()=>{try{let e=t.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER)return e;throw new Error("Invalid time value returned")}catch{t!==o&&(t=o);try{let e=o.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0)return e}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}function k(t,o=0){let n=typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0;return ()=>{let e=t();return typeof e!="number"||!Number.isFinite(e)||e<n||(n=e),n}}y();var z=10,j=100;function B(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?j:Math.max(z,Math.floor(t))}function A(t,o,n={}){if(typeof t!="number"||!Number.isFinite(t))throw new Error("initialSeconds must be a finite number");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");let e=Math.floor(Math.max(0,t)),s=null,a=null,u=0,c=e,i=e,S=B(n.tickIntervalMs),g=y(),f=n.timeProvider??(()=>g.now()),l=()=>setInterval(()=>{try{if(a===null)return;let h=f()-a-u,D=Math.floor(h/1e3),r=Number.isFinite(D)?Math.min(i,Math.max(0,i-D)):i;e=r,r!==c&&(c=r,o.onTick(r)),r===0&&(d(),o.onComplete());}catch(p){d(),o.onError(p instanceof Error?p:new Error(String(p)));}},S),N=()=>{if(s||e<=0)return false;try{if(a!==null&&e<i){let p=(i-e)*1e3;u=f()-a-p;}else a=f(),u=0,c=e;return s=l(),!0}catch(p){return o.onError(p instanceof Error?p:new Error(String(p))),false}},d=()=>{s&&(clearInterval(s),s=null);},b=()=>{d(),e=i,a=null,u=0,c=i;},v=p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));e=h,a=null,u=0,c=h;};return {start:N,stop:d,reset:b,setSeconds:v,setInitialValue:p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));i=h,v(h);},getTotalSeconds:()=>e,getInitialValue:()=>i,isRunning:()=>s!==null,destroy:()=>{d(),e=0,a=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),I={[m.IDLE]:{start:m.RUNNING},[m.RUNNING]:{pause:m.PAUSED,reset:m.IDLE,stop:m.STOPPED,complete:m.STOPPED},[m.PAUSED]:{resume:m.RUNNING,reset:m.IDLE,stop:m.STOPPED},[m.STOPPED]:{reset:m.IDLE}};function O(t){if(t!==void 0){if(typeof t!="object"||t===null)throw new Error("events must be an object");if(t.onStateChange!==void 0&&typeof t.onStateChange!="function")throw new Error("events.onStateChange must be a function")}let o=m.IDLE;function n(g){let f=I[o]?.[g];if(!f)return false;o=f;try{t?.onStateChange?.(f);}catch{}return true}function e(){return n("start")}function s(){return n("resume")}function a(){return n("pause")}function u(){return n("reset")}function c(){return n("stop")}function i(){return n("complete")}function S(){o===m.IDLE||o===m.STOPPED||n("stop");}return {start:e,resume:s,pause:a,reset:u,stop:c,complete:i,getCurrentState:()=>o,canStart:()=>!!I[o]?.start,canResume:()=>!!I[o]?.resume,canPause:()=>!!I[o]?.pause,isRunning:()=>o===m.RUNNING,destroy:S}}function x(t){let o=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,n=o,e=Math.floor(n/31536e3);n-=e*31536e3;let s=Math.floor(n/604800);n-=s*604800;let a=Math.floor(n/86400);n-=a*86400;let u=Math.floor(n/3600);n-=u*3600;let c=Math.floor(n/60);return n-=c*60,{years:e,weeks:s,days:a,hours:u,minutes:c,seconds:n,totalDays:Math.floor(o/86400),totalHours:Math.floor(o/3600),totalMinutes:Math.floor(o/60)}}function C(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function R(t){if(typeof t!="number"||!Number.isFinite(t)||!Number.isInteger(t))throw new Error("initialSeconds must be a finite, non-negative integer");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");return t}function J(t){let o;if(t)if(typeof t=="function")o=t;else if(typeof t.now=="function"){let n=t;o=()=>n.now();}else throw new Error("timeProvider must implement a now(): number method");else {let n=y();o=()=>n.now();}return k(o)}function P(t,o,n){let e=C(t),s=C(o),a=x(s);return {initialSeconds:e,totalSeconds:s,parts:a,state:n,isRunning:n===m.RUNNING,isCompleted:s===0&&n===m.STOPPED}}function F(t,o={}){let n=R(t),e=J(o.timeProvider),s=n,a=P(n,n,m.IDLE),u=new Set,c=r=>{try{o.onSnapshot?.(r);}catch{}for(let w of u)try{w(r);}catch{}},i=r=>{let w=r===m.STOPPED?0:l.getTotalSeconds();a=P(s,w,r);try{o.onStateChange?.(r,a);}catch{}c(a);},S=r=>{a=P(s,r,f.getCurrentState()),c(a);},g=r=>{try{o.onError?.(r);}catch{}},f=O({onStateChange:r=>{i(r);}}),l=A(n,{onTick:r=>{S(r);},onComplete:()=>{f.complete();},onError:r=>{g(r),f.stop();}},{timeProvider:e,tickIntervalMs:o.tickIntervalMs});return {start:()=>{if(!f.canStart())return false;let r=l.start();return r&&f.start(),r},pause:()=>f.canPause()?(l.stop(),f.pause()):false,resume:()=>{if(!f.canResume())return false;let r=l.start();return r&&f.resume(),r},reset:r=>{if(l.stop(),typeof r=="number"){let _=R(r);s=_,l.setInitialValue(_);}else s=n,l.reset();return f.reset()||S(l.getTotalSeconds()),true},stop:()=>{l.stop();let r=f.stop();return r&&l.setSeconds(0),r},setSeconds:r=>{let w=R(r);l.setSeconds(w),S(l.getTotalSeconds());},getSnapshot:()=>a,subscribe:r=>{u.add(r);try{r(a);}catch{}return {unsubscribe:()=>{u.delete(r);}}},destroy:()=>{l.destroy(),f.destroy(),u.clear(),a=P(s,0,m.STOPPED);}}}function Q(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function T(t,o=2){return t.toString().padStart(o,"0")}function M(){let t=i=>x(C(Q(i)));return {formatTime:i=>{let S=t(i);return {minutes:T(S.totalMinutes),seconds:T(S.seconds)}},formatMinutes:i=>T(t(i).minutes),formatSeconds:i=>T(t(i).seconds),formatHours:i=>T(t(i).hours),formatDays:i=>T(t(i).days),formatWeeks:i=>T(t(i).weeks),formatYears:i=>T(t(i).years)}}var E=M();function Z(t){return E.formatTime(t)}function $(t){return E.formatMinutes(t)}function tt(t){return E.formatSeconds(t)}function et(t){return E.formatHours(t)}function nt(t){return E.formatDays(t)}function ot(t){return E.formatWeeks(t)}function rt(t){return E.formatYears(t)}function it(t,o={}){let{onSnapshot:n,onStateChange:e,onError:s}=o;if(n!==void 0&&typeof n!="function")throw new Error("onSnapshot must be a function");if(e!==void 0&&typeof e!="function")throw new Error("onStateChange must be a function");if(s!==void 0&&typeof s!="function")throw new Error("onError must be a function");let a=M(),u=F(t,{onError:s}),c=u.getSnapshot(),i=c.state,S=Number.NaN,g=d=>{if(s)try{s(d);}catch{}},f=d=>{if(n)try{n(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},l=d=>{if(e)try{e(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},N=u.subscribe(d=>{c=d,d.state!==i&&(i=d.state,l(d.state)),d.totalSeconds!==S&&(S=d.totalSeconds,f(d));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:d=>{u.reset(d);},stop:()=>{u.stop();},getSeconds:()=>a.formatSeconds(c.totalSeconds),getMinutes:()=>a.formatMinutes(c.totalSeconds),getHours:()=>a.formatHours(c.totalSeconds),getDays:()=>a.formatDays(c.totalSeconds),getWeeks:()=>a.formatWeeks(c.totalSeconds),getYears:()=>a.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{N.unsubscribe();}finally{u.destroy();}}}}exports.Countdown=it;exports.CountdownEngine=F;exports.Formatter=M;exports.TimerState=m;exports.buildSnapshot=P;exports.defaultFormatter=E;exports.formatDays=nt;exports.formatHours=et;exports.formatMinutes=$;exports.formatSeconds=tt;exports.formatTime=Z;exports.formatWeeks=ot;exports.formatYears=rt;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CountdownSnapshot, T as TimerState } from './countdown-engine-
|
|
2
|
-
export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-
|
|
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 {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CountdownSnapshot, T as TimerState } from './countdown-engine-
|
|
2
|
-
export { a as CountdownEngine, c as CountdownEngineInstance, d as CountdownEngineOptions, e as CountdownParts, b as buildSnapshot } from './countdown-engine-
|
|
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 {
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function W(){return {now:()=>performance.now(),isHighResolution:true,type:"performance"}}function X(){return {now:()=>Date.now(),isHighResolution:false,type:"date"}}function y(){let t,o=X();function n(){try{if(typeof performance<"u"&&typeof performance.now=="function"){let e=performance.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER){t=W();return}}throw new Error("performance.now() not available or invalid")}catch{t=o;}}return n(),{now:()=>{try{let e=t.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0&&e<=Number.MAX_SAFE_INTEGER)return e;throw new Error("Invalid time value returned")}catch{t!==o&&(t=o);try{let e=o.now();if(typeof e=="number"&&Number.isFinite(e)&&!isNaN(e)&&e>=0)return e}catch{}return 0}},get isHighResolution(){return t.isHighResolution},get type(){return t.type}}}function k(t,o=0){let n=typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0;return ()=>{let e=t();return typeof e!="number"||!Number.isFinite(e)||e<n||(n=e),n}}y();var z=10,j=100;function B(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?j:Math.max(z,Math.floor(t))}function A(t,o,n={}){if(typeof t!="number"||!Number.isFinite(t))throw new Error("initialSeconds must be a finite number");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");let e=Math.floor(Math.max(0,t)),s=null,a=null,u=0,c=e,i=e,S=B(n.tickIntervalMs),g=y(),f=n.timeProvider??(()=>g.now()),l=()=>setInterval(()=>{try{if(a===null)return;let h=f()-a-u,D=Math.floor(h/1e3),r=Number.isFinite(D)?Math.min(i,Math.max(0,i-D)):i;e=r,r!==c&&(c=r,o.onTick(r)),r===0&&(d(),o.onComplete());}catch(p){d(),o.onError(p instanceof Error?p:new Error(String(p)));}},S),N=()=>{if(s||e<=0)return false;try{if(a!==null&&e<i){let p=(i-e)*1e3;u=f()-a-p;}else a=f(),u=0,c=e;return s=l(),!0}catch(p){return o.onError(p instanceof Error?p:new Error(String(p))),false}},d=()=>{s&&(clearInterval(s),s=null);},b=()=>{d(),e=i,a=null,u=0,c=i;},v=p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));e=h,a=null,u=0,c=h;};return {start:N,stop:d,reset:b,setSeconds:v,setInitialValue:p=>{if(typeof p!="number"||!Number.isFinite(p))return;let h=Math.floor(Math.max(0,Math.min(p,Number.MAX_SAFE_INTEGER)));i=h,v(h);},getTotalSeconds:()=>e,getInitialValue:()=>i,isRunning:()=>s!==null,destroy:()=>{d(),e=0,a=null,u=0;}}}var m=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"}),I={[m.IDLE]:{start:m.RUNNING},[m.RUNNING]:{pause:m.PAUSED,reset:m.IDLE,stop:m.STOPPED,complete:m.STOPPED},[m.PAUSED]:{resume:m.RUNNING,reset:m.IDLE,stop:m.STOPPED},[m.STOPPED]:{reset:m.IDLE}};function O(t){if(t!==void 0){if(typeof t!="object"||t===null)throw new Error("events must be an object");if(t.onStateChange!==void 0&&typeof t.onStateChange!="function")throw new Error("events.onStateChange must be a function")}let o=m.IDLE;function n(g){let f=I[o]?.[g];if(!f)return false;o=f;try{t?.onStateChange?.(f);}catch{}return true}function e(){return n("start")}function s(){return n("resume")}function a(){return n("pause")}function u(){return n("reset")}function c(){return n("stop")}function i(){return n("complete")}function S(){o===m.IDLE||o===m.STOPPED||n("stop");}return {start:e,resume:s,pause:a,reset:u,stop:c,complete:i,getCurrentState:()=>o,canStart:()=>!!I[o]?.start,canResume:()=>!!I[o]?.resume,canPause:()=>!!I[o]?.pause,isRunning:()=>o===m.RUNNING,destroy:S}}function x(t){let o=Number.isFinite(t)?Math.max(0,Math.floor(t)):0,n=o,e=Math.floor(n/31536e3);n-=e*31536e3;let s=Math.floor(n/604800);n-=s*604800;let a=Math.floor(n/86400);n-=a*86400;let u=Math.floor(n/3600);n-=u*3600;let c=Math.floor(n/60);return n-=c*60,{years:e,weeks:s,days:a,hours:u,minutes:c,seconds:n,totalDays:Math.floor(o/86400),totalHours:Math.floor(o/3600),totalMinutes:Math.floor(o/60)}}function C(t){return typeof t!="number"||!Number.isFinite(t)||t<=0?0:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(t)}function R(t){if(typeof t!="number"||!Number.isFinite(t)||!Number.isInteger(t))throw new Error("initialSeconds must be a finite, non-negative integer");if(t<0)throw new Error("initialSeconds must be non-negative");if(t>Number.MAX_SAFE_INTEGER)throw new Error("initialSeconds exceeds maximum safe integer");return t}function J(t){let o;if(t)if(typeof t=="function")o=t;else if(typeof t.now=="function"){let n=t;o=()=>n.now();}else throw new Error("timeProvider must implement a now(): number method");else {let n=y();o=()=>n.now();}return k(o)}function P(t,o,n){let e=C(t),s=C(o),a=x(s);return {initialSeconds:e,totalSeconds:s,parts:a,state:n,isRunning:n===m.RUNNING,isCompleted:s===0&&n===m.STOPPED}}function F(t,o={}){let n=R(t),e=J(o.timeProvider),s=n,a=P(n,n,m.IDLE),u=new Set,c=r=>{try{o.onSnapshot?.(r);}catch{}for(let w of u)try{w(r);}catch{}},i=r=>{let w=r===m.STOPPED?0:l.getTotalSeconds();a=P(s,w,r);try{o.onStateChange?.(r,a);}catch{}c(a);},S=r=>{a=P(s,r,f.getCurrentState()),c(a);},g=r=>{try{o.onError?.(r);}catch{}},f=O({onStateChange:r=>{i(r);}}),l=A(n,{onTick:r=>{S(r);},onComplete:()=>{f.complete();},onError:r=>{g(r),f.stop();}},{timeProvider:e,tickIntervalMs:o.tickIntervalMs});return {start:()=>{if(!f.canStart())return false;let r=l.start();return r&&f.start(),r},pause:()=>f.canPause()?(l.stop(),f.pause()):false,resume:()=>{if(!f.canResume())return false;let r=l.start();return r&&f.resume(),r},reset:r=>{if(l.stop(),typeof r=="number"){let _=R(r);s=_,l.setInitialValue(_);}else s=n,l.reset();return f.reset()||S(l.getTotalSeconds()),true},stop:()=>{l.stop();let r=f.stop();return r&&l.setSeconds(0),r},setSeconds:r=>{let w=R(r);l.setSeconds(w),S(l.getTotalSeconds());},getSnapshot:()=>a,subscribe:r=>{u.add(r);try{r(a);}catch{}return {unsubscribe:()=>{u.delete(r);}}},destroy:()=>{l.destroy(),f.destroy(),u.clear(),a=P(s,0,m.STOPPED);}}}function Q(t){return typeof t=="number"?t:t&&typeof t.totalSeconds=="number"?t.totalSeconds:0}function T(t,o=2){return t.toString().padStart(o,"0")}function M(){let t=i=>x(C(Q(i)));return {formatTime:i=>{let S=t(i);return {minutes:T(S.totalMinutes),seconds:T(S.seconds)}},formatMinutes:i=>T(t(i).minutes),formatSeconds:i=>T(t(i).seconds),formatHours:i=>T(t(i).hours),formatDays:i=>T(t(i).days),formatWeeks:i=>T(t(i).weeks),formatYears:i=>T(t(i).years)}}var E=M();function Z(t){return E.formatTime(t)}function $(t){return E.formatMinutes(t)}function tt(t){return E.formatSeconds(t)}function et(t){return E.formatHours(t)}function nt(t){return E.formatDays(t)}function ot(t){return E.formatWeeks(t)}function rt(t){return E.formatYears(t)}function it(t,o={}){let{onSnapshot:n,onStateChange:e,onError:s}=o;if(n!==void 0&&typeof n!="function")throw new Error("onSnapshot must be a function");if(e!==void 0&&typeof e!="function")throw new Error("onStateChange must be a function");if(s!==void 0&&typeof s!="function")throw new Error("onError must be a function");let a=M(),u=F(t,{onError:s}),c=u.getSnapshot(),i=c.state,S=Number.NaN,g=d=>{if(s)try{s(d);}catch{}},f=d=>{if(n)try{n(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},l=d=>{if(e)try{e(d);}catch(b){g(b instanceof Error?b:new Error(String(b)));}},N=u.subscribe(d=>{c=d,d.state!==i&&(i=d.state,l(d.state)),d.totalSeconds!==S&&(S=d.totalSeconds,f(d));});return {start:()=>{u.start();},pause:()=>{u.pause();},resume:()=>{u.resume();},reset:d=>{u.reset(d);},stop:()=>{u.stop();},getSeconds:()=>a.formatSeconds(c.totalSeconds),getMinutes:()=>a.formatMinutes(c.totalSeconds),getHours:()=>a.formatHours(c.totalSeconds),getDays:()=>a.formatDays(c.totalSeconds),getWeeks:()=>a.formatWeeks(c.totalSeconds),getYears:()=>a.formatYears(c.totalSeconds),getCurrentState:()=>c.state,getSnapshot:()=>c,destroy:()=>{try{N.unsubscribe();}finally{u.destroy();}}}}export{it as Countdown,F as CountdownEngine,M as Formatter,m as TimerState,P as buildSnapshot,E as defaultFormatter,nt as formatDays,et as formatHours,$ as formatMinutes,tt as formatSeconds,Z as formatTime,ot as formatWeeks,rt as formatYears};
|
package/dist/testing-utils.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var u=e=>!Number.isFinite(e)||e<0?0:e>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e);function M(e={}){let{startMs:n=0,tickMs:t=1e3,highResolution:a=true}=e,r=u(n),s=u(t);return {now:()=>r,advance:(i=s)=>(r=u(r+u(i)),r),set:i=>(r=u(i),r),reset:()=>(r=u(n),r),getTime:()=>r,isHighResolution:a,type:"fake"}}function R(e){return {now:()=>e.now(),isHighResolution:e.isHighResolution,type:e.type}}function h(e){let n=Number.isFinite(e)?Math.max(0,Math.floor(e)):0,t=n,a=Math.floor(t/31536e3);t-=a*31536e3;let r=Math.floor(t/604800);t-=r*604800;let s=Math.floor(t/86400);t-=s*86400;let m=Math.floor(t/3600);t-=m*3600;let p=Math.floor(t/60);return t-=p*60,{years:a,weeks:r,days:s,hours:m,minutes:p,seconds:t,totalDays:Math.floor(n/86400),totalHours:Math.floor(n/3600),totalMinutes:Math.floor(n/60)}}function c(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?0:e>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e)}var o=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[o.IDLE]:{start:o.RUNNING},[o.RUNNING]:{pause:o.PAUSED,reset:o.IDLE,stop:o.STOPPED,complete:o.STOPPED},[o.PAUSED]:{resume:o.RUNNING,reset:o.IDLE,stop:o.STOPPED},[o.STOPPED]:{reset:o.IDLE}});function P(e={}){let n=c(e.initialSeconds??e.totalSeconds??0),t=c(e.totalSeconds??e.initialSeconds??0),a=e.state??(t>0?o.IDLE:o.STOPPED),r=h(t);return {initialSeconds:n,totalSeconds:t,parts:r,state:a,isRunning:a===o.RUNNING,isCompleted:t===0&&a===o.STOPPED}}function A(e={}){let{totalSeconds:n=0,step:t=1,count:a=1,initialSeconds:r}=e,s=c(n),m=c(t),p=c(a)||1,S=[];for(let f=0;f<p;f+=1){let i=Math.max(s-m*f,0);S.push(P({initialSeconds:r??s,totalSeconds:i,state:i===0?o.STOPPED:o.RUNNING}));}return S}var d=(e,n)=>n?`${e}: ${n}`:e;function U(e,n,t){if(e.state!==n)throw new Error(d(t??"Unexpected countdown state",`expected ${n} but received ${e.state}`))}function F(e,n){if(!e.isCompleted||e.totalSeconds!==0)throw new Error(d(n??"Countdown should be completed"))}function v(e,n,t=0,a){if(typeof n!="number"||!Number.isFinite(n))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(e.totalSeconds-Math.floor(n))>t)throw new Error(d(a??"Unexpected remaining seconds",`expected ${n}\xB1${t} but received ${e.totalSeconds}`))}exports.TimerState=o;exports.assertRemainingSeconds=v;exports.assertSnapshotCompleted=F;exports.assertSnapshotState=U;exports.buildSnapshot=P;exports.buildSnapshotSequence=A;exports.createFakeTimeProvider=M;exports.toTimeProvider=R;
|
package/dist/testing-utils.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.cjs';
|
|
2
2
|
|
|
3
3
|
interface FakeTimeProvider extends TimeProvider {
|
|
4
4
|
advance: (ms?: number) => number;
|
package/dist/testing-utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-
|
|
1
|
+
import { f as TimeProvider, T as TimerState, C as CountdownSnapshot } from './countdown-engine-DEKF_8iT.js';
|
|
2
2
|
|
|
3
3
|
interface FakeTimeProvider extends TimeProvider {
|
|
4
4
|
advance: (ms?: number) => number;
|
package/dist/testing-utils.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=e=>!Number.isFinite(e)||e<0?0:e>Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e);function M(e={}){let{startMs:n=0,tickMs:t=1e3,highResolution:a=true}=e,r=u(n),s=u(t);return {now:()=>r,advance:(i=s)=>(r=u(r+u(i)),r),set:i=>(r=u(i),r),reset:()=>(r=u(n),r),getTime:()=>r,isHighResolution:a,type:"fake"}}function R(e){return {now:()=>e.now(),isHighResolution:e.isHighResolution,type:e.type}}function h(e){let n=Number.isFinite(e)?Math.max(0,Math.floor(e)):0,t=n,a=Math.floor(t/31536e3);t-=a*31536e3;let r=Math.floor(t/604800);t-=r*604800;let s=Math.floor(t/86400);t-=s*86400;let m=Math.floor(t/3600);t-=m*3600;let p=Math.floor(t/60);return t-=p*60,{years:a,weeks:r,days:s,hours:m,minutes:p,seconds:t,totalDays:Math.floor(n/86400),totalHours:Math.floor(n/3600),totalMinutes:Math.floor(n/60)}}function c(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?0:e>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:Math.floor(e)}var o=Object.freeze({IDLE:"IDLE",RUNNING:"RUNNING",PAUSED:"PAUSED",STOPPED:"STOPPED"});({[o.IDLE]:{start:o.RUNNING},[o.RUNNING]:{pause:o.PAUSED,reset:o.IDLE,stop:o.STOPPED,complete:o.STOPPED},[o.PAUSED]:{resume:o.RUNNING,reset:o.IDLE,stop:o.STOPPED},[o.STOPPED]:{reset:o.IDLE}});function P(e={}){let n=c(e.initialSeconds??e.totalSeconds??0),t=c(e.totalSeconds??e.initialSeconds??0),a=e.state??(t>0?o.IDLE:o.STOPPED),r=h(t);return {initialSeconds:n,totalSeconds:t,parts:r,state:a,isRunning:a===o.RUNNING,isCompleted:t===0&&a===o.STOPPED}}function A(e={}){let{totalSeconds:n=0,step:t=1,count:a=1,initialSeconds:r}=e,s=c(n),m=c(t),p=c(a)||1,S=[];for(let f=0;f<p;f+=1){let i=Math.max(s-m*f,0);S.push(P({initialSeconds:r??s,totalSeconds:i,state:i===0?o.STOPPED:o.RUNNING}));}return S}var d=(e,n)=>n?`${e}: ${n}`:e;function U(e,n,t){if(e.state!==n)throw new Error(d(t??"Unexpected countdown state",`expected ${n} but received ${e.state}`))}function F(e,n){if(!e.isCompleted||e.totalSeconds!==0)throw new Error(d(n??"Countdown should be completed"))}function v(e,n,t=0,a){if(typeof n!="number"||!Number.isFinite(n))throw new Error("Expected remaining seconds must be a finite number");if(Math.abs(e.totalSeconds-Math.floor(n))>t)throw new Error(d(a??"Unexpected remaining seconds",`expected ${n}\xB1${t} but received ${e.totalSeconds}`))}export{o as TimerState,v as assertRemainingSeconds,F as assertSnapshotCompleted,U as assertSnapshotState,P as buildSnapshot,A as buildSnapshotSequence,M as createFakeTimeProvider,R as toTimeProvider};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timekeeper-countdown/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|