actual-css 0.2.0 → 0.3.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 +38 -1
- package/README.md +24 -9
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1244 -915
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +94 -14
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +11 -14
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +2 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +37 -1
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +4 -4
- package/package.json +20 -12
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -5
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +126 -99
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +55 -35
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +6 -0
- package/src/css/components/button.css +78 -32
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -109
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -2
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +6 -7
- package/src/css/components/status-bar.css +15 -24
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +19 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +14 -38
- package/src/css/forms/choice-card.css +13 -14
- package/src/css/forms/choice.css +21 -27
- package/src/css/forms/control.css +16 -17
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +4 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +50 -11
- package/src/css/forms/range.css +17 -9
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -12
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +13 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +21 -5
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +8 -10
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +74 -20
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +67 -35
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +54 -23
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +104 -87
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +87 -34
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +12 -4
- package/src/js/selectors.js +0 -7
- package/src/js/status.js +3 -1
- package/src/js/surface.js +185 -136
- package/src/js/tab.js +36 -53
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +34 -8
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -28
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -496
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -504
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -929
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
package/src/css/tokens.css
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
:root {
|
|
2
|
+
/*
|
|
3
|
+
* Global framework tokens.
|
|
4
|
+
*
|
|
5
|
+
* Public: properties in the named groups below are public API (typography,
|
|
6
|
+
* spacing, shape, motion, focus, controls, viewport, z-index, icons) unless
|
|
7
|
+
* a group is explicitly marked Internal or Derived.
|
|
8
|
+
*
|
|
9
|
+
* Derived: "Theme-derived aliases" and "State tokens" reference intent and
|
|
10
|
+
* surface tokens and follow the theme; do not hard-code values into them.
|
|
11
|
+
* Internal: indicator offsets, --bar-height, --disabled-opacity and the
|
|
12
|
+
* like are consumed by components, not authored.
|
|
13
|
+
*
|
|
14
|
+
* Component-local hooks and internal state relays live with their
|
|
15
|
+
* components. See docs/pages/foundations/tokens.md for the public/internal
|
|
16
|
+
* token contract.
|
|
17
|
+
*/
|
|
18
|
+
|
|
2
19
|
/* Typography */
|
|
3
20
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
4
21
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
@@ -16,20 +33,22 @@
|
|
|
16
33
|
--font-size-md: 1rem;
|
|
17
34
|
--font-size-lg: 1.125rem;
|
|
18
35
|
|
|
19
|
-
/* Spacing
|
|
36
|
+
/* Spacing — official steps are named in tens so the scale stays open for
|
|
37
|
+
application themes to intercalate intermediate values (--space-15,
|
|
38
|
+
--space-45…) without colliding with future framework steps. */
|
|
20
39
|
--gap: 0.75rem;
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--space-
|
|
24
|
-
--space-
|
|
25
|
-
--space-
|
|
26
|
-
--space-
|
|
27
|
-
--space-
|
|
40
|
+
--density-space: var(--space-40);
|
|
41
|
+
--density-compact-size: 1.5rem;
|
|
42
|
+
--space-10: 0.25rem;
|
|
43
|
+
--space-20: 0.5rem;
|
|
44
|
+
--space-30: 0.75rem;
|
|
45
|
+
--space-40: 1rem;
|
|
46
|
+
--space-50: 1.5rem;
|
|
47
|
+
--space-60: 2rem;
|
|
28
48
|
|
|
29
49
|
/* Shape */
|
|
30
50
|
--border-width: 1px;
|
|
31
51
|
--radius-full: 9999px;
|
|
32
|
-
--shadow-xs: 0 1px 2px rgb(0 0 0 / 0.04);
|
|
33
52
|
--shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
|
|
34
53
|
--shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
|
|
35
54
|
|
|
@@ -53,7 +72,6 @@
|
|
|
53
72
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
54
73
|
dot with the parent surface color. */
|
|
55
74
|
--indicator-offset: 14.65%;
|
|
56
|
-
--indicator-ring: var(--surface);
|
|
57
75
|
|
|
58
76
|
/* Shared backdrop — consumed by modal, drawer, and mobile flyout sheets. */
|
|
59
77
|
--backdrop-color: rgb(0 0 0);
|
|
@@ -64,7 +82,8 @@
|
|
|
64
82
|
--bar-height: 0.65rem;
|
|
65
83
|
|
|
66
84
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
67
|
-
.sm/.lg (variants.css) switch --control-size
|
|
85
|
+
.sm/.lg (variants.css) switch --control-size; --control-font-size stays
|
|
86
|
+
at its baseline because density does not change typography. */
|
|
68
87
|
--control-size-sm: 2rem;
|
|
69
88
|
--control-size-md: 2.375rem;
|
|
70
89
|
--control-size-lg: 2.75rem;
|
|
@@ -72,19 +91,38 @@
|
|
|
72
91
|
--control-pad-x: 1em;
|
|
73
92
|
--control-font-size: var(--font-size-md);
|
|
74
93
|
--disabled-opacity: 0.65;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Theme-derived aliases. These reference intent/surface tokens, so they must
|
|
97
|
+
be re-declared on every theme boundary: a custom property resolves its
|
|
98
|
+
var() references at computed-value time on the element that declares it,
|
|
99
|
+
so an alias declared only on :root is inherited as an already-resolved
|
|
100
|
+
value and would not follow a [data-theme] island's overridden tokens.
|
|
101
|
+
Themes that want a distinct alias override it explicitly afterwards. */
|
|
102
|
+
:root,
|
|
103
|
+
[data-theme] {
|
|
104
|
+
/* State tokens — semantic colors for selected/pressed/current and disabled.
|
|
105
|
+
Components consume these instead of hard-wiring --primary/--text-subtle
|
|
106
|
+
so forced-colors can override them in one place. */
|
|
107
|
+
--state-selected: var(--primary);
|
|
108
|
+
--state-selected-fg: var(--primary-fg);
|
|
109
|
+
--state-disabled: var(--text-subtle);
|
|
110
|
+
|
|
111
|
+
/* Status dot ring — follows the island's surface. */
|
|
112
|
+
--indicator-ring: var(--surface);
|
|
113
|
+
}
|
|
75
114
|
|
|
115
|
+
:root {
|
|
76
116
|
/* Variant tuning — themes may adjust generated soft surfaces without
|
|
77
117
|
replacing the component rules. */
|
|
78
118
|
--soft-bg-mix: 88%;
|
|
79
119
|
--soft-border-mix: 65%;
|
|
80
120
|
--soft-hover-alpha: 12%;
|
|
81
121
|
|
|
82
|
-
/* Shared
|
|
83
|
-
--icon-chevron
|
|
84
|
-
--icon-check
|
|
85
|
-
|
|
86
|
-
The stroke matches the default theme's text-subtle (hue 258 neutral). */
|
|
87
|
-
--icon-chevron-image: 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");
|
|
122
|
+
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
123
|
+
--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");
|
|
124
|
+
--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");
|
|
125
|
+
--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");
|
|
88
126
|
|
|
89
127
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
90
128
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -93,13 +131,24 @@
|
|
|
93
131
|
--viewport-block: 100vh;
|
|
94
132
|
--viewport-inline: 100vw;
|
|
95
133
|
|
|
96
|
-
/*
|
|
134
|
+
/* Z-index scale — used ONLY by non-dialog overlays (see the layering
|
|
135
|
+
note below for the full model). Actual CSS reserves multiples of 10 for
|
|
136
|
+
framework layers; applications may use intermediate values for local
|
|
137
|
+
layering. */
|
|
97
138
|
--z-sticky: 10;
|
|
98
139
|
--z-menu: 20;
|
|
99
140
|
--z-tooltip: 50;
|
|
100
141
|
--z-status: 60;
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
@media (forced-colors: active) {
|
|
145
|
+
:root {
|
|
146
|
+
--state-selected: Highlight;
|
|
147
|
+
--state-selected-fg: HighlightText;
|
|
148
|
+
--state-disabled: GrayText;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
103
152
|
@supports (block-size: 100dvh) {
|
|
104
153
|
:root {
|
|
105
154
|
--viewport-block: 100dvh;
|
|
@@ -113,12 +162,17 @@
|
|
|
113
162
|
}
|
|
114
163
|
|
|
115
164
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
116
|
-
:root
|
|
117
|
-
|
|
165
|
+
:root,
|
|
166
|
+
[data-theme] {
|
|
167
|
+
/* Shadows derive from --shadow-color (theme.css) — re-declared on every
|
|
168
|
+
theme boundary so islands follow their own shadow-color. */
|
|
118
169
|
--shadow:
|
|
119
170
|
0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent),
|
|
120
171
|
0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent);
|
|
121
172
|
--shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
:root {
|
|
122
176
|
--backdrop-fill: color-mix(
|
|
123
177
|
in oklch,
|
|
124
178
|
var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
|
package/src/css/utilities.css
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
min-inline-size: 0;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
.nowrap {
|
|
33
|
+
.text-nowrap {
|
|
34
34
|
white-space: nowrap;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -125,15 +125,13 @@
|
|
|
125
125
|
text-decoration: none;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
color: var(--text);
|
|
131
|
-
}
|
|
128
|
+
.link-muted:hover {
|
|
129
|
+
color: var(--text);
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
/* Spacing step helpers — semantic scale (sm / default / lg).
|
|
135
133
|
Names map to logical properties for writing-direction safety. The default
|
|
136
|
-
step uses --space-
|
|
134
|
+
step uses --space-40, which matches the framework's built-in padding on
|
|
137
135
|
cards, dialogs, and form-actions. */
|
|
138
136
|
|
|
139
137
|
/* Gap */
|
|
@@ -143,20 +141,20 @@
|
|
|
143
141
|
|
|
144
142
|
/* Padding — block (vertical) */
|
|
145
143
|
.py {
|
|
146
|
-
padding-block: var(--
|
|
144
|
+
padding-block: var(--density-space);
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
/* Padding — inline (horizontal) */
|
|
150
148
|
.px {
|
|
151
|
-
padding-inline: var(--
|
|
149
|
+
padding-inline: var(--density-space);
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
/* Margin — block-start */
|
|
155
153
|
.mbs {
|
|
156
|
-
margin-block-start: var(--
|
|
154
|
+
margin-block-start: var(--density-space);
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
/* Margin — block-end */
|
|
160
158
|
.mbe {
|
|
161
|
-
margin-block-end: var(--
|
|
159
|
+
margin-block-end: var(--density-space);
|
|
162
160
|
}
|
package/src/css/variants.css
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
* Cross-cutting variant token mapping.
|
|
3
3
|
*
|
|
4
4
|
* Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
|
|
5
|
-
* shared variants.
|
|
6
|
-
*
|
|
5
|
+
* shared variants. .inverted is a surface modifier, not a variant: it also
|
|
6
|
+
* paints the element directly (background/color/border-color) so it works on
|
|
7
|
+
* any block, competing with component surface treatments (.card.subtle).
|
|
8
|
+
* Component-specific defaults (.btn solid-by-default, .badge
|
|
9
|
+
* soft-by-default, .alert soft-by-default) live with their components:
|
|
7
10
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
8
|
-
* components/badge.css — .badge default
|
|
9
|
-
* components/alert.css — .alert default
|
|
11
|
+
* components/badge.css — .badge default soft
|
|
12
|
+
* components/alert.css — .alert default soft (consumes --ui-* directly)
|
|
10
13
|
*
|
|
11
|
-
* .sm/.lg set shared
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* .sm/.lg set shared density tokens: spacing rhythm and geometry only.
|
|
15
|
+
* Density never changes typography — controls keep their font-size and
|
|
16
|
+
* content components keep theirs. Controls consume --control-size; non-control
|
|
17
|
+
* components may consume --density-* when the density context should affect
|
|
18
|
+
* their local rhythm. Compact components may consume --density-compact-size.
|
|
14
19
|
* Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
|
|
15
20
|
* applying .sm/.lg to card, dialog, accordion, navbar, table is
|
|
16
21
|
* a no-op.
|
|
@@ -36,27 +41,34 @@
|
|
|
36
41
|
--ui-border: var(--intent, var(--border));
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
/* Inverse surface treatment. Components (e.g. .card) compose through --ui-*;
|
|
45
|
+
the direct background/color/border-color also make .inverted useful on a
|
|
46
|
+
generic element. --heading is set so global headings (var(--heading, inherit))
|
|
47
|
+
do not inherit an outside heading color onto the dark surface. */
|
|
48
|
+
.inverted {
|
|
49
|
+
--ui-bg: var(--surface-solid);
|
|
50
|
+
--ui-fg: var(--surface);
|
|
51
|
+
--ui-border: transparent;
|
|
52
|
+
|
|
53
|
+
--heading: var(--surface);
|
|
54
|
+
--busy-overlay-bg: var(--surface-solid);
|
|
55
|
+
|
|
56
|
+
background: var(--ui-bg);
|
|
57
|
+
color: var(--ui-fg);
|
|
58
|
+
border-color: var(--ui-border);
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
.sm {
|
|
40
|
-
--gap: var(--space-
|
|
41
|
-
--
|
|
62
|
+
--gap: var(--space-20);
|
|
63
|
+
--density-space: var(--space-20);
|
|
42
64
|
--control-size: var(--control-size-sm);
|
|
43
|
-
--
|
|
44
|
-
--variant-font-size: var(--font-size-sm);
|
|
45
|
-
--variant-icon-size: 1rem;
|
|
46
|
-
--variant-pad-block: var(--space-3);
|
|
47
|
-
--variant-compact-size: 1.25rem;
|
|
48
|
-
--variant-compact-font-size: 0.75rem;
|
|
65
|
+
--density-compact-size: 1.25rem;
|
|
49
66
|
}
|
|
50
67
|
.lg {
|
|
51
|
-
--gap: var(--space-
|
|
52
|
-
--
|
|
68
|
+
--gap: var(--space-50);
|
|
69
|
+
--density-space: var(--space-50);
|
|
53
70
|
--control-size: var(--control-size-lg);
|
|
54
|
-
--
|
|
55
|
-
--variant-font-size: var(--font-size-lg);
|
|
56
|
-
--variant-icon-size: 1.5rem;
|
|
57
|
-
--variant-pad-block: var(--space-5);
|
|
58
|
-
--variant-compact-size: 1.875rem;
|
|
59
|
-
--variant-compact-font-size: var(--font-size-sm);
|
|
71
|
+
--density-compact-size: 1.875rem;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -74,45 +86,5 @@
|
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
/* Interactive hover
|
|
78
|
-
|
|
79
|
-
rules, so these values are inert until hovered. :is() carries real
|
|
80
|
-
specificity so the components' :where() intent boundaries
|
|
81
|
-
(--ui-hover-bg: initial) cannot override the recipe. The solid/soft overlay
|
|
82
|
-
darkening lives in the consuming components. */
|
|
83
|
-
:is(.btn, a.badge, button.badge):is(.outline, .ghost) {
|
|
84
|
-
--ui-hover-bg: var(--surface-subtle);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
88
|
-
/* Solid has no literal class requirement: bare .btn/.badge default to solid,
|
|
89
|
-
so this must match anything that isn't one of the other variants rather
|
|
90
|
-
than an explicit .solid class. Stacks with the box-shadow overlay the
|
|
91
|
-
consuming components apply on top for extra depth. */
|
|
92
|
-
:is(.btn, a.badge, button.badge):not(:where(.soft, .outline, .ghost, .link)) {
|
|
93
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/* Same recipe as the resting .soft bg with the surface share reduced by
|
|
97
|
-
--soft-hover-alpha: stays opaque, so it reads on any backdrop instead of
|
|
98
|
-
letting the container show through on hover. */
|
|
99
|
-
:is(.btn, a.badge, button.badge):is(.soft) {
|
|
100
|
-
--ui-hover-bg: color-mix(
|
|
101
|
-
in oklch,
|
|
102
|
-
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
103
|
-
var(--intent, var(--neutral))
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/* Mixed against the opaque --surface-subtle rather than transparent: a
|
|
108
|
-
transparent-based tint reads clean on light surfaces but goes muddy and
|
|
109
|
-
low-contrast on dark ones, since the same low alpha lands very
|
|
110
|
-
differently over a light vs. a near-black backdrop. */
|
|
111
|
-
:is(.btn, a.badge, button.badge):is(.outline) {
|
|
112
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
:is(.btn, a.badge, button.badge):is(.ghost) {
|
|
116
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
89
|
+
/* Interactive hover recipes live in the components that consume them:
|
|
90
|
+
components/button.css and components/badge.css. */
|
package/src/js/command.js
CHANGED
|
@@ -25,9 +25,7 @@ function commandNames(commands, caller) {
|
|
|
25
25
|
// Native command keywords are ASCII case-insensitive. Custom commands keep
|
|
26
26
|
// their exact spelling, as required by the command invoker contract.
|
|
27
27
|
function commandKey(command) {
|
|
28
|
-
return command.startsWith("--")
|
|
29
|
-
? command
|
|
30
|
-
: command.replace(/[A-Z]/g, (character) => character.toLowerCase());
|
|
28
|
+
return command.startsWith("--") ? command : command.toLowerCase();
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
/**
|
package/src/js/context-menu.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import enhance from "./enhance.js";
|
|
2
2
|
import { EVENTS } from "./events.js";
|
|
3
|
-
import { focusFirstMenuItem, hasMenuItems } from "./menu.js";
|
|
4
|
-
import { closeSurface, isSurfaceOpen, openSurface,
|
|
3
|
+
import { connectMenu, focusFirstMenuItem, hasMenuItems } from "./menu.js";
|
|
4
|
+
import { closeSurface, isSurfaceOpen, openSurface, retainSurface } from "./surface.js";
|
|
5
5
|
|
|
6
6
|
const LONG_PRESS_MS = 450;
|
|
7
|
+
const LONG_PRESS_CLICK_SUPPRESSION_MS = 750;
|
|
7
8
|
const MOVE_TOLERANCE = 10;
|
|
8
9
|
const CONTEXT_MENU_SELECTOR = 'menu, [role="menu"]';
|
|
9
10
|
const CONTEXT_TARGET_SELECTOR = "[data-context-menu]";
|
|
10
11
|
const contextMap = new WeakMap();
|
|
11
12
|
const contextByMenu = new WeakMap();
|
|
12
13
|
|
|
13
|
-
// Returns the detail from the last accepted `actual:context-menu` event for a
|
|
14
|
-
// menu. This is intentionally optional: static menus need no application JS.
|
|
15
14
|
export function contextFor(menu) {
|
|
16
15
|
return contextByMenu.get(menu) || null;
|
|
17
16
|
}
|
|
@@ -48,27 +47,29 @@ function getLongPressDelay(target, menu) {
|
|
|
48
47
|
if (value === "") return LONG_PRESS_MS;
|
|
49
48
|
|
|
50
49
|
const delay = Number.parseInt(value, 10);
|
|
51
|
-
return Number.isFinite(delay) ? delay : LONG_PRESS_MS;
|
|
50
|
+
return Number.isFinite(delay) ? Math.max(0, delay) : LONG_PRESS_MS;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readPanelOptions(menu) {
|
|
54
|
+
const opts = { autoClose: "true" };
|
|
55
|
+
const ds = menu.dataset;
|
|
56
|
+
if (ds.flyoutMobile) opts.mobile = ds.flyoutMobile;
|
|
57
|
+
if (ds.flyoutAutoClose) opts.autoClose = ds.flyoutAutoClose;
|
|
58
|
+
return opts;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
function focusMenuContainer(menu) {
|
|
55
62
|
if (!menu.hasAttribute("tabindex")) {
|
|
56
63
|
menu.tabIndex = -1;
|
|
57
64
|
}
|
|
58
|
-
menu.
|
|
65
|
+
// Moving the mounted menu into the surface root must not scroll the page.
|
|
66
|
+
// Genuine user scrolling is handled separately by the surface policy.
|
|
67
|
+
menu.focus({ preventScroll: true });
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
// Pointer-triggered opens (right-click, long-press) focus the menu container,
|
|
62
|
-
// not the first item: a pointer-set focus ring is invisible (:focus-visible
|
|
63
|
-
// doesn't match), so focusing "Open" makes the first ArrowDown look like it
|
|
64
|
-
// skips to the second item. Focusing the container keeps the ring hidden and
|
|
65
|
-
// lets keys.js's out-of-list fallback (indexOf === -1) land ArrowDown/ArrowUp
|
|
66
|
-
// on the first/last item, matching native OS context menus. Keyboard-triggered
|
|
67
|
-
// opens (openFromKeyboard) still focus the first item directly.
|
|
68
70
|
function focusMenu(menu, mode) {
|
|
69
|
-
if (!hasMenuItems(menu)) return;
|
|
70
71
|
if (mode === "first-item") {
|
|
71
|
-
focusFirstMenuItem(menu);
|
|
72
|
+
if (hasMenuItems(menu)) focusFirstMenuItem(menu);
|
|
72
73
|
} else {
|
|
73
74
|
focusMenuContainer(menu);
|
|
74
75
|
}
|
|
@@ -101,9 +102,11 @@ function openContextMenu(context, menu, opts = {}) {
|
|
|
101
102
|
y: opts.y,
|
|
102
103
|
placement: opts.placement || "bottom-start",
|
|
103
104
|
distance: opts.distance ?? 2,
|
|
104
|
-
|
|
105
|
+
dismissOnScroll: true,
|
|
106
|
+
mobile: opts.mobile || "auto",
|
|
105
107
|
scope: opts.scope || getContextScope(context),
|
|
106
108
|
restoreFocusTo: opts.restoreFocusTo,
|
|
109
|
+
...readPanelOptions(menu),
|
|
107
110
|
})
|
|
108
111
|
) {
|
|
109
112
|
focusMenu(menu, opts.focus);
|
|
@@ -137,11 +140,16 @@ function connectContextTarget(target) {
|
|
|
137
140
|
const menu = menuFor(target);
|
|
138
141
|
if (!menu) return;
|
|
139
142
|
|
|
140
|
-
|
|
143
|
+
const release = retainSurface(menu);
|
|
141
144
|
const controller = new AbortController();
|
|
145
|
+
const releaseMenu = connectMenu(menu, {
|
|
146
|
+
close: (menu) => closeSurface(menu),
|
|
147
|
+
});
|
|
142
148
|
const state = {
|
|
143
149
|
controller,
|
|
150
|
+
releaseMenu,
|
|
144
151
|
menu,
|
|
152
|
+
release,
|
|
145
153
|
timer: null,
|
|
146
154
|
pointerId: null,
|
|
147
155
|
startX: 0,
|
|
@@ -190,7 +198,7 @@ function connectContextTarget(target) {
|
|
|
190
198
|
state.startY = e.clientY;
|
|
191
199
|
state.timer = setTimeout(() => {
|
|
192
200
|
state.timer = null;
|
|
193
|
-
state.suppressClickUntil = Date.now() +
|
|
201
|
+
state.suppressClickUntil = Date.now() + LONG_PRESS_CLICK_SUPPRESSION_MS;
|
|
194
202
|
openContextMenu(target, menu, {
|
|
195
203
|
x: state.startX,
|
|
196
204
|
y: state.startY,
|
|
@@ -238,6 +246,45 @@ function connectContextTarget(target) {
|
|
|
238
246
|
{ signal: controller.signal, capture: true },
|
|
239
247
|
);
|
|
240
248
|
|
|
249
|
+
target.addEventListener(
|
|
250
|
+
"click",
|
|
251
|
+
(e) => {
|
|
252
|
+
const trigger = e.target.closest("[data-context-menu-trigger]");
|
|
253
|
+
if (
|
|
254
|
+
!trigger ||
|
|
255
|
+
trigger.getAttribute("aria-controls") !== target.getAttribute("data-context-menu")
|
|
256
|
+
)
|
|
257
|
+
return;
|
|
258
|
+
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
|
|
262
|
+
if (isSurfaceOpen(menu)) {
|
|
263
|
+
closeSurface(menu);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!requestContextMenu(target, menu, { origin: trigger, trigger: "button" })) return;
|
|
268
|
+
|
|
269
|
+
const rect = trigger.getBoundingClientRect();
|
|
270
|
+
if (
|
|
271
|
+
openSurface(menu, {
|
|
272
|
+
source: target,
|
|
273
|
+
x: rect.left,
|
|
274
|
+
y: rect.bottom,
|
|
275
|
+
placement: "bottom-start",
|
|
276
|
+
distance: 4,
|
|
277
|
+
dismissOnScroll: true,
|
|
278
|
+
restoreFocusTo: trigger,
|
|
279
|
+
...readPanelOptions(menu),
|
|
280
|
+
})
|
|
281
|
+
) {
|
|
282
|
+
focusMenuContainer(menu);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{ signal: controller.signal },
|
|
286
|
+
);
|
|
287
|
+
|
|
241
288
|
contextMap.set(target, state);
|
|
242
289
|
}
|
|
243
290
|
|
|
@@ -246,33 +293,18 @@ function disconnectContextTarget(target) {
|
|
|
246
293
|
if (!state) return;
|
|
247
294
|
clearLongPress(state);
|
|
248
295
|
state.controller.abort();
|
|
296
|
+
state.releaseMenu?.();
|
|
249
297
|
if (contextFor(state.menu)?.context === target) {
|
|
250
298
|
contextByMenu.delete(state.menu);
|
|
251
299
|
closeSurface(state.menu, { restoreFocus: false });
|
|
252
300
|
}
|
|
301
|
+
state.release();
|
|
253
302
|
contextMap.delete(target);
|
|
254
303
|
}
|
|
255
304
|
|
|
256
|
-
function onSurfaceOpen(event) {
|
|
257
|
-
const options = event.detail?.options;
|
|
258
|
-
const trigger = options?.trigger;
|
|
259
|
-
const context = trigger?.closest?.(CONTEXT_TARGET_SELECTOR);
|
|
260
|
-
if (!context || menuFor(context) !== event.detail?.surface) return;
|
|
261
|
-
if (!requestContextMenu(context, event.detail.surface, { origin: trigger, trigger: "button" })) {
|
|
262
|
-
event.preventDefault();
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
options.source = context;
|
|
266
|
-
options.restoreFocusTo = trigger;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
305
|
enhance({
|
|
270
306
|
[CONTEXT_TARGET_SELECTOR]: (target) => {
|
|
271
307
|
connectContextTarget(target);
|
|
272
308
|
return () => disconnectContextTarget(target);
|
|
273
309
|
},
|
|
274
310
|
});
|
|
275
|
-
|
|
276
|
-
if (typeof document !== "undefined") {
|
|
277
|
-
document.addEventListener(EVENTS.surfaceOpen, onSurfaceOpen);
|
|
278
|
-
}
|