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