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