actual-css 0.7.0 → 0.9.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 +217 -0
- package/README.md +13 -11
- package/components.json +668 -0
- package/dist/actual.css +166 -37
- package/dist/actual.full.css +1087 -643
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/accessibility-decisions.md +33 -0
- package/docs/design-notes/browser-support.md +62 -30
- package/docs/design-notes/column-layout.md +3 -3
- package/docs/design-notes/css-anchor.md +75 -21
- package/docs/design-notes/enhancement-contract.md +14 -6
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +6 -1
- package/docs/design-notes/platform-alignment.md +53 -7
- package/docs/design-notes/popover-manual-poc.md +105 -25
- package/docs/design-notes/surface-reparenting.md +56 -47
- package/docs/design-notes/tooltip-coordinate-space.md +132 -0
- package/docs/design-notes/widget-primitives.md +7 -4
- package/docs/pages/components/accordion.md +41 -2
- package/docs/pages/components/alert.md +85 -30
- package/docs/pages/components/app-navigation.md +6 -0
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +101 -16
- package/docs/pages/components/button.md +16 -8
- package/docs/pages/components/card.md +10 -10
- package/docs/pages/components/dialog.md +21 -6
- package/docs/pages/components/drawer.md +13 -0
- package/docs/pages/components/fab.md +4 -3
- package/docs/pages/components/list.md +42 -4
- package/docs/pages/components/navbar.md +17 -1
- package/docs/pages/components/pagination.md +2 -2
- package/docs/pages/components/skeleton.md +3 -1
- package/docs/pages/components/steps.md +19 -0
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/flyout.md +82 -38
- package/docs/pages/enhancements/overview.md +25 -0
- package/docs/pages/enhancements/status-bar.md +1 -1
- package/docs/pages/enhancements/tabs.md +45 -37
- package/docs/pages/enhancements/tooltip.md +36 -10
- package/docs/pages/examples/overview.md +38 -5
- package/docs/pages/forms/choice-cards.md +11 -6
- package/docs/pages/forms/combobox.md +1 -1
- package/docs/pages/forms/floating-field.md +4 -3
- package/docs/pages/forms/joined-controls.md +22 -0
- package/docs/pages/forms/otp.md +2 -1
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +16 -18
- package/docs/pages/forms/sticky-actions.md +2 -2
- package/docs/pages/forms/switch.md +1 -1
- package/docs/pages/foundations/philosophy.md +57 -26
- package/docs/pages/foundations/theming.md +2 -2
- package/docs/pages/foundations/tokens.md +118 -21
- package/docs/pages/foundations/typography.md +35 -14
- package/docs/pages/guides/introduction.md +11 -9
- package/docs/pages/guides/progressive-enhancement.md +115 -30
- package/docs/pages/guides/tailwind.md +3 -2
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +11 -1
- package/docs/pages/layout/scroll-snap.md +3 -3
- package/docs/pages/layout/stack.md +17 -9
- package/docs/pages/patterns/icon-input.md +4 -2
- package/docs/pages/patterns/icon-slot.md +2 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/utilities/overview.md +23 -2
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +10 -8
- package/llms.txt +2 -1
- package/package.json +159 -149
- package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
- package/src/css/components/accordion.css +42 -3
- package/src/css/components/alert.css +25 -13
- package/src/css/components/app-nav.css +18 -8
- package/src/css/components/avatar.css +13 -1
- package/src/css/components/badge.css +78 -12
- package/src/css/components/breadcrumb.css +2 -3
- package/src/css/components/button.css +9 -16
- package/src/css/components/card.css +9 -2
- package/src/css/components/chat.css +1 -0
- package/src/css/components/data-list.css +1 -1
- package/src/css/components/drawer.css +101 -32
- package/src/css/components/fab.css +0 -6
- package/src/css/components/flyout.css +35 -98
- package/src/css/components/index.css +0 -1
- package/src/css/components/indicator.css +21 -0
- package/src/css/components/key.css +10 -0
- package/src/css/components/list.css +40 -4
- package/src/css/components/menu.css +2 -1
- package/src/css/components/modal.css +48 -33
- package/src/css/components/navbar.css +37 -4
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/skeleton.css +15 -4
- package/src/css/components/spinner.css +6 -6
- package/src/css/components/status-bar.css +0 -5
- package/src/css/components/steps.css +45 -59
- package/src/css/components/tab.css +29 -9
- package/src/css/components/table.css +13 -5
- package/src/css/components/tooltip.css +36 -40
- package/src/css/core/base.css +23 -1
- package/src/css/core/intents.css +17 -6
- package/src/css/core/theme.css +27 -0
- package/src/css/core/tokens.css +35 -15
- package/src/css/core/variants.css +64 -15
- package/src/css/forms/choice-card.css +4 -4
- package/src/css/forms/choice.css +76 -29
- package/src/css/forms/control.css +2 -1
- package/src/css/forms/floating-field.css +3 -3
- package/src/css/forms/form.css +4 -3
- package/src/css/forms/input-icon.css +20 -14
- package/src/css/forms/native.css +6 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +2 -0
- package/src/css/forms/select.css +29 -9
- package/src/css/forms/switch.css +28 -8
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/grid.css +50 -76
- package/src/css/layout/scroll-snap.css +11 -7
- package/src/css/layout/scroller.css +3 -4
- package/src/css/layout/stack.css +2 -2
- package/src/css/typography/fluid.css +4 -3
- package/src/css/typography/prose.css +6 -5
- package/src/css/utilities/base.css +2 -1
- package/src/css/utilities/extra.css +20 -17
- package/src/js/context-menu.js +27 -26
- package/src/js/dialog.js +2 -1
- package/src/js/dismiss.js +5 -0
- package/src/js/enhance.js +34 -2
- package/src/js/enhancement-loader.js +36 -43
- package/src/js/events.js +0 -3
- package/src/js/filter.js +0 -1
- package/src/js/floating-space.js +42 -0
- package/src/js/flyout.js +9 -44
- package/src/js/focus-group.js +7 -0
- package/src/js/focus.js +6 -0
- package/src/js/menu.js +10 -1
- package/src/js/selectors.js +5 -9
- package/src/js/status.js +6 -5
- package/src/js/surface.js +97 -139
- package/src/js/tab.js +6 -1
- package/src/js/tooltip.js +150 -51
- package/src/js/validation.js +1 -1
- package/src/css/components/surface.css +0 -49
|
@@ -29,51 +29,45 @@ const styleLoads = new Map();
|
|
|
29
29
|
|
|
30
30
|
let moduleImporter = (url) => import(url);
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
function loadAsset(url, loads, kind) {
|
|
33
33
|
const absoluteUrl = new URL(url, document.baseURI).href;
|
|
34
34
|
|
|
35
|
-
if (!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
35
|
+
if (!loads.has(absoluteUrl)) {
|
|
36
|
+
const pending = new Promise((resolve, reject) => {
|
|
37
|
+
const element = document.createElement(kind === "script" ? "script" : "link");
|
|
38
|
+
if (kind === "script") {
|
|
39
|
+
element.src = absoluteUrl;
|
|
40
|
+
} else {
|
|
41
|
+
element.rel = "stylesheet";
|
|
42
|
+
element.href = absoluteUrl;
|
|
43
|
+
}
|
|
44
|
+
element.onload = resolve;
|
|
45
|
+
element.onerror = () => {
|
|
46
|
+
element.remove();
|
|
47
|
+
reject(new Error(`Unable to load ${kind}: ${absoluteUrl}`));
|
|
48
|
+
};
|
|
49
|
+
document.head.append(element);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// The DOM clears the failed element; the cache evicts the rejected
|
|
53
|
+
// promise. The guard keeps a finished load from wiping out a newer one.
|
|
54
|
+
// A catch never runs synchronously, so the eviction always lands after
|
|
55
|
+
// the `set` above, even when the transport rejects during append.
|
|
56
|
+
loads.set(absoluteUrl, pending);
|
|
57
|
+
pending.catch(() => {
|
|
58
|
+
if (loads.get(absoluteUrl) === pending) loads.delete(absoluteUrl);
|
|
59
|
+
});
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
return
|
|
62
|
+
return loads.get(absoluteUrl);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
export function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!styleLoads.has(absoluteUrl)) {
|
|
59
|
-
styleLoads.set(
|
|
60
|
-
absoluteUrl,
|
|
61
|
-
new Promise((resolve, reject) => {
|
|
62
|
-
const link = document.createElement("link");
|
|
63
|
-
link.rel = "stylesheet";
|
|
64
|
-
link.href = absoluteUrl;
|
|
65
|
-
link.onload = resolve;
|
|
66
|
-
link.onerror = () => {
|
|
67
|
-
styleLoads.delete(absoluteUrl);
|
|
68
|
-
link.remove();
|
|
69
|
-
reject(new Error(`Unable to load style: ${absoluteUrl}`));
|
|
70
|
-
};
|
|
71
|
-
document.head.append(link);
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
74
|
-
}
|
|
65
|
+
export function loadScript(url) {
|
|
66
|
+
return loadAsset(url, scriptLoads, "script");
|
|
67
|
+
}
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
export function loadStyle(url) {
|
|
70
|
+
return loadAsset(url, styleLoads, "style");
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
function makeInit(name, connect) {
|
|
@@ -177,8 +171,7 @@ export function loadEnhancement(name, url) {
|
|
|
177
171
|
return promise;
|
|
178
172
|
}
|
|
179
173
|
|
|
180
|
-
async function loadManifest(
|
|
181
|
-
const entries = Object.entries(manifest);
|
|
174
|
+
async function loadManifest(entries) {
|
|
182
175
|
const results = await Promise.allSettled(
|
|
183
176
|
entries.map(([name, url]) => loadEnhancement(name, url)),
|
|
184
177
|
);
|
|
@@ -239,7 +232,7 @@ export async function loadEnhancements(scope = document) {
|
|
|
239
232
|
}
|
|
240
233
|
}
|
|
241
234
|
|
|
242
|
-
const result = await loadManifest(
|
|
235
|
+
const result = await loadManifest([...manifest]);
|
|
243
236
|
|
|
244
237
|
if (result.failed.length === 0) {
|
|
245
238
|
for (const block of blocks) block.remove();
|
|
@@ -250,7 +243,7 @@ export async function loadEnhancements(scope = document) {
|
|
|
250
243
|
|
|
251
244
|
async function loadManifestBlock(block) {
|
|
252
245
|
const entries = parseManifestEntries(block.textContent);
|
|
253
|
-
const result = await loadManifest(
|
|
246
|
+
const result = await loadManifest(entries);
|
|
254
247
|
|
|
255
248
|
for (const failure of result.failed) {
|
|
256
249
|
console.error(`Enhancement "${failure.name}" failed to load:`, failure.error);
|
|
@@ -286,7 +279,7 @@ export async function loadResponse(response) {
|
|
|
286
279
|
throw new TypeError("Enhance-Modules header must be a JSON object.");
|
|
287
280
|
}
|
|
288
281
|
|
|
289
|
-
return loadManifest(manifest);
|
|
282
|
+
return loadManifest(Object.entries(manifest));
|
|
290
283
|
}
|
|
291
284
|
|
|
292
285
|
export function __setModuleImporter(fn) {
|
package/src/js/events.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export const ACTUAL_EVENT_PREFIX = "actual";
|
|
2
2
|
|
|
3
3
|
export const EVENTS = {
|
|
4
|
-
reposition: `${ACTUAL_EVENT_PREFIX}:reposition`,
|
|
5
|
-
hide: `${ACTUAL_EVENT_PREFIX}:hide`,
|
|
6
|
-
outOfView: `${ACTUAL_EVENT_PREFIX}:out-of-view`,
|
|
7
4
|
// Cancelable: dispatched by a surface before it opens.
|
|
8
5
|
// detail: { surface, options }
|
|
9
6
|
surfaceOpen: `${ACTUAL_EVENT_PREFIX}:surface-open`,
|
package/src/js/filter.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The coordinate space and CSS positioning scheme are one decision. Keeping
|
|
3
|
+
* them in one value prevents a caller from writing document coordinates to a
|
|
4
|
+
* fixed box, or viewport coordinates to an absolute one.
|
|
5
|
+
*
|
|
6
|
+
* An open top-layer element with position: absolute uses the initial
|
|
7
|
+
* containing block, so only the anchor determines the correct mode.
|
|
8
|
+
*/
|
|
9
|
+
const VIEWPORT_ANCHORED = "dialog:modal, :popover-open";
|
|
10
|
+
|
|
11
|
+
function isViewportAnchored(anchor) {
|
|
12
|
+
const win = anchor?.ownerDocument?.defaultView;
|
|
13
|
+
if (!win) return true;
|
|
14
|
+
|
|
15
|
+
// Modal dialogs and open popovers are viewport-anchored without computing
|
|
16
|
+
// position: fixed, so the ancestor walk below cannot identify them.
|
|
17
|
+
try {
|
|
18
|
+
if (anchor.closest(VIEWPORT_ANCHORED)) return true;
|
|
19
|
+
} catch {
|
|
20
|
+
/* An engine without :modal / :popover-open: the walk is all there is. */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let el = anchor; el; el = el.parentElement) {
|
|
24
|
+
const { position } = win.getComputedStyle(el);
|
|
25
|
+
// Resolve sticky once even while unstuck: changing mode when it sticks
|
|
26
|
+
// would jump the floating element between coordinate systems mid-scroll.
|
|
27
|
+
if (position === "fixed" || position === "sticky") return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {Element | null | undefined} anchor
|
|
34
|
+
* @returns {{ space: "viewport" | "document", position: "fixed" | "absolute" }}
|
|
35
|
+
*/
|
|
36
|
+
export function positionModeFor(anchor) {
|
|
37
|
+
const space = isViewportAnchored(anchor) ? "viewport" : "document";
|
|
38
|
+
return {
|
|
39
|
+
space,
|
|
40
|
+
position: space === "document" ? "absolute" : "fixed",
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/js/flyout.js
CHANGED
|
@@ -3,15 +3,8 @@ import { focusFirstDescendant } from "./focus.js";
|
|
|
3
3
|
import { connectMenu, focusFirstMenuItem, focusLastMenuItem } from "./menu.js";
|
|
4
4
|
import { closeSurface, isSurfaceOpen, openSurface, retainSurface } from "./surface.js";
|
|
5
5
|
|
|
6
|
-
const panelRefs = new WeakMap();
|
|
7
6
|
const triggerMap = new WeakMap();
|
|
8
7
|
|
|
9
|
-
function normalizeBreakpoint(raw) {
|
|
10
|
-
const BREAKPOINTS = { sm: 640, md: 768, lg: 1024 };
|
|
11
|
-
const value = Number.parseInt(BREAKPOINTS[raw] || raw, 10);
|
|
12
|
-
return Number.isFinite(value) ? value : undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
8
|
function readFlyoutOptions(panel) {
|
|
16
9
|
const opts = { autoClose: "true" };
|
|
17
10
|
const ds = panel.dataset;
|
|
@@ -20,11 +13,6 @@ function readFlyoutOptions(panel) {
|
|
|
20
13
|
const d = Number.parseFloat(ds.flyoutDistance);
|
|
21
14
|
if (Number.isFinite(d)) opts.distance = d;
|
|
22
15
|
}
|
|
23
|
-
if (ds.flyoutMobile) opts.mobile = ds.flyoutMobile;
|
|
24
|
-
if (ds.flyoutBreakpoint) {
|
|
25
|
-
const bp = normalizeBreakpoint(ds.flyoutBreakpoint);
|
|
26
|
-
if (bp != null) opts.breakpoint = bp;
|
|
27
|
-
}
|
|
28
16
|
if (ds.flyoutAutoClose) opts.autoClose = ds.flyoutAutoClose;
|
|
29
17
|
return opts;
|
|
30
18
|
}
|
|
@@ -93,20 +81,12 @@ function onTriggerKeydown(e) {
|
|
|
93
81
|
|
|
94
82
|
switch (e.key) {
|
|
95
83
|
case "ArrowDown":
|
|
96
|
-
e.preventDefault();
|
|
97
|
-
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
98
|
-
focusFirstMenuItem(panel);
|
|
99
|
-
break;
|
|
100
|
-
case "ArrowUp":
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
103
|
-
focusLastMenuItem(panel);
|
|
104
|
-
break;
|
|
105
84
|
case "Home":
|
|
106
85
|
e.preventDefault();
|
|
107
86
|
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
108
87
|
focusFirstMenuItem(panel);
|
|
109
88
|
break;
|
|
89
|
+
case "ArrowUp":
|
|
110
90
|
case "End":
|
|
111
91
|
e.preventDefault();
|
|
112
92
|
if (!isSurfaceOpen(panel) && !openFlyout(panel, trigger)) break;
|
|
@@ -131,7 +111,7 @@ function connectTrigger(trigger) {
|
|
|
131
111
|
if (triggerMap.has(trigger)) return;
|
|
132
112
|
|
|
133
113
|
const controller = new AbortController();
|
|
134
|
-
const state = { panel: null, controller, releaseMenu: null };
|
|
114
|
+
const state = { panel: null, controller, releaseMenu: null, releaseSurface: null };
|
|
135
115
|
triggerMap.set(trigger, state);
|
|
136
116
|
|
|
137
117
|
resolvePanel(trigger, state);
|
|
@@ -144,38 +124,23 @@ function connectTrigger(trigger) {
|
|
|
144
124
|
trigger.addEventListener("keydown", onTriggerKeydown, { signal: controller.signal });
|
|
145
125
|
}
|
|
146
126
|
|
|
147
|
-
function releasePanelReference(
|
|
127
|
+
function releasePanelReference(state) {
|
|
148
128
|
state.releaseMenu?.();
|
|
149
129
|
state.releaseMenu = null;
|
|
150
|
-
|
|
151
|
-
|
|
130
|
+
state.releaseSurface?.();
|
|
131
|
+
state.releaseSurface = null;
|
|
152
132
|
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
133
|
}
|
|
164
134
|
|
|
165
135
|
function resolvePanel(trigger, state) {
|
|
166
136
|
const panel = panelFor(trigger);
|
|
167
137
|
if (state.panel === panel && panel?.isConnected) return panel;
|
|
168
138
|
|
|
169
|
-
releasePanelReference(
|
|
139
|
+
releasePanelReference(state);
|
|
170
140
|
if (!panel) return null;
|
|
171
141
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const release = retainSurface(panel);
|
|
175
|
-
panelEntry = { triggers: new Set(), release };
|
|
176
|
-
panelRefs.set(panel, panelEntry);
|
|
177
|
-
}
|
|
178
|
-
panelEntry.triggers.add(trigger);
|
|
142
|
+
// Surface retention owns the shared lifetime across all triggers and consumers.
|
|
143
|
+
state.releaseSurface = retainSurface(panel);
|
|
179
144
|
state.panel = panel;
|
|
180
145
|
|
|
181
146
|
if (isMenuFlyout(panel)) {
|
|
@@ -197,7 +162,7 @@ function disconnectTrigger(trigger) {
|
|
|
197
162
|
if (state.panel?.isConnected && isSurfaceOpen(state.panel)) {
|
|
198
163
|
closeSurface(state.panel, { restoreFocus: false });
|
|
199
164
|
}
|
|
200
|
-
releasePanelReference(
|
|
165
|
+
releasePanelReference(state);
|
|
201
166
|
triggerMap.delete(trigger);
|
|
202
167
|
}
|
|
203
168
|
|
package/src/js/focus-group.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Focus group — one remembered tab stop and Arrow/Home/End navigation for a
|
|
3
|
+
* composite widget. The consumer supplies getItems(); the controller only
|
|
4
|
+
* updates tabindex and focus, never activation or ARIA. sync() re-reads the
|
|
5
|
+
* candidate list; the primitive does not observe the subtree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import { itemForKey } from "./keys.js";
|
|
2
9
|
|
|
3
10
|
const ORIENTATIONS = new Set(["horizontal", "vertical"]);
|
package/src/js/focus.js
CHANGED
|
@@ -9,6 +9,12 @@ const FOCUSABLE_SELECTOR = [
|
|
|
9
9
|
"[tabindex]",
|
|
10
10
|
].join(",");
|
|
11
11
|
|
|
12
|
+
/*
|
|
13
|
+
* The branch below checkVisibility() is not dead code: checkVisibility() is
|
|
14
|
+
* Safari 17.4 and the floor is Safari 17, so it is the branch that runs at the
|
|
15
|
+
* bottom of the supported range — where it also decides whether a closed
|
|
16
|
+
* popover's contents are reachable. Measured there; do not drop it.
|
|
17
|
+
*/
|
|
12
18
|
export function isElementVisible(el) {
|
|
13
19
|
if (!el || el.hidden) return false;
|
|
14
20
|
if (typeof el.checkVisibility === "function") return el.checkVisibility();
|
package/src/js/menu.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Menu — the shared `.menu > li > .menu-item` vocabulary and wiring.
|
|
3
|
+
*
|
|
4
|
+
* getMenuItems() filters to usable items; onMenuKeydown() adds directional
|
|
5
|
+
* navigation and activation; connectMenu() owns the ref-counted listeners a
|
|
6
|
+
* menu shares across its triggers. Wiring is per menu, not per trigger, so a
|
|
7
|
+
* menu retained by several triggers (or by flyout and context-menu at once)
|
|
8
|
+
* tears down only when the last one releases it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
import { isElementVisible } from "./focus.js";
|
|
2
12
|
import { connectFocusGroup } from "./focus-group.js";
|
|
3
13
|
import { firstItem, itemForKey, lastItem } from "./keys.js";
|
|
@@ -134,7 +144,6 @@ export function connectMenu(menu, { close }) {
|
|
|
134
144
|
released = true;
|
|
135
145
|
entry.count--;
|
|
136
146
|
if (entry.count <= 0) {
|
|
137
|
-
entry.focusGroup?.disconnect();
|
|
138
147
|
entry.controller.abort();
|
|
139
148
|
menuConnections.delete(menu);
|
|
140
149
|
}
|
package/src/js/selectors.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Selectors — the state-class vocabulary the runtime writes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The file lists the class names modules write as state (is-open,
|
|
5
|
+
* was-validated, …) and validation's two read-side presentation adapters
|
|
6
|
+
* (field, fieldError). Behavior discovery is not listed here: it uses
|
|
7
|
+
* data-enhance tokens. To run this runtime against a different CSS
|
|
8
|
+
* 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
|
|
@@ -27,10 +27,6 @@ export const CLASSES = {
|
|
|
27
27
|
// surface.js — state class on the open flyout/menu/dialog surface.
|
|
28
28
|
// status.js writes the same class on the status bar while a message shows.
|
|
29
29
|
open: "is-open",
|
|
30
|
-
// surface.js — state class while the surface renders as a mobile sheet.
|
|
31
|
-
sheet: "is-sheet",
|
|
32
|
-
// surface.js — class name written on the generated sheet backdrop.
|
|
33
|
-
backdrop: "surface-backdrop",
|
|
34
30
|
// dialog.js — transient class for the "can't dismiss" shake animation.
|
|
35
31
|
static: "is-static",
|
|
36
32
|
// dialog.js — written on <html> while any modal dialog is open.
|
package/src/js/status.js
CHANGED
|
@@ -58,6 +58,7 @@ import { waitForTransitions } from "./transition.js";
|
|
|
58
58
|
const STATUS_SELECTOR = '[data-status][role="status"]';
|
|
59
59
|
const STATUS_CLASSES_ATTR = "statusClasses";
|
|
60
60
|
const VIEWPORT_OFFSET_PROPERTY = "--status-viewport-offset";
|
|
61
|
+
const DEFAULT_DURATION_MS = 3000;
|
|
61
62
|
|
|
62
63
|
let statusTimer;
|
|
63
64
|
// Bumped by every show and every clear. A deferred exit cleanup only runs when
|
|
@@ -151,10 +152,10 @@ export function status(message, options = {}) {
|
|
|
151
152
|
const target = statusTarget();
|
|
152
153
|
if (!target || message == null) return;
|
|
153
154
|
|
|
154
|
-
// A live region with nothing to announce is not a message.
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
155
|
+
// A live region with nothing to announce is not a message. The open state
|
|
156
|
+
// is explicit, so an empty message has to mean "close" or it would open a
|
|
157
|
+
// blank pill. This also keeps status(await response.text()) from leaving a
|
|
158
|
+
// stale message on screen.
|
|
158
159
|
if (message === "") {
|
|
159
160
|
status.clear();
|
|
160
161
|
return;
|
|
@@ -179,7 +180,7 @@ export function status(message, options = {}) {
|
|
|
179
180
|
trackViewport(target);
|
|
180
181
|
|
|
181
182
|
if (options.duration !== false) {
|
|
182
|
-
statusTimer = setTimeout(() => status.clear(), options.duration ??
|
|
183
|
+
statusTimer = setTimeout(() => status.clear(), options.duration ?? DEFAULT_DURATION_MS);
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|