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

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