@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
@@ -167,1674 +167,1533 @@ let s$1 = class s{get value(){return this.o}set value(s){this.setValue(s);}setVa
167
167
  * SPDX-License-Identifier: BSD-3-Clause
168
168
  */let e$2 = class e extends Event{constructor(t,s){super("context-provider",{bubbles:true,composed:true}),this.context=t,this.contextTarget=s;}};let i$2 = class i extends s$1{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$3(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$2(this.context,this.host));}};
169
169
 
170
- /* eslint-disable */
170
+ /**
171
+ * @license
172
+ * Copyright 2020 Google LLC
173
+ * SPDX-License-Identifier: BSD-3-Clause
174
+ */
175
+ const a=Symbol.for(""),o$1=t=>{if(t?.r===a)return t?._$litStatic$},s=t=>({_$litStatic$:t,r:a}),i$1=(t,...r)=>({_$litStatic$:r.reduce((r,e,a)=>r+(t=>{if(void 0!==t._$litStatic$)return t._$litStatic$;throw Error(`Value passed to 'literal' function must be a 'literal' result: ${t}. Use 'unsafeStatic' to pass non-literal values, but\n take care to ensure page security.`)})(e)+t[a+1],t[0]),r:a}),l=new Map,n=t=>(r,...e)=>{const a=e.length;let s,i;const n=[],u=[];let c,$=0,f=false;for(;$<a;){for(c=r[$];$<a&&void 0!==(i=e[$],s=o$1(i));)c+=s+r[++$],f=true;$!==a&&u.push(i),n.push(c),$++;}if($===a&&n.push(r[a]),f){const t=n.join("$$lit$$");void 0===(r=l.get(t))&&(n.raw=n,l.set(t,r=n)),e=u;}return t(r,...e)},u$1=n(b);
171
176
 
172
- class MenuService {
177
+ var styleCss = i$6`.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}`;
173
178
 
174
- /**
175
- * PROPERTIES AND GETTERS
176
- */
179
+ var colorCss = i$6`: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)}}`;
177
180
 
178
- /**
179
- * Gets the list of registered menu options.
180
- * @returns {AuroMenuOption[]}
181
- */
182
- get menuOptions() {
183
- return this._menuOptions;
184
- }
181
+ // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
182
+ // See LICENSE in the project root for license information.
185
183
 
186
- /**
187
- * Gets the currently highlighted option.
188
- * @returns {AuroMenuOption|null}
189
- */
190
- get highlightedOption() {
191
- return this._menuOptions[this.highlightedIndex] || null;
192
- }
184
+ // ---------------------------------------------------------------------
185
+
186
+ /* eslint-disable line-comment-position, no-inline-comments, no-confusing-arrow, no-nested-ternary, implicit-arrow-linebreak */
187
+
188
+ class AuroLibraryRuntimeUtils {
189
+
190
+ /* eslint-disable jsdoc/require-param */
193
191
 
194
192
  /**
195
- * Gets the current value(s) of the selected option(s).
196
- * @returns {string|string[]|undefined}
193
+ * This will register a new custom element with the browser.
194
+ * @param {String} name - The name of the custom element.
195
+ * @param {Object} componentClass - The class to register as a custom element.
196
+ * @returns {void}
197
197
  */
198
- get currentValue() {
199
- const values = (this.selectedOptions || []).map(option => option.value);
200
- return this.multiSelect ? values : values[0];
198
+ registerComponent(name, componentClass) {
199
+ if (!customElements.get(name)) {
200
+ customElements.define(name, class extends componentClass {});
201
+ }
201
202
  }
202
203
 
203
204
  /**
204
- * Gets the label(s) of the currently selected option(s).
205
- * @returns {string}
205
+ * Finds and returns the closest HTML Element based on a selector.
206
+ * @returns {void}
206
207
  */
207
- get currentLabel() {
208
- const labels = (this.selectedOptions || []).map(option => option.textContent);
209
- return this.multiSelect ? labels.join(", ") : labels[0] || '';
208
+ closestElement(
209
+ selector, // selector like in .closest()
210
+ base = this, // extra functionality to skip a parent
211
+ __Closest = (el, found = el && el.closest(selector)) =>
212
+ !el || el === document || el === window
213
+ ? null // standard .closest() returns null for non-found selectors also
214
+ : found
215
+ ? found // found a selector INside this element
216
+ : __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
217
+ ) {
218
+ return __Closest(base);
210
219
  }
220
+ /* eslint-enable jsdoc/require-param */
211
221
 
212
222
  /**
213
- * Gets the string representation of the current value(s).
214
- * For multi-select, this is a JSON stringified array.
215
- * @returns {string|undefined}
223
+ * If the element passed is registered with a different tag name than what is passed in, the tag name is added as an attribute to the element.
224
+ * @param {Object} elem - The element to check.
225
+ * @param {String} tagName - The name of the Auro component to check for or add as an attribute.
226
+ * @returns {void}
216
227
  */
217
- get stringValue() {
218
- const { currentValue } = this;
219
-
220
- if (Array.isArray(currentValue)) {
221
- if (currentValue.length > 0) {
222
- return JSON.stringify(currentValue);
223
- }
224
- return undefined;
225
- }
228
+ handleComponentTagRename(elem, tagName) {
229
+ const tag = tagName.toLowerCase();
230
+ const elemTag = elem.tagName.toLowerCase();
226
231
 
227
- if (typeof currentValue === 'string') {
228
- if (currentValue.length > 0) {
229
- return currentValue;
230
- }
231
- return undefined;
232
+ if (elemTag !== tag) {
233
+ elem.setAttribute(tag, true);
232
234
  }
233
-
234
- // Future: handle other types here (e.g., number, object, etc.)
235
- return undefined;
236
235
  }
237
236
 
238
237
  /**
239
- * Gets the key(s) of the currently selected option(s).
240
- * @returns {string|string[]|undefined}
238
+ * Validates if an element is a specific Auro component.
239
+ * @param {Object} elem - The element to validate.
240
+ * @param {String} tagName - The name of the Auro component to check against.
241
+ * @returns {Boolean} - Returns true if the element is the specified Auro component.
241
242
  */
242
- get currentKeys() {
243
- const keys = (this.selectedOptions || []).map(option => option.key);
244
- return this.multiSelect ? keys : keys[0];
245
- }
243
+ elementMatch(elem, tagName) {
244
+ const tag = tagName.toLowerCase();
245
+ const elemTag = elem.tagName.toLowerCase();
246
246
 
247
- /**
248
- * CONSTRUCTOR
249
- */
247
+ return elemTag === tag || elem.hasAttribute(tag);
248
+ }
250
249
 
251
250
  /**
252
- * Creates a new MenuService instance.
253
- * @param {Object} options - The options object.
254
- * @param {AuroMenu} options.host - The host element that this service will control. Required.
255
- * @throws {Error} If the host is not provided.
251
+ * Gets the text content of a named slot.
252
+ * @returns {String}
253
+ * @private
256
254
  */
257
- constructor({ host } = {}) {
258
-
259
- // Ensure a host was passed
260
- if (!host) {
261
- throw new Error("MenuService requires a host element.");
262
- }
263
-
264
- // Attach the service to the host
265
- this.host = host;
266
- this.host.addController(this);
255
+ getSlotText(elem, name) {
256
+ const slot = elem.shadowRoot?.querySelector(`slot[name="${name}"]`);
257
+ const nodes = slot?.assignedNodes({ flatten: true }) || [];
258
+ const text = nodes.map(n => n.textContent?.trim()).join(' ').trim();
267
259
 
268
- // Set default properties
269
- this.size = undefined;
270
- this.shape = undefined;
271
- this.noCheckmark = undefined;
272
- this.disabled = undefined;
273
- this.matchWord = undefined;
274
- this.multiSelect = undefined;
275
- this.allowDeselect = undefined;
276
- this.selectAllMatchingOptions = undefined;
260
+ return text || null;
261
+ }
262
+ }
277
263
 
278
- this.highlightedIndex = -1;
264
+ // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
265
+ // See LICENSE in the project root for license information.
279
266
 
280
- this._menuOptions = [];
281
- this._subscribers = [];
282
- this.internalUpdateInProgress = false;
283
- this.selectedOptions = [];
284
- this._pendingValue = null;
285
- this._pendingRetryScheduled = false;
286
- this._pendingRetryCount = 0;
287
- }
288
267
 
289
- /**
290
- * PROPERTY SYNCING
291
- */
268
+ class AuroDependencyVersioning {
292
269
 
293
270
  /**
294
- * Handles host updates.
295
- * This is a lit reactive lifecycle method.
296
- * This comes from the Lit controller interface provided by adding this service as a controller to the host.
297
- * See constructor for `this.host.addController(this)`
298
- * You can read more about Lit reactive controllers here: https://lit.dev/docs/composition/controllers/
271
+ * Generates a unique string to be used for child auro element naming.
272
+ * @private
273
+ * @param {string} baseName - Defines the first part of the unique element name.
274
+ * @param {string} version - Version of the component that will be appended to the baseName.
275
+ * @returns {string} - Unique string to be used for naming.
299
276
  */
300
- hostUpdated() {
301
-
302
- // Reset selection if multiSelect mode changes
303
- if (this.host.multiSelect !== this.multiSelect) {
304
- this.selectedOptions = [];
305
- }
277
+ generateElementName(baseName, version) {
278
+ let result = baseName;
306
279
 
307
- // Update properties on host update
308
- this.setProperties({
309
- size: this.host.size,
310
- shape: this.host.shape,
311
- noCheckmark: this.host.noCheckmark,
312
- disabled: this.host.disabled,
313
- matchWord: this.host.matchWord,
314
- multiSelect: this.host.multiSelect,
315
- allowDeselect: this.host.allowDeselect,
316
- selectAllMatchingOptions: this.host.selectAllMatchingOptions
317
- });
318
- }
280
+ result += '-';
281
+ result += version.replace(/[.]/g, '_');
319
282
 
320
- /**
321
- * Handles host disconnection and memory cleanup.
322
- */
323
- hostDisconnected() {
324
- this._subscribers = [];
325
- this._menuOptions = [];
326
- this._pendingValue = null;
327
- this._pendingRetryScheduled = false;
328
- this._pendingRetryCount = 0;
283
+ return result;
329
284
  }
330
285
 
331
286
  /**
332
- * Sets a property value if it exists on the instance and the value has changed.
333
- * @param {string} property
334
- * @param {any} value
287
+ * Generates a unique string to be used for child auro element naming.
288
+ * @param {string} baseName - Defines the first part of the unique element name.
289
+ * @param {string} version - Version of the component that will be appended to the baseName.
290
+ * @returns {string} - Unique string to be used for naming.
335
291
  */
336
- setProperty(property, value) {
337
-
338
- // Only update if we are tracking the property in this service
339
- if (this.hasOwnProperty(property)) {
340
-
341
- // Check if the value has changed
342
- const valueChanged = this[property] !== value;
292
+ generateTag(baseName, version, tagClass) {
293
+ const elementName = this.generateElementName(baseName, version);
294
+ const tag = i$1`${s(elementName)}`;
343
295
 
344
- // Update and notify if changed
345
- if (valueChanged) {
346
- this[property] = value;
347
- this.notify({ property, value });
348
- }
296
+ if (!customElements.get(elementName)) {
297
+ customElements.define(elementName, class extends tagClass {});
349
298
  }
350
- }
351
299
 
352
- /**
353
- * Sets multiple properties on the instance.
354
- * @param {Object} properties - Key-value pairs of properties to set.
355
- */
356
- setProperties(properties) {
357
- for (const [key, value] of Object.entries(properties)) {
358
- this.setProperty(key, value);
359
- }
300
+ return tag;
360
301
  }
302
+ }
361
303
 
362
- /**
363
- * MENU OPTION HIGHLIGHTING
364
- */
304
+ /**
305
+ * @license
306
+ * Copyright 2017 Google LLC
307
+ * SPDX-License-Identifier: BSD-3-Clause
308
+ */
309
+ const t={ATTRIBUTE:1},e$1=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}
365
310
 
366
- /**
367
- * Highlights the next active option in the menu.
368
- */
369
- highlightNext() {
370
- this.moveHighlightedOption("next");
371
- }
311
+ /**
312
+ * @license
313
+ * Copyright 2018 Google LLC
314
+ * SPDX-License-Identifier: BSD-3-Clause
315
+ */const e=e$1(class extends i{constructor(t$1){if(super(t$1),t$1.type!==t.ATTRIBUTE||"class"!==t$1.name||t$1.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return " "+Object.keys(t).filter(s=>t[s]).join(" ")+" "}update(s,[i]){if(void 0===this.st){this.st=new Set,void 0!==s.strings&&(this.nt=new Set(s.strings.join(" ").split(/\s/).filter(t=>""!==t)));for(const t in i)i[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.st)t in i||(r.remove(t),this.st.delete(t));for(const t in i){const s=!!i[t];s===this.st.has(t)||this.nt?.has(t)||(s?(r.add(t),this.st.add(t)):(r.remove(t),this.st.delete(t)));}return E}});
372
316
 
373
- /**
374
- * Highlights the previous active option in the menu.
375
- */
376
- highlightPrevious() {
377
- this.moveHighlightedOption("previous");
378
- }
317
+ /**
318
+ * @license
319
+ * Copyright 2018 Google LLC
320
+ * SPDX-License-Identifier: BSD-3-Clause
321
+ */const o=o=>o??A;
379
322
 
380
- /**
381
- * Moves the highlighted option in the specified direction.
382
- * @param {string} direction - The direction to move the highlight ("next" or "previous").
383
- */
384
- moveHighlightedOption(direction) {
323
+ 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$3{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$6`: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}
324
+ `;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$6`.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}
325
+ `;var y=i$6`: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)}
326
+ `;var x=i$6`: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)}
327
+ `;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`
328
+ <div class="componentWrapper">
329
+ <div
330
+ class="${e({svgWrapper:true})}"
331
+ title="${o(this.title||void 0)}">
332
+ <span aria-hidden="${o(this.ariaHidden||true)}" part="svg">
333
+ ${this.customSvg?b`
334
+ <slot name="svg"></slot>
335
+ `:b`
336
+ ${this.svg}
337
+ `}
338
+ </span>
339
+ </div>
385
340
 
386
- // Get the active options
387
- const activeOptions = this._menuOptions.filter(option => option.isActive);
341
+ <div class="${e(t)}" part="label">
342
+ <slot></slot>
343
+ </div>
344
+ </div>
345
+ `}}
388
346
 
389
- // Get the currently active option
390
- const currentActiveOption = activeOptions[activeOptions.indexOf(this.highlightedOption)];
347
+ var iconVersion = '9.1.2';
391
348
 
392
- // Determine the new index based on the currently active option and direction
393
- let newIndex = currentActiveOption
394
- ? direction === "previous"
395
- ? activeOptions.indexOf(currentActiveOption) - 1
396
- : activeOptions.indexOf(currentActiveOption) + 1
397
- : direction === "previous"
398
- ? activeOptions.length - 1
399
- : 0;
349
+ 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>"};
400
350
 
401
- // Wrap around the index if needed
402
- newIndex = newIndex < 0 ? activeOptions.length - 1 : newIndex >= activeOptions.length ? 0 : newIndex;
351
+ // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
352
+ // See LICENSE in the project root for license information.
403
353
 
404
- // Get the new active option and set it as highlighted
405
- const newActiveOption = activeOptions[newIndex];
406
- this.setHighlightedOption(newActiveOption);
407
- }
408
354
 
409
- /**
410
- * Sets the highlighted index to the specified option.
411
- * @param {AuroMenuOption} option - The option to highlight.
412
- */
413
- setHighlightedOption(option) {
355
+ /**
356
+ * Helper method to dispatch custom events.
357
+ * @param {HTMLElement} element - Element to dispatch event from.
358
+ * @param {string} eventName - Name of the event to dispatch.
359
+ * @param {Object} [detail] - Optional detail object to include with the event.
360
+ */
361
+ function dispatchMenuEvent(element, eventName, detail = null) {
362
+ const eventConfig = {
363
+ bubbles: true,
364
+ cancelable: false,
365
+ composed: true
366
+ };
414
367
 
415
- if (!option) return;
368
+ if (detail !== null) {
369
+ eventConfig.detail = detail;
370
+ }
416
371
 
417
- // Get the index of the option to highlight
418
- const index = this._menuOptions.indexOf(option);
372
+ element.dispatchEvent(new CustomEvent(eventName, eventConfig));
373
+ }
419
374
 
420
- // Update highlighted index
421
- this.highlightedIndex = index;
375
+ // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
376
+ // See LICENSE in the project root for license information.
422
377
 
423
- // Notify subscribers of highlight change
424
- this.notify({ type: 'highlightChange', option, index: this.highlightedIndex });
425
378
 
426
- // Dispatch the change event
427
- this.dispatchChangeEvent('auroMenu-activatedOption', option);
428
- }
379
+ let menuOptionIdCounter = 0;
380
+
381
+ /**
382
+ * The `auro-menuoption` element provides users a way to define a menu option.
383
+ * @customElement auro-menuoption
384
+ *
385
+ * @slot default - The default slot for the menu option text.
386
+ *
387
+ * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
388
+ */
389
+ class AuroMenuOption extends AuroElement {
429
390
 
430
391
  /**
431
- * Sets the highlighted option to the option at the specified index if it exists.
432
- * @param {number} index
392
+ * This will register this element with the browser.
393
+ * @param {string} [name="auro-menuoption"] - The name of the element that you want to register.
394
+ *
395
+ * @example
396
+ * AuroMenuOption.register("custom-menuoption") // this will register this element to <custom-menuoption/>
397
+ *
433
398
  */
434
- setHighlightedIndex(index) {
435
- const option = this._menuOptions[index] || null;
436
- this.setHighlightedOption(option);
399
+ static register(name = "auro-menuoption") {
400
+ AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroMenuOption);
437
401
  }
438
402
 
439
403
  /**
440
- * Selects the currently highlighted option.
404
+ * Returns whether the menu option is currently active and selectable.
405
+ * An option is considered active if it is not hidden, not disabled, and not static.
406
+ * @returns {boolean} True if the option is active, false otherwise.
441
407
  */
442
- selectHighlightedOption() {
443
- if (this.highlightedOption) {
444
- this.toggleOption(this.highlightedOption);
445
- }
408
+ get isActive() {
409
+ return !this.hasAttribute('hidden') &&
410
+ !this.disabled &&
411
+ !this.hasAttribute('static');
446
412
  }
447
413
 
448
- /**
449
- * SELECTION AND DESELECTION METHODS
450
- */
414
+ constructor() {
415
+ super();
451
416
 
452
- /**
453
- * Selects one or more options in a batch operation
454
- * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to select
455
- */
456
- selectOptions(options) {
457
- let optionsToSelect = Array.isArray(options) ? options : [options];
417
+ this.bindEvents();
458
418
 
459
- // Filter out options that are inactive
460
- optionsToSelect = optionsToSelect.filter(option => option.isActive);
419
+ /**
420
+ * @private
421
+ */
422
+ this.shape = undefined;
461
423
 
462
- if (!optionsToSelect.length) return;
424
+ /**
425
+ * @private
426
+ */
427
+ this.size = undefined;
463
428
 
464
- if (this.multiSelect) {
465
- this.selectedOptions = [...(this.selectedOptions || []), ...optionsToSelect];
466
- } else {
467
- // In single select mode, only take the last option
468
- this.selectedOptions = [optionsToSelect[optionsToSelect.length - 1]];
469
- }
429
+ /**
430
+ * Generate unique names for dependency components.
431
+ */
432
+ const versioning = new AuroDependencyVersioning();
433
+ this.iconTag = versioning.generateTag('auro-formkit-menuoption-icon', iconVersion, _);
470
434
 
471
- this.stageUpdate();
472
- }
435
+ this.selected = false;
436
+ this.noCheckmark = false;
437
+ this.disabled = false;
438
+ this.noMatch = false;
473
439
 
474
- /**
475
- * Deselects one or more options in a batch operation
476
- * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to deselect
477
- */
478
- deselectOptions(options) {
479
- const optionsToDeselect = Array.isArray(options) ? options : [options];
440
+ /**
441
+ * @private
442
+ */
443
+ this.runtimeUtils = new AuroLibraryRuntimeUtils();
480
444
 
481
- if (!optionsToDeselect.length) return;
445
+ // Initialize context-related properties
446
+ this.menuService = null;
447
+ this.unsubscribe = null;
482
448
 
483
- // Check if deselection should be prevented
484
- const shouldPreventDeselect = !this.allowDeselect && !this.multiSelect;
485
- const isOnlySelectedOption = this.selectedOptions.length === 1 && optionsToDeselect.includes(this.selectedOptions[0]);
449
+ /**
450
+ * @private
451
+ */
452
+ this.handleMenuChange = this.handleMenuChange.bind(this);
453
+ }
486
454
 
487
- // Prevent deselecting the only selected option if not allowed
488
- if (shouldPreventDeselect && isOnlySelectedOption) {
489
- optionsToDeselect.forEach(option => {
490
- option.selected = true;
491
- });
492
- this.dispatchChangeEvent('auroMenu-deselectPrevented', {
493
- values: optionsToDeselect
494
- });
495
- return;
496
- }
455
+ static get properties() {
456
+ return {
457
+ ...super.properties,
497
458
 
498
- const optionsSet = new Set(optionsToDeselect);
499
- this.selectedOptions = (this.selectedOptions || [])
500
- .filter(opt => !optionsSet.has(opt));
459
+ /**
460
+ * When true, disables the menu option.
461
+ */
462
+ disabled: {
463
+ type: Boolean,
464
+ reflect: true
465
+ },
501
466
 
502
- this.stageUpdate();
503
- }
467
+ /**
468
+ * @private
469
+ */
470
+ event: {
471
+ type: String,
472
+ reflect: true
473
+ },
504
474
 
505
- /**
506
- * Selects a single option.
507
- * @param {AuroMenuOption} option
508
- */
509
- selectOption(option) {
510
- this.selectOptions(option);
511
- }
475
+ /**
476
+ * @private
477
+ */
478
+ layout: {
479
+ type: String
480
+ },
512
481
 
513
- /**
514
- * Deselects a single option.
515
- * @param {AuroMenuOption} option
516
- */
517
- deselectOption(option) {
518
- this.deselectOptions(option);
519
- }
482
+ /**
483
+ * 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.
484
+ */
485
+ key: {
486
+ type: String,
487
+ reflect: true
488
+ },
520
489
 
521
- /**
522
- * Toggles the selection state of a single option.
523
- * @param {AuroMenuOption} option
524
- */
525
- toggleOption(option) {
526
- if (option.selected) {
527
- this.deselectOption(option);
528
- } else {
529
- this.selectOption(option);
530
- }
490
+ /**
491
+ * @private
492
+ */
493
+ menuService: {
494
+ type: Object,
495
+ state: true
496
+ },
497
+
498
+ /**
499
+ * @private
500
+ */
501
+ matchWord: {
502
+ type: String,
503
+ state: true
504
+ },
505
+
506
+ /**
507
+ * @private
508
+ */
509
+ noCheckmark: {
510
+ type: Boolean,
511
+ reflect: true
512
+ },
513
+
514
+ /**
515
+ * 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.
516
+ */
517
+ noMatch: {
518
+ type: Boolean,
519
+ reflect: true,
520
+ attribute: 'nomatch'
521
+ },
522
+
523
+ /**
524
+ * Specifies that an option is selected.
525
+ */
526
+ selected: {
527
+ type: Boolean,
528
+ reflect: true
529
+ },
530
+
531
+ /**
532
+ * Specifies the tab index of the menu option.
533
+ */
534
+ tabIndex: {
535
+ type: Number,
536
+ reflect: true
537
+ },
538
+
539
+ /**
540
+ * Specifies the value to be sent to a server.
541
+ */
542
+ value: {
543
+ type: String,
544
+ reflect: true
545
+ },
546
+ };
531
547
  }
532
548
 
533
- /**
534
- * Selects options based on their value(s) when compared to a passed value or values.
535
- * Value or values are normalized to an array of strings that can be matched to option keys.
536
- * @param {string|number|Array<string|number>} value - The value(s) to select.
537
- */
538
- selectByValue(value) {
539
- const isEmptyValue = value === undefined ||
540
- value === null ||
541
- (Array.isArray(value) && value.length === 0) ||
542
- (typeof value === 'string' && value.trim() === '');
549
+ static get styles() {
550
+ return [
551
+ styleCss,
552
+ colorCss,
553
+ tokensCss
554
+ ];
555
+ }
543
556
 
544
- // Early exit for invalid/empty values
545
- if (isEmptyValue) {
546
- this.selectedOptions.forEach(opt => opt.selected = false);
547
- this.selectedOptions = [];
548
- return;
549
- }
557
+ connectedCallback() {
558
+ super.connectedCallback();
550
559
 
551
- // If an internal update cycle is still in progress, defer value application
552
- // rather than dropping it.
553
- if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
554
- this.queuePendingValue(value);
555
- return;
556
- }
560
+ // Add the tag name as an attribute if it is different than the component name
561
+ // Add this step soon as this node gets attached to the DOM to avoid racing condition with menu's value setting logic.
562
+ this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
557
563
 
558
- // Normalize values to array of strings
559
- const normalizedValues = this._getNormalizedValues(value);
564
+ // Set up context consumption in connectedCallback
565
+ this._contextConsumer = new s$2(this, {
566
+ context: MenuContext,
567
+ callback: this.attachTo.bind(this),
568
+ subscribe: true
569
+ });
560
570
 
561
- // Validate for single-select mode
562
- let validatedValues = normalizedValues;
563
- if (normalizedValues.length > 1 && !this.multiSelect) {
564
- console.warn("MenuService - Multiple values provided for single-select menu. Only the first value will be selected.");
565
- validatedValues = [normalizedValues[0]];
571
+ // Establish the key property as early as possible.
572
+ // When a framework (e.g. Svelte) inserts the element into the DOM before
573
+ // setting its `value` property, both `getAttribute('value')` and
574
+ // `getAttribute('key')` return null here. Setting `this.key = null`
575
+ // would block the fallback in `updated()` that assigns key from the
576
+ // value property (the guard checked `=== undefined`). Only assign key
577
+ // if at least one source attribute is actually present so that the
578
+ // `updated()` fallback can run when the value property arrives later.
579
+ const valueAttr = this.getAttribute('value');
580
+ const keyAttr = this.getAttribute('key');
581
+ const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
582
+ if (resolvedKey !== null) {
583
+ this.key = resolvedKey;
566
584
  }
585
+ }
567
586
 
568
- if (this._menuOptions.length === 0) {
569
- this.queuePendingValue(value);
570
- return;
571
- }
587
+ firstUpdated() {
588
+ // Add the tag name as an attribute if it is different than the component name
589
+ this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
572
590
 
573
- // Find matching options by comparing available options to validated values
574
- const trackedKeys = new Set();
575
- const optionsToSelect = this._menuOptions.filter(option => {
576
- const passesFilter = validatedValues.includes(option.key);
577
- const alreadyTracked = trackedKeys.has(option.key);
578
- const isActive = option.isActive;
591
+ // Generate unique ID if not already set (required for aria-activedescendant)
592
+ if (!this.id) {
593
+ menuOptionIdCounter += 1;
594
+ this.id = `menuoption-${menuOptionIdCounter}`;
595
+ }
579
596
 
580
- trackedKeys.add(option.key);
597
+ this.setAttribute('role', 'option');
598
+ this.setAttribute('aria-selected', 'false');
581
599
 
582
- // Include the option in the options to be selected if it passes the filter check and
583
- // either hasn't been tracked yet or selectAllMatchingOptions is true
584
- return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
600
+ this.addEventListener('mouseover', () => {
601
+ this.dispatchEvent(new CustomEvent('auroMenuOption-mouseover', {
602
+ bubbles: true,
603
+ cancelable: false,
604
+ composed: true,
605
+ detail: this
606
+ }));
585
607
  });
608
+ }
586
609
 
587
- // Handle no matches: clear existing selection, but do not dispatch an intermediate
588
- // undefined value that can overwrite the host value in parent components.
589
- if (!optionsToSelect.length) {
590
- const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
610
+ updated(changedProperties) {
611
+ super.updated(changedProperties);
591
612
 
592
- if (hasUnresolvedKeys) {
593
- this.queuePendingValue(value);
594
- return;
595
- }
613
+ // Update aria-selected attribute if selected changed
614
+ if (changedProperties.has('selected')) {
596
615
 
597
- this.clearPendingValue();
616
+ // Update aria-selected attribute
617
+ this.setAttribute('aria-selected', this.selected.toString());
598
618
 
599
- if (this.selectedOptions.length > 0) {
600
- this.selectedOptions = [];
619
+ // Update menu service selection state if this isn't an internal update
620
+ if (this.internalUpdateInProgress !== true) {
621
+ this.menuService[this.selected ? 'selectOption' : 'deselectOption'](this);
601
622
  }
623
+ }
602
624
 
603
- // Always notify so the host resets any stale invalid value, even when
604
- // selectedOptions was already empty (e.g. double-clicking set-invalid).
605
- this.stageUpdate({ reason: 'no-match' });
606
-
607
- // Dispatch failure event if no matches found
608
- if (validatedValues.length) {
609
- this.dispatchChangeEvent('auroMenu-selectValueFailure', {
610
- message: 'No matching options found for the provided value(s).',
611
- values: validatedValues
612
- });
625
+ if (changedProperties.has('disabled')) {
626
+ if (this.disabled) {
627
+ this.setAttribute('aria-disabled', 'true');
628
+ } else {
629
+ this.removeAttribute('aria-disabled');
613
630
  }
614
-
615
- return;
616
631
  }
617
632
 
618
- this.clearPendingValue();
633
+ if (changedProperties.has('active')) {
634
+ this.updateActiveClasses();
635
+ }
619
636
 
620
- if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
621
- return;
637
+ // Update text highlight if matchWord changed
638
+ if (changedProperties.has('matchWord')) {
639
+ this.updateTextHighlight();
622
640
  }
623
641
 
624
- // Apply programmatic selection as a single transaction and emit one final state.
625
- this.selectedOptions = optionsToSelect;
626
- this.stageUpdate();
642
+ // Set the key to be the passed value if no key is provided.
643
+ // Loose equality (== null) is intentional: it catches both null AND
644
+ // undefined. When a framework (e.g. Svelte, React) inserts the element
645
+ // before setting its value property, connectedCallback skips key
646
+ // assignment because both attributes are null at that point. The Lit
647
+ // property default for `key` is undefined (not null), so strict
648
+ // === null would miss the case and the fallback would never run.
649
+ if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
650
+ this.key = this.value;
651
+ }
627
652
  }
628
653
 
629
- /**
630
- * Queues a pending value and schedules a bounded retry.
631
- * @param {string|number|Array<string|number>} value - The value to retry.
632
- */
633
- queuePendingValue(value) {
634
- this._pendingValue = value;
635
-
636
- if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
637
- return;
654
+ disconnectedCallback() {
655
+ if (this.menuService) {
656
+ this.menuService.unsubscribe(this.handleMenuChange);
657
+ this.menuService.removeMenuOption(this);
638
658
  }
639
-
640
- this._pendingRetryScheduled = true;
641
- this._pendingRetryCount += 1;
642
-
643
- setTimeout(() => {
644
- this._pendingRetryScheduled = false;
645
-
646
- if (this._pendingValue == null) {
647
- return;
648
- }
649
-
650
- const pendingValue = this._pendingValue;
651
- this.selectByValue(pendingValue);
652
- }, 0);
653
659
  }
654
660
 
655
661
  /**
656
- * Clears pending retry state.
662
+ * Sets up event listeners for user interaction with the menu option.
663
+ * This function enables click and mouse enter events to trigger selection and highlighting logic.
657
664
  */
658
- clearPendingValue() {
659
- this._pendingValue = null;
660
- this._pendingRetryScheduled = false;
661
- this._pendingRetryCount = 0;
665
+ bindEvents() {
666
+ this.addEventListener('click', this.handleClick.bind(this));
667
+ this.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
662
668
  }
663
669
 
664
670
  /**
665
- * Resets the selected options to an empty array.
671
+ * Attaches this menu option to a menu service and subscribes to its events.
672
+ * This method enables the option to participate in menu selection and highlighting logic.
673
+ * @param {Object} service - The menu service instance to attach to.
666
674
  */
667
- reset() {
668
- const previousOptions = [...this.selectedOptions];
669
- previousOptions.forEach(opt => opt.selected = false);
670
- this.selectedOptions = [];
671
-
672
- // Single update after clearing all
673
- if (previousOptions.length) {
674
- this.stageUpdate();
675
+ attachTo(service) {
676
+ if (!service) {
677
+ return;
675
678
  }
679
+ this.menuService = service;
680
+ this.menuService.addMenuOption(this);
681
+ this.menuService.subscribe(this.handleMenuChange);
676
682
  }
677
683
 
678
684
  /**
679
- * SUBSCRIPTION, NOTIFICATION AND EVENT DISPATCH METHODS
685
+ * Handles changes from the menu service and updates the option's state.
686
+ * This function synchronizes the option's properties and selection/highlight state with menu events.
687
+ * @param {Object} event - The event object from the menu service.
680
688
  */
689
+ handleMenuChange(event) {
681
690
 
682
- /**
683
- * Subscribes a callback to menu service events.
684
- * @param {Function} callback - The callback to invoke on events.
685
- */
686
- subscribe(callback) {
687
- this._subscribers.push(callback);
691
+ // Ignore events without a type or property
692
+ if (!event || (!event.type && !event.property)) {
693
+ return;
694
+ }
695
+
696
+ // Update reactive properties based on event type
697
+ if (event.property && Object.keys(AuroMenuOption.properties).includes(event.property)) {
698
+ this[event.property] = event.value;
699
+ }
700
+
701
+ // Handle highlight changes
702
+ if (event.type === 'highlightChange') {
703
+ const isActive = event.option === this;
704
+ this.active = isActive;
705
+ this.updateActiveClasses();
706
+ }
707
+
708
+ if (event.type === 'stateChange') {
709
+ const isSelected = event.selectedOptions.includes(this);
710
+ this.setInternalSelected(isSelected);
711
+ }
688
712
  }
689
713
 
690
714
  /**
691
- * Remove a previously subscribed callback from menu service events.
692
- * @param {Function} callback
715
+ * Updates the internal selected state of the menu option bypassing 'updated' and triggers custom events if selected.
716
+ * This function ensures the option's selection state is synchronized with menu logic and notifies listeners.
717
+ * @param {boolean} isSelected - Whether the option should be marked as selected.
693
718
  */
694
- unsubscribe(callback) {
695
- this._subscribers = this._subscribers.filter(cb => cb !== callback);
719
+ setInternalSelected(isSelected) {
720
+ this.internalUpdateInProgress = true;
721
+ this.selected = isSelected;
722
+
723
+ // Fire custom event if selected
724
+ if (isSelected) {
725
+ this.handleCustomEvent();
726
+ }
727
+
728
+ setTimeout(() => {
729
+ this.internalUpdateInProgress = false;
730
+ }, 0);
696
731
  }
697
732
 
698
733
  /**
699
- * Stages an update to notify subscribers of state and value changes.
734
+ * Sets the selected state of the menu option.
735
+ * This function updates whether the option is currently selected.
736
+ * @param {boolean} isSelected - Whether the option should be marked as selected.
737
+ * @deprecated Simply modify the `selected` property directly instead.
700
738
  */
701
- stageUpdate(meta = {}) {
702
- this.notifyStateChange(meta);
703
- this.notifyValueChange(meta);
739
+ setSelected(isSelected) {
740
+ this.selected = isSelected;
704
741
  }
705
742
 
706
743
  /**
707
- * Notifies subscribers of a menu service event.
708
- * All notifications are sent to all subscribers.
709
- * @param {string} event - The event to send to subscribers.
744
+ * Updates the active state and visual highlighting of the menu option.
745
+ * This function toggles the option's active status and applies or removes the active CSS class.
746
+ * @param {boolean} isActive - Whether the option should be marked as active.
747
+ * @deprecated Simply modify the `active` property directly instead.
710
748
  */
711
- notify(event) {
712
- this._subscribers.forEach(callback => callback(event));
749
+ updateActive(isActive) {
750
+
751
+ // Set active state
752
+ this.active = isActive;
753
+ this.updateActiveClasses();
713
754
  }
714
755
 
715
756
  /**
716
- * Notifies subscribers of a state change (selected options has changed).
757
+ * Updates the CSS class for the menu option based on its active state.
758
+ * This function adds or removes the 'active' class to visually indicate the option's active status.
759
+ * @private
717
760
  */
718
- notifyStateChange(meta = {}) {
719
- this.notify({
720
- type: 'stateChange',
721
- selectedOptions: this.selectedOptions,
722
- ...meta
723
- });
761
+ updateActiveClasses() {
762
+ // Update class based on active state
763
+ if (this.active) this.classList.add('active');
764
+ else this.classList.remove('active');
724
765
  }
725
766
 
767
+
726
768
  /**
727
- * Notifies subscribers of a value change (current value has changed).
769
+ * Updates the visual highlighting of text within the menu option based on the current match word.
770
+ * This function highlights matching text segments and manages nested spacers for display formatting.
771
+ * @private
728
772
  */
729
- notifyValueChange(meta = {}) {
773
+ updateTextHighlight() {
730
774
 
731
- // Prepare details for the event
732
- const details = {
733
- value: this.currentValue,
734
- stringValue: this.stringValue,
735
- keys: this.currentKeys,
736
- options: this.selectedOptions,
737
- label: this.currentLabel
738
- };
775
+ // Regex for matchWord if needed
776
+ let regexWord = null;
739
777
 
740
- // If only one option is selected, include its index
741
- if (this.selectedOptions.length === 1) details.index = this._menuOptions.indexOf(this.selectedOptions[0]);
778
+ if (this.matchWord && this.matchWord.length) {
779
+ const escapedWord = this.matchWord.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
780
+ regexWord = new RegExp(escapedWord, 'giu');
781
+ }
742
782
 
743
- this.notify({
744
- type: 'valueChange',
745
- ...meta,
746
- ...details
747
- });
748
- }
783
+ // Update text highlighting if matchWord changed
784
+ if (regexWord &&
785
+ this.isActive && !this.hasAttribute('persistent')) {
786
+ const nested = this.querySelectorAll('.nestingSpacer');
749
787
 
750
- /**
751
- * Dispatches a custom event from the host element.
752
- * @param {string} eventName
753
- * @param {any} detail
754
- */
755
- dispatchChangeEvent(eventName, detail) {
756
- this.host.dispatchEvent(new CustomEvent(eventName, {
757
- bubbles: true,
758
- cancelable: false,
759
- composed: true,
760
- detail
761
- }));
788
+ const displayValueEl = this.querySelector('[slot="displayValue"]');
789
+ if (displayValueEl) {
790
+ this.removeChild(displayValueEl);
791
+ }
792
+
793
+ // Create nested spacers
794
+ const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
795
+
796
+ // Update with spacers and matchWord
797
+ this.innerHTML = nestingSpacerBundle +
798
+ this.textContent.replace(
799
+ regexWord,
800
+ (match) => `<strong>${match}</strong>`
801
+ );
802
+ if (displayValueEl) {
803
+ this.append(displayValueEl);
804
+ }
805
+ }
762
806
  }
763
807
 
764
808
  /**
765
- * MENU OPTION MANAGEMENT METHODS
809
+ * Handles click events on the menu option, toggling its selected state.
810
+ * This function dispatches a click event and updates selection if the option is not disabled.
811
+ * @private
766
812
  */
813
+ handleClick() {
814
+ if (!this.disabled) {
815
+ this.dispatchClickEvent();
816
+ this.selected = !this.selected;
817
+ }
818
+ }
767
819
 
768
820
  /**
769
- * Adds a menu option to the service's list.
770
- * @param {AuroMenuOption} option - the option to track
821
+ * Handles mouse enter events to highlight the menu option.
822
+ * This function updates the menu service to set this option as the currently highlighted item if not disabled.
823
+ * @private
771
824
  */
772
- addMenuOption(option) {
773
- this._menuOptions.push(option);
774
- this.notify({ type: 'optionsChange', options: this._menuOptions });
775
-
776
- if (this._pendingValue != null) {
777
- this.queuePendingValue(this._pendingValue);
825
+ handleMouseEnter() {
826
+ if (!this.disabled) {
827
+ this.menuService.setHighlightedOption(this);
778
828
  }
779
829
  }
780
830
 
781
831
  /**
782
- * Removes a menu option from the service's list.
783
- * @param {AuroMenuOption} option - the option to remove
832
+ * Dispatches custom events defined for this menu option.
833
+ * This function notifies listeners when a custom event is triggered by the option.
834
+ * @private
784
835
  */
785
- removeMenuOption(option) {
786
- this._menuOptions = this._menuOptions.filter(opt => opt !== option);
787
- this.notify({ type: 'optionsChange', options: this._menuOptions });
788
-
789
- if (this._menuOptions.length === 0) {
790
- this.clearPendingValue();
836
+ handleCustomEvent() {
837
+ if (this.event) {
838
+ dispatchMenuEvent(this, this.event, { option: this });
839
+ dispatchMenuEvent(this, 'auroMenu-customEventFired', { option: this });
791
840
  }
792
841
  }
793
842
 
794
843
  /**
795
- * UTILITIES
844
+ * Dispatches a click event for this menu option.
845
+ * This function notifies listeners that the option has been clicked.
846
+ * @private
796
847
  */
848
+ dispatchClickEvent() {
849
+ this.dispatchEvent(new CustomEvent('auroMenuOption-click', {
850
+ bubbles: true,
851
+ cancelable: false,
852
+ composed: true,
853
+ detail: this
854
+ }));
855
+ }
797
856
 
798
857
  /**
799
- * Normalizes a value or array of values into an array of strings for option selection.
800
- * This function ensures that input values are consistently formatted for matching menu options.
858
+ * Generates an HTML element containing an SVG icon based on the provided `svgContent`.
801
859
  *
802
- * @param {string|number|Array<string|number>} value - The value(s) to normalize.
803
- * @returns {Array<string>} An array of string values suitable for option matching.
804
- * @throws {Error} If any value is not a string or number.
860
+ * @private
861
+ * @param {string} svgContent - The SVG content to be embedded.
862
+ * @returns {Element} The HTML element containing the SVG icon.
805
863
  */
806
- _getNormalizedValues(value) {
807
- let values = value;
864
+ generateIconHtml(svgContent) {
865
+ const dom = new DOMParser().parseFromString(svgContent, 'text/html');
866
+ const svg = dom.body.firstChild;
808
867
 
809
- // Handle JSON string and single value string input
810
- if (!Array.isArray(values) && typeof values === 'string') {
868
+ svg.setAttribute('slot', 'svg');
811
869
 
812
- // Attempt to parse as JSON array
813
- try {
870
+ return u$1`<${this.iconTag} customColor customSvg>${svg}</${this.iconTag}>`;
871
+ }
814
872
 
815
- // Normalize single quotes to double quotes for JSON parsing
816
- // This will not handle complex cases but will cover basic usage
817
- const parseValue = values.replace(/'([^']*?)'/g, '"$1"');
873
+ /**
874
+ * Logic to determine the layout of the component.
875
+ * @protected
876
+ * @returns {void}
877
+ */
878
+ renderLayout() {
818
879
 
819
- // Attempt parse
820
- const parsed = JSON.parse(parseValue);
880
+ const fontClassMap = {
881
+ xs: 'body-sm',
882
+ sm: 'body-default',
883
+ md: 'body-default',
884
+ lg: 'body-lg',
885
+ xl: 'body-lg'
886
+ };
821
887
 
822
- // Ensure parsed value is an array
823
- if (!Array.isArray(parsed)) throw new Error('Not an array');
824
-
825
- // Set values to parsed array
826
- values = parsed;
827
- } catch (err) {
828
-
829
- // If parsing fails, treat as single value
830
- values = [value];
831
- }
832
- }
833
-
834
- // Handle a single number being passed
835
- if (typeof values === 'number') {
836
- values = [String(values)];
837
- }
838
-
839
- // Coerce each value to string and validate types
840
- values.forEach((val, index) => {
841
-
842
- // Throw an error for invalid value types
843
- if (typeof val !== 'string' && typeof val !== 'number') {
844
- throw new Error('Value contains invalid value type. Supported types are string and number.');
845
- }
846
-
847
- // Convert numbers to strings for consistency
848
- if (typeof val === 'number') {
849
- values[index] = String(val);
850
- }
888
+ const classes = e({
889
+ 'wrapper': true,
890
+ [this.size ? fontClassMap[this.size] : 'body-sm']: true,
851
891
  });
852
892
 
853
- // Return the resulting array of string values
854
- return values;
855
- }
856
-
857
- /**
858
- * Returns whether two arrays of options contain the same elements.
859
- * @param {AuroMenuOption[]} arr1 - First array of options.
860
- * @param {AuroMenuOption[]} arr2 - Second array of options.
861
- * @returns {boolean} True if arrays match, false otherwise.
862
- */
863
- optionsArraysMatch(arr1, arr2) {
864
- if (arr1.length !== arr2.length) return false;
865
-
866
- const set1 = new Set(arr1);
867
- const set2 = new Set(arr2);
868
-
869
- for (let item of set1) {
870
- if (!set2.has(item)) {
871
- return false;
872
- }
873
- }
874
-
875
- return true;
893
+ return u$1`
894
+ <div class="${classes}">
895
+ ${this.selected && !this.noCheckmark
896
+ ? this.generateIconHtml(checkmarkIcon.svg)
897
+ : undefined}
898
+ <slot></slot>
899
+ </div>
900
+ `;
876
901
  }
877
902
  }
878
903
 
879
- const MenuContext = n$1('menu-context');
880
-
881
- // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
882
- // See LICENSE in the project root for license information.
883
-
884
- // ---------------------------------------------------------------------
885
-
886
- /* eslint-disable line-comment-position, no-inline-comments, no-confusing-arrow, no-nested-ternary, implicit-arrow-linebreak */
887
-
888
- class AuroLibraryRuntimeUtils {
904
+ /* eslint-disable */
889
905
 
890
- /* eslint-disable jsdoc/require-param */
906
+ class MenuService {
891
907
 
892
908
  /**
893
- * This will register a new custom element with the browser.
894
- * @param {String} name - The name of the custom element.
895
- * @param {Object} componentClass - The class to register as a custom element.
896
- * @returns {void}
909
+ * PROPERTIES AND GETTERS
897
910
  */
898
- registerComponent(name, componentClass) {
899
- if (!customElements.get(name)) {
900
- customElements.define(name, class extends componentClass {});
901
- }
902
- }
903
911
 
904
912
  /**
905
- * Finds and returns the closest HTML Element based on a selector.
906
- * @returns {void}
913
+ * Gets the list of registered menu options.
914
+ * @returns {AuroMenuOption[]}
907
915
  */
908
- closestElement(
909
- selector, // selector like in .closest()
910
- base = this, // extra functionality to skip a parent
911
- __Closest = (el, found = el && el.closest(selector)) =>
912
- !el || el === document || el === window
913
- ? null // standard .closest() returns null for non-found selectors also
914
- : found
915
- ? found // found a selector INside this element
916
- : __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
917
- ) {
918
- return __Closest(base);
916
+ get menuOptions() {
917
+ return this._menuOptions;
919
918
  }
920
- /* eslint-enable jsdoc/require-param */
921
919
 
922
920
  /**
923
- * If the element passed is registered with a different tag name than what is passed in, the tag name is added as an attribute to the element.
924
- * @param {Object} elem - The element to check.
925
- * @param {String} tagName - The name of the Auro component to check for or add as an attribute.
926
- * @returns {void}
921
+ * Gets the currently highlighted option.
922
+ * @returns {AuroMenuOption|null}
927
923
  */
928
- handleComponentTagRename(elem, tagName) {
929
- const tag = tagName.toLowerCase();
930
- const elemTag = elem.tagName.toLowerCase();
931
-
932
- if (elemTag !== tag) {
933
- elem.setAttribute(tag, true);
934
- }
924
+ get highlightedOption() {
925
+ return this._menuOptions[this.highlightedIndex] || null;
935
926
  }
936
927
 
937
928
  /**
938
- * Validates if an element is a specific Auro component.
939
- * @param {Object} elem - The element to validate.
940
- * @param {String} tagName - The name of the Auro component to check against.
941
- * @returns {Boolean} - Returns true if the element is the specified Auro component.
929
+ * Gets the current value(s) of the selected option(s).
930
+ * @returns {string|string[]|undefined}
942
931
  */
943
- elementMatch(elem, tagName) {
944
- const tag = tagName.toLowerCase();
945
- const elemTag = elem.tagName.toLowerCase();
946
-
947
- return elemTag === tag || elem.hasAttribute(tag);
932
+ get currentValue() {
933
+ const values = (this.selectedOptions || []).map(option => option.value);
934
+ return this.multiSelect ? values : values[0];
948
935
  }
949
936
 
950
937
  /**
951
- * Gets the text content of a named slot.
952
- * @returns {String}
953
- * @private
938
+ * Gets the label(s) of the currently selected option(s).
939
+ * @returns {string}
954
940
  */
955
- getSlotText(elem, name) {
956
- const slot = elem.shadowRoot?.querySelector(`slot[name="${name}"]`);
957
- const nodes = slot?.assignedNodes({ flatten: true }) || [];
958
- const text = nodes.map(n => n.textContent?.trim()).join(' ').trim();
959
-
960
- return text || null;
941
+ get currentLabel() {
942
+ const labels = (this.selectedOptions || []).map(option => option.textContent);
943
+ return this.multiSelect ? labels.join(", ") : labels[0] || '';
961
944
  }
962
- }
963
945
 
964
- // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
965
- // See LICENSE in the project root for license information.
946
+ /**
947
+ * Gets the string representation of the current value(s).
948
+ * For multi-select, this is a JSON stringified array.
949
+ * @returns {string|undefined}
950
+ */
951
+ get stringValue() {
952
+ const { currentValue } = this;
966
953
 
954
+ if (Array.isArray(currentValue)) {
955
+ if (currentValue.length > 0) {
956
+ return JSON.stringify(currentValue);
957
+ }
958
+ return undefined;
959
+ }
967
960
 
968
- /**
969
- * Helper method to dispatch custom events.
970
- * @param {HTMLElement} element - Element to dispatch event from.
971
- * @param {string} eventName - Name of the event to dispatch.
972
- * @param {Object} [detail] - Optional detail object to include with the event.
973
- */
974
- function dispatchMenuEvent(element, eventName, detail = null) {
975
- const eventConfig = {
976
- bubbles: true,
977
- cancelable: false,
978
- composed: true
979
- };
961
+ if (typeof currentValue === 'string') {
962
+ if (currentValue.length > 0) {
963
+ return currentValue;
964
+ }
965
+ return undefined;
966
+ }
980
967
 
981
- if (detail !== null) {
982
- eventConfig.detail = detail;
968
+ // Future: handle other types here (e.g., number, object, etc.)
969
+ return undefined;
983
970
  }
984
971
 
985
- element.dispatchEvent(new CustomEvent(eventName, eventConfig));
986
- }
987
-
988
- /**
989
- * @license
990
- * Copyright 2017 Google LLC
991
- * SPDX-License-Identifier: BSD-3-Clause
992
- */
993
- const t={ATTRIBUTE:1},e$1=t=>(...e)=>({_$litDirective$:t,values:e});let i$1 = class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};
994
-
995
- /**
996
- * @license
997
- * Copyright 2018 Google LLC
998
- * SPDX-License-Identifier: BSD-3-Clause
999
- */const e=e$1(class extends i$1{constructor(t$1){if(super(t$1),t$1.type!==t.ATTRIBUTE||"class"!==t$1.name||t$1.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return " "+Object.keys(t).filter(s=>t[s]).join(" ")+" "}update(s,[i]){if(void 0===this.st){this.st=new Set,void 0!==s.strings&&(this.nt=new Set(s.strings.join(" ").split(/\s/).filter(t=>""!==t)));for(const t in i)i[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.st)t in i||(r.remove(t),this.st.delete(t));for(const t in i){const s=!!i[t];s===this.st.has(t)||this.nt?.has(t)||(s?(r.add(t),this.st.add(t)):(r.remove(t),this.st.delete(t)));}return E}});
1000
-
1001
- /* eslint-disable no-underscore-dangle */
1002
- // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1003
- // See LICENSE in the project root for license information.
1004
-
1005
-
1006
-
1007
- /**
1008
- * The `auro-menu` element provides users a way to select from a list of options.
1009
- * @customElement auro-menu
1010
- *
1011
- * @event {CustomEvent<Element>} auroMenu-activatedOption - Notifies that a menuoption has been made `active`.
1012
- * @event {CustomEvent<any>} auroMenu-customEventFired - Notifies that a custom event has been fired.
1013
- * @event {CustomEvent<{ loading: boolean; hasLoadingPlaceholder: boolean; }>} auroMenu-loadingChange - Notifies when the loading attribute is changed.
1014
- * @event {CustomEvent<any>} auroMenu-selectValueFailure - Notifies that an attempt to select a menuoption by matching a value has failed.
1015
- * @event {CustomEvent<{ values: HTMLElement[] }>} auroMenu-deselectPrevented - Notifies that deselection was prevented and includes the affected options in `detail.values`.
1016
- * @event {CustomEvent<any>} auroMenu-selectValueReset - Notifies that the component value has been reset.
1017
- * @event {CustomEvent<any>} auroMenu-selectedOption - Notifies that a new menuoption selection has been made.
1018
- * @slot loadingText - Text to show while loading attribute is set
1019
- * @slot loadingIcon - Icon to show while loading attribute is set
1020
- * @slot - Slot for insertion of menu options.
1021
- */
1022
-
1023
- /* eslint-disable max-lines */
1024
-
1025
- class AuroMenu extends AuroElement {
972
+ /**
973
+ * Gets the key(s) of the currently selected option(s).
974
+ * @returns {string|string[]|undefined}
975
+ */
976
+ get currentKeys() {
977
+ const keys = (this.selectedOptions || []).map(option => option.key);
978
+ return this.multiSelect ? keys : keys[0];
979
+ }
1026
980
 
1027
- constructor() {
1028
- super();
981
+ /**
982
+ * CONSTRUCTOR
983
+ */
1029
984
 
1030
- // State properties (reactive)
985
+ /**
986
+ * Creates a new MenuService instance.
987
+ * @param {Object} options - The options object.
988
+ * @param {AuroMenu} options.host - The host element that this service will control. Required.
989
+ * @throws {Error} If the host is not provided.
990
+ */
991
+ constructor({ host } = {}) {
1031
992
 
1032
- /**
1033
- * @private
1034
- */
1035
- this.shape = "box";
993
+ // Ensure a host was passed
994
+ if (!host) {
995
+ throw new Error("MenuService requires a host element.");
996
+ }
1036
997
 
1037
- /**
1038
- * @private
1039
- */
1040
- this.size = "sm";
998
+ // Attach the service to the host
999
+ this.host = host;
1000
+ this.host.addController(this);
1041
1001
 
1042
- // Value of the selected options
1043
- this.value = undefined;
1044
- // Currently selected option
1045
- this.optionSelected = undefined;
1046
- // String used for highlighting/filtering
1002
+ // Set default properties
1003
+ this.size = undefined;
1004
+ this.shape = undefined;
1005
+ this.noCheckmark = undefined;
1006
+ this.disabled = undefined;
1047
1007
  this.matchWord = undefined;
1048
- // Hide the checkmark icon on selected options
1049
- this.noCheckmark = false;
1050
- // Currently active option
1051
- this.optionActive = undefined;
1052
- // Loading state
1053
- this.loading = false;
1054
- // Multi-select mode
1055
- this.multiSelect = false;
1056
- // Allow deselecting of menu options
1057
- this.allowDeselect = false;
1058
- // Select all matching options when setting value in multi-select mode
1059
- this.selectAllMatchingOptions = false;
1060
-
1061
- // Event Bindings
1062
-
1063
- /**
1064
- * @private
1065
- */
1066
- this.handleSlotChange = this.handleSlotChange.bind(this);
1008
+ this.multiSelect = undefined;
1009
+ this.allowDeselect = undefined;
1010
+ this.selectAllMatchingOptions = undefined;
1067
1011
 
1068
- // Instance properties (non-reactive)
1012
+ this.highlightedIndex = -1;
1069
1013
 
1070
- /**
1071
- * @private
1072
- */
1073
- Object.assign(this, {
1074
- // Root-level menu (true) or a nested submenu (false)
1075
- rootMenu: true,
1076
- // Currently focused/active menu item index
1077
- _index: -1,
1078
- // Nested menu spacer
1079
- nestingSpacer: '<span class="nestingSpacer"></span>',
1080
- // Loading indicator for slot elements
1081
- loadingSlots: null,
1082
- });
1014
+ this._menuOptions = [];
1015
+ this._subscribers = [];
1016
+ this.internalUpdateInProgress = false;
1017
+ this.selectedOptions = [];
1018
+ this._pendingValue = null;
1019
+ this._pendingRetryScheduled = false;
1020
+ this._pendingRetryCount = 0;
1083
1021
  }
1084
1022
 
1085
- static get properties() {
1086
- return {
1087
- ...super.properties,
1088
-
1089
- /**
1090
- * Allows deselecting an already selected option when clicked again in single-select mode.
1091
- */
1092
- allowDeselect: {
1093
- type: Boolean,
1094
- reflect: true,
1095
- },
1096
-
1097
- /**
1098
- * When true, the entire menu and all options are disabled.
1099
- */
1100
- disabled: {
1101
- type: Boolean,
1102
- reflect: true
1103
- },
1104
-
1105
- /**
1106
- * Indicates whether the menu has a loadingIcon or loadingText to render when in a loading state.
1107
- */
1108
- hasLoadingPlaceholder: {
1109
- type: Boolean
1110
- },
1111
-
1112
- /**
1113
- * @private
1114
- */
1115
- layout: {
1116
- type: String
1117
- },
1118
-
1119
- /**
1120
- * Indent level for submenus.
1121
- * @private
1122
- */
1123
- level: {
1124
- type: Number,
1125
- reflect: false,
1126
- attribute: false
1127
- },
1128
-
1129
- /**
1130
- * When true, displays a loading state using the loadingIcon and loadingText slots if provided.
1131
- */
1132
- loading: {
1133
- type: Boolean,
1134
- reflect: true
1135
- },
1136
-
1137
- /**
1138
- * Specifies a string used to highlight matched string parts in options.
1139
- */
1140
- matchWord: {
1141
- type: String,
1142
- attribute: 'matchword'
1143
- },
1144
-
1145
- /**
1146
- * When true, the selected option can be multiple options.
1147
- */
1148
- multiSelect: {
1149
- type: Boolean,
1150
- reflect: true,
1151
- attribute: 'multiselect'
1152
- },
1153
-
1154
- /**
1155
- * When true, selected option will not show the checkmark.
1156
- */
1157
- noCheckmark: {
1158
- type: Boolean,
1159
- reflect: true,
1160
- attribute: 'nocheckmark'
1161
- },
1023
+ /**
1024
+ * PROPERTY SYNCING
1025
+ */
1162
1026
 
1163
- /**
1164
- * Specifies the current active menuOption.
1165
- */
1166
- optionActive: {
1167
- type: Object,
1168
- attribute: 'optionactive'
1169
- },
1027
+ /**
1028
+ * Handles host updates.
1029
+ * This is a lit reactive lifecycle method.
1030
+ * This comes from the Lit controller interface provided by adding this service as a controller to the host.
1031
+ * See constructor for `this.host.addController(this)`
1032
+ * You can read more about Lit reactive controllers here: https://lit.dev/docs/composition/controllers/
1033
+ */
1034
+ hostUpdated() {
1170
1035
 
1171
- /**
1172
- * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
1173
- */
1174
- optionSelected: {
1175
- // Allow HTMLElement, HTMLElement[] arrays and undefined
1176
- type: Object
1177
- },
1036
+ // Reset selection if multiSelect mode changes
1037
+ if (this.host.multiSelect !== this.multiSelect) {
1038
+ this.selectedOptions = [];
1039
+ }
1178
1040
 
1179
- /**
1180
- * Available menu options.
1181
- * @readonly
1182
- */
1183
- options: {
1184
- type: Array,
1185
- reflect: false,
1186
- attribute: false
1187
- },
1041
+ // Update properties on host update
1042
+ this.setProperties({
1043
+ size: this.host.size,
1044
+ shape: this.host.shape,
1045
+ noCheckmark: this.host.noCheckmark,
1046
+ disabled: this.host.disabled,
1047
+ matchWord: this.host.matchWord,
1048
+ multiSelect: this.host.multiSelect,
1049
+ allowDeselect: this.host.allowDeselect,
1050
+ selectAllMatchingOptions: this.host.selectAllMatchingOptions
1051
+ });
1052
+ }
1188
1053
 
1189
- /**
1190
- * Sets the size of the menu.
1191
- * @type {'sm' | 'md'}
1192
- * @default 'sm'
1193
- */
1194
- size: {
1195
- type: String,
1196
- reflect: true
1197
- },
1054
+ /**
1055
+ * Handles host disconnection and memory cleanup.
1056
+ */
1057
+ hostDisconnected() {
1058
+ this._subscribers = [];
1059
+ this._menuOptions = [];
1060
+ this._pendingValue = null;
1061
+ this._pendingRetryScheduled = false;
1062
+ this._pendingRetryCount = 0;
1063
+ }
1198
1064
 
1199
- /**
1200
- * When true, selects all options that match the provided value/key when setting value and multiselect is enabled.
1201
- */
1202
- selectAllMatchingOptions: {
1203
- type: Boolean,
1204
- reflect: true,
1205
- },
1065
+ /**
1066
+ * Sets a property value if it exists on the instance and the value has changed.
1067
+ * @param {string} property
1068
+ * @param {any} value
1069
+ */
1070
+ setProperty(property, value) {
1206
1071
 
1207
- /**
1208
- * Sets the shape of the menu.
1209
- * @type {'box' | 'round'}
1210
- * @default 'box'
1211
- */
1212
- shape: {
1213
- type: String,
1214
- reflect: true
1215
- },
1072
+ // Only update if we are tracking the property in this service
1073
+ if (this.hasOwnProperty(property)) {
1216
1074
 
1217
- /**
1218
- * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
1219
- */
1220
- value: {
1221
- type: String,
1222
- reflect: true,
1223
- attribute: 'value'
1224
- }
1225
- };
1226
- }
1075
+ // Check if the value has changed
1076
+ const valueChanged = this[property] !== value;
1227
1077
 
1228
- static get styles() {
1229
- return [
1230
- styleCss$1,
1231
- colorCss$1,
1232
- tokensCss
1233
- ];
1078
+ // Update and notify if changed
1079
+ if (valueChanged) {
1080
+ this[property] = value;
1081
+ this.notify({ property, value });
1082
+ }
1083
+ }
1234
1084
  }
1235
1085
 
1236
1086
  /**
1237
- * @readonly
1238
- * @returns {string} - Returns the label of the currently selected option(s).
1087
+ * Sets multiple properties on the instance.
1088
+ * @param {Object} properties - Key-value pairs of properties to set.
1239
1089
  */
1240
- get currentLabel() {
1241
- return this.menuService.currentLabel;
1242
- };
1090
+ setProperties(properties) {
1091
+ for (const [key, value] of Object.entries(properties)) {
1092
+ this.setProperty(key, value);
1093
+ }
1094
+ }
1243
1095
 
1244
1096
  /**
1245
- * @readonly
1246
- * @returns {Array<HTMLElement>} - Returns the array of available menu options.
1247
- * @deprecated Use `options` property instead.
1097
+ * MENU OPTION HIGHLIGHTING
1248
1098
  */
1249
- get items() {
1250
- return this.options;
1251
- }
1252
1099
 
1253
1100
  /**
1254
- * @returns {number} - Returns the index of the currently active option.
1101
+ * Highlights the next active option in the menu.
1255
1102
  */
1256
- get index() {
1257
- return this._index;
1103
+ highlightNext() {
1104
+ this.moveHighlightedOption("next");
1258
1105
  }
1259
1106
 
1260
1107
  /**
1261
- * @param {number} value - Sets the index of the currently active option.
1108
+ * Highlights the previous active option in the menu.
1262
1109
  */
1263
- set index(value) {
1264
- this.menuService.setHighlightedIndex(value);
1110
+ highlightPrevious() {
1111
+ this.moveHighlightedOption("previous");
1265
1112
  }
1266
1113
 
1267
1114
  /**
1268
- * This will register this element with the browser.
1269
- * @param {string} [name="auro-menu"] - The name of the element that you want to register.
1270
- *
1271
- * @example
1272
- * AuroMenu.register("custom-menu") // this will register this element to <custom-menu/>
1273
- *
1115
+ * Moves the highlighted option in the specified direction.
1116
+ * @param {string} direction - The direction to move the highlight ("next" or "previous").
1274
1117
  */
1275
- static register(name = "auro-menu") {
1276
- AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroMenu);
1118
+ moveHighlightedOption(direction) {
1119
+
1120
+ // Get the active options
1121
+ const activeOptions = this._menuOptions.filter(option => option.isActive);
1122
+
1123
+ // Get the currently active option
1124
+ const currentActiveOption = activeOptions[activeOptions.indexOf(this.highlightedOption)];
1125
+
1126
+ // Determine the new index based on the currently active option and direction
1127
+ let newIndex = currentActiveOption
1128
+ ? direction === "previous"
1129
+ ? activeOptions.indexOf(currentActiveOption) - 1
1130
+ : activeOptions.indexOf(currentActiveOption) + 1
1131
+ : direction === "previous"
1132
+ ? activeOptions.length - 1
1133
+ : 0;
1134
+
1135
+ // Wrap around the index if needed
1136
+ newIndex = newIndex < 0 ? activeOptions.length - 1 : newIndex >= activeOptions.length ? 0 : newIndex;
1137
+
1138
+ // Get the new active option and set it as highlighted
1139
+ const newActiveOption = activeOptions[newIndex];
1140
+ this.setHighlightedOption(newActiveOption);
1277
1141
  }
1278
1142
 
1279
1143
  /**
1280
- * Formatted value based on `multiSelect` state.
1281
- * Default type is `String`, changing to `Array<String>` when `multiSelect` is true.
1282
- * @private
1283
- * @returns {String|Array<String>}
1144
+ * Sets the highlighted index to the specified option.
1145
+ * @param {AuroMenuOption} option - The option to highlight.
1284
1146
  */
1285
- get formattedValue() {
1286
- return this.menuService.currentValue;
1147
+ setHighlightedOption(option) {
1148
+
1149
+ if (!option) return;
1150
+
1151
+ // Get the index of the option to highlight
1152
+ const index = this._menuOptions.indexOf(option);
1153
+
1154
+ // Update highlighted index
1155
+ this.highlightedIndex = index;
1156
+
1157
+ // Notify subscribers of highlight change
1158
+ this.notify({ type: 'highlightChange', option, index: this.highlightedIndex });
1159
+
1160
+ // Dispatch the change event
1161
+ this.dispatchChangeEvent('auroMenu-activatedOption', option);
1287
1162
  }
1288
1163
 
1289
1164
  /**
1290
- * Gets the current property values for the menu service.
1291
- * @private
1292
- * @returns {Object}
1165
+ * Sets the highlighted option to the option at the specified index if it exists.
1166
+ * @param {number} index
1293
1167
  */
1294
- get propertyValues() {
1295
- return {
1296
- size: this.size,
1297
- shape: this.shape,
1298
- noCheckmark: this.nocheckmark,
1299
- disabled: this.disabled
1300
- };
1168
+ setHighlightedIndex(index) {
1169
+ const option = this._menuOptions[index] || null;
1170
+ this.setHighlightedOption(option);
1301
1171
  }
1302
1172
 
1303
1173
  /**
1304
- * Provides the menu context to child components.
1305
- * Initializes the MenuService and subscribes to menu changes.
1306
- * @protected
1174
+ * Selects the currently highlighted option.
1307
1175
  */
1308
- provideContext() {
1309
- if (this.parentElement && this.parentElement.closest('auro-menu, [auro-menu]')) {
1310
- this.rootMenu = false;
1311
- this.menuService = this.parentElement.menuService;
1312
- this._contextProvider = this.parentElement._contextProvider;
1313
- return;
1176
+ selectHighlightedOption() {
1177
+ if (this.highlightedOption) {
1178
+ this.toggleOption(this.highlightedOption);
1314
1179
  }
1315
-
1316
- this.menuService = new MenuService({host: this});
1317
- this.menuService.setProperties(this.propertyValues);
1318
- this.menuService.subscribe(this.handleMenuChange.bind(this));
1319
- this._contextProvider = new i$2(this, {
1320
- context: MenuContext,
1321
- initialValue: this.menuService
1322
- });
1323
1180
  }
1324
1181
 
1325
1182
  /**
1326
- * Updates the currently active option in the menu.
1327
- * @param {HTMLElement} option - The option to set as active.
1183
+ * SELECTION AND DESELECTION METHODS
1328
1184
  */
1329
- updateActiveOption(option) {
1330
- this.menuService.setHighlightedOption(option);
1185
+
1186
+ /**
1187
+ * Selects one or more options in a batch operation
1188
+ * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to select
1189
+ */
1190
+ selectOptions(options) {
1191
+ let optionsToSelect = Array.isArray(options) ? options : [options];
1192
+
1193
+ // Filter out options that are inactive
1194
+ optionsToSelect = optionsToSelect.filter(option => option.isActive);
1195
+
1196
+ if (!optionsToSelect.length) return;
1197
+
1198
+ if (this.multiSelect) {
1199
+ this.selectedOptions = [...(this.selectedOptions || []), ...optionsToSelect];
1200
+ } else {
1201
+ // In single select mode, only take the last option
1202
+ this.selectedOptions = [optionsToSelect[optionsToSelect.length - 1]];
1203
+ }
1204
+
1205
+ this.stageUpdate();
1331
1206
  }
1332
1207
 
1333
1208
  /**
1334
- * Sets the internal value and manages update state.
1335
- * @param {String|Array<String>} value - The value to set.
1336
- * @protected
1209
+ * Deselects one or more options in a batch operation
1210
+ * @param {AuroMenuOption|AuroMenuOption[]} options - Single option or array of options to deselect
1337
1211
  */
1338
- setInternalValue(value) {
1339
- if (this.value !== value) {
1340
- this.internalUpdateInProgress = true;
1341
- this.value = value;
1212
+ deselectOptions(options) {
1213
+ const optionsToDeselect = Array.isArray(options) ? options : [options];
1342
1214
 
1343
- setTimeout(() => {
1344
- this.internalUpdateInProgress = false;
1215
+ if (!optionsToDeselect.length) return;
1216
+
1217
+ // Check if deselection should be prevented
1218
+ const shouldPreventDeselect = !this.allowDeselect && !this.multiSelect;
1219
+ const isOnlySelectedOption = this.selectedOptions.length === 1 && optionsToDeselect.includes(this.selectedOptions[0]);
1220
+
1221
+ // Prevent deselecting the only selected option if not allowed
1222
+ if (shouldPreventDeselect && isOnlySelectedOption) {
1223
+ optionsToDeselect.forEach(option => {
1224
+ option.selected = true;
1345
1225
  });
1226
+ this.dispatchChangeEvent('auroMenu-deselectPrevented', {
1227
+ values: optionsToDeselect
1228
+ });
1229
+ return;
1346
1230
  }
1231
+
1232
+ const optionsSet = new Set(optionsToDeselect);
1233
+ this.selectedOptions = (this.selectedOptions || [])
1234
+ .filter(opt => !optionsSet.has(opt));
1235
+
1236
+ this.stageUpdate();
1347
1237
  }
1348
1238
 
1349
1239
  /**
1350
- * Handles changes from the menu service and updates component state.
1351
- * @param {Object} event - The event object from the menu service.
1352
- * @protected
1240
+ * Selects a single option.
1241
+ * @param {AuroMenuOption} option
1353
1242
  */
1354
- handleMenuChange(event) {
1355
- if (event.type === 'valueChange') {
1356
-
1357
- // New option is array value or first option with fallback to undefined for empty array in all cases
1358
- const newOption = this.multiSelect && event.options.length ? event.options : event.options[0] || undefined;
1359
- const newValue = event.stringValue;
1243
+ selectOption(option) {
1244
+ this.selectOptions(option);
1245
+ }
1360
1246
 
1361
- // Check if the option or value has actually changed
1362
- if (this.optionSelected !== newOption || this.stringValue !== newValue) {
1363
- this.optionSelected = newOption;
1364
- this.setInternalValue(newValue);
1365
- }
1247
+ /**
1248
+ * Deselects a single option.
1249
+ * @param {AuroMenuOption} option
1250
+ */
1251
+ deselectOption(option) {
1252
+ this.deselectOptions(option);
1253
+ }
1366
1254
 
1367
- // Notify components of selection change
1368
- this.notifySelectionChange(event);
1255
+ /**
1256
+ * Toggles the selection state of a single option.
1257
+ * @param {AuroMenuOption} option
1258
+ */
1259
+ toggleOption(option) {
1260
+ if (option.selected) {
1261
+ this.deselectOption(option);
1262
+ } else {
1263
+ this.selectOption(option);
1369
1264
  }
1265
+ }
1370
1266
 
1371
- if (event.type === 'highlightChange') {
1372
- this.optionActive = event.option;
1373
- this._index = event.index;
1267
+ /**
1268
+ * Selects options based on their value(s) when compared to a passed value or values.
1269
+ * Value or values are normalized to an array of strings that can be matched to option keys.
1270
+ * @param {string|number|Array<string|number>} value - The value(s) to select.
1271
+ */
1272
+ selectByValue(value) {
1273
+ const isEmptyValue = value === undefined ||
1274
+ value === null ||
1275
+ (Array.isArray(value) && value.length === 0) ||
1276
+ (typeof value === 'string' && value.trim() === '');
1277
+
1278
+ // Early exit for invalid/empty values
1279
+ if (isEmptyValue) {
1280
+ this.selectedOptions.forEach(opt => opt.selected = false);
1281
+ this.selectedOptions = [];
1282
+ return;
1374
1283
  }
1375
1284
 
1376
- if (event.type === 'optionsChange') {
1377
- this.options = event.options;
1378
- this.dispatchEvent(new CustomEvent('auroMenu-optionsChange', {
1379
- detail: {
1380
- options: event.options
1381
- }
1382
- }));
1285
+ // If an internal update cycle is still in progress, defer value application
1286
+ // rather than dropping it.
1287
+ if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
1288
+ this.queuePendingValue(value);
1289
+ return;
1383
1290
  }
1384
- }
1385
1291
 
1386
- /**
1387
- * Gets the currently selected options.
1388
- * @returns {Array<HTMLElement>}
1389
- */
1390
- get selectedOptions() {
1391
- return this.menuService ? this.menuService.selectedOptions : [];
1392
- }
1292
+ // Normalize values to array of strings
1293
+ const normalizedValues = this._getNormalizedValues(value);
1393
1294
 
1394
- /**
1395
- * Gets the first selected option, or null if none.
1396
- * @returns {HTMLElement|null}
1397
- */
1398
- get selectedOption() {
1399
- return this.menuService ? this.menuService.selectedOptions[0] : null;
1400
- }
1295
+ // Validate for single-select mode
1296
+ let validatedValues = normalizedValues;
1297
+ if (normalizedValues.length > 1 && !this.multiSelect) {
1298
+ console.warn("MenuService - Multiple values provided for single-select menu. Only the first value will be selected.");
1299
+ validatedValues = [normalizedValues[0]];
1300
+ }
1401
1301
 
1402
- // Lifecycle Methods
1302
+ if (this._menuOptions.length === 0) {
1303
+ this.queuePendingValue(value);
1304
+ return;
1305
+ }
1403
1306
 
1404
- connectedCallback() {
1405
- super.connectedCallback();
1307
+ // Find matching options by comparing available options to validated values
1308
+ const trackedKeys = new Set();
1309
+ const optionsToSelect = this._menuOptions.filter(option => {
1310
+ const passesFilter = validatedValues.includes(option.key);
1311
+ const alreadyTracked = trackedKeys.has(option.key);
1312
+ const isActive = option.isActive;
1406
1313
 
1407
- this.provideContext();
1314
+ trackedKeys.add(option.key);
1408
1315
 
1409
- // this.addEventListener('keydown', this.handleKeyDown);
1410
- this.addEventListener('auroMenuOption-click', this.handleMouseSelect);
1411
- this.addEventListener('auroMenuOption-mouseover', this.handleOptionHover);
1412
- this.addEventListener('slotchange', this.handleSlotChange);
1413
- this.setTagAttribute("auro-menu");
1414
- }
1316
+ // Include the option in the options to be selected if it passes the filter check and
1317
+ // either hasn't been tracked yet or selectAllMatchingOptions is true
1318
+ return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
1319
+ });
1415
1320
 
1416
- disconnectedCallback() {
1417
- // this.removeEventListener('keydown', this.handleKeyDown);
1418
- this.removeEventListener('auroMenuOption-click', this.handleMouseSelect);
1419
- this.removeEventListener('auroMenuOption-mouseover', this.handleOptionHover);
1420
- this.removeEventListener('slotchange', this.handleSlotChange);
1321
+ // Handle no matches: clear existing selection, but do not dispatch an intermediate
1322
+ // undefined value that can overwrite the host value in parent components.
1323
+ if (!optionsToSelect.length) {
1324
+ const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
1421
1325
 
1422
- super.disconnectedCallback();
1423
- }
1326
+ if (hasUnresolvedKeys) {
1327
+ this.queuePendingValue(value);
1328
+ return;
1329
+ }
1424
1330
 
1425
- firstUpdated() {
1426
- AuroLibraryRuntimeUtils.prototype.handleComponentTagRename(this, 'auro-menu');
1331
+ this.clearPendingValue();
1427
1332
 
1428
- this.loadingSlots = this.querySelectorAll("[slot='loadingText'], [slot='loadingIcon']");
1429
- this.initializeMenu();
1430
- }
1333
+ if (this.selectedOptions.length > 0) {
1334
+ this.selectedOptions = [];
1335
+ }
1431
1336
 
1337
+ // Always notify so the host resets any stale invalid value, even when
1338
+ // selectedOptions was already empty (e.g. double-clicking set-invalid).
1339
+ this.stageUpdate({ reason: 'no-match' });
1432
1340
 
1433
- updated(changedProperties) {
1434
- super.updated(changedProperties);
1341
+ // Dispatch failure event if no matches found
1342
+ if (validatedValues.length) {
1343
+ this.dispatchChangeEvent('auroMenu-selectValueFailure', {
1344
+ message: 'No matching options found for the provided value(s).',
1345
+ values: validatedValues
1346
+ });
1347
+ }
1435
1348
 
1436
- // Apply value selection synchronously so that static-HTML fixtures
1437
- // resolve within a single update cycle. The refactored selectByValue
1438
- // no longer calls reset() first, so the destructive intermediate-event
1439
- // cascade that originally required deferral is eliminated. If option
1440
- // keys are not yet resolved (framework mount-order race), selectByValue
1441
- // queues a bounded retry automatically via queuePendingValue.
1442
- if (changedProperties.has('value') && !this.internalUpdateInProgress) {
1443
- this.menuService.selectByValue(this.value);
1349
+ return;
1444
1350
  }
1445
1351
 
1446
- // Handle loading state changes
1447
- if (changedProperties.has('loading')) {
1448
- this.setLoadingState(this.loading);
1449
- }
1352
+ this.clearPendingValue();
1450
1353
 
1451
- if (changedProperties.has('multiSelect') && this.rootMenu) {
1452
- if (this.multiSelect) {
1453
- this.setAttribute('aria-multiselectable', 'true');
1454
- } else {
1455
- this.removeAttribute('aria-multiselectable');
1456
- }
1354
+ if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
1355
+ return;
1457
1356
  }
1357
+
1358
+ // Apply programmatic selection as a single transaction and emit one final state.
1359
+ this.selectedOptions = optionsToSelect;
1360
+ this.stageUpdate();
1458
1361
  }
1459
1362
 
1460
1363
  /**
1461
- * Sets an attribute that matches the default tag name if the tag name is not the default.
1462
- * @param {string} tagName - The tag name to set as an attribute.
1463
- * @private
1364
+ * Queues a pending value and schedules a bounded retry.
1365
+ * @param {string|number|Array<string|number>} value - The value to retry.
1464
1366
  */
1465
- setTagAttribute(tagName) {
1466
- if (this.tagName.toLowerCase() !== tagName) {
1467
- this.setAttribute(tagName, true);
1367
+ queuePendingValue(value) {
1368
+ this._pendingValue = value;
1369
+
1370
+ if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
1371
+ return;
1468
1372
  }
1373
+
1374
+ this._pendingRetryScheduled = true;
1375
+ this._pendingRetryCount += 1;
1376
+
1377
+ setTimeout(() => {
1378
+ this._pendingRetryScheduled = false;
1379
+
1380
+ if (this._pendingValue == null) {
1381
+ return;
1382
+ }
1383
+
1384
+ const pendingValue = this._pendingValue;
1385
+ this.selectByValue(pendingValue);
1386
+ }, 0);
1469
1387
  }
1470
1388
 
1471
1389
  /**
1472
- * Sets the loading state and dispatches a loading change event.
1473
- * @param {boolean} isLoading - Whether the menu is loading.
1474
- * @protected
1390
+ * Clears pending retry state.
1475
1391
  */
1476
- setLoadingState(isLoading) {
1477
- this.setAttribute("aria-busy", isLoading);
1478
- dispatchMenuEvent(this, "auroMenu-loadingChange", {
1479
- loading: isLoading,
1480
- hasLoadingPlaceholder: this.hasLoadingPlaceholder
1481
- });
1392
+ clearPendingValue() {
1393
+ this._pendingValue = null;
1394
+ this._pendingRetryScheduled = false;
1395
+ this._pendingRetryCount = 0;
1482
1396
  }
1483
1397
 
1484
- // Init Methods
1485
-
1486
1398
  /**
1487
- * Initializes the menu's state and structure.
1488
- * @private
1399
+ * Resets the selected options to an empty array.
1489
1400
  */
1490
- initializeMenu() {
1491
- if (this.rootMenu) {
1492
- this.setAttribute('role', 'listbox');
1493
- this.setAttribute('root', '');
1401
+ reset() {
1402
+ const previousOptions = [...this.selectedOptions];
1403
+ previousOptions.forEach(opt => opt.selected = false);
1404
+ this.selectedOptions = [];
1494
1405
 
1495
- if (this.multiSelect) {
1496
- this.setAttribute('aria-multiselectable', 'true');
1497
- }
1406
+ // Single update after clearing all
1407
+ if (previousOptions.length) {
1408
+ this.stageUpdate();
1498
1409
  }
1499
-
1500
- this.handleNestedMenus(this);
1501
1410
  }
1502
1411
 
1503
1412
  /**
1504
- * Selects the currently highlighted option.
1505
- * @protected
1413
+ * SUBSCRIPTION, NOTIFICATION AND EVENT DISPATCH METHODS
1506
1414
  */
1507
- makeSelection() {
1508
- this.menuService.selectHighlightedOption();
1509
- }
1510
1415
 
1511
1416
  /**
1512
- * Resets all options to their default state.
1513
- * @private
1417
+ * Subscribes a callback to menu service events.
1418
+ * @param {Function} callback - The callback to invoke on events.
1514
1419
  */
1515
- clearSelection() {
1516
- this.optionSelected = undefined;
1517
- this.value = undefined;
1518
- this._index = -1;
1420
+ subscribe(callback) {
1421
+ this._subscribers.push(callback);
1519
1422
  }
1520
1423
 
1521
1424
  /**
1522
- * Resets the menu to its initial state.
1523
- * This is the only way to return value to undefined.
1524
- * @public
1425
+ * Remove a previously subscribed callback from menu service events.
1426
+ * @param {Function} callback
1525
1427
  */
1526
- reset() {
1527
- this.menuService.reset();
1528
-
1529
- // Dispatch reset event
1530
- dispatchMenuEvent(this, 'auroMenu-selectValueReset');
1428
+ unsubscribe(callback) {
1429
+ this._subscribers = this._subscribers.filter(cb => cb !== callback);
1531
1430
  }
1532
1431
 
1533
1432
  /**
1534
- * Handles nested menu structure.
1535
- * @private
1536
- * @param {HTMLElement} menu - Root menu element.
1433
+ * Stages an update to notify subscribers of state and value changes.
1537
1434
  */
1538
- handleNestedMenus(menu) {
1539
- menu.level = menu.parentElement.level >= 0 ? menu.parentElement.level + 1 : 0;
1435
+ stageUpdate(meta = {}) {
1436
+ this.notifyStateChange(meta);
1437
+ this.notifyValueChange(meta);
1438
+ }
1540
1439
 
1541
- if (menu.level > 0) {
1542
- menu.setAttribute('role', 'group');
1543
- menu.removeAttribute("root");
1544
- if (!menu.hasAttribute('aria-label')) {
1545
- menu.setAttribute('aria-label', 'submenu');
1546
- }
1547
- }
1440
+ /**
1441
+ * Notifies subscribers of a menu service event.
1442
+ * All notifications are sent to all subscribers.
1443
+ * @param {string} event - The event to send to subscribers.
1444
+ */
1445
+ notify(event) {
1446
+ this._subscribers.forEach(callback => callback(event));
1447
+ }
1548
1448
 
1549
- const options = menu.querySelectorAll(':scope > auro-menuoption, :scope > [auro-menuoption]');
1550
- options.forEach((option) => {
1551
- const regex = new RegExp(this.nestingSpacer, "gu");
1552
- option.innerHTML = this.nestingSpacer.repeat(menu.level) + option.innerHTML.replace(regex, '');
1449
+ /**
1450
+ * Notifies subscribers of a state change (selected options has changed).
1451
+ */
1452
+ notifyStateChange(meta = {}) {
1453
+ this.notify({
1454
+ type: 'stateChange',
1455
+ selectedOptions: this.selectedOptions,
1456
+ ...meta
1553
1457
  });
1554
1458
  }
1555
1459
 
1556
1460
  /**
1557
- * Navigates the menu options in the specified direction.
1558
- * @param {'up'|'down'} direction - The direction to navigate.
1559
- * @protected
1461
+ * Notifies subscribers of a value change (current value has changed).
1560
1462
  */
1561
- navigateOptions(direction) {
1562
- if (direction === 'up') {
1563
- this.menuService.highlightPrevious();
1564
- } else if (direction === 'down') {
1565
- this.menuService.highlightNext();
1566
- }
1567
- }
1463
+ notifyValueChange(meta = {}) {
1464
+
1465
+ // Prepare details for the event
1466
+ const details = {
1467
+ value: this.currentValue,
1468
+ stringValue: this.stringValue,
1469
+ keys: this.currentKeys,
1470
+ options: this.selectedOptions,
1471
+ label: this.currentLabel
1472
+ };
1568
1473
 
1569
- /**
1570
- * Handles slot change events.
1571
- * @private
1572
- */
1573
- handleSlotChange() {
1574
- if (this.rootMenu) {
1575
- this.initializeMenu();
1576
- }
1474
+ // If only one option is selected, include its index
1475
+ if (this.selectedOptions.length === 1) details.index = this._menuOptions.indexOf(this.selectedOptions[0]);
1476
+
1477
+ this.notify({
1478
+ type: 'valueChange',
1479
+ ...meta,
1480
+ ...details
1481
+ });
1577
1482
  }
1578
1483
 
1579
1484
  /**
1580
- * Handles custom events defined on options.
1581
- * @private
1582
- * @param {HTMLElement} option - Option with custom event.
1485
+ * Dispatches a custom event from the host element.
1486
+ * @param {string} eventName
1487
+ * @param {any} detail
1583
1488
  */
1584
- handleCustomEvent(option) {
1585
- const eventName = option.getAttribute('event');
1586
- dispatchMenuEvent(this, eventName);
1587
- dispatchMenuEvent(this, 'auroMenu-customEventFired');
1489
+ dispatchChangeEvent(eventName, detail) {
1490
+ this.host.dispatchEvent(new CustomEvent(eventName, {
1491
+ bubbles: true,
1492
+ cancelable: false,
1493
+ composed: true,
1494
+ detail
1495
+ }));
1588
1496
  }
1589
1497
 
1590
1498
  /**
1591
- * Notifies selection change to parent components.
1592
- * @param {any} source - The source that triggers this event.
1593
- * @private
1499
+ * MENU OPTION MANAGEMENT METHODS
1594
1500
  */
1595
- notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
1596
- dispatchMenuEvent(this, 'auroMenu-selectedOption', {
1597
- value,
1598
- stringValue,
1599
- keys,
1600
- options,
1601
- reason
1602
- });
1603
- }
1604
1501
 
1605
1502
  /**
1606
- * Checks if an option is currently selected.
1607
- * @private
1608
- * @param {HTMLElement} option - The option to check.
1609
- * @returns {boolean}
1503
+ * Adds a menu option to the service's list.
1504
+ * @param {AuroMenuOption} option - the option to track
1610
1505
  */
1611
- isOptionSelected(option) {
1612
- if (!this.optionSelected) {
1613
- return false;
1614
- }
1506
+ addMenuOption(option) {
1507
+ this._menuOptions.push(option);
1508
+ this.notify({ type: 'optionsChange', options: this._menuOptions });
1615
1509
 
1616
- if (this.multiSelect) {
1617
- // In multi-select mode, check if the option is in the selected array
1618
- return Array.isArray(this.optionSelected) && this.optionSelected.some((selectedOption) => selectedOption === option);
1510
+ if (this._pendingValue != null) {
1511
+ this.queuePendingValue(this._pendingValue);
1619
1512
  }
1620
-
1621
- return this.optionSelected === option;
1622
1513
  }
1623
1514
 
1624
1515
  /**
1625
- * Getter for loading placeholder state.
1626
- * @returns {boolean} - True if loading slots are present and non-empty.
1516
+ * Removes a menu option from the service's list.
1517
+ * @param {AuroMenuOption} option - the option to remove
1627
1518
  */
1628
- get hasLoadingPlaceholder() {
1629
- return this.loadingSlots && this.loadingSlots.length > 0;
1519
+ removeMenuOption(option) {
1520
+ this._menuOptions = this._menuOptions.filter(opt => opt !== option);
1521
+ this.notify({ type: 'optionsChange', options: this._menuOptions });
1522
+
1523
+ if (this._menuOptions.length === 0) {
1524
+ this.clearPendingValue();
1525
+ }
1630
1526
  }
1631
1527
 
1632
1528
  /**
1633
- * Getter for wrapper classes based on size.
1634
- * @returns {Object} - Class map for the wrapper element.
1635
- * @private
1529
+ * UTILITIES
1636
1530
  */
1637
- get wrapperClasses() {
1638
- return e({
1639
- 'menuWrapper': true,
1640
- [this.size]: true,
1641
- });
1642
- }
1643
1531
 
1644
1532
  /**
1645
- * Logic to determine the layout of the component.
1646
- * @protected
1647
- * @returns {void}
1533
+ * Normalizes a value or array of values into an array of strings for option selection.
1534
+ * This function ensures that input values are consistently formatted for matching menu options.
1535
+ *
1536
+ * @param {string|number|Array<string|number>} value - The value(s) to normalize.
1537
+ * @returns {Array<string>} An array of string values suitable for option matching.
1538
+ * @throws {Error} If any value is not a string or number.
1648
1539
  */
1649
- renderLayout() {
1650
- if (this.loading) {
1651
- return b`
1652
- <div class="${this.wrapperClasses}">
1653
- <auro-menuoption
1654
- disabled
1655
- loadingplaceholder
1656
- class="${this.hasLoadingPlaceholder ? "" : "empty"}"
1657
- >
1658
- <div>
1659
- <slot name="loadingIcon" class="body-lg"></slot>
1660
- <slot name="loadingText"></slot>
1661
- </div>
1662
- </auro-menuoption>
1663
- </div>
1664
- `;
1665
- }
1540
+ _getNormalizedValues(value) {
1541
+ let values = value;
1666
1542
 
1667
- return b`
1668
- <div class="${this.wrapperClasses}">
1669
- <slot @slotchange=${this.handleSlotChange}></slot>
1670
- </div>
1671
- `;
1672
- }
1673
- }
1543
+ // Handle JSON string and single value string input
1544
+ if (!Array.isArray(values) && typeof values === 'string') {
1674
1545
 
1675
- /**
1676
- * @license
1677
- * Copyright 2020 Google LLC
1678
- * SPDX-License-Identifier: BSD-3-Clause
1679
- */
1680
- const a=Symbol.for(""),o$1=t=>{if(t?.r===a)return t?._$litStatic$},s=t=>({_$litStatic$:t,r:a}),i=(t,...r)=>({_$litStatic$:r.reduce((r,e,a)=>r+(t=>{if(void 0!==t._$litStatic$)return t._$litStatic$;throw Error(`Value passed to 'literal' function must be a 'literal' result: ${t}. Use 'unsafeStatic' to pass non-literal values, but\n take care to ensure page security.`)})(e)+t[a+1],t[0]),r:a}),l=new Map,n=t=>(r,...e)=>{const a=e.length;let s,i;const n=[],u=[];let c,$=0,f=false;for(;$<a;){for(c=r[$];$<a&&void 0!==(i=e[$],s=o$1(i));)c+=s+r[++$],f=true;$!==a&&u.push(i),n.push(c),$++;}if($===a&&n.push(r[a]),f){const t=n.join("$$lit$$");void 0===(r=l.get(t))&&(n.raw=n,l.set(t,r=n)),e=u;}return t(r,...e)},u$1=n(b);
1546
+ // Attempt to parse as JSON array
1547
+ try {
1681
1548
 
1682
- var styleCss = i$6`.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}`;
1549
+ // Normalize single quotes to double quotes for JSON parsing
1550
+ // This will not handle complex cases but will cover basic usage
1551
+ const parseValue = values.replace(/'([^']*?)'/g, '"$1"');
1683
1552
 
1684
- var colorCss = i$6`: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)}}`;
1553
+ // Attempt parse
1554
+ const parsed = JSON.parse(parseValue);
1685
1555
 
1686
- // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1687
- // See LICENSE in the project root for license information.
1556
+ // Ensure parsed value is an array
1557
+ if (!Array.isArray(parsed)) throw new Error('Not an array');
1688
1558
 
1559
+ // Set values to parsed array
1560
+ values = parsed;
1561
+ } catch (err) {
1689
1562
 
1690
- class AuroDependencyVersioning {
1563
+ // If parsing fails, treat as single value
1564
+ values = [value];
1565
+ }
1566
+ }
1691
1567
 
1692
- /**
1693
- * Generates a unique string to be used for child auro element naming.
1694
- * @private
1695
- * @param {string} baseName - Defines the first part of the unique element name.
1696
- * @param {string} version - Version of the component that will be appended to the baseName.
1697
- * @returns {string} - Unique string to be used for naming.
1698
- */
1699
- generateElementName(baseName, version) {
1700
- let result = baseName;
1568
+ // Handle a single number being passed
1569
+ if (typeof values === 'number') {
1570
+ values = [String(values)];
1571
+ }
1701
1572
 
1702
- result += '-';
1703
- result += version.replace(/[.]/g, '_');
1573
+ // Coerce each value to string and validate types
1574
+ values.forEach((val, index) => {
1704
1575
 
1705
- return result;
1576
+ // Throw an error for invalid value types
1577
+ if (typeof val !== 'string' && typeof val !== 'number') {
1578
+ throw new Error('Value contains invalid value type. Supported types are string and number.');
1579
+ }
1580
+
1581
+ // Convert numbers to strings for consistency
1582
+ if (typeof val === 'number') {
1583
+ values[index] = String(val);
1584
+ }
1585
+ });
1586
+
1587
+ // Return the resulting array of string values
1588
+ return values;
1706
1589
  }
1707
1590
 
1708
1591
  /**
1709
- * Generates a unique string to be used for child auro element naming.
1710
- * @param {string} baseName - Defines the first part of the unique element name.
1711
- * @param {string} version - Version of the component that will be appended to the baseName.
1712
- * @returns {string} - Unique string to be used for naming.
1592
+ * Returns whether two arrays of options contain the same elements.
1593
+ * @param {AuroMenuOption[]} arr1 - First array of options.
1594
+ * @param {AuroMenuOption[]} arr2 - Second array of options.
1595
+ * @returns {boolean} True if arrays match, false otherwise.
1713
1596
  */
1714
- generateTag(baseName, version, tagClass) {
1715
- const elementName = this.generateElementName(baseName, version);
1716
- const tag = i`${s(elementName)}`;
1597
+ optionsArraysMatch(arr1, arr2) {
1598
+ if (arr1.length !== arr2.length) return false;
1717
1599
 
1718
- if (!customElements.get(elementName)) {
1719
- customElements.define(elementName, class extends tagClass {});
1600
+ const set1 = new Set(arr1);
1601
+ const set2 = new Set(arr2);
1602
+
1603
+ for (let item of set1) {
1604
+ if (!set2.has(item)) {
1605
+ return false;
1606
+ }
1720
1607
  }
1721
1608
 
1722
- return tag;
1609
+ return true;
1723
1610
  }
1724
1611
  }
1725
1612
 
1726
- /**
1727
- * @license
1728
- * Copyright 2018 Google LLC
1729
- * SPDX-License-Identifier: BSD-3-Clause
1730
- */const o=o=>o??A;
1731
-
1732
- 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$3{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$6`: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}
1733
- `;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$6`.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}
1734
- `;var y=i$6`: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)}
1735
- `;var x=i$6`: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)}
1736
- `;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`
1737
- <div class="componentWrapper">
1738
- <div
1739
- class="${e({svgWrapper:true})}"
1740
- title="${o(this.title||void 0)}">
1741
- <span aria-hidden="${o(this.ariaHidden||true)}" part="svg">
1742
- ${this.customSvg?b`
1743
- <slot name="svg"></slot>
1744
- `:b`
1745
- ${this.svg}
1746
- `}
1747
- </span>
1748
- </div>
1749
-
1750
- <div class="${e(t)}" part="label">
1751
- <slot></slot>
1752
- </div>
1753
- </div>
1754
- `}}
1755
-
1756
- var iconVersion = '9.1.2';
1757
-
1758
- 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>"};
1613
+ const MenuContext = n$1('menu-context');
1759
1614
 
1760
- // Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1615
+ /* eslint-disable no-underscore-dangle */
1616
+ // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1761
1617
  // See LICENSE in the project root for license information.
1762
1618
 
1763
1619
 
1764
- let menuOptionIdCounter = 0;
1765
1620
 
1766
1621
  /**
1767
- * The `auro-menuoption` element provides users a way to define a menu option.
1768
- * @customElement auro-menuoption
1769
- *
1770
- * @slot default - The default slot for the menu option text.
1622
+ * The `auro-menu` element provides users a way to select from a list of options.
1623
+ * @customElement auro-menu
1771
1624
  *
1772
- * @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
1625
+ * @event {CustomEvent<Element>} auroMenu-activatedOption - Notifies that a menuoption has been made `active`.
1626
+ * @event {CustomEvent<any>} auroMenu-customEventFired - Notifies that a custom event has been fired.
1627
+ * @event {CustomEvent<{ loading: boolean; hasLoadingPlaceholder: boolean; }>} auroMenu-loadingChange - Notifies when the loading attribute is changed.
1628
+ * @event {CustomEvent<any>} auroMenu-selectValueFailure - Notifies that an attempt to select a menuoption by matching a value has failed.
1629
+ * @event {CustomEvent<{ values: HTMLElement[] }>} auroMenu-deselectPrevented - Notifies that deselection was prevented and includes the affected options in `detail.values`.
1630
+ * @event {CustomEvent<any>} auroMenu-selectValueReset - Notifies that the component value has been reset.
1631
+ * @event {CustomEvent<any>} auroMenu-selectedOption - Notifies that a new menuoption selection has been made.
1632
+ * @slot loadingText - Text to show while loading attribute is set
1633
+ * @slot loadingIcon - Icon to show while loading attribute is set
1634
+ * @slot - Slot for insertion of menu options.
1773
1635
  */
1774
- class AuroMenuOption extends AuroElement {
1775
1636
 
1776
- /**
1777
- * This will register this element with the browser.
1778
- * @param {string} [name="auro-menuoption"] - The name of the element that you want to register.
1779
- *
1780
- * @example
1781
- * AuroMenuOption.register("custom-menuoption") // this will register this element to <custom-menuoption/>
1782
- *
1783
- */
1784
- static register(name = "auro-menuoption") {
1785
- AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroMenuOption);
1786
- }
1637
+ /* eslint-disable max-lines */
1787
1638
 
1788
- /**
1789
- * Returns whether the menu option is currently active and selectable.
1790
- * An option is considered active if it is not hidden, not disabled, and not static.
1791
- * @returns {boolean} True if the option is active, false otherwise.
1792
- */
1793
- get isActive() {
1794
- return !this.hasAttribute('hidden') &&
1795
- !this.disabled &&
1796
- !this.hasAttribute('static');
1797
- }
1639
+ class AuroMenu extends AuroElement {
1798
1640
 
1799
1641
  constructor() {
1800
1642
  super();
1801
1643
 
1802
- this.bindEvents();
1644
+ // State properties (reactive)
1803
1645
 
1804
1646
  /**
1805
1647
  * @private
1806
1648
  */
1807
- this.shape = undefined;
1649
+ this.shape = "box";
1808
1650
 
1809
1651
  /**
1810
1652
  * @private
1811
1653
  */
1812
- this.size = undefined;
1813
-
1814
- /**
1815
- * Generate unique names for dependency components.
1816
- */
1817
- const versioning = new AuroDependencyVersioning();
1818
- this.iconTag = versioning.generateTag('auro-formkit-menuoption-icon', iconVersion, _);
1654
+ this.size = "sm";
1819
1655
 
1820
- this.selected = false;
1656
+ // Value of the selected options
1657
+ this.value = undefined;
1658
+ // Currently selected option
1659
+ this.optionSelected = undefined;
1660
+ // String used for highlighting/filtering
1661
+ this.matchWord = undefined;
1662
+ // Hide the checkmark icon on selected options
1821
1663
  this.noCheckmark = false;
1822
- this.disabled = false;
1823
- this.noMatch = false;
1664
+ // Currently active option
1665
+ this.optionActive = undefined;
1666
+ // Loading state
1667
+ this.loading = false;
1668
+ // Multi-select mode
1669
+ this.multiSelect = false;
1670
+ // Allow deselecting of menu options
1671
+ this.allowDeselect = false;
1672
+ // Select all matching options when setting value in multi-select mode
1673
+ this.selectAllMatchingOptions = false;
1674
+
1675
+ // Event Bindings
1824
1676
 
1825
1677
  /**
1826
1678
  * @private
1827
1679
  */
1828
- this.runtimeUtils = new AuroLibraryRuntimeUtils();
1680
+ this.handleSlotChange = this.handleSlotChange.bind(this);
1829
1681
 
1830
- // Initialize context-related properties
1831
- this.menuService = null;
1832
- this.unsubscribe = null;
1682
+ // Instance properties (non-reactive)
1833
1683
 
1834
1684
  /**
1835
1685
  * @private
1836
1686
  */
1837
- this.handleMenuChange = this.handleMenuChange.bind(this);
1687
+ Object.assign(this, {
1688
+ // Root-level menu (true) or a nested submenu (false)
1689
+ rootMenu: true,
1690
+ // Currently focused/active menu item index
1691
+ _index: -1,
1692
+ // Nested menu spacer
1693
+ nestingSpacer: '<span class="nestingSpacer"></span>',
1694
+ // Loading indicator for slot elements
1695
+ loadingSlots: null,
1696
+ });
1838
1697
  }
1839
1698
 
1840
1699
  static get properties() {
@@ -1842,7 +1701,15 @@ class AuroMenuOption extends AuroElement {
1842
1701
  ...super.properties,
1843
1702
 
1844
1703
  /**
1845
- * When true, disables the menu option.
1704
+ * Allows deselecting an already selected option when clicked again in single-select mode.
1705
+ */
1706
+ allowDeselect: {
1707
+ type: Boolean,
1708
+ reflect: true,
1709
+ },
1710
+
1711
+ /**
1712
+ * When true, the entire menu and all options are disabled.
1846
1713
  */
1847
1714
  disabled: {
1848
1715
  type: Boolean,
@@ -1850,11 +1717,10 @@ class AuroMenuOption extends AuroElement {
1850
1717
  },
1851
1718
 
1852
1719
  /**
1853
- * @private
1720
+ * Indicates whether the menu has a loadingIcon or loadingText to render when in a loading state.
1854
1721
  */
1855
- event: {
1856
- type: String,
1857
- reflect: true
1722
+ hasLoadingPlaceholder: {
1723
+ type: Boolean
1858
1724
  },
1859
1725
 
1860
1726
  /**
@@ -1865,394 +1731,528 @@ class AuroMenuOption extends AuroElement {
1865
1731
  },
1866
1732
 
1867
1733
  /**
1868
- * 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.
1734
+ * Indent level for submenus.
1735
+ * @private
1869
1736
  */
1870
- key: {
1871
- type: String,
1872
- reflect: true
1737
+ level: {
1738
+ type: Number,
1739
+ reflect: false,
1740
+ attribute: false
1873
1741
  },
1874
1742
 
1875
1743
  /**
1876
- * @private
1744
+ * When true, displays a loading state using the loadingIcon and loadingText slots if provided.
1877
1745
  */
1878
- menuService: {
1879
- type: Object,
1880
- state: true
1746
+ loading: {
1747
+ type: Boolean,
1748
+ reflect: true
1881
1749
  },
1882
1750
 
1883
1751
  /**
1884
- * @private
1752
+ * Specifies a string used to highlight matched string parts in options.
1885
1753
  */
1886
1754
  matchWord: {
1887
1755
  type: String,
1888
- state: true
1756
+ attribute: 'matchword'
1889
1757
  },
1890
1758
 
1891
1759
  /**
1892
- * @private
1760
+ * When true, the selected option can be multiple options.
1893
1761
  */
1894
- noCheckmark: {
1762
+ multiSelect: {
1895
1763
  type: Boolean,
1896
- reflect: true
1764
+ reflect: true,
1765
+ attribute: 'multiselect'
1897
1766
  },
1898
1767
 
1899
1768
  /**
1900
- * 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.
1769
+ * When true, selected option will not show the checkmark.
1901
1770
  */
1902
- noMatch: {
1771
+ noCheckmark: {
1903
1772
  type: Boolean,
1904
1773
  reflect: true,
1905
- attribute: 'nomatch'
1774
+ attribute: 'nocheckmark'
1906
1775
  },
1907
1776
 
1908
1777
  /**
1909
- * Specifies that an option is selected.
1778
+ * Specifies the current active menuOption.
1910
1779
  */
1911
- selected: {
1912
- type: Boolean,
1913
- reflect: true
1780
+ optionActive: {
1781
+ type: Object,
1782
+ attribute: 'optionactive'
1914
1783
  },
1915
1784
 
1916
1785
  /**
1917
- * Specifies the tab index of the menu option.
1786
+ * An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
1918
1787
  */
1919
- tabIndex: {
1920
- type: Number,
1788
+ optionSelected: {
1789
+ // Allow HTMLElement, HTMLElement[] arrays and undefined
1790
+ type: Object
1791
+ },
1792
+
1793
+ /**
1794
+ * Available menu options.
1795
+ * @readonly
1796
+ */
1797
+ options: {
1798
+ type: Array,
1799
+ reflect: false,
1800
+ attribute: false
1801
+ },
1802
+
1803
+ /**
1804
+ * Sets the size of the menu.
1805
+ * @type {'sm' | 'md'}
1806
+ * @default 'sm'
1807
+ */
1808
+ size: {
1809
+ type: String,
1921
1810
  reflect: true
1922
1811
  },
1923
1812
 
1924
1813
  /**
1925
- * Specifies the value to be sent to a server.
1814
+ * When true, selects all options that match the provided value/key when setting value and multiselect is enabled.
1926
1815
  */
1927
- value: {
1816
+ selectAllMatchingOptions: {
1817
+ type: Boolean,
1818
+ reflect: true,
1819
+ },
1820
+
1821
+ /**
1822
+ * Sets the shape of the menu.
1823
+ * @type {'box' | 'round'}
1824
+ * @default 'box'
1825
+ */
1826
+ shape: {
1928
1827
  type: String,
1929
1828
  reflect: true
1930
1829
  },
1830
+
1831
+ /**
1832
+ * The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
1833
+ */
1834
+ value: {
1835
+ type: String,
1836
+ reflect: true,
1837
+ attribute: 'value'
1838
+ }
1931
1839
  };
1932
1840
  }
1933
1841
 
1934
1842
  static get styles() {
1935
1843
  return [
1936
- styleCss,
1937
- colorCss,
1844
+ styleCss$1,
1845
+ colorCss$1,
1938
1846
  tokensCss
1939
1847
  ];
1940
1848
  }
1941
1849
 
1942
- connectedCallback() {
1943
- super.connectedCallback();
1944
-
1945
- // Add the tag name as an attribute if it is different than the component name
1946
- // Add this step soon as this node gets attached to the DOM to avoid racing condition with menu's value setting logic.
1947
- this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
1850
+ /**
1851
+ * @readonly
1852
+ * @returns {string} - Returns the label of the currently selected option(s).
1853
+ */
1854
+ get currentLabel() {
1855
+ return this.menuService.currentLabel;
1856
+ };
1857
+
1858
+ /**
1859
+ * @readonly
1860
+ * @returns {Array<HTMLElement>} - Returns the array of available menu options.
1861
+ * @deprecated Use `options` property instead.
1862
+ */
1863
+ get items() {
1864
+ return this.options;
1865
+ }
1866
+
1867
+ /**
1868
+ * @returns {number} - Returns the index of the currently active option.
1869
+ */
1870
+ get index() {
1871
+ return this._index;
1872
+ }
1873
+
1874
+ /**
1875
+ * @param {number} value - Sets the index of the currently active option.
1876
+ */
1877
+ set index(value) {
1878
+ this.menuService.setHighlightedIndex(value);
1879
+ }
1880
+
1881
+ /**
1882
+ * This will register this element with the browser.
1883
+ * @param {string} [name="auro-menu"] - The name of the element that you want to register.
1884
+ *
1885
+ * @example
1886
+ * AuroMenu.register("custom-menu") // this will register this element to <custom-menu/>
1887
+ *
1888
+ */
1889
+ static register(name = "auro-menu") {
1890
+ AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroMenu);
1891
+ }
1892
+
1893
+ /**
1894
+ * Formatted value based on `multiSelect` state.
1895
+ * Default type is `String`, changing to `Array<String>` when `multiSelect` is true.
1896
+ * @private
1897
+ * @returns {String|Array<String>}
1898
+ */
1899
+ get formattedValue() {
1900
+ return this.menuService.currentValue;
1901
+ }
1902
+
1903
+ /**
1904
+ * Gets the current property values for the menu service.
1905
+ * @private
1906
+ * @returns {Object}
1907
+ */
1908
+ get propertyValues() {
1909
+ return {
1910
+ size: this.size,
1911
+ shape: this.shape,
1912
+ noCheckmark: this.nocheckmark,
1913
+ disabled: this.disabled
1914
+ };
1915
+ }
1916
+
1917
+ /**
1918
+ * Provides the menu context to child components.
1919
+ * Initializes the MenuService and subscribes to menu changes.
1920
+ * @protected
1921
+ */
1922
+ provideContext() {
1923
+ if (this.parentElement && this.parentElement.closest('auro-menu, [auro-menu]')) {
1924
+ this.rootMenu = false;
1925
+ this.menuService = this.parentElement.menuService;
1926
+ this._contextProvider = this.parentElement._contextProvider;
1927
+ return;
1928
+ }
1948
1929
 
1949
- // Set up context consumption in connectedCallback
1950
- this._contextConsumer = new s$2(this, {
1930
+ this.menuService = new MenuService({host: this});
1931
+ this.menuService.setProperties(this.propertyValues);
1932
+ this.menuService.subscribe(this.handleMenuChange.bind(this));
1933
+ this._contextProvider = new i$2(this, {
1951
1934
  context: MenuContext,
1952
- callback: this.attachTo.bind(this),
1953
- subscribe: true
1935
+ initialValue: this.menuService
1954
1936
  });
1937
+ }
1955
1938
 
1956
- // Establish the key property as early as possible.
1957
- // When a framework (e.g. Svelte) inserts the element into the DOM before
1958
- // setting its `value` property, both `getAttribute('value')` and
1959
- // `getAttribute('key')` return null here. Setting `this.key = null`
1960
- // would block the fallback in `updated()` that assigns key from the
1961
- // value property (the guard checked `=== undefined`). Only assign key
1962
- // if at least one source attribute is actually present so that the
1963
- // `updated()` fallback can run when the value property arrives later.
1964
- const valueAttr = this.getAttribute('value');
1965
- const keyAttr = this.getAttribute('key');
1966
- const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
1967
- if (resolvedKey !== null) {
1968
- this.key = resolvedKey;
1969
- }
1939
+ /**
1940
+ * Updates the currently active option in the menu.
1941
+ * @param {HTMLElement} option - The option to set as active.
1942
+ */
1943
+ updateActiveOption(option) {
1944
+ this.menuService.setHighlightedOption(option);
1970
1945
  }
1971
1946
 
1972
- firstUpdated() {
1973
- // Add the tag name as an attribute if it is different than the component name
1974
- this.runtimeUtils.handleComponentTagRename(this, 'auro-menuoption');
1947
+ /**
1948
+ * Sets the internal value and manages update state.
1949
+ * @param {String|Array<String>} value - The value to set.
1950
+ * @protected
1951
+ */
1952
+ setInternalValue(value) {
1953
+ if (this.value !== value) {
1954
+ this.internalUpdateInProgress = true;
1955
+ this.value = value;
1975
1956
 
1976
- // Generate unique ID if not already set (required for aria-activedescendant)
1977
- if (!this.id) {
1978
- menuOptionIdCounter += 1;
1979
- this.id = `menuoption-${menuOptionIdCounter}`;
1957
+ setTimeout(() => {
1958
+ this.internalUpdateInProgress = false;
1959
+ });
1980
1960
  }
1981
-
1982
- this.setAttribute('role', 'option');
1983
- this.setAttribute('aria-selected', 'false');
1984
-
1985
- this.addEventListener('mouseover', () => {
1986
- this.dispatchEvent(new CustomEvent('auroMenuOption-mouseover', {
1987
- bubbles: true,
1988
- cancelable: false,
1989
- composed: true,
1990
- detail: this
1991
- }));
1992
- });
1993
1961
  }
1994
1962
 
1995
- updated(changedProperties) {
1996
- super.updated(changedProperties);
1997
-
1998
- // Update aria-selected attribute if selected changed
1999
- if (changedProperties.has('selected')) {
2000
-
2001
- // Update aria-selected attribute
2002
- this.setAttribute('aria-selected', this.selected.toString());
1963
+ /**
1964
+ * Handles changes from the menu service and updates component state.
1965
+ * @param {Object} event - The event object from the menu service.
1966
+ * @protected
1967
+ */
1968
+ handleMenuChange(event) {
1969
+ if (event.type === 'valueChange') {
2003
1970
 
2004
- // Update menu service selection state if this isn't an internal update
2005
- if (this.internalUpdateInProgress !== true) {
2006
- this.menuService[this.selected ? 'selectOption' : 'deselectOption'](this);
2007
- }
2008
- }
1971
+ // New option is array value or first option with fallback to undefined for empty array in all cases
1972
+ const newOption = this.multiSelect && event.options.length ? event.options : event.options[0] || undefined;
1973
+ const newValue = event.stringValue;
2009
1974
 
2010
- if (changedProperties.has('disabled')) {
2011
- if (this.disabled) {
2012
- this.setAttribute('aria-disabled', 'true');
2013
- } else {
2014
- this.removeAttribute('aria-disabled');
1975
+ // Check if the option or value has actually changed
1976
+ if (this.optionSelected !== newOption || this.stringValue !== newValue) {
1977
+ this.optionSelected = newOption;
1978
+ this.setInternalValue(newValue);
2015
1979
  }
2016
- }
2017
-
2018
- if (changedProperties.has('active')) {
2019
- this.updateActiveClasses();
2020
- }
2021
1980
 
2022
- // Update text highlight if matchWord changed
2023
- if (changedProperties.has('matchWord')) {
2024
- this.updateTextHighlight();
1981
+ // Notify components of selection change
1982
+ this.notifySelectionChange(event);
2025
1983
  }
2026
1984
 
2027
- // Set the key to be the passed value if no key is provided.
2028
- // Loose equality (== null) is intentional: it catches both null AND
2029
- // undefined. When a framework (e.g. Svelte, React) inserts the element
2030
- // before setting its value property, connectedCallback skips key
2031
- // assignment because both attributes are null at that point. The Lit
2032
- // property default for `key` is undefined (not null), so strict
2033
- // === null would miss the case and the fallback would never run.
2034
- if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
2035
- this.key = this.value;
1985
+ if (event.type === 'highlightChange') {
1986
+ this.optionActive = event.option;
1987
+ this._index = event.index;
2036
1988
  }
2037
- }
2038
1989
 
2039
- disconnectedCallback() {
2040
- if (this.menuService) {
2041
- this.menuService.unsubscribe(this.handleMenuChange);
2042
- this.menuService.removeMenuOption(this);
1990
+ if (event.type === 'optionsChange') {
1991
+ this.options = event.options;
1992
+ this.dispatchEvent(new CustomEvent('auroMenu-optionsChange', {
1993
+ detail: {
1994
+ options: event.options
1995
+ }
1996
+ }));
2043
1997
  }
2044
1998
  }
2045
1999
 
2046
2000
  /**
2047
- * Sets up event listeners for user interaction with the menu option.
2048
- * This function enables click and mouse enter events to trigger selection and highlighting logic.
2001
+ * Gets the currently selected options.
2002
+ * @returns {Array<HTMLElement>}
2049
2003
  */
2050
- bindEvents() {
2051
- this.addEventListener('click', this.handleClick.bind(this));
2052
- this.addEventListener('mouseenter', this.handleMouseEnter.bind(this));
2004
+ get selectedOptions() {
2005
+ return this.menuService ? this.menuService.selectedOptions : [];
2053
2006
  }
2054
2007
 
2055
2008
  /**
2056
- * Attaches this menu option to a menu service and subscribes to its events.
2057
- * This method enables the option to participate in menu selection and highlighting logic.
2058
- * @param {Object} service - The menu service instance to attach to.
2009
+ * Gets the first selected option, or null if none.
2010
+ * @returns {HTMLElement|null}
2059
2011
  */
2060
- attachTo(service) {
2061
- if (!service) {
2062
- return;
2063
- }
2064
- this.menuService = service;
2065
- this.menuService.addMenuOption(this);
2066
- this.menuService.subscribe(this.handleMenuChange);
2012
+ get selectedOption() {
2013
+ return this.menuService ? this.menuService.selectedOptions[0] : null;
2067
2014
  }
2068
2015
 
2069
- /**
2070
- * Handles changes from the menu service and updates the option's state.
2071
- * This function synchronizes the option's properties and selection/highlight state with menu events.
2072
- * @param {Object} event - The event object from the menu service.
2073
- */
2074
- handleMenuChange(event) {
2016
+ // Lifecycle Methods
2075
2017
 
2076
- // Ignore events without a type or property
2077
- if (!event || (!event.type && !event.property)) {
2078
- return;
2018
+ connectedCallback() {
2019
+ super.connectedCallback();
2020
+
2021
+ this.provideContext();
2022
+
2023
+ // this.addEventListener('keydown', this.handleKeyDown);
2024
+ this.addEventListener('auroMenuOption-click', this.handleMouseSelect);
2025
+ this.addEventListener('auroMenuOption-mouseover', this.handleOptionHover);
2026
+ this.addEventListener('slotchange', this.handleSlotChange);
2027
+ this.setTagAttribute("auro-menu");
2028
+ }
2029
+
2030
+ disconnectedCallback() {
2031
+ // this.removeEventListener('keydown', this.handleKeyDown);
2032
+ this.removeEventListener('auroMenuOption-click', this.handleMouseSelect);
2033
+ this.removeEventListener('auroMenuOption-mouseover', this.handleOptionHover);
2034
+ this.removeEventListener('slotchange', this.handleSlotChange);
2035
+
2036
+ super.disconnectedCallback();
2037
+ }
2038
+
2039
+ firstUpdated() {
2040
+ AuroLibraryRuntimeUtils.prototype.handleComponentTagRename(this, 'auro-menu');
2041
+
2042
+ this.loadingSlots = this.querySelectorAll("[slot='loadingText'], [slot='loadingIcon']");
2043
+ this.initializeMenu();
2044
+ }
2045
+
2046
+
2047
+ updated(changedProperties) {
2048
+ super.updated(changedProperties);
2049
+
2050
+ // Apply value selection synchronously so that static-HTML fixtures
2051
+ // resolve within a single update cycle. The refactored selectByValue
2052
+ // no longer calls reset() first, so the destructive intermediate-event
2053
+ // cascade that originally required deferral is eliminated. If option
2054
+ // keys are not yet resolved (framework mount-order race), selectByValue
2055
+ // queues a bounded retry automatically via queuePendingValue.
2056
+ if (changedProperties.has('value') && !this.internalUpdateInProgress) {
2057
+ this.menuService.selectByValue(this.value);
2079
2058
  }
2080
2059
 
2081
- // Update reactive properties based on event type
2082
- if (event.property && Object.keys(AuroMenuOption.properties).includes(event.property)) {
2083
- this[event.property] = event.value;
2060
+ // Handle loading state changes
2061
+ if (changedProperties.has('loading')) {
2062
+ this.setLoadingState(this.loading);
2084
2063
  }
2085
2064
 
2086
- // Handle highlight changes
2087
- if (event.type === 'highlightChange') {
2088
- const isActive = event.option === this;
2089
- this.active = isActive;
2090
- this.updateActiveClasses();
2065
+ if (changedProperties.has('multiSelect') && this.rootMenu) {
2066
+ if (this.multiSelect) {
2067
+ this.setAttribute('aria-multiselectable', 'true');
2068
+ } else {
2069
+ this.removeAttribute('aria-multiselectable');
2070
+ }
2091
2071
  }
2072
+ }
2092
2073
 
2093
- if (event.type === 'stateChange') {
2094
- const isSelected = event.selectedOptions.includes(this);
2095
- this.setInternalSelected(isSelected);
2074
+ /**
2075
+ * Sets an attribute that matches the default tag name if the tag name is not the default.
2076
+ * @param {string} tagName - The tag name to set as an attribute.
2077
+ * @private
2078
+ */
2079
+ setTagAttribute(tagName) {
2080
+ if (this.tagName.toLowerCase() !== tagName) {
2081
+ this.setAttribute(tagName, true);
2096
2082
  }
2097
2083
  }
2098
2084
 
2099
2085
  /**
2100
- * Updates the internal selected state of the menu option bypassing 'updated' and triggers custom events if selected.
2101
- * This function ensures the option's selection state is synchronized with menu logic and notifies listeners.
2102
- * @param {boolean} isSelected - Whether the option should be marked as selected.
2086
+ * Sets the loading state and dispatches a loading change event.
2087
+ * @param {boolean} isLoading - Whether the menu is loading.
2088
+ * @protected
2089
+ */
2090
+ setLoadingState(isLoading) {
2091
+ this.setAttribute("aria-busy", isLoading);
2092
+ dispatchMenuEvent(this, "auroMenu-loadingChange", {
2093
+ loading: isLoading,
2094
+ hasLoadingPlaceholder: this.hasLoadingPlaceholder
2095
+ });
2096
+ }
2097
+
2098
+ // Init Methods
2099
+
2100
+ /**
2101
+ * Initializes the menu's state and structure.
2102
+ * @private
2103
2103
  */
2104
- setInternalSelected(isSelected) {
2105
- this.internalUpdateInProgress = true;
2106
- this.selected = isSelected;
2104
+ initializeMenu() {
2105
+ if (this.rootMenu) {
2106
+ this.setAttribute('role', 'listbox');
2107
+ this.setAttribute('root', '');
2107
2108
 
2108
- // Fire custom event if selected
2109
- if (isSelected) {
2110
- this.handleCustomEvent();
2109
+ if (this.multiSelect) {
2110
+ this.setAttribute('aria-multiselectable', 'true');
2111
+ }
2111
2112
  }
2112
2113
 
2113
- setTimeout(() => {
2114
- this.internalUpdateInProgress = false;
2115
- }, 0);
2114
+ this.handleNestedMenus(this);
2116
2115
  }
2117
2116
 
2118
2117
  /**
2119
- * Sets the selected state of the menu option.
2120
- * This function updates whether the option is currently selected.
2121
- * @param {boolean} isSelected - Whether the option should be marked as selected.
2122
- * @deprecated Simply modify the `selected` property directly instead.
2118
+ * Selects the currently highlighted option.
2119
+ * @protected
2123
2120
  */
2124
- setSelected(isSelected) {
2125
- this.selected = isSelected;
2121
+ makeSelection() {
2122
+ this.menuService.selectHighlightedOption();
2126
2123
  }
2127
2124
 
2128
2125
  /**
2129
- * Updates the active state and visual highlighting of the menu option.
2130
- * This function toggles the option's active status and applies or removes the active CSS class.
2131
- * @param {boolean} isActive - Whether the option should be marked as active.
2132
- * @deprecated Simply modify the `active` property directly instead.
2126
+ * Resets all options to their default state.
2127
+ * @private
2133
2128
  */
2134
- updateActive(isActive) {
2135
-
2136
- // Set active state
2137
- this.active = isActive;
2138
- this.updateActiveClasses();
2129
+ clearSelection() {
2130
+ this.optionSelected = undefined;
2131
+ this.value = undefined;
2132
+ this._index = -1;
2139
2133
  }
2140
2134
 
2141
2135
  /**
2142
- * Updates the CSS class for the menu option based on its active state.
2143
- * This function adds or removes the 'active' class to visually indicate the option's active status.
2144
- * @private
2136
+ * Resets the menu to its initial state.
2137
+ * This is the only way to return value to undefined.
2138
+ * @public
2145
2139
  */
2146
- updateActiveClasses() {
2147
- // Update class based on active state
2148
- if (this.active) this.classList.add('active');
2149
- else this.classList.remove('active');
2150
- }
2140
+ reset() {
2141
+ this.menuService.reset();
2151
2142
 
2143
+ // Dispatch reset event
2144
+ dispatchMenuEvent(this, 'auroMenu-selectValueReset');
2145
+ }
2152
2146
 
2153
2147
  /**
2154
- * Updates the visual highlighting of text within the menu option based on the current match word.
2155
- * This function highlights matching text segments and manages nested spacers for display formatting.
2148
+ * Handles nested menu structure.
2156
2149
  * @private
2150
+ * @param {HTMLElement} menu - Root menu element.
2157
2151
  */
2158
- updateTextHighlight() {
2159
-
2160
- // Regex for matchWord if needed
2161
- let regexWord = null;
2162
-
2163
- if (this.matchWord && this.matchWord.length) {
2164
- const escapedWord = this.matchWord.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
2165
- regexWord = new RegExp(escapedWord, 'giu');
2166
- }
2167
-
2168
- // Update text highlighting if matchWord changed
2169
- if (regexWord &&
2170
- this.isActive && !this.hasAttribute('persistent')) {
2171
- const nested = this.querySelectorAll('.nestingSpacer');
2172
-
2173
- const displayValueEl = this.querySelector('[slot="displayValue"]');
2174
- if (displayValueEl) {
2175
- this.removeChild(displayValueEl);
2176
- }
2177
-
2178
- // Create nested spacers
2179
- const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
2152
+ handleNestedMenus(menu) {
2153
+ menu.level = menu.parentElement.level >= 0 ? menu.parentElement.level + 1 : 0;
2180
2154
 
2181
- // Update with spacers and matchWord
2182
- this.innerHTML = nestingSpacerBundle +
2183
- this.textContent.replace(
2184
- regexWord,
2185
- (match) => `<strong>${match}</strong>`
2186
- );
2187
- if (displayValueEl) {
2188
- this.append(displayValueEl);
2155
+ if (menu.level > 0) {
2156
+ menu.setAttribute('role', 'group');
2157
+ menu.removeAttribute("root");
2158
+ if (!menu.hasAttribute('aria-label')) {
2159
+ menu.setAttribute('aria-label', 'submenu');
2189
2160
  }
2190
2161
  }
2162
+
2163
+ const options = menu.querySelectorAll(':scope > auro-menuoption, :scope > [auro-menuoption]');
2164
+ options.forEach((option) => {
2165
+ const regex = new RegExp(this.nestingSpacer, "gu");
2166
+ option.innerHTML = this.nestingSpacer.repeat(menu.level) + option.innerHTML.replace(regex, '');
2167
+ });
2191
2168
  }
2192
2169
 
2193
2170
  /**
2194
- * Handles click events on the menu option, toggling its selected state.
2195
- * This function dispatches a click event and updates selection if the option is not disabled.
2196
- * @private
2171
+ * Navigates the menu options in the specified direction.
2172
+ * @param {'up'|'down'} direction - The direction to navigate.
2173
+ * @protected
2197
2174
  */
2198
- handleClick() {
2199
- if (!this.disabled) {
2200
- this.dispatchClickEvent();
2201
- this.selected = !this.selected;
2175
+ navigateOptions(direction) {
2176
+ if (direction === 'up') {
2177
+ this.menuService.highlightPrevious();
2178
+ } else if (direction === 'down') {
2179
+ this.menuService.highlightNext();
2202
2180
  }
2203
2181
  }
2204
2182
 
2205
2183
  /**
2206
- * Handles mouse enter events to highlight the menu option.
2207
- * This function updates the menu service to set this option as the currently highlighted item if not disabled.
2184
+ * Handles slot change events.
2208
2185
  * @private
2209
2186
  */
2210
- handleMouseEnter() {
2211
- if (!this.disabled) {
2212
- this.menuService.setHighlightedOption(this);
2187
+ handleSlotChange() {
2188
+ if (this.rootMenu) {
2189
+ this.initializeMenu();
2213
2190
  }
2214
2191
  }
2215
2192
 
2216
2193
  /**
2217
- * Dispatches custom events defined for this menu option.
2218
- * This function notifies listeners when a custom event is triggered by the option.
2194
+ * Handles custom events defined on options.
2219
2195
  * @private
2196
+ * @param {HTMLElement} option - Option with custom event.
2220
2197
  */
2221
- handleCustomEvent() {
2222
- if (this.event) {
2223
- dispatchMenuEvent(this, this.event, { option: this });
2224
- dispatchMenuEvent(this, 'auroMenu-customEventFired', { option: this });
2225
- }
2198
+ handleCustomEvent(option) {
2199
+ const eventName = option.getAttribute('event');
2200
+ dispatchMenuEvent(this, eventName);
2201
+ dispatchMenuEvent(this, 'auroMenu-customEventFired');
2226
2202
  }
2227
2203
 
2228
2204
  /**
2229
- * Dispatches a click event for this menu option.
2230
- * This function notifies listeners that the option has been clicked.
2205
+ * Notifies selection change to parent components.
2206
+ * @param {any} source - The source that triggers this event.
2231
2207
  * @private
2232
2208
  */
2233
- dispatchClickEvent() {
2234
- this.dispatchEvent(new CustomEvent('auroMenuOption-click', {
2235
- bubbles: true,
2236
- cancelable: false,
2237
- composed: true,
2238
- detail: this
2239
- }));
2209
+ notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
2210
+ dispatchMenuEvent(this, 'auroMenu-selectedOption', {
2211
+ value,
2212
+ stringValue,
2213
+ keys,
2214
+ options,
2215
+ reason
2216
+ });
2240
2217
  }
2241
2218
 
2242
2219
  /**
2243
- * Generates an HTML element containing an SVG icon based on the provided `svgContent`.
2244
- *
2220
+ * Checks if an option is currently selected.
2245
2221
  * @private
2246
- * @param {string} svgContent - The SVG content to be embedded.
2247
- * @returns {Element} The HTML element containing the SVG icon.
2222
+ * @param {HTMLElement} option - The option to check.
2223
+ * @returns {boolean}
2248
2224
  */
2249
- generateIconHtml(svgContent) {
2250
- const dom = new DOMParser().parseFromString(svgContent, 'text/html');
2251
- const svg = dom.body.firstChild;
2225
+ isOptionSelected(option) {
2226
+ if (!this.optionSelected) {
2227
+ return false;
2228
+ }
2252
2229
 
2253
- svg.setAttribute('slot', 'svg');
2230
+ if (this.multiSelect) {
2231
+ // In multi-select mode, check if the option is in the selected array
2232
+ return Array.isArray(this.optionSelected) && this.optionSelected.some((selectedOption) => selectedOption === option);
2233
+ }
2254
2234
 
2255
- return u$1`<${this.iconTag} customColor customSvg>${svg}</${this.iconTag}>`;
2235
+ return this.optionSelected === option;
2236
+ }
2237
+
2238
+ /**
2239
+ * Getter for loading placeholder state.
2240
+ * @returns {boolean} - True if loading slots are present and non-empty.
2241
+ */
2242
+ get hasLoadingPlaceholder() {
2243
+ return this.loadingSlots && this.loadingSlots.length > 0;
2244
+ }
2245
+
2246
+ /**
2247
+ * Getter for wrapper classes based on size.
2248
+ * @returns {Object} - Class map for the wrapper element.
2249
+ * @private
2250
+ */
2251
+ get wrapperClasses() {
2252
+ return e({
2253
+ 'menuWrapper': true,
2254
+ [this.size]: true,
2255
+ });
2256
2256
  }
2257
2257
 
2258
2258
  /**
@@ -2261,26 +2261,26 @@ class AuroMenuOption extends AuroElement {
2261
2261
  * @returns {void}
2262
2262
  */
2263
2263
  renderLayout() {
2264
+ if (this.loading) {
2265
+ return b`
2266
+ <div class="${this.wrapperClasses}">
2267
+ <auro-menuoption
2268
+ disabled
2269
+ loadingplaceholder
2270
+ class="${this.hasLoadingPlaceholder ? "" : "empty"}"
2271
+ >
2272
+ <div>
2273
+ <slot name="loadingIcon" class="body-lg"></slot>
2274
+ <slot name="loadingText"></slot>
2275
+ </div>
2276
+ </auro-menuoption>
2277
+ </div>
2278
+ `;
2279
+ }
2264
2280
 
2265
- const fontClassMap = {
2266
- xs: 'body-sm',
2267
- sm: 'body-default',
2268
- md: 'body-default',
2269
- lg: 'body-lg',
2270
- xl: 'body-lg'
2271
- };
2272
-
2273
- const classes = e({
2274
- 'wrapper': true,
2275
- [this.size ? fontClassMap[this.size] : 'body-sm']: true,
2276
- });
2277
-
2278
- return u$1`
2279
- <div class="${classes}">
2280
- ${this.selected && !this.noCheckmark
2281
- ? this.generateIconHtml(checkmarkIcon.svg)
2282
- : undefined}
2283
- <slot></slot>
2281
+ return b`
2282
+ <div class="${this.wrapperClasses}">
2283
+ <slot @slotchange=${this.handleSlotChange}></slot>
2284
2284
  </div>
2285
2285
  `;
2286
2286
  }