webtools/site/countdown/timer.mjs
2026-10-10 06:45:15 +00:00

64 lines
1.7 KiB
JavaScript

// BigInt keeps entered integers exact, even for very large countdowns.
export function integer(value) {
return /^\d+$/.test(String(value)) ? BigInt(value) : 0n;
}
export function splitSeconds(seconds) {
return {
minutes: String(seconds / 60n),
seconds: String(seconds % 60n).padStart(2, '0'),
};
}
export class Countdown {
constructor(now = Date.now) {
this.now = now;
this.state = 'idle';
this.finished = false;
this.duration = 0n;
this.remaining = 0n;
this.deadline = 0n;
}
edit(minutes, seconds) {
if (this.state === 'running') return;
this.duration = (integer(minutes) * 60n + integer(seconds)) * 1000n;
this.remaining = this.duration;
this.state = 'idle';
this.finished = false;
}
update() {
if (this.state !== 'running') return;
const delta = this.deadline - BigInt(this.now());
this.remaining = delta > 0n ? delta : 0n;
if (this.remaining === 0n) {
this.state = 'idle';
this.finished = true;
}
}
toggle() {
if (this.state === 'running') {
this.update();
if (!this.finished) this.state = 'paused';
return;
}
if (this.finished) this.remaining = this.duration;
this.finished = false;
this.deadline = BigInt(this.now()) + this.remaining;
this.state = 'running';
this.update();
}
reset() {
this.remaining = this.duration;
this.state = 'idle';
this.finished = false;
}
display() {
// Round up so the timer only displays zero once its deadline is reached.
return splitSeconds((this.remaining + 999n) / 1000n);
}
}