@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
@@ -387,11 +387,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
387
387
  return;
388
388
  }
389
389
 
390
- // Validate that the date passed was the correct format and is a valid date
390
+ // Validate that the date passed was the correct format and is a valid date.
391
+ // For partial date formats, valueObject is never populated; validate them directly.
391
392
  if (elem.value && !elem.valueObject) {
392
- elem.validity = 'patternMismatch';
393
- elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
394
- return;
393
+ const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
394
+ const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
395
+
396
+ if (!isValidPartial) {
397
+ elem.validity = 'patternMismatch';
398
+ elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
399
+ return;
400
+ }
395
401
  }
396
402
 
397
403
  // Perform the rest of the validation
@@ -485,15 +491,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
485
491
  );
486
492
  }
487
493
 
488
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
489
- if (this.auroInputElements?.length === 2) {
490
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
494
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
495
+
496
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
497
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
498
+ // field (datepicker is the intended consumer — start/end are independently required).
499
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
500
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
491
501
  hasValue = false;
492
502
  }
493
503
  }
494
504
 
495
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
496
-
497
505
  if (isCombobox) {
498
506
 
499
507
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -810,6 +818,8 @@ function navigateArrow(component, direction, options = {}) {
810
818
  }
811
819
  }
812
820
 
821
+ /* eslint-disable no-underscore-dangle */
822
+
813
823
  /**
814
824
  * Returns the clear button element from the active input's shadow
815
825
  * DOM, if available.
@@ -845,11 +855,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
845
855
  * @param {Object} menu - The menu component.
846
856
  */
847
857
  function reconcileMenuIndex(menu) {
848
- // eslint-disable-next-line no-underscore-dangle
849
858
  if (menu._index < 0 && menu.optionActive && menu.items) {
850
859
  const idx = menu.items.indexOf(menu.optionActive);
851
860
  if (idx >= 0) {
852
- // eslint-disable-next-line no-underscore-dangle
853
861
  menu._index = idx;
854
862
  }
855
863
  }
@@ -868,11 +876,7 @@ const comboboxKeyboardStrategy = {
868
876
 
869
877
  // navigate if bib is open otherwise open it
870
878
  if (component.dropdown.isPopoverVisible) {
871
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
872
- component.activateLastEnabledAvailableOption();
873
- } else {
874
- navigateArrow(component, 'down');
875
- }
879
+ navigateArrow(component, 'down');
876
880
  } else {
877
881
  component.showBib();
878
882
  }
@@ -891,11 +895,7 @@ const comboboxKeyboardStrategy = {
891
895
 
892
896
  // navigate if bib is open otherwise open it
893
897
  if (component.dropdown.isPopoverVisible) {
894
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
895
- component.activateFirstEnabledAvailableOption();
896
- } else {
897
- navigateArrow(component, 'up');
898
- }
898
+ navigateArrow(component, 'up');
899
899
  } else {
900
900
  component.showBib();
901
901
  }
@@ -911,13 +911,18 @@ const comboboxKeyboardStrategy = {
911
911
  },
912
912
 
913
913
  Enter(component, evt, ctx) {
914
+ // Forms should not submit on Enter from a combobox, regardless of which
915
+ // child element (input, clear button, menu) is focused.
916
+ evt.stopPropagation();
917
+
914
918
  if (isClearBtnFocused(ctx)) {
915
- // If the clear button has focus, let the browser activate it normally.
916
- // stopPropagation prevents parent containers (e.g., forms) from treating
917
- // Enter as a submit, but we must NOT call preventDefault — that would
918
- // block the browser's built-in "Enter activates focused button" behavior.
919
- evt.stopPropagation();
920
- } else if (ctx.isExpanded && component.menu.optionActive) {
919
+ // Let the browser dispatch Enter to the focused clear button so its
920
+ // built-in activation fires and clears the selection. Do NOT call
921
+ // preventDefault — that would block the activation.
922
+ return;
923
+ }
924
+
925
+ if (ctx.isExpanded && component.menu.optionActive) {
921
926
  reconcileMenuIndex(component.menu);
922
927
  component.menu.makeSelection();
923
928
 
@@ -926,14 +931,8 @@ const comboboxKeyboardStrategy = {
926
931
  }
927
932
 
928
933
  evt.preventDefault();
929
- evt.stopPropagation();
930
934
  } else {
931
- // Prevent the keypress from bubbling to parent containers (e.g., forms)
932
- // which could interpret Enter as a submit or trigger other unintended behavior.
933
- // This is safe because showBib() opens the dialog programmatically,
934
- // not via event propagation.
935
935
  evt.preventDefault();
936
- evt.stopPropagation();
937
936
  component.showBib();
938
937
  }
939
938
  },
@@ -978,7 +977,7 @@ const comboboxKeyboardStrategy = {
978
977
  component.setClearBtnFocus();
979
978
  }
980
979
  }
981
- },
980
+ }
982
981
  };
983
982
 
984
983
  /**
@@ -4873,7 +4872,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4873
4872
  }
4874
4873
  };
4875
4874
 
4876
- var formkitVersion$2 = '202606292156';
4875
+ var formkitVersion$2 = '202607011722';
4877
4876
 
4878
4877
  let AuroElement$2 = class AuroElement extends i$3 {
4879
4878
  static get properties() {
@@ -5625,7 +5624,19 @@ class AuroDropdown extends AuroElement$2 {
5625
5624
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
5626
5625
  this.trigger.focus();
5627
5626
  }
5628
- }
5627
+
5628
+
5629
+ if (!this.isPopoverVisible) {
5630
+ // wait til the bib gets fully closed and rendered
5631
+ setTimeout(() => {
5632
+ // 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.
5633
+ if (!this.isPopoverVisible || !this.bib.matches(':focus-within')) {
5634
+ return;
5635
+ }
5636
+ // Move focus out of bib into trigger.
5637
+ this.trigger.focus();
5638
+ });
5639
+ } }
5629
5640
 
5630
5641
  firstUpdated() {
5631
5642
  // Configure the floater to, this will generate the ID for the bib
@@ -10557,11 +10568,17 @@ class AuroFormValidation {
10557
10568
  return;
10558
10569
  }
10559
10570
 
10560
- // Validate that the date passed was the correct format and is a valid date
10571
+ // Validate that the date passed was the correct format and is a valid date.
10572
+ // For partial date formats, valueObject is never populated; validate them directly.
10561
10573
  if (elem.value && !elem.valueObject) {
10562
- elem.validity = 'patternMismatch';
10563
- elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
10564
- return;
10574
+ const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
10575
+ const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
10576
+
10577
+ if (!isValidPartial) {
10578
+ elem.validity = 'patternMismatch';
10579
+ elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
10580
+ return;
10581
+ }
10565
10582
  }
10566
10583
 
10567
10584
  // Perform the rest of the validation
@@ -10655,15 +10672,17 @@ class AuroFormValidation {
10655
10672
  );
10656
10673
  }
10657
10674
 
10658
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
10659
- if (this.auroInputElements?.length === 2) {
10660
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
10675
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10676
+
10677
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
10678
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
10679
+ // field (datepicker is the intended consumer — start/end are independently required).
10680
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
10681
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
10661
10682
  hasValue = false;
10662
10683
  }
10663
10684
  }
10664
10685
 
10665
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10666
-
10667
10686
  if (isCombobox) {
10668
10687
 
10669
10688
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -16563,6 +16582,42 @@ class AuroInputUtilities {
16563
16582
  return type === 'date' && normalizedFormat.includes('yy') && normalizedFormat.includes('mm') && normalizedFormat.includes('dd');
16564
16583
  }
16565
16584
 
16585
+ /**
16586
+ * Validates a value against a partial date format (one that lacks yy/mm/dd all three).
16587
+ * Single-component formats are checked as integer ranges; multi-component formats use
16588
+ * a date-fns parse + round-trip to confirm both validity and exact formatting.
16589
+ * @param {string} value - The user-facing display value.
16590
+ * @param {string} format - The partial date format string (e.g. "mm/yyyy", "yyyy", "dd").
16591
+ * @returns {boolean}
16592
+ */
16593
+ isValidPartialDate(value, format$1) {
16594
+ if (!value || !format$1) {
16595
+ return false;
16596
+ }
16597
+ const normalizedFormat = format$1.toLowerCase();
16598
+
16599
+ if (normalizedFormat === 'dd') {
16600
+ const num = Number(value);
16601
+ return (/^\d{2}$/u).test(value) && num >= 1 && num <= 31;
16602
+ }
16603
+ if (normalizedFormat === 'yy') {
16604
+ const num = Number(value);
16605
+ return (/^\d{2}$/u).test(value) && num >= 0 && num <= 99;
16606
+ }
16607
+ if (normalizedFormat === 'yyyy') {
16608
+ const num = Number(value);
16609
+ return (/^\d{4}$/u).test(value) && num >= 1 && num <= 9999;
16610
+ }
16611
+
16612
+ const dateFnsMask = this.toDateFnsMask(normalizedFormat);
16613
+ // Use the 1st of the current month as the reference so that formats
16614
+ // omitting a day (e.g. MM/yyyy) never roll over on days 29–31.
16615
+ const referenceDate = new Date();
16616
+ referenceDate.setDate(1);
16617
+ const parsed = parse(value, dateFnsMask, referenceDate);
16618
+ return isValid(parsed) && format(parsed, dateFnsMask) === value;
16619
+ }
16620
+
16566
16621
  /**
16567
16622
  * Converts a display string to its model value.
16568
16623
  * For full date formats, converts the display string to an ISO date string.
@@ -16610,6 +16665,7 @@ class AuroInputUtilities {
16610
16665
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16611
16666
 
16612
16667
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16668
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16613
16669
  return undefined;
16614
16670
  }
16615
16671
 
@@ -16767,6 +16823,7 @@ class BaseInput extends AuroElement$1 {
16767
16823
  // so a parent (datepicker/combobox) calling `validate()` synchronously
16768
16824
  // during its own update cycle sees a populated util instance.
16769
16825
  this.activeLabel = false;
16826
+
16770
16827
  /** @private */
16771
16828
  this.allowedInputTypes = [
16772
16829
  "text",
@@ -16777,6 +16834,7 @@ class BaseInput extends AuroElement$1 {
16777
16834
  "tel"
16778
16835
  ];
16779
16836
  this.appearance = "default";
16837
+
16780
16838
  /** @private */
16781
16839
  this.dateFormatMap = {
16782
16840
  'mm/dd/yyyy': 'dateMMDDYYYY',
@@ -16795,23 +16853,24 @@ class BaseInput extends AuroElement$1 {
16795
16853
  'mm/dd': 'dateMMDD'
16796
16854
  };
16797
16855
  this.disabled = false;
16856
+
16798
16857
  /** @private */
16799
16858
  this.domHandler = new DomHandler();
16800
16859
  this.dvInputOnly = false;
16801
16860
  this.hasValue = false;
16802
16861
  this.hideLabelVisually = false;
16803
16862
  this.icon = false;
16863
+
16804
16864
  /** @private */
16805
16865
  this.inputIconName = undefined;
16866
+
16806
16867
  /** @private */
16807
16868
  this.label = 'Input label is undefined';
16808
16869
  this.layout = 'classic';
16809
16870
  this.locale = 'en-US';
16810
16871
  this.max = undefined;
16811
- this._maxObject = undefined;
16812
16872
  this.maxLength = undefined;
16813
16873
  this.min = undefined;
16814
- this._minObject = undefined;
16815
16874
  this.minLength = undefined;
16816
16875
  this.noValidate = false;
16817
16876
  this.onDark = false;
@@ -16828,23 +16887,27 @@ class BaseInput extends AuroElement$1 {
16828
16887
  "email"
16829
16888
  ];
16830
16889
  this.shape = 'classic';
16890
+
16831
16891
  /** @private */
16832
16892
  this.showPassword = false;
16833
16893
  this.size = 'lg';
16834
16894
  this.touched = false;
16895
+
16835
16896
  /** @private */
16836
16897
  this.uniqueId = new UniqueId().create();
16898
+
16837
16899
  /** @private */
16838
16900
  this.util = new AuroInputUtilities({
16839
16901
  locale: this.locale,
16840
16902
  format: this.format
16841
16903
  });
16904
+
16842
16905
  /** @private */
16843
16906
  this.validation = new AuroFormValidation();
16907
+
16844
16908
  /** @private */
16845
16909
  this.validationCCLength = undefined;
16846
16910
  this.value = undefined;
16847
- this._valueObject = undefined;
16848
16911
  }
16849
16912
 
16850
16913
  // function to define props used within the scope of this component
@@ -17174,6 +17237,13 @@ class BaseInput extends AuroElement$1 {
17174
17237
  type: String
17175
17238
  },
17176
17239
 
17240
+ /**
17241
+ * Custom help text message to display when validity = `patternMismatch`.
17242
+ */
17243
+ setCustomValidityPatternMismatch: {
17244
+ type: String
17245
+ },
17246
+
17177
17247
  /**
17178
17248
  * Custom help text message to display when validity = `rangeOverflow`.
17179
17249
  */
@@ -17244,7 +17314,7 @@ class BaseInput extends AuroElement$1 {
17244
17314
 
17245
17315
  /**
17246
17316
  * Populates the `type` attribute on the input.
17247
- * @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number'}
17317
+ * @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number' | 'date'}
17248
17318
  * @default 'text'
17249
17319
  */
17250
17320
  type: {
@@ -17282,7 +17352,7 @@ class BaseInput extends AuroElement$1 {
17282
17352
  * @returns {Date|undefined}
17283
17353
  */
17284
17354
  get valueObject() {
17285
- return this._valueObject || this._computeDateObjectFallback(this.value);
17355
+ return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
17286
17356
  }
17287
17357
 
17288
17358
  /**
@@ -17290,7 +17360,7 @@ class BaseInput extends AuroElement$1 {
17290
17360
  * @returns {Date|undefined}
17291
17361
  */
17292
17362
  get minObject() {
17293
- return this._minObject || this._computeDateObjectFallback(this.min);
17363
+ return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
17294
17364
  }
17295
17365
 
17296
17366
  /**
@@ -17298,50 +17368,7 @@ class BaseInput extends AuroElement$1 {
17298
17368
  * @returns {Date|undefined}
17299
17369
  */
17300
17370
  get maxObject() {
17301
- return this._maxObject || this._computeDateObjectFallback(this.max);
17302
- }
17303
-
17304
- /**
17305
- * Parses a date string into a Date object when the corresponding `_*Object`
17306
- * field hasn't been synced yet by `updated()`. Returns undefined when the
17307
- * input type/format isn't a full date or the string is not a valid date.
17308
- *
17309
- * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17310
- * inside its own `updated()` before this input's `updated()` has run
17311
- * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17312
- * and range checks would otherwise silently no-op (flipping the result to
17313
- * `valid` or `patternMismatch`).
17314
- * @private
17315
- * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17316
- * @returns {Date|undefined}
17317
- */
17318
- _computeDateObjectFallback(dateStr) {
17319
- if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17320
- return undefined;
17321
- }
17322
- if (!dateFormatter.isValidDate(dateStr)) {
17323
- return undefined;
17324
- }
17325
- return dateFormatter.stringToDateInstance(dateStr);
17326
- }
17327
-
17328
- /**
17329
- * Internal setter for readonly date object properties.
17330
- * @private
17331
- * @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
17332
- * @param {Date|undefined} propertyValue - Value to assign.
17333
- * @returns {void}
17334
- */
17335
- setDateObjectProperty(propertyName, propertyValue) {
17336
- const internalPropertyName = `_${propertyName}`;
17337
- const previousValue = this[internalPropertyName];
17338
-
17339
- if (previousValue === propertyValue) {
17340
- return;
17341
- }
17342
-
17343
- this[internalPropertyName] = propertyValue;
17344
- this.requestUpdate(propertyName, previousValue);
17371
+ return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
17345
17372
  }
17346
17373
 
17347
17374
  connectedCallback() {
@@ -17370,7 +17397,6 @@ class BaseInput extends AuroElement$1 {
17370
17397
  format: this.format
17371
17398
  });
17372
17399
  this.configureDataForType();
17373
- this.syncDateValues();
17374
17400
  }
17375
17401
 
17376
17402
  disconnectedCallback() {
@@ -17409,7 +17435,6 @@ class BaseInput extends AuroElement$1 {
17409
17435
  this.setCustomHelpTextMessage();
17410
17436
  this.configureAutoFormatting();
17411
17437
  this.configureDataForType();
17412
- this.syncDateValues();
17413
17438
  }
17414
17439
 
17415
17440
  /**
@@ -17553,8 +17578,6 @@ class BaseInput extends AuroElement$1 {
17553
17578
  this.configureDataForType();
17554
17579
  }
17555
17580
 
17556
- this.syncDateValues(changedProperties);
17557
-
17558
17581
  if (changedProperties.has('value')) {
17559
17582
  if (this.value && this.value.length > 0) {
17560
17583
  this.hasValue = true;
@@ -17576,14 +17599,14 @@ class BaseInput extends AuroElement$1 {
17576
17599
 
17577
17600
  if (formattedValue !== this.inputElement.value) {
17578
17601
  this.skipNextProgrammaticInputEvent = true;
17579
- if (this.maskInstance && this.type === 'credit-card') {
17602
+ if (this.maskInstance && this.type !== 'date') {
17580
17603
  // Route through the mask so its _value and el.value stay in lock-step
17581
17604
  // (set value calls updateControl which writes el.value = displayValue).
17582
17605
  // Writing el.value directly leaves the mask thinking displayValue is
17583
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17584
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17585
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17586
- // and flip validity from patternMismatch to tooShort.
17606
+ // stale; _saveSelection on the next focus/click then warns. Date is
17607
+ // excluded because its formattedValue can be raw ISO when the calendar
17608
+ // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17609
+ // flip validity from patternMismatch to tooShort.
17587
17610
  this.maskInstance.value = formattedValue || '';
17588
17611
  } else if (formattedValue) {
17589
17612
  this.inputElement.value = formattedValue;
@@ -17625,120 +17648,65 @@ class BaseInput extends AuroElement$1 {
17625
17648
  }));
17626
17649
  }
17627
17650
 
17628
-
17629
- /**
17630
- * Synchronizes the ISO string values and Date object representations for date-related properties.
17631
- * This keeps the model and display values aligned when either side changes.
17632
- *
17633
- * When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
17634
- * Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
17635
- * represents a full year/month/day date format.
17636
- *
17637
- * @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
17638
- * @returns {void}
17639
- * @private
17640
- */
17641
- syncDateValues(changedProperties = undefined) {
17642
- if (!this.util.isFullDateFormat(this.type, this.format)) {
17643
- return;
17644
- }
17645
-
17646
- this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
17647
- this.syncSingleDateValue(changedProperties, 'minObject', 'min');
17648
- this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
17649
- }
17650
-
17651
- /**
17652
- * Synchronizes one date object/string property pair.
17653
- * @private
17654
- * @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
17655
- * @param {string} objectProperty - Date object property name.
17656
- * @param {string} valueProperty - ISO string property name.
17657
- * @returns {void}
17658
- */
17659
- syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
17660
- const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
17661
-
17662
- // objectProperty wins over valueProperty when both changed
17663
- if (objectPropertyChanged && this[objectProperty]) {
17664
- this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
17665
- return;
17666
- }
17667
-
17668
- const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
17669
- if (!valuePropertyChanged) {
17670
- return;
17671
- }
17672
-
17673
- // 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)
17674
- if (
17675
- changedProperties &&
17676
- valueProperty === 'value' &&
17677
- changedProperties.get('value') === undefined &&
17678
- this[objectProperty] instanceof Date &&
17679
- this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
17680
- ) {
17681
- return;
17682
- }
17683
-
17684
- if (dateFormatter.isValidDate(this[valueProperty])) {
17685
- this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
17686
- } else {
17687
- this.setDateObjectProperty(objectProperty, undefined);
17688
- }
17689
- }
17690
-
17691
17651
  /**
17692
17652
  * Sets up IMasks and logic based on auto-formatting requirements.
17693
17653
  * @private
17694
17654
  * @returns {void}
17695
17655
  */
17696
17656
  configureAutoFormatting() {
17697
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17698
- // by _configuringMask above) could otherwise trigger handleInput
17699
- // processCreditCard configureAutoFormatting before the outer call's
17700
- // set value has finished its alignCursor pass.
17657
+ // _configuringMask gates two things: external re-entry into this method
17658
+ // while setup is mid-flight (from property changes that call back here),
17659
+ // and the accept/complete listeners below — both need to ignore the mask
17660
+ // events fired by our own value-restore step.
17701
17661
  if (this._configuringMask) return;
17702
17662
  this._configuringMask = true;
17703
17663
  try {
17664
+ // Destroy any prior mask so IMask can attach fresh under the new format.
17665
+ // Null the reference too — if the new maskOptions.mask is falsy (e.g.
17666
+ // type switched from credit-card to text) the IMask() reassignment
17667
+ // below is skipped, and downstream writes at line ~823 would otherwise
17668
+ // route through the destroyed instance.
17704
17669
  if (this.maskInstance) {
17705
17670
  this.maskInstance.destroy();
17671
+ this.maskInstance = null;
17706
17672
  }
17707
17673
 
17708
- // Pass new format to util
17709
17674
  this.util.updateFormat(this.format);
17710
17675
 
17711
17676
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17712
17677
 
17713
17678
  if (this.inputElement && maskOptions.mask) {
17714
-
17715
- // Stash and clear any existing value before IMask init.
17716
- // IMask's constructor processes the current input value which requires
17717
- // selection state clearing first avoids that scenario entirely.
17718
- // When the format changes (e.g. locale switch) and we have a valid ISO
17719
- // model value, compute the display string for the NEW format instead of
17720
- // re-using the old display string, which may be invalid in the new mask.
17679
+ // Capture the current display so it can be re-applied after IMask
17680
+ // attaches. The restore at the bottom goes through maskInstance.value
17681
+ // (not inputElement.value directly) so the mask's internal state and
17682
+ // the input's displayed text stay in lock-step.
17721
17683
  let existingValue = this.inputElement.value;
17684
+
17685
+ // Format-change case (e.g. locale switch): existingValue is the OLD
17686
+ // mask's display string and may not parse under the new mask. When
17687
+ // we have a valid date model, rebuild the display from valueObject
17688
+ // (the canonical source) using the new mask's format function.
17722
17689
  if (
17723
17690
  this.util.isFullDateFormat(this.type, this.format) &&
17724
17691
  this.value &&
17725
- dateFormatter.isValidDate(this.value) &&
17726
- this.valueObject instanceof Date &&
17727
- !Number.isNaN(this.valueObject.getTime()) &&
17692
+ this.valueObject &&
17728
17693
  typeof maskOptions.format === 'function'
17729
17694
  ) {
17730
17695
  existingValue = maskOptions.format(this.valueObject);
17731
17696
  }
17732
17697
 
17698
+ // Clear before IMask attaches so the constructor seeds an empty
17699
+ // internal value. Otherwise IMask reads the stale unmasked string
17700
+ // and emits a spurious 'accept' before the restore below runs.
17733
17701
  this.skipNextProgrammaticInputEvent = true;
17734
17702
  this.inputElement.value = '';
17735
17703
 
17736
17704
  this.maskInstance = IMask(this.inputElement, maskOptions);
17737
17705
 
17706
+ // Mask fires 'accept' on every value change, including the restore
17707
+ // step below. Skip events fired during configureAutoFormatting so
17708
+ // we don't overwrite a value the parent just pushed.
17738
17709
  this.maskInstance.on('accept', () => {
17739
- // Suppress propagation during configureAutoFormatting's own value-restoration
17740
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17741
- // and we don't want to overwrite a value the parent just pushed.
17742
17710
  if (this._configuringMask) return;
17743
17711
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17744
17712
  if (this.type === "date") {
@@ -17746,6 +17714,8 @@ class BaseInput extends AuroElement$1 {
17746
17714
  }
17747
17715
  });
17748
17716
 
17717
+ // Mask fires 'complete' on the restore step below for any value that
17718
+ // happens to be a complete match. Same setup-suppression as 'accept'.
17749
17719
  this.maskInstance.on('complete', () => {
17750
17720
  if (this._configuringMask) return;
17751
17721
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17754,7 +17724,9 @@ class BaseInput extends AuroElement$1 {
17754
17724
  }
17755
17725
  });
17756
17726
 
17757
- // Restore the stashed value through IMask so it's properly masked
17727
+ // Write existingValue through the mask (not the input directly) so
17728
+ // the mask reformats it under the new rules and keeps its internal
17729
+ // _value aligned with the input's displayed text.
17758
17730
  if (existingValue) {
17759
17731
  this.maskInstance.value = existingValue;
17760
17732
  }
@@ -17848,9 +17820,9 @@ class BaseInput extends AuroElement$1 {
17848
17820
  // the gated types currently support it, since the list is a public
17849
17821
  // property a consumer could mutate.
17850
17822
  if (this.setSelectionInputTypes.includes(this.type)) {
17851
- let selectionStart;
17823
+ let selectionStart = null;
17852
17824
  try {
17853
- selectionStart = this.inputElement.selectionStart;
17825
+ ({ selectionStart } = this.inputElement);
17854
17826
  } catch (error) { // eslint-disable-line no-unused-vars
17855
17827
  return;
17856
17828
  }
@@ -17954,7 +17926,6 @@ class BaseInput extends AuroElement$1 {
17954
17926
  */
17955
17927
  reset() {
17956
17928
  this.value = undefined;
17957
- this.setDateObjectProperty('valueObject', undefined);
17958
17929
  this.validation.reset(this);
17959
17930
  }
17960
17931
 
@@ -17963,7 +17934,6 @@ class BaseInput extends AuroElement$1 {
17963
17934
  */
17964
17935
  clear() {
17965
17936
  this.value = undefined;
17966
- this.setDateObjectProperty('valueObject', undefined);
17967
17937
  }
17968
17938
 
17969
17939
  /**
@@ -18492,7 +18462,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18492
18462
  }
18493
18463
  };
18494
18464
 
18495
- var formkitVersion$1 = '202606292156';
18465
+ var formkitVersion$1 = '202607011722';
18496
18466
 
18497
18467
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18498
18468
  // See LICENSE in the project root for license information.
@@ -19618,7 +19588,7 @@ class AuroBibtemplate extends i$3 {
19618
19588
  }
19619
19589
  }
19620
19590
 
19621
- var formkitVersion = '202606292156';
19591
+ var formkitVersion = '202607011722';
19622
19592
 
19623
19593
  var styleCss$3 = i$6`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
19624
19594
 
@@ -19986,12 +19956,23 @@ function getOptionLabel(option) {
19986
19956
  if (!option) {
19987
19957
  return '';
19988
19958
  }
19989
- const clone = option.cloneNode(true);
19990
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19991
- if (displayValueEl) {
19992
- displayValueEl.remove();
19959
+
19960
+ // Consumer-provided override: short-circuit the DOM walk entirely.
19961
+ if (option.dataset && option.dataset.label) {
19962
+ return option.dataset.label;
19963
+ }
19964
+
19965
+ // Walk direct children — the `slot` attribute only applies to direct children
19966
+ // of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
19967
+ // querySelector allocation that ran on every keystroke via syncValuesAndStates.
19968
+ let text = '';
19969
+ for (const node of option.childNodes) {
19970
+ const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
19971
+ if (!isDisplayValueSlot) {
19972
+ text += node.textContent || '';
19973
+ }
19993
19974
  }
19994
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19975
+ return text.replace(/\s+/gu, ' ').trim();
19995
19976
  }
19996
19977
 
19997
19978
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
@@ -20066,8 +20047,6 @@ class AuroCombobox extends AuroElement {
20066
20047
  this.availableOptions = [];
20067
20048
  this.dropdownId = undefined;
20068
20049
  this.dropdownOpen = false;
20069
- this.triggerExpandedState = false;
20070
- this._expandedTimeout = null;
20071
20050
  this._inFullscreenTransition = false;
20072
20051
  this.errorMessage = null;
20073
20052
  this.isHiddenWhileLoading = false;
@@ -20078,6 +20057,30 @@ class AuroCombobox extends AuroElement {
20078
20057
  this.touched = false;
20079
20058
  this.validation = new AuroFormValidation$1();
20080
20059
  this.validity = undefined;
20060
+ this._userTyped = false;
20061
+ // Tracks every setTimeout scheduled via _scheduleTimer so
20062
+ // disconnectedCallback can cancel them. Without this, a detached
20063
+ // combobox's pending timers still fire — most are no-ops, but
20064
+ // configureMenu's racing-condition retry would otherwise loop.
20065
+ this._pendingTimers = new Set();
20066
+ }
20067
+
20068
+ /**
20069
+ * setTimeout wrapper that records the timer id so disconnectedCallback
20070
+ * can cancel any outstanding callbacks. The id is removed from the set
20071
+ * once the callback fires so the set doesn't grow unbounded.
20072
+ * @param {Function} fn - Callback to run.
20073
+ * @param {number} ms - Delay in milliseconds.
20074
+ * @returns {number} The timer id.
20075
+ * @private
20076
+ */
20077
+ _scheduleTimer(fn, ms) {
20078
+ const id = setTimeout(() => {
20079
+ this._pendingTimers.delete(id);
20080
+ fn();
20081
+ }, ms);
20082
+ this._pendingTimers.add(id);
20083
+ return id;
20081
20084
  }
20082
20085
 
20083
20086
  // This function is to define props used within the scope of this component
@@ -20439,17 +20442,6 @@ class AuroCombobox extends AuroElement {
20439
20442
  attribute: false
20440
20443
  },
20441
20444
 
20442
- /**
20443
- * Deferred aria-expanded state for the trigger input.
20444
- * Delays the "true" transition so VoiceOver finishes its character echo
20445
- * before announcing "expanded".
20446
- * @private
20447
- */
20448
- triggerExpandedState: {
20449
- type: Boolean,
20450
- reflect: false,
20451
- attribute: false
20452
- },
20453
20445
  };
20454
20446
  }
20455
20447
 
@@ -20472,13 +20464,6 @@ class AuroCombobox extends AuroElement {
20472
20464
  return this.input.value;
20473
20465
  }
20474
20466
 
20475
- // /**
20476
- // * Sets the value of the input element within the combobox.
20477
- // */
20478
- // set inputValue(value) {
20479
- // this.input.value = value;
20480
- // }
20481
-
20482
20467
  /**
20483
20468
  * Checks if the element is valid.
20484
20469
  * @returns {boolean} - Returns true if the element is valid, false otherwise.
@@ -20605,7 +20590,7 @@ class AuroCombobox extends AuroElement {
20605
20590
  if (this.menu) {
20606
20591
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20607
20592
  }
20608
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20593
+ const label = getOptionLabel(this.menu.optionSelected);
20609
20594
  this.updateTriggerTextDisplay(label || this.value);
20610
20595
  }
20611
20596
 
@@ -20619,44 +20604,16 @@ class AuroCombobox extends AuroElement {
20619
20604
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20620
20605
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20621
20606
 
20622
- if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20623
- const nextValue = label || this.value;
20624
- // Only set the flag when there's an actual write to suppress —
20625
- // syncValuesAndStates re-enters here during typing when both inputs
20626
- // already match, and a no-op flag flip would make the bib branch's
20627
- // bail eat legitimate user-input events.
20628
- const triggerNeedsSync = this.input.value !== nextValue;
20629
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20630
- if (triggerNeedsSync || bibNeedsSync) {
20631
- this._syncingDisplayValue = true;
20632
- if (triggerNeedsSync) {
20633
- this.input.value = nextValue;
20634
- }
20635
- if (bibNeedsSync) {
20636
- this.inputInBib.value = nextValue;
20637
- }
20638
- const pending = [];
20639
- if (triggerNeedsSync) {
20640
- pending.push(this.input.updateComplete);
20641
- }
20642
- if (bibNeedsSync) {
20643
- pending.push(this.inputInBib.updateComplete);
20644
- }
20645
- Promise.all(pending).then(() => {
20646
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20647
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20648
- this.input.maskInstance.updateValue();
20649
- }
20650
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20651
- this.inputInBib.maskInstance.updateValue();
20652
- }
20653
- this._syncingDisplayValue = false;
20654
- });
20655
- }
20607
+ const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20608
+ if (!suppressed) {
20609
+ this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20656
20610
  }
20657
20611
 
20658
- // update the displayValue in the trigger if displayValue slot content is present
20659
- const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]');
20612
+ // Replace any previously appended displayValue clone in the trigger.
20613
+ // :not(slot) excludes the template's <slot name="displayValue"
20614
+ // slot="displayValue"> forwarder, which also has slot="displayValue"
20615
+ // and would otherwise be matched first and removed.
20616
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
20660
20617
 
20661
20618
  if (displayValueInTrigger) {
20662
20619
  displayValueInTrigger.remove();
@@ -20680,6 +20637,53 @@ class AuroCombobox extends AuroElement {
20680
20637
  this.requestUpdate();
20681
20638
  }
20682
20639
 
20640
+ /**
20641
+ * Writes nextValue to the trigger input and the bib input when their current
20642
+ * value differs, then re-asserts imask after Lit's update flushes.
20643
+ * @param {string} nextValue - The value to write to both inputs.
20644
+ * @returns {Promise<void>} Resolves after both inputs flush and imask
20645
+ * re-asserts; resolves immediately when no sync is needed.
20646
+ * @private
20647
+ */
20648
+ async syncInputValuesAcrossTriggerAndBib(nextValue) {
20649
+ // Only set the flag when there's an actual write to suppress —
20650
+ // syncValuesAndStates re-enters here during typing when both inputs
20651
+ // already match, and a no-op flag flip would make the bib branch's
20652
+ // bail eat legitimate user-input events.
20653
+ const triggerNeedsSync = this.input.value !== nextValue;
20654
+ const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
20655
+ if (!triggerNeedsSync && !bibNeedsSync) {
20656
+ return;
20657
+ }
20658
+
20659
+ this._syncingDisplayValue = true;
20660
+
20661
+ const pending = [];
20662
+ if (triggerNeedsSync) {
20663
+ this.input.value = nextValue;
20664
+ pending.push(this.input.updateComplete);
20665
+ }
20666
+ if (bibNeedsSync) {
20667
+ this.inputInBib.value = nextValue;
20668
+ pending.push(this.inputInBib.updateComplete);
20669
+ }
20670
+ // finally — not a bare .then — so that an imask throw (see commit
20671
+ // d1857401c: imask can throw on credit-card format change) doesn't strand
20672
+ // _syncingDisplayValue=true and silently swallow every subsequent input.
20673
+ try {
20674
+ await Promise.all(pending);
20675
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20676
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20677
+ this.input.maskInstance.updateValue();
20678
+ }
20679
+ if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20680
+ this.inputInBib.maskInstance.updateValue();
20681
+ }
20682
+ } finally {
20683
+ this._syncingDisplayValue = false;
20684
+ }
20685
+ }
20686
+
20683
20687
  /**
20684
20688
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20685
20689
  * @private
@@ -20688,6 +20692,13 @@ class AuroCombobox extends AuroElement {
20688
20692
  handleMenuOptions() {
20689
20693
  this.generateOptionsArray();
20690
20694
  this.availableOptions = [];
20695
+ // Single source of truth for the menu's filter/highlight token per call.
20696
+ // syncValuesAndStates re-writes the same value on exact-match keystrokes —
20697
+ // Lit's hasChanged makes that a no-op — and the prior duplicate set inside
20698
+ // handleTriggerInputValueChange is gone.
20699
+ if (this.menu) {
20700
+ this.menu.matchWord = normalizeFilterValue(this.input.value);
20701
+ }
20691
20702
  this.updateFilter();
20692
20703
 
20693
20704
  // Set aria-setsize/aria-posinset on each visible option so screen readers
@@ -20699,17 +20710,14 @@ class AuroCombobox extends AuroElement {
20699
20710
  option.setAttribute('aria-posinset', index + 1);
20700
20711
  });
20701
20712
 
20702
- if (this.value && this.input.value && !this.menu.value) {
20703
- if (this.behavior === 'suggestion' && this.menu.options && this.menu.options.some((opt) => opt.value === this.value)) {
20704
- this.setMenuValue(this.value);
20705
- }
20706
-
20713
+ if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
20714
+ this.setMenuValue(this.input.value);
20707
20715
  this.syncValuesAndStates();
20708
20716
  }
20709
20717
 
20710
- // Re-activate when optionActive is no longer visible, or when _index has
20711
- // been reset (e.g. by clearSelection in an async update) so that
20712
- // makeSelection() can find the correct option.
20718
+ // Re-activate when optionActive is not in the current scrollable viewport,
20719
+ // or when _index has been reset (e.g. by clearSelection in an async update)
20720
+ // so that makeSelection() can find the correct option.
20713
20721
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20714
20722
  this.activateFirstEnabledAvailableOption();
20715
20723
  }
@@ -20782,18 +20790,6 @@ class AuroCombobox extends AuroElement {
20782
20790
  this.dropdownOpen = ev.detail.expanded;
20783
20791
  this.updateMenuShapeSize();
20784
20792
 
20785
- // Defer aria-expanded "true" so VoiceOver finishes character echo
20786
- // before announcing "expanded". Set "false" immediately on close.
20787
- clearTimeout(this._expandedTimeout);
20788
- if (this.dropdownOpen) {
20789
- const expandedDelay = 150;
20790
- this._expandedTimeout = setTimeout(() => {
20791
- this.triggerExpandedState = true;
20792
- }, expandedDelay);
20793
- } else {
20794
- this.triggerExpandedState = false;
20795
- }
20796
-
20797
20793
  // Clear aria-activedescendant when dropdown closes
20798
20794
  if (!this.dropdownOpen && this.input) {
20799
20795
  this.input.setActiveDescendant(null);
@@ -20843,28 +20839,25 @@ class AuroCombobox extends AuroElement {
20843
20839
 
20844
20840
  guardTouchPassthrough(this.menu);
20845
20841
 
20846
- // The dialog's showModal() steals focus from the trigger.
20847
- // A single rAF is enough for the dialog DOM to be painted and
20848
- // focusable, then doubleRaf finalizes the transition.
20842
+ // showModal() takes focus away from the trigger. Early focus once
20843
+ // the dialog has painted; the shared doubleRaf below is the fallback
20844
+ // for when the dialog needs an extra frame and also clears the
20845
+ // validation guard.
20849
20846
  requestAnimationFrame(() => {
20850
20847
  this.setInputFocus();
20851
20848
  });
20852
-
20853
- doubleRaf(() => {
20854
- this.setInputFocus();
20855
- this._inFullscreenTransition = false;
20856
- });
20857
- } else {
20858
- // Desktop popover-open: restore the trigger caret to end-of-text.
20859
- // Clicking the trigger lands on auro-input's floating <label for="…">
20860
- // overlay; Chrome resets the native input's selection to [0, 0] on
20861
- // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20862
- // branch parks the caret at end. doubleRaf lets the dropdown layout
20863
- // settle first, matching the fullscreen branch timing.
20864
- doubleRaf(() => {
20865
- this.setInputFocus();
20866
- });
20867
20849
  }
20850
+ // else (desktop popover-open): Chrome resets the trigger caret to
20851
+ // [0, 0] when its floating <label for="…"> overlay receives focus.
20852
+ // The shared doubleRaf below parks the caret back at end-of-text
20853
+ // after the dropdown layout settles.
20854
+
20855
+ doubleRaf(() => {
20856
+ this.setInputFocus();
20857
+ if (this._inFullscreenTransition) {
20858
+ this._inFullscreenTransition = false;
20859
+ }
20860
+ });
20868
20861
  }
20869
20862
  });
20870
20863
 
@@ -20901,7 +20894,7 @@ class AuroCombobox extends AuroElement {
20901
20894
  this.dropdown.trigger.inert = false;
20902
20895
  }
20903
20896
 
20904
- setTimeout(() => {
20897
+ this._scheduleTimer(() => {
20905
20898
  this.setInputFocus();
20906
20899
  }, 0);
20907
20900
  });
@@ -21045,7 +21038,7 @@ class AuroCombobox extends AuroElement {
21045
21038
 
21046
21039
  // racing condition on custom-combobox with custom-menu
21047
21040
  if (!this.menu) {
21048
- setTimeout(() => {
21041
+ this._scheduleTimer(() => {
21049
21042
  this.configureMenu();
21050
21043
  }, 0);
21051
21044
  return;
@@ -21075,7 +21068,7 @@ class AuroCombobox extends AuroElement {
21075
21068
  if (this.menu.optionSelected) {
21076
21069
  const selected = this.menu.optionSelected;
21077
21070
 
21078
- if (!this.optionSelected || this.optionSelected !== selected) {
21071
+ if (this.optionSelected !== selected) {
21079
21072
  this.optionSelected = selected;
21080
21073
  }
21081
21074
 
@@ -21088,7 +21081,7 @@ class AuroCombobox extends AuroElement {
21088
21081
  }
21089
21082
 
21090
21083
  // Update display
21091
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
21084
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21092
21085
 
21093
21086
  // Update match word for filtering
21094
21087
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -21100,17 +21093,24 @@ class AuroCombobox extends AuroElement {
21100
21093
  // Hide dropdown on selection (except during slot changes)
21101
21094
  if (evt.detail && evt.detail.source !== 'slotchange') {
21102
21095
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21103
- if (this.menu.value || this.behavior !== 'suggestion') {
21104
- this.hideBib();
21096
+ this.hideBib();
21097
+
21098
+ // Move focus to the clear button when the user makes a selection.
21099
+ if (!isEcho && this.menu.value !== undefined) {
21100
+ this.setClearBtnFocus();
21105
21101
  }
21106
21102
 
21107
21103
  // Announce the selection after the dropdown closes so it isn't
21108
21104
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
21105
+ // Skip when there's no selected value (e.g. menu.clearSelection() from
21106
+ // the unmatched-value path), otherwise VoiceOver reads "undefined".
21109
21107
  const selectedValue = this.menu.value;
21110
- const announcementDelay = 300;
21111
- setTimeout(() => {
21112
- announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21113
- }, announcementDelay);
21108
+ if (selectedValue) {
21109
+ const announcementDelay = 300;
21110
+ this._scheduleTimer(() => {
21111
+ announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21112
+ }, announcementDelay);
21113
+ }
21114
21114
  }
21115
21115
 
21116
21116
  // Programmatic value syncs leave availableOptions stale because
@@ -21119,22 +21119,6 @@ class AuroCombobox extends AuroElement {
21119
21119
  // only — fresh user selections take the existing hideBib path.
21120
21120
  if (isEcho && this.menu.optionSelected) {
21121
21121
  this._programmaticFilterRefresh = true;
21122
- this.handleMenuOptions();
21123
- setTimeout(() => {
21124
- this._programmaticFilterRefresh = false;
21125
- }, 0);
21126
- }
21127
-
21128
- // base-input skips auto-validate when focus is inside its own shadow,
21129
- // which it is after setTriggerInputFocus — re-run so prior tooShort
21130
- // clears. processCreditCard reasserts errorMessage on every render.
21131
- if (!isEcho && this.menu.optionSelected && this.input.validate) {
21132
- this.input.updateComplete.then(() => {
21133
- this.input.validate(true);
21134
- if (this.input.validity === 'valid') {
21135
- this.input.errorMessage = '';
21136
- }
21137
- });
21138
21122
  }
21139
21123
  });
21140
21124
 
@@ -21147,6 +21131,14 @@ class AuroCombobox extends AuroElement {
21147
21131
  // stale option. Safe from re-entrancy because any resulting
21148
21132
  // input.value changes dispatch isProgrammatic events.
21149
21133
  this.menu.addEventListener('auroMenu-selectValueFailure', () => {
21134
+ // Announce the rejection BEFORE we clear `this.value` so the live
21135
+ // region carries the attempted value — without this the bib closes
21136
+ // silently and screen-reader users get no signal that their request
21137
+ // (e.g. a direct setMenuValue() call) was dropped.
21138
+ const attemptedValue = this.value;
21139
+ if (attemptedValue) {
21140
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
21141
+ }
21150
21142
  this.value = undefined;
21151
21143
  this.optionSelected = undefined;
21152
21144
  });
@@ -21158,10 +21150,13 @@ class AuroCombobox extends AuroElement {
21158
21150
  this.input.setActiveDescendant(this.optionActive);
21159
21151
  }
21160
21152
 
21161
- // Announce the active option for screen readers including position,
21162
- // since shadow DOM boundaries prevent native reading of
21163
- // aria-setsize/aria-posinset via aria-activedescendant.
21164
- if (this.optionActive) {
21153
+ // In fullscreen mode the menu sits inside a nested <dialog> shadow root,
21154
+ // and aria-activedescendant references across that boundary are lost —
21155
+ // VoiceOver/NVDA don't read the active option natively, so we mirror it
21156
+ // into the polite live region. In popover mode aria-activedescendant on
21157
+ // the trigger input is read natively; double-announcing would flood the
21158
+ // queue on arrow-key repeat.
21159
+ if (this.optionActive && this.dropdown.isBibFullscreen) {
21165
21160
  const optionText = this.optionActive.textContent.trim();
21166
21161
  const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
21167
21162
  const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
@@ -21194,7 +21189,12 @@ class AuroCombobox extends AuroElement {
21194
21189
  * Validate every time we remove focus from the combo box.
21195
21190
  */
21196
21191
  this.addEventListener('focusout', () => {
21197
- if (!this.componentHasFocus && !this._inFullscreenTransition) {
21192
+ // Skip while the dropdown is open — focus transits out briefly on
21193
+ // mousedown of a menu option (popover top-layer breaks :focus-within),
21194
+ // and validating against the pre-selection value flashes a stale error
21195
+ // between mousedown and mouseup. The next focusout fires after the
21196
+ // dropdown closes and validates against the post-selection value.
21197
+ if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
21198
21198
  this.validate();
21199
21199
  }
21200
21200
  });
@@ -21257,49 +21257,40 @@ class AuroCombobox extends AuroElement {
21257
21257
  if (this._syncingDisplayValue) {
21258
21258
  return;
21259
21259
  }
21260
- this._syncingBibValue = true;
21261
- this.input.value = this.inputInBib.value;
21262
- this.input.updateComplete.then(() => {
21263
- this._syncingBibValue = false;
21264
- });
21265
21260
 
21266
- // Run filtering inline — the re-entrant event won't reach this code.
21267
- this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21268
- this.optionActive = null;
21269
-
21270
- // In suggestion mode, keep the combobox value in sync with the typed
21271
- // text so that freeform values are captured (mirroring the non-fullscreen
21272
- // path). Clear the selection when the input is emptied.
21273
- if (this.behavior === 'suggestion') {
21274
- this.value = this.inputInBib.value || undefined;
21261
+ // Filtering runs via re-entrance: writing this.input.value below
21262
+ // dispatches an 'input' event that the trigger's listener routes to
21263
+ // handleTriggerInputValueChange, which refreshes the menu filter.
21264
+ if (this.input.value !== this.inputInBib.value) {
21265
+ this._syncingBibValue = true;
21266
+ this.input.value = this.inputInBib.value;
21267
+ this.input.updateComplete.then(() => {
21268
+ this._syncingBibValue = false;
21269
+ });
21275
21270
  }
21276
21271
 
21277
- this.handleMenuOptions();
21278
21272
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21279
- return;
21273
+ } else if (event.target === this.input) {
21274
+
21275
+ // Also sync the native input immediately so keystrokes arriving
21276
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
21277
+ // fullscreen transition) operate on the correct content.
21278
+ // Skip the next programmatic input event to prevent the patched setter
21279
+ // from re-entering handleInputValueChange via the bib path.
21280
+ const bibNativeInput = this.inputInBib.inputElement;
21281
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21282
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
21283
+ bibNativeInput.value = this.input.value || '';
21284
+ }
21280
21285
  }
21281
21286
 
21287
+ this.value = this.input.value;
21288
+
21282
21289
  // Ignore re-entrant input events caused by programmatic value sets.
21283
21290
  if (this._syncingBibValue || this._syncingDisplayValue) {
21284
21291
  return;
21285
21292
  }
21286
21293
 
21287
- this.inputInBib.value = this.input.value;
21288
-
21289
- // Also sync the native input immediately so keystrokes arriving
21290
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21291
- // fullscreen transition) operate on the correct content.
21292
- // Skip the next programmatic input event to prevent the patched setter
21293
- // from re-entering handleInputValueChange via the bib path.
21294
- const bibNativeInput = this.inputInBib.inputElement;
21295
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21296
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21297
- bibNativeInput.value = this.input.value || '';
21298
- }
21299
-
21300
- this.menu.matchWord = normalizeFilterValue(this.input.value);
21301
- this.optionActive = null;
21302
-
21303
21294
  if (this.behavior === 'suggestion') {
21304
21295
  this.value = this.input.value;
21305
21296
  }
@@ -21307,7 +21298,6 @@ class AuroCombobox extends AuroElement {
21307
21298
  if (!this.input.value && !this._clearing) {
21308
21299
  this.clear();
21309
21300
  }
21310
- this.handleMenuOptions();
21311
21301
 
21312
21302
  // Validate only if the value was set programmatically (not during user
21313
21303
  // interaction). In fullscreen dialog mode, componentHasFocus returns false
@@ -21317,34 +21307,44 @@ class AuroCombobox extends AuroElement {
21317
21307
  this.validate();
21318
21308
  }
21319
21309
 
21320
- if (this.input.value && this.input.value.length === 0) {
21321
- // Hide menu if value is empty, otherwise show if there are available suggestions
21322
- this.hideBib();
21323
- } else if (this.menu.loading) {
21324
- // if input has value but menu is loading, show bib immediately
21325
- this.showBib();
21326
- } else if (this.availableOptions.length === 0 && !this.dropdown.isBibFullscreen) {
21327
- // Force dropdown bib to hide if input value has no matching suggestions
21328
- this.hideBib();
21310
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21311
+ }
21312
+
21313
+ /**
21314
+ * Handles input value changes originating from the trigger input.
21315
+ * Refreshes menu options and filtering, delegates to handleInputValueChange
21316
+ * for value synchronization, and manages fullscreen bib focus.
21317
+ * @private
21318
+ * @param {Event} event - The input event from the trigger input element.
21319
+ * @returns {void}
21320
+ */
21321
+ handleTriggerInputValueChange(event) {
21322
+ // 'input' fires for every user-initiated value change — typing, paste,
21323
+ // IME composition end, dead-key composition, drag-drop. Flip _userTyped
21324
+ // here so updated('availableOptions') auto-opens the bib for sources
21325
+ // that keydown alone misses: paste fires no keydown, IME uses
21326
+ // key='Process', and dead keys produce multi-char keys (all bypass the
21327
+ // prior keydown.key.length===1 gate). Skip programmatic syncs.
21328
+ if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
21329
+ this._userTyped = true;
21329
21330
  }
21330
21331
 
21331
- // iOS virtual keyboard retention: when in fullscreen mode, ensure the
21332
- // dialog opens and the bib input is focused synchronously within the
21333
- // input event (user gesture) chain. Without this, Lit's async update
21334
- // cycle delays showModal() past the user activation window, causing
21335
- // iOS Safari to dismiss the virtual keyboard when the fullscreen
21336
- // dialog opens — the user then has to tap the input again to resume
21337
- // typing.
21338
- if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
21339
- if (!this.dropdown.isPopoverVisible) {
21340
- this.showBib();
21341
- }
21342
- if (this.dropdown.isPopoverVisible) {
21343
- this.setInputFocus();
21344
- }
21332
+ this.handleMenuOptions();
21333
+ this.optionActive = null;
21334
+
21335
+ if (this.value === this.input.value) {
21336
+ return;
21345
21337
  }
21346
21338
 
21347
- this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21339
+ this.handleInputValueChange(event);
21340
+
21341
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
21342
+ this.setInputFocus();
21343
+ }
21344
+
21345
+ if (this._programmaticFilterRefresh) {
21346
+ this._programmaticFilterRefresh = false;
21347
+ }
21348
21348
  }
21349
21349
 
21350
21350
  /**
@@ -21412,6 +21412,11 @@ class AuroCombobox extends AuroElement {
21412
21412
  disconnectedCallback() {
21413
21413
  super.disconnectedCallback();
21414
21414
  this._inFullscreenTransition = false;
21415
+ // Cancel any outstanding timers so detached callbacks don't fire on
21416
+ // disposed DOM — most are no-ops, but configureMenu's racing-condition
21417
+ // retry would otherwise keep rescheduling itself indefinitely.
21418
+ this._pendingTimers.forEach((id) => clearTimeout(id));
21419
+ this._pendingTimers.clear();
21415
21420
  }
21416
21421
 
21417
21422
  firstUpdated() {
@@ -21453,7 +21458,7 @@ class AuroCombobox extends AuroElement {
21453
21458
  this.menu.value = value;
21454
21459
  // Backup clear: if menu.value === menu.optionSelected.value already, the
21455
21460
  // listener won't fire and the flag would swallow the next user click.
21456
- setTimeout(() => {
21461
+ this._scheduleTimer(() => {
21457
21462
  this._pendingMenuValueSync = false;
21458
21463
  }, 0);
21459
21464
  }
@@ -21470,15 +21475,7 @@ class AuroCombobox extends AuroElement {
21470
21475
  this.menu.value = undefined;
21471
21476
  this.validation.reset(this);
21472
21477
  this.touched = false;
21473
- // Force validity back to the cleared state. validation.reset() calls
21474
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21475
- // residual validity may still be copied into the combobox by the
21476
- // auroInputElements loop. Explicitly clear here so reset() actually
21477
- // leaves the component in a "no validity" state.
21478
21478
  this.validity = undefined;
21479
- this.errorMessage = '';
21480
- this.input.validity = undefined;
21481
- this.input.errorMessage = '';
21482
21479
  }
21483
21480
 
21484
21481
  /**
@@ -21496,6 +21493,17 @@ class AuroCombobox extends AuroElement {
21496
21493
  this.optionSelected = undefined;
21497
21494
  this.value = undefined;
21498
21495
 
21496
+ // Clear the appended displayValue clone in the trigger if present.
21497
+ // :not(slot) excludes the template's <slot name="displayValue"
21498
+ // slot="displayValue"> forwarder (line 1816), which also has
21499
+ // slot="displayValue" and would otherwise be matched first and removed,
21500
+ // permanently breaking the consumer-provided displayValue slot.
21501
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
21502
+
21503
+ if (displayValueInTrigger) {
21504
+ displayValueInTrigger.remove();
21505
+ }
21506
+
21499
21507
  if (this.input.value) {
21500
21508
  this.input.clear();
21501
21509
  }
@@ -21519,81 +21527,50 @@ class AuroCombobox extends AuroElement {
21519
21527
  }
21520
21528
 
21521
21529
  updated(changedProperties) {
21522
- // After the component is ready, send direct value changes to auro-menu.
21530
+ // After the component is ready, propagate direct changes down to child components.
21523
21531
  if (changedProperties.has('value')) {
21524
- if (this.value && this.value.length > 0) {
21525
- this.hasValue = true;
21526
- } else {
21527
- this.hasValue = false;
21532
+ // Only flag programmatic refreshes — user-typed value changes must not
21533
+ // suppress the availableOptions branch's showBib(). Firefox batches
21534
+ // 'value' and 'availableOptions' into the same updated() call, so
21535
+ // setting the flag unconditionally here masks the user-typed open path.
21536
+ if (!this._userTyped) {
21537
+ this._programmaticFilterRefresh = true;
21528
21538
  }
21529
21539
 
21530
- if (this.hasValue && !this.input.value && (!this.menu.options || this.menu.options.length === 0)) {
21531
- this.input.value = this.value;
21532
- }
21540
+ if (this.input.value !== this.value) {
21541
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21542
+ // menu.value leaves the previously-selected option element pinned
21543
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21544
+ // would then write its stale .value back into combobox.value
21545
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21546
+ if (this.menu.value || this.menu.optionSelected) {
21547
+ this.menu.clearSelection();
21548
+ }
21533
21549
 
21534
- // Sync menu.value only when an option actually matches; otherwise clear it.
21535
- if (this.menu.options && this.menu.options.length > 0) {
21536
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21537
- this.setMenuValue(this.value);
21538
- } else if (this.behavior === 'filter') {
21539
- // In filter mode, freeform values aren't allowed. Push the unmatched
21540
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21541
- // and the listener at line 1206 clears combobox.value (mirrors select's
21542
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21543
- this.setMenuValue(this.value);
21544
- } else {
21545
- if (this.menu.value) {
21546
- this.menu.value = undefined;
21547
- }
21548
- // Sync the display for programmatic freeform value changes (e.g. the
21549
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21550
- // re-entrant input event from base-input.notifyValueChanged() is
21551
- // ignored by handleInputValueChange. Skip the sync when input already
21552
- // matches to avoid spurious events during the normal typing path.
21553
- const nextValue = this.value || '';
21554
- const triggerNeedsSync = this.input.value !== nextValue;
21555
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21556
- if (triggerNeedsSync || bibNeedsSync) {
21557
- this._syncingDisplayValue = true;
21558
- if (triggerNeedsSync) {
21559
- this.input.value = nextValue;
21560
- }
21561
- if (bibNeedsSync) {
21562
- this.inputInBib.value = nextValue;
21563
- }
21564
- const pending = [];
21565
- if (triggerNeedsSync) {
21566
- pending.push(this.input.updateComplete);
21567
- }
21568
- if (bibNeedsSync) {
21569
- pending.push(this.inputInBib.updateComplete);
21570
- }
21571
- Promise.all(pending).then(() => {
21572
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21573
- this.input.maskInstance.updateValue();
21574
- }
21575
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21576
- this.inputInBib.maskInstance.updateValue();
21577
- }
21578
- this._syncingDisplayValue = false;
21579
- // handleInputValueChange bailed on the flag — refresh the filter.
21580
- this._programmaticFilterRefresh = true;
21581
- this.handleMenuOptions();
21582
- setTimeout(() => {
21583
- this._programmaticFilterRefresh = false;
21584
- }, 0);
21585
- });
21586
- }
21550
+ if (!this.persistInput) {
21551
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21552
+ }
21553
+
21554
+ // Programmatic value with no matching option: updateFilter will close
21555
+ // the bib silently (see line 648 no noMatchOption + 0 results
21556
+ // hides). Announce so screen-reader users hear the request was
21557
+ // dropped. Gated on `input.value !== this.value` so this never fires
21558
+ // for user typing that path always reconciles input.value to
21559
+ // this.value before updated() runs.
21560
+ if (
21561
+ this.value &&
21562
+ this.menu &&
21563
+ this.menu.options &&
21564
+ this.menu.options.length > 0 &&
21565
+ !this.menu.options.some((opt) => opt.value === this.value)
21566
+ ) {
21567
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21587
21568
  }
21588
21569
  }
21570
+
21589
21571
  if (!this.value) {
21590
21572
  this.clear();
21591
21573
  }
21592
- if (this.value && !this.componentHasFocus) {
21593
- // If the value got set programmatically make sure we hide the bib
21594
- // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
21595
- this.hideBib();
21596
- }
21597
21574
 
21598
21575
  // Sync the input and match word, but don't directly set menu.value again
21599
21576
  if (this.menu) {
@@ -21606,7 +21583,7 @@ class AuroCombobox extends AuroElement {
21606
21583
  composed: true,
21607
21584
  detail: {
21608
21585
  optionSelected: this.menu.optionSelected,
21609
- value: this.menu.value
21586
+ value: this.value
21610
21587
  }
21611
21588
  }));
21612
21589
 
@@ -21629,16 +21606,30 @@ class AuroCombobox extends AuroElement {
21629
21606
  // from a programmatic filter refresh after a value swap — the user
21630
21607
  // didn't interact, so we shouldn't auto-open the bib (especially for
21631
21608
  // the no-match path where the existing condition unconditionally fires).
21632
- if (this._programmaticFilterRefresh) {
21633
- this._programmaticFilterRefresh = false;
21634
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21635
- this.showBib();
21609
+ if ((this.menu && !this._programmaticFilterRefresh)) {
21610
+ if (
21611
+ this.availableOptions.length > 0 ||
21612
+ this.menu.loading ||
21613
+ this.noMatchOption
21614
+ ) {
21615
+ if (this._userTyped) {
21616
+ if (!this.dropdownOpen) {
21617
+ this.showBib();
21618
+ }
21619
+ this._userTyped = false;
21620
+ }
21621
+ }
21622
+
21636
21623
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21637
21624
  this.activateFirstEnabledAvailableOption();
21638
21625
  }
21639
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21626
+ } else if (!this.dropdown.isBibFullscreen) {
21640
21627
  this.hideBib();
21641
21628
  }
21629
+
21630
+ if (this._programmaticFilterRefresh) {
21631
+ this._programmaticFilterRefresh = false;
21632
+ }
21642
21633
  }
21643
21634
 
21644
21635
  if (changedProperties.has('error')) {
@@ -21780,10 +21771,10 @@ class AuroCombobox extends AuroElement {
21780
21771
  shape="${this.shape}"
21781
21772
  size="${this.size}">
21782
21773
  <${this.inputTag}
21783
- @input="${this.handleInputValueChange}"
21774
+ @input="${this.handleTriggerInputValueChange}"
21784
21775
  appearance="${this.onDark ? 'inverse' : this.appearance}"
21785
21776
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
21786
- .a11yExpanded="${this.triggerExpandedState}"
21777
+ .a11yExpanded="${this.dropdownOpen}"
21787
21778
  .a11yControls="${this.dropdownId}"
21788
21779
  .autocomplete="${this.autocomplete}"
21789
21780
  .inputmode="${this.inputmode}"
@@ -23388,14 +23379,10 @@ async function dynamicMenuExample() {
23388
23379
  // Helper function that generates HTML for menuoptions
23389
23380
  function generateMenuOptionHtml(menu, label, value) {
23390
23381
  const option = document.createElement('auro-menuoption');
23391
- const displayValue = document.createElement('div');
23392
- displayValue.setAttribute("slot", "displayValue");
23393
- displayValue.innerHTML = value;
23394
23382
 
23395
23383
  option.value = value;
23396
23384
  option.innerHTML = label;
23397
23385
  menu.appendChild(option);
23398
- option.appendChild(displayValue);
23399
23386
  }
23400
23387
 
23401
23388
  // Main javascript that runs all JS to create example