actual-css 0.1.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 +90 -1
- package/README.md +25 -8
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1492 -931
- 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 +95 -6
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +101 -0
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +3 -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 +119 -0
- 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 +10 -4
- package/package.json +30 -16
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -9
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +162 -49
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +82 -39
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +7 -0
- package/src/css/components/button.css +82 -38
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -110
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -3
- 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/scrollspy.css +2 -2
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +10 -11
- package/src/css/components/status-bar.css +18 -28
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +20 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +16 -3
- package/src/css/forms/choice-card.css +13 -15
- package/src/css/forms/choice.css +21 -28
- package/src/css/forms/control.css +16 -26
- 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 +5 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +48 -33
- package/src/css/forms/range.css +103 -0
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -13
- 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 +19 -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 +29 -13
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +11 -37
- 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 +81 -21
- 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 +72 -53
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +76 -19
- 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 +106 -96
- 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 +89 -35
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +133 -62
- package/src/js/selectors.js +14 -31
- package/src/js/status.js +16 -14
- package/src/js/surface.js +219 -125
- package/src/js/tab.js +46 -66
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +45 -51
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -26
- 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 -426
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -415
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -937
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
package/src/js/scrollspy.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Scrollspy — navigation that highlights the active section on scroll.
|
|
3
3
|
*
|
|
4
|
-
* Nav:
|
|
4
|
+
* Nav: [data-enhance="scrollspy"] containing <a href="#section-id"> links
|
|
5
5
|
* Target: elements referenced by the href fragments
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Deterministic geometry, not IntersectionObserver: on each scroll (throttled
|
|
8
|
+
* with requestAnimationFrame) the active section is the last one whose top has
|
|
9
|
+
* crossed an activation line. An IntersectionObserver reports *which* sections
|
|
10
|
+
* intersect, not which one is current, so it has to guess whenever several
|
|
11
|
+
* share the viewport — and guesses wrong for sections shorter than the band.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* activation line = root top + offset (default 20% of the root's height)
|
|
14
|
+
* active section = last section whose top <= activation line
|
|
15
|
+
* scrolled to end = last section, whatever the line says
|
|
16
|
+
* above the first = no active section
|
|
12
17
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
18
|
+
* Optional root: data-scrollspy-root="#scroll-container" measures against a
|
|
19
|
+
* scroll container instead of the viewport.
|
|
20
|
+
* Optional offset: data-scrollspy-offset="24" | "24px" | "20%" moves the
|
|
21
|
+
* activation line. Invalid values fall back to 20%; negatives clamp to 0.
|
|
22
|
+
*
|
|
23
|
+
* Self-registers via registerEnhancement: injected navs wire automatically.
|
|
24
|
+
* Cleanup removes the scroll/resize listeners and the small MutationObserver
|
|
25
|
+
* that refreshes the link->section map as nav links are injected. New sections
|
|
26
|
+
* outside the nav subtree are not observed — call refreshScrollspy(nav) after
|
|
27
|
+
* those mutations.
|
|
28
|
+
*
|
|
29
|
+
* The .scrollspy class alone still gives :target-current behavior via CSS —
|
|
30
|
+
* a documented no-JS mode, not a fallback remark.
|
|
16
31
|
*/
|
|
17
32
|
|
|
18
|
-
import
|
|
19
|
-
|
|
33
|
+
import { registerEnhancement } from "./enhance.js";
|
|
34
|
+
|
|
35
|
+
const DEFAULT_OFFSET_RATIO = 0.2;
|
|
36
|
+
const navState = new WeakMap();
|
|
20
37
|
|
|
21
38
|
function rootFor(nav) {
|
|
22
39
|
const selector = nav.getAttribute("data-scrollspy-root");
|
|
@@ -42,80 +59,134 @@ function sectionsFor(nav) {
|
|
|
42
59
|
return sections;
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
/*
|
|
63
|
+
* Viewport-relative measurements for either scroll root. The window branch
|
|
64
|
+
* reads documentElement rather than a captured window so a nav in another
|
|
65
|
+
* document measures its own root — same reason surface.js resolves
|
|
66
|
+
* ownerDocument instead of binding at import time.
|
|
67
|
+
*/
|
|
68
|
+
function measureRoot(nav, root) {
|
|
69
|
+
if (root) {
|
|
70
|
+
return {
|
|
71
|
+
top: root.getBoundingClientRect().top,
|
|
72
|
+
height: root.clientHeight,
|
|
73
|
+
scrollTop: root.scrollTop,
|
|
74
|
+
scrollHeight: root.scrollHeight,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
46
77
|
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
// scrollingElement is the real scroll container in standards mode; fall
|
|
79
|
+
// back to documentElement (quirks mode, or older engines without it).
|
|
80
|
+
const doc = nav.ownerDocument;
|
|
81
|
+
const el = doc.scrollingElement || doc.documentElement;
|
|
82
|
+
return {
|
|
83
|
+
top: 0,
|
|
84
|
+
height: el.clientHeight,
|
|
85
|
+
scrollTop: el.scrollTop,
|
|
86
|
+
scrollHeight: el.scrollHeight,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* "24" and "24px" are pixels, "20%" is a share of the root's visible height.
|
|
92
|
+
* Percentages resolve per measurement, so a resize needs no rebuild.
|
|
93
|
+
*/
|
|
94
|
+
function activationOffset(nav, height) {
|
|
95
|
+
const raw = (nav.getAttribute("data-scrollspy-offset") ?? "").trim();
|
|
96
|
+
if (!raw) return height * DEFAULT_OFFSET_RATIO;
|
|
97
|
+
|
|
98
|
+
const value = Number.parseFloat(raw);
|
|
99
|
+
if (!Number.isFinite(value)) return height * DEFAULT_OFFSET_RATIO;
|
|
100
|
+
|
|
101
|
+
return Math.max(0, raw.endsWith("%") ? (height * value) / 100 : value);
|
|
49
102
|
}
|
|
50
103
|
|
|
51
104
|
function setupNav(nav) {
|
|
52
|
-
|
|
53
|
-
let io = null;
|
|
105
|
+
const controller = new AbortController();
|
|
54
106
|
let sections = [];
|
|
55
|
-
|
|
56
|
-
let
|
|
107
|
+
let frame = 0;
|
|
108
|
+
let current;
|
|
109
|
+
|
|
110
|
+
function activate(section) {
|
|
111
|
+
if (section === current) return;
|
|
112
|
+
current = section;
|
|
113
|
+
|
|
114
|
+
for (const entry of sections) {
|
|
115
|
+
if (entry.section === section) {
|
|
116
|
+
entry.link.setAttribute("aria-current", "location");
|
|
117
|
+
} else {
|
|
118
|
+
entry.link.removeAttribute("aria-current");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
57
122
|
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
io?.disconnect();
|
|
61
|
-
sections = sectionsFor(nav);
|
|
62
|
-
activeEntries.clear();
|
|
123
|
+
function measure() {
|
|
124
|
+
frame = 0;
|
|
63
125
|
if (!sections.length) return;
|
|
64
126
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const visible = [...activeEntries.values()].sort(
|
|
76
|
-
(a, b) => a.boundingClientRect.top - b.boundingClientRect.top,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const id = visible[0]?.target.id;
|
|
80
|
-
for (const { link } of sections) {
|
|
81
|
-
if (id && link.getAttribute("href") === `#${id}`) {
|
|
82
|
-
link.setAttribute("aria-current", "location");
|
|
83
|
-
} else {
|
|
84
|
-
link.removeAttribute("aria-current");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
root: rootFor(nav),
|
|
90
|
-
rootMargin: "-20% 0px -70% 0px",
|
|
91
|
-
threshold: 0,
|
|
92
|
-
},
|
|
93
|
-
);
|
|
127
|
+
const root = rootFor(nav);
|
|
128
|
+
const { top, height, scrollTop, scrollHeight } = measureRoot(nav, root);
|
|
129
|
+
|
|
130
|
+
// Scrolled to the end: the last section wins even when its top never
|
|
131
|
+
// crosses the line. This is the case IntersectionObserver cannot express.
|
|
132
|
+
if (scrollHeight > height && scrollTop + height >= scrollHeight - 1) {
|
|
133
|
+
activate(sections.at(-1).section);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
94
136
|
|
|
137
|
+
const line = top + activationOffset(nav, height);
|
|
138
|
+
let active = null;
|
|
95
139
|
for (const { section } of sections) {
|
|
96
|
-
|
|
140
|
+
if (section.getBoundingClientRect().top <= line) active = section;
|
|
97
141
|
}
|
|
142
|
+
|
|
143
|
+
activate(active);
|
|
98
144
|
}
|
|
99
145
|
|
|
100
|
-
function
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
queueMicrotask(rebuild);
|
|
146
|
+
function schedule(run = measure) {
|
|
147
|
+
if (frame) cancelAnimationFrame(frame);
|
|
148
|
+
frame = requestAnimationFrame(run);
|
|
104
149
|
}
|
|
105
150
|
|
|
106
|
-
|
|
151
|
+
function rebuild() {
|
|
152
|
+
sections = sectionsFor(nav);
|
|
153
|
+
current = undefined;
|
|
154
|
+
measure();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// The scroll root (custom container or the document viewport) is resolved
|
|
158
|
+
// once at connect and is stable for the lifetime of the connection; measure
|
|
159
|
+
// still re-reads the current root element so a replaced *element* under a
|
|
160
|
+
// stable container is handled, but swapping the container itself is out of
|
|
161
|
+
// contract (re-inject the nav or call refreshScrollspy after such a move).
|
|
162
|
+
const scrollTarget = rootFor(nav) ?? nav.ownerDocument.defaultView;
|
|
163
|
+
scrollTarget?.addEventListener("scroll", () => schedule(), {
|
|
164
|
+
passive: true,
|
|
165
|
+
signal: controller.signal,
|
|
166
|
+
});
|
|
167
|
+
nav.ownerDocument.defaultView?.addEventListener("resize", () => schedule(), {
|
|
168
|
+
passive: true,
|
|
169
|
+
signal: controller.signal,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Observer is scoped to the nav subtree — link injection is caught;
|
|
173
|
+
// section-only DOM mutations elsewhere need an explicit refreshScrollspy().
|
|
174
|
+
const mo = new MutationObserver(() => schedule(rebuild));
|
|
107
175
|
mo.observe(nav, { childList: true, subtree: true });
|
|
108
|
-
navState.set(nav, { rebuild
|
|
176
|
+
navState.set(nav, { rebuild });
|
|
109
177
|
|
|
110
178
|
rebuild();
|
|
111
179
|
|
|
112
180
|
return () => {
|
|
113
|
-
|
|
181
|
+
controller.abort();
|
|
182
|
+
if (frame) cancelAnimationFrame(frame);
|
|
114
183
|
mo.disconnect();
|
|
115
184
|
navState.delete(nav);
|
|
116
185
|
};
|
|
117
186
|
}
|
|
118
187
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
188
|
+
export function refreshScrollspy(nav) {
|
|
189
|
+
navState.get(nav)?.rebuild();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
registerEnhancement("scrollspy", setupNav);
|
package/src/js/selectors.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Selectors — the
|
|
3
|
-
* in one file.
|
|
2
|
+
* Selectors — the state-class vocabulary the runtime writes.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This file no longer lists discovery selectors: behavior discovery moved
|
|
5
|
+
* to data-enhance tokens in 0.2. What remains are the class names modules
|
|
6
|
+
* write as state (is-open, was-validated, …) and validation's two read-side
|
|
7
|
+
* presentation adapters (field, fieldError). To run this runtime against a
|
|
8
|
+
* different CSS framework, edit this file (or alias it in a bundler).
|
|
9
9
|
*
|
|
10
10
|
* data-* attributes and ARIA are not listed: they are each module's own
|
|
11
11
|
* behavior API, not CSS vocabulary, so they stay framework-neutral by
|
|
12
|
-
* construction and never need remapping.
|
|
13
|
-
* status.js) has no CSS class dependency at all.
|
|
12
|
+
* construction and never need remapping.
|
|
14
13
|
*/
|
|
14
|
+
|
|
15
15
|
export const CLASSES = {
|
|
16
|
-
// flyout.js, context-menu.js — opt-in class on the panel/menu element.
|
|
17
|
-
flyout: "flyout",
|
|
18
|
-
// tab.js — opt-in class on the tablist element.
|
|
19
|
-
tabs: "tabs",
|
|
20
|
-
// scrollspy.js — opt-in class on the nav element.
|
|
21
|
-
scrollspy: "scrollspy",
|
|
22
|
-
// validation.js — opt-in class on the <form>.
|
|
23
|
-
needsValidation: "needs-validation",
|
|
24
16
|
// validation.js — written on <form> after the first submit attempt.
|
|
25
17
|
wasValidated: "was-validated",
|
|
26
|
-
// validation.js — read on the element an aria-describedby id resolves to.
|
|
27
|
-
fieldError: "field-error",
|
|
28
|
-
// validation.js — ancestor container searched for aria-invalid/danger state.
|
|
29
|
-
field: "field",
|
|
30
18
|
// validation.js — intents.css vocabulary class toggled on the field
|
|
31
19
|
// container while any control inside it is invalid.
|
|
32
20
|
danger: "danger",
|
|
21
|
+
// validation.js — read-side presentation adapter: error slot among
|
|
22
|
+
// aria-describedby targets. [data-field-error] is an accepted alias (D6).
|
|
23
|
+
fieldError: "field-error",
|
|
24
|
+
// validation.js — read-side presentation adapter: ancestor container
|
|
25
|
+
// for aria-invalid/danger state. Optional — degrades to no-op.
|
|
26
|
+
field: "field",
|
|
33
27
|
// surface.js — state class on the open flyout/menu/dialog surface.
|
|
34
28
|
open: "is-open",
|
|
35
29
|
// surface.js — state class while the surface renders as a mobile sheet.
|
|
@@ -38,13 +32,6 @@ export const CLASSES = {
|
|
|
38
32
|
backdrop: "surface-backdrop",
|
|
39
33
|
// dialog.js — transient class for the "can't dismiss" shake animation.
|
|
40
34
|
static: "is-static",
|
|
41
|
-
// dialog-fallback.js — marker written on a dialog controlled by the legacy
|
|
42
|
-
// shim (browsers without native <dialog>); dialog-fallback.css keys its
|
|
43
|
-
// emulated dialog presentation on it.
|
|
44
|
-
dialogFallback: "dialog-fallback",
|
|
45
|
-
// dialog-fallback.js — state class on a shimmed dialog while it is open as
|
|
46
|
-
// a modal; carries the fixed positioning and simulated backdrop.
|
|
47
|
-
fallbackModal: "is-fallback-modal",
|
|
48
35
|
// dialog.js — written on <html> while any modal dialog is open.
|
|
49
36
|
modalOpen: "has-modal-open",
|
|
50
37
|
// dialog.js — written on <html> when the viewport had a classic scrollbar
|
|
@@ -52,8 +39,4 @@ export const CLASSES = {
|
|
|
52
39
|
hadScrollbar: "had-scrollbar",
|
|
53
40
|
// tooltip.js — class name written on a shorthand-generated tooltip element.
|
|
54
41
|
tooltip: "tooltip",
|
|
55
|
-
// status.js — required alongside [data-status]. "status" is a common
|
|
56
|
-
// domain word (order status, task status, …); the class keeps a
|
|
57
|
-
// document-wide querySelector from matching an unrelated app element.
|
|
58
|
-
statusBar: "status-bar",
|
|
59
42
|
};
|
package/src/js/status.js
CHANGED
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
* - Any other code, without importing this module: dispatch `actual:status`
|
|
25
25
|
* directly — `detail: { message, intent, duration }`, or `{}` to clear.
|
|
26
26
|
*
|
|
27
|
-
* The element contract is
|
|
28
|
-
* "status" is a generic domain word, so
|
|
29
|
-
* lookup from matching an unrelated app element that happens to
|
|
30
|
-
* `data-status`. `intent` is applied verbatim as one or more
|
|
31
|
-
* target (space-separated for more than one, e.g. "danger
|
|
32
|
-
* status.js does not know or validate any specific names —
|
|
33
|
-
* and friends are Actual's own intents.css vocabulary, not
|
|
34
|
-
* module hardcodes. Each call removes the intent classes the
|
|
35
|
-
* added (tracked in data-status-classes on the target itself),
|
|
36
|
-
* current call's, so stale intent classes never accumulate and
|
|
37
|
-
* app added independently are left alone.
|
|
27
|
+
* The element contract is `[data-status][role="status"]`. Both are required:
|
|
28
|
+
* "status" is a generic domain word, so `role="status"` keeps the
|
|
29
|
+
* document-wide lookup from matching an unrelated app element that happens to
|
|
30
|
+
* carry its own `data-status`. `intent` is applied verbatim as one or more
|
|
31
|
+
* classes on the target (space-separated for more than one, e.g. "danger
|
|
32
|
+
* uppercase"). status.js does not know or validate any specific names —
|
|
33
|
+
* .danger, .success, and friends are Actual's own intents.css vocabulary, not
|
|
34
|
+
* something this module hardcodes. Each call removes the intent classes the
|
|
35
|
+
* previous call added (tracked in data-status-classes on the target itself),
|
|
36
|
+
* then adds the current call's, so stale intent classes never accumulate and
|
|
37
|
+
* classes the app added independently are left alone.
|
|
38
38
|
*/
|
|
39
|
+
|
|
39
40
|
import { registerCommands, targetFor } from "./command.js";
|
|
40
41
|
import { EVENTS } from "./events.js";
|
|
41
|
-
import { CLASSES } from "./selectors.js";
|
|
42
42
|
|
|
43
|
-
const STATUS_SELECTOR =
|
|
43
|
+
const STATUS_SELECTOR = '[data-status][role="status"]';
|
|
44
44
|
const STATUS_CLASSES_ATTR = "statusClasses";
|
|
45
45
|
|
|
46
46
|
let statusTimer;
|
|
@@ -125,7 +125,9 @@ if (typeof document !== "undefined") {
|
|
|
125
125
|
// importing this module. A message shows it; omitting one clears it.
|
|
126
126
|
document.addEventListener(EVENTS.status, (event) => {
|
|
127
127
|
const { message, intent, duration } = event.detail ?? {};
|
|
128
|
-
|
|
128
|
+
// `message != null` keeps "" (an intentional empty message) on the show
|
|
129
|
+
// path, consistent with status() itself; an absent message clears.
|
|
130
|
+
if (message != null) {
|
|
129
131
|
status(message, { intent, duration });
|
|
130
132
|
} else {
|
|
131
133
|
status.clear();
|