@aurodesignsystem-dev/auro-formkit 0.0.0-pr1516.0 → 0.0.0-pr1519.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 (54) hide show
  1. package/components/checkbox/demo/customize.min.js +18 -10
  2. package/components/checkbox/demo/getting-started.min.js +18 -10
  3. package/components/checkbox/demo/index.min.js +18 -10
  4. package/components/checkbox/dist/index.js +18 -10
  5. package/components/checkbox/dist/registered.js +18 -10
  6. package/components/combobox/README.md +1 -1
  7. package/components/combobox/demo/customize.md +2 -10
  8. package/components/combobox/demo/customize.min.js +454 -467
  9. package/components/combobox/demo/getting-started.min.js +454 -463
  10. package/components/combobox/demo/index.md +1 -1
  11. package/components/combobox/demo/index.min.js +454 -463
  12. package/components/combobox/demo/keyboard-behavior.md +2 -142
  13. package/components/combobox/demo/readme.md +1 -1
  14. package/components/combobox/demo/why-combobox.md +2 -2
  15. package/components/combobox/dist/auro-combobox.d.ts +30 -14
  16. package/components/combobox/dist/index.js +454 -463
  17. package/components/combobox/dist/registered.js +454 -463
  18. package/components/counter/demo/customize.min.js +32 -12
  19. package/components/counter/demo/index.min.js +32 -12
  20. package/components/counter/dist/index.js +32 -12
  21. package/components/counter/dist/registered.js +32 -12
  22. package/components/datepicker/demo/customize.min.js +183 -167
  23. package/components/datepicker/demo/index.min.js +183 -167
  24. package/components/datepicker/dist/index.js +183 -167
  25. package/components/datepicker/dist/registered.js +183 -167
  26. package/components/dropdown/demo/customize.min.js +14 -2
  27. package/components/dropdown/demo/getting-started.min.js +14 -2
  28. package/components/dropdown/demo/index.min.js +14 -2
  29. package/components/dropdown/dist/index.js +14 -2
  30. package/components/dropdown/dist/registered.js +14 -2
  31. package/components/form/demo/customize.min.js +851 -829
  32. package/components/form/demo/getting-started.min.js +851 -829
  33. package/components/form/demo/index.min.js +851 -829
  34. package/components/form/demo/registerDemoDeps.min.js +851 -829
  35. package/components/input/demo/api.md +58 -57
  36. package/components/input/demo/customize.min.js +114 -155
  37. package/components/input/demo/getting-started.min.js +114 -155
  38. package/components/input/demo/index.min.js +114 -155
  39. package/components/input/dist/base-input.d.ts +9 -51
  40. package/components/input/dist/index.js +114 -155
  41. package/components/input/dist/registered.js +114 -155
  42. package/components/input/dist/utilities.d.ts +9 -0
  43. package/components/radio/demo/customize.min.js +18 -10
  44. package/components/radio/demo/getting-started.min.js +18 -10
  45. package/components/radio/demo/index.min.js +18 -10
  46. package/components/radio/dist/index.js +18 -10
  47. package/components/radio/dist/registered.js +18 -10
  48. package/components/select/demo/customize.min.js +32 -12
  49. package/components/select/demo/getting-started.min.js +32 -12
  50. package/components/select/demo/index.min.js +32 -12
  51. package/components/select/dist/index.js +32 -12
  52. package/components/select/dist/registered.js +32 -12
  53. package/custom-elements.json +142 -278
  54. package/package.json +1 -1
@@ -350,11 +350,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
350
350
  return;
351
351
  }
352
352
 
353
- // Validate that the date passed was the correct format and is a valid date
353
+ // Validate that the date passed was the correct format and is a valid date.
354
+ // For partial date formats, valueObject is never populated; validate them directly.
354
355
  if (elem.value && !elem.valueObject) {
355
- elem.validity = 'patternMismatch';
356
- elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
357
- return;
356
+ const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
357
+ const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
358
+
359
+ if (!isValidPartial) {
360
+ elem.validity = 'patternMismatch';
361
+ elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
362
+ return;
363
+ }
358
364
  }
359
365
 
360
366
  // Perform the rest of the validation
@@ -448,15 +454,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
448
454
  );
449
455
  }
450
456
 
451
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
452
- if (this.auroInputElements?.length === 2) {
453
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
457
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
458
+
459
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
460
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
461
+ // field (datepicker is the intended consumer — start/end are independently required).
462
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
463
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
454
464
  hasValue = false;
455
465
  }
456
466
  }
457
467
 
458
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
459
-
460
468
  if (isCombobox) {
461
469
 
462
470
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -767,6 +775,8 @@ function navigateArrow(component, direction, options = {}) {
767
775
  }
768
776
  }
769
777
 
778
+ /* eslint-disable no-underscore-dangle */
779
+
770
780
  /**
771
781
  * Returns the clear button element from the active input's shadow
772
782
  * DOM, if available.
@@ -802,11 +812,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
802
812
  * @param {Object} menu - The menu component.
803
813
  */
804
814
  function reconcileMenuIndex(menu) {
805
- // eslint-disable-next-line no-underscore-dangle
806
815
  if (menu._index < 0 && menu.optionActive && menu.items) {
807
816
  const idx = menu.items.indexOf(menu.optionActive);
808
817
  if (idx >= 0) {
809
- // eslint-disable-next-line no-underscore-dangle
810
818
  menu._index = idx;
811
819
  }
812
820
  }
@@ -825,11 +833,7 @@ const comboboxKeyboardStrategy = {
825
833
 
826
834
  // navigate if bib is open otherwise open it
827
835
  if (component.dropdown.isPopoverVisible) {
828
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
829
- component.activateLastEnabledAvailableOption();
830
- } else {
831
- navigateArrow(component, 'down');
832
- }
836
+ navigateArrow(component, 'down');
833
837
  } else {
834
838
  component.showBib();
835
839
  }
@@ -848,11 +852,7 @@ const comboboxKeyboardStrategy = {
848
852
 
849
853
  // navigate if bib is open otherwise open it
850
854
  if (component.dropdown.isPopoverVisible) {
851
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
852
- component.activateFirstEnabledAvailableOption();
853
- } else {
854
- navigateArrow(component, 'up');
855
- }
855
+ navigateArrow(component, 'up');
856
856
  } else {
857
857
  component.showBib();
858
858
  }
@@ -868,13 +868,18 @@ const comboboxKeyboardStrategy = {
868
868
  },
869
869
 
870
870
  Enter(component, evt, ctx) {
871
+ // Forms should not submit on Enter from a combobox, regardless of which
872
+ // child element (input, clear button, menu) is focused.
873
+ evt.stopPropagation();
874
+
871
875
  if (isClearBtnFocused(ctx)) {
872
- // If the clear button has focus, let the browser activate it normally.
873
- // stopPropagation prevents parent containers (e.g., forms) from treating
874
- // Enter as a submit, but we must NOT call preventDefault — that would
875
- // block the browser's built-in "Enter activates focused button" behavior.
876
- evt.stopPropagation();
877
- } else if (ctx.isExpanded && component.menu.optionActive) {
876
+ // Let the browser dispatch Enter to the focused clear button so its
877
+ // built-in activation fires and clears the selection. Do NOT call
878
+ // preventDefault — that would block the activation.
879
+ return;
880
+ }
881
+
882
+ if (ctx.isExpanded && component.menu.optionActive) {
878
883
  reconcileMenuIndex(component.menu);
879
884
  component.menu.makeSelection();
880
885
 
@@ -883,14 +888,8 @@ const comboboxKeyboardStrategy = {
883
888
  }
884
889
 
885
890
  evt.preventDefault();
886
- evt.stopPropagation();
887
891
  } else {
888
- // Prevent the keypress from bubbling to parent containers (e.g., forms)
889
- // which could interpret Enter as a submit or trigger other unintended behavior.
890
- // This is safe because showBib() opens the dialog programmatically,
891
- // not via event propagation.
892
892
  evt.preventDefault();
893
- evt.stopPropagation();
894
893
  component.showBib();
895
894
  }
896
895
  },
@@ -935,7 +934,7 @@ const comboboxKeyboardStrategy = {
935
934
  component.setClearBtnFocus();
936
935
  }
937
936
  }
938
- },
937
+ }
939
938
  };
940
939
 
941
940
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
@@ -4806,7 +4805,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4806
4805
  }
4807
4806
  };
4808
4807
 
4809
- var formkitVersion$2 = '202606292156';
4808
+ var formkitVersion$2 = '202607011722';
4810
4809
 
4811
4810
  let AuroElement$2 = class AuroElement extends LitElement {
4812
4811
  static get properties() {
@@ -5558,7 +5557,19 @@ class AuroDropdown extends AuroElement$2 {
5558
5557
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
5559
5558
  this.trigger.focus();
5560
5559
  }
5561
- }
5560
+
5561
+
5562
+ if (!this.isPopoverVisible) {
5563
+ // wait til the bib gets fully closed and rendered
5564
+ setTimeout(() => {
5565
+ // check if it's still closed and the focus is still within the dropdown, but not in the bib. If so, move focus to the trigger.
5566
+ if (!this.isPopoverVisible || !this.bib.matches(':focus-within')) {
5567
+ return;
5568
+ }
5569
+ // Move focus out of bib into trigger.
5570
+ this.trigger.focus();
5571
+ });
5572
+ } }
5562
5573
 
5563
5574
  firstUpdated() {
5564
5575
  // Configure the floater to, this will generate the ID for the bib
@@ -10483,11 +10494,17 @@ class AuroFormValidation {
10483
10494
  return;
10484
10495
  }
10485
10496
 
10486
- // Validate that the date passed was the correct format and is a valid date
10497
+ // Validate that the date passed was the correct format and is a valid date.
10498
+ // For partial date formats, valueObject is never populated; validate them directly.
10487
10499
  if (elem.value && !elem.valueObject) {
10488
- elem.validity = 'patternMismatch';
10489
- elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
10490
- return;
10500
+ const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
10501
+ const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
10502
+
10503
+ if (!isValidPartial) {
10504
+ elem.validity = 'patternMismatch';
10505
+ elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
10506
+ return;
10507
+ }
10491
10508
  }
10492
10509
 
10493
10510
  // Perform the rest of the validation
@@ -10581,15 +10598,17 @@ class AuroFormValidation {
10581
10598
  );
10582
10599
  }
10583
10600
 
10584
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
10585
- if (this.auroInputElements?.length === 2) {
10586
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
10601
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10602
+
10603
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
10604
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
10605
+ // field (datepicker is the intended consumer — start/end are independently required).
10606
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
10607
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
10587
10608
  hasValue = false;
10588
10609
  }
10589
10610
  }
10590
10611
 
10591
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10592
-
10593
10612
  if (isCombobox) {
10594
10613
 
10595
10614
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -16489,6 +16508,42 @@ class AuroInputUtilities {
16489
16508
  return type === 'date' && normalizedFormat.includes('yy') && normalizedFormat.includes('mm') && normalizedFormat.includes('dd');
16490
16509
  }
16491
16510
 
16511
+ /**
16512
+ * Validates a value against a partial date format (one that lacks yy/mm/dd all three).
16513
+ * Single-component formats are checked as integer ranges; multi-component formats use
16514
+ * a date-fns parse + round-trip to confirm both validity and exact formatting.
16515
+ * @param {string} value - The user-facing display value.
16516
+ * @param {string} format - The partial date format string (e.g. "mm/yyyy", "yyyy", "dd").
16517
+ * @returns {boolean}
16518
+ */
16519
+ isValidPartialDate(value, format$1) {
16520
+ if (!value || !format$1) {
16521
+ return false;
16522
+ }
16523
+ const normalizedFormat = format$1.toLowerCase();
16524
+
16525
+ if (normalizedFormat === 'dd') {
16526
+ const num = Number(value);
16527
+ return (/^\d{2}$/u).test(value) && num >= 1 && num <= 31;
16528
+ }
16529
+ if (normalizedFormat === 'yy') {
16530
+ const num = Number(value);
16531
+ return (/^\d{2}$/u).test(value) && num >= 0 && num <= 99;
16532
+ }
16533
+ if (normalizedFormat === 'yyyy') {
16534
+ const num = Number(value);
16535
+ return (/^\d{4}$/u).test(value) && num >= 1 && num <= 9999;
16536
+ }
16537
+
16538
+ const dateFnsMask = this.toDateFnsMask(normalizedFormat);
16539
+ // Use the 1st of the current month as the reference so that formats
16540
+ // omitting a day (e.g. MM/yyyy) never roll over on days 29–31.
16541
+ const referenceDate = new Date();
16542
+ referenceDate.setDate(1);
16543
+ const parsed = parse(value, dateFnsMask, referenceDate);
16544
+ return isValid(parsed) && format(parsed, dateFnsMask) === value;
16545
+ }
16546
+
16492
16547
  /**
16493
16548
  * Converts a display string to its model value.
16494
16549
  * For full date formats, converts the display string to an ISO date string.
@@ -16536,6 +16591,7 @@ class AuroInputUtilities {
16536
16591
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16537
16592
 
16538
16593
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16594
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16539
16595
  return undefined;
16540
16596
  }
16541
16597
 
@@ -16693,6 +16749,7 @@ class BaseInput extends AuroElement$1 {
16693
16749
  // so a parent (datepicker/combobox) calling `validate()` synchronously
16694
16750
  // during its own update cycle sees a populated util instance.
16695
16751
  this.activeLabel = false;
16752
+
16696
16753
  /** @private */
16697
16754
  this.allowedInputTypes = [
16698
16755
  "text",
@@ -16703,6 +16760,7 @@ class BaseInput extends AuroElement$1 {
16703
16760
  "tel"
16704
16761
  ];
16705
16762
  this.appearance = "default";
16763
+
16706
16764
  /** @private */
16707
16765
  this.dateFormatMap = {
16708
16766
  'mm/dd/yyyy': 'dateMMDDYYYY',
@@ -16721,23 +16779,24 @@ class BaseInput extends AuroElement$1 {
16721
16779
  'mm/dd': 'dateMMDD'
16722
16780
  };
16723
16781
  this.disabled = false;
16782
+
16724
16783
  /** @private */
16725
16784
  this.domHandler = new DomHandler();
16726
16785
  this.dvInputOnly = false;
16727
16786
  this.hasValue = false;
16728
16787
  this.hideLabelVisually = false;
16729
16788
  this.icon = false;
16789
+
16730
16790
  /** @private */
16731
16791
  this.inputIconName = undefined;
16792
+
16732
16793
  /** @private */
16733
16794
  this.label = 'Input label is undefined';
16734
16795
  this.layout = 'classic';
16735
16796
  this.locale = 'en-US';
16736
16797
  this.max = undefined;
16737
- this._maxObject = undefined;
16738
16798
  this.maxLength = undefined;
16739
16799
  this.min = undefined;
16740
- this._minObject = undefined;
16741
16800
  this.minLength = undefined;
16742
16801
  this.noValidate = false;
16743
16802
  this.onDark = false;
@@ -16754,23 +16813,27 @@ class BaseInput extends AuroElement$1 {
16754
16813
  "email"
16755
16814
  ];
16756
16815
  this.shape = 'classic';
16816
+
16757
16817
  /** @private */
16758
16818
  this.showPassword = false;
16759
16819
  this.size = 'lg';
16760
16820
  this.touched = false;
16821
+
16761
16822
  /** @private */
16762
16823
  this.uniqueId = new UniqueId().create();
16824
+
16763
16825
  /** @private */
16764
16826
  this.util = new AuroInputUtilities({
16765
16827
  locale: this.locale,
16766
16828
  format: this.format
16767
16829
  });
16830
+
16768
16831
  /** @private */
16769
16832
  this.validation = new AuroFormValidation();
16833
+
16770
16834
  /** @private */
16771
16835
  this.validationCCLength = undefined;
16772
16836
  this.value = undefined;
16773
- this._valueObject = undefined;
16774
16837
  }
16775
16838
 
16776
16839
  // function to define props used within the scope of this component
@@ -17100,6 +17163,13 @@ class BaseInput extends AuroElement$1 {
17100
17163
  type: String
17101
17164
  },
17102
17165
 
17166
+ /**
17167
+ * Custom help text message to display when validity = `patternMismatch`.
17168
+ */
17169
+ setCustomValidityPatternMismatch: {
17170
+ type: String
17171
+ },
17172
+
17103
17173
  /**
17104
17174
  * Custom help text message to display when validity = `rangeOverflow`.
17105
17175
  */
@@ -17170,7 +17240,7 @@ class BaseInput extends AuroElement$1 {
17170
17240
 
17171
17241
  /**
17172
17242
  * Populates the `type` attribute on the input.
17173
- * @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number'}
17243
+ * @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number' | 'date'}
17174
17244
  * @default 'text'
17175
17245
  */
17176
17246
  type: {
@@ -17208,7 +17278,7 @@ class BaseInput extends AuroElement$1 {
17208
17278
  * @returns {Date|undefined}
17209
17279
  */
17210
17280
  get valueObject() {
17211
- return this._valueObject || this._computeDateObjectFallback(this.value);
17281
+ return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
17212
17282
  }
17213
17283
 
17214
17284
  /**
@@ -17216,7 +17286,7 @@ class BaseInput extends AuroElement$1 {
17216
17286
  * @returns {Date|undefined}
17217
17287
  */
17218
17288
  get minObject() {
17219
- return this._minObject || this._computeDateObjectFallback(this.min);
17289
+ return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
17220
17290
  }
17221
17291
 
17222
17292
  /**
@@ -17224,50 +17294,7 @@ class BaseInput extends AuroElement$1 {
17224
17294
  * @returns {Date|undefined}
17225
17295
  */
17226
17296
  get maxObject() {
17227
- return this._maxObject || this._computeDateObjectFallback(this.max);
17228
- }
17229
-
17230
- /**
17231
- * Parses a date string into a Date object when the corresponding `_*Object`
17232
- * field hasn't been synced yet by `updated()`. Returns undefined when the
17233
- * input type/format isn't a full date or the string is not a valid date.
17234
- *
17235
- * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17236
- * inside its own `updated()` before this input's `updated()` has run
17237
- * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17238
- * and range checks would otherwise silently no-op (flipping the result to
17239
- * `valid` or `patternMismatch`).
17240
- * @private
17241
- * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17242
- * @returns {Date|undefined}
17243
- */
17244
- _computeDateObjectFallback(dateStr) {
17245
- if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17246
- return undefined;
17247
- }
17248
- if (!dateFormatter.isValidDate(dateStr)) {
17249
- return undefined;
17250
- }
17251
- return dateFormatter.stringToDateInstance(dateStr);
17252
- }
17253
-
17254
- /**
17255
- * Internal setter for readonly date object properties.
17256
- * @private
17257
- * @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
17258
- * @param {Date|undefined} propertyValue - Value to assign.
17259
- * @returns {void}
17260
- */
17261
- setDateObjectProperty(propertyName, propertyValue) {
17262
- const internalPropertyName = `_${propertyName}`;
17263
- const previousValue = this[internalPropertyName];
17264
-
17265
- if (previousValue === propertyValue) {
17266
- return;
17267
- }
17268
-
17269
- this[internalPropertyName] = propertyValue;
17270
- this.requestUpdate(propertyName, previousValue);
17297
+ return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
17271
17298
  }
17272
17299
 
17273
17300
  connectedCallback() {
@@ -17296,7 +17323,6 @@ class BaseInput extends AuroElement$1 {
17296
17323
  format: this.format
17297
17324
  });
17298
17325
  this.configureDataForType();
17299
- this.syncDateValues();
17300
17326
  }
17301
17327
 
17302
17328
  disconnectedCallback() {
@@ -17335,7 +17361,6 @@ class BaseInput extends AuroElement$1 {
17335
17361
  this.setCustomHelpTextMessage();
17336
17362
  this.configureAutoFormatting();
17337
17363
  this.configureDataForType();
17338
- this.syncDateValues();
17339
17364
  }
17340
17365
 
17341
17366
  /**
@@ -17479,8 +17504,6 @@ class BaseInput extends AuroElement$1 {
17479
17504
  this.configureDataForType();
17480
17505
  }
17481
17506
 
17482
- this.syncDateValues(changedProperties);
17483
-
17484
17507
  if (changedProperties.has('value')) {
17485
17508
  if (this.value && this.value.length > 0) {
17486
17509
  this.hasValue = true;
@@ -17502,14 +17525,14 @@ class BaseInput extends AuroElement$1 {
17502
17525
 
17503
17526
  if (formattedValue !== this.inputElement.value) {
17504
17527
  this.skipNextProgrammaticInputEvent = true;
17505
- if (this.maskInstance && this.type === 'credit-card') {
17528
+ if (this.maskInstance && this.type !== 'date') {
17506
17529
  // Route through the mask so its _value and el.value stay in lock-step
17507
17530
  // (set value calls updateControl which writes el.value = displayValue).
17508
17531
  // Writing el.value directly leaves the mask thinking displayValue is
17509
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17510
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17511
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17512
- // and flip validity from patternMismatch to tooShort.
17532
+ // stale; _saveSelection on the next focus/click then warns. Date is
17533
+ // excluded because its formattedValue can be raw ISO when the calendar
17534
+ // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17535
+ // flip validity from patternMismatch to tooShort.
17513
17536
  this.maskInstance.value = formattedValue || '';
17514
17537
  } else if (formattedValue) {
17515
17538
  this.inputElement.value = formattedValue;
@@ -17551,120 +17574,65 @@ class BaseInput extends AuroElement$1 {
17551
17574
  }));
17552
17575
  }
17553
17576
 
17554
-
17555
- /**
17556
- * Synchronizes the ISO string values and Date object representations for date-related properties.
17557
- * This keeps the model and display values aligned when either side changes.
17558
- *
17559
- * When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
17560
- * Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
17561
- * represents a full year/month/day date format.
17562
- *
17563
- * @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
17564
- * @returns {void}
17565
- * @private
17566
- */
17567
- syncDateValues(changedProperties = undefined) {
17568
- if (!this.util.isFullDateFormat(this.type, this.format)) {
17569
- return;
17570
- }
17571
-
17572
- this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
17573
- this.syncSingleDateValue(changedProperties, 'minObject', 'min');
17574
- this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
17575
- }
17576
-
17577
- /**
17578
- * Synchronizes one date object/string property pair.
17579
- * @private
17580
- * @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
17581
- * @param {string} objectProperty - Date object property name.
17582
- * @param {string} valueProperty - ISO string property name.
17583
- * @returns {void}
17584
- */
17585
- syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
17586
- const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
17587
-
17588
- // objectProperty wins over valueProperty when both changed
17589
- if (objectPropertyChanged && this[objectProperty]) {
17590
- this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
17591
- return;
17592
- }
17593
-
17594
- const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
17595
- if (!valuePropertyChanged) {
17596
- return;
17597
- }
17598
-
17599
- // 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)
17600
- if (
17601
- changedProperties &&
17602
- valueProperty === 'value' &&
17603
- changedProperties.get('value') === undefined &&
17604
- this[objectProperty] instanceof Date &&
17605
- this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
17606
- ) {
17607
- return;
17608
- }
17609
-
17610
- if (dateFormatter.isValidDate(this[valueProperty])) {
17611
- this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
17612
- } else {
17613
- this.setDateObjectProperty(objectProperty, undefined);
17614
- }
17615
- }
17616
-
17617
17577
  /**
17618
17578
  * Sets up IMasks and logic based on auto-formatting requirements.
17619
17579
  * @private
17620
17580
  * @returns {void}
17621
17581
  */
17622
17582
  configureAutoFormatting() {
17623
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17624
- // by _configuringMask above) could otherwise trigger handleInput
17625
- // processCreditCard configureAutoFormatting before the outer call's
17626
- // set value has finished its alignCursor pass.
17583
+ // _configuringMask gates two things: external re-entry into this method
17584
+ // while setup is mid-flight (from property changes that call back here),
17585
+ // and the accept/complete listeners below — both need to ignore the mask
17586
+ // events fired by our own value-restore step.
17627
17587
  if (this._configuringMask) return;
17628
17588
  this._configuringMask = true;
17629
17589
  try {
17590
+ // Destroy any prior mask so IMask can attach fresh under the new format.
17591
+ // Null the reference too — if the new maskOptions.mask is falsy (e.g.
17592
+ // type switched from credit-card to text) the IMask() reassignment
17593
+ // below is skipped, and downstream writes at line ~823 would otherwise
17594
+ // route through the destroyed instance.
17630
17595
  if (this.maskInstance) {
17631
17596
  this.maskInstance.destroy();
17597
+ this.maskInstance = null;
17632
17598
  }
17633
17599
 
17634
- // Pass new format to util
17635
17600
  this.util.updateFormat(this.format);
17636
17601
 
17637
17602
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17638
17603
 
17639
17604
  if (this.inputElement && maskOptions.mask) {
17640
-
17641
- // Stash and clear any existing value before IMask init.
17642
- // IMask's constructor processes the current input value which requires
17643
- // selection state clearing first avoids that scenario entirely.
17644
- // When the format changes (e.g. locale switch) and we have a valid ISO
17645
- // model value, compute the display string for the NEW format instead of
17646
- // re-using the old display string, which may be invalid in the new mask.
17605
+ // Capture the current display so it can be re-applied after IMask
17606
+ // attaches. The restore at the bottom goes through maskInstance.value
17607
+ // (not inputElement.value directly) so the mask's internal state and
17608
+ // the input's displayed text stay in lock-step.
17647
17609
  let existingValue = this.inputElement.value;
17610
+
17611
+ // Format-change case (e.g. locale switch): existingValue is the OLD
17612
+ // mask's display string and may not parse under the new mask. When
17613
+ // we have a valid date model, rebuild the display from valueObject
17614
+ // (the canonical source) using the new mask's format function.
17648
17615
  if (
17649
17616
  this.util.isFullDateFormat(this.type, this.format) &&
17650
17617
  this.value &&
17651
- dateFormatter.isValidDate(this.value) &&
17652
- this.valueObject instanceof Date &&
17653
- !Number.isNaN(this.valueObject.getTime()) &&
17618
+ this.valueObject &&
17654
17619
  typeof maskOptions.format === 'function'
17655
17620
  ) {
17656
17621
  existingValue = maskOptions.format(this.valueObject);
17657
17622
  }
17658
17623
 
17624
+ // Clear before IMask attaches so the constructor seeds an empty
17625
+ // internal value. Otherwise IMask reads the stale unmasked string
17626
+ // and emits a spurious 'accept' before the restore below runs.
17659
17627
  this.skipNextProgrammaticInputEvent = true;
17660
17628
  this.inputElement.value = '';
17661
17629
 
17662
17630
  this.maskInstance = IMask(this.inputElement, maskOptions);
17663
17631
 
17632
+ // Mask fires 'accept' on every value change, including the restore
17633
+ // step below. Skip events fired during configureAutoFormatting so
17634
+ // we don't overwrite a value the parent just pushed.
17664
17635
  this.maskInstance.on('accept', () => {
17665
- // Suppress propagation during configureAutoFormatting's own value-restoration
17666
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17667
- // and we don't want to overwrite a value the parent just pushed.
17668
17636
  if (this._configuringMask) return;
17669
17637
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17670
17638
  if (this.type === "date") {
@@ -17672,6 +17640,8 @@ class BaseInput extends AuroElement$1 {
17672
17640
  }
17673
17641
  });
17674
17642
 
17643
+ // Mask fires 'complete' on the restore step below for any value that
17644
+ // happens to be a complete match. Same setup-suppression as 'accept'.
17675
17645
  this.maskInstance.on('complete', () => {
17676
17646
  if (this._configuringMask) return;
17677
17647
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17680,7 +17650,9 @@ class BaseInput extends AuroElement$1 {
17680
17650
  }
17681
17651
  });
17682
17652
 
17683
- // Restore the stashed value through IMask so it's properly masked
17653
+ // Write existingValue through the mask (not the input directly) so
17654
+ // the mask reformats it under the new rules and keeps its internal
17655
+ // _value aligned with the input's displayed text.
17684
17656
  if (existingValue) {
17685
17657
  this.maskInstance.value = existingValue;
17686
17658
  }
@@ -17774,9 +17746,9 @@ class BaseInput extends AuroElement$1 {
17774
17746
  // the gated types currently support it, since the list is a public
17775
17747
  // property a consumer could mutate.
17776
17748
  if (this.setSelectionInputTypes.includes(this.type)) {
17777
- let selectionStart;
17749
+ let selectionStart = null;
17778
17750
  try {
17779
- selectionStart = this.inputElement.selectionStart;
17751
+ ({ selectionStart } = this.inputElement);
17780
17752
  } catch (error) { // eslint-disable-line no-unused-vars
17781
17753
  return;
17782
17754
  }
@@ -17880,7 +17852,6 @@ class BaseInput extends AuroElement$1 {
17880
17852
  */
17881
17853
  reset() {
17882
17854
  this.value = undefined;
17883
- this.setDateObjectProperty('valueObject', undefined);
17884
17855
  this.validation.reset(this);
17885
17856
  }
17886
17857
 
@@ -17889,7 +17860,6 @@ class BaseInput extends AuroElement$1 {
17889
17860
  */
17890
17861
  clear() {
17891
17862
  this.value = undefined;
17892
- this.setDateObjectProperty('valueObject', undefined);
17893
17863
  }
17894
17864
 
17895
17865
  /**
@@ -18418,7 +18388,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18418
18388
  }
18419
18389
  };
18420
18390
 
18421
- var formkitVersion$1 = '202606292156';
18391
+ var formkitVersion$1 = '202607011722';
18422
18392
 
18423
18393
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18424
18394
  // See LICENSE in the project root for license information.
@@ -19544,7 +19514,7 @@ class AuroBibtemplate extends LitElement {
19544
19514
  }
19545
19515
  }
19546
19516
 
19547
- var formkitVersion = '202606292156';
19517
+ var formkitVersion = '202607011722';
19548
19518
 
19549
19519
  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}`;
19550
19520
 
@@ -19912,12 +19882,23 @@ function getOptionLabel(option) {
19912
19882
  if (!option) {
19913
19883
  return '';
19914
19884
  }
19915
- const clone = option.cloneNode(true);
19916
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19917
- if (displayValueEl) {
19918
- displayValueEl.remove();
19885
+
19886
+ // Consumer-provided override: short-circuit the DOM walk entirely.
19887
+ if (option.dataset && option.dataset.label) {
19888
+ return option.dataset.label;
19889
+ }
19890
+
19891
+ // Walk direct children — the `slot` attribute only applies to direct children
19892
+ // of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
19893
+ // querySelector allocation that ran on every keystroke via syncValuesAndStates.
19894
+ let text = '';
19895
+ for (const node of option.childNodes) {
19896
+ const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
19897
+ if (!isDisplayValueSlot) {
19898
+ text += node.textContent || '';
19899
+ }
19919
19900
  }
19920
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19901
+ return text.replace(/\s+/gu, ' ').trim();
19921
19902
  }
19922
19903
 
19923
19904
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
@@ -19992,8 +19973,6 @@ class AuroCombobox extends AuroElement {
19992
19973
  this.availableOptions = [];
19993
19974
  this.dropdownId = undefined;
19994
19975
  this.dropdownOpen = false;
19995
- this.triggerExpandedState = false;
19996
- this._expandedTimeout = null;
19997
19976
  this._inFullscreenTransition = false;
19998
19977
  this.errorMessage = null;
19999
19978
  this.isHiddenWhileLoading = false;
@@ -20004,6 +19983,30 @@ class AuroCombobox extends AuroElement {
20004
19983
  this.touched = false;
20005
19984
  this.validation = new AuroFormValidation$1();
20006
19985
  this.validity = undefined;
19986
+ this._userTyped = false;
19987
+ // Tracks every setTimeout scheduled via _scheduleTimer so
19988
+ // disconnectedCallback can cancel them. Without this, a detached
19989
+ // combobox's pending timers still fire — most are no-ops, but
19990
+ // configureMenu's racing-condition retry would otherwise loop.
19991
+ this._pendingTimers = new Set();
19992
+ }
19993
+
19994
+ /**
19995
+ * setTimeout wrapper that records the timer id so disconnectedCallback
19996
+ * can cancel any outstanding callbacks. The id is removed from the set
19997
+ * once the callback fires so the set doesn't grow unbounded.
19998
+ * @param {Function} fn - Callback to run.
19999
+ * @param {number} ms - Delay in milliseconds.
20000
+ * @returns {number} The timer id.
20001
+ * @private
20002
+ */
20003
+ _scheduleTimer(fn, ms) {
20004
+ const id = setTimeout(() => {
20005
+ this._pendingTimers.delete(id);
20006
+ fn();
20007
+ }, ms);
20008
+ this._pendingTimers.add(id);
20009
+ return id;
20007
20010
  }
20008
20011
 
20009
20012
  // This function is to define props used within the scope of this component
@@ -20365,17 +20368,6 @@ class AuroCombobox extends AuroElement {
20365
20368
  attribute: false
20366
20369
  },
20367
20370
 
20368
- /**
20369
- * Deferred aria-expanded state for the trigger input.
20370
- * Delays the "true" transition so VoiceOver finishes its character echo
20371
- * before announcing "expanded".
20372
- * @private
20373
- */
20374
- triggerExpandedState: {
20375
- type: Boolean,
20376
- reflect: false,
20377
- attribute: false
20378
- },
20379
20371
  };
20380
20372
  }
20381
20373
 
@@ -20398,13 +20390,6 @@ class AuroCombobox extends AuroElement {
20398
20390
  return this.input.value;
20399
20391
  }
20400
20392
 
20401
- // /**
20402
- // * Sets the value of the input element within the combobox.
20403
- // */
20404
- // set inputValue(value) {
20405
- // this.input.value = value;
20406
- // }
20407
-
20408
20393
  /**
20409
20394
  * Checks if the element is valid.
20410
20395
  * @returns {boolean} - Returns true if the element is valid, false otherwise.
@@ -20531,7 +20516,7 @@ class AuroCombobox extends AuroElement {
20531
20516
  if (this.menu) {
20532
20517
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20533
20518
  }
20534
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20519
+ const label = getOptionLabel(this.menu.optionSelected);
20535
20520
  this.updateTriggerTextDisplay(label || this.value);
20536
20521
  }
20537
20522
 
@@ -20545,44 +20530,16 @@ class AuroCombobox extends AuroElement {
20545
20530
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20546
20531
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20547
20532
 
20548
- if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20549
- const nextValue = label || this.value;
20550
- // Only set the flag when there's an actual write to suppress —
20551
- // syncValuesAndStates re-enters here during typing when both inputs
20552
- // already match, and a no-op flag flip would make the bib branch's
20553
- // bail eat legitimate user-input events.
20554
- const triggerNeedsSync = this.input.value !== nextValue;
20555
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20556
- if (triggerNeedsSync || bibNeedsSync) {
20557
- this._syncingDisplayValue = true;
20558
- if (triggerNeedsSync) {
20559
- this.input.value = nextValue;
20560
- }
20561
- if (bibNeedsSync) {
20562
- this.inputInBib.value = nextValue;
20563
- }
20564
- const pending = [];
20565
- if (triggerNeedsSync) {
20566
- pending.push(this.input.updateComplete);
20567
- }
20568
- if (bibNeedsSync) {
20569
- pending.push(this.inputInBib.updateComplete);
20570
- }
20571
- Promise.all(pending).then(() => {
20572
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20573
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20574
- this.input.maskInstance.updateValue();
20575
- }
20576
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20577
- this.inputInBib.maskInstance.updateValue();
20578
- }
20579
- this._syncingDisplayValue = false;
20580
- });
20581
- }
20533
+ const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20534
+ if (!suppressed) {
20535
+ this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20582
20536
  }
20583
20537
 
20584
- // update the displayValue in the trigger if displayValue slot content is present
20585
- const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]');
20538
+ // Replace any previously appended displayValue clone in the trigger.
20539
+ // :not(slot) excludes the template's <slot name="displayValue"
20540
+ // slot="displayValue"> forwarder, which also has slot="displayValue"
20541
+ // and would otherwise be matched first and removed.
20542
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
20586
20543
 
20587
20544
  if (displayValueInTrigger) {
20588
20545
  displayValueInTrigger.remove();
@@ -20606,6 +20563,53 @@ class AuroCombobox extends AuroElement {
20606
20563
  this.requestUpdate();
20607
20564
  }
20608
20565
 
20566
+ /**
20567
+ * Writes nextValue to the trigger input and the bib input when their current
20568
+ * value differs, then re-asserts imask after Lit's update flushes.
20569
+ * @param {string} nextValue - The value to write to both inputs.
20570
+ * @returns {Promise<void>} Resolves after both inputs flush and imask
20571
+ * re-asserts; resolves immediately when no sync is needed.
20572
+ * @private
20573
+ */
20574
+ async syncInputValuesAcrossTriggerAndBib(nextValue) {
20575
+ // Only set the flag when there's an actual write to suppress —
20576
+ // syncValuesAndStates re-enters here during typing when both inputs
20577
+ // already match, and a no-op flag flip would make the bib branch's
20578
+ // bail eat legitimate user-input events.
20579
+ const triggerNeedsSync = this.input.value !== nextValue;
20580
+ const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
20581
+ if (!triggerNeedsSync && !bibNeedsSync) {
20582
+ return;
20583
+ }
20584
+
20585
+ this._syncingDisplayValue = true;
20586
+
20587
+ const pending = [];
20588
+ if (triggerNeedsSync) {
20589
+ this.input.value = nextValue;
20590
+ pending.push(this.input.updateComplete);
20591
+ }
20592
+ if (bibNeedsSync) {
20593
+ this.inputInBib.value = nextValue;
20594
+ pending.push(this.inputInBib.updateComplete);
20595
+ }
20596
+ // finally — not a bare .then — so that an imask throw (see commit
20597
+ // d1857401c: imask can throw on credit-card format change) doesn't strand
20598
+ // _syncingDisplayValue=true and silently swallow every subsequent input.
20599
+ try {
20600
+ await Promise.all(pending);
20601
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20602
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20603
+ this.input.maskInstance.updateValue();
20604
+ }
20605
+ if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20606
+ this.inputInBib.maskInstance.updateValue();
20607
+ }
20608
+ } finally {
20609
+ this._syncingDisplayValue = false;
20610
+ }
20611
+ }
20612
+
20609
20613
  /**
20610
20614
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20611
20615
  * @private
@@ -20614,6 +20618,13 @@ class AuroCombobox extends AuroElement {
20614
20618
  handleMenuOptions() {
20615
20619
  this.generateOptionsArray();
20616
20620
  this.availableOptions = [];
20621
+ // Single source of truth for the menu's filter/highlight token per call.
20622
+ // syncValuesAndStates re-writes the same value on exact-match keystrokes —
20623
+ // Lit's hasChanged makes that a no-op — and the prior duplicate set inside
20624
+ // handleTriggerInputValueChange is gone.
20625
+ if (this.menu) {
20626
+ this.menu.matchWord = normalizeFilterValue(this.input.value);
20627
+ }
20617
20628
  this.updateFilter();
20618
20629
 
20619
20630
  // Set aria-setsize/aria-posinset on each visible option so screen readers
@@ -20625,17 +20636,14 @@ class AuroCombobox extends AuroElement {
20625
20636
  option.setAttribute('aria-posinset', index + 1);
20626
20637
  });
20627
20638
 
20628
- if (this.value && this.input.value && !this.menu.value) {
20629
- if (this.behavior === 'suggestion' && this.menu.options && this.menu.options.some((opt) => opt.value === this.value)) {
20630
- this.setMenuValue(this.value);
20631
- }
20632
-
20639
+ if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
20640
+ this.setMenuValue(this.input.value);
20633
20641
  this.syncValuesAndStates();
20634
20642
  }
20635
20643
 
20636
- // Re-activate when optionActive is no longer visible, or when _index has
20637
- // been reset (e.g. by clearSelection in an async update) so that
20638
- // makeSelection() can find the correct option.
20644
+ // Re-activate when optionActive is not in the current scrollable viewport,
20645
+ // or when _index has been reset (e.g. by clearSelection in an async update)
20646
+ // so that makeSelection() can find the correct option.
20639
20647
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20640
20648
  this.activateFirstEnabledAvailableOption();
20641
20649
  }
@@ -20708,18 +20716,6 @@ class AuroCombobox extends AuroElement {
20708
20716
  this.dropdownOpen = ev.detail.expanded;
20709
20717
  this.updateMenuShapeSize();
20710
20718
 
20711
- // Defer aria-expanded "true" so VoiceOver finishes character echo
20712
- // before announcing "expanded". Set "false" immediately on close.
20713
- clearTimeout(this._expandedTimeout);
20714
- if (this.dropdownOpen) {
20715
- const expandedDelay = 150;
20716
- this._expandedTimeout = setTimeout(() => {
20717
- this.triggerExpandedState = true;
20718
- }, expandedDelay);
20719
- } else {
20720
- this.triggerExpandedState = false;
20721
- }
20722
-
20723
20719
  // Clear aria-activedescendant when dropdown closes
20724
20720
  if (!this.dropdownOpen && this.input) {
20725
20721
  this.input.setActiveDescendant(null);
@@ -20769,28 +20765,25 @@ class AuroCombobox extends AuroElement {
20769
20765
 
20770
20766
  guardTouchPassthrough(this.menu);
20771
20767
 
20772
- // The dialog's showModal() steals focus from the trigger.
20773
- // A single rAF is enough for the dialog DOM to be painted and
20774
- // focusable, then doubleRaf finalizes the transition.
20768
+ // showModal() takes focus away from the trigger. Early focus once
20769
+ // the dialog has painted; the shared doubleRaf below is the fallback
20770
+ // for when the dialog needs an extra frame and also clears the
20771
+ // validation guard.
20775
20772
  requestAnimationFrame(() => {
20776
20773
  this.setInputFocus();
20777
20774
  });
20778
-
20779
- doubleRaf(() => {
20780
- this.setInputFocus();
20781
- this._inFullscreenTransition = false;
20782
- });
20783
- } else {
20784
- // Desktop popover-open: restore the trigger caret to end-of-text.
20785
- // Clicking the trigger lands on auro-input's floating <label for="…">
20786
- // overlay; Chrome resets the native input's selection to [0, 0] on
20787
- // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20788
- // branch parks the caret at end. doubleRaf lets the dropdown layout
20789
- // settle first, matching the fullscreen branch timing.
20790
- doubleRaf(() => {
20791
- this.setInputFocus();
20792
- });
20793
20775
  }
20776
+ // else (desktop popover-open): Chrome resets the trigger caret to
20777
+ // [0, 0] when its floating <label for="…"> overlay receives focus.
20778
+ // The shared doubleRaf below parks the caret back at end-of-text
20779
+ // after the dropdown layout settles.
20780
+
20781
+ doubleRaf(() => {
20782
+ this.setInputFocus();
20783
+ if (this._inFullscreenTransition) {
20784
+ this._inFullscreenTransition = false;
20785
+ }
20786
+ });
20794
20787
  }
20795
20788
  });
20796
20789
 
@@ -20827,7 +20820,7 @@ class AuroCombobox extends AuroElement {
20827
20820
  this.dropdown.trigger.inert = false;
20828
20821
  }
20829
20822
 
20830
- setTimeout(() => {
20823
+ this._scheduleTimer(() => {
20831
20824
  this.setInputFocus();
20832
20825
  }, 0);
20833
20826
  });
@@ -20971,7 +20964,7 @@ class AuroCombobox extends AuroElement {
20971
20964
 
20972
20965
  // racing condition on custom-combobox with custom-menu
20973
20966
  if (!this.menu) {
20974
- setTimeout(() => {
20967
+ this._scheduleTimer(() => {
20975
20968
  this.configureMenu();
20976
20969
  }, 0);
20977
20970
  return;
@@ -21001,7 +20994,7 @@ class AuroCombobox extends AuroElement {
21001
20994
  if (this.menu.optionSelected) {
21002
20995
  const selected = this.menu.optionSelected;
21003
20996
 
21004
- if (!this.optionSelected || this.optionSelected !== selected) {
20997
+ if (this.optionSelected !== selected) {
21005
20998
  this.optionSelected = selected;
21006
20999
  }
21007
21000
 
@@ -21014,7 +21007,7 @@ class AuroCombobox extends AuroElement {
21014
21007
  }
21015
21008
 
21016
21009
  // Update display
21017
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
21010
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21018
21011
 
21019
21012
  // Update match word for filtering
21020
21013
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -21026,17 +21019,24 @@ class AuroCombobox extends AuroElement {
21026
21019
  // Hide dropdown on selection (except during slot changes)
21027
21020
  if (evt.detail && evt.detail.source !== 'slotchange') {
21028
21021
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21029
- if (this.menu.value || this.behavior !== 'suggestion') {
21030
- this.hideBib();
21022
+ this.hideBib();
21023
+
21024
+ // Move focus to the clear button when the user makes a selection.
21025
+ if (!isEcho && this.menu.value !== undefined) {
21026
+ this.setClearBtnFocus();
21031
21027
  }
21032
21028
 
21033
21029
  // Announce the selection after the dropdown closes so it isn't
21034
21030
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
21031
+ // Skip when there's no selected value (e.g. menu.clearSelection() from
21032
+ // the unmatched-value path), otherwise VoiceOver reads "undefined".
21035
21033
  const selectedValue = this.menu.value;
21036
- const announcementDelay = 300;
21037
- setTimeout(() => {
21038
- announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21039
- }, announcementDelay);
21034
+ if (selectedValue) {
21035
+ const announcementDelay = 300;
21036
+ this._scheduleTimer(() => {
21037
+ announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21038
+ }, announcementDelay);
21039
+ }
21040
21040
  }
21041
21041
 
21042
21042
  // Programmatic value syncs leave availableOptions stale because
@@ -21045,22 +21045,6 @@ class AuroCombobox extends AuroElement {
21045
21045
  // only — fresh user selections take the existing hideBib path.
21046
21046
  if (isEcho && this.menu.optionSelected) {
21047
21047
  this._programmaticFilterRefresh = true;
21048
- this.handleMenuOptions();
21049
- setTimeout(() => {
21050
- this._programmaticFilterRefresh = false;
21051
- }, 0);
21052
- }
21053
-
21054
- // base-input skips auto-validate when focus is inside its own shadow,
21055
- // which it is after setTriggerInputFocus — re-run so prior tooShort
21056
- // clears. processCreditCard reasserts errorMessage on every render.
21057
- if (!isEcho && this.menu.optionSelected && this.input.validate) {
21058
- this.input.updateComplete.then(() => {
21059
- this.input.validate(true);
21060
- if (this.input.validity === 'valid') {
21061
- this.input.errorMessage = '';
21062
- }
21063
- });
21064
21048
  }
21065
21049
  });
21066
21050
 
@@ -21073,6 +21057,14 @@ class AuroCombobox extends AuroElement {
21073
21057
  // stale option. Safe from re-entrancy because any resulting
21074
21058
  // input.value changes dispatch isProgrammatic events.
21075
21059
  this.menu.addEventListener('auroMenu-selectValueFailure', () => {
21060
+ // Announce the rejection BEFORE we clear `this.value` so the live
21061
+ // region carries the attempted value — without this the bib closes
21062
+ // silently and screen-reader users get no signal that their request
21063
+ // (e.g. a direct setMenuValue() call) was dropped.
21064
+ const attemptedValue = this.value;
21065
+ if (attemptedValue) {
21066
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
21067
+ }
21076
21068
  this.value = undefined;
21077
21069
  this.optionSelected = undefined;
21078
21070
  });
@@ -21084,10 +21076,13 @@ class AuroCombobox extends AuroElement {
21084
21076
  this.input.setActiveDescendant(this.optionActive);
21085
21077
  }
21086
21078
 
21087
- // Announce the active option for screen readers including position,
21088
- // since shadow DOM boundaries prevent native reading of
21089
- // aria-setsize/aria-posinset via aria-activedescendant.
21090
- if (this.optionActive) {
21079
+ // In fullscreen mode the menu sits inside a nested <dialog> shadow root,
21080
+ // and aria-activedescendant references across that boundary are lost —
21081
+ // VoiceOver/NVDA don't read the active option natively, so we mirror it
21082
+ // into the polite live region. In popover mode aria-activedescendant on
21083
+ // the trigger input is read natively; double-announcing would flood the
21084
+ // queue on arrow-key repeat.
21085
+ if (this.optionActive && this.dropdown.isBibFullscreen) {
21091
21086
  const optionText = this.optionActive.textContent.trim();
21092
21087
  const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
21093
21088
  const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
@@ -21120,7 +21115,12 @@ class AuroCombobox extends AuroElement {
21120
21115
  * Validate every time we remove focus from the combo box.
21121
21116
  */
21122
21117
  this.addEventListener('focusout', () => {
21123
- if (!this.componentHasFocus && !this._inFullscreenTransition) {
21118
+ // Skip while the dropdown is open — focus transits out briefly on
21119
+ // mousedown of a menu option (popover top-layer breaks :focus-within),
21120
+ // and validating against the pre-selection value flashes a stale error
21121
+ // between mousedown and mouseup. The next focusout fires after the
21122
+ // dropdown closes and validates against the post-selection value.
21123
+ if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
21124
21124
  this.validate();
21125
21125
  }
21126
21126
  });
@@ -21183,49 +21183,40 @@ class AuroCombobox extends AuroElement {
21183
21183
  if (this._syncingDisplayValue) {
21184
21184
  return;
21185
21185
  }
21186
- this._syncingBibValue = true;
21187
- this.input.value = this.inputInBib.value;
21188
- this.input.updateComplete.then(() => {
21189
- this._syncingBibValue = false;
21190
- });
21191
21186
 
21192
- // Run filtering inline — the re-entrant event won't reach this code.
21193
- this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21194
- this.optionActive = null;
21195
-
21196
- // In suggestion mode, keep the combobox value in sync with the typed
21197
- // text so that freeform values are captured (mirroring the non-fullscreen
21198
- // path). Clear the selection when the input is emptied.
21199
- if (this.behavior === 'suggestion') {
21200
- this.value = this.inputInBib.value || undefined;
21187
+ // Filtering runs via re-entrance: writing this.input.value below
21188
+ // dispatches an 'input' event that the trigger's listener routes to
21189
+ // handleTriggerInputValueChange, which refreshes the menu filter.
21190
+ if (this.input.value !== this.inputInBib.value) {
21191
+ this._syncingBibValue = true;
21192
+ this.input.value = this.inputInBib.value;
21193
+ this.input.updateComplete.then(() => {
21194
+ this._syncingBibValue = false;
21195
+ });
21201
21196
  }
21202
21197
 
21203
- this.handleMenuOptions();
21204
21198
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21205
- return;
21199
+ } else if (event.target === this.input) {
21200
+
21201
+ // Also sync the native input immediately so keystrokes arriving
21202
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
21203
+ // fullscreen transition) operate on the correct content.
21204
+ // Skip the next programmatic input event to prevent the patched setter
21205
+ // from re-entering handleInputValueChange via the bib path.
21206
+ const bibNativeInput = this.inputInBib.inputElement;
21207
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21208
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
21209
+ bibNativeInput.value = this.input.value || '';
21210
+ }
21206
21211
  }
21207
21212
 
21213
+ this.value = this.input.value;
21214
+
21208
21215
  // Ignore re-entrant input events caused by programmatic value sets.
21209
21216
  if (this._syncingBibValue || this._syncingDisplayValue) {
21210
21217
  return;
21211
21218
  }
21212
21219
 
21213
- this.inputInBib.value = this.input.value;
21214
-
21215
- // Also sync the native input immediately so keystrokes arriving
21216
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21217
- // fullscreen transition) operate on the correct content.
21218
- // Skip the next programmatic input event to prevent the patched setter
21219
- // from re-entering handleInputValueChange via the bib path.
21220
- const bibNativeInput = this.inputInBib.inputElement;
21221
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21222
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21223
- bibNativeInput.value = this.input.value || '';
21224
- }
21225
-
21226
- this.menu.matchWord = normalizeFilterValue(this.input.value);
21227
- this.optionActive = null;
21228
-
21229
21220
  if (this.behavior === 'suggestion') {
21230
21221
  this.value = this.input.value;
21231
21222
  }
@@ -21233,7 +21224,6 @@ class AuroCombobox extends AuroElement {
21233
21224
  if (!this.input.value && !this._clearing) {
21234
21225
  this.clear();
21235
21226
  }
21236
- this.handleMenuOptions();
21237
21227
 
21238
21228
  // Validate only if the value was set programmatically (not during user
21239
21229
  // interaction). In fullscreen dialog mode, componentHasFocus returns false
@@ -21243,34 +21233,44 @@ class AuroCombobox extends AuroElement {
21243
21233
  this.validate();
21244
21234
  }
21245
21235
 
21246
- if (this.input.value && this.input.value.length === 0) {
21247
- // Hide menu if value is empty, otherwise show if there are available suggestions
21248
- this.hideBib();
21249
- } else if (this.menu.loading) {
21250
- // if input has value but menu is loading, show bib immediately
21251
- this.showBib();
21252
- } else if (this.availableOptions.length === 0 && !this.dropdown.isBibFullscreen) {
21253
- // Force dropdown bib to hide if input value has no matching suggestions
21254
- this.hideBib();
21236
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21237
+ }
21238
+
21239
+ /**
21240
+ * Handles input value changes originating from the trigger input.
21241
+ * Refreshes menu options and filtering, delegates to handleInputValueChange
21242
+ * for value synchronization, and manages fullscreen bib focus.
21243
+ * @private
21244
+ * @param {Event} event - The input event from the trigger input element.
21245
+ * @returns {void}
21246
+ */
21247
+ handleTriggerInputValueChange(event) {
21248
+ // 'input' fires for every user-initiated value change — typing, paste,
21249
+ // IME composition end, dead-key composition, drag-drop. Flip _userTyped
21250
+ // here so updated('availableOptions') auto-opens the bib for sources
21251
+ // that keydown alone misses: paste fires no keydown, IME uses
21252
+ // key='Process', and dead keys produce multi-char keys (all bypass the
21253
+ // prior keydown.key.length===1 gate). Skip programmatic syncs.
21254
+ if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
21255
+ this._userTyped = true;
21255
21256
  }
21256
21257
 
21257
- // iOS virtual keyboard retention: when in fullscreen mode, ensure the
21258
- // dialog opens and the bib input is focused synchronously within the
21259
- // input event (user gesture) chain. Without this, Lit's async update
21260
- // cycle delays showModal() past the user activation window, causing
21261
- // iOS Safari to dismiss the virtual keyboard when the fullscreen
21262
- // dialog opens — the user then has to tap the input again to resume
21263
- // typing.
21264
- if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
21265
- if (!this.dropdown.isPopoverVisible) {
21266
- this.showBib();
21267
- }
21268
- if (this.dropdown.isPopoverVisible) {
21269
- this.setInputFocus();
21270
- }
21258
+ this.handleMenuOptions();
21259
+ this.optionActive = null;
21260
+
21261
+ if (this.value === this.input.value) {
21262
+ return;
21271
21263
  }
21272
21264
 
21273
- this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21265
+ this.handleInputValueChange(event);
21266
+
21267
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
21268
+ this.setInputFocus();
21269
+ }
21270
+
21271
+ if (this._programmaticFilterRefresh) {
21272
+ this._programmaticFilterRefresh = false;
21273
+ }
21274
21274
  }
21275
21275
 
21276
21276
  /**
@@ -21338,6 +21338,11 @@ class AuroCombobox extends AuroElement {
21338
21338
  disconnectedCallback() {
21339
21339
  super.disconnectedCallback();
21340
21340
  this._inFullscreenTransition = false;
21341
+ // Cancel any outstanding timers so detached callbacks don't fire on
21342
+ // disposed DOM — most are no-ops, but configureMenu's racing-condition
21343
+ // retry would otherwise keep rescheduling itself indefinitely.
21344
+ this._pendingTimers.forEach((id) => clearTimeout(id));
21345
+ this._pendingTimers.clear();
21341
21346
  }
21342
21347
 
21343
21348
  firstUpdated() {
@@ -21379,7 +21384,7 @@ class AuroCombobox extends AuroElement {
21379
21384
  this.menu.value = value;
21380
21385
  // Backup clear: if menu.value === menu.optionSelected.value already, the
21381
21386
  // listener won't fire and the flag would swallow the next user click.
21382
- setTimeout(() => {
21387
+ this._scheduleTimer(() => {
21383
21388
  this._pendingMenuValueSync = false;
21384
21389
  }, 0);
21385
21390
  }
@@ -21396,15 +21401,7 @@ class AuroCombobox extends AuroElement {
21396
21401
  this.menu.value = undefined;
21397
21402
  this.validation.reset(this);
21398
21403
  this.touched = false;
21399
- // Force validity back to the cleared state. validation.reset() calls
21400
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21401
- // residual validity may still be copied into the combobox by the
21402
- // auroInputElements loop. Explicitly clear here so reset() actually
21403
- // leaves the component in a "no validity" state.
21404
21404
  this.validity = undefined;
21405
- this.errorMessage = '';
21406
- this.input.validity = undefined;
21407
- this.input.errorMessage = '';
21408
21405
  }
21409
21406
 
21410
21407
  /**
@@ -21422,6 +21419,17 @@ class AuroCombobox extends AuroElement {
21422
21419
  this.optionSelected = undefined;
21423
21420
  this.value = undefined;
21424
21421
 
21422
+ // Clear the appended displayValue clone in the trigger if present.
21423
+ // :not(slot) excludes the template's <slot name="displayValue"
21424
+ // slot="displayValue"> forwarder (line 1816), which also has
21425
+ // slot="displayValue" and would otherwise be matched first and removed,
21426
+ // permanently breaking the consumer-provided displayValue slot.
21427
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
21428
+
21429
+ if (displayValueInTrigger) {
21430
+ displayValueInTrigger.remove();
21431
+ }
21432
+
21425
21433
  if (this.input.value) {
21426
21434
  this.input.clear();
21427
21435
  }
@@ -21445,81 +21453,50 @@ class AuroCombobox extends AuroElement {
21445
21453
  }
21446
21454
 
21447
21455
  updated(changedProperties) {
21448
- // After the component is ready, send direct value changes to auro-menu.
21456
+ // After the component is ready, propagate direct changes down to child components.
21449
21457
  if (changedProperties.has('value')) {
21450
- if (this.value && this.value.length > 0) {
21451
- this.hasValue = true;
21452
- } else {
21453
- this.hasValue = false;
21458
+ // Only flag programmatic refreshes — user-typed value changes must not
21459
+ // suppress the availableOptions branch's showBib(). Firefox batches
21460
+ // 'value' and 'availableOptions' into the same updated() call, so
21461
+ // setting the flag unconditionally here masks the user-typed open path.
21462
+ if (!this._userTyped) {
21463
+ this._programmaticFilterRefresh = true;
21454
21464
  }
21455
21465
 
21456
- if (this.hasValue && !this.input.value && (!this.menu.options || this.menu.options.length === 0)) {
21457
- this.input.value = this.value;
21458
- }
21466
+ if (this.input.value !== this.value) {
21467
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21468
+ // menu.value leaves the previously-selected option element pinned
21469
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21470
+ // would then write its stale .value back into combobox.value
21471
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21472
+ if (this.menu.value || this.menu.optionSelected) {
21473
+ this.menu.clearSelection();
21474
+ }
21459
21475
 
21460
- // Sync menu.value only when an option actually matches; otherwise clear it.
21461
- if (this.menu.options && this.menu.options.length > 0) {
21462
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21463
- this.setMenuValue(this.value);
21464
- } else if (this.behavior === 'filter') {
21465
- // In filter mode, freeform values aren't allowed. Push the unmatched
21466
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21467
- // and the listener at line 1206 clears combobox.value (mirrors select's
21468
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21469
- this.setMenuValue(this.value);
21470
- } else {
21471
- if (this.menu.value) {
21472
- this.menu.value = undefined;
21473
- }
21474
- // Sync the display for programmatic freeform value changes (e.g. the
21475
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21476
- // re-entrant input event from base-input.notifyValueChanged() is
21477
- // ignored by handleInputValueChange. Skip the sync when input already
21478
- // matches to avoid spurious events during the normal typing path.
21479
- const nextValue = this.value || '';
21480
- const triggerNeedsSync = this.input.value !== nextValue;
21481
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21482
- if (triggerNeedsSync || bibNeedsSync) {
21483
- this._syncingDisplayValue = true;
21484
- if (triggerNeedsSync) {
21485
- this.input.value = nextValue;
21486
- }
21487
- if (bibNeedsSync) {
21488
- this.inputInBib.value = nextValue;
21489
- }
21490
- const pending = [];
21491
- if (triggerNeedsSync) {
21492
- pending.push(this.input.updateComplete);
21493
- }
21494
- if (bibNeedsSync) {
21495
- pending.push(this.inputInBib.updateComplete);
21496
- }
21497
- Promise.all(pending).then(() => {
21498
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21499
- this.input.maskInstance.updateValue();
21500
- }
21501
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21502
- this.inputInBib.maskInstance.updateValue();
21503
- }
21504
- this._syncingDisplayValue = false;
21505
- // handleInputValueChange bailed on the flag — refresh the filter.
21506
- this._programmaticFilterRefresh = true;
21507
- this.handleMenuOptions();
21508
- setTimeout(() => {
21509
- this._programmaticFilterRefresh = false;
21510
- }, 0);
21511
- });
21512
- }
21476
+ if (!this.persistInput) {
21477
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21478
+ }
21479
+
21480
+ // Programmatic value with no matching option: updateFilter will close
21481
+ // the bib silently (see line 648 no noMatchOption + 0 results
21482
+ // hides). Announce so screen-reader users hear the request was
21483
+ // dropped. Gated on `input.value !== this.value` so this never fires
21484
+ // for user typing that path always reconciles input.value to
21485
+ // this.value before updated() runs.
21486
+ if (
21487
+ this.value &&
21488
+ this.menu &&
21489
+ this.menu.options &&
21490
+ this.menu.options.length > 0 &&
21491
+ !this.menu.options.some((opt) => opt.value === this.value)
21492
+ ) {
21493
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21513
21494
  }
21514
21495
  }
21496
+
21515
21497
  if (!this.value) {
21516
21498
  this.clear();
21517
21499
  }
21518
- if (this.value && !this.componentHasFocus) {
21519
- // If the value got set programmatically make sure we hide the bib
21520
- // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
21521
- this.hideBib();
21522
- }
21523
21500
 
21524
21501
  // Sync the input and match word, but don't directly set menu.value again
21525
21502
  if (this.menu) {
@@ -21532,7 +21509,7 @@ class AuroCombobox extends AuroElement {
21532
21509
  composed: true,
21533
21510
  detail: {
21534
21511
  optionSelected: this.menu.optionSelected,
21535
- value: this.menu.value
21512
+ value: this.value
21536
21513
  }
21537
21514
  }));
21538
21515
 
@@ -21555,16 +21532,30 @@ class AuroCombobox extends AuroElement {
21555
21532
  // from a programmatic filter refresh after a value swap — the user
21556
21533
  // didn't interact, so we shouldn't auto-open the bib (especially for
21557
21534
  // the no-match path where the existing condition unconditionally fires).
21558
- if (this._programmaticFilterRefresh) {
21559
- this._programmaticFilterRefresh = false;
21560
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21561
- this.showBib();
21535
+ if ((this.menu && !this._programmaticFilterRefresh)) {
21536
+ if (
21537
+ this.availableOptions.length > 0 ||
21538
+ this.menu.loading ||
21539
+ this.noMatchOption
21540
+ ) {
21541
+ if (this._userTyped) {
21542
+ if (!this.dropdownOpen) {
21543
+ this.showBib();
21544
+ }
21545
+ this._userTyped = false;
21546
+ }
21547
+ }
21548
+
21562
21549
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21563
21550
  this.activateFirstEnabledAvailableOption();
21564
21551
  }
21565
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21552
+ } else if (!this.dropdown.isBibFullscreen) {
21566
21553
  this.hideBib();
21567
21554
  }
21555
+
21556
+ if (this._programmaticFilterRefresh) {
21557
+ this._programmaticFilterRefresh = false;
21558
+ }
21568
21559
  }
21569
21560
 
21570
21561
  if (changedProperties.has('error')) {
@@ -21706,10 +21697,10 @@ class AuroCombobox extends AuroElement {
21706
21697
  shape="${this.shape}"
21707
21698
  size="${this.size}">
21708
21699
  <${this.inputTag}
21709
- @input="${this.handleInputValueChange}"
21700
+ @input="${this.handleTriggerInputValueChange}"
21710
21701
  appearance="${this.onDark ? 'inverse' : this.appearance}"
21711
21702
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
21712
- .a11yExpanded="${this.triggerExpandedState}"
21703
+ .a11yExpanded="${this.dropdownOpen}"
21713
21704
  .a11yControls="${this.dropdownId}"
21714
21705
  .autocomplete="${this.autocomplete}"
21715
21706
  .inputmode="${this.inputmode}"