@aurodesignsystem-dev/auro-formkit 0.0.0-pr1541.4 → 0.0.0-pr1542.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) 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/api.md +2 -2
  7. package/components/combobox/demo/customize.md +1 -1
  8. package/components/combobox/demo/customize.min.js +40 -90
  9. package/components/combobox/demo/getting-started.min.js +40 -90
  10. package/components/combobox/demo/index.md +0 -1
  11. package/components/combobox/demo/index.min.js +40 -90
  12. package/components/combobox/demo/keyboard-behavior.md +1 -4
  13. package/components/combobox/dist/auro-combobox.d.ts +3 -1
  14. package/components/combobox/dist/index.js +23 -4
  15. package/components/combobox/dist/registered.js +23 -4
  16. package/components/counter/demo/customize.min.js +2 -2
  17. package/components/counter/demo/index.min.js +2 -2
  18. package/components/counter/dist/index.js +2 -2
  19. package/components/counter/dist/registered.js +2 -2
  20. package/components/datepicker/demo/customize.min.js +3 -3
  21. package/components/datepicker/demo/index.min.js +3 -3
  22. package/components/datepicker/dist/index.js +3 -3
  23. package/components/datepicker/dist/registered.js +3 -3
  24. package/components/dropdown/demo/customize.min.js +1 -1
  25. package/components/dropdown/demo/getting-started.min.js +1 -1
  26. package/components/dropdown/demo/index.min.js +1 -1
  27. package/components/dropdown/dist/index.js +1 -1
  28. package/components/dropdown/dist/registered.js +1 -1
  29. package/components/form/demo/customize.min.js +51 -105
  30. package/components/form/demo/getting-started.min.js +51 -105
  31. package/components/form/demo/index.min.js +51 -105
  32. package/components/form/demo/registerDemoDeps.min.js +51 -105
  33. package/components/input/demo/customize.min.js +1 -1
  34. package/components/input/demo/getting-started.min.js +1 -1
  35. package/components/input/demo/index.min.js +1 -1
  36. package/components/input/dist/index.js +1 -1
  37. package/components/input/dist/registered.js +1 -1
  38. package/components/menu/demo/accessibility.md +4 -4
  39. package/components/menu/demo/api.md +19 -26
  40. package/components/menu/demo/css-only.md +19 -26
  41. package/components/menu/demo/customize.md +47 -307
  42. package/components/menu/demo/design.md +1 -1
  43. package/components/menu/demo/getting-started.md +7 -144
  44. package/components/menu/demo/index.min.js +18 -213
  45. package/components/menu/demo/keyboard-behavior.md +4 -83
  46. package/components/menu/demo/voiceover.md +3 -3
  47. package/components/menu/demo/why-menu.md +9 -8
  48. package/components/menu/dist/auro-menu-utils.d.ts +0 -19
  49. package/components/menu/dist/auro-menu.d.ts +7 -3
  50. package/components/menu/dist/auro-menuoption.d.ts +0 -12
  51. package/components/menu/dist/index.js +17 -86
  52. package/components/menu/dist/registered.js +17 -86
  53. package/components/radio/demo/customize.min.js +1 -1
  54. package/components/radio/demo/getting-started.min.js +1 -1
  55. package/components/radio/demo/index.min.js +1 -1
  56. package/components/radio/dist/index.js +1 -1
  57. package/components/radio/dist/registered.js +1 -1
  58. package/components/select/demo/api.md +2 -2
  59. package/components/select/demo/customize.md +2 -5
  60. package/components/select/demo/customize.min.js +20 -93
  61. package/components/select/demo/design.md +10 -10
  62. package/components/select/demo/getting-started.md +1 -1
  63. package/components/select/demo/getting-started.min.js +20 -93
  64. package/components/select/demo/index.md +2 -2
  65. package/components/select/demo/index.min.js +20 -93
  66. package/components/select/demo/keyboard-behavior.md +18 -49
  67. package/components/select/dist/auro-select.d.ts +2 -6
  68. package/components/select/dist/index.js +3 -7
  69. package/components/select/dist/registered.js +3 -7
  70. package/custom-elements.json +1514 -1599
  71. package/package.json +1 -1
@@ -20,7 +20,7 @@
20
20
  </ol>
21
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>
22
22
  <auro-header level="3" id="voiceOverSelection">Selection</auro-header>
23
- <p>When an option is selected via Enter (or Tab, which selects and moves focus out of the menu):</p>
23
+ <p>When an option is selected via Enter or Space:</p>
24
24
  <ul>
25
25
  <li>VoiceOver announces the option label followed by <em>"selected"</em></li>
26
26
  <li>In multi-select mode, previously selected options remain selected and their state is preserved</li>
@@ -29,8 +29,8 @@
29
29
  <ol>
30
30
  <li><strong>Enter the menu:</strong> Focus moves into the listbox; VoiceOver announces role and item count</li>
31
31
  <li><strong>Navigate options:</strong> Use Arrow Down/Up keys; VoiceOver announces each option's label, role, and position</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>
32
+ <li><strong>Select an option:</strong> Press Enter or Space; VoiceOver confirms the selection</li>
33
+ <li><strong>Exit:</strong> Tab out of the menu or press Escape (when used within a dropdown context)</li>
34
34
  </ol>
35
35
  <auro-header level="2" id="voiceOverNestedMenus">Nested Menus</auro-header>
36
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 Up/Down move the active option; Enter selects it; Tab selects and moves focus out of the menu.</li>
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>
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,14 +24,15 @@
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>Click-to-toggle behavior that deselects an already-selected option</li>
28
- <li><code>reset()</code> method for programmatic control</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>
29
30
  </ul>
30
31
  <auro-header level="2" id="searchAndFiltering">Search and filtering</auro-header>
31
32
  <p>Native menus have no concept of filtering or highlighting matched text.</p>
32
33
  <p><code>auro-menu</code> supports:</p>
33
34
  <ul>
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>
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>
35
36
  <li>Programmatic <code>navigateOptions(direction)</code> for keyboard-driven traversal</li>
36
37
  </ul>
37
38
  <auro-header level="2" id="loadingState">Loading state</auro-header>
@@ -87,7 +88,7 @@
87
88
  <tr>
88
89
  <td>Keyboard navigation</td>
89
90
  <td>Browser-dependent</td>
90
- <td>Arrow Up/Down, Enter, Tab</td>
91
+ <td>Full arrow/Enter/Escape/Home/End</td>
91
92
  </tr>
92
93
  <tr>
93
94
  <td>Composable primitive</td>
@@ -95,9 +96,9 @@
95
96
  <td>Foundation for select and combobox</td>
96
97
  </tr>
97
98
  <tr>
98
- <td>Multi-select deselect</td>
99
- <td>Ctrl+click list box</td>
100
- <td>Click-to-toggle in multi-select</td>
99
+ <td>Deselect support</td>
100
+ <td>No</td>
101
+ <td><code>allowDeselect</code> attribute</td>
101
102
  </tr>
102
103
  </tbody>
103
104
  </table>
@@ -10,15 +10,6 @@
10
10
  * or if the value cannot be parsed into an array from a JSON string.
11
11
  */
12
12
  export function arrayConverter(value: any): any[] | undefined;
13
- /**
14
- * Serializes a multi-select value array back into the String `value` property.
15
- * An empty (or missing) array collapses to `undefined` so an emptied selection
16
- * clears `value` rather than reflecting a `"[]"` attribute.
17
- * @private
18
- * @param {Array<string>|undefined} values - The selected values.
19
- * @returns {string|undefined} JSON string of the values, or undefined when empty.
20
- */
21
- export function serializeMultiSelectValue(values: Array<string> | undefined): string | undefined;
22
13
  /**
23
14
  * Compare two arrays for equality.
24
15
  * @private
@@ -34,16 +25,6 @@ export function arraysAreEqual(arr1: any[], arr2: any[]): boolean;
34
25
  * @returns {boolean} True if option is interactive.
35
26
  */
36
27
  export function isOptionInteractive(option: HTMLElement): boolean;
37
- /**
38
- * Validates if an option may be selected by matching a programmatic value.
39
- * Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
40
- * `hidden` as its type-ahead filter, so a filtered-out option is still a
41
- * valid programmatic selection. Only disabled and static options — which are
42
- * never selectable — are rejected.
43
- * @param {HTMLElement} option - The option to check.
44
- * @returns {boolean} True if option can be selected by value.
45
- */
46
- export function isSelectableByValue(option: HTMLElement): boolean;
47
28
  /**
48
29
  * Helper method to dispatch custom events.
49
30
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -60,17 +60,21 @@ export class AuroMenu extends AuroElement {
60
60
  reflect: boolean;
61
61
  attribute: string;
62
62
  };
63
+ /**
64
+ * Specifies the current active menuOption.
65
+ */
63
66
  optionActive: {
64
67
  type: ObjectConstructor;
65
68
  attribute: string;
66
69
  };
70
+ /**
71
+ * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
72
+ */
67
73
  optionSelected: {
68
74
  type: ObjectConstructor;
69
- attribute: boolean;
70
75
  };
71
76
  /**
72
77
  * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
73
- * Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
74
78
  */
75
79
  value: {
76
80
  type: StringConstructor;
@@ -167,7 +171,7 @@ export class AuroMenu extends AuroElement {
167
171
  */
168
172
  private get formattedValue();
169
173
  /**
170
- * Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
174
+ * Selects options by value.
171
175
  * @param {string|string[]|undefined|null} value - The value(s) to select.
172
176
  * @public
173
177
  */
@@ -2,7 +2,6 @@
2
2
  * The `auro-menuoption` element provides users a way to define a menu option.
3
3
  * @customElement auro-menuoption
4
4
  *
5
- * @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
6
5
  * @slot default - The default slot for the menu option text.
7
6
  *
8
7
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -14,11 +13,6 @@ export class AuroMenuOption extends AuroElement {
14
13
  type: BooleanConstructor;
15
14
  reflect: boolean;
16
15
  };
17
- /**
18
- * **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.
19
- *
20
- * @deprecated Use the `value` attribute on `auro-menu` instead.
21
- */
22
16
  selected: {
23
17
  type: BooleanConstructor;
24
18
  reflect: boolean;
@@ -51,11 +45,6 @@ export class AuroMenuOption extends AuroElement {
51
45
  reflect: boolean;
52
46
  attribute: string;
53
47
  };
54
- /** 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. */
55
- persistent: {
56
- type: BooleanConstructor;
57
- reflect: boolean;
58
- };
59
48
  layout: {
60
49
  type: StringConstructor;
61
50
  attribute: string;
@@ -85,7 +74,6 @@ export class AuroMenuOption extends AuroElement {
85
74
  noCheckmark: boolean;
86
75
  disabled: boolean;
87
76
  noMatch: boolean;
88
- persistent: boolean;
89
77
  /**
90
78
  * @private
91
79
  */
@@ -241,18 +241,6 @@ function arrayConverter(value) {
241
241
  throw new Error('Invalid value: Input must be an array or undefined');
242
242
  }
243
243
 
244
- /**
245
- * Serializes a multi-select value array back into the String `value` property.
246
- * An empty (or missing) array collapses to `undefined` so an emptied selection
247
- * clears `value` rather than reflecting a `"[]"` attribute.
248
- * @private
249
- * @param {Array<string>|undefined} values - The selected values.
250
- * @returns {string|undefined} JSON string of the values, or undefined when empty.
251
- */
252
- function serializeMultiSelectValue(values) {
253
- return values && values.length > 0 ? JSON.stringify(values) : undefined;
254
- }
255
-
256
244
  /**
257
245
  * Validates if an option can be interacted with.
258
246
  * @private
@@ -265,20 +253,6 @@ function isOptionInteractive(option) {
265
253
  !option.hasAttribute('static');
266
254
  }
267
255
 
268
- /**
269
- * Validates if an option may be selected by matching a programmatic value.
270
- * Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
271
- * `hidden` as its type-ahead filter, so a filtered-out option is still a
272
- * valid programmatic selection. Only disabled and static options — which are
273
- * never selectable — are rejected.
274
- * @param {HTMLElement} option - The option to check.
275
- * @returns {boolean} True if option can be selected by value.
276
- */
277
- function isSelectableByValue(option) {
278
- return !option.hasAttribute('disabled') &&
279
- !option.hasAttribute('static');
280
- }
281
-
282
256
  /**
283
257
  * Helper method to dispatch custom events.
284
258
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -448,7 +422,6 @@ class AuroMenu extends AuroElement {
448
422
 
449
423
  /**
450
424
  * Specifies the current active menuOption.
451
- * @readonly
452
425
  */
453
426
  optionActive: {
454
427
  type: Object,
@@ -456,18 +429,15 @@ class AuroMenu extends AuroElement {
456
429
  },
457
430
 
458
431
  /**
459
- * 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.
460
- * @readonly
432
+ * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
461
433
  */
462
434
  optionSelected: {
463
435
  // Allow HTMLElement, HTMLElement[] arrays and undefined
464
- type: Object,
465
- attribute: false
436
+ type: Object
466
437
  },
467
438
 
468
439
  /**
469
440
  * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
470
- * Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
471
441
  */
472
442
  value: {
473
443
  type: String,
@@ -591,7 +561,7 @@ class AuroMenu extends AuroElement {
591
561
  }
592
562
 
593
563
  /**
594
- * Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
564
+ * Selects options by value.
595
565
  * @param {string|string[]|undefined|null} value - The value(s) to select.
596
566
  * @public
597
567
  */
@@ -676,11 +646,6 @@ class AuroMenu extends AuroElement {
676
646
  this.initItems();
677
647
  }
678
648
 
679
- // Set when reconciliation reassigns `value` below. That reassignment schedules a
680
- // second updated() cycle, so the `event`-attribute dispatch is deferred to that
681
- // cycle to avoid firing option custom events twice on the same selection.
682
- let valueReconciled = false;
683
-
684
649
  // Handle null/undefined/empty case — empty/whitespace strings clear selection
685
650
  // consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
686
651
  if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
@@ -693,31 +658,11 @@ class AuroMenu extends AuroElement {
693
658
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
694
659
  // and calling `.includes` on undefined would throw during reconciliation.
695
660
  const valueArray = this.formattedValue || [];
696
- const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
661
+ const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
697
662
  newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
698
-
699
- // Reconcile `value` with the selectable set. Drop only entries whose option is
700
- // loaded but non-selectable (disabled/static) — leaving them would desync `value`
701
- // from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
702
- // so the rejected entry would resurface on the next select/deselect. Entries with no
703
- // matching item yet are preserved so async preselection still works once options render.
704
- const rejectedValues = this.items
705
- ? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
706
- : [];
707
- if (rejectedValues.length > 0) {
708
- const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
709
- this.value = serializeMultiSelectValue(reconciled);
710
- valueReconciled = true;
711
- }
712
663
  } else {
713
- // In single-select mode, this.value should be a string. Reject
714
- // disabled/static options so a programmatic value pointing at a
715
- // non-selectable option falls through to the no-match path below
716
- // (dispatching auroMenu-selectValueFailure) instead of pinning it.
717
- // `hidden` is intentionally NOT excluded: the combobox toggles
718
- // `hidden` as its type-ahead filter, so a filtered-out option is
719
- // still a valid programmatic selection.
720
- const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
664
+ // In single-select mode, this.value should be a string
665
+ const matchingOption = this.items ? this.items.find((item) => item.value === this.value) : undefined;
721
666
 
722
667
  if (matchingOption) {
723
668
  newSelected = matchingOption;
@@ -759,9 +704,8 @@ class AuroMenu extends AuroElement {
759
704
  ]
760
705
  ]));
761
706
 
762
- // Notify of changes. Skip when reconciliation just reassigned `value`: the
763
- // follow-on update cycle re-runs this branch and fires the events exactly once.
764
- if (this.optionSelected !== undefined && !valueReconciled) {
707
+ // Notify of changes
708
+ if (this.optionSelected !== undefined) {
765
709
  const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
766
710
  selected.forEach((opt) => {
767
711
  if (opt.hasAttribute('event')) {
@@ -974,7 +918,7 @@ class AuroMenu extends AuroElement {
974
918
  const currentSelected = this.optionSelected || [];
975
919
 
976
920
  if (!currentValue.includes(option.value)) {
977
- this.value = serializeMultiSelectValue([
921
+ this.value = JSON.stringify([
978
922
  ...currentValue,
979
923
  option.value
980
924
  ]);
@@ -1000,9 +944,15 @@ class AuroMenu extends AuroElement {
1000
944
  */
1001
945
  handleDeselectState(option) {
1002
946
  if (this.multiSelect) {
1003
- // Remove this option from array; an empty result collapses `value` to undefined.
947
+ // Remove this option from array
1004
948
  const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
1005
- this.value = serializeMultiSelectValue(newFormattedValue);
949
+
950
+ // If array is empty after removal, set back to undefined
951
+ if (newFormattedValue && newFormattedValue.length === 0) {
952
+ this.value = undefined;
953
+ } else {
954
+ this.value = JSON.stringify(newFormattedValue);
955
+ }
1006
956
 
1007
957
  this.optionSelected = this.optionSelected.filter((val) => val !== option);
1008
958
  if (this.optionSelected.length === 0) {
@@ -1049,11 +999,6 @@ class AuroMenu extends AuroElement {
1049
999
  this.optionSelected = undefined;
1050
1000
  this._index = -1;
1051
1001
 
1052
- // Clear active option state so a follow-up open/navigation starts fresh
1053
- // rather than reusing a stale reference from before the reset.
1054
- this.items?.forEach((item) => item.classList.remove('active'));
1055
- this.optionActive = undefined;
1056
-
1057
1002
  // Reset UI state
1058
1003
  this.updateItemsState(new Map([
1059
1004
  [
@@ -1503,7 +1448,6 @@ let menuOptionIdCounter = 0;
1503
1448
  * The `auro-menuoption` element provides users a way to define a menu option.
1504
1449
  * @customElement auro-menuoption
1505
1450
  *
1506
- * @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
1507
1451
  * @slot default - The default slot for the menu option text.
1508
1452
  *
1509
1453
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -1533,7 +1477,6 @@ class AuroMenuOption extends AuroElement {
1533
1477
  this.noCheckmark = false;
1534
1478
  this.disabled = false;
1535
1479
  this.noMatch = false;
1536
- this.persistent = false;
1537
1480
 
1538
1481
  /**
1539
1482
  * @private
@@ -1555,12 +1498,6 @@ class AuroMenuOption extends AuroElement {
1555
1498
  type: Boolean,
1556
1499
  reflect: true
1557
1500
  },
1558
-
1559
- /**
1560
- * **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.
1561
- *
1562
- * @deprecated Use the `value` attribute on `auro-menu` instead.
1563
- */
1564
1501
  selected: {
1565
1502
  type: Boolean,
1566
1503
  reflect: true
@@ -1595,12 +1532,6 @@ class AuroMenuOption extends AuroElement {
1595
1532
  reflect: true,
1596
1533
  attribute: 'nomatch'
1597
1534
  },
1598
-
1599
- /** 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. */
1600
- persistent: {
1601
- type: Boolean,
1602
- reflect: true
1603
- },
1604
1535
  };
1605
1536
  }
1606
1537
 
@@ -200,18 +200,6 @@ class AuroLibraryRuntimeUtils {
200
200
  // See LICENSE in the project root for license information.
201
201
 
202
202
 
203
- /**
204
- * Serializes a multi-select value array back into the String `value` property.
205
- * An empty (or missing) array collapses to `undefined` so an emptied selection
206
- * clears `value` rather than reflecting a `"[]"` attribute.
207
- * @private
208
- * @param {Array<string>|undefined} values - The selected values.
209
- * @returns {string|undefined} JSON string of the values, or undefined when empty.
210
- */
211
- function serializeMultiSelectValue(values) {
212
- return values && values.length > 0 ? JSON.stringify(values) : undefined;
213
- }
214
-
215
203
  /**
216
204
  * Validates if an option can be interacted with.
217
205
  * @private
@@ -224,20 +212,6 @@ function isOptionInteractive(option) {
224
212
  !option.hasAttribute('static');
225
213
  }
226
214
 
227
- /**
228
- * Validates if an option may be selected by matching a programmatic value.
229
- * Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
230
- * `hidden` as its type-ahead filter, so a filtered-out option is still a
231
- * valid programmatic selection. Only disabled and static options — which are
232
- * never selectable — are rejected.
233
- * @param {HTMLElement} option - The option to check.
234
- * @returns {boolean} True if option can be selected by value.
235
- */
236
- function isSelectableByValue(option) {
237
- return !option.hasAttribute('disabled') &&
238
- !option.hasAttribute('static');
239
- }
240
-
241
215
  /**
242
216
  * Helper method to dispatch custom events.
243
217
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -407,7 +381,6 @@ class AuroMenu extends AuroElement {
407
381
 
408
382
  /**
409
383
  * Specifies the current active menuOption.
410
- * @readonly
411
384
  */
412
385
  optionActive: {
413
386
  type: Object,
@@ -415,18 +388,15 @@ class AuroMenu extends AuroElement {
415
388
  },
416
389
 
417
390
  /**
418
- * 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.
419
- * @readonly
391
+ * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
420
392
  */
421
393
  optionSelected: {
422
394
  // Allow HTMLElement, HTMLElement[] arrays and undefined
423
- type: Object,
424
- attribute: false
395
+ type: Object
425
396
  },
426
397
 
427
398
  /**
428
399
  * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
429
- * Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
430
400
  */
431
401
  value: {
432
402
  type: String,
@@ -550,7 +520,7 @@ class AuroMenu extends AuroElement {
550
520
  }
551
521
 
552
522
  /**
553
- * Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
523
+ * Selects options by value.
554
524
  * @param {string|string[]|undefined|null} value - The value(s) to select.
555
525
  * @public
556
526
  */
@@ -635,11 +605,6 @@ class AuroMenu extends AuroElement {
635
605
  this.initItems();
636
606
  }
637
607
 
638
- // Set when reconciliation reassigns `value` below. That reassignment schedules a
639
- // second updated() cycle, so the `event`-attribute dispatch is deferred to that
640
- // cycle to avoid firing option custom events twice on the same selection.
641
- let valueReconciled = false;
642
-
643
608
  // Handle null/undefined/empty case — empty/whitespace strings clear selection
644
609
  // consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
645
610
  if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
@@ -652,31 +617,11 @@ class AuroMenu extends AuroElement {
652
617
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
653
618
  // and calling `.includes` on undefined would throw during reconciliation.
654
619
  const valueArray = this.formattedValue || [];
655
- const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
620
+ const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
656
621
  newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
657
-
658
- // Reconcile `value` with the selectable set. Drop only entries whose option is
659
- // loaded but non-selectable (disabled/static) — leaving them would desync `value`
660
- // from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
661
- // so the rejected entry would resurface on the next select/deselect. Entries with no
662
- // matching item yet are preserved so async preselection still works once options render.
663
- const rejectedValues = this.items
664
- ? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
665
- : [];
666
- if (rejectedValues.length > 0) {
667
- const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
668
- this.value = serializeMultiSelectValue(reconciled);
669
- valueReconciled = true;
670
- }
671
622
  } else {
672
- // In single-select mode, this.value should be a string. Reject
673
- // disabled/static options so a programmatic value pointing at a
674
- // non-selectable option falls through to the no-match path below
675
- // (dispatching auroMenu-selectValueFailure) instead of pinning it.
676
- // `hidden` is intentionally NOT excluded: the combobox toggles
677
- // `hidden` as its type-ahead filter, so a filtered-out option is
678
- // still a valid programmatic selection.
679
- const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
623
+ // In single-select mode, this.value should be a string
624
+ const matchingOption = this.items ? this.items.find((item) => item.value === this.value) : undefined;
680
625
 
681
626
  if (matchingOption) {
682
627
  newSelected = matchingOption;
@@ -718,9 +663,8 @@ class AuroMenu extends AuroElement {
718
663
  ]
719
664
  ]));
720
665
 
721
- // Notify of changes. Skip when reconciliation just reassigned `value`: the
722
- // follow-on update cycle re-runs this branch and fires the events exactly once.
723
- if (this.optionSelected !== undefined && !valueReconciled) {
666
+ // Notify of changes
667
+ if (this.optionSelected !== undefined) {
724
668
  const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
725
669
  selected.forEach((opt) => {
726
670
  if (opt.hasAttribute('event')) {
@@ -933,7 +877,7 @@ class AuroMenu extends AuroElement {
933
877
  const currentSelected = this.optionSelected || [];
934
878
 
935
879
  if (!currentValue.includes(option.value)) {
936
- this.value = serializeMultiSelectValue([
880
+ this.value = JSON.stringify([
937
881
  ...currentValue,
938
882
  option.value
939
883
  ]);
@@ -959,9 +903,15 @@ class AuroMenu extends AuroElement {
959
903
  */
960
904
  handleDeselectState(option) {
961
905
  if (this.multiSelect) {
962
- // Remove this option from array; an empty result collapses `value` to undefined.
906
+ // Remove this option from array
963
907
  const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
964
- this.value = serializeMultiSelectValue(newFormattedValue);
908
+
909
+ // If array is empty after removal, set back to undefined
910
+ if (newFormattedValue && newFormattedValue.length === 0) {
911
+ this.value = undefined;
912
+ } else {
913
+ this.value = JSON.stringify(newFormattedValue);
914
+ }
965
915
 
966
916
  this.optionSelected = this.optionSelected.filter((val) => val !== option);
967
917
  if (this.optionSelected.length === 0) {
@@ -1008,11 +958,6 @@ class AuroMenu extends AuroElement {
1008
958
  this.optionSelected = undefined;
1009
959
  this._index = -1;
1010
960
 
1011
- // Clear active option state so a follow-up open/navigation starts fresh
1012
- // rather than reusing a stale reference from before the reset.
1013
- this.items?.forEach((item) => item.classList.remove('active'));
1014
- this.optionActive = undefined;
1015
-
1016
961
  // Reset UI state
1017
962
  this.updateItemsState(new Map([
1018
963
  [
@@ -1462,7 +1407,6 @@ let menuOptionIdCounter = 0;
1462
1407
  * The `auro-menuoption` element provides users a way to define a menu option.
1463
1408
  * @customElement auro-menuoption
1464
1409
  *
1465
- * @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
1466
1410
  * @slot default - The default slot for the menu option text.
1467
1411
  *
1468
1412
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -1492,7 +1436,6 @@ class AuroMenuOption extends AuroElement {
1492
1436
  this.noCheckmark = false;
1493
1437
  this.disabled = false;
1494
1438
  this.noMatch = false;
1495
- this.persistent = false;
1496
1439
 
1497
1440
  /**
1498
1441
  * @private
@@ -1514,12 +1457,6 @@ class AuroMenuOption extends AuroElement {
1514
1457
  type: Boolean,
1515
1458
  reflect: true
1516
1459
  },
1517
-
1518
- /**
1519
- * **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.
1520
- *
1521
- * @deprecated Use the `value` attribute on `auro-menu` instead.
1522
- */
1523
1460
  selected: {
1524
1461
  type: Boolean,
1525
1462
  reflect: true
@@ -1554,12 +1491,6 @@ class AuroMenuOption extends AuroElement {
1554
1491
  reflect: true,
1555
1492
  attribute: 'nomatch'
1556
1493
  },
1557
-
1558
- /** 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. */
1559
- persistent: {
1560
- type: Boolean,
1561
- reflect: true
1562
- },
1563
1494
  };
1564
1495
  }
1565
1496
 
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607082038';
1230
+ var formkitVersion = '202607082142';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607082038';
1230
+ var formkitVersion = '202607082142';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607082038';
1230
+ var formkitVersion = '202607082142';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1166,7 +1166,7 @@ class AuroHelpText extends LitElement {
1166
1166
  }
1167
1167
  }
1168
1168
 
1169
- var formkitVersion = '202607082038';
1169
+ var formkitVersion = '202607082142';
1170
1170
 
1171
1171
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1172
1172
  // See LICENSE in the project root for license information.
@@ -1166,7 +1166,7 @@ class AuroHelpText extends LitElement {
1166
1166
  }
1167
1167
  }
1168
1168
 
1169
- var formkitVersion = '202607082038';
1169
+ var formkitVersion = '202607082142';
1170
1170
 
1171
1171
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1172
1172
  // See LICENSE in the project root for license information.