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
package/src/js/surface.js
CHANGED
|
@@ -2,14 +2,13 @@ import { autoUpdate, reposition, repositionAt } from "@lekoala/floating";
|
|
|
2
2
|
import enhance from "./enhance.js";
|
|
3
3
|
import { registerEscapeDismissal } from "./escape.js";
|
|
4
4
|
import { EVENTS } from "./events.js";
|
|
5
|
+
import { positionModeFor } from "./floating-space.js";
|
|
5
6
|
|
|
6
7
|
import { CLASSES } from "./selectors.js";
|
|
7
|
-
import { waitForTransitions } from "./transition.js";
|
|
8
8
|
|
|
9
9
|
const openSurfaces = new Set();
|
|
10
10
|
const surfaceMap = new WeakMap();
|
|
11
11
|
const surfaceRetainers = new WeakMap();
|
|
12
|
-
const mountedSurfaces = new WeakMap();
|
|
13
12
|
const clickBoundDocuments = new WeakSet();
|
|
14
13
|
|
|
15
14
|
const SURFACE_MARKER = "data-actual-surface";
|
|
@@ -38,61 +37,57 @@ function reaperFor(menu) {
|
|
|
38
37
|
|
|
39
38
|
let reaper = reapers.get(root);
|
|
40
39
|
if (!reaper) {
|
|
41
|
-
reaper = enhance(
|
|
42
|
-
{ [`[${SURFACE_MARKER}]`]: (el) => () => disconnectSurface(el, { restore: false }) },
|
|
43
|
-
root,
|
|
44
|
-
);
|
|
40
|
+
reaper = enhance({ [`[${SURFACE_MARKER}]`]: (el) => () => disconnectSurface(el) }, root);
|
|
45
41
|
reapers.set(root, reaper);
|
|
46
42
|
}
|
|
47
43
|
return reaper;
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (mount.parent.isConnected) {
|
|
70
|
-
const next = mount.next?.parentNode === mount.parent ? mount.next : null;
|
|
71
|
-
mount.parent.insertBefore(menu, next);
|
|
72
|
-
} else {
|
|
73
|
-
menu.remove();
|
|
46
|
+
/*
|
|
47
|
+
* Transport: the top layer, via popover="manual".
|
|
48
|
+
*
|
|
49
|
+
* The runtime owns the whole lifecycle, so "manual" is the only usable mode —
|
|
50
|
+
* it supplies promotion to the top layer and nothing else, leaving the
|
|
51
|
+
* dismissal policy, Escape ordering and focus restoration below untouched.
|
|
52
|
+
*
|
|
53
|
+
* The attribute is set by the runtime, not asked of the author: a surface is
|
|
54
|
+
* still marked up exactly as before. showPopover/hidePopover throw on an
|
|
55
|
+
* out-of-order call (already open, not connected), which is a state Actual's
|
|
56
|
+
* own guards should already have excluded — the try/catch is there so a
|
|
57
|
+
* surprising DOM does not take the lifecycle down with it.
|
|
58
|
+
*/
|
|
59
|
+
function showTransport(menu) {
|
|
60
|
+
try {
|
|
61
|
+
menu.showPopover();
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
74
65
|
}
|
|
75
|
-
|
|
76
|
-
mountedSurfaces.delete(menu);
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const view = menu.ownerDocument?.defaultView;
|
|
85
|
-
if (!view || typeof view.matchMedia !== "function") {
|
|
86
|
-
return false;
|
|
68
|
+
function hideTransport(menu) {
|
|
69
|
+
try {
|
|
70
|
+
menu.hidePopover();
|
|
71
|
+
} catch {
|
|
72
|
+
/* Already closed or detached; nothing left to hide. */
|
|
87
73
|
}
|
|
88
|
-
|
|
89
|
-
const breakpoint = state.breakpoint ?? 768;
|
|
90
|
-
return (
|
|
91
|
-
view.matchMedia("(pointer: coarse)").matches &&
|
|
92
|
-
view.matchMedia(`(max-width: ${breakpoint}px)`).matches
|
|
93
|
-
);
|
|
94
74
|
}
|
|
95
75
|
|
|
76
|
+
/*
|
|
77
|
+
* Trigger state sync stays Actual's job, deliberately.
|
|
78
|
+
*
|
|
79
|
+
* A button carrying popovertarget gets an implicit invoker relation and the
|
|
80
|
+
* platform computes aria-expanded on it by itself — measured on the AX tree:
|
|
81
|
+
* expanded false when closed, true when open. Tempting, and out of scope: the
|
|
82
|
+
* public contract here is aria-controls, and popovertarget would replace it.
|
|
83
|
+
* That is a markup change for adopters, not an internal transport detail, so it
|
|
84
|
+
* cannot ride along with a transport swap that changes no HTML.
|
|
85
|
+
*
|
|
86
|
+
* It is also not obviously a win. linkedTriggers() is small, explicit, and
|
|
87
|
+
* already supports several triggers pointing at one panel; popovertarget covers
|
|
88
|
+
* the single-invoker case. Any future move needs to weigh what is lost, not
|
|
89
|
+
* just count the lines saved.
|
|
90
|
+
*/
|
|
96
91
|
function linkedTriggers(menu) {
|
|
97
92
|
if (!menu.id) return [];
|
|
98
93
|
return [...menu.ownerDocument.querySelectorAll(`[aria-controls="${CSS.escape(menu.id)}"]`)];
|
|
@@ -104,39 +99,9 @@ function syncExpanded(menu, expanded) {
|
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
101
|
|
|
107
|
-
function ensureBackdrop(menu, state) {
|
|
108
|
-
if (!state.isSheet) {
|
|
109
|
-
state.backdrop?.remove();
|
|
110
|
-
state.backdrop = null;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (state.backdrop?.isConnected) {
|
|
115
|
-
state.backdrop.hidden = false;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const backdrop = menu.ownerDocument.createElement("div");
|
|
120
|
-
backdrop.className = CLASSES.backdrop;
|
|
121
|
-
backdrop.hidden = false;
|
|
122
|
-
|
|
123
|
-
menu.before(backdrop);
|
|
124
|
-
state.backdrop = backdrop;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function applyPresentation(menu, state) {
|
|
128
|
-
state.isSheet = shouldUseSheet(menu, state);
|
|
129
|
-
if (state.isSheet) {
|
|
130
|
-
menu.style.removeProperty("left");
|
|
131
|
-
menu.style.removeProperty("top");
|
|
132
|
-
}
|
|
133
|
-
menu.classList.toggle(CLASSES.sheet, state.isSheet);
|
|
134
|
-
ensureBackdrop(menu, state);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
102
|
function positionSurface(menu) {
|
|
138
103
|
const state = surfaceMap.get(menu);
|
|
139
|
-
if (!state
|
|
104
|
+
if (!state) return true;
|
|
140
105
|
|
|
141
106
|
if (state.trigger) {
|
|
142
107
|
const triggerWidth = state.trigger.getBoundingClientRect().width;
|
|
@@ -152,6 +117,7 @@ function positionSurface(menu) {
|
|
|
152
117
|
shift: state.shift !== false,
|
|
153
118
|
shiftPadding: state.shiftPadding ?? 4,
|
|
154
119
|
scope: state.scope,
|
|
120
|
+
coordinateSpace: state.coordinateSpace,
|
|
155
121
|
};
|
|
156
122
|
|
|
157
123
|
if (state.point) {
|
|
@@ -171,12 +137,9 @@ function ensureSurfaceWired(menu) {
|
|
|
171
137
|
stopTracking: null,
|
|
172
138
|
stopScrollIntent: null,
|
|
173
139
|
unregisterEscape: null,
|
|
174
|
-
backdrop: null,
|
|
175
140
|
trigger: null,
|
|
176
141
|
source: null,
|
|
177
142
|
point: null,
|
|
178
|
-
mobile: "auto",
|
|
179
|
-
breakpoint: 768,
|
|
180
143
|
placement: "bottom-start",
|
|
181
144
|
distance: 4,
|
|
182
145
|
flip: true,
|
|
@@ -185,8 +148,7 @@ function ensureSurfaceWired(menu) {
|
|
|
185
148
|
autoClose: "outside",
|
|
186
149
|
dismissOnScroll: false,
|
|
187
150
|
scrollIntentAt: null,
|
|
188
|
-
|
|
189
|
-
closeId: 0,
|
|
151
|
+
coordinateSpace: "viewport",
|
|
190
152
|
};
|
|
191
153
|
|
|
192
154
|
surfaceMap.set(menu, state);
|
|
@@ -226,50 +188,37 @@ function startSurfaceResources(menu, state) {
|
|
|
226
188
|
}
|
|
227
189
|
const anchor = state.point ? null : state.trigger || state.source;
|
|
228
190
|
state.stopTracking = autoUpdate(anchor, menu, ({ type }) => {
|
|
229
|
-
if (menu
|
|
191
|
+
if (!isSurfaceOpen(menu)) return;
|
|
230
192
|
if (type === "scroll" && state.dismissOnScroll) return;
|
|
231
|
-
applyPresentation(menu, state);
|
|
232
193
|
if (!positionSurface(menu)) closeSurface(menu);
|
|
233
194
|
});
|
|
234
195
|
}
|
|
235
196
|
|
|
236
197
|
export function isSurfaceOpen(menu) {
|
|
237
|
-
return menu.classList.contains(CLASSES.open)
|
|
198
|
+
return menu.classList.contains(CLASSES.open);
|
|
238
199
|
}
|
|
239
200
|
|
|
240
201
|
export function prepareSurface(menu) {
|
|
241
202
|
if (!menu) return;
|
|
242
203
|
menu.style.position = "fixed";
|
|
243
|
-
|
|
204
|
+
// A closed popover is hidden by the platform, so [hidden] is no longer the
|
|
205
|
+
// closed state. Any left over from author markup would outrank it.
|
|
206
|
+
menu.removeAttribute("hidden");
|
|
207
|
+
// "manual" is written even over an author's own value, because it is the
|
|
208
|
+
// whole of what the transport asks for. Any other mode hands the UA a
|
|
209
|
+
// dismissal policy of its own: light dismiss closes on outside clicks only,
|
|
210
|
+
// which silently defeats data-flyout-auto-close.
|
|
211
|
+
menu.setAttribute("popover", "manual");
|
|
244
212
|
syncExpanded(menu, false);
|
|
245
213
|
menu.setAttribute(SURFACE_MARKER, "");
|
|
246
214
|
reaperFor(menu)?.refresh(menu);
|
|
247
215
|
}
|
|
248
216
|
|
|
249
|
-
/*
|
|
250
|
-
* One lifecycle owner per surface. surface.js opens by clearing [hidden] and
|
|
251
|
-
* adding .is-open; it never calls showPopover(). So a [popover] panel handed to
|
|
252
|
-
* the runtime has two owners and stays shut: the platform keeps a popover it
|
|
253
|
-
* was never asked to show closed, whatever the runtime writes.
|
|
254
|
-
*
|
|
255
|
-
* This covers every popover value. "manual" is a candidate future transport for
|
|
256
|
-
* this module, which does not make it a legitimate mix today. Delete this guard
|
|
257
|
-
* in the commit that makes surface.js call showPopover().
|
|
258
|
-
*/
|
|
259
|
-
function warnPopoverConflict(panel) {
|
|
260
|
-
if (!panel.hasAttribute("popover")) return;
|
|
261
|
-
console.warn(
|
|
262
|
-
"Actual: the surface lifecycle cannot own a [popover] element — choose one lifecycle owner (remove the popover attribute, or drop the Actual enhancement).",
|
|
263
|
-
panel,
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
217
|
export function retainSurface(panel) {
|
|
268
218
|
if (!panel) return () => {};
|
|
269
219
|
|
|
270
220
|
let entry = surfaceRetainers.get(panel);
|
|
271
221
|
if (!entry) {
|
|
272
|
-
warnPopoverConflict(panel);
|
|
273
222
|
prepareSurface(panel);
|
|
274
223
|
entry = { count: 0 };
|
|
275
224
|
surfaceRetainers.set(panel, entry);
|
|
@@ -282,8 +231,10 @@ export function retainSurface(panel) {
|
|
|
282
231
|
released = true;
|
|
283
232
|
entry.count--;
|
|
284
233
|
if (entry.count <= 0) {
|
|
285
|
-
//
|
|
286
|
-
//
|
|
234
|
+
// Nothing was moved, so nothing has to be put back. What the last
|
|
235
|
+
// release still owes is the teardown: the panel may be open, and an
|
|
236
|
+
// open surface holds an Escape entry and a position tracker, neither of
|
|
237
|
+
// which has an owner once the last retainer is gone.
|
|
287
238
|
disconnectSurface(panel);
|
|
288
239
|
surfaceRetainers.delete(panel);
|
|
289
240
|
}
|
|
@@ -301,20 +252,12 @@ export function openSurface(menu, opts = {}) {
|
|
|
301
252
|
});
|
|
302
253
|
if (!menu.dispatchEvent(beforeOpen)) return false;
|
|
303
254
|
|
|
304
|
-
for (const other of openSurfaces) {
|
|
305
|
-
if (other !== menu && other.ownerDocument === menu.ownerDocument) closeSurface(other);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const anchor = opts.trigger || opts.source || null;
|
|
309
|
-
mountSurface(menu, anchor);
|
|
310
255
|
prepareSurface(menu);
|
|
311
256
|
const state = ensureSurfaceWired(menu);
|
|
312
257
|
state.trigger = opts.trigger || null;
|
|
313
258
|
state.source = opts.source || null;
|
|
314
259
|
state.point =
|
|
315
260
|
Number.isFinite(opts.x) && Number.isFinite(opts.y) ? { x: opts.x, y: opts.y } : null;
|
|
316
|
-
state.mobile = opts.mobile || "auto";
|
|
317
|
-
state.breakpoint = opts.breakpoint ?? 768;
|
|
318
261
|
state.autoClose = normalizeAutoClose(opts.autoClose);
|
|
319
262
|
state.dismissOnScroll = opts.dismissOnScroll === true;
|
|
320
263
|
state.scrollIntentAt = null;
|
|
@@ -325,11 +268,18 @@ export function openSurface(menu, opts = {}) {
|
|
|
325
268
|
state.shiftPadding = opts.shiftPadding ?? 4;
|
|
326
269
|
state.scope = opts.scope;
|
|
327
270
|
state.restoreFocusTo = opts.restoreFocusTo || opts.trigger || opts.source || null;
|
|
328
|
-
state.closeId++;
|
|
329
271
|
|
|
272
|
+
// Points are client coordinates and therefore always viewport-relative.
|
|
273
|
+
// Element anchors choose their coupled coordinate/CSS mode once per open;
|
|
274
|
+
// in particular, a sticky anchor must not switch modes while scrolling.
|
|
275
|
+
const positionAnchor = state.point ? null : state.trigger || state.source;
|
|
276
|
+
const mode = positionModeFor(positionAnchor);
|
|
277
|
+
state.coordinateSpace = mode.space;
|
|
278
|
+
menu.style.position = mode.position;
|
|
279
|
+
|
|
280
|
+
// Promote before measuring: a closed popover has no box to position.
|
|
281
|
+
if (!showTransport(menu)) return false;
|
|
330
282
|
menu.classList.add(CLASSES.open);
|
|
331
|
-
menu.hidden = false;
|
|
332
|
-
applyPresentation(menu, state);
|
|
333
283
|
syncExpanded(menu, true);
|
|
334
284
|
|
|
335
285
|
if (!positionSurface(menu)) {
|
|
@@ -337,6 +287,33 @@ export function openSurface(menu, opts = {}) {
|
|
|
337
287
|
return false;
|
|
338
288
|
}
|
|
339
289
|
|
|
290
|
+
/*
|
|
291
|
+
* Surfaces are mutually exclusive per document, including when one contains
|
|
292
|
+
* the other's trigger. That is a deliberate contract, not a missing feature:
|
|
293
|
+
* making this loop ancestor-aware is most of a nesting implementation but
|
|
294
|
+
* not all of it. The platform closes a popover with display: none, and what
|
|
295
|
+
* that does to a descendant surface already in the top layer is unmeasured —
|
|
296
|
+
* nothing here cascades a close, so a live Escape entry and position tracker
|
|
297
|
+
* outliving their panel is the expected failure. Measure before relaxing it.
|
|
298
|
+
*
|
|
299
|
+
* The others come down only once this one is up and placed. With
|
|
300
|
+
* popover="manual" nothing requires the old surface to leave the top layer
|
|
301
|
+
* before the new one enters it, so there is no reason to spend it before
|
|
302
|
+
* knowing the open will succeed — a detached anchor or a positioner failure
|
|
303
|
+
* used to dismiss the current menu and then open nothing.
|
|
304
|
+
*
|
|
305
|
+
* restoreFocus: false because these are superseded, not dismissed. Restoring
|
|
306
|
+
* would bounce focus through the outgoing trigger on the way to the new
|
|
307
|
+
* surface, firing focusin on a control the user has just navigated away
|
|
308
|
+
* from; placing focus in the new surface is the caller's job, and both
|
|
309
|
+
* callers in tree do it as soon as this returns.
|
|
310
|
+
*/
|
|
311
|
+
for (const other of openSurfaces) {
|
|
312
|
+
if (other !== menu && other.ownerDocument === menu.ownerDocument) {
|
|
313
|
+
closeSurface(other, { restoreFocus: false });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
340
317
|
openSurfaces.add(menu);
|
|
341
318
|
startSurfaceResources(menu, state);
|
|
342
319
|
return true;
|
|
@@ -346,17 +323,10 @@ export function closeSurface(menu, opts = {}) {
|
|
|
346
323
|
if (!menu || !isSurfaceOpen(menu)) return;
|
|
347
324
|
|
|
348
325
|
const state = surfaceMap.get(menu);
|
|
349
|
-
const closeId = state ? ++state.closeId : 0;
|
|
350
|
-
const wasSheet = state?.isSheet === true;
|
|
351
326
|
const activeElement = menu.ownerDocument.activeElement;
|
|
352
327
|
const shouldRestoreFocus = opts.restoreFocus ?? menu.contains(activeElement);
|
|
353
328
|
menu.classList.remove(CLASSES.open);
|
|
354
|
-
|
|
355
|
-
menu.classList.remove(CLASSES.sheet);
|
|
356
|
-
}
|
|
357
|
-
menu.hidden = true;
|
|
358
|
-
const backdrop = state?.backdrop || null;
|
|
359
|
-
if (backdrop) backdrop.hidden = true;
|
|
329
|
+
hideTransport(menu);
|
|
360
330
|
openSurfaces.delete(menu);
|
|
361
331
|
syncExpanded(menu, false);
|
|
362
332
|
state?.unregisterEscape?.();
|
|
@@ -366,30 +336,18 @@ export function closeSurface(menu, opts = {}) {
|
|
|
366
336
|
if (shouldRestoreFocus && state?.restoreFocusTo?.isConnected) {
|
|
367
337
|
state.restoreFocusTo.focus({ preventScroll: true });
|
|
368
338
|
}
|
|
369
|
-
|
|
370
|
-
waitForTransitions(menu, backdrop).then(() => {
|
|
371
|
-
if (!state || state.closeId !== closeId) return;
|
|
372
|
-
state.isSheet = false;
|
|
373
|
-
menu.classList.remove(CLASSES.sheet);
|
|
374
|
-
backdrop?.remove();
|
|
375
|
-
if (state.backdrop === backdrop) state.backdrop = null;
|
|
376
|
-
restoreSurface(menu);
|
|
377
|
-
});
|
|
378
339
|
}
|
|
379
340
|
|
|
380
|
-
export function disconnectSurface(menu
|
|
341
|
+
export function disconnectSurface(menu) {
|
|
381
342
|
if (!menu) return;
|
|
382
|
-
if (!restore) mountedSurfaces.delete(menu);
|
|
383
343
|
closeSurface(menu);
|
|
384
344
|
const state = surfaceMap.get(menu);
|
|
385
345
|
if (state) {
|
|
386
|
-
state.backdrop?.remove();
|
|
387
346
|
state.unregisterEscape?.();
|
|
388
347
|
state.stopScrollIntent?.();
|
|
389
348
|
state.stopTracking?.();
|
|
390
349
|
surfaceMap.delete(menu);
|
|
391
350
|
}
|
|
392
|
-
restoreSurface(menu);
|
|
393
351
|
}
|
|
394
352
|
|
|
395
353
|
function onDocumentClick(e) {
|
package/src/js/tab.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Keyboard: ArrowLeft/Right, Home/End (select tab, wrapping)
|
|
9
9
|
* ArrowUp/Down for aria-orientation="vertical"
|
|
10
10
|
* ArrowDown (focus selected panel)
|
|
11
|
+
* [hidden], disabled and panel-less tabs are skipped
|
|
11
12
|
*
|
|
12
13
|
* Self-registers via registerEnhancement: injected tablists wire automatically.
|
|
13
14
|
* The tab→panel map is rebuilt from the live tablist on each interaction,
|
|
@@ -47,8 +48,12 @@ function isTabDisabled(tab) {
|
|
|
47
48
|
return tab.disabled || tab.getAttribute("aria-disabled") === "true";
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/* A [hidden] tab is not on screen, so it must not be a keyboard stop either:
|
|
52
|
+
an application that filters a tab strip would otherwise leave the hidden
|
|
53
|
+
tabs in the arrow-key sequence, selecting a panel whose tab is invisible.
|
|
54
|
+
Rebuilding from the live tablist is not enough — live is not visible. */
|
|
50
55
|
function activatableTabs(list) {
|
|
51
|
-
return tabsOf(list).filter((tab) => !isTabDisabled(tab) && panelFor(tab));
|
|
56
|
+
return tabsOf(list).filter((tab) => !tab.hidden && !isTabDisabled(tab) && panelFor(tab));
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
function makePanelFocusable(panel) {
|