@aurodesignsystem-dev/auro-formkit 0.0.0-pr1576.8 → 0.0.0-pr1577.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 (52) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.md +0 -40
  7. package/components/combobox/demo/customize.min.js +65 -368
  8. package/components/combobox/demo/getting-started.min.js +65 -368
  9. package/components/combobox/demo/index.min.js +65 -368
  10. package/components/combobox/dist/index.js +38 -56
  11. package/components/combobox/dist/registered.js +38 -56
  12. package/components/counter/demo/customize.min.js +11 -2
  13. package/components/counter/demo/index.min.js +11 -2
  14. package/components/counter/dist/index.js +11 -2
  15. package/components/counter/dist/registered.js +11 -2
  16. package/components/datepicker/demo/customize.min.js +12 -3
  17. package/components/datepicker/demo/index.min.js +12 -3
  18. package/components/datepicker/dist/index.js +12 -3
  19. package/components/datepicker/dist/registered.js +12 -3
  20. package/components/dropdown/demo/customize.min.js +10 -1
  21. package/components/dropdown/demo/getting-started.min.js +10 -1
  22. package/components/dropdown/demo/index.min.js +10 -1
  23. package/components/dropdown/dist/index.js +10 -1
  24. package/components/dropdown/dist/registered.js +10 -1
  25. package/components/form/demo/customize.min.js +102 -378
  26. package/components/form/demo/getting-started.min.js +102 -378
  27. package/components/form/demo/index.min.js +102 -378
  28. package/components/form/demo/registerDemoDeps.min.js +102 -378
  29. package/components/input/demo/customize.min.js +1 -1
  30. package/components/input/demo/getting-started.min.js +1 -1
  31. package/components/input/demo/index.min.js +1 -1
  32. package/components/input/dist/index.js +1 -1
  33. package/components/input/dist/registered.js +1 -1
  34. package/components/menu/demo/customize.md +0 -50
  35. package/components/menu/demo/index.min.js +27 -312
  36. package/components/menu/dist/auro-menu-utils.d.ts +0 -30
  37. package/components/menu/dist/auro-menu.d.ts +0 -23
  38. package/components/menu/dist/index.js +27 -312
  39. package/components/menu/dist/registered.js +27 -312
  40. package/components/radio/demo/customize.min.js +1 -1
  41. package/components/radio/demo/getting-started.min.js +1 -1
  42. package/components/radio/demo/index.min.js +1 -1
  43. package/components/radio/dist/index.js +1 -1
  44. package/components/radio/dist/registered.js +1 -1
  45. package/components/select/demo/customize.md +0 -72
  46. package/components/select/demo/customize.min.js +38 -314
  47. package/components/select/demo/getting-started.min.js +38 -314
  48. package/components/select/demo/index.min.js +38 -314
  49. package/components/select/dist/index.js +11 -2
  50. package/components/select/dist/registered.js +11 -2
  51. package/custom-elements.json +0 -103
  52. package/package.json +1 -1
@@ -4992,7 +4992,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4992
4992
  }
4993
4993
  };
4994
4994
 
4995
- var formkitVersion$2 = '202608041755';
4995
+ var formkitVersion$2 = '202608041712';
4996
4996
 
4997
4997
  let AuroElement$2 = class AuroElement extends i$3 {
4998
4998
  static get properties() {
@@ -5694,6 +5694,15 @@ class AuroDropdown extends AuroElement$2 {
5694
5694
 
5695
5695
  connectedCallback() {
5696
5696
  super.connectedCallback();
5697
+ // Re-register trigger event listeners removed by floater.disconnect() in
5698
+ // disconnectedCallback(). Fires when a parent element (e.g. auro-drawer)
5699
+ // moves this element to a new DOM location. firstUpdated() won't re-run,
5700
+ // so without this the trigger click/keydown handlers are permanently gone.
5701
+ // Guard on floater.element so this is a no-op on the initial connect
5702
+ // (before firstUpdated() has called configure()).
5703
+ if (this.floater?.element) {
5704
+ this.floater.configure(this, 'auroDropdown', !this.disableKeyboardHandling);
5705
+ }
5697
5706
  }
5698
5707
 
5699
5708
  disconnectedCallback() {
@@ -18681,7 +18690,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18681
18690
  }
18682
18691
  };
18683
18692
 
18684
- var formkitVersion$1 = '202608041755';
18693
+ var formkitVersion$1 = '202608041712';
18685
18694
 
18686
18695
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18687
18696
  // See LICENSE in the project root for license information.
@@ -19837,7 +19846,7 @@ class AuroBibtemplate extends i$3 {
19837
19846
  }
19838
19847
  }
19839
19848
 
19840
- var formkitVersion = '202608041755';
19849
+ var formkitVersion = '202608041712';
19841
19850
 
19842
19851
  var styleCss$3 = i$6`.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}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
19843
19852
 
@@ -21643,17 +21652,7 @@ class AuroCombobox extends AuroElement {
21643
21652
  return;
21644
21653
  }
21645
21654
 
21646
- // Skip this write during a display-value sync: that path sets the input to
21647
- // the selected option's LABEL, which is not its machine value. When an
21648
- // option's label differs from its value (and especially when several
21649
- // options share a value but render distinct labels — AB#1602086), writing
21650
- // the label back into this.value clobbers the machine value the selection
21651
- // listener just set (this.value = optionSelected.value), collapsing the
21652
- // selection. _syncingBibValue still tracks (that mirrors user-typed text
21653
- // from the fullscreen bib, where value should follow the input).
21654
- if (!this._syncingDisplayValue) {
21655
- this.value = this.input.value;
21656
- }
21655
+ this.value = this.input.value;
21657
21656
 
21658
21657
  // Ignore re-entrant input events caused by programmatic value sets.
21659
21658
  if (this._syncingBibValue || this._syncingDisplayValue) {
@@ -21917,50 +21916,33 @@ class AuroCombobox extends AuroElement {
21917
21916
  }
21918
21917
 
21919
21918
  if (this.input.value !== this.value) {
21920
- // A fresh user selection leaves the input showing the option's LABEL
21921
- // while this.value holds its machine value, so input.value !== this.value
21922
- // is expected whenever label ≠ value. In that case the menu's current
21923
- // selection is authoritative — and when several options share a value it
21924
- // is the ONLY thing that records WHICH same-value option the user picked
21925
- // (the menu tracks it by element identity via `_selectedKey`). Clearing
21926
- // it here drops that key; the ensuing value-only re-resolution then
21927
- // collapses the selection onto the first same-value option (AB#1602086).
21928
- // So only treat the divergence as a stale menu when the selected option
21929
- // does NOT match the new value.
21930
- const menuSelectionMatchesValue =
21931
- this.menu.optionSelected &&
21932
- !Array.isArray(this.menu.optionSelected) &&
21933
- this.menu.optionSelected.value === this.value;
21934
-
21935
- if (!menuSelectionMatchesValue) {
21936
- // Clear menu.value AND menu.optionSelected together. Clearing only
21937
- // menu.value leaves the previously-selected option element pinned
21938
- // as menu.optionSelected; a later auroMenu-selectedOption event
21939
- // would then write its stale .value back into combobox.value
21940
- // (e.g. Tab-after-Backspace re-selecting the prior option).
21941
- if (this.menu.value || this.menu.optionSelected) {
21942
- this.menu.clearSelection();
21943
- }
21919
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21920
+ // menu.value leaves the previously-selected option element pinned
21921
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21922
+ // would then write its stale .value back into combobox.value
21923
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21924
+ if (this.menu.value || this.menu.optionSelected) {
21925
+ this.menu.clearSelection();
21926
+ }
21944
21927
 
21945
- if (!this.persistInput) {
21946
- this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21947
- }
21928
+ if (!this.persistInput) {
21929
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21930
+ }
21948
21931
 
21949
- // Programmatic value with no matching option: updateFilter will close
21950
- // the bib silently (see line 648 — no noMatchOption + 0 results
21951
- // hides). Announce so screen-reader users hear the request was
21952
- // dropped. Gated on `input.value !== this.value` so this never fires
21953
- // for user typing — that path always reconciles input.value to
21954
- // this.value before updated() runs.
21955
- if (
21956
- this.value &&
21957
- this.menu &&
21958
- this.menu.options &&
21959
- this.menu.options.length > 0 &&
21960
- !this.menu.options.some((opt) => opt.value === this.value)
21961
- ) {
21962
- announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21963
- }
21932
+ // Programmatic value with no matching option: updateFilter will close
21933
+ // the bib silently (see line 648 — no noMatchOption + 0 results
21934
+ // hides). Announce so screen-reader users hear the request was
21935
+ // dropped. Gated on `input.value !== this.value` so this never fires
21936
+ // for user typing — that path always reconciles input.value to
21937
+ // this.value before updated() runs.
21938
+ if (
21939
+ this.value &&
21940
+ this.menu &&
21941
+ this.menu.options &&
21942
+ this.menu.options.length > 0 &&
21943
+ !this.menu.options.some((opt) => opt.value === this.value)
21944
+ ) {
21945
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21964
21946
  }
21965
21947
  }
21966
21948
 
@@ -22297,116 +22279,6 @@ function isSelectableByValue(option) {
22297
22279
  !option.hasAttribute('static');
22298
22280
  }
22299
22281
 
22300
- /* eslint-disable no-underscore-dangle */
22301
- /**
22302
- * Resolves the single selected option for a given `value`, preferring the
22303
- * option tracked by `selectedKey` (a user-initiated selection) over a
22304
- * first-by-value match. When multiple options share the same `value`, matching
22305
- * by `value` alone cannot distinguish which one the user picked; the key
22306
- * disambiguates it.
22307
- *
22308
- * The key is trusted only when it still resolves to an option whose `value`
22309
- * matches the requested `value`. If the key is stale (option removed) or the
22310
- * value was changed programmatically, resolution falls back to value matching —
22311
- * preserving backward-compatible behavior for preselection and `selectByValue`.
22312
- * @private
22313
- * @param {Array<HTMLElement>} items - The menu's flat option list.
22314
- * @param {string} value - The value to resolve.
22315
- * @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
22316
- * @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
22317
- */
22318
- function resolveSelectedOption(items, value, selectedKey) {
22319
- if (!items) {
22320
- return undefined;
22321
- }
22322
-
22323
- if (selectedKey !== undefined) {
22324
- const keyed = items.find((item) => item._optionKey === selectedKey);
22325
- if (keyed && isSelectableByValue(keyed) && keyed.value === value) {
22326
- return keyed;
22327
- }
22328
- // Key exists but the option is gone or its value no longer matches — fall
22329
- // through to value-based matching.
22330
- }
22331
-
22332
- return items.find((item) => isSelectableByValue(item) && item.value === value);
22333
- }
22334
-
22335
- /**
22336
- * Resolves the selected options for a multi-select `value` array, preferring
22337
- * options tracked by `selectedKeys` (user-initiated selections) and falling
22338
- * back to value matching for any values not resolved by key. The result is
22339
- * always sorted into DOM order regardless of selection sequence.
22340
- * @private
22341
- * @param {Array<HTMLElement>} items - The menu's flat option list.
22342
- * @param {Array<string>} valueArray - The selected values.
22343
- * @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
22344
- * @returns {Array<HTMLElement>} The resolved options in DOM order.
22345
- */
22346
- function resolveSelectedOptions(items, valueArray, selectedKeys) {
22347
- if (!items) {
22348
- return [];
22349
- }
22350
-
22351
- const resolved = [];
22352
- // Mirror `resolved` as a Set for O(1) membership checks below, matching the
22353
- // indexMap optimization used for the sort rather than scanning `resolved`
22354
- // on every candidate.
22355
- const resolvedSet = new Set();
22356
-
22357
- // Track how many of each value are still available to resolve. A value that
22358
- // appears N times in `valueArray` may be satisfied at most N times total across
22359
- // the key pass and the value fallback below — matching by count, not presence,
22360
- // on BOTH passes. This stops a duplicate value from being over-resolved: e.g.
22361
- // two keyed options that both carry `SEA` cannot both match a single requested
22362
- // `SEA` (which happens when `value` is set directly without clearing
22363
- // `_selectedKey`, so more keys survive than the value set now asks for).
22364
- const remaining = new Map();
22365
- valueArray.forEach((val) => remaining.set(val, (remaining.get(val) || 0) + 1));
22366
-
22367
- // Resolve by key first: trust a key only when its option is still selectable
22368
- // and there is still an unmatched occurrence of its value in the request set.
22369
- if (Array.isArray(selectedKeys)) {
22370
- selectedKeys.forEach((key) => {
22371
- const keyed = items.find((item) => item._optionKey === key);
22372
- if (keyed && isSelectableByValue(keyed) && (remaining.get(keyed.value) || 0) > 0 && !resolvedSet.has(keyed)) {
22373
- resolved.push(keyed);
22374
- resolvedSet.add(keyed);
22375
- remaining.set(keyed.value, remaining.get(keyed.value) - 1);
22376
- }
22377
- });
22378
- }
22379
-
22380
- // Fall back to value matching for the occurrences not resolved by key. Iterate
22381
- // the leftover per-value counts so a value that appears twice but was only
22382
- // resolved once by key still matches its remaining occurrence(s).
22383
- remaining.forEach((count, val) => {
22384
- for (let occurrence = 0; occurrence < count; occurrence += 1) {
22385
- const option = items.find((item) => isSelectableByValue(item) && item.value === val && !resolvedSet.has(item));
22386
- if (option) {
22387
- resolved.push(option);
22388
- resolvedSet.add(option);
22389
- }
22390
- }
22391
- });
22392
-
22393
- // Always return in DOM order so display is consistent regardless of the order
22394
- // keys/values were selected. Every resolved option came from `items`, so an
22395
- // O(1) index lookup mirrors `_sortSelectedByDomOrder` and avoids the O(n)
22396
- // `items.indexOf` per comparison for large combobox option sets. Any element
22397
- // not in `items` (a stale snapshot from a future caller) sorts to the END via
22398
- // `?? items.length`, matching `_sortSelectedByDomOrder` and avoiding NaN
22399
- // comparisons.
22400
- const indexMap = new Map(items.map((item, index) => [
22401
- item,
22402
- index
22403
- ]));
22404
- resolved.sort((optionA, optionB) => (indexMap.get(optionA) ?? items.length) - (indexMap.get(optionB) ?? items.length));
22405
-
22406
- return resolved;
22407
- }
22408
- /* eslint-enable no-underscore-dangle */
22409
-
22410
22282
  /**
22411
22283
  * Helper method to dispatch custom events.
22412
22284
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -22432,14 +22304,6 @@ function dispatchMenuEvent(element, eventName, detail = null) {
22432
22304
  // See LICENSE in the project root for license information.
22433
22305
 
22434
22306
 
22435
- /**
22436
- * Monotonically increasing counter used to give each menu instance a unique
22437
- * `_menuInstanceId` prefix. Auto-generating the id (rather than using a random
22438
- * string) keeps option keys deterministic and collision-free across menus.
22439
- * @private
22440
- */
22441
- let menuInstanceIdCounter = 0;
22442
-
22443
22307
 
22444
22308
  /**
22445
22309
  * The `auro-menu` element provides users a way to select from a list of options.
@@ -22511,8 +22375,9 @@ class AuroMenu extends AuroElement {
22511
22375
 
22512
22376
  // Instance properties (non-reactive)
22513
22377
 
22514
- menuInstanceIdCounter += 1;
22515
-
22378
+ /**
22379
+ * @private
22380
+ */
22516
22381
  Object.assign(this, {
22517
22382
  // Root-level menu (true) or a nested submenu (false)
22518
22383
  rootMenu: true,
@@ -22522,21 +22387,6 @@ class AuroMenu extends AuroElement {
22522
22387
  nestingSpacer: '<span class="nestingSpacer"></span>',
22523
22388
  // Loading indicator for slot elements
22524
22389
  loadingSlots: null,
22525
- // Unique id for this menu instance; prefixes every auto-generated option
22526
- // key so keys never collide across menus in the same document.
22527
- _menuInstanceId: `menu-${menuInstanceIdCounter}`,
22528
- // Monotonically increasing counter for option key generation. Never
22529
- // resets, so a key is never reused within this instance's lifetime.
22530
- _optionKeyCounter: 0,
22531
- // Key(s) of the option(s) the user has actively selected. A single string
22532
- // in single-select, an array in multi-select, undefined when nothing is
22533
- // user-selected. Used to disambiguate options that share a `value`.
22534
- _selectedKey: undefined,
22535
- // True only for the one updated() cycle following a user selection, so
22536
- // reconciliation trusts `_selectedKey`. A `value` change from a consumer's
22537
- // direct property assignment leaves this false, dropping the stale key so
22538
- // reconciliation falls back to first-by-value (see updated()).
22539
- _valueChangeFromSelection: false,
22540
22390
  });
22541
22391
  }
22542
22392
 
@@ -22756,13 +22606,6 @@ class AuroMenu extends AuroElement {
22756
22606
  return;
22757
22607
  }
22758
22608
 
22759
- // A programmatic value set carries no positional intent, so drop any
22760
- // `_selectedKey` left over from a prior user click. This makes reconciliation
22761
- // in updated() fall back to first-by-value (single) / value-in-DOM-order
22762
- // (multi), matching the documented contract for programmatic selection even
22763
- // when a stale key would still resolve to a duplicate-value option.
22764
- this._selectedKey = undefined;
22765
-
22766
22609
  // `value` is a String property; stringify arrays so attribute reflection and `formattedValue` parsing stay correct.
22767
22610
  this.value = Array.isArray(value) ? JSON.stringify(value) : value;
22768
22611
  }
@@ -22810,17 +22653,6 @@ class AuroMenu extends AuroElement {
22810
22653
  updated(changedProperties) {
22811
22654
  super.updated(changedProperties);
22812
22655
 
22813
- // Consume the selection-driven flag for THIS cycle up front. Clearing it
22814
- // unconditionally — not only inside the `value` branch below — prevents it
22815
- // from lingering `true` when a selection produces a serialized `value`
22816
- // byte-identical to the current one, in which case Lit schedules no
22817
- // `value`-change cycle to consume it. A lingering flag would misclassify a
22818
- // later consumer's programmatic `value` set as selection-driven and keep a
22819
- // stale `_selectedKey`. The reconcile path below re-sets the instance flag
22820
- // after this point, so its intentional cross-cycle hand-off still works.
22821
- const valueChangeFromSelection = this._valueChangeFromSelection;
22822
- this._valueChangeFromSelection = false;
22823
-
22824
22656
  // Single source of truth for 'auroMenu-selectedOption'. Selection handlers
22825
22657
  // mutate optionSelected and let Lit's update cycle dispatch here; the prior
22826
22658
  // .value comparison missed multi-select array changes and combined with the
@@ -22844,17 +22676,6 @@ class AuroMenu extends AuroElement {
22844
22676
  this.initItems();
22845
22677
  }
22846
22678
 
22847
- // Distinguish a selection-driven `value` change (a user click, which set
22848
- // the flag in handleSelectState / _sortSelectedByDomOrder) from a
22849
- // programmatic assignment by a consumer. A programmatic set carries no
22850
- // positional intent, so drop any leftover `_selectedKey` and let
22851
- // reconciliation fall back to first-by-value (single) / value-in-DOM-order
22852
- // (multi) — the same contract selectByValue() guarantees, even when a
22853
- // stale key would otherwise still resolve to a duplicate-value option.
22854
- if (!valueChangeFromSelection) {
22855
- this._selectedKey = undefined;
22856
- }
22857
-
22858
22679
  // Set when reconciliation reassigns `value` below. That reassignment schedules a
22859
22680
  // second updated() cycle, so the `event`-attribute dispatch is deferred to that
22860
22681
  // cycle to avoid firing option custom events twice on the same selection.
@@ -22872,55 +22693,19 @@ class AuroMenu extends AuroElement {
22872
22693
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
22873
22694
  // and calling `.includes` on undefined would throw during reconciliation.
22874
22695
  const valueArray = this.formattedValue || [];
22875
- // Resolve by key first (the user's exact picks), then fall back to
22876
- // value matching for any values not resolved by key — so pre-selection
22877
- // and programmatic value sets keep working. Result is DOM-ordered.
22878
- const matchingOptions = resolveSelectedOptions(this.items, valueArray, this._selectedKey);
22696
+ const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
22879
22697
  newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
22880
22698
 
22881
- // Reconcile `value` with the selectable set. An occurrence is dropped
22882
- // only when it is loaded but no selectable option can satisfy it —
22883
- // every loaded item sharing that value is non-selectable, or the value
22884
- // recurs more often than it has selectable options (a duplicate value
22885
- // whose extra siblings are disabled/static). This is count-based, not
22886
- // presence-based, so an enabled option is kept even when a disabled
22887
- // sibling shares its value — mirroring how `resolveSelectedOptions`
22888
- // resolves the same set. Entries with no matching item yet are
22889
- // preserved so async preselection still works, and the toggle handlers
22890
- // rebuild `value` from `formattedValue`, so a rejected entry cannot
22891
- // resurface on the next select/deselect.
22892
- const selectableByValue = new Map();
22893
- const loadedValues = new Set();
22894
- if (this.items) {
22895
- this.items.forEach((item) => {
22896
- loadedValues.add(item.value);
22897
- if (isSelectableByValue(item)) {
22898
- selectableByValue.set(item.value, (selectableByValue.get(item.value) || 0) + 1);
22899
- }
22900
- });
22901
- }
22902
-
22903
- const reconciled = valueArray.filter((val) => {
22904
- // Not loaded yet (async preselection) — keep for a later cycle.
22905
- if (!loadedValues.has(val)) {
22906
- return true;
22907
- }
22908
- // Consume one selectable option per occurrence; drop once exhausted.
22909
- const remaining = selectableByValue.get(val) || 0;
22910
- if (remaining > 0) {
22911
- selectableByValue.set(val, remaining - 1);
22912
- return true;
22913
- }
22914
- return false;
22915
- });
22916
-
22917
- if (reconciled.length !== valueArray.length) {
22918
- // This is an internal correction, not a consumer's programmatic set,
22919
- // so preserve the selection-driven flag through the re-entrant
22920
- // updated() cycle it schedules. Otherwise that cycle would treat the
22921
- // reassignment as programmatic and drop `_selectedKey` mid-cascade,
22922
- // flipping resolution and looping.
22923
- this._valueChangeFromSelection = true;
22699
+ // Reconcile `value` with the selectable set. Drop only entries whose option is
22700
+ // loaded but non-selectable (disabled/static) — leaving them would desync `value`
22701
+ // from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
22702
+ // so the rejected entry would resurface on the next select/deselect. Entries with no
22703
+ // matching item yet are preserved so async preselection still works once options render.
22704
+ const rejectedValues = this.items
22705
+ ? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
22706
+ : [];
22707
+ if (rejectedValues.length > 0) {
22708
+ const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
22924
22709
  this.value = serializeMultiSelectValue(reconciled);
22925
22710
  valueReconciled = true;
22926
22711
  }
@@ -22932,11 +22717,7 @@ class AuroMenu extends AuroElement {
22932
22717
  // `hidden` is intentionally NOT excluded: the combobox toggles
22933
22718
  // `hidden` as its type-ahead filter, so a filtered-out option is
22934
22719
  // still a valid programmatic selection.
22935
- // Prefer the option the user actually selected (tracked by
22936
- // `_selectedKey`) so a click on the second of two options sharing a
22937
- // `value` resolves back to that exact element instead of the first
22938
- // value match. Falls back to first-by-value for programmatic sets.
22939
- const matchingOption = resolveSelectedOption(this.items, this.value, this._selectedKey);
22720
+ const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
22940
22721
 
22941
22722
  if (matchingOption) {
22942
22723
  newSelected = matchingOption;
@@ -23164,14 +22945,6 @@ class AuroMenu extends AuroElement {
23164
22945
  }
23165
22946
  });
23166
22947
 
23167
- // Assign private keys once items are populated. Only the root menu assigns
23168
- // keys: its `items` is a deep query that already includes nested submenu
23169
- // options, so a single pass keys the entire tree. Nested menus skip this
23170
- // and inherit keys from the root.
23171
- if (this.rootMenu) {
23172
- this._assignOptionKeys();
23173
- }
23174
-
23175
22948
  if (this.noCheckmark) {
23176
22949
  this.updateItemsState(new Map([
23177
22950
  [
@@ -23188,31 +22961,6 @@ class AuroMenu extends AuroElement {
23188
22961
  }));
23189
22962
  }
23190
22963
 
23191
- /**
23192
- * Assigns a private, auto-generated unique key (`_optionKey`) to each menu
23193
- * option that does not already have one. Keys are internal state on the
23194
- * element instance — never reflected as an attribute or exposed publicly —
23195
- * and let selection tracking distinguish options that share the same `value`.
23196
- *
23197
- * The `_optionKey === undefined` guard makes this idempotent: options keep the
23198
- * key they were first assigned across re-renders and slot changes, and if a
23199
- * nested menu's lifecycle runs a pass before the root, options simply wait for
23200
- * the root to key them (or keep whatever key they already hold).
23201
- * @private
23202
- */
23203
- _assignOptionKeys() {
23204
- if (!this.items) {
23205
- return;
23206
- }
23207
-
23208
- this.items.forEach((option) => {
23209
- if (option._optionKey === undefined) {
23210
- this._optionKeyCounter += 1;
23211
- option._optionKey = `${this._menuInstanceId}-${this._optionKeyCounter}`;
23212
- }
23213
- });
23214
- }
23215
-
23216
22964
  // Logic Methods
23217
22965
 
23218
22966
  /**
@@ -23222,28 +22970,24 @@ class AuroMenu extends AuroElement {
23222
22970
  */
23223
22971
  handleSelectState(option) {
23224
22972
  if (this.multiSelect) {
22973
+ const currentValue = this.formattedValue || [];
23225
22974
  const currentSelected = this.optionSelected || [];
23226
22975
 
22976
+ if (!currentValue.includes(option.value)) {
22977
+ this.value = serializeMultiSelectValue([
22978
+ ...currentValue,
22979
+ option.value
22980
+ ]);
22981
+ }
23227
22982
  if (!currentSelected.includes(option)) {
23228
22983
  this.optionSelected = [
23229
22984
  ...currentSelected,
23230
22985
  option
23231
22986
  ];
23232
22987
  }
23233
-
23234
- // Re-sort by DOM order and rebuild `_selectedKey`/`value` from the
23235
- // selected set so display order stays consistent with the menu, not with
23236
- // click order.
23237
- this._sortSelectedByDomOrder();
23238
22988
  } else {
23239
22989
  this.value = option.value;
23240
22990
  this.optionSelected = option;
23241
- // Track the specific option the user selected so the value→option
23242
- // reconciliation in updated() resolves back to this exact element even
23243
- // when another option shares the same `value`.
23244
- this._selectedKey = option._optionKey;
23245
- // Mark this `value` change as selection-driven so updated() trusts the key.
23246
- this._valueChangeFromSelection = true;
23247
22991
  }
23248
22992
 
23249
22993
  this._index = this.items.indexOf(option);
@@ -23256,22 +23000,18 @@ class AuroMenu extends AuroElement {
23256
23000
  */
23257
23001
  handleDeselectState(option) {
23258
23002
  if (this.multiSelect) {
23259
- // Remove this exact element from the selection (identity, not value — two
23260
- // options can share a `value`), then rebuild `value`/`_selectedKey` from
23261
- // the remaining set in DOM order. An empty result collapses to undefined.
23262
- this.optionSelected = this.optionSelected.filter((selected) => selected !== option);
23003
+ // Remove this option from array; an empty result collapses `value` to undefined.
23004
+ const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
23005
+ this.value = serializeMultiSelectValue(newFormattedValue);
23006
+
23007
+ this.optionSelected = this.optionSelected.filter((val) => val !== option);
23263
23008
  if (this.optionSelected.length === 0) {
23264
23009
  this.optionSelected = undefined;
23265
- this._selectedKey = undefined;
23266
- this.value = undefined;
23267
- } else {
23268
- this._sortSelectedByDomOrder();
23269
23010
  }
23270
23011
  } else {
23271
23012
  // For single-select: Back to undefined when deselected
23272
23013
  this.value = undefined;
23273
23014
  this.optionSelected = undefined;
23274
- this._selectedKey = undefined;
23275
23015
  }
23276
23016
 
23277
23017
  // Update the index tracking
@@ -23295,38 +23035,9 @@ class AuroMenu extends AuroElement {
23295
23035
  clearSelection() {
23296
23036
  this.optionSelected = undefined;
23297
23037
  this.value = undefined;
23298
- this._selectedKey = undefined;
23299
23038
  this._index = -1;
23300
23039
  }
23301
23040
 
23302
- /**
23303
- * Re-sorts the multi-select selection into DOM order and rebuilds the derived
23304
- * `_selectedKey` and `value` from `optionSelected`. Selection is always stored
23305
- * and serialized in the order options appear in the menu, never in click
23306
- * order — so selecting C then A yields `[A, C]`.
23307
- * @private
23308
- */
23309
- _sortSelectedByDomOrder() {
23310
- if (!this.multiSelect || !Array.isArray(this.optionSelected) || !this.items) {
23311
- return;
23312
- }
23313
-
23314
- const indexMap = new Map(this.items.map((item, index) => [
23315
- item,
23316
- index
23317
- ]));
23318
-
23319
- // Sort any element no longer in `items` (a stale selection left over from a
23320
- // dynamic rebuild that the consumer has not cleared) to the END rather than
23321
- // the front, so it never displaces a live option to the head of the
23322
- // serialized order. Value reconciliation drops it on the next updated() cycle.
23323
- this.optionSelected.sort((optionA, optionB) => (indexMap.get(optionA) ?? this.items.length) - (indexMap.get(optionB) ?? this.items.length));
23324
- this._selectedKey = this.optionSelected.map((option) => option._optionKey);
23325
- this.value = serializeMultiSelectValue(this.optionSelected.map((option) => option.value));
23326
- // Mark this `value` change as selection-driven so updated() trusts the keys.
23327
- this._valueChangeFromSelection = true;
23328
- }
23329
-
23330
23041
  /**
23331
23042
  * Resets the menu to its initial state.
23332
23043
  * This is the only way to return value to undefined.
@@ -23336,7 +23047,6 @@ class AuroMenu extends AuroElement {
23336
23047
  // Reset to undefined - initial state
23337
23048
  this.value = undefined;
23338
23049
  this.optionSelected = undefined;
23339
- this._selectedKey = undefined;
23340
23050
  this._index = -1;
23341
23051
 
23342
23052
  // Clear active option state so a follow-up open/navigation starts fresh
@@ -23398,19 +23108,6 @@ class AuroMenu extends AuroElement {
23398
23108
  this.initItems();
23399
23109
  }
23400
23110
 
23401
- // Recover `_index` from the highlighted option when it has been reset to -1.
23402
- // In multi-select, deselecting the last remaining option collapses the value
23403
- // to undefined, and the updated() reconciliation resets `_index = -1` even
23404
- // though `optionActive` still points at the highlighted option. Without this,
23405
- // reading `items[-1]` returns undefined and the re-select no-ops until the
23406
- // highlight is moved away and back. Mirrors auro-combobox's reconcileMenuIndex.
23407
- if (this._index < 0 && this.optionActive && this.items) {
23408
- const activeIndex = this.items.indexOf(this.optionActive);
23409
- if (activeIndex >= 0) {
23410
- this._index = activeIndex;
23411
- }
23412
- }
23413
-
23414
23111
  // Get currently selected menu option based on index
23415
23112
  const option = this.items ? this.items[this._index] : undefined;
23416
23113