actual-css 0.9.2 → 0.10.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 +96 -0
- package/README.md +65 -44
- package/components.json +33 -13
- package/dist/actual.full.css +585 -499
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/app-ui.md +1 -1
- package/docs/design-notes/column-layout.md +103 -520
- package/docs/design-notes/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- package/docs/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +2 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/badge.md +6 -7
- package/docs/pages/components/bleed.md +42 -0
- package/docs/pages/components/button.md +30 -4
- package/docs/pages/components/card.md +21 -86
- package/docs/pages/components/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +16 -16
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- package/docs/pages/components/spinner.md +3 -3
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/choice-cards.md +39 -0
- package/docs/pages/forms/combobox.md +23 -11
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +6 -0
- package/docs/pages/foundations/theming.md +64 -18
- package/docs/pages/foundations/tokens.md +28 -10
- package/docs/pages/guides/introduction.md +15 -23
- package/docs/pages/guides/modular-import.md +53 -6
- package/docs/pages/guides/progressive-enhancement.md +6 -3
- package/docs/pages/guides/tailwind.md +17 -5
- package/docs/pages/layout/column-layout.md +92 -54
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +5 -3
- package/llms.txt +24 -4
- package/package.json +6 -6
- package/reserved-classes.json +5 -30
- package/skills/actual-css/PROJECT.md.example +65 -0
- package/skills/actual-css/README.md +64 -0
- package/skills/actual-css/SKILL.md +234 -0
- package/skills/actual-css/references/accessibility.md +44 -0
- package/skills/actual-css/references/api-and-vocabulary.md +94 -0
- package/skills/actual-css/references/enhancements.md +59 -0
- package/skills/actual-css/references/forms.md +49 -0
- package/skills/actual-css/references/layout-and-theming.md +82 -0
- package/skills/actual-css/references/review-checklist.md +51 -0
- package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
- package/src/css/components/accordion.css +44 -7
- package/src/css/components/alert.css +23 -43
- package/src/css/components/app-nav.css +9 -0
- package/src/css/components/badge.css +11 -39
- package/src/css/components/bleed.css +79 -0
- package/src/css/components/button.css +6 -17
- package/src/css/components/card.css +7 -40
- package/src/css/components/close.css +91 -0
- package/src/css/components/data-list.css +18 -0
- package/src/css/components/drawer.css +15 -56
- package/src/css/components/fab.css +10 -0
- package/src/css/components/flyout.css +1 -0
- package/src/css/components/index.css +2 -0
- package/src/css/components/join.css +36 -20
- package/src/css/components/menu.css +12 -0
- package/src/css/components/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- package/src/css/core/focus.css +9 -3
- package/src/css/core/theme.css +25 -43
- package/src/css/core/tokens.css +1 -1
- package/src/css/core/variants.css +3 -33
- package/src/css/forms/choice-card.css +74 -4
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +15 -8
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/range.css +23 -8
- package/src/css/forms/switch.css +2 -3
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/column-layout.css +26 -111
- package/src/css/utilities/extra.css +7 -6
- package/src/js/dialog.js +95 -24
- package/src/js/enhance.js +7 -3
- package/src/js/escape.js +1 -0
- package/src/js/flyout.js +10 -1
- package/src/js/focus-group.js +2 -1
- package/src/js/keys.js +14 -0
- package/src/js/menu.js +2 -1
- package/src/js/status.js +19 -0
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +18 -1
- package/src/js/validation.js +5 -6
- package/dist/actual.css +0 -876
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* --btn-min-size
|
|
18
18
|
* --btn-pad-x
|
|
19
19
|
* --btn-radius
|
|
20
|
-
* --btn-focus-color
|
|
21
20
|
* --btn-gap
|
|
22
21
|
* --btn-hover-overlay
|
|
23
22
|
* --btn-default-bg
|
|
@@ -30,7 +29,6 @@
|
|
|
30
29
|
* --btn-fg
|
|
31
30
|
* --btn-border
|
|
32
31
|
* --btn-default-hover-bg
|
|
33
|
-
* --btn-focus-ring-color
|
|
34
32
|
* --btn-border-block-end-color
|
|
35
33
|
* --btn-border-block-end-width
|
|
36
34
|
* --spinner-width
|
|
@@ -57,7 +55,6 @@
|
|
|
57
55
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
58
56
|
--btn-default-hover-bg: var(--btn-bg);
|
|
59
57
|
--btn-radius: var(--radius);
|
|
60
|
-
--btn-focus-color: var(--intent, var(--focus));
|
|
61
58
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
62
59
|
|
|
63
60
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
@@ -99,12 +96,6 @@
|
|
|
99
96
|
padding: 0;
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
103
|
-
.btn {
|
|
104
|
-
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
99
|
/* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
|
|
109
100
|
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
110
101
|
.btn.ghost {
|
|
@@ -192,15 +183,13 @@
|
|
|
192
183
|
text-decoration-thickness: 0.12em;
|
|
193
184
|
}
|
|
194
185
|
|
|
186
|
+
/* why: the ring must contrast with the surface around the button, not with
|
|
187
|
+
the button, so it is --focus for every intent and variant — the theme
|
|
188
|
+
guarantees --focus against --surface and --surface-solid, which covers
|
|
189
|
+
the page, a solid band and nested surfaces without tracking any context. */
|
|
195
190
|
.btn:focus-visible {
|
|
196
|
-
outline:
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.btn.link:focus-visible {
|
|
201
|
-
outline: var(--focus-outline);
|
|
202
|
-
outline-offset: 0.2em;
|
|
203
|
-
box-shadow: none;
|
|
191
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
192
|
+
outline-offset: var(--focus-outline-offset);
|
|
204
193
|
}
|
|
205
194
|
|
|
206
195
|
.btn:disabled {
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Child contract:
|
|
21
21
|
* Direct .bleed children escape --card-pad (negative inset, wider media);
|
|
22
|
-
* edge children inherit the card corners. .bleed must be a direct child
|
|
22
|
+
* edge children inherit the card corners. .bleed must be a direct child, and
|
|
23
|
+
* its rules live in components/bleed.css — the card relays --card-pad to the
|
|
24
|
+
* shared --surface-pad that they read.
|
|
23
25
|
*
|
|
24
26
|
* Internal:
|
|
25
27
|
* --card-bg
|
|
@@ -50,6 +52,10 @@
|
|
|
50
52
|
--card-bg: var(--ui-bg, var(--surface-raised));
|
|
51
53
|
--card-fg: var(--ui-fg, var(--text));
|
|
52
54
|
|
|
55
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). Relayed
|
|
56
|
+
from --card-pad so .compact and an author override both reach it. */
|
|
57
|
+
--surface-pad: var(--card-pad);
|
|
58
|
+
|
|
53
59
|
/* Keep contextual descendants tied to the surface this card actually owns. */
|
|
54
60
|
--heading: var(--card-fg);
|
|
55
61
|
--busy-overlay-bg: var(--card-bg);
|
|
@@ -115,45 +121,6 @@
|
|
|
115
121
|
--card-pad: var(--space-20);
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
/* Full-bleed children */
|
|
119
|
-
.card > .bleed {
|
|
120
|
-
margin-inline: calc(var(--card-pad) * -1);
|
|
121
|
-
max-inline-size: none;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.card > .bleed:first-child {
|
|
125
|
-
margin-block-start: calc(var(--card-pad) * -1);
|
|
126
|
-
border-start-start-radius: inherit;
|
|
127
|
-
border-start-end-radius: inherit;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.card > .bleed:last-child {
|
|
131
|
-
margin-block-end: calc(var(--card-pad) * -1);
|
|
132
|
-
border-end-start-radius: inherit;
|
|
133
|
-
border-end-end-radius: inherit;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* Media bleed */
|
|
137
|
-
.card > :where(img, picture, video, canvas, svg).bleed {
|
|
138
|
-
display: block;
|
|
139
|
-
inline-size: calc(100% + var(--card-pad) * 2);
|
|
140
|
-
max-inline-size: none;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
.card > picture.bleed {
|
|
144
|
-
overflow: hidden;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.card > picture.bleed > img {
|
|
148
|
-
display: block;
|
|
149
|
-
inline-size: 100%;
|
|
150
|
-
block-size: auto;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/* Padded structural bleed */
|
|
154
|
-
.card > :where(header, footer, figure).bleed {
|
|
155
|
-
padding: var(--card-pad);
|
|
156
|
-
}
|
|
157
124
|
@media print {
|
|
158
125
|
.card {
|
|
159
126
|
border: var(--border-width) solid CanvasText;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Close button — the shared X that removes or closes the thing it sits in.
|
|
3
|
+
*
|
|
4
|
+
* Docs: docs/pages/components/close.md
|
|
5
|
+
*
|
|
6
|
+
* .close is always the X, painted from the --icon-close mask: an empty
|
|
7
|
+
* <button class="close" aria-label="…"> needs no icon font and no text glyph.
|
|
8
|
+
* The mechanism stays with the container (command="request-close",
|
|
9
|
+
* method="dialog", command="--dismiss", application logic); the class only
|
|
10
|
+
* names the visual action.
|
|
11
|
+
*
|
|
12
|
+
* The containers that host one (alert, badge) tune it from their own files by
|
|
13
|
+
* declaring the size hooks and their geometry; this file owns the control
|
|
14
|
+
* and the dialog-surface corner, shared by .modal and .drawer.
|
|
15
|
+
*
|
|
16
|
+
* Public hooks:
|
|
17
|
+
* --close-size
|
|
18
|
+
* --close-icon-size
|
|
19
|
+
*/
|
|
20
|
+
.close {
|
|
21
|
+
display: inline-grid;
|
|
22
|
+
place-items: center;
|
|
23
|
+
flex: none;
|
|
24
|
+
inline-size: var(--close-size, var(--control-size));
|
|
25
|
+
block-size: var(--close-size, var(--control-size));
|
|
26
|
+
padding: 0;
|
|
27
|
+
border: 0;
|
|
28
|
+
border-radius: var(--radius);
|
|
29
|
+
background: transparent;
|
|
30
|
+
color: inherit;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.close::before {
|
|
35
|
+
content: "";
|
|
36
|
+
inline-size: var(--close-icon-size, 1rem);
|
|
37
|
+
block-size: var(--close-icon-size, 1rem);
|
|
38
|
+
background: currentColor;
|
|
39
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
40
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* An inset overlay, not a background: it tints whatever fill the context gave
|
|
44
|
+
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
45
|
+
@media (hover: hover) {
|
|
46
|
+
.close:hover {
|
|
47
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.close:active {
|
|
52
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.close:focus-visible {
|
|
56
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
57
|
+
outline-offset: var(--focus-outline-offset);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Dialog corner — out of the content flow, anchored to the positioned dialog
|
|
61
|
+
surface rather than to the header or a scroll container inside it. Matched
|
|
62
|
+
on the documented positions (the dialog, its form, its header, or a form
|
|
63
|
+
inside that header) so a .close inside content — an alert in the body —
|
|
64
|
+
keeps its own place. */
|
|
65
|
+
:where(dialog):is(.modal, .drawer) > .close,
|
|
66
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
67
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
68
|
+
position: absolute;
|
|
69
|
+
z-index: 1;
|
|
70
|
+
inset-block-start: var(--space-20);
|
|
71
|
+
inset-inline-end: var(--space-20);
|
|
72
|
+
background: var(--surface-subtle);
|
|
73
|
+
color: var(--text-muted);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (hover: hover) {
|
|
77
|
+
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
78
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
79
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
80
|
+
color: var(--text);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (forced-colors: active) {
|
|
85
|
+
/* The X is a currentColor mask fill: without this the system remaps the
|
|
86
|
+
background to Canvas and the glyph disappears. Kept on the pseudo so the
|
|
87
|
+
host keeps its native color and focus adaptation. */
|
|
88
|
+
.close::before {
|
|
89
|
+
forced-color-adjust: none;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* A plain <dl> handles richer relationships (several terms or descriptions)
|
|
8
8
|
* and stays untouched by this component.
|
|
9
9
|
*
|
|
10
|
+
* Variants:
|
|
11
|
+
* .stacked — value under its term rather than beside it.
|
|
12
|
+
*
|
|
10
13
|
* Why margin resets: neutralizes .prose list margins if .data-list appears
|
|
11
14
|
* inside an authored rich-text scope; the grid owns row/column rhythm.
|
|
12
15
|
*/
|
|
@@ -27,3 +30,18 @@
|
|
|
27
30
|
min-inline-size: 0;
|
|
28
31
|
margin: 0;
|
|
29
32
|
}
|
|
33
|
+
|
|
34
|
+
/* Stacked — the value sits under its term instead of beside it, for narrow
|
|
35
|
+
columns and for values too long to sit in a second column.
|
|
36
|
+
Why the pair separation rides on the term's margin: the children alternate
|
|
37
|
+
dt/dd with no element around a pair, so the grid has no row boundary to give
|
|
38
|
+
a larger gap to. The tight gap is the row gap; the term reopens the space. */
|
|
39
|
+
.data-list.stacked {
|
|
40
|
+
grid-template-columns: minmax(0, 1fr);
|
|
41
|
+
gap: var(--space-10);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.data-list.stacked > dt + dt,
|
|
45
|
+
.data-list.stacked > dd + dt {
|
|
46
|
+
margin-block-start: var(--space-30);
|
|
47
|
+
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* --drawer-size
|
|
18
18
|
* --drawer-pad
|
|
19
19
|
*
|
|
20
|
+
* The corner close control is .close (components/close.css).
|
|
21
|
+
*
|
|
20
22
|
* Internal:
|
|
21
23
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
22
24
|
* --drawer-focus-bleed
|
|
@@ -26,6 +28,8 @@
|
|
|
26
28
|
:where(dialog).drawer {
|
|
27
29
|
--drawer-size: 20rem;
|
|
28
30
|
--drawer-pad: var(--space-40);
|
|
31
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). */
|
|
32
|
+
--surface-pad: var(--drawer-pad);
|
|
29
33
|
--drawer-closed-translate: -100%;
|
|
30
34
|
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
31
35
|
the same variable through inheritance and never lags the panel. */
|
|
@@ -125,15 +129,20 @@
|
|
|
125
129
|
/* The body region absorbs the panel's height and scrolls. A drawer is a
|
|
126
130
|
full-height shell by definition, so it always scrolls its body — unlike the
|
|
127
131
|
modal, which sizes to its content and opts in with .scrollable. Header and
|
|
128
|
-
footer keep their own tracks; .
|
|
132
|
+
footer keep their own tracks; .close is named because a generic
|
|
129
133
|
exclusion list is easier to trust than its `position: absolute`. */
|
|
130
|
-
:where(dialog).drawer > :not(:is(header, footer, form, .
|
|
131
|
-
:where(dialog).drawer > form > :not(:is(header, footer, .
|
|
132
|
-
--drawer-focus-bleed: calc(
|
|
134
|
+
:where(dialog).drawer > :not(:is(header, footer, form, .close)),
|
|
135
|
+
:where(dialog).drawer > form > :not(:is(header, footer, .close)) {
|
|
136
|
+
--drawer-focus-bleed: calc(
|
|
137
|
+
var(--focus-outline-offset) +
|
|
138
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
139
|
+
);
|
|
133
140
|
|
|
134
141
|
min-block-size: 0;
|
|
135
142
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
136
|
-
header and footer edges.
|
|
143
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
144
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
145
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
137
146
|
margin-inline: calc(var(--drawer-focus-bleed) * -1);
|
|
138
147
|
padding-inline: var(--drawer-focus-bleed);
|
|
139
148
|
overflow: auto;
|
|
@@ -205,55 +214,5 @@
|
|
|
205
214
|
control. */
|
|
206
215
|
:where(dialog).drawer > header,
|
|
207
216
|
:where(dialog).drawer > form > header {
|
|
208
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/* Close control, owned here so a drawer-only build does not depend on
|
|
212
|
-
modal.css. Mirrors the modal's .dialog-close placement: absolute at the
|
|
213
|
-
panel's top inline-end, out of the content flow. */
|
|
214
|
-
.drawer-close {
|
|
215
|
-
--drawer-close-icon-size: 1rem;
|
|
216
|
-
|
|
217
|
-
display: inline-grid;
|
|
218
|
-
place-items: center;
|
|
219
|
-
padding: 0;
|
|
220
|
-
border: 0;
|
|
221
|
-
border-radius: var(--radius);
|
|
222
|
-
color: var(--text-muted);
|
|
223
|
-
cursor: pointer;
|
|
224
|
-
|
|
225
|
-
position: absolute;
|
|
226
|
-
z-index: 1;
|
|
227
|
-
inset-block-start: var(--space-20);
|
|
228
|
-
inset-inline-end: var(--space-20);
|
|
229
|
-
|
|
230
|
-
inline-size: var(--control-size);
|
|
231
|
-
block-size: var(--control-size);
|
|
232
|
-
|
|
233
|
-
background: var(--surface-subtle);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.drawer-close::before {
|
|
237
|
-
content: "";
|
|
238
|
-
inline-size: var(--drawer-close-icon-size);
|
|
239
|
-
block-size: var(--drawer-close-icon-size);
|
|
240
|
-
background: currentColor;
|
|
241
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
242
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
@media (hover: hover) {
|
|
246
|
-
.drawer-close:hover {
|
|
247
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
248
|
-
color: var(--text);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.drawer-close:active {
|
|
253
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.drawer-close:focus-visible {
|
|
257
|
-
outline: 2px solid transparent;
|
|
258
|
-
box-shadow: var(--focus-ring-shadow);
|
|
217
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
259
218
|
}
|
|
@@ -128,3 +128,13 @@
|
|
|
128
128
|
opacity: 1;
|
|
129
129
|
transform: none;
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
@media (forced-colors: active) {
|
|
133
|
+
/* Both toggle markers are currentColor mask fills: without this the system
|
|
134
|
+
remaps the backgrounds to Canvas and the glyphs disappear. Kept on the
|
|
135
|
+
pseudos so the host keeps its native color and focus adaptation. */
|
|
136
|
+
.fab > summary::before,
|
|
137
|
+
.fab > summary::after {
|
|
138
|
+
forced-color-adjust: none;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Controls */
|
|
2
2
|
@import "./button.css";
|
|
3
|
+
@import "./close.css";
|
|
3
4
|
@import "./badge.css";
|
|
4
5
|
@import "./alert.css";
|
|
5
6
|
@import "./actions.css";
|
|
@@ -8,6 +9,7 @@
|
|
|
8
9
|
@import "./card.css";
|
|
9
10
|
@import "./modal.css";
|
|
10
11
|
@import "./drawer.css";
|
|
12
|
+
@import "./bleed.css";
|
|
11
13
|
|
|
12
14
|
/* Overlays */
|
|
13
15
|
@import "./flyout.css";
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
min-block-size: var(--control-size);
|
|
51
51
|
padding-inline: var(--space-30);
|
|
52
52
|
border: var(--border-width) solid var(--control-border);
|
|
53
|
+
/* An addon is a field segment: it honours the same lower-edge extension
|
|
54
|
+
points as text controls (control.css), so a theme's underline or accent
|
|
55
|
+
edge reaches it instead of stopping at the field. */
|
|
56
|
+
border-block-end-color: var(--control-border-block-end-color, var(--control-border));
|
|
57
|
+
border-block-end-width: var(--control-border-block-end-width, var(--border-width));
|
|
53
58
|
border-radius: var(--radius);
|
|
54
59
|
background: var(--control-bg);
|
|
55
60
|
color: var(--control-fg);
|
|
@@ -97,37 +102,48 @@
|
|
|
97
102
|
z-index: 1;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
/* Keep an active focus ring above an adjacent hovered control. This matters
|
|
101
|
-
on platforms where clicking a button does not move focus from the input. */
|
|
102
|
-
.join > :is(:focus, :focus-within) {
|
|
103
|
-
z-index: 4;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
105
|
/* An input's border must read as the "primary" edge against an attached
|
|
107
106
|
action (e.g. a password-reveal button) at all times, not only while
|
|
108
107
|
hovered or focused — otherwise the shared edge flips ownership as the
|
|
109
|
-
user's pointer moves, which reads as a rendering glitch.
|
|
110
|
-
|
|
108
|
+
user's pointer moves, which reads as a rendering glitch. The :has()
|
|
109
|
+
variant covers a control nested one wrapper level deep (e.g. a combobox
|
|
110
|
+
element): the wrapper is the flex item, so it carries the z-index — a
|
|
111
|
+
z-index on the inner control would be a no-op on a static grandchild. */
|
|
112
|
+
.join > :is(.input, .textarea, .select),
|
|
113
|
+
.join > :has(> :is(.input, .textarea, .select)) {
|
|
111
114
|
z-index: 3;
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
/*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
/* Keep an active focus line above every neighbour, including an adjacent
|
|
118
|
+
field and a hovered control (some platforms keep focus on the input while
|
|
119
|
+
a button is clicked). It must follow the primary-edge rule above: both are
|
|
120
|
+
(0,2,0), and when it came first a focused field stayed at 3 and the next
|
|
121
|
+
field, painted later, covered the 1px shared edge of its line. */
|
|
122
|
+
.join > :is(:focus, :focus-within) {
|
|
123
|
+
z-index: 4;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
/* One wrapper level (e.g. a combobox element around the native control) is
|
|
127
|
+
supported generically — no widget is named here. The wrapper is the flex
|
|
128
|
+
item that receives the corner longhands above; the inner control inherits
|
|
129
|
+
them. The wrapper is also what align-items stretches: block-size: 100%
|
|
130
|
+
resolves against that stretched size, so the field fills it. :hover and
|
|
131
|
+
:focus-within already propagate to the wrapper, so the z-index rules above
|
|
132
|
+
need no duplication. */
|
|
133
|
+
.join > * > :is(.input, .textarea, .select) {
|
|
134
|
+
block-size: 100%;
|
|
135
|
+
border-radius: inherit;
|
|
126
136
|
}
|
|
127
137
|
|
|
138
|
+
/* Text controls paint their own inset focus outline (control.css), so a
|
|
139
|
+
.join never moves the ring to the group: button-only groups and wrapped
|
|
140
|
+
inputs alike keep per-control focus, with no :has() group treatment. */
|
|
141
|
+
|
|
128
142
|
/* A default outline action belongs to the composite field border. An explicit
|
|
129
143
|
intent still provides its own outline color — --intent is guaranteed-invalid
|
|
130
|
-
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
131
|
-
|
|
144
|
+
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
145
|
+
The second :has() covers a control nested one wrapper level deep. */
|
|
146
|
+
.join:has(> :is(.input, .textarea, .select)) > .btn.outline,
|
|
147
|
+
.join:has(> * > :is(.input, .textarea, .select)) > .btn.outline {
|
|
132
148
|
--btn-border-color: var(--intent, var(--border));
|
|
133
149
|
}
|
|
@@ -133,3 +133,15 @@
|
|
|
133
133
|
border: 0;
|
|
134
134
|
border-block-start: var(--border-width) solid var(--border);
|
|
135
135
|
}
|
|
136
|
+
|
|
137
|
+
@media (forced-colors: active) {
|
|
138
|
+
/* The check mark is a currentColor mask fill and the mixed/radio marks are
|
|
139
|
+
currentColor gradients: without this the system remaps them to Canvas or
|
|
140
|
+
drops the image and the state disappears. Kept on the pseudo so the host
|
|
141
|
+
keeps its native color and focus adaptation. */
|
|
142
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before,
|
|
143
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before,
|
|
144
|
+
.menu-item[role="menuitemradio"]::before {
|
|
145
|
+
forced-color-adjust: none;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* --dialog-icon-size
|
|
13
13
|
* --dialog-icon-glyph-size
|
|
14
14
|
*
|
|
15
|
+
* The corner close control is .close (components/close.css).
|
|
16
|
+
*
|
|
15
17
|
* Internal:
|
|
16
|
-
* --dialog-close-icon-size
|
|
17
18
|
* --dialog-focus-bleed
|
|
18
19
|
* --dialog-viewport-block
|
|
19
20
|
* --dialog-viewport-gap-default
|
|
@@ -99,11 +100,16 @@
|
|
|
99
100
|
|
|
100
101
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
101
102
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
102
|
-
--dialog-focus-bleed: calc(
|
|
103
|
+
--dialog-focus-bleed: calc(
|
|
104
|
+
var(--focus-outline-offset) +
|
|
105
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
106
|
+
);
|
|
103
107
|
|
|
104
108
|
min-block-size: 0;
|
|
105
109
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
106
|
-
header and footer edges.
|
|
110
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
111
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
112
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
107
113
|
margin-inline: calc(var(--dialog-focus-bleed) * -1);
|
|
108
114
|
padding-inline: var(--dialog-focus-bleed);
|
|
109
115
|
overflow: auto;
|
|
@@ -126,7 +132,7 @@
|
|
|
126
132
|
The form/.stack anatomy is structural, so it stays in :where() and never
|
|
127
133
|
lends .stack's specificity to a bare <form>. */
|
|
128
134
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
129
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
135
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
130
136
|
}
|
|
131
137
|
|
|
132
138
|
:where(dialog).modal > :where(form, .stack) > footer {
|
|
@@ -289,56 +295,3 @@ html.has-modal-open.had-scrollbar {
|
|
|
289
295
|
transform: translateX(0.35rem);
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
|
-
|
|
293
|
-
/* Close control. A close button is an action attached to a surface, so it
|
|
294
|
-
never participates in the content flow — absolute at the panel's top
|
|
295
|
-
inline-end, inside the surface. The glyph comes from --icon-close, masked to
|
|
296
|
-
currentColor, so no icon font or text glyph is required in the markup. */
|
|
297
|
-
.dialog-close {
|
|
298
|
-
--dialog-close-icon-size: 1rem;
|
|
299
|
-
|
|
300
|
-
display: inline-grid;
|
|
301
|
-
place-items: center;
|
|
302
|
-
padding: 0;
|
|
303
|
-
border: 0;
|
|
304
|
-
border-radius: var(--radius);
|
|
305
|
-
color: var(--text-muted);
|
|
306
|
-
cursor: pointer;
|
|
307
|
-
|
|
308
|
-
/* Out of flow, anchored to the positioned dialog surface, not the header or
|
|
309
|
-
any scroll container inside it. */
|
|
310
|
-
position: absolute;
|
|
311
|
-
z-index: 1;
|
|
312
|
-
inset-block-start: var(--space-20);
|
|
313
|
-
inset-inline-end: var(--space-20);
|
|
314
|
-
|
|
315
|
-
inline-size: var(--control-size);
|
|
316
|
-
block-size: var(--control-size);
|
|
317
|
-
|
|
318
|
-
background: var(--surface-subtle);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
.dialog-close::before {
|
|
322
|
-
content: "";
|
|
323
|
-
inline-size: var(--dialog-close-icon-size);
|
|
324
|
-
block-size: var(--dialog-close-icon-size);
|
|
325
|
-
background: currentColor;
|
|
326
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
327
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
@media (hover: hover) {
|
|
331
|
-
.dialog-close:hover {
|
|
332
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
333
|
-
color: var(--text);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
.dialog-close:active {
|
|
338
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.dialog-close:focus-visible {
|
|
342
|
-
outline: 2px solid transparent;
|
|
343
|
-
box-shadow: var(--focus-ring-shadow);
|
|
344
|
-
}
|
|
@@ -68,11 +68,16 @@
|
|
|
68
68
|
display: inline-flex;
|
|
69
69
|
align-items: center;
|
|
70
70
|
min-block-size: var(--control-size-sm);
|
|
71
|
+
padding-block: 0;
|
|
71
72
|
padding-inline: var(--space-20);
|
|
73
|
+
border: 0;
|
|
72
74
|
border-radius: var(--radius);
|
|
75
|
+
background: transparent;
|
|
73
76
|
color: var(--ui-fg, var(--text-muted));
|
|
77
|
+
font: inherit;
|
|
74
78
|
font-weight: var(--font-weight-medium);
|
|
75
79
|
text-decoration: none;
|
|
80
|
+
cursor: pointer;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
@@ -97,6 +102,28 @@
|
|
|
97
102
|
color: var(--state-selected);
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
/* On a bar with its own --ui-bg/--ui-fg the fill is a tint of the link's own
|
|
106
|
+
ink, the .btn.ghost hover recipe, so it reads on light and dark bars alike
|
|
107
|
+
instead of snapping back to --surface-subtle. Not --ui-hover-bg: that is the
|
|
108
|
+
bar's own hover and can equal its fill. The ink follows --ui-fg too — the
|
|
109
|
+
selected accent has no contrast pair with an arbitrary bar. The baseline
|
|
110
|
+
above keeps the light chip where color-mix is missing, with ink that is
|
|
111
|
+
legible on it. */
|
|
112
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
113
|
+
.nav-link:is(:hover, [aria-current]) {
|
|
114
|
+
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.nav-link:hover {
|
|
118
|
+
color: var(--ui-fg, var(--text));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.nav-link[aria-current],
|
|
122
|
+
.nav-link[aria-current]:hover {
|
|
123
|
+
color: var(--ui-fg, var(--state-selected));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
100
127
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
101
128
|
Links fill each row so the whole line is an interactive target; the
|
|
102
129
|
horizontal .navbar-nav links keep their intrinsic size. */
|
package/src/css/core/focus.css
CHANGED
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
pointer/mouse focus and interactive components layer their ring on top.
|
|
4
4
|
|
|
5
5
|
Invariant: an interactive component must never rely on box-shadow alone
|
|
6
|
-
for its keyboard focus indicator
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
7
|
+
shadows.
|
|
8
|
+
|
|
9
|
+
Component indicators are a solid --focus-ring-width line in --focus: inset
|
|
10
|
+
on fields (inside their border box), offset by --focus-outline-offset on
|
|
11
|
+
discrete actions. The color never derives from an intent or a local
|
|
12
|
+
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
13
|
+
--surface-solid, so one token reads on the page, on a solid band and on
|
|
14
|
+
any nested surface. */
|
|
9
15
|
:focus {
|
|
10
16
|
outline: var(--focus-outline);
|
|
11
17
|
outline-offset: var(--focus-outline-offset);
|