fix: address a11y code review findings

Bugs fixed:
- Space key was hijacked in tag input when a suggestion was
  highlighted, preventing users from typing spaces. Removed
  Space as a selection key (Enter is sufficient per combobox
  pattern).
- ArrowUp was clamped to index 0, making it impossible to
  deselect all suggestions and return to free typing. Now
  allows arrowing back to -1 which clears aria-activedescendant.

Cleanup:
- Remove dead inline onkeydown handlers from tag suggestion
  <li> elements (tabindex="-1" means they never receive focus,
  so the handlers never fire; the global keydown listener in
  app.js handles keyboard navigation).
- Add outline to aria-selected="true" state for visual parity
  with hover (keyboard users now see the same indicator).
- Announce "Ingen forslag" in live region when suggestions are
  empty (screen readers previously got silence).
- Add responsive table wrapper to admin tags and admin requests
  tables (was only on admin users).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-03-29 17:58:40 +02:00
commit a64d0c5dff
5 changed files with 22 additions and 13 deletions

View file

@ -164,6 +164,8 @@
.tag-suggestion[aria-selected="true"] {
background: var(--pico-primary-focus);
outline: 2px solid var(--pico-primary);
outline-offset: -2px;
}
/* Fave detail actions */

View file

@ -48,7 +48,7 @@
if (status) {
status.textContent = count > 0
? count + " forslag tilgjengelig"
: "";
: "Ingen forslag";
}
// Reset active descendant tracking.
@ -88,21 +88,19 @@
break;
case "ArrowUp":
event.preventDefault();
activeIndex = Math.max(activeIndex - 1, 0);
setActiveDescendant(items);
// Allow arrowing back to -1 to deselect all options.
activeIndex = Math.max(activeIndex - 1, -1);
if (activeIndex === -1) {
clearActiveDescendant();
clearAriaSelected(items);
} else {
setActiveDescendant(items);
}
break;
case "Enter":
if (activeIndex >= 0 && activeIndex < items.length) {
event.preventDefault();
var tagName = items[activeIndex].textContent.trim();
addTag(null, tagName);
}
break;
case " ":
if (activeIndex >= 0 && activeIndex < items.length) {
event.preventDefault();
var tagName2 = items[activeIndex].textContent.trim();
addTag(null, tagName2);
addTag(null, items[activeIndex].textContent.trim());
}
break;
case "Escape":
@ -133,6 +131,12 @@
}
}
function clearAriaSelected(items) {
for (var i = 0; i < items.length; i++) {
items[i].setAttribute("aria-selected", "false");
}
}
function closeSuggestions() {
var listbox = document.getElementById("tag-suggestions");
if (listbox) {