@timekeeper-countdown/core 0.1.0 → 0.1.2
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 +231 -2
- package/dist/format.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,3 +1,232 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @timekeeper-countdown/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lightweight countdown engine used by Timekeeper Countdown. This package exposes the finite-state machine, snapshot APIs, formatting helpers, and testing utilities that power the React hook and future framework adapters.
|
|
4
|
+
|
|
5
|
+
- Written in TypeScript with zero runtime dependencies.
|
|
6
|
+
- Ships modern ESM bundles and type definitions.
|
|
7
|
+
- Designed to run in browsers, Node.js, or custom runtimes.
|
|
8
|
+
- Tested with deterministic fake timers for reliable behavior.
|
|
9
|
+
|
|
10
|
+
> **Looking for the React hook?** Install [`@timekeeper-countdown/react`](https://www.npmjs.com/package/@timekeeper-countdown/react) for an idiomatic React API that wraps this engine.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @timekeeper-countdown/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The published bundle is pure ESM. When targeting CommonJS environments use a bundler that understands `type: "module"` packages.
|
|
21
|
+
|
|
22
|
+
Supported runtimes:
|
|
23
|
+
|
|
24
|
+
- Node.js 18+
|
|
25
|
+
- Modern browsers (ES2022 modules)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### High-level helper (`Countdown`)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { Countdown, TimerState } from '@timekeeper-countdown/core';
|
|
35
|
+
|
|
36
|
+
const countdown = Countdown(300, {
|
|
37
|
+
onUpdate: (minutes, seconds) => {
|
|
38
|
+
timerElement.textContent = `${minutes}:${seconds}`;
|
|
39
|
+
},
|
|
40
|
+
onStateChange: state => {
|
|
41
|
+
if (state === TimerState.STOPPED) {
|
|
42
|
+
console.log('Finished!');
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
countdown.start(); // Begin the countdown
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`Countdown` wraps the lower-level engine and returns convenient methods:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
countdown.start(); // boolean (false when invalid transition)
|
|
54
|
+
countdown.pause();
|
|
55
|
+
countdown.resume();
|
|
56
|
+
countdown.reset(nextInitialSeconds?);
|
|
57
|
+
countdown.stop();
|
|
58
|
+
countdown.destroy(); // dispose timers and listeners
|
|
59
|
+
|
|
60
|
+
countdown.getSnapshot(); // CountdownSnapshot
|
|
61
|
+
countdown.getCurrentState(); // TimerState
|
|
62
|
+
countdown.getMinutes(); // string e.g. "05"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Low-level engine (`CountdownEngine`)
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { CountdownEngine, TimerState } from '@timekeeper-countdown/core';
|
|
69
|
+
|
|
70
|
+
const engine = CountdownEngine(90, {
|
|
71
|
+
tickIntervalMs: 50,
|
|
72
|
+
onSnapshot: snapshot => {
|
|
73
|
+
console.log(snapshot.totalSeconds);
|
|
74
|
+
},
|
|
75
|
+
onStateChange: (state, snapshot) => {
|
|
76
|
+
if (state === TimerState.STOPPED && snapshot.isCompleted) {
|
|
77
|
+
console.log('Done!');
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onError: error => {
|
|
81
|
+
console.error('Timer failure', error);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
engine.start();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`CountdownEngine` exposes fine-grained control:
|
|
89
|
+
|
|
90
|
+
- `start`, `pause`, `resume`, `reset(nextInitialSeconds?)`, `stop`, `setSeconds(value)`, `destroy`
|
|
91
|
+
- `getSnapshot()` returns the latest snapshot.
|
|
92
|
+
- `subscribe(listener)` emits the current snapshot immediately and on every tick.
|
|
93
|
+
|
|
94
|
+
Snapshot structure:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface CountdownSnapshot {
|
|
98
|
+
initialSeconds: number; // starting value
|
|
99
|
+
totalSeconds: number; // remaining seconds, floor-clamped
|
|
100
|
+
parts: {
|
|
101
|
+
years: number;
|
|
102
|
+
weeks: number;
|
|
103
|
+
days: number;
|
|
104
|
+
hours: number;
|
|
105
|
+
minutes: number;
|
|
106
|
+
seconds: number;
|
|
107
|
+
totalDays: number;
|
|
108
|
+
totalHours: number;
|
|
109
|
+
totalMinutes: number;
|
|
110
|
+
};
|
|
111
|
+
state: TimerState; // IDLE | RUNNING | PAUSED | STOPPED
|
|
112
|
+
isRunning: boolean;
|
|
113
|
+
isCompleted: boolean;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Formatting Helpers
|
|
120
|
+
|
|
121
|
+
Use the helpers exported at `@timekeeper-countdown/core/format` to avoid reimplementing `padStart` logic.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { formatTime, formatMinutes, formatSeconds, Formatter } from '@timekeeper-countdown/core/format';
|
|
125
|
+
|
|
126
|
+
const snapshot = engine.getSnapshot();
|
|
127
|
+
|
|
128
|
+
formatTime(snapshot); // { minutes: "01", seconds: "30" }
|
|
129
|
+
formatMinutes(snapshot); // "01"
|
|
130
|
+
|
|
131
|
+
const formatter = Formatter();
|
|
132
|
+
formatter.formatHours(snapshot); // memoised string helpers
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
All helpers accept either a snapshot or any object that exposes `totalSeconds`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Testing Utilities
|
|
140
|
+
|
|
141
|
+
The package includes utilities under `@timekeeper-countdown/core/testing-utils` to make unit tests deterministic.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import {
|
|
145
|
+
createFakeTimeProvider,
|
|
146
|
+
toTimeProvider,
|
|
147
|
+
buildSnapshot,
|
|
148
|
+
assertSnapshotState,
|
|
149
|
+
} from '@timekeeper-countdown/core/testing-utils';
|
|
150
|
+
|
|
151
|
+
const fake = createFakeTimeProvider({ startMs: 0, tickMs: 1000 });
|
|
152
|
+
const engine = CountdownEngine(5, {
|
|
153
|
+
timeProvider: toTimeProvider(fake),
|
|
154
|
+
tickIntervalMs: 5,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
engine.start();
|
|
158
|
+
fake.advance(3000);
|
|
159
|
+
|
|
160
|
+
expect(engine.getSnapshot().totalSeconds).toBe(2);
|
|
161
|
+
|
|
162
|
+
const snapshot = buildSnapshot({ totalSeconds: 42 });
|
|
163
|
+
assertSnapshotState(snapshot, 'RUNNING');
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Utilities include:
|
|
167
|
+
|
|
168
|
+
- `createFakeTimeProvider` / `toTimeProvider` for manual clock control.
|
|
169
|
+
- `buildSnapshot`, `assertSnapshotState` helpers for quick snapshot fabrication.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Custom Time Providers
|
|
174
|
+
|
|
175
|
+
`CountdownEngine` accepts either:
|
|
176
|
+
|
|
177
|
+
- A function: `() => number` returning milliseconds.
|
|
178
|
+
- An object implementing the `TimeProvider` interface: `{ now(): number; isHighResolution: boolean; type: string }`.
|
|
179
|
+
|
|
180
|
+
This allows plugging in custom schedulers or synchronizing multiple engines.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const provider = {
|
|
184
|
+
now: () => performance.now(),
|
|
185
|
+
isHighResolution: true,
|
|
186
|
+
type: 'custom',
|
|
187
|
+
};
|
|
188
|
+
CountdownEngine(60, { timeProvider: provider });
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## TypeScript Support
|
|
194
|
+
|
|
195
|
+
All exports are fully typed. Useful entry points:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import type {
|
|
199
|
+
CountdownSnapshot,
|
|
200
|
+
CountdownEngineOptions,
|
|
201
|
+
CountdownEngineInstance,
|
|
202
|
+
TimerState,
|
|
203
|
+
} from '@timekeeper-countdown/core';
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Documentation & Examples
|
|
209
|
+
|
|
210
|
+
- Monorepo overview: [GitHub repository](https://github.com/eagle-head/timekeeper-countdown)
|
|
211
|
+
- React hook guide: [`@timekeeper-countdown/react` README](https://github.com/eagle-head/timekeeper-countdown/tree/main/packages/react#readme)
|
|
212
|
+
- Complete docs site (guides, API reference, roadmap): <https://eagle-head.github.io/timekeeper-countdown/>
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Contributing
|
|
217
|
+
|
|
218
|
+
Issues and pull requests are welcome. Please review the [repository guidelines](https://github.com/eagle-head/timekeeper-countdown/blob/main/AGENTS.md) for more details and run:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
npm run lint --workspaces
|
|
222
|
+
npm run test --workspaces
|
|
223
|
+
npm run typecheck --workspaces
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
before submitting changes.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT © [Eduardo Kohn](https://www.linkedin.com/in/eduardo-kohn-56817b195/)
|
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 Math.floor(t/3600)%24}function
|
|
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};
|
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
|
|
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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timekeeper-countdown/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Lightweight countdown engine that powers the Timekeeper packages. Pure TypeScript with no runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"countdown",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
|
-
"dist"
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md"
|
|
45
46
|
],
|
|
46
47
|
"sideEffects": false,
|
|
47
48
|
"scripts": {
|