openvibers/openvibe.codes Commits

Codes: tables that scroll are focusable, named regions (#78)

OP OpenVibers committed 29 days ago · b8b878a92f · 2 files, +4 −1

The harness table on `/harnesses` scrolls sideways on a phone but could not be reached by keyboard (axe `scrollable-region-focusable`, serious, in a scan of inner pages at 390 px).

The shared table helper (`server/render/html.js`) now makes its wrapper `tabindex="0" role="region"`, named after the columns. Every table on Codes gets it. `test/layout.test.js` checks it.

Changes

ddiff --git a/server/render/html.js b/server/render/html.js
iindex c0f69d9..a0e2a36 100644
---- a/server/render/html.js
++++ b/server/render/html.js
@@@ -27,7 +27,8 @@ const time = (iso) => (iso ? html`<time datetime="${iso}">${String(iso).replace(
/** A <table> from headers and rows of cells (cells are already html or plain values). */
function table(headers, rows, { cls = '', empty = 'Nothing here yet.' } = {}) {
if (!rows.length) return html`<p class="muted">${empty}</p>`;
-- return html`<div class="table-wrap"><table class="${cls}"><thead><tr>${headers.map((h) => html`<th scope="col">${h}</th>`)}</tr></thead><tbody>${rows.map((r) => html`<tr>${r.map((c) => html`<td>${c}</td>`)}</tr>`)}</tbody></table></div>`;
++ // The wrapper scrolls sideways on a phone, so it is a focusable, named region (axe scrollable-region-focusable).
++ return html`<div class="table-wrap" tabindex="0" role="region" aria-label="Table: ${headers.join(', ')}"><table class="${cls}"><thead><tr>${headers.map((h) => html`<th scope="col">${h}</th>`)}</tr></thead><tbody>${rows.map((r) => html`<tr>${r.map((c) => html`<td>${c}</td>`)}</tr>`)}</tbody></table></div>`;
}
/** A problem as returned by Network, Events or Media — shown as it came, never softened. */
ddiff --git a/test/layout.test.js b/test/layout.test.js
iindex 0e049a2..dca81ff 100644
---- a/test/layout.test.js
++++ b/test/layout.test.js
@@@ -57,6 +57,8 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
await check('the navbar config uses the {path} login template, not the path baked in', async () => {
const r = await t.get('/harnesses');
assert.match(r.text, /"loginUrl":"\/auth\/login\?next=\{path\}"/);
++ // A table scrolls sideways on a phone: its wrapper is a focusable, named region (axe scrollable-region-focusable).
++ assert.ok(r.text.includes('<div class="table-wrap" tabindex="0" role="region" aria-label="Table: '), 'focusable table region');
assert.ok(!/"loginUrl":"\/auth\/login\?next=%2F/.test(r.text), 'loginUrl still has the current path baked in');
});