@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 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.3';
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.3';
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.3/+esm';
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.3';
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.some((token2) => token2.toLowerCase() === lower)) {
1426
- return;
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 = getActiveElement(),
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 = getActiveElement();
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 getActiveElement() {
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 = getActiveElement2();
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 getActiveElement2() {
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.some((token2) => token2.toLowerCase() === lower)) {
2034
- return;
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
- if (direction) {
2344
- Object.assign(settings, { direction });
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
- if (!focusable) {
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[!this.#isSubmenu ? "trigger" : "item"]
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[!this.#isSubmenu ? "menu" : "submenu"];
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
- !this.#isSubmenu ? this.#triggerElement?.ariaExpanded !== "true" : event.currentTarget === this.#triggerElement
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
- "Enter",
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
- if (key !== "Enter" && key !== " ") {
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[!this.#isSubmenu ? "menu" : "submenu"];
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.3
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.0
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.9
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.0
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.1
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.6
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.0
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.1
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.3
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.3
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.some((token2) => token2.toLowerCase() === lower)) {
1424
- return;
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 = getActiveElement(),
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 = getActiveElement();
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 getActiveElement() {
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 = getActiveElement2();
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 getActiveElement2() {
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.some((token2) => token2.toLowerCase() === lower)) {
2032
- return;
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
- if (direction) {
2342
- Object.assign(settings, { direction });
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
- if (!focusable) {
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[!this.#isSubmenu ? "trigger" : "item"]
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[!this.#isSubmenu ? "menu" : "submenu"];
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
- !this.#isSubmenu ? this.#triggerElement?.ariaExpanded !== "true" : event.currentTarget === this.#triggerElement
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
- "Enter",
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
- if (key !== "Enter" && key !== " ") {
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[!this.#isSubmenu ? "menu" : "submenu"];
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.3
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.0
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.9
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.0
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.1
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.6
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.0
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.1
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",
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.0",
48
- "@y14e/portal": "^1.2.9",
49
- "@y14e/roving-tabindex": "^2.0.6",
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"