@aurodesignsystem-dev/auro-formkit 0.0.0-pr1540.1 → 0.0.0-pr1541.1

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.
Files changed (64) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.min.js +22 -4
  7. package/components/combobox/demo/getting-started.min.js +22 -4
  8. package/components/combobox/demo/index.min.js +22 -4
  9. package/components/combobox/dist/index.js +3 -3
  10. package/components/combobox/dist/registered.js +3 -3
  11. package/components/counter/demo/customize.min.js +2 -2
  12. package/components/counter/demo/index.min.js +2 -2
  13. package/components/counter/dist/index.js +2 -2
  14. package/components/counter/dist/registered.js +2 -2
  15. package/components/datepicker/demo/customize.min.js +3 -3
  16. package/components/datepicker/demo/index.min.js +3 -3
  17. package/components/datepicker/dist/index.js +3 -3
  18. package/components/datepicker/dist/registered.js +3 -3
  19. package/components/dropdown/demo/customize.min.js +1 -1
  20. package/components/dropdown/demo/getting-started.min.js +1 -1
  21. package/components/dropdown/demo/index.min.js +1 -1
  22. package/components/dropdown/dist/index.js +1 -1
  23. package/components/dropdown/dist/registered.js +1 -1
  24. package/components/form/demo/customize.min.js +34 -16
  25. package/components/form/demo/getting-started.min.js +34 -16
  26. package/components/form/demo/index.min.js +34 -16
  27. package/components/form/demo/registerDemoDeps.min.js +34 -16
  28. package/components/input/demo/customize.min.js +1 -1
  29. package/components/input/demo/getting-started.min.js +1 -1
  30. package/components/input/demo/index.min.js +1 -1
  31. package/components/input/dist/index.js +1 -1
  32. package/components/input/dist/registered.js +1 -1
  33. package/components/menu/demo/accessibility.md +9 -4
  34. package/components/menu/demo/api.md +3 -2
  35. package/components/menu/demo/css-only.md +26 -19
  36. package/components/menu/demo/customize.md +306 -46
  37. package/components/menu/demo/design.md +1 -1
  38. package/components/menu/demo/getting-started.md +143 -6
  39. package/components/menu/demo/index.min.js +146 -2
  40. package/components/menu/demo/keyboard-behavior.md +83 -4
  41. package/components/menu/demo/voiceover.md +12 -9
  42. package/components/menu/demo/why-menu.md +8 -9
  43. package/components/menu/dist/auro-menu.d.ts +1 -1
  44. package/components/menu/dist/auro-menuoption.d.ts +11 -0
  45. package/components/menu/dist/index.js +19 -1
  46. package/components/menu/dist/registered.js +19 -1
  47. package/components/radio/demo/customize.min.js +1 -1
  48. package/components/radio/demo/getting-started.min.js +1 -1
  49. package/components/radio/demo/index.min.js +1 -1
  50. package/components/radio/dist/index.js +1 -1
  51. package/components/radio/dist/registered.js +1 -1
  52. package/components/select/demo/accessibility.md +5 -0
  53. package/components/select/demo/customize.md +2 -2
  54. package/components/select/demo/customize.min.js +23 -5
  55. package/components/select/demo/design.md +10 -10
  56. package/components/select/demo/getting-started.min.js +23 -5
  57. package/components/select/demo/index.md +1 -1
  58. package/components/select/demo/index.min.js +23 -5
  59. package/components/select/demo/keyboard-behavior.md +53 -58
  60. package/components/select/demo/voiceover.md +28 -15
  61. package/components/select/dist/index.js +4 -4
  62. package/components/select/dist/registered.js +4 -4
  63. package/custom-elements.json +1541 -1516
  64. package/package.json +1 -1
@@ -22,10 +22,13 @@
22
22
  <auro-anchorlink fluid href="#options" class="level2 body-xs">options</auro-anchorlink>
23
23
  <auro-anchorlink fluid href="#index" class="level2 body-xs">index</auro-anchorlink>
24
24
  <auro-anchorlink fluid href="#functions">Functions</auro-anchorlink>
25
- <auro-anchorlink fluid href="#makeSelection" class="level2 body-xs">makeSelection()</auro-anchorlink>
26
25
  <auro-anchorlink fluid href="#reset" class="level2 body-xs">reset()</auro-anchorlink>
26
+ <auro-anchorlink fluid href="#selectByValue" class="level2 body-xs">selectByValue()</auro-anchorlink>
27
27
  <auro-anchorlink fluid href="#updateActiveOption" class="level2 body-xs">updateActiveOption()</auro-anchorlink>
28
28
  <auro-anchorlink fluid href="#navigateOptions" class="level2 body-xs">navigateOptions()</auro-anchorlink>
29
+ <auro-anchorlink fluid href="#events">Events</auro-anchorlink>
30
+ <auro-anchorlink fluid href="#eventList" class="level2 body-xs">Event List</auro-anchorlink>
31
+ <auro-anchorlink fluid href="#eventAttribute" class="level2 body-xs">Option event attribute</auro-anchorlink>
29
32
  </auro-nav>
30
33
  </nav>
31
34
  <div class="mainContent">
@@ -308,14 +311,148 @@ Ensure your `tsconfig.json` uses `"moduleResolution": "bundler"` so TypeScript c
308
311
  <section>
309
312
  <auro-header level="2" id="functions">Functions</auro-header>
310
313
  <p>The following public methods are available on the <code>&lt;auro-menu&gt;</code> element.</p>
311
- <auro-header level="3" id="makeSelection">makeSelection()</auro-header>
312
- <p>Selects the currently active menu option. This is the programmatic equivalent of clicking on an option or pressing Enter while an option is focused.</p>
313
314
  <auro-header level="3" id="reset">reset()</auro-header>
314
315
  <p>Resets the menu to its initial state, clearing all selected options and restoring the value to <code>undefined</code>.</p>
315
- <auro-header level="3" id="updateActiveOption">updateActiveOption(option)</auro-header>
316
- <p>Sets the specified <code>&lt;auro-menuoption&gt;</code> element as the currently active (highlighted) option.</p>
316
+ <div class="exampleWrapper">
317
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/reset.html) -->
318
+ <!-- The below content is automatically added from ./../apiExamples/reset.html -->
319
+ <auro-menu id="resetExample" value="duration">
320
+ <auro-menuoption value="stops">Stops</auro-menuoption>
321
+ <auro-menuoption value="price">Price</auro-menuoption>
322
+ <auro-menuoption value="duration">Duration</auro-menuoption>
323
+ <auro-menuoption value="departure">Departure</auro-menuoption>
324
+ <auro-menuoption value="arrival">Arrival</auro-menuoption>
325
+ </auro-menu>
326
+ <br/><br/>
327
+ <auro-button id="resetExampleBtn">RESET</auro-button>
328
+ <!-- AURO-GENERATED-CONTENT:END -->
329
+ </div>
330
+ <auro-accordion alignRight>
331
+ <span slot="trigger">See code</span>
332
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/reset.html) -->
333
+ <!-- The below code snippet is automatically added from ./../apiExamples/reset.html -->
334
+ <pre class="language-html"><code class="language-html">&lt;auro-menu id="resetExample" value="duration"&gt;
335
+ &lt;auro-menuoption value="stops"&gt;Stops&lt;/auro-menuoption&gt;
336
+ &lt;auro-menuoption value="price"&gt;Price&lt;/auro-menuoption&gt;
337
+ &lt;auro-menuoption value="duration"&gt;Duration&lt;/auro-menuoption&gt;
338
+ &lt;auro-menuoption value="departure"&gt;Departure&lt;/auro-menuoption&gt;
339
+ &lt;auro-menuoption value="arrival"&gt;Arrival&lt;/auro-menuoption&gt;
340
+ &lt;/auro-menu&gt;
341
+ &lt;br/&gt;&lt;br/&gt;
342
+ &lt;auro-button id="resetExampleBtn"&gt;RESET&lt;/auro-button&gt;</code></pre>
343
+ <!-- AURO-GENERATED-CONTENT:END -->
344
+ </auro-accordion>
345
+ <auro-header level="3" id="selectByValue">selectByValue(value)</auro-header>
346
+ <p>Selects the option(s) whose <code>value</code> matches the argument. Accepts a string in single-select mode or an array of strings in multi-select mode; passing <code>undefined</code>, <code>null</code>, an empty string, or an empty array clears the selection.</p>
347
+ <div class="exampleWrapper">
348
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/select-by-value.html) -->
349
+ <!-- The below content is automatically added from ./../apiExamples/select-by-value.html -->
350
+ <auro-menu id="selectByValueMenu">
351
+ <auro-menuoption value="stops">Stops</auro-menuoption>
352
+ <auro-menuoption value="price">Price</auro-menuoption>
353
+ <auro-menuoption value="duration">Duration</auro-menuoption>
354
+ <auro-menuoption value="departure">Departure</auro-menuoption>
355
+ <auro-menuoption value="arrival">Arrival</auro-menuoption>
356
+ </auro-menu>
357
+ <br />
358
+ <auro-button id="selectByValueStops">Select "Stops"</auro-button>
359
+ <auro-button id="selectByValueDuration">Select "Duration"</auro-button>
360
+ <auro-button id="selectByValueClear">Clear selection</auro-button>
361
+ <!-- AURO-GENERATED-CONTENT:END -->
362
+ </div>
363
+ <auro-accordion alignRight>
364
+ <span slot="trigger">See code</span>
365
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/select-by-value.html) -->
366
+ <!-- The below code snippet is automatically added from ./../apiExamples/select-by-value.html -->
367
+ <pre class="language-html"><code class="language-html">&lt;auro-menu id="selectByValueMenu"&gt;
368
+ &lt;auro-menuoption value="stops"&gt;Stops&lt;/auro-menuoption&gt;
369
+ &lt;auro-menuoption value="price"&gt;Price&lt;/auro-menuoption&gt;
370
+ &lt;auro-menuoption value="duration"&gt;Duration&lt;/auro-menuoption&gt;
371
+ &lt;auro-menuoption value="departure"&gt;Departure&lt;/auro-menuoption&gt;
372
+ &lt;auro-menuoption value="arrival"&gt;Arrival&lt;/auro-menuoption&gt;
373
+ &lt;/auro-menu&gt;
374
+ &lt;br /&gt;
375
+ &lt;auro-button id="selectByValueStops"&gt;Select "Stops"&lt;/auro-button&gt;
376
+ &lt;auro-button id="selectByValueDuration"&gt;Select "Duration"&lt;/auro-button&gt;
377
+ &lt;auro-button id="selectByValueClear"&gt;Clear selection&lt;/auro-button&gt;</code></pre>
378
+ <!-- AURO-GENERATED-CONTENT:END -->
379
+ </auro-accordion>
380
+ <auro-header level="3" id="updateActiveOption">updateActiveOption(indexOrOption)</auro-header>
381
+ <p>Sets the specified option as the currently active (highlighted) option. Accepts either a numeric index into <code>options</code> or an <code>&lt;auro-menuoption&gt;</code> element reference.</p>
317
382
  <auro-header level="3" id="navigateOptions">navigateOptions(direction)</auro-header>
318
- <p>Moves the highlight to the next or previous option. Accepts <code>'up'</code> or <code>'down'</code> as the direction parameter.</p>
383
+ <p>Moves the active option to the next or previous option. Accepts <code>'up'</code> or <code>'down'</code> as the direction parameter.</p>
384
+ </section>
385
+ <section>
386
+ <auro-header level="2" id="events">Events</auro-header>
387
+ <p>The <code>&lt;auro-menu&gt;</code> element dispatches custom events that consumers can subscribe to via <code>addEventListener</code>. The example below wires listeners for the full event set and logs each firing.</p>
388
+ <auro-header level="3" id="eventList">Event List</auro-header>
389
+ <ul>
390
+ <li><code>auroMenu-selectedOption</code> — a new selection has been made.</li>
391
+ <li><code>auroMenu-activatedOption</code> — the active (highlighted) option changed via keyboard or hover.</li>
392
+ <li><code>auroMenu-optionsChange</code> — the set of available options was updated (e.g., after a slot change).</li>
393
+ <li><code>auroMenu-selectValueReset</code> — the menu's <code>value</code> was reset via <code>reset()</code>.</li>
394
+ <li><code>auroMenu-selectValueFailure</code> — a call to <code>selectByValue()</code> found no matching option.</li>
395
+ </ul>
396
+ <div class="exampleWrapper">
397
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/events.html) -->
398
+ <!-- The below content is automatically added from ./../apiExamples/events.html -->
399
+ <auro-menu id="eventsExampleMenu">
400
+ <auro-menuoption value="stops">Stops</auro-menuoption>
401
+ <auro-menuoption value="price">Price</auro-menuoption>
402
+ <auro-menuoption value="duration">Duration</auro-menuoption>
403
+ <auro-menuoption value="departure">Departure</auro-menuoption>
404
+ <auro-menuoption value="arrival">Arrival</auro-menuoption>
405
+ </auro-menu>
406
+ <br />
407
+ <auro-button id="eventsExampleReset">reset()</auro-button>
408
+ <auro-button id="eventsExampleBadValue">selectByValue("nonexistent")</auro-button>
409
+ <auro-button id="eventsExampleClearLog">Clear log</auro-button>
410
+ <pre id="eventsExampleLog" style="max-height: 200px; overflow: auto; padding: var(--ds-size-100, 8px); background: var(--ds-color-container-secondary, #f5f5f5); border-radius: 4px;">(interact with the menu above)</pre>
411
+ <!-- AURO-GENERATED-CONTENT:END -->
412
+ </div>
413
+ <auro-accordion alignRight>
414
+ <span slot="trigger">See code</span>
415
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/events.html) -->
416
+ <!-- The below code snippet is automatically added from ./../apiExamples/events.html -->
417
+ <pre class="language-html"><code class="language-html">&lt;auro-menu id="eventsExampleMenu"&gt;
418
+ &lt;auro-menuoption value="stops"&gt;Stops&lt;/auro-menuoption&gt;
419
+ &lt;auro-menuoption value="price"&gt;Price&lt;/auro-menuoption&gt;
420
+ &lt;auro-menuoption value="duration"&gt;Duration&lt;/auro-menuoption&gt;
421
+ &lt;auro-menuoption value="departure"&gt;Departure&lt;/auro-menuoption&gt;
422
+ &lt;auro-menuoption value="arrival"&gt;Arrival&lt;/auro-menuoption&gt;
423
+ &lt;/auro-menu&gt;
424
+ &lt;br /&gt;
425
+ &lt;auro-button id="eventsExampleReset"&gt;reset()&lt;/auro-button&gt;
426
+ &lt;auro-button id="eventsExampleBadValue"&gt;selectByValue("nonexistent")&lt;/auro-button&gt;
427
+ &lt;auro-button id="eventsExampleClearLog"&gt;Clear log&lt;/auro-button&gt;
428
+ &lt;pre id="eventsExampleLog" style="max-height: 200px; overflow: auto; padding: var(--ds-size-100, 8px); background: var(--ds-color-container-secondary, #f5f5f5); border-radius: 4px;"&gt;(interact with the menu above)&lt;/pre&gt;</code></pre>
429
+ <!-- AURO-GENERATED-CONTENT:END -->
430
+ </auro-accordion>
431
+ <auro-header level="3" id="eventAttribute">Option event attribute</auro-header>
432
+ <p>Attach an <code>event</code> attribute to any <code>auro-menuoption</code> to dispatch a named custom event from the menu when that option is selected. The generic <code>auroMenu-customEventFired</code> event is also dispatched on every firing — its <code>detail.option</code> references the option that triggered it, so a single listener can route on the <code>event</code> attribute value.</p>
433
+ <div class="exampleWrapper">
434
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/event-attribute.html) -->
435
+ <!-- The below content is automatically added from ./../apiExamples/event-attribute.html -->
436
+ <auro-menu id="eventAttrExample">
437
+ <auro-menuoption value="search" event="custom-flight-search">Search flights</auro-menuoption>
438
+ <auro-menuoption value="clear" event="custom-clear-filters">Clear filters</auro-menuoption>
439
+ <auro-menuoption value="save" event="custom-save-preferences">Save preferences</auro-menuoption>
440
+ </auro-menu>
441
+ <p>Last custom event: <output id="eventAttrOutput">(none)</output></p>
442
+ <!-- AURO-GENERATED-CONTENT:END -->
443
+ </div>
444
+ <auro-accordion alignRight>
445
+ <span slot="trigger">See code</span>
446
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/event-attribute.html) -->
447
+ <!-- The below code snippet is automatically added from ./../apiExamples/event-attribute.html -->
448
+ <pre class="language-html"><code class="language-html">&lt;auro-menu id="eventAttrExample"&gt;
449
+ &lt;auro-menuoption value="search" event="custom-flight-search"&gt;Search flights&lt;/auro-menuoption&gt;
450
+ &lt;auro-menuoption value="clear" event="custom-clear-filters"&gt;Clear filters&lt;/auro-menuoption&gt;
451
+ &lt;auro-menuoption value="save" event="custom-save-preferences"&gt;Save preferences&lt;/auro-menuoption&gt;
452
+ &lt;/auro-menu&gt;
453
+ &lt;p&gt;Last custom event: &lt;output id="eventAttrOutput"&gt;(none)&lt;/output&gt;&lt;/p&gt;</code></pre>
454
+ <!-- AURO-GENERATED-CONTENT:END -->
455
+ </auro-accordion>
319
456
  </section>
320
457
  </div>
321
458
  </div>
@@ -422,7 +422,7 @@ class AuroMenu extends AuroElement {
422
422
  },
423
423
 
424
424
  /**
425
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
425
+ * The currently selected menu option(s). In single-select mode this is a single `HTMLElement` (or `undefined` when nothing is selected). In multi-select mode this is an array of `HTMLElement`s.
426
426
  */
427
427
  optionSelected: {
428
428
  // Allow HTMLElement, HTMLElement[] arrays and undefined
@@ -992,6 +992,11 @@ class AuroMenu extends AuroElement {
992
992
  this.optionSelected = undefined;
993
993
  this._index = -1;
994
994
 
995
+ // Clear active option state so a follow-up open/navigation starts fresh
996
+ // rather than reusing a stale reference from before the reset.
997
+ this.items?.forEach((item) => item.classList.remove('active'));
998
+ this.optionActive = undefined;
999
+
995
1000
  // Reset UI state
996
1001
  this.updateItemsState(new Map([
997
1002
  [
@@ -1483,6 +1488,7 @@ class AuroMenuOption extends AuroElement {
1483
1488
  this.noCheckmark = false;
1484
1489
  this.disabled = false;
1485
1490
  this.noMatch = false;
1491
+ this.persistent = false;
1486
1492
 
1487
1493
  /**
1488
1494
  * @private
@@ -1504,6 +1510,12 @@ class AuroMenuOption extends AuroElement {
1504
1510
  type: Boolean,
1505
1511
  reflect: true
1506
1512
  },
1513
+
1514
+ /**
1515
+ * **Deprecated.** Use the `value` attribute on `auro-menu` to set the selected option when the menu renders (or call `menu.selectByValue(value)` programmatically). Support for the child-level `selected` attribute will be removed in a future major release.
1516
+ *
1517
+ * @deprecated Use the `value` attribute on `auro-menu` instead.
1518
+ */
1507
1519
  selected: {
1508
1520
  type: Boolean,
1509
1521
  reflect: true
@@ -1538,6 +1550,12 @@ class AuroMenuOption extends AuroElement {
1538
1550
  reflect: true,
1539
1551
  attribute: 'nomatch'
1540
1552
  },
1553
+
1554
+ /** When true, this option is excluded from `matchWord` DOM rewriting — useful for utility rows (e.g., "Add new…") that must render identically regardless of the current filter. */
1555
+ persistent: {
1556
+ type: Boolean,
1557
+ reflect: true
1558
+ },
1541
1559
  };
1542
1560
  }
1543
1561
 
@@ -1712,6 +1730,103 @@ class AuroMenuOption extends AuroElement {
1712
1730
  }
1713
1731
  }
1714
1732
 
1733
+ function auroMenuLoadingExample() {
1734
+ document.querySelector("#loadingExampleToggleButton").addEventListener("click", () => {
1735
+ document.querySelectorAll("#loadingExampleTable auro-menu").forEach(menu => menu.toggleAttribute("loading"));
1736
+ });
1737
+ }
1738
+
1739
+ function updateMatch() {
1740
+ let matchWordMenu = document.querySelector('#matchWordMenu');
1741
+
1742
+ matchWordMenu.matchWord = matchWordInput.value;
1743
+ }
1744
+
1745
+ function auroMenuMatchWordExample() {
1746
+ let matchWordInput = document.querySelector('#matchWordInput');
1747
+
1748
+ matchWordInput.addEventListener('keyup', updateMatch);
1749
+ }
1750
+
1751
+ function auroMenuResetExample() {
1752
+ const resetExampleBtnElem = document.querySelector('#resetExampleBtn');
1753
+ const resetExampleElem = document.querySelector('#resetExample');
1754
+
1755
+ if (resetExampleElem && resetExampleBtnElem) {
1756
+ resetExampleBtnElem.addEventListener('click', () => {
1757
+ resetExampleElem.reset();
1758
+ });
1759
+ }
1760
+ }
1761
+
1762
+ function auroMenuEventAttributeExample() {
1763
+ const menu = document.querySelector('#eventAttrExample');
1764
+ const output = document.querySelector('#eventAttrOutput');
1765
+
1766
+ menu.addEventListener('auroMenu-customEventFired', (evt) => {
1767
+ const eventName = evt.detail.option.getAttribute('event');
1768
+ output.textContent = `${eventName} (from "${evt.detail.option.textContent}")`;
1769
+ });
1770
+ }
1771
+
1772
+ function auroMenuEventsExample() {
1773
+ const menu = document.querySelector('#eventsExampleMenu');
1774
+ const log = document.querySelector('#eventsExampleLog');
1775
+ const resetBtn = document.querySelector('#eventsExampleReset');
1776
+ const badValueBtn = document.querySelector('#eventsExampleBadValue');
1777
+ const clearBtn = document.querySelector('#eventsExampleClearLog');
1778
+
1779
+ let entries = [];
1780
+ const write = (name, detail) => {
1781
+ const label = detail && detail.tagName ? detail.tagName.toLowerCase() : JSON.stringify(detail);
1782
+ entries.unshift(`${name}: ${label}`);
1783
+ log.textContent = entries.slice(0, 10).join('\n');
1784
+ };
1785
+
1786
+ menu.addEventListener('auroMenu-selectedOption', () => {
1787
+ write('auroMenu-selectedOption', menu.optionSelected);
1788
+ });
1789
+ menu.addEventListener('auroMenu-activatedOption', (evt) => {
1790
+ write('auroMenu-activatedOption', evt.detail);
1791
+ });
1792
+ menu.addEventListener('auroMenu-optionsChange', (evt) => {
1793
+ write('auroMenu-optionsChange', { count: (evt.detail.options || []).length });
1794
+ });
1795
+ menu.addEventListener('auroMenu-selectValueReset', () => {
1796
+ write('auroMenu-selectValueReset', { value: menu.value });
1797
+ });
1798
+ menu.addEventListener('auroMenu-selectValueFailure', (evt) => {
1799
+ write('auroMenu-selectValueFailure', evt.detail);
1800
+ });
1801
+
1802
+ resetBtn.addEventListener('click', () => menu.reset());
1803
+ badValueBtn.addEventListener('click', () => menu.selectByValue('nonexistent'));
1804
+ clearBtn.addEventListener('click', () => {
1805
+ entries = [];
1806
+ log.textContent = '(interact with the menu above)';
1807
+ });
1808
+ }
1809
+
1810
+ function auroMenuPersistentExample() {
1811
+ const input = document.querySelector('#persistentExampleInput');
1812
+ const menu = document.querySelector('#persistentExampleMenu');
1813
+
1814
+ input.addEventListener('keyup', () => {
1815
+ menu.matchWord = input.value;
1816
+ });
1817
+ }
1818
+
1819
+ function auroMenuSelectByValueExample() {
1820
+ const menu = document.querySelector('#selectByValueMenu');
1821
+ const stopsBtn = document.querySelector('#selectByValueStops');
1822
+ const durationBtn = document.querySelector('#selectByValueDuration');
1823
+ const clearBtn = document.querySelector('#selectByValueClear');
1824
+
1825
+ stopsBtn.addEventListener('click', () => menu.selectByValue('stops'));
1826
+ durationBtn.addEventListener('click', () => menu.selectByValue('duration'));
1827
+ clearBtn.addEventListener('click', () => menu.selectByValue(undefined));
1828
+ }
1829
+
1715
1830
  /* eslint-disable jsdoc/require-jsdoc, no-magic-numbers, no-param-reassign */
1716
1831
 
1717
1832
 
@@ -1720,7 +1835,36 @@ AuroMenuOption.register();
1720
1835
  AuroMenu.register('custom-menu');
1721
1836
  AuroMenuOption.register('custom-menuoption');
1722
1837
 
1723
- function initExamples(initCount) {
1838
+ const examples = [
1839
+ auroMenuLoadingExample,
1840
+ auroMenuMatchWordExample,
1841
+ auroMenuResetExample,
1842
+ auroMenuEventAttributeExample,
1843
+ auroMenuEventsExample,
1844
+ auroMenuPersistentExample,
1845
+ auroMenuSelectByValueExample
1846
+ ];
1847
+
1848
+ function initExamples(pending, initCount) {
1849
+ pending = pending || examples.slice();
1850
+ initCount = initCount || 0;
1851
+ const stillPending = [];
1852
+
1853
+ for (const fn of pending) {
1854
+ try {
1855
+ fn();
1856
+ } catch (err) {
1857
+ stillPending.push(fn);
1858
+ }
1859
+ }
1860
+
1861
+ if (stillPending.length && initCount <= 20) {
1862
+ setTimeout(() => {
1863
+ initExamples(stillPending, initCount + 1);
1864
+ }, 100);
1865
+ }
1724
1866
  }
1725
1867
 
1868
+ initExamples();
1869
+
1726
1870
  export { initExamples };
@@ -7,11 +7,90 @@
7
7
  <auro-header level="2" id="keyEvents">Key Events</auro-header>
8
8
  <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../docs/partials/keyEvents.md) -->
9
9
  <!-- The below content is automatically added from ./../docs/partials/keyEvents.md -->
10
- <!-- auro-menu does not implement direct keyboard event handling. -->
11
- <!-- Keyboard navigation is delegated to parent components (auro-select, auro-combobox) via their keyboard strategy files. -->
12
10
  <div class="note">
13
- <p><strong>Note:</strong> The <code>&lt;auro-menu&gt;</code> component does not handle keyboard events directly. Keyboard navigation is managed by the parent component (e.g., <code>&lt;auro-select&gt;</code>, <code>&lt;auro-combobox&gt;</code>) through their keyboard strategy classes, which call menu methods such as <code>navigateOptions()</code>, <code>makeSelection()</code>, and <code>updateActiveOption()</code>.</p>
14
- </div>
11
+ <p><strong>Note:</strong> <code>&lt;auro-menu&gt;</code> registers a <code>keydown</code> listener on its host. By default the menu is not focusable no <code>tabindex</code> on the host, and menuoptions are <code>tabindex="-1"</code> so the listener does not fire on its own. It fires whenever a <code>keydown</code> reaches the host, which happens in any of the following ways:</p>
12
+ <ul>
13
+ <li>A wrapping component focuses something inside the menu subtree and the event bubbles up to the host. In the standard integrations (<code>&lt;auro-select&gt;</code>, <code>&lt;auro-combobox&gt;</code>), keyboard behavior is primarily parent-managed rather than bubble-driven: focus stays on the parent's trigger/input, and the parent captures keys there and invokes <code>navigateOptions()</code> / <code>makeSelection()</code> on the menu directly.</li>
14
+ <li>A wrapping component dispatches or reroutes a <code>KeyboardEvent</code> at the menu programmatically (for example, <code>menu.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))</code>).</li>
15
+ <li>The author makes the host focusable (for example, by adding <code>tabindex="0"</code> to the <code>&lt;auro-menu&gt;</code> element) and focuses it. The host already has <code>role="listbox"</code>, so this yields a bare, focusable listbox that responds to the keys in the table below. For full combobox semantics use <code>&lt;auro-select&gt;</code> or <code>&lt;auro-combobox&gt;</code>, or replicate their <code>aria-activedescendant</code> wiring from a separate trigger.</li>
16
+ </ul>
17
+ <p>Keys not listed below (<code>Home</code>, <code>End</code>, <code>Escape</code>, type-ahead) are handled by the parent — see the <code>&lt;auro-select&gt;</code> and <code>&lt;auro-combobox&gt;</code> keyboard behavior docs. An "interactive option" is one that is not <code>disabled</code>, <code>hidden</code>, or <code>static</code> — these are always skipped during navigation and selection, along with <code>&lt;hr&gt;</code> dividers.</p>
18
+ </div>
19
+ <table>
20
+ <thead>
21
+ <tr>
22
+ <th>Key</th>
23
+ <th>Current State</th>
24
+ <th>Behavior</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <tr>
29
+ <td rowspan="5">ArrowDown</td>
30
+ <td>No option is active</td>
31
+ <td>The first interactive option becomes active.</td>
32
+ </tr>
33
+ <tr>
34
+ <td>An option is active, followed by interactive options</td>
35
+ <td>Advances the active option to the next interactive option.</td>
36
+ </tr>
37
+ <tr>
38
+ <td>The last interactive option is active</td>
39
+ <td>Wraps to the first interactive option.</td>
40
+ </tr>
41
+ <tr>
42
+ <td>Menu has no options or all options are non-interactive</td>
43
+ <td>No option becomes active; no <code>auroMenu-activatedOption</code> event fires.</td>
44
+ </tr>
45
+ <tr>
46
+ <td>Menu is in <code>loading</code> state</td>
47
+ <td>The rendered loading placeholder is inert (marked <code>disabled</code>) and is not part of <code>items</code>, so it cannot become active.</td>
48
+ </tr>
49
+ <tr>
50
+ <td rowspan="4">ArrowUp</td>
51
+ <td>No option is active</td>
52
+ <td>The last interactive option becomes active.</td>
53
+ </tr>
54
+ <tr>
55
+ <td>An option is active, preceded by interactive options</td>
56
+ <td>Advances the active option to the previous interactive option.</td>
57
+ </tr>
58
+ <tr>
59
+ <td>The first interactive option is active</td>
60
+ <td>Wraps to the last interactive option.</td>
61
+ </tr>
62
+ <tr>
63
+ <td>Menu has no options or all options are non-interactive</td>
64
+ <td>No option becomes active.</td>
65
+ </tr>
66
+ <tr>
67
+ <td rowspan="4">Enter</td>
68
+ <td>Single-select, active option is not currently selected</td>
69
+ <td>The active option is selected. Any previous selection is cleared. <code>auroMenu-selectedOption</code> fires.</td>
70
+ </tr>
71
+ <tr>
72
+ <td>Single-select, active option is already selected</td>
73
+ <td>No selection change, but <code>auroMenu-selectedOption</code> still fires so the parent can react (for example, close the dropdown).</td>
74
+ </tr>
75
+ <tr>
76
+ <td>Multi-select</td>
77
+ <td>The active option toggles selected. Other selections are preserved.</td>
78
+ </tr>
79
+ <tr>
80
+ <td>Active option is disabled, hidden, or static</td>
81
+ <td>Selection is a no-op.</td>
82
+ </tr>
83
+ <tr>
84
+ <td rowspan="2">Tab</td>
85
+ <td>An option is active</td>
86
+ <td>The active option is selected (same behavior as <code>Enter</code>).<div class="note"><strong>Note:</strong> <code>Tab</code> does not <code>preventDefault</code>, so focus continues to the next element in the tabindex sequence after selection.</div></td>
87
+ </tr>
88
+ <tr>
89
+ <td>No option is active</td>
90
+ <td>Focus moves to the next tabbable element; no selection change.</td>
91
+ </tr>
92
+ </tbody>
93
+ </table>
15
94
  <!-- AURO-GENERATED-CONTENT:END -->
16
95
  </div>
17
96
  </div>
@@ -4,20 +4,23 @@
4
4
  <auro-header level="3" id="voiceOverMenuContainer">Menu Container</auro-header>
5
5
  <p>When focus enters the menu, VoiceOver announces:</p>
6
6
  <ol>
7
- <li><strong>Role:</strong> <em>"list box"</em></li>
7
+ <li><strong>Role:</strong> <em>"list box"</em> — set on the root <code>&lt;auro-menu&gt;</code>; nested menus use <code>role="group"</code> with <code>aria-label="submenu"</code>.</li>
8
8
  <li><strong>Item count:</strong> The number of visible options (e.g., <em>"5 items"</em>)</li>
9
+ <li><strong>Multi-select:</strong> When <code>multiSelect</code> is set on the root menu, <code>aria-multiselectable="true"</code> is exposed so VoiceOver announces the listbox as multi-selectable.</li>
10
+ <li><strong>Loading:</strong> While <code>loading</code> is set, <code>aria-busy="true"</code> is exposed so VoiceOver signals that the option set is not yet stable.</li>
9
11
  </ol>
10
12
  <auro-header level="3" id="voiceOverMenuOptions">Menu Options</auro-header>
11
- <p>Each <code>auro-menuoption</code> announces the following when focused:</p>
13
+ <p>Each <code>auro-menuoption</code> announces the following when the parent field points at it (via <code>aria-activedescendant</code>) or when the user reads through the listbox:</p>
12
14
  <ol>
13
15
  <li><strong>Label:</strong> The option's text content</li>
14
16
  <li><strong>Role:</strong> <em>"option"</em></li>
15
- <li><strong>Position:</strong> Position in the list (e.g., <em>"3 of 5"</em>)</li>
16
- <li><strong>Selected state:</strong> <em>"selected"</em> if the option is currently selected</li>
17
- <li><strong>Disabled state:</strong> <em>"dimmed"</em> if the option is disabled</li>
18
- </ol>
17
+ <li><strong>Position:</strong> Position in the list (e.g., <em>"3 of 5"</em>) — when the menu is used inside <code>&lt;auro-select&gt;</code> (or another host that stamps them), each option receives <code>aria-setsize</code> and <code>aria-posinset</code>. Standalone <code>&lt;auro-menu&gt;</code> does not set these attributes itself; positional announcements outside a select/combobox host rely on VoiceOver's own listbox item count.</li>
18
+ <li><strong>Selected state:</strong> <em>"selected"</em> if <code>aria-selected="true"</code> is set on the option</li>
19
+ <li><strong>Disabled state:</strong> <em>"dimmed"</em> if <code>aria-disabled="true"</code> is set on the option</li>
20
+ </ol>
21
+ <div class="note"><strong>Note:</strong> <code>&lt;auro-menuoption&gt;</code> is not directly focusable — <code>tabindex="-1"</code> keeps it out of the tab order. In a select or combobox context, focus stays on the parent trigger and the active option is pointed at via <code>aria-activedescendant</code> (using <code>ariaActiveDescendantElement</code> to cross the shadow-root boundary).</div>
19
22
  <auro-header level="3" id="voiceOverSelection">Selection</auro-header>
20
- <p>When an option is selected via Enter or Space:</p>
23
+ <p>When an option is selected via Enter (or Tab, which selects and moves focus out of the menu):</p>
21
24
  <ul>
22
25
  <li>VoiceOver announces the option label followed by <em>"selected"</em></li>
23
26
  <li>In multi-select mode, previously selected options remain selected and their state is preserved</li>
@@ -26,8 +29,8 @@
26
29
  <ol>
27
30
  <li><strong>Enter the menu:</strong> Focus moves into the listbox; VoiceOver announces role and item count</li>
28
31
  <li><strong>Navigate options:</strong> Use Arrow Down/Up keys; VoiceOver announces each option's label, role, and position</li>
29
- <li><strong>Select an option:</strong> Press Enter or Space; VoiceOver confirms the selection</li>
30
- <li><strong>Exit:</strong> Tab out of the menu or press Escape (when used within a dropdown context)</li>
32
+ <li><strong>Select an option:</strong> Press Enter; VoiceOver confirms the selection</li>
33
+ <li><strong>Exit:</strong> Tab out of the menu (Tab also selects the active option). When the menu is used inside a dropdown context, the parent handles Escape.</li>
31
34
  </ol>
32
35
  <auro-header level="2" id="voiceOverNestedMenus">Nested Menus</auro-header>
33
36
  <p>When a nested <code>auro-menu</code> is encountered, VoiceOver announces a new listbox context. Each nested menu maintains its own option count and position tracking.</p>
@@ -4,7 +4,7 @@
4
4
  <p>Custom list implementations often fail to provide correct ARIA semantics, keyboard navigation, or screen reader announcements.</p>
5
5
  <p><code>auro-menu</code> provides:</p>
6
6
  <ul>
7
- <li><strong>Keyboard navigation</strong> — Arrow keys move between options, Enter selects, Escape cancels, Home/End jump to the first and last options.</li>
7
+ <li><strong>Keyboard navigation</strong> — Arrow Up/Down move the active option; Enter selects it; Tab selects and moves focus out of the menu.</li>
8
8
  <li><strong>Active option tracking</strong> — The currently highlighted option is tracked programmatically, enabling <code>aria-activedescendant</code> patterns in parent components like <code>auro-select</code> and <code>auro-combobox</code>.</li>
9
9
  <li><strong>Multi-select semantics</strong> — When <code>multiSelect</code> is enabled, <code>aria-multiselectable</code> is set on the menu.</li>
10
10
  <li><strong>Loading state</strong> — <code>aria-busy</code> is set during loading, with customizable loading icon and text slots.</li>
@@ -24,15 +24,14 @@
24
24
  <ul>
25
25
  <li>Checkmark indicators for each selected option</li>
26
26
  <li>A <code>value</code> that returns a JSON-stringified array of selections</li>
27
- <li><code>allowDeselect</code> to toggle selections off</li>
28
- <li><code>selectAllMatchingOptions</code> for bulk selection operations</li>
29
- <li><code>clearSelection()</code> and <code>reset()</code> methods for programmatic control</li>
27
+ <li>Click-to-toggle behavior that deselects an already-selected option</li>
28
+ <li><code>reset()</code> method for programmatic control</li>
30
29
  </ul>
31
30
  <auro-header level="2" id="searchAndFiltering">Search and filtering</auro-header>
32
31
  <p>Native menus have no concept of filtering or highlighting matched text.</p>
33
32
  <p><code>auro-menu</code> supports:</p>
34
33
  <ul>
35
- <li><code>matchWord</code> to highlight matching portions of option text as the user types (used by <code>auro-combobox</code> for real-time filtering)</li>
34
+ <li><auro-hyperlink href="customize#matchWord"><code>matchWord</code></auro-hyperlink> to highlight matching portions of option text as the user types (used by <code>auro-combobox</code> for real-time filtering)</li>
36
35
  <li>Programmatic <code>navigateOptions(direction)</code> for keyboard-driven traversal</li>
37
36
  </ul>
38
37
  <auro-header level="2" id="loadingState">Loading state</auro-header>
@@ -88,7 +87,7 @@
88
87
  <tr>
89
88
  <td>Keyboard navigation</td>
90
89
  <td>Browser-dependent</td>
91
- <td>Full arrow/Enter/Escape/Home/End</td>
90
+ <td>Arrow Up/Down, Enter, Tab</td>
92
91
  </tr>
93
92
  <tr>
94
93
  <td>Composable primitive</td>
@@ -96,9 +95,9 @@
96
95
  <td>Foundation for select and combobox</td>
97
96
  </tr>
98
97
  <tr>
99
- <td>Deselect support</td>
100
- <td>No</td>
101
- <td><code>allowDeselect</code> attribute</td>
98
+ <td>Multi-select deselect</td>
99
+ <td>Ctrl+click list box</td>
100
+ <td>Click-to-toggle in multi-select</td>
102
101
  </tr>
103
102
  </tbody>
104
103
  </table>
@@ -68,7 +68,7 @@ export class AuroMenu extends AuroElement {
68
68
  attribute: string;
69
69
  };
70
70
  /**
71
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
71
+ * The currently selected menu option(s). In single-select mode this is a single `HTMLElement` (or `undefined` when nothing is selected). In multi-select mode this is an array of `HTMLElement`s.
72
72
  */
73
73
  optionSelected: {
74
74
  type: ObjectConstructor;
@@ -13,6 +13,11 @@ export class AuroMenuOption extends AuroElement {
13
13
  type: BooleanConstructor;
14
14
  reflect: boolean;
15
15
  };
16
+ /**
17
+ * **Deprecated.** Use the `value` attribute on `auro-menu` to set the selected option when the menu renders (or call `menu.selectByValue(value)` programmatically). Support for the child-level `selected` attribute will be removed in a future major release.
18
+ *
19
+ * @deprecated Use the `value` attribute on `auro-menu` instead.
20
+ */
16
21
  selected: {
17
22
  type: BooleanConstructor;
18
23
  reflect: boolean;
@@ -45,6 +50,11 @@ export class AuroMenuOption extends AuroElement {
45
50
  reflect: boolean;
46
51
  attribute: string;
47
52
  };
53
+ /** When true, this option is excluded from `matchWord` DOM rewriting — useful for utility rows (e.g., "Add new…") that must render identically regardless of the current filter. */
54
+ persistent: {
55
+ type: BooleanConstructor;
56
+ reflect: boolean;
57
+ };
48
58
  layout: {
49
59
  type: StringConstructor;
50
60
  attribute: string;
@@ -74,6 +84,7 @@ export class AuroMenuOption extends AuroElement {
74
84
  noCheckmark: boolean;
75
85
  disabled: boolean;
76
86
  noMatch: boolean;
87
+ persistent: boolean;
77
88
  /**
78
89
  * @private
79
90
  */