@aurodesignsystem-dev/auro-formkit 0.0.0-pr1452.0 → 0.0.0-pr1456.0

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