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