openvibers/openvibe.shared Commits

4.109.1: data-ov-select="bare" for selects inside chips and toolbars (#286)

OP OpenVibers committed 10 days ago · c2992e069e · 10 files, +29 −9

The chip keeps its look (no box, chevron or badge of the select's own); only the list is
OpenVibe's. Codes' composer chips use it.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 4439591..cce6e52 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.109.1 — 2026-11-03
++
++- **`data-ov-select="bare"`: a select inside a chip or a toolbar keeps that look.** It gets no box, chevron or badge of
++ its own, and only its list (the menu, or the phone sheet) is OpenVibe's. Codes' composer chips use it.
++ `test/ov-forms-chrome.test.js`.
++
## 4.109.0 — 2026-11-03
- **OpenVibe's own select and form controls, on every site (`ov-forms.js`, `ov-forms.css`, docs/forms.md).** The owner
ddiff --git a/README.md b/README.md
iindex 23d8e1d..3cb3a31 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.109.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.109.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 d69cdcf..ee4ce59 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.109.0",
++ "version": "4.109.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/docs/forms.md b/docs/forms.md
iindex effc2c9..141bd7c 100644
---- a/docs/forms.md
++++ b/docs/forms.md
@@@ -51,6 +51,7 @@ None of these change a size: every rule is `:where()`, so a site's own rule alwa
| `data-placeholder` | the button's words when nothing is chosen (a multiple select) |
| `multiple` | rows with boxes; the list stays open, and the button says "a, c" or "3 chosen" |
| `size="5"` | stays the browser's list box: it is already a list on the page |
++| `data-ov-select="bare"` | no box, chevron or badge of its own: for a select inside a chip or a toolbar (4.109.1) |
| `data-ov-native` | stays the browser's own (also on any ancestor) |
Groups (`<optgroup>`) are headings in the list. A `required` select left empty marks its button and takes the focus:
ddiff --git a/navbar.js b/navbar.js
iindex ed037c7..9526593 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -491,7 +491,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
-- const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '2c505c7b96a3', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': 'd1ce994449ca', 'ov-forms.css': 'c99414a7ab49' };
++ const SIBLING_REV = { 'ov-mark.js': '1e91c9c0f347', 'ov-glyphs.js': '2c505c7b96a3', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2a6c53a7b176', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '80d19bb69142', 'ov-forms.css': '8b2504444998' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
ddiff --git a/ov-forms.css b/ov-forms.css
iindex 49a7f56..7abb39b 100644
---- a/ov-forms.css
++++ b/ov-forms.css
@@@ -40,6 +40,12 @@
.ov-sel-chev svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: transform .18s ease; }
.ov-sel[aria-expanded="true"] .ov-sel-chev svg { transform: rotate(180deg); }
.ov-sel .ov-sel-badge { margin-inline-start: auto; }
++/* data-ov-select="bare": the chip or toolbar around it is the box (4.109.1); the list is still OpenVibe's. */
++.ov-sel.is-bare { min-height: 0; padding: 0; gap: 6px; border: 0; border-radius: 4px; background: none; color: inherit; font: inherit; box-shadow: none; }
++.ov-sel.is-bare .ov-sel-chev, .ov-sel.is-bare .ov-sel-badge { display: none; }
++.ov-sel.is-bare:hover:not(:disabled) { border-color: transparent; }
++.ov-sel.is-bare:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--ovf-accent) 45%, transparent); }
++.ov-sel.is-bare[aria-expanded="true"] { box-shadow: none; }
.ov-sel-native {
position: absolute !important; width: 1px !important; height: 1px !important; min-width: 0 !important; min-height: 0 !important;
margin: 0 !important; padding: 0 !important; border: 0 !important; opacity: 0 !important; pointer-events: none !important;
ddiff --git a/ov-forms.js b/ov-forms.js
iindex 05eff2e..9048715 100644
---- a/ov-forms.js
++++ b/ov-forms.js
@@@ -18,6 +18,8 @@
* data-ov-native keep the browser's own select (also on any ancestor, or <html data-ov-forms="off">)
* data-ov-search="on|off" a search box (on by itself from 9 choices)
* data-ov-title="…" the phone sheet's title (else the select's label)
++ * data-ov-select="bare" no box of its own and no chevron: a select inside a chip or a toolbar keeps that look,
++ * and only its list is OpenVibe's (4.109.1)
*
* OVForms.enhance(root) every select under root (the navbar calls it once the page settles, and for new markup)
* OVForms.refresh(select) read the select again (its options changed in a way the page did not announce)
@@@ -107,11 +109,13 @@
'grid-column', 'grid-row', 'order', 'max-width', 'vertical-align', 'font-size', 'border-radius'];
for (const p of keep) { const v = cs.getPropertyValue(p); if (v && v !== 'auto' && v !== 'normal' && v !== '0px' && v !== 'none') btn.style.setProperty(p, v); }
if (cs.display === 'block' || cs.display === 'flex' || cs.display === 'grid') btn.classList.add('is-block');
++ const bare = s.dataset.ovSelect === 'bare';
++ if (bare) btn.classList.add('is-bare');
const pw = parent ? parent.clientWidth - parseFloat(root.getComputedStyle(parent).paddingLeft || 0) - parseFloat(root.getComputedStyle(parent).paddingRight || 0) : 0;
-- if (s.offsetWidth && pw && s.offsetWidth >= pw - 1) btn.classList.add('is-full');
-- else if (s.offsetWidth) btn.style.minWidth = `${s.offsetWidth}px`;
++ if (s.offsetWidth && pw && s.offsetWidth >= pw - 1 && !bare) btn.classList.add('is-full');
++ else if (s.offsetWidth && !bare) btn.style.minWidth = `${s.offsetWidth}px`;
// Inside an .ov-field the field's own height rules (44 px, the same as its text inputs); elsewhere the select's.
-- if (s.offsetHeight && !s.closest('.ov-field')) btn.style.minHeight = `${s.offsetHeight}px`;
++ if (s.offsetHeight && !s.closest('.ov-field') && !bare) btn.style.minHeight = `${s.offsetHeight}px`;
if (s.className) btn.dataset.from = s.className.slice(0, 120);
s.insertAdjacentElement('afterend', btn);
s.classList.add('ov-sel-native');
ddiff --git a/package-lock.json b/package-lock.json
iindex ae69b6e..bbd327d 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "4.109.0",
++ "version": "4.109.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "4.109.0",
++ "version": "4.109.1",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex 3b2217d..4c41ba0 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.109.0",
++ "version": "4.109.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/ov-forms-chrome.test.js b/test/ov-forms-chrome.test.js
iindex 7d47830..0e05a4b 100644
---- a/test/ov-forms-chrome.test.js
++++ b/test/ov-forms-chrome.test.js
@@@ -30,6 +30,7 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
<label>Many <select id="many" name="many" multiple><option>a</option><option>b</option><option>c</option></select></label>
<label>Long <select id="long" name="long"><optgroup label="Small">${LONG.slice(0, 6).map((x) => `<option>${x}</option>`).join('')}</optgroup><optgroup label="Big">${LONG.slice(6).map((x) => `<option>${x}</option>`).join('')}</optgroup></select></label>
<span data-ov-native><select id="raw" name="raw"><option>x</option></select></span>
++<span class="chip" style="display:inline-flex;gap:4px;border:1px solid #888;border-radius:99px;padding:4px 10px">● <select id="bare" name="bare" data-ov-select="bare"><option>Quick</option><option>Deep</option></select> ▾</span>
<label><input type="checkbox" role="switch" id="sw"> A switch</label>
<button id="send">Send</button>
</form>
@@@ -64,6 +65,8 @@ const key = (k, target = 'document.activeElement') => `(${target}).dispatchEvent
assert.strictEqual(await p.evaluate("document.getElementById('agent').classList.contains('ov-sel-native')"), true, 'the select is enhanced');
assert.strictEqual(await p.evaluate(`${btn}.textContent.trim()`), 'Choose…');
assert.strictEqual(await p.evaluate("!!document.querySelector('#raw + .ov-sel')"), false, 'data-ov-native keeps the browser\'s own');
++ assert.deepStrictEqual(await p.evaluate("(() => { const b = document.querySelector('#bare + .ov-sel'); const cs = getComputedStyle(b); return [b.classList.contains('is-bare'), cs.borderTopWidth, cs.backgroundColor, getComputedStyle(b.querySelector('.ov-sel-chev')).display]; })()"),
++ [true, '0px', 'rgba(0, 0, 0, 0)', 'none'], 'a bare select keeps its chip\'s look: no box, no chevron');
assert.strictEqual(await p.evaluate(`${btn}.getAttribute('aria-labelledby').split(' ').some((id) => document.getElementById(id) && document.getElementById(id).tagName === 'LABEL')`), true, 'the button is named by the select\'s label');
assert.strictEqual(await p.evaluate(`${btn}.getBoundingClientRect().width === document.getElementById('f').getBoundingClientRect().width`), true, 'a full-width select gives a full-width button');