openvibers/openvibe.shared Commits

4.91.1: a numbers strip can be reached by keyboard (kit.stats) (#247)

OP OpenVibers committed 11 days ago · 564de480c7 · 6 files, +13 −4

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex abc7a82..c58e8ae 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,12 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.91.1 — 2026-11-01
++
++- **A numbers strip can be reached by keyboard** (`kit.stats`): on a phone the strip scrolls sideways, and a strip with
++ no link in it had no way in (axe `scrollable-region-focusable`, a WCAG AA sweep of every home). It carries
++ `tabindex="0"` beside its label.
++
## 4.91.0 — 2026-11-01
- **Buttons say they are working, then done or failed, with the mark** (`ov-mark.js`, which the navbar loads on every
ddiff --git a/README.md b/README.md
iindex ac00eb5..9df123b 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.91.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.91.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex e91207a..63cefbc 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.91.0",
++ "version": "4.91.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/kit.js b/kit.js
iindex 76fb37e..d7d83ce 100644
---- a/kit.js
++++ b/kit.js
@@@ -344,7 +344,9 @@
const fmt = (v) => (typeof v === 'number' ? v.toLocaleString('en-US') : String(v));
const shown = items.filter((x) => x && x[0] != null && x[0] !== '' && x[0] !== 0);
if (!shown.length) return '';
-- return `<dl class="ovk-stats" aria-label="${esc(label)}">${shown.map(([v, l, href]) => `<div${href ? ' class="ovk-stat--link"' : ''}><dt>${href ? `<a href="${esc(url(href))}">${esc(l)}</a>` : esc(l)}</dt><dd>${esc(fmt(v))}</dd></div>`).join('')}</dl>`;
++ // tabindex: on a phone the row scrolls sideways, and a row with no link in it had no way in by keyboard
++ // (axe scrollable-region-focusable, 2026-11-01 sweep).
++ return `<dl class="ovk-stats" aria-label="${esc(label)}" tabindex="0">${shown.map(([v, l, href]) => `<div${href ? ' class="ovk-stat--link"' : ''}><dt>${href ? `<a href="${esc(url(href))}">${esc(l)}</a>` : esc(l)}</dt><dd>${esc(fmt(v))}</dd></div>`).join('')}</dl>`;
}
/** What an empty list says: a title, one line, and the one thing to do next. */
const empty = ({ title = '', text = '', action: act = null, icon: ic = '' } = {}) => `<div class="ovk-empty">${ic ? icon(ic) : ''}${title ? `<p class="ovk-empty-title">${esc(title)}</p>` : ''}${text ? `<p>${esc(text)}</p>` : ''}${act ? button({ href: act.href, label: act.label, kind: act.kind || 'primary', icon: act.icon || '' }) : ''}</div>`;
ddiff --git a/package.json b/package.json
iindex 3b9f6a2..8149511 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.91.0",
++ "version": "4.91.1",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/test/kit.test.js b/test/kit.test.js
iindex e0966f0..653f974 100644
---- a/test/kit.test.js
++++ b/test/kit.test.js
@@@ -77,6 +77,7 @@ const kit = require('../kit');
const st = kit.stats([[1234, 'messages'], [0, 'hidden'], [null, 'unknown'], ['12k', 'views']]);
assert.ok(st.includes('<dd>1,234</dd>') && st.includes('<dd>12k</dd>') && !st.includes('hidden') && !st.includes('unknown'));
assert.strictEqual(kit.stats([[0, 'none']]), '', 'nothing to show, no strip');
++ assert.ok(st.startsWith('<dl class="ovk-stats" aria-label="In numbers" tabindex="0">'), 'the strip scrolls sideways on a phone: it can be reached by keyboard');
assert.ok(kit.notice({ kind: 'error', text: 'No' }).includes('role="alert"'));
assert.ok(kit.notice({ kind: 'ok', text: 'Saved' }).includes('role="status"'));
assert.ok(kit.empty({ title: 'No rooms', text: 'Start one', action: { href: '/rooms?new=1', label: 'Start a room' } }).includes('href="/rooms?new=1"'));