Normalize countdown keyboard adjustments and roll seconds across minutes

Co-authored-by: Shelley <shelley@exe.dev>
This commit is contained in:
Leon Mika 2026-10-10 06:50:45 +00:00
parent 9c993beda8
commit ebfd6e4ce1
3 changed files with 48 additions and 4 deletions

View file

@ -1,4 +1,4 @@
import { Countdown, integer } from './timer.mjs'; import { Countdown, integer, adjustTime } from './timer.mjs';
const timer = new Countdown(); const timer = new Countdown();
const minutes = document.querySelector('#minutes'); const minutes = document.querySelector('#minutes');
@ -55,8 +55,10 @@ for (const field of fields) {
if (field.readOnly || !['ArrowUp', 'ArrowDown'].includes(event.key)) return; if (field.readOnly || !['ArrowUp', 'ArrowDown'].includes(event.key)) return;
event.preventDefault(); event.preventDefault();
const step = event.shiftKey ? 10n : 1n; const step = event.shiftKey ? 10n : 1n;
const value = integer(field.value) + (event.key === 'ArrowUp' ? step : -step); const adjusted = adjustTime(minutes.value, seconds.value, field.id,
field.value = String(value < 0n ? 0n : value); event.key === 'ArrowUp' ? step : -step);
minutes.value = adjusted.minutes;
seconds.value = adjusted.seconds;
edited(); edited();
}); });
field.addEventListener('blur', () => { field.addEventListener('blur', () => {

View file

@ -10,6 +10,16 @@ export function splitSeconds(seconds) {
}; };
} }
export function adjustTime(minutes, seconds, field, delta) {
const total = integer(minutes) * 60n + integer(seconds);
if (field === 'minutes') {
const adjusted = total / 60n + delta;
return splitSeconds((adjusted < 0n ? 0n : adjusted) * 60n + total % 60n);
}
const adjusted = total + delta;
return splitSeconds(adjusted < 0n ? 0n : adjusted);
}
export class Countdown { export class Countdown {
constructor(now = Date.now) { constructor(now = Date.now) {
this.now = now; this.now = now;

View file

@ -1,6 +1,38 @@
import { test } from 'node:test'; import { test } from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { Countdown, integer, splitSeconds } from './timer.mjs'; import { Countdown, integer, splitSeconds, adjustTime } from './timer.mjs';
for (const [minutes, seconds, delta, expectedMinutes, expectedSeconds] of [
['1', '59', 1n, '2', '00'],
['2', '00', -1n, '1', '59'],
['2', '05', -10n, '1', '55'],
['1', '58', 10n, '2', '08'],
['2', '00', -10n, '1', '50'],
['0', '90', 1n, '1', '31'],
['0', '90', -10n, '1', '20'],
['0', '00', -1n, '0', '00'],
['0', '05', -10n, '0', '00'],
]) {
test(`seconds adjustment ${minutes}:${seconds} ${delta} normalizes and rolls`, () => {
assert.deepEqual(adjustTime(minutes, seconds, 'seconds', delta), {
minutes: expectedMinutes,
seconds: expectedSeconds,
});
});
}
test('minute adjustments normalize first and keep minutes nonnegative', () => {
assert.deepEqual(adjustTime('0', '90', 'minutes', 1n), { minutes: '2', seconds: '30' });
assert.deepEqual(adjustTime('0', '90', 'minutes', -1n), { minutes: '0', seconds: '30' });
assert.deepEqual(adjustTime('1', '90', 'minutes', 10n), { minutes: '12', seconds: '30' });
assert.deepEqual(adjustTime('1', '90', 'minutes', -10n), { minutes: '0', seconds: '30' });
});
test('keyboard adjustments preserve large integer precision', () => {
assert.deepEqual(adjustTime('12345678901234567890', '59', 'seconds', 1n), {
minutes: '12345678901234567891', seconds: '00',
});
});
function fixture() { function fixture() {
let now = 1000; let now = 1000;