@ponchia/ui 0.5.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 +386 -4
- package/MIGRATIONS.json +14 -0
- package/README.md +29 -6
- package/annotations/index.d.ts +398 -276
- package/annotations/index.d.ts.map +1 -0
- package/annotations/index.js +350 -77
- package/behaviors/carousel.d.ts +28 -0
- package/behaviors/carousel.d.ts.map +1 -0
- package/behaviors/carousel.js +20 -16
- package/behaviors/combobox.d.ts +40 -0
- package/behaviors/combobox.d.ts.map +1 -0
- package/behaviors/combobox.js +111 -29
- package/behaviors/command.d.ts +41 -0
- package/behaviors/command.d.ts.map +1 -0
- package/behaviors/command.js +27 -15
- package/behaviors/connectors.d.ts +17 -0
- package/behaviors/connectors.d.ts.map +1 -0
- package/behaviors/connectors.js +7 -5
- package/behaviors/crosshair.d.ts +42 -0
- package/behaviors/crosshair.d.ts.map +1 -0
- package/behaviors/crosshair.js +23 -6
- package/behaviors/dialog.d.ts +20 -0
- package/behaviors/dialog.d.ts.map +1 -0
- package/behaviors/dialog.js +6 -2
- package/behaviors/disclosure.d.ts +10 -0
- package/behaviors/disclosure.d.ts.map +1 -0
- package/behaviors/disclosure.js +6 -2
- package/behaviors/dismissible.d.ts +10 -0
- package/behaviors/dismissible.d.ts.map +1 -0
- package/behaviors/dismissible.js +6 -2
- package/behaviors/forms.d.ts +27 -0
- package/behaviors/forms.d.ts.map +1 -0
- package/behaviors/forms.js +54 -13
- package/behaviors/glyph.d.ts +14 -0
- package/behaviors/glyph.d.ts.map +1 -0
- package/behaviors/glyph.js +28 -5
- 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 +77 -1
- package/behaviors/legend.d.ts +35 -0
- package/behaviors/legend.d.ts.map +1 -0
- package/behaviors/legend.js +32 -2
- package/behaviors/menu.d.ts +16 -0
- package/behaviors/menu.d.ts.map +1 -0
- package/behaviors/menu.js +6 -2
- 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 +78 -7
- package/behaviors/spotlight.d.ts +17 -0
- package/behaviors/spotlight.d.ts.map +1 -0
- package/behaviors/spotlight.js +7 -5
- package/behaviors/table.d.ts +36 -0
- package/behaviors/table.d.ts.map +1 -0
- package/behaviors/table.js +84 -17
- package/behaviors/tabs.d.ts +20 -0
- package/behaviors/tabs.d.ts.map +1 -0
- package/behaviors/tabs.js +17 -14
- package/behaviors/theme.d.ts +54 -0
- package/behaviors/theme.d.ts.map +1 -0
- package/behaviors/theme.js +22 -3
- package/behaviors/toast.d.ts +49 -0
- package/behaviors/toast.d.ts.map +1 -0
- package/behaviors/toast.js +47 -3
- package/classes/classes.json +2527 -0
- package/classes/index.d.ts +134 -15
- package/classes/index.js +280 -80
- package/classes/vscode.css-custom-data.json +12 -0
- package/connectors/index.d.ts +201 -69
- package/connectors/index.d.ts.map +1 -0
- package/connectors/index.js +142 -25
- package/css/app.css +69 -13
- package/css/base.css +15 -10
- package/css/bullet.css +108 -0
- package/css/code.css +98 -0
- package/css/connectors.css +17 -0
- package/css/content.css +22 -3
- package/css/crosshair.css +7 -7
- package/css/dataviz.css +5 -1
- package/css/diff.css +153 -0
- package/css/disclosure.css +53 -7
- package/css/dots.css +94 -7
- package/css/feedback.css +97 -7
- package/css/forms.css +113 -4
- package/css/legend.css +16 -9
- package/css/marks.css +38 -8
- package/css/motion.css +98 -53
- package/css/navigation.css +7 -0
- package/css/overlay.css +90 -3
- package/css/primitives.css +158 -13
- package/css/report.css +73 -56
- package/css/sidenote.css +67 -0
- package/css/site.css +16 -2
- package/css/sources.css +43 -1
- package/css/spark.css +62 -0
- package/css/spotlight.css +1 -1
- 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 +49 -1
- package/css/tree.css +134 -0
- package/css/workbench.css +1 -1
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/base.css +1 -1
- package/dist/css/bullet.css +1 -0
- package/dist/css/code.css +1 -0
- package/dist/css/connectors.css +1 -1
- 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/marks.css +1 -1
- package/dist/css/motion.css +1 -1
- package/dist/css/navigation.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/site.css +1 -1
- package/dist/css/sources.css +1 -1
- package/dist/css/spark.css +1 -0
- package/dist/css/spotlight.css +1 -1
- 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/dist/css/workbench.css +1 -1
- package/docs/adr/0003-theme-model.md +1 -1
- package/docs/annotations.md +133 -14
- package/docs/architecture.md +49 -6
- package/docs/bullet.md +78 -0
- package/docs/code.md +76 -0
- package/docs/contrast.md +116 -92
- package/docs/d2.md +196 -0
- package/docs/diff.md +146 -0
- package/docs/legends.md +23 -3
- package/docs/marks.md +9 -2
- package/docs/mermaid.md +169 -0
- package/docs/reference.md +201 -26
- package/docs/reporting.md +416 -57
- package/docs/sidenote.md +64 -0
- package/docs/sources.md +27 -0
- package/docs/spark.md +78 -0
- package/docs/stability.md +10 -2
- 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 +354 -16
- package/docs/vega.md +244 -0
- package/docs/workbench.md +7 -1
- package/glyphs/glyphs.js +13 -5
- package/llms.txt +285 -14
- package/package.json +95 -17
- package/qwik/index.d.ts +44 -59
- package/qwik/index.d.ts.map +1 -0
- package/qwik/index.js +65 -3
- package/react/index.d.ts +41 -61
- package/react/index.d.ts.map +1 -0
- package/react/index.js +63 -3
- package/solid/index.d.ts +68 -61
- package/solid/index.d.ts.map +1 -0
- package/solid/index.js +66 -3
- package/tokens/d2.d.ts +38 -0
- package/tokens/d2.js +71 -0
- package/tokens/d2.json +43 -0
- package/tokens/index.d.ts +5 -5
- package/tokens/index.js +15 -1
- package/tokens/index.json +9 -0
- package/tokens/mermaid.d.ts +23 -0
- package/tokens/mermaid.js +181 -0
- package/tokens/mermaid.json +163 -0
- package/tokens/resolved.json +45 -1
- package/tokens/skins.js +3 -2
- package/tokens/tokens.dtcg.json +26 -0
- package/tokens/vega.d.ts +34 -0
- package/tokens/vega.js +155 -0
- package/tokens/vega.json +179 -0
package/css/forms.css
CHANGED
|
@@ -77,12 +77,99 @@
|
|
|
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
|
+
|
|
90
|
+
/* Disabled affordance parity. The text inputs above style :disabled directly;
|
|
91
|
+
the controls that WRAP a native input (switch/check/segmented) showed no
|
|
92
|
+
disabled cue and their label kept cursor:pointer — a lie. Mirror the cue via
|
|
93
|
+
:has(input:disabled); the native-element controls (range/file) take :disabled
|
|
94
|
+
directly. (a11y/forms review C4.) */
|
|
95
|
+
.ui-range:disabled,
|
|
96
|
+
.ui-file:disabled,
|
|
97
|
+
.ui-switch:has(input:disabled),
|
|
98
|
+
.ui-check:has(input:disabled),
|
|
99
|
+
.ui-segmented__option:has(input:disabled) {
|
|
100
|
+
cursor: not-allowed;
|
|
101
|
+
opacity: 0.5;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Keep autofilled fields on-theme — the UA's yellow fill otherwise paints over
|
|
105
|
+
the monochrome surface and breaks the contrast story. (forms review C24.) */
|
|
106
|
+
.ui-input:autofill,
|
|
107
|
+
.ui-select:autofill,
|
|
108
|
+
.ui-textarea:autofill,
|
|
109
|
+
.ui-search input:autofill {
|
|
110
|
+
-webkit-text-fill-color: var(--text);
|
|
111
|
+
box-shadow: inset 0 0 0 100rem var(--bg-elevated);
|
|
112
|
+
caret-color: var(--text);
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
.ui-input[aria-invalid='true'],
|
|
81
116
|
.ui-select[aria-invalid='true'],
|
|
82
117
|
.ui-textarea[aria-invalid='true'] {
|
|
83
118
|
border-color: var(--danger);
|
|
84
119
|
}
|
|
85
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
|
+
|
|
86
173
|
.ui-hint {
|
|
87
174
|
color: var(--text-dim);
|
|
88
175
|
font-size: var(--text-2xs);
|
|
@@ -116,14 +203,18 @@
|
|
|
116
203
|
border-end-end-radius: var(--radius-md);
|
|
117
204
|
}
|
|
118
205
|
|
|
119
|
-
.ui-input-group > .ui-input:focus-visible
|
|
120
|
-
|
|
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) */
|
|
121
209
|
}
|
|
122
210
|
|
|
123
211
|
.ui-input-group__addon {
|
|
124
212
|
align-items: center;
|
|
125
213
|
background: var(--panel-soft);
|
|
126
|
-
|
|
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);
|
|
127
218
|
color: var(--text-dim);
|
|
128
219
|
display: flex;
|
|
129
220
|
font-size: var(--text-sm);
|
|
@@ -221,7 +312,10 @@
|
|
|
221
312
|
|
|
222
313
|
.ui-error-summary__title {
|
|
223
314
|
color: var(--danger);
|
|
224
|
-
|
|
315
|
+
|
|
316
|
+
/* The must-be-read failure heading uses the legible sans, not the low-legibility
|
|
317
|
+
Doto display face — reserve --display for decorative numerals. (forms C27.) */
|
|
318
|
+
font-family: var(--sans);
|
|
225
319
|
font-size: var(--text-sm);
|
|
226
320
|
font-weight: 700;
|
|
227
321
|
letter-spacing: var(--tracking-wide);
|
|
@@ -262,6 +356,14 @@
|
|
|
262
356
|
border-color: var(--accent);
|
|
263
357
|
}
|
|
264
358
|
|
|
359
|
+
/* Keyboard focus ring to match every sibling input (which use a 2px ring); the
|
|
360
|
+
border-colour shift alone read markedly fainter on this one control. Keyed to
|
|
361
|
+
:focus-visible on the inner input so it stays keyboard-only. (a11y review C12.) */
|
|
362
|
+
.ui-search:has(input:focus-visible) {
|
|
363
|
+
outline: 2px solid var(--focus-ring);
|
|
364
|
+
outline-offset: 1px;
|
|
365
|
+
}
|
|
366
|
+
|
|
265
367
|
.ui-search:focus-within::before {
|
|
266
368
|
background: var(--accent);
|
|
267
369
|
}
|
|
@@ -384,6 +486,13 @@
|
|
|
384
486
|
min-block-size: 2.9rem;
|
|
385
487
|
}
|
|
386
488
|
|
|
489
|
+
/* The whole label is the toggle target — float it to the ~44px floor so the
|
|
490
|
+
small track/box isn't a sub-target on touch (WCAG 2.5.8 — C24). */
|
|
491
|
+
.ui-switch,
|
|
492
|
+
.ui-check {
|
|
493
|
+
min-block-size: 2.9rem;
|
|
494
|
+
}
|
|
495
|
+
|
|
387
496
|
.ui-check input {
|
|
388
497
|
block-size: 1.15rem;
|
|
389
498
|
inline-size: 1.15rem;
|
package/css/legend.css
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
2
|
legend — opt-in data keys for charts, reports, and analytical figures.
|
|
3
3
|
|
|
4
|
-
A standalone, portable
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
A standalone, portable data key: `.ui-legend` reads the Tier-4 `--chart-*`
|
|
5
|
+
tokens and pairs with any chart (a token-themed inline SVG/canvas figure, or
|
|
6
|
+
an external engine like Vega-Lite — see docs/vega.md). Not imported by
|
|
7
|
+
core.css — import it beside css/dataviz.css.
|
|
8
8
|
|
|
9
9
|
Bronto paints and positions; it owns no scales, data mapping, or series
|
|
10
10
|
state. Colour is never the sole channel: a swatch mirrors its chart mark
|
|
@@ -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
|
}
|
|
@@ -205,12 +208,16 @@
|
|
|
205
208
|
|
|
206
209
|
.ui-legend--with-values .ui-legend__value {
|
|
207
210
|
margin-inline-start: 0;
|
|
211
|
+
text-align: end;
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
/* Interactive entries are real <button aria-pressed> controls. Bronto styles
|
|
211
215
|
the control + the inactive state; behaviors/legend.js (optional) flips
|
|
212
|
-
aria-pressed and emits an event. The host hides the series and announces it.
|
|
213
|
-
|
|
216
|
+
aria-pressed and emits an event. The host hides the series and announces it.
|
|
217
|
+
Scoped to button/role=button so a non-button entry never gets cursor:pointer
|
|
218
|
+
— a pointer-only affordance with no keyboard path is a lie (WCAG 2.1.1 — C11;
|
|
219
|
+
the behavior ignores and warns about non-button entries to match). */
|
|
220
|
+
.ui-legend--interactive .ui-legend__item:is(button, [role='button']) {
|
|
214
221
|
background: none;
|
|
215
222
|
border: 0;
|
|
216
223
|
border-radius: var(--radius-sm);
|
|
@@ -222,7 +229,7 @@
|
|
|
222
229
|
text-align: start;
|
|
223
230
|
}
|
|
224
231
|
|
|
225
|
-
.ui-legend--interactive .ui-legend__item:focus-visible {
|
|
232
|
+
.ui-legend--interactive .ui-legend__item:is(button, [role='button']):focus-visible {
|
|
226
233
|
outline: 2px solid var(--accent);
|
|
227
234
|
outline-offset: 2px;
|
|
228
235
|
}
|
package/css/marks.css
CHANGED
|
@@ -12,12 +12,20 @@
|
|
|
12
12
|
========================================================================== */
|
|
13
13
|
|
|
14
14
|
.ui-mark {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/* No base `--mark-color`: each draw style supplies its own fallback default
|
|
16
|
+
(highlight/box keep the subtle `--line-strong`; the full-opacity
|
|
17
|
+
`--underline`/`--strike` decorations default to the darker `--text-dim` so
|
|
18
|
+
they read on a light surface — C33). A tone modifier SETS `--mark-color`,
|
|
19
|
+
so it still wins everywhere the fallback would otherwise apply. */
|
|
20
|
+
|
|
21
|
+
/* A bare `<mark>` carries the UA `background-color: yellow`; the translucent
|
|
22
|
+
highlight gradient sits on top of it, so without this reset the UA yellow
|
|
23
|
+
bleeds through (an author declaration always beats the UA origin). */
|
|
24
|
+
background-color: transparent;
|
|
17
25
|
background-image: linear-gradient(
|
|
18
26
|
90deg,
|
|
19
|
-
color-mix(in srgb, var(--mark-color) 30%, transparent),
|
|
20
|
-
color-mix(in srgb, var(--mark-color) 30%, transparent)
|
|
27
|
+
color-mix(in srgb, var(--mark-color, var(--line-strong)) 30%, transparent),
|
|
28
|
+
color-mix(in srgb, var(--mark-color, var(--line-strong)) 30%, transparent)
|
|
21
29
|
);
|
|
22
30
|
background-repeat: no-repeat;
|
|
23
31
|
background-size: 100% 100%;
|
|
@@ -59,7 +67,7 @@
|
|
|
59
67
|
.ui-mark--underline {
|
|
60
68
|
background: none;
|
|
61
69
|
padding: 0;
|
|
62
|
-
text-decoration-color: var(--mark-color);
|
|
70
|
+
text-decoration-color: var(--mark-color, var(--text-dim));
|
|
63
71
|
text-decoration-line: underline;
|
|
64
72
|
text-decoration-thickness: 0.12em;
|
|
65
73
|
text-underline-offset: 0.18em;
|
|
@@ -67,21 +75,24 @@
|
|
|
67
75
|
|
|
68
76
|
.ui-mark--box {
|
|
69
77
|
background: none;
|
|
70
|
-
border: 1px solid var(--mark-color);
|
|
78
|
+
border: 1px solid var(--mark-color, var(--line-strong));
|
|
71
79
|
border-radius: var(--radius-sm);
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
.ui-mark--strike {
|
|
75
83
|
background: none;
|
|
76
84
|
padding: 0;
|
|
77
|
-
text-decoration-color: var(--mark-color);
|
|
85
|
+
text-decoration-color: var(--mark-color, var(--text-dim));
|
|
78
86
|
text-decoration-line: line-through;
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
/* Draw-on highlight sweep. Only the highlight fill animates, and only when
|
|
82
90
|
motion is welcome — reduced-motion keeps the resting full highlight. */
|
|
83
91
|
@media (prefers-reduced-motion: no-preference) {
|
|
84
|
-
|
|
92
|
+
/* The sweep animates `background-size`, i.e. the highlight FILL — so it is
|
|
93
|
+
inert on the no-fill styles (underline/box/strike). Scope it out rather
|
|
94
|
+
than let `--draw` look applied but do nothing. (content review C14.) */
|
|
95
|
+
.ui-mark--draw:not(.ui-mark--underline, .ui-mark--box, .ui-mark--strike) {
|
|
85
96
|
animation: ui-mark-draw 0.6s var(--ease, ease) both;
|
|
86
97
|
}
|
|
87
98
|
}
|
|
@@ -120,6 +131,10 @@
|
|
|
120
131
|
--mark-color: var(--accent);
|
|
121
132
|
}
|
|
122
133
|
|
|
134
|
+
.ui-bracket-note--success {
|
|
135
|
+
--mark-color: var(--success);
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
.ui-bracket-note--warning {
|
|
124
139
|
--mark-color: var(--warning);
|
|
125
140
|
}
|
|
@@ -142,3 +157,18 @@
|
|
|
142
157
|
text-decoration-line: underline;
|
|
143
158
|
}
|
|
144
159
|
}
|
|
160
|
+
|
|
161
|
+
/* Print: the highlight is a painted background, so force it through the print
|
|
162
|
+
"economy" default that would otherwise drop it. Settle the draw sweep to its
|
|
163
|
+
resting full highlight in case it is mid-animation when the page is printed. */
|
|
164
|
+
@media print {
|
|
165
|
+
.ui-mark {
|
|
166
|
+
-webkit-print-color-adjust: exact;
|
|
167
|
+
background-size: 100% 100%;
|
|
168
|
+
print-color-adjust: exact;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ui-mark--draw {
|
|
172
|
+
animation: none;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/css/motion.css
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
2
|
motion — keyframes + animation utilities
|
|
3
3
|
Restrained, dot-flavoured. Everything collapses under reduced-motion.
|
|
4
|
-
Keyframes from the legacy responsive layer live here so consumers that
|
|
5
|
-
import only core.css keep their existing animations.
|
|
6
4
|
========================================================================== */
|
|
7
5
|
|
|
8
6
|
@keyframes pulseDot {
|
|
@@ -18,45 +16,6 @@
|
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
@keyframes scan {
|
|
22
|
-
0% {
|
|
23
|
-
transform: translateY(-120%);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
100% {
|
|
27
|
-
transform: translateY(320%);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@keyframes growBar {
|
|
32
|
-
to {
|
|
33
|
-
transform: scaleX(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@keyframes drawLine {
|
|
38
|
-
from {
|
|
39
|
-
opacity: 0;
|
|
40
|
-
stroke-dasharray: 0 999;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
to {
|
|
44
|
-
opacity: 1;
|
|
45
|
-
stroke-dasharray: 999 0;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@keyframes pulseNode {
|
|
50
|
-
0%,
|
|
51
|
-
100% {
|
|
52
|
-
transform: scale(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
50% {
|
|
56
|
-
transform: scale(1.08);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
19
|
@keyframes pulseRing {
|
|
61
20
|
0% {
|
|
62
21
|
opacity: 0.7;
|
|
@@ -93,6 +52,28 @@
|
|
|
93
52
|
}
|
|
94
53
|
}
|
|
95
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
|
+
|
|
96
77
|
@keyframes uiDotIn {
|
|
97
78
|
0% {
|
|
98
79
|
opacity: 0;
|
|
@@ -199,10 +180,13 @@
|
|
|
199
180
|
animation: uiMatrixReveal var(--duration-slow) var(--ease-out) both;
|
|
200
181
|
}
|
|
201
182
|
|
|
202
|
-
/* 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.) */
|
|
203
187
|
.ui-stagger > * {
|
|
204
188
|
animation: uiRise var(--duration-slow) var(--ease-spring) both;
|
|
205
|
-
animation-delay: calc(var(--i, 0) * 60ms);
|
|
189
|
+
animation-delay: calc(min(var(--i, 0), 6) * 60ms);
|
|
206
190
|
}
|
|
207
191
|
|
|
208
192
|
.ui-stagger--auto > *:nth-child(1) {
|
|
@@ -233,9 +217,19 @@
|
|
|
233
217
|
animation-delay: 360ms;
|
|
234
218
|
}
|
|
235
219
|
|
|
236
|
-
/* Reveal-on-scroll: add .ui-reveal, toggle .is-visible
|
|
237
|
-
|
|
238
|
-
|
|
220
|
+
/* Reveal-on-scroll: add .ui-reveal, then toggle .is-visible (e.g. from an
|
|
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. */
|
|
232
|
+
@media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
|
|
239
233
|
.ui-reveal {
|
|
240
234
|
opacity: 0;
|
|
241
235
|
transform: translateY(14px);
|
|
@@ -283,10 +277,12 @@
|
|
|
283
277
|
|
|
284
278
|
/* --- Scroll-driven (progressive enhancement) — the scroll/view timeline
|
|
285
279
|
IS the engine, no JS. Everything is gated on `@supports
|
|
286
|
-
(animation-timeline: …)` so
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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). --- */
|
|
290
286
|
|
|
291
287
|
/* Reading-progress bar. Fixed hairline that fills with document scroll;
|
|
292
288
|
unsupported → a static, empty (scaleX(0)) bar. Pair with role="progressbar"
|
|
@@ -307,6 +303,30 @@
|
|
|
307
303
|
transform-origin: 100% 50%;
|
|
308
304
|
}
|
|
309
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
|
+
|
|
310
330
|
@supports (animation-timeline: scroll()) {
|
|
311
331
|
@media (prefers-reduced-motion: no-preference) {
|
|
312
332
|
.ui-scroll-progress {
|
|
@@ -321,13 +341,21 @@
|
|
|
321
341
|
|
|
322
342
|
/* Reveal-on-scroll with no JS and no IntersectionObserver: the element
|
|
323
343
|
rises + fades as it scrolls into view. Unsupported → fully visible
|
|
324
|
-
(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. */
|
|
325
353
|
@supports (animation-timeline: view()) {
|
|
326
354
|
@media (prefers-reduced-motion: no-preference) {
|
|
327
355
|
.ui-scroll-reveal {
|
|
328
|
-
animation:
|
|
356
|
+
animation: uiScrollReveal linear both;
|
|
329
357
|
animation-timeline: view();
|
|
330
|
-
animation-range: entry 0%
|
|
358
|
+
animation-range: entry 0% entry 100%;
|
|
331
359
|
}
|
|
332
360
|
}
|
|
333
361
|
}
|
|
@@ -365,14 +393,31 @@
|
|
|
365
393
|
scroll-behavior: auto;
|
|
366
394
|
}
|
|
367
395
|
|
|
396
|
+
/* !important is required: inside @layer bronto, layered rules lose to
|
|
397
|
+
unlayered author rules at equal specificity — only a layered !important
|
|
398
|
+
stays authoritative over unlayered declarations (CSS cascade §6.2). */
|
|
368
399
|
*,
|
|
369
400
|
*::before,
|
|
370
401
|
*::after {
|
|
371
402
|
animation-duration: 0.01ms !important;
|
|
372
403
|
animation-iteration-count: 1 !important;
|
|
404
|
+
|
|
405
|
+
/* Zero the delay too: `.ui-stagger` children animate `uiRise` with
|
|
406
|
+
`fill-mode: both`, so a non-zero `animation-delay` holds them at the
|
|
407
|
+
`opacity: 0` from-state for the full delay and then pops them in — the
|
|
408
|
+
exact late flash a reduced-motion user asked to avoid (C4). */
|
|
409
|
+
animation-delay: 0s !important;
|
|
373
410
|
transition-duration: 0.01ms !important;
|
|
374
411
|
}
|
|
375
412
|
|
|
413
|
+
/* Freeze the shimmer gradient mid-sweep would expose a diagonal band.
|
|
414
|
+
Flatten the skeleton to its base panel colour instead. */
|
|
415
|
+
.ui-skeleton {
|
|
416
|
+
animation: none;
|
|
417
|
+
background: var(--panel-soft);
|
|
418
|
+
background-size: auto;
|
|
419
|
+
}
|
|
420
|
+
|
|
376
421
|
.ui-reveal {
|
|
377
422
|
opacity: 1 !important;
|
|
378
423
|
transform: none !important;
|
package/css/navigation.css
CHANGED
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
|
|
@@ -249,7 +275,7 @@ dialog.ui-modal[open]::backdrop {
|
|
|
249
275
|
}
|
|
250
276
|
|
|
251
277
|
.ui-menu__item::before {
|
|
252
|
-
background:
|
|
278
|
+
background: currentcolor;
|
|
253
279
|
border-radius: 50%;
|
|
254
280
|
content: '';
|
|
255
281
|
block-size: 0.3rem;
|
|
@@ -275,6 +301,34 @@ dialog.ui-modal[open]::backdrop {
|
|
|
275
301
|
}
|
|
276
302
|
}
|
|
277
303
|
|
|
304
|
+
.ui-menu__item:focus-visible {
|
|
305
|
+
background: var(--bg-accent);
|
|
306
|
+
color: var(--text);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.ui-menu__item:focus-visible::before {
|
|
310
|
+
opacity: 1;
|
|
311
|
+
}
|
|
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
|
+
|
|
278
332
|
/* --- Combobox: an input with a filtered listbox popup (APG pattern,
|
|
279
333
|
wired by initCombobox). Reuses the menu surface tokens. --- */
|
|
280
334
|
.ui-combobox {
|
|
@@ -328,6 +382,18 @@ dialog.ui-modal[open]::backdrop {
|
|
|
328
382
|
color: var(--text);
|
|
329
383
|
}
|
|
330
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
|
+
|
|
331
397
|
.ui-combobox__empty {
|
|
332
398
|
color: var(--text-dim);
|
|
333
399
|
font-size: var(--text-2xs);
|
|
@@ -363,6 +429,14 @@ dialog.ui-modal[open]::backdrop {
|
|
|
363
429
|
inset-inline: 0;
|
|
364
430
|
min-inline-size: 0;
|
|
365
431
|
}
|
|
432
|
+
|
|
433
|
+
/* Let the combobox shed its 14rem floor and span the viewport so the input
|
|
434
|
+
(and its full-width listbox) can't overflow a narrow screen. */
|
|
435
|
+
.ui-combobox {
|
|
436
|
+
display: block;
|
|
437
|
+
inline-size: 100%;
|
|
438
|
+
min-inline-size: 0;
|
|
439
|
+
}
|
|
366
440
|
}
|
|
367
441
|
|
|
368
442
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -376,3 +450,16 @@ dialog.ui-modal[open]::backdrop {
|
|
|
376
450
|
transition: none;
|
|
377
451
|
}
|
|
378
452
|
}
|
|
453
|
+
|
|
454
|
+
/* Forced-colors drops both `backdrop-filter: blur()` and the `color-mix()`
|
|
455
|
+
scrim, so the dialog/lightbox would float over an undimmed page with no
|
|
456
|
+
separation. Re-assert a translucent scrim with `forced-color-adjust: none`
|
|
457
|
+
so the layering reads in High Contrast, matching the meter/dot precedent
|
|
458
|
+
(C28). */
|
|
459
|
+
@media (forced-colors: active) {
|
|
460
|
+
.ui-modal::backdrop,
|
|
461
|
+
.ui-lightbox::backdrop {
|
|
462
|
+
background: color-mix(in srgb, #000 50%, transparent);
|
|
463
|
+
forced-color-adjust: none;
|
|
464
|
+
}
|
|
465
|
+
}
|