openvibers/openvibe.shared Commits
4.109.1: data-ov-select="bare" for selects inside chips and toolbars (#286)
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.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@@@ -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 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.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)",@@@ -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:@@@ -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">.@@@ -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;@@@ -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');@@@ -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",@@@ -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": {@@@ -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}).dispatchEventassert.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');
@@ -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.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
## 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
- **OpenVibe's own select and form controls, on every site (`ov-forms.js`, `ov-forms.css`, docs/forms.md).** The owner
@@ -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.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
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.109.0",
+ "version": "4.109.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)",
@@ -51,6 +51,7 @@ None of these change a size: every rule is `:where()`, so a site's own rule alwa
@@ -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) |
| `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" |
| `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 |
| `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) |
| `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:
Groups (`<optgroup>`) are headings in the list. A `required` select left empty marks its button and takes the focus:
@@ -491,7 +491,7 @@
@@ -491,7 +491,7 @@
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = 'd9a4febbbad7'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
// 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
// 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
// (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">.
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@ -40,6 +40,12 @@
@@ -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-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[aria-expanded="true"] .ov-sel-chev svg { transform: rotate(180deg); }
.ov-sel .ov-sel-badge { margin-inline-start: auto; }
.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 {
.ov-sel-native {
position: absolute !important; width: 1px !important; height: 1px !important; min-width: 0 !important; min-height: 0 !important;
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;
margin: 0 !important; padding: 0 !important; border: 0 !important; opacity: 0 !important; pointer-events: none !important;
@@ -18,6 +18,8 @@
@@ -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-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-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-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.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)
* OVForms.refresh(select) read the select again (its options changed in a way the page did not announce)
@@ -107,11 +109,13 @@
@@ -107,11 +109,13 @@
'grid-column', 'grid-row', 'order', 'max-width', 'vertical-align', 'font-size', 'border-radius'];
'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); }
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');
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;
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.
// 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);
if (s.className) btn.dataset.from = s.className.slice(0, 120);
s.insertAdjacentElement('afterend', btn);
s.insertAdjacentElement('afterend', btn);
s.classList.add('ov-sel-native');
s.classList.add('ov-sel-native');
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.109.0",
+ "version": "4.109.1",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.109.0",
+ "version": "4.109.1",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -30,6 +30,7 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
@@ -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>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>
<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 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>
<label><input type="checkbox" role="switch" id="sw"> A switch</label>
<button id="send">Send</button>
<button id="send">Send</button>
</form>
</form>
@@ -64,6 +65,8 @@ const key = (k, target = 'document.activeElement') => `(${target}).dispatchEvent
@@ -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("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(`${btn}.textContent.trim()`), 'Choose…');
assert.strictEqual(await p.evaluate("!!document.querySelector('#raw + .ov-sel')"), false, 'data-ov-native keeps the browser\'s own');
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}.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');
assert.strictEqual(await p.evaluate(`${btn}.getBoundingClientRect().width === document.getElementById('f').getBoundingClientRect().width`), true, 'a full-width select gives a full-width button');