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