actual-css 0.3.0 → 0.4.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 +78 -3
- package/README.md +35 -30
- package/dist/actual.css +98 -4882
- package/dist/actual.full.css +7090 -0
- package/dist/actual.full.js +1 -0
- package/dist/actual.full.min.css +1 -0
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/cascade-layer.md +35 -13
- package/docs/design-notes/column-layout.md +677 -0
- package/docs/design-notes/forced-colors.md +3 -0
- package/docs/design-notes/grid.md +612 -0
- package/docs/design-notes/naming.md +15 -0
- package/docs/design-notes/widget-primitives.md +1 -0
- package/docs/navigation.json +5 -1
- package/docs/pages/components/alert.md +16 -14
- package/docs/pages/components/aura.md +1 -1
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +7 -7
- package/docs/pages/components/button.md +22 -17
- package/docs/pages/components/card.md +29 -13
- package/docs/pages/components/chat.md +8 -8
- package/docs/pages/components/dialog.md +7 -7
- package/docs/pages/components/drawer.md +3 -3
- package/docs/pages/components/fab.md +7 -7
- package/docs/pages/components/navbar.md +9 -8
- package/docs/pages/components/pagination.md +4 -3
- package/docs/pages/components/skeleton.md +4 -4
- package/docs/pages/components/spinner.md +5 -5
- package/docs/pages/components/table.md +30 -5
- package/docs/pages/enhancements/flyout.md +13 -13
- package/docs/pages/enhancements/overview.md +1 -1
- package/docs/pages/enhancements/scrollspy.md +6 -6
- package/docs/pages/enhancements/status-bar.md +5 -1
- package/docs/pages/enhancements/tabs.md +6 -6
- package/docs/pages/enhancements/tooltip.md +1 -1
- package/docs/pages/examples/overview.md +41 -22
- package/docs/pages/forms/choice-cards.md +11 -6
- package/docs/pages/forms/controls.md +1 -1
- package/docs/pages/forms/detached-actions.md +1 -1
- package/docs/pages/forms/floating-field.md +10 -5
- package/docs/pages/forms/form-actions.md +1 -1
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/joined-controls.md +4 -4
- package/docs/pages/forms/labels.md +6 -6
- package/docs/pages/forms/otp.md +1 -1
- package/docs/pages/forms/overview.md +19 -19
- package/docs/pages/forms/password.md +1 -1
- package/docs/pages/forms/range.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +41 -45
- package/docs/pages/forms/sticky-actions.md +3 -3
- package/docs/pages/forms/switch.md +110 -2
- package/docs/pages/forms/validation.md +9 -9
- package/docs/pages/foundations/philosophy.md +1 -1
- package/docs/pages/foundations/theming.md +29 -7
- package/docs/pages/foundations/tokens.md +22 -10
- package/docs/pages/foundations/typography.md +50 -20
- package/docs/pages/guides/bootstrap.md +11 -11
- package/docs/pages/guides/integrating-actual-css.md +186 -0
- package/docs/pages/guides/introduction.md +23 -26
- package/docs/pages/guides/modular-import.md +173 -0
- package/docs/pages/guides/progressive-enhancement.md +81 -78
- package/docs/pages/guides/tailwind.md +6 -8
- package/docs/pages/layout/app-shell.md +1 -1
- package/docs/pages/layout/center.md +1 -1
- package/docs/pages/layout/choosing.md +39 -0
- package/docs/pages/layout/cluster.md +4 -2
- package/docs/pages/layout/column-layout.md +206 -0
- package/docs/pages/layout/frame.md +6 -1
- package/docs/pages/layout/grid.md +100 -102
- package/docs/pages/layout/list.md +1 -1
- package/docs/pages/layout/media.md +10 -3
- package/docs/pages/layout/overview.md +15 -2
- package/docs/pages/layout/scroll-snap.md +2 -2
- package/docs/pages/layout/scroll-target.md +45 -0
- package/docs/pages/layout/sidebar-layout.md +20 -19
- package/docs/pages/layout/stack.md +90 -1
- package/docs/pages/layout/switcher.md +10 -26
- package/docs/pages/layout/topbar.md +2 -2
- package/docs/pages/patterns/actions.md +1 -1
- package/docs/pages/patterns/code-block.md +1 -1
- package/docs/pages/patterns/exclusive-accordion.md +1 -1
- package/docs/pages/patterns/hierarchical-nav.md +1 -1
- package/docs/pages/patterns/icon-input.md +1 -1
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/patterns/navigation-list.md +1 -1
- package/docs/pages/patterns/overview.md +1 -1
- package/docs/pages/patterns/structured-lists.md +1 -1
- package/docs/pages/utilities/accessibility.md +3 -3
- package/docs/pages/utilities/flex.md +15 -15
- package/docs/pages/utilities/links.md +4 -4
- package/docs/pages/utilities/list.md +8 -7
- package/docs/pages/utilities/overflow.md +8 -8
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/shape.md +3 -3
- package/docs/pages/utilities/sizing-wrapping.md +23 -16
- package/docs/pages/utilities/spacing.md +20 -20
- package/docs/pages/utilities/text.md +10 -10
- package/llms.txt +46 -4
- package/package.json +43 -13
- package/scripts/reserved-classes.json +261 -0
- package/src/cli/actual-css.js +133 -0
- package/src/css/actual.css +1 -28
- package/src/css/actual.full.css +11 -2
- package/src/css/components/alert.css +33 -10
- package/src/css/components/badge.css +38 -19
- package/src/css/components/breadcrumb.css +1 -1
- package/src/css/components/button.css +35 -13
- package/src/css/components/card.css +37 -4
- package/src/css/components/drawer.css +27 -2
- package/src/css/components/flyout.css +5 -0
- package/src/css/components/index.css +32 -27
- package/src/css/components/join.css +8 -1
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +11 -1
- package/src/css/components/meter.css +4 -1
- package/src/css/components/modal.css +5 -0
- package/src/css/components/navbar.css +13 -5
- package/src/css/components/pagination.css +11 -0
- package/src/css/components/spinner.css +5 -0
- package/src/css/components/status-bar.css +5 -0
- package/src/css/components/surface.css +5 -0
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +5 -0
- package/src/css/core/index.css +9 -0
- package/src/css/core/print.css +18 -0
- package/src/css/{theme.css → core/theme.css} +20 -10
- package/src/css/{tokens.css → core/tokens.css} +15 -2
- package/src/css/{variants.css → core/variants.css} +27 -13
- package/src/css/effects/index.css +2 -0
- package/src/css/forms/base.css +8 -0
- package/src/css/forms/choice-card.css +5 -4
- package/src/css/forms/choice.css +49 -37
- package/src/css/forms/control.css +6 -1
- package/src/css/forms/custom-select.css +2 -0
- package/src/css/{optional → forms}/floating-field.css +25 -7
- package/src/css/forms/index.css +10 -13
- package/src/css/forms/native.css +15 -2
- package/src/css/{optional → forms}/otp.css +20 -16
- package/src/css/forms/switch.css +31 -29
- package/src/css/layout/app-shell.css +6 -0
- package/src/css/layout/center.css +13 -0
- package/src/css/layout/cluster.css +21 -0
- package/src/css/layout/column-layout.css +144 -0
- package/src/css/layout/container-query.css +13 -0
- package/src/css/layout/frame.css +19 -0
- package/src/css/layout/grid.css +141 -0
- package/src/css/layout/index.css +42 -0
- package/src/css/layout/measure.css +9 -0
- package/src/css/layout/media.css +10 -0
- package/src/css/layout/scroll-target.css +15 -0
- package/src/css/layout/scroller.css +41 -0
- package/src/css/layout/sidebar-layout.css +31 -0
- package/src/css/layout/stack.css +21 -0
- package/src/css/layout/switcher.css +23 -0
- package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
- package/src/css/typography/index.css +5 -0
- package/src/css/typography/lead.css +7 -0
- package/src/css/{components → typography}/overline.css +10 -4
- package/src/css/{prose.css → typography/prose.css} +53 -38
- package/src/css/{utilities.css → utilities/base.css} +2 -0
- package/src/css/utilities/index.css +3 -0
- package/src/js/escape.js +36 -0
- package/src/js/flyout.js +22 -12
- package/src/js/full.js +26 -0
- package/src/js/index.js +5 -21
- package/src/js/surface.js +4 -32
- package/src/js/tooltip.js +65 -24
- package/src/js/validation.js +1 -1
- package/src/tooling/css-bundle.js +308 -0
- package/dist/actual-themes.min.css +0 -1
- package/dist/optional.css +0 -1048
- package/dist/optional.min.css +0 -1
- package/docs/pages/guides/optional-components.md +0 -33
- package/src/css/grid.css +0 -96
- package/src/css/layout.css +0 -108
- package/src/css/optional/index.css +0 -10
- package/src/css/optional/scroller.css +0 -63
- package/src/css/print.css +0 -47
- package/src/css/themes/corporate.css +0 -60
- package/src/css/themes/cyberpunk.css +0 -60
- package/src/css/themes/dim.css +0 -33
- package/src/css/themes/edge.css +0 -117
- package/src/css/themes/forest.css +0 -65
- package/src/css/themes/gradient.css +0 -97
- package/src/css/themes/index.css +0 -43
- package/src/css/themes/indigo.css +0 -33
- package/src/css/themes/lavender.css +0 -66
- package/src/css/themes/mono.css +0 -60
- package/src/css/themes/neon.css +0 -66
- package/src/css/themes/ocean.css +0 -60
- package/src/css/themes/petrol.css +0 -64
- package/src/css/themes/spruce.css +0 -64
- package/src/css/themes/square.css +0 -65
- package/src/css/themes/sunset.css +0 -66
- /package/src/css/{optional → components}/chat.css +0 -0
- /package/src/css/{optional → components}/fab.css +0 -0
- /package/src/css/{base.css → core/base.css} +0 -0
- /package/src/css/{focus.css → core/focus.css} +0 -0
- /package/src/css/{intents.css → core/intents.css} +0 -0
- /package/src/css/{reset.css → core/reset.css} +0 -0
- /package/src/css/{optional → effects}/aura.css +0 -0
- /package/src/css/{optional → layout}/scroll-snap.css +0 -0
- /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
- /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
|
|
2
|
+
Non-fluid and always available — use .text-lead from the optional fluid
|
|
3
|
+
module for marketing/display surfaces. */
|
|
4
|
+
.lead {
|
|
5
|
+
font-size: 1.125em;
|
|
6
|
+
line-height: var(--line-height-relaxed);
|
|
7
|
+
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Base form is unstyled chrome that takes its surrounding color. Add .pill for
|
|
5
5
|
* the bordered rounded shape, and combine with an intent class (.primary,
|
|
6
6
|
* .success, etc.) to tint the text, border, and background.
|
|
7
|
+
*
|
|
8
|
+
* Public hooks:
|
|
9
|
+
* --overline-radius
|
|
7
10
|
*/
|
|
8
11
|
/* Intent boundary — inherited intents do not tint this component.
|
|
9
12
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -12,7 +15,6 @@
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
.overline {
|
|
15
|
-
align-self: flex-start;
|
|
16
18
|
color: var(--text-muted);
|
|
17
19
|
font-size: var(--font-size-sm);
|
|
18
20
|
font-weight: var(--font-weight-medium);
|
|
@@ -21,14 +23,18 @@
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
.overline.pill {
|
|
26
|
+
--overline-radius: var(--radius-full);
|
|
27
|
+
|
|
24
28
|
display: inline-flex;
|
|
25
29
|
align-items: center;
|
|
26
30
|
gap: 0.5em;
|
|
27
31
|
padding-inline: 0.6em;
|
|
28
32
|
padding-block: 0.25em;
|
|
33
|
+
inline-size: fit-content;
|
|
34
|
+
max-inline-size: 100%;
|
|
29
35
|
color: var(--intent, var(--text-muted));
|
|
30
36
|
border: var(--border-width) solid var(--border);
|
|
31
|
-
border-radius: var(--radius
|
|
37
|
+
border-radius: var(--overline-radius);
|
|
32
38
|
background: var(--surface);
|
|
33
39
|
}
|
|
34
40
|
|
|
@@ -36,7 +42,7 @@
|
|
|
36
42
|
keeps a readable bordered pill everywhere else. */
|
|
37
43
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
38
44
|
.overline.pill {
|
|
39
|
-
border-color: color-mix(in
|
|
40
|
-
background: color-mix(in
|
|
45
|
+
border-color: color-mix(in oklab, var(--intent, var(--border)) 35%, var(--border));
|
|
46
|
+
background: color-mix(in oklab, var(--intent, transparent) 6%, var(--surface));
|
|
41
47
|
}
|
|
42
48
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* --prose-h2
|
|
15
15
|
* --prose-h3
|
|
16
16
|
* --prose-h4
|
|
17
|
+
* --link
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
.prose {
|
|
@@ -29,19 +30,23 @@
|
|
|
29
30
|
--prose-h4: 1.125rem;
|
|
30
31
|
|
|
31
32
|
max-inline-size: var(--prose-measure);
|
|
32
|
-
color: var(--text);
|
|
33
33
|
font-size: var(--prose-size);
|
|
34
34
|
line-height: var(--prose-line-height);
|
|
35
35
|
overflow-wrap: break-word;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/* Vertical rhythm belongs to the direct block that participates in the
|
|
39
|
+
document, so it must not be injected into the internals of a nested
|
|
40
|
+
component (e.g. a .table-wrap around a .table would trap the margin). */
|
|
41
|
+
.prose > * + * {
|
|
42
|
+
margin-block-start: var(--prose-flow);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
/* Heading hierarchy remains low-specificity and contextual: a surface may
|
|
46
|
+
remap --heading without prose repainting it with the root text color. */
|
|
42
47
|
.prose :where(h1, h2, h3, h4, h5, h6) {
|
|
43
48
|
margin-block: 1.5em 0;
|
|
44
|
-
color: var(--text);
|
|
49
|
+
color: var(--heading, var(--text));
|
|
45
50
|
font-weight: var(--font-weight-strong);
|
|
46
51
|
line-height: var(--prose-heading-line-height);
|
|
47
52
|
}
|
|
@@ -54,48 +59,71 @@
|
|
|
54
59
|
margin-block-start: 0;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
.prose
|
|
62
|
+
/* Element recipes are wrapped in :where() so every .prose rule is 0-1-0.
|
|
63
|
+
An explicit component/util class can therefore override prose defaults
|
|
64
|
+
regardless of the underlying element — a .btn or .alert link must not be
|
|
65
|
+
reshaped just because it lives inside authored content. */
|
|
66
|
+
.prose :where(h1) {
|
|
58
67
|
font-size: var(--prose-h1);
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
.prose h2 {
|
|
70
|
+
.prose :where(h2) {
|
|
62
71
|
font-size: var(--prose-h2);
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
.prose h3 {
|
|
74
|
+
.prose :where(h3) {
|
|
66
75
|
font-size: var(--prose-h3);
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
.prose h4 {
|
|
78
|
+
.prose :where(h4) {
|
|
70
79
|
font-size: var(--prose-h4);
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
.
|
|
74
|
-
|
|
82
|
+
/* Link ink defaults to --primary. --link is an optional escape hatch: a
|
|
83
|
+
theme whose primary is too light or too vivid to read as inline text sets
|
|
84
|
+
it without having to restate the rule. */
|
|
85
|
+
.prose :where(a) {
|
|
86
|
+
color: var(--link, var(--primary));
|
|
75
87
|
text-decoration-line: underline;
|
|
76
88
|
text-decoration-thickness: 0.08em;
|
|
77
89
|
text-underline-offset: 0.18em;
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
.prose a:hover {
|
|
92
|
+
.prose :where(a:hover) {
|
|
81
93
|
text-decoration-thickness: 0.12em;
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
85
|
-
.prose a:hover {
|
|
86
|
-
color: color-mix(in
|
|
97
|
+
.prose :where(a:hover) {
|
|
98
|
+
color: color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%);
|
|
87
99
|
}
|
|
88
100
|
}
|
|
89
101
|
|
|
102
|
+
/* Rich-text descendants are intentional here. Components and application UI
|
|
103
|
+
belong outside the prose boundary. */
|
|
90
104
|
.prose :where(ul, ol) {
|
|
91
105
|
padding-inline-start: 1.5em;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
.prose li + li {
|
|
108
|
+
.prose :where(li + li) {
|
|
95
109
|
margin-block-start: 0.35em;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
/* Description lists keep document flow: a valid <dl> may pair several <dt>
|
|
113
|
+
with one <dd> or the reverse, so no grid layout is imposed here. */
|
|
114
|
+
.prose :where(dt) {
|
|
115
|
+
font-weight: var(--font-weight-strong);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.prose :where(dd) {
|
|
119
|
+
margin-inline-start: 1.5em;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.prose :where(dd + dt) {
|
|
123
|
+
margin-block-start: 0.75em;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.prose :where(code) {
|
|
99
127
|
padding: 0.15em 0.3em;
|
|
100
128
|
border-radius: var(--radius-sm);
|
|
101
129
|
background: var(--surface-subtle);
|
|
@@ -104,7 +132,8 @@
|
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
/* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
|
|
107
|
-
|
|
135
|
+
/* @sync key-recipe */
|
|
136
|
+
.prose :where(kbd) {
|
|
108
137
|
display: inline-flex;
|
|
109
138
|
align-items: center;
|
|
110
139
|
justify-content: center;
|
|
@@ -123,8 +152,9 @@
|
|
|
123
152
|
white-space: nowrap;
|
|
124
153
|
vertical-align: 0.08em;
|
|
125
154
|
}
|
|
155
|
+
/* @sync key-recipe */
|
|
126
156
|
|
|
127
|
-
.prose pre {
|
|
157
|
+
.prose :where(pre) {
|
|
128
158
|
overflow-x: auto;
|
|
129
159
|
padding: 1em;
|
|
130
160
|
border: var(--border-width) solid var(--border);
|
|
@@ -132,53 +162,38 @@
|
|
|
132
162
|
background: var(--surface-subtle);
|
|
133
163
|
}
|
|
134
164
|
|
|
135
|
-
.prose pre code {
|
|
165
|
+
.prose :where(pre) :where(code) {
|
|
136
166
|
padding: 0;
|
|
137
167
|
background: transparent;
|
|
138
168
|
}
|
|
139
169
|
|
|
140
|
-
.prose blockquote {
|
|
170
|
+
.prose :where(blockquote) {
|
|
141
171
|
padding-inline-start: 1em;
|
|
142
172
|
border-inline-start: var(--border-width) solid var(--border);
|
|
143
173
|
color: var(--text-muted);
|
|
144
174
|
}
|
|
145
175
|
|
|
146
|
-
.prose figure {
|
|
176
|
+
.prose :where(figure) {
|
|
147
177
|
margin-inline: 0;
|
|
148
178
|
}
|
|
149
179
|
|
|
150
|
-
.prose figcaption {
|
|
180
|
+
.prose :where(figcaption) {
|
|
151
181
|
margin-block-start: 0.5em;
|
|
152
182
|
color: var(--text-muted);
|
|
153
183
|
font-size: 0.875em;
|
|
154
184
|
}
|
|
155
185
|
|
|
156
|
-
.prose table {
|
|
186
|
+
.prose :where(table) {
|
|
157
187
|
inline-size: 100%;
|
|
158
188
|
border-collapse: collapse;
|
|
159
189
|
}
|
|
160
190
|
|
|
161
|
-
.prose th,
|
|
162
|
-
.prose td {
|
|
191
|
+
.prose :where(th, td) {
|
|
163
192
|
padding: 0.5em;
|
|
164
193
|
border-block-end: var(--border-width) solid var(--border);
|
|
165
194
|
text-align: start;
|
|
166
195
|
}
|
|
167
196
|
|
|
168
|
-
/* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
|
|
169
|
-
Non-fluid and always available — use .text-lead from the optional fluid
|
|
170
|
-
module for marketing/display surfaces. */
|
|
171
|
-
.lead {
|
|
172
|
-
font-size: 1.125em;
|
|
173
|
-
line-height: var(--line-height-relaxed);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/* Readable content measure. Composes with .center for centering or with
|
|
177
|
-
margin-inline: auto for a one-off centered block. */
|
|
178
|
-
.measure {
|
|
179
|
-
max-inline-size: var(--prose-measure, 70ch);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
197
|
.prose.sm {
|
|
183
198
|
--prose-size: 0.9375rem;
|
|
184
199
|
--prose-h1: 1.75rem;
|
package/src/js/escape.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Per-document LIFO dismissal for transient UI that closes on Escape.
|
|
3
|
+
* Consumers register only while visible, so the topmost dismissable UI owns
|
|
4
|
+
* one key press and pinned or hidden UI never consumes it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const dismissableStacks = new WeakMap();
|
|
8
|
+
|
|
9
|
+
export function registerEscapeDismissal(element, dismiss) {
|
|
10
|
+
const doc = element.ownerDocument;
|
|
11
|
+
let stack = dismissableStacks.get(doc);
|
|
12
|
+
if (!stack) {
|
|
13
|
+
stack = [];
|
|
14
|
+
dismissableStacks.set(doc, stack);
|
|
15
|
+
doc.addEventListener("keydown", onDocumentEscape);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entry = { element, dismiss };
|
|
19
|
+
stack.push(entry);
|
|
20
|
+
|
|
21
|
+
return () => {
|
|
22
|
+
const index = stack.indexOf(entry);
|
|
23
|
+
if (index >= 0) stack.splice(index, 1);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onDocumentEscape(event) {
|
|
28
|
+
if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
29
|
+
|
|
30
|
+
const stack = dismissableStacks.get(event.currentTarget);
|
|
31
|
+
const entry = stack.at(-1);
|
|
32
|
+
if (!entry) return;
|
|
33
|
+
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
entry.dismiss({ restoreFocus: true });
|
|
36
|
+
}
|
package/src/js/flyout.js
CHANGED
|
@@ -144,10 +144,29 @@ function connectTrigger(trigger) {
|
|
|
144
144
|
trigger.addEventListener("keydown", onTriggerKeydown, { signal: controller.signal });
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
function
|
|
148
|
-
|
|
147
|
+
function releasePanelReference(trigger, state) {
|
|
148
|
+
state.releaseMenu?.();
|
|
149
|
+
state.releaseMenu = null;
|
|
150
|
+
|
|
151
|
+
const panel = state.panel;
|
|
152
|
+
state.panel = null;
|
|
153
|
+
if (!panel) return;
|
|
154
|
+
|
|
155
|
+
const panelEntry = panelRefs.get(panel);
|
|
156
|
+
if (!panelEntry) return;
|
|
157
|
+
|
|
158
|
+
panelEntry.triggers.delete(trigger);
|
|
159
|
+
if (panelEntry.triggers.size === 0) {
|
|
160
|
+
panelEntry.release();
|
|
161
|
+
panelRefs.delete(panel);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
149
164
|
|
|
165
|
+
function resolvePanel(trigger, state) {
|
|
150
166
|
const panel = panelFor(trigger);
|
|
167
|
+
if (state.panel === panel && panel?.isConnected) return panel;
|
|
168
|
+
|
|
169
|
+
releasePanelReference(trigger, state);
|
|
151
170
|
if (!panel) return null;
|
|
152
171
|
|
|
153
172
|
let panelEntry = panelRefs.get(panel);
|
|
@@ -174,21 +193,12 @@ function resolvePanel(trigger, state) {
|
|
|
174
193
|
function disconnectTrigger(trigger) {
|
|
175
194
|
const state = triggerMap.get(trigger);
|
|
176
195
|
if (!state) return;
|
|
177
|
-
state.releaseMenu?.();
|
|
178
196
|
state.controller.abort();
|
|
179
197
|
if (state.panel?.isConnected && isSurfaceOpen(state.panel)) {
|
|
180
198
|
closeSurface(state.panel, { restoreFocus: false });
|
|
181
199
|
}
|
|
200
|
+
releasePanelReference(trigger, state);
|
|
182
201
|
triggerMap.delete(trigger);
|
|
183
|
-
|
|
184
|
-
const panelEntry = state.panel ? panelRefs.get(state.panel) : undefined;
|
|
185
|
-
if (panelEntry) {
|
|
186
|
-
panelEntry.triggers.delete(trigger);
|
|
187
|
-
if (panelEntry.triggers.size === 0) {
|
|
188
|
-
panelEntry.release();
|
|
189
|
-
panelRefs.delete(state.panel);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
202
|
}
|
|
193
203
|
|
|
194
204
|
registerEnhancement("flyout", (trigger) => {
|
package/src/js/full.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Actual CSS - JavaScript runtime, full bundle entry.
|
|
3
|
+
*
|
|
4
|
+
* Each module self-registers via observer: importing this bundle enables
|
|
5
|
+
* common behaviors. No DOMContentLoaded ceremony, no init calls.
|
|
6
|
+
* Injected content wires automatically; removal cleans up automatically.
|
|
7
|
+
*
|
|
8
|
+
* A plain `<script src="actual.full.js"></script>` (no bundler, no import) still
|
|
9
|
+
* gets the full runtime, including declarative triggers like status's
|
|
10
|
+
* `command="--status"` and the `actual:status` event — no global needed to
|
|
11
|
+
* drive them from other inline scripts either.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import "./flyout.js";
|
|
15
|
+
import "./context-menu.js";
|
|
16
|
+
import "./dialog.js";
|
|
17
|
+
import "./dismiss.js";
|
|
18
|
+
import "./tab.js";
|
|
19
|
+
import "./tooltip.js";
|
|
20
|
+
import "./scrollspy.js";
|
|
21
|
+
import "./filter.js";
|
|
22
|
+
import "./mask.js";
|
|
23
|
+
import "./password.js";
|
|
24
|
+
import "./validation.js";
|
|
25
|
+
import "./status.js";
|
|
26
|
+
import "./index.js";
|
package/src/js/index.js
CHANGED
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Actual CSS - JavaScript runtime.
|
|
2
|
+
* Actual CSS - JavaScript runtime, core entry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* A plain `<script src="actual.js"></script>` (no bundler, no import) still
|
|
9
|
-
* gets the full runtime, including declarative triggers like status's
|
|
10
|
-
* `command="--status"` and the `actual:status` event — no global needed to
|
|
11
|
-
* drive them from other inline scripts either.
|
|
4
|
+
* This entry only starts the enhancement manifest loader. Built-in behaviors
|
|
5
|
+
* (dialogs, menus, tabs, validation, ...) ship in the full bundle entry
|
|
6
|
+
* (src/js/full.js). Use this module for pages that only need progressive
|
|
7
|
+
* enhancement via enhancement manifests.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
import "./flyout.js";
|
|
15
|
-
import "./context-menu.js";
|
|
16
|
-
import "./dialog.js";
|
|
17
|
-
import "./dismiss.js";
|
|
18
|
-
import "./tab.js";
|
|
19
|
-
import "./tooltip.js";
|
|
20
|
-
import "./scrollspy.js";
|
|
21
|
-
import "./filter.js";
|
|
22
|
-
import "./mask.js";
|
|
23
|
-
import "./password.js";
|
|
24
|
-
import "./validation.js";
|
|
25
|
-
import "./status.js";
|
|
26
10
|
import { watchEnhancementManifests } from "./enhancement-loader.js";
|
|
27
11
|
|
|
28
12
|
watchEnhancementManifests();
|
package/src/js/surface.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import enhance from "./enhance.js";
|
|
2
|
+
import { registerEscapeDismissal } from "./escape.js";
|
|
2
3
|
import { EVENTS } from "./events.js";
|
|
3
4
|
import { autoUpdate, reposition, repositionAt } from "./floating.js";
|
|
4
5
|
|
|
@@ -274,7 +275,9 @@ export function retainSurface(panel) {
|
|
|
274
275
|
released = true;
|
|
275
276
|
entry.count--;
|
|
276
277
|
if (entry.count <= 0) {
|
|
277
|
-
|
|
278
|
+
// A released retainer may leave a mounted panel whose original parent
|
|
279
|
+
// either still needs it back or has disappeared along with the trigger.
|
|
280
|
+
disconnectSurface(panel);
|
|
278
281
|
surfaceRetainers.delete(panel);
|
|
279
282
|
}
|
|
280
283
|
};
|
|
@@ -416,37 +419,6 @@ function ensureDocumentClick(menu) {
|
|
|
416
419
|
doc.addEventListener("click", onDocumentClick);
|
|
417
420
|
}
|
|
418
421
|
|
|
419
|
-
const dismissableStacks = new WeakMap();
|
|
420
|
-
|
|
421
|
-
export function registerEscapeDismissal(element, dismiss) {
|
|
422
|
-
const doc = element.ownerDocument;
|
|
423
|
-
let stack = dismissableStacks.get(doc);
|
|
424
|
-
if (!stack) {
|
|
425
|
-
stack = [];
|
|
426
|
-
dismissableStacks.set(doc, stack);
|
|
427
|
-
doc.addEventListener("keydown", onDocumentEscape);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const entry = { element, dismiss };
|
|
431
|
-
stack.push(entry);
|
|
432
|
-
|
|
433
|
-
return () => {
|
|
434
|
-
const index = stack.indexOf(entry);
|
|
435
|
-
if (index >= 0) stack.splice(index, 1);
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function onDocumentEscape(event) {
|
|
440
|
-
if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
441
|
-
|
|
442
|
-
const stack = dismissableStacks.get(event.currentTarget);
|
|
443
|
-
const entry = stack.at(-1);
|
|
444
|
-
if (!entry) return;
|
|
445
|
-
|
|
446
|
-
event.preventDefault();
|
|
447
|
-
entry.dismiss({ restoreFocus: true });
|
|
448
|
-
}
|
|
449
|
-
|
|
450
422
|
export function getSurfaceAutoClose(menu) {
|
|
451
423
|
return surfaceMap.get(menu)?.autoClose ?? "outside";
|
|
452
424
|
}
|
package/src/js/tooltip.js
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* treated as a tooltip trigger.
|
|
15
15
|
*
|
|
16
16
|
* Sharing: an explicit tooltip may be referenced by several triggers. The
|
|
17
|
-
* tooltip element is wired once
|
|
18
|
-
*
|
|
19
|
-
* when the last trigger leaves the DOM.
|
|
17
|
+
* tooltip element is wired once; each trigger only registers
|
|
18
|
+
* itself. Positional tracking runs only while the tip is visible,
|
|
19
|
+
* and all wiring is torn down when the last trigger leaves the DOM.
|
|
20
20
|
*
|
|
21
21
|
* Lazy: tooltip elements are created (shorthand) or wired (explicit) on
|
|
22
22
|
* first mouseover / focusin. No page-load scan, no DOM overhead
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import enhance from "./enhance.js";
|
|
31
|
+
import { registerEscapeDismissal } from "./escape.js";
|
|
31
32
|
import { autoUpdate, reposition } from "./floating.js";
|
|
32
33
|
import { CLASSES } from "./selectors.js";
|
|
33
34
|
|
|
@@ -39,7 +40,9 @@ const HIDE_DELAY_MS = 100;
|
|
|
39
40
|
* @property {Set<Element>} refs Triggers that currently reference this tip.
|
|
40
41
|
* @property {Element | null} activeRef Trigger that last showed this tip.
|
|
41
42
|
* @property {AbortController} controller Tip-level event listener cleanup.
|
|
42
|
-
* @property {() => void} stopTracking Floating-position cleanup.
|
|
43
|
+
* @property {(() => void) | null} stopTracking Floating-position cleanup while visible.
|
|
44
|
+
* @property {(() => void) | null} unregisterEscape Escape-stack cleanup while visible and dismissable.
|
|
45
|
+
* @property {Element | null} escapeRef Trigger represented by the current Escape-stack entry.
|
|
43
46
|
* @property {boolean} generated True when the tip was created from data-tooltip text.
|
|
44
47
|
* @property {{ parent: Node, next: ChildNode | null } | null} mount Original DOM position for explicit tips moved into a dialog/body root.
|
|
45
48
|
* @property {ReturnType<typeof setTimeout> | null} timer Pending delayed show.
|
|
@@ -52,7 +55,7 @@ const HIDE_DELAY_MS = 100;
|
|
|
52
55
|
let uid = 0;
|
|
53
56
|
const triggerStates = new WeakMap(); // trigger -> { tip, cleanup }
|
|
54
57
|
/** @type {WeakMap<Element, TooltipState>} */
|
|
55
|
-
const tipStates = new WeakMap(); // tip -> { refs, activeRef, controller,
|
|
58
|
+
const tipStates = new WeakMap(); // tip -> { refs, activeRef, controller, stopTracking, generated, mount, timer }
|
|
56
59
|
|
|
57
60
|
function mountTip(tip, trigger) {
|
|
58
61
|
const root = trigger.closest("dialog") || trigger.ownerDocument.body;
|
|
@@ -140,6 +143,50 @@ function repositionTip(ref, tip) {
|
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
function updateTrackedTip(tip, state) {
|
|
147
|
+
const ref = state.activeRef;
|
|
148
|
+
if (!ref) return;
|
|
149
|
+
|
|
150
|
+
const alwaysVisible = isAlwaysVisible(ref);
|
|
151
|
+
// Reveal before measuring: an always-visible tooltip may currently be
|
|
152
|
+
// hidden because its trigger was outside the positioning boundary.
|
|
153
|
+
if (alwaysVisible) tip.hidden = false;
|
|
154
|
+
if (tip.hidden || repositionTip(ref, tip)) {
|
|
155
|
+
syncEscapeDismissal(tip, state);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (alwaysVisible) tip.hidden = true;
|
|
160
|
+
else hideTip(tip, true);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function startTracking(tip, state) {
|
|
164
|
+
if (state.stopTracking) return;
|
|
165
|
+
|
|
166
|
+
state.stopTracking = autoUpdate(tip, () => updateTrackedTip(tip, state));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function stopTracking(state) {
|
|
170
|
+
state?.stopTracking?.();
|
|
171
|
+
if (state) state.stopTracking = null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function syncEscapeDismissal(tip, state) {
|
|
175
|
+
const ref = state.activeRef;
|
|
176
|
+
const dismissable = !tip.hidden && !isAlwaysVisible(ref);
|
|
177
|
+
// Position tracking calls this after every successful update. Preserve the
|
|
178
|
+
// current entry so a scroll/resize tick cannot reorder the LIFO stack.
|
|
179
|
+
if (dismissable && state.unregisterEscape && state.escapeRef === ref) return;
|
|
180
|
+
|
|
181
|
+
state.unregisterEscape?.();
|
|
182
|
+
state.unregisterEscape = null;
|
|
183
|
+
state.escapeRef = null;
|
|
184
|
+
if (!dismissable) return;
|
|
185
|
+
|
|
186
|
+
state.unregisterEscape = registerEscapeDismissal(tip, () => hideTip(tip, true));
|
|
187
|
+
state.escapeRef = ref;
|
|
188
|
+
}
|
|
189
|
+
|
|
143
190
|
function hideTip(tip, force = false) {
|
|
144
191
|
const state = tipStates.get(tip);
|
|
145
192
|
if (!force && isAlwaysVisible(state?.activeRef)) return;
|
|
@@ -152,6 +199,12 @@ function hideTip(tip, force = false) {
|
|
|
152
199
|
state.hideTimer = null;
|
|
153
200
|
}
|
|
154
201
|
tip.hidden = true;
|
|
202
|
+
state?.unregisterEscape?.();
|
|
203
|
+
if (state) {
|
|
204
|
+
state.unregisterEscape = null;
|
|
205
|
+
state.escapeRef = null;
|
|
206
|
+
}
|
|
207
|
+
stopTracking(state);
|
|
155
208
|
}
|
|
156
209
|
|
|
157
210
|
function clearHide(tip) {
|
|
@@ -185,12 +238,9 @@ function wireTip(tip, options = {}) {
|
|
|
185
238
|
refs: new Set(),
|
|
186
239
|
activeRef: null,
|
|
187
240
|
controller,
|
|
188
|
-
stopTracking:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (!repositionTip(state.activeRef, tip)) hideTip(tip, true);
|
|
192
|
-
}
|
|
193
|
-
}),
|
|
241
|
+
stopTracking: null,
|
|
242
|
+
unregisterEscape: null,
|
|
243
|
+
escapeRef: null,
|
|
194
244
|
generated: options.generated === true,
|
|
195
245
|
mount: options.mount || null,
|
|
196
246
|
timer: null,
|
|
@@ -217,17 +267,6 @@ function wireTip(tip, options = {}) {
|
|
|
217
267
|
{ signal: controller.signal },
|
|
218
268
|
);
|
|
219
269
|
|
|
220
|
-
tip.ownerDocument.addEventListener(
|
|
221
|
-
"keydown",
|
|
222
|
-
(event) => {
|
|
223
|
-
if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
224
|
-
if (tip.hidden) return;
|
|
225
|
-
event.preventDefault();
|
|
226
|
-
hideTip(tip);
|
|
227
|
-
},
|
|
228
|
-
{ signal: controller.signal },
|
|
229
|
-
);
|
|
230
|
-
|
|
231
270
|
tipStates.set(tip, state);
|
|
232
271
|
return state;
|
|
233
272
|
}
|
|
@@ -308,7 +347,7 @@ function ensureTip(trigger) {
|
|
|
308
347
|
|
|
309
348
|
hideTip(tip, true);
|
|
310
349
|
state.controller.abort();
|
|
311
|
-
|
|
350
|
+
stopTracking(state);
|
|
312
351
|
tipStates.delete(tip);
|
|
313
352
|
if (state.generated) tip.remove();
|
|
314
353
|
else restoreTip(tip, state);
|
|
@@ -328,12 +367,14 @@ function show(tip, ref, immediate = false) {
|
|
|
328
367
|
if (!state) return;
|
|
329
368
|
|
|
330
369
|
state.activeRef = ref;
|
|
370
|
+
syncEscapeDismissal(tip, state);
|
|
331
371
|
clearHide(tip);
|
|
332
372
|
if (state.timer) clearTimeout(state.timer);
|
|
333
373
|
const reveal = () => {
|
|
334
374
|
state.timer = null;
|
|
335
375
|
tip.hidden = false;
|
|
336
|
-
|
|
376
|
+
startTracking(tip, state);
|
|
377
|
+
updateTrackedTip(tip, state);
|
|
337
378
|
};
|
|
338
379
|
|
|
339
380
|
if (immediate) reveal();
|
package/src/js/validation.js
CHANGED
|
@@ -391,7 +391,7 @@ export class FormValidator {
|
|
|
391
391
|
static setErrors(form, errors) {
|
|
392
392
|
if (!isFormElement(form)) return;
|
|
393
393
|
for (const [name, message] of Object.entries(errors)) {
|
|
394
|
-
const found = form.elements
|
|
394
|
+
const found = form.elements.namedItem(name);
|
|
395
395
|
if (!found) continue;
|
|
396
396
|
if (found instanceof Element) {
|
|
397
397
|
FormValidator.setFieldError(found, message);
|