@ponchia/ui 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +3 -3
- package/classes/classes.json +26 -3
- package/classes/index.d.ts +8 -0
- package/classes/index.js +8 -0
- package/css/core.css +1 -0
- package/css/overlay.css +24 -10
- package/css/row.css +154 -0
- package/css/workbench.css +1 -1
- package/dist/bronto.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/row.css +1 -0
- package/dist/css/workbench.css +1 -1
- package/docs/migrations/0.8-to-0.9.md +66 -0
- package/docs/package-contract.md +4 -1
- package/docs/reference.md +14 -1
- package/docs/reporting.md +8 -8
- package/docs/stability.md +2 -1
- package/docs/theming.md +16 -0
- package/docs/usage.md +42 -0
- package/llms.txt +1 -1
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,86 @@
|
|
|
5
5
|
|> `^0` / `*` wildcard does **not** protect you. See README → Versioning, and
|
|
6
6
|
|> the deprecation policy in CONTRIBUTING.md.
|
|
7
7
|
|
|
8
|
+
## 0.9.0 — 2026-08-11
|
|
9
|
+
|
|
10
|
+
Two additions and one correction, all from the same source: migrating a
|
|
11
|
+
downstream workbench onto 0.8.1 and watching where adoption stalled.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`.ui-row` — the dense selectable row, in the default bundle.** Every
|
|
16
|
+
workbench grows a list of these: a search result, a file in an explorer, an
|
|
17
|
+
outline entry, a backlink, a commit. Bronto already had the shape and it was
|
|
18
|
+
imprisoned — `.ui-menu__item` was exactly it, minus the persistence — so a
|
|
19
|
+
consumer building an explorer either claimed to be a menu or wrote the row
|
|
20
|
+
again. The one that migrated had **63 families of it, about 1,200 lines**,
|
|
21
|
+
each re-deciding the selected state, the hover, the ellipsis and the coarse
|
|
22
|
+
floor.
|
|
23
|
+
|
|
24
|
+
`__title` is the part that truncates and `__meta` is the part that does not;
|
|
25
|
+
that asymmetry is the contract. Selection reads `aria-selected` /
|
|
26
|
+
`aria-current` — the attributes a host already sets for assistive tech — so
|
|
27
|
+
the visual state cannot disagree with the announced one. `--stacked` for a
|
|
28
|
+
row with a snippet, `--ruled` for a list that reads as one object.
|
|
29
|
+
|
|
30
|
+
Selection: use **`aria-current`** for a row that is merely the current one —
|
|
31
|
+
that is the common case and valid anywhere. `aria-selected` is only legal on a
|
|
32
|
+
role that accepts it (`option` in a `listbox`, or `row`/`tab`/`gridcell`/
|
|
33
|
+
`treeitem`); on a bare `<button>` it is invalid ARIA and axe rates it
|
|
34
|
+
*critical*. This release's own demo shipped that mistake, three a11y specs
|
|
35
|
+
went red, and a new unit gate now catches the same class of error in
|
|
36
|
+
milliseconds rather than twenty minutes of browser matrix.
|
|
37
|
+
|
|
38
|
+
`min-inline-size: 0` is on the row itself and is not optional: a row is
|
|
39
|
+
usually a flex or grid item, whose automatic minimum is its min-content width,
|
|
40
|
+
and `__title` is `white-space: nowrap` — so without it a long title stops the
|
|
41
|
+
row shrinking and pushes its container past the viewport instead of
|
|
42
|
+
truncating. The demo proved that at 320/360/390px before release.
|
|
43
|
+
|
|
44
|
+
It is in **core**, unusually for a new surface, because `.ui-menu__item` now
|
|
45
|
+
composes it and a core component cannot depend on an opt-in leaf. It also
|
|
46
|
+
earns the roadmap's stronger argument for a core addition on two counts:
|
|
47
|
+
universal application chrome, and it *reduces duplicated core markup*. A unit
|
|
48
|
+
proof asserts the menu item does not restate what the row says, so the two
|
|
49
|
+
cannot drift back apart.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **`.ui-menu` no longer welds placement into the surface.** It used to declare
|
|
54
|
+
`position: absolute` plus a trigger-relative offset, so a menu opened at a
|
|
55
|
+
pointer — a canvas context menu, a long-press — could not use it at all, and
|
|
56
|
+
consumers re-declared the panel, border, radius and shadow to get a surface.
|
|
57
|
+
The dropdown placement is now `--dropdown` (unchanged behaviour, opted into)
|
|
58
|
+
and `--at-pointer` takes a menu out of flow for a host that computes its own
|
|
59
|
+
position.
|
|
60
|
+
|
|
61
|
+
**Migration:** add `ui-menu--dropdown` to an existing `ui-menu` inside a
|
|
62
|
+
`ui-menu-host`. Without it the surface renders in flow.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **`data-density` claimed more than it does, and the guard could not tell.**
|
|
67
|
+
`docs/theming.md` said the preset applies "on any element". Measured: it
|
|
68
|
+
re-points the `--space-*` scale, so it reaches the ~40 components whose
|
|
69
|
+
padding is expressed in that scale and **none** of the rest — `ui-alert` and
|
|
70
|
+
`ui-menu__item` among them. The remaining paddings are tuned pairs like
|
|
71
|
+
`0.5rem 0.55rem` that a seven-step scale cannot express, so tokenising them
|
|
72
|
+
would change how they look at the default density; exactly one declaration in
|
|
73
|
+
the whole catalog mapped cleanly and was converted.
|
|
74
|
+
|
|
75
|
+
The docs now say which components respond and why the others cannot, and a
|
|
76
|
+
test pins the responding set. The previous guard checked that the preset
|
|
77
|
+
changed the token *family*, which is true and useless — it would have passed
|
|
78
|
+
with every component hardcoded. That is the third contract in three releases
|
|
79
|
+
whose gate validated the mechanism instead of the effect, after the 43.5px tap
|
|
80
|
+
floor and the shipped-docs list.
|
|
81
|
+
|
|
82
|
+
### Notes
|
|
83
|
+
|
|
84
|
+
- Bundle budget raised to 95,600 B / 16,400 B for `.ui-row` (+1,234 B raw /
|
|
85
|
+
+83 B gzip). The gzip cost is small because the new rules compress against the
|
|
86
|
+
menu rules they replaced.
|
|
87
|
+
|
|
8
88
|
## 0.8.1 — 2026-08-11
|
|
9
89
|
|
|
10
90
|
A packaging fix, plus dependency hygiene. **No published CSS, token, class, or
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@ponchia/ui)
|
|
4
4
|
[](https://www.npmjs.com/package/@ponchia/ui#provenance)
|
|
5
5
|
[](https://github.com/Ponchia/bronto-ui/blob/main/package.json)
|
|
6
|
-
[](https://github.com/Ponchia/bronto-ui/blob/main/scripts/check-dist.mjs)
|
|
7
7
|
[](https://github.com/Ponchia/bronto-ui/actions/workflows/ci.yml)
|
|
8
8
|
[](https://scorecard.dev/viewer/?uri=github.com/Ponchia/bronto-ui)
|
|
9
9
|
[](https://github.com/Ponchia/bronto-ui/blob/main/LICENSE)
|
|
@@ -81,12 +81,12 @@ Or drop it in with no build step, straight from a CDN (replace the version only
|
|
|
81
81
|
when deliberately upgrading across a breaking pre-1.0 minor):
|
|
82
82
|
|
|
83
83
|
```html
|
|
84
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.
|
|
84
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.9.0/dist/bronto.css">
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
## Quick start
|
|
88
88
|
|
|
89
|
-
**1. Load the CSS.** One flattened, minified default CSS bundle — the standard component set, one request (~
|
|
89
|
+
**1. Load the CSS.** One flattened, minified default CSS bundle — the standard component set, one request (~93 kB raw / ~16 kB gzip) — that is `dist/bronto.css`, not the whole package tarball:
|
|
90
90
|
|
|
91
91
|
```css
|
|
92
92
|
@import '@ponchia/ui'; /* via a bundler */
|
package/classes/classes.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "@ponchia/ui class vocabulary as language-neutral data — validate emitted markup without executing the ESM cls map or parsing the .d.ts. Generated from classes/index.js — do not edit by hand; run `npm run classes:json:build`. Drift-checked in CI. `groups[].base` is null for a parts-only namespace (no standalone base class — do NOT emit it). A modifier whose name contains `__` (e.g. `ui-spark__bar--pos`) attaches to THAT part, not the base host. `states` is the author-applied `is-*` hooks (runtime/behavior-managed hooks are excluded); `behaviorAttributes` are the `data-bronto-*` wiring hooks the optional behaviors delegate on; `requiredAria` is the role/aria a generator must emit per component. `states` + `customProperties` are documented in docs/reference.md and ship outside `cls` by design.",
|
|
3
3
|
"counts": {
|
|
4
|
-
"classes":
|
|
5
|
-
"groups":
|
|
4
|
+
"classes": 677,
|
|
5
|
+
"groups": 184
|
|
6
6
|
},
|
|
7
7
|
"groups": {
|
|
8
8
|
"ui-accordion": {
|
|
@@ -830,7 +830,10 @@
|
|
|
830
830
|
},
|
|
831
831
|
"ui-menu": {
|
|
832
832
|
"base": "ui-menu",
|
|
833
|
-
"modifiers": [
|
|
833
|
+
"modifiers": [
|
|
834
|
+
"ui-menu--at-pointer",
|
|
835
|
+
"ui-menu--dropdown"
|
|
836
|
+
],
|
|
834
837
|
"parts": [
|
|
835
838
|
"ui-menu__item",
|
|
836
839
|
"ui-menu__label",
|
|
@@ -1074,6 +1077,18 @@
|
|
|
1074
1077
|
"modifiers": [],
|
|
1075
1078
|
"parts": []
|
|
1076
1079
|
},
|
|
1080
|
+
"ui-row": {
|
|
1081
|
+
"base": "ui-row",
|
|
1082
|
+
"modifiers": [
|
|
1083
|
+
"ui-row--ruled",
|
|
1084
|
+
"ui-row--stacked"
|
|
1085
|
+
],
|
|
1086
|
+
"parts": [
|
|
1087
|
+
"ui-row__mark",
|
|
1088
|
+
"ui-row__meta",
|
|
1089
|
+
"ui-row__title"
|
|
1090
|
+
]
|
|
1091
|
+
},
|
|
1077
1092
|
"ui-screen-only": {
|
|
1078
1093
|
"base": "ui-screen-only",
|
|
1079
1094
|
"modifiers": [],
|
|
@@ -1924,6 +1939,8 @@
|
|
|
1924
1939
|
"ui-mark--warning",
|
|
1925
1940
|
"ui-matrix",
|
|
1926
1941
|
"ui-menu",
|
|
1942
|
+
"ui-menu--at-pointer",
|
|
1943
|
+
"ui-menu--dropdown",
|
|
1927
1944
|
"ui-menu-host",
|
|
1928
1945
|
"ui-menu__item",
|
|
1929
1946
|
"ui-menu__label",
|
|
@@ -2039,6 +2056,12 @@
|
|
|
2039
2056
|
"ui-report__title",
|
|
2040
2057
|
"ui-report__toc",
|
|
2041
2058
|
"ui-reveal",
|
|
2059
|
+
"ui-row",
|
|
2060
|
+
"ui-row--ruled",
|
|
2061
|
+
"ui-row--stacked",
|
|
2062
|
+
"ui-row__mark",
|
|
2063
|
+
"ui-row__meta",
|
|
2064
|
+
"ui-row__title",
|
|
2042
2065
|
"ui-screen-only",
|
|
2043
2066
|
"ui-scroll-progress",
|
|
2044
2067
|
"ui-scroll-reveal",
|
package/classes/index.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ export declare const cls: {
|
|
|
55
55
|
readonly linkArrow: 'ui-link--arrow';
|
|
56
56
|
readonly linkCta: 'ui-link--cta';
|
|
57
57
|
readonly keyValue: 'ui-key-value';
|
|
58
|
+
readonly row: 'ui-row';
|
|
59
|
+
readonly rowTitle: 'ui-row__title';
|
|
60
|
+
readonly rowMeta: 'ui-row__meta';
|
|
61
|
+
readonly rowMark: 'ui-row__mark';
|
|
62
|
+
readonly rowStacked: 'ui-row--stacked';
|
|
63
|
+
readonly rowRuled: 'ui-row--ruled';
|
|
58
64
|
readonly emptyState: 'ui-empty-state';
|
|
59
65
|
readonly emptyStateInvite: 'ui-empty-state--invite';
|
|
60
66
|
readonly emptyStateGlyph: 'ui-empty-state__glyph';
|
|
@@ -164,6 +170,8 @@ export declare const cls: {
|
|
|
164
170
|
readonly modalDrawer: 'ui-modal--drawer';
|
|
165
171
|
readonly menuHost: 'ui-menu-host';
|
|
166
172
|
readonly menu: 'ui-menu';
|
|
173
|
+
readonly menuDropdown: 'ui-menu--dropdown';
|
|
174
|
+
readonly menuAtPointer: 'ui-menu--at-pointer';
|
|
167
175
|
readonly menuLabel: 'ui-menu__label';
|
|
168
176
|
readonly menuItem: 'ui-menu__item';
|
|
169
177
|
readonly menuSep: 'ui-menu__sep';
|
package/classes/index.js
CHANGED
|
@@ -59,6 +59,12 @@ export const cls = Object.freeze({
|
|
|
59
59
|
linkArrow: 'ui-link--arrow',
|
|
60
60
|
linkCta: 'ui-link--cta',
|
|
61
61
|
keyValue: 'ui-key-value',
|
|
62
|
+
row: 'ui-row',
|
|
63
|
+
rowTitle: 'ui-row__title',
|
|
64
|
+
rowMeta: 'ui-row__meta',
|
|
65
|
+
rowMark: 'ui-row__mark',
|
|
66
|
+
rowStacked: 'ui-row--stacked',
|
|
67
|
+
rowRuled: 'ui-row--ruled',
|
|
62
68
|
emptyState: 'ui-empty-state',
|
|
63
69
|
emptyStateInvite: 'ui-empty-state--invite',
|
|
64
70
|
emptyStateGlyph: 'ui-empty-state__glyph',
|
|
@@ -173,6 +179,8 @@ export const cls = Object.freeze({
|
|
|
173
179
|
modalDrawer: 'ui-modal--drawer',
|
|
174
180
|
menuHost: 'ui-menu-host',
|
|
175
181
|
menu: 'ui-menu',
|
|
182
|
+
menuDropdown: 'ui-menu--dropdown',
|
|
183
|
+
menuAtPointer: 'ui-menu--at-pointer',
|
|
176
184
|
menuLabel: 'ui-menu__label',
|
|
177
185
|
menuItem: 'ui-menu__item',
|
|
178
186
|
menuSep: 'ui-menu__sep',
|
package/css/core.css
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
@import url('./site.css') layer(bronto);
|
|
19
19
|
@import url('./content.css') layer(bronto);
|
|
20
20
|
@import url('./primitives.css') layer(bronto);
|
|
21
|
+
@import url('./row.css') layer(bronto);
|
|
21
22
|
@import url('./forms.css') layer(bronto);
|
|
22
23
|
@import url('./feedback.css') layer(bronto);
|
|
23
24
|
@import url('./overlay.css') layer(bronto);
|
package/css/overlay.css
CHANGED
|
@@ -247,6 +247,12 @@ html:has(.ui-modal.is-open) {
|
|
|
247
247
|
position: relative;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/* The SURFACE only. Placement is a separate decision, because a menu is not
|
|
251
|
+
always a dropdown: a canvas context menu opens at the pointer, a mobile one
|
|
252
|
+
arrives as a sheet. This rule used to weld `position: absolute` plus a
|
|
253
|
+
trigger-relative offset into the surface itself, so a consumer with a
|
|
254
|
+
pointer-positioned menu could not use it at all and re-declared the panel,
|
|
255
|
+
the border, the radius and the shadow to get one. */
|
|
250
256
|
.ui-menu {
|
|
251
257
|
background: var(--panel-strong);
|
|
252
258
|
border: 1px solid var(--line-strong);
|
|
@@ -256,12 +262,25 @@ html:has(.ui-modal.is-open) {
|
|
|
256
262
|
gap: 1px;
|
|
257
263
|
min-inline-size: 11rem;
|
|
258
264
|
padding: 0.3rem;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* The dropdown placement this class always had: anchored under its trigger
|
|
268
|
+
inside a `.ui-menu-host`. Unchanged behaviour, now opted into. */
|
|
269
|
+
.ui-menu--dropdown {
|
|
259
270
|
position: absolute;
|
|
260
271
|
inset-inline-end: 0;
|
|
261
272
|
inset-block-start: calc(100% + 0.4rem);
|
|
262
273
|
z-index: var(--z-overlay);
|
|
263
274
|
}
|
|
264
275
|
|
|
276
|
+
/* Opened at a point the host computes — a right-click, a long-press. The host
|
|
277
|
+
sets `left`/`top` (or `inset-inline-start`/`inset-block-start`); Bronto only
|
|
278
|
+
takes it out of flow and gives it the layer. */
|
|
279
|
+
.ui-menu--at-pointer {
|
|
280
|
+
position: fixed;
|
|
281
|
+
z-index: var(--z-popover);
|
|
282
|
+
}
|
|
283
|
+
|
|
265
284
|
.ui-menu__label {
|
|
266
285
|
color: var(--text-dim);
|
|
267
286
|
font-family: var(--mono);
|
|
@@ -271,20 +290,15 @@ html:has(.ui-modal.is-open) {
|
|
|
271
290
|
text-transform: uppercase;
|
|
272
291
|
}
|
|
273
292
|
|
|
293
|
+
/* Composes `.ui-row` (css/row.css) — a menu item IS a dense selectable row,
|
|
294
|
+
and keeping two copies of that shape is how they drift. What is menu-specific
|
|
295
|
+
stays here: the mono face, the slightly roomier padding a pointer target
|
|
296
|
+
wants, and the leading bullet below. `row.css` is in the core bundle, which
|
|
297
|
+
is what lets a core component compose it. */
|
|
274
298
|
.ui-menu__item {
|
|
275
|
-
align-items: center;
|
|
276
|
-
background: transparent;
|
|
277
|
-
border: 0;
|
|
278
|
-
border-radius: var(--radius-sm);
|
|
279
|
-
color: var(--text-soft);
|
|
280
|
-
cursor: pointer;
|
|
281
|
-
display: flex;
|
|
282
299
|
font-family: var(--mono);
|
|
283
300
|
font-size: var(--text-sm);
|
|
284
|
-
gap: 0.5rem;
|
|
285
301
|
padding: 0.5rem 0.55rem;
|
|
286
|
-
text-align: start;
|
|
287
|
-
inline-size: 100%;
|
|
288
302
|
}
|
|
289
303
|
|
|
290
304
|
.ui-menu__item::before {
|
package/css/row.css
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
row — the dense selectable row. IN THE DEFAULT BUNDLE.
|
|
3
|
+
|
|
4
|
+
Every workbench grows a list of these: a search result, a file in an
|
|
5
|
+
explorer, an outline entry, a backlink, an inbox item, a commit. They are not
|
|
6
|
+
table rows — there is no column contract and no header — and they are not
|
|
7
|
+
menu items, because they persist rather than dismissing. But they are all the
|
|
8
|
+
same object: a full-width clickable line with a leading mark, a title that
|
|
9
|
+
gives up space first, and trailing metadata that does not.
|
|
10
|
+
|
|
11
|
+
Bronto already had this shape and it was imprisoned. `.ui-menu__item` is
|
|
12
|
+
exactly it, minus the persistence — so a consumer building an explorer either
|
|
13
|
+
claimed to be a menu or wrote the row again. One downstream workbench had
|
|
14
|
+
SIXTY-THREE families of it, about 1,200 lines, each re-deciding the selected
|
|
15
|
+
state, the hover, the ellipsis and the coarse-pointer floor.
|
|
16
|
+
|
|
17
|
+
`.ui-menu__item` now composes this, so the two cannot drift.
|
|
18
|
+
|
|
19
|
+
BOUNDARY. The host owns what a row means, what selects it, and what its
|
|
20
|
+
columns are. Bronto owns the line: its rhythm, its states, and the fact that
|
|
21
|
+
the title is the part that truncates. Rows that carry a severity should reach
|
|
22
|
+
for `.ui-severity-row` in `state.css` instead — that one adds the tone gutter.
|
|
23
|
+
|
|
24
|
+
In core rather than opt-in, unusually for a new surface, because
|
|
25
|
+
`.ui-menu__item` composes it and a core component cannot depend on a leaf.
|
|
26
|
+
========================================================================== */
|
|
27
|
+
|
|
28
|
+
.ui-row {
|
|
29
|
+
align-items: center;
|
|
30
|
+
background: transparent;
|
|
31
|
+
border: 0;
|
|
32
|
+
border-radius: var(--radius-sm);
|
|
33
|
+
color: var(--text-soft);
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
display: flex;
|
|
36
|
+
font: inherit;
|
|
37
|
+
gap: 0.5rem;
|
|
38
|
+
|
|
39
|
+
/* The companion to `__title`'s ellipsis, and the demo proved it is not
|
|
40
|
+
optional: a row is usually a flex or grid ITEM, whose automatic minimum
|
|
41
|
+
size is its min-content width — and `__title` is `white-space: nowrap`, so
|
|
42
|
+
that min-content is the WHOLE title. Without this a long title stops the
|
|
43
|
+
row shrinking and pushes its container past the viewport instead of
|
|
44
|
+
truncating. Caught at 320/360/390px by the overflow specs. */
|
|
45
|
+
min-inline-size: 0;
|
|
46
|
+
padding: 0.4rem 0.5rem;
|
|
47
|
+
text-align: start;
|
|
48
|
+
inline-size: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* A row is usually a <button> or an <a>; when it is neither, it still needs to
|
|
52
|
+
look inert rather than pressable. */
|
|
53
|
+
.ui-row:not(button, a, [role='button'], [role='option'], [tabindex]) {
|
|
54
|
+
cursor: default;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* The title gives up space FIRST — it is the only part of a row that can be
|
|
58
|
+
truncated without losing a fact. Everything else keeps its size. */
|
|
59
|
+
.ui-row__title {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
min-inline-size: 0;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Trailing facts: a count, an age, a path fragment. Never the thing that
|
|
68
|
+
shrinks, because a half-rendered number is worse than no number. */
|
|
69
|
+
.ui-row__meta {
|
|
70
|
+
color: var(--text-dim);
|
|
71
|
+
flex: none;
|
|
72
|
+
font-family: var(--mono);
|
|
73
|
+
font-size: var(--text-2xs);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* A leading mark — an icon, a glyph, a status dot. Sized by the host. */
|
|
77
|
+
.ui-row__mark {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
flex: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* A row that wraps to a second line: a result with a snippet under its title.
|
|
83
|
+
The parts stack rather than sharing the baseline. */
|
|
84
|
+
.ui-row--stacked {
|
|
85
|
+
align-items: stretch;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: 0.15rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ui-row--stacked .ui-row__title {
|
|
91
|
+
flex: none;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Rows in a list read as one object when they share a rule; rows in a menu
|
|
95
|
+
float. `--ruled` is the list form. */
|
|
96
|
+
.ui-row--ruled {
|
|
97
|
+
border-block-end: 1px solid var(--line);
|
|
98
|
+
border-radius: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* --- States, and WHICH attribute is yours to use.
|
|
102
|
+
|
|
103
|
+
`aria-selected` is only valid on a row that has a role which accepts it —
|
|
104
|
+
`option` inside a `listbox`, or `row` / `tab` / `gridcell` / `treeitem`. On a
|
|
105
|
+
bare `<button>` it is invalid ARIA, and axe rates that CRITICAL. This project
|
|
106
|
+
shipped exactly that mistake in its own demo and the a11y gate caught it.
|
|
107
|
+
|
|
108
|
+
So: `aria-current` for a row that is merely the current one (the common
|
|
109
|
+
case), `aria-selected` only when the row genuinely carries an option/row
|
|
110
|
+
role, and `.is-selected` when neither applies. All three paint the same. --- */
|
|
111
|
+
|
|
112
|
+
.ui-row[aria-selected='true'],
|
|
113
|
+
.ui-row[aria-current]:not([aria-current='false']),
|
|
114
|
+
.ui-row.is-selected {
|
|
115
|
+
background: var(--panel-soft);
|
|
116
|
+
color: var(--text);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (hover: hover) {
|
|
120
|
+
.ui-row:is(button, a, [role='button'], [role='option'], [tabindex]):hover {
|
|
121
|
+
background: var(--bg-accent);
|
|
122
|
+
color: var(--text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ui-row:focus-visible {
|
|
127
|
+
background: var(--bg-accent);
|
|
128
|
+
color: var(--text);
|
|
129
|
+
outline: 2px solid var(--focus-ring);
|
|
130
|
+
outline-offset: -2px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Touch: a row is a primary target in a list, so it floats to the full
|
|
134
|
+
tap-target rather than the 24px minimum. */
|
|
135
|
+
@media (pointer: coarse) {
|
|
136
|
+
.ui-row {
|
|
137
|
+
min-block-size: var(--tap-target);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Forced colours strip the tinted hover/selected background, which would leave
|
|
142
|
+
the current row indistinguishable from its siblings. Re-assert a system
|
|
143
|
+
Highlight, the same way the menu and combobox rows do. */
|
|
144
|
+
@media (forced-colors: active) {
|
|
145
|
+
.ui-row[aria-selected='true'],
|
|
146
|
+
.ui-row[aria-current]:not([aria-current='false']),
|
|
147
|
+
.ui-row.is-selected,
|
|
148
|
+
.ui-row:hover,
|
|
149
|
+
.ui-row:focus-visible {
|
|
150
|
+
forced-color-adjust: none;
|
|
151
|
+
background: Highlight;
|
|
152
|
+
color: HighlightText;
|
|
153
|
+
}
|
|
154
|
+
}
|