@antadesign/anta 0.3.33 → 0.3.35
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/LICENSE.md +14 -1
- package/README.md +9 -0
- package/dist/anta_helpers.d.ts +46 -30
- package/dist/anta_helpers.js +66 -25
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +2 -1353
- package/dist/components/Banner.d.ts +25 -40
- package/dist/components/Banner.js +15 -17
- package/dist/components/Breadcrumbs.d.ts +0 -1
- package/dist/components/Breadcrumbs.js +0 -1
- package/dist/components/Button.d.ts +2 -3
- package/dist/components/Button.js +1 -0
- package/dist/components/ButtonCopy.js +3 -2
- package/dist/components/Calendar.js +13 -13
- package/dist/components/Card.d.ts +9 -17
- package/dist/components/Card.js +50 -46
- package/dist/components/Dialog.d.ts +1 -0
- package/dist/components/Dialog.js +16 -10
- package/dist/components/Expander.d.ts +6 -21
- package/dist/components/Expander.js +24 -23
- package/dist/components/Icon.d.ts +8 -14
- package/dist/components/Input.d.ts +77 -96
- package/dist/components/Input.js +23 -27
- package/dist/components/InputAutocomplete.d.ts +29 -10
- package/dist/components/InputAutocomplete.js +25 -8
- package/dist/components/InputDate.d.ts +0 -1
- package/dist/components/InputDate.js +2 -3
- package/dist/components/InputTime.d.ts +10 -4
- package/dist/components/InputTime.js +20 -16
- package/dist/components/Loader.d.ts +2 -2
- package/dist/components/Menu.d.ts +5 -4
- package/dist/components/Menu.js +13 -8
- package/dist/components/MenuItem.js +1 -14
- package/dist/components/MenuItemCopy.js +2 -2
- package/dist/components/Panel.d.ts +3 -1
- package/dist/components/Panel.js +17 -11
- package/dist/components/Progress.d.ts +21 -23
- package/dist/components/Progress.js +13 -9
- package/dist/components/RadioGroup.js +6 -8
- package/dist/components/{select-parts.css → Select.css} +1 -1
- package/dist/components/Select.d.ts +53 -36
- package/dist/components/Select.js +53 -98
- package/dist/components/SelectFaceted.d.ts +7 -4
- package/dist/components/SelectFaceted.js +21 -10
- package/dist/components/Slider.d.ts +28 -11
- package/dist/components/Slider.js +20 -20
- package/dist/components/Steps.css +1 -1
- package/dist/components/Steps.d.ts +0 -1
- package/dist/components/Steps.js +1 -12
- package/dist/components/Tabs.d.ts +17 -20
- package/dist/components/Tabs.js +7 -9
- package/dist/components/Text.d.ts +55 -36
- package/dist/components/Text.js +35 -22
- package/dist/components/Title.d.ts +19 -27
- package/dist/components/Title.js +2 -1
- package/dist/components/Toaster.js +9 -2
- package/dist/components/Tooltip.d.ts +10 -15
- package/dist/components/copy-props.d.ts +14 -3
- package/dist/components/copy-props.js +14 -3
- package/dist/components/select-tree.d.ts +24 -0
- package/dist/components/select-tree.js +35 -0
- package/dist/element-base.d.ts +10 -0
- package/dist/element-base.js +15 -0
- package/dist/elements/a-avatar.css +1 -1
- package/dist/elements/a-avatar.js +2 -28
- package/dist/elements/a-banner.css +1 -1
- package/dist/elements/a-banner.d.ts +4 -1
- package/dist/elements/a-banner.js +22 -112
- package/dist/elements/a-breadcrumbs.d.ts +1 -0
- package/dist/elements/a-breadcrumbs.js +1 -0
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-button.d.ts +10 -7
- package/dist/elements/a-button.js +18 -1
- package/dist/elements/a-calendar.d.ts +14 -13
- package/dist/elements/a-calendar.js +29 -24
- package/dist/elements/a-card.css +1 -1
- package/dist/elements/a-card.d.ts +0 -9
- package/dist/elements/a-card.js +0 -184
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-copy.css +1 -1
- package/dist/elements/a-copy.d.ts +1 -5
- package/dist/elements/a-copy.js +129 -108
- package/dist/elements/a-dialog.css +1 -1
- package/dist/elements/a-dialog.d.ts +1 -1
- package/dist/elements/a-dialog.js +68 -194
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-expander.d.ts +4 -30
- package/dist/elements/a-expander.js +85 -344
- package/dist/elements/a-icon.d.ts +5 -23
- package/dist/elements/a-icon.js +1 -2
- package/dist/elements/a-input-date.d.ts +1 -0
- package/dist/elements/a-input-date.js +1 -0
- package/dist/elements/a-input-time.css +1 -1
- package/dist/elements/a-input-time.d.ts +2 -1
- package/dist/elements/a-input-time.js +101 -168
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.d.ts +10 -8
- package/dist/elements/a-input.js +155 -230
- package/dist/elements/a-loader.d.ts +5 -8
- package/dist/elements/a-loader.js +1 -2
- package/dist/elements/a-menu-item.css +1 -1
- package/dist/elements/a-menu-item.d.ts +5 -3
- package/dist/elements/a-menu-item.js +5 -1
- package/dist/elements/a-menu.css +1 -1
- package/dist/elements/a-menu.d.ts +5 -7
- package/dist/elements/a-menu.js +383 -299
- package/dist/elements/a-panel.css +1 -1
- package/dist/elements/a-panel.js +19 -37
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-progress.d.ts +0 -22
- package/dist/elements/a-progress.js +0 -100
- package/dist/elements/a-radio-group.d.ts +1 -1
- package/dist/elements/a-radio-group.js +6 -4
- package/dist/elements/a-select-faceted.d.ts +1 -0
- package/dist/elements/a-select-faceted.js +1 -0
- package/dist/elements/a-select.d.ts +1 -0
- package/dist/elements/a-select.js +1 -0
- package/dist/elements/a-slider.css +1 -1
- package/dist/elements/a-slider.d.ts +11 -3
- package/dist/elements/a-slider.js +244 -242
- package/dist/elements/a-steps.d.ts +1 -0
- package/dist/elements/a-steps.js +1 -0
- package/dist/elements/a-switch.css +1 -1
- package/dist/elements/a-tab.css +1 -1
- package/dist/elements/a-tabpanel.d.ts +2 -2
- package/dist/elements/a-tabpanel.js +13 -3
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.d.ts +1 -1
- package/dist/elements/a-tabs.js +6 -4
- package/dist/elements/a-tag.css +1 -1
- package/dist/elements/a-tag.d.ts +1 -0
- package/dist/elements/a-tag.js +1 -0
- package/dist/elements/a-text.css +1 -1
- package/dist/elements/a-text.d.ts +6 -43
- package/dist/elements/a-text.js +110 -193
- package/dist/elements/a-title.css +1 -1
- package/dist/elements/a-title.d.ts +1 -0
- package/dist/elements/a-title.js +1 -0
- package/dist/elements/a-toast.css +1 -1
- package/dist/elements/a-toast.d.ts +10 -45
- package/dist/elements/a-toast.js +80 -66
- package/dist/elements/a-toaster.css +1 -1
- package/dist/elements/a-toaster.js +9 -44
- package/dist/elements/a-tooltip.css +1 -1
- package/dist/elements/a-tooltip.d.ts +1 -1
- package/dist/elements/a-tooltip.js +142 -136
- package/dist/elements/copy-behavior.d.ts +1 -1
- package/dist/elements/copy-behavior.js +21 -21
- package/dist/elements/index.d.ts +9 -3
- package/dist/elements/index.js +6 -6
- package/dist/elements/input-focus-modality.d.ts +15 -0
- package/dist/elements/input-focus-modality.js +44 -0
- package/dist/elements/input-trailing-actions.d.ts +5 -0
- package/dist/elements/input-trailing-actions.js +38 -0
- package/dist/elements/shadow-aria.d.ts +2 -0
- package/dist/elements/shadow-aria.js +2 -1
- package/dist/elements/value-input.d.ts +3 -0
- package/dist/elements/value-input.js +12 -0
- package/dist/general_types.d.ts +103 -96
- package/dist/index.d.ts +1 -1
- package/dist/input-events.d.ts +4 -0
- package/dist/input-events.js +15 -0
- package/dist/jsx-runtime.d.ts +70 -23
- package/dist/jsx-runtime.js +26 -19
- package/dist/native-event-props.d.ts +2 -0
- package/dist/native-event-props.js +10 -0
- package/dist/reset.css +1 -1
- package/dist/theme-antithesis.css +10 -1
- package/dist/theme-antune.css +1 -1
- package/docs/accessibility.md +17 -3
- package/docs/changelog.md +384 -2
- package/docs/comparison.md +2 -2
- package/docs/components/avatar.md +5 -0
- package/docs/components/banner.md +117 -187
- package/docs/components/breadcrumbs.md +9 -6
- package/docs/components/button.md +22 -0
- package/docs/components/card.md +74 -28
- package/docs/components/checkbox.md +9 -10
- package/docs/components/dialog.md +83 -32
- package/docs/components/expander.md +198 -435
- package/docs/components/icon.md +11 -17
- package/docs/components/input-autocomplete.md +152 -52
- package/docs/components/input-date.md +175 -148
- package/docs/components/input-time.md +81 -38
- package/docs/components/input.md +343 -349
- package/docs/components/loader.md +2 -1
- package/docs/components/menu.md +35 -6
- package/docs/components/panel.md +18 -0
- package/docs/components/progress.md +83 -49
- package/docs/components/radio.md +39 -43
- package/docs/components/select-faceted.md +54 -7
- package/docs/components/select.md +390 -386
- package/docs/components/slider.md +136 -19
- package/docs/components/steps.md +6 -3
- package/docs/components/switch.md +8 -9
- package/docs/components/tabs.md +110 -104
- package/docs/components/text.md +110 -86
- package/docs/components/title.md +47 -33
- package/docs/components/toaster.md +242 -84
- package/docs/components/tooltip.md +204 -113
- package/docs/install-config.md +461 -96
- package/docs/packages/plot.md +228 -143
- package/docs/theming.md +29 -13
- package/package.json +43 -4
- package/theme-antithesis-license.md +66 -0
package/dist/elements/a-menu.js
CHANGED
|
@@ -3,10 +3,11 @@ import {
|
|
|
3
3
|
SYNC_POPUP_ARIA,
|
|
4
4
|
anchorRect,
|
|
5
5
|
isPopupAriaReceiver,
|
|
6
|
+
isComposingKey,
|
|
6
7
|
isModifiedNavigationKey,
|
|
7
8
|
setMenuPresence
|
|
8
9
|
} from "../anta_helpers";
|
|
9
|
-
import {
|
|
10
|
+
import { MENU_ITEM_SELECTOR, isMenuItemEl, isCustomMenuItem, ensureMenuItemKeyListener } from "./a-menu-item";
|
|
10
11
|
import { activeFocus, cycleFocus, focusParent, tabStops } from "./focus";
|
|
11
12
|
import { emitCopyRequest } from "./copy-behavior";
|
|
12
13
|
import "./a-menu.css";
|
|
@@ -18,131 +19,201 @@ const TYPEAHEAD_RESET = 500;
|
|
|
18
19
|
const SELF_ACTIVATING = 'a-button, a-input[button], button, a[href], input[type="button"], input[type="submit"], input[type="reset"], [role="button"]';
|
|
19
20
|
const EDITABLE_FIELD = "input:not([readonly]), textarea:not([readonly]), a-input:not([readonly]), a-input-time:not([readonly])";
|
|
20
21
|
const DECLARATIVE_TRIGGER = "[data-dialog-open], [data-dialog-close], [data-toast-dismiss], [data-banner-dismiss]";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let boundDoc = null;
|
|
24
|
-
let boundView = null;
|
|
25
|
-
let mouseX = null;
|
|
26
|
-
let mouseY = 0;
|
|
27
|
-
function trackMouse(e) {
|
|
28
|
-
mouseX = e.pointerType === "mouse" ? e.clientX : null;
|
|
29
|
-
if (mouseX !== null) mouseY = e.clientY;
|
|
30
|
-
}
|
|
31
|
-
function mouseInside(rect) {
|
|
32
|
-
return mouseX !== null && mouseX >= rect.left && mouseX < rect.right && mouseY >= rect.top && mouseY < rect.bottom;
|
|
33
|
-
}
|
|
34
|
-
function bindDocListeners(doc, view) {
|
|
35
|
-
if (docBound) return;
|
|
36
|
-
doc.addEventListener("pointerdown", onDocPointerDown, true);
|
|
37
|
-
doc.addEventListener("pointermove", trackMouse, { capture: true, passive: true });
|
|
38
|
-
doc.addEventListener("pointerleave", clearMouse);
|
|
39
|
-
doc.addEventListener("keydown", onDocKeyDown, true);
|
|
40
|
-
doc.addEventListener("keyup", onDocKeyUp, true);
|
|
41
|
-
doc.addEventListener("contextmenu", onDocContextMenu, true);
|
|
42
|
-
view.addEventListener("resize", onResize);
|
|
43
|
-
boundDoc = doc;
|
|
44
|
-
boundView = view;
|
|
45
|
-
docBound = true;
|
|
46
|
-
}
|
|
47
|
-
function unbindDocListeners() {
|
|
48
|
-
if (!docBound) return;
|
|
49
|
-
boundDoc?.removeEventListener("pointerdown", onDocPointerDown, true);
|
|
50
|
-
boundDoc?.removeEventListener("pointermove", trackMouse, true);
|
|
51
|
-
boundDoc?.removeEventListener("pointerleave", clearMouse);
|
|
52
|
-
boundDoc?.removeEventListener("keydown", onDocKeyDown, true);
|
|
53
|
-
boundDoc?.removeEventListener("keyup", onDocKeyUp, true);
|
|
54
|
-
boundDoc?.removeEventListener("contextmenu", onDocContextMenu, true);
|
|
55
|
-
boundView?.removeEventListener("resize", onResize);
|
|
56
|
-
boundDoc = null;
|
|
57
|
-
boundView = null;
|
|
58
|
-
docBound = false;
|
|
59
|
-
mouseX = null;
|
|
60
|
-
}
|
|
61
|
-
function clearMouse() {
|
|
62
|
-
mouseX = null;
|
|
63
|
-
}
|
|
64
|
-
function menuIndexForEvent(e, primaryClick = false) {
|
|
65
|
-
const path = e.composedPath();
|
|
66
|
-
for (let i = openStack.length - 1; i >= 0; i--) {
|
|
67
|
-
const m = openStack[i];
|
|
68
|
-
if (path.includes(m.surface)) return i;
|
|
69
|
-
const anchor = m.triggerAnchor;
|
|
70
|
-
if (!anchor) continue;
|
|
71
|
-
if (primaryClick && m.hasAttribute("context")) continue;
|
|
72
|
-
if (path.includes(anchor)) return i;
|
|
73
|
-
}
|
|
74
|
-
return -1;
|
|
22
|
+
function isElement(node) {
|
|
23
|
+
return !!node && node.nodeType === 1;
|
|
75
24
|
}
|
|
76
25
|
function pathCrossesTopLayerBeforeAnchor(e, anchor) {
|
|
77
26
|
for (const node of e.composedPath()) {
|
|
78
27
|
if (node === anchor) return false;
|
|
79
|
-
if (node
|
|
28
|
+
if (isElement(node) && node.localName === "dialog" && node.matches(":modal") || isElement(node) && node.matches(":popover-open"))
|
|
80
29
|
return true;
|
|
81
30
|
}
|
|
82
31
|
return false;
|
|
83
32
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
setMenuPresence({
|
|
91
|
-
isOpen: () => openStack.length > 0,
|
|
92
|
-
contains: nodeHitsMenus
|
|
93
|
-
});
|
|
94
|
-
function onDocPointerDown(e) {
|
|
95
|
-
trackMouse(e);
|
|
96
|
-
if (!openStack.length) return;
|
|
97
|
-
const index = menuIndexForEvent(e, e.button === 0);
|
|
98
|
-
openStack[index + 1]?.requestClose(e);
|
|
99
|
-
}
|
|
100
|
-
function onDocContextMenu(e) {
|
|
101
|
-
if (!openStack.length) return;
|
|
102
|
-
if (menuIndexForEvent(e) === -1) dismiss(e);
|
|
103
|
-
}
|
|
104
|
-
function onResize() {
|
|
105
|
-
if (!openStack.length) return;
|
|
106
|
-
dismiss();
|
|
33
|
+
const MENU_COORDINATION = /* @__PURE__ */ Symbol.for("anta.menu-coordination");
|
|
34
|
+
function coordination(doc) {
|
|
35
|
+
return doc[MENU_COORDINATION] ??= createCoordination(doc);
|
|
107
36
|
}
|
|
108
|
-
function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
37
|
+
function createCoordination(doc) {
|
|
38
|
+
const openStack = [];
|
|
39
|
+
let docBound = false;
|
|
40
|
+
let boundDoc = null;
|
|
41
|
+
let boundView = null;
|
|
42
|
+
let mouseX = null;
|
|
43
|
+
let mouseY = 0;
|
|
44
|
+
function trackMouse(e) {
|
|
45
|
+
mouseX = e.pointerType === "mouse" ? e.clientX : null;
|
|
46
|
+
if (mouseX !== null) mouseY = e.clientY;
|
|
47
|
+
}
|
|
48
|
+
function mouseInside(rect) {
|
|
49
|
+
return mouseX !== null && mouseX >= rect.left && mouseX < rect.right && mouseY >= rect.top && mouseY < rect.bottom;
|
|
50
|
+
}
|
|
51
|
+
function bindDocListeners(doc2, view) {
|
|
52
|
+
if (docBound) return;
|
|
53
|
+
doc2.addEventListener("pointerdown", onDocPointerDown, true);
|
|
54
|
+
doc2.addEventListener("pointermove", trackMouse, { capture: true, passive: true });
|
|
55
|
+
doc2.addEventListener("pointerleave", clearMouse);
|
|
56
|
+
doc2.addEventListener("keydown", onDocKeyDown, true);
|
|
57
|
+
doc2.addEventListener("keyup", onDocKeyUp, true);
|
|
58
|
+
doc2.addEventListener("contextmenu", onDocContextMenu, true);
|
|
59
|
+
view.addEventListener("resize", onResize);
|
|
60
|
+
boundDoc = doc2;
|
|
61
|
+
boundView = view;
|
|
62
|
+
docBound = true;
|
|
63
|
+
}
|
|
64
|
+
function unbindDocListeners() {
|
|
65
|
+
if (!docBound) return;
|
|
66
|
+
boundDoc?.removeEventListener("pointerdown", onDocPointerDown, true);
|
|
67
|
+
boundDoc?.removeEventListener("pointermove", trackMouse, true);
|
|
68
|
+
boundDoc?.removeEventListener("pointerleave", clearMouse);
|
|
69
|
+
boundDoc?.removeEventListener("keydown", onDocKeyDown, true);
|
|
70
|
+
boundDoc?.removeEventListener("keyup", onDocKeyUp, true);
|
|
71
|
+
boundDoc?.removeEventListener("contextmenu", onDocContextMenu, true);
|
|
72
|
+
boundView?.removeEventListener("resize", onResize);
|
|
73
|
+
boundDoc = null;
|
|
74
|
+
boundView = null;
|
|
75
|
+
docBound = false;
|
|
76
|
+
mouseX = null;
|
|
77
|
+
}
|
|
78
|
+
function clearMouse() {
|
|
79
|
+
mouseX = null;
|
|
80
|
+
}
|
|
81
|
+
function menuIndexForEvent(e, primaryClick = false) {
|
|
82
|
+
const path = e.composedPath();
|
|
83
|
+
for (let i = openStack.length - 1; i >= 0; i--) {
|
|
84
|
+
const m = openStack[i];
|
|
85
|
+
if (path.includes(m.surface)) return i;
|
|
86
|
+
const anchor = m.triggerAnchor;
|
|
87
|
+
if (!anchor) continue;
|
|
88
|
+
if (primaryClick && m.hasAttribute("context")) continue;
|
|
89
|
+
if (path.includes(anchor)) return i;
|
|
90
|
+
}
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
function nodeHitsMenus(node) {
|
|
94
|
+
for (const m of openStack) {
|
|
95
|
+
if (m.contains(node) || m.surface.contains(node)) return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const menuPresence = {
|
|
100
|
+
isOpen: (doc2) => openStack.some((menu) => !doc2 || menu.ownerDocument === doc2),
|
|
101
|
+
contains: nodeHitsMenus
|
|
102
|
+
};
|
|
103
|
+
setMenuPresence(menuPresence, doc);
|
|
104
|
+
function onDocPointerDown(e) {
|
|
105
|
+
trackMouse(e);
|
|
106
|
+
if (!openStack.length) return;
|
|
107
|
+
const index = menuIndexForEvent(e, e.button === 0);
|
|
108
|
+
openStack[index + 1]?.requestClose(e);
|
|
109
|
+
}
|
|
110
|
+
function onDocContextMenu(e) {
|
|
111
|
+
if (!openStack.length) return;
|
|
112
|
+
if (menuIndexForEvent(e) === -1) dismiss(e);
|
|
113
|
+
}
|
|
114
|
+
function onResize() {
|
|
115
|
+
if (!openStack.length) return;
|
|
116
|
+
dismiss();
|
|
117
|
+
}
|
|
118
|
+
function onDocKeyDown(e) {
|
|
119
|
+
const menu = openStack[openStack.length - 1];
|
|
120
|
+
if (!menu) return;
|
|
121
|
+
menu.handleKey(e);
|
|
122
|
+
}
|
|
123
|
+
function onDocKeyUp(e) {
|
|
124
|
+
const menu = openStack[openStack.length - 1];
|
|
125
|
+
if (!menu) return;
|
|
126
|
+
menu.handleKeyUp(e);
|
|
127
|
+
}
|
|
128
|
+
function dismiss(originEvent) {
|
|
129
|
+
openStack[0]?.requestClose(originEvent);
|
|
130
|
+
}
|
|
131
|
+
const parents = /* @__PURE__ */ new Map();
|
|
132
|
+
function watchParent(parent, refresh) {
|
|
133
|
+
const Observer = doc.defaultView?.MutationObserver;
|
|
134
|
+
if (!Observer) return;
|
|
135
|
+
let entry = parents.get(parent);
|
|
136
|
+
if (!entry) {
|
|
137
|
+
const callbacks2 = /* @__PURE__ */ new Set();
|
|
138
|
+
const observer3 = new Observer(() => {
|
|
139
|
+
for (const callback of callbacks2) callback();
|
|
141
140
|
});
|
|
141
|
+
observer3.observe(parent, { childList: true });
|
|
142
|
+
entry = { observer: observer3, refresh: callbacks2 };
|
|
143
|
+
parents.set(parent, entry);
|
|
142
144
|
}
|
|
145
|
+
const { observer: observer2, refresh: callbacks } = entry;
|
|
146
|
+
callbacks.add(refresh);
|
|
147
|
+
return () => {
|
|
148
|
+
if (!callbacks.delete(refresh) || callbacks.size) return;
|
|
149
|
+
observer2.disconnect();
|
|
150
|
+
parents.delete(parent);
|
|
151
|
+
};
|
|
143
152
|
}
|
|
153
|
+
const anchorToMenu = /* @__PURE__ */ new Map();
|
|
154
|
+
const frames = /* @__PURE__ */ new Map();
|
|
155
|
+
let observer;
|
|
156
|
+
function observe(menu, anchor) {
|
|
157
|
+
const previous = anchorToMenu.get(anchor);
|
|
158
|
+
if (previous && previous !== menu) {
|
|
159
|
+
previous.teardownListeners();
|
|
160
|
+
const frame = frames.get(anchor);
|
|
161
|
+
if (frame !== void 0) doc.defaultView?.cancelAnimationFrame(frame);
|
|
162
|
+
frames.delete(anchor);
|
|
163
|
+
observer?.unobserve(anchor);
|
|
164
|
+
}
|
|
165
|
+
anchorToMenu.set(anchor, menu);
|
|
166
|
+
const Observer = doc.defaultView?.IntersectionObserver;
|
|
167
|
+
if (!Observer) {
|
|
168
|
+
menu.setupListeners();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
observer ??= new Observer((entries) => {
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const menu2 = anchorToMenu.get(entry.target);
|
|
174
|
+
if (!menu2) continue;
|
|
175
|
+
const view = doc.defaultView;
|
|
176
|
+
if (!view) continue;
|
|
177
|
+
const previous2 = frames.get(entry.target);
|
|
178
|
+
if (previous2 !== void 0) view.cancelAnimationFrame(previous2);
|
|
179
|
+
frames.set(entry.target, view.requestAnimationFrame(() => {
|
|
180
|
+
frames.delete(entry.target);
|
|
181
|
+
if (anchorToMenu.get(entry.target) !== menu2 || !menu2.isConnected || menu2.ownerDocument !== doc) return;
|
|
182
|
+
if (entry.isIntersecting) menu2.setupListeners();
|
|
183
|
+
else if (menu2.listening) {
|
|
184
|
+
menu2.teardownListeners();
|
|
185
|
+
if (menu2.isOpen) menu2.close();
|
|
186
|
+
}
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
}, { root: null, rootMargin: "0px", threshold: 0 });
|
|
190
|
+
observer.observe(anchor);
|
|
191
|
+
}
|
|
192
|
+
function unobserve(menu, anchor) {
|
|
193
|
+
if (anchorToMenu.get(anchor) !== menu) return;
|
|
194
|
+
anchorToMenu.delete(anchor);
|
|
195
|
+
const frame = frames.get(anchor);
|
|
196
|
+
if (frame !== void 0) doc.defaultView?.cancelAnimationFrame(frame);
|
|
197
|
+
frames.delete(anchor);
|
|
198
|
+
observer?.unobserve(anchor);
|
|
199
|
+
if (!anchorToMenu.size) {
|
|
200
|
+
observer?.disconnect();
|
|
201
|
+
observer = void 0;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
stack: openStack,
|
|
206
|
+
observe,
|
|
207
|
+
unobserve,
|
|
208
|
+
watchParent,
|
|
209
|
+
bind: bindDocListeners,
|
|
210
|
+
unbind: unbindDocListeners,
|
|
211
|
+
get mouseX() {
|
|
212
|
+
return mouseX;
|
|
213
|
+
},
|
|
214
|
+
mouseInside
|
|
215
|
+
};
|
|
144
216
|
}
|
|
145
|
-
const lazyObserver = typeof IntersectionObserver !== "undefined" ? new IntersectionObserver(handleIntersection, { root: null, rootMargin: "0px", threshold: 0 }) : null;
|
|
146
217
|
class AMenuElement extends HTMLElementBase {
|
|
147
218
|
static observedAttributes = ["placement", "context", "coord", "offset", "nohover", "state", "stop-propagation", "role"];
|
|
148
219
|
/** Shadow-internal popover surface — the only thing we ever mutate. */
|
|
@@ -185,6 +256,8 @@ class AMenuElement extends HTMLElementBase {
|
|
|
185
256
|
// layout shifts. Those movements do not all produce a DOM observer callback, so
|
|
186
257
|
// this frame is active only while the menu is visible.
|
|
187
258
|
#anchorTrackingFrame;
|
|
259
|
+
// Preserve anchoring mode while a sibling trigger is temporarily absent.
|
|
260
|
+
#anchorRelative = false;
|
|
188
261
|
#lastAnchorRect;
|
|
189
262
|
// Vertical-follow baseline captured on each full position(): the frozen
|
|
190
263
|
// horizontal offset and the gap to the anchor's top. Tracking translates to hold
|
|
@@ -211,118 +284,22 @@ class AMenuElement extends HTMLElementBase {
|
|
|
211
284
|
} catch {
|
|
212
285
|
}
|
|
213
286
|
const shadow = this.attachShadow({ mode: "open" });
|
|
214
|
-
|
|
215
|
-
style.textContent = `
|
|
216
|
-
:host { display: contents; }
|
|
217
|
-
|
|
218
|
-
.container {
|
|
219
|
-
position: fixed;
|
|
220
|
-
left: 0;
|
|
221
|
-
top: 0;
|
|
222
|
-
margin: 0;
|
|
223
|
-
padding: 0;
|
|
224
|
-
box-sizing: border-box;
|
|
225
|
-
flex-direction: column;
|
|
226
|
-
min-width: max(var(--menu-min-width, 88px), var(--_anchor-width, 0px));
|
|
227
|
-
max-width: calc(100vw - ${2 * MARGIN}px);
|
|
228
|
-
max-height: calc(100dvh - ${2 * MARGIN}px);
|
|
229
|
-
overflow: hidden;
|
|
230
|
-
background: var(--menu-bg, Canvas);
|
|
231
|
-
color: var(--text-2, CanvasText);
|
|
232
|
-
border: var(--menu-border, 1px solid);
|
|
233
|
-
border-radius: var(--menu-radius, 8px);
|
|
234
|
-
box-shadow: var(--menu-shadow, 0 8px 24px rgba(0,0,0,0.2));
|
|
235
|
-
-webkit-backdrop-filter: var(--menu-backdrop-filter, blur(20px));
|
|
236
|
-
backdrop-filter: var(--menu-backdrop-filter, blur(20px));
|
|
237
|
-
outline: none;
|
|
238
|
-
|
|
239
|
-
/* Closed / exit state \u2014 fade + a tiny vertical settle (no horizontal
|
|
240
|
-
shift). This 140ms governs the EXIT (open \u2192 closed); the enter is a
|
|
241
|
-
touch quicker (see :popover-open below) so the menu feels snappy to
|
|
242
|
-
open but unhurried to dismiss. 'display' and 'overlay' transition with
|
|
243
|
-
'allow-discrete' so the menu stays in the top layer and visible while
|
|
244
|
-
it animates OUT, then hides. */
|
|
245
|
-
opacity: 0;
|
|
246
|
-
translate: 0 -4px;
|
|
247
|
-
transition:
|
|
248
|
-
opacity 140ms ease-out,
|
|
249
|
-
translate 140ms ease-out,
|
|
250
|
-
width var(--menu-width-duration, 160ms) ease,
|
|
251
|
-
display 140ms allow-discrete,
|
|
252
|
-
overlay 140ms allow-discrete;
|
|
253
|
-
}
|
|
254
|
-
.container:popover-open {
|
|
255
|
-
display: flex;
|
|
256
|
-
opacity: 1;
|
|
257
|
-
translate: 0 0;
|
|
258
|
-
/* Enter (closed \u2192 open) \u2014 quicker than the exit above. */
|
|
259
|
-
transition:
|
|
260
|
-
opacity 100ms ease-out,
|
|
261
|
-
translate 100ms ease-out,
|
|
262
|
-
width var(--menu-width-duration, 160ms) ease,
|
|
263
|
-
display 100ms allow-discrete,
|
|
264
|
-
overlay 100ms allow-discrete;
|
|
265
|
-
}
|
|
266
|
-
/* Enter: start from the closed state and transition in. */
|
|
267
|
-
@starting-style {
|
|
268
|
-
.container:popover-open { opacity: 0; translate: 0 -4px; }
|
|
269
|
-
}
|
|
270
|
-
slot[name="header"],
|
|
271
|
-
slot[name="footer"] {
|
|
272
|
-
display: block;
|
|
273
|
-
flex: none;
|
|
274
|
-
}
|
|
275
|
-
slot[name="header"]::slotted(a-menu-header),
|
|
276
|
-
slot[name="header"]::slotted(a-select-header),
|
|
277
|
-
slot[name="footer"]::slotted(a-menu-footer),
|
|
278
|
-
slot[name="footer"]::slotted(a-select-footer) {
|
|
279
|
-
display: block;
|
|
280
|
-
padding: var(--menu-padding, 4px);
|
|
281
|
-
}
|
|
282
|
-
/* The scrolling body \u2014 only the items scroll; the header / footer slots stay
|
|
283
|
-
fixed above and below it. min-height:0 lets it shrink so its own scrollbar
|
|
284
|
-
engages instead of the flex item overflowing. The 1px inter-item gap that
|
|
285
|
-
used to live on the container lives here now. */
|
|
286
|
-
.scroll {
|
|
287
|
-
flex: 1 1 auto;
|
|
288
|
-
min-height: 0;
|
|
289
|
-
display: flex;
|
|
290
|
-
flex-direction: column;
|
|
291
|
-
gap: 1px;
|
|
292
|
-
overflow-y: auto;
|
|
293
|
-
overscroll-behavior: contain;
|
|
294
|
-
scrollbar-width: thin;
|
|
295
|
-
/* The menu's padding lives here (not on the clipping container) so an item's
|
|
296
|
-
focus outline has room inside the scroll box instead of being clipped at
|
|
297
|
-
the edge; scroll-padding keeps a focused row off the edge when scrolled. */
|
|
298
|
-
padding: var(--menu-padding, 4px);
|
|
299
|
-
scroll-padding-block: var(--menu-padding, 4px);
|
|
300
|
-
/* Soft scroll edges: fade the content into the top / bottom only on the side
|
|
301
|
-
with more to scroll (the --fade-* vars, set from JS, are 0 otherwise). The
|
|
302
|
-
top also clears a hard --gap-top band (fully transparent, ahead of the ramp)
|
|
303
|
-
so a row scrolling up disappears cleanly under a pinned header / filter field
|
|
304
|
-
before the fade begins, instead of bleeding right against it. */
|
|
305
|
-
--fade-top: 0px;
|
|
306
|
-
--fade-bottom: 0px;
|
|
307
|
-
--gap-top: 0px;
|
|
308
|
-
-webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent var(--gap-top), #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent 100%);
|
|
309
|
-
mask-image: linear-gradient(to bottom, transparent 0, transparent var(--gap-top), #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent 100%);
|
|
310
|
-
}
|
|
311
|
-
`;
|
|
312
|
-
this.surface = document.createElement("div");
|
|
287
|
+
this.surface = this.doc.createElement("div");
|
|
313
288
|
this.surface.className = "container";
|
|
314
289
|
this.surface.setAttribute("part", "menu");
|
|
315
290
|
this.surface.setAttribute("popover", "manual");
|
|
316
|
-
const header =
|
|
291
|
+
const header = this.doc.createElement("slot");
|
|
317
292
|
header.setAttribute("name", "header");
|
|
318
|
-
|
|
293
|
+
header.part.add("header");
|
|
294
|
+
const scroll = this.doc.createElement("div");
|
|
319
295
|
scroll.className = "scroll";
|
|
320
296
|
scroll.setAttribute("part", "scroll");
|
|
321
|
-
scroll.append(
|
|
297
|
+
scroll.append(this.doc.createElement("slot"));
|
|
322
298
|
scroll.addEventListener("scroll", () => this.updateScrollFade(), { passive: true });
|
|
323
299
|
this.scrollEl = scroll;
|
|
324
|
-
const footer =
|
|
300
|
+
const footer = this.doc.createElement("slot");
|
|
325
301
|
footer.setAttribute("name", "footer");
|
|
302
|
+
footer.part.add("footer");
|
|
326
303
|
this.surface.append(header, scroll, footer);
|
|
327
304
|
this.surface.addEventListener("pointerenter", (e) => {
|
|
328
305
|
if (e.pointerType !== "mouse") return;
|
|
@@ -336,24 +313,53 @@ class AMenuElement extends HTMLElementBase {
|
|
|
336
313
|
if (this.isSubmenu && this.#isHover) this.scheduleClose();
|
|
337
314
|
});
|
|
338
315
|
this.surface.addEventListener("click", this.onSurfaceClick);
|
|
339
|
-
|
|
316
|
+
this.surface.addEventListener("mousedown", (e) => {
|
|
317
|
+
if (e.button === 0 && this.#comboAnchor) e.preventDefault();
|
|
318
|
+
});
|
|
319
|
+
shadow.append(this.surface);
|
|
320
|
+
}
|
|
321
|
+
#connectedDocument;
|
|
322
|
+
#observedAnchor;
|
|
323
|
+
#unwatchTrigger;
|
|
324
|
+
#stateFrame;
|
|
325
|
+
#positionFrame;
|
|
326
|
+
get #coordination() {
|
|
327
|
+
return coordination(this.#connectedDocument ?? this.doc);
|
|
328
|
+
}
|
|
329
|
+
#closeAll() {
|
|
330
|
+
const state = this.#coordination;
|
|
331
|
+
for (let i = state.stack.length - 1; i >= 0; i--) {
|
|
332
|
+
const menu = state.stack[i];
|
|
333
|
+
if (menu.isOpen && !menu._dismissNotified) menu.emitChange("closed");
|
|
334
|
+
menu._doHide();
|
|
335
|
+
}
|
|
336
|
+
state.stack.length = 0;
|
|
337
|
+
state.unbind();
|
|
340
338
|
}
|
|
341
339
|
connectedCallback() {
|
|
340
|
+
this.#connectedDocument = this.doc;
|
|
342
341
|
ensureMenuItemKeyListener(this.doc);
|
|
343
342
|
this.#syncStopPropagation();
|
|
344
343
|
this.#syncAccessibilityRegions();
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
lazyObserver?.observe(anchor);
|
|
344
|
+
this.#syncTriggerAnchor();
|
|
345
|
+
if (!this.isSubmenu && this.parentNode) {
|
|
346
|
+
this.#unwatchTrigger = this.#coordination.watchParent(this.parentNode, () => this.#syncTriggerAnchor());
|
|
349
347
|
}
|
|
350
348
|
this.#syncPopupAriaRelations();
|
|
351
349
|
queueMicrotask(() => {
|
|
352
350
|
if (this.isConnected) this.#syncPopupAriaRelations();
|
|
353
351
|
});
|
|
354
|
-
if (this.hasAttribute("state")) requestAnimationFrame(() =>
|
|
352
|
+
if (this.hasAttribute("state")) this.#stateFrame = this.view.requestAnimationFrame(() => {
|
|
353
|
+
this.#stateFrame = void 0;
|
|
354
|
+
this.syncState();
|
|
355
|
+
});
|
|
355
356
|
}
|
|
356
357
|
disconnectedCallback() {
|
|
358
|
+
this.#unwatchTrigger?.();
|
|
359
|
+
this.#unwatchTrigger = void 0;
|
|
360
|
+
const oldView = this.#connectedDocument?.defaultView;
|
|
361
|
+
if (this.#stateFrame !== void 0) oldView?.cancelAnimationFrame(this.#stateFrame);
|
|
362
|
+
this.#stateFrame = void 0;
|
|
357
363
|
this.hide(false);
|
|
358
364
|
this.teardownListeners();
|
|
359
365
|
this.cancelOpenTimer();
|
|
@@ -361,11 +367,39 @@ class AMenuElement extends HTMLElementBase {
|
|
|
361
367
|
this.accessibilityObserver?.disconnect();
|
|
362
368
|
this.accessibilityObserver = void 0;
|
|
363
369
|
this.#clearPopupAriaRelations();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
370
|
+
if (this.#observedAnchor) this.#coordination.unobserve(this, this.#observedAnchor);
|
|
371
|
+
this.#observedAnchor = void 0;
|
|
372
|
+
this.#connectedDocument = void 0;
|
|
373
|
+
}
|
|
374
|
+
/** Rebind only when the authored sibling node changes, after reconciliation.
|
|
375
|
+
* Keep open state intact; focus remains with the application replacing it. */
|
|
376
|
+
#syncTriggerAnchor() {
|
|
377
|
+
if (!this.isConnected) return;
|
|
378
|
+
const anchor = this.triggerAnchor ?? void 0;
|
|
379
|
+
if (anchor === this.#observedAnchor) return;
|
|
380
|
+
if (this.#observedAnchor) this.#coordination.unobserve(this, this.#observedAnchor);
|
|
381
|
+
this.teardownListeners();
|
|
382
|
+
this.listening = false;
|
|
383
|
+
this.cancelOpenTimer();
|
|
384
|
+
this.cancelCloseTimer();
|
|
385
|
+
this.#pendingComboActivate = null;
|
|
386
|
+
this.#observedAnchor = anchor;
|
|
387
|
+
if (anchor) this.#coordination.observe(this, anchor);
|
|
388
|
+
if (this._shown) {
|
|
389
|
+
if (this.#searchField) this.startComboObserver();
|
|
390
|
+
else {
|
|
391
|
+
this.comboObserver?.disconnect();
|
|
392
|
+
this.comboObserver = void 0;
|
|
393
|
+
if (this.activeItem) this.setActive(null);
|
|
394
|
+
}
|
|
395
|
+
if (this.#anchorRelative) {
|
|
396
|
+
if (anchor) {
|
|
397
|
+
this.position();
|
|
398
|
+
this.#startAnchorTracking();
|
|
399
|
+
} else this.#stopAnchorTracking();
|
|
400
|
+
}
|
|
368
401
|
}
|
|
402
|
+
this.#syncPopupAriaRelations();
|
|
369
403
|
}
|
|
370
404
|
attributeChangedCallback(name) {
|
|
371
405
|
if (name === "state") {
|
|
@@ -482,7 +516,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
482
516
|
/** Custom menu rows that support the combobox cursor and selection state. */
|
|
483
517
|
focusableItems() {
|
|
484
518
|
return this.navigableItems().filter(
|
|
485
|
-
(el) => el
|
|
519
|
+
(el) => isCustomMenuItem(el)
|
|
486
520
|
);
|
|
487
521
|
}
|
|
488
522
|
/** The rows the arrow keys / Home / End / initial-focus step between:
|
|
@@ -529,15 +563,21 @@ class AMenuElement extends HTMLElementBase {
|
|
|
529
563
|
scrollItemIntoView(item) {
|
|
530
564
|
const c = this.scrollEl;
|
|
531
565
|
if (!c) return;
|
|
532
|
-
const
|
|
566
|
+
const style = this.view.getComputedStyle(c);
|
|
567
|
+
const padding = (value) => {
|
|
568
|
+
const amount = Number.parseFloat(value);
|
|
569
|
+
return Number.isFinite(amount) ? amount * (value.endsWith("%") ? c.clientHeight / 100 : 1) : 0;
|
|
570
|
+
};
|
|
533
571
|
const cr = c.getBoundingClientRect();
|
|
572
|
+
const top = cr.top + c.clientTop + padding(style.scrollPaddingTop);
|
|
573
|
+
const bottom = cr.top + c.clientTop + c.clientHeight - padding(style.scrollPaddingBottom);
|
|
534
574
|
const ir = item.getBoundingClientRect();
|
|
535
|
-
if (ir.top <
|
|
536
|
-
else if (ir.bottom >
|
|
575
|
+
if (ir.top < top) c.scrollTop -= top - ir.top;
|
|
576
|
+
else if (ir.bottom > bottom) c.scrollTop += ir.bottom - bottom;
|
|
537
577
|
}
|
|
538
578
|
/** Fade the scrolling body's content into the top / bottom edges — but only on the
|
|
539
579
|
* side that actually has more to scroll, so a short (non-scrolling) menu and the
|
|
540
|
-
* true top / bottom stay crisp. Drives the
|
|
580
|
+
* true top / bottom stay crisp. Drives the private fade/gap properties the
|
|
541
581
|
* `.scroll` mask reads; runs on scroll and after every (re)position.
|
|
542
582
|
* Shadow-internal only. */
|
|
543
583
|
updateScrollFade() {
|
|
@@ -545,9 +585,9 @@ class AMenuElement extends HTMLElementBase {
|
|
|
545
585
|
if (!el) return;
|
|
546
586
|
const top = el.scrollTop > 1;
|
|
547
587
|
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 1;
|
|
548
|
-
el.style.setProperty("--fade-top", top ? "16px" : "0px");
|
|
549
|
-
el.style.setProperty("--gap-top", top ? "3px" : "0px");
|
|
550
|
-
el.style.setProperty("--fade-bottom", bottom ? "14px" : "0px");
|
|
588
|
+
el.style.setProperty("--_menu-fade-top", top ? "16px" : "0px");
|
|
589
|
+
el.style.setProperty("--_menu-gap-top", top ? "3px" : "0px");
|
|
590
|
+
el.style.setProperty("--_menu-fade-bottom", bottom ? "14px" : "0px");
|
|
551
591
|
}
|
|
552
592
|
/* ------------------------------ combobox mode ------------------------------ */
|
|
553
593
|
/** Combobox-anchor mode: the trigger anchor ITSELF is the filter field (it
|
|
@@ -645,13 +685,17 @@ class AMenuElement extends HTMLElementBase {
|
|
|
645
685
|
this.activeItem = item;
|
|
646
686
|
if (item) {
|
|
647
687
|
item.active = true;
|
|
648
|
-
|
|
688
|
+
this.scrollItemIntoView(item);
|
|
649
689
|
}
|
|
650
690
|
this.#syncPopupAriaRelations();
|
|
651
691
|
this.dispatchEvent(
|
|
652
692
|
new CustomEvent("activedescendant", { detail: { id: item?.id ?? null } })
|
|
653
693
|
);
|
|
654
694
|
}
|
|
695
|
+
#clearComboCursor() {
|
|
696
|
+
this.#pendingComboActivate = null;
|
|
697
|
+
if (this.activeItem) this.setActive(null);
|
|
698
|
+
}
|
|
655
699
|
/** Re-seat the cursor on the first option — but only once the filter has input.
|
|
656
700
|
* An empty filter (e.g. right after opening) shows NO active row, so the first
|
|
657
701
|
* ArrowDown is what steps onto the list; typing then keeps the top match active.
|
|
@@ -659,9 +703,10 @@ class AMenuElement extends HTMLElementBase {
|
|
|
659
703
|
* the seat target — the cursor lands on the first real option — but they stay
|
|
660
704
|
* arrow-reachable. */
|
|
661
705
|
resetActive() {
|
|
706
|
+
if (this.#comboAnchor) return this.#clearComboCursor();
|
|
707
|
+
this.#pendingComboActivate = null;
|
|
662
708
|
const q = this.#searchField?.value;
|
|
663
709
|
if (!q || !q.trim()) return this.setActive(null);
|
|
664
|
-
if (this.#comboAnchor) return this.setActive(null);
|
|
665
710
|
const items = this.focusableItems();
|
|
666
711
|
this.setActive(items.find((it) => !it.hasAttribute("data-menu-skip-active")) ?? items[0] ?? null);
|
|
667
712
|
}
|
|
@@ -672,6 +717,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
672
717
|
* instead. Any other key falls through to the input (typing). */
|
|
673
718
|
handleComboKey(e) {
|
|
674
719
|
const items = this.focusableItems();
|
|
720
|
+
if (this.activeItem && !items.includes(this.activeItem)) this.#clearComboCursor();
|
|
675
721
|
const idx = this.activeItem ? items.indexOf(this.activeItem) : -1;
|
|
676
722
|
switch (e.key) {
|
|
677
723
|
case "ArrowDown":
|
|
@@ -709,10 +755,14 @@ class AMenuElement extends HTMLElementBase {
|
|
|
709
755
|
/** Combobox keyup: activate the item a filter-field Enter armed. Paired with the
|
|
710
756
|
* keydown pre-request in `handleComboKey` so lazy content resolves in the gap. */
|
|
711
757
|
handleKeyUp(e) {
|
|
758
|
+
if (isComposingKey(e)) {
|
|
759
|
+
this.#pendingComboActivate = null;
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
712
762
|
if (e.key !== "Enter") return;
|
|
713
763
|
const el = this.#pendingComboActivate;
|
|
714
764
|
this.#pendingComboActivate = null;
|
|
715
|
-
el
|
|
765
|
+
if (el && this.focusableItems().includes(el)) el.click();
|
|
716
766
|
}
|
|
717
767
|
/* ============================ open / close ============================ */
|
|
718
768
|
/** Public imperative API. Routes through the same intent path as the
|
|
@@ -747,6 +797,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
747
797
|
* `statechange`; applies the visibility itself ONLY when uncontrolled and
|
|
748
798
|
* not vetoed — a controlled menu waits for the consumer to flip `state`. */
|
|
749
799
|
requestOpen(opts) {
|
|
800
|
+
if (!this.isConnected) return;
|
|
750
801
|
if (this._shown) {
|
|
751
802
|
this.show(opts?.coord, opts?.viaKeyboard, opts?.originEvent);
|
|
752
803
|
return;
|
|
@@ -759,14 +810,15 @@ class AMenuElement extends HTMLElementBase {
|
|
|
759
810
|
* uncontrolled and not vetoed. */
|
|
760
811
|
requestClose(originEvent) {
|
|
761
812
|
if (!this._shown) return;
|
|
762
|
-
const
|
|
813
|
+
const stack = this.#coordination.stack;
|
|
814
|
+
const idx = stack.indexOf(this);
|
|
763
815
|
if (idx !== -1) {
|
|
764
|
-
for (let i =
|
|
765
|
-
const m =
|
|
816
|
+
for (let i = stack.length - 1; i > idx; i--) {
|
|
817
|
+
const m = stack[i];
|
|
766
818
|
if (m.isOpen && !m._dismissNotified) m.emitChange("closed");
|
|
767
819
|
m._doHide();
|
|
768
820
|
}
|
|
769
|
-
|
|
821
|
+
stack.length = idx + 1;
|
|
770
822
|
}
|
|
771
823
|
const ok = this.emitChange("closed", { originEvent });
|
|
772
824
|
if (this.isControlled) {
|
|
@@ -778,50 +830,51 @@ class AMenuElement extends HTMLElementBase {
|
|
|
778
830
|
/** Apply OPEN to the DOM (no event) — used by uncontrolled intent and by the
|
|
779
831
|
* controlled `state` sync. */
|
|
780
832
|
show(coord, viaKeyboard = false, _originEvent) {
|
|
833
|
+
const stack = this.#coordination.stack;
|
|
781
834
|
if (this.isSubmenu) {
|
|
782
835
|
const parent = this.#ownerMenu;
|
|
783
836
|
if (parent) {
|
|
784
|
-
const pidx =
|
|
837
|
+
const pidx = stack.indexOf(parent);
|
|
785
838
|
if (pidx !== -1) {
|
|
786
|
-
for (let i =
|
|
787
|
-
const menu =
|
|
839
|
+
for (let i = stack.length - 1; i > pidx; i--) {
|
|
840
|
+
const menu = stack[i];
|
|
788
841
|
if (menu !== this && menu.isOpen && !menu._dismissNotified) menu.emitChange("closed");
|
|
789
842
|
menu._doHide();
|
|
790
843
|
}
|
|
791
|
-
|
|
844
|
+
stack.length = pidx + 1;
|
|
792
845
|
}
|
|
793
846
|
}
|
|
794
|
-
} else if (!
|
|
847
|
+
} else if (!stack.includes(this)) {
|
|
795
848
|
const anchor = this.triggerAnchor;
|
|
796
849
|
let container;
|
|
797
850
|
if (anchor)
|
|
798
|
-
for (let i =
|
|
799
|
-
if (
|
|
800
|
-
container =
|
|
851
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
852
|
+
if (stack[i] !== this && stack[i].contains(anchor)) {
|
|
853
|
+
container = stack[i];
|
|
801
854
|
break;
|
|
802
855
|
}
|
|
803
856
|
}
|
|
804
857
|
if (container) {
|
|
805
|
-
const idx =
|
|
806
|
-
for (let i =
|
|
807
|
-
const m =
|
|
858
|
+
const idx = stack.indexOf(container);
|
|
859
|
+
for (let i = stack.length - 1; i > idx; i--) {
|
|
860
|
+
const m = stack[i];
|
|
808
861
|
if (m.isOpen && !m._dismissNotified) m.emitChange("closed");
|
|
809
862
|
m._doHide();
|
|
810
863
|
}
|
|
811
|
-
|
|
864
|
+
stack.length = idx + 1;
|
|
812
865
|
} else {
|
|
813
|
-
closeAll();
|
|
866
|
+
this.#closeAll();
|
|
814
867
|
}
|
|
815
868
|
}
|
|
816
|
-
if (
|
|
869
|
+
if (stack.includes(this)) {
|
|
817
870
|
this.position(coord);
|
|
818
871
|
return;
|
|
819
872
|
}
|
|
820
|
-
const wasEmpty =
|
|
873
|
+
const wasEmpty = stack.length === 0;
|
|
821
874
|
this._doShow(coord, !wasEmpty);
|
|
822
|
-
|
|
875
|
+
stack.push(this);
|
|
823
876
|
if (wasEmpty) {
|
|
824
|
-
|
|
877
|
+
this.#coordination.bind(this.doc, this.view);
|
|
825
878
|
}
|
|
826
879
|
const search = this.#searchField;
|
|
827
880
|
if (search) {
|
|
@@ -833,40 +886,39 @@ class AMenuElement extends HTMLElementBase {
|
|
|
833
886
|
this.seatInitialFocus(viaKeyboard);
|
|
834
887
|
}
|
|
835
888
|
}
|
|
836
|
-
/**
|
|
837
|
-
*
|
|
838
|
-
* match. `childList` only — an item toggling its own `selected` (multi-select)
|
|
839
|
-
* mutates deep in its subtree, not this menu's direct children, so it won't
|
|
840
|
-
* spuriously reset the cursor. */
|
|
889
|
+
/** Reconcile changed results while filtering. Slotted filters observe direct
|
|
890
|
+
* children only; autocomplete also observes row content and disabled changes. */
|
|
841
891
|
startComboObserver() {
|
|
842
892
|
this.comboObserver?.disconnect();
|
|
843
|
-
this.comboObserver = new this.view.MutationObserver(() => {
|
|
893
|
+
this.comboObserver = new this.view.MutationObserver((records) => {
|
|
844
894
|
if (!this._shown || !this.#searchField) return;
|
|
895
|
+
if (this.#comboAnchor && !records.some(
|
|
896
|
+
(record) => record.type !== "attributes" || isMenuItemEl(record.target) && record.target.parentElement === this
|
|
897
|
+
)) return;
|
|
845
898
|
this.resetActive();
|
|
846
899
|
this.position(void 0, false, true);
|
|
847
900
|
});
|
|
848
|
-
this.comboObserver.observe(this, {
|
|
901
|
+
this.comboObserver.observe(this, {
|
|
902
|
+
childList: true,
|
|
903
|
+
...this.#comboAnchor ? { characterData: true, attributes: true, subtree: true, attributeFilter: ["disabled"] } : {}
|
|
904
|
+
});
|
|
849
905
|
}
|
|
850
906
|
/** Apply CLOSE without notifying this menu's owner. Notify nested menus so
|
|
851
907
|
* their controlled state follows the closing parent. */
|
|
852
908
|
hide(notifyDescendants = true) {
|
|
853
|
-
this.
|
|
854
|
-
|
|
855
|
-
this.setActive(null);
|
|
856
|
-
this._flippedTop = null;
|
|
857
|
-
this._flippedSide = null;
|
|
858
|
-
const idx = openStack.indexOf(this);
|
|
909
|
+
const stack = this.#coordination.stack;
|
|
910
|
+
const idx = stack.indexOf(this);
|
|
859
911
|
if (idx === -1) {
|
|
860
912
|
if (this._shown) this._doHide();
|
|
861
913
|
return;
|
|
862
914
|
}
|
|
863
|
-
for (let i =
|
|
864
|
-
const menu =
|
|
915
|
+
for (let i = stack.length - 1; i >= idx; i--) {
|
|
916
|
+
const menu = stack[i];
|
|
865
917
|
if (notifyDescendants && i > idx && menu.isOpen && !menu._dismissNotified) menu.emitChange("closed");
|
|
866
918
|
menu._doHide();
|
|
867
919
|
}
|
|
868
|
-
|
|
869
|
-
if (
|
|
920
|
+
stack.length = idx;
|
|
921
|
+
if (stack.length === 0) this.#coordination.unbind();
|
|
870
922
|
}
|
|
871
923
|
/** Shadow-only show: open the popover and position it. `instant` positions
|
|
872
924
|
* synchronously (no rAF), so a menu opening over an already-visible one is
|
|
@@ -893,15 +945,24 @@ class AMenuElement extends HTMLElementBase {
|
|
|
893
945
|
}
|
|
894
946
|
/** Shadow-only hide. */
|
|
895
947
|
_doHide() {
|
|
948
|
+
this.comboObserver?.disconnect();
|
|
949
|
+
this.comboObserver = void 0;
|
|
950
|
+
this.#clearComboCursor();
|
|
951
|
+
this._flippedTop = null;
|
|
952
|
+
this._flippedSide = null;
|
|
953
|
+
clearTimeout(this.typeTimer);
|
|
954
|
+
this.typeTimer = void 0;
|
|
955
|
+
this.typeBuffer = "";
|
|
956
|
+
if (this.#positionFrame !== void 0) (this.#connectedDocument?.defaultView ?? this.view).cancelAnimationFrame(this.#positionFrame);
|
|
957
|
+
this.#positionFrame = void 0;
|
|
896
958
|
const parent = this.parentElement?.closest("a-menu");
|
|
897
|
-
if (this._shown && parent && mouseX !== null && mouseInside(this.surface.getBoundingClientRect())) {
|
|
959
|
+
if (this._shown && parent && this.#coordination.mouseX !== null && this.#coordination.mouseInside(this.surface.getBoundingClientRect())) {
|
|
898
960
|
for (let ancestor = parent; ancestor; ancestor = ancestor.parentElement?.closest("a-menu")) {
|
|
899
|
-
|
|
900
|
-
ancestor.#holdHoverClose = true;
|
|
901
|
-
ancestor.cancelCloseTimer();
|
|
961
|
+
ancestor.holdHoverClose?.();
|
|
902
962
|
}
|
|
903
963
|
}
|
|
904
964
|
this.#stopAnchorTracking();
|
|
965
|
+
this.#anchorRelative = false;
|
|
905
966
|
if (this.surface.isConnected && this._shown) this.surface.hidePopover();
|
|
906
967
|
this._shown = false;
|
|
907
968
|
this.#holdHoverClose = false;
|
|
@@ -933,7 +994,8 @@ class AMenuElement extends HTMLElementBase {
|
|
|
933
994
|
* opening point. */
|
|
934
995
|
#startAnchorTracking(coord) {
|
|
935
996
|
this.#stopAnchorTracking();
|
|
936
|
-
|
|
997
|
+
this.#anchorRelative = !coord && !this.#isCoord;
|
|
998
|
+
if (!this.#anchorRelative) return;
|
|
937
999
|
const anchor = this.triggerAnchor;
|
|
938
1000
|
if (!anchor) return;
|
|
939
1001
|
this.#lastAnchorRect = this.#anchorRectValues(anchor);
|
|
@@ -970,7 +1032,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
970
1032
|
}
|
|
971
1033
|
#stopAnchorTracking() {
|
|
972
1034
|
if (this.#anchorTrackingFrame !== void 0) {
|
|
973
|
-
this.view.cancelAnimationFrame(this.#anchorTrackingFrame);
|
|
1035
|
+
(this.#connectedDocument?.defaultView ?? this.view).cancelAnimationFrame(this.#anchorTrackingFrame);
|
|
974
1036
|
this.#anchorTrackingFrame = void 0;
|
|
975
1037
|
}
|
|
976
1038
|
this.#lastAnchorRect = void 0;
|
|
@@ -995,7 +1057,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
995
1057
|
let left = MARGIN;
|
|
996
1058
|
let top = MARGIN;
|
|
997
1059
|
if (coord) {
|
|
998
|
-
surface.style.
|
|
1060
|
+
surface.style.setProperty("--_menu-max-height", `${Math.max(MIN_HEIGHT, vh - 2 * MARGIN)}px`);
|
|
999
1061
|
const box = surface.getBoundingClientRect();
|
|
1000
1062
|
left = coord[0];
|
|
1001
1063
|
if (left + box.width > vw - MARGIN) left = vw - box.width - MARGIN;
|
|
@@ -1006,7 +1068,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1006
1068
|
} else if (this.isSubmenu) {
|
|
1007
1069
|
const it = this.triggerAnchor ? anchorRect(this.triggerAnchor) : null;
|
|
1008
1070
|
if (!it) return;
|
|
1009
|
-
surface.style.
|
|
1071
|
+
surface.style.setProperty("--_menu-max-height", `${Math.max(MIN_HEIGHT, vh - 2 * MARGIN)}px`);
|
|
1010
1072
|
const box = surface.getBoundingClientRect();
|
|
1011
1073
|
left = it.right + this.#offset;
|
|
1012
1074
|
if (left + box.width > vw - MARGIN) {
|
|
@@ -1027,7 +1089,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1027
1089
|
const p = this.#placement;
|
|
1028
1090
|
if (p.startsWith("right") || p.startsWith("left")) {
|
|
1029
1091
|
surface.style.removeProperty("--_anchor-width");
|
|
1030
|
-
surface.style.
|
|
1092
|
+
surface.style.setProperty("--_menu-max-height", `${Math.max(MIN_HEIGHT, vh - 2 * MARGIN)}px`);
|
|
1031
1093
|
const box = surface.getBoundingClientRect();
|
|
1032
1094
|
const spaceRight = vw - a.right - 2 * MARGIN;
|
|
1033
1095
|
const spaceLeft = a.left - 2 * MARGIN;
|
|
@@ -1046,7 +1108,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1046
1108
|
const spaceBelow = vh - a.bottom - 2 * MARGIN;
|
|
1047
1109
|
const spaceAbove = a.top - 2 * MARGIN;
|
|
1048
1110
|
let onTop = p.startsWith("top");
|
|
1049
|
-
surface.style.
|
|
1111
|
+
surface.style.removeProperty("--_menu-max-height");
|
|
1050
1112
|
const natural = surface.scrollHeight;
|
|
1051
1113
|
if (reanchor && this._flippedTop !== null) {
|
|
1052
1114
|
onTop = this._flippedTop;
|
|
@@ -1056,7 +1118,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1056
1118
|
}
|
|
1057
1119
|
this._flippedTop = onTop;
|
|
1058
1120
|
const space = onTop ? spaceAbove : spaceBelow;
|
|
1059
|
-
surface.style.
|
|
1121
|
+
surface.style.setProperty("--_menu-max-height", `${Math.max(MIN_HEIGHT, Math.floor(space))}px`);
|
|
1060
1122
|
const box = surface.getBoundingClientRect();
|
|
1061
1123
|
const align = p.endsWith("end") ? "end" : p.endsWith("start") ? "start" : "center";
|
|
1062
1124
|
left = align === "center" ? a.left + a.width / 2 - box.width / 2 : align === "end" ? a.right - box.width : a.left;
|
|
@@ -1075,8 +1137,13 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1075
1137
|
this.#followGap = appliedTop - anchorRect(this.triggerAnchor).top;
|
|
1076
1138
|
}
|
|
1077
1139
|
};
|
|
1140
|
+
if (this.#positionFrame !== void 0) this.view.cancelAnimationFrame(this.#positionFrame);
|
|
1141
|
+
this.#positionFrame = void 0;
|
|
1078
1142
|
if (sync) run();
|
|
1079
|
-
else requestAnimationFrame(
|
|
1143
|
+
else this.#positionFrame = this.view.requestAnimationFrame(() => {
|
|
1144
|
+
this.#positionFrame = void 0;
|
|
1145
|
+
run();
|
|
1146
|
+
});
|
|
1080
1147
|
}
|
|
1081
1148
|
/* ====================== click / close contract ====================== */
|
|
1082
1149
|
/**
|
|
@@ -1097,20 +1164,20 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1097
1164
|
onSurfaceClick = (e) => {
|
|
1098
1165
|
for (const node of e.composedPath()) {
|
|
1099
1166
|
if (node === this.surface) break;
|
|
1100
|
-
if (node
|
|
1167
|
+
if (isElement(node) && node.localName === "a-menu" && node !== this) return;
|
|
1101
1168
|
}
|
|
1102
1169
|
let contain = false;
|
|
1103
1170
|
let exemptTrigger = false;
|
|
1104
1171
|
for (const node of e.composedPath()) {
|
|
1105
1172
|
if (node === this.surface) break;
|
|
1106
|
-
if (!(node
|
|
1173
|
+
if (!isElement(node)) continue;
|
|
1107
1174
|
if (node.matches(DECLARATIVE_TRIGGER)) exemptTrigger = true;
|
|
1108
|
-
if (node
|
|
1175
|
+
if (isCustomMenuItem(node) || node.hasAttribute("data-menu-close")) contain = true;
|
|
1109
1176
|
}
|
|
1110
1177
|
if (contain && !exemptTrigger) e.stopPropagation();
|
|
1111
1178
|
for (const node of e.composedPath()) {
|
|
1112
1179
|
if (node === this.surface) break;
|
|
1113
|
-
if (node
|
|
1180
|
+
if (isCustomMenuItem(node)) {
|
|
1114
1181
|
if (!node.hasAttribute("disabled") && !node.querySelector(":scope > a-menu") && node.closest("a-menu") === this) {
|
|
1115
1182
|
node.dispatchEvent(
|
|
1116
1183
|
new MouseEvent("menuselect", {
|
|
@@ -1127,9 +1194,9 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1127
1194
|
}
|
|
1128
1195
|
for (const node of e.composedPath()) {
|
|
1129
1196
|
if (node === this.surface) break;
|
|
1130
|
-
if (!(node
|
|
1197
|
+
if (!isElement(node)) continue;
|
|
1131
1198
|
if (node.hasAttribute("data-menu-open")) return;
|
|
1132
|
-
if (node
|
|
1199
|
+
if (isCustomMenuItem(node)) {
|
|
1133
1200
|
if (node.hasAttribute("disabled")) {
|
|
1134
1201
|
e.preventDefault();
|
|
1135
1202
|
return;
|
|
@@ -1147,7 +1214,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1147
1214
|
this.requestClose(e);
|
|
1148
1215
|
return;
|
|
1149
1216
|
}
|
|
1150
|
-
const root =
|
|
1217
|
+
const root = this.#coordination.stack[0] ?? this;
|
|
1151
1218
|
root.requestClose(e);
|
|
1152
1219
|
}
|
|
1153
1220
|
/** Event types the `stop-propagation` attribute contains at the surface. A bare
|
|
@@ -1175,6 +1242,10 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1175
1242
|
* (synthesizes a click → routed through onSurfaceClick). Combobox Enter (focus
|
|
1176
1243
|
* in the filter field) is handled here via handleComboKey / handleKeyUp. */
|
|
1177
1244
|
handleKey(e) {
|
|
1245
|
+
if (isComposingKey(e)) {
|
|
1246
|
+
this.#pendingComboActivate = null;
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1178
1249
|
if (isModifiedNavigationKey(e)) return;
|
|
1179
1250
|
const active = this.doc.activeElement;
|
|
1180
1251
|
if (e.key === "Escape") {
|
|
@@ -1244,7 +1315,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1244
1315
|
}
|
|
1245
1316
|
}
|
|
1246
1317
|
submenuOf(item) {
|
|
1247
|
-
if (!item || !(item
|
|
1318
|
+
if (!item || !isCustomMenuItem(item)) return null;
|
|
1248
1319
|
return item.querySelector(":scope > a-menu");
|
|
1249
1320
|
}
|
|
1250
1321
|
typeahead(ch, items) {
|
|
@@ -1270,7 +1341,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1270
1341
|
return [r.left, r.bottom];
|
|
1271
1342
|
}
|
|
1272
1343
|
setupListeners() {
|
|
1273
|
-
if (this.listening) return;
|
|
1344
|
+
if (this.listening || !this.isConnected) return;
|
|
1274
1345
|
const anchor = this.triggerAnchor;
|
|
1275
1346
|
if (!anchor) {
|
|
1276
1347
|
this.listening = true;
|
|
@@ -1314,14 +1385,22 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1314
1385
|
this.listening = false;
|
|
1315
1386
|
};
|
|
1316
1387
|
} else if (this.#comboAnchor) {
|
|
1388
|
+
const onInput = () => this.#clearComboCursor();
|
|
1389
|
+
anchor.addEventListener("input", onInput);
|
|
1317
1390
|
this.teardown = () => {
|
|
1391
|
+
anchor.removeEventListener("input", onInput);
|
|
1318
1392
|
this.listening = false;
|
|
1319
1393
|
};
|
|
1320
1394
|
this.listening = true;
|
|
1321
1395
|
return;
|
|
1322
1396
|
} else {
|
|
1323
1397
|
const onClick = (e) => {
|
|
1324
|
-
if (anchor.matches("
|
|
1398
|
+
if (anchor.matches(":disabled, [disabled], [loading]")) return;
|
|
1399
|
+
const path = e.composedPath();
|
|
1400
|
+
const anchorPath = path.slice(0, path.indexOf(anchor));
|
|
1401
|
+
if (anchor.matches("a-input") && anchorPath.some((node) => isElement(node) && node.getAttribute("slot") === "clear")) return;
|
|
1402
|
+
const field = anchor.shadowRoot?.querySelector('[part~="field"]');
|
|
1403
|
+
if (field && !anchorPath.includes(field)) return;
|
|
1325
1404
|
const viaKeyboard = e.detail === 0;
|
|
1326
1405
|
const coord = this.#isCoord ? viaKeyboard ? this.#anchorCoord(anchor) : [e.clientX, e.clientY] : void 0;
|
|
1327
1406
|
if (this._shown) this.requestClose(e);
|
|
@@ -1330,7 +1409,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1330
1409
|
const onKey = (e) => {
|
|
1331
1410
|
if (isModifiedNavigationKey(e)) return;
|
|
1332
1411
|
if (this._shown) return;
|
|
1333
|
-
if (anchor.
|
|
1412
|
+
if (anchor.matches(":disabled, [disabled], [loading]")) return;
|
|
1334
1413
|
const arrow = e.key === "ArrowDown" || e.key === "ArrowUp";
|
|
1335
1414
|
if (!arrow && anchor.matches(SELF_ACTIVATING)) return;
|
|
1336
1415
|
const editable = anchor.matches(EDITABLE_FIELD);
|
|
@@ -1369,7 +1448,7 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1369
1448
|
if (!this._shown) return;
|
|
1370
1449
|
if (this.#holdHoverClose) return;
|
|
1371
1450
|
if (this.#hasKeyboardFocusInside) return;
|
|
1372
|
-
if (
|
|
1451
|
+
if (this.#coordination.stack.some((m) => m !== this && (this.contains(m) || m.triggerAnchor != null && this.contains(m.triggerAnchor)))) {
|
|
1373
1452
|
this.cancelCloseTimer();
|
|
1374
1453
|
return;
|
|
1375
1454
|
}
|
|
@@ -1389,6 +1468,11 @@ class AMenuElement extends HTMLElementBase {
|
|
|
1389
1468
|
const active = this.doc.activeElement;
|
|
1390
1469
|
return !!active && this.contains(active) && active.matches(":focus-visible");
|
|
1391
1470
|
}
|
|
1471
|
+
holdHoverClose() {
|
|
1472
|
+
if (!this.isSubmenu || !this.#isHover || !this._shown || this.surface.matches(":hover")) return;
|
|
1473
|
+
this.#holdHoverClose = true;
|
|
1474
|
+
this.cancelCloseTimer();
|
|
1475
|
+
}
|
|
1392
1476
|
cancelOpenTimer() {
|
|
1393
1477
|
if (this.openTimer !== void 0) {
|
|
1394
1478
|
clearTimeout(this.openTimer);
|