actual-css 0.5.0 → 0.7.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 +63 -0
- package/dist/actual.css +1 -0
- package/dist/actual.full.css +589 -131
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/browser-support.md +39 -14
- package/docs/design-notes/css-anchor.md +1 -1
- package/docs/design-notes/enhancement-contract.md +30 -13
- package/docs/design-notes/forced-colors.md +11 -7
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +15 -9
- package/docs/design-notes/platform-alignment.md +60 -0
- package/docs/design-notes/popover-manual-poc.md +248 -0
- package/docs/design-notes/references.md +4 -0
- package/docs/design-notes/steps.md +748 -0
- package/docs/design-notes/surface-reparenting.md +122 -0
- package/docs/design-notes/widget-primitives.md +32 -15
- package/docs/navigation.json +1 -0
- package/docs/pages/components/alert.md +13 -8
- package/docs/pages/components/aura.md +102 -6
- package/docs/pages/components/avatar.md +9 -6
- package/docs/pages/components/badge.md +11 -7
- package/docs/pages/components/button.md +17 -17
- package/docs/pages/components/card.md +15 -8
- package/docs/pages/components/chat.md +1 -1
- package/docs/pages/components/dialog.md +10 -7
- package/docs/pages/components/drawer.md +1 -1
- package/docs/pages/components/fab.md +12 -10
- package/docs/pages/components/indicator.md +6 -6
- package/docs/pages/components/list.md +11 -2
- package/docs/pages/components/navbar.md +1 -1
- package/docs/pages/components/pagination.md +1 -1
- package/docs/pages/components/rating.md +4 -4
- package/docs/pages/components/skeleton.md +1 -1
- package/docs/pages/components/spinner.md +1 -1
- package/docs/pages/components/steps.md +376 -52
- package/docs/pages/components/table.md +5 -5
- package/docs/pages/enhancements/flyout.md +104 -12
- package/docs/pages/enhancements/overview.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +20 -0
- package/docs/pages/enhancements/tabs.md +1 -1
- package/docs/pages/enhancements/tooltip.md +12 -0
- package/docs/pages/examples/overview.md +2 -0
- package/docs/pages/forms/choice-cards.md +4 -4
- package/docs/pages/forms/combobox.md +87 -0
- package/docs/pages/forms/floating-field.md +5 -3
- package/docs/pages/forms/joined-controls.md +4 -4
- package/docs/pages/forms/labels.md +8 -5
- package/docs/pages/forms/overview.md +25 -18
- package/docs/pages/forms/password.md +3 -2
- package/docs/pages/forms/range.md +13 -2
- package/docs/pages/forms/states-and-sizes.md +6 -4
- package/docs/pages/forms/validation.md +13 -9
- package/docs/pages/foundations/philosophy.md +15 -1
- package/docs/pages/foundations/theming.md +8 -0
- package/docs/pages/foundations/tokens.md +9 -6
- package/docs/pages/foundations/typography.md +1 -1
- package/docs/pages/guides/bootstrap.md +3 -3
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/guides/modular-import.md +21 -10
- package/docs/pages/guides/progressive-enhancement.md +96 -73
- package/docs/pages/guides/tailwind.md +2 -2
- package/docs/pages/layout/app-shell.md +4 -4
- package/docs/pages/layout/choosing.md +10 -6
- package/docs/pages/layout/grid.md +11 -10
- package/docs/pages/layout/stack.md +10 -6
- package/docs/pages/patterns/structured-lists.md +7 -7
- package/docs/pages/utilities/accessibility.md +1 -1
- package/docs/pages/utilities/flex.md +20 -13
- package/docs/pages/utilities/links.md +1 -1
- package/docs/pages/utilities/list.md +1 -1
- package/docs/pages/utilities/overflow.md +14 -7
- package/docs/pages/utilities/overview.md +22 -0
- package/docs/pages/utilities/shape.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +10 -8
- package/docs/pages/utilities/spacing.md +26 -19
- package/docs/pages/utilities/text.md +1 -1
- package/llms.txt +27 -4
- package/package.json +7 -4
- package/scripts/reserved-classes.json +4 -1
- package/src/css/components/fab.css +44 -8
- package/src/css/components/flyout.css +49 -3
- package/src/css/components/join.css +7 -0
- package/src/css/components/list.css +3 -1
- package/src/css/components/menu.css +16 -10
- package/src/css/components/steps.css +277 -69
- package/src/css/components/tooltip.css +37 -1
- package/src/css/core/tokens.css +1 -0
- package/src/css/effects/aura.css +101 -11
- package/src/css/forms/custom-select.css +1 -1
- package/src/css/forms/floating-field.css +8 -6
- package/src/css/layout/container-query.css +37 -13
- package/src/css/layout/grid.css +7 -7
- package/src/css/layout/index.css +1 -1
- package/src/js/password.js +1 -1
- package/src/js/status.js +38 -2
- package/src/js/surface.js +28 -12
- package/src/js/tooltip.js +2 -2
- package/src/js/floating.js +0 -432
package/dist/actual.full.css
CHANGED
|
@@ -201,6 +201,7 @@ select {
|
|
|
201
201
|
--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
202
202
|
--icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
|
|
203
203
|
--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E");
|
|
204
|
+
--icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
|
|
204
205
|
|
|
205
206
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
206
207
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -1073,7 +1074,7 @@ small {
|
|
|
1073
1074
|
* .sidebar-layout main content + secondary aside
|
|
1074
1075
|
* .grid responsive collection; item width is the contract
|
|
1075
1076
|
* .grid-N structural density of N; balanced inside a container named
|
|
1076
|
-
* actual-
|
|
1077
|
+
* actual-container (in grid.css)
|
|
1077
1078
|
* .column-layout explicit 12-column region composition; the author owns
|
|
1078
1079
|
* narrow-container recomposition
|
|
1079
1080
|
* .scroller theme-aware scrollbar density and colour
|
|
@@ -1084,20 +1085,44 @@ small {
|
|
|
1084
1085
|
*
|
|
1085
1086
|
* Prefer these compositions over viewport breakpoint utilities.
|
|
1086
1087
|
*/
|
|
1087
|
-
/* Convenience helper
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1088
|
+
/* Convenience helper for size-aware components: establishes an inline-size
|
|
1089
|
+
query container named actual-container. The name is the contract, not this
|
|
1090
|
+
class — an author can declare `container: actual-container / inline-size` on
|
|
1091
|
+
their own region and get the same result.
|
|
1092
|
+
|
|
1093
|
+
actual-container is shared. Granting it to a region may enable size-aware
|
|
1094
|
+
enhancements for any Actual component inside that region, not only the one
|
|
1095
|
+
that motivated the container: .grid-N reads it for balanced subdivision,
|
|
1096
|
+
.steps for its label collapse, and future components will read the same
|
|
1097
|
+
context. That is the scope of the API, not a caveat.
|
|
1098
|
+
|
|
1099
|
+
An anonymous query container is not part of it. A region that declares
|
|
1100
|
+
`container-type: inline-size` for the application's own purposes carries no
|
|
1101
|
+
actual-container name, so no framework component changes behaviour there.
|
|
1102
|
+
Opting into the protocol is always explicit. Nothing here is a prerequisite
|
|
1103
|
+
either — every primitive is responsive without it; this adds precision.
|
|
1104
|
+
|
|
1105
|
+
Place it where the width you want measured lives: usually a wrapper for a
|
|
1106
|
+
grid, whose queried children sit inside it, and the component itself for
|
|
1107
|
+
.steps, whose own row is the width in question.
|
|
1108
|
+
|
|
1109
|
+
The rule for where it is safe is the containment rule, not a list of
|
|
1110
|
+
primitives. Query containment removes the element's intrinsic inline-size
|
|
1111
|
+
contribution, so:
|
|
1112
|
+
|
|
1113
|
+
safe the element's width is imposed by its context
|
|
1114
|
+
(.steps sets inline-size: 100%, so it survives even as a
|
|
1115
|
+
.cluster child — it wraps and fills)
|
|
1116
|
+
|
|
1117
|
+
unsafe the element's width depends on its contents' intrinsic
|
|
1118
|
+
contribution (a bare .grid child of .cluster measures 0 once
|
|
1119
|
+
contained, as does a float, inline-block, table, grid auto track,
|
|
1120
|
+
max-content parent, or an abspos box sized by its contents)
|
|
1121
|
+
|
|
1122
|
+
So .cluster is neither safe nor unsafe; what matters is where the child's
|
|
1123
|
+
width comes from. */
|
|
1099
1124
|
.container-query {
|
|
1100
|
-
container: actual-
|
|
1125
|
+
container: actual-container / inline-size;
|
|
1101
1126
|
}
|
|
1102
1127
|
|
|
1103
1128
|
/* Vertical flow. .stack owns the block axis only: block margins reset, inline
|
|
@@ -1468,13 +1493,13 @@ small {
|
|
|
1468
1493
|
min-inline-size: 0;
|
|
1469
1494
|
}
|
|
1470
1495
|
|
|
1471
|
-
/* Balanced subdivision, activated by an ancestor container named actual-
|
|
1496
|
+
/* Balanced subdivision, activated by an ancestor container named actual-container.
|
|
1472
1497
|
* A .grid-N inside such a container enters only divisor states of N. The name
|
|
1473
1498
|
* is deliberate: a grid must not key off the nearest anonymous query container,
|
|
1474
1499
|
* or a region that sets container-type: inline-size for an unrelated reason
|
|
1475
1500
|
* would silently change the grid. The author opts the grid in by naming the
|
|
1476
1501
|
* context — .container-query is the official helper, or declare
|
|
1477
|
-
* container: actual-
|
|
1502
|
+
* container: actual-container / inline-size on your own region.
|
|
1478
1503
|
*
|
|
1479
1504
|
* .grid-2 2 -> 1
|
|
1480
1505
|
* .grid-3 3 -> 1
|
|
@@ -1491,7 +1516,7 @@ small {
|
|
|
1491
1516
|
* inline contribution and collapses it inside .cluster, .topbar, floats, and
|
|
1492
1517
|
* any shrink-to-fit box. See docs/design-notes/grid.md.
|
|
1493
1518
|
*
|
|
1494
|
-
* Without an actual-
|
|
1519
|
+
* Without an actual-container ancestor none of these @container rules resolve, so
|
|
1495
1520
|
* the auto-fill baseline above stays. The min-width: 0 rule is the lowest
|
|
1496
1521
|
* step: it pins the grid to one column inside a container before any larger
|
|
1497
1522
|
* threshold fires (a bare grid would otherwise show a 2 + 1 orphan at 28-48rem).
|
|
@@ -1507,25 +1532,25 @@ small {
|
|
|
1507
1532
|
* Ascending min-width only: the last matching step wins by cascade order.
|
|
1508
1533
|
*/
|
|
1509
1534
|
@supports (container-type: inline-size) {
|
|
1510
|
-
@container actual-
|
|
1535
|
+
@container actual-container (min-width: 0px) {
|
|
1511
1536
|
:where(.grid-2, .grid-3, .grid-4, .grid-6) {
|
|
1512
1537
|
grid-template-columns: 1fr;
|
|
1513
1538
|
}
|
|
1514
1539
|
}
|
|
1515
1540
|
|
|
1516
|
-
@container actual-
|
|
1541
|
+
@container actual-container (min-width: 28rem) {
|
|
1517
1542
|
:where(.grid-2, .grid-4, .grid-6) {
|
|
1518
1543
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1519
1544
|
}
|
|
1520
1545
|
}
|
|
1521
1546
|
|
|
1522
|
-
@container actual-
|
|
1547
|
+
@container actual-container (min-width: 48rem) {
|
|
1523
1548
|
:where(.grid-3, .grid-6) {
|
|
1524
1549
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1525
1550
|
}
|
|
1526
1551
|
}
|
|
1527
1552
|
|
|
1528
|
-
@container actual-
|
|
1553
|
+
@container actual-container (min-width: 64rem) {
|
|
1529
1554
|
:where(.grid-4) {
|
|
1530
1555
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1531
1556
|
}
|
|
@@ -2975,7 +3000,7 @@ small {
|
|
|
2975
3000
|
|
|
2976
3001
|
&::picker(select) {
|
|
2977
3002
|
appearance: base-select;
|
|
2978
|
-
max-
|
|
3003
|
+
max-block-size: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
2979
3004
|
margin-block: var(--space-20);
|
|
2980
3005
|
padding: 0;
|
|
2981
3006
|
border: var(--border-width) solid var(--border);
|
|
@@ -3052,8 +3077,10 @@ small {
|
|
|
3052
3077
|
* messages stay in the surrounding .field, not inside the cell, so the
|
|
3053
3078
|
* resting label can center on the control alone.
|
|
3054
3079
|
*
|
|
3055
|
-
* The control must precede the label: the float is driven by the
|
|
3056
|
-
* combinator and :placeholder-shown.
|
|
3080
|
+
* The control must precede the label: the float is driven by the general
|
|
3081
|
+
* sibling combinator and :placeholder-shown. Injected nodes may appear between
|
|
3082
|
+
* the control and the label (password managers, extension wrappers); the cell
|
|
3083
|
+
* contract stays a single control plus one label. Every .input/.textarea needs
|
|
3057
3084
|
* placeholder=" " to expose a reliable empty state — without it the label
|
|
3058
3085
|
* only reacts to focus.
|
|
3059
3086
|
*
|
|
@@ -3101,14 +3128,14 @@ small {
|
|
|
3101
3128
|
}
|
|
3102
3129
|
|
|
3103
3130
|
/* Single-line controls rest in the middle until they receive a value. */
|
|
3104
|
-
.floating-field > :is(.input, .select)
|
|
3131
|
+
.floating-field > :is(.input, .select) ~ .field-label {
|
|
3105
3132
|
inset-block-start: 50%;
|
|
3106
3133
|
transform: translateY(-50%);
|
|
3107
3134
|
}
|
|
3108
3135
|
|
|
3109
3136
|
/* An empty textarea label follows the control's normal first-line position;
|
|
3110
3137
|
the control itself keeps the extra headroom reserved for its floated state. */
|
|
3111
|
-
.floating-field > .textarea
|
|
3138
|
+
.floating-field > .textarea ~ .field-label {
|
|
3112
3139
|
inset-block-start: calc(1em + var(--border-width));
|
|
3113
3140
|
transform: none;
|
|
3114
3141
|
}
|
|
@@ -3117,7 +3144,7 @@ small {
|
|
|
3117
3144
|
not have propagated the value to the placeholder state yet. */
|
|
3118
3145
|
.floating-field
|
|
3119
3146
|
> :is(.input, .textarea):is(:focus, :autofill, :not(:placeholder-shown))
|
|
3120
|
-
|
|
3147
|
+
~ .field-label {
|
|
3121
3148
|
inset-block-start: var(--floating-label-top);
|
|
3122
3149
|
transform: none;
|
|
3123
3150
|
font-size: var(--font-size-xs);
|
|
@@ -3136,7 +3163,7 @@ small {
|
|
|
3136
3163
|
.input[type="month"],
|
|
3137
3164
|
.input[type="week"]
|
|
3138
3165
|
)
|
|
3139
|
-
|
|
3166
|
+
~ .field-label {
|
|
3140
3167
|
inset-block-start: var(--floating-label-top);
|
|
3141
3168
|
transform: none;
|
|
3142
3169
|
font-size: var(--font-size-xs);
|
|
@@ -4812,12 +4839,50 @@ dialog.drawer > form > header {
|
|
|
4812
4839
|
transition: opacity var(--duration-fast) ease;
|
|
4813
4840
|
}
|
|
4814
4841
|
|
|
4815
|
-
|
|
4842
|
+
/* Native popovers enter the top layer with UA geometry that conflicts with
|
|
4843
|
+
anchored positioning. The reset is gated on real Popover support because an
|
|
4844
|
+
attribute selector matches whether or not the engine implements Popover:
|
|
4845
|
+
without it the UA neither hides nor promotes the element, so an ungated
|
|
4846
|
+
reset would strand a permanently visible panel at inset: auto. Popover is
|
|
4847
|
+
not part of Actual's Minimal contract — engines inside the Minimal range
|
|
4848
|
+
may lack it — so the gate is about the capability, not the floor. Kept
|
|
4849
|
+
scoped to Actual surfaces so bare [popover] elements retain their platform
|
|
4850
|
+
presentation. */
|
|
4851
|
+
@supports selector(:popover-open) {
|
|
4852
|
+
.flyout[popover] {
|
|
4853
|
+
position: fixed;
|
|
4854
|
+
inset: auto;
|
|
4855
|
+
margin: 0;
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
/* A presentation class may own its *visible* display type, but must not
|
|
4859
|
+
override the platform's hidden state. The UA closes a popover with
|
|
4860
|
+
display: none, and an author declaration beats the UA origin whatever the
|
|
4861
|
+
specificity — so .flyout's own display: grid would leave a closed popover
|
|
4862
|
+
painted at its static position. Restoring both properties here also gives
|
|
4863
|
+
the native path the exit target the runtime path already has in
|
|
4864
|
+
.flyout[hidden]: without an opacity to animate, the overlay/display
|
|
4865
|
+
allow-discrete transition below would hold a fully opaque panel in the top
|
|
4866
|
+
layer and then snap it away. */
|
|
4867
|
+
.flyout[popover]:not(:popover-open) {
|
|
4868
|
+
display: none;
|
|
4869
|
+
opacity: 0;
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
/* compat-ok: :popover-open rides in a forgiving :is(), where an engine without
|
|
4874
|
+
Popover discards just that member and .is-open alone keeps driving the open
|
|
4875
|
+
state. This is a property of the forgiving list, not of the pseudo-class: in
|
|
4876
|
+
a plain selector list an unsupported member would invalidate the whole rule.
|
|
4877
|
+
Keep it inside :is()/:where() or gate it. */
|
|
4878
|
+
.flyout:is(.is-open, :popover-open):not(.is-sheet) {
|
|
4816
4879
|
inset: auto;
|
|
4817
4880
|
}
|
|
4818
4881
|
|
|
4882
|
+
/* compat-ok: forgiving :is() again, and @starting-style is itself unsupported
|
|
4883
|
+
across part of the Minimal range, so the rule simply does not exist there. */
|
|
4819
4884
|
@starting-style {
|
|
4820
|
-
.flyout.is-open {
|
|
4885
|
+
.flyout:is(.is-open, :popover-open) {
|
|
4821
4886
|
opacity: 0;
|
|
4822
4887
|
}
|
|
4823
4888
|
}
|
|
@@ -4856,16 +4921,23 @@ dialog.drawer > form > header {
|
|
|
4856
4921
|
translate: 0 var(--space-30);
|
|
4857
4922
|
}
|
|
4858
4923
|
|
|
4924
|
+
/* compat-ok: forgiving :is() inside @starting-style, as above. */
|
|
4859
4925
|
@starting-style {
|
|
4860
|
-
.flyout.is-sheet.is-open {
|
|
4926
|
+
.flyout.is-sheet:is(.is-open, :popover-open) {
|
|
4861
4927
|
translate: 0 var(--space-30);
|
|
4862
4928
|
}
|
|
4863
4929
|
}
|
|
4864
4930
|
|
|
4931
|
+
/* display keeps the exit transition alive for the [hidden] path; overlay does
|
|
4932
|
+
the same for the native popover path, where hidePopover() would otherwise
|
|
4933
|
+
drop the panel out of the top layer on the first frame and let a parent
|
|
4934
|
+
overflow or a dialog clip the fade. An engine that cannot transition either
|
|
4935
|
+
property ignores it and the exit is instant. */
|
|
4865
4936
|
@supports (transition-behavior: allow-discrete) {
|
|
4866
4937
|
.flyout {
|
|
4867
4938
|
transition:
|
|
4868
4939
|
opacity var(--duration-fast) ease,
|
|
4940
|
+
overlay var(--duration-fast) allow-discrete,
|
|
4869
4941
|
display var(--duration-fast) allow-discrete;
|
|
4870
4942
|
}
|
|
4871
4943
|
|
|
@@ -4873,6 +4945,7 @@ dialog.drawer > form > header {
|
|
|
4873
4945
|
transition:
|
|
4874
4946
|
opacity var(--duration-fast) ease,
|
|
4875
4947
|
translate var(--duration-fast) ease,
|
|
4948
|
+
overlay var(--duration-fast) allow-discrete,
|
|
4876
4949
|
display var(--duration-fast) allow-discrete;
|
|
4877
4950
|
}
|
|
4878
4951
|
}
|
|
@@ -4991,30 +5064,36 @@ dialog.drawer > form > header {
|
|
|
4991
5064
|
/* Checkable menu items are one command with one accessible state. Their
|
|
4992
5065
|
indicator is generated so authors do not add a second interactive input. */
|
|
4993
5066
|
.menu-item:is([role="menuitemcheckbox"], [role="menuitemradio"])::before {
|
|
4994
|
-
|
|
5067
|
+
content: "";
|
|
5068
|
+
display: block;
|
|
4995
5069
|
flex: 0 0 var(--menu-item-icon-size);
|
|
4996
5070
|
inline-size: var(--menu-item-icon-size);
|
|
4997
|
-
|
|
4998
|
-
}
|
|
4999
|
-
|
|
5000
|
-
.menu-item[role="menuitemcheckbox"]::before {
|
|
5001
|
-
content: "";
|
|
5071
|
+
block-size: var(--menu-item-icon-size);
|
|
5002
5072
|
}
|
|
5003
5073
|
|
|
5004
5074
|
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before {
|
|
5005
|
-
|
|
5075
|
+
background: currentColor;
|
|
5076
|
+
-webkit-mask: var(--icon-check) center / contain no-repeat;
|
|
5077
|
+
mask: var(--icon-check) center / contain no-repeat;
|
|
5006
5078
|
}
|
|
5007
5079
|
|
|
5008
5080
|
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before {
|
|
5009
|
-
|
|
5081
|
+
background:
|
|
5082
|
+
linear-gradient(currentColor 0 0) center / 60% max(var(--border-width), 0.125em) no-repeat;
|
|
5010
5083
|
}
|
|
5011
5084
|
|
|
5012
5085
|
.menu-item[role="menuitemradio"]::before {
|
|
5013
|
-
|
|
5086
|
+
background: radial-gradient(circle, transparent 0 46%, currentColor 48% 58%, transparent 60%);
|
|
5014
5087
|
}
|
|
5015
5088
|
|
|
5016
5089
|
.menu-item[role="menuitemradio"][aria-checked="true"]::before {
|
|
5017
|
-
|
|
5090
|
+
background: radial-gradient(
|
|
5091
|
+
circle,
|
|
5092
|
+
currentColor 0 25%,
|
|
5093
|
+
transparent 27% 46%,
|
|
5094
|
+
currentColor 48% 58%,
|
|
5095
|
+
transparent 60%
|
|
5096
|
+
);
|
|
5018
5097
|
}
|
|
5019
5098
|
|
|
5020
5099
|
.menu-item:focus-visible {
|
|
@@ -5084,21 +5163,57 @@ dialog.drawer > form > header {
|
|
|
5084
5163
|
transition: opacity var(--duration-fast) ease;
|
|
5085
5164
|
}
|
|
5086
5165
|
|
|
5166
|
+
/* Neutralize only the native geometry that conflicts with a positioned
|
|
5167
|
+
tooltip. In particular, the UA overflow would turn the arrow into scroll
|
|
5168
|
+
overflow instead of letting it project beyond the bubble. Gated on real
|
|
5169
|
+
Popover support: the attribute selector matches whether or not the engine
|
|
5170
|
+
implements Popover, and without it nothing hides the element, so an ungated
|
|
5171
|
+
reset would leave a stray bubble parked at inset: auto. Popover sits outside
|
|
5172
|
+
Actual's Minimal contract, so the gate tracks the capability rather than the
|
|
5173
|
+
floor. */
|
|
5174
|
+
@supports selector(:popover-open) {
|
|
5175
|
+
.tooltip[popover] {
|
|
5176
|
+
inset: auto;
|
|
5177
|
+
margin: 0;
|
|
5178
|
+
overflow: visible;
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
/* .tooltip declares no display of its own, so the UA already hides a closed
|
|
5182
|
+
popover here. The rule is written anyway: both components advertised as
|
|
5183
|
+
Popover-compatible should cede the hidden state the same way, and the
|
|
5184
|
+
opacity target is what actually makes the native exit fade — see the
|
|
5185
|
+
overlay note below. */
|
|
5186
|
+
.tooltip[popover]:not(:popover-open) {
|
|
5187
|
+
display: none;
|
|
5188
|
+
opacity: 0;
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5087
5192
|
.tooltip[hidden] {
|
|
5088
5193
|
display: none;
|
|
5089
5194
|
opacity: 0;
|
|
5090
5195
|
}
|
|
5091
5196
|
|
|
5197
|
+
/* Entry opacity for both lifecycles: a runtime-driven tip is visible when it
|
|
5198
|
+
is neither a popover nor [hidden], a native one when it is :popover-open.
|
|
5199
|
+
compat-ok: [popover] appears here only under negation, which every engine
|
|
5200
|
+
evaluates correctly whether or not it implements Popover, :popover-open
|
|
5201
|
+
rides in a forgiving :is(), and @starting-style is itself unsupported across
|
|
5202
|
+
part of the Minimal range — so the rule does not exist there at all. */
|
|
5092
5203
|
@starting-style {
|
|
5093
|
-
.tooltip:not([hidden]) {
|
|
5204
|
+
.tooltip:is(:not([popover]):not([hidden]), :popover-open) {
|
|
5094
5205
|
opacity: 0;
|
|
5095
5206
|
}
|
|
5096
5207
|
}
|
|
5097
5208
|
|
|
5209
|
+
/* overlay is the native popover counterpart of display: it holds the tip in
|
|
5210
|
+
the top layer until the fade finishes, instead of letting hidePopover()
|
|
5211
|
+
demote it on the first frame. Ignored where unsupported. */
|
|
5098
5212
|
@supports (transition-behavior: allow-discrete) {
|
|
5099
5213
|
.tooltip {
|
|
5100
5214
|
transition:
|
|
5101
5215
|
opacity var(--duration-fast) ease,
|
|
5216
|
+
overlay var(--duration-fast) allow-discrete,
|
|
5102
5217
|
display var(--duration-fast) allow-discrete;
|
|
5103
5218
|
}
|
|
5104
5219
|
}
|
|
@@ -6033,15 +6148,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
6033
6148
|
|
|
6034
6149
|
/*
|
|
6035
6150
|
* Fixed floating action placement and a native-details speed dial.
|
|
6036
|
-
* Button owns the
|
|
6151
|
+
* Button owns the trigger's size, shape, and intent; FAB owns placement and
|
|
6152
|
+
* the speed dial's structural open/close markers (--icon-plus / --icon-close).
|
|
6037
6153
|
*
|
|
6038
6154
|
* Public hooks:
|
|
6039
6155
|
* --fab-offset
|
|
6040
6156
|
* --fab-gap
|
|
6157
|
+
* --fab-icon-size
|
|
6041
6158
|
*/
|
|
6042
6159
|
.fab {
|
|
6043
6160
|
--fab-offset: var(--space-40);
|
|
6044
6161
|
--fab-gap: var(--space-20);
|
|
6162
|
+
--fab-icon-size: 1.5rem;
|
|
6045
6163
|
|
|
6046
6164
|
position: fixed;
|
|
6047
6165
|
z-index: var(--z-menu);
|
|
@@ -6050,6 +6168,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6050
6168
|
}
|
|
6051
6169
|
|
|
6052
6170
|
.fab > summary {
|
|
6171
|
+
/* Centers the toggle markers: the summary is the markers' positioned
|
|
6172
|
+
context, not just the app-button class. */
|
|
6173
|
+
position: relative;
|
|
6053
6174
|
list-style: none;
|
|
6054
6175
|
cursor: pointer;
|
|
6055
6176
|
}
|
|
@@ -6058,11 +6179,35 @@ a.avatar[aria-disabled="true"] {
|
|
|
6058
6179
|
display: none;
|
|
6059
6180
|
}
|
|
6060
6181
|
|
|
6061
|
-
/*
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6182
|
+
/* The speed dial trigger is an icon-only toggle. FAB owns both structural
|
|
6183
|
+
markers: masked pseudo-elements swap the shared plus (closed) and close
|
|
6184
|
+
(open) glyphs at the same size, so the two states never drift apart. */
|
|
6185
|
+
.fab > summary::before,
|
|
6186
|
+
.fab > summary::after {
|
|
6187
|
+
content: "";
|
|
6188
|
+
position: absolute;
|
|
6189
|
+
inset: 0;
|
|
6190
|
+
margin: auto;
|
|
6191
|
+
inline-size: var(--fab-icon-size);
|
|
6192
|
+
block-size: var(--fab-icon-size);
|
|
6193
|
+
background: currentColor;
|
|
6194
|
+
opacity: 0;
|
|
6195
|
+
transform: scale(0.8);
|
|
6196
|
+
transition:
|
|
6197
|
+
opacity var(--duration),
|
|
6198
|
+
transform var(--duration);
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
.fab > summary::before {
|
|
6202
|
+
-webkit-mask: var(--icon-plus) center / contain no-repeat;
|
|
6203
|
+
mask: var(--icon-plus) center / contain no-repeat;
|
|
6204
|
+
opacity: 1;
|
|
6205
|
+
transform: none;
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
.fab > summary::after {
|
|
6209
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
6210
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
6066
6211
|
}
|
|
6067
6212
|
|
|
6068
6213
|
.fab-actions {
|
|
@@ -6120,8 +6265,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6120
6265
|
transition-delay: 0s;
|
|
6121
6266
|
}
|
|
6122
6267
|
|
|
6123
|
-
.fab[open] > summary
|
|
6124
|
-
|
|
6268
|
+
.fab[open] > summary::before {
|
|
6269
|
+
opacity: 0;
|
|
6270
|
+
transform: scale(0.8);
|
|
6271
|
+
}
|
|
6272
|
+
|
|
6273
|
+
.fab[open] > summary::after {
|
|
6274
|
+
opacity: 1;
|
|
6275
|
+
transform: none;
|
|
6125
6276
|
}
|
|
6126
6277
|
|
|
6127
6278
|
@media print {
|
|
@@ -6233,22 +6384,56 @@ a.avatar[aria-disabled="true"] {
|
|
|
6233
6384
|
}
|
|
6234
6385
|
|
|
6235
6386
|
/*
|
|
6236
|
-
* Steps — semantic ordered-list progression
|
|
6237
|
-
*
|
|
6238
|
-
*
|
|
6239
|
-
*
|
|
6387
|
+
* Steps — semantic ordered-list progression for a multi-step process.
|
|
6388
|
+
*
|
|
6389
|
+
* One primitive, two explicit compositions. Both classes are required:
|
|
6390
|
+
*
|
|
6391
|
+
* <ol class="steps steps-horizontal"> horizontal, 2 to 5 stages
|
|
6392
|
+
* <ol class="steps steps-vertical"> vertical, any number of stages
|
|
6393
|
+
*
|
|
6394
|
+
* `.steps` is what a step *is* — the counter, the marker, `.step-label`,
|
|
6395
|
+
* `.step-complete`, `aria-current="step"`. It carries no orientation, so it is
|
|
6396
|
+
* never written alone. `.steps-horizontal` and `.steps-vertical` are what a
|
|
6397
|
+
* sequence *looks like*, and they are peers: neither is the default the other
|
|
6398
|
+
* has to exclude, so no rule says `:not(...)` and no rule undoes another's
|
|
6399
|
+
* layout.
|
|
6400
|
+
*
|
|
6401
|
+
* Orientation and labels are independent dimensions. `.step-label` is optional
|
|
6402
|
+
* in either orientation; when a sequence carries names, every step carries
|
|
6403
|
+
* one:
|
|
6404
|
+
*
|
|
6405
|
+
* empty steps .step-label on every step
|
|
6406
|
+
* horizontal compact progression named sequence, stacked or inline
|
|
6407
|
+
* vertical compact rail named sequence, labels beside markers
|
|
6408
|
+
*
|
|
6409
|
+
* An empty step is a marker and nothing else, so it is compact by structure —
|
|
6410
|
+
* `> li:empty` in both orientations, no variant class, and nothing authored
|
|
6411
|
+
* only to be hidden again. The page around it owns the step name.
|
|
6412
|
+
*
|
|
6413
|
+
* A label may be an `a[href]`, which is the whole interactive surface: the
|
|
6414
|
+
* marker is never navigable, so navigating a flow means naming its
|
|
6415
|
+
* destinations. Anything else focusable in a step is out of contract.
|
|
6416
|
+
*
|
|
6417
|
+
* The framework never hides an authored label to solve a layout problem. A
|
|
6418
|
+
* labelled row that outgrows its space scrolls. An `actual-container` size
|
|
6419
|
+
* context buys it one thing: the wide representation, marker beside label.
|
|
6420
|
+
*
|
|
6421
|
+
* See docs/design-notes/steps.md for the threshold, the selector shapes, the
|
|
6422
|
+
* rejected alternatives, and why there is no forced-colors branch.
|
|
6240
6423
|
*
|
|
6241
|
-
*
|
|
6242
|
-
* aria-current="step", so the component needs no runtime.
|
|
6424
|
+
* Each public hook is followed by the class that owns it.
|
|
6243
6425
|
*
|
|
6244
6426
|
* Public hooks:
|
|
6245
|
-
* --step-size
|
|
6246
|
-
* --step-
|
|
6247
|
-
* --step-
|
|
6248
|
-
* --step-
|
|
6249
|
-
* --step-
|
|
6250
|
-
* --step-
|
|
6251
|
-
* --step-
|
|
6427
|
+
* --step-size .steps
|
|
6428
|
+
* --step-inline-gap .steps
|
|
6429
|
+
* --step-line-size .steps
|
|
6430
|
+
* --step-marker-radius .steps
|
|
6431
|
+
* --step-complete-mark .steps
|
|
6432
|
+
* --step-min .steps-horizontal
|
|
6433
|
+
* --step-gap .steps-horizontal
|
|
6434
|
+
* --step-connector .steps-horizontal
|
|
6435
|
+
* --step-inline-connector .steps-horizontal
|
|
6436
|
+
* --step-vertical-gap .steps-vertical
|
|
6252
6437
|
*
|
|
6253
6438
|
* Internal:
|
|
6254
6439
|
* --step-marker-bg
|
|
@@ -6256,10 +6441,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
6256
6441
|
* --step-marker-fg
|
|
6257
6442
|
* --step-line
|
|
6258
6443
|
*/
|
|
6444
|
+
|
|
6445
|
+
/* Step primitive --------------------------------------------------------- */
|
|
6446
|
+
|
|
6259
6447
|
.steps {
|
|
6260
6448
|
--step-size: 1.75rem;
|
|
6261
|
-
--step-
|
|
6262
|
-
--step-gap: var(--space-20);
|
|
6449
|
+
--step-inline-gap: var(--space-20);
|
|
6263
6450
|
--step-line-size: var(--border-width);
|
|
6264
6451
|
--step-marker-bg: var(--surface);
|
|
6265
6452
|
--step-marker-border: var(--border);
|
|
@@ -6269,29 +6456,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
6269
6456
|
|
|
6270
6457
|
counter-reset: actual-step;
|
|
6271
6458
|
display: flex;
|
|
6272
|
-
flex-direction: row;
|
|
6273
|
-
inline-size: 100%;
|
|
6274
6459
|
margin: 0;
|
|
6275
6460
|
padding: 0;
|
|
6276
6461
|
list-style: none;
|
|
6277
|
-
/* Fill when it fits, scroll when it cannot: items never shrink below
|
|
6278
|
-
--step-min, so labels keep a readable width and the sequence scrolls
|
|
6279
|
-
horizontally instead of squishing. overscroll-behavior keeps the scroll
|
|
6280
|
-
from chaining to the page. */
|
|
6281
|
-
overflow-x: auto;
|
|
6282
|
-
overscroll-behavior-inline: contain;
|
|
6283
6462
|
}
|
|
6284
6463
|
|
|
6285
6464
|
.steps > li {
|
|
6286
6465
|
position: relative;
|
|
6287
|
-
display: grid;
|
|
6288
|
-
flex: 1 0 var(--step-min);
|
|
6289
|
-
grid-template-rows: var(--step-size) auto;
|
|
6290
|
-
gap: var(--step-gap);
|
|
6291
|
-
justify-items: center;
|
|
6292
|
-
min-inline-size: max(var(--step-size), var(--step-min));
|
|
6293
6466
|
color: var(--text-muted);
|
|
6294
|
-
text-align: center;
|
|
6295
6467
|
counter-increment: actual-step;
|
|
6296
6468
|
}
|
|
6297
6469
|
|
|
@@ -6312,30 +6484,38 @@ a.avatar[aria-disabled="true"] {
|
|
|
6312
6484
|
line-height: 1;
|
|
6313
6485
|
}
|
|
6314
6486
|
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6487
|
+
/* The marker carries the state, so the label reads a notch below it — set on
|
|
6488
|
+
the component, never per representation: a size that changed with the layout
|
|
6489
|
+
would resize the text under a reader dragging a window. `color: inherit` is
|
|
6490
|
+
here rather than in a rule of its own for `a.step-label`, because a span
|
|
6491
|
+
inherits its ink anyway; only a link has anything to take back, and taking it
|
|
6492
|
+
back for every label costs nothing and one rule less. A navigable label keeps
|
|
6493
|
+
its underline, hover and focus ring — just not a link color, which would read
|
|
6494
|
+
ahead of the marker states.
|
|
6495
|
+
|
|
6496
|
+
Scoped to `.steps`, unlike `.menu-item-text` and the framework's other
|
|
6497
|
+
sub-part classes: a bare `.step-label` weighs (0,1,0), exactly tying
|
|
6498
|
+
`.prose :where(a)`, so a stepper in prose would be decided by bundle order. */
|
|
6499
|
+
.steps .step-label {
|
|
6500
|
+
font-size: var(--font-size-sm);
|
|
6501
|
+
line-height: var(--line-height-tight);
|
|
6502
|
+
color: inherit;
|
|
6328
6503
|
}
|
|
6329
6504
|
|
|
6330
|
-
|
|
6331
|
-
|
|
6505
|
+
/* Each composition gives the connector its own geometry and background, so the
|
|
6506
|
+
only shared fact is which steps get one — every step but the last. Stated as
|
|
6507
|
+
the condition rather than as a `content: none` rule undoing a `content: ""`
|
|
6508
|
+
rule: a pseudo-element with no content generates no box, so a composition can
|
|
6509
|
+
address `> li::after` freely and still not trail a line past the last step. */
|
|
6510
|
+
.steps > li:not(:last-child)::after {
|
|
6511
|
+
content: "";
|
|
6332
6512
|
}
|
|
6333
6513
|
|
|
6334
|
-
/*
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
.steps > .complete {
|
|
6514
|
+
/* Prefixed like `.step-label`, unlike the framework's bare `.primary` /
|
|
6515
|
+
`.outline` modifiers: those qualify a component on its own element, this
|
|
6516
|
+
marks one item inside one. A bare `.complete` would also claim a very
|
|
6517
|
+
ordinary English word globally, for a single component's single state. */
|
|
6518
|
+
.steps > .step-complete {
|
|
6339
6519
|
--step-marker-bg: var(--state-selected);
|
|
6340
6520
|
--step-marker-border: var(--state-selected);
|
|
6341
6521
|
--step-marker-fg: var(--state-selected-fg);
|
|
@@ -6344,10 +6524,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6344
6524
|
color: var(--text);
|
|
6345
6525
|
}
|
|
6346
6526
|
|
|
6347
|
-
/*
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
current step is owned by the previous step, which is complete. */
|
|
6527
|
+
/* Complete owns the accented connector that reaches the current step, so the
|
|
6528
|
+
connector leaving current goes back to neutral. Declared after
|
|
6529
|
+
.step-complete at equal specificity: current wins on every channel. */
|
|
6351
6530
|
.steps > [aria-current="step"] {
|
|
6352
6531
|
--step-marker-bg: var(--surface);
|
|
6353
6532
|
--step-marker-border: var(--state-selected);
|
|
@@ -6357,31 +6536,211 @@ a.avatar[aria-disabled="true"] {
|
|
|
6357
6536
|
color: var(--text);
|
|
6358
6537
|
}
|
|
6359
6538
|
|
|
6360
|
-
/* Thicker border is the current affordance in every mode — structural, so it
|
|
6361
|
-
survives forced colors, and it never competes with the outline channel owned
|
|
6362
|
-
by focus. */
|
|
6363
6539
|
.steps > [aria-current="step"]::before {
|
|
6364
6540
|
border-width: calc(var(--border-width) * 2);
|
|
6365
6541
|
}
|
|
6366
6542
|
|
|
6367
|
-
/*
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6543
|
+
/* Weight marks "where I am now". Complete needs none: its filled disc already
|
|
6544
|
+
reads as strongly. */
|
|
6545
|
+
.steps > [aria-current="step"] .step-label {
|
|
6546
|
+
font-weight: var(--font-weight-strong);
|
|
6547
|
+
}
|
|
6548
|
+
|
|
6549
|
+
.steps > .step-complete:not([aria-current="step"])::before {
|
|
6372
6550
|
content: var(--step-complete-mark, counter(actual-step));
|
|
6373
6551
|
}
|
|
6374
6552
|
|
|
6375
|
-
/*
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6553
|
+
/* Horizontal ------------------------------------------------------------- */
|
|
6554
|
+
|
|
6555
|
+
.steps-horizontal {
|
|
6556
|
+
--step-min: 7rem;
|
|
6557
|
+
--step-gap: var(--space-20);
|
|
6558
|
+
|
|
6559
|
+
inline-size: 100%;
|
|
6560
|
+
overflow-x: auto;
|
|
6561
|
+
/* Stated, never left to `overflow-x` alone: one scrolling axis computes the
|
|
6562
|
+
other to `auto`, and a row hugs fractional content closely enough that a
|
|
6563
|
+
rounding difference then raises a phantom vertical scrollbar. */
|
|
6564
|
+
overflow-y: hidden;
|
|
6565
|
+
overscroll-behavior-inline: contain;
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
/* A scroll container clips at the padding edge, so a navigable row reserves the
|
|
6569
|
+
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
6570
|
+
link has a ring to protect. */
|
|
6571
|
+
.steps-horizontal:has(a[href]) {
|
|
6572
|
+
padding-block: calc(var(--focus-outline-offset) + var(--border-width) * 2);
|
|
6573
|
+
}
|
|
6574
|
+
|
|
6575
|
+
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
6576
|
+
implicit second row, and two tracks are also the only thing `gap` has to sit
|
|
6577
|
+
between. So an empty step measures exactly `--step-size`, and the rule below
|
|
6578
|
+
takes back nothing. `var(--step-size) auto` would build that second row
|
|
6579
|
+
whether or not a label ever arrives, and the compact form would then have to
|
|
6580
|
+
remove both the track and the gap. */
|
|
6581
|
+
.steps-horizontal > li {
|
|
6582
|
+
display: grid;
|
|
6583
|
+
flex: 1 0 var(--step-min);
|
|
6584
|
+
grid-template-rows: var(--step-size);
|
|
6585
|
+
gap: var(--step-gap);
|
|
6586
|
+
justify-items: center;
|
|
6587
|
+
min-inline-size: max(var(--step-size), var(--step-min));
|
|
6588
|
+
text-align: center;
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
/* An empty step is a marker and nothing else, so it needs a marker's width
|
|
6592
|
+
rather than a reading width. That is what lets five of them fit a phone with
|
|
6593
|
+
no size context and no variant class.
|
|
6594
|
+
|
|
6595
|
+
`justify-items` is not an override of the row's centring: the marker is
|
|
6596
|
+
centred because a label sits under it and the two share a centre, and with no
|
|
6597
|
+
label there is nothing to centre against. The marker takes the start of its
|
|
6598
|
+
own share instead, the connector below fills the rest of it, and the last
|
|
6599
|
+
step has no share to fill — so the track runs edge to edge and lines up with
|
|
6600
|
+
the text around it, the way the inline representation does.
|
|
6601
|
+
|
|
6602
|
+
`:empty` says exactly what the markup says, the way `.badge:empty` does, and
|
|
6603
|
+
it reads the item's own content: bare text in an `<li>` keeps a reading width
|
|
6604
|
+
like its wrapped peers, where `> li:has(.step-label)` would give it a marker
|
|
6605
|
+
floor and skew the row. It also predates `:has()` by a decade, so the compact
|
|
6606
|
+
form reaches the Degraded tier and only the inline bonus needs `:has()`. */
|
|
6607
|
+
.steps-horizontal > li:empty {
|
|
6608
|
+
flex: 1 1 0;
|
|
6609
|
+
justify-items: start;
|
|
6610
|
+
min-inline-size: var(--step-size);
|
|
6611
|
+
}
|
|
6612
|
+
|
|
6613
|
+
.steps-horizontal > li:empty::after {
|
|
6614
|
+
inset-inline-start: var(--step-size);
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
.steps-horizontal > li:empty:last-child {
|
|
6618
|
+
flex: 0 0 auto;
|
|
6619
|
+
}
|
|
6620
|
+
|
|
6621
|
+
/* The pseudo-element is marker-height so --step-connector may be any complete
|
|
6622
|
+
background value. */
|
|
6623
|
+
.steps-horizontal > li::after {
|
|
6624
|
+
position: absolute;
|
|
6625
|
+
inset-block-start: 0;
|
|
6626
|
+
inset-inline-start: calc(50% + var(--step-size) / 2);
|
|
6627
|
+
inline-size: calc(100% - var(--step-size));
|
|
6628
|
+
block-size: var(--step-size);
|
|
6629
|
+
background: var(
|
|
6630
|
+
--step-connector,
|
|
6631
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
6632
|
+
100% var(--step-line-size) no-repeat
|
|
6633
|
+
);
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
/*
|
|
6637
|
+
* The one horizontal container query. Past 60rem a labelled row has the room
|
|
6638
|
+
* to put marker and label inline and still leave the connector a real region
|
|
6639
|
+
* between groups, rather than a stub. Calibrated on five steps — the widest
|
|
6640
|
+
* supported row, and therefore the binding case — and it counts nothing, so it
|
|
6641
|
+
* serves every count. Below it, stacked; a marker row is already the shape
|
|
6642
|
+
* this query produces and stays out of it.
|
|
6643
|
+
*
|
|
6644
|
+
* Horizontal is a 2-5 step component. Outside that domain the rule simply
|
|
6645
|
+
* applies as written, because a row that is out of contract is not owed a
|
|
6646
|
+
* curated fallback.
|
|
6647
|
+
*/
|
|
6648
|
+
@supports (container-type: inline-size) {
|
|
6649
|
+
@container actual-container (inline-size >= 60rem) {
|
|
6650
|
+
.steps-horizontal:has(.step-label) > li {
|
|
6651
|
+
display: flex;
|
|
6652
|
+
flex: 1 1 auto;
|
|
6653
|
+
align-items: center;
|
|
6654
|
+
gap: var(--step-inline-gap);
|
|
6655
|
+
min-inline-size: 0;
|
|
6656
|
+
text-align: start;
|
|
6657
|
+
white-space: nowrap;
|
|
6658
|
+
}
|
|
6659
|
+
|
|
6660
|
+
.steps-horizontal:has(.step-label) > li::after {
|
|
6661
|
+
position: static;
|
|
6662
|
+
flex: 1 1 2rem;
|
|
6663
|
+
inline-size: auto;
|
|
6664
|
+
block-size: var(--step-size);
|
|
6665
|
+
margin-inline-end: var(--step-inline-gap);
|
|
6666
|
+
background: var(
|
|
6667
|
+
--step-inline-connector,
|
|
6668
|
+
var(
|
|
6669
|
+
--step-connector,
|
|
6670
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
6671
|
+
100% var(--step-line-size) no-repeat
|
|
6672
|
+
)
|
|
6673
|
+
);
|
|
6674
|
+
}
|
|
6675
|
+
|
|
6676
|
+
.steps-horizontal:has(.step-label) > li:last-child {
|
|
6677
|
+
flex: 0 0 auto;
|
|
6678
|
+
}
|
|
6382
6679
|
}
|
|
6383
6680
|
}
|
|
6384
6681
|
|
|
6682
|
+
/* Vertical --------------------------------------------------------------- */
|
|
6683
|
+
|
|
6684
|
+
.steps-vertical {
|
|
6685
|
+
--step-vertical-gap: var(--space-40);
|
|
6686
|
+
|
|
6687
|
+
flex-direction: column;
|
|
6688
|
+
gap: var(--step-vertical-gap);
|
|
6689
|
+
}
|
|
6690
|
+
|
|
6691
|
+
/* `justify-items` looks like a reset and is not one — nothing here resets
|
|
6692
|
+
anything, since the horizontal rules never applied. Grid items blockify, so
|
|
6693
|
+
without it a label stretches the whole 1fr track: 264px against 75px for an
|
|
6694
|
+
`a.step-label`, a full-width focus ring the row does not give the same link. */
|
|
6695
|
+
.steps-vertical > li {
|
|
6696
|
+
display: grid;
|
|
6697
|
+
grid-template-columns: var(--step-size) minmax(0, 1fr);
|
|
6698
|
+
align-items: start;
|
|
6699
|
+
justify-items: start;
|
|
6700
|
+
gap: var(--step-inline-gap);
|
|
6701
|
+
min-block-size: var(--step-size);
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6704
|
+
/* An empty step is a marker rail. The column cannot use the row's implicit
|
|
6705
|
+
second track, because `grid-auto-flow` is `row`: an auto-placed label would
|
|
6706
|
+
land *under* its marker. Its second track is therefore explicit, and this
|
|
6707
|
+
takes it back.
|
|
6708
|
+
|
|
6709
|
+
Silent in a stretched column, where the item fills its line whatever its
|
|
6710
|
+
tracks say. It bites in a shrink-to-fit box — which is what a sticky
|
|
6711
|
+
progress rail usually is — where the label track and the gap to it make the
|
|
6712
|
+
rail `--step-inline-gap` wider than the markers it draws: measured 36px
|
|
6713
|
+
against 28px. No `gap: 0` needed; one track has nothing to sit beside. */
|
|
6714
|
+
.steps-vertical > li:empty {
|
|
6715
|
+
grid-template-columns: var(--step-size);
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
/* The row starts at the top so a wrapped label begins level with its marker.
|
|
6719
|
+
align-self only bites on a single line, which would otherwise float above
|
|
6720
|
+
the marker's centre.
|
|
6721
|
+
|
|
6722
|
+
The one `.step-label` rule that keeps a child combinator: `align-self` is a
|
|
6723
|
+
grid-item property, so this declaration is meaningless on anything but a
|
|
6724
|
+
direct child of the row. Elsewhere the label is addressed as a descendant —
|
|
6725
|
+
the framework owns the structure, and repeating it buys nothing. */
|
|
6726
|
+
.steps-vertical > li > .step-label {
|
|
6727
|
+
align-self: center;
|
|
6728
|
+
}
|
|
6729
|
+
|
|
6730
|
+
/* The vertical track publishes no hook of its own: a horizontal connector value
|
|
6731
|
+
cannot transpose onto this geometry, and this selector is already a reachable
|
|
6732
|
+
override for the design that finally asks. */
|
|
6733
|
+
.steps-vertical > li::after {
|
|
6734
|
+
position: absolute;
|
|
6735
|
+
inset-block-start: var(--step-size);
|
|
6736
|
+
inset-inline-start: calc((var(--step-size) - var(--step-line-size)) / 2);
|
|
6737
|
+
inline-size: var(--step-line-size);
|
|
6738
|
+
block-size: calc(100% - var(--step-size) + var(--step-vertical-gap));
|
|
6739
|
+
background:
|
|
6740
|
+
linear-gradient(var(--step-line), var(--step-line)) center / var(--step-line-size) 100%
|
|
6741
|
+
no-repeat;
|
|
6742
|
+
}
|
|
6743
|
+
|
|
6385
6744
|
|
|
6386
6745
|
/* Navigation */
|
|
6387
6746
|
/*
|
|
@@ -6846,7 +7205,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6846
7205
|
.list > li > .list-item {
|
|
6847
7206
|
display: grid;
|
|
6848
7207
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
6849
|
-
align
|
|
7208
|
+
/* Regions align to the first line, so leading, title, and trailing controls
|
|
7209
|
+
share one horizontal line while supporting text wraps beneath. */
|
|
7210
|
+
align-items: start;
|
|
6850
7211
|
gap: var(--list-item-gap);
|
|
6851
7212
|
min-block-size: var(--list-item-min-size);
|
|
6852
7213
|
padding-block: var(--list-item-pad-block);
|
|
@@ -7125,6 +7486,13 @@ a.list-item:hover {
|
|
|
7125
7486
|
border-end-start-radius: var(--join-es);
|
|
7126
7487
|
}
|
|
7127
7488
|
|
|
7489
|
+
/* A square icon button has an explicit block size, which otherwise prevents
|
|
7490
|
+
align-items: stretch from matching a taller labelled sibling. The button's
|
|
7491
|
+
existing min-block-size keeps the standalone square-size invariant intact. */
|
|
7492
|
+
.join > .btn.icon-only {
|
|
7493
|
+
block-size: auto;
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7128
7496
|
.join > :first-child {
|
|
7129
7497
|
--join-ss: var(--join-radius);
|
|
7130
7498
|
--join-es: var(--join-radius);
|
|
@@ -7189,12 +7557,23 @@ a.list-item:hover {
|
|
|
7189
7557
|
/*
|
|
7190
7558
|
* Decorative intent-colored frame for one direct child.
|
|
7191
7559
|
*
|
|
7560
|
+
* The effect is built from three layers with distinct roles: a frame carrying
|
|
7561
|
+
* a short accent of light, a small halo that belongs to the frame and keeps
|
|
7562
|
+
* that light attached to the edge, and a wide static halo that belongs to the
|
|
7563
|
+
* object and gives it a permanent, diffuse presence. Only the small halo
|
|
7564
|
+
* follows the animated gradient, so a rotating aura repaints one blurred layer
|
|
7565
|
+
* per frame, not two.
|
|
7566
|
+
*
|
|
7192
7567
|
* Public hooks:
|
|
7193
7568
|
* --aura-color
|
|
7194
7569
|
* --aura-width
|
|
7195
7570
|
* --aura-blur
|
|
7571
|
+
* --aura-intensity
|
|
7196
7572
|
* --aura-radius
|
|
7197
7573
|
* --aura-duration
|
|
7574
|
+
*
|
|
7575
|
+
* Internal:
|
|
7576
|
+
* --aura-angle
|
|
7198
7577
|
*/
|
|
7199
7578
|
@property --aura-angle {
|
|
7200
7579
|
syntax: "<angle>";
|
|
@@ -7211,7 +7590,8 @@ a.list-item:hover {
|
|
|
7211
7590
|
.aura {
|
|
7212
7591
|
--aura-color: var(--intent, var(--primary));
|
|
7213
7592
|
--aura-width: 2px;
|
|
7214
|
-
--aura-blur: 0.
|
|
7593
|
+
--aura-blur: 0.25rem;
|
|
7594
|
+
--aura-intensity: 1;
|
|
7215
7595
|
--aura-radius: var(--radius-lg);
|
|
7216
7596
|
--aura-duration: 6s;
|
|
7217
7597
|
|
|
@@ -7222,9 +7602,19 @@ a.list-item:hover {
|
|
|
7222
7602
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
7223
7603
|
border: var(--border-width) solid transparent;
|
|
7224
7604
|
background: var(--aura-color);
|
|
7225
|
-
|
|
7605
|
+
|
|
7606
|
+
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
7607
|
+
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
7608
|
+
across 2px draws an outline, the opposite of the effect. */
|
|
7609
|
+
background: conic-gradient(
|
|
7610
|
+
from var(--aura-angle),
|
|
7611
|
+
transparent 0 78%,
|
|
7612
|
+
var(--aura-color) 93%,
|
|
7613
|
+
transparent
|
|
7614
|
+
);
|
|
7226
7615
|
}
|
|
7227
7616
|
|
|
7617
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. */
|
|
7228
7618
|
.aura::before {
|
|
7229
7619
|
position: absolute;
|
|
7230
7620
|
z-index: -1;
|
|
@@ -7232,15 +7622,40 @@ a.list-item:hover {
|
|
|
7232
7622
|
border-radius: inherit;
|
|
7233
7623
|
background: inherit;
|
|
7234
7624
|
filter: blur(var(--aura-blur));
|
|
7235
|
-
opacity: 0.
|
|
7625
|
+
opacity: calc(0.55 * var(--aura-intensity));
|
|
7626
|
+
pointer-events: none;
|
|
7627
|
+
content: "";
|
|
7628
|
+
}
|
|
7629
|
+
|
|
7630
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
7631
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
7632
|
+
layer. It outgrows the frame, otherwise the child covers it and the whole
|
|
7633
|
+
effect reads as a drop shadow. This is the layer that stays lit at all
|
|
7634
|
+
times, so it — not the frame — is what keeps the object present between two
|
|
7635
|
+
passes of the accent. */
|
|
7636
|
+
.aura::after {
|
|
7637
|
+
position: absolute;
|
|
7638
|
+
z-index: -1;
|
|
7639
|
+
inset: calc(-1 * var(--aura-blur));
|
|
7640
|
+
border-radius: calc(var(--aura-radius) + var(--aura-width) + var(--aura-blur));
|
|
7641
|
+
background: var(--aura-color);
|
|
7642
|
+
filter: blur(calc(var(--aura-blur) * 4));
|
|
7643
|
+
opacity: calc(0.15 * var(--aura-intensity));
|
|
7236
7644
|
pointer-events: none;
|
|
7237
7645
|
content: "";
|
|
7238
7646
|
}
|
|
7239
7647
|
|
|
7648
|
+
/* Lifts the child above both halos. It also has to be opaque: the frame paints
|
|
7649
|
+
across the whole box and only its edge is meant to show, so a transparent
|
|
7650
|
+
child (an .outline alert, a .ghost button) is filled with the frame color
|
|
7651
|
+
rather than framed by it. Documented as a constraint, not worked around —
|
|
7652
|
+
masking a ring would cost more than the case is worth. */
|
|
7240
7653
|
.aura > * {
|
|
7241
7654
|
position: relative;
|
|
7242
7655
|
}
|
|
7243
7656
|
|
|
7657
|
+
/* Glow lights the whole perimeter instead of a short accent, so both halos
|
|
7658
|
+
become a single soft field around the child. */
|
|
7244
7659
|
.aura-glow {
|
|
7245
7660
|
background: var(--aura-color);
|
|
7246
7661
|
}
|
|
@@ -7249,6 +7664,29 @@ a.list-item:hover {
|
|
|
7249
7664
|
.aura:not(.aura-glow) {
|
|
7250
7665
|
animation: aura-rotate var(--aura-duration) linear infinite;
|
|
7251
7666
|
}
|
|
7667
|
+
|
|
7668
|
+
/* Both halos breathe: moving the near one alone changes a line's brightness,
|
|
7669
|
+
which barely registers. Opening the wide field with it is what reads as a
|
|
7670
|
+
breath. */
|
|
7671
|
+
.aura-glow::before {
|
|
7672
|
+
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
7673
|
+
}
|
|
7674
|
+
|
|
7675
|
+
.aura-glow::after {
|
|
7676
|
+
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
7677
|
+
}
|
|
7678
|
+
}
|
|
7679
|
+
|
|
7680
|
+
/* Without motion the accent would freeze at an arbitrary angle and read as a
|
|
7681
|
+
broken animation, so it becomes a deliberate composition: two opposite
|
|
7682
|
+
corners. Fixed radii keep those corners stable across aspect ratios;
|
|
7683
|
+
proportional radii turn wide or tall elements into lit edges instead. */
|
|
7684
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7685
|
+
.aura:not(.aura-glow) {
|
|
7686
|
+
background:
|
|
7687
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%),
|
|
7688
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%);
|
|
7689
|
+
}
|
|
7252
7690
|
}
|
|
7253
7691
|
|
|
7254
7692
|
@keyframes aura-rotate {
|
|
@@ -7257,18 +7695,38 @@ a.list-item:hover {
|
|
|
7257
7695
|
}
|
|
7258
7696
|
}
|
|
7259
7697
|
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7698
|
+
/* Only the peak is declared, so each halo breathes up from its own resting
|
|
7699
|
+
opacity and never dips below the static glow. The peaks scale with the same
|
|
7700
|
+
intensity as the resting values, which keeps the breath proportional at
|
|
7701
|
+
every setting. */
|
|
7702
|
+
@keyframes aura-breathe-near {
|
|
7703
|
+
50% {
|
|
7704
|
+
opacity: calc(0.95 * var(--aura-intensity));
|
|
7705
|
+
transform: scale(1.03);
|
|
7263
7706
|
}
|
|
7707
|
+
}
|
|
7264
7708
|
|
|
7265
|
-
|
|
7266
|
-
|
|
7709
|
+
@keyframes aura-breathe-far {
|
|
7710
|
+
50% {
|
|
7711
|
+
opacity: calc(0.28 * var(--aura-intensity));
|
|
7712
|
+
transform: scale(1.07);
|
|
7267
7713
|
}
|
|
7714
|
+
}
|
|
7268
7715
|
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7716
|
+
/* Each rule is a lone :has() setting a decorative radius, so a browser without
|
|
7717
|
+
:has() simply drops it and keeps the default. No @supports needed — that
|
|
7718
|
+
guard only earns its place around a selector list that mixes :has() with
|
|
7719
|
+
selectors worth keeping. */
|
|
7720
|
+
.aura:has(> .btn) {
|
|
7721
|
+
--aura-radius: var(--radius);
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
.aura:has(> :is(.card, .alert)) {
|
|
7725
|
+
--aura-radius: var(--radius-lg);
|
|
7726
|
+
}
|
|
7727
|
+
|
|
7728
|
+
.aura:has(> .badge) {
|
|
7729
|
+
--aura-radius: var(--radius-full);
|
|
7272
7730
|
}
|
|
7273
7731
|
|
|
7274
7732
|
/* Utilities — small, single-purpose escape hatches. */
|