This commit is contained in:
1 parent
9a3d0b8a72
commit
83a26c9476
3 files changed
+192
-69
No files matched your search
+61
-1
@@ -8,12 +8,17 @@ const BASE = document.documentElement.dataset.base || '/';
|
||||
const POLL_MS = 5000;
|
||||
// How long a dropped cursor waits for the player to report it got there.
|
||||
const SEEK_HOLD_MS = 10000;
|
||||
// How far an arrow key moves the progress cursor.
|
||||
const SEEK_KEY_MS = 10000;
|
||||
// And how long a pressed play or pause on the AVR waits for the same. The
|
||||
// Zidoo acknowledges the key press at once but takes a moment to act on it.
|
||||
const PLAY_HOLD_MS = 6000;
|
||||
// Motion turned down in the OS means cards change places without sliding.
|
||||
const CALM = matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
// Which way each arrow key moves a slider.
|
||||
const ARROWS = { ArrowRight: 1, ArrowUp: 1, ArrowLeft: -1, ArrowDown: -1 };
|
||||
|
||||
const el = (sel, root = document) => root.querySelector(sel);
|
||||
const els = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
||||
|
||||
@@ -170,6 +175,10 @@ function paintVolume(room) {
|
||||
room.level.textContent = known ? room.volume : '—';
|
||||
// The fill and the knob riding it both size themselves off this.
|
||||
room.meter.style.setProperty('--level', known ? room.volume : 0);
|
||||
// The knob is a slider to VoiceOver, which reads these rather than its digits.
|
||||
if (known) room.level.setAttribute('aria-valuenow', room.volume);
|
||||
else room.level.removeAttribute('aria-valuenow');
|
||||
room.level.setAttribute('aria-disabled', String(!room.available));
|
||||
room.steps.forEach((button) => { button.disabled = !room.available; });
|
||||
}
|
||||
|
||||
@@ -282,6 +291,16 @@ function paintProgress(refs, known, position, duration) {
|
||||
if (known) label.style.setProperty('--half', `${label.offsetWidth / 2}px`);
|
||||
});
|
||||
el('[data-role="progress-total"]', progress).textContent = known ? clock(total) : '';
|
||||
|
||||
const cursor = el('[data-role="progress-cursor"]', progress);
|
||||
const seekable = progress.classList.contains('seekable');
|
||||
cursor.tabIndex = seekable ? 0 : -1;
|
||||
cursor.setAttribute('aria-readonly', String(!seekable));
|
||||
if (known) {
|
||||
cursor.setAttribute('aria-valuemax', total);
|
||||
cursor.setAttribute('aria-valuenow', elapsed);
|
||||
cursor.setAttribute('aria-valuetext', `${clock(elapsed)} of ${clock(total)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function tickProgress(refs) {
|
||||
@@ -339,6 +358,19 @@ function seekable(refs) {
|
||||
};
|
||||
line.addEventListener('pointerup', stop);
|
||||
line.addEventListener('pointercancel', stop);
|
||||
|
||||
// The arrow keys, and VoiceOver's swipe up and down on a slider, move the
|
||||
// cursor a step at a time, each one its own seek.
|
||||
el('[data-role="progress-cursor"]', progress).addEventListener('keydown', (event) => {
|
||||
const direction = ARROWS[event.key];
|
||||
if (!direction || seeking(refs) || !refs.shown || !progress.classList.contains('seekable')) return;
|
||||
event.preventDefault();
|
||||
const { duration_ms: duration } = refs.shown;
|
||||
const ms = refs.shownMs + direction * SEEK_KEY_MS;
|
||||
refs.seek = { ms: Math.round(Math.max(0, Math.min(duration, ms))), stage: 'drag', song: refs.shown.song };
|
||||
renderProgress(refs.shown, refs);
|
||||
sendSeek(refs);
|
||||
});
|
||||
}
|
||||
|
||||
/* A seek that fails snaps the cursor back; one that works holds it where it
|
||||
@@ -452,6 +484,11 @@ function holdable(node, action) {
|
||||
let timer = null;
|
||||
let delay = 420;
|
||||
|
||||
// A press by pointer acts on pointerdown and preventDefault()s, so the
|
||||
// click behind it is not meant to act again. One with no pointer press
|
||||
// just before it -- Enter or Space on a focused button, VoiceOver's
|
||||
// double-tap -- is the only way in for those, and acts once.
|
||||
let pressed = false;
|
||||
const stop = () => { clearTimeout(timer); timer = null; delay = 420; };
|
||||
const tick = () => {
|
||||
action();
|
||||
@@ -462,10 +499,15 @@ function holdable(node, action) {
|
||||
node.addEventListener('pointerdown', (event) => {
|
||||
if (event.button > 0 || node.disabled) return;
|
||||
event.preventDefault(); // also suppresses the click that would double-fire
|
||||
pressed = true;
|
||||
action();
|
||||
timer = setTimeout(tick, delay);
|
||||
});
|
||||
['pointerup', 'pointercancel', 'pointerleave'].forEach((type) => node.addEventListener(type, stop));
|
||||
['pointerup', 'pointercancel'].forEach((type) => node.addEventListener(type, () => setTimeout(() => { pressed = false; }, 500)));
|
||||
node.addEventListener('click', () => {
|
||||
if (!pressed && !node.disabled) action();
|
||||
});
|
||||
}
|
||||
|
||||
function nextLevel(current, direction) {
|
||||
@@ -552,6 +594,14 @@ function draggable(room) {
|
||||
};
|
||||
knob.addEventListener('pointerup', stop);
|
||||
knob.addEventListener('pointercancel', stop);
|
||||
|
||||
// The arrow keys, and VoiceOver's swipe up and down on a slider.
|
||||
knob.addEventListener('keydown', (event) => {
|
||||
const direction = ARROWS[event.key];
|
||||
if (!direction) return;
|
||||
event.preventDefault();
|
||||
nudge(room.key, direction);
|
||||
});
|
||||
}
|
||||
|
||||
async function setGrouped(key, joined) {
|
||||
@@ -733,9 +783,19 @@ function openSheet() {
|
||||
ui.options.appendChild(option);
|
||||
});
|
||||
ui.sheet.hidden = false;
|
||||
// Into the sheet, on the input already selected, so a keyboard or VoiceOver
|
||||
// starts there rather than on the page behind it.
|
||||
(el('[aria-current="true"]', ui.options) || ui.options.firstElementChild).focus();
|
||||
}
|
||||
|
||||
function closeSheet() { ui.sheet.hidden = true; }
|
||||
function closeSheet() {
|
||||
ui.sheet.hidden = true;
|
||||
ui.inputButton.focus();
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && sheetOpen()) closeSheet();
|
||||
});
|
||||
|
||||
async function chooseInput(source) {
|
||||
closeSheet();
|
||||
|
||||
Reference in new issue
Block a user