@aurodesignsystem/auro-formkit 6.0.2 → 6.0.3

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 (57) hide show
  1. package/CHANGELOG.md +8 -10
  2. package/components/checkbox/demo/customize.min.js +1 -1
  3. package/components/checkbox/demo/getting-started.min.js +20 -1
  4. package/components/checkbox/demo/index.min.js +1 -1
  5. package/components/checkbox/dist/index.js +1 -1
  6. package/components/checkbox/dist/registered.js +1 -1
  7. package/components/combobox/demo/customize.md +40 -0
  8. package/components/combobox/demo/customize.min.js +390 -56
  9. package/components/combobox/demo/getting-started.min.js +390 -56
  10. package/components/combobox/demo/index.html +1 -1
  11. package/components/combobox/demo/index.min.js +390 -56
  12. package/components/combobox/dist/index.js +68 -29
  13. package/components/combobox/dist/registered.js +68 -29
  14. package/components/counter/demo/customize.min.js +38 -3
  15. package/components/counter/demo/index.min.js +12 -3
  16. package/components/counter/dist/index.js +12 -3
  17. package/components/counter/dist/registered.js +12 -3
  18. package/components/datepicker/demo/customize.min.js +49 -5
  19. package/components/datepicker/demo/index.min.js +14 -5
  20. package/components/datepicker/dist/index.js +14 -5
  21. package/components/datepicker/dist/registered.js +14 -5
  22. package/components/dropdown/demo/customize.md +4 -4
  23. package/components/dropdown/demo/customize.min.js +53 -5
  24. package/components/dropdown/demo/getting-started.html +3 -0
  25. package/components/dropdown/demo/getting-started.md +4 -4
  26. package/components/dropdown/demo/getting-started.min.js +13087 -77
  27. package/components/dropdown/demo/index.min.js +30 -11
  28. package/components/dropdown/dist/index.js +10 -1
  29. package/components/dropdown/dist/registered.js +10 -1
  30. package/components/form/demo/customize.min.js +430 -69
  31. package/components/form/demo/getting-started.min.js +430 -69
  32. package/components/form/demo/index.min.js +430 -69
  33. package/components/form/demo/registerDemoDeps.min.js +430 -69
  34. package/components/input/demo/customize.min.js +1 -1
  35. package/components/input/demo/getting-started.min.js +1 -1
  36. package/components/input/demo/index.min.js +1 -1
  37. package/components/input/dist/index.js +1 -1
  38. package/components/input/dist/registered.js +1 -1
  39. package/components/menu/demo/customize.md +50 -0
  40. package/components/menu/demo/index.min.js +322 -27
  41. package/components/menu/dist/auro-menu-utils.d.ts +30 -0
  42. package/components/menu/dist/auro-menu.d.ts +23 -0
  43. package/components/menu/dist/index.js +322 -27
  44. package/components/menu/dist/registered.js +322 -27
  45. package/components/radio/demo/customize.min.js +1 -1
  46. package/components/radio/demo/getting-started.min.js +1 -1
  47. package/components/radio/demo/index.min.js +1 -1
  48. package/components/radio/dist/index.js +1 -1
  49. package/components/radio/dist/registered.js +1 -1
  50. package/components/select/demo/customize.md +72 -0
  51. package/components/select/demo/customize.min.js +333 -29
  52. package/components/select/demo/getting-started.min.js +333 -29
  53. package/components/select/demo/index.min.js +333 -29
  54. package/components/select/dist/index.js +11 -2
  55. package/components/select/dist/registered.js +11 -2
  56. package/custom-elements.json +1613 -1510
  57. package/package.json +1 -1
@@ -4836,7 +4836,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$2 {
4836
4836
  }
4837
4837
  };
4838
4838
 
4839
- var formkitVersion$1 = '202607291953';
4839
+ var formkitVersion$1 = '202608111803';
4840
4840
 
4841
4841
  class AuroElement extends i$2 {
4842
4842
  static get properties() {
@@ -5538,6 +5538,15 @@ class AuroDropdown extends AuroElement {
5538
5538
 
5539
5539
  connectedCallback() {
5540
5540
  super.connectedCallback();
5541
+ // Re-register trigger event listeners removed by floater.disconnect() in
5542
+ // disconnectedCallback(). Fires when a parent element (e.g. auro-drawer)
5543
+ // moves this element to a new DOM location. firstUpdated() won't re-run,
5544
+ // so without this the trigger click/keydown handlers are permanently gone.
5545
+ // Guard on floater.element so this is a no-op on the initial connect
5546
+ // (before firstUpdated() has called configure()).
5547
+ if (this.floater?.element) {
5548
+ this.floater.configure(this, 'auroDropdown', !this.disableKeyboardHandling);
5549
+ }
5541
5550
  }
5542
5551
 
5543
5552
  disconnectedCallback() {
@@ -6938,7 +6947,7 @@ class AuroHelpText extends i$2 {
6938
6947
  }
6939
6948
  }
6940
6949
 
6941
- var formkitVersion = '202607291953';
6950
+ var formkitVersion = '202608111803';
6942
6951
 
6943
6952
  var styleCss$2 = i$5`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}.body-default{font-size:var(--wcss-body-default-font-size, 1rem);font-weight:var(--wcss-body-default-weight, );line-height:var(--wcss-body-default-line-height, 1.5rem)}.body-default,.body-default-emphasized{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-default-emphasized{font-size:var(--wcss-body-default-emphasized-font-size, 1rem);font-weight:var(--wcss-body-default-emphasized-weight, );line-height:var(--wcss-body-default-emphasized-line-height, 1.5rem)}.body-lg{font-size:var(--wcss-body-lg-font-size, 1.125rem);font-weight:var(--wcss-body-lg-weight, );line-height:var(--wcss-body-lg-line-height, 1.625rem)}.body-lg,.body-lg-emphasized{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-lg-emphasized{font-size:var(--wcss-body-lg-emphasized-font-size, 1.125rem);font-weight:var(--wcss-body-lg-emphasized-weight, );line-height:var(--wcss-body-lg-emphasized-line-height, 1.625rem)}.body-sm{font-size:var(--wcss-body-sm-font-size, 0.875rem);font-weight:var(--wcss-body-sm-weight, );line-height:var(--wcss-body-sm-line-height, 1.25rem)}.body-sm,.body-sm-emphasized{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-sm-emphasized{font-size:var(--wcss-body-sm-emphasized-font-size, 0.875rem);font-weight:var(--wcss-body-sm-emphasized-weight, );line-height:var(--wcss-body-sm-emphasized-line-height, 1.25rem)}.body-xs{font-size:var(--wcss-body-xs-font-size, 0.75rem);font-weight:var(--wcss-body-xs-weight, );line-height:var(--wcss-body-xs-line-height, 1rem)}.body-xs,.body-xs-emphasized{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-xs-emphasized{font-size:var(--wcss-body-xs-emphasized-font-size, 0.75rem);font-weight:var(--wcss-body-xs-emphasized-weight, );line-height:var(--wcss-body-xs-emphasized-line-height, 1rem)}.body-2xs{font-size:var(--wcss-body-2xs-font-size, 0.625rem);font-weight:var(--wcss-body-2xs-weight, );line-height:var(--wcss-body-2xs-line-height, 0.875rem)}.body-2xs,.body-2xs-emphasized{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-2xs-emphasized{font-size:var(--wcss-body-2xs-emphasized-font-size, 0.625rem);font-weight:var(--wcss-body-2xs-emphasized-weight, );line-height:var(--wcss-body-2xs-emphasized-line-height, 0.875rem)}.display-2xl{font-family:var(--wcss-display-2xl-family, "AS Circular"),var(--wcss-display-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-2xl-font-size, clamp(3.5rem, 6vw, 5.375rem));font-weight:var(--wcss-display-2xl-weight, 300);letter-spacing:var(--wcss-display-2xl-letter-spacing, 0);line-height:var(--wcss-display-2xl-line-height, 1.3)}.display-xl{font-family:var(--wcss-display-xl-family, "AS Circular"),var(--wcss-display-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xl-font-size, clamp(3rem, 5.3333333333vw, 4.5rem));font-weight:var(--wcss-display-xl-weight, 300);letter-spacing:var(--wcss-display-xl-letter-spacing, 0);line-height:var(--wcss-display-xl-line-height, 1.3)}.display-lg{font-family:var(--wcss-display-lg-family, "AS Circular"),var(--wcss-display-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-lg-font-size, clamp(2.75rem, 4.6666666667vw, 4rem));font-weight:var(--wcss-display-lg-weight, 300);letter-spacing:var(--wcss-display-lg-letter-spacing, 0);line-height:var(--wcss-display-lg-line-height, 1.3)}.display-md{font-family:var(--wcss-display-md-family, "AS Circular"),var(--wcss-display-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-md-font-size, clamp(2.5rem, 4vw, 3.5rem));font-weight:var(--wcss-display-md-weight, 300);letter-spacing:var(--wcss-display-md-letter-spacing, 0);line-height:var(--wcss-display-md-line-height, 1.3)}.display-sm{font-family:var(--wcss-display-sm-family, "AS Circular"),var(--wcss-display-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-sm-font-size, clamp(2rem, 3.6666666667vw, 3rem));font-weight:var(--wcss-display-sm-weight, 300);letter-spacing:var(--wcss-display-sm-letter-spacing, 0);line-height:var(--wcss-display-sm-line-height, 1.3)}.display-xs{font-family:var(--wcss-display-xs-family, "AS Circular"),var(--wcss-display-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xs-font-size, clamp(1.75rem, 3vw, 2.375rem));font-weight:var(--wcss-display-xs-weight, 300);letter-spacing:var(--wcss-display-xs-letter-spacing, 0);line-height:var(--wcss-display-xs-line-height, 1.3)}.heading-xl{font-family:var(--wcss-heading-xl-family, "AS Circular"),var(--wcss-heading-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xl-font-size, clamp(2rem, 3vw, 2.5rem));font-weight:var(--wcss-heading-xl-weight, 300);letter-spacing:var(--wcss-heading-xl-letter-spacing, 0);line-height:var(--wcss-heading-xl-line-height, 1.3)}.heading-lg{font-family:var(--wcss-heading-lg-family, "AS Circular"),var(--wcss-heading-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-lg-font-size, clamp(1.75rem, 2.6666666667vw, 2.25rem));font-weight:var(--wcss-heading-lg-weight, 300);letter-spacing:var(--wcss-heading-lg-letter-spacing, 0);line-height:var(--wcss-heading-lg-line-height, 1.3)}.heading-md{font-family:var(--wcss-heading-md-family, "AS Circular"),var(--wcss-heading-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-md-font-size, clamp(1.625rem, 2.3333333333vw, 1.75rem));font-weight:var(--wcss-heading-md-weight, 300);letter-spacing:var(--wcss-heading-md-letter-spacing, 0);line-height:var(--wcss-heading-md-line-height, 1.3)}.heading-sm{font-family:var(--wcss-heading-sm-family, "AS Circular"),var(--wcss-heading-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-sm-font-size, clamp(1.375rem, 2vw, 1.5rem));font-weight:var(--wcss-heading-sm-weight, 300);letter-spacing:var(--wcss-heading-sm-letter-spacing, 0);line-height:var(--wcss-heading-sm-line-height, 1.3)}.heading-xs{font-family:var(--wcss-heading-xs-family, "AS Circular"),var(--wcss-heading-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xs-font-size, clamp(1.25rem, 1.6666666667vw, 1.25rem));font-weight:var(--wcss-heading-xs-weight, 300);letter-spacing:var(--wcss-heading-xs-letter-spacing, 0);line-height:var(--wcss-heading-xs-line-height, 1.3)}.heading-2xs{font-family:var(--wcss-heading-2xs-family, "AS Circular"),var(--wcss-heading-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-2xs-font-size, clamp(1.125rem, 1.5vw, 1.125rem));font-weight:var(--wcss-heading-2xs-weight, 300);letter-spacing:var(--wcss-heading-2xs-letter-spacing, 0);line-height:var(--wcss-heading-2xs-line-height, 1.3)}.accent-2xl{font-family:var(--wcss-accent-2xl-family, "Good OT"),var(--wcss-accent-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xl-font-size, clamp(2rem, 3.1666666667vw, 2.375rem));font-weight:var(--wcss-accent-2xl-weight, 450);letter-spacing:var(--wcss-accent-2xl-letter-spacing, 0.05em);line-height:var(--wcss-accent-2xl-line-height, 1)}.accent-2xl,.accent-xl{text-transform:uppercase}.accent-xl{font-family:var(--wcss-accent-xl-family, "Good OT"),var(--wcss-accent-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xl-font-size, clamp(1.625rem, 2.3333333333vw, 2rem));font-weight:var(--wcss-accent-xl-weight, 450);letter-spacing:var(--wcss-accent-xl-letter-spacing, 0.05em);line-height:var(--wcss-accent-xl-line-height, 1.3)}.accent-lg{font-family:var(--wcss-accent-lg-family, "Good OT"),var(--wcss-accent-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-lg-font-size, clamp(1.5rem, 2.1666666667vw, 1.75rem));font-weight:var(--wcss-accent-lg-weight, 450);letter-spacing:var(--wcss-accent-lg-letter-spacing, 0.05em);line-height:var(--wcss-accent-lg-line-height, 1.3)}.accent-lg,.accent-md{text-transform:uppercase}.accent-md{font-family:var(--wcss-accent-md-family, "Good OT"),var(--wcss-accent-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-md-font-size, clamp(1.375rem, 1.8333333333vw, 1.5rem));font-weight:var(--wcss-accent-md-weight, 500);letter-spacing:var(--wcss-accent-md-letter-spacing, 0.05em);line-height:var(--wcss-accent-md-line-height, 1.3)}.accent-sm{font-family:var(--wcss-accent-sm-family, "Good OT"),var(--wcss-accent-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-sm-font-size, clamp(1.125rem, 1.5vw, 1.25rem));font-weight:var(--wcss-accent-sm-weight, 500);letter-spacing:var(--wcss-accent-sm-letter-spacing, 0.05em);line-height:var(--wcss-accent-sm-line-height, 1.3)}.accent-sm,.accent-xs{text-transform:uppercase}.accent-xs{font-family:var(--wcss-accent-xs-family, "Good OT"),var(--wcss-accent-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xs-font-size, clamp(1rem, 1.3333333333vw, 1rem));font-weight:var(--wcss-accent-xs-weight, 500);letter-spacing:var(--wcss-accent-xs-letter-spacing, 0.1em);line-height:var(--wcss-accent-xs-line-height, 1.3)}.accent-2xs{font-family:var(--wcss-accent-2xs-family, "Good OT"),var(--wcss-accent-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xs-font-size, clamp(0.875rem, 1.1666666667vw, 0.875rem));font-weight:var(--wcss-accent-2xs-weight, 450);letter-spacing:var(--wcss-accent-2xs-letter-spacing, 0.1em);line-height:var(--wcss-accent-2xs-line-height, 1.3);text-transform:uppercase}[auro-dropdown]{--ds-auro-dropdown-trigger-border-color: var(--ds-auro-select-border-color);--ds-auro-dropdown-trigger-background-color: var(--ds-auro-select-background-color);--ds-auro-dropdown-trigger-container-color: var(--ds-auro-select-background-color);--ds-auro-dropdown-trigger-outline-color: var(--ds-auro-select-outline-color)}:host{display:inline-block;text-align:left;vertical-align:top}:host([layout*=emphasized]) [auro-dropdown],:host([layout*=snowflake]) [auro-dropdown]{--ds-auro-select-border-color: transparent}:host([layout*=emphasized]) .mainContent,:host([layout*=snowflake]) .mainContent{text-align:center}.mainContent{position:relative;display:flex;overflow:hidden;flex:1;flex-direction:column;align-items:center;justify-content:center}.valueContainer{max-width:100%}.accents{display:flex;flex-direction:row;align-items:center;justify-content:center}::slotted([slot=typeIcon]){margin-right:var(--ds-size-100, 0.5rem)}.displayValue{display:block}.displayValue:not(.force){display:none}.displayValue:not(.force).hasContent:is(.withValue):not(.hasFocus){display:block}.triggerContent{display:flex;width:100%;box-sizing:border-box;align-items:center;justify-content:center}:host([layout*=emphasized]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem) 0 var(--ds-size-300, 1.5rem)}:host([layout*=snowflake]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem) 0 var(--ds-size-200, 1rem)}:host([layout*=snowflake]) label{padding-block:var(--ds-size-25, 0.125rem)}:host([layout*=classic]) .triggerContent{padding:0 var(--ds-size-100, 0.5rem)}:host([layout*=classic]) .mainContent{align-items:start}:host([layout*=classic]) label{overflow:hidden;cursor:text;text-overflow:ellipsis;white-space:nowrap}:host([layout*=classic]) .value{overflow:hidden;height:auto;text-overflow:ellipsis;white-space:nowrap}label{color:var(--ds-auro-select-label-text-color)}:host(:is([validity]:not([validity=valid]))) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-basic-color-status-error, #e31f26);--ds-auro-select-outline-color: var(--ds-basic-color-status-error, #e31f26);--ds-auro-dropdown-helptext-text-color: var(--ds-basic-color-texticon-default, #2a2a2a)}:host([ondark]:is([validity]:not([validity=valid]))) [auro-dropdown],:host([appearance=inverse]:is([validity]:not([validity=valid]))) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8);--ds-auro-select-outline-color: var(--ds-advanced-color-state-error-inverse, #f9a4a8);--ds-auro-dropdown-helptext-text-color: var(--ds-basic-color-texticon-inverse, #ffffff)}#slotHolder{display:none}:host([fluid]){width:100%}:host([disabled]){pointer-events:none;user-select:none}:host([disabled]:not([ondark])) [auro-dropdown],:host([disabled]:not([appearance=inverse])) [auro-dropdown]{--ds-auro-select-border-color: var(--ds-basic-color-border-subtle, #dddddd)}:host(:not([layout*=classic])[disabled][ondark]) [auro-dropdown],:host(:not([layout*=classic])[disabled][appearance=inverse]) [auro-dropdown]{--ds-auro-select-border-color: transparent}`;
6944
6953
 
@@ -8769,6 +8778,116 @@ function isSelectableByValue(option) {
8769
8778
  !option.hasAttribute('static');
8770
8779
  }
8771
8780
 
8781
+ /* eslint-disable no-underscore-dangle */
8782
+ /**
8783
+ * Resolves the single selected option for a given `value`, preferring the
8784
+ * option tracked by `selectedKey` (a user-initiated selection) over a
8785
+ * first-by-value match. When multiple options share the same `value`, matching
8786
+ * by `value` alone cannot distinguish which one the user picked; the key
8787
+ * disambiguates it.
8788
+ *
8789
+ * The key is trusted only when it still resolves to an option whose `value`
8790
+ * matches the requested `value`. If the key is stale (option removed) or the
8791
+ * value was changed programmatically, resolution falls back to value matching —
8792
+ * preserving backward-compatible behavior for preselection and `selectByValue`.
8793
+ * @private
8794
+ * @param {Array<HTMLElement>} items - The menu's flat option list.
8795
+ * @param {string} value - The value to resolve.
8796
+ * @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
8797
+ * @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
8798
+ */
8799
+ function resolveSelectedOption(items, value, selectedKey) {
8800
+ if (!items) {
8801
+ return undefined;
8802
+ }
8803
+
8804
+ if (selectedKey !== undefined) {
8805
+ const keyed = items.find((item) => item._optionKey === selectedKey);
8806
+ if (keyed && isSelectableByValue(keyed) && keyed.value === value) {
8807
+ return keyed;
8808
+ }
8809
+ // Key exists but the option is gone or its value no longer matches — fall
8810
+ // through to value-based matching.
8811
+ }
8812
+
8813
+ return items.find((item) => isSelectableByValue(item) && item.value === value);
8814
+ }
8815
+
8816
+ /**
8817
+ * Resolves the selected options for a multi-select `value` array, preferring
8818
+ * options tracked by `selectedKeys` (user-initiated selections) and falling
8819
+ * back to value matching for any values not resolved by key. The result is
8820
+ * always sorted into DOM order regardless of selection sequence.
8821
+ * @private
8822
+ * @param {Array<HTMLElement>} items - The menu's flat option list.
8823
+ * @param {Array<string>} valueArray - The selected values.
8824
+ * @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
8825
+ * @returns {Array<HTMLElement>} The resolved options in DOM order.
8826
+ */
8827
+ function resolveSelectedOptions(items, valueArray, selectedKeys) {
8828
+ if (!items) {
8829
+ return [];
8830
+ }
8831
+
8832
+ const resolved = [];
8833
+ // Mirror `resolved` as a Set for O(1) membership checks below, matching the
8834
+ // indexMap optimization used for the sort rather than scanning `resolved`
8835
+ // on every candidate.
8836
+ const resolvedSet = new Set();
8837
+
8838
+ // Track how many of each value are still available to resolve. A value that
8839
+ // appears N times in `valueArray` may be satisfied at most N times total across
8840
+ // the key pass and the value fallback below — matching by count, not presence,
8841
+ // on BOTH passes. This stops a duplicate value from being over-resolved: e.g.
8842
+ // two keyed options that both carry `SEA` cannot both match a single requested
8843
+ // `SEA` (which happens when `value` is set directly without clearing
8844
+ // `_selectedKey`, so more keys survive than the value set now asks for).
8845
+ const remaining = new Map();
8846
+ valueArray.forEach((val) => remaining.set(val, (remaining.get(val) || 0) + 1));
8847
+
8848
+ // Resolve by key first: trust a key only when its option is still selectable
8849
+ // and there is still an unmatched occurrence of its value in the request set.
8850
+ if (Array.isArray(selectedKeys)) {
8851
+ selectedKeys.forEach((key) => {
8852
+ const keyed = items.find((item) => item._optionKey === key);
8853
+ if (keyed && isSelectableByValue(keyed) && (remaining.get(keyed.value) || 0) > 0 && !resolvedSet.has(keyed)) {
8854
+ resolved.push(keyed);
8855
+ resolvedSet.add(keyed);
8856
+ remaining.set(keyed.value, remaining.get(keyed.value) - 1);
8857
+ }
8858
+ });
8859
+ }
8860
+
8861
+ // Fall back to value matching for the occurrences not resolved by key. Iterate
8862
+ // the leftover per-value counts so a value that appears twice but was only
8863
+ // resolved once by key still matches its remaining occurrence(s).
8864
+ remaining.forEach((count, val) => {
8865
+ for (let occurrence = 0; occurrence < count; occurrence += 1) {
8866
+ const option = items.find((item) => isSelectableByValue(item) && item.value === val && !resolvedSet.has(item));
8867
+ if (option) {
8868
+ resolved.push(option);
8869
+ resolvedSet.add(option);
8870
+ }
8871
+ }
8872
+ });
8873
+
8874
+ // Always return in DOM order so display is consistent regardless of the order
8875
+ // keys/values were selected. Every resolved option came from `items`, so an
8876
+ // O(1) index lookup mirrors `_sortSelectedByDomOrder` and avoids the O(n)
8877
+ // `items.indexOf` per comparison for large combobox option sets. Any element
8878
+ // not in `items` (a stale snapshot from a future caller) sorts to the END via
8879
+ // `?? items.length`, matching `_sortSelectedByDomOrder` and avoiding NaN
8880
+ // comparisons.
8881
+ const indexMap = new Map(items.map((item, index) => [
8882
+ item,
8883
+ index
8884
+ ]));
8885
+ resolved.sort((optionA, optionB) => (indexMap.get(optionA) ?? items.length) - (indexMap.get(optionB) ?? items.length));
8886
+
8887
+ return resolved;
8888
+ }
8889
+ /* eslint-enable no-underscore-dangle */
8890
+
8772
8891
  /**
8773
8892
  * Helper method to dispatch custom events.
8774
8893
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -8794,6 +8913,14 @@ function dispatchMenuEvent(element, eventName, detail = null) {
8794
8913
  // See LICENSE in the project root for license information.
8795
8914
 
8796
8915
 
8916
+ /**
8917
+ * Monotonically increasing counter used to give each menu instance a unique
8918
+ * `_menuInstanceId` prefix. Auto-generating the id (rather than using a random
8919
+ * string) keeps option keys deterministic and collision-free across menus.
8920
+ * @private
8921
+ */
8922
+ let menuInstanceIdCounter = 0;
8923
+
8797
8924
 
8798
8925
  /**
8799
8926
  * The `auro-menu` element provides users a way to select from a list of options.
@@ -8865,9 +8992,8 @@ class AuroMenu extends AuroElement$1 {
8865
8992
 
8866
8993
  // Instance properties (non-reactive)
8867
8994
 
8868
- /**
8869
- * @private
8870
- */
8995
+ menuInstanceIdCounter += 1;
8996
+
8871
8997
  Object.assign(this, {
8872
8998
  // Root-level menu (true) or a nested submenu (false)
8873
8999
  rootMenu: true,
@@ -8877,6 +9003,21 @@ class AuroMenu extends AuroElement$1 {
8877
9003
  nestingSpacer: '<span class="nestingSpacer"></span>',
8878
9004
  // Loading indicator for slot elements
8879
9005
  loadingSlots: null,
9006
+ // Unique id for this menu instance; prefixes every auto-generated option
9007
+ // key so keys never collide across menus in the same document.
9008
+ _menuInstanceId: `menu-${menuInstanceIdCounter}`,
9009
+ // Monotonically increasing counter for option key generation. Never
9010
+ // resets, so a key is never reused within this instance's lifetime.
9011
+ _optionKeyCounter: 0,
9012
+ // Key(s) of the option(s) the user has actively selected. A single string
9013
+ // in single-select, an array in multi-select, undefined when nothing is
9014
+ // user-selected. Used to disambiguate options that share a `value`.
9015
+ _selectedKey: undefined,
9016
+ // True only for the one updated() cycle following a user selection, so
9017
+ // reconciliation trusts `_selectedKey`. A `value` change from a consumer's
9018
+ // direct property assignment leaves this false, dropping the stale key so
9019
+ // reconciliation falls back to first-by-value (see updated()).
9020
+ _valueChangeFromSelection: false,
8880
9021
  });
8881
9022
  }
8882
9023
 
@@ -9096,6 +9237,13 @@ class AuroMenu extends AuroElement$1 {
9096
9237
  return;
9097
9238
  }
9098
9239
 
9240
+ // A programmatic value set carries no positional intent, so drop any
9241
+ // `_selectedKey` left over from a prior user click. This makes reconciliation
9242
+ // in updated() fall back to first-by-value (single) / value-in-DOM-order
9243
+ // (multi), matching the documented contract for programmatic selection even
9244
+ // when a stale key would still resolve to a duplicate-value option.
9245
+ this._selectedKey = undefined;
9246
+
9099
9247
  // `value` is a String property; stringify arrays so attribute reflection and `formattedValue` parsing stay correct.
9100
9248
  this.value = Array.isArray(value) ? JSON.stringify(value) : value;
9101
9249
  }
@@ -9143,6 +9291,17 @@ class AuroMenu extends AuroElement$1 {
9143
9291
  updated(changedProperties) {
9144
9292
  super.updated(changedProperties);
9145
9293
 
9294
+ // Consume the selection-driven flag for THIS cycle up front. Clearing it
9295
+ // unconditionally — not only inside the `value` branch below — prevents it
9296
+ // from lingering `true` when a selection produces a serialized `value`
9297
+ // byte-identical to the current one, in which case Lit schedules no
9298
+ // `value`-change cycle to consume it. A lingering flag would misclassify a
9299
+ // later consumer's programmatic `value` set as selection-driven and keep a
9300
+ // stale `_selectedKey`. The reconcile path below re-sets the instance flag
9301
+ // after this point, so its intentional cross-cycle hand-off still works.
9302
+ const valueChangeFromSelection = this._valueChangeFromSelection;
9303
+ this._valueChangeFromSelection = false;
9304
+
9146
9305
  // Single source of truth for 'auroMenu-selectedOption'. Selection handlers
9147
9306
  // mutate optionSelected and let Lit's update cycle dispatch here; the prior
9148
9307
  // .value comparison missed multi-select array changes and combined with the
@@ -9166,6 +9325,17 @@ class AuroMenu extends AuroElement$1 {
9166
9325
  this.initItems();
9167
9326
  }
9168
9327
 
9328
+ // Distinguish a selection-driven `value` change (a user click, which set
9329
+ // the flag in handleSelectState / _sortSelectedByDomOrder) from a
9330
+ // programmatic assignment by a consumer. A programmatic set carries no
9331
+ // positional intent, so drop any leftover `_selectedKey` and let
9332
+ // reconciliation fall back to first-by-value (single) / value-in-DOM-order
9333
+ // (multi) — the same contract selectByValue() guarantees, even when a
9334
+ // stale key would otherwise still resolve to a duplicate-value option.
9335
+ if (!valueChangeFromSelection) {
9336
+ this._selectedKey = undefined;
9337
+ }
9338
+
9169
9339
  // Set when reconciliation reassigns `value` below. That reassignment schedules a
9170
9340
  // second updated() cycle, so the `event`-attribute dispatch is deferred to that
9171
9341
  // cycle to avoid firing option custom events twice on the same selection.
@@ -9183,19 +9353,55 @@ class AuroMenu extends AuroElement$1 {
9183
9353
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
9184
9354
  // and calling `.includes` on undefined would throw during reconciliation.
9185
9355
  const valueArray = this.formattedValue || [];
9186
- const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
9356
+ // Resolve by key first (the user's exact picks), then fall back to
9357
+ // value matching for any values not resolved by key — so pre-selection
9358
+ // and programmatic value sets keep working. Result is DOM-ordered.
9359
+ const matchingOptions = resolveSelectedOptions(this.items, valueArray, this._selectedKey);
9187
9360
  newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
9188
9361
 
9189
- // Reconcile `value` with the selectable set. Drop only entries whose option is
9190
- // loaded but non-selectable (disabled/static) leaving them would desync `value`
9191
- // from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
9192
- // so the rejected entry would resurface on the next select/deselect. Entries with no
9193
- // matching item yet are preserved so async preselection still works once options render.
9194
- const rejectedValues = this.items
9195
- ? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
9196
- : [];
9197
- if (rejectedValues.length > 0) {
9198
- const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
9362
+ // Reconcile `value` with the selectable set. An occurrence is dropped
9363
+ // only when it is loaded but no selectable option can satisfy it
9364
+ // every loaded item sharing that value is non-selectable, or the value
9365
+ // recurs more often than it has selectable options (a duplicate value
9366
+ // whose extra siblings are disabled/static). This is count-based, not
9367
+ // presence-based, so an enabled option is kept even when a disabled
9368
+ // sibling shares its value mirroring how `resolveSelectedOptions`
9369
+ // resolves the same set. Entries with no matching item yet are
9370
+ // preserved so async preselection still works, and the toggle handlers
9371
+ // rebuild `value` from `formattedValue`, so a rejected entry cannot
9372
+ // resurface on the next select/deselect.
9373
+ const selectableByValue = new Map();
9374
+ const loadedValues = new Set();
9375
+ if (this.items) {
9376
+ this.items.forEach((item) => {
9377
+ loadedValues.add(item.value);
9378
+ if (isSelectableByValue(item)) {
9379
+ selectableByValue.set(item.value, (selectableByValue.get(item.value) || 0) + 1);
9380
+ }
9381
+ });
9382
+ }
9383
+
9384
+ const reconciled = valueArray.filter((val) => {
9385
+ // Not loaded yet (async preselection) — keep for a later cycle.
9386
+ if (!loadedValues.has(val)) {
9387
+ return true;
9388
+ }
9389
+ // Consume one selectable option per occurrence; drop once exhausted.
9390
+ const remaining = selectableByValue.get(val) || 0;
9391
+ if (remaining > 0) {
9392
+ selectableByValue.set(val, remaining - 1);
9393
+ return true;
9394
+ }
9395
+ return false;
9396
+ });
9397
+
9398
+ if (reconciled.length !== valueArray.length) {
9399
+ // This is an internal correction, not a consumer's programmatic set,
9400
+ // so preserve the selection-driven flag through the re-entrant
9401
+ // updated() cycle it schedules. Otherwise that cycle would treat the
9402
+ // reassignment as programmatic and drop `_selectedKey` mid-cascade,
9403
+ // flipping resolution and looping.
9404
+ this._valueChangeFromSelection = true;
9199
9405
  this.value = serializeMultiSelectValue(reconciled);
9200
9406
  valueReconciled = true;
9201
9407
  }
@@ -9207,7 +9413,11 @@ class AuroMenu extends AuroElement$1 {
9207
9413
  // `hidden` is intentionally NOT excluded: the combobox toggles
9208
9414
  // `hidden` as its type-ahead filter, so a filtered-out option is
9209
9415
  // still a valid programmatic selection.
9210
- const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
9416
+ // Prefer the option the user actually selected (tracked by
9417
+ // `_selectedKey`) so a click on the second of two options sharing a
9418
+ // `value` resolves back to that exact element instead of the first
9419
+ // value match. Falls back to first-by-value for programmatic sets.
9420
+ const matchingOption = resolveSelectedOption(this.items, this.value, this._selectedKey);
9211
9421
 
9212
9422
  if (matchingOption) {
9213
9423
  newSelected = matchingOption;
@@ -9435,6 +9645,14 @@ class AuroMenu extends AuroElement$1 {
9435
9645
  }
9436
9646
  });
9437
9647
 
9648
+ // Assign private keys once items are populated. Only the root menu assigns
9649
+ // keys: its `items` is a deep query that already includes nested submenu
9650
+ // options, so a single pass keys the entire tree. Nested menus skip this
9651
+ // and inherit keys from the root.
9652
+ if (this.rootMenu) {
9653
+ this._assignOptionKeys();
9654
+ }
9655
+
9438
9656
  if (this.noCheckmark) {
9439
9657
  this.updateItemsState(new Map([
9440
9658
  [
@@ -9451,6 +9669,31 @@ class AuroMenu extends AuroElement$1 {
9451
9669
  }));
9452
9670
  }
9453
9671
 
9672
+ /**
9673
+ * Assigns a private, auto-generated unique key (`_optionKey`) to each menu
9674
+ * option that does not already have one. Keys are internal state on the
9675
+ * element instance — never reflected as an attribute or exposed publicly —
9676
+ * and let selection tracking distinguish options that share the same `value`.
9677
+ *
9678
+ * The `_optionKey === undefined` guard makes this idempotent: options keep the
9679
+ * key they were first assigned across re-renders and slot changes, and if a
9680
+ * nested menu's lifecycle runs a pass before the root, options simply wait for
9681
+ * the root to key them (or keep whatever key they already hold).
9682
+ * @private
9683
+ */
9684
+ _assignOptionKeys() {
9685
+ if (!this.items) {
9686
+ return;
9687
+ }
9688
+
9689
+ this.items.forEach((option) => {
9690
+ if (option._optionKey === undefined) {
9691
+ this._optionKeyCounter += 1;
9692
+ option._optionKey = `${this._menuInstanceId}-${this._optionKeyCounter}`;
9693
+ }
9694
+ });
9695
+ }
9696
+
9454
9697
  // Logic Methods
9455
9698
 
9456
9699
  /**
@@ -9460,24 +9703,28 @@ class AuroMenu extends AuroElement$1 {
9460
9703
  */
9461
9704
  handleSelectState(option) {
9462
9705
  if (this.multiSelect) {
9463
- const currentValue = this.formattedValue || [];
9464
9706
  const currentSelected = this.optionSelected || [];
9465
9707
 
9466
- if (!currentValue.includes(option.value)) {
9467
- this.value = serializeMultiSelectValue([
9468
- ...currentValue,
9469
- option.value
9470
- ]);
9471
- }
9472
9708
  if (!currentSelected.includes(option)) {
9473
9709
  this.optionSelected = [
9474
9710
  ...currentSelected,
9475
9711
  option
9476
9712
  ];
9477
9713
  }
9714
+
9715
+ // Re-sort by DOM order and rebuild `_selectedKey`/`value` from the
9716
+ // selected set so display order stays consistent with the menu, not with
9717
+ // click order.
9718
+ this._sortSelectedByDomOrder();
9478
9719
  } else {
9479
9720
  this.value = option.value;
9480
9721
  this.optionSelected = option;
9722
+ // Track the specific option the user selected so the value→option
9723
+ // reconciliation in updated() resolves back to this exact element even
9724
+ // when another option shares the same `value`.
9725
+ this._selectedKey = option._optionKey;
9726
+ // Mark this `value` change as selection-driven so updated() trusts the key.
9727
+ this._valueChangeFromSelection = true;
9481
9728
  }
9482
9729
 
9483
9730
  this._index = this.items.indexOf(option);
@@ -9490,18 +9737,22 @@ class AuroMenu extends AuroElement$1 {
9490
9737
  */
9491
9738
  handleDeselectState(option) {
9492
9739
  if (this.multiSelect) {
9493
- // Remove this option from array; an empty result collapses `value` to undefined.
9494
- const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
9495
- this.value = serializeMultiSelectValue(newFormattedValue);
9496
-
9497
- this.optionSelected = this.optionSelected.filter((val) => val !== option);
9740
+ // Remove this exact element from the selection (identity, not value two
9741
+ // options can share a `value`), then rebuild `value`/`_selectedKey` from
9742
+ // the remaining set in DOM order. An empty result collapses to undefined.
9743
+ this.optionSelected = this.optionSelected.filter((selected) => selected !== option);
9498
9744
  if (this.optionSelected.length === 0) {
9499
9745
  this.optionSelected = undefined;
9746
+ this._selectedKey = undefined;
9747
+ this.value = undefined;
9748
+ } else {
9749
+ this._sortSelectedByDomOrder();
9500
9750
  }
9501
9751
  } else {
9502
9752
  // For single-select: Back to undefined when deselected
9503
9753
  this.value = undefined;
9504
9754
  this.optionSelected = undefined;
9755
+ this._selectedKey = undefined;
9505
9756
  }
9506
9757
 
9507
9758
  // Update the index tracking
@@ -9525,9 +9776,46 @@ class AuroMenu extends AuroElement$1 {
9525
9776
  clearSelection() {
9526
9777
  this.optionSelected = undefined;
9527
9778
  this.value = undefined;
9779
+ this._selectedKey = undefined;
9528
9780
  this._index = -1;
9529
9781
  }
9530
9782
 
9783
+ /**
9784
+ * Re-sorts the multi-select selection into DOM order and rebuilds the derived
9785
+ * `_selectedKey` and `value` from `optionSelected`. Selection is always stored
9786
+ * and serialized in the order options appear in the menu, never in click
9787
+ * order — so selecting C then A yields `[A, C]`.
9788
+ * @private
9789
+ */
9790
+ _sortSelectedByDomOrder() {
9791
+ if (!this.multiSelect || !Array.isArray(this.optionSelected) || !this.items) {
9792
+ return;
9793
+ }
9794
+
9795
+ const indexMap = new Map(this.items.map((item, index) => [
9796
+ item,
9797
+ index
9798
+ ]));
9799
+
9800
+ // Sorting in place mutates `optionSelected` without a new array reference,
9801
+ // which Lit's `===` change-detection cannot see on its own — but that is
9802
+ // intentional and safe: both callers (handleSelectState / handleDeselectState)
9803
+ // assign a fresh `optionSelected` array immediately before calling, so Lit
9804
+ // already has a changed reference to react to, and the `value` write below
9805
+ // schedules the updated() cycle that re-derives `optionSelected` in DOM order
9806
+ // via resolveSelectedOptions. Do not "fix" this into a new-array assignment.
9807
+ //
9808
+ // Sort any element no longer in `items` (a stale selection left over from a
9809
+ // dynamic rebuild that the consumer has not cleared) to the END rather than
9810
+ // the front, so it never displaces a live option to the head of the
9811
+ // serialized order. Value reconciliation drops it on the next updated() cycle.
9812
+ this.optionSelected.sort((optionA, optionB) => (indexMap.get(optionA) ?? this.items.length) - (indexMap.get(optionB) ?? this.items.length));
9813
+ this._selectedKey = this.optionSelected.map((option) => option._optionKey);
9814
+ this.value = serializeMultiSelectValue(this.optionSelected.map((option) => option.value));
9815
+ // Mark this `value` change as selection-driven so updated() trusts the keys.
9816
+ this._valueChangeFromSelection = true;
9817
+ }
9818
+
9531
9819
  /**
9532
9820
  * Resets the menu to its initial state.
9533
9821
  * This is the only way to return value to undefined.
@@ -9537,6 +9825,7 @@ class AuroMenu extends AuroElement$1 {
9537
9825
  // Reset to undefined - initial state
9538
9826
  this.value = undefined;
9539
9827
  this.optionSelected = undefined;
9828
+ this._selectedKey = undefined;
9540
9829
  this._index = -1;
9541
9830
 
9542
9831
  // Clear active option state so a follow-up open/navigation starts fresh
@@ -9598,6 +9887,21 @@ class AuroMenu extends AuroElement$1 {
9598
9887
  this.initItems();
9599
9888
  }
9600
9889
 
9890
+ // Recover `_index` from the highlighted option when it has been reset to -1.
9891
+ // The updated() reconciliation resets `_index = -1` whenever the value
9892
+ // collapses to undefined while `optionActive` still points at the highlighted
9893
+ // option — e.g. deselecting the last remaining option in multi-select, or a
9894
+ // programmatic clearSelection() in single-select while keyboard focus is on an
9895
+ // option. Without this, reading `items[-1]` returns undefined and the re-select
9896
+ // no-ops until the highlight is moved away and back. Mirrors auro-combobox's
9897
+ // reconcileMenuIndex.
9898
+ if (this._index < 0 && this.optionActive && this.items) {
9899
+ const activeIndex = this.items.indexOf(this.optionActive);
9900
+ if (activeIndex >= 0) {
9901
+ this._index = activeIndex;
9902
+ }
9903
+ }
9904
+
9601
9905
  // Get currently selected menu option based on index
9602
9906
  const option = this.items ? this.items[this._index] : undefined;
9603
9907