@aurodesignsystem-dev/auro-formkit 0.0.0-pr1540.0 → 0.0.0-pr1540.2

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 (82) hide show
  1. package/components/checkbox/demo/accessibility.md +1 -1
  2. package/components/checkbox/demo/customize.min.js +1 -1
  3. package/components/checkbox/demo/getting-started.min.js +1 -1
  4. package/components/checkbox/demo/index.min.js +1 -1
  5. package/components/checkbox/dist/index.js +1 -1
  6. package/components/checkbox/dist/registered.js +1 -1
  7. package/components/combobox/demo/accessibility.md +1 -1
  8. package/components/combobox/demo/api.md +2 -2
  9. package/components/combobox/demo/customize.md +54 -2
  10. package/components/combobox/demo/customize.min.js +421 -240
  11. package/components/combobox/demo/getting-started.min.js +421 -240
  12. package/components/combobox/demo/index.md +1 -0
  13. package/components/combobox/demo/index.min.js +421 -240
  14. package/components/combobox/demo/keyboard-behavior.md +1 -4
  15. package/components/combobox/dist/auro-combobox.d.ts +4 -1
  16. package/components/combobox/dist/index.js +334 -222
  17. package/components/combobox/dist/registered.js +334 -222
  18. package/components/counter/demo/customize.min.js +59 -222
  19. package/components/counter/demo/index.min.js +59 -222
  20. package/components/counter/dist/auro-counter.d.ts +0 -8
  21. package/components/counter/dist/index.js +59 -222
  22. package/components/counter/dist/registered.js +59 -222
  23. package/components/datepicker/demo/accessibility.md +1 -1
  24. package/components/datepicker/demo/customize.md +30 -1
  25. package/components/datepicker/demo/customize.min.js +84 -220
  26. package/components/datepicker/demo/getting-started.md +118 -2
  27. package/components/datepicker/demo/index.min.js +84 -220
  28. package/components/datepicker/dist/index.js +84 -220
  29. package/components/datepicker/dist/registered.js +84 -220
  30. package/components/dropdown/demo/customize.html +3 -0
  31. package/components/dropdown/demo/customize.min.js +55 -206
  32. package/components/dropdown/demo/getting-started.min.js +44 -205
  33. package/components/dropdown/demo/index.min.js +44 -205
  34. package/components/dropdown/dist/index.js +44 -205
  35. package/components/dropdown/dist/registered.js +44 -205
  36. package/components/form/demo/customize.min.js +670 -945
  37. package/components/form/demo/getting-started.min.js +670 -945
  38. package/components/form/demo/index.min.js +670 -945
  39. package/components/form/demo/registerDemoDeps.min.js +670 -945
  40. package/components/input/demo/accessibility.md +1 -1
  41. package/components/input/demo/customize.min.js +1 -1
  42. package/components/input/demo/getting-started.min.js +1 -1
  43. package/components/input/demo/index.min.js +1 -1
  44. package/components/input/dist/index.js +1 -1
  45. package/components/input/dist/registered.js +1 -1
  46. package/components/menu/demo/accessibility.md +9 -4
  47. package/components/menu/demo/api.md +26 -19
  48. package/components/menu/demo/css-only.md +26 -19
  49. package/components/menu/demo/customize.md +307 -47
  50. package/components/menu/demo/design.md +1 -1
  51. package/components/menu/demo/getting-started.md +144 -7
  52. package/components/menu/demo/index.min.js +214 -19
  53. package/components/menu/demo/keyboard-behavior.md +83 -4
  54. package/components/menu/demo/voiceover.md +21 -14
  55. package/components/menu/demo/why-menu.md +8 -9
  56. package/components/menu/dist/auro-menu-utils.d.ts +19 -0
  57. package/components/menu/dist/auro-menu.d.ts +4 -8
  58. package/components/menu/dist/auro-menuoption.d.ts +12 -0
  59. package/components/menu/dist/index.js +87 -18
  60. package/components/menu/dist/registered.js +87 -18
  61. package/components/radio/demo/accessibility.md +1 -1
  62. package/components/radio/demo/customize.min.js +1 -1
  63. package/components/radio/demo/getting-started.min.js +1 -1
  64. package/components/radio/demo/index.min.js +1 -1
  65. package/components/radio/dist/index.js +1 -1
  66. package/components/radio/dist/registered.js +1 -1
  67. package/components/select/demo/accessibility.md +6 -1
  68. package/components/select/demo/api.md +2 -2
  69. package/components/select/demo/customize.md +6 -3
  70. package/components/select/demo/customize.min.js +139 -227
  71. package/components/select/demo/design.md +10 -10
  72. package/components/select/demo/getting-started.md +1 -1
  73. package/components/select/demo/getting-started.min.js +139 -227
  74. package/components/select/demo/index.md +2 -2
  75. package/components/select/demo/index.min.js +139 -227
  76. package/components/select/demo/keyboard-behavior.md +53 -58
  77. package/components/select/demo/voiceover.md +28 -15
  78. package/components/select/dist/auro-select.d.ts +6 -2
  79. package/components/select/dist/index.js +52 -209
  80. package/components/select/dist/registered.js +52 -209
  81. package/custom-elements.json +1611 -1552
  82. package/package.json +2 -2
@@ -2,6 +2,7 @@
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.
5
6
  * @slot default - The default slot for the menu option text.
6
7
  *
7
8
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -13,6 +14,11 @@ export class AuroMenuOption extends AuroElement {
13
14
  type: BooleanConstructor;
14
15
  reflect: boolean;
15
16
  };
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
+ */
16
22
  selected: {
17
23
  type: BooleanConstructor;
18
24
  reflect: boolean;
@@ -45,6 +51,11 @@ export class AuroMenuOption extends AuroElement {
45
51
  reflect: boolean;
46
52
  attribute: string;
47
53
  };
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
+ };
48
59
  layout: {
49
60
  type: StringConstructor;
50
61
  attribute: string;
@@ -74,6 +85,7 @@ export class AuroMenuOption extends AuroElement {
74
85
  noCheckmark: boolean;
75
86
  disabled: boolean;
76
87
  noMatch: boolean;
88
+ persistent: boolean;
77
89
  /**
78
90
  * @private
79
91
  */
@@ -241,6 +241,18 @@ 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
+
244
256
  /**
245
257
  * Validates if an option can be interacted with.
246
258
  * @private
@@ -253,6 +265,20 @@ function isOptionInteractive(option) {
253
265
  !option.hasAttribute('static');
254
266
  }
255
267
 
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
+
256
282
  /**
257
283
  * Helper method to dispatch custom events.
258
284
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -422,22 +448,26 @@ class AuroMenu extends AuroElement {
422
448
 
423
449
  /**
424
450
  * Specifies the current active menuOption.
451
+ * @readonly
425
452
  */
426
453
  optionActive: {
427
454
  type: Object,
428
- attribute: 'optionactive'
455
+ attribute: false
429
456
  },
430
457
 
431
458
  /**
432
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
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
433
461
  */
434
462
  optionSelected: {
435
463
  // Allow HTMLElement, HTMLElement[] arrays and undefined
436
- type: Object
464
+ type: Object,
465
+ attribute: false
437
466
  },
438
467
 
439
468
  /**
440
469
  * 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.
441
471
  */
442
472
  value: {
443
473
  type: String,
@@ -561,7 +591,7 @@ class AuroMenu extends AuroElement {
561
591
  }
562
592
 
563
593
  /**
564
- * Selects options by value.
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.
565
595
  * @param {string|string[]|undefined|null} value - The value(s) to select.
566
596
  * @public
567
597
  */
@@ -646,6 +676,11 @@ class AuroMenu extends AuroElement {
646
676
  this.initItems();
647
677
  }
648
678
 
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
+
649
684
  // Handle null/undefined/empty case — empty/whitespace strings clear selection
650
685
  // consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
651
686
  if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
@@ -658,11 +693,31 @@ class AuroMenu extends AuroElement {
658
693
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
659
694
  // and calling `.includes` on undefined would throw during reconciliation.
660
695
  const valueArray = this.formattedValue || [];
661
- const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
696
+ const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
662
697
  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
+ }
663
712
  } else {
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;
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;
666
721
 
667
722
  if (matchingOption) {
668
723
  newSelected = matchingOption;
@@ -704,8 +759,9 @@ class AuroMenu extends AuroElement {
704
759
  ]
705
760
  ]));
706
761
 
707
- // Notify of changes
708
- if (this.optionSelected !== undefined) {
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) {
709
765
  const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
710
766
  selected.forEach((opt) => {
711
767
  if (opt.hasAttribute('event')) {
@@ -918,7 +974,7 @@ class AuroMenu extends AuroElement {
918
974
  const currentSelected = this.optionSelected || [];
919
975
 
920
976
  if (!currentValue.includes(option.value)) {
921
- this.value = JSON.stringify([
977
+ this.value = serializeMultiSelectValue([
922
978
  ...currentValue,
923
979
  option.value
924
980
  ]);
@@ -944,15 +1000,9 @@ class AuroMenu extends AuroElement {
944
1000
  */
945
1001
  handleDeselectState(option) {
946
1002
  if (this.multiSelect) {
947
- // Remove this option from array
1003
+ // Remove this option from array; an empty result collapses `value` to undefined.
948
1004
  const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
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
- }
1005
+ this.value = serializeMultiSelectValue(newFormattedValue);
956
1006
 
957
1007
  this.optionSelected = this.optionSelected.filter((val) => val !== option);
958
1008
  if (this.optionSelected.length === 0) {
@@ -999,6 +1049,11 @@ class AuroMenu extends AuroElement {
999
1049
  this.optionSelected = undefined;
1000
1050
  this._index = -1;
1001
1051
 
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
+
1002
1057
  // Reset UI state
1003
1058
  this.updateItemsState(new Map([
1004
1059
  [
@@ -1448,6 +1503,7 @@ let menuOptionIdCounter = 0;
1448
1503
  * The `auro-menuoption` element provides users a way to define a menu option.
1449
1504
  * @customElement auro-menuoption
1450
1505
  *
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.
1451
1507
  * @slot default - The default slot for the menu option text.
1452
1508
  *
1453
1509
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -1477,6 +1533,7 @@ class AuroMenuOption extends AuroElement {
1477
1533
  this.noCheckmark = false;
1478
1534
  this.disabled = false;
1479
1535
  this.noMatch = false;
1536
+ this.persistent = false;
1480
1537
 
1481
1538
  /**
1482
1539
  * @private
@@ -1498,6 +1555,12 @@ class AuroMenuOption extends AuroElement {
1498
1555
  type: Boolean,
1499
1556
  reflect: true
1500
1557
  },
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
+ */
1501
1564
  selected: {
1502
1565
  type: Boolean,
1503
1566
  reflect: true
@@ -1532,6 +1595,12 @@ class AuroMenuOption extends AuroElement {
1532
1595
  reflect: true,
1533
1596
  attribute: 'nomatch'
1534
1597
  },
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
+ },
1535
1604
  };
1536
1605
  }
1537
1606
 
@@ -200,6 +200,18 @@ 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
+
203
215
  /**
204
216
  * Validates if an option can be interacted with.
205
217
  * @private
@@ -212,6 +224,20 @@ function isOptionInteractive(option) {
212
224
  !option.hasAttribute('static');
213
225
  }
214
226
 
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
+
215
241
  /**
216
242
  * Helper method to dispatch custom events.
217
243
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -381,22 +407,26 @@ class AuroMenu extends AuroElement {
381
407
 
382
408
  /**
383
409
  * Specifies the current active menuOption.
410
+ * @readonly
384
411
  */
385
412
  optionActive: {
386
413
  type: Object,
387
- attribute: 'optionactive'
414
+ attribute: false
388
415
  },
389
416
 
390
417
  /**
391
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
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
392
420
  */
393
421
  optionSelected: {
394
422
  // Allow HTMLElement, HTMLElement[] arrays and undefined
395
- type: Object
423
+ type: Object,
424
+ attribute: false
396
425
  },
397
426
 
398
427
  /**
399
428
  * 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.
400
430
  */
401
431
  value: {
402
432
  type: String,
@@ -520,7 +550,7 @@ class AuroMenu extends AuroElement {
520
550
  }
521
551
 
522
552
  /**
523
- * Selects options by value.
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.
524
554
  * @param {string|string[]|undefined|null} value - The value(s) to select.
525
555
  * @public
526
556
  */
@@ -605,6 +635,11 @@ class AuroMenu extends AuroElement {
605
635
  this.initItems();
606
636
  }
607
637
 
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
+
608
643
  // Handle null/undefined/empty case — empty/whitespace strings clear selection
609
644
  // consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
610
645
  if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
@@ -617,11 +652,31 @@ class AuroMenu extends AuroElement {
617
652
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
618
653
  // and calling `.includes` on undefined would throw during reconciliation.
619
654
  const valueArray = this.formattedValue || [];
620
- const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
655
+ const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
621
656
  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
+ }
622
671
  } else {
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;
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;
625
680
 
626
681
  if (matchingOption) {
627
682
  newSelected = matchingOption;
@@ -663,8 +718,9 @@ class AuroMenu extends AuroElement {
663
718
  ]
664
719
  ]));
665
720
 
666
- // Notify of changes
667
- if (this.optionSelected !== undefined) {
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) {
668
724
  const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
669
725
  selected.forEach((opt) => {
670
726
  if (opt.hasAttribute('event')) {
@@ -877,7 +933,7 @@ class AuroMenu extends AuroElement {
877
933
  const currentSelected = this.optionSelected || [];
878
934
 
879
935
  if (!currentValue.includes(option.value)) {
880
- this.value = JSON.stringify([
936
+ this.value = serializeMultiSelectValue([
881
937
  ...currentValue,
882
938
  option.value
883
939
  ]);
@@ -903,15 +959,9 @@ class AuroMenu extends AuroElement {
903
959
  */
904
960
  handleDeselectState(option) {
905
961
  if (this.multiSelect) {
906
- // Remove this option from array
962
+ // Remove this option from array; an empty result collapses `value` to undefined.
907
963
  const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
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
- }
964
+ this.value = serializeMultiSelectValue(newFormattedValue);
915
965
 
916
966
  this.optionSelected = this.optionSelected.filter((val) => val !== option);
917
967
  if (this.optionSelected.length === 0) {
@@ -958,6 +1008,11 @@ class AuroMenu extends AuroElement {
958
1008
  this.optionSelected = undefined;
959
1009
  this._index = -1;
960
1010
 
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
+
961
1016
  // Reset UI state
962
1017
  this.updateItemsState(new Map([
963
1018
  [
@@ -1407,6 +1462,7 @@ let menuOptionIdCounter = 0;
1407
1462
  * The `auro-menuoption` element provides users a way to define a menu option.
1408
1463
  * @customElement auro-menuoption
1409
1464
  *
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.
1410
1466
  * @slot default - The default slot for the menu option text.
1411
1467
  *
1412
1468
  * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
@@ -1436,6 +1492,7 @@ class AuroMenuOption extends AuroElement {
1436
1492
  this.noCheckmark = false;
1437
1493
  this.disabled = false;
1438
1494
  this.noMatch = false;
1495
+ this.persistent = false;
1439
1496
 
1440
1497
  /**
1441
1498
  * @private
@@ -1457,6 +1514,12 @@ class AuroMenuOption extends AuroElement {
1457
1514
  type: Boolean,
1458
1515
  reflect: true
1459
1516
  },
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
+ */
1460
1523
  selected: {
1461
1524
  type: Boolean,
1462
1525
  reflect: true
@@ -1491,6 +1554,12 @@ class AuroMenuOption extends AuroElement {
1491
1554
  reflect: true,
1492
1555
  attribute: 'nomatch'
1493
1556
  },
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
+ },
1494
1563
  };
1495
1564
  }
1496
1565
 
@@ -4,7 +4,7 @@
4
4
  <div class="scrollWrapper">
5
5
  The `auro-radio` component follows native HTML radio button semantics. This page documents the ARIA attributes, screen reader behavior, and other accessibility features built into the component.
6
6
 
7
- For keyboard interaction details, see the <a href="./keyboard-behavior.html">Keyboard Behavior</a> page.
7
+ For keyboard interaction details, see the <auro-hyperlink href="keyboard-behavior">Keyboard Behavior</auro-hyperlink> page.
8
8
 
9
9
  <auro-header level="2" id="ariaRolesAndAttributes">ARIA Roles and Attributes</auro-header>
10
10
  <auro-header level="3" id="radioGroup">Radio Group</auro-header>
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607072023';
1230
+ var formkitVersion = '202607102214';
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 = '202607072023';
1230
+ var formkitVersion = '202607102214';
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 = '202607072023';
1230
+ var formkitVersion = '202607102214';
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 = '202607072023';
1169
+ var formkitVersion = '202607102214';
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 = '202607072023';
1169
+ var formkitVersion = '202607102214';
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.
@@ -4,7 +4,7 @@
4
4
  <div class="scrollWrapper">
5
5
  The `auro-select` component follows the <a href="https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/" target="_blank">W3C APG Select-Only Combobox</a> pattern. This page documents the ARIA semantics, screen reader announcements, and other accessibility behaviors built into the component.
6
6
 
7
- For keyboard interaction details, see the <a href="./keyboard-behavior.html">Keyboard Behavior</a> page.
7
+ For keyboard interaction details, see the <auro-hyperlink href="keyboard-behavior">Keyboard Behavior</auro-hyperlink> page.
8
8
 
9
9
  <auro-header level="2" id="ariaRolesAndAttributes">ARIA Roles and Attributes</auro-header>
10
10
  <auro-header level="3" id="trigger">Trigger</auro-header>
@@ -61,6 +61,11 @@ On smaller viewports, the bib opens as a fullscreen modal dialog:
61
61
  - A close button inside the dialog is focused when the modal opens, giving screen reader users an immediate action to dismiss it.
62
62
  - Touch scrolling on the page behind the dialog is blocked to prevent the background from scrolling into view.
63
63
 
64
+ <auro-header level="2" id="modifierKeys">Modifier Keys</auro-header>
65
+ The **Meta** key (Command on macOS, Windows/Super elsewhere) is intentionally not used as a modifier for select keyboard shortcuts. On Windows, Linux, and ChromeOS, the operating system intercepts `Meta` + Arrow combinations for window management (e.g. window snapping) before the browser receives the event, so any shortcut bound to `Meta` + Arrow would work only on macOS and silently fail on other platforms.
66
+
67
+ To jump to the first or last option in a way that works uniformly across platforms, use **Home** and **End**, which follow the <a href="https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/" target="_blank">WAI-ARIA Select-Only Combobox pattern</a>.
68
+
64
69
  <auro-header level="2" id="reducedMotion">Reduced Motion</auro-header>
65
70
  The component respects the `prefers-reduced-motion` media query. When the user has requested reduced motion, scroll animations (e.g. scrolling the selected or active option into view) use instant scrolling instead of smooth scrolling.
66
71
 
@@ -40,7 +40,7 @@ The `auro-select` element is a wrapper for auro-dropdown and auro-menu to create
40
40
  | `size` | `size` | `'lg' \| 'xl'` | | Determines the size of the dropdown bib. Only the `emphasized` layout supports size=`xl`, while all other layouts support size=`lg`. |
41
41
  | `typeaheadTimeoutMs` | `typeaheadTimeoutMs` | `number` | "500" | Milliseconds of keyboard inactivity before the type-ahead buffer resets.<br />Increase for users who type slowly. |
42
42
  | `validity` | `validity` | `string` | | Specifies the `validityState` this element is in. |
43
- | `value` | `value` | `string` | | Value selected for the component. |
43
+ | `value` | `value` | `string` | | Value selected for the component. When set programmatically or as a preset attribute, the value must match a selectable option. If it matches an option marked `disabled` or `static`, the selection is rejected: `value` is cleared to `undefined` while `optionSelected` is cleared to `undefined` in single-select or `[]` in multiSelect. `hidden` options remain selectable by value. |
44
44
 
45
45
  ## Methods
46
46
 
@@ -48,7 +48,7 @@ The `auro-select` element is a wrapper for auro-dropdown and auro-menu to create
48
48
  |----------------------|----------------------------------------|--------------------------------------------------|
49
49
  | `hideBib` | `(): void` | Hides the dropdown bib if its open. |
50
50
  | `reset` | `(): void` | Resets component to initial state. |
51
- | `setMenuValue` | `(value: any): void` | |
51
+ | `setMenuValue` | `(value: string): void` | Sets the selected value by matching it against the menu options. Options marked `disabled` or `static` are not selectable: if the value matches one, the selection is rejected and `value` is cleared to `undefined` while `optionSelected` is cleared to `undefined` in single-select or `[]` in multiSelect. `hidden` options remain selectable.<br /><br />**value**: The value to match against the menu options. |
52
52
  | `showBib` | `(): void` | Shows the dropdown bib if there are options to show. |
53
53
  | `updateActiveOption` | `(index: number): void` | Updates the active option in the menu.<br /><br />**index**: Index of the option to make active. |
54
54
  | `validate` | `(force?: boolean \| undefined): void` | Validates value.<br /><br />**force**: Whether to force validation. |
@@ -40,7 +40,7 @@
40
40
  <auro-header level="2" id="appearance">Appearance</auro-header>
41
41
  <auro-header level="3" id="layout">Shape, Size & Layout</auro-header>
42
42
  <p>The <code>shape</code>, <code>size</code> and <code>layout</code> attributes work in collaboration to control the overall architecture of the component.</p>
43
- <p>See the <a href="./design.html">Design page</a> for a detailed breakdown.</p>
43
+ <p>See the <auro-hyperlink href="design">Design page</auro-hyperlink> for a detailed breakdown.</p>
44
44
  <auro-header level="3" id="background">Light vs. Dark Background</auro-header>
45
45
  <p>The <code>appearance</code> attribute defines whether the component renders on lighter or darker backgrounds. Supported values are <code>default</code> and <code>inverse</code>. The default value is <code>default</code>.</p>
46
46
  <div class="exampleWrapper">
@@ -796,7 +796,7 @@
796
796
  <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../docs/partials/customize/typeahead.md) -->
797
797
  <!-- The below content is automatically added from ./../docs/partials/customize/typeahead.md -->
798
798
  <p>Type-ahead lets a guest jump to an option by typing. As keys are pressed, the active option advances to the first enabled option whose <strong>displayed text</strong> starts with the buffered keystrokes — following the <auro-hyperlink href="https://www.w3.org/WAI/ARIA/apg/patterns/listbox/">WAI-ARIA Listbox pattern</auro-hyperlink>, and mirroring native HTML <code>&lt;select&gt;</code> behavior. Matching uses the rendered text rather than the <code>value</code> attribute.</p>
799
- <p>The <code>typeaheadTimeoutMs</code> attribute (default <code>500</code>) controls how long the buffer persists between keystrokes. Repeating the same character cycles through every enabled option starting with it; disabled options are skipped.</p>
799
+ <p>The <code>typeaheadTimeoutMs</code> attribute (default <code>500</code>) controls how long the buffer persists between keystrokes. Repeating the same character cycles through every enabled option starting with it; <code>disabled</code>, <code>hidden</code>, and <code>static</code> options are skipped.</p>
800
800
  <div class="note">
801
801
  <strong>Note:</strong> The <kbd>Space</kbd> key is context-sensitive. When the type-ahead buffer is empty it toggles the bib open or closed, matching the rest of <code>&lt;auro-select&gt;</code>. When the buffer is active it extends the buffer instead, so multi-word options such as "San Francisco" can be matched by typing <kbd>s</kbd>, <kbd>a</kbd>, <kbd>n</kbd>, <kbd>Space</kbd>, <kbd>f</kbd>.
802
802
  </div>
@@ -864,7 +864,7 @@
864
864
  </auro-accordion>
865
865
  <auro-header level="3" id="disableComponent">Disable Component</auro-header>
866
866
  <p>The entire component may be disabled. When disabled, the component will render to reflect the state, may not receive focus nor react to any key or pointer events.</p>
867
- <p>When the component is disabled and part of a form, the components value is still included in the form submission.</p>
867
+ <p>When the component is disabled and part of a form, the component's value is not included in the form submission (matching native HTML behavior for disabled form controls).</p>
868
868
  <p class="note">
869
869
  <strong>Note:</strong> If the component is marked as both <strong>invalid</strong> and <code>disabled</code>, the <strong>invalid</strong> state UI/UX and functional behavior are ignored. The <code>disabled</code> UI/UX and functional behavior works normally.
870
870
  </p>
@@ -907,6 +907,9 @@
907
907
  <p>The component may be rendered with one or more <code>disabled</code> options. When navigating the list of options with the keyboard or assistive technology to mark the next or previous option as active, disabled options will be skipped, jumping to the next enabled option.</p>
908
908
  <p>While using the pointer to mark options as active, hovering over disabled options will be ignored and the previous active option will remain active.</p>
909
909
  <p class="note">
910
+ <strong>Note:</strong> Setting the component <code>value</code> to one that matches a <code>disabled</code> or <code>static</code> option — whether via the <code>value</code> attribute or programmatic assignment (e.g. <code>el.value = 'x'</code>) — is rejected: <code>value</code> and <code>optionSelected</code> are cleared to <code>undefined</code>. A value matching a <code>hidden</code> option is still applied.
911
+ </p>
912
+ <p class="note">
910
913
  <strong>Note:</strong> If the currently <code>selected</code> option is marked as <code>disabled</code>, the component value is reset to <code>undefined</code> and the component validation workflow is performed (e.g., if the component instance is <code>required</code> it will set <code>validity="valueMissing".</code>).
911
914
  </p>
912
915
  <p class="note">