@aurodesignsystem-dev/auro-formkit 0.0.0-pr1508.0 → 0.0.0-pr1509.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 (46) 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 +293 -141
  7. package/components/combobox/demo/getting-started.min.js +293 -141
  8. package/components/combobox/demo/index.min.js +293 -141
  9. package/components/combobox/dist/auro-combobox.d.ts +0 -9
  10. package/components/combobox/dist/index.js +293 -141
  11. package/components/combobox/dist/registered.js +293 -141
  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/customize.min.js +167 -44
  17. package/components/datepicker/demo/index.min.js +167 -44
  18. package/components/datepicker/dist/index.js +167 -44
  19. package/components/datepicker/dist/registered.js +167 -44
  20. package/components/dropdown/demo/customize.min.js +1 -1
  21. package/components/dropdown/demo/getting-started.min.js +1 -1
  22. package/components/dropdown/demo/index.min.js +1 -1
  23. package/components/dropdown/dist/index.js +1 -1
  24. package/components/dropdown/dist/registered.js +1 -1
  25. package/components/form/demo/customize.min.js +631 -232
  26. package/components/form/demo/getting-started.min.js +631 -232
  27. package/components/form/demo/index.min.js +631 -232
  28. package/components/form/demo/registerDemoDeps.min.js +631 -232
  29. package/components/input/demo/customize.min.js +165 -41
  30. package/components/input/demo/getting-started.min.js +165 -41
  31. package/components/input/demo/index.min.js +165 -41
  32. package/components/input/dist/base-input.d.ts +49 -1
  33. package/components/input/dist/index.js +165 -41
  34. package/components/input/dist/registered.js +165 -41
  35. package/components/radio/demo/customize.min.js +1 -1
  36. package/components/radio/demo/getting-started.min.js +1 -1
  37. package/components/radio/demo/index.min.js +1 -1
  38. package/components/radio/dist/index.js +1 -1
  39. package/components/radio/dist/registered.js +1 -1
  40. package/components/select/demo/customize.min.js +2 -2
  41. package/components/select/demo/getting-started.min.js +2 -2
  42. package/components/select/demo/index.min.js +2 -2
  43. package/components/select/dist/index.js +2 -2
  44. package/components/select/dist/registered.js +2 -2
  45. package/custom-elements.json +264 -20
  46. package/package.json +1 -1
@@ -767,8 +767,6 @@ function navigateArrow(component, direction, options = {}) {
767
767
  }
768
768
  }
769
769
 
770
- /* eslint-disable no-underscore-dangle */
771
-
772
770
  /**
773
771
  * Returns the clear button element from the active input's shadow
774
772
  * DOM, if available.
@@ -804,9 +802,11 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
804
802
  * @param {Object} menu - The menu component.
805
803
  */
806
804
  function reconcileMenuIndex(menu) {
805
+ // eslint-disable-next-line no-underscore-dangle
807
806
  if (menu._index < 0 && menu.optionActive && menu.items) {
808
807
  const idx = menu.items.indexOf(menu.optionActive);
809
808
  if (idx >= 0) {
809
+ // eslint-disable-next-line no-underscore-dangle
810
810
  menu._index = idx;
811
811
  }
812
812
  }
@@ -4806,7 +4806,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4806
4806
  }
4807
4807
  };
4808
4808
 
4809
- var formkitVersion$2 = '202606231948';
4809
+ var formkitVersion$2 = '202606232113';
4810
4810
 
4811
4811
  let AuroElement$2 = class AuroElement extends LitElement {
4812
4812
  static get properties() {
@@ -16528,7 +16528,6 @@ class AuroInputUtilities {
16528
16528
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16529
16529
 
16530
16530
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16531
- console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16532
16531
  return undefined;
16533
16532
  }
16534
16533
 
@@ -16680,8 +16679,10 @@ class BaseInput extends AuroElement$1 {
16680
16679
  this.layout = 'classic';
16681
16680
  this.locale = 'en-US';
16682
16681
  this.max = undefined;
16682
+ this._maxObject = undefined;
16683
16683
  this.maxLength = undefined;
16684
16684
  this.min = undefined;
16685
+ this._minObject = undefined;
16685
16686
  this.minLength = undefined;
16686
16687
  this.required = false;
16687
16688
  this.onDark = false;
@@ -16689,6 +16690,7 @@ class BaseInput extends AuroElement$1 {
16689
16690
  this.size = 'lg';
16690
16691
  this.shape = 'classic';
16691
16692
  this.value = undefined;
16693
+ this._valueObject = undefined;
16692
16694
 
16693
16695
  this._initializePrivateDefaults();
16694
16696
  }
@@ -17228,7 +17230,7 @@ class BaseInput extends AuroElement$1 {
17228
17230
  * @returns {Date|undefined}
17229
17231
  */
17230
17232
  get valueObject() {
17231
- return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
17233
+ return this._valueObject || this._computeDateObjectFallback(this.value);
17232
17234
  }
17233
17235
 
17234
17236
  /**
@@ -17236,7 +17238,7 @@ class BaseInput extends AuroElement$1 {
17236
17238
  * @returns {Date|undefined}
17237
17239
  */
17238
17240
  get minObject() {
17239
- return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
17241
+ return this._minObject || this._computeDateObjectFallback(this.min);
17240
17242
  }
17241
17243
 
17242
17244
  /**
@@ -17244,7 +17246,50 @@ class BaseInput extends AuroElement$1 {
17244
17246
  * @returns {Date|undefined}
17245
17247
  */
17246
17248
  get maxObject() {
17247
- return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
17249
+ return this._maxObject || this._computeDateObjectFallback(this.max);
17250
+ }
17251
+
17252
+ /**
17253
+ * Parses a date string into a Date object when the corresponding `_*Object`
17254
+ * field hasn't been synced yet by `updated()`. Returns undefined when the
17255
+ * input type/format isn't a full date or the string is not a valid date.
17256
+ *
17257
+ * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17258
+ * inside its own `updated()` before this input's `updated()` has run
17259
+ * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17260
+ * and range checks would otherwise silently no-op (flipping the result to
17261
+ * `valid` or `patternMismatch`).
17262
+ * @private
17263
+ * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17264
+ * @returns {Date|undefined}
17265
+ */
17266
+ _computeDateObjectFallback(dateStr) {
17267
+ if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17268
+ return undefined;
17269
+ }
17270
+ if (!dateFormatter.isValidDate(dateStr)) {
17271
+ return undefined;
17272
+ }
17273
+ return dateFormatter.stringToDateInstance(dateStr);
17274
+ }
17275
+
17276
+ /**
17277
+ * Internal setter for readonly date object properties.
17278
+ * @private
17279
+ * @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
17280
+ * @param {Date|undefined} propertyValue - Value to assign.
17281
+ * @returns {void}
17282
+ */
17283
+ setDateObjectProperty(propertyName, propertyValue) {
17284
+ const internalPropertyName = `_${propertyName}`;
17285
+ const previousValue = this[internalPropertyName];
17286
+
17287
+ if (previousValue === propertyValue) {
17288
+ return;
17289
+ }
17290
+
17291
+ this[internalPropertyName] = propertyValue;
17292
+ this.requestUpdate(propertyName, previousValue);
17248
17293
  }
17249
17294
 
17250
17295
  connectedCallback() {
@@ -17273,6 +17318,7 @@ class BaseInput extends AuroElement$1 {
17273
17318
  format: this.format
17274
17319
  });
17275
17320
  this.configureDataForType();
17321
+ this.syncDateValues();
17276
17322
  }
17277
17323
 
17278
17324
  disconnectedCallback() {
@@ -17292,6 +17338,15 @@ class BaseInput extends AuroElement$1 {
17292
17338
  this.wrapperElement.addEventListener('click', this.handleClick);
17293
17339
  }
17294
17340
 
17341
+ // add attribute for query selectors when auro-input is registered under a custom name
17342
+ // COVERAGE: the body of this branch is unreachable in WTR — connectedCallback
17343
+ // performs the same `setAttribute('auro-input', '')` earlier (see L665-667),
17344
+ // so by the time firstUpdated runs the attribute is already present and the
17345
+ // `!hasAttribute('auro-input')` guard is false. Retained as a defensive
17346
+ // safety net in case connectedCallback is ever short-circuited.
17347
+ if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17348
+ this.setAttribute('auro-input', '');
17349
+ }
17295
17350
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17296
17351
 
17297
17352
  // use validity message override if declared when initializing the component
@@ -17302,6 +17357,7 @@ class BaseInput extends AuroElement$1 {
17302
17357
  this.setCustomHelpTextMessage();
17303
17358
  this.configureAutoFormatting();
17304
17359
  this.configureDataForType();
17360
+ this.syncDateValues();
17305
17361
  }
17306
17362
 
17307
17363
  /**
@@ -17377,6 +17433,13 @@ class BaseInput extends AuroElement$1 {
17377
17433
 
17378
17434
  if (typeToI18n.includes(this.type)) {
17379
17435
  this.setCustomValidityForType = i18n(this.lang, this.type);
17436
+ // COVERAGE: this `else if` branch is unreachable in WTR. connectedCallback
17437
+ // (L682) calls configureDataForType, which at L1266-1268 assigns
17438
+ // `this.format = this.util.getDateMaskFromLocale().toLowerCase()` for any
17439
+ // type=date input whose `format` attribute is unset. That runs before
17440
+ // firstUpdated invokes setCustomHelpTextMessage, so `!this.format` is
17441
+ // always false here. Retained as a defensive fallback; the whole function
17442
+ // is @deprecated per AB#1557296 and slated for removal.
17380
17443
  } else if (!this.format && this.type === 'date') {
17381
17444
  this.setCustomValidityForType = i18n(this.lang, 'dateMMDDYYYY');
17382
17445
  } else if (this.dateFormatMap[this.format]) {
@@ -17438,6 +17501,8 @@ class BaseInput extends AuroElement$1 {
17438
17501
  this.configureDataForType();
17439
17502
  }
17440
17503
 
17504
+ this.syncDateValues(changedProperties);
17505
+
17441
17506
  if (changedProperties.has('value')) {
17442
17507
  if (this.value && this.value.length > 0) {
17443
17508
  this.hasValue = true;
@@ -17459,14 +17524,14 @@ class BaseInput extends AuroElement$1 {
17459
17524
 
17460
17525
  if (formattedValue !== this.inputElement.value) {
17461
17526
  this.skipNextProgrammaticInputEvent = true;
17462
- if (this.maskInstance && this.type !== 'date') {
17527
+ if (this.maskInstance && this.type === 'credit-card') {
17463
17528
  // Route through the mask so its _value and el.value stay in lock-step
17464
17529
  // (set value calls updateControl which writes el.value = displayValue).
17465
17530
  // Writing el.value directly leaves the mask thinking displayValue is
17466
- // stale; _saveSelection on the next focus/click then warns. Date is
17467
- // excluded because its formattedValue can be raw ISO when the calendar
17468
- // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17469
- // flip validity from patternMismatch to tooShort.
17531
+ // stale; _saveSelection on the next focus/click then warns. Scoped to
17532
+ // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17533
+ // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17534
+ // and flip validity from patternMismatch to tooShort.
17470
17535
  this.maskInstance.value = formattedValue || '';
17471
17536
  } else if (formattedValue) {
17472
17537
  this.inputElement.value = formattedValue;
@@ -17508,65 +17573,120 @@ class BaseInput extends AuroElement$1 {
17508
17573
  }));
17509
17574
  }
17510
17575
 
17576
+
17577
+ /**
17578
+ * Synchronizes the ISO string values and Date object representations for date-related properties.
17579
+ * This keeps the model and display values aligned when either side changes.
17580
+ *
17581
+ * When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
17582
+ * Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
17583
+ * represents a full year/month/day date format.
17584
+ *
17585
+ * @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
17586
+ * @returns {void}
17587
+ * @private
17588
+ */
17589
+ syncDateValues(changedProperties = undefined) {
17590
+ if (!this.util.isFullDateFormat(this.type, this.format)) {
17591
+ return;
17592
+ }
17593
+
17594
+ this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
17595
+ this.syncSingleDateValue(changedProperties, 'minObject', 'min');
17596
+ this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
17597
+ }
17598
+
17599
+ /**
17600
+ * Synchronizes one date object/string property pair.
17601
+ * @private
17602
+ * @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
17603
+ * @param {string} objectProperty - Date object property name.
17604
+ * @param {string} valueProperty - ISO string property name.
17605
+ * @returns {void}
17606
+ */
17607
+ syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
17608
+ const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
17609
+
17610
+ // objectProperty wins over valueProperty when both changed
17611
+ if (objectPropertyChanged && this[objectProperty]) {
17612
+ this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
17613
+ return;
17614
+ }
17615
+
17616
+ const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
17617
+ if (!valuePropertyChanged) {
17618
+ return;
17619
+ }
17620
+
17621
+ // 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)
17622
+ if (
17623
+ changedProperties &&
17624
+ valueProperty === 'value' &&
17625
+ changedProperties.get('value') === undefined &&
17626
+ this[objectProperty] instanceof Date &&
17627
+ this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
17628
+ ) {
17629
+ return;
17630
+ }
17631
+
17632
+ if (dateFormatter.isValidDate(this[valueProperty])) {
17633
+ this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
17634
+ } else {
17635
+ this.setDateObjectProperty(objectProperty, undefined);
17636
+ }
17637
+ }
17638
+
17511
17639
  /**
17512
17640
  * Sets up IMasks and logic based on auto-formatting requirements.
17513
17641
  * @private
17514
17642
  * @returns {void}
17515
17643
  */
17516
17644
  configureAutoFormatting() {
17517
- // _configuringMask gates two things: external re-entry into this method
17518
- // while setup is mid-flight (from property changes that call back here),
17519
- // and the accept/complete listeners below both need to ignore the mask
17520
- // events fired by our own value-restore step.
17645
+ // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17646
+ // by _configuringMask above) could otherwise trigger handleInput
17647
+ // processCreditCard configureAutoFormatting before the outer call's
17648
+ // set value has finished its alignCursor pass.
17521
17649
  if (this._configuringMask) return;
17522
17650
  this._configuringMask = true;
17523
17651
  try {
17524
- // Destroy any prior mask so IMask can attach fresh under the new format.
17525
- // Null the reference too — if the new maskOptions.mask is falsy (e.g.
17526
- // type switched from credit-card to text) the IMask() reassignment
17527
- // below is skipped, and downstream writes at line ~823 would otherwise
17528
- // route through the destroyed instance.
17529
17652
  if (this.maskInstance) {
17530
17653
  this.maskInstance.destroy();
17531
- this.maskInstance = null;
17532
17654
  }
17533
17655
 
17656
+ // Pass new format to util
17534
17657
  this.util.updateFormat(this.format);
17535
17658
 
17536
17659
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17537
17660
 
17538
17661
  if (this.inputElement && maskOptions.mask) {
17539
- // Capture the current display so it can be re-applied after IMask
17540
- // attaches. The restore at the bottom goes through maskInstance.value
17541
- // (not inputElement.value directly) so the mask's internal state and
17542
- // the input's displayed text stay in lock-step.
17543
- let existingValue = this.inputElement.value;
17544
17662
 
17545
- // Format-change case (e.g. locale switch): existingValue is the OLD
17546
- // mask's display string and may not parse under the new mask. When
17547
- // we have a valid date model, rebuild the display from valueObject
17548
- // (the canonical source) using the new mask's format function.
17663
+ // Stash and clear any existing value before IMask init.
17664
+ // IMask's constructor processes the current input value which requires
17665
+ // selection state clearing first avoids that scenario entirely.
17666
+ // When the format changes (e.g. locale switch) and we have a valid ISO
17667
+ // model value, compute the display string for the NEW format instead of
17668
+ // re-using the old display string, which may be invalid in the new mask.
17669
+ let existingValue = this.inputElement.value;
17549
17670
  if (
17550
17671
  this.util.isFullDateFormat(this.type, this.format) &&
17551
17672
  this.value &&
17552
- this.valueObject &&
17673
+ dateFormatter.isValidDate(this.value) &&
17674
+ this.valueObject instanceof Date &&
17675
+ !Number.isNaN(this.valueObject.getTime()) &&
17553
17676
  typeof maskOptions.format === 'function'
17554
17677
  ) {
17555
17678
  existingValue = maskOptions.format(this.valueObject);
17556
17679
  }
17557
17680
 
17558
- // Clear before IMask attaches so the constructor seeds an empty
17559
- // internal value. Otherwise IMask reads the stale unmasked string
17560
- // and emits a spurious 'accept' before the restore below runs.
17561
17681
  this.skipNextProgrammaticInputEvent = true;
17562
17682
  this.inputElement.value = '';
17563
17683
 
17564
17684
  this.maskInstance = IMask(this.inputElement, maskOptions);
17565
17685
 
17566
- // Mask fires 'accept' on every value change, including the restore
17567
- // step below. Skip events fired during configureAutoFormatting so
17568
- // we don't overwrite a value the parent just pushed.
17569
17686
  this.maskInstance.on('accept', () => {
17687
+ // Suppress propagation during configureAutoFormatting's own value-restoration
17688
+ // (line below) — the mask emits 'accept' on every value-set, including ours,
17689
+ // and we don't want to overwrite a value the parent just pushed.
17570
17690
  if (this._configuringMask) return;
17571
17691
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17572
17692
  if (this.type === "date") {
@@ -17574,8 +17694,6 @@ class BaseInput extends AuroElement$1 {
17574
17694
  }
17575
17695
  });
17576
17696
 
17577
- // Mask fires 'complete' on the restore step below for any value that
17578
- // happens to be a complete match. Same setup-suppression as 'accept'.
17579
17697
  this.maskInstance.on('complete', () => {
17580
17698
  if (this._configuringMask) return;
17581
17699
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17584,9 +17702,7 @@ class BaseInput extends AuroElement$1 {
17584
17702
  }
17585
17703
  });
17586
17704
 
17587
- // Write existingValue through the mask (not the input directly) so
17588
- // the mask reformats it under the new rules and keeps its internal
17589
- // _value aligned with the input's displayed text.
17705
+ // Restore the stashed value through IMask so it's properly masked
17590
17706
  if (existingValue) {
17591
17707
  this.maskInstance.value = existingValue;
17592
17708
  }
@@ -17754,6 +17870,7 @@ class BaseInput extends AuroElement$1 {
17754
17870
  */
17755
17871
  reset() {
17756
17872
  this.value = undefined;
17873
+ this.setDateObjectProperty('valueObject', undefined);
17757
17874
  this.validation.reset(this);
17758
17875
  }
17759
17876
 
@@ -17762,6 +17879,7 @@ class BaseInput extends AuroElement$1 {
17762
17879
  */
17763
17880
  clear() {
17764
17881
  this.value = undefined;
17882
+ this.setDateObjectProperty('valueObject', undefined);
17765
17883
  }
17766
17884
 
17767
17885
  /**
@@ -17799,6 +17917,12 @@ class BaseInput extends AuroElement$1 {
17799
17917
  */
17800
17918
  get placeholderStr() {
17801
17919
  if (!this.placeholder && this.type === 'date') {
17920
+ // COVERAGE: the `'MM/DD/YYYY'` literal fallback is unreachable in WTR.
17921
+ // connectedCallback (L682) calls configureDataForType, which at L1266-1268
17922
+ // assigns `this.format = this.util.getDateMaskFromLocale().toLowerCase()`
17923
+ // when type=date and format is unset. That runs before the first render
17924
+ // reads this getter, so the ternary's truthy arm always wins. Retained
17925
+ // as a defensive fallback for direct getter calls before connection.
17802
17926
  return this.format ? this.format.toUpperCase() : 'MM/DD/YYYY';
17803
17927
  }
17804
17928
  return this.placeholder || "";
@@ -18290,7 +18414,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18290
18414
  }
18291
18415
  };
18292
18416
 
18293
- var formkitVersion$1 = '202606231948';
18417
+ var formkitVersion$1 = '202606232113';
18294
18418
 
18295
18419
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18296
18420
  // See LICENSE in the project root for license information.
@@ -19411,7 +19535,7 @@ class AuroBibtemplate extends LitElement {
19411
19535
  }
19412
19536
  }
19413
19537
 
19414
- var formkitVersion = '202606231948';
19538
+ var formkitVersion = '202606232113';
19415
19539
 
19416
19540
  var styleCss$1 = css`.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}`;
19417
19541
 
@@ -20398,7 +20522,7 @@ class AuroCombobox extends AuroElement {
20398
20522
  if (this.menu) {
20399
20523
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20400
20524
  }
20401
- const label = getOptionLabel(this.menu.optionSelected);
20525
+ const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20402
20526
  this.updateTriggerTextDisplay(label || this.value);
20403
20527
  }
20404
20528
 
@@ -20412,9 +20536,40 @@ class AuroCombobox extends AuroElement {
20412
20536
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20413
20537
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20414
20538
 
20415
- const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20416
- if (!suppressed) {
20417
- this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20539
+ if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20540
+ const nextValue = label || this.value;
20541
+ // Only set the flag when there's an actual write to suppress —
20542
+ // syncValuesAndStates re-enters here during typing when both inputs
20543
+ // already match, and a no-op flag flip would make the bib branch's
20544
+ // bail eat legitimate user-input events.
20545
+ const triggerNeedsSync = this.input.value !== nextValue;
20546
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20547
+ if (triggerNeedsSync || bibNeedsSync) {
20548
+ this._syncingDisplayValue = true;
20549
+ if (triggerNeedsSync) {
20550
+ this.input.value = nextValue;
20551
+ }
20552
+ if (bibNeedsSync) {
20553
+ this.inputInBib.value = nextValue;
20554
+ }
20555
+ const pending = [];
20556
+ if (triggerNeedsSync) {
20557
+ pending.push(this.input.updateComplete);
20558
+ }
20559
+ if (bibNeedsSync) {
20560
+ pending.push(this.inputInBib.updateComplete);
20561
+ }
20562
+ Promise.all(pending).then(() => {
20563
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20564
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20565
+ this.input.maskInstance.updateValue();
20566
+ }
20567
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20568
+ this.inputInBib.maskInstance.updateValue();
20569
+ }
20570
+ this._syncingDisplayValue = false;
20571
+ });
20572
+ }
20418
20573
  }
20419
20574
 
20420
20575
  // update the displayValue in the trigger if displayValue slot content is present
@@ -20442,48 +20597,6 @@ class AuroCombobox extends AuroElement {
20442
20597
  this.requestUpdate();
20443
20598
  }
20444
20599
 
20445
- /**
20446
- * Writes nextValue to the trigger input and the bib input when their current
20447
- * value differs, then re-asserts imask after Lit's update flushes.
20448
- * @param {string} nextValue - The value to write to both inputs.
20449
- * @returns {Promise<void> | null} Promise that resolves after the inputs flush,
20450
- * or null when no sync was needed (so callers can skip post-flush work).
20451
- * @private
20452
- */
20453
- syncInputValuesAcrossTriggerAndBib(nextValue) {
20454
- // Only set the flag when there's an actual write to suppress —
20455
- // syncValuesAndStates re-enters here during typing when both inputs
20456
- // already match, and a no-op flag flip would make the bib branch's
20457
- // bail eat legitimate user-input events.
20458
- const triggerNeedsSync = this.input.value !== nextValue;
20459
- const bibNeedsSync = this.inputInBib.value !== nextValue;
20460
- if (!triggerNeedsSync && !bibNeedsSync) {
20461
- return null;
20462
- }
20463
-
20464
- this._syncingDisplayValue = true;
20465
-
20466
- const pending = [];
20467
- if (triggerNeedsSync) {
20468
- this.input.value = nextValue;
20469
- pending.push(this.input.updateComplete);
20470
- }
20471
- if (bibNeedsSync) {
20472
- this.inputInBib.value = nextValue;
20473
- pending.push(this.inputInBib.updateComplete);
20474
- }
20475
- return Promise.all(pending).then(() => {
20476
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20477
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20478
- this.input.maskInstance.updateValue();
20479
- }
20480
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20481
- this.inputInBib.maskInstance.updateValue();
20482
- }
20483
- this._syncingDisplayValue = false;
20484
- });
20485
- }
20486
-
20487
20600
  /**
20488
20601
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20489
20602
  * @private
@@ -20511,9 +20624,9 @@ class AuroCombobox extends AuroElement {
20511
20624
  this.syncValuesAndStates();
20512
20625
  }
20513
20626
 
20514
- // Re-activate when optionActive is not in the current scrollable viewport,
20515
- // or when _index has been reset (e.g. by clearSelection in an async update)
20516
- // so that makeSelection() can find the correct option.
20627
+ // Re-activate when optionActive is no longer visible, or when _index has
20628
+ // been reset (e.g. by clearSelection in an async update) so that
20629
+ // makeSelection() can find the correct option.
20517
20630
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20518
20631
  this.activateFirstEnabledAvailableOption();
20519
20632
  }
@@ -20647,25 +20760,28 @@ class AuroCombobox extends AuroElement {
20647
20760
 
20648
20761
  guardTouchPassthrough(this.menu);
20649
20762
 
20650
- // showModal() takes focus away from the trigger. Early focus once
20651
- // the dialog has painted; the shared doubleRaf below is the fallback
20652
- // for when the dialog needs an extra frame and also clears the
20653
- // validation guard.
20763
+ // The dialog's showModal() steals focus from the trigger.
20764
+ // A single rAF is enough for the dialog DOM to be painted and
20765
+ // focusable, then doubleRaf finalizes the transition.
20654
20766
  requestAnimationFrame(() => {
20655
20767
  this.setInputFocus();
20656
20768
  });
20657
- }
20658
- // else (desktop popover-open): Chrome resets the trigger caret to
20659
- // [0, 0] when its floating <label for="…"> overlay receives focus.
20660
- // The shared doubleRaf below parks the caret back at end-of-text
20661
- // after the dropdown layout settles.
20662
-
20663
- doubleRaf(() => {
20664
- this.setInputFocus();
20665
- if (this._inFullscreenTransition) {
20769
+
20770
+ doubleRaf(() => {
20771
+ this.setInputFocus();
20666
20772
  this._inFullscreenTransition = false;
20667
- }
20668
- });
20773
+ });
20774
+ } else {
20775
+ // Desktop popover-open: restore the trigger caret to end-of-text.
20776
+ // Clicking the trigger lands on auro-input's floating <label for="…">
20777
+ // overlay; Chrome resets the native input's selection to [0, 0] on
20778
+ // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20779
+ // branch parks the caret at end. doubleRaf lets the dropdown layout
20780
+ // settle first, matching the fullscreen branch timing.
20781
+ doubleRaf(() => {
20782
+ this.setInputFocus();
20783
+ });
20784
+ }
20669
20785
  }
20670
20786
  });
20671
20787
 
@@ -20876,7 +20992,7 @@ class AuroCombobox extends AuroElement {
20876
20992
  if (this.menu.optionSelected) {
20877
20993
  const selected = this.menu.optionSelected;
20878
20994
 
20879
- if (this.optionSelected !== selected) {
20995
+ if (!this.optionSelected || this.optionSelected !== selected) {
20880
20996
  this.optionSelected = selected;
20881
20997
  }
20882
20998
 
@@ -20889,7 +21005,7 @@ class AuroCombobox extends AuroElement {
20889
21005
  }
20890
21006
 
20891
21007
  // Update display
20892
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21008
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
20893
21009
 
20894
21010
  // Update match word for filtering
20895
21011
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -20905,22 +21021,13 @@ class AuroCombobox extends AuroElement {
20905
21021
  this.hideBib();
20906
21022
  }
20907
21023
 
20908
- // Move focus to the clear button when the user makes a selection.
20909
- if (!isEcho && this.menu.value !== undefined) {
20910
- this.setClearBtnFocus();
20911
- }
20912
-
20913
21024
  // Announce the selection after the dropdown closes so it isn't
20914
21025
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
20915
- // Skip when there's no selected value (e.g. menu.clearSelection() from
20916
- // the unmatched-value path), otherwise VoiceOver reads "undefined".
20917
21026
  const selectedValue = this.menu.value;
20918
- if (selectedValue) {
20919
- const announcementDelay = 300;
20920
- setTimeout(() => {
20921
- announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
20922
- }, announcementDelay);
20923
- }
21027
+ const announcementDelay = 300;
21028
+ setTimeout(() => {
21029
+ announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21030
+ }, announcementDelay);
20924
21031
  }
20925
21032
 
20926
21033
  // Programmatic value syncs leave availableOptions stale because
@@ -20934,6 +21041,18 @@ class AuroCombobox extends AuroElement {
20934
21041
  this._programmaticFilterRefresh = false;
20935
21042
  }, 0);
20936
21043
  }
21044
+
21045
+ // base-input skips auto-validate when focus is inside its own shadow,
21046
+ // which it is after setTriggerInputFocus — re-run so prior tooShort
21047
+ // clears. processCreditCard reasserts errorMessage on every render.
21048
+ if (!isEcho && this.menu.optionSelected && this.input.validate) {
21049
+ this.input.updateComplete.then(() => {
21050
+ this.input.validate(true);
21051
+ if (this.input.validity === 'valid') {
21052
+ this.input.errorMessage = '';
21053
+ }
21054
+ });
21055
+ }
20937
21056
  });
20938
21057
 
20939
21058
  this.menu.addEventListener('auroMenu-customEventFired', () => {
@@ -21268,7 +21387,15 @@ class AuroCombobox extends AuroElement {
21268
21387
  this.menu.value = undefined;
21269
21388
  this.validation.reset(this);
21270
21389
  this.touched = false;
21390
+ // Force validity back to the cleared state. validation.reset() calls
21391
+ // validate() at the end, and (post-credit-card-fix) the trigger input's
21392
+ // residual validity may still be copied into the combobox by the
21393
+ // auroInputElements loop. Explicitly clear here so reset() actually
21394
+ // leaves the component in a "no validity" state.
21271
21395
  this.validity = undefined;
21396
+ this.errorMessage = '';
21397
+ this.input.validity = undefined;
21398
+ this.input.errorMessage = '';
21272
21399
  }
21273
21400
 
21274
21401
  /**
@@ -21321,27 +21448,52 @@ class AuroCombobox extends AuroElement {
21321
21448
  this.input.value = this.value;
21322
21449
  }
21323
21450
 
21324
- // Sync menu.value only when an option actually matches, or when filter
21325
- // mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
21326
- // listener at line 1206 clears combobox.value, mirroring select's
21327
- // pattern). Otherwise sync the input display for freeform values.
21451
+ // Sync menu.value only when an option actually matches; otherwise clear it.
21328
21452
  if (this.menu.options && this.menu.options.length > 0) {
21329
- if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
21453
+ if (this.menu.options.some((opt) => opt.value === this.value)) {
21454
+ this.setMenuValue(this.value);
21455
+ } else if (this.behavior === 'filter') {
21456
+ // In filter mode, freeform values aren't allowed. Push the unmatched
21457
+ // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21458
+ // and the listener at line 1206 clears combobox.value (mirrors select's
21459
+ // pattern). Bypassing setMenuValue here would skip the failure cascade.
21330
21460
  this.setMenuValue(this.value);
21331
21461
  } else {
21332
- // Clear menu.value AND menu.optionSelected together. Clearing only
21333
- // menu.value leaves the previously-selected option element pinned
21334
- // as menu.optionSelected; a later auroMenu-selectedOption event
21335
- // would then write its stale .value back into combobox.value.
21336
- if (this.menu.value || this.menu.optionSelected) {
21337
- this.menu.clearSelection();
21462
+ if (this.menu.value) {
21463
+ this.menu.value = undefined;
21338
21464
  }
21339
- // Suggestion-mode freeform value: sync the trigger + bib to show it,
21340
- // then refresh the filter once the inputs flush (handleInputValueChange
21341
- // bailed on _syncingDisplayValue).
21342
- const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21343
- if (syncPromise) {
21344
- syncPromise.then(() => {
21465
+ // Sync the display for programmatic freeform value changes (e.g. the
21466
+ // swap-values pattern). Guard with _syncingDisplayValue so that the
21467
+ // re-entrant input event from base-input.notifyValueChanged() is
21468
+ // ignored by handleInputValueChange. Skip the sync when input already
21469
+ // matches to avoid spurious events during the normal typing path.
21470
+ const nextValue = this.value || '';
21471
+ const triggerNeedsSync = this.input.value !== nextValue;
21472
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21473
+ if (triggerNeedsSync || bibNeedsSync) {
21474
+ this._syncingDisplayValue = true;
21475
+ if (triggerNeedsSync) {
21476
+ this.input.value = nextValue;
21477
+ }
21478
+ if (bibNeedsSync) {
21479
+ this.inputInBib.value = nextValue;
21480
+ }
21481
+ const pending = [];
21482
+ if (triggerNeedsSync) {
21483
+ pending.push(this.input.updateComplete);
21484
+ }
21485
+ if (bibNeedsSync) {
21486
+ pending.push(this.inputInBib.updateComplete);
21487
+ }
21488
+ Promise.all(pending).then(() => {
21489
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21490
+ this.input.maskInstance.updateValue();
21491
+ }
21492
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21493
+ this.inputInBib.maskInstance.updateValue();
21494
+ }
21495
+ this._syncingDisplayValue = false;
21496
+ // handleInputValueChange bailed on the flag — refresh the filter.
21345
21497
  this._programmaticFilterRefresh = true;
21346
21498
  this.handleMenuOptions();
21347
21499
  setTimeout(() => {