mnw-server: fix discover filter aria state and header affordances
The filter sidebar's htmx:beforeRequest handler toggled only the
is-selected class, so the server-rendered aria-selected went stale on the
first click and then inverted: the selected item reported false while the
deselected "All" still reported true. These are role=option in a
role=listbox, so assistive tech reads the attribute rather than the class
and the listbox announced the opposite of the screen. Set the attribute
alongside the class.
The column headers were gated on mode but never on view, so the default
grid view rendered a five-column table header above a card grid, labelling
nothing. Toggle them from applyView, which also covers the htmx:afterSwap
re-apply so the header cannot drift out of sync after a filter swap.
The headers also carried cursor: pointer plus a hover response while
nothing bound them, inviting a click that did nothing. Sorting exists via
the sort select, which made the invitation more plausible. Both rules were
global, so feed.html's equally unsortable headers are fixed too.
Found and verified in a real browser against testnot.work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 files changed,
+15 insertions,
-4 deletions
| 3 |
3 |
|
// search/sort submissions include it.
|
| 4 |
4 |
|
document.body.addEventListener('htmx:beforeRequest', function(evt) {
|
| 5 |
5 |
|
if (evt.detail.elt.classList.contains('filter-item')) {
|
| 6 |
|
- |
// Only deactivate siblings in the same filter section
|
|
6 |
+ |
// Only deactivate siblings in the same filter section.
|
|
7 |
+ |
// aria-selected must track is-selected: these are role="option" in a
|
|
8 |
+ |
// role="listbox", so assistive tech reads the attribute, not the class.
|
| 7 |
9 |
|
var section = evt.detail.elt.closest('.filter-list');
|
| 8 |
10 |
|
if (section) {
|
| 9 |
|
- |
section.querySelectorAll('.filter-item').forEach(function(i) { i.classList.remove('is-selected'); });
|
|
11 |
+ |
section.querySelectorAll('.filter-item').forEach(function(i) {
|
|
12 |
+ |
i.classList.remove('is-selected');
|
|
13 |
+ |
i.setAttribute('aria-selected', 'false');
|
|
14 |
+ |
});
|
| 10 |
15 |
|
}
|
| 11 |
16 |
|
evt.detail.elt.classList.add('is-selected');
|
|
17 |
+ |
evt.detail.elt.setAttribute('aria-selected', 'true');
|
| 12 |
18 |
|
|
| 13 |
19 |
|
var hxVals = JSON.parse(evt.detail.elt.getAttribute('hx-vals') || '{}');
|
| 14 |
20 |
|
if ('item_type' in hxVals) {
|
| 40 |
46 |
|
document.querySelectorAll('.view-btn').forEach(function(btn) {
|
| 41 |
47 |
|
btn.classList.toggle('is-selected', btn.dataset.view === view);
|
| 42 |
48 |
|
});
|
|
49 |
+ |
// The column headers label the list layout; in grid view they label nothing.
|
|
50 |
+ |
var header = document.querySelector('.table-header');
|
|
51 |
+ |
if (header) {
|
|
52 |
+ |
header.classList.toggle('is-hidden', view !== 'list');
|
|
53 |
+ |
}
|
| 43 |
54 |
|
}
|
| 44 |
55 |
|
|
| 45 |
56 |
|
// Load saved preference on page load
|
| 7480 |
7480 |
|
.sort-select { background: var(--surface-page); border: none; padding: 0.3rem 0.4rem; font-size: 0.75rem; font-family: inherit; cursor: pointer; width: auto; flex-shrink: 0; }
|
| 7481 |
7481 |
|
.table-header { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: 0.5rem; padding: 0.5rem 0.75rem; background: var(--surface-overlay); font-size: 0.75rem; opacity: 0.7; text-transform: uppercase; letter-spacing: 0.03em; }
|
| 7482 |
7482 |
|
.table-header.projects-header { grid-template-columns: 1fr 100px 80px 70px; }
|
| 7483 |
|
- |
.table-header span { cursor: pointer; }
|
| 7484 |
|
- |
.table-header span:hover { opacity: 0.6; }
|
|
7483 |
+ |
/* Column headers describe the list layout only; hidden when the grid view is active. */
|
|
7484 |
+ |
.table-header.is-hidden { display: none; }
|
| 7485 |
7485 |
|
.col-right { text-align: right; }
|
| 7486 |
7486 |
|
.results-table { border: 1px solid var(--border); border-top: none; }
|
| 7487 |
7487 |
|
.table-row { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: 0.5rem; padding: 0.4rem 0.75rem; align-items: center; text-decoration: none; color: var(--content); font-size: 0.85rem; border-bottom: 1px solid var(--border); transition: background 0.1s ease; }
|