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 46 additions and 16 deletions
+6
View File
@@ -6,6 +6,12 @@ at boot. Entries start at 0.3.0.
## Unreleased ## Unreleased
### A filter shows its selected and focused choice in browsers without `:has()`
On Chromium below 105, Firefox below 121 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 ### The tests fail on a stylesheet feature the browser floor lacks
`npm test` checks every stylesheet in `public/`, `examples/` and `plugins/` against Chromium 87, `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.test.ts` 1
- `release-tooling/dockerhub-overview.ts` 1 - `release-tooling/dockerhub-overview.ts` 1
- `src/ci-gate.test.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
+11 -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; } .footer-actions { display: flex; align-items: center; gap: 4px; }
.profile { .profile {
display: flex; align-items: center; gap: 9px; flex: 1 1 auto; display: flex; align-items: center; gap: 9px; flex: 1 1 auto;
@@ -485,6 +478,17 @@ span.nav-self { cursor: default; } /* static / non-clickable */
.chip:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 1px; } .chip:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 1px; }
} }
/* Without :has() every nav branch stays open and no theme rule applies, so the disclosure toggles and
the theme switch are hidden. The filter inputs are shown instead: the native control is then the
only sign of a choice or of focus, and the dot goes, since it would stay grey beside a ticked box.
This block must come 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; }
.chip .chip-dot { display: none; }
}
/* date range */ /* date range */
.daterange { .daterange {
display: inline-flex; align-items: center; gap: 6px; 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/); assert.match(block, /\.nav-disc\s*\{\s*visibility:\s*hidden/);
}); });
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"]) {
const base = stylesheet.indexOf(`${input} {`);
assert.ok(base >= 0 && base < guard, `the base ${input} rule is missing or comes after the guard`);
}
assert.match(block, /\.segmented input,\s*\.chip input\s*\{[^}]*position:\s*static[^}]*opacity:\s*1/);
assert.match(block, /\.chip \.chip-dot\s*\{\s*display:\s*none/);
});
// Without `:has()` nothing reads #nav-toggle, so an off-canvas sidebar there is unreachable and the // 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. // 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))", () => { 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 | | 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 | | 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 | | 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 | | 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 ## 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. ### 13. Accept a verification code pasted with surrounding whitespace.
A pasted code fails the browser's `pattern` check. No zero-JS fix exists: `pattern` A pasted code fails the browser's `pattern` check. No zero-JS fix exists: `pattern`