openvibers/openvibe.shared Commits
4.91.1: a numbers strip can be reached by keyboard (kit.stats) (#247)
Changes
@@@ -4,6 +4,12 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking 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@@@ -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 deployedconsumer 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` namesthe tag it pins.@@@ -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)",@@@ -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>`;@@@ -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": {@@@ -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"'));
@@ -4,6 +4,12 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -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.
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).
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
## 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
- **Buttons say they are working, then done or failed, with the mark** (`ov-mark.js`, which the navbar loads on every
@@ -2,7 +2,7 @@
@@ -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.
> 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
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
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.91.0",
+ "version": "4.91.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"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)",
"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)",
@@ -344,7 +344,9 @@
@@ -344,7 +344,9 @@
const fmt = (v) => (typeof v === 'number' ? v.toLocaleString('en-US') : String(v));
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);
const shown = items.filter((x) => x && x[0] != null && x[0] !== '' && x[0] !== 0);
if (!shown.length) return '';
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. */
/** 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>`;
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>`;
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -77,6 +77,7 @@ const kit = require('../kit');
@@ -77,6 +77,7 @@ const kit = require('../kit');
const st = kit.stats([[1234, 'messages'], [0, 'hidden'], [null, 'unknown'], ['12k', 'views']]);
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.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.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: 'error', text: 'No' }).includes('role="alert"'));
assert.ok(kit.notice({ kind: 'ok', text: 'Saved' }).includes('role="status"'));
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"'));
assert.ok(kit.empty({ title: 'No rooms', text: 'Start one', action: { href: '/rooms?new=1', label: 'Start a room' } }).includes('href="/rooms?new=1"'));