@aurodesignsystem-dev/auro-formkit 0.0.0-pr1505.0 → 0.0.0-pr1506.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 (55) 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.min.js +120 -139
  7. package/components/combobox/demo/getting-started.min.js +120 -139
  8. package/components/combobox/demo/index.min.js +120 -139
  9. package/components/combobox/dist/auro-combobox.d.ts +9 -0
  10. package/components/combobox/dist/index.js +120 -139
  11. package/components/combobox/dist/registered.js +120 -139
  12. package/components/counter/demo/customize.min.js +2 -2
  13. package/components/counter/demo/index.min.js +2 -2
  14. package/components/counter/dist/index.js +2 -2
  15. package/components/counter/dist/registered.js +2 -2
  16. package/components/datepicker/demo/api.md +0 -2
  17. package/components/datepicker/demo/customize.md +33 -195
  18. package/components/datepicker/demo/customize.min.js +37 -43
  19. package/components/datepicker/demo/index.min.js +36 -35
  20. package/components/datepicker/dist/index.js +36 -35
  21. package/components/datepicker/dist/registered.js +36 -35
  22. package/components/datepicker/dist/src/auro-datepicker.d.ts +0 -2
  23. package/components/dropdown/demo/customize.min.js +1 -1
  24. package/components/dropdown/demo/getting-started.min.js +1 -1
  25. package/components/dropdown/demo/index.min.js +1 -1
  26. package/components/dropdown/dist/index.js +1 -1
  27. package/components/dropdown/dist/registered.js +1 -1
  28. package/components/form/demo/customize.min.js +325 -230
  29. package/components/form/demo/getting-started.min.js +325 -230
  30. package/components/form/demo/index.min.js +325 -230
  31. package/components/form/demo/registerDemoDeps.min.js +325 -230
  32. package/components/input/demo/customize.md +55 -56
  33. package/components/input/demo/customize.min.js +32 -26
  34. package/components/input/demo/getting-started.min.js +32 -26
  35. package/components/input/demo/index.md +2 -2
  36. package/components/input/demo/index.min.js +32 -26
  37. package/components/input/dist/index.js +32 -26
  38. package/components/input/dist/registered.js +32 -26
  39. package/components/radio/demo/customize.min.js +1 -1
  40. package/components/radio/demo/getting-started.min.js +1 -1
  41. package/components/radio/demo/index.min.js +1 -1
  42. package/components/radio/dist/index.js +1 -1
  43. package/components/radio/dist/registered.js +1 -1
  44. package/components/select/demo/api.md +1 -0
  45. package/components/select/demo/customize.md +71 -0
  46. package/components/select/demo/customize.min.js +133 -26
  47. package/components/select/demo/getting-started.min.js +133 -26
  48. package/components/select/demo/index.min.js +133 -26
  49. package/components/select/demo/keyboard-behavior.md +18 -0
  50. package/components/select/dist/auro-select.d.ts +38 -2
  51. package/components/select/dist/index.js +133 -26
  52. package/components/select/dist/registered.js +133 -26
  53. package/components/select/dist/selectUtils.d.ts +12 -0
  54. package/custom-elements.json +114 -10
  55. package/package.json +1 -1
@@ -810,6 +810,8 @@ function navigateArrow(component, direction, options = {}) {
810
810
  }
811
811
  }
812
812
 
813
+ /* eslint-disable no-underscore-dangle */
814
+
813
815
  /**
814
816
  * Returns the clear button element from the active input's shadow
815
817
  * DOM, if available.
@@ -845,11 +847,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
845
847
  * @param {Object} menu - The menu component.
846
848
  */
847
849
  function reconcileMenuIndex(menu) {
848
- // eslint-disable-next-line no-underscore-dangle
849
850
  if (menu._index < 0 && menu.optionActive && menu.items) {
850
851
  const idx = menu.items.indexOf(menu.optionActive);
851
852
  if (idx >= 0) {
852
- // eslint-disable-next-line no-underscore-dangle
853
853
  menu._index = idx;
854
854
  }
855
855
  }
@@ -4873,7 +4873,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4873
4873
  }
4874
4874
  };
4875
4875
 
4876
- var formkitVersion$2 = '202606171946';
4876
+ var formkitVersion$2 = '202606190840';
4877
4877
 
4878
4878
  let AuroElement$2 = class AuroElement extends i$3 {
4879
4879
  static get properties() {
@@ -16594,6 +16594,7 @@ class AuroInputUtilities {
16594
16594
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16595
16595
 
16596
16596
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16597
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16597
16598
  return undefined;
16598
16599
  }
16599
16600
 
@@ -17404,10 +17405,6 @@ class BaseInput extends AuroElement$1 {
17404
17405
  this.wrapperElement.addEventListener('click', this.handleClick);
17405
17406
  }
17406
17407
 
17407
- // add attribute for query selectors when auro-input is registered under a custom name
17408
- if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17409
- this.setAttribute('auro-input', '');
17410
- }
17411
17408
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17412
17409
 
17413
17410
  // use validity message override if declared when initializing the component
@@ -17578,14 +17575,14 @@ class BaseInput extends AuroElement$1 {
17578
17575
 
17579
17576
  if (formattedValue !== this.inputElement.value) {
17580
17577
  this.skipNextProgrammaticInputEvent = true;
17581
- if (this.maskInstance && this.type === 'credit-card') {
17578
+ if (this.maskInstance && this.type !== 'date') {
17582
17579
  // Route through the mask so its _value and el.value stay in lock-step
17583
17580
  // (set value calls updateControl which writes el.value = displayValue).
17584
17581
  // Writing el.value directly leaves the mask thinking displayValue is
17585
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17586
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17587
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17588
- // and flip validity from patternMismatch to tooShort.
17582
+ // stale; _saveSelection on the next focus/click then warns. Date is
17583
+ // excluded because its formattedValue can be raw ISO when the calendar
17584
+ // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17585
+ // flip validity from patternMismatch to tooShort.
17589
17586
  this.maskInstance.value = formattedValue || '';
17590
17587
  } else if (formattedValue) {
17591
17588
  this.inputElement.value = formattedValue;
@@ -17696,31 +17693,33 @@ class BaseInput extends AuroElement$1 {
17696
17693
  * @returns {void}
17697
17694
  */
17698
17695
  configureAutoFormatting() {
17699
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17700
- // by _configuringMask above) could otherwise trigger handleInput
17701
- // processCreditCard configureAutoFormatting before the outer call's
17702
- // set value has finished its alignCursor pass.
17696
+ // _configuringMask gates two things: external re-entry into this method
17697
+ // while setup is mid-flight (from property changes that call back here),
17698
+ // and the accept/complete listeners below — both need to ignore the mask
17699
+ // events fired by our own value-restore step.
17703
17700
  if (this._configuringMask) return;
17704
17701
  this._configuringMask = true;
17705
17702
  try {
17703
+ // Destroy any prior mask so IMask can attach fresh under the new format.
17706
17704
  if (this.maskInstance) {
17707
17705
  this.maskInstance.destroy();
17708
17706
  }
17709
17707
 
17710
- // Pass new format to util
17711
17708
  this.util.updateFormat(this.format);
17712
17709
 
17713
17710
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17714
17711
 
17715
17712
  if (this.inputElement && maskOptions.mask) {
17716
-
17717
- // Stash and clear any existing value before IMask init.
17718
- // IMask's constructor processes the current input value which requires
17719
- // selection state clearing first avoids that scenario entirely.
17720
- // When the format changes (e.g. locale switch) and we have a valid ISO
17721
- // model value, compute the display string for the NEW format instead of
17722
- // re-using the old display string, which may be invalid in the new mask.
17713
+ // Capture the current display so it can be re-applied after IMask
17714
+ // attaches. The restore at the bottom goes through maskInstance.value
17715
+ // (not inputElement.value directly) so the mask's internal state and
17716
+ // the input's displayed text stay in lock-step.
17723
17717
  let existingValue = this.inputElement.value;
17718
+
17719
+ // Format-change case (e.g. locale switch): existingValue is the OLD
17720
+ // mask's display string and may not parse under the new mask. When
17721
+ // we have a valid date model, rebuild the display from valueObject
17722
+ // (the canonical source) using the new mask's format function.
17724
17723
  if (
17725
17724
  this.util.isFullDateFormat(this.type, this.format) &&
17726
17725
  this.value &&
@@ -17732,15 +17731,18 @@ class BaseInput extends AuroElement$1 {
17732
17731
  existingValue = maskOptions.format(this.valueObject);
17733
17732
  }
17734
17733
 
17734
+ // Clear before IMask attaches so the constructor seeds an empty
17735
+ // internal value. Otherwise IMask reads the stale unmasked string
17736
+ // and emits a spurious 'accept' before the restore below runs.
17735
17737
  this.skipNextProgrammaticInputEvent = true;
17736
17738
  this.inputElement.value = '';
17737
17739
 
17738
17740
  this.maskInstance = IMask(this.inputElement, maskOptions);
17739
17741
 
17742
+ // Mask fires 'accept' on every value change, including the restore
17743
+ // step below. Skip events fired during configureAutoFormatting so
17744
+ // we don't overwrite a value the parent just pushed.
17740
17745
  this.maskInstance.on('accept', () => {
17741
- // Suppress propagation during configureAutoFormatting's own value-restoration
17742
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17743
- // and we don't want to overwrite a value the parent just pushed.
17744
17746
  if (this._configuringMask) return;
17745
17747
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17746
17748
  if (this.type === "date") {
@@ -17748,6 +17750,8 @@ class BaseInput extends AuroElement$1 {
17748
17750
  }
17749
17751
  });
17750
17752
 
17753
+ // Mask fires 'complete' on the restore step below for any value that
17754
+ // happens to be a complete match. Same setup-suppression as 'accept'.
17751
17755
  this.maskInstance.on('complete', () => {
17752
17756
  if (this._configuringMask) return;
17753
17757
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17756,7 +17760,9 @@ class BaseInput extends AuroElement$1 {
17756
17760
  }
17757
17761
  });
17758
17762
 
17759
- // Restore the stashed value through IMask so it's properly masked
17763
+ // Write existingValue through the mask (not the input directly) so
17764
+ // the mask reformats it under the new rules and keeps its internal
17765
+ // _value aligned with the input's displayed text.
17760
17766
  if (existingValue) {
17761
17767
  this.maskInstance.value = existingValue;
17762
17768
  }
@@ -18462,7 +18468,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18462
18468
  }
18463
18469
  };
18464
18470
 
18465
- var formkitVersion$1 = '202606171946';
18471
+ var formkitVersion$1 = '202606190840';
18466
18472
 
18467
18473
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18468
18474
  // See LICENSE in the project root for license information.
@@ -19583,7 +19589,7 @@ class AuroBibtemplate extends i$3 {
19583
19589
  }
19584
19590
  }
19585
19591
 
19586
- var formkitVersion = '202606171946';
19592
+ var formkitVersion = '202606190840';
19587
19593
 
19588
19594
  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}`;
19589
19595
 
@@ -20570,7 +20576,7 @@ class AuroCombobox extends AuroElement {
20570
20576
  if (this.menu) {
20571
20577
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20572
20578
  }
20573
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20579
+ const label = getOptionLabel(this.menu.optionSelected);
20574
20580
  this.updateTriggerTextDisplay(label || this.value);
20575
20581
  }
20576
20582
 
@@ -20584,40 +20590,9 @@ class AuroCombobox extends AuroElement {
20584
20590
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20585
20591
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20586
20592
 
20587
- if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20588
- const nextValue = label || this.value;
20589
- // Only set the flag when there's an actual write to suppress —
20590
- // syncValuesAndStates re-enters here during typing when both inputs
20591
- // already match, and a no-op flag flip would make the bib branch's
20592
- // bail eat legitimate user-input events.
20593
- const triggerNeedsSync = this.input.value !== nextValue;
20594
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20595
- if (triggerNeedsSync || bibNeedsSync) {
20596
- this._syncingDisplayValue = true;
20597
- if (triggerNeedsSync) {
20598
- this.input.value = nextValue;
20599
- }
20600
- if (bibNeedsSync) {
20601
- this.inputInBib.value = nextValue;
20602
- }
20603
- const pending = [];
20604
- if (triggerNeedsSync) {
20605
- pending.push(this.input.updateComplete);
20606
- }
20607
- if (bibNeedsSync) {
20608
- pending.push(this.inputInBib.updateComplete);
20609
- }
20610
- Promise.all(pending).then(() => {
20611
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20612
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20613
- this.input.maskInstance.updateValue();
20614
- }
20615
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20616
- this.inputInBib.maskInstance.updateValue();
20617
- }
20618
- this._syncingDisplayValue = false;
20619
- });
20620
- }
20593
+ const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20594
+ if (!suppressed) {
20595
+ this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20621
20596
  }
20622
20597
 
20623
20598
  // update the displayValue in the trigger if displayValue slot content is present
@@ -20645,6 +20620,52 @@ class AuroCombobox extends AuroElement {
20645
20620
  this.requestUpdate();
20646
20621
  }
20647
20622
 
20623
+ /**
20624
+ * Writes nextValue to the trigger input and the bib input when their current
20625
+ * value differs, then re-asserts imask after Lit's update flushes.
20626
+ * @param {string} nextValue - The value to write to both inputs.
20627
+ * @returns {Promise<void> | null} Promise that resolves after the inputs flush,
20628
+ * or null when no sync was needed (so callers can skip post-flush work).
20629
+ * @private
20630
+ */
20631
+ syncInputValuesAcrossTriggerAndBib(nextValue) {
20632
+ // Only set the flag when there's an actual write to suppress —
20633
+ // syncValuesAndStates re-enters here during typing when both inputs
20634
+ // already match, and a no-op flag flip would make the bib branch's
20635
+ // bail eat legitimate user-input events.
20636
+ const triggerNeedsSync = this.input.value !== nextValue;
20637
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20638
+ if (!triggerNeedsSync && !bibNeedsSync) {
20639
+ return null;
20640
+ }
20641
+
20642
+ this._syncingDisplayValue = true;
20643
+ if (triggerNeedsSync) {
20644
+ this.input.value = nextValue;
20645
+ }
20646
+ if (bibNeedsSync) {
20647
+ this.inputInBib.value = nextValue;
20648
+ }
20649
+
20650
+ const pending = [];
20651
+ if (triggerNeedsSync) {
20652
+ pending.push(this.input.updateComplete);
20653
+ }
20654
+ if (bibNeedsSync) {
20655
+ pending.push(this.inputInBib.updateComplete);
20656
+ }
20657
+ return Promise.all(pending).then(() => {
20658
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20659
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20660
+ this.input.maskInstance.updateValue();
20661
+ }
20662
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20663
+ this.inputInBib.maskInstance.updateValue();
20664
+ }
20665
+ this._syncingDisplayValue = false;
20666
+ });
20667
+ }
20668
+
20648
20669
  /**
20649
20670
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20650
20671
  * @private
@@ -20672,9 +20693,9 @@ class AuroCombobox extends AuroElement {
20672
20693
  this.syncValuesAndStates();
20673
20694
  }
20674
20695
 
20675
- // Re-activate when optionActive is no longer visible, or when _index has
20676
- // been reset (e.g. by clearSelection in an async update) so that
20677
- // makeSelection() can find the correct option.
20696
+ // Re-activate when optionActive is not in the current scrollable viewport,
20697
+ // or when _index has been reset (e.g. by clearSelection in an async update)
20698
+ // so that makeSelection() can find the correct option.
20678
20699
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20679
20700
  this.activateFirstEnabledAvailableOption();
20680
20701
  }
@@ -20808,28 +20829,25 @@ class AuroCombobox extends AuroElement {
20808
20829
 
20809
20830
  guardTouchPassthrough(this.menu);
20810
20831
 
20811
- // The dialog's showModal() steals focus from the trigger.
20812
- // A single rAF is enough for the dialog DOM to be painted and
20813
- // focusable, then doubleRaf finalizes the transition.
20832
+ // showModal() takes focus away from the trigger. Early focus once
20833
+ // the dialog has painted; the shared doubleRaf below is the fallback
20834
+ // for when the dialog needs an extra frame and also clears the
20835
+ // validation guard.
20814
20836
  requestAnimationFrame(() => {
20815
20837
  this.setInputFocus();
20816
20838
  });
20817
-
20818
- doubleRaf(() => {
20819
- this.setInputFocus();
20820
- this._inFullscreenTransition = false;
20821
- });
20822
- } else {
20823
- // Desktop popover-open: restore the trigger caret to end-of-text.
20824
- // Clicking the trigger lands on auro-input's floating <label for="…">
20825
- // overlay; Chrome resets the native input's selection to [0, 0] on
20826
- // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20827
- // branch parks the caret at end. doubleRaf lets the dropdown layout
20828
- // settle first, matching the fullscreen branch timing.
20829
- doubleRaf(() => {
20830
- this.setInputFocus();
20831
- });
20832
20839
  }
20840
+ // else (desktop popover-open): Chrome resets the trigger caret to
20841
+ // [0, 0] when its floating <label for="…"> overlay receives focus.
20842
+ // The shared doubleRaf below parks the caret back at end-of-text
20843
+ // after the dropdown layout settles.
20844
+
20845
+ doubleRaf(() => {
20846
+ this.setInputFocus();
20847
+ if (this._inFullscreenTransition) {
20848
+ this._inFullscreenTransition = false;
20849
+ }
20850
+ });
20833
20851
  }
20834
20852
  });
20835
20853
 
@@ -20936,9 +20954,9 @@ class AuroCombobox extends AuroElement {
20936
20954
  } else {
20937
20955
  // Safety net for the strategy-change setTimeout: a viewport-crossing
20938
20956
  // fullscreen→floating switch can close the dialog and leave focus on
20939
- // body. Every other Branch 2 callsite already has focus on the input
20940
- // via the focusin → this.focus() redirect (L1448-1452), so this is a
20941
- // no-op in the common case.
20957
+ // body. Every other call into this branch already has focus on the
20958
+ // input via the combobox-level focusin → this.focus() redirect, so
20959
+ // this is a no-op in the common case.
20942
20960
  if (!this.input.componentHasFocus) {
20943
20961
  this.input.focus();
20944
20962
  }
@@ -21057,7 +21075,7 @@ class AuroCombobox extends AuroElement {
21057
21075
  if (this.menu.optionSelected) {
21058
21076
  const selected = this.menu.optionSelected;
21059
21077
 
21060
- if (!this.optionSelected || this.optionSelected !== selected) {
21078
+ if (this.optionSelected !== selected) {
21061
21079
  this.optionSelected = selected;
21062
21080
  }
21063
21081
 
@@ -21070,7 +21088,7 @@ class AuroCombobox extends AuroElement {
21070
21088
  }
21071
21089
 
21072
21090
  // Update display
21073
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
21091
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21074
21092
 
21075
21093
  // Update match word for filtering
21076
21094
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -21452,15 +21470,7 @@ class AuroCombobox extends AuroElement {
21452
21470
  this.menu.value = undefined;
21453
21471
  this.validation.reset(this);
21454
21472
  this.touched = false;
21455
- // Force validity back to the cleared state. validation.reset() calls
21456
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21457
- // residual validity may still be copied into the combobox by the
21458
- // auroInputElements loop. Explicitly clear here so reset() actually
21459
- // leaves the component in a "no validity" state.
21460
21473
  this.validity = undefined;
21461
- this.errorMessage = '';
21462
- this.input.validity = undefined;
21463
- this.input.errorMessage = '';
21464
21474
  }
21465
21475
 
21466
21476
  /**
@@ -21513,52 +21523,23 @@ class AuroCombobox extends AuroElement {
21513
21523
  this.input.value = this.value;
21514
21524
  }
21515
21525
 
21516
- // Sync menu.value only when an option actually matches; otherwise clear it.
21526
+ // Sync menu.value only when an option actually matches, or when filter
21527
+ // mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
21528
+ // listener at line 1206 clears combobox.value, mirroring select's
21529
+ // pattern). Otherwise sync the input display for freeform values.
21517
21530
  if (this.menu.options && this.menu.options.length > 0) {
21518
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21519
- this.setMenuValue(this.value);
21520
- } else if (this.behavior === 'filter') {
21521
- // In filter mode, freeform values aren't allowed. Push the unmatched
21522
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21523
- // and the listener at line 1206 clears combobox.value (mirrors select's
21524
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21531
+ if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
21525
21532
  this.setMenuValue(this.value);
21526
21533
  } else {
21527
21534
  if (this.menu.value) {
21528
21535
  this.menu.value = undefined;
21529
21536
  }
21530
- // Sync the display for programmatic freeform value changes (e.g. the
21531
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21532
- // re-entrant input event from base-input.notifyValueChanged() is
21533
- // ignored by handleInputValueChange. Skip the sync when input already
21534
- // matches to avoid spurious events during the normal typing path.
21535
- const nextValue = this.value || '';
21536
- const triggerNeedsSync = this.input.value !== nextValue;
21537
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21538
- if (triggerNeedsSync || bibNeedsSync) {
21539
- this._syncingDisplayValue = true;
21540
- if (triggerNeedsSync) {
21541
- this.input.value = nextValue;
21542
- }
21543
- if (bibNeedsSync) {
21544
- this.inputInBib.value = nextValue;
21545
- }
21546
- const pending = [];
21547
- if (triggerNeedsSync) {
21548
- pending.push(this.input.updateComplete);
21549
- }
21550
- if (bibNeedsSync) {
21551
- pending.push(this.inputInBib.updateComplete);
21552
- }
21553
- Promise.all(pending).then(() => {
21554
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21555
- this.input.maskInstance.updateValue();
21556
- }
21557
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21558
- this.inputInBib.maskInstance.updateValue();
21559
- }
21560
- this._syncingDisplayValue = false;
21561
- // handleInputValueChange bailed on the flag — refresh the filter.
21537
+ // Suggestion-mode freeform value: sync the trigger + bib to show it,
21538
+ // then refresh the filter once the inputs flush (handleInputValueChange
21539
+ // bailed on _syncingDisplayValue).
21540
+ const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21541
+ if (syncPromise) {
21542
+ syncPromise.then(() => {
21562
21543
  this._programmaticFilterRefresh = true;
21563
21544
  this.handleMenuOptions();
21564
21545
  setTimeout(() => {