Show which filter segment or chip is selected and focused without :has() #198

Merged
lilleman merged 5 commits from item-44 into main 2026-10-31 12:29:15 +01:00
5 changed files with 44 additions and 16 deletions
Showing only changes of commit d0e1de96b8 - Show all commits
+6
View File
@@ -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,
+17
View File
@@ -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
+9 -7
View File
@@ -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;
+12
View File
@@ -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))", () => {
-9
View File
@@ -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`