actual-css 0.4.1 → 0.5.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 +54 -0
- package/README.md +2 -2
- package/dist/actual.css +53 -14
- package/dist/actual.full.css +300 -91
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/spacing-units.md +286 -0
- package/docs/navigation.json +1 -0
- package/docs/pages/components/alert.md +1 -1
- package/docs/pages/components/app-navigation.md +2 -0
- package/docs/pages/components/badge.md +17 -3
- package/docs/pages/components/button.md +9 -2
- package/docs/pages/components/card.md +13 -0
- package/docs/pages/components/data-list.md +72 -0
- package/docs/pages/components/dialog.md +2 -0
- package/docs/pages/components/drawer.md +2 -0
- package/docs/pages/components/fab.md +2 -0
- package/docs/pages/components/meter.md +56 -1
- package/docs/pages/components/progress.md +10 -0
- package/docs/pages/components/skeleton.md +2 -0
- package/docs/pages/components/steps.md +10 -2
- package/docs/pages/components/table.md +1 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/status-bar.md +10 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/joined-controls.md +2 -0
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/range.md +2 -0
- package/docs/pages/forms/states-and-sizes.md +1 -1
- package/docs/pages/forms/validation.md +2 -0
- package/docs/pages/foundations/philosophy.md +11 -1
- package/docs/pages/foundations/tokens.md +7 -3
- package/docs/pages/foundations/typography.md +10 -2
- package/docs/pages/guides/introduction.md +1 -1
- package/docs/pages/layout/scroll-snap.md +2 -0
- package/docs/pages/layout/sidebar-layout.md +2 -0
- package/docs/pages/layout/topbar.md +2 -0
- package/docs/pages/utilities/accessibility.md +18 -0
- package/package.json +1 -1
- package/scripts/reserved-classes.json +4 -0
- package/src/css/components/actions.css +1 -0
- package/src/css/components/breadcrumb.css +1 -0
- package/src/css/components/data-list.css +27 -0
- package/src/css/components/drawer.css +30 -11
- package/src/css/components/flyout.css +17 -12
- package/src/css/components/index.css +1 -0
- package/src/css/components/meter.css +28 -1
- package/src/css/components/modal.css +28 -11
- package/src/css/components/navbar.css +2 -0
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/progress.css +5 -0
- package/src/css/components/status-bar.css +31 -15
- package/src/css/components/steps.css +36 -9
- package/src/css/components/surface.css +5 -3
- package/src/css/components/tab.css +1 -0
- package/src/css/components/tooltip.css +5 -8
- package/src/css/core/base.css +7 -1
- package/src/css/core/intents.css +6 -0
- package/src/css/core/tokens.css +23 -9
- package/src/css/core/variants.css +17 -4
- package/src/css/forms/form-actions.css +1 -0
- package/src/css/forms/form.css +4 -4
- package/src/css/layout/cluster.css +1 -0
- package/src/css/layout/scroll-snap.css +1 -0
- package/src/css/layout/sidebar-layout.css +1 -0
- package/src/css/layout/switcher.css +1 -0
- package/src/css/layout/topbar.css +1 -0
- package/src/css/typography/prose.css +2 -3
- package/src/css/utilities/base.css +16 -0
- package/src/js/selectors.js +1 -0
- package/src/js/status.js +88 -7
- package/src/js/surface.js +2 -11
- package/src/js/transition.js +20 -0
package/src/js/status.js
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
* Status — singleton floating status bar.
|
|
3
3
|
*
|
|
4
4
|
* A status bar is a transient, non-critical feedback area. Keep one element in
|
|
5
|
-
* the HTML, empty by default; JavaScript
|
|
6
|
-
*
|
|
5
|
+
* the HTML, empty by default; JavaScript writes its text content and toggles
|
|
6
|
+
* the shared `.is-open` state class, which is the visual state the CSS
|
|
7
|
+
* animates. The element itself is never hidden, so it stays a live region.
|
|
8
|
+
*
|
|
9
|
+
* Showing and clearing are asymmetric on purpose. A show writes content first
|
|
10
|
+
* and opens after; a clear only closes, and empties content and intent once the
|
|
11
|
+
* exit transition has finished, so the bar leaves in the state it was shown in
|
|
12
|
+
* rather than collapsing to a neutral empty pill mid-animation. An empty
|
|
13
|
+
* message is a clear, not a blank pill.
|
|
7
14
|
*
|
|
8
15
|
* Importing the module auto-wires it to Actual's validation: a form that fails
|
|
9
16
|
* to submit shows its data-validation-message in the status bar. The bar is a
|
|
@@ -39,17 +46,62 @@
|
|
|
39
46
|
|
|
40
47
|
import { registerCommands, targetFor } from "./command.js";
|
|
41
48
|
import { EVENTS } from "./events.js";
|
|
49
|
+
import { CLASSES } from "./selectors.js";
|
|
50
|
+
import { waitForTransitions } from "./transition.js";
|
|
42
51
|
|
|
43
52
|
const STATUS_SELECTOR = '[data-status][role="status"]';
|
|
44
53
|
const STATUS_CLASSES_ATTR = "statusClasses";
|
|
54
|
+
const VIEWPORT_OFFSET_PROPERTY = "--status-viewport-offset";
|
|
45
55
|
|
|
46
56
|
let statusTimer;
|
|
57
|
+
// Bumped by every show and every clear. A deferred exit cleanup only runs when
|
|
58
|
+
// its own generation is still current, so a message that arrives mid-exit is
|
|
59
|
+
// never emptied by the previous message's cleanup.
|
|
60
|
+
let statusGeneration = 0;
|
|
61
|
+
let viewportTracker = null;
|
|
47
62
|
|
|
48
63
|
function statusTarget() {
|
|
49
64
|
if (typeof document === "undefined") return null;
|
|
50
65
|
return document.querySelector(STATUS_SELECTOR);
|
|
51
66
|
}
|
|
52
67
|
|
|
68
|
+
// The bar is fixed to the layout viewport, so the mobile software keyboard
|
|
69
|
+
// covers it. visualViewport reports the occluded band; the CSS adds it to the
|
|
70
|
+
// bottom inset. Tracking runs only while a message is on screen.
|
|
71
|
+
function trackViewport(target) {
|
|
72
|
+
if (viewportTracker?.target === target) return;
|
|
73
|
+
releaseViewport();
|
|
74
|
+
|
|
75
|
+
const view = target.ownerDocument?.defaultView;
|
|
76
|
+
const viewport = view?.visualViewport;
|
|
77
|
+
if (!viewport) return;
|
|
78
|
+
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
const update = () => {
|
|
81
|
+
// Self-heal: an app that drops the bar while a message is up never reaches
|
|
82
|
+
// the clear() path, so the tracker would outlive its element.
|
|
83
|
+
if (!target.isConnected) {
|
|
84
|
+
releaseViewport();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const occluded = Math.max(0, view.innerHeight - viewport.height - viewport.offsetTop);
|
|
88
|
+
target.style.setProperty(VIEWPORT_OFFSET_PROPERTY, `${occluded}px`);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
viewport.addEventListener("resize", update, { signal: controller.signal });
|
|
92
|
+
viewport.addEventListener("scroll", update, { signal: controller.signal });
|
|
93
|
+
update();
|
|
94
|
+
|
|
95
|
+
viewportTracker = { target, stop: () => controller.abort() };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function releaseViewport() {
|
|
99
|
+
if (!viewportTracker) return;
|
|
100
|
+
viewportTracker.stop();
|
|
101
|
+
viewportTracker.target.style.removeProperty(VIEWPORT_OFFSET_PROPERTY);
|
|
102
|
+
viewportTracker = null;
|
|
103
|
+
}
|
|
104
|
+
|
|
53
105
|
function intentClasses(value) {
|
|
54
106
|
return typeof value === "string" ? value.trim().split(/\s+/).filter(Boolean) : [];
|
|
55
107
|
}
|
|
@@ -66,7 +118,17 @@ export function status(message, options = {}) {
|
|
|
66
118
|
const target = statusTarget();
|
|
67
119
|
if (!target || message == null) return;
|
|
68
120
|
|
|
121
|
+
// A live region with nothing to announce is not a message. `:empty` used to
|
|
122
|
+
// absorb this case for free; now that the open state is explicit, an empty
|
|
123
|
+
// message has to mean "close" or it would open a blank pill. This also keeps
|
|
124
|
+
// status(await response.text()) from leaving a stale message on screen.
|
|
125
|
+
if (message === "") {
|
|
126
|
+
status.clear();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
69
130
|
clearTimeout(statusTimer);
|
|
131
|
+
statusGeneration++;
|
|
70
132
|
|
|
71
133
|
clearIntentClasses(target);
|
|
72
134
|
const intent = intentClasses(options.intent);
|
|
@@ -75,7 +137,11 @@ export function status(message, options = {}) {
|
|
|
75
137
|
target.dataset[STATUS_CLASSES_ATTR] = intent.join(" ");
|
|
76
138
|
}
|
|
77
139
|
|
|
140
|
+
// Content and intent are in place before the bar opens, so it enters at its
|
|
141
|
+
// final size and color.
|
|
78
142
|
target.textContent = message;
|
|
143
|
+
target.classList.add(CLASSES.open);
|
|
144
|
+
trackViewport(target);
|
|
79
145
|
|
|
80
146
|
if (options.duration !== false) {
|
|
81
147
|
statusTimer = setTimeout(() => status.clear(), options.duration ?? 3000);
|
|
@@ -83,12 +149,26 @@ export function status(message, options = {}) {
|
|
|
83
149
|
}
|
|
84
150
|
|
|
85
151
|
status.clear = function clear() {
|
|
152
|
+
clearTimeout(statusTimer);
|
|
153
|
+
|
|
86
154
|
const target = statusTarget();
|
|
87
|
-
if (target) {
|
|
155
|
+
if (!target) {
|
|
156
|
+
// No bar to animate out — but one may have been removed while open, and its
|
|
157
|
+
// viewport listeners and element reference still have to go.
|
|
158
|
+
releaseViewport();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const generation = ++statusGeneration;
|
|
163
|
+
target.classList.remove(CLASSES.open);
|
|
164
|
+
|
|
165
|
+
waitForTransitions(target).then(() => {
|
|
166
|
+
// A show during the exit owns the bar now; leave its message alone.
|
|
167
|
+
if (generation !== statusGeneration) return;
|
|
88
168
|
target.textContent = "";
|
|
89
169
|
clearIntentClasses(target);
|
|
90
|
-
|
|
91
|
-
|
|
170
|
+
releaseViewport();
|
|
171
|
+
});
|
|
92
172
|
};
|
|
93
173
|
|
|
94
174
|
const STATUS_SHOW_COMMANDS = ["--status"];
|
|
@@ -125,8 +205,9 @@ if (typeof document !== "undefined") {
|
|
|
125
205
|
// importing this module. A message shows it; omitting one clears it.
|
|
126
206
|
document.addEventListener(EVENTS.status, (event) => {
|
|
127
207
|
const { message, intent, duration } = event.detail ?? {};
|
|
128
|
-
//
|
|
129
|
-
//
|
|
208
|
+
// An absent message clears here; "" reaches status() and closes there. Both
|
|
209
|
+
// paths end in the same exit, so a detail that lost its message never
|
|
210
|
+
// strands a stale one on screen.
|
|
130
211
|
if (message != null) {
|
|
131
212
|
status(message, { intent, duration });
|
|
132
213
|
} else {
|
package/src/js/surface.js
CHANGED
|
@@ -4,6 +4,7 @@ import { EVENTS } from "./events.js";
|
|
|
4
4
|
import { autoUpdate, reposition, repositionAt } from "./floating.js";
|
|
5
5
|
|
|
6
6
|
import { CLASSES } from "./selectors.js";
|
|
7
|
+
import { waitForTransitions } from "./transition.js";
|
|
7
8
|
|
|
8
9
|
const openSurfaces = new Set();
|
|
9
10
|
const surfaceMap = new WeakMap();
|
|
@@ -46,16 +47,6 @@ function reaperFor(menu) {
|
|
|
46
47
|
return reaper;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function waitForAnimations(...elements) {
|
|
50
|
-
const animations = elements
|
|
51
|
-
.filter(Boolean)
|
|
52
|
-
.flatMap((el) =>
|
|
53
|
-
typeof el.getAnimations === "function" ? el.getAnimations({ subtree: true }) : [],
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
return Promise.allSettled(animations.map((animation) => animation.finished));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
50
|
function getSurfaceRoot(menu, anchor) {
|
|
60
51
|
return anchor?.closest("dialog") || menu.closest("dialog") || menu.ownerDocument.body;
|
|
61
52
|
}
|
|
@@ -360,7 +351,7 @@ export function closeSurface(menu, opts = {}) {
|
|
|
360
351
|
state.restoreFocusTo.focus({ preventScroll: true });
|
|
361
352
|
}
|
|
362
353
|
|
|
363
|
-
|
|
354
|
+
waitForTransitions(menu, backdrop).then(() => {
|
|
364
355
|
if (!state || state.closeId !== closeId) return;
|
|
365
356
|
state.isSheet = false;
|
|
366
357
|
menu.classList.remove(CLASSES.sheet);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CSS transition lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* getAnimations() exposes both CSS transitions and keyframe animations.
|
|
5
|
+
* Cleanup that belongs to a state transition must not wait for arbitrary
|
|
6
|
+
* author animations, which may be infinite. CSSTransition instances expose
|
|
7
|
+
* transitionProperty; using that structural contract avoids a global check
|
|
8
|
+
* and keeps component JavaScript independent from the properties CSS owns.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function waitForTransitions(...elements) {
|
|
12
|
+
const transitions = elements
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.flatMap((element) =>
|
|
15
|
+
typeof element.getAnimations === "function" ? element.getAnimations({ subtree: true }) : [],
|
|
16
|
+
)
|
|
17
|
+
.filter((animation) => typeof animation.transitionProperty === "string");
|
|
18
|
+
|
|
19
|
+
return Promise.allSettled(transitions.map((transition) => transition.finished));
|
|
20
|
+
}
|