@ponchia/ui 0.6.0 → 0.6.3
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 +64 -4
- package/README.md +1 -1
- package/annotations/index.d.ts.map +1 -1
- package/annotations/index.js +36 -33
- package/behaviors/carousel.d.ts +28 -0
- package/behaviors/carousel.d.ts.map +1 -0
- package/behaviors/carousel.js +3 -0
- package/behaviors/combobox.d.ts +40 -0
- package/behaviors/combobox.d.ts.map +1 -0
- package/behaviors/combobox.js +71 -20
- package/behaviors/command.d.ts +41 -0
- package/behaviors/command.d.ts.map +1 -0
- package/behaviors/command.js +9 -0
- package/behaviors/connectors.d.ts +17 -0
- package/behaviors/connectors.d.ts.map +1 -0
- package/behaviors/connectors.js +3 -0
- package/behaviors/crosshair.d.ts +42 -0
- package/behaviors/crosshair.d.ts.map +1 -0
- package/behaviors/crosshair.js +19 -1
- package/behaviors/dialog.d.ts +20 -0
- package/behaviors/dialog.d.ts.map +1 -0
- package/behaviors/dialog.js +3 -0
- package/behaviors/disclosure.d.ts +10 -0
- package/behaviors/disclosure.d.ts.map +1 -0
- package/behaviors/disclosure.js +3 -0
- package/behaviors/dismissible.d.ts +10 -0
- package/behaviors/dismissible.d.ts.map +1 -0
- package/behaviors/dismissible.js +3 -0
- package/behaviors/forms.d.ts +27 -0
- package/behaviors/forms.d.ts.map +1 -0
- package/behaviors/forms.js +18 -5
- package/behaviors/glyph.d.ts +14 -0
- package/behaviors/glyph.d.ts.map +1 -0
- package/behaviors/glyph.js +24 -0
- package/behaviors/index.d.ts +31 -237
- package/behaviors/index.d.ts.map +1 -0
- package/behaviors/index.js +17 -0
- package/behaviors/inert.d.ts +20 -0
- package/behaviors/inert.d.ts.map +1 -0
- package/behaviors/inert.js +46 -0
- package/behaviors/internal.d.ts +25 -0
- package/behaviors/internal.d.ts.map +1 -0
- package/behaviors/internal.js +30 -1
- package/behaviors/legend.d.ts +35 -0
- package/behaviors/legend.d.ts.map +1 -0
- package/behaviors/legend.js +9 -0
- package/behaviors/menu.d.ts +16 -0
- package/behaviors/menu.d.ts.map +1 -0
- package/behaviors/menu.js +3 -0
- package/behaviors/modal.d.ts +41 -0
- package/behaviors/modal.d.ts.map +1 -0
- package/behaviors/modal.js +124 -0
- package/behaviors/popover.d.ts +28 -0
- package/behaviors/popover.d.ts.map +1 -0
- package/behaviors/popover.js +17 -17
- package/behaviors/spotlight.d.ts +17 -0
- package/behaviors/spotlight.d.ts.map +1 -0
- package/behaviors/spotlight.js +3 -0
- package/behaviors/table.d.ts +36 -0
- package/behaviors/table.d.ts.map +1 -0
- package/behaviors/table.js +48 -8
- package/behaviors/tabs.d.ts +20 -0
- package/behaviors/tabs.d.ts.map +1 -0
- package/behaviors/tabs.js +3 -0
- package/behaviors/theme.d.ts +54 -0
- package/behaviors/theme.d.ts.map +1 -0
- package/behaviors/theme.js +17 -0
- package/behaviors/toast.d.ts +49 -0
- package/behaviors/toast.d.ts.map +1 -0
- package/behaviors/toast.js +34 -2
- package/classes/classes.json +683 -13
- package/classes/index.d.ts +106 -2
- package/classes/index.js +249 -65
- package/connectors/index.d.ts +12 -0
- package/connectors/index.d.ts.map +1 -1
- package/connectors/index.js +23 -2
- package/css/app.css +26 -0
- package/css/bullet.css +108 -0
- package/css/code.css +98 -0
- package/css/content.css +15 -2
- package/css/crosshair.css +7 -7
- package/css/diff.css +153 -0
- package/css/disclosure.css +18 -4
- package/css/dots.css +37 -7
- package/css/feedback.css +39 -7
- package/css/forms.css +71 -3
- package/css/legend.css +5 -2
- package/css/motion.css +79 -14
- package/css/overlay.css +59 -2
- package/css/primitives.css +67 -8
- package/css/report.css +40 -0
- package/css/sidenote.css +67 -0
- package/css/spark.css +62 -0
- package/css/table.css +9 -2
- package/css/term.css +110 -0
- package/css/textref.css +63 -0
- package/css/toc.css +91 -0
- package/css/tokens.css +14 -1
- package/css/tree.css +134 -0
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/bullet.css +1 -0
- package/dist/css/code.css +1 -0
- package/dist/css/content.css +1 -1
- package/dist/css/crosshair.css +1 -1
- package/dist/css/diff.css +1 -0
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/forms.css +1 -1
- package/dist/css/legend.css +1 -1
- package/dist/css/motion.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/report.css +1 -1
- package/dist/css/sidenote.css +1 -0
- package/dist/css/spark.css +1 -0
- package/dist/css/table.css +1 -1
- package/dist/css/term.css +1 -0
- package/dist/css/textref.css +1 -0
- package/dist/css/toc.css +1 -0
- package/dist/css/tokens.css +1 -1
- package/dist/css/tree.css +1 -0
- package/docs/annotations.md +39 -0
- package/docs/architecture.md +2 -3
- package/docs/bullet.md +78 -0
- package/docs/code.md +76 -0
- package/docs/d2.md +4 -3
- package/docs/diff.md +146 -0
- package/docs/legends.md +8 -4
- package/docs/mermaid.md +21 -4
- package/docs/reference.md +127 -8
- package/docs/reporting.md +35 -14
- package/docs/sidenote.md +64 -0
- package/docs/spark.md +78 -0
- package/docs/stability.md +1 -0
- package/docs/term.md +81 -0
- package/docs/textref.md +78 -0
- package/docs/theming.md +44 -5
- package/docs/toc.md +83 -0
- package/docs/tree.md +74 -0
- package/docs/usage.md +264 -23
- package/docs/vega.md +22 -3
- package/glyphs/glyphs.js +7 -1
- package/llms.txt +159 -13
- package/package.json +47 -7
- package/qwik/index.d.ts +4 -2
- package/qwik/index.d.ts.map +1 -1
- package/qwik/index.js +10 -0
- package/react/index.d.ts +4 -2
- package/react/index.d.ts.map +1 -1
- package/react/index.js +6 -0
- package/solid/index.d.ts +6 -2
- package/solid/index.d.ts.map +1 -1
- package/solid/index.js +6 -0
package/css/forms.css
CHANGED
|
@@ -77,6 +77,16 @@
|
|
|
77
77
|
opacity: 0.5;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/* Read-only is editable-looking but not editable; give it a distinct, quieter
|
|
81
|
+
cue (muted fill + default cursor) so it doesn't read as a live field. Not
|
|
82
|
+
disabled — value still submits and the field stays focusable/selectable.
|
|
83
|
+
(component audit C28.) */
|
|
84
|
+
.ui-input:read-only:not(:disabled),
|
|
85
|
+
.ui-textarea:read-only:not(:disabled) {
|
|
86
|
+
background: var(--panel-soft);
|
|
87
|
+
cursor: default;
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
/* Disabled affordance parity. The text inputs above style :disabled directly;
|
|
81
91
|
the controls that WRAP a native input (switch/check/segmented) showed no
|
|
82
92
|
disabled cue and their label kept cursor:pointer — a lie. Mirror the cue via
|
|
@@ -94,6 +104,8 @@
|
|
|
94
104
|
/* Keep autofilled fields on-theme — the UA's yellow fill otherwise paints over
|
|
95
105
|
the monochrome surface and breaks the contrast story. (forms review C24.) */
|
|
96
106
|
.ui-input:autofill,
|
|
107
|
+
.ui-select:autofill,
|
|
108
|
+
.ui-textarea:autofill,
|
|
97
109
|
.ui-search input:autofill {
|
|
98
110
|
-webkit-text-fill-color: var(--text);
|
|
99
111
|
box-shadow: inset 0 0 0 100rem var(--bg-elevated);
|
|
@@ -106,6 +118,58 @@
|
|
|
106
118
|
border-color: var(--danger);
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
/* Wrapper controls (switch / check / segmented) hide their native <input>, so
|
|
122
|
+
the `[aria-invalid]` the validator sets on it paints nothing — a sighted,
|
|
123
|
+
non-AT user couldn't see the error (WCAG 1.4.1). Mirror the invalid cue onto
|
|
124
|
+
the visible surface via :has(), the same way the disabled cue is mirrored.
|
|
125
|
+
(component audit C7.) */
|
|
126
|
+
.ui-check:has(input[aria-invalid='true']) input {
|
|
127
|
+
outline: 2px solid var(--danger);
|
|
128
|
+
outline-offset: 1px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.ui-switch:has(input[aria-invalid='true']) .ui-switch__track {
|
|
132
|
+
border-color: var(--danger);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ui-segmented:has(input[aria-invalid='true']) {
|
|
136
|
+
border-color: var(--danger);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Forced colours flatten `var(--danger)` to a system colour identical to the
|
|
140
|
+
resting border, so the invalid border becomes indistinguishable from a valid
|
|
141
|
+
one and sighted HCM users lose the only error cue (WCAG 1.4.1). The switch got
|
|
142
|
+
a forced-colors block; the error family did not. Re-assert the state on a
|
|
143
|
+
NON-colour channel — a thicker, doubled border — that survives HCM, and prefix
|
|
144
|
+
the error hint with a glyph so the message itself carries the error.
|
|
145
|
+
(component audit C5.) */
|
|
146
|
+
@media (forced-colors: active) {
|
|
147
|
+
.ui-input[aria-invalid='true'],
|
|
148
|
+
.ui-select[aria-invalid='true'],
|
|
149
|
+
.ui-textarea[aria-invalid='true'] {
|
|
150
|
+
border-style: double;
|
|
151
|
+
border-width: 3px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Same NON-colour re-assertion for the wrapper controls (C7): a thicker,
|
|
155
|
+
doubled outline/border survives the HCM colour flattening. */
|
|
156
|
+
.ui-check:has(input[aria-invalid='true']) input {
|
|
157
|
+
outline-width: 3px;
|
|
158
|
+
outline-style: double;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.ui-switch:has(input[aria-invalid='true']) .ui-switch__track,
|
|
162
|
+
.ui-segmented:has(input[aria-invalid='true']) {
|
|
163
|
+
border-style: double;
|
|
164
|
+
border-width: 3px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.ui-hint--error::before,
|
|
168
|
+
.ui-error-summary__title::before {
|
|
169
|
+
content: '⚠ ';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
109
173
|
.ui-hint {
|
|
110
174
|
color: var(--text-dim);
|
|
111
175
|
font-size: var(--text-2xs);
|
|
@@ -139,14 +203,18 @@
|
|
|
139
203
|
border-end-end-radius: var(--radius-md);
|
|
140
204
|
}
|
|
141
205
|
|
|
142
|
-
.ui-input-group > .ui-input:focus-visible
|
|
143
|
-
|
|
206
|
+
.ui-input-group > .ui-input:focus-visible,
|
|
207
|
+
.ui-input-group > .ui-select:focus-visible {
|
|
208
|
+
z-index: 1; /* keep the focus ring above the adjacent addon border (select too — audit C29) */
|
|
144
209
|
}
|
|
145
210
|
|
|
146
211
|
.ui-input-group__addon {
|
|
147
212
|
align-items: center;
|
|
148
213
|
background: var(--panel-soft);
|
|
149
|
-
|
|
214
|
+
|
|
215
|
+
/* Match the wrapped control's `--line-strong` border so the prefix/suffix seam
|
|
216
|
+
isn't a fainter cap than the field it abuts. (component audit C33.) */
|
|
217
|
+
border: 1px solid var(--line-strong);
|
|
150
218
|
color: var(--text-dim);
|
|
151
219
|
display: flex;
|
|
152
220
|
font-size: var(--text-sm);
|
package/css/legend.css
CHANGED
|
@@ -90,10 +90,13 @@
|
|
|
90
90
|
inline-size: 1.1rem;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/* Glyph/symbol swatch — fill an `.ui-icon` mask with the series colour.
|
|
93
|
+
/* Glyph/symbol swatch — fill an `.ui-icon` mask with the series colour. Match the
|
|
94
|
+
__swatch fallback chain exactly (--chart-color → --chart-1 → --accent): without
|
|
95
|
+
the --chart-1 layer, a symbol and a swatch for the SAME series diverge to two
|
|
96
|
+
colours whenever a theme overrides --chart-1. (audit C22.) */
|
|
94
97
|
.ui-legend__symbol {
|
|
95
98
|
block-size: 0.95rem;
|
|
96
|
-
color: var(--chart-color, var(--accent));
|
|
99
|
+
color: var(--chart-color, var(--chart-1, var(--accent)));
|
|
97
100
|
flex: 0 0 auto;
|
|
98
101
|
inline-size: 0.95rem;
|
|
99
102
|
}
|
package/css/motion.css
CHANGED
|
@@ -52,6 +52,28 @@
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/* Scroll-driven reveal (`.ui-scroll-reveal`). Unlike the time-based uiRise, a
|
|
56
|
+
scroll timeline can FREEZE part-way: an element near the document bottom can't
|
|
57
|
+
scroll far enough to finish its range, so a fade-to-opacity-1 would strand the
|
|
58
|
+
content permanently transparent. Reach full opacity early (35%) and hold it,
|
|
59
|
+
so even a partially-driven reveal is fully legible — only the last few px of
|
|
60
|
+
the rise are left unfinished. (component audit C9.) */
|
|
61
|
+
@keyframes uiScrollReveal {
|
|
62
|
+
0% {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
transform: translateY(10px);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
35% {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
100% {
|
|
72
|
+
opacity: 1;
|
|
73
|
+
transform: translateY(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
@keyframes uiDotIn {
|
|
56
78
|
0% {
|
|
57
79
|
opacity: 0;
|
|
@@ -158,10 +180,13 @@
|
|
|
158
180
|
animation: uiMatrixReveal var(--duration-slow) var(--ease-out) both;
|
|
159
181
|
}
|
|
160
182
|
|
|
161
|
-
/* Stagger children: set --i on each child (or use nth-child cap).
|
|
183
|
+
/* Stagger children: set --i on each child (or use nth-child cap). Cap the manual
|
|
184
|
+
--i path at index 6 (360ms) to match the .ui-stagger--auto ceiling — a long
|
|
185
|
+
list with --i:30 would otherwise hold the last child at opacity:0 for 1.8s
|
|
186
|
+
before popping in. (audit C32.) */
|
|
162
187
|
.ui-stagger > * {
|
|
163
188
|
animation: uiRise var(--duration-slow) var(--ease-spring) both;
|
|
164
|
-
animation-delay: calc(var(--i, 0) * 60ms);
|
|
189
|
+
animation-delay: calc(min(var(--i, 0), 6) * 60ms);
|
|
165
190
|
}
|
|
166
191
|
|
|
167
192
|
.ui-stagger--auto > *:nth-child(1) {
|
|
@@ -193,11 +218,17 @@
|
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
/* Reveal-on-scroll: add .ui-reveal, then toggle .is-visible (e.g. from an
|
|
196
|
-
IntersectionObserver you own) to play it in.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
on `scripting: enabled
|
|
200
|
-
|
|
221
|
+
IntersectionObserver you own) to play it in.
|
|
222
|
+
|
|
223
|
+
⚠ `.ui-reveal` requires you to wire that observer. The hidden initial state is
|
|
224
|
+
gated only on `scripting: enabled` — it can't detect whether an observer is
|
|
225
|
+
actually attached, so merely loading bronto.js (or any script) hides every
|
|
226
|
+
`.ui-reveal` until YOUR code adds `.is-visible`. If you add the class and never
|
|
227
|
+
wire the toggle, the content stays invisible. For an LLM-authored or no-build
|
|
228
|
+
report, DON'T use `.ui-reveal` — use `.ui-scroll-reveal` below (scroll-driven,
|
|
229
|
+
CSS-only, no observer, and it can never strand content invisible). With
|
|
230
|
+
scripting OFF, `.ui-reveal` content is fully visible and never silently hidden
|
|
231
|
+
behind a script that will never run. */
|
|
201
232
|
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
|
|
202
233
|
.ui-reveal {
|
|
203
234
|
opacity: 0;
|
|
@@ -246,10 +277,12 @@
|
|
|
246
277
|
|
|
247
278
|
/* --- Scroll-driven (progressive enhancement) — the scroll/view timeline
|
|
248
279
|
IS the engine, no JS. Everything is gated on `@supports
|
|
249
|
-
(animation-timeline: …)` so
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
280
|
+
(animation-timeline: …)` so an engine without it keeps the static end state
|
|
281
|
+
(as of 2026 Chrome 115+ and Safari 18.4+ drive scroll()/view(); Firefox is
|
|
282
|
+
the remaining holdout, still graceful via the @supports gate), and on
|
|
283
|
+
`prefers-reduced-motion: no-preference` (a scroll timeline ignores
|
|
284
|
+
animation-duration, so the global reduced-motion reset below can't neutralise
|
|
285
|
+
it — it must be gated here). --- */
|
|
253
286
|
|
|
254
287
|
/* Reading-progress bar. Fixed hairline that fills with document scroll;
|
|
255
288
|
unsupported → a static, empty (scaleX(0)) bar. Pair with role="progressbar"
|
|
@@ -270,6 +303,30 @@
|
|
|
270
303
|
transform-origin: 100% 50%;
|
|
271
304
|
}
|
|
272
305
|
|
|
306
|
+
/* Forced colours (HCM) flatten the spinner's accent top-border and its --line
|
|
307
|
+
rest to one system colour, so the ring looks uniform with no visible sweep,
|
|
308
|
+
and the scroll-progress bar can vanish into the canvas. Re-assert distinct
|
|
309
|
+
system colours so each keeps a visible channel; AT is already covered via
|
|
310
|
+
aria-busy / role="status" / role="progressbar". (component audit C35.) */
|
|
311
|
+
@media (forced-colors: active) {
|
|
312
|
+
.ui-spinner {
|
|
313
|
+
border-color: CanvasText;
|
|
314
|
+
border-block-start-color: Highlight;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.ui-scroll-progress {
|
|
318
|
+
background: Highlight;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* HCM strips the shimmer gradient, leaving an invisible box where a loading
|
|
322
|
+
placeholder should be. Give it a system-colour border so the skeleton
|
|
323
|
+
still reads as present. Same decorative-loading family as the spinner +
|
|
324
|
+
scroll-progress above. (audit C15.) */
|
|
325
|
+
.ui-skeleton {
|
|
326
|
+
border: 1px solid CanvasText;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
273
330
|
@supports (animation-timeline: scroll()) {
|
|
274
331
|
@media (prefers-reduced-motion: no-preference) {
|
|
275
332
|
.ui-scroll-progress {
|
|
@@ -284,13 +341,21 @@
|
|
|
284
341
|
|
|
285
342
|
/* Reveal-on-scroll with no JS and no IntersectionObserver: the element
|
|
286
343
|
rises + fades as it scrolls into view. Unsupported → fully visible
|
|
287
|
-
(the
|
|
344
|
+
(the keyframe end state), same graceful default as `.ui-reveal`.
|
|
345
|
+
|
|
346
|
+
The range is `entry 0% entry 100%`, NOT the old `cover 40%`: `cover` only
|
|
347
|
+
completes once the element has scrolled most of the way THROUGH the viewport,
|
|
348
|
+
which an element near the document bottom can never do — it froze part-way,
|
|
349
|
+
leaving a conclusion section permanently semi-transparent (C9). `entry`
|
|
350
|
+
completes the moment the element is fully in view, which any scroll-to-bottom
|
|
351
|
+
reaches. Paired with the early-opacity uiScrollReveal keyframe, an element
|
|
352
|
+
taller than the viewport is legible even if its range never fully completes. */
|
|
288
353
|
@supports (animation-timeline: view()) {
|
|
289
354
|
@media (prefers-reduced-motion: no-preference) {
|
|
290
355
|
.ui-scroll-reveal {
|
|
291
|
-
animation:
|
|
356
|
+
animation: uiScrollReveal linear both;
|
|
292
357
|
animation-timeline: view();
|
|
293
|
-
animation-range: entry 0%
|
|
358
|
+
animation-range: entry 0% entry 100%;
|
|
294
359
|
}
|
|
295
360
|
}
|
|
296
361
|
}
|
package/css/overlay.css
CHANGED
|
@@ -78,14 +78,25 @@ dialog.ui-modal[open]::backdrop {
|
|
|
78
78
|
|
|
79
79
|
/* Controlled (non-<dialog>) usage. A portal/React modal that can't be a
|
|
80
80
|
native <dialog> wears the same skin + open layout via `.is-open`.
|
|
81
|
-
Backdrop
|
|
82
|
-
|
|
81
|
+
Backdrop and top-layer stacking are then the consumer's responsibility
|
|
82
|
+
(the native <dialog> path gets them free); focus-trapping no longer is —
|
|
83
|
+
mark the overlay `data-bronto-modal` and run `initModal` for an
|
|
84
|
+
inert-based trap + focus-return + Escape signal. */
|
|
83
85
|
.ui-modal.is-open {
|
|
84
86
|
animation: uiToastIn var(--duration-base) var(--ease-spring) both;
|
|
85
87
|
display: grid;
|
|
86
88
|
grid-template-rows: auto 1fr auto;
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/* Background scroll-lock. A native <dialog> top layer does NOT stop the page
|
|
92
|
+
behind it scrolling, and the no-JS native path can't lock it in script — so
|
|
93
|
+
lock it in CSS, for both the native [open] dialog and the controlled .is-open
|
|
94
|
+
overlay (incl. the drawer modifier, same base). (component audit C13.) */
|
|
95
|
+
html:has(dialog.ui-modal[open]),
|
|
96
|
+
html:has(.ui-modal.is-open) {
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
.ui-modal__head {
|
|
90
101
|
align-items: flex-start;
|
|
91
102
|
border-block-end: 1px solid var(--line);
|
|
@@ -150,6 +161,21 @@ dialog.ui-modal[open]::backdrop {
|
|
|
150
161
|
}
|
|
151
162
|
}
|
|
152
163
|
|
|
164
|
+
/* Comfortable hit target on coarse pointers — this bespoke close button measured
|
|
165
|
+
~26px on touch, below the 2.9rem floor the rest of the button family meets
|
|
166
|
+
(WCAG 2.5.8). Scoped to coarse so the fine-pointer (mouse) rendering, which
|
|
167
|
+
already clears 24×24, is unchanged; centre the glyph in the enlarged box.
|
|
168
|
+
(audit C3.) */
|
|
169
|
+
@media (pointer: coarse) {
|
|
170
|
+
.ui-modal__close {
|
|
171
|
+
align-items: center;
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
min-block-size: 2.9rem;
|
|
175
|
+
min-inline-size: 2.9rem;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
153
179
|
/* --- Lightbox — a full-viewport <dialog> wrapping a .ui-carousel. The
|
|
154
180
|
native <dialog> brings the top layer, focus-trap and Escape; initDialog
|
|
155
181
|
adds open (`data-bronto-open`) + focus-return; initCarousel drives the
|
|
@@ -284,6 +310,25 @@ dialog.ui-modal[open]::backdrop {
|
|
|
284
310
|
opacity: 1;
|
|
285
311
|
}
|
|
286
312
|
|
|
313
|
+
/* Menu hover/focus paints var(--bg-accent), which HCM strips, so the hovered row
|
|
314
|
+
is indistinguishable from its siblings (focus keeps the UA ring; hover has no
|
|
315
|
+
such fallback). Re-assert a system Highlight like the combobox/command rows,
|
|
316
|
+
for parity with those sibling surfaces. (component audit C22.) */
|
|
317
|
+
@media (forced-colors: active) {
|
|
318
|
+
.ui-menu__item:hover,
|
|
319
|
+
.ui-menu__item:focus-visible {
|
|
320
|
+
forced-color-adjust: none;
|
|
321
|
+
background: Highlight;
|
|
322
|
+
color: HighlightText;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.ui-menu__item:hover::before,
|
|
326
|
+
.ui-menu__item:focus-visible::before {
|
|
327
|
+
background: HighlightText;
|
|
328
|
+
opacity: 1;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
287
332
|
/* --- Combobox: an input with a filtered listbox popup (APG pattern,
|
|
288
333
|
wired by initCombobox). Reuses the menu surface tokens. --- */
|
|
289
334
|
.ui-combobox {
|
|
@@ -337,6 +382,18 @@ dialog.ui-modal[open]::backdrop {
|
|
|
337
382
|
color: var(--text);
|
|
338
383
|
}
|
|
339
384
|
|
|
385
|
+
/* The active option is tracked by aria-activedescendant — it is NEVER DOM-focused,
|
|
386
|
+
so it gets no UA focus ring under HCM, and its only cue (--bg-accent) is stripped.
|
|
387
|
+
Re-assert a system Highlight like the command palette does. (audit C2.) */
|
|
388
|
+
@media (forced-colors: active) {
|
|
389
|
+
.ui-combobox__option.is-active,
|
|
390
|
+
.ui-combobox__option[aria-selected='true'] {
|
|
391
|
+
forced-color-adjust: none;
|
|
392
|
+
background: Highlight;
|
|
393
|
+
color: HighlightText;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
340
397
|
.ui-combobox__empty {
|
|
341
398
|
color: var(--text-dim);
|
|
342
399
|
font-size: var(--text-2xs);
|
package/css/primitives.css
CHANGED
|
@@ -71,12 +71,15 @@
|
|
|
71
71
|
padding-inline: var(--center-gutter, var(--space-md));
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/* Intrinsic aspect-ratio box; the media child fills it.
|
|
74
|
+
/* Intrinsic aspect-ratio box; the media child fills it. The contract is ONE
|
|
75
|
+
child (a single <img>/<video>/<iframe>). Scope the fill to :first-child rather
|
|
76
|
+
than every child: a second child would otherwise be forced to 100%/100% +
|
|
77
|
+
object-fit and stack on top, silently breaking the ratio. (audit C34.) */
|
|
75
78
|
.ui-ratio {
|
|
76
79
|
aspect-ratio: var(--ratio, 16 / 9);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
.ui-ratio >
|
|
82
|
+
.ui-ratio > :first-child {
|
|
80
83
|
block-size: 100%;
|
|
81
84
|
inline-size: 100%;
|
|
82
85
|
object-fit: cover;
|
|
@@ -94,7 +97,11 @@
|
|
|
94
97
|
container: bronto / inline-size;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
/* Logical `max-inline-size`, not physical `max-width`: the container is typed
|
|
101
|
+
`inline-size`, so the inline axis is the one actually tracked — the logical
|
|
102
|
+
query matches it in any writing mode (a physical `width` query silently
|
|
103
|
+
misses in a vertical WM). (component audit C34.) */
|
|
104
|
+
@container bronto (max-inline-size: 34rem) {
|
|
98
105
|
.ui-grid {
|
|
99
106
|
--grid-min: 100%;
|
|
100
107
|
}
|
|
@@ -168,7 +175,7 @@
|
|
|
168
175
|
/* Inside an opt-in .ui-cq container, collapse to one column when the
|
|
169
176
|
container (not the viewport) is narrow — keeps tiles usable in a slim
|
|
170
177
|
panel. Inert without .ui-cq, so baselines are unaffected. */
|
|
171
|
-
@container bronto (max-
|
|
178
|
+
@container bronto (max-inline-size: 30rem) {
|
|
172
179
|
.ui-statgrid,
|
|
173
180
|
.ui-app-metrics {
|
|
174
181
|
grid-template-columns: 1fr;
|
|
@@ -182,6 +189,12 @@
|
|
|
182
189
|
border-radius: var(--radius-md);
|
|
183
190
|
display: grid;
|
|
184
191
|
gap: 0.4rem;
|
|
192
|
+
|
|
193
|
+
/* These tiles hold IDs / hashes / big numbers — the unbreakable-token case is
|
|
194
|
+
the common one. A grid item defaults to min-inline-size:auto, so a long
|
|
195
|
+
value pushes the whole statgrid track wider; allow the tile to shrink so the
|
|
196
|
+
value can wrap instead (paired with overflow-wrap on __value). (audit C5.) */
|
|
197
|
+
min-inline-size: 0;
|
|
185
198
|
padding: var(--space-md);
|
|
186
199
|
}
|
|
187
200
|
|
|
@@ -203,6 +216,7 @@
|
|
|
203
216
|
font-weight: var(--display-weight-strong);
|
|
204
217
|
letter-spacing: 0.02em;
|
|
205
218
|
line-height: 1.05;
|
|
219
|
+
overflow-wrap: anywhere; /* break an unspaced ID/hash rather than overflow (audit C5) */
|
|
206
220
|
}
|
|
207
221
|
|
|
208
222
|
.ui-stat__delta,
|
|
@@ -243,6 +257,10 @@
|
|
|
243
257
|
share one P&L vocabulary. Token-identical to the table's is-num/
|
|
244
258
|
is-pos/is-neg (which stay table-local). */
|
|
245
259
|
.ui-num {
|
|
260
|
+
/* inline-block so `text-align: end` actually applies: on a bare inline element
|
|
261
|
+
it computes but never paints (the box is shrink-wrapped), so an author who
|
|
262
|
+
followed the docs to right-align an inline figure saw no effect. (audit C17.) */
|
|
263
|
+
display: inline-block;
|
|
246
264
|
font-variant-numeric: tabular-nums;
|
|
247
265
|
text-align: end;
|
|
248
266
|
}
|
|
@@ -413,13 +431,26 @@
|
|
|
413
431
|
|
|
414
432
|
/* aria-disabled keeps the element in the a11y tree but the browser does NOT
|
|
415
433
|
block activation (a real <a class="ui-button" aria-disabled> still navigates,
|
|
416
|
-
a <button> still fires).
|
|
417
|
-
|
|
434
|
+
a <button> still fires). `pointer-events: none` kills POINTER activation only
|
|
435
|
+
— a focused control still fires on Enter/Space, which CSS cannot intercept. So
|
|
436
|
+
this looks-dead state is NOT keyboard-inert: for that, prefer native
|
|
437
|
+
`<button disabled>`, run `initDisabledGuard()` (it intercepts Enter/Space on
|
|
438
|
+
aria-disabled controls), or add `tabindex="-1"` (and drop `href` on a link).
|
|
439
|
+
See docs/usage.md "Disabled vs aria-disabled". (a11y review C3 / audit C4;
|
|
440
|
+
native :disabled already inert.) */
|
|
418
441
|
.ui-button[aria-disabled='true'],
|
|
419
442
|
.ui-link[aria-disabled='true'] {
|
|
420
443
|
pointer-events: none;
|
|
421
444
|
}
|
|
422
445
|
|
|
446
|
+
/* The button family dims + shows not-allowed via its `:disabled` rule above, but
|
|
447
|
+
a disabled LINK got only pointer-events:none — it looked fully live. Give it
|
|
448
|
+
the same visual disabled cue. (component audit C30.) */
|
|
449
|
+
.ui-link[aria-disabled='true'] {
|
|
450
|
+
cursor: not-allowed;
|
|
451
|
+
opacity: 0.45;
|
|
452
|
+
}
|
|
453
|
+
|
|
423
454
|
.ui-button:active {
|
|
424
455
|
transform: translateY(1px);
|
|
425
456
|
}
|
|
@@ -445,8 +476,13 @@
|
|
|
445
476
|
}
|
|
446
477
|
|
|
447
478
|
@media (prefers-reduced-motion: reduce) {
|
|
479
|
+
/* The global reduced-motion reset freezes the spin on a single frame with an
|
|
480
|
+
!important `animation-duration`, so the old non-important `1.4s` slow-spin
|
|
481
|
+
here was dead code AND left a broken, transparent-topped ring looking like a
|
|
482
|
+
rendering bug. Drop the dead rule; show a STATIC complete ring instead — a
|
|
483
|
+
still busy cue with no implied motion. (component audit C15.) */
|
|
448
484
|
.ui-button[aria-busy='true']::before {
|
|
449
|
-
|
|
485
|
+
border-block-start-color: currentcolor;
|
|
450
486
|
}
|
|
451
487
|
}
|
|
452
488
|
|
|
@@ -492,6 +528,27 @@
|
|
|
492
528
|
inline-size: 0.42rem;
|
|
493
529
|
}
|
|
494
530
|
|
|
531
|
+
/* RTL: the logical borders flip sides, but a fixed `rotate(45deg)` then points
|
|
532
|
+
the chevron UP rather than toward the inline-end (the reading-forward way).
|
|
533
|
+
Mirror the rotation so the resting affordance points forward in RTL too.
|
|
534
|
+
(component audit C14.) */
|
|
535
|
+
[dir='rtl'] .ui-link--arrow::after,
|
|
536
|
+
[dir='rtl'] .ui-link--cta::after {
|
|
537
|
+
transform: rotate(-45deg);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* Standalone CTA links are tap targets, not inline prose links: on a coarse
|
|
541
|
+
pointer float them to the WCAG 2.5.8 AA 24px floor (the 2.5.8 inline-link
|
|
542
|
+
exception doesn't cover a block-level call-to-action, which is what these
|
|
543
|
+
are). Buttons already auto-grow to ~44px on coarse pointers. (component
|
|
544
|
+
audit C14.) */
|
|
545
|
+
@media (pointer: coarse) {
|
|
546
|
+
.ui-link--arrow,
|
|
547
|
+
.ui-link--cta {
|
|
548
|
+
min-block-size: 1.5rem;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
495
552
|
/* --- Chip / tag --- */
|
|
496
553
|
|
|
497
554
|
.ui-chip {
|
|
@@ -637,6 +694,8 @@
|
|
|
637
694
|
color: var(--text);
|
|
638
695
|
font-family: var(--mono);
|
|
639
696
|
margin: 0;
|
|
697
|
+
min-inline-size: 0;
|
|
698
|
+
overflow-wrap: anywhere; /* IDs/hashes/paths are the common value — break, don't overflow (audit C5) */
|
|
640
699
|
}
|
|
641
700
|
|
|
642
701
|
/* --- Hover (pointer only) --- */
|
|
@@ -679,7 +738,7 @@
|
|
|
679
738
|
|
|
680
739
|
[dir='rtl'] .ui-link--arrow:hover::after,
|
|
681
740
|
[dir='rtl'] .ui-link--cta:hover::after {
|
|
682
|
-
transform: translateX(-0.14rem) rotate(45deg);
|
|
741
|
+
transform: translateX(-0.14rem) rotate(-45deg);
|
|
683
742
|
}
|
|
684
743
|
|
|
685
744
|
.ui-chip--accent:hover {
|
package/css/report.css
CHANGED
|
@@ -257,6 +257,46 @@
|
|
|
257
257
|
text-transform: uppercase;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/* --- Labelled meter row ---
|
|
261
|
+
A multi-meter block (SLO burn, error budgets, capacity) lays out as
|
|
262
|
+
label | bar | value. The bare `ui-meter` base lives in feedback.css; this is
|
|
263
|
+
the report-document grammar around it so authors stop hand-rolling the grid.
|
|
264
|
+
The bar NEVER carries the reading alone (WCAG 1.4.1) — `ui-meter__value` is
|
|
265
|
+
the data of record, and the bar clamps at 100 so an over-target figure still
|
|
266
|
+
reads correctly in the value text. Collapses to a stack on a narrow screen. */
|
|
267
|
+
.ui-meter__row {
|
|
268
|
+
align-items: center;
|
|
269
|
+
display: grid;
|
|
270
|
+
gap: var(--space-2xs) var(--space-md);
|
|
271
|
+
grid-template-columns: minmax(9rem, 14rem) 1fr auto;
|
|
272
|
+
margin-block: var(--space-2xs);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.ui-meter__row .ui-meter {
|
|
276
|
+
min-inline-size: 8rem;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.ui-meter__label {
|
|
280
|
+
color: var(--text-soft);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.ui-meter__value {
|
|
284
|
+
color: var(--text);
|
|
285
|
+
font-family: var(--mono);
|
|
286
|
+
font-variant-numeric: tabular-nums;
|
|
287
|
+
text-align: end;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@media (max-width: 32rem) {
|
|
291
|
+
.ui-meter__row {
|
|
292
|
+
grid-template-columns: 1fr;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.ui-meter__value {
|
|
296
|
+
text-align: start;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
260
300
|
/* A chart is NOT a bronto component — it needs scales + data binding, which the
|
|
261
301
|
analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
|
|
262
302
|
(docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
|
package/css/sidenote.css
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
sidenote — opt-in Tufte-style margin notes.
|
|
3
|
+
|
|
4
|
+
A numbered `ui-sidenote` and an unnumbered `ui-marginnote` for evidence,
|
|
5
|
+
caveats, and provenance asides that belong beside the text, not in it. Wide
|
|
6
|
+
viewports float the note into the inline-end margin; narrow viewports collapse
|
|
7
|
+
it to an indented inline block. CSS counters number the sidenotes. Not
|
|
8
|
+
imported by core.css.
|
|
9
|
+
|
|
10
|
+
Boundary / wiring: the HOST owns where numbering restarts — set
|
|
11
|
+
`counter-reset: ui-sidenote` on the article (or a section) — and reserves the
|
|
12
|
+
margin gutter by giving that container
|
|
13
|
+
`padding-inline-end: calc(var(--sidenote-width) + var(--sidenote-gap))` at the
|
|
14
|
+
same breakpoint. Place each note in the DOM right after its `.ui-sidenote__ref`.
|
|
15
|
+
========================================================================== */
|
|
16
|
+
|
|
17
|
+
.ui-sidenote,
|
|
18
|
+
.ui-marginnote {
|
|
19
|
+
--sidenote-width: 12rem;
|
|
20
|
+
--sidenote-gap: 2rem;
|
|
21
|
+
|
|
22
|
+
border-inline-start: 2px solid var(--line);
|
|
23
|
+
color: var(--text-dim);
|
|
24
|
+
display: block;
|
|
25
|
+
font-size: var(--text-2xs);
|
|
26
|
+
line-height: 1.5;
|
|
27
|
+
margin-block: var(--space-2xs);
|
|
28
|
+
padding-inline-start: var(--space-md);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* The inline superscript that anchors a numbered sidenote. Use --accent-text,
|
|
32
|
+
not raw --accent: this is readable text and must clear WCAG AA 4.5:1 even
|
|
33
|
+
after a one-knob re-brand to a paler --accent (raw --accent drops to ~1.5:1).
|
|
34
|
+
Same accent-as-text contract as .ui-eyebrow / .ui-link--cta. (audit C6.) */
|
|
35
|
+
.ui-sidenote__ref {
|
|
36
|
+
color: var(--accent-text);
|
|
37
|
+
counter-increment: ui-sidenote;
|
|
38
|
+
font-size: 0.75em;
|
|
39
|
+
vertical-align: super;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ui-sidenote__ref::after {
|
|
43
|
+
content: counter(ui-sidenote);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* The note repeats its number (display only — the ref already incremented).
|
|
47
|
+
--accent-text for the same WCAG-AA reason as the ref above. (audit C6.) */
|
|
48
|
+
.ui-sidenote::before {
|
|
49
|
+
color: var(--accent-text);
|
|
50
|
+
content: counter(ui-sidenote) '. ';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Wide viewports: float the note into the inline-end margin. The container must
|
|
54
|
+
reserve the gutter (see header). */
|
|
55
|
+
@media (min-width: 60rem) {
|
|
56
|
+
.ui-sidenote,
|
|
57
|
+
.ui-marginnote {
|
|
58
|
+
border-inline-start: 0;
|
|
59
|
+
clear: inline-end;
|
|
60
|
+
float: inline-end;
|
|
61
|
+
inline-size: var(--sidenote-width);
|
|
62
|
+
margin-block: 0;
|
|
63
|
+
margin-inline-end: calc(-1 * (var(--sidenote-width) + var(--sidenote-gap)));
|
|
64
|
+
padding-inline-start: 0;
|
|
65
|
+
text-align: start;
|
|
66
|
+
}
|
|
67
|
+
}
|