From d0e1de96b88fbdaa1dc12f0fe5316719e723adf7 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 12:19:21 +0100 Subject: [PATCH] Show which filter segment or chip is selected and focused without :has() --- CHANGELOG.md | 6 ++++++ docs/reading-cost.md | 17 +++++++++++++++++ public/css/styles.css | 16 +++++++++------- src/ui/stylesheet-floor.test.ts | 12 ++++++++++++ todo.md | 9 --------- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c005e02..f8ba92b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ at boot. Entries start at 0.3.0. ## Unreleased +### A filter shows its selected and focused choice in browsers without `:has()` + +On Firefox 115, Chromium below 105 and Safari below 15.4, a filter's segments and chips now show +their native radio or checkbox, which marks the selected choice and takes the keyboard focus ring. +Before, they showed neither. Newer browsers look as before. + ### The tests fail on a stylesheet feature the browser floor lacks `npm test` checks every stylesheet in `public/`, `examples/` and `plugins/` against Chromium 87, diff --git a/docs/reading-cost.md b/docs/reading-cost.md index 853a87c..15a4500 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -226,3 +226,20 @@ Read most (opens and searches): - `release-tooling/dockerhub-overview.test.ts` 1 - `release-tooling/dockerhub-overview.ts` 1 - `src/ci-gate.test.ts` 1 + +## Reading cost, chunk 984f4d4c8a76, finished 2026-10-31T11:18:20Z + +209334 new input tokens before the first commit. + +Read most (opens and searches): + +- `README.md` 6 +- `todo.md` 6 +- `src/ui/stylesheet-floor.test.ts` 4 +- `src/ui/stylesheet-floor.ts` 4 +- `CHANGELOG.md` 3 +- `AGENTS.md` 2 +- `examples/plugins/scheduling/public/scheduling.css` 2 +- `package.json` 2 +- `public/css/styles.css` 2 +- `ci.sh` 1 diff --git a/public/css/styles.css b/public/css/styles.css index 32cdef3..3ee1871 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -294,13 +294,6 @@ span.nav-self { cursor: default; } /* static / non-clickable */ } } -/* Without :has() every branch is open and every theme rule is inert: neither control does anything. - After the base rules, since @supports adds no specificity. */ -@supports not selector(:has(a)) { - .nav-disc { visibility: hidden; } /* keeps its width, so the labels stay aligned */ - .theme-switch { display: none; } -} - .footer-actions { display: flex; align-items: center; gap: 4px; } .profile { display: flex; align-items: center; gap: 9px; flex: 1 1 auto; @@ -485,6 +478,15 @@ span.nav-self { cursor: default; } /* static / non-clickable */ .chip:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 1px; } } +/* Without :has() every branch is open and every theme rule is inert: neither control does anything. + The filter inputs stay visible, since the native control is then the only sign of a choice or of + focus. After the base rules, since @supports adds no specificity. */ +@supports not selector(:has(a)) { + .nav-disc { visibility: hidden; } /* keeps its width, so the labels stay aligned */ + .theme-switch { display: none; } + .segmented input, .chip input { position: static; opacity: 1; margin: 0; } +} + /* date range */ .daterange { display: inline-flex; align-items: center; gap: 6px; diff --git a/src/ui/stylesheet-floor.test.ts b/src/ui/stylesheet-floor.test.ts index df44fc4..8358043 100644 --- a/src/ui/stylesheet-floor.test.ts +++ b/src/ui/stylesheet-floor.test.ts @@ -153,6 +153,18 @@ test("styles.css hides the theme switch and the nav toggles where :has() is miss assert.match(block, /\.nav-disc\s*\{\s*visibility:\s*hidden/); }); +// Both inputs are invisible in the base rules and only a `:has()` rule marks the choice, so without +// `:has()` the native control is the only sign of what is selected or focused. +test("styles.css shows the segmented and chip inputs where :has() is missing, after their base rules", () => { + const guard = stylesheet.indexOf("@supports not selector(:has(a)) {"); + assert.ok(guard >= 0, "no @supports not selector(:has(a)) block"); + const block = stylesheet.slice(guard, blockEnd(guard)); + for (const input of [".segmented input", ".chip input"]) { + assert.ok(stylesheet.indexOf(`${input} {`) < guard, `the base ${input} rule comes after the guard`); + } + assert.match(block, /\.segmented input,\s*\.chip input\s*\{[^}]*position:\s*static[^}]*opacity:\s*1/); +}); + // Without `:has()` nothing reads #nav-toggle, so an off-canvas sidebar there is unreachable and the // hamburger does nothing: the sidebar has to stay in the flow, with the hamburger hidden. test("styles.css moves the sidebar off-canvas at 860px only inside @supports selector(:has(a))", () => { diff --git a/todo.md b/todo.md index fec2e7c..8022d7f 100644 --- a/todo.md +++ b/todo.md @@ -12,7 +12,6 @@ | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| -| 44 | 0.4.21 | | defect | **Show which filter segment or chip is selected and focused on a browser without `:has()`.** | 1 | 2 | 2 | 3 | 2 | 4 by 4 | 23.9 | | 13 | 0.4.21 | | | **Accept a verification code pasted with surrounding whitespace.** | 4 | 2 | 3 | 7 | 7 | 2 by 8 | 1.0 | | 19 | 0.5.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | | 16 | 0.5.0 | | question | **Decide whether the single Keto `Resource` namespace splits into per-domain namespaces.** | 3 | 2 | 3 | 6 | 6 | 7 by 2 | 0.5 | @@ -27,14 +26,6 @@ ## Details -### 44. Show which filter segment or chip is selected and focused on a browser without `:has()`. - -`.segmented input` and `.chip input` are `opacity: 0`, and only `:has(input:checked)` and -`:has(input:focus-visible)` rules mark the choice. On Firefox 115, Chromium 87–104 and Safari -15.0–15.3 a filter shows no selected state and no keyboard focus, which README → "Two browser -floors" forbids. Show the native inputs in `public/css/styles.css`'s `@supports not selector(:has(a))` -block. - ### 13. Accept a verification code pasted with surrounding whitespace. A pasted code fails the browser's `pattern` check. No zero-JS fix exists: `pattern`