Batch of accessibility and touch fixes from the audit
- Esc closes whichever panel a card has open and returns focus to the cell that owns it; opening a confirm row moves focus to its primary button, so the announcement is deterministic and tab order stays inside the confirm. - The inline error scrolls itself into view and no longer self-destructs after 5s: this reader gets interrupted mid-tap, and an expiring notice left a failed write with no trace at all. It clears on the next request from that card. - Every tab clears 44px wide (All was 15.25); desktop action cells go 40 -> 44. - login.html's error gets role="alert". - The card monogram carries its own aria-hidden — Chromium exposes it through the cover link's, because that link is programmatically focusable. - The busy bar ends its travel flush with the card edge, so .card.htmx-request no longer needs overflow: hidden and cannot clip a panel mid-request. Verified at 390 and 1280 in both themes; detector over templates+static reports only the two known Instrument Serif false positives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -101,9 +101,26 @@ function toggleChapterForm(key) {
|
||||
// cell share it.
|
||||
function toggleConfirmRow(key, kind) {
|
||||
var cls = { archive: ".box", finish: ".finish", remove: ".remove" }[kind];
|
||||
togglePanel(key, "confirm-" + kind + "-" + key, ".actions " + cls);
|
||||
var row = togglePanel(key, "confirm-" + kind + "-" + key, ".actions " + cls);
|
||||
// Focus the answer rather than trusting aria-live on a container that merely
|
||||
// unhides: it makes the announcement deterministic, keeps tab order inside
|
||||
// the confirm instead of running on into the next card, and means the row
|
||||
// cannot be opened and scrolled past unnoticed.
|
||||
if (row && !row.hidden) row.querySelector("button").focus();
|
||||
}
|
||||
|
||||
// Esc closes whichever panel this card has open and hands focus back to the
|
||||
// cell that owns it — otherwise the only way out is finding that exact cell
|
||||
// again.
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key !== "Escape" || !e.target.closest) return;
|
||||
var card = e.target.closest(".card");
|
||||
var owner = card && card.querySelector(".actions .open");
|
||||
if (!owner) return;
|
||||
closeCardPanels(card.id.replace(/^card-/, ""));
|
||||
owner.focus();
|
||||
});
|
||||
|
||||
// A failed favourite/chapter/delete request leaves the card in place (htmx
|
||||
// does not swap on a non-2xx response) but otherwise gives no sign anything
|
||||
// went wrong. Surface it inline instead of leaving the tap looking ignored.
|
||||
@@ -121,12 +138,23 @@ function toggleConfirmRow(key, kind) {
|
||||
slot.append(" ", a);
|
||||
}
|
||||
slot.hidden = false;
|
||||
clearTimeout(slot._hideTimer);
|
||||
slot._hideTimer = setTimeout(function () {
|
||||
slot.hidden = true;
|
||||
}, linkHref ? 30000 : 5000);
|
||||
// No self-destruct timer: this reader gets interrupted mid-tap, and a
|
||||
// notice that expires after 5s leaves a failed write with no trace at all
|
||||
// — the star is back off and nothing says why. The notice stays until the
|
||||
// next request from this card clears it (or a successful one swaps the
|
||||
// whole card away).
|
||||
slot.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
document.body.addEventListener("htmx:beforeRequest", function (e) {
|
||||
var card = e.detail.elt.closest(".card");
|
||||
var slot = card && card.querySelector(".error-inline");
|
||||
if (slot) slot.hidden = true;
|
||||
});
|
||||
|
||||
// The handlers answer a bad value with http.Error, i.e. a short plain-text
|
||||
// line — worth showing verbatim. Anything long or HTML-ish is an error page,
|
||||
// not a reason, so fall back to the generic copy.
|
||||
|
||||
Reference in New Issue
Block a user