@aurodesignsystem-dev/auro-formkit 0.0.0-pr1452.0 → 0.0.0-pr1456.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 (88) hide show
  1. package/components/checkbox/demo/api.min.js +3 -2
  2. package/components/checkbox/demo/index.min.js +3 -2
  3. package/components/checkbox/dist/auro-checkbox-group.d.ts +6 -6
  4. package/components/checkbox/dist/auro-checkbox.d.ts +9 -8
  5. package/components/checkbox/dist/index.js +3 -2
  6. package/components/checkbox/dist/registered.js +3 -2
  7. package/components/combobox/demo/api.min.js +1436 -1434
  8. package/components/combobox/demo/index.min.js +1436 -1434
  9. package/components/combobox/dist/auro-combobox.d.ts +35 -35
  10. package/components/combobox/dist/index.js +8 -6
  11. package/components/combobox/dist/registered.js +8 -6
  12. package/components/counter/demo/api.min.js +2 -2
  13. package/components/counter/demo/index.min.js +2 -2
  14. package/components/counter/dist/auro-counter-group.d.ts +2 -2
  15. package/components/counter/dist/auro-counter.d.ts +10 -10
  16. package/components/counter/dist/index.js +2 -2
  17. package/components/counter/dist/registered.js +2 -2
  18. package/components/datepicker/demo/api.min.js +6 -6
  19. package/components/datepicker/demo/index.min.js +6 -6
  20. package/components/datepicker/dist/{src/auro-calendar-cell.d.ts → auro-calendar-cell.d.ts} +2 -2
  21. package/components/datepicker/dist/{src/auro-datepicker.d.ts → auro-datepicker.d.ts} +13 -13
  22. package/components/datepicker/dist/index.js +6 -6
  23. package/components/datepicker/dist/registered.js +6 -6
  24. package/components/datepicker/dist/{src/utilities.d.ts → utilities.d.ts} +4 -4
  25. package/components/datepicker/dist/{src/utilitiesCalendar.d.ts → utilitiesCalendar.d.ts} +3 -3
  26. package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-calendar.d.ts +2 -2
  27. package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker.d.ts +1 -1
  28. package/components/dropdown/demo/api.min.js +1 -1
  29. package/components/dropdown/demo/index.min.js +1 -1
  30. package/components/dropdown/dist/auro-dropdown.d.ts +22 -22
  31. package/components/dropdown/dist/auro-dropdownBib.d.ts +3 -3
  32. package/components/dropdown/dist/dropdownBibKeyboardStrategy.d.ts +1 -1
  33. package/components/dropdown/dist/index.js +1 -1
  34. package/components/dropdown/dist/registered.js +1 -1
  35. package/components/form/demo/api.min.js +1527 -1524
  36. package/components/form/demo/index.min.js +1527 -1524
  37. package/components/input/demo/api.min.js +4 -4
  38. package/components/input/demo/index.min.js +4 -4
  39. package/components/input/dist/auro-input.d.ts +1 -1
  40. package/components/input/dist/base-input.d.ts +30 -29
  41. package/components/input/dist/index.js +4 -4
  42. package/components/input/dist/registered.js +4 -4
  43. package/components/menu/demo/api.md +2 -2
  44. package/components/menu/demo/api.min.js +1536 -1536
  45. package/components/menu/demo/index.min.js +1536 -1536
  46. package/components/menu/dist/auro-menu-utils.d.ts +1 -1
  47. package/components/menu/dist/auro-menu.context.d.ts +4 -3
  48. package/components/menu/dist/auro-menu.d.ts +4 -4
  49. package/components/menu/dist/auro-menuoption.d.ts +6 -6
  50. package/components/menu/dist/index.js +1565 -1565
  51. package/components/menu/dist/registered.js +1521 -1521
  52. package/components/radio/demo/api.min.js +1 -1
  53. package/components/radio/demo/index.min.js +1 -1
  54. package/components/radio/dist/auro-radio-group.d.ts +9 -9
  55. package/components/radio/dist/auro-radio.d.ts +8 -8
  56. package/components/radio/dist/index.js +1 -1
  57. package/components/radio/dist/registered.js +1 -1
  58. package/components/select/demo/api.min.js +1433 -1433
  59. package/components/select/demo/index.min.js +1433 -1433
  60. package/components/select/dist/auro-select.d.ts +11 -11
  61. package/components/select/dist/index.js +2 -2
  62. package/components/select/dist/registered.js +2 -2
  63. package/custom-elements.json +5 -2
  64. package/package.json +27 -41
  65. /package/components/datepicker/dist/{src/auro-calendar-month.d.ts → auro-calendar-month.d.ts} +0 -0
  66. /package/components/datepicker/dist/{src/auro-calendar.d.ts → auro-calendar.d.ts} +0 -0
  67. /package/components/datepicker/dist/{src/buttonVersion.d.ts → buttonVersion.d.ts} +0 -0
  68. /package/components/datepicker/dist/{src/datepickerKeyboardStrategy.d.ts → datepickerKeyboardStrategy.d.ts} +0 -0
  69. /package/components/datepicker/dist/{src/iconVersion.d.ts → iconVersion.d.ts} +0 -0
  70. /package/components/datepicker/dist/{src/index.d.ts → index.d.ts} +0 -0
  71. /package/components/datepicker/dist/{src/popoverVersion.d.ts → popoverVersion.d.ts} +0 -0
  72. /package/components/datepicker/dist/{src/styles → styles}/classic/color-css.d.ts +0 -0
  73. /package/components/datepicker/dist/{src/styles → styles}/classic/style-css.d.ts +0 -0
  74. /package/components/datepicker/dist/{src/styles → styles}/color-calendar-css.d.ts +0 -0
  75. /package/components/datepicker/dist/{src/styles → styles}/color-cell-css.d.ts +0 -0
  76. /package/components/datepicker/dist/{src/styles → styles}/color-css.d.ts +0 -0
  77. /package/components/datepicker/dist/{src/styles → styles}/color-month-css.d.ts +0 -0
  78. /package/components/datepicker/dist/{src/styles → styles}/shapeSize-css.d.ts +0 -0
  79. /package/components/datepicker/dist/{src/styles → styles}/snowflake/color-css.d.ts +0 -0
  80. /package/components/datepicker/dist/{src/styles → styles}/snowflake/style-css.d.ts +0 -0
  81. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-cell-css.d.ts +0 -0
  82. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-css.d.ts +0 -0
  83. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-month-css.d.ts +0 -0
  84. /package/components/datepicker/dist/{src/styles → styles}/style-css.d.ts +0 -0
  85. /package/components/datepicker/dist/{src/styles → styles}/tokens-css.d.ts +0 -0
  86. /package/components/datepicker/dist/{src/utilitiesCalendarRender.d.ts → utilitiesCalendarRender.d.ts} +0 -0
  87. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/day.d.ts +0 -0
  88. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-cell.d.ts +0 -0
@@ -5228,7 +5228,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$4 {
5228
5228
  }
5229
5229
  };
5230
5230
 
5231
- var formkitVersion$2 = '202604242248';
5231
+ var formkitVersion$2 = '202605011613';
5232
5232
 
5233
5233
  let AuroElement$2 = class AuroElement extends i$4 {
5234
5234
  static get properties() {
@@ -11700,7 +11700,8 @@ class BaseInput extends AuroElement$1 {
11700
11700
  * The id global attribute defines an identifier (ID) which must be unique in the whole document.
11701
11701
  */
11702
11702
  id: {
11703
- type: String
11703
+ type: String,
11704
+ reflect: true,
11704
11705
  },
11705
11706
 
11706
11707
  /**
@@ -12237,11 +12238,10 @@ class BaseInput extends AuroElement$1 {
12237
12238
  */
12238
12239
  handleClickClear() {
12239
12240
  this.inputElement.value = "";
12240
- this.value = "";
12241
12241
  this.labelElement.classList.remove('inputElement-label--sticky');
12242
+ this.validation.reset(this);
12242
12243
  this.notifyValueChanged();
12243
12244
  this.focus();
12244
- this.validation.validate(this);
12245
12245
  }
12246
12246
 
12247
12247
  /**
@@ -12980,7 +12980,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12980
12980
  }
12981
12981
  };
12982
12982
 
12983
- var formkitVersion$1 = '202604242248';
12983
+ var formkitVersion$1 = '202605011613';
12984
12984
 
12985
12985
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12986
12986
  // See LICENSE in the project root for license information.
@@ -14045,7 +14045,7 @@ class AuroBibtemplate extends i$4 {
14045
14045
  }
14046
14046
  }
14047
14047
 
14048
- var formkitVersion = '202604242248';
14048
+ var formkitVersion = '202605011613';
14049
14049
 
14050
14050
  var styleCss$3 = i$7`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
14051
14051
 
@@ -15091,6 +15091,8 @@ class AuroCombobox extends AuroElement {
15091
15091
  * @returns {void}
15092
15092
  */
15093
15093
  showBib() {
15094
+ // for fullscreen bib, with `noFilter` showBib() gets called again as availableOptions gets updated
15095
+ // to prevent closing bib in that case, adding guard not to hide bib for empty input on fullscreen bib
15094
15096
  if (!this.input.value && !this.dropdown.isBibFullscreen) {
15095
15097
  this.dropdown.hide();
15096
15098
  return;
@@ -16068,1525 +16070,1384 @@ class s{get value(){return this.o}set value(s){this.setValue(s);}setValue(s,t=fa
16068
16070
  * SPDX-License-Identifier: BSD-3-Clause
16069
16071
  */class e extends Event{constructor(t,s){super("context-provider",{bubbles:true,composed:true}),this.context=t,this.contextTarget=s;}}class i extends s{constructor(s,e,i){super(void 0!==e.context?e.initialValue:i),this.onContextRequest=t=>{if(t.context!==this.context)return;const s=t.contextTarget??t.composedPath()[0];s!==this.host&&(t.stopPropagation(),this.addCallback(t.callback,s,t.subscribe));},this.onProviderRequest=s=>{if(s.context!==this.context)return;if((s.contextTarget??s.composedPath()[0])===this.host)return;const e=new Set;for(const[s,{consumerHost:i}]of this.subscriptions)e.has(s)||(e.add(s),i.dispatchEvent(new s$2(this.context,i,s,true)));s.stopPropagation();},this.host=s,void 0!==e.context?this.context=e.context:this.context=e,this.attachListeners(),this.host.addController?.(this);}attachListeners(){this.host.addEventListener("context-request",this.onContextRequest),this.host.addEventListener("context-provider",this.onProviderRequest);}hostConnected(){this.host.dispatchEvent(new e(this.context,this.host));}}
16070
16072
 
16071
- /* eslint-disable */
16073
+ var styleCss = i$7`.body-default{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-default-font-size, 1rem);line-height:var(--wcss-body-default-line-height, 1.5rem)}.body-lg{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-lg-font-size, 1.125rem);line-height:var(--wcss-body-lg-line-height, 1.625rem)}.body-sm{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-sm-font-size, 0.875rem);line-height:var(--wcss-body-sm-line-height, 1.25rem)}.body-xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-xs-font-size, 0.75rem);line-height:var(--wcss-body-xs-line-height, 1rem)}.body-2xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-2xs-font-size, 0.625rem);line-height:var(--wcss-body-2xs-line-height, 0.875rem)}.display-2xl{font-family:var(--wcss-display-2xl-family, "AS Circular"),var(--wcss-display-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-2xl-letter-spacing, 0);font-weight:var(--wcss-display-2xl-weight, 300);line-height:var(--wcss-display-2xl-line-height, 1.3);font-size:var(--wcss-display-2xl-font-size, clamp(3.5rem, 6vw, 5.375rem))}.display-xl{font-family:var(--wcss-display-xl-family, "AS Circular"),var(--wcss-display-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-xl-letter-spacing, 0);font-weight:var(--wcss-display-xl-weight, 300);line-height:var(--wcss-display-xl-line-height, 1.3);font-size:var(--wcss-display-xl-font-size, clamp(3rem, 5.3333333333vw, 4.5rem))}.display-lg{font-family:var(--wcss-display-lg-family, "AS Circular"),var(--wcss-display-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-lg-letter-spacing, 0);font-weight:var(--wcss-display-lg-weight, 300);line-height:var(--wcss-display-lg-line-height, 1.3);font-size:var(--wcss-display-lg-font-size, clamp(2.75rem, 4.6666666667vw, 4rem))}.display-md{font-family:var(--wcss-display-md-family, "AS Circular"),var(--wcss-display-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-md-letter-spacing, 0);font-weight:var(--wcss-display-md-weight, 300);line-height:var(--wcss-display-md-line-height, 1.3);font-size:var(--wcss-display-md-font-size, clamp(2.5rem, 4vw, 3.5rem))}.display-sm{font-family:var(--wcss-display-sm-family, "AS Circular"),var(--wcss-display-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-sm-letter-spacing, 0);font-weight:var(--wcss-display-sm-weight, 300);line-height:var(--wcss-display-sm-line-height, 1.3);font-size:var(--wcss-display-sm-font-size, clamp(2rem, 3.6666666667vw, 3rem))}.display-xs{font-family:var(--wcss-display-xs-family, "AS Circular"),var(--wcss-display-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-xs-letter-spacing, 0);font-weight:var(--wcss-display-xs-weight, 300);line-height:var(--wcss-display-xs-line-height, 1.3);font-size:var(--wcss-display-xs-font-size, clamp(1.75rem, 3vw, 2.375rem))}.heading-xl{font-family:var(--wcss-heading-xl-family, "AS Circular"),var(--wcss-heading-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-xl-letter-spacing, 0);font-weight:var(--wcss-heading-xl-weight, 300);line-height:var(--wcss-heading-xl-line-height, 1.3);font-size:var(--wcss-heading-xl-font-size, clamp(2rem, 3vw, 2.5rem))}.heading-lg{font-family:var(--wcss-heading-lg-family, "AS Circular"),var(--wcss-heading-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-lg-letter-spacing, 0);font-weight:var(--wcss-heading-lg-weight, 300);line-height:var(--wcss-heading-lg-line-height, 1.3);font-size:var(--wcss-heading-lg-font-size, clamp(1.75rem, 2.6666666667vw, 2.25rem))}.heading-md{font-family:var(--wcss-heading-md-family, "AS Circular"),var(--wcss-heading-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-md-letter-spacing, 0);font-weight:var(--wcss-heading-md-weight, 300);line-height:var(--wcss-heading-md-line-height, 1.3);font-size:var(--wcss-heading-md-font-size, clamp(1.625rem, 2.3333333333vw, 1.75rem))}.heading-sm{font-family:var(--wcss-heading-sm-family, "AS Circular"),var(--wcss-heading-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-sm-letter-spacing, 0);font-weight:var(--wcss-heading-sm-weight, 300);line-height:var(--wcss-heading-sm-line-height, 1.3);font-size:var(--wcss-heading-sm-font-size, clamp(1.375rem, 2vw, 1.5rem))}.heading-xs{font-family:var(--wcss-heading-xs-family, "AS Circular"),var(--wcss-heading-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-xs-letter-spacing, 0);font-weight:var(--wcss-heading-xs-weight, 450);line-height:var(--wcss-heading-xs-line-height, 1.3);font-size:var(--wcss-heading-xs-font-size, clamp(1.25rem, 1.6666666667vw, 1.25rem))}.heading-2xs{font-family:var(--wcss-heading-2xs-family, "AS Circular"),var(--wcss-heading-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-2xs-letter-spacing, 0);font-weight:var(--wcss-heading-2xs-weight, 450);line-height:var(--wcss-heading-2xs-line-height, 1.3);font-size:var(--wcss-heading-2xs-font-size, clamp(1.125rem, 1.5vw, 1.125rem))}.accent-2xl{font-family:var(--wcss-accent-2xl-family, "Good OT"),var(--wcss-accent-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-2xl-letter-spacing, 0.05em);font-weight:var(--wcss-accent-2xl-weight, 450);line-height:var(--wcss-accent-2xl-line-height, 1);font-size:var(--wcss-accent-2xl-font-size, clamp(2rem, 3.1666666667vw, 2.375rem));text-transform:uppercase}.accent-xl{font-family:var(--wcss-accent-xl-family, "Good OT"),var(--wcss-accent-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-xl-letter-spacing, 0.05em);font-weight:var(--wcss-accent-xl-weight, 450);line-height:var(--wcss-accent-xl-line-height, 1.3);font-size:var(--wcss-accent-xl-font-size, clamp(1.625rem, 2.3333333333vw, 2rem));text-transform:uppercase}.accent-lg{font-family:var(--wcss-accent-lg-family, "Good OT"),var(--wcss-accent-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-lg-letter-spacing, 0.05em);font-weight:var(--wcss-accent-lg-weight, 450);line-height:var(--wcss-accent-lg-line-height, 1.3);font-size:var(--wcss-accent-lg-font-size, clamp(1.5rem, 2.1666666667vw, 1.75rem));text-transform:uppercase}.accent-md{font-family:var(--wcss-accent-md-family, "Good OT"),var(--wcss-accent-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-md-letter-spacing, 0.05em);font-weight:var(--wcss-accent-md-weight, 500);line-height:var(--wcss-accent-md-line-height, 1.3);font-size:var(--wcss-accent-md-font-size, clamp(1.375rem, 1.8333333333vw, 1.5rem));text-transform:uppercase}.accent-sm{font-family:var(--wcss-accent-sm-family, "Good OT"),var(--wcss-accent-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-sm-letter-spacing, 0.05em);font-weight:var(--wcss-accent-sm-weight, 500);line-height:var(--wcss-accent-sm-line-height, 1.3);font-size:var(--wcss-accent-sm-font-size, clamp(1.125rem, 1.5vw, 1.25rem));text-transform:uppercase}.accent-xs{font-family:var(--wcss-accent-xs-family, "Good OT"),var(--wcss-accent-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-xs-letter-spacing, 0.1em);font-weight:var(--wcss-accent-xs-weight, 500);line-height:var(--wcss-accent-xs-line-height, 1.3);font-size:var(--wcss-accent-xs-font-size, clamp(1rem, 1.3333333333vw, 1rem));text-transform:uppercase}.accent-2xs{font-family:var(--wcss-accent-2xs-family, "Good OT"),var(--wcss-accent-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-2xs-letter-spacing, 0.1em);font-weight:var(--wcss-accent-2xs-weight, 450);line-height:var(--wcss-accent-2xs-line-height, 1.3);font-size:var(--wcss-accent-2xs-font-size, clamp(0.875rem, 1.1666666667vw, 0.875rem));text-transform:uppercase}:host{cursor:pointer;user-select:none;text-overflow:ellipsis;max-width:100dvw}:host .wrapper{display:flex;align-items:center;height:var(--ds-size-400, 2rem);padding-right:var(--ds-size-200, 1rem);padding-left:calc(var(--ds-size-150, 0.75rem) + var(--ds-size-300, 1.5rem) + var(--ds-size-100, 0.5rem));border-radius:var(--ds-size-100, 0.5rem);-webkit-tap-highlight-color:transparent}:host .wrapper[class*=shape-box]{border-radius:unset}:host .wrapper[class*=shape-snowflake]{border-radius:unset;line-height:24px}:host .wrapper[class*=shape-pill]{border-radius:30px}:host .wrapper[class*=-lg]{padding-top:var(--ds-size-75, 0.375rem);padding-bottom:var(--ds-size-75, 0.375rem);padding-right:var(--ds-size-150, 0.75rem);line-height:26px}:host .wrapper[class*=-xl]{padding-top:var(--ds-size-100, 0.5rem);padding-bottom:var(--ds-size-100, 0.5rem);padding-right:var(--ds-size-200, 1rem);line-height:26px}:host slot{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host [auro-icon]{--ds-auro-icon-size: var(--ds-size-300, 1.5rem);margin-right:var(--ds-size-150, 0.75rem);margin-left:var(--ds-size-100, 0.5rem)}:host ::slotted(.nestingSpacer){display:inline-block;width:var(--ds-size-300, 1.5rem)}[slot=displayValue]{display:none}:host([loadingplaceholder]) .wrapper{padding-left:calc(var(--ds-size-150, 0.75rem) + var(--ds-size-300, 1.5rem) + var(--ds-size-100, 0.5rem))}:host([selected]) .wrapper{padding-left:0}:host([nocheckmark]) .wrapper{padding-left:var(--ds-size-150, 0.75rem)}:host([nocheckmark]) .wrapper[class*=-lg]{padding-left:var(--ds-size-150, 0.75rem)}:host([nocheckmark]) .wrapper[class*=-xl]{padding-left:var(--ds-size-200, 1rem)}:host([hidden]){display:none}:host([static]){pointer-events:none}:host([disabled]:hover){cursor:auto}:host([disabled]){user-select:none;pointer-events:none}`;
16072
16074
 
16073
- class MenuService {
16075
+ var colorCss = i$7`:host .wrapper{background-color:var(--ds-auro-menuoption-container-color, transparent);box-shadow:inset 0 0 0 1px var(--ds-auro-menuoption-container-border-color, transparent);color:var(--ds-auro-menuoption-text-color)}:host svg{fill:var(--ds-auro-menuoption-icon-color)}:host([disabled]){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-menuoption-text-color: var(--ds-basic-color-texticon-disabled, #d0d0d0);--ds-auro-menuoption-icon-color: var(--ds-basic-color-texticon-disabled, #d0d0d0)}:host(.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}@media(hover: hover){:host(:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}}:host(:focus){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}:host([selected]){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-subtle, #b4eff9);--ds-auro-menuoption-text-color: var(--ds-basic-color-texticon-default, #2a2a2a);--ds-auro-menuoption-icon-color: var(--ds-basic-color-texticon-default, #2a2a2a)}:host([selected].active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}@media(hover: hover){:host([selected]:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}}:host([selected]:focus){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-subtle, #b4eff9);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}:host(:focus.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}@media(hover: hover){:host(:focus:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}}:host([selected]:focus.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}@media(hover: hover){:host([selected]:focus:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}}`;
16074
16076
 
16075
- /**
16076
- * PROPERTIES AND GETTERS
16077
- */
16077
+ class p{registerComponent(t,a){customElements.get(t)||customElements.define(t,class extends a{});}closestElement(t,a=this,e=(a,s=a&&a.closest(t))=>a&&a!==document&&a!==window?s||e(a.getRootNode().host):null){return e(a)}handleComponentTagRename(t,a){const e=a.toLowerCase();t.tagName.toLowerCase()!==e&&t.setAttribute(e,true);}elementMatch(t,a){const e=a.toLowerCase();return t.tagName.toLowerCase()===e||t.hasAttribute(e)}getSlotText(t,a){const e=t.shadowRoot?.querySelector(`slot[name="${a}"]`);return (e?.assignedNodes({flatten:true})||[]).map(t=>t.textContent?.trim()).join(" ").trim()||null}}var u='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-labelledby="error__desc" class="ico_squareLarge" data-deprecated="true" role="img" style="min-width:var(--auro-size-lg, var(--ds-size-300, 1.5rem));height:var(--auro-size-lg, var(--ds-size-300, 1.5rem));fill:currentColor" viewBox="0 0 24 24" part="svg"><title/><desc id="error__desc">Error alert indicator.</desc><path d="m13.047 5.599 6.786 11.586A1.207 1.207 0 0 1 18.786 19H5.214a1.207 1.207 0 0 1-1.047-1.815l6.786-11.586a1.214 1.214 0 0 1 2.094 0m-1.165.87a.23.23 0 0 0-.085.085L5.419 17.442a.232.232 0 0 0 .203.35h12.756a.234.234 0 0 0 .203-.35L12.203 6.554a.236.236 0 0 0-.321-.084M12 15.5a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5m-.024-6.22c.325 0 .589.261.589.583v4.434a.586.586 0 0 1-.589.583.586.586 0 0 1-.588-.583V9.863c0-.322.264-.583.588-.583"/></svg>';class m extends i$4{static get properties(){return {hidden:{type:Boolean,reflect:true},hiddenVisually:{type:Boolean,reflect:true},hiddenAudible:{type:Boolean,reflect:true}}}hideAudible(t){return t?"true":"false"}}const g=new Map,f=(t,a={})=>{const e=a.responseParser||(t=>t.text());return g.has(t)||g.set(t,fetch(t).then(e)),g.get(t)};var w=i$7`:focus:not(:focus-visible){outline:3px solid transparent}.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock,:host{display:block}.util_displayFlex{display:flex}.util_displayHidden,:host([hidden]:not(:focus):not(:active)){display:none}.util_displayHiddenVisually,:host([hiddenVisually]:not(:focus):not(:active)){position:absolute;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px;padding:0;border:0}.ico_squareLarge{fill:currentColor;height:var(--auro-size-lg, var(--ds-size-300, 1.5rem))}.ico_squareSmall{fill:currentColor;height:.6rem}.ico_squareMed{fill:currentColor;height:var(--auro-size-md, var(--ds-size-200, 1rem))}.ico_squareSml{fill:currentColor;height:var(--auro-size-sm, var(--ds-size-150, .75rem))}:host{color:currentColor;vertical-align:middle;display:inline-block}svg{min-width:var(--ds-auro-icon-size, 1.5rem)!important;width:var(--ds-auro-icon-size, 1.5rem)!important;height:var(--ds-auro-icon-size, 1.5rem)!important}.componentWrapper{display:flex;line-height:var(--ds-auro-icon-size)}.svgWrapper{height:var(--ds-auro-icon-size);width:var(--ds-auro-icon-size)}.svgWrapper [part=svg]{display:flex}.labelWrapper{margin-left:var(--ds-size-50, .25rem)}.labelWrapper ::slotted(*){line-height:inherit!important}
16078
+ `;class z extends m{constructor(){super(),this._initializeDefaults();}_initializeDefaults(){this.onDark=false,this.appearance="default";}static get properties(){return {...m.properties,onDark:{type:Boolean,reflect:true},appearance:{type:String,reflect:true},svg:{attribute:false,reflect:true}}}static get styles(){return w}async fetchIcon(t,a){let e="";e="logos"===t?await f(`${this.uri}/${t}/${a}.svg`):await f(`${this.uri}/icons/${t}/${a}.svg`);return (new DOMParser).parseFromString(e,"text/html").body.querySelector("svg")}async firstUpdated(){try{if(!this.customSvg){const t=await this.fetchIcon(this.category,this.name);if(t)this.svg=t;else if(!t){const t=(new DOMParser).parseFromString(u,"text/html");this.svg=t.body.firstChild;}}}catch(t){this.svg=void 0;}}}i$7`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock,:host{display:block}.util_displayFlex{display:flex}.util_displayHidden,:host([hidden]:not(:focus):not(:active)){display:none}.util_displayHiddenVisually,:host([hiddenVisually]:not(:focus):not(:active)){position:absolute;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px;padding:0;border:0}:host{display:inline-block;--ds-auro-icon-size: 100%;width:100%;height:100%}:host .logo{color:var(--ds-auro-alaska-color)}:host([onDark]),:host([appearance=inverse]){--ds-auro-alaska-color: #FFF}
16079
+ `;var y=i$7`:host{--ds-auro-icon-color: var(--ds-basic-color-texticon-default, #2a2a2a);--ds-auro-alaska-color: #02426D;--ds-auro-icon-size: var(--ds-size-300, 1.5rem)}
16080
+ `;var x=i$7`:host{color:var(--ds-auro-icon-color)}:host([customColor]){color:inherit}:host(:not([onDark])[variant=accent1]),:host(:not([appearance=inverse])[variant=accent1]){--ds-auro-icon-color: var(--ds-basic-color-texticon-accent1, #265688)}:host(:not([onDark])[variant=disabled]),:host(:not([appearance=inverse])[variant=disabled]){--ds-auro-icon-color: var(--ds-basic-color-texticon-disabled, #d0d0d0)}:host(:not([onDark])[variant=muted]),:host(:not([appearance=inverse])[variant=muted]){--ds-auro-icon-color: var(--ds-basic-color-texticon-muted, #676767)}:host(:not([onDark])[variant=statusDefault]),:host(:not([appearance=inverse])[variant=statusDefault]){--ds-auro-icon-color: var(--ds-basic-color-status-default, #afb9c6)}:host(:not([onDark])[variant=statusInfo]),:host(:not([appearance=inverse])[variant=statusInfo]){--ds-auro-icon-color: var(--ds-basic-color-status-info, #01426a)}:host(:not([onDark])[variant=statusSuccess]),:host(:not([appearance=inverse])[variant=statusSuccess]){--ds-auro-icon-color: var(--ds-basic-color-status-success, #447a1f)}:host(:not([onDark])[variant=statusWarning]),:host(:not([appearance=inverse])[variant=statusWarning]){--ds-auro-icon-color: var(--ds-basic-color-status-warning, #fac200)}:host(:not([onDark])[variant=statusError]),:host(:not([appearance=inverse])[variant=statusError]){--ds-auro-icon-color: var(--ds-basic-color-status-error, #e31f26)}:host(:not([onDark])[variant=statusInfoSubtle]),:host(:not([appearance=inverse])[variant=statusInfoSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-info-subtle, #ebf3f9)}:host(:not([onDark])[variant=statusSuccessSubtle]),:host(:not([appearance=inverse])[variant=statusSuccessSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-success-subtle, #d6eac7)}:host(:not([onDark])[variant=statusWarningSubtle]),:host(:not([appearance=inverse])[variant=statusWarningSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-warning-subtle, #fff0b2)}:host(:not([onDark])[variant=statusErrorSubtle]),:host(:not([appearance=inverse])[variant=statusErrorSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-error-subtle, #fbc6c6)}:host(:not([onDark])[variant=fareBasicEconomy]),:host(:not([appearance=inverse])[variant=fareBasicEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-basiceconomy, #97eaf8)}:host(:not([onDark])[variant=fareBusiness]),:host(:not([appearance=inverse])[variant=fareBusiness]){--ds-auro-icon-color: var(--ds-basic-color-fare-business, #01426a)}:host(:not([onDark])[variant=fareEconomy]),:host(:not([appearance=inverse])[variant=fareEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-economy, #0074ca)}:host(:not([onDark])[variant=fareFirst]),:host(:not([appearance=inverse])[variant=fareFirst]){--ds-auro-icon-color: var(--ds-basic-color-fare-first, #00274a)}:host(:not([onDark])[variant=farePremiumEconomy]),:host(:not([appearance=inverse])[variant=farePremiumEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-premiumeconomy, #005154)}:host(:not([onDark])[variant=tierOneWorldEmerald]),:host(:not([appearance=inverse])[variant=tierOneWorldEmerald]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-emerald, #139142)}:host(:not([onDark])[variant=tierOneWorldSapphire]),:host(:not([appearance=inverse])[variant=tierOneWorldSapphire]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-sapphire, #015daa)}:host(:not([onDark])[variant=tierOneWorldRuby]),:host(:not([appearance=inverse])[variant=tierOneWorldRuby]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-ruby, #a41d4a)}:host([onDark]),:host([appearance=inverse]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse, #ffffff)}:host([onDark][variant=disabled]),:host([appearance=inverse][variant=disabled]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse-disabled, #7e8894)}:host([onDark][variant=muted]),:host([appearance=inverse][variant=muted]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse-muted, #ccd2db)}:host([onDark][variant=statusError]),:host([appearance=inverse][variant=statusError]){--ds-auro-icon-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8)}
16081
+ `;class _ extends z{constructor(){super(),this._initializeDefaults();}_initializeDefaults(){this.variant=void 0,this.uri="https://cdn.jsdelivr.net/npm/@alaskaairux/icons@latest/dist",this.runtimeUtils=new p;}static get properties(){return {...z.properties,ariaHidden:{type:String,reflect:true},category:{type:String,reflect:true},customColor:{type:Boolean,reflect:true},customSvg:{type:Boolean},label:{type:Boolean,reflect:true},name:{type:String,reflect:true},variant:{type:String,reflect:true}}}static get styles(){return [z.styles,y,w,x]}static register(t="auro-icon"){p.prototype.registerComponent(t,_);}connectedCallback(){super.connectedCallback(),this.runtimeUtils.handleComponentTagRename(this,"auro-icon");}exposeCssParts(){this.setAttribute("exportparts","svg:iconSvg");}async firstUpdated(){if(await super.firstUpdated(),this.hasAttribute("ariaHidden")&&this.svg){const t=this.svg.querySelector("desc");t&&(t.remove(),this.svg.removeAttribute("aria-labelledby"));}}render(){const t={labelWrapper:true,util_displayHiddenVisually:!this.label};return b$2`
16082
+ <div class="componentWrapper">
16083
+ <div
16084
+ class="${e$3({svgWrapper:true})}"
16085
+ title="${o(this.title||void 0)}">
16086
+ <span aria-hidden="${o(this.ariaHidden||true)}" part="svg">
16087
+ ${this.customSvg?b$2`
16088
+ <slot name="svg"></slot>
16089
+ `:b$2`
16090
+ ${this.svg}
16091
+ `}
16092
+ </span>
16093
+ </div>
16078
16094
 
16079
- /**
16080
- * Gets the list of registered menu options.
16081
- * @returns {AuroMenuOption[]}
16082
- */
16083
- get menuOptions() {
16084
- return this._menuOptions;
16085
- }
16095
+ <div class="${e$3(t)}" part="label">
16096
+ <slot></slot>
16097
+ </div>
16098
+ </div>
16099
+ `}}
16086
16100
 
16087
- /**
16088
- * Gets the currently highlighted option.
16089
- * @returns {AuroMenuOption|null}
16090
- */
16091
- get highlightedOption() {
16092
- return this._menuOptions[this.highlightedIndex] || null;
16093
- }
16101
+ var iconVersion = '9.1.2';
16094
16102
 
16095
- /**
16096
- * Gets the current value(s) of the selected option(s).
16097
- * @returns {string|string[]|undefined}
16098
- */
16099
- get currentValue() {
16100
- const values = (this.selectedOptions || []).map(option => option.value);
16101
- return this.multiSelect ? values : values[0];
16102
- }
16103
+ var checkmarkIcon = {"svg":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" aria-labelledby=\"checkmark-sm__desc\" class=\"ico_squareLarge\" role=\"img\" style=\"min-width:var(--auro-size-lg, var(--ds-size-300, 1.5rem));height:var(--auro-size-lg, var(--ds-size-300, 1.5rem));fill:currentColor\" viewBox=\"0 0 24 24\" part=\"svg\"><title/><desc id=\"checkmark-sm__desc\">a small check mark.</desc><path d=\"M8.461 11.84a.625.625 0 1 0-.922.844l2.504 2.738c.247.27.674.27.922 0l5.496-6a.625.625 0 1 0-.922-.844l-5.035 5.496z\"/></svg>"};
16103
16104
 
16104
- /**
16105
- * Gets the label(s) of the currently selected option(s).
16106
- * @returns {string}
16107
- */
16108
- get currentLabel() {
16109
- const labels = (this.selectedOptions || []).map(option => option.textContent);
16110
- return this.multiSelect ? labels.join(", ") : labels[0] || '';
16105
+ // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16106
+ // See LICENSE in the project root for license information.
16107
+
16108
+
16109
+ /**
16110
+ * Helper method to dispatch custom events.
16111
+ * @param {HTMLElement} element - Element to dispatch event from.
16112
+ * @param {string} eventName - Name of the event to dispatch.
16113
+ * @param {Object} [detail] - Optional detail object to include with the event.
16114
+ */
16115
+ function dispatchMenuEvent(element, eventName, detail = null) {
16116
+ const eventConfig = {
16117
+ bubbles: true,
16118
+ cancelable: false,
16119
+ composed: true
16120
+ };
16121
+
16122
+ if (detail !== null) {
16123
+ eventConfig.detail = detail;
16111
16124
  }
16112
16125
 
16113
- /**
16114
- * Gets the string representation of the current value(s).
16115
- * For multi-select, this is a JSON stringified array.
16116
- * @returns {string|undefined}
16117
- */
16118
- get stringValue() {
16119
- const { currentValue } = this;
16126
+ element.dispatchEvent(new CustomEvent(eventName, eventConfig));
16127
+ }
16120
16128
 
16121
- if (Array.isArray(currentValue)) {
16122
- if (currentValue.length > 0) {
16123
- return JSON.stringify(currentValue);
16124
- }
16125
- return undefined;
16126
- }
16129
+ // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16130
+ // See LICENSE in the project root for license information.
16127
16131
 
16128
- if (typeof currentValue === 'string') {
16129
- if (currentValue.length > 0) {
16130
- return currentValue;
16131
- }
16132
- return undefined;
16133
- }
16134
16132
 
16135
- // Future: handle other types here (e.g., number, object, etc.)
16136
- return undefined;
16137
- }
16133
+ let menuOptionIdCounter = 0;
16138
16134
 
16139
- /**
16140
- * Gets the key(s) of the currently selected option(s).
16141
- * @returns {string|string[]|undefined}
16142
- */
16143
- get currentKeys() {
16144
- const keys = (this.selectedOptions || []).map(option => option.key);
16145
- return this.multiSelect ? keys : keys[0];
16146
- }
16135
+ /**
16136
+ * The `auro-menuoption` element provides users a way to define a menu option.
16137
+ * @customElement auro-menuoption
16138
+ *
16139
+ * @slot default - The default slot for the menu option text.
16140
+ *
16141
+ * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
16142
+ */
16143
+ class AuroMenuOption extends AuroElement {
16147
16144
 
16148
16145
  /**
16149
- * CONSTRUCTOR
16146
+ * This will register this element with the browser.
16147
+ * @param {string} [name="auro-menuoption"] - The name of the element that you want to register.
16148
+ *
16149
+ * @example
16150
+ * AuroMenuOption.register("custom-menuoption") // this will register this element to <custom-menuoption/>
16151
+ *
16150
16152
  */
16153
+ static register(name = "auro-menuoption") {
16154
+ AuroLibraryRuntimeUtils$4.prototype.registerComponent(name, AuroMenuOption);
16155
+ }
16151
16156
 
16152
16157
  /**
16153
- * Creates a new MenuService instance.
16154
- * @param {Object} options - The options object.
16155
- * @param {AuroMenu} options.host - The host element that this service will control. Required.
16156
- * @throws {Error} If the host is not provided.
16158
+ * Returns whether the menu option is currently active and selectable.
16159
+ * An option is considered active if it is not hidden, not disabled, and not static.
16160
+ * @returns {boolean} True if the option is active, false otherwise.
16157
16161
  */
16158
- constructor({ host } = {}) {
16162
+ get isActive() {
16163
+ return !this.hasAttribute('hidden') &&
16164
+ !this.disabled &&
16165
+ !this.hasAttribute('static');
16166
+ }
16159
16167
 
16160
- // Ensure a host was passed
16161
- if (!host) {
16162
- throw new Error("MenuService requires a host element.");
16163
- }
16168
+ constructor() {
16169
+ super();
16164
16170
 
16165
- // Attach the service to the host
16166
- this.host = host;
16167
- this.host.addController(this);
16171
+ this.bindEvents();
16168
16172
 
16169
- // Set default properties
16170
- this.size = undefined;
16173
+ /**
16174
+ * @private
16175
+ */
16171
16176
  this.shape = undefined;
16172
- this.noCheckmark = undefined;
16173
- this.disabled = undefined;
16174
- this.matchWord = undefined;
16175
- this.multiSelect = undefined;
16176
- this.allowDeselect = undefined;
16177
- this.selectAllMatchingOptions = undefined;
16178
16177
 
16179
- this.highlightedIndex = -1;
16178
+ /**
16179
+ * @private
16180
+ */
16181
+ this.size = undefined;
16180
16182
 
16181
- this._menuOptions = [];
16182
- this._subscribers = [];
16183
- this.internalUpdateInProgress = false;
16184
- this.selectedOptions = [];
16185
- this._pendingValue = null;
16186
- this._pendingRetryScheduled = false;
16187
- this._pendingRetryCount = 0;
16188
- }
16183
+ /**
16184
+ * Generate unique names for dependency components.
16185
+ */
16186
+ const versioning = new AuroDependencyVersioning$3();
16187
+ this.iconTag = versioning.generateTag('auro-formkit-menuoption-icon', iconVersion, _);
16189
16188
 
16190
- /**
16191
- * PROPERTY SYNCING
16192
- */
16189
+ this.selected = false;
16190
+ this.noCheckmark = false;
16191
+ this.disabled = false;
16192
+ this.noMatch = false;
16193
16193
 
16194
- /**
16195
- * Handles host updates.
16196
- * This is a lit reactive lifecycle method.
16197
- * This comes from the Lit controller interface provided by adding this service as a controller to the host.
16198
- * See constructor for `this.host.addController(this)`
16199
- * You can read more about Lit reactive controllers here: https://lit.dev/docs/composition/controllers/
16200
- */
16201
- hostUpdated() {
16194
+ /**
16195
+ * @private
16196
+ */
16197
+ this.runtimeUtils = new AuroLibraryRuntimeUtils$4();
16202
16198
 
16203
- // Reset selection if multiSelect mode changes
16204
- if (this.host.multiSelect !== this.multiSelect) {
16205
- this.selectedOptions = [];
16206
- }
16199
+ // Initialize context-related properties
16200
+ this.menuService = null;
16201
+ this.unsubscribe = null;
16207
16202
 
16208
- // Update properties on host update
16209
- this.setProperties({
16210
- size: this.host.size,
16211
- shape: this.host.shape,
16212
- noCheckmark: this.host.noCheckmark,
16213
- disabled: this.host.disabled,
16214
- matchWord: this.host.matchWord,
16215
- multiSelect: this.host.multiSelect,
16216
- allowDeselect: this.host.allowDeselect,
16217
- selectAllMatchingOptions: this.host.selectAllMatchingOptions
16218
- });
16203
+ /**
16204
+ * @private
16205
+ */
16206
+ this.handleMenuChange = this.handleMenuChange.bind(this);
16219
16207
  }
16220
16208
 
16221
- /**
16222
- * Handles host disconnection and memory cleanup.
16223
- */
16224
- hostDisconnected() {
16225
- this._subscribers = [];
16226
- this._menuOptions = [];
16227
- this._pendingValue = null;
16228
- this._pendingRetryScheduled = false;
16229
- this._pendingRetryCount = 0;
16230
- }
16209
+ static get properties() {
16210
+ return {
16211
+ ...super.properties,
16231
16212
 
16232
- /**
16233
- * Sets a property value if it exists on the instance and the value has changed.
16234
- * @param {string} property
16235
- * @param {any} value
16236
- */
16237
- setProperty(property, value) {
16213
+ /**
16214
+ * When true, disables the menu option.
16215
+ */
16216
+ disabled: {
16217
+ type: Boolean,
16218
+ reflect: true
16219
+ },
16238
16220
 
16239
- // Only update if we are tracking the property in this service
16240
- if (this.hasOwnProperty(property)) {
16221
+ /**
16222
+ * @private
16223
+ */
16224
+ event: {
16225
+ type: String,
16226
+ reflect: true
16227
+ },
16241
16228
 
16242
- // Check if the value has changed
16243
- const valueChanged = this[property] !== value;
16229
+ /**
16230
+ * @private
16231
+ */
16232
+ layout: {
16233
+ type: String
16234
+ },
16244
16235
 
16245
- // Update and notify if changed
16246
- if (valueChanged) {
16247
- this[property] = value;
16248
- this.notify({ property, value });
16249
- }
16250
- }
16251
- }
16236
+ /**
16237
+ * Allows users to set a unique key for the menu option for specified option selection. If no key is provided, the value property will be used.
16238
+ */
16239
+ key: {
16240
+ type: String,
16241
+ reflect: true
16242
+ },
16252
16243
 
16253
- /**
16254
- * Sets multiple properties on the instance.
16255
- * @param {Object} properties - Key-value pairs of properties to set.
16256
- */
16257
- setProperties(properties) {
16258
- for (const [key, value] of Object.entries(properties)) {
16259
- this.setProperty(key, value);
16260
- }
16261
- }
16244
+ /**
16245
+ * @private
16246
+ */
16247
+ menuService: {
16248
+ type: Object,
16249
+ state: true
16250
+ },
16262
16251
 
16263
- /**
16264
- * MENU OPTION HIGHLIGHTING
16265
- */
16252
+ /**
16253
+ * @private
16254
+ */
16255
+ matchWord: {
16256
+ type: String,
16257
+ state: true
16258
+ },
16266
16259
 
16267
- /**
16268
- * Highlights the next active option in the menu.
16269
- */
16270
- highlightNext() {
16271
- this.moveHighlightedOption("next");
16260
+ /**
16261
+ * @private
16262
+ */
16263
+ noCheckmark: {
16264
+ type: Boolean,
16265
+ reflect: true
16266
+ },
16267
+
16268
+ /**
16269
+ * When true, marks this option as the "no matching results" placeholder shown by combobox when the user's input does not match any available options. Enables distinct styling and prevents the option from being treated as a selectable match.
16270
+ */
16271
+ noMatch: {
16272
+ type: Boolean,
16273
+ reflect: true,
16274
+ attribute: 'nomatch'
16275
+ },
16276
+
16277
+ /**
16278
+ * Specifies that an option is selected.
16279
+ */
16280
+ selected: {
16281
+ type: Boolean,
16282
+ reflect: true
16283
+ },
16284
+
16285
+ /**
16286
+ * Specifies the tab index of the menu option.
16287
+ */
16288
+ tabIndex: {
16289
+ type: Number,
16290
+ reflect: true
16291
+ },
16292
+
16293
+ /**
16294
+ * Specifies the value to be sent to a server.
16295
+ */
16296
+ value: {
16297
+ type: String,
16298
+ reflect: true
16299
+ },
16300
+ };
16272
16301
  }
16273
16302
 
16274
- /**
16275
- * Highlights the previous active option in the menu.
16276
- */
16277
- highlightPrevious() {
16278
- this.moveHighlightedOption("previous");
16303
+ static get styles() {
16304
+ return [
16305
+ styleCss,
16306
+ colorCss,
16307
+ tokensCss
16308
+ ];
16279
16309
  }
16280
16310
 
16281
- /**
16282
- * Moves the highlighted option in the specified direction.
16283
- * @param {string} direction - The direction to move the highlight ("next" or "previous").
16284
- */
16285
- moveHighlightedOption(direction) {
16311
+ connectedCallback() {
16312
+ super.connectedCallback();
16286
16313
 
16287
- // Get the active options
16288
- const activeOptions = this._menuOptions.filter(option => option.isActive);
16314
+ // Add the tag name as an attribute if it is different than the component name
16315
+ // Add this step soon as this node gets attached to the DOM to avoid racing condition with menu's value setting logic.
16316
+ this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
16289
16317
 
16290
- // Get the currently active option
16291
- const currentActiveOption = activeOptions[activeOptions.indexOf(this.highlightedOption)];
16318
+ // Set up context consumption in connectedCallback
16319
+ this._contextConsumer = new s$1(this, {
16320
+ context: MenuContext,
16321
+ callback: this.attachTo.bind(this),
16322
+ subscribe: true
16323
+ });
16292
16324
 
16293
- // Determine the new index based on the currently active option and direction
16294
- let newIndex = currentActiveOption
16295
- ? direction === "previous"
16296
- ? activeOptions.indexOf(currentActiveOption) - 1
16297
- : activeOptions.indexOf(currentActiveOption) + 1
16298
- : direction === "previous"
16299
- ? activeOptions.length - 1
16300
- : 0;
16325
+ // Establish the key property as early as possible.
16326
+ // When a framework (e.g. Svelte) inserts the element into the DOM before
16327
+ // setting its `value` property, both `getAttribute('value')` and
16328
+ // `getAttribute('key')` return null here. Setting `this.key = null`
16329
+ // would block the fallback in `updated()` that assigns key from the
16330
+ // value property (the guard checked `=== undefined`). Only assign key
16331
+ // if at least one source attribute is actually present so that the
16332
+ // `updated()` fallback can run when the value property arrives later.
16333
+ const valueAttr = this.getAttribute('value');
16334
+ const keyAttr = this.getAttribute('key');
16335
+ const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
16336
+ if (resolvedKey !== null) {
16337
+ this.key = resolvedKey;
16338
+ }
16339
+ }
16301
16340
 
16302
- // Wrap around the index if needed
16303
- newIndex = newIndex < 0 ? activeOptions.length - 1 : newIndex >= activeOptions.length ? 0 : newIndex;
16341
+ firstUpdated() {
16342
+ // Add the tag name as an attribute if it is different than the component name
16343
+ this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
16304
16344
 
16305
- // Get the new active option and set it as highlighted
16306
- const newActiveOption = activeOptions[newIndex];
16307
- this.setHighlightedOption(newActiveOption);
16345
+ // Generate unique ID if not already set (required for aria-activedescendant)
16346
+ if (!this.id) {
16347
+ menuOptionIdCounter += 1;
16348
+ this.id = `menuoption-${menuOptionIdCounter}`;
16349
+ }
16350
+
16351
+ this.setAttribute('role', 'option');
16352
+ this.setAttribute('aria-selected', 'false');
16353
+
16354
+ this.addEventListener('mouseover', () => {
16355
+ this.dispatchEvent(new CustomEvent('auroMenuOption-mouseover', {
16356
+ bubbles: true,
16357
+ cancelable: false,
16358
+ composed: true,
16359
+ detail: this
16360
+ }));
16361
+ });
16308
16362
  }
16309
16363
 
16310
- /**
16311
- * Sets the highlighted index to the specified option.
16312
- * @param {AuroMenuOption} option - The option to highlight.
16313
- */
16314
- setHighlightedOption(option) {
16364
+ updated(changedProperties) {
16365
+ super.updated(changedProperties);
16315
16366
 
16316
- if (!option) return;
16367
+ // Update aria-selected attribute if selected changed
16368
+ if (changedProperties.has('selected')) {
16317
16369
 
16318
- // Get the index of the option to highlight
16319
- const index = this._menuOptions.indexOf(option);
16370
+ // Update aria-selected attribute
16371
+ this.setAttribute('aria-selected', this.selected.toString());
16320
16372
 
16321
- // Update highlighted index
16322
- this.highlightedIndex = index;
16373
+ // Update menu service selection state if this isn't an internal update
16374
+ if (this.internalUpdateInProgress !== true) {
16375
+ this.menuService[this.selected ? 'selectOption' : 'deselectOption'](this);
16376
+ }
16377
+ }
16323
16378
 
16324
- // Notify subscribers of highlight change
16325
- this.notify({ type: 'highlightChange', option, index: this.highlightedIndex });
16379
+ if (changedProperties.has('disabled')) {
16380
+ if (this.disabled) {
16381
+ this.setAttribute('aria-disabled', 'true');
16382
+ } else {
16383
+ this.removeAttribute('aria-disabled');
16384
+ }
16385
+ }
16326
16386
 
16327
- // Dispatch the change event
16328
- this.dispatchChangeEvent('auroMenu-activatedOption', option);
16387
+ if (changedProperties.has('active')) {
16388
+ this.updateActiveClasses();
16389
+ }
16390
+
16391
+ // Update text highlight if matchWord changed
16392
+ if (changedProperties.has('matchWord')) {
16393
+ this.updateTextHighlight();
16394
+ }
16395
+
16396
+ // Set the key to be the passed value if no key is provided.
16397
+ // Loose equality (== null) is intentional: it catches both null AND
16398
+ // undefined. When a framework (e.g. Svelte, React) inserts the element
16399
+ // before setting its value property, connectedCallback skips key
16400
+ // assignment because both attributes are null at that point. The Lit
16401
+ // property default for `key` is undefined (not null), so strict
16402
+ // === null would miss the case and the fallback would never run.
16403
+ if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
16404
+ this.key = this.value;
16405
+ }
16329
16406
  }
16330
16407
 
16331
- /**
16332
- * Sets the highlighted option to the option at the specified index if it exists.
16333
- * @param {number} index
16334
- */
16335
- setHighlightedIndex(index) {
16336
- const option = this._menuOptions[index] || null;
16337
- this.setHighlightedOption(option);
16408
+ disconnectedCallback() {
16409
+ if (this.menuService) {
16410
+ this.menuService.unsubscribe(this.handleMenuChange);
16411
+ this.menuService.removeMenuOption(this);
16412
+ }
16338
16413
  }
16339
16414
 
16340
16415
  /**
16341
- * Selects the currently highlighted option.
16416
+ * Sets up event listeners for user interaction with the menu option.
16417
+ * This function enables click and mouse enter events to trigger selection and highlighting logic.
16342
16418
  */
16343
- selectHighlightedOption() {
16344
- if (this.highlightedOption) {
16345
- this.toggleOption(this.highlightedOption);
16346
- }
16419
+ bindEvents() {
16420
+ this.addEventListener('click', this.handleClick.bind(this));
16421
+ this.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
16347
16422
  }
16348
16423
 
16349
16424
  /**
16350
- * SELECTION AND DESELECTION METHODS
16425
+ * Attaches this menu option to a menu service and subscribes to its events.
16426
+ * This method enables the option to participate in menu selection and highlighting logic.
16427
+ * @param {Object} service - The menu service instance to attach to.
16351
16428
  */
16429
+ attachTo(service) {
16430
+ if (!service) {
16431
+ return;
16432
+ }
16433
+ this.menuService = service;
16434
+ this.menuService.addMenuOption(this);
16435
+ this.menuService.subscribe(this.handleMenuChange);
16436
+ }
16352
16437
 
16353
16438
  /**
16354
- * Selects one or more options in a batch operation
16355
- * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to select
16439
+ * Handles changes from the menu service and updates the option's state.
16440
+ * This function synchronizes the option's properties and selection/highlight state with menu events.
16441
+ * @param {Object} event - The event object from the menu service.
16356
16442
  */
16357
- selectOptions(options) {
16358
- let optionsToSelect = Array.isArray(options) ? options : [options];
16443
+ handleMenuChange(event) {
16359
16444
 
16360
- // Filter out options that are inactive
16361
- optionsToSelect = optionsToSelect.filter(option => option.isActive);
16445
+ // Ignore events without a type or property
16446
+ if (!event || (!event.type && !event.property)) {
16447
+ return;
16448
+ }
16362
16449
 
16363
- if (!optionsToSelect.length) return;
16450
+ // Update reactive properties based on event type
16451
+ if (event.property && Object.keys(AuroMenuOption.properties).includes(event.property)) {
16452
+ this[event.property] = event.value;
16453
+ }
16364
16454
 
16365
- if (this.multiSelect) {
16366
- this.selectedOptions = [...(this.selectedOptions || []), ...optionsToSelect];
16367
- } else {
16368
- // In single select mode, only take the last option
16369
- this.selectedOptions = [optionsToSelect[optionsToSelect.length - 1]];
16455
+ // Handle highlight changes
16456
+ if (event.type === 'highlightChange') {
16457
+ const isActive = event.option === this;
16458
+ this.active = isActive;
16459
+ this.updateActiveClasses();
16370
16460
  }
16371
16461
 
16372
- this.stageUpdate();
16462
+ if (event.type === 'stateChange') {
16463
+ const isSelected = event.selectedOptions.includes(this);
16464
+ this.setInternalSelected(isSelected);
16465
+ }
16373
16466
  }
16374
16467
 
16375
16468
  /**
16376
- * Deselects one or more options in a batch operation
16377
- * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to deselect
16469
+ * Updates the internal selected state of the menu option bypassing 'updated' and triggers custom events if selected.
16470
+ * This function ensures the option's selection state is synchronized with menu logic and notifies listeners.
16471
+ * @param {boolean} isSelected - Whether the option should be marked as selected.
16378
16472
  */
16379
- deselectOptions(options) {
16380
- const optionsToDeselect = Array.isArray(options) ? options : [options];
16381
-
16382
- if (!optionsToDeselect.length) return;
16383
-
16384
- // Check if deselection should be prevented
16385
- const shouldPreventDeselect = !this.allowDeselect && !this.multiSelect;
16386
- const isOnlySelectedOption = this.selectedOptions.length === 1 && optionsToDeselect.includes(this.selectedOptions[0]);
16473
+ setInternalSelected(isSelected) {
16474
+ this.internalUpdateInProgress = true;
16475
+ this.selected = isSelected;
16387
16476
 
16388
- // Prevent deselecting the only selected option if not allowed
16389
- if (shouldPreventDeselect && isOnlySelectedOption) {
16390
- optionsToDeselect.forEach(option => {
16391
- option.selected = true;
16392
- });
16393
- this.dispatchChangeEvent('auroMenu-deselectPrevented', {
16394
- values: optionsToDeselect
16395
- });
16396
- return;
16477
+ // Fire custom event if selected
16478
+ if (isSelected) {
16479
+ this.handleCustomEvent();
16397
16480
  }
16398
16481
 
16399
- const optionsSet = new Set(optionsToDeselect);
16400
- this.selectedOptions = (this.selectedOptions || [])
16401
- .filter(opt => !optionsSet.has(opt));
16402
-
16403
- this.stageUpdate();
16482
+ setTimeout(() => {
16483
+ this.internalUpdateInProgress = false;
16484
+ }, 0);
16404
16485
  }
16405
16486
 
16406
16487
  /**
16407
- * Selects a single option.
16408
- * @param {AuroMenuOption} option
16488
+ * Sets the selected state of the menu option.
16489
+ * This function updates whether the option is currently selected.
16490
+ * @param {boolean} isSelected - Whether the option should be marked as selected.
16491
+ * @deprecated Simply modify the `selected` property directly instead.
16409
16492
  */
16410
- selectOption(option) {
16411
- this.selectOptions(option);
16493
+ setSelected(isSelected) {
16494
+ this.selected = isSelected;
16412
16495
  }
16413
16496
 
16414
16497
  /**
16415
- * Deselects a single option.
16416
- * @param {AuroMenuOption} option
16498
+ * Updates the active state and visual highlighting of the menu option.
16499
+ * This function toggles the option's active status and applies or removes the active CSS class.
16500
+ * @param {boolean} isActive - Whether the option should be marked as active.
16501
+ * @deprecated Simply modify the `active` property directly instead.
16417
16502
  */
16418
- deselectOption(option) {
16419
- this.deselectOptions(option);
16503
+ updateActive(isActive) {
16504
+
16505
+ // Set active state
16506
+ this.active = isActive;
16507
+ this.updateActiveClasses();
16420
16508
  }
16421
16509
 
16422
16510
  /**
16423
- * Toggles the selection state of a single option.
16424
- * @param {AuroMenuOption} option
16511
+ * Updates the CSS class for the menu option based on its active state.
16512
+ * This function adds or removes the 'active' class to visually indicate the option's active status.
16513
+ * @private
16425
16514
  */
16426
- toggleOption(option) {
16427
- if (option.selected) {
16428
- this.deselectOption(option);
16429
- } else {
16430
- this.selectOption(option);
16431
- }
16515
+ updateActiveClasses() {
16516
+ // Update class based on active state
16517
+ if (this.active) this.classList.add('active');
16518
+ else this.classList.remove('active');
16432
16519
  }
16433
16520
 
16521
+
16434
16522
  /**
16435
- * Selects options based on their value(s) when compared to a passed value or values.
16436
- * Value or values are normalized to an array of strings that can be matched to option keys.
16437
- * @param {string|number|Array<string|number>} value - The value(s) to select.
16523
+ * Updates the visual highlighting of text within the menu option based on the current match word.
16524
+ * This function highlights matching text segments and manages nested spacers for display formatting.
16525
+ * @private
16438
16526
  */
16439
- selectByValue(value) {
16440
- const isEmptyValue = value === undefined ||
16441
- value === null ||
16442
- (Array.isArray(value) && value.length === 0) ||
16443
- (typeof value === 'string' && value.trim() === '');
16444
-
16445
- // Early exit for invalid/empty values
16446
- if (isEmptyValue) {
16447
- this.selectedOptions.forEach(opt => opt.selected = false);
16448
- this.selectedOptions = [];
16449
- return;
16450
- }
16451
-
16452
- // If an internal update cycle is still in progress, defer value application
16453
- // rather than dropping it.
16454
- if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
16455
- this.queuePendingValue(value);
16456
- return;
16457
- }
16458
-
16459
- // Normalize values to array of strings
16460
- const normalizedValues = this._getNormalizedValues(value);
16527
+ updateTextHighlight() {
16461
16528
 
16462
- // Validate for single-select mode
16463
- let validatedValues = normalizedValues;
16464
- if (normalizedValues.length > 1 && !this.multiSelect) {
16465
- console.warn("MenuService - Multiple values provided for single-select menu. Only the first value will be selected.");
16466
- validatedValues = [normalizedValues[0]];
16467
- }
16529
+ // Regex for matchWord if needed
16530
+ let regexWord = null;
16468
16531
 
16469
- if (this._menuOptions.length === 0) {
16470
- this.queuePendingValue(value);
16471
- return;
16532
+ if (this.matchWord && this.matchWord.length) {
16533
+ const escapedWord = this.matchWord.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
16534
+ regexWord = new RegExp(escapedWord, 'giu');
16472
16535
  }
16473
16536
 
16474
- // Find matching options by comparing available options to validated values
16475
- const trackedKeys = new Set();
16476
- const optionsToSelect = this._menuOptions.filter(option => {
16477
- const passesFilter = validatedValues.includes(option.key);
16478
- const alreadyTracked = trackedKeys.has(option.key);
16479
- const isActive = option.isActive;
16480
-
16481
- trackedKeys.add(option.key);
16482
-
16483
- // Include the option in the options to be selected if it passes the filter check and
16484
- // either hasn't been tracked yet or selectAllMatchingOptions is true
16485
- return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
16486
- });
16487
-
16488
- // Handle no matches: clear existing selection, but do not dispatch an intermediate
16489
- // undefined value that can overwrite the host value in parent components.
16490
- if (!optionsToSelect.length) {
16491
- const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
16492
-
16493
- if (hasUnresolvedKeys) {
16494
- this.queuePendingValue(value);
16495
- return;
16496
- }
16497
-
16498
- this.clearPendingValue();
16537
+ // Update text highlighting if matchWord changed
16538
+ if (regexWord &&
16539
+ this.isActive && !this.hasAttribute('persistent')) {
16540
+ const nested = this.querySelectorAll('.nestingSpacer');
16499
16541
 
16500
- if (this.selectedOptions.length > 0) {
16501
- this.selectedOptions = [];
16542
+ const displayValueEl = this.querySelector('[slot="displayValue"]');
16543
+ if (displayValueEl) {
16544
+ this.removeChild(displayValueEl);
16502
16545
  }
16503
16546
 
16504
- // Always notify so the host resets any stale invalid value, even when
16505
- // selectedOptions was already empty (e.g. double-clicking set-invalid).
16506
- this.stageUpdate({ reason: 'no-match' });
16547
+ // Create nested spacers
16548
+ const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
16507
16549
 
16508
- // Dispatch failure event if no matches found
16509
- if (validatedValues.length) {
16510
- this.dispatchChangeEvent('auroMenu-selectValueFailure', {
16511
- message: 'No matching options found for the provided value(s).',
16512
- values: validatedValues
16513
- });
16550
+ // Update with spacers and matchWord
16551
+ this.innerHTML = nestingSpacerBundle +
16552
+ this.textContent.replace(
16553
+ regexWord,
16554
+ (match) => `<strong>${match}</strong>`
16555
+ );
16556
+ if (displayValueEl) {
16557
+ this.append(displayValueEl);
16514
16558
  }
16515
-
16516
- return;
16517
- }
16518
-
16519
- this.clearPendingValue();
16520
-
16521
- if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
16522
- return;
16523
16559
  }
16524
-
16525
- // Apply programmatic selection as a single transaction and emit one final state.
16526
- this.selectedOptions = optionsToSelect;
16527
- this.stageUpdate();
16528
16560
  }
16529
16561
 
16530
16562
  /**
16531
- * Queues a pending value and schedules a bounded retry.
16532
- * @param {string|number|Array<string|number>} value - The value to retry.
16563
+ * Handles click events on the menu option, toggling its selected state.
16564
+ * This function dispatches a click event and updates selection if the option is not disabled.
16565
+ * @private
16533
16566
  */
16534
- queuePendingValue(value) {
16535
- this._pendingValue = value;
16536
-
16537
- if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
16538
- return;
16567
+ handleClick() {
16568
+ if (!this.disabled) {
16569
+ this.dispatchClickEvent();
16570
+ this.selected = !this.selected;
16539
16571
  }
16540
-
16541
- this._pendingRetryScheduled = true;
16542
- this._pendingRetryCount += 1;
16543
-
16544
- setTimeout(() => {
16545
- this._pendingRetryScheduled = false;
16546
-
16547
- if (this._pendingValue == null) {
16548
- return;
16549
- }
16550
-
16551
- const pendingValue = this._pendingValue;
16552
- this.selectByValue(pendingValue);
16553
- }, 0);
16554
16572
  }
16555
16573
 
16556
16574
  /**
16557
- * Clears pending retry state.
16575
+ * Handles mouse enter events to highlight the menu option.
16576
+ * This function updates the menu service to set this option as the currently highlighted item if not disabled.
16577
+ * @private
16558
16578
  */
16559
- clearPendingValue() {
16560
- this._pendingValue = null;
16561
- this._pendingRetryScheduled = false;
16562
- this._pendingRetryCount = 0;
16579
+ handleMouseEnter() {
16580
+ if (!this.disabled) {
16581
+ this.menuService.setHighlightedOption(this);
16582
+ }
16563
16583
  }
16564
16584
 
16565
16585
  /**
16566
- * Resets the selected options to an empty array.
16586
+ * Dispatches custom events defined for this menu option.
16587
+ * This function notifies listeners when a custom event is triggered by the option.
16588
+ * @private
16567
16589
  */
16568
- reset() {
16569
- const previousOptions = [...this.selectedOptions];
16570
- previousOptions.forEach(opt => opt.selected = false);
16571
- this.selectedOptions = [];
16572
-
16573
- // Single update after clearing all
16574
- if (previousOptions.length) {
16575
- this.stageUpdate();
16590
+ handleCustomEvent() {
16591
+ if (this.event) {
16592
+ dispatchMenuEvent(this, this.event, { option: this });
16593
+ dispatchMenuEvent(this, 'auroMenu-customEventFired', { option: this });
16576
16594
  }
16577
16595
  }
16578
16596
 
16579
16597
  /**
16580
- * SUBSCRIPTION, NOTIFICATION AND EVENT DISPATCH METHODS
16598
+ * Dispatches a click event for this menu option.
16599
+ * This function notifies listeners that the option has been clicked.
16600
+ * @private
16581
16601
  */
16602
+ dispatchClickEvent() {
16603
+ this.dispatchEvent(new CustomEvent('auroMenuOption-click', {
16604
+ bubbles: true,
16605
+ cancelable: false,
16606
+ composed: true,
16607
+ detail: this
16608
+ }));
16609
+ }
16582
16610
 
16583
16611
  /**
16584
- * Subscribes a callback to menu service events.
16585
- * @param {Function} callback - The callback to invoke on events.
16612
+ * Generates an HTML element containing an SVG icon based on the provided `svgContent`.
16613
+ *
16614
+ * @private
16615
+ * @param {string} svgContent - The SVG content to be embedded.
16616
+ * @returns {Element} The HTML element containing the SVG icon.
16586
16617
  */
16587
- subscribe(callback) {
16588
- this._subscribers.push(callback);
16618
+ generateIconHtml(svgContent) {
16619
+ const dom = new DOMParser().parseFromString(svgContent, 'text/html');
16620
+ const svg = dom.body.firstChild;
16621
+
16622
+ svg.setAttribute('slot', 'svg');
16623
+
16624
+ return u$7`<${this.iconTag} customColor customSvg>${svg}</${this.iconTag}>`;
16589
16625
  }
16590
16626
 
16591
16627
  /**
16592
- * Remove a previously subscribed callback from menu service events.
16593
- * @param {Function} callback
16628
+ * Logic to determine the layout of the component.
16629
+ * @protected
16630
+ * @returns {void}
16594
16631
  */
16595
- unsubscribe(callback) {
16596
- this._subscribers = this._subscribers.filter(cb => cb !== callback);
16632
+ renderLayout() {
16633
+
16634
+ const fontClassMap = {
16635
+ xs: 'body-sm',
16636
+ sm: 'body-default',
16637
+ md: 'body-default',
16638
+ lg: 'body-lg',
16639
+ xl: 'body-lg'
16640
+ };
16641
+
16642
+ const classes = e$3({
16643
+ 'wrapper': true,
16644
+ [this.size ? fontClassMap[this.size] : 'body-sm']: true,
16645
+ });
16646
+
16647
+ return u$7`
16648
+ <div class="${classes}">
16649
+ ${this.selected && !this.noCheckmark
16650
+ ? this.generateIconHtml(checkmarkIcon.svg)
16651
+ : undefined}
16652
+ <slot></slot>
16653
+ </div>
16654
+ `;
16597
16655
  }
16656
+ }
16657
+
16658
+ /* eslint-disable */
16659
+
16660
+ class MenuService {
16598
16661
 
16599
16662
  /**
16600
- * Stages an update to notify subscribers of state and value changes.
16663
+ * PROPERTIES AND GETTERS
16601
16664
  */
16602
- stageUpdate(meta = {}) {
16603
- this.notifyStateChange(meta);
16604
- this.notifyValueChange(meta);
16605
- }
16606
16665
 
16607
16666
  /**
16608
- * Notifies subscribers of a menu service event.
16609
- * All notifications are sent to all subscribers.
16610
- * @param {string} event - The event to send to subscribers.
16667
+ * Gets the list of registered menu options.
16668
+ * @returns {AuroMenuOption[]}
16611
16669
  */
16612
- notify(event) {
16613
- this._subscribers.forEach(callback => callback(event));
16670
+ get menuOptions() {
16671
+ return this._menuOptions;
16614
16672
  }
16615
16673
 
16616
16674
  /**
16617
- * Notifies subscribers of a state change (selected options has changed).
16675
+ * Gets the currently highlighted option.
16676
+ * @returns {AuroMenuOption|null}
16618
16677
  */
16619
- notifyStateChange(meta = {}) {
16620
- this.notify({
16621
- type: 'stateChange',
16622
- selectedOptions: this.selectedOptions,
16623
- ...meta
16624
- });
16678
+ get highlightedOption() {
16679
+ return this._menuOptions[this.highlightedIndex] || null;
16625
16680
  }
16626
16681
 
16627
16682
  /**
16628
- * Notifies subscribers of a value change (current value has changed).
16683
+ * Gets the current value(s) of the selected option(s).
16684
+ * @returns {string|string[]|undefined}
16629
16685
  */
16630
- notifyValueChange(meta = {}) {
16631
-
16632
- // Prepare details for the event
16633
- const details = {
16634
- value: this.currentValue,
16635
- stringValue: this.stringValue,
16636
- keys: this.currentKeys,
16637
- options: this.selectedOptions,
16638
- label: this.currentLabel
16639
- };
16640
-
16641
- // If only one option is selected, include its index
16642
- if (this.selectedOptions.length === 1) details.index = this._menuOptions.indexOf(this.selectedOptions[0]);
16643
-
16644
- this.notify({
16645
- type: 'valueChange',
16646
- ...meta,
16647
- ...details
16648
- });
16686
+ get currentValue() {
16687
+ const values = (this.selectedOptions || []).map(option => option.value);
16688
+ return this.multiSelect ? values : values[0];
16649
16689
  }
16650
16690
 
16651
16691
  /**
16652
- * Dispatches a custom event from the host element.
16653
- * @param {string} eventName
16654
- * @param {any} detail
16692
+ * Gets the label(s) of the currently selected option(s).
16693
+ * @returns {string}
16655
16694
  */
16656
- dispatchChangeEvent(eventName, detail) {
16657
- this.host.dispatchEvent(new CustomEvent(eventName, {
16658
- bubbles: true,
16659
- cancelable: false,
16660
- composed: true,
16661
- detail
16662
- }));
16695
+ get currentLabel() {
16696
+ const labels = (this.selectedOptions || []).map(option => option.textContent);
16697
+ return this.multiSelect ? labels.join(", ") : labels[0] || '';
16663
16698
  }
16664
16699
 
16665
16700
  /**
16666
- * MENU OPTION MANAGEMENT METHODS
16701
+ * Gets the string representation of the current value(s).
16702
+ * For multi-select, this is a JSON stringified array.
16703
+ * @returns {string|undefined}
16667
16704
  */
16705
+ get stringValue() {
16706
+ const { currentValue } = this;
16668
16707
 
16669
- /**
16670
- * Adds a menu option to the service's list.
16671
- * @param {AuroMenuOption} option - the option to track
16672
- */
16673
- addMenuOption(option) {
16674
- this._menuOptions.push(option);
16675
- this.notify({ type: 'optionsChange', options: this._menuOptions });
16708
+ if (Array.isArray(currentValue)) {
16709
+ if (currentValue.length > 0) {
16710
+ return JSON.stringify(currentValue);
16711
+ }
16712
+ return undefined;
16713
+ }
16676
16714
 
16677
- if (this._pendingValue != null) {
16678
- this.queuePendingValue(this._pendingValue);
16715
+ if (typeof currentValue === 'string') {
16716
+ if (currentValue.length > 0) {
16717
+ return currentValue;
16718
+ }
16719
+ return undefined;
16679
16720
  }
16721
+
16722
+ // Future: handle other types here (e.g., number, object, etc.)
16723
+ return undefined;
16680
16724
  }
16681
16725
 
16682
16726
  /**
16683
- * Removes a menu option from the service's list.
16684
- * @param {AuroMenuOption} option - the option to remove
16727
+ * Gets the key(s) of the currently selected option(s).
16728
+ * @returns {string|string[]|undefined}
16685
16729
  */
16686
- removeMenuOption(option) {
16687
- this._menuOptions = this._menuOptions.filter(opt => opt !== option);
16688
- this.notify({ type: 'optionsChange', options: this._menuOptions });
16689
-
16690
- if (this._menuOptions.length === 0) {
16691
- this.clearPendingValue();
16692
- }
16730
+ get currentKeys() {
16731
+ const keys = (this.selectedOptions || []).map(option => option.key);
16732
+ return this.multiSelect ? keys : keys[0];
16693
16733
  }
16694
16734
 
16695
16735
  /**
16696
- * UTILITIES
16736
+ * CONSTRUCTOR
16697
16737
  */
16698
16738
 
16699
16739
  /**
16700
- * Normalizes a value or array of values into an array of strings for option selection.
16701
- * This function ensures that input values are consistently formatted for matching menu options.
16702
- *
16703
- * @param {string|number|Array<string|number>} value - The value(s) to normalize.
16704
- * @returns {Array<string>} An array of string values suitable for option matching.
16705
- * @throws {Error} If any value is not a string or number.
16740
+ * Creates a new MenuService instance.
16741
+ * @param {Object} options - The options object.
16742
+ * @param {AuroMenu} options.host - The host element that this service will control. Required.
16743
+ * @throws {Error} If the host is not provided.
16706
16744
  */
16707
- _getNormalizedValues(value) {
16708
- let values = value;
16709
-
16710
- // Handle JSON string and single value string input
16711
- if (!Array.isArray(values) && typeof values === 'string') {
16745
+ constructor({ host } = {}) {
16712
16746
 
16713
- // Attempt to parse as JSON array
16714
- try {
16747
+ // Ensure a host was passed
16748
+ if (!host) {
16749
+ throw new Error("MenuService requires a host element.");
16750
+ }
16715
16751
 
16716
- // Normalize single quotes to double quotes for JSON parsing
16717
- // This will not handle complex cases but will cover basic usage
16718
- const parseValue = values.replace(/'([^']*?)'/g, '"$1"');
16752
+ // Attach the service to the host
16753
+ this.host = host;
16754
+ this.host.addController(this);
16719
16755
 
16720
- // Attempt parse
16721
- const parsed = JSON.parse(parseValue);
16756
+ // Set default properties
16757
+ this.size = undefined;
16758
+ this.shape = undefined;
16759
+ this.noCheckmark = undefined;
16760
+ this.disabled = undefined;
16761
+ this.matchWord = undefined;
16762
+ this.multiSelect = undefined;
16763
+ this.allowDeselect = undefined;
16764
+ this.selectAllMatchingOptions = undefined;
16722
16765
 
16723
- // Ensure parsed value is an array
16724
- if (!Array.isArray(parsed)) throw new Error('Not an array');
16766
+ this.highlightedIndex = -1;
16725
16767
 
16726
- // Set values to parsed array
16727
- values = parsed;
16728
- } catch (err) {
16768
+ this._menuOptions = [];
16769
+ this._subscribers = [];
16770
+ this.internalUpdateInProgress = false;
16771
+ this.selectedOptions = [];
16772
+ this._pendingValue = null;
16773
+ this._pendingRetryScheduled = false;
16774
+ this._pendingRetryCount = 0;
16775
+ }
16729
16776
 
16730
- // If parsing fails, treat as single value
16731
- values = [value];
16732
- }
16733
- }
16734
-
16735
- // Handle a single number being passed
16736
- if (typeof values === 'number') {
16737
- values = [String(values)];
16738
- }
16777
+ /**
16778
+ * PROPERTY SYNCING
16779
+ */
16739
16780
 
16740
- // Coerce each value to string and validate types
16741
- values.forEach((val, index) => {
16781
+ /**
16782
+ * Handles host updates.
16783
+ * This is a lit reactive lifecycle method.
16784
+ * This comes from the Lit controller interface provided by adding this service as a controller to the host.
16785
+ * See constructor for `this.host.addController(this)`
16786
+ * You can read more about Lit reactive controllers here: https://lit.dev/docs/composition/controllers/
16787
+ */
16788
+ hostUpdated() {
16742
16789
 
16743
- // Throw an error for invalid value types
16744
- if (typeof val !== 'string' && typeof val !== 'number') {
16745
- throw new Error('Value contains invalid value type. Supported types are string and number.');
16746
- }
16790
+ // Reset selection if multiSelect mode changes
16791
+ if (this.host.multiSelect !== this.multiSelect) {
16792
+ this.selectedOptions = [];
16793
+ }
16747
16794
 
16748
- // Convert numbers to strings for consistency
16749
- if (typeof val === 'number') {
16750
- values[index] = String(val);
16751
- }
16795
+ // Update properties on host update
16796
+ this.setProperties({
16797
+ size: this.host.size,
16798
+ shape: this.host.shape,
16799
+ noCheckmark: this.host.noCheckmark,
16800
+ disabled: this.host.disabled,
16801
+ matchWord: this.host.matchWord,
16802
+ multiSelect: this.host.multiSelect,
16803
+ allowDeselect: this.host.allowDeselect,
16804
+ selectAllMatchingOptions: this.host.selectAllMatchingOptions
16752
16805
  });
16806
+ }
16753
16807
 
16754
- // Return the resulting array of string values
16755
- return values;
16808
+ /**
16809
+ * Handles host disconnection and memory cleanup.
16810
+ */
16811
+ hostDisconnected() {
16812
+ this._subscribers = [];
16813
+ this._menuOptions = [];
16814
+ this._pendingValue = null;
16815
+ this._pendingRetryScheduled = false;
16816
+ this._pendingRetryCount = 0;
16756
16817
  }
16757
16818
 
16758
16819
  /**
16759
- * Returns whether two arrays of options contain the same elements.
16760
- * @param {AuroMenuOption[]} arr1 - First array of options.
16761
- * @param {AuroMenuOption[]} arr2 - Second array of options.
16762
- * @returns {boolean} True if arrays match, false otherwise.
16820
+ * Sets a property value if it exists on the instance and the value has changed.
16821
+ * @param {string} property
16822
+ * @param {any} value
16763
16823
  */
16764
- optionsArraysMatch(arr1, arr2) {
16765
- if (arr1.length !== arr2.length) return false;
16824
+ setProperty(property, value) {
16766
16825
 
16767
- const set1 = new Set(arr1);
16768
- const set2 = new Set(arr2);
16826
+ // Only update if we are tracking the property in this service
16827
+ if (this.hasOwnProperty(property)) {
16769
16828
 
16770
- for (let item of set1) {
16771
- if (!set2.has(item)) {
16772
- return false;
16829
+ // Check if the value has changed
16830
+ const valueChanged = this[property] !== value;
16831
+
16832
+ // Update and notify if changed
16833
+ if (valueChanged) {
16834
+ this[property] = value;
16835
+ this.notify({ property, value });
16773
16836
  }
16774
16837
  }
16775
-
16776
- return true;
16777
16838
  }
16778
- }
16779
-
16780
- const MenuContext = n('menu-context');
16781
-
16782
- // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16783
- // See LICENSE in the project root for license information.
16784
-
16785
-
16786
- /**
16787
- * Helper method to dispatch custom events.
16788
- * @param {HTMLElement} element - Element to dispatch event from.
16789
- * @param {string} eventName - Name of the event to dispatch.
16790
- * @param {Object} [detail] - Optional detail object to include with the event.
16791
- */
16792
- function dispatchMenuEvent(element, eventName, detail = null) {
16793
- const eventConfig = {
16794
- bubbles: true,
16795
- cancelable: false,
16796
- composed: true
16797
- };
16798
16839
 
16799
- if (detail !== null) {
16800
- eventConfig.detail = detail;
16840
+ /**
16841
+ * Sets multiple properties on the instance.
16842
+ * @param {Object} properties - Key-value pairs of properties to set.
16843
+ */
16844
+ setProperties(properties) {
16845
+ for (const [key, value] of Object.entries(properties)) {
16846
+ this.setProperty(key, value);
16847
+ }
16801
16848
  }
16802
16849
 
16803
- element.dispatchEvent(new CustomEvent(eventName, eventConfig));
16804
- }
16805
-
16806
- /* eslint-disable no-underscore-dangle */
16807
- // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16808
- // See LICENSE in the project root for license information.
16809
-
16810
-
16811
-
16812
- /**
16813
- * The `auro-menu` element provides users a way to select from a list of options.
16814
- * @customElement auro-menu
16815
- *
16816
- * @event {CustomEvent<Element>} auroMenu-activatedOption - Notifies that a menuoption has been made `active`.
16817
- * @event {CustomEvent<any>} auroMenu-customEventFired - Notifies that a custom event has been fired.
16818
- * @event {CustomEvent<{ loading: boolean; hasLoadingPlaceholder: boolean; }>} auroMenu-loadingChange - Notifies when the loading attribute is changed.
16819
- * @event {CustomEvent<any>} auroMenu-selectValueFailure - Notifies that an attempt to select a menuoption by matching a value has failed.
16820
- * @event {CustomEvent<{ values: HTMLElement[] }>} auroMenu-deselectPrevented - Notifies that deselection was prevented and includes the affected options in `detail.values`.
16821
- * @event {CustomEvent<any>} auroMenu-selectValueReset - Notifies that the component value has been reset.
16822
- * @event {CustomEvent<any>} auroMenu-selectedOption - Notifies that a new menuoption selection has been made.
16823
- * @slot loadingText - Text to show while loading attribute is set
16824
- * @slot loadingIcon - Icon to show while loading attribute is set
16825
- * @slot - Slot for insertion of menu options.
16826
- */
16827
-
16828
- /* eslint-disable max-lines */
16829
-
16830
- class AuroMenu extends AuroElement {
16831
-
16832
- constructor() {
16833
- super();
16834
-
16835
- // State properties (reactive)
16836
-
16837
- /**
16838
- * @private
16839
- */
16840
- this.shape = "box";
16841
-
16842
- /**
16843
- * @private
16844
- */
16845
- this.size = "sm";
16846
-
16847
- // Value of the selected options
16848
- this.value = undefined;
16849
- // Currently selected option
16850
- this.optionSelected = undefined;
16851
- // String used for highlighting/filtering
16852
- this.matchWord = undefined;
16853
- // Hide the checkmark icon on selected options
16854
- this.noCheckmark = false;
16855
- // Currently active option
16856
- this.optionActive = undefined;
16857
- // Loading state
16858
- this.loading = false;
16859
- // Multi-select mode
16860
- this.multiSelect = false;
16861
- // Allow deselecting of menu options
16862
- this.allowDeselect = false;
16863
- // Select all matching options when setting value in multi-select mode
16864
- this.selectAllMatchingOptions = false;
16865
-
16866
- // Event Bindings
16867
-
16868
- /**
16869
- * @private
16870
- */
16871
- this.handleSlotChange = this.handleSlotChange.bind(this);
16872
-
16873
- // Instance properties (non-reactive)
16850
+ /**
16851
+ * MENU OPTION HIGHLIGHTING
16852
+ */
16874
16853
 
16875
- /**
16876
- * @private
16877
- */
16878
- Object.assign(this, {
16879
- // Root-level menu (true) or a nested submenu (false)
16880
- rootMenu: true,
16881
- // Currently focused/active menu item index
16882
- _index: -1,
16883
- // Nested menu spacer
16884
- nestingSpacer: '<span class="nestingSpacer"></span>',
16885
- // Loading indicator for slot elements
16886
- loadingSlots: null,
16887
- });
16854
+ /**
16855
+ * Highlights the next active option in the menu.
16856
+ */
16857
+ highlightNext() {
16858
+ this.moveHighlightedOption("next");
16888
16859
  }
16889
16860
 
16890
- static get properties() {
16891
- return {
16892
- ...super.properties,
16893
-
16894
- /**
16895
- * Allows deselecting an already selected option when clicked again in single-select mode.
16896
- */
16897
- allowDeselect: {
16898
- type: Boolean,
16899
- reflect: true,
16900
- },
16901
-
16902
- /**
16903
- * When true, the entire menu and all options are disabled.
16904
- */
16905
- disabled: {
16906
- type: Boolean,
16907
- reflect: true
16908
- },
16909
-
16910
- /**
16911
- * Indicates whether the menu has a loadingIcon or loadingText to render when in a loading state.
16912
- */
16913
- hasLoadingPlaceholder: {
16914
- type: Boolean
16915
- },
16916
-
16917
- /**
16918
- * @private
16919
- */
16920
- layout: {
16921
- type: String
16922
- },
16861
+ /**
16862
+ * Highlights the previous active option in the menu.
16863
+ */
16864
+ highlightPrevious() {
16865
+ this.moveHighlightedOption("previous");
16866
+ }
16923
16867
 
16924
- /**
16925
- * Indent level for submenus.
16926
- * @private
16927
- */
16928
- level: {
16929
- type: Number,
16930
- reflect: false,
16931
- attribute: false
16932
- },
16868
+ /**
16869
+ * Moves the highlighted option in the specified direction.
16870
+ * @param {string} direction - The direction to move the highlight ("next" or "previous").
16871
+ */
16872
+ moveHighlightedOption(direction) {
16933
16873
 
16934
- /**
16935
- * When true, displays a loading state using the loadingIcon and loadingText slots if provided.
16936
- */
16937
- loading: {
16938
- type: Boolean,
16939
- reflect: true
16940
- },
16874
+ // Get the active options
16875
+ const activeOptions = this._menuOptions.filter(option => option.isActive);
16941
16876
 
16942
- /**
16943
- * Specifies a string used to highlight matched string parts in options.
16944
- */
16945
- matchWord: {
16946
- type: String,
16947
- attribute: 'matchword'
16948
- },
16877
+ // Get the currently active option
16878
+ const currentActiveOption = activeOptions[activeOptions.indexOf(this.highlightedOption)];
16949
16879
 
16950
- /**
16951
- * When true, the selected option can be multiple options.
16952
- */
16953
- multiSelect: {
16954
- type: Boolean,
16955
- reflect: true,
16956
- attribute: 'multiselect'
16957
- },
16880
+ // Determine the new index based on the currently active option and direction
16881
+ let newIndex = currentActiveOption
16882
+ ? direction === "previous"
16883
+ ? activeOptions.indexOf(currentActiveOption) - 1
16884
+ : activeOptions.indexOf(currentActiveOption) + 1
16885
+ : direction === "previous"
16886
+ ? activeOptions.length - 1
16887
+ : 0;
16958
16888
 
16959
- /**
16960
- * When true, selected option will not show the checkmark.
16961
- */
16962
- noCheckmark: {
16963
- type: Boolean,
16964
- reflect: true,
16965
- attribute: 'nocheckmark'
16966
- },
16889
+ // Wrap around the index if needed
16890
+ newIndex = newIndex < 0 ? activeOptions.length - 1 : newIndex >= activeOptions.length ? 0 : newIndex;
16967
16891
 
16968
- /**
16969
- * Specifies the current active menuOption.
16970
- */
16971
- optionActive: {
16972
- type: Object,
16973
- attribute: 'optionactive'
16974
- },
16892
+ // Get the new active option and set it as highlighted
16893
+ const newActiveOption = activeOptions[newIndex];
16894
+ this.setHighlightedOption(newActiveOption);
16895
+ }
16975
16896
 
16976
- /**
16977
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
16978
- */
16979
- optionSelected: {
16980
- // Allow HTMLElement, HTMLElement[] arrays and undefined
16981
- type: Object
16982
- },
16897
+ /**
16898
+ * Sets the highlighted index to the specified option.
16899
+ * @param {AuroMenuOption} option - The option to highlight.
16900
+ */
16901
+ setHighlightedOption(option) {
16983
16902
 
16984
- /**
16985
- * Available menu options.
16986
- * @readonly
16987
- */
16988
- options: {
16989
- type: Array,
16990
- reflect: false,
16991
- attribute: false
16992
- },
16903
+ if (!option) return;
16993
16904
 
16994
- /**
16995
- * Sets the size of the menu.
16996
- * @type {'sm' | 'md'}
16997
- * @default 'sm'
16998
- */
16999
- size: {
17000
- type: String,
17001
- reflect: true
17002
- },
16905
+ // Get the index of the option to highlight
16906
+ const index = this._menuOptions.indexOf(option);
17003
16907
 
17004
- /**
17005
- * When true, selects all options that match the provided value/key when setting value and multiselect is enabled.
17006
- */
17007
- selectAllMatchingOptions: {
17008
- type: Boolean,
17009
- reflect: true,
17010
- },
16908
+ // Update highlighted index
16909
+ this.highlightedIndex = index;
17011
16910
 
17012
- /**
17013
- * Sets the shape of the menu.
17014
- * @type {'box' | 'round'}
17015
- * @default 'box'
17016
- */
17017
- shape: {
17018
- type: String,
17019
- reflect: true
17020
- },
16911
+ // Notify subscribers of highlight change
16912
+ this.notify({ type: 'highlightChange', option, index: this.highlightedIndex });
17021
16913
 
17022
- /**
17023
- * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
17024
- */
17025
- value: {
17026
- type: String,
17027
- reflect: true,
17028
- attribute: 'value'
17029
- }
17030
- };
16914
+ // Dispatch the change event
16915
+ this.dispatchChangeEvent('auroMenu-activatedOption', option);
17031
16916
  }
17032
16917
 
17033
- static get styles() {
17034
- return [
17035
- styleCss$1,
17036
- colorCss$1,
17037
- tokensCss
17038
- ];
16918
+ /**
16919
+ * Sets the highlighted option to the option at the specified index if it exists.
16920
+ * @param {number} index
16921
+ */
16922
+ setHighlightedIndex(index) {
16923
+ const option = this._menuOptions[index] || null;
16924
+ this.setHighlightedOption(option);
17039
16925
  }
17040
16926
 
17041
16927
  /**
17042
- * @readonly
17043
- * @returns {string} - Returns the label of the currently selected option(s).
16928
+ * Selects the currently highlighted option.
17044
16929
  */
17045
- get currentLabel() {
17046
- return this.menuService.currentLabel;
17047
- };
16930
+ selectHighlightedOption() {
16931
+ if (this.highlightedOption) {
16932
+ this.toggleOption(this.highlightedOption);
16933
+ }
16934
+ }
17048
16935
 
17049
16936
  /**
17050
- * @readonly
17051
- * @returns {Array<HTMLElement>} - Returns the array of available menu options.
17052
- * @deprecated Use `options` property instead.
16937
+ * SELECTION AND DESELECTION METHODS
17053
16938
  */
17054
- get items() {
17055
- return this.options;
17056
- }
17057
16939
 
17058
16940
  /**
17059
- * @returns {number} - Returns the index of the currently active option.
16941
+ * Selects one or more options in a batch operation
16942
+ * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to select
17060
16943
  */
17061
- get index() {
17062
- return this._index;
16944
+ selectOptions(options) {
16945
+ let optionsToSelect = Array.isArray(options) ? options : [options];
16946
+
16947
+ // Filter out options that are inactive
16948
+ optionsToSelect = optionsToSelect.filter(option => option.isActive);
16949
+
16950
+ if (!optionsToSelect.length) return;
16951
+
16952
+ if (this.multiSelect) {
16953
+ this.selectedOptions = [...(this.selectedOptions || []), ...optionsToSelect];
16954
+ } else {
16955
+ // In single select mode, only take the last option
16956
+ this.selectedOptions = [optionsToSelect[optionsToSelect.length - 1]];
16957
+ }
16958
+
16959
+ this.stageUpdate();
17063
16960
  }
17064
16961
 
17065
16962
  /**
17066
- * @param {number} value - Sets the index of the currently active option.
16963
+ * Deselects one or more options in a batch operation
16964
+ * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to deselect
17067
16965
  */
17068
- set index(value) {
17069
- this.menuService.setHighlightedIndex(value);
16966
+ deselectOptions(options) {
16967
+ const optionsToDeselect = Array.isArray(options) ? options : [options];
16968
+
16969
+ if (!optionsToDeselect.length) return;
16970
+
16971
+ // Check if deselection should be prevented
16972
+ const shouldPreventDeselect = !this.allowDeselect && !this.multiSelect;
16973
+ const isOnlySelectedOption = this.selectedOptions.length === 1 && optionsToDeselect.includes(this.selectedOptions[0]);
16974
+
16975
+ // Prevent deselecting the only selected option if not allowed
16976
+ if (shouldPreventDeselect && isOnlySelectedOption) {
16977
+ optionsToDeselect.forEach(option => {
16978
+ option.selected = true;
16979
+ });
16980
+ this.dispatchChangeEvent('auroMenu-deselectPrevented', {
16981
+ values: optionsToDeselect
16982
+ });
16983
+ return;
16984
+ }
16985
+
16986
+ const optionsSet = new Set(optionsToDeselect);
16987
+ this.selectedOptions = (this.selectedOptions || [])
16988
+ .filter(opt => !optionsSet.has(opt));
16989
+
16990
+ this.stageUpdate();
17070
16991
  }
17071
16992
 
17072
16993
  /**
17073
- * This will register this element with the browser.
17074
- * @param {string} [name="auro-menu"] - The name of the element that you want to register.
17075
- *
17076
- * @example
17077
- * AuroMenu.register("custom-menu") // this will register this element to <custom-menu/>
17078
- *
16994
+ * Selects a single option.
16995
+ * @param {AuroMenuOption} option
17079
16996
  */
17080
- static register(name = "auro-menu") {
17081
- AuroLibraryRuntimeUtils$4.prototype.registerComponent(name, AuroMenu);
16997
+ selectOption(option) {
16998
+ this.selectOptions(option);
17082
16999
  }
17083
17000
 
17084
17001
  /**
17085
- * Formatted value based on `multiSelect` state.
17086
- * Default type is `String`, changing to `Array<String>` when `multiSelect` is true.
17087
- * @private
17088
- * @returns {String|Array<String>}
17002
+ * Deselects a single option.
17003
+ * @param {AuroMenuOption} option
17089
17004
  */
17090
- get formattedValue() {
17091
- return this.menuService.currentValue;
17005
+ deselectOption(option) {
17006
+ this.deselectOptions(option);
17092
17007
  }
17093
17008
 
17094
17009
  /**
17095
- * Gets the current property values for the menu service.
17096
- * @private
17097
- * @returns {Object}
17010
+ * Toggles the selection state of a single option.
17011
+ * @param {AuroMenuOption} option
17098
17012
  */
17099
- get propertyValues() {
17100
- return {
17101
- size: this.size,
17102
- shape: this.shape,
17103
- noCheckmark: this.nocheckmark,
17104
- disabled: this.disabled
17105
- };
17013
+ toggleOption(option) {
17014
+ if (option.selected) {
17015
+ this.deselectOption(option);
17016
+ } else {
17017
+ this.selectOption(option);
17018
+ }
17106
17019
  }
17107
17020
 
17108
17021
  /**
17109
- * Provides the menu context to child components.
17110
- * Initializes the MenuService and subscribes to menu changes.
17111
- * @protected
17022
+ * Selects options based on their value(s) when compared to a passed value or values.
17023
+ * Value or values are normalized to an array of strings that can be matched to option keys.
17024
+ * @param {string|number|Array<string|number>} value - The value(s) to select.
17112
17025
  */
17113
- provideContext() {
17114
- if (this.parentElement && this.parentElement.closest('auro-menu, [auro-menu]')) {
17115
- this.rootMenu = false;
17116
- this.menuService = this.parentElement.menuService;
17117
- this._contextProvider = this.parentElement._contextProvider;
17026
+ selectByValue(value) {
17027
+ const isEmptyValue = value === undefined ||
17028
+ value === null ||
17029
+ (Array.isArray(value) && value.length === 0) ||
17030
+ (typeof value === 'string' && value.trim() === '');
17031
+
17032
+ // Early exit for invalid/empty values
17033
+ if (isEmptyValue) {
17034
+ this.selectedOptions.forEach(opt => opt.selected = false);
17035
+ this.selectedOptions = [];
17118
17036
  return;
17119
17037
  }
17120
17038
 
17121
- this.menuService = new MenuService({host: this});
17122
- this.menuService.setProperties(this.propertyValues);
17123
- this.menuService.subscribe(this.handleMenuChange.bind(this));
17124
- this._contextProvider = new i(this, {
17125
- context: MenuContext,
17126
- initialValue: this.menuService
17039
+ // If an internal update cycle is still in progress, defer value application
17040
+ // rather than dropping it.
17041
+ if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
17042
+ this.queuePendingValue(value);
17043
+ return;
17044
+ }
17045
+
17046
+ // Normalize values to array of strings
17047
+ const normalizedValues = this._getNormalizedValues(value);
17048
+
17049
+ // Validate for single-select mode
17050
+ let validatedValues = normalizedValues;
17051
+ if (normalizedValues.length > 1 && !this.multiSelect) {
17052
+ console.warn("MenuService - Multiple values provided for single-select menu. Only the first value will be selected.");
17053
+ validatedValues = [normalizedValues[0]];
17054
+ }
17055
+
17056
+ if (this._menuOptions.length === 0) {
17057
+ this.queuePendingValue(value);
17058
+ return;
17059
+ }
17060
+
17061
+ // Find matching options by comparing available options to validated values
17062
+ const trackedKeys = new Set();
17063
+ const optionsToSelect = this._menuOptions.filter(option => {
17064
+ const passesFilter = validatedValues.includes(option.key);
17065
+ const alreadyTracked = trackedKeys.has(option.key);
17066
+ const isActive = option.isActive;
17067
+
17068
+ trackedKeys.add(option.key);
17069
+
17070
+ // Include the option in the options to be selected if it passes the filter check and
17071
+ // either hasn't been tracked yet or selectAllMatchingOptions is true
17072
+ return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
17127
17073
  });
17128
- }
17129
17074
 
17130
- /**
17131
- * Updates the currently active option in the menu.
17132
- * @param {HTMLElement} option - The option to set as active.
17133
- */
17134
- updateActiveOption(option) {
17135
- this.menuService.setHighlightedOption(option);
17136
- }
17075
+ // Handle no matches: clear existing selection, but do not dispatch an intermediate
17076
+ // undefined value that can overwrite the host value in parent components.
17077
+ if (!optionsToSelect.length) {
17078
+ const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
17137
17079
 
17138
- /**
17139
- * Sets the internal value and manages update state.
17140
- * @param {String|Array<String>} value - The value to set.
17141
- * @protected
17142
- */
17143
- setInternalValue(value) {
17144
- if (this.value !== value) {
17145
- this.internalUpdateInProgress = true;
17146
- this.value = value;
17080
+ if (hasUnresolvedKeys) {
17081
+ this.queuePendingValue(value);
17082
+ return;
17083
+ }
17147
17084
 
17148
- setTimeout(() => {
17149
- this.internalUpdateInProgress = false;
17150
- });
17151
- }
17152
- }
17085
+ this.clearPendingValue();
17153
17086
 
17154
- /**
17155
- * Handles changes from the menu service and updates component state.
17156
- * @param {Object} event - The event object from the menu service.
17157
- * @protected
17158
- */
17159
- handleMenuChange(event) {
17160
- if (event.type === 'valueChange') {
17087
+ if (this.selectedOptions.length > 0) {
17088
+ this.selectedOptions = [];
17089
+ }
17161
17090
 
17162
- // New option is array value or first option with fallback to undefined for empty array in all cases
17163
- const newOption = this.multiSelect && event.options.length ? event.options : event.options[0] || undefined;
17164
- const newValue = event.stringValue;
17091
+ // Always notify so the host resets any stale invalid value, even when
17092
+ // selectedOptions was already empty (e.g. double-clicking set-invalid).
17093
+ this.stageUpdate({ reason: 'no-match' });
17165
17094
 
17166
- // Check if the option or value has actually changed
17167
- if (this.optionSelected !== newOption || this.stringValue !== newValue) {
17168
- this.optionSelected = newOption;
17169
- this.setInternalValue(newValue);
17095
+ // Dispatch failure event if no matches found
17096
+ if (validatedValues.length) {
17097
+ this.dispatchChangeEvent('auroMenu-selectValueFailure', {
17098
+ message: 'No matching options found for the provided value(s).',
17099
+ values: validatedValues
17100
+ });
17170
17101
  }
17171
17102
 
17172
- // Notify components of selection change
17173
- this.notifySelectionChange(event);
17103
+ return;
17174
17104
  }
17175
17105
 
17176
- if (event.type === 'highlightChange') {
17177
- this.optionActive = event.option;
17178
- this._index = event.index;
17179
- }
17106
+ this.clearPendingValue();
17180
17107
 
17181
- if (event.type === 'optionsChange') {
17182
- this.options = event.options;
17183
- this.dispatchEvent(new CustomEvent('auroMenu-optionsChange', {
17184
- detail: {
17185
- options: event.options
17186
- }
17187
- }));
17108
+ if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
17109
+ return;
17188
17110
  }
17189
- }
17190
17111
 
17191
- /**
17192
- * Gets the currently selected options.
17193
- * @returns {Array<HTMLElement>}
17194
- */
17195
- get selectedOptions() {
17196
- return this.menuService ? this.menuService.selectedOptions : [];
17112
+ // Apply programmatic selection as a single transaction and emit one final state.
17113
+ this.selectedOptions = optionsToSelect;
17114
+ this.stageUpdate();
17197
17115
  }
17198
17116
 
17199
17117
  /**
17200
- * Gets the first selected option, or null if none.
17201
- * @returns {HTMLElement|null}
17118
+ * Queues a pending value and schedules a bounded retry.
17119
+ * @param {string|number|Array<string|number>} value - The value to retry.
17202
17120
  */
17203
- get selectedOption() {
17204
- return this.menuService ? this.menuService.selectedOptions[0] : null;
17205
- }
17206
-
17207
- // Lifecycle Methods
17121
+ queuePendingValue(value) {
17122
+ this._pendingValue = value;
17208
17123
 
17209
- connectedCallback() {
17210
- super.connectedCallback();
17124
+ if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
17125
+ return;
17126
+ }
17211
17127
 
17212
- this.provideContext();
17128
+ this._pendingRetryScheduled = true;
17129
+ this._pendingRetryCount += 1;
17213
17130
 
17214
- // this.addEventListener('keydown', this.handleKeyDown);
17215
- this.addEventListener('auroMenuOption-click', this.handleMouseSelect);
17216
- this.addEventListener('auroMenuOption-mouseover', this.handleOptionHover);
17217
- this.addEventListener('slotchange', this.handleSlotChange);
17218
- this.setTagAttribute("auro-menu");
17219
- }
17131
+ setTimeout(() => {
17132
+ this._pendingRetryScheduled = false;
17220
17133
 
17221
- disconnectedCallback() {
17222
- // this.removeEventListener('keydown', this.handleKeyDown);
17223
- this.removeEventListener('auroMenuOption-click', this.handleMouseSelect);
17224
- this.removeEventListener('auroMenuOption-mouseover', this.handleOptionHover);
17225
- this.removeEventListener('slotchange', this.handleSlotChange);
17134
+ if (this._pendingValue == null) {
17135
+ return;
17136
+ }
17226
17137
 
17227
- super.disconnectedCallback();
17138
+ const pendingValue = this._pendingValue;
17139
+ this.selectByValue(pendingValue);
17140
+ }, 0);
17228
17141
  }
17229
17142
 
17230
- firstUpdated() {
17231
- AuroLibraryRuntimeUtils$4.prototype.handleComponentTagRename(this, 'auro-menu');
17232
-
17233
- this.loadingSlots = this.querySelectorAll("[slot='loadingText'], [slot='loadingIcon']");
17234
- this.initializeMenu();
17143
+ /**
17144
+ * Clears pending retry state.
17145
+ */
17146
+ clearPendingValue() {
17147
+ this._pendingValue = null;
17148
+ this._pendingRetryScheduled = false;
17149
+ this._pendingRetryCount = 0;
17235
17150
  }
17236
17151
 
17152
+ /**
17153
+ * Resets the selected options to an empty array.
17154
+ */
17155
+ reset() {
17156
+ const previousOptions = [...this.selectedOptions];
17157
+ previousOptions.forEach(opt => opt.selected = false);
17158
+ this.selectedOptions = [];
17237
17159
 
17238
- updated(changedProperties) {
17239
- super.updated(changedProperties);
17240
-
17241
- // Apply value selection synchronously so that static-HTML fixtures
17242
- // resolve within a single update cycle. The refactored selectByValue
17243
- // no longer calls reset() first, so the destructive intermediate-event
17244
- // cascade that originally required deferral is eliminated. If option
17245
- // keys are not yet resolved (framework mount-order race), selectByValue
17246
- // queues a bounded retry automatically via queuePendingValue.
17247
- if (changedProperties.has('value') && !this.internalUpdateInProgress) {
17248
- this.menuService.selectByValue(this.value);
17160
+ // Single update after clearing all
17161
+ if (previousOptions.length) {
17162
+ this.stageUpdate();
17249
17163
  }
17164
+ }
17250
17165
 
17251
- // Handle loading state changes
17252
- if (changedProperties.has('loading')) {
17253
- this.setLoadingState(this.loading);
17254
- }
17166
+ /**
17167
+ * SUBSCRIPTION, NOTIFICATION AND EVENT DISPATCH METHODS
17168
+ */
17255
17169
 
17256
- if (changedProperties.has('multiSelect') && this.rootMenu) {
17257
- if (this.multiSelect) {
17258
- this.setAttribute('aria-multiselectable', 'true');
17259
- } else {
17260
- this.removeAttribute('aria-multiselectable');
17261
- }
17262
- }
17170
+ /**
17171
+ * Subscribes a callback to menu service events.
17172
+ * @param {Function} callback - The callback to invoke on events.
17173
+ */
17174
+ subscribe(callback) {
17175
+ this._subscribers.push(callback);
17263
17176
  }
17264
17177
 
17265
17178
  /**
17266
- * Sets an attribute that matches the default tag name if the tag name is not the default.
17267
- * @param {string} tagName - The tag name to set as an attribute.
17268
- * @private
17179
+ * Remove a previously subscribed callback from menu service events.
17180
+ * @param {Function} callback
17269
17181
  */
17270
- setTagAttribute(tagName) {
17271
- if (this.tagName.toLowerCase() !== tagName) {
17272
- this.setAttribute(tagName, true);
17273
- }
17182
+ unsubscribe(callback) {
17183
+ this._subscribers = this._subscribers.filter(cb => cb !== callback);
17274
17184
  }
17275
17185
 
17276
17186
  /**
17277
- * Sets the loading state and dispatches a loading change event.
17278
- * @param {boolean} isLoading - Whether the menu is loading.
17279
- * @protected
17187
+ * Stages an update to notify subscribers of state and value changes.
17280
17188
  */
17281
- setLoadingState(isLoading) {
17282
- this.setAttribute("aria-busy", isLoading);
17283
- dispatchMenuEvent(this, "auroMenu-loadingChange", {
17284
- loading: isLoading,
17285
- hasLoadingPlaceholder: this.hasLoadingPlaceholder
17286
- });
17189
+ stageUpdate(meta = {}) {
17190
+ this.notifyStateChange(meta);
17191
+ this.notifyValueChange(meta);
17287
17192
  }
17288
17193
 
17289
- // Init Methods
17290
-
17291
17194
  /**
17292
- * Initializes the menu's state and structure.
17293
- * @private
17195
+ * Notifies subscribers of a menu service event.
17196
+ * All notifications are sent to all subscribers.
17197
+ * @param {string} event - The event to send to subscribers.
17294
17198
  */
17295
- initializeMenu() {
17296
- if (this.rootMenu) {
17297
- this.setAttribute('role', 'listbox');
17298
- this.setAttribute('root', '');
17299
-
17300
- if (this.multiSelect) {
17301
- this.setAttribute('aria-multiselectable', 'true');
17302
- }
17303
- }
17304
-
17305
- this.handleNestedMenus(this);
17199
+ notify(event) {
17200
+ this._subscribers.forEach(callback => callback(event));
17306
17201
  }
17307
17202
 
17308
17203
  /**
17309
- * Selects the currently highlighted option.
17310
- * @protected
17204
+ * Notifies subscribers of a state change (selected options has changed).
17311
17205
  */
17312
- makeSelection() {
17313
- this.menuService.selectHighlightedOption();
17206
+ notifyStateChange(meta = {}) {
17207
+ this.notify({
17208
+ type: 'stateChange',
17209
+ selectedOptions: this.selectedOptions,
17210
+ ...meta
17211
+ });
17314
17212
  }
17315
17213
 
17316
17214
  /**
17317
- * Resets all options to their default state.
17318
- * @private
17215
+ * Notifies subscribers of a value change (current value has changed).
17319
17216
  */
17320
- clearSelection() {
17321
- this.optionSelected = undefined;
17322
- this.value = undefined;
17323
- this._index = -1;
17217
+ notifyValueChange(meta = {}) {
17218
+
17219
+ // Prepare details for the event
17220
+ const details = {
17221
+ value: this.currentValue,
17222
+ stringValue: this.stringValue,
17223
+ keys: this.currentKeys,
17224
+ options: this.selectedOptions,
17225
+ label: this.currentLabel
17226
+ };
17227
+
17228
+ // If only one option is selected, include its index
17229
+ if (this.selectedOptions.length === 1) details.index = this._menuOptions.indexOf(this.selectedOptions[0]);
17230
+
17231
+ this.notify({
17232
+ type: 'valueChange',
17233
+ ...meta,
17234
+ ...details
17235
+ });
17324
17236
  }
17325
17237
 
17326
17238
  /**
17327
- * Resets the menu to its initial state.
17328
- * This is the only way to return value to undefined.
17329
- * @public
17239
+ * Dispatches a custom event from the host element.
17240
+ * @param {string} eventName
17241
+ * @param {any} detail
17330
17242
  */
17331
- reset() {
17332
- this.menuService.reset();
17333
-
17334
- // Dispatch reset event
17335
- dispatchMenuEvent(this, 'auroMenu-selectValueReset');
17243
+ dispatchChangeEvent(eventName, detail) {
17244
+ this.host.dispatchEvent(new CustomEvent(eventName, {
17245
+ bubbles: true,
17246
+ cancelable: false,
17247
+ composed: true,
17248
+ detail
17249
+ }));
17336
17250
  }
17337
17251
 
17338
17252
  /**
17339
- * Handles nested menu structure.
17340
- * @private
17341
- * @param {HTMLElement} menu - Root menu element.
17253
+ * MENU OPTION MANAGEMENT METHODS
17342
17254
  */
17343
- handleNestedMenus(menu) {
17344
- menu.level = menu.parentElement.level >= 0 ? menu.parentElement.level + 1 : 0;
17345
-
17346
- if (menu.level > 0) {
17347
- menu.setAttribute('role', 'group');
17348
- menu.removeAttribute("root");
17349
- if (!menu.hasAttribute('aria-label')) {
17350
- menu.setAttribute('aria-label', 'submenu');
17351
- }
17352
- }
17353
-
17354
- const options = menu.querySelectorAll(':scope > auro-menuoption, :scope > [auro-menuoption]');
17355
- options.forEach((option) => {
17356
- const regex = new RegExp(this.nestingSpacer, "gu");
17357
- option.innerHTML = this.nestingSpacer.repeat(menu.level) + option.innerHTML.replace(regex, '');
17358
- });
17359
- }
17360
17255
 
17361
17256
  /**
17362
- * Navigates the menu options in the specified direction.
17363
- * @param {'up'|'down'} direction - The direction to navigate.
17364
- * @protected
17257
+ * Adds a menu option to the service's list.
17258
+ * @param {AuroMenuOption} option - the option to track
17365
17259
  */
17366
- navigateOptions(direction) {
17367
- if (direction === 'up') {
17368
- this.menuService.highlightPrevious();
17369
- } else if (direction === 'down') {
17370
- this.menuService.highlightNext();
17260
+ addMenuOption(option) {
17261
+ this._menuOptions.push(option);
17262
+ this.notify({ type: 'optionsChange', options: this._menuOptions });
17263
+
17264
+ if (this._pendingValue != null) {
17265
+ this.queuePendingValue(this._pendingValue);
17371
17266
  }
17372
17267
  }
17373
17268
 
17374
17269
  /**
17375
- * Handles slot change events.
17376
- * @private
17270
+ * Removes a menu option from the service's list.
17271
+ * @param {AuroMenuOption} option - the option to remove
17377
17272
  */
17378
- handleSlotChange() {
17379
- if (this.rootMenu) {
17380
- this.initializeMenu();
17273
+ removeMenuOption(option) {
17274
+ this._menuOptions = this._menuOptions.filter(opt => opt !== option);
17275
+ this.notify({ type: 'optionsChange', options: this._menuOptions });
17276
+
17277
+ if (this._menuOptions.length === 0) {
17278
+ this.clearPendingValue();
17381
17279
  }
17382
17280
  }
17383
17281
 
17384
17282
  /**
17385
- * Handles custom events defined on options.
17386
- * @private
17387
- * @param {HTMLElement} option - Option with custom event.
17283
+ * UTILITIES
17388
17284
  */
17389
- handleCustomEvent(option) {
17390
- const eventName = option.getAttribute('event');
17391
- dispatchMenuEvent(this, eventName);
17392
- dispatchMenuEvent(this, 'auroMenu-customEventFired');
17393
- }
17394
17285
 
17395
17286
  /**
17396
- * Notifies selection change to parent components.
17397
- * @param {any} source - The source that triggers this event.
17398
- * @private
17287
+ * Normalizes a value or array of values into an array of strings for option selection.
17288
+ * This function ensures that input values are consistently formatted for matching menu options.
17289
+ *
17290
+ * @param {string|number|Array<string|number>} value - The value(s) to normalize.
17291
+ * @returns {Array<string>} An array of string values suitable for option matching.
17292
+ * @throws {Error} If any value is not a string or number.
17399
17293
  */
17400
- notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
17401
- dispatchMenuEvent(this, 'auroMenu-selectedOption', {
17402
- value,
17403
- stringValue,
17404
- keys,
17405
- options,
17406
- reason
17407
- });
17408
- }
17294
+ _getNormalizedValues(value) {
17295
+ let values = value;
17409
17296
 
17410
- /**
17411
- * Checks if an option is currently selected.
17412
- * @private
17413
- * @param {HTMLElement} option - The option to check.
17414
- * @returns {boolean}
17415
- */
17416
- isOptionSelected(option) {
17417
- if (!this.optionSelected) {
17418
- return false;
17297
+ // Handle JSON string and single value string input
17298
+ if (!Array.isArray(values) && typeof values === 'string') {
17299
+
17300
+ // Attempt to parse as JSON array
17301
+ try {
17302
+
17303
+ // Normalize single quotes to double quotes for JSON parsing
17304
+ // This will not handle complex cases but will cover basic usage
17305
+ const parseValue = values.replace(/'([^']*?)'/g, '"$1"');
17306
+
17307
+ // Attempt parse
17308
+ const parsed = JSON.parse(parseValue);
17309
+
17310
+ // Ensure parsed value is an array
17311
+ if (!Array.isArray(parsed)) throw new Error('Not an array');
17312
+
17313
+ // Set values to parsed array
17314
+ values = parsed;
17315
+ } catch (err) {
17316
+
17317
+ // If parsing fails, treat as single value
17318
+ values = [value];
17319
+ }
17419
17320
  }
17420
17321
 
17421
- if (this.multiSelect) {
17422
- // In multi-select mode, check if the option is in the selected array
17423
- return Array.isArray(this.optionSelected) && this.optionSelected.some((selectedOption) => selectedOption === option);
17322
+ // Handle a single number being passed
17323
+ if (typeof values === 'number') {
17324
+ values = [String(values)];
17424
17325
  }
17425
17326
 
17426
- return this.optionSelected === option;
17427
- }
17327
+ // Coerce each value to string and validate types
17328
+ values.forEach((val, index) => {
17428
17329
 
17429
- /**
17430
- * Getter for loading placeholder state.
17431
- * @returns {boolean} - True if loading slots are present and non-empty.
17432
- */
17433
- get hasLoadingPlaceholder() {
17434
- return this.loadingSlots && this.loadingSlots.length > 0;
17435
- }
17330
+ // Throw an error for invalid value types
17331
+ if (typeof val !== 'string' && typeof val !== 'number') {
17332
+ throw new Error('Value contains invalid value type. Supported types are string and number.');
17333
+ }
17436
17334
 
17437
- /**
17438
- * Getter for wrapper classes based on size.
17439
- * @returns {Object} - Class map for the wrapper element.
17440
- * @private
17441
- */
17442
- get wrapperClasses() {
17443
- return e$3({
17444
- 'menuWrapper': true,
17445
- [this.size]: true,
17335
+ // Convert numbers to strings for consistency
17336
+ if (typeof val === 'number') {
17337
+ values[index] = String(val);
17338
+ }
17446
17339
  });
17340
+
17341
+ // Return the resulting array of string values
17342
+ return values;
17447
17343
  }
17448
17344
 
17449
17345
  /**
17450
- * Logic to determine the layout of the component.
17451
- * @protected
17452
- * @returns {void}
17346
+ * Returns whether two arrays of options contain the same elements.
17347
+ * @param {AuroMenuOption[]} arr1 - First array of options.
17348
+ * @param {AuroMenuOption[]} arr2 - Second array of options.
17349
+ * @returns {boolean} True if arrays match, false otherwise.
17453
17350
  */
17454
- renderLayout() {
17455
- if (this.loading) {
17456
- return b$2`
17457
- <div class="${this.wrapperClasses}">
17458
- <auro-menuoption
17459
- disabled
17460
- loadingplaceholder
17461
- class="${this.hasLoadingPlaceholder ? "" : "empty"}"
17462
- >
17463
- <div>
17464
- <slot name="loadingIcon" class="body-lg"></slot>
17465
- <slot name="loadingText"></slot>
17466
- </div>
17467
- </auro-menuoption>
17468
- </div>
17469
- `;
17351
+ optionsArraysMatch(arr1, arr2) {
17352
+ if (arr1.length !== arr2.length) return false;
17353
+
17354
+ const set1 = new Set(arr1);
17355
+ const set2 = new Set(arr2);
17356
+
17357
+ for (let item of set1) {
17358
+ if (!set2.has(item)) {
17359
+ return false;
17360
+ }
17470
17361
  }
17471
17362
 
17472
- return b$2`
17473
- <div class="${this.wrapperClasses}">
17474
- <slot @slotchange=${this.handleSlotChange}></slot>
17475
- </div>
17476
- `;
17363
+ return true;
17477
17364
  }
17478
17365
  }
17479
17366
 
17480
- var styleCss = i$7`.body-default{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-default-font-size, 1rem);line-height:var(--wcss-body-default-line-height, 1.5rem)}.body-lg{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-lg-font-size, 1.125rem);line-height:var(--wcss-body-lg-line-height, 1.625rem)}.body-sm{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-sm-font-size, 0.875rem);line-height:var(--wcss-body-sm-line-height, 1.25rem)}.body-xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-xs-font-size, 0.75rem);line-height:var(--wcss-body-xs-line-height, 1rem)}.body-2xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);font-size:var(--wcss-body-2xs-font-size, 0.625rem);line-height:var(--wcss-body-2xs-line-height, 0.875rem)}.display-2xl{font-family:var(--wcss-display-2xl-family, "AS Circular"),var(--wcss-display-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-2xl-letter-spacing, 0);font-weight:var(--wcss-display-2xl-weight, 300);line-height:var(--wcss-display-2xl-line-height, 1.3);font-size:var(--wcss-display-2xl-font-size, clamp(3.5rem, 6vw, 5.375rem))}.display-xl{font-family:var(--wcss-display-xl-family, "AS Circular"),var(--wcss-display-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-xl-letter-spacing, 0);font-weight:var(--wcss-display-xl-weight, 300);line-height:var(--wcss-display-xl-line-height, 1.3);font-size:var(--wcss-display-xl-font-size, clamp(3rem, 5.3333333333vw, 4.5rem))}.display-lg{font-family:var(--wcss-display-lg-family, "AS Circular"),var(--wcss-display-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-lg-letter-spacing, 0);font-weight:var(--wcss-display-lg-weight, 300);line-height:var(--wcss-display-lg-line-height, 1.3);font-size:var(--wcss-display-lg-font-size, clamp(2.75rem, 4.6666666667vw, 4rem))}.display-md{font-family:var(--wcss-display-md-family, "AS Circular"),var(--wcss-display-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-md-letter-spacing, 0);font-weight:var(--wcss-display-md-weight, 300);line-height:var(--wcss-display-md-line-height, 1.3);font-size:var(--wcss-display-md-font-size, clamp(2.5rem, 4vw, 3.5rem))}.display-sm{font-family:var(--wcss-display-sm-family, "AS Circular"),var(--wcss-display-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-sm-letter-spacing, 0);font-weight:var(--wcss-display-sm-weight, 300);line-height:var(--wcss-display-sm-line-height, 1.3);font-size:var(--wcss-display-sm-font-size, clamp(2rem, 3.6666666667vw, 3rem))}.display-xs{font-family:var(--wcss-display-xs-family, "AS Circular"),var(--wcss-display-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-display-xs-letter-spacing, 0);font-weight:var(--wcss-display-xs-weight, 300);line-height:var(--wcss-display-xs-line-height, 1.3);font-size:var(--wcss-display-xs-font-size, clamp(1.75rem, 3vw, 2.375rem))}.heading-xl{font-family:var(--wcss-heading-xl-family, "AS Circular"),var(--wcss-heading-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-xl-letter-spacing, 0);font-weight:var(--wcss-heading-xl-weight, 300);line-height:var(--wcss-heading-xl-line-height, 1.3);font-size:var(--wcss-heading-xl-font-size, clamp(2rem, 3vw, 2.5rem))}.heading-lg{font-family:var(--wcss-heading-lg-family, "AS Circular"),var(--wcss-heading-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-lg-letter-spacing, 0);font-weight:var(--wcss-heading-lg-weight, 300);line-height:var(--wcss-heading-lg-line-height, 1.3);font-size:var(--wcss-heading-lg-font-size, clamp(1.75rem, 2.6666666667vw, 2.25rem))}.heading-md{font-family:var(--wcss-heading-md-family, "AS Circular"),var(--wcss-heading-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-md-letter-spacing, 0);font-weight:var(--wcss-heading-md-weight, 300);line-height:var(--wcss-heading-md-line-height, 1.3);font-size:var(--wcss-heading-md-font-size, clamp(1.625rem, 2.3333333333vw, 1.75rem))}.heading-sm{font-family:var(--wcss-heading-sm-family, "AS Circular"),var(--wcss-heading-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-sm-letter-spacing, 0);font-weight:var(--wcss-heading-sm-weight, 300);line-height:var(--wcss-heading-sm-line-height, 1.3);font-size:var(--wcss-heading-sm-font-size, clamp(1.375rem, 2vw, 1.5rem))}.heading-xs{font-family:var(--wcss-heading-xs-family, "AS Circular"),var(--wcss-heading-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-xs-letter-spacing, 0);font-weight:var(--wcss-heading-xs-weight, 450);line-height:var(--wcss-heading-xs-line-height, 1.3);font-size:var(--wcss-heading-xs-font-size, clamp(1.25rem, 1.6666666667vw, 1.25rem))}.heading-2xs{font-family:var(--wcss-heading-2xs-family, "AS Circular"),var(--wcss-heading-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-heading-2xs-letter-spacing, 0);font-weight:var(--wcss-heading-2xs-weight, 450);line-height:var(--wcss-heading-2xs-line-height, 1.3);font-size:var(--wcss-heading-2xs-font-size, clamp(1.125rem, 1.5vw, 1.125rem))}.accent-2xl{font-family:var(--wcss-accent-2xl-family, "Good OT"),var(--wcss-accent-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-2xl-letter-spacing, 0.05em);font-weight:var(--wcss-accent-2xl-weight, 450);line-height:var(--wcss-accent-2xl-line-height, 1);font-size:var(--wcss-accent-2xl-font-size, clamp(2rem, 3.1666666667vw, 2.375rem));text-transform:uppercase}.accent-xl{font-family:var(--wcss-accent-xl-family, "Good OT"),var(--wcss-accent-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-xl-letter-spacing, 0.05em);font-weight:var(--wcss-accent-xl-weight, 450);line-height:var(--wcss-accent-xl-line-height, 1.3);font-size:var(--wcss-accent-xl-font-size, clamp(1.625rem, 2.3333333333vw, 2rem));text-transform:uppercase}.accent-lg{font-family:var(--wcss-accent-lg-family, "Good OT"),var(--wcss-accent-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-lg-letter-spacing, 0.05em);font-weight:var(--wcss-accent-lg-weight, 450);line-height:var(--wcss-accent-lg-line-height, 1.3);font-size:var(--wcss-accent-lg-font-size, clamp(1.5rem, 2.1666666667vw, 1.75rem));text-transform:uppercase}.accent-md{font-family:var(--wcss-accent-md-family, "Good OT"),var(--wcss-accent-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-md-letter-spacing, 0.05em);font-weight:var(--wcss-accent-md-weight, 500);line-height:var(--wcss-accent-md-line-height, 1.3);font-size:var(--wcss-accent-md-font-size, clamp(1.375rem, 1.8333333333vw, 1.5rem));text-transform:uppercase}.accent-sm{font-family:var(--wcss-accent-sm-family, "Good OT"),var(--wcss-accent-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-sm-letter-spacing, 0.05em);font-weight:var(--wcss-accent-sm-weight, 500);line-height:var(--wcss-accent-sm-line-height, 1.3);font-size:var(--wcss-accent-sm-font-size, clamp(1.125rem, 1.5vw, 1.25rem));text-transform:uppercase}.accent-xs{font-family:var(--wcss-accent-xs-family, "Good OT"),var(--wcss-accent-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-xs-letter-spacing, 0.1em);font-weight:var(--wcss-accent-xs-weight, 500);line-height:var(--wcss-accent-xs-line-height, 1.3);font-size:var(--wcss-accent-xs-font-size, clamp(1rem, 1.3333333333vw, 1rem));text-transform:uppercase}.accent-2xs{font-family:var(--wcss-accent-2xs-family, "Good OT"),var(--wcss-accent-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);letter-spacing:var(--wcss-accent-2xs-letter-spacing, 0.1em);font-weight:var(--wcss-accent-2xs-weight, 450);line-height:var(--wcss-accent-2xs-line-height, 1.3);font-size:var(--wcss-accent-2xs-font-size, clamp(0.875rem, 1.1666666667vw, 0.875rem));text-transform:uppercase}:host{cursor:pointer;user-select:none;text-overflow:ellipsis;max-width:100dvw}:host .wrapper{display:flex;align-items:center;height:var(--ds-size-400, 2rem);padding-right:var(--ds-size-200, 1rem);padding-left:calc(var(--ds-size-150, 0.75rem) + var(--ds-size-300, 1.5rem) + var(--ds-size-100, 0.5rem));border-radius:var(--ds-size-100, 0.5rem);-webkit-tap-highlight-color:transparent}:host .wrapper[class*=shape-box]{border-radius:unset}:host .wrapper[class*=shape-snowflake]{border-radius:unset;line-height:24px}:host .wrapper[class*=shape-pill]{border-radius:30px}:host .wrapper[class*=-lg]{padding-top:var(--ds-size-75, 0.375rem);padding-bottom:var(--ds-size-75, 0.375rem);padding-right:var(--ds-size-150, 0.75rem);line-height:26px}:host .wrapper[class*=-xl]{padding-top:var(--ds-size-100, 0.5rem);padding-bottom:var(--ds-size-100, 0.5rem);padding-right:var(--ds-size-200, 1rem);line-height:26px}:host slot{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}:host [auro-icon]{--ds-auro-icon-size: var(--ds-size-300, 1.5rem);margin-right:var(--ds-size-150, 0.75rem);margin-left:var(--ds-size-100, 0.5rem)}:host ::slotted(.nestingSpacer){display:inline-block;width:var(--ds-size-300, 1.5rem)}[slot=displayValue]{display:none}:host([loadingplaceholder]) .wrapper{padding-left:calc(var(--ds-size-150, 0.75rem) + var(--ds-size-300, 1.5rem) + var(--ds-size-100, 0.5rem))}:host([selected]) .wrapper{padding-left:0}:host([nocheckmark]) .wrapper{padding-left:var(--ds-size-150, 0.75rem)}:host([nocheckmark]) .wrapper[class*=-lg]{padding-left:var(--ds-size-150, 0.75rem)}:host([nocheckmark]) .wrapper[class*=-xl]{padding-left:var(--ds-size-200, 1rem)}:host([hidden]){display:none}:host([static]){pointer-events:none}:host([disabled]:hover){cursor:auto}:host([disabled]){user-select:none;pointer-events:none}`;
17481
-
17482
- var colorCss = i$7`:host .wrapper{background-color:var(--ds-auro-menuoption-container-color, transparent);box-shadow:inset 0 0 0 1px var(--ds-auro-menuoption-container-border-color, transparent);color:var(--ds-auro-menuoption-text-color)}:host svg{fill:var(--ds-auro-menuoption-icon-color)}:host([disabled]){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-menuoption-text-color: var(--ds-basic-color-texticon-disabled, #d0d0d0);--ds-auro-menuoption-icon-color: var(--ds-basic-color-texticon-disabled, #d0d0d0)}:host(.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}@media(hover: hover){:host(:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}}:host(:focus){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}:host([selected]){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-subtle, #b4eff9);--ds-auro-menuoption-text-color: var(--ds-basic-color-texticon-default, #2a2a2a);--ds-auro-menuoption-icon-color: var(--ds-basic-color-texticon-default, #2a2a2a)}:host([selected].active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}@media(hover: hover){:host([selected]:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd)}}:host([selected]:focus){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-subtle, #b4eff9);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}:host(:focus.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}@media(hover: hover){:host(:focus:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}}:host([selected]:focus.active){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}@media(hover: hover){:host([selected]:focus:hover){--ds-auro-menuoption-container-color: var(--ds-basic-color-surface-accent1-muted, #ebfafd);--ds-auro-menuoption-container-border-color: var(--ds-basic-color-border-brand, #00274a)}}`;
17483
-
17484
- class p{registerComponent(t,a){customElements.get(t)||customElements.define(t,class extends a{});}closestElement(t,a=this,e=(a,s=a&&a.closest(t))=>a&&a!==document&&a!==window?s||e(a.getRootNode().host):null){return e(a)}handleComponentTagRename(t,a){const e=a.toLowerCase();t.tagName.toLowerCase()!==e&&t.setAttribute(e,true);}elementMatch(t,a){const e=a.toLowerCase();return t.tagName.toLowerCase()===e||t.hasAttribute(e)}getSlotText(t,a){const e=t.shadowRoot?.querySelector(`slot[name="${a}"]`);return (e?.assignedNodes({flatten:true})||[]).map(t=>t.textContent?.trim()).join(" ").trim()||null}}var u='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-labelledby="error__desc" class="ico_squareLarge" data-deprecated="true" role="img" style="min-width:var(--auro-size-lg, var(--ds-size-300, 1.5rem));height:var(--auro-size-lg, var(--ds-size-300, 1.5rem));fill:currentColor" viewBox="0 0 24 24" part="svg"><title/><desc id="error__desc">Error alert indicator.</desc><path d="m13.047 5.599 6.786 11.586A1.207 1.207 0 0 1 18.786 19H5.214a1.207 1.207 0 0 1-1.047-1.815l6.786-11.586a1.214 1.214 0 0 1 2.094 0m-1.165.87a.23.23 0 0 0-.085.085L5.419 17.442a.232.232 0 0 0 .203.35h12.756a.234.234 0 0 0 .203-.35L12.203 6.554a.236.236 0 0 0-.321-.084M12 15.5a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5m-.024-6.22c.325 0 .589.261.589.583v4.434a.586.586 0 0 1-.589.583.586.586 0 0 1-.588-.583V9.863c0-.322.264-.583.588-.583"/></svg>';class m extends i$4{static get properties(){return {hidden:{type:Boolean,reflect:true},hiddenVisually:{type:Boolean,reflect:true},hiddenAudible:{type:Boolean,reflect:true}}}hideAudible(t){return t?"true":"false"}}const g=new Map,f=(t,a={})=>{const e=a.responseParser||(t=>t.text());return g.has(t)||g.set(t,fetch(t).then(e)),g.get(t)};var w=i$7`:focus:not(:focus-visible){outline:3px solid transparent}.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock,:host{display:block}.util_displayFlex{display:flex}.util_displayHidden,:host([hidden]:not(:focus):not(:active)){display:none}.util_displayHiddenVisually,:host([hiddenVisually]:not(:focus):not(:active)){position:absolute;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px;padding:0;border:0}.ico_squareLarge{fill:currentColor;height:var(--auro-size-lg, var(--ds-size-300, 1.5rem))}.ico_squareSmall{fill:currentColor;height:.6rem}.ico_squareMed{fill:currentColor;height:var(--auro-size-md, var(--ds-size-200, 1rem))}.ico_squareSml{fill:currentColor;height:var(--auro-size-sm, var(--ds-size-150, .75rem))}:host{color:currentColor;vertical-align:middle;display:inline-block}svg{min-width:var(--ds-auro-icon-size, 1.5rem)!important;width:var(--ds-auro-icon-size, 1.5rem)!important;height:var(--ds-auro-icon-size, 1.5rem)!important}.componentWrapper{display:flex;line-height:var(--ds-auro-icon-size)}.svgWrapper{height:var(--ds-auro-icon-size);width:var(--ds-auro-icon-size)}.svgWrapper [part=svg]{display:flex}.labelWrapper{margin-left:var(--ds-size-50, .25rem)}.labelWrapper ::slotted(*){line-height:inherit!important}
17485
- `;class z extends m{constructor(){super(),this._initializeDefaults();}_initializeDefaults(){this.onDark=false,this.appearance="default";}static get properties(){return {...m.properties,onDark:{type:Boolean,reflect:true},appearance:{type:String,reflect:true},svg:{attribute:false,reflect:true}}}static get styles(){return w}async fetchIcon(t,a){let e="";e="logos"===t?await f(`${this.uri}/${t}/${a}.svg`):await f(`${this.uri}/icons/${t}/${a}.svg`);return (new DOMParser).parseFromString(e,"text/html").body.querySelector("svg")}async firstUpdated(){try{if(!this.customSvg){const t=await this.fetchIcon(this.category,this.name);if(t)this.svg=t;else if(!t){const t=(new DOMParser).parseFromString(u,"text/html");this.svg=t.body.firstChild;}}}catch(t){this.svg=void 0;}}}i$7`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock,:host{display:block}.util_displayFlex{display:flex}.util_displayHidden,:host([hidden]:not(:focus):not(:active)){display:none}.util_displayHiddenVisually,:host([hiddenVisually]:not(:focus):not(:active)){position:absolute;overflow:hidden;clip:rect(1px,1px,1px,1px);width:1px;height:1px;padding:0;border:0}:host{display:inline-block;--ds-auro-icon-size: 100%;width:100%;height:100%}:host .logo{color:var(--ds-auro-alaska-color)}:host([onDark]),:host([appearance=inverse]){--ds-auro-alaska-color: #FFF}
17486
- `;var y=i$7`:host{--ds-auro-icon-color: var(--ds-basic-color-texticon-default, #2a2a2a);--ds-auro-alaska-color: #02426D;--ds-auro-icon-size: var(--ds-size-300, 1.5rem)}
17487
- `;var x=i$7`:host{color:var(--ds-auro-icon-color)}:host([customColor]){color:inherit}:host(:not([onDark])[variant=accent1]),:host(:not([appearance=inverse])[variant=accent1]){--ds-auro-icon-color: var(--ds-basic-color-texticon-accent1, #265688)}:host(:not([onDark])[variant=disabled]),:host(:not([appearance=inverse])[variant=disabled]){--ds-auro-icon-color: var(--ds-basic-color-texticon-disabled, #d0d0d0)}:host(:not([onDark])[variant=muted]),:host(:not([appearance=inverse])[variant=muted]){--ds-auro-icon-color: var(--ds-basic-color-texticon-muted, #676767)}:host(:not([onDark])[variant=statusDefault]),:host(:not([appearance=inverse])[variant=statusDefault]){--ds-auro-icon-color: var(--ds-basic-color-status-default, #afb9c6)}:host(:not([onDark])[variant=statusInfo]),:host(:not([appearance=inverse])[variant=statusInfo]){--ds-auro-icon-color: var(--ds-basic-color-status-info, #01426a)}:host(:not([onDark])[variant=statusSuccess]),:host(:not([appearance=inverse])[variant=statusSuccess]){--ds-auro-icon-color: var(--ds-basic-color-status-success, #447a1f)}:host(:not([onDark])[variant=statusWarning]),:host(:not([appearance=inverse])[variant=statusWarning]){--ds-auro-icon-color: var(--ds-basic-color-status-warning, #fac200)}:host(:not([onDark])[variant=statusError]),:host(:not([appearance=inverse])[variant=statusError]){--ds-auro-icon-color: var(--ds-basic-color-status-error, #e31f26)}:host(:not([onDark])[variant=statusInfoSubtle]),:host(:not([appearance=inverse])[variant=statusInfoSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-info-subtle, #ebf3f9)}:host(:not([onDark])[variant=statusSuccessSubtle]),:host(:not([appearance=inverse])[variant=statusSuccessSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-success-subtle, #d6eac7)}:host(:not([onDark])[variant=statusWarningSubtle]),:host(:not([appearance=inverse])[variant=statusWarningSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-warning-subtle, #fff0b2)}:host(:not([onDark])[variant=statusErrorSubtle]),:host(:not([appearance=inverse])[variant=statusErrorSubtle]){--ds-auro-icon-color: var(--ds-basic-color-status-error-subtle, #fbc6c6)}:host(:not([onDark])[variant=fareBasicEconomy]),:host(:not([appearance=inverse])[variant=fareBasicEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-basiceconomy, #97eaf8)}:host(:not([onDark])[variant=fareBusiness]),:host(:not([appearance=inverse])[variant=fareBusiness]){--ds-auro-icon-color: var(--ds-basic-color-fare-business, #01426a)}:host(:not([onDark])[variant=fareEconomy]),:host(:not([appearance=inverse])[variant=fareEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-economy, #0074ca)}:host(:not([onDark])[variant=fareFirst]),:host(:not([appearance=inverse])[variant=fareFirst]){--ds-auro-icon-color: var(--ds-basic-color-fare-first, #00274a)}:host(:not([onDark])[variant=farePremiumEconomy]),:host(:not([appearance=inverse])[variant=farePremiumEconomy]){--ds-auro-icon-color: var(--ds-basic-color-fare-premiumeconomy, #005154)}:host(:not([onDark])[variant=tierOneWorldEmerald]),:host(:not([appearance=inverse])[variant=tierOneWorldEmerald]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-emerald, #139142)}:host(:not([onDark])[variant=tierOneWorldSapphire]),:host(:not([appearance=inverse])[variant=tierOneWorldSapphire]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-sapphire, #015daa)}:host(:not([onDark])[variant=tierOneWorldRuby]),:host(:not([appearance=inverse])[variant=tierOneWorldRuby]){--ds-auro-icon-color: var(--ds-basic-color-tier-program-oneworld-ruby, #a41d4a)}:host([onDark]),:host([appearance=inverse]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse, #ffffff)}:host([onDark][variant=disabled]),:host([appearance=inverse][variant=disabled]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse-disabled, #7e8894)}:host([onDark][variant=muted]),:host([appearance=inverse][variant=muted]){--ds-auro-icon-color: var(--ds-basic-color-texticon-inverse-muted, #ccd2db)}:host([onDark][variant=statusError]),:host([appearance=inverse][variant=statusError]){--ds-auro-icon-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8)}
17488
- `;class _ extends z{constructor(){super(),this._initializeDefaults();}_initializeDefaults(){this.variant=void 0,this.uri="https://cdn.jsdelivr.net/npm/@alaskaairux/icons@latest/dist",this.runtimeUtils=new p;}static get properties(){return {...z.properties,ariaHidden:{type:String,reflect:true},category:{type:String,reflect:true},customColor:{type:Boolean,reflect:true},customSvg:{type:Boolean},label:{type:Boolean,reflect:true},name:{type:String,reflect:true},variant:{type:String,reflect:true}}}static get styles(){return [z.styles,y,w,x]}static register(t="auro-icon"){p.prototype.registerComponent(t,_);}connectedCallback(){super.connectedCallback(),this.runtimeUtils.handleComponentTagRename(this,"auro-icon");}exposeCssParts(){this.setAttribute("exportparts","svg:iconSvg");}async firstUpdated(){if(await super.firstUpdated(),this.hasAttribute("ariaHidden")&&this.svg){const t=this.svg.querySelector("desc");t&&(t.remove(),this.svg.removeAttribute("aria-labelledby"));}}render(){const t={labelWrapper:true,util_displayHiddenVisually:!this.label};return b$2`
17489
- <div class="componentWrapper">
17490
- <div
17491
- class="${e$3({svgWrapper:true})}"
17492
- title="${o(this.title||void 0)}">
17493
- <span aria-hidden="${o(this.ariaHidden||true)}" part="svg">
17494
- ${this.customSvg?b$2`
17495
- <slot name="svg"></slot>
17496
- `:b$2`
17497
- ${this.svg}
17498
- `}
17499
- </span>
17500
- </div>
17501
-
17502
- <div class="${e$3(t)}" part="label">
17503
- <slot></slot>
17504
- </div>
17505
- </div>
17506
- `}}
17507
-
17508
- var iconVersion = '9.1.2';
17509
-
17510
- var checkmarkIcon = {"svg":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" aria-labelledby=\"checkmark-sm__desc\" class=\"ico_squareLarge\" role=\"img\" style=\"min-width:var(--auro-size-lg, var(--ds-size-300, 1.5rem));height:var(--auro-size-lg, var(--ds-size-300, 1.5rem));fill:currentColor\" viewBox=\"0 0 24 24\" part=\"svg\"><title/><desc id=\"checkmark-sm__desc\">a small check mark.</desc><path d=\"M8.461 11.84a.625.625 0 1 0-.922.844l2.504 2.738c.247.27.674.27.922 0l5.496-6a.625.625 0 1 0-.922-.844l-5.035 5.496z\"/></svg>"};
17367
+ const MenuContext = n('menu-context');
17511
17368
 
17512
- // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
17369
+ /* eslint-disable no-underscore-dangle */
17370
+ // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
17513
17371
  // See LICENSE in the project root for license information.
17514
17372
 
17515
17373
 
17516
- let menuOptionIdCounter = 0;
17517
17374
 
17518
17375
  /**
17519
- * The `auro-menuoption` element provides users a way to define a menu option.
17520
- * @customElement auro-menuoption
17521
- *
17522
- * @slot default - The default slot for the menu option text.
17376
+ * The `auro-menu` element provides users a way to select from a list of options.
17377
+ * @customElement auro-menu
17523
17378
  *
17524
- * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
17379
+ * @event {CustomEvent<Element>} auroMenu-activatedOption - Notifies that a menuoption has been made `active`.
17380
+ * @event {CustomEvent<any>} auroMenu-customEventFired - Notifies that a custom event has been fired.
17381
+ * @event {CustomEvent<{ loading: boolean; hasLoadingPlaceholder: boolean; }>} auroMenu-loadingChange - Notifies when the loading attribute is changed.
17382
+ * @event {CustomEvent<any>} auroMenu-selectValueFailure - Notifies that an attempt to select a menuoption by matching a value has failed.
17383
+ * @event {CustomEvent<{ values: HTMLElement[] }>} auroMenu-deselectPrevented - Notifies that deselection was prevented and includes the affected options in `detail.values`.
17384
+ * @event {CustomEvent<any>} auroMenu-selectValueReset - Notifies that the component value has been reset.
17385
+ * @event {CustomEvent<any>} auroMenu-selectedOption - Notifies that a new menuoption selection has been made.
17386
+ * @slot loadingText - Text to show while loading attribute is set
17387
+ * @slot loadingIcon - Icon to show while loading attribute is set
17388
+ * @slot - Slot for insertion of menu options.
17525
17389
  */
17526
- class AuroMenuOption extends AuroElement {
17527
17390
 
17528
- /**
17529
- * This will register this element with the browser.
17530
- * @param {string} [name="auro-menuoption"] - The name of the element that you want to register.
17531
- *
17532
- * @example
17533
- * AuroMenuOption.register("custom-menuoption") // this will register this element to <custom-menuoption/>
17534
- *
17535
- */
17536
- static register(name = "auro-menuoption") {
17537
- AuroLibraryRuntimeUtils$4.prototype.registerComponent(name, AuroMenuOption);
17538
- }
17391
+ /* eslint-disable max-lines */
17539
17392
 
17540
- /**
17541
- * Returns whether the menu option is currently active and selectable.
17542
- * An option is considered active if it is not hidden, not disabled, and not static.
17543
- * @returns {boolean} True if the option is active, false otherwise.
17544
- */
17545
- get isActive() {
17546
- return !this.hasAttribute('hidden') &&
17547
- !this.disabled &&
17548
- !this.hasAttribute('static');
17549
- }
17393
+ class AuroMenu extends AuroElement {
17550
17394
 
17551
17395
  constructor() {
17552
17396
  super();
17553
17397
 
17554
- this.bindEvents();
17398
+ // State properties (reactive)
17555
17399
 
17556
17400
  /**
17557
17401
  * @private
17558
17402
  */
17559
- this.shape = undefined;
17403
+ this.shape = "box";
17560
17404
 
17561
17405
  /**
17562
17406
  * @private
17563
17407
  */
17564
- this.size = undefined;
17565
-
17566
- /**
17567
- * Generate unique names for dependency components.
17568
- */
17569
- const versioning = new AuroDependencyVersioning$3();
17570
- this.iconTag = versioning.generateTag('auro-formkit-menuoption-icon', iconVersion, _);
17408
+ this.size = "sm";
17571
17409
 
17572
- this.selected = false;
17410
+ // Value of the selected options
17411
+ this.value = undefined;
17412
+ // Currently selected option
17413
+ this.optionSelected = undefined;
17414
+ // String used for highlighting/filtering
17415
+ this.matchWord = undefined;
17416
+ // Hide the checkmark icon on selected options
17573
17417
  this.noCheckmark = false;
17574
- this.disabled = false;
17575
- this.noMatch = false;
17418
+ // Currently active option
17419
+ this.optionActive = undefined;
17420
+ // Loading state
17421
+ this.loading = false;
17422
+ // Multi-select mode
17423
+ this.multiSelect = false;
17424
+ // Allow deselecting of menu options
17425
+ this.allowDeselect = false;
17426
+ // Select all matching options when setting value in multi-select mode
17427
+ this.selectAllMatchingOptions = false;
17428
+
17429
+ // Event Bindings
17576
17430
 
17577
17431
  /**
17578
17432
  * @private
17579
17433
  */
17580
- this.runtimeUtils = new AuroLibraryRuntimeUtils$4();
17434
+ this.handleSlotChange = this.handleSlotChange.bind(this);
17581
17435
 
17582
- // Initialize context-related properties
17583
- this.menuService = null;
17584
- this.unsubscribe = null;
17436
+ // Instance properties (non-reactive)
17585
17437
 
17586
17438
  /**
17587
17439
  * @private
17588
17440
  */
17589
- this.handleMenuChange = this.handleMenuChange.bind(this);
17441
+ Object.assign(this, {
17442
+ // Root-level menu (true) or a nested submenu (false)
17443
+ rootMenu: true,
17444
+ // Currently focused/active menu item index
17445
+ _index: -1,
17446
+ // Nested menu spacer
17447
+ nestingSpacer: '<span class="nestingSpacer"></span>',
17448
+ // Loading indicator for slot elements
17449
+ loadingSlots: null,
17450
+ });
17590
17451
  }
17591
17452
 
17592
17453
  static get properties() {
@@ -17594,7 +17455,15 @@ class AuroMenuOption extends AuroElement {
17594
17455
  ...super.properties,
17595
17456
 
17596
17457
  /**
17597
- * When true, disables the menu option.
17458
+ * Allows deselecting an already selected option when clicked again in single-select mode.
17459
+ */
17460
+ allowDeselect: {
17461
+ type: Boolean,
17462
+ reflect: true,
17463
+ },
17464
+
17465
+ /**
17466
+ * When true, the entire menu and all options are disabled.
17598
17467
  */
17599
17468
  disabled: {
17600
17469
  type: Boolean,
@@ -17602,11 +17471,10 @@ class AuroMenuOption extends AuroElement {
17602
17471
  },
17603
17472
 
17604
17473
  /**
17605
- * @private
17474
+ * Indicates whether the menu has a loadingIcon or loadingText to render when in a loading state.
17606
17475
  */
17607
- event: {
17608
- type: String,
17609
- reflect: true
17476
+ hasLoadingPlaceholder: {
17477
+ type: Boolean
17610
17478
  },
17611
17479
 
17612
17480
  /**
@@ -17617,394 +17485,528 @@ class AuroMenuOption extends AuroElement {
17617
17485
  },
17618
17486
 
17619
17487
  /**
17620
- * Allows users to set a unique key for the menu option for specified option selection. If no key is provided, the value property will be used.
17488
+ * Indent level for submenus.
17489
+ * @private
17621
17490
  */
17622
- key: {
17623
- type: String,
17624
- reflect: true
17491
+ level: {
17492
+ type: Number,
17493
+ reflect: false,
17494
+ attribute: false
17625
17495
  },
17626
17496
 
17627
17497
  /**
17628
- * @private
17498
+ * When true, displays a loading state using the loadingIcon and loadingText slots if provided.
17629
17499
  */
17630
- menuService: {
17631
- type: Object,
17632
- state: true
17500
+ loading: {
17501
+ type: Boolean,
17502
+ reflect: true
17633
17503
  },
17634
17504
 
17635
17505
  /**
17636
- * @private
17506
+ * Specifies a string used to highlight matched string parts in options.
17637
17507
  */
17638
17508
  matchWord: {
17639
17509
  type: String,
17640
- state: true
17510
+ attribute: 'matchword'
17641
17511
  },
17642
17512
 
17643
17513
  /**
17644
- * @private
17514
+ * When true, the selected option can be multiple options.
17645
17515
  */
17646
- noCheckmark: {
17516
+ multiSelect: {
17647
17517
  type: Boolean,
17648
- reflect: true
17518
+ reflect: true,
17519
+ attribute: 'multiselect'
17649
17520
  },
17650
17521
 
17651
17522
  /**
17652
- * When true, marks this option as the "no matching results" placeholder shown by combobox when the user's input does not match any available options. Enables distinct styling and prevents the option from being treated as a selectable match.
17523
+ * When true, selected option will not show the checkmark.
17653
17524
  */
17654
- noMatch: {
17525
+ noCheckmark: {
17655
17526
  type: Boolean,
17656
17527
  reflect: true,
17657
- attribute: 'nomatch'
17528
+ attribute: 'nocheckmark'
17658
17529
  },
17659
17530
 
17660
17531
  /**
17661
- * Specifies that an option is selected.
17532
+ * Specifies the current active menuOption.
17662
17533
  */
17663
- selected: {
17664
- type: Boolean,
17665
- reflect: true
17534
+ optionActive: {
17535
+ type: Object,
17536
+ attribute: 'optionactive'
17666
17537
  },
17667
17538
 
17668
17539
  /**
17669
- * Specifies the tab index of the menu option.
17540
+ * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
17670
17541
  */
17671
- tabIndex: {
17672
- type: Number,
17542
+ optionSelected: {
17543
+ // Allow HTMLElement, HTMLElement[] arrays and undefined
17544
+ type: Object
17545
+ },
17546
+
17547
+ /**
17548
+ * Available menu options.
17549
+ * @readonly
17550
+ */
17551
+ options: {
17552
+ type: Array,
17553
+ reflect: false,
17554
+ attribute: false
17555
+ },
17556
+
17557
+ /**
17558
+ * Sets the size of the menu.
17559
+ * @type {'sm' | 'md'}
17560
+ * @default 'sm'
17561
+ */
17562
+ size: {
17563
+ type: String,
17673
17564
  reflect: true
17674
17565
  },
17675
17566
 
17676
17567
  /**
17677
- * Specifies the value to be sent to a server.
17568
+ * When true, selects all options that match the provided value/key when setting value and multiselect is enabled.
17678
17569
  */
17679
- value: {
17570
+ selectAllMatchingOptions: {
17571
+ type: Boolean,
17572
+ reflect: true,
17573
+ },
17574
+
17575
+ /**
17576
+ * Sets the shape of the menu.
17577
+ * @type {'box' | 'round'}
17578
+ * @default 'box'
17579
+ */
17580
+ shape: {
17680
17581
  type: String,
17681
17582
  reflect: true
17682
17583
  },
17584
+
17585
+ /**
17586
+ * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
17587
+ */
17588
+ value: {
17589
+ type: String,
17590
+ reflect: true,
17591
+ attribute: 'value'
17592
+ }
17683
17593
  };
17684
17594
  }
17685
17595
 
17686
17596
  static get styles() {
17687
17597
  return [
17688
- styleCss,
17689
- colorCss,
17598
+ styleCss$1,
17599
+ colorCss$1,
17690
17600
  tokensCss
17691
17601
  ];
17692
17602
  }
17693
17603
 
17694
- connectedCallback() {
17695
- super.connectedCallback();
17696
-
17697
- // Add the tag name as an attribute if it is different than the component name
17698
- // Add this step soon as this node gets attached to the DOM to avoid racing condition with menu's value setting logic.
17699
- this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
17604
+ /**
17605
+ * @readonly
17606
+ * @returns {string} - Returns the label of the currently selected option(s).
17607
+ */
17608
+ get currentLabel() {
17609
+ return this.menuService.currentLabel;
17610
+ };
17611
+
17612
+ /**
17613
+ * @readonly
17614
+ * @returns {Array<HTMLElement>} - Returns the array of available menu options.
17615
+ * @deprecated Use `options` property instead.
17616
+ */
17617
+ get items() {
17618
+ return this.options;
17619
+ }
17620
+
17621
+ /**
17622
+ * @returns {number} - Returns the index of the currently active option.
17623
+ */
17624
+ get index() {
17625
+ return this._index;
17626
+ }
17627
+
17628
+ /**
17629
+ * @param {number} value - Sets the index of the currently active option.
17630
+ */
17631
+ set index(value) {
17632
+ this.menuService.setHighlightedIndex(value);
17633
+ }
17634
+
17635
+ /**
17636
+ * This will register this element with the browser.
17637
+ * @param {string} [name="auro-menu"] - The name of the element that you want to register.
17638
+ *
17639
+ * @example
17640
+ * AuroMenu.register("custom-menu") // this will register this element to <custom-menu/>
17641
+ *
17642
+ */
17643
+ static register(name = "auro-menu") {
17644
+ AuroLibraryRuntimeUtils$4.prototype.registerComponent(name, AuroMenu);
17645
+ }
17646
+
17647
+ /**
17648
+ * Formatted value based on `multiSelect` state.
17649
+ * Default type is `String`, changing to `Array<String>` when `multiSelect` is true.
17650
+ * @private
17651
+ * @returns {String|Array<String>}
17652
+ */
17653
+ get formattedValue() {
17654
+ return this.menuService.currentValue;
17655
+ }
17656
+
17657
+ /**
17658
+ * Gets the current property values for the menu service.
17659
+ * @private
17660
+ * @returns {Object}
17661
+ */
17662
+ get propertyValues() {
17663
+ return {
17664
+ size: this.size,
17665
+ shape: this.shape,
17666
+ noCheckmark: this.nocheckmark,
17667
+ disabled: this.disabled
17668
+ };
17669
+ }
17670
+
17671
+ /**
17672
+ * Provides the menu context to child components.
17673
+ * Initializes the MenuService and subscribes to menu changes.
17674
+ * @protected
17675
+ */
17676
+ provideContext() {
17677
+ if (this.parentElement && this.parentElement.closest('auro-menu, [auro-menu]')) {
17678
+ this.rootMenu = false;
17679
+ this.menuService = this.parentElement.menuService;
17680
+ this._contextProvider = this.parentElement._contextProvider;
17681
+ return;
17682
+ }
17700
17683
 
17701
- // Set up context consumption in connectedCallback
17702
- this._contextConsumer = new s$1(this, {
17684
+ this.menuService = new MenuService({host: this});
17685
+ this.menuService.setProperties(this.propertyValues);
17686
+ this.menuService.subscribe(this.handleMenuChange.bind(this));
17687
+ this._contextProvider = new i(this, {
17703
17688
  context: MenuContext,
17704
- callback: this.attachTo.bind(this),
17705
- subscribe: true
17689
+ initialValue: this.menuService
17706
17690
  });
17691
+ }
17707
17692
 
17708
- // Establish the key property as early as possible.
17709
- // When a framework (e.g. Svelte) inserts the element into the DOM before
17710
- // setting its `value` property, both `getAttribute('value')` and
17711
- // `getAttribute('key')` return null here. Setting `this.key = null`
17712
- // would block the fallback in `updated()` that assigns key from the
17713
- // value property (the guard checked `=== undefined`). Only assign key
17714
- // if at least one source attribute is actually present so that the
17715
- // `updated()` fallback can run when the value property arrives later.
17716
- const valueAttr = this.getAttribute('value');
17717
- const keyAttr = this.getAttribute('key');
17718
- const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
17719
- if (resolvedKey !== null) {
17720
- this.key = resolvedKey;
17721
- }
17693
+ /**
17694
+ * Updates the currently active option in the menu.
17695
+ * @param {HTMLElement} option - The option to set as active.
17696
+ */
17697
+ updateActiveOption(option) {
17698
+ this.menuService.setHighlightedOption(option);
17722
17699
  }
17723
17700
 
17724
- firstUpdated() {
17725
- // Add the tag name as an attribute if it is different than the component name
17726
- this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
17701
+ /**
17702
+ * Sets the internal value and manages update state.
17703
+ * @param {String|Array<String>} value - The value to set.
17704
+ * @protected
17705
+ */
17706
+ setInternalValue(value) {
17707
+ if (this.value !== value) {
17708
+ this.internalUpdateInProgress = true;
17709
+ this.value = value;
17727
17710
 
17728
- // Generate unique ID if not already set (required for aria-activedescendant)
17729
- if (!this.id) {
17730
- menuOptionIdCounter += 1;
17731
- this.id = `menuoption-${menuOptionIdCounter}`;
17711
+ setTimeout(() => {
17712
+ this.internalUpdateInProgress = false;
17713
+ });
17732
17714
  }
17733
-
17734
- this.setAttribute('role', 'option');
17735
- this.setAttribute('aria-selected', 'false');
17736
-
17737
- this.addEventListener('mouseover', () => {
17738
- this.dispatchEvent(new CustomEvent('auroMenuOption-mouseover', {
17739
- bubbles: true,
17740
- cancelable: false,
17741
- composed: true,
17742
- detail: this
17743
- }));
17744
- });
17745
17715
  }
17746
17716
 
17747
- updated(changedProperties) {
17748
- super.updated(changedProperties);
17749
-
17750
- // Update aria-selected attribute if selected changed
17751
- if (changedProperties.has('selected')) {
17752
-
17753
- // Update aria-selected attribute
17754
- this.setAttribute('aria-selected', this.selected.toString());
17717
+ /**
17718
+ * Handles changes from the menu service and updates component state.
17719
+ * @param {Object} event - The event object from the menu service.
17720
+ * @protected
17721
+ */
17722
+ handleMenuChange(event) {
17723
+ if (event.type === 'valueChange') {
17755
17724
 
17756
- // Update menu service selection state if this isn't an internal update
17757
- if (this.internalUpdateInProgress !== true) {
17758
- this.menuService[this.selected ? 'selectOption' : 'deselectOption'](this);
17759
- }
17760
- }
17725
+ // New option is array value or first option with fallback to undefined for empty array in all cases
17726
+ const newOption = this.multiSelect && event.options.length ? event.options : event.options[0] || undefined;
17727
+ const newValue = event.stringValue;
17761
17728
 
17762
- if (changedProperties.has('disabled')) {
17763
- if (this.disabled) {
17764
- this.setAttribute('aria-disabled', 'true');
17765
- } else {
17766
- this.removeAttribute('aria-disabled');
17729
+ // Check if the option or value has actually changed
17730
+ if (this.optionSelected !== newOption || this.stringValue !== newValue) {
17731
+ this.optionSelected = newOption;
17732
+ this.setInternalValue(newValue);
17767
17733
  }
17768
- }
17769
-
17770
- if (changedProperties.has('active')) {
17771
- this.updateActiveClasses();
17772
- }
17773
17734
 
17774
- // Update text highlight if matchWord changed
17775
- if (changedProperties.has('matchWord')) {
17776
- this.updateTextHighlight();
17735
+ // Notify components of selection change
17736
+ this.notifySelectionChange(event);
17777
17737
  }
17778
17738
 
17779
- // Set the key to be the passed value if no key is provided.
17780
- // Loose equality (== null) is intentional: it catches both null AND
17781
- // undefined. When a framework (e.g. Svelte, React) inserts the element
17782
- // before setting its value property, connectedCallback skips key
17783
- // assignment because both attributes are null at that point. The Lit
17784
- // property default for `key` is undefined (not null), so strict
17785
- // === null would miss the case and the fallback would never run.
17786
- if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
17787
- this.key = this.value;
17739
+ if (event.type === 'highlightChange') {
17740
+ this.optionActive = event.option;
17741
+ this._index = event.index;
17788
17742
  }
17789
- }
17790
17743
 
17791
- disconnectedCallback() {
17792
- if (this.menuService) {
17793
- this.menuService.unsubscribe(this.handleMenuChange);
17794
- this.menuService.removeMenuOption(this);
17744
+ if (event.type === 'optionsChange') {
17745
+ this.options = event.options;
17746
+ this.dispatchEvent(new CustomEvent('auroMenu-optionsChange', {
17747
+ detail: {
17748
+ options: event.options
17749
+ }
17750
+ }));
17795
17751
  }
17796
17752
  }
17797
17753
 
17798
17754
  /**
17799
- * Sets up event listeners for user interaction with the menu option.
17800
- * This function enables click and mouse enter events to trigger selection and highlighting logic.
17755
+ * Gets the currently selected options.
17756
+ * @returns {Array<HTMLElement>}
17801
17757
  */
17802
- bindEvents() {
17803
- this.addEventListener('click', this.handleClick.bind(this));
17804
- this.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
17758
+ get selectedOptions() {
17759
+ return this.menuService ? this.menuService.selectedOptions : [];
17805
17760
  }
17806
17761
 
17807
17762
  /**
17808
- * Attaches this menu option to a menu service and subscribes to its events.
17809
- * This method enables the option to participate in menu selection and highlighting logic.
17810
- * @param {Object} service - The menu service instance to attach to.
17763
+ * Gets the first selected option, or null if none.
17764
+ * @returns {HTMLElement|null}
17811
17765
  */
17812
- attachTo(service) {
17813
- if (!service) {
17814
- return;
17815
- }
17816
- this.menuService = service;
17817
- this.menuService.addMenuOption(this);
17818
- this.menuService.subscribe(this.handleMenuChange);
17766
+ get selectedOption() {
17767
+ return this.menuService ? this.menuService.selectedOptions[0] : null;
17819
17768
  }
17820
17769
 
17821
- /**
17822
- * Handles changes from the menu service and updates the option's state.
17823
- * This function synchronizes the option's properties and selection/highlight state with menu events.
17824
- * @param {Object} event - The event object from the menu service.
17825
- */
17826
- handleMenuChange(event) {
17770
+ // Lifecycle Methods
17827
17771
 
17828
- // Ignore events without a type or property
17829
- if (!event || (!event.type && !event.property)) {
17830
- return;
17772
+ connectedCallback() {
17773
+ super.connectedCallback();
17774
+
17775
+ this.provideContext();
17776
+
17777
+ // this.addEventListener('keydown', this.handleKeyDown);
17778
+ this.addEventListener('auroMenuOption-click', this.handleMouseSelect);
17779
+ this.addEventListener('auroMenuOption-mouseover', this.handleOptionHover);
17780
+ this.addEventListener('slotchange', this.handleSlotChange);
17781
+ this.setTagAttribute("auro-menu");
17782
+ }
17783
+
17784
+ disconnectedCallback() {
17785
+ // this.removeEventListener('keydown', this.handleKeyDown);
17786
+ this.removeEventListener('auroMenuOption-click', this.handleMouseSelect);
17787
+ this.removeEventListener('auroMenuOption-mouseover', this.handleOptionHover);
17788
+ this.removeEventListener('slotchange', this.handleSlotChange);
17789
+
17790
+ super.disconnectedCallback();
17791
+ }
17792
+
17793
+ firstUpdated() {
17794
+ AuroLibraryRuntimeUtils$4.prototype.handleComponentTagRename(this, 'auro-menu');
17795
+
17796
+ this.loadingSlots = this.querySelectorAll("[slot='loadingText'], [slot='loadingIcon']");
17797
+ this.initializeMenu();
17798
+ }
17799
+
17800
+
17801
+ updated(changedProperties) {
17802
+ super.updated(changedProperties);
17803
+
17804
+ // Apply value selection synchronously so that static-HTML fixtures
17805
+ // resolve within a single update cycle. The refactored selectByValue
17806
+ // no longer calls reset() first, so the destructive intermediate-event
17807
+ // cascade that originally required deferral is eliminated. If option
17808
+ // keys are not yet resolved (framework mount-order race), selectByValue
17809
+ // queues a bounded retry automatically via queuePendingValue.
17810
+ if (changedProperties.has('value') && !this.internalUpdateInProgress) {
17811
+ this.menuService.selectByValue(this.value);
17831
17812
  }
17832
17813
 
17833
- // Update reactive properties based on event type
17834
- if (event.property && Object.keys(AuroMenuOption.properties).includes(event.property)) {
17835
- this[event.property] = event.value;
17814
+ // Handle loading state changes
17815
+ if (changedProperties.has('loading')) {
17816
+ this.setLoadingState(this.loading);
17836
17817
  }
17837
17818
 
17838
- // Handle highlight changes
17839
- if (event.type === 'highlightChange') {
17840
- const isActive = event.option === this;
17841
- this.active = isActive;
17842
- this.updateActiveClasses();
17819
+ if (changedProperties.has('multiSelect') && this.rootMenu) {
17820
+ if (this.multiSelect) {
17821
+ this.setAttribute('aria-multiselectable', 'true');
17822
+ } else {
17823
+ this.removeAttribute('aria-multiselectable');
17824
+ }
17843
17825
  }
17826
+ }
17844
17827
 
17845
- if (event.type === 'stateChange') {
17846
- const isSelected = event.selectedOptions.includes(this);
17847
- this.setInternalSelected(isSelected);
17828
+ /**
17829
+ * Sets an attribute that matches the default tag name if the tag name is not the default.
17830
+ * @param {string} tagName - The tag name to set as an attribute.
17831
+ * @private
17832
+ */
17833
+ setTagAttribute(tagName) {
17834
+ if (this.tagName.toLowerCase() !== tagName) {
17835
+ this.setAttribute(tagName, true);
17848
17836
  }
17849
17837
  }
17850
17838
 
17851
17839
  /**
17852
- * Updates the internal selected state of the menu option bypassing 'updated' and triggers custom events if selected.
17853
- * This function ensures the option's selection state is synchronized with menu logic and notifies listeners.
17854
- * @param {boolean} isSelected - Whether the option should be marked as selected.
17840
+ * Sets the loading state and dispatches a loading change event.
17841
+ * @param {boolean} isLoading - Whether the menu is loading.
17842
+ * @protected
17843
+ */
17844
+ setLoadingState(isLoading) {
17845
+ this.setAttribute("aria-busy", isLoading);
17846
+ dispatchMenuEvent(this, "auroMenu-loadingChange", {
17847
+ loading: isLoading,
17848
+ hasLoadingPlaceholder: this.hasLoadingPlaceholder
17849
+ });
17850
+ }
17851
+
17852
+ // Init Methods
17853
+
17854
+ /**
17855
+ * Initializes the menu's state and structure.
17856
+ * @private
17855
17857
  */
17856
- setInternalSelected(isSelected) {
17857
- this.internalUpdateInProgress = true;
17858
- this.selected = isSelected;
17858
+ initializeMenu() {
17859
+ if (this.rootMenu) {
17860
+ this.setAttribute('role', 'listbox');
17861
+ this.setAttribute('root', '');
17859
17862
 
17860
- // Fire custom event if selected
17861
- if (isSelected) {
17862
- this.handleCustomEvent();
17863
+ if (this.multiSelect) {
17864
+ this.setAttribute('aria-multiselectable', 'true');
17865
+ }
17863
17866
  }
17864
17867
 
17865
- setTimeout(() => {
17866
- this.internalUpdateInProgress = false;
17867
- }, 0);
17868
+ this.handleNestedMenus(this);
17868
17869
  }
17869
17870
 
17870
17871
  /**
17871
- * Sets the selected state of the menu option.
17872
- * This function updates whether the option is currently selected.
17873
- * @param {boolean} isSelected - Whether the option should be marked as selected.
17874
- * @deprecated Simply modify the `selected` property directly instead.
17872
+ * Selects the currently highlighted option.
17873
+ * @protected
17875
17874
  */
17876
- setSelected(isSelected) {
17877
- this.selected = isSelected;
17875
+ makeSelection() {
17876
+ this.menuService.selectHighlightedOption();
17878
17877
  }
17879
17878
 
17880
17879
  /**
17881
- * Updates the active state and visual highlighting of the menu option.
17882
- * This function toggles the option's active status and applies or removes the active CSS class.
17883
- * @param {boolean} isActive - Whether the option should be marked as active.
17884
- * @deprecated Simply modify the `active` property directly instead.
17880
+ * Resets all options to their default state.
17881
+ * @private
17885
17882
  */
17886
- updateActive(isActive) {
17887
-
17888
- // Set active state
17889
- this.active = isActive;
17890
- this.updateActiveClasses();
17883
+ clearSelection() {
17884
+ this.optionSelected = undefined;
17885
+ this.value = undefined;
17886
+ this._index = -1;
17891
17887
  }
17892
17888
 
17893
17889
  /**
17894
- * Updates the CSS class for the menu option based on its active state.
17895
- * This function adds or removes the 'active' class to visually indicate the option's active status.
17896
- * @private
17890
+ * Resets the menu to its initial state.
17891
+ * This is the only way to return value to undefined.
17892
+ * @public
17897
17893
  */
17898
- updateActiveClasses() {
17899
- // Update class based on active state
17900
- if (this.active) this.classList.add('active');
17901
- else this.classList.remove('active');
17902
- }
17894
+ reset() {
17895
+ this.menuService.reset();
17903
17896
 
17897
+ // Dispatch reset event
17898
+ dispatchMenuEvent(this, 'auroMenu-selectValueReset');
17899
+ }
17904
17900
 
17905
17901
  /**
17906
- * Updates the visual highlighting of text within the menu option based on the current match word.
17907
- * This function highlights matching text segments and manages nested spacers for display formatting.
17902
+ * Handles nested menu structure.
17908
17903
  * @private
17904
+ * @param {HTMLElement} menu - Root menu element.
17909
17905
  */
17910
- updateTextHighlight() {
17911
-
17912
- // Regex for matchWord if needed
17913
- let regexWord = null;
17914
-
17915
- if (this.matchWord && this.matchWord.length) {
17916
- const escapedWord = this.matchWord.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
17917
- regexWord = new RegExp(escapedWord, 'giu');
17918
- }
17919
-
17920
- // Update text highlighting if matchWord changed
17921
- if (regexWord &&
17922
- this.isActive && !this.hasAttribute('persistent')) {
17923
- const nested = this.querySelectorAll('.nestingSpacer');
17924
-
17925
- const displayValueEl = this.querySelector('[slot="displayValue"]');
17926
- if (displayValueEl) {
17927
- this.removeChild(displayValueEl);
17928
- }
17929
-
17930
- // Create nested spacers
17931
- const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
17906
+ handleNestedMenus(menu) {
17907
+ menu.level = menu.parentElement.level >= 0 ? menu.parentElement.level + 1 : 0;
17932
17908
 
17933
- // Update with spacers and matchWord
17934
- this.innerHTML = nestingSpacerBundle +
17935
- this.textContent.replace(
17936
- regexWord,
17937
- (match) => `<strong>${match}</strong>`
17938
- );
17939
- if (displayValueEl) {
17940
- this.append(displayValueEl);
17909
+ if (menu.level > 0) {
17910
+ menu.setAttribute('role', 'group');
17911
+ menu.removeAttribute("root");
17912
+ if (!menu.hasAttribute('aria-label')) {
17913
+ menu.setAttribute('aria-label', 'submenu');
17941
17914
  }
17942
17915
  }
17916
+
17917
+ const options = menu.querySelectorAll(':scope > auro-menuoption, :scope > [auro-menuoption]');
17918
+ options.forEach((option) => {
17919
+ const regex = new RegExp(this.nestingSpacer, "gu");
17920
+ option.innerHTML = this.nestingSpacer.repeat(menu.level) + option.innerHTML.replace(regex, '');
17921
+ });
17943
17922
  }
17944
17923
 
17945
17924
  /**
17946
- * Handles click events on the menu option, toggling its selected state.
17947
- * This function dispatches a click event and updates selection if the option is not disabled.
17948
- * @private
17925
+ * Navigates the menu options in the specified direction.
17926
+ * @param {'up'|'down'} direction - The direction to navigate.
17927
+ * @protected
17949
17928
  */
17950
- handleClick() {
17951
- if (!this.disabled) {
17952
- this.dispatchClickEvent();
17953
- this.selected = !this.selected;
17929
+ navigateOptions(direction) {
17930
+ if (direction === 'up') {
17931
+ this.menuService.highlightPrevious();
17932
+ } else if (direction === 'down') {
17933
+ this.menuService.highlightNext();
17954
17934
  }
17955
17935
  }
17956
17936
 
17957
17937
  /**
17958
- * Handles mouse enter events to highlight the menu option.
17959
- * This function updates the menu service to set this option as the currently highlighted item if not disabled.
17938
+ * Handles slot change events.
17960
17939
  * @private
17961
17940
  */
17962
- handleMouseEnter() {
17963
- if (!this.disabled) {
17964
- this.menuService.setHighlightedOption(this);
17941
+ handleSlotChange() {
17942
+ if (this.rootMenu) {
17943
+ this.initializeMenu();
17965
17944
  }
17966
17945
  }
17967
17946
 
17968
17947
  /**
17969
- * Dispatches custom events defined for this menu option.
17970
- * This function notifies listeners when a custom event is triggered by the option.
17948
+ * Handles custom events defined on options.
17971
17949
  * @private
17950
+ * @param {HTMLElement} option - Option with custom event.
17972
17951
  */
17973
- handleCustomEvent() {
17974
- if (this.event) {
17975
- dispatchMenuEvent(this, this.event, { option: this });
17976
- dispatchMenuEvent(this, 'auroMenu-customEventFired', { option: this });
17977
- }
17952
+ handleCustomEvent(option) {
17953
+ const eventName = option.getAttribute('event');
17954
+ dispatchMenuEvent(this, eventName);
17955
+ dispatchMenuEvent(this, 'auroMenu-customEventFired');
17978
17956
  }
17979
17957
 
17980
17958
  /**
17981
- * Dispatches a click event for this menu option.
17982
- * This function notifies listeners that the option has been clicked.
17959
+ * Notifies selection change to parent components.
17960
+ * @param {any} source - The source that triggers this event.
17983
17961
  * @private
17984
17962
  */
17985
- dispatchClickEvent() {
17986
- this.dispatchEvent(new CustomEvent('auroMenuOption-click', {
17987
- bubbles: true,
17988
- cancelable: false,
17989
- composed: true,
17990
- detail: this
17991
- }));
17963
+ notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
17964
+ dispatchMenuEvent(this, 'auroMenu-selectedOption', {
17965
+ value,
17966
+ stringValue,
17967
+ keys,
17968
+ options,
17969
+ reason
17970
+ });
17992
17971
  }
17993
17972
 
17994
17973
  /**
17995
- * Generates an HTML element containing an SVG icon based on the provided `svgContent`.
17996
- *
17974
+ * Checks if an option is currently selected.
17997
17975
  * @private
17998
- * @param {string} svgContent - The SVG content to be embedded.
17999
- * @returns {Element} The HTML element containing the SVG icon.
17976
+ * @param {HTMLElement} option - The option to check.
17977
+ * @returns {boolean}
18000
17978
  */
18001
- generateIconHtml(svgContent) {
18002
- const dom = new DOMParser().parseFromString(svgContent, 'text/html');
18003
- const svg = dom.body.firstChild;
17979
+ isOptionSelected(option) {
17980
+ if (!this.optionSelected) {
17981
+ return false;
17982
+ }
18004
17983
 
18005
- svg.setAttribute('slot', 'svg');
17984
+ if (this.multiSelect) {
17985
+ // In multi-select mode, check if the option is in the selected array
17986
+ return Array.isArray(this.optionSelected) && this.optionSelected.some((selectedOption) => selectedOption === option);
17987
+ }
18006
17988
 
18007
- return u$7`<${this.iconTag} customColor customSvg>${svg}</${this.iconTag}>`;
17989
+ return this.optionSelected === option;
17990
+ }
17991
+
17992
+ /**
17993
+ * Getter for loading placeholder state.
17994
+ * @returns {boolean} - True if loading slots are present and non-empty.
17995
+ */
17996
+ get hasLoadingPlaceholder() {
17997
+ return this.loadingSlots && this.loadingSlots.length > 0;
17998
+ }
17999
+
18000
+ /**
18001
+ * Getter for wrapper classes based on size.
18002
+ * @returns {Object} - Class map for the wrapper element.
18003
+ * @private
18004
+ */
18005
+ get wrapperClasses() {
18006
+ return e$3({
18007
+ 'menuWrapper': true,
18008
+ [this.size]: true,
18009
+ });
18008
18010
  }
18009
18011
 
18010
18012
  /**
@@ -18013,26 +18015,26 @@ class AuroMenuOption extends AuroElement {
18013
18015
  * @returns {void}
18014
18016
  */
18015
18017
  renderLayout() {
18018
+ if (this.loading) {
18019
+ return b$2`
18020
+ <div class="${this.wrapperClasses}">
18021
+ <auro-menuoption
18022
+ disabled
18023
+ loadingplaceholder
18024
+ class="${this.hasLoadingPlaceholder ? "" : "empty"}"
18025
+ >
18026
+ <div>
18027
+ <slot name="loadingIcon" class="body-lg"></slot>
18028
+ <slot name="loadingText"></slot>
18029
+ </div>
18030
+ </auro-menuoption>
18031
+ </div>
18032
+ `;
18033
+ }
18016
18034
 
18017
- const fontClassMap = {
18018
- xs: 'body-sm',
18019
- sm: 'body-default',
18020
- md: 'body-default',
18021
- lg: 'body-lg',
18022
- xl: 'body-lg'
18023
- };
18024
-
18025
- const classes = e$3({
18026
- 'wrapper': true,
18027
- [this.size ? fontClassMap[this.size] : 'body-sm']: true,
18028
- });
18029
-
18030
- return u$7`
18031
- <div class="${classes}">
18032
- ${this.selected && !this.noCheckmark
18033
- ? this.generateIconHtml(checkmarkIcon.svg)
18034
- : undefined}
18035
- <slot></slot>
18035
+ return b$2`
18036
+ <div class="${this.wrapperClasses}">
18037
+ <slot @slotchange=${this.handleSlotChange}></slot>
18036
18038
  </div>
18037
18039
  `;
18038
18040
  }