@aurodesignsystem-dev/auro-formkit 0.0.0-pr1505.0 → 0.0.0-pr1506.1

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 (56) 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 +138 -266
  7. package/components/combobox/demo/getting-started.min.js +138 -266
  8. package/components/combobox/demo/index.min.js +138 -266
  9. package/components/combobox/dist/auro-combobox.d.ts +9 -0
  10. package/components/combobox/dist/index.js +138 -266
  11. package/components/combobox/dist/registered.js +138 -266
  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 +41 -164
  19. package/components/datepicker/demo/index.min.js +40 -156
  20. package/components/datepicker/dist/index.js +40 -156
  21. package/components/datepicker/dist/registered.js +40 -156
  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 +351 -599
  29. package/components/form/demo/getting-started.min.js +351 -599
  30. package/components/form/demo/index.min.js +351 -599
  31. package/components/form/demo/registerDemoDeps.min.js +351 -599
  32. package/components/input/demo/customize.md +55 -56
  33. package/components/input/demo/customize.min.js +36 -147
  34. package/components/input/demo/getting-started.min.js +36 -147
  35. package/components/input/demo/index.md +2 -2
  36. package/components/input/demo/index.min.js +36 -147
  37. package/components/input/dist/base-input.d.ts +0 -48
  38. package/components/input/dist/index.js +36 -147
  39. package/components/input/dist/registered.js +36 -147
  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/api.md +1 -0
  46. package/components/select/demo/customize.md +71 -0
  47. package/components/select/demo/customize.min.js +133 -26
  48. package/components/select/demo/getting-started.min.js +133 -26
  49. package/components/select/demo/index.min.js +133 -26
  50. package/components/select/demo/keyboard-behavior.md +18 -0
  51. package/components/select/dist/auro-select.d.ts +38 -2
  52. package/components/select/dist/index.js +133 -26
  53. package/components/select/dist/registered.js +133 -26
  54. package/components/select/dist/selectUtils.d.ts +12 -0
  55. package/custom-elements.json +114 -274
  56. package/package.json +1 -1
@@ -825,6 +825,8 @@ function navigateArrow(component, direction, options = {}) {
825
825
  }
826
826
  }
827
827
 
828
+ /* eslint-disable no-underscore-dangle */
829
+
828
830
  /**
829
831
  * Returns the clear button element from the active input's shadow
830
832
  * DOM, if available.
@@ -860,11 +862,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
860
862
  * @param {Object} menu - The menu component.
861
863
  */
862
864
  function reconcileMenuIndex(menu) {
863
- // eslint-disable-next-line no-underscore-dangle
864
865
  if (menu._index < 0 && menu.optionActive && menu.items) {
865
866
  const idx = menu.items.indexOf(menu.optionActive);
866
867
  if (idx >= 0) {
867
- // eslint-disable-next-line no-underscore-dangle
868
868
  menu._index = idx;
869
869
  }
870
870
  }
@@ -4888,7 +4888,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4888
4888
  }
4889
4889
  };
4890
4890
 
4891
- var formkitVersion$2 = '202606171946';
4891
+ var formkitVersion$2 = '202606192004';
4892
4892
 
4893
4893
  let AuroElement$2 = class AuroElement extends i$3 {
4894
4894
  static get properties() {
@@ -16609,6 +16609,7 @@ class AuroInputUtilities {
16609
16609
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16610
16610
 
16611
16611
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16612
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16612
16613
  return undefined;
16613
16614
  }
16614
16615
 
@@ -16760,10 +16761,8 @@ class BaseInput extends AuroElement$1 {
16760
16761
  this.layout = 'classic';
16761
16762
  this.locale = 'en-US';
16762
16763
  this.max = undefined;
16763
- this._maxObject = undefined;
16764
16764
  this.maxLength = undefined;
16765
16765
  this.min = undefined;
16766
- this._minObject = undefined;
16767
16766
  this.minLength = undefined;
16768
16767
  this.required = false;
16769
16768
  this.onDark = false;
@@ -16771,7 +16770,6 @@ class BaseInput extends AuroElement$1 {
16771
16770
  this.size = 'lg';
16772
16771
  this.shape = 'classic';
16773
16772
  this.value = undefined;
16774
- this._valueObject = undefined;
16775
16773
 
16776
16774
  this._initializePrivateDefaults();
16777
16775
  }
@@ -17311,7 +17309,7 @@ class BaseInput extends AuroElement$1 {
17311
17309
  * @returns {Date|undefined}
17312
17310
  */
17313
17311
  get valueObject() {
17314
- return this._valueObject || this._computeDateObjectFallback(this.value);
17312
+ return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
17315
17313
  }
17316
17314
 
17317
17315
  /**
@@ -17319,7 +17317,7 @@ class BaseInput extends AuroElement$1 {
17319
17317
  * @returns {Date|undefined}
17320
17318
  */
17321
17319
  get minObject() {
17322
- return this._minObject || this._computeDateObjectFallback(this.min);
17320
+ return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
17323
17321
  }
17324
17322
 
17325
17323
  /**
@@ -17327,50 +17325,7 @@ class BaseInput extends AuroElement$1 {
17327
17325
  * @returns {Date|undefined}
17328
17326
  */
17329
17327
  get maxObject() {
17330
- return this._maxObject || this._computeDateObjectFallback(this.max);
17331
- }
17332
-
17333
- /**
17334
- * Parses a date string into a Date object when the corresponding `_*Object`
17335
- * field hasn't been synced yet by `updated()`. Returns undefined when the
17336
- * input type/format isn't a full date or the string is not a valid date.
17337
- *
17338
- * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17339
- * inside its own `updated()` before this input's `updated()` has run
17340
- * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17341
- * and range checks would otherwise silently no-op (flipping the result to
17342
- * `valid` or `patternMismatch`).
17343
- * @private
17344
- * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17345
- * @returns {Date|undefined}
17346
- */
17347
- _computeDateObjectFallback(dateStr) {
17348
- if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17349
- return undefined;
17350
- }
17351
- if (!dateFormatter.isValidDate(dateStr)) {
17352
- return undefined;
17353
- }
17354
- return dateFormatter.stringToDateInstance(dateStr);
17355
- }
17356
-
17357
- /**
17358
- * Internal setter for readonly date object properties.
17359
- * @private
17360
- * @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
17361
- * @param {Date|undefined} propertyValue - Value to assign.
17362
- * @returns {void}
17363
- */
17364
- setDateObjectProperty(propertyName, propertyValue) {
17365
- const internalPropertyName = `_${propertyName}`;
17366
- const previousValue = this[internalPropertyName];
17367
-
17368
- if (previousValue === propertyValue) {
17369
- return;
17370
- }
17371
-
17372
- this[internalPropertyName] = propertyValue;
17373
- this.requestUpdate(propertyName, previousValue);
17328
+ return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
17374
17329
  }
17375
17330
 
17376
17331
  connectedCallback() {
@@ -17399,7 +17354,6 @@ class BaseInput extends AuroElement$1 {
17399
17354
  format: this.format
17400
17355
  });
17401
17356
  this.configureDataForType();
17402
- this.syncDateValues();
17403
17357
  }
17404
17358
 
17405
17359
  disconnectedCallback() {
@@ -17419,10 +17373,6 @@ class BaseInput extends AuroElement$1 {
17419
17373
  this.wrapperElement.addEventListener('click', this.handleClick);
17420
17374
  }
17421
17375
 
17422
- // add attribute for query selectors when auro-input is registered under a custom name
17423
- if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17424
- this.setAttribute('auro-input', '');
17425
- }
17426
17376
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17427
17377
 
17428
17378
  // use validity message override if declared when initializing the component
@@ -17433,7 +17383,6 @@ class BaseInput extends AuroElement$1 {
17433
17383
  this.setCustomHelpTextMessage();
17434
17384
  this.configureAutoFormatting();
17435
17385
  this.configureDataForType();
17436
- this.syncDateValues();
17437
17386
  }
17438
17387
 
17439
17388
  /**
@@ -17570,8 +17519,6 @@ class BaseInput extends AuroElement$1 {
17570
17519
  this.configureDataForType();
17571
17520
  }
17572
17521
 
17573
- this.syncDateValues(changedProperties);
17574
-
17575
17522
  if (changedProperties.has('value')) {
17576
17523
  if (this.value && this.value.length > 0) {
17577
17524
  this.hasValue = true;
@@ -17593,14 +17540,14 @@ class BaseInput extends AuroElement$1 {
17593
17540
 
17594
17541
  if (formattedValue !== this.inputElement.value) {
17595
17542
  this.skipNextProgrammaticInputEvent = true;
17596
- if (this.maskInstance && this.type === 'credit-card') {
17543
+ if (this.maskInstance && this.type !== 'date') {
17597
17544
  // Route through the mask so its _value and el.value stay in lock-step
17598
17545
  // (set value calls updateControl which writes el.value = displayValue).
17599
17546
  // Writing el.value directly leaves the mask thinking displayValue is
17600
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17601
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17602
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17603
- // and flip validity from patternMismatch to tooShort.
17547
+ // stale; _saveSelection on the next focus/click then warns. Date is
17548
+ // excluded because its formattedValue can be raw ISO when the calendar
17549
+ // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17550
+ // flip validity from patternMismatch to tooShort.
17604
17551
  this.maskInstance.value = formattedValue || '';
17605
17552
  } else if (formattedValue) {
17606
17553
  this.inputElement.value = formattedValue;
@@ -17642,120 +17589,60 @@ class BaseInput extends AuroElement$1 {
17642
17589
  }));
17643
17590
  }
17644
17591
 
17645
-
17646
- /**
17647
- * Synchronizes the ISO string values and Date object representations for date-related properties.
17648
- * This keeps the model and display values aligned when either side changes.
17649
- *
17650
- * When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
17651
- * Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
17652
- * represents a full year/month/day date format.
17653
- *
17654
- * @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
17655
- * @returns {void}
17656
- * @private
17657
- */
17658
- syncDateValues(changedProperties = undefined) {
17659
- if (!this.util.isFullDateFormat(this.type, this.format)) {
17660
- return;
17661
- }
17662
-
17663
- this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
17664
- this.syncSingleDateValue(changedProperties, 'minObject', 'min');
17665
- this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
17666
- }
17667
-
17668
- /**
17669
- * Synchronizes one date object/string property pair.
17670
- * @private
17671
- * @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
17672
- * @param {string} objectProperty - Date object property name.
17673
- * @param {string} valueProperty - ISO string property name.
17674
- * @returns {void}
17675
- */
17676
- syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
17677
- const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
17678
-
17679
- // objectProperty wins over valueProperty when both changed
17680
- if (objectPropertyChanged && this[objectProperty]) {
17681
- this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
17682
- return;
17683
- }
17684
-
17685
- const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
17686
- if (!valuePropertyChanged) {
17687
- return;
17688
- }
17689
-
17690
- // when value is newly set to the same ISO string that corresponds to the existing Date object, do not clear the Date object (avoid unnecessary updates)
17691
- if (
17692
- changedProperties &&
17693
- valueProperty === 'value' &&
17694
- changedProperties.get('value') === undefined &&
17695
- this[objectProperty] instanceof Date &&
17696
- this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
17697
- ) {
17698
- return;
17699
- }
17700
-
17701
- if (dateFormatter.isValidDate(this[valueProperty])) {
17702
- this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
17703
- } else {
17704
- this.setDateObjectProperty(objectProperty, undefined);
17705
- }
17706
- }
17707
-
17708
17592
  /**
17709
17593
  * Sets up IMasks and logic based on auto-formatting requirements.
17710
17594
  * @private
17711
17595
  * @returns {void}
17712
17596
  */
17713
17597
  configureAutoFormatting() {
17714
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17715
- // by _configuringMask above) could otherwise trigger handleInput
17716
- // processCreditCard configureAutoFormatting before the outer call's
17717
- // set value has finished its alignCursor pass.
17598
+ // _configuringMask gates two things: external re-entry into this method
17599
+ // while setup is mid-flight (from property changes that call back here),
17600
+ // and the accept/complete listeners below — both need to ignore the mask
17601
+ // events fired by our own value-restore step.
17718
17602
  if (this._configuringMask) return;
17719
17603
  this._configuringMask = true;
17720
17604
  try {
17605
+ // Destroy any prior mask so IMask can attach fresh under the new format.
17721
17606
  if (this.maskInstance) {
17722
17607
  this.maskInstance.destroy();
17723
17608
  }
17724
17609
 
17725
- // Pass new format to util
17726
17610
  this.util.updateFormat(this.format);
17727
17611
 
17728
17612
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17729
17613
 
17730
17614
  if (this.inputElement && maskOptions.mask) {
17731
-
17732
- // Stash and clear any existing value before IMask init.
17733
- // IMask's constructor processes the current input value which requires
17734
- // selection state clearing first avoids that scenario entirely.
17735
- // When the format changes (e.g. locale switch) and we have a valid ISO
17736
- // model value, compute the display string for the NEW format instead of
17737
- // re-using the old display string, which may be invalid in the new mask.
17615
+ // Capture the current display so it can be re-applied after IMask
17616
+ // attaches. The restore at the bottom goes through maskInstance.value
17617
+ // (not inputElement.value directly) so the mask's internal state and
17618
+ // the input's displayed text stay in lock-step.
17738
17619
  let existingValue = this.inputElement.value;
17620
+
17621
+ // Format-change case (e.g. locale switch): existingValue is the OLD
17622
+ // mask's display string and may not parse under the new mask. When
17623
+ // we have a valid date model, rebuild the display from valueObject
17624
+ // (the canonical source) using the new mask's format function.
17739
17625
  if (
17740
17626
  this.util.isFullDateFormat(this.type, this.format) &&
17741
17627
  this.value &&
17742
- dateFormatter.isValidDate(this.value) &&
17743
- this.valueObject instanceof Date &&
17744
- !Number.isNaN(this.valueObject.getTime()) &&
17628
+ this.valueObject &&
17745
17629
  typeof maskOptions.format === 'function'
17746
17630
  ) {
17747
17631
  existingValue = maskOptions.format(this.valueObject);
17748
17632
  }
17749
17633
 
17634
+ // Clear before IMask attaches so the constructor seeds an empty
17635
+ // internal value. Otherwise IMask reads the stale unmasked string
17636
+ // and emits a spurious 'accept' before the restore below runs.
17750
17637
  this.skipNextProgrammaticInputEvent = true;
17751
17638
  this.inputElement.value = '';
17752
17639
 
17753
17640
  this.maskInstance = IMask(this.inputElement, maskOptions);
17754
17641
 
17642
+ // Mask fires 'accept' on every value change, including the restore
17643
+ // step below. Skip events fired during configureAutoFormatting so
17644
+ // we don't overwrite a value the parent just pushed.
17755
17645
  this.maskInstance.on('accept', () => {
17756
- // Suppress propagation during configureAutoFormatting's own value-restoration
17757
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17758
- // and we don't want to overwrite a value the parent just pushed.
17759
17646
  if (this._configuringMask) return;
17760
17647
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17761
17648
  if (this.type === "date") {
@@ -17763,6 +17650,8 @@ class BaseInput extends AuroElement$1 {
17763
17650
  }
17764
17651
  });
17765
17652
 
17653
+ // Mask fires 'complete' on the restore step below for any value that
17654
+ // happens to be a complete match. Same setup-suppression as 'accept'.
17766
17655
  this.maskInstance.on('complete', () => {
17767
17656
  if (this._configuringMask) return;
17768
17657
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17771,7 +17660,9 @@ class BaseInput extends AuroElement$1 {
17771
17660
  }
17772
17661
  });
17773
17662
 
17774
- // Restore the stashed value through IMask so it's properly masked
17663
+ // Write existingValue through the mask (not the input directly) so
17664
+ // the mask reformats it under the new rules and keeps its internal
17665
+ // _value aligned with the input's displayed text.
17775
17666
  if (existingValue) {
17776
17667
  this.maskInstance.value = existingValue;
17777
17668
  }
@@ -17939,7 +17830,6 @@ class BaseInput extends AuroElement$1 {
17939
17830
  */
17940
17831
  reset() {
17941
17832
  this.value = undefined;
17942
- this.setDateObjectProperty('valueObject', undefined);
17943
17833
  this.validation.reset(this);
17944
17834
  }
17945
17835
 
@@ -17948,7 +17838,6 @@ class BaseInput extends AuroElement$1 {
17948
17838
  */
17949
17839
  clear() {
17950
17840
  this.value = undefined;
17951
- this.setDateObjectProperty('valueObject', undefined);
17952
17841
  }
17953
17842
 
17954
17843
  /**
@@ -18477,7 +18366,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18477
18366
  }
18478
18367
  };
18479
18368
 
18480
- var formkitVersion$1 = '202606171946';
18369
+ var formkitVersion$1 = '202606192004';
18481
18370
 
18482
18371
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18483
18372
  // See LICENSE in the project root for license information.
@@ -19598,7 +19487,7 @@ class AuroBibtemplate extends i$3 {
19598
19487
  }
19599
19488
  }
19600
19489
 
19601
- var formkitVersion = '202606171946';
19490
+ var formkitVersion = '202606192004';
19602
19491
 
19603
19492
  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}`;
19604
19493
 
@@ -20585,7 +20474,7 @@ class AuroCombobox extends AuroElement {
20585
20474
  if (this.menu) {
20586
20475
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20587
20476
  }
20588
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20477
+ const label = getOptionLabel(this.menu.optionSelected);
20589
20478
  this.updateTriggerTextDisplay(label || this.value);
20590
20479
  }
20591
20480
 
@@ -20599,40 +20488,9 @@ class AuroCombobox extends AuroElement {
20599
20488
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20600
20489
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20601
20490
 
20602
- if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20603
- const nextValue = label || this.value;
20604
- // Only set the flag when there's an actual write to suppress —
20605
- // syncValuesAndStates re-enters here during typing when both inputs
20606
- // already match, and a no-op flag flip would make the bib branch's
20607
- // bail eat legitimate user-input events.
20608
- const triggerNeedsSync = this.input.value !== nextValue;
20609
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20610
- if (triggerNeedsSync || bibNeedsSync) {
20611
- this._syncingDisplayValue = true;
20612
- if (triggerNeedsSync) {
20613
- this.input.value = nextValue;
20614
- }
20615
- if (bibNeedsSync) {
20616
- this.inputInBib.value = nextValue;
20617
- }
20618
- const pending = [];
20619
- if (triggerNeedsSync) {
20620
- pending.push(this.input.updateComplete);
20621
- }
20622
- if (bibNeedsSync) {
20623
- pending.push(this.inputInBib.updateComplete);
20624
- }
20625
- Promise.all(pending).then(() => {
20626
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20627
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20628
- this.input.maskInstance.updateValue();
20629
- }
20630
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20631
- this.inputInBib.maskInstance.updateValue();
20632
- }
20633
- this._syncingDisplayValue = false;
20634
- });
20635
- }
20491
+ const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20492
+ if (!suppressed) {
20493
+ this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20636
20494
  }
20637
20495
 
20638
20496
  // update the displayValue in the trigger if displayValue slot content is present
@@ -20660,6 +20518,52 @@ class AuroCombobox extends AuroElement {
20660
20518
  this.requestUpdate();
20661
20519
  }
20662
20520
 
20521
+ /**
20522
+ * Writes nextValue to the trigger input and the bib input when their current
20523
+ * value differs, then re-asserts imask after Lit's update flushes.
20524
+ * @param {string} nextValue - The value to write to both inputs.
20525
+ * @returns {Promise<void> | null} Promise that resolves after the inputs flush,
20526
+ * or null when no sync was needed (so callers can skip post-flush work).
20527
+ * @private
20528
+ */
20529
+ syncInputValuesAcrossTriggerAndBib(nextValue) {
20530
+ // Only set the flag when there's an actual write to suppress —
20531
+ // syncValuesAndStates re-enters here during typing when both inputs
20532
+ // already match, and a no-op flag flip would make the bib branch's
20533
+ // bail eat legitimate user-input events.
20534
+ const triggerNeedsSync = this.input.value !== nextValue;
20535
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20536
+ if (!triggerNeedsSync && !bibNeedsSync) {
20537
+ return null;
20538
+ }
20539
+
20540
+ this._syncingDisplayValue = true;
20541
+ if (triggerNeedsSync) {
20542
+ this.input.value = nextValue;
20543
+ }
20544
+ if (bibNeedsSync) {
20545
+ this.inputInBib.value = nextValue;
20546
+ }
20547
+
20548
+ const pending = [];
20549
+ if (triggerNeedsSync) {
20550
+ pending.push(this.input.updateComplete);
20551
+ }
20552
+ if (bibNeedsSync) {
20553
+ pending.push(this.inputInBib.updateComplete);
20554
+ }
20555
+ return Promise.all(pending).then(() => {
20556
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20557
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20558
+ this.input.maskInstance.updateValue();
20559
+ }
20560
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20561
+ this.inputInBib.maskInstance.updateValue();
20562
+ }
20563
+ this._syncingDisplayValue = false;
20564
+ });
20565
+ }
20566
+
20663
20567
  /**
20664
20568
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20665
20569
  * @private
@@ -20687,9 +20591,9 @@ class AuroCombobox extends AuroElement {
20687
20591
  this.syncValuesAndStates();
20688
20592
  }
20689
20593
 
20690
- // Re-activate when optionActive is no longer visible, or when _index has
20691
- // been reset (e.g. by clearSelection in an async update) so that
20692
- // makeSelection() can find the correct option.
20594
+ // Re-activate when optionActive is not in the current scrollable viewport,
20595
+ // or when _index has been reset (e.g. by clearSelection in an async update)
20596
+ // so that makeSelection() can find the correct option.
20693
20597
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20694
20598
  this.activateFirstEnabledAvailableOption();
20695
20599
  }
@@ -20823,28 +20727,25 @@ class AuroCombobox extends AuroElement {
20823
20727
 
20824
20728
  guardTouchPassthrough(this.menu);
20825
20729
 
20826
- // The dialog's showModal() steals focus from the trigger.
20827
- // A single rAF is enough for the dialog DOM to be painted and
20828
- // focusable, then doubleRaf finalizes the transition.
20730
+ // showModal() takes focus away from the trigger. Early focus once
20731
+ // the dialog has painted; the shared doubleRaf below is the fallback
20732
+ // for when the dialog needs an extra frame and also clears the
20733
+ // validation guard.
20829
20734
  requestAnimationFrame(() => {
20830
20735
  this.setInputFocus();
20831
20736
  });
20832
-
20833
- doubleRaf(() => {
20834
- this.setInputFocus();
20835
- this._inFullscreenTransition = false;
20836
- });
20837
- } else {
20838
- // Desktop popover-open: restore the trigger caret to end-of-text.
20839
- // Clicking the trigger lands on auro-input's floating <label for="…">
20840
- // overlay; Chrome resets the native input's selection to [0, 0] on
20841
- // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20842
- // branch parks the caret at end. doubleRaf lets the dropdown layout
20843
- // settle first, matching the fullscreen branch timing.
20844
- doubleRaf(() => {
20845
- this.setInputFocus();
20846
- });
20847
20737
  }
20738
+ // else (desktop popover-open): Chrome resets the trigger caret to
20739
+ // [0, 0] when its floating <label for="…"> overlay receives focus.
20740
+ // The shared doubleRaf below parks the caret back at end-of-text
20741
+ // after the dropdown layout settles.
20742
+
20743
+ doubleRaf(() => {
20744
+ this.setInputFocus();
20745
+ if (this._inFullscreenTransition) {
20746
+ this._inFullscreenTransition = false;
20747
+ }
20748
+ });
20848
20749
  }
20849
20750
  });
20850
20751
 
@@ -20951,9 +20852,9 @@ class AuroCombobox extends AuroElement {
20951
20852
  } else {
20952
20853
  // Safety net for the strategy-change setTimeout: a viewport-crossing
20953
20854
  // fullscreen→floating switch can close the dialog and leave focus on
20954
- // body. Every other Branch 2 callsite already has focus on the input
20955
- // via the focusin → this.focus() redirect (L1448-1452), so this is a
20956
- // no-op in the common case.
20855
+ // body. Every other call into this branch already has focus on the
20856
+ // input via the combobox-level focusin → this.focus() redirect, so
20857
+ // this is a no-op in the common case.
20957
20858
  if (!this.input.componentHasFocus) {
20958
20859
  this.input.focus();
20959
20860
  }
@@ -21072,7 +20973,7 @@ class AuroCombobox extends AuroElement {
21072
20973
  if (this.menu.optionSelected) {
21073
20974
  const selected = this.menu.optionSelected;
21074
20975
 
21075
- if (!this.optionSelected || this.optionSelected !== selected) {
20976
+ if (this.optionSelected !== selected) {
21076
20977
  this.optionSelected = selected;
21077
20978
  }
21078
20979
 
@@ -21085,7 +20986,7 @@ class AuroCombobox extends AuroElement {
21085
20986
  }
21086
20987
 
21087
20988
  // Update display
21088
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
20989
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21089
20990
 
21090
20991
  // Update match word for filtering
21091
20992
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -21103,11 +21004,15 @@ class AuroCombobox extends AuroElement {
21103
21004
 
21104
21005
  // Announce the selection after the dropdown closes so it isn't
21105
21006
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
21007
+ // Skip when there's no selected value (e.g. menu.clearSelection() from
21008
+ // the unmatched-value path), otherwise VoiceOver reads "undefined".
21106
21009
  const selectedValue = this.menu.value;
21107
- const announcementDelay = 300;
21108
- setTimeout(() => {
21109
- announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21110
- }, announcementDelay);
21010
+ if (selectedValue) {
21011
+ const announcementDelay = 300;
21012
+ setTimeout(() => {
21013
+ announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21014
+ }, announcementDelay);
21015
+ }
21111
21016
  }
21112
21017
 
21113
21018
  // Programmatic value syncs leave availableOptions stale because
@@ -21467,15 +21372,7 @@ class AuroCombobox extends AuroElement {
21467
21372
  this.menu.value = undefined;
21468
21373
  this.validation.reset(this);
21469
21374
  this.touched = false;
21470
- // Force validity back to the cleared state. validation.reset() calls
21471
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21472
- // residual validity may still be copied into the combobox by the
21473
- // auroInputElements loop. Explicitly clear here so reset() actually
21474
- // leaves the component in a "no validity" state.
21475
21375
  this.validity = undefined;
21476
- this.errorMessage = '';
21477
- this.input.validity = undefined;
21478
- this.input.errorMessage = '';
21479
21376
  }
21480
21377
 
21481
21378
  /**
@@ -21528,52 +21425,27 @@ class AuroCombobox extends AuroElement {
21528
21425
  this.input.value = this.value;
21529
21426
  }
21530
21427
 
21531
- // Sync menu.value only when an option actually matches; otherwise clear it.
21428
+ // Sync menu.value only when an option actually matches, or when filter
21429
+ // mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
21430
+ // listener at line 1206 clears combobox.value, mirroring select's
21431
+ // pattern). Otherwise sync the input display for freeform values.
21532
21432
  if (this.menu.options && this.menu.options.length > 0) {
21533
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21534
- this.setMenuValue(this.value);
21535
- } else if (this.behavior === 'filter') {
21536
- // In filter mode, freeform values aren't allowed. Push the unmatched
21537
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21538
- // and the listener at line 1206 clears combobox.value (mirrors select's
21539
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21433
+ if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
21540
21434
  this.setMenuValue(this.value);
21541
21435
  } else {
21542
- if (this.menu.value) {
21543
- this.menu.value = undefined;
21436
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21437
+ // menu.value leaves the previously-selected option element pinned
21438
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21439
+ // would then write its stale .value back into combobox.value.
21440
+ if (this.menu.value || this.menu.optionSelected) {
21441
+ this.menu.clearSelection();
21544
21442
  }
21545
- // Sync the display for programmatic freeform value changes (e.g. the
21546
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21547
- // re-entrant input event from base-input.notifyValueChanged() is
21548
- // ignored by handleInputValueChange. Skip the sync when input already
21549
- // matches to avoid spurious events during the normal typing path.
21550
- const nextValue = this.value || '';
21551
- const triggerNeedsSync = this.input.value !== nextValue;
21552
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21553
- if (triggerNeedsSync || bibNeedsSync) {
21554
- this._syncingDisplayValue = true;
21555
- if (triggerNeedsSync) {
21556
- this.input.value = nextValue;
21557
- }
21558
- if (bibNeedsSync) {
21559
- this.inputInBib.value = nextValue;
21560
- }
21561
- const pending = [];
21562
- if (triggerNeedsSync) {
21563
- pending.push(this.input.updateComplete);
21564
- }
21565
- if (bibNeedsSync) {
21566
- pending.push(this.inputInBib.updateComplete);
21567
- }
21568
- Promise.all(pending).then(() => {
21569
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21570
- this.input.maskInstance.updateValue();
21571
- }
21572
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21573
- this.inputInBib.maskInstance.updateValue();
21574
- }
21575
- this._syncingDisplayValue = false;
21576
- // handleInputValueChange bailed on the flag — refresh the filter.
21443
+ // Suggestion-mode freeform value: sync the trigger + bib to show it,
21444
+ // then refresh the filter once the inputs flush (handleInputValueChange
21445
+ // bailed on _syncingDisplayValue).
21446
+ const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21447
+ if (syncPromise) {
21448
+ syncPromise.then(() => {
21577
21449
  this._programmaticFilterRefresh = true;
21578
21450
  this.handleMenuOptions();
21579
21451
  setTimeout(() => {