@y14e/menu 1.4.3 → 1.4.4
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/README.md +4 -4
- package/dist/index.cjs +103 -60
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +103 -60
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -10,14 +10,14 @@ npm i @y14e/menu
|
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
// npm
|
|
13
|
-
import Menu from '@y14e/menu@1.4.
|
|
13
|
+
import Menu from '@y14e/menu@1.4.4';
|
|
14
14
|
|
|
15
15
|
// CDNs
|
|
16
|
-
import Menu from 'https://esm.sh/@y14e/menu@1.4.
|
|
16
|
+
import Menu from 'https://esm.sh/@y14e/menu@1.4.4';
|
|
17
17
|
// or
|
|
18
|
-
import Menu from 'https://cdn.jsdelivr.net/npm/@y14e/menu@1.4.
|
|
18
|
+
import Menu from 'https://cdn.jsdelivr.net/npm/@y14e/menu@1.4.4/+esm';
|
|
19
19
|
// or
|
|
20
|
-
import Menu from 'https://esm.unpkg.com/@y14e/menu@1.4.
|
|
20
|
+
import Menu from 'https://esm.unpkg.com/@y14e/menu@1.4.4';
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Usage
|
package/dist/index.cjs
CHANGED
|
@@ -1422,11 +1422,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
|
|
|
1422
1422
|
const tokens = value ? parse(value).filter(Boolean) : [];
|
|
1423
1423
|
if (caseInsensitive) {
|
|
1424
1424
|
const lower = token.toLowerCase();
|
|
1425
|
-
if (tokens.
|
|
1426
|
-
|
|
1425
|
+
if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
|
|
1426
|
+
tokens.push(token);
|
|
1427
|
+
element.setAttribute(attribute, serialize(tokens));
|
|
1427
1428
|
}
|
|
1428
|
-
tokens.push(token);
|
|
1429
|
-
element.setAttribute(attribute, serialize(tokens));
|
|
1430
1429
|
return;
|
|
1431
1430
|
}
|
|
1432
1431
|
const set = new Set(tokens);
|
|
@@ -1459,6 +1458,62 @@ function saveAttributes(elements, attributes) {
|
|
|
1459
1458
|
});
|
|
1460
1459
|
}
|
|
1461
1460
|
|
|
1461
|
+
// node_modules/@y14e/button/dist/index.js
|
|
1462
|
+
var Button = class {
|
|
1463
|
+
#element;
|
|
1464
|
+
#controller = null;
|
|
1465
|
+
#isDestroyed = false;
|
|
1466
|
+
constructor(element) {
|
|
1467
|
+
if (!(element instanceof HTMLElement)) {
|
|
1468
|
+
throw new TypeError("Invalid element");
|
|
1469
|
+
}
|
|
1470
|
+
if (element.hasAttribute("data-button-initialized")) {
|
|
1471
|
+
console.warn("Already initialized");
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
this.#element = element;
|
|
1475
|
+
this.#initialize();
|
|
1476
|
+
}
|
|
1477
|
+
destroy() {
|
|
1478
|
+
if (this.#isDestroyed) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
this.#isDestroyed = true;
|
|
1482
|
+
this.#controller?.abort();
|
|
1483
|
+
this.#controller = null;
|
|
1484
|
+
this.#element.removeAttribute("data-button-initialized");
|
|
1485
|
+
}
|
|
1486
|
+
#initialize() {
|
|
1487
|
+
this.#controller = new AbortController();
|
|
1488
|
+
this.#element.addEventListener("keydown", this.#onKeyDown, {
|
|
1489
|
+
signal: this.#controller.signal
|
|
1490
|
+
});
|
|
1491
|
+
this.#element.setAttribute("data-button-initialized", "");
|
|
1492
|
+
}
|
|
1493
|
+
#onKeyDown = (event) => {
|
|
1494
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
1495
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
if (!["Enter", " "].includes(key)) {
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
const active = getActiveElement();
|
|
1502
|
+
if (!(active instanceof HTMLElement)) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
event.preventDefault();
|
|
1506
|
+
active.click();
|
|
1507
|
+
};
|
|
1508
|
+
};
|
|
1509
|
+
function getActiveElement() {
|
|
1510
|
+
let current = document.activeElement;
|
|
1511
|
+
while (current?.shadowRoot?.activeElement) {
|
|
1512
|
+
current = current.shadowRoot.activeElement;
|
|
1513
|
+
}
|
|
1514
|
+
return current;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1462
1517
|
// node_modules/@y14e/portal/dist/index.js
|
|
1463
1518
|
var snapshots2 = /* @__PURE__ */ new WeakMap();
|
|
1464
1519
|
function restoreAttributes2(elements) {
|
|
@@ -1610,7 +1665,7 @@ function getRelativeFocusable(container, offset3, options) {
|
|
|
1610
1665
|
container = document.body;
|
|
1611
1666
|
}
|
|
1612
1667
|
let {
|
|
1613
|
-
anchor =
|
|
1668
|
+
anchor = getActiveElement2(),
|
|
1614
1669
|
composed = false,
|
|
1615
1670
|
filter,
|
|
1616
1671
|
include,
|
|
@@ -1619,7 +1674,7 @@ function getRelativeFocusable(container, offset3, options) {
|
|
|
1619
1674
|
wrap = false
|
|
1620
1675
|
} = options;
|
|
1621
1676
|
if (!(anchor instanceof Element)) {
|
|
1622
|
-
const active =
|
|
1677
|
+
const active = getActiveElement2();
|
|
1623
1678
|
if (active instanceof Element) {
|
|
1624
1679
|
console.warn("Invalid anchor element. Fallback: active element.");
|
|
1625
1680
|
anchor = active;
|
|
@@ -1794,7 +1849,7 @@ function getComposedChildren(node) {
|
|
|
1794
1849
|
}
|
|
1795
1850
|
return getChildren(node);
|
|
1796
1851
|
}
|
|
1797
|
-
function
|
|
1852
|
+
function getActiveElement2() {
|
|
1798
1853
|
let current = document.activeElement;
|
|
1799
1854
|
while (current?.shadowRoot?.activeElement) {
|
|
1800
1855
|
current = current.shadowRoot.activeElement;
|
|
@@ -1925,7 +1980,7 @@ var Portal = class {
|
|
|
1925
1980
|
if (key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
1926
1981
|
return;
|
|
1927
1982
|
}
|
|
1928
|
-
const active =
|
|
1983
|
+
const active = getActiveElement22();
|
|
1929
1984
|
if (!(active instanceof Element)) {
|
|
1930
1985
|
return;
|
|
1931
1986
|
}
|
|
@@ -2009,7 +2064,7 @@ function containsComposed2(container, element) {
|
|
|
2009
2064
|
function focusElement(element) {
|
|
2010
2065
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
2011
2066
|
}
|
|
2012
|
-
function
|
|
2067
|
+
function getActiveElement22() {
|
|
2013
2068
|
let current = document.activeElement;
|
|
2014
2069
|
while (current?.shadowRoot?.activeElement) {
|
|
2015
2070
|
current = current.shadowRoot.activeElement;
|
|
@@ -2030,11 +2085,10 @@ function addTokenToAttribute2(element, attribute, token, options = {}) {
|
|
|
2030
2085
|
const tokens = value ? parse(value).filter(Boolean) : [];
|
|
2031
2086
|
if (caseInsensitive) {
|
|
2032
2087
|
const lower = token.toLowerCase();
|
|
2033
|
-
if (tokens.
|
|
2034
|
-
|
|
2088
|
+
if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
|
|
2089
|
+
tokens.push(token);
|
|
2090
|
+
element.setAttribute(attribute, serialize(tokens));
|
|
2035
2091
|
}
|
|
2036
|
-
tokens.push(token);
|
|
2037
|
-
element.setAttribute(attribute, serialize(tokens));
|
|
2038
2092
|
return;
|
|
2039
2093
|
}
|
|
2040
2094
|
const set = new Set(tokens);
|
|
@@ -2340,12 +2394,8 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
2340
2394
|
wrap = false;
|
|
2341
2395
|
}
|
|
2342
2396
|
const settings = { navigationOnly, typeahead, wrap };
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2346
|
-
if (selector) {
|
|
2347
|
-
Object.assign(settings, { selector });
|
|
2348
|
-
}
|
|
2397
|
+
direction && Object.assign(settings, { direction });
|
|
2398
|
+
selector && Object.assign(settings, { selector });
|
|
2349
2399
|
const roving = new RovingTabIndex(container, settings);
|
|
2350
2400
|
return () => roving.destroy();
|
|
2351
2401
|
}
|
|
@@ -2457,10 +2507,7 @@ var RovingTabIndex = class {
|
|
|
2457
2507
|
}
|
|
2458
2508
|
}
|
|
2459
2509
|
const focusable = target.at(newIndex);
|
|
2460
|
-
|
|
2461
|
-
return;
|
|
2462
|
-
}
|
|
2463
|
-
focusElement2(focusable);
|
|
2510
|
+
focusable && focusElement2(focusable);
|
|
2464
2511
|
};
|
|
2465
2512
|
#update(active) {
|
|
2466
2513
|
const current = new Set(this.#getFocusables());
|
|
@@ -2591,6 +2638,7 @@ var Menu = class _Menu {
|
|
|
2591
2638
|
#cleanupPortal = null;
|
|
2592
2639
|
#cleanupRovingTabIndex = null;
|
|
2593
2640
|
#cleanupPopover = null;
|
|
2641
|
+
#buttons = [];
|
|
2594
2642
|
constructor(root, options = {}, _internal = {}) {
|
|
2595
2643
|
if (!(root instanceof HTMLElement)) {
|
|
2596
2644
|
throw new TypeError("Invalid root element");
|
|
@@ -2608,7 +2656,7 @@ var Menu = class _Menu {
|
|
|
2608
2656
|
this.#isPortal = isPortal;
|
|
2609
2657
|
const { selector } = this.#settings;
|
|
2610
2658
|
this.#triggerElement = this.#rootElement.querySelector(
|
|
2611
|
-
selector[
|
|
2659
|
+
selector[this.#isSubmenu ? "item" : "trigger"]
|
|
2612
2660
|
);
|
|
2613
2661
|
this.#listElement = this.#rootElement.querySelector(
|
|
2614
2662
|
selector.list
|
|
@@ -2643,7 +2691,7 @@ var Menu = class _Menu {
|
|
|
2643
2691
|
items.push(item);
|
|
2644
2692
|
this.#radioItemElementsByGroup.set(group, items);
|
|
2645
2693
|
});
|
|
2646
|
-
const settings = this.#settings.popover[
|
|
2694
|
+
const settings = this.#settings.popover[this.#isSubmenu ? "submenu" : "menu"];
|
|
2647
2695
|
if (settings.arrow) {
|
|
2648
2696
|
this.#arrowElement = document.createElement("div");
|
|
2649
2697
|
this.#arrowElement.setAttribute("data-menu-arrow", "");
|
|
@@ -2679,6 +2727,10 @@ var Menu = class _Menu {
|
|
|
2679
2727
|
this.#cleanupPopover?.();
|
|
2680
2728
|
this.#cleanupPopover = null;
|
|
2681
2729
|
this.#clearSubmenuTimer();
|
|
2730
|
+
this.#buttons.forEach((button) => {
|
|
2731
|
+
button.destroy();
|
|
2732
|
+
});
|
|
2733
|
+
this.#buttons.length = 0;
|
|
2682
2734
|
_Menu.#menus = _Menu.#menus.filter((menu) => menu !== this);
|
|
2683
2735
|
this.#submenus && await Promise.all(this.#submenus.map((submenu) => submenu.destroy()));
|
|
2684
2736
|
if (!force) {
|
|
@@ -2761,6 +2813,7 @@ var Menu = class _Menu {
|
|
|
2761
2813
|
"aria-labelledby",
|
|
2762
2814
|
this.#triggerElement.id
|
|
2763
2815
|
);
|
|
2816
|
+
this.#buttons.push(new Button(this.#triggerElement));
|
|
2764
2817
|
}
|
|
2765
2818
|
this.#listElement.setAttribute("role", "menu");
|
|
2766
2819
|
this.#listElement.addEventListener("keydown", this.#onListKeyDown, {
|
|
@@ -2842,40 +2895,19 @@ var Menu = class _Menu {
|
|
|
2842
2895
|
#onTriggerClick = (event) => {
|
|
2843
2896
|
event.preventDefault();
|
|
2844
2897
|
this.#toggle(
|
|
2845
|
-
|
|
2898
|
+
this.#isSubmenu ? event.currentTarget === this.#triggerElement : this.#triggerElement?.ariaExpanded !== "true"
|
|
2846
2899
|
);
|
|
2847
2900
|
};
|
|
2848
2901
|
#onTriggerKeyDown = (event) => {
|
|
2849
2902
|
const { key } = event;
|
|
2850
|
-
if (![
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
...!this.#isSubmenu ? ["ArrowUp", "ArrowDown"] : ["ArrowRight"]
|
|
2854
|
-
].includes(key)) {
|
|
2903
|
+
if (!(this.#isSubmenu ? ["ArrowRight"] : ["ArrowUp", "ArrowDown"]).includes(
|
|
2904
|
+
key
|
|
2905
|
+
)) {
|
|
2855
2906
|
return;
|
|
2856
2907
|
}
|
|
2857
2908
|
event.preventDefault();
|
|
2858
2909
|
event.stopPropagation();
|
|
2859
|
-
|
|
2860
|
-
this.open();
|
|
2861
|
-
}
|
|
2862
|
-
const focusables = this.#itemElements.filter(isFocusable3);
|
|
2863
|
-
let index = 0;
|
|
2864
|
-
switch (key) {
|
|
2865
|
-
case "Enter":
|
|
2866
|
-
case " ":
|
|
2867
|
-
this.#triggerElement?.click();
|
|
2868
|
-
return;
|
|
2869
|
-
case "ArrowUp":
|
|
2870
|
-
index = -1;
|
|
2871
|
-
break;
|
|
2872
|
-
case "ArrowRight":
|
|
2873
|
-
return;
|
|
2874
|
-
case "ArrowDown":
|
|
2875
|
-
index = 0;
|
|
2876
|
-
break;
|
|
2877
|
-
}
|
|
2878
|
-
focusables.at(index)?.focus();
|
|
2910
|
+
this.open(key === "ArrowUp");
|
|
2879
2911
|
};
|
|
2880
2912
|
#onListKeyDown = (event) => {
|
|
2881
2913
|
const { shiftKey, key } = event;
|
|
@@ -3103,7 +3135,7 @@ var Menu = class _Menu {
|
|
|
3103
3135
|
if (!this.#triggerElement || !this.#listElement) {
|
|
3104
3136
|
return;
|
|
3105
3137
|
}
|
|
3106
|
-
const options = this.#settings.popover[
|
|
3138
|
+
const options = this.#settings.popover[this.#isSubmenu ? "submenu" : "menu"];
|
|
3107
3139
|
computePosition2(this.#triggerElement, this.#listElement, {
|
|
3108
3140
|
...options,
|
|
3109
3141
|
placement: options.placement
|
|
@@ -3197,7 +3229,7 @@ function isFocusable3(element) {
|
|
|
3197
3229
|
* WAI-ARIA compliant menu (menu button) pattern implementation in TypeScript.
|
|
3198
3230
|
* Supports checkbox item, radio item, and infinitely nested menus.
|
|
3199
3231
|
*
|
|
3200
|
-
* @version 1.4.
|
|
3232
|
+
* @version 1.4.4
|
|
3201
3233
|
* @author Yusuke Kamiyamane
|
|
3202
3234
|
* @license MIT
|
|
3203
3235
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3209,20 +3241,31 @@ function isFocusable3(element) {
|
|
|
3209
3241
|
(**
|
|
3210
3242
|
* Attributes Utils
|
|
3211
3243
|
*
|
|
3212
|
-
* @version 1.1.
|
|
3244
|
+
* @version 1.1.1
|
|
3213
3245
|
* @author Yusuke Kamiyamane
|
|
3214
3246
|
* @license MIT
|
|
3215
3247
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
3216
3248
|
* @see {@link https://github.com/y14e/attributes-utils}
|
|
3217
3249
|
*)
|
|
3218
3250
|
|
|
3251
|
+
@y14e/button/dist/index.js:
|
|
3252
|
+
(**
|
|
3253
|
+
* Button
|
|
3254
|
+
*
|
|
3255
|
+
* @version 1.0.0
|
|
3256
|
+
* @author Yusuke Kamiyamane
|
|
3257
|
+
* @license MIT
|
|
3258
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
3259
|
+
* @see {@link https://github.com/y14e/button}
|
|
3260
|
+
*)
|
|
3261
|
+
|
|
3219
3262
|
@y14e/portal/dist/index.js:
|
|
3220
3263
|
(**
|
|
3221
3264
|
* Portal
|
|
3222
3265
|
* Lightweight DOM portal (teleport) utility with fully focus management.
|
|
3223
3266
|
* Designed for accessible dialogs, menus, overlays, popovers.
|
|
3224
3267
|
*
|
|
3225
|
-
* @version 1.2.
|
|
3268
|
+
* @version 1.2.10
|
|
3226
3269
|
* @author Yusuke Kamiyamane
|
|
3227
3270
|
* @license MIT
|
|
3228
3271
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3234,7 +3277,7 @@ function isFocusable3(element) {
|
|
|
3234
3277
|
(**
|
|
3235
3278
|
* Attributes Utils
|
|
3236
3279
|
*
|
|
3237
|
-
* @version 1.1.
|
|
3280
|
+
* @version 1.1.1
|
|
3238
3281
|
* @author Yusuke Kamiyamane
|
|
3239
3282
|
* @license MIT
|
|
3240
3283
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3247,7 +3290,7 @@ function isFocusable3(element) {
|
|
|
3247
3290
|
* High-precision focus management utility with full composed tree support.
|
|
3248
3291
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
3249
3292
|
*
|
|
3250
|
-
* @version 4.3.
|
|
3293
|
+
* @version 4.3.2
|
|
3251
3294
|
* @author Yusuke Kamiyamane
|
|
3252
3295
|
* @license MIT
|
|
3253
3296
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3261,7 +3304,7 @@ function isFocusable3(element) {
|
|
|
3261
3304
|
* Lightweight roving tabindex utility with fully focus management.
|
|
3262
3305
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
3263
3306
|
*
|
|
3264
|
-
* @version 2.0.
|
|
3307
|
+
* @version 2.0.8
|
|
3265
3308
|
* @author Yusuke Kamiyamane
|
|
3266
3309
|
* @license MIT
|
|
3267
3310
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3273,7 +3316,7 @@ function isFocusable3(element) {
|
|
|
3273
3316
|
(**
|
|
3274
3317
|
* Attributes Utils
|
|
3275
3318
|
*
|
|
3276
|
-
* @version 1.1.
|
|
3319
|
+
* @version 1.1.1
|
|
3277
3320
|
* @author Yusuke Kamiyamane
|
|
3278
3321
|
* @license MIT
|
|
3279
3322
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3286,7 +3329,7 @@ function isFocusable3(element) {
|
|
|
3286
3329
|
* High-precision focus management utility with full composed tree support.
|
|
3287
3330
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
3288
3331
|
*
|
|
3289
|
-
* @version 4.3.
|
|
3332
|
+
* @version 4.3.2
|
|
3290
3333
|
* @author Yusuke Kamiyamane
|
|
3291
3334
|
* @license MIT
|
|
3292
3335
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.d.cts
CHANGED
|
@@ -5,7 +5,7 @@ import { Middleware, Placement } from '@floating-ui/dom';
|
|
|
5
5
|
* WAI-ARIA compliant menu (menu button) pattern implementation in TypeScript.
|
|
6
6
|
* Supports checkbox item, radio item, and infinitely nested menus.
|
|
7
7
|
*
|
|
8
|
-
* @version 1.4.
|
|
8
|
+
* @version 1.4.4
|
|
9
9
|
* @author Yusuke Kamiyamane
|
|
10
10
|
* @license MIT
|
|
11
11
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Middleware, Placement } from '@floating-ui/dom';
|
|
|
5
5
|
* WAI-ARIA compliant menu (menu button) pattern implementation in TypeScript.
|
|
6
6
|
* Supports checkbox item, radio item, and infinitely nested menus.
|
|
7
7
|
*
|
|
8
|
-
* @version 1.4.
|
|
8
|
+
* @version 1.4.4
|
|
9
9
|
* @author Yusuke Kamiyamane
|
|
10
10
|
* @license MIT
|
|
11
11
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.js
CHANGED
|
@@ -1420,11 +1420,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
|
|
|
1420
1420
|
const tokens = value ? parse(value).filter(Boolean) : [];
|
|
1421
1421
|
if (caseInsensitive) {
|
|
1422
1422
|
const lower = token.toLowerCase();
|
|
1423
|
-
if (tokens.
|
|
1424
|
-
|
|
1423
|
+
if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
|
|
1424
|
+
tokens.push(token);
|
|
1425
|
+
element.setAttribute(attribute, serialize(tokens));
|
|
1425
1426
|
}
|
|
1426
|
-
tokens.push(token);
|
|
1427
|
-
element.setAttribute(attribute, serialize(tokens));
|
|
1428
1427
|
return;
|
|
1429
1428
|
}
|
|
1430
1429
|
const set = new Set(tokens);
|
|
@@ -1457,6 +1456,62 @@ function saveAttributes(elements, attributes) {
|
|
|
1457
1456
|
});
|
|
1458
1457
|
}
|
|
1459
1458
|
|
|
1459
|
+
// node_modules/@y14e/button/dist/index.js
|
|
1460
|
+
var Button = class {
|
|
1461
|
+
#element;
|
|
1462
|
+
#controller = null;
|
|
1463
|
+
#isDestroyed = false;
|
|
1464
|
+
constructor(element) {
|
|
1465
|
+
if (!(element instanceof HTMLElement)) {
|
|
1466
|
+
throw new TypeError("Invalid element");
|
|
1467
|
+
}
|
|
1468
|
+
if (element.hasAttribute("data-button-initialized")) {
|
|
1469
|
+
console.warn("Already initialized");
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
this.#element = element;
|
|
1473
|
+
this.#initialize();
|
|
1474
|
+
}
|
|
1475
|
+
destroy() {
|
|
1476
|
+
if (this.#isDestroyed) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
this.#isDestroyed = true;
|
|
1480
|
+
this.#controller?.abort();
|
|
1481
|
+
this.#controller = null;
|
|
1482
|
+
this.#element.removeAttribute("data-button-initialized");
|
|
1483
|
+
}
|
|
1484
|
+
#initialize() {
|
|
1485
|
+
this.#controller = new AbortController();
|
|
1486
|
+
this.#element.addEventListener("keydown", this.#onKeyDown, {
|
|
1487
|
+
signal: this.#controller.signal
|
|
1488
|
+
});
|
|
1489
|
+
this.#element.setAttribute("data-button-initialized", "");
|
|
1490
|
+
}
|
|
1491
|
+
#onKeyDown = (event) => {
|
|
1492
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
1493
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (!["Enter", " "].includes(key)) {
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const active = getActiveElement();
|
|
1500
|
+
if (!(active instanceof HTMLElement)) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
event.preventDefault();
|
|
1504
|
+
active.click();
|
|
1505
|
+
};
|
|
1506
|
+
};
|
|
1507
|
+
function getActiveElement() {
|
|
1508
|
+
let current = document.activeElement;
|
|
1509
|
+
while (current?.shadowRoot?.activeElement) {
|
|
1510
|
+
current = current.shadowRoot.activeElement;
|
|
1511
|
+
}
|
|
1512
|
+
return current;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1460
1515
|
// node_modules/@y14e/portal/dist/index.js
|
|
1461
1516
|
var snapshots2 = /* @__PURE__ */ new WeakMap();
|
|
1462
1517
|
function restoreAttributes2(elements) {
|
|
@@ -1608,7 +1663,7 @@ function getRelativeFocusable(container, offset3, options) {
|
|
|
1608
1663
|
container = document.body;
|
|
1609
1664
|
}
|
|
1610
1665
|
let {
|
|
1611
|
-
anchor =
|
|
1666
|
+
anchor = getActiveElement2(),
|
|
1612
1667
|
composed = false,
|
|
1613
1668
|
filter,
|
|
1614
1669
|
include,
|
|
@@ -1617,7 +1672,7 @@ function getRelativeFocusable(container, offset3, options) {
|
|
|
1617
1672
|
wrap = false
|
|
1618
1673
|
} = options;
|
|
1619
1674
|
if (!(anchor instanceof Element)) {
|
|
1620
|
-
const active =
|
|
1675
|
+
const active = getActiveElement2();
|
|
1621
1676
|
if (active instanceof Element) {
|
|
1622
1677
|
console.warn("Invalid anchor element. Fallback: active element.");
|
|
1623
1678
|
anchor = active;
|
|
@@ -1792,7 +1847,7 @@ function getComposedChildren(node) {
|
|
|
1792
1847
|
}
|
|
1793
1848
|
return getChildren(node);
|
|
1794
1849
|
}
|
|
1795
|
-
function
|
|
1850
|
+
function getActiveElement2() {
|
|
1796
1851
|
let current = document.activeElement;
|
|
1797
1852
|
while (current?.shadowRoot?.activeElement) {
|
|
1798
1853
|
current = current.shadowRoot.activeElement;
|
|
@@ -1923,7 +1978,7 @@ var Portal = class {
|
|
|
1923
1978
|
if (key !== "Tab" || altKey || ctrlKey || metaKey) {
|
|
1924
1979
|
return;
|
|
1925
1980
|
}
|
|
1926
|
-
const active =
|
|
1981
|
+
const active = getActiveElement22();
|
|
1927
1982
|
if (!(active instanceof Element)) {
|
|
1928
1983
|
return;
|
|
1929
1984
|
}
|
|
@@ -2007,7 +2062,7 @@ function containsComposed2(container, element) {
|
|
|
2007
2062
|
function focusElement(element) {
|
|
2008
2063
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
2009
2064
|
}
|
|
2010
|
-
function
|
|
2065
|
+
function getActiveElement22() {
|
|
2011
2066
|
let current = document.activeElement;
|
|
2012
2067
|
while (current?.shadowRoot?.activeElement) {
|
|
2013
2068
|
current = current.shadowRoot.activeElement;
|
|
@@ -2028,11 +2083,10 @@ function addTokenToAttribute2(element, attribute, token, options = {}) {
|
|
|
2028
2083
|
const tokens = value ? parse(value).filter(Boolean) : [];
|
|
2029
2084
|
if (caseInsensitive) {
|
|
2030
2085
|
const lower = token.toLowerCase();
|
|
2031
|
-
if (tokens.
|
|
2032
|
-
|
|
2086
|
+
if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
|
|
2087
|
+
tokens.push(token);
|
|
2088
|
+
element.setAttribute(attribute, serialize(tokens));
|
|
2033
2089
|
}
|
|
2034
|
-
tokens.push(token);
|
|
2035
|
-
element.setAttribute(attribute, serialize(tokens));
|
|
2036
2090
|
return;
|
|
2037
2091
|
}
|
|
2038
2092
|
const set = new Set(tokens);
|
|
@@ -2338,12 +2392,8 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
2338
2392
|
wrap = false;
|
|
2339
2393
|
}
|
|
2340
2394
|
const settings = { navigationOnly, typeahead, wrap };
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2344
|
-
if (selector) {
|
|
2345
|
-
Object.assign(settings, { selector });
|
|
2346
|
-
}
|
|
2395
|
+
direction && Object.assign(settings, { direction });
|
|
2396
|
+
selector && Object.assign(settings, { selector });
|
|
2347
2397
|
const roving = new RovingTabIndex(container, settings);
|
|
2348
2398
|
return () => roving.destroy();
|
|
2349
2399
|
}
|
|
@@ -2455,10 +2505,7 @@ var RovingTabIndex = class {
|
|
|
2455
2505
|
}
|
|
2456
2506
|
}
|
|
2457
2507
|
const focusable = target.at(newIndex);
|
|
2458
|
-
|
|
2459
|
-
return;
|
|
2460
|
-
}
|
|
2461
|
-
focusElement2(focusable);
|
|
2508
|
+
focusable && focusElement2(focusable);
|
|
2462
2509
|
};
|
|
2463
2510
|
#update(active) {
|
|
2464
2511
|
const current = new Set(this.#getFocusables());
|
|
@@ -2589,6 +2636,7 @@ var Menu = class _Menu {
|
|
|
2589
2636
|
#cleanupPortal = null;
|
|
2590
2637
|
#cleanupRovingTabIndex = null;
|
|
2591
2638
|
#cleanupPopover = null;
|
|
2639
|
+
#buttons = [];
|
|
2592
2640
|
constructor(root, options = {}, _internal = {}) {
|
|
2593
2641
|
if (!(root instanceof HTMLElement)) {
|
|
2594
2642
|
throw new TypeError("Invalid root element");
|
|
@@ -2606,7 +2654,7 @@ var Menu = class _Menu {
|
|
|
2606
2654
|
this.#isPortal = isPortal;
|
|
2607
2655
|
const { selector } = this.#settings;
|
|
2608
2656
|
this.#triggerElement = this.#rootElement.querySelector(
|
|
2609
|
-
selector[
|
|
2657
|
+
selector[this.#isSubmenu ? "item" : "trigger"]
|
|
2610
2658
|
);
|
|
2611
2659
|
this.#listElement = this.#rootElement.querySelector(
|
|
2612
2660
|
selector.list
|
|
@@ -2641,7 +2689,7 @@ var Menu = class _Menu {
|
|
|
2641
2689
|
items.push(item);
|
|
2642
2690
|
this.#radioItemElementsByGroup.set(group, items);
|
|
2643
2691
|
});
|
|
2644
|
-
const settings = this.#settings.popover[
|
|
2692
|
+
const settings = this.#settings.popover[this.#isSubmenu ? "submenu" : "menu"];
|
|
2645
2693
|
if (settings.arrow) {
|
|
2646
2694
|
this.#arrowElement = document.createElement("div");
|
|
2647
2695
|
this.#arrowElement.setAttribute("data-menu-arrow", "");
|
|
@@ -2677,6 +2725,10 @@ var Menu = class _Menu {
|
|
|
2677
2725
|
this.#cleanupPopover?.();
|
|
2678
2726
|
this.#cleanupPopover = null;
|
|
2679
2727
|
this.#clearSubmenuTimer();
|
|
2728
|
+
this.#buttons.forEach((button) => {
|
|
2729
|
+
button.destroy();
|
|
2730
|
+
});
|
|
2731
|
+
this.#buttons.length = 0;
|
|
2680
2732
|
_Menu.#menus = _Menu.#menus.filter((menu) => menu !== this);
|
|
2681
2733
|
this.#submenus && await Promise.all(this.#submenus.map((submenu) => submenu.destroy()));
|
|
2682
2734
|
if (!force) {
|
|
@@ -2759,6 +2811,7 @@ var Menu = class _Menu {
|
|
|
2759
2811
|
"aria-labelledby",
|
|
2760
2812
|
this.#triggerElement.id
|
|
2761
2813
|
);
|
|
2814
|
+
this.#buttons.push(new Button(this.#triggerElement));
|
|
2762
2815
|
}
|
|
2763
2816
|
this.#listElement.setAttribute("role", "menu");
|
|
2764
2817
|
this.#listElement.addEventListener("keydown", this.#onListKeyDown, {
|
|
@@ -2840,40 +2893,19 @@ var Menu = class _Menu {
|
|
|
2840
2893
|
#onTriggerClick = (event) => {
|
|
2841
2894
|
event.preventDefault();
|
|
2842
2895
|
this.#toggle(
|
|
2843
|
-
|
|
2896
|
+
this.#isSubmenu ? event.currentTarget === this.#triggerElement : this.#triggerElement?.ariaExpanded !== "true"
|
|
2844
2897
|
);
|
|
2845
2898
|
};
|
|
2846
2899
|
#onTriggerKeyDown = (event) => {
|
|
2847
2900
|
const { key } = event;
|
|
2848
|
-
if (![
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
...!this.#isSubmenu ? ["ArrowUp", "ArrowDown"] : ["ArrowRight"]
|
|
2852
|
-
].includes(key)) {
|
|
2901
|
+
if (!(this.#isSubmenu ? ["ArrowRight"] : ["ArrowUp", "ArrowDown"]).includes(
|
|
2902
|
+
key
|
|
2903
|
+
)) {
|
|
2853
2904
|
return;
|
|
2854
2905
|
}
|
|
2855
2906
|
event.preventDefault();
|
|
2856
2907
|
event.stopPropagation();
|
|
2857
|
-
|
|
2858
|
-
this.open();
|
|
2859
|
-
}
|
|
2860
|
-
const focusables = this.#itemElements.filter(isFocusable3);
|
|
2861
|
-
let index = 0;
|
|
2862
|
-
switch (key) {
|
|
2863
|
-
case "Enter":
|
|
2864
|
-
case " ":
|
|
2865
|
-
this.#triggerElement?.click();
|
|
2866
|
-
return;
|
|
2867
|
-
case "ArrowUp":
|
|
2868
|
-
index = -1;
|
|
2869
|
-
break;
|
|
2870
|
-
case "ArrowRight":
|
|
2871
|
-
return;
|
|
2872
|
-
case "ArrowDown":
|
|
2873
|
-
index = 0;
|
|
2874
|
-
break;
|
|
2875
|
-
}
|
|
2876
|
-
focusables.at(index)?.focus();
|
|
2908
|
+
this.open(key === "ArrowUp");
|
|
2877
2909
|
};
|
|
2878
2910
|
#onListKeyDown = (event) => {
|
|
2879
2911
|
const { shiftKey, key } = event;
|
|
@@ -3101,7 +3133,7 @@ var Menu = class _Menu {
|
|
|
3101
3133
|
if (!this.#triggerElement || !this.#listElement) {
|
|
3102
3134
|
return;
|
|
3103
3135
|
}
|
|
3104
|
-
const options = this.#settings.popover[
|
|
3136
|
+
const options = this.#settings.popover[this.#isSubmenu ? "submenu" : "menu"];
|
|
3105
3137
|
computePosition2(this.#triggerElement, this.#listElement, {
|
|
3106
3138
|
...options,
|
|
3107
3139
|
placement: options.placement
|
|
@@ -3195,7 +3227,7 @@ function isFocusable3(element) {
|
|
|
3195
3227
|
* WAI-ARIA compliant menu (menu button) pattern implementation in TypeScript.
|
|
3196
3228
|
* Supports checkbox item, radio item, and infinitely nested menus.
|
|
3197
3229
|
*
|
|
3198
|
-
* @version 1.4.
|
|
3230
|
+
* @version 1.4.4
|
|
3199
3231
|
* @author Yusuke Kamiyamane
|
|
3200
3232
|
* @license MIT
|
|
3201
3233
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3207,20 +3239,31 @@ function isFocusable3(element) {
|
|
|
3207
3239
|
(**
|
|
3208
3240
|
* Attributes Utils
|
|
3209
3241
|
*
|
|
3210
|
-
* @version 1.1.
|
|
3242
|
+
* @version 1.1.1
|
|
3211
3243
|
* @author Yusuke Kamiyamane
|
|
3212
3244
|
* @license MIT
|
|
3213
3245
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
3214
3246
|
* @see {@link https://github.com/y14e/attributes-utils}
|
|
3215
3247
|
*)
|
|
3216
3248
|
|
|
3249
|
+
@y14e/button/dist/index.js:
|
|
3250
|
+
(**
|
|
3251
|
+
* Button
|
|
3252
|
+
*
|
|
3253
|
+
* @version 1.0.0
|
|
3254
|
+
* @author Yusuke Kamiyamane
|
|
3255
|
+
* @license MIT
|
|
3256
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
3257
|
+
* @see {@link https://github.com/y14e/button}
|
|
3258
|
+
*)
|
|
3259
|
+
|
|
3217
3260
|
@y14e/portal/dist/index.js:
|
|
3218
3261
|
(**
|
|
3219
3262
|
* Portal
|
|
3220
3263
|
* Lightweight DOM portal (teleport) utility with fully focus management.
|
|
3221
3264
|
* Designed for accessible dialogs, menus, overlays, popovers.
|
|
3222
3265
|
*
|
|
3223
|
-
* @version 1.2.
|
|
3266
|
+
* @version 1.2.10
|
|
3224
3267
|
* @author Yusuke Kamiyamane
|
|
3225
3268
|
* @license MIT
|
|
3226
3269
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3232,7 +3275,7 @@ function isFocusable3(element) {
|
|
|
3232
3275
|
(**
|
|
3233
3276
|
* Attributes Utils
|
|
3234
3277
|
*
|
|
3235
|
-
* @version 1.1.
|
|
3278
|
+
* @version 1.1.1
|
|
3236
3279
|
* @author Yusuke Kamiyamane
|
|
3237
3280
|
* @license MIT
|
|
3238
3281
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3245,7 +3288,7 @@ function isFocusable3(element) {
|
|
|
3245
3288
|
* High-precision focus management utility with full composed tree support.
|
|
3246
3289
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
3247
3290
|
*
|
|
3248
|
-
* @version 4.3.
|
|
3291
|
+
* @version 4.3.2
|
|
3249
3292
|
* @author Yusuke Kamiyamane
|
|
3250
3293
|
* @license MIT
|
|
3251
3294
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3259,7 +3302,7 @@ function isFocusable3(element) {
|
|
|
3259
3302
|
* Lightweight roving tabindex utility with fully focus management.
|
|
3260
3303
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
3261
3304
|
*
|
|
3262
|
-
* @version 2.0.
|
|
3305
|
+
* @version 2.0.8
|
|
3263
3306
|
* @author Yusuke Kamiyamane
|
|
3264
3307
|
* @license MIT
|
|
3265
3308
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3271,7 +3314,7 @@ function isFocusable3(element) {
|
|
|
3271
3314
|
(**
|
|
3272
3315
|
* Attributes Utils
|
|
3273
3316
|
*
|
|
3274
|
-
* @version 1.1.
|
|
3317
|
+
* @version 1.1.1
|
|
3275
3318
|
* @author Yusuke Kamiyamane
|
|
3276
3319
|
* @license MIT
|
|
3277
3320
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -3284,7 +3327,7 @@ function isFocusable3(element) {
|
|
|
3284
3327
|
* High-precision focus management utility with full composed tree support.
|
|
3285
3328
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
3286
3329
|
*
|
|
3287
|
-
* @version 4.3.
|
|
3330
|
+
* @version 4.3.2
|
|
3288
3331
|
* @author Yusuke Kamiyamane
|
|
3289
3332
|
* @license MIT
|
|
3290
3333
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@y14e/menu",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "WAI-ARIA compliant menu (menu button) pattern implementation in TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
"homepage": "https://github.com/y14e/menu#readme",
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@floating-ui/dom": "^1.7.6",
|
|
47
|
-
"@y14e/attributes-utils": "^1.1.
|
|
48
|
-
"@y14e/
|
|
49
|
-
"@y14e/
|
|
47
|
+
"@y14e/attributes-utils": "^1.1.1",
|
|
48
|
+
"@y14e/button": "^1.0.0",
|
|
49
|
+
"@y14e/portal": "^1.2.10",
|
|
50
|
+
"@y14e/roving-tabindex": "^2.0.8",
|
|
50
51
|
"bun-types": "latest",
|
|
51
52
|
"tsup": "^8.0.0",
|
|
52
53
|
"typescript": "^5.6.0"
|