@aurodesignsystem-dev/auro-formkit 0.0.0-pr1507.0 → 0.0.0-pr1508.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/components/bibtemplate/dist/index.js +1 -1
  2. package/components/bibtemplate/dist/registered.js +1 -1
  3. package/components/checkbox/demo/customize.html +1 -2
  4. package/components/checkbox/demo/customize.min.js +8 -6
  5. package/components/checkbox/demo/getting-started.min.js +8 -6
  6. package/components/checkbox/demo/index.min.js +8 -6
  7. package/components/checkbox/dist/index.js +8 -6
  8. package/components/checkbox/dist/registered.js +8 -6
  9. package/components/combobox/demo/customize.html +1 -2
  10. package/components/combobox/demo/customize.md +105 -137
  11. package/components/combobox/demo/customize.min.js +506 -568
  12. package/components/combobox/demo/getting-started.min.js +506 -564
  13. package/components/combobox/demo/index.min.js +506 -564
  14. package/components/combobox/demo/keyboard-behavior.md +2 -142
  15. package/components/combobox/demo/why-combobox.md +1 -1
  16. package/components/combobox/dist/auro-combobox.d.ts +30 -14
  17. package/components/combobox/dist/index.js +505 -563
  18. package/components/combobox/dist/registered.js +505 -563
  19. package/components/counter/demo/customize.min.js +26 -8
  20. package/components/counter/demo/index.min.js +26 -8
  21. package/components/counter/dist/index.js +26 -8
  22. package/components/counter/dist/registered.js +26 -8
  23. package/components/datepicker/demo/accessibility.md +2 -1
  24. package/components/datepicker/demo/api.md +65 -62
  25. package/components/datepicker/demo/customize.md +150 -39
  26. package/components/datepicker/demo/customize.min.js +988 -500
  27. package/components/datepicker/demo/index.min.js +988 -482
  28. package/components/datepicker/dist/{src/auro-calendar-cell.d.ts → auro-calendar-cell.d.ts} +20 -3
  29. package/components/datepicker/dist/{src/auro-calendar.d.ts → auro-calendar.d.ts} +121 -7
  30. package/components/datepicker/dist/{src/auro-datepicker.d.ts → auro-datepicker.d.ts} +80 -5
  31. package/components/datepicker/dist/blackoutUtils.d.ts +43 -0
  32. package/components/datepicker/dist/index.js +988 -482
  33. package/components/datepicker/dist/registered.js +988 -482
  34. package/components/dropdown/demo/customize.min.js +10 -1
  35. package/components/dropdown/demo/getting-started.min.js +10 -1
  36. package/components/dropdown/demo/index.min.js +10 -1
  37. package/components/dropdown/dist/auro-dropdown.d.ts +1 -0
  38. package/components/dropdown/dist/index.js +10 -1
  39. package/components/dropdown/dist/registered.js +10 -1
  40. package/components/form/demo/customize.html +6 -6
  41. package/components/form/demo/customize.md +203 -51
  42. package/components/form/demo/customize.min.js +2357 -1419
  43. package/components/form/demo/getting-started.min.js +2285 -1419
  44. package/components/form/demo/index.min.js +2285 -1419
  45. package/components/form/demo/registerDemoDeps.min.js +1922 -1382
  46. package/components/form/dist/auro-form.d.ts +122 -4
  47. package/components/form/dist/index.js +363 -37
  48. package/components/form/dist/registered.js +363 -37
  49. package/components/input/demo/api.md +5 -5
  50. package/components/input/demo/customize.html +1 -2
  51. package/components/input/demo/customize.md +62 -61
  52. package/components/input/demo/customize.min.js +177 -241
  53. package/components/input/demo/getting-started.min.js +177 -241
  54. package/components/input/demo/index.min.js +177 -241
  55. package/components/input/dist/base-input.d.ts +34 -93
  56. package/components/input/dist/index.js +190 -242
  57. package/components/input/dist/registered.js +177 -241
  58. package/components/radio/demo/customize.min.js +8 -6
  59. package/components/radio/demo/getting-started.min.js +8 -6
  60. package/components/radio/demo/index.min.js +8 -6
  61. package/components/radio/dist/index.js +8 -6
  62. package/components/radio/dist/registered.js +8 -6
  63. package/components/select/demo/customize.html +1 -2
  64. package/components/select/demo/customize.md +86 -0
  65. package/components/select/demo/customize.min.js +202 -68
  66. package/components/select/demo/getting-started.min.js +204 -115
  67. package/components/select/demo/index.min.js +202 -68
  68. package/components/select/demo/keyboard-behavior.md +18 -4
  69. package/components/select/dist/auro-select.d.ts +20 -8
  70. package/components/select/dist/index.js +202 -68
  71. package/components/select/dist/registered.js +202 -68
  72. package/custom-elements.json +1035 -604
  73. package/package.json +6 -2
  74. package/components/checkbox/demo/customize.js +0 -22
  75. package/components/checkbox/demo/demo-support.js +0 -1
  76. package/components/checkbox/demo/getting-started.js +0 -22
  77. package/components/checkbox/demo/index.js +0 -8
  78. package/components/checkbox/demo/styles.scss +0 -1
  79. package/components/combobox/demo/customize.js +0 -24
  80. package/components/combobox/demo/demo-support.js +0 -1
  81. package/components/combobox/demo/getting-started.js +0 -21
  82. package/components/combobox/demo/index.js +0 -23
  83. package/components/combobox/demo/styles.scss +0 -1
  84. package/components/counter/demo/customize.js +0 -21
  85. package/components/counter/demo/demo-support.js +0 -1
  86. package/components/counter/demo/index.js +0 -21
  87. package/components/counter/demo/styles.scss +0 -1
  88. package/components/datepicker/demo/customize.js +0 -19
  89. package/components/datepicker/demo/demo-support.js +0 -1
  90. package/components/datepicker/demo/index.js +0 -23
  91. package/components/datepicker/demo/styles.scss +0 -1
  92. package/components/dropdown/demo/customize.js +0 -9
  93. package/components/dropdown/demo/demo-support.js +0 -1
  94. package/components/dropdown/demo/getting-started.js +0 -9
  95. package/components/dropdown/demo/index.js +0 -16
  96. package/components/dropdown/demo/styles.scss +0 -1
  97. package/components/form/demo/customize.js +0 -9
  98. package/components/form/demo/demo-support.js +0 -1
  99. package/components/form/demo/getting-started.js +0 -9
  100. package/components/form/demo/index.js +0 -5
  101. package/components/form/demo/registerDemoDeps.js +0 -23
  102. package/components/form/demo/styles.scss +0 -1
  103. package/components/input/demo/customize.js +0 -25
  104. package/components/input/demo/demo-support.js +0 -1
  105. package/components/input/demo/getting-started.js +0 -8
  106. package/components/input/demo/index.js +0 -8
  107. package/components/input/demo/styles.css +0 -974
  108. package/components/input/demo/styles.scss +0 -1
  109. package/components/menu/demo/demo-support.js +0 -1
  110. package/components/menu/demo/index.js +0 -25
  111. package/components/menu/demo/styles.scss +0 -1
  112. package/components/radio/demo/customize.js +0 -22
  113. package/components/radio/demo/demo-support.js +0 -1
  114. package/components/radio/demo/getting-started.js +0 -24
  115. package/components/radio/demo/index.js +0 -8
  116. package/components/radio/demo/styles.scss +0 -1
  117. package/components/select/demo/customize.js +0 -11
  118. package/components/select/demo/demo-support.js +0 -1
  119. package/components/select/demo/getting-started.js +0 -26
  120. package/components/select/demo/index.js +0 -11
  121. package/components/select/demo/styles.scss +0 -1
  122. /package/components/datepicker/dist/{src/auro-calendar-month.d.ts → auro-calendar-month.d.ts} +0 -0
  123. /package/components/datepicker/dist/{src/buttonVersion.d.ts → buttonVersion.d.ts} +0 -0
  124. /package/components/datepicker/dist/{src/datepickerKeyboardStrategy.d.ts → datepickerKeyboardStrategy.d.ts} +0 -0
  125. /package/components/datepicker/dist/{src/iconVersion.d.ts → iconVersion.d.ts} +0 -0
  126. /package/components/datepicker/dist/{src/index.d.ts → index.d.ts} +0 -0
  127. /package/components/datepicker/dist/{src/popoverVersion.d.ts → popoverVersion.d.ts} +0 -0
  128. /package/components/datepicker/dist/{src/styles → styles}/classic/color-css.d.ts +0 -0
  129. /package/components/datepicker/dist/{src/styles → styles}/classic/style-css.d.ts +0 -0
  130. /package/components/datepicker/dist/{src/styles → styles}/color-calendar-css.d.ts +0 -0
  131. /package/components/datepicker/dist/{src/styles → styles}/color-cell-css.d.ts +0 -0
  132. /package/components/datepicker/dist/{src/styles → styles}/color-css.d.ts +0 -0
  133. /package/components/datepicker/dist/{src/styles → styles}/color-month-css.d.ts +0 -0
  134. /package/components/datepicker/dist/{src/styles → styles}/shapeSize-css.d.ts +0 -0
  135. /package/components/datepicker/dist/{src/styles → styles}/snowflake/color-css.d.ts +0 -0
  136. /package/components/datepicker/dist/{src/styles → styles}/snowflake/style-css.d.ts +0 -0
  137. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-cell-css.d.ts +0 -0
  138. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-css.d.ts +0 -0
  139. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-month-css.d.ts +0 -0
  140. /package/components/datepicker/dist/{src/styles → styles}/style-css.d.ts +0 -0
  141. /package/components/datepicker/dist/{src/styles → styles}/tokens-css.d.ts +0 -0
  142. /package/components/datepicker/dist/{src/utilities.d.ts → utilities.d.ts} +0 -0
  143. /package/components/datepicker/dist/{src/utilitiesCalendar.d.ts → utilitiesCalendar.d.ts} +0 -0
  144. /package/components/datepicker/dist/{src/utilitiesCalendarRender.d.ts → utilitiesCalendarRender.d.ts} +0 -0
  145. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/day.d.ts +0 -0
  146. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-calendar.d.ts +0 -0
  147. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-cell.d.ts +0 -0
  148. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker.d.ts +0 -0
@@ -448,15 +448,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
448
448
  );
449
449
  }
450
450
 
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) {
451
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
452
+
453
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
454
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
455
+ // field (datepicker is the intended consumer — start/end are independently required).
456
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
457
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
454
458
  hasValue = false;
455
459
  }
456
460
  }
457
461
 
458
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
459
-
460
462
  if (isCombobox) {
461
463
 
462
464
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -767,6 +769,8 @@ function navigateArrow(component, direction, options = {}) {
767
769
  }
768
770
  }
769
771
 
772
+ /* eslint-disable no-underscore-dangle */
773
+
770
774
  /**
771
775
  * Returns the clear button element from the active input's shadow
772
776
  * DOM, if available.
@@ -802,11 +806,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
802
806
  * @param {Object} menu - The menu component.
803
807
  */
804
808
  function reconcileMenuIndex(menu) {
805
- // eslint-disable-next-line no-underscore-dangle
806
809
  if (menu._index < 0 && menu.optionActive && menu.items) {
807
810
  const idx = menu.items.indexOf(menu.optionActive);
808
811
  if (idx >= 0) {
809
- // eslint-disable-next-line no-underscore-dangle
810
812
  menu._index = idx;
811
813
  }
812
814
  }
@@ -825,11 +827,7 @@ const comboboxKeyboardStrategy = {
825
827
 
826
828
  // navigate if bib is open otherwise open it
827
829
  if (component.dropdown.isPopoverVisible) {
828
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
829
- component.activateLastEnabledAvailableOption();
830
- } else {
831
- navigateArrow(component, 'down');
832
- }
830
+ navigateArrow(component, 'down');
833
831
  } else {
834
832
  component.showBib();
835
833
  }
@@ -848,11 +846,7 @@ const comboboxKeyboardStrategy = {
848
846
 
849
847
  // navigate if bib is open otherwise open it
850
848
  if (component.dropdown.isPopoverVisible) {
851
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
852
- component.activateFirstEnabledAvailableOption();
853
- } else {
854
- navigateArrow(component, 'up');
855
- }
849
+ navigateArrow(component, 'up');
856
850
  } else {
857
851
  component.showBib();
858
852
  }
@@ -868,13 +862,18 @@ const comboboxKeyboardStrategy = {
868
862
  },
869
863
 
870
864
  Enter(component, evt, ctx) {
865
+ // Forms should not submit on Enter from a combobox, regardless of which
866
+ // child element (input, clear button, menu) is focused.
867
+ evt.stopPropagation();
868
+
871
869
  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) {
870
+ // Let the browser dispatch Enter to the focused clear button so its
871
+ // built-in activation fires and clears the selection. Do NOT call
872
+ // preventDefault — that would block the activation.
873
+ return;
874
+ }
875
+
876
+ if (ctx.isExpanded && component.menu.optionActive) {
878
877
  reconcileMenuIndex(component.menu);
879
878
  component.menu.makeSelection();
880
879
 
@@ -883,14 +882,8 @@ const comboboxKeyboardStrategy = {
883
882
  }
884
883
 
885
884
  evt.preventDefault();
886
- evt.stopPropagation();
887
885
  } 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
886
  evt.preventDefault();
893
- evt.stopPropagation();
894
887
  component.showBib();
895
888
  }
896
889
  },
@@ -935,7 +928,7 @@ const comboboxKeyboardStrategy = {
935
928
  component.setClearBtnFocus();
936
929
  }
937
930
  }
938
- },
931
+ }
939
932
  };
940
933
 
941
934
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
@@ -4806,7 +4799,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4806
4799
  }
4807
4800
  };
4808
4801
 
4809
- var formkitVersion$2 = '202606192121';
4802
+ var formkitVersion$2 = '202607010100';
4810
4803
 
4811
4804
  let AuroElement$2 = class AuroElement extends LitElement {
4812
4805
  static get properties() {
@@ -5757,6 +5750,14 @@ class AuroDropdown extends AuroElement$2 {
5757
5750
  };
5758
5751
  this.addEventListener('keydown', this._bibTabHandler);
5759
5752
 
5753
+ // Suppress AuroFloatingUI's auto-hide-on-focus-loss while the
5754
+ // desktopModal trap owns focus management. Without this, the very
5755
+ // first focus move into the bib (from the RAF below) triggers
5756
+ // handleFocusLoss → hideBib, tearing down the trap before the
5757
+ // user can press Tab.
5758
+ this._priorNoHideOnFocusLoss = this.noHideOnThisFocusLoss;
5759
+ this.noHideOnThisFocusLoss = true;
5760
+
5760
5761
  // Move initial focus into the bib content, matching FocusTrap behavior
5761
5762
  requestAnimationFrame(() => {
5762
5763
  const focusables = getFocusableElements(this.bibContent);
@@ -6070,6 +6071,7 @@ class AuroDropdown extends AuroElement$2 {
6070
6071
  role="${ifDefined(this.triggerContentFocusable ? undefined : this.a11yRole)}"
6071
6072
  aria-expanded="${ifDefined(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
6072
6073
  aria-controls="${ifDefined(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.dropdownId)}"
6074
+ aria-haspopup="${ifDefined(this.a11yRole === 'combobox' && !this.triggerContentFocusable ? 'listbox' : undefined)}"
6073
6075
  aria-labelledby="${ifDefined(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
6074
6076
  aria-disabled="${ifDefined(this.disabled ? 'true' : undefined)}"
6075
6077
  @focusin="${this.handleFocusin}"
@@ -10572,15 +10574,17 @@ class AuroFormValidation {
10572
10574
  );
10573
10575
  }
10574
10576
 
10575
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
10576
- if (this.auroInputElements?.length === 2) {
10577
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
10577
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10578
+
10579
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
10580
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
10581
+ // field (datepicker is the intended consumer — start/end are independently required).
10582
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
10583
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
10578
10584
  hasValue = false;
10579
10585
  }
10580
10586
  }
10581
10587
 
10582
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10583
-
10584
10588
  if (isCombobox) {
10585
10589
 
10586
10590
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -16516,10 +16520,18 @@ class AuroInputUtilities {
16516
16520
  * @returns {string | undefined}
16517
16521
  */
16518
16522
  toFormattedValue(valueObject, format) {
16523
+ // Exposed publicly via `AuroInputUtil.toFormattedValue`, so a missing
16524
+ // `format` argument has to fail gracefully rather than throw on
16525
+ // `format.toLowerCase()`. Treat any falsy value (undefined, null, '')
16526
+ // as "no format → no display value".
16527
+ if (!format) {
16528
+ return undefined;
16529
+ }
16519
16530
  const normalizedFormat = format.toLowerCase();
16520
16531
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16521
16532
 
16522
16533
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16534
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16523
16535
  return undefined;
16524
16536
  }
16525
16537
 
@@ -16666,79 +16678,19 @@ class BaseInput extends AuroElement$1 {
16666
16678
  constructor() {
16667
16679
  super();
16668
16680
 
16669
- this.appearance = "default";
16670
- this.disabled = false;
16671
- this.layout = 'classic';
16672
- this.locale = 'en-US';
16673
- this.max = undefined;
16674
- this._maxObject = undefined;
16675
- this.maxLength = undefined;
16676
- this.min = undefined;
16677
- this._minObject = undefined;
16678
- this.minLength = undefined;
16679
- this.required = false;
16680
- this.onDark = false;
16681
- this.setCustomValidityForType = undefined;
16682
- this.size = 'lg';
16683
- this.shape = 'classic';
16684
- this.value = undefined;
16685
- this._valueObject = undefined;
16686
-
16687
- this._initializePrivateDefaults();
16688
- }
16689
-
16690
- /**
16691
- * Internal Defaults.
16692
- * @private
16693
- * @returns {void}
16694
- */
16695
- _initializePrivateDefaults() {
16681
+ // Single-source initialization. Alphabetized so duplicate or stale
16682
+ // defaults are immediately obvious on a diff. Every field is assigned
16683
+ // exactly once here (previously the constructor + the old
16684
+ // `_initializePrivateDefaults` overlapped — both wrote ~14 of the same
16685
+ // fields and double-allocated `util` and `validation`, discarding the
16686
+ // first instance). `validation` is now allocated exactly once; `util`
16687
+ // is seeded here with an en-US default and then rebuilt in
16688
+ // `connectedCallback` once the consumer-resolved locale is available,
16689
+ // so a parent (datepicker/combobox) calling `validate()` synchronously
16690
+ // during its own update cycle sees a populated util instance.
16696
16691
  this.activeLabel = false;
16697
- this.appearance = "default";
16698
- this.icon = false;
16699
- this.disabled = false;
16700
- this.dvInputOnly = false;
16701
- this.hideLabelVisually = false;
16702
- this.max = undefined;
16703
- this.maxLength = undefined;
16704
- this.min = undefined;
16705
- this.minLength = undefined;
16706
- this.noValidate = false;
16707
- this.onDark = false;
16708
- this.required = false;
16709
- this.setCustomValidityForType = undefined;
16710
-
16711
- // Used for storing raw values returned from input mask.
16712
- this._rawMaskValue = undefined;
16713
-
16714
- /**
16715
- * @private
16716
- */
16717
- this.layout = 'classic';
16718
-
16719
- /**
16720
- * @private
16721
- */
16722
- this.shape = 'classic';
16723
-
16724
- /**
16725
- * @private
16726
- */
16727
- this.size = 'lg';
16728
-
16729
- this.touched = false;
16730
- this.util = new AuroInputUtilities({
16731
- locale: "en-US",
16732
- format: this.format
16733
- });
16734
- this.validation = new AuroFormValidation();
16735
- this.inputIconName = undefined;
16736
- this.showPassword = false;
16737
- this.validationCCLength = undefined;
16738
- this.hasValue = false;
16739
- this.label = 'Input label is undefined';
16740
-
16741
16692
 
16693
+ /** @private */
16742
16694
  this.allowedInputTypes = [
16743
16695
  "text",
16744
16696
  "number",
@@ -16747,7 +16699,9 @@ class BaseInput extends AuroElement$1 {
16747
16699
  "credit-card",
16748
16700
  "tel"
16749
16701
  ];
16750
- this.icon = false;
16702
+ this.appearance = "default";
16703
+
16704
+ /** @private */
16751
16705
  this.dateFormatMap = {
16752
16706
  'mm/dd/yyyy': 'dateMMDDYYYY',
16753
16707
  'dd/mm/yyyy': 'dateDDMMYYYY',
@@ -16764,27 +16718,62 @@ class BaseInput extends AuroElement$1 {
16764
16718
  'dd/mm': 'dateDDMM',
16765
16719
  'mm/dd': 'dateMMDD'
16766
16720
  };
16721
+ this.disabled = false;
16722
+
16723
+ /** @private */
16767
16724
  this.domHandler = new DomHandler();
16768
16725
  this.dvInputOnly = false;
16769
16726
  this.hasValue = false;
16727
+ this.hideLabelVisually = false;
16728
+ this.icon = false;
16729
+
16730
+ /** @private */
16770
16731
  this.inputIconName = undefined;
16732
+
16733
+ /** @private */
16771
16734
  this.label = 'Input label is undefined';
16735
+ this.layout = 'classic';
16736
+ this.locale = 'en-US';
16737
+ this.max = undefined;
16738
+ this.maxLength = undefined;
16739
+ this.min = undefined;
16740
+ this.minLength = undefined;
16772
16741
  this.noValidate = false;
16773
- this._rawMaskValue = undefined; // Used for storing raw values returned from input mask.
16742
+ this.onDark = false;
16743
+ // Raw values returned from the input mask before model normalization.
16744
+ this._rawMaskValue = undefined;
16745
+ this.required = false;
16746
+ this.setCustomValidityForType = undefined;
16747
+ // Credit Card is intentionally excluded — its mask manages the cursor
16748
+ // itself, and listing it here caused cursor placement issues in Safari.
16749
+ /** @private */
16774
16750
  this.setSelectionInputTypes = [
16775
16751
  "text",
16776
16752
  "password",
16777
16753
  "email"
16778
- ]; // Credit Card is not included as this caused cursor placement issues in Safari.
16754
+ ];
16755
+ this.shape = 'classic';
16756
+
16757
+ /** @private */
16779
16758
  this.showPassword = false;
16759
+ this.size = 'lg';
16780
16760
  this.touched = false;
16761
+
16762
+ /** @private */
16781
16763
  this.uniqueId = new UniqueId().create();
16764
+
16765
+ /** @private */
16782
16766
  this.util = new AuroInputUtilities({
16783
16767
  locale: this.locale,
16784
16768
  format: this.format
16785
16769
  });
16770
+
16771
+ /** @private */
16786
16772
  this.validation = new AuroFormValidation();
16773
+
16774
+ /** @private */
16787
16775
  this.validationCCLength = undefined;
16776
+ this.value = undefined;
16788
16777
  }
16789
16778
 
16790
16779
  // function to define props used within the scope of this component
@@ -17222,7 +17211,7 @@ class BaseInput extends AuroElement$1 {
17222
17211
  * @returns {Date|undefined}
17223
17212
  */
17224
17213
  get valueObject() {
17225
- return this._valueObject || this._computeDateObjectFallback(this.value);
17214
+ return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
17226
17215
  }
17227
17216
 
17228
17217
  /**
@@ -17230,7 +17219,7 @@ class BaseInput extends AuroElement$1 {
17230
17219
  * @returns {Date|undefined}
17231
17220
  */
17232
17221
  get minObject() {
17233
- return this._minObject || this._computeDateObjectFallback(this.min);
17222
+ return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
17234
17223
  }
17235
17224
 
17236
17225
  /**
@@ -17238,50 +17227,7 @@ class BaseInput extends AuroElement$1 {
17238
17227
  * @returns {Date|undefined}
17239
17228
  */
17240
17229
  get maxObject() {
17241
- return this._maxObject || this._computeDateObjectFallback(this.max);
17242
- }
17243
-
17244
- /**
17245
- * Parses a date string into a Date object when the corresponding `_*Object`
17246
- * field hasn't been synced yet by `updated()`. Returns undefined when the
17247
- * input type/format isn't a full date or the string is not a valid date.
17248
- *
17249
- * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17250
- * inside its own `updated()` before this input's `updated()` has run
17251
- * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17252
- * and range checks would otherwise silently no-op (flipping the result to
17253
- * `valid` or `patternMismatch`).
17254
- * @private
17255
- * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17256
- * @returns {Date|undefined}
17257
- */
17258
- _computeDateObjectFallback(dateStr) {
17259
- if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17260
- return undefined;
17261
- }
17262
- if (!dateFormatter.isValidDate(dateStr)) {
17263
- return undefined;
17264
- }
17265
- return dateFormatter.stringToDateInstance(dateStr);
17266
- }
17267
-
17268
- /**
17269
- * Internal setter for readonly date object properties.
17270
- * @private
17271
- * @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
17272
- * @param {Date|undefined} propertyValue - Value to assign.
17273
- * @returns {void}
17274
- */
17275
- setDateObjectProperty(propertyName, propertyValue) {
17276
- const internalPropertyName = `_${propertyName}`;
17277
- const previousValue = this[internalPropertyName];
17278
-
17279
- if (previousValue === propertyValue) {
17280
- return;
17281
- }
17282
-
17283
- this[internalPropertyName] = propertyValue;
17284
- this.requestUpdate(propertyName, previousValue);
17230
+ return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
17285
17231
  }
17286
17232
 
17287
17233
  connectedCallback() {
@@ -17310,7 +17256,6 @@ class BaseInput extends AuroElement$1 {
17310
17256
  format: this.format
17311
17257
  });
17312
17258
  this.configureDataForType();
17313
- this.syncDateValues();
17314
17259
  }
17315
17260
 
17316
17261
  disconnectedCallback() {
@@ -17330,12 +17275,17 @@ class BaseInput extends AuroElement$1 {
17330
17275
  this.wrapperElement.addEventListener('click', this.handleClick);
17331
17276
  }
17332
17277
 
17333
- // add attribute for query selectors when auro-input is registered under a custom name
17334
- if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17335
- this.setAttribute('auro-input', '');
17336
- }
17337
17278
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17338
17279
 
17280
+ // Normalize the format token to lowercase so case-mixed values supplied
17281
+ // via attribute (e.g. `format="MM/DD/YYYY"`) hit the `dateFormatMap`
17282
+ // lookup inside `setCustomHelpTextMessage`. Without this, an uppercase
17283
+ // format silently misses the map and leaves `setCustomValidityForType`
17284
+ // unset.
17285
+ if (this.format) {
17286
+ this.format = this.format.toLowerCase();
17287
+ }
17288
+
17339
17289
  // use validity message override if declared when initializing the component
17340
17290
  if (this.hasAttribute('setCustomValidity')) {
17341
17291
  this.ValidityMessageOverride = this.setCustomValidity;
@@ -17344,7 +17294,6 @@ class BaseInput extends AuroElement$1 {
17344
17294
  this.setCustomHelpTextMessage();
17345
17295
  this.configureAutoFormatting();
17346
17296
  this.configureDataForType();
17347
- this.syncDateValues();
17348
17297
  }
17349
17298
 
17350
17299
  /**
@@ -17420,6 +17369,13 @@ class BaseInput extends AuroElement$1 {
17420
17369
 
17421
17370
  if (typeToI18n.includes(this.type)) {
17422
17371
  this.setCustomValidityForType = i18n(this.lang, this.type);
17372
+ // COVERAGE: this `else if` branch is unreachable in WTR. connectedCallback
17373
+ // (L682) calls configureDataForType, which at L1266-1268 assigns
17374
+ // `this.format = this.util.getDateMaskFromLocale().toLowerCase()` for any
17375
+ // type=date input whose `format` attribute is unset. That runs before
17376
+ // firstUpdated invokes setCustomHelpTextMessage, so `!this.format` is
17377
+ // always false here. Retained as a defensive fallback; the whole function
17378
+ // is @deprecated per AB#1557296 and slated for removal.
17423
17379
  } else if (!this.format && this.type === 'date') {
17424
17380
  this.setCustomValidityForType = i18n(this.lang, 'dateMMDDYYYY');
17425
17381
  } else if (this.dateFormatMap[this.format]) {
@@ -17481,8 +17437,6 @@ class BaseInput extends AuroElement$1 {
17481
17437
  this.configureDataForType();
17482
17438
  }
17483
17439
 
17484
- this.syncDateValues(changedProperties);
17485
-
17486
17440
  if (changedProperties.has('value')) {
17487
17441
  if (this.value && this.value.length > 0) {
17488
17442
  this.hasValue = true;
@@ -17504,14 +17458,14 @@ class BaseInput extends AuroElement$1 {
17504
17458
 
17505
17459
  if (formattedValue !== this.inputElement.value) {
17506
17460
  this.skipNextProgrammaticInputEvent = true;
17507
- if (this.maskInstance && this.type === 'credit-card') {
17461
+ if (this.maskInstance && this.type !== 'date') {
17508
17462
  // Route through the mask so its _value and el.value stay in lock-step
17509
17463
  // (set value calls updateControl which writes el.value = displayValue).
17510
17464
  // Writing el.value directly leaves the mask thinking displayValue is
17511
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17512
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17513
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17514
- // and flip validity from patternMismatch to tooShort.
17465
+ // stale; _saveSelection on the next focus/click then warns. Date is
17466
+ // excluded because its formattedValue can be raw ISO when the calendar
17467
+ // is invalid, and re-masking through mm/dd/yyyy would truncate it and
17468
+ // flip validity from patternMismatch to tooShort.
17515
17469
  this.maskInstance.value = formattedValue || '';
17516
17470
  } else if (formattedValue) {
17517
17471
  this.inputElement.value = formattedValue;
@@ -17553,120 +17507,65 @@ class BaseInput extends AuroElement$1 {
17553
17507
  }));
17554
17508
  }
17555
17509
 
17556
-
17557
- /**
17558
- * Synchronizes the ISO string values and Date object representations for date-related properties.
17559
- * This keeps the model and display values aligned when either side changes.
17560
- *
17561
- * When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
17562
- * Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
17563
- * represents a full year/month/day date format.
17564
- *
17565
- * @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
17566
- * @returns {void}
17567
- * @private
17568
- */
17569
- syncDateValues(changedProperties = undefined) {
17570
- if (!this.util.isFullDateFormat(this.type, this.format)) {
17571
- return;
17572
- }
17573
-
17574
- this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
17575
- this.syncSingleDateValue(changedProperties, 'minObject', 'min');
17576
- this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
17577
- }
17578
-
17579
- /**
17580
- * Synchronizes one date object/string property pair.
17581
- * @private
17582
- * @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
17583
- * @param {string} objectProperty - Date object property name.
17584
- * @param {string} valueProperty - ISO string property name.
17585
- * @returns {void}
17586
- */
17587
- syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
17588
- const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
17589
-
17590
- // objectProperty wins over valueProperty when both changed
17591
- if (objectPropertyChanged && this[objectProperty]) {
17592
- this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
17593
- return;
17594
- }
17595
-
17596
- const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
17597
- if (!valuePropertyChanged) {
17598
- return;
17599
- }
17600
-
17601
- // 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)
17602
- if (
17603
- changedProperties &&
17604
- valueProperty === 'value' &&
17605
- changedProperties.get('value') === undefined &&
17606
- this[objectProperty] instanceof Date &&
17607
- this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
17608
- ) {
17609
- return;
17610
- }
17611
-
17612
- if (dateFormatter.isValidDate(this[valueProperty])) {
17613
- this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
17614
- } else {
17615
- this.setDateObjectProperty(objectProperty, undefined);
17616
- }
17617
- }
17618
-
17619
17510
  /**
17620
17511
  * Sets up IMasks and logic based on auto-formatting requirements.
17621
17512
  * @private
17622
17513
  * @returns {void}
17623
17514
  */
17624
17515
  configureAutoFormatting() {
17625
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17626
- // by _configuringMask above) could otherwise trigger handleInput
17627
- // processCreditCard configureAutoFormatting before the outer call's
17628
- // set value has finished its alignCursor pass.
17516
+ // _configuringMask gates two things: external re-entry into this method
17517
+ // while setup is mid-flight (from property changes that call back here),
17518
+ // and the accept/complete listeners below — both need to ignore the mask
17519
+ // events fired by our own value-restore step.
17629
17520
  if (this._configuringMask) return;
17630
17521
  this._configuringMask = true;
17631
17522
  try {
17523
+ // Destroy any prior mask so IMask can attach fresh under the new format.
17524
+ // Null the reference too — if the new maskOptions.mask is falsy (e.g.
17525
+ // type switched from credit-card to text) the IMask() reassignment
17526
+ // below is skipped, and downstream writes at line ~823 would otherwise
17527
+ // route through the destroyed instance.
17632
17528
  if (this.maskInstance) {
17633
17529
  this.maskInstance.destroy();
17530
+ this.maskInstance = null;
17634
17531
  }
17635
17532
 
17636
- // Pass new format to util
17637
17533
  this.util.updateFormat(this.format);
17638
17534
 
17639
17535
  const maskOptions = this.util.getMaskOptions(this.type, this.format);
17640
17536
 
17641
17537
  if (this.inputElement && maskOptions.mask) {
17642
-
17643
- // Stash and clear any existing value before IMask init.
17644
- // IMask's constructor processes the current input value which requires
17645
- // selection state clearing first avoids that scenario entirely.
17646
- // When the format changes (e.g. locale switch) and we have a valid ISO
17647
- // model value, compute the display string for the NEW format instead of
17648
- // re-using the old display string, which may be invalid in the new mask.
17538
+ // Capture the current display so it can be re-applied after IMask
17539
+ // attaches. The restore at the bottom goes through maskInstance.value
17540
+ // (not inputElement.value directly) so the mask's internal state and
17541
+ // the input's displayed text stay in lock-step.
17649
17542
  let existingValue = this.inputElement.value;
17543
+
17544
+ // Format-change case (e.g. locale switch): existingValue is the OLD
17545
+ // mask's display string and may not parse under the new mask. When
17546
+ // we have a valid date model, rebuild the display from valueObject
17547
+ // (the canonical source) using the new mask's format function.
17650
17548
  if (
17651
17549
  this.util.isFullDateFormat(this.type, this.format) &&
17652
17550
  this.value &&
17653
- dateFormatter.isValidDate(this.value) &&
17654
- this.valueObject instanceof Date &&
17655
- !Number.isNaN(this.valueObject.getTime()) &&
17551
+ this.valueObject &&
17656
17552
  typeof maskOptions.format === 'function'
17657
17553
  ) {
17658
17554
  existingValue = maskOptions.format(this.valueObject);
17659
17555
  }
17660
17556
 
17557
+ // Clear before IMask attaches so the constructor seeds an empty
17558
+ // internal value. Otherwise IMask reads the stale unmasked string
17559
+ // and emits a spurious 'accept' before the restore below runs.
17661
17560
  this.skipNextProgrammaticInputEvent = true;
17662
17561
  this.inputElement.value = '';
17663
17562
 
17664
17563
  this.maskInstance = IMask(this.inputElement, maskOptions);
17665
17564
 
17565
+ // Mask fires 'accept' on every value change, including the restore
17566
+ // step below. Skip events fired during configureAutoFormatting so
17567
+ // we don't overwrite a value the parent just pushed.
17666
17568
  this.maskInstance.on('accept', () => {
17667
- // Suppress propagation during configureAutoFormatting's own value-restoration
17668
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17669
- // and we don't want to overwrite a value the parent just pushed.
17670
17569
  if (this._configuringMask) return;
17671
17570
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17672
17571
  if (this.type === "date") {
@@ -17674,6 +17573,8 @@ class BaseInput extends AuroElement$1 {
17674
17573
  }
17675
17574
  });
17676
17575
 
17576
+ // Mask fires 'complete' on the restore step below for any value that
17577
+ // happens to be a complete match. Same setup-suppression as 'accept'.
17677
17578
  this.maskInstance.on('complete', () => {
17678
17579
  if (this._configuringMask) return;
17679
17580
  this.value = this.util.toModelValue(this.maskInstance.value, this.format);
@@ -17682,7 +17583,9 @@ class BaseInput extends AuroElement$1 {
17682
17583
  }
17683
17584
  });
17684
17585
 
17685
- // Restore the stashed value through IMask so it's properly masked
17586
+ // Write existingValue through the mask (not the input directly) so
17587
+ // the mask reformats it under the new rules and keeps its internal
17588
+ // _value aligned with the input's displayed text.
17686
17589
  if (existingValue) {
17687
17590
  this.maskInstance.value = existingValue;
17688
17591
  }
@@ -17762,6 +17665,38 @@ class BaseInput extends AuroElement$1 {
17762
17665
  this.touched = true;
17763
17666
  this.validation.validate(this);
17764
17667
  }
17668
+
17669
+ // Prevents cursor jumping in Safari. Setting `this.value` triggers a Lit
17670
+ // update that can re-render the input and reset the native cursor; we
17671
+ // capture the caret position before that update commits and restore it
17672
+ // via `setSelectionRange` once the update has flushed. Gated on
17673
+ // `setSelectionInputTypes` so credit-card (and other masked types whose
17674
+ // formatter manages the cursor itself) doesn't get a competing write.
17675
+ // Capture the caret position INSIDE the gate — reading `selectionStart`
17676
+ // on input types that don't support text selection (number, email in
17677
+ // some browsers) throws InvalidStateError, which would crash all input
17678
+ // handling. Wrap the read in try/catch belt-and-suspenders even though
17679
+ // the gated types currently support it, since the list is a public
17680
+ // property a consumer could mutate.
17681
+ if (this.setSelectionInputTypes.includes(this.type)) {
17682
+ let selectionStart = null;
17683
+ try {
17684
+ ({ selectionStart } = this.inputElement);
17685
+ } catch (error) { // eslint-disable-line no-unused-vars
17686
+ return;
17687
+ }
17688
+ if (typeof selectionStart !== 'number') {
17689
+ return;
17690
+ }
17691
+ this.updateComplete.then(() => {
17692
+ try {
17693
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
17694
+ } catch (error) { // eslint-disable-line no-unused-vars
17695
+ // Some input types (number/email in certain UAs) throw on
17696
+ // setSelectionRange; swallow and let the native cursor stand.
17697
+ }
17698
+ });
17699
+ }
17765
17700
  }
17766
17701
 
17767
17702
  /**
@@ -17850,7 +17785,6 @@ class BaseInput extends AuroElement$1 {
17850
17785
  */
17851
17786
  reset() {
17852
17787
  this.value = undefined;
17853
- this.setDateObjectProperty('valueObject', undefined);
17854
17788
  this.validation.reset(this);
17855
17789
  }
17856
17790
 
@@ -17859,7 +17793,6 @@ class BaseInput extends AuroElement$1 {
17859
17793
  */
17860
17794
  clear() {
17861
17795
  this.value = undefined;
17862
- this.setDateObjectProperty('valueObject', undefined);
17863
17796
  }
17864
17797
 
17865
17798
  /**
@@ -17897,7 +17830,7 @@ class BaseInput extends AuroElement$1 {
17897
17830
  */
17898
17831
  get placeholderStr() {
17899
17832
  if (!this.placeholder && this.type === 'date') {
17900
- return this.format ? this.format.toUpperCase() : 'MM/DD/YYYY';
17833
+ return this.format.toUpperCase();
17901
17834
  }
17902
17835
  return this.placeholder || "";
17903
17836
  }
@@ -18388,7 +18321,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18388
18321
  }
18389
18322
  };
18390
18323
 
18391
- var formkitVersion$1 = '202606192121';
18324
+ var formkitVersion$1 = '202607010100';
18392
18325
 
18393
18326
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18394
18327
  // See LICENSE in the project root for license information.
@@ -18891,6 +18824,7 @@ class AuroInput extends BaseInput {
18891
18824
  <${this.buttonTag}
18892
18825
  @click="${this.handleClickShowPassword}"
18893
18826
  appearance="${this.onDark ? 'inverse' : this.appearance}"
18827
+ aria-pressed="${this.showPassword ? 'true' : 'false'}"
18894
18828
  class="notificationBtn passwordBtn"
18895
18829
  shape="circle"
18896
18830
  size="sm"
@@ -18964,25 +18898,29 @@ class AuroInput extends BaseInput {
18964
18898
  * @returns {html} - Returns HTML for the help text and error message.
18965
18899
  */
18966
18900
  renderHtmlHelpText() {
18901
+ // Single `<p>` with stable identity across validity transitions —
18902
+ // previously two distinct templates (valid vs invalid) caused Lit to
18903
+ // replace the node entirely on a flip, and VoiceOver wouldn't
18904
+ // re-announce because the live-region element it was watching had been
18905
+ // removed and a new one inserted. Keeping one node means the `role`,
18906
+ // `aria-live`, and text content all change in-place, which AT does
18907
+ // observe and announce.
18908
+ const isError = this.validity && this.validity !== 'valid';
18967
18909
  return html`
18968
- ${!this.validity || this.validity === undefined || this.validity === 'valid'
18969
- ? html`
18970
- <${this.helpTextTag}
18971
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18972
- <p id="${this.uniqueId}" part="helpText">
18973
- <slot name="helpText">${this.getHelpText()}</slot>
18974
- </p>
18975
- </${this.helpTextTag}>
18976
- `
18977
- : html`
18978
- <${this.helpTextTag} error
18979
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18980
- <p id="${this.uniqueId}" role="alert" aria-live="assertive" part="helpText">
18981
- ${this.errorMessage}
18982
- </p>
18983
- </${this.helpTextTag}>
18984
- `
18910
+ <${this.helpTextTag}
18911
+ appearance="${this.onDark ? 'inverse' : this.appearance}"
18912
+ ?error=${isError}>
18913
+ <p
18914
+ id="${this.uniqueId}"
18915
+ part="helpText"
18916
+ role="${ifDefined(isError ? 'alert' : undefined)}"
18917
+ aria-live="${ifDefined(isError ? 'assertive' : undefined)}">
18918
+ ${isError
18919
+ ? this.errorMessage
18920
+ : html`<slot name="helpText">${this.getHelpText()}</slot>`
18985
18921
  }
18922
+ </p>
18923
+ </${this.helpTextTag}>
18986
18924
  `;
18987
18925
  }
18988
18926
 
@@ -19114,7 +19052,7 @@ class AuroInput extends BaseInput {
19114
19052
 
19115
19053
  var colorCss$1 = css`#headerContainer{box-shadow:var(--ds-auro-dropdownbib-header-boxshadow-color)}#footerContainer{background-color:var(--ds-auro-dropdownbib-footer-container-color)}`;
19116
19054
 
19117
- var styleCss$2 = css`#bibTemplate{display:flex;overflow:hidden;height:100%;max-height:100%;flex-direction:column}#headerContainer{display:flex;flex-direction:column;padding:var(--ds-size-200, 1rem);padding-top:var(--ds-size-400, 2rem);padding-bottom:0}#headerContainer .titleRow{display:flex;flex-direction:row;justify-content:space-between;gap:var(--ds-size-100, 0.5rem)}#headerContainer .header{max-width:calc(100vw - var(--ds-size-900, 4.5rem));padding-top:var(--ds-size-200, 1rem)}#headerContainer .subheader{width:100%}#closeButton [auro-icon]{--ds-auro-icon-size: var(--ds-size-500, 2.5rem)}#bodyContainer{position:relative;overflow:auto;flex:1 1 100%}:host([isfullscreen]) #bibTemplate{max-height:100dvh}:host([isfullscreen]) #bodyContainer{margin-top:var(--ds-size-100, 0.5rem)}#footerContainer{position:absolute;right:0;bottom:0;left:0;padding:var(--ds-size-200, 1rem)}`;
19055
+ var styleCss$2 = css`#bibTemplate{display:flex;overflow:hidden;height:100%;max-height:100%;flex-direction:column}#headerContainer{display:flex;flex-direction:column;padding:var(--ds-size-200, 1rem);padding-top:var(--ds-size-400, 2rem);padding-bottom:0}#headerContainer .titleRow{display:flex;flex-direction:row;justify-content:space-between;gap:var(--ds-size-100, 0.5rem)}#headerContainer .header{max-width:calc(100vw - var(--ds-size-900, 4.5rem));padding-top:var(--ds-size-200, 1rem)}#headerContainer .subheader{width:100%}#closeButton [auro-icon]{--ds-auro-icon-size: var(--ds-size-500, 2.5rem)}#bodyContainer{position:relative;overflow:auto;flex:1}:host([isfullscreen]) #bibTemplate{max-height:100dvh}:host([isfullscreen]) #bodyContainer{margin-top:var(--ds-size-100, 0.5rem)}#footerContainer{padding:var(--ds-size-200, 1rem)}`;
19118
19056
 
19119
19057
  var tokenCss = css`:host{--ds-auro-dropdownbib-header-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-footer-container-color: var(--ds-basic-color-surface-default, #ffffff)}`;
19120
19058
 
@@ -19509,7 +19447,7 @@ class AuroBibtemplate extends LitElement {
19509
19447
  }
19510
19448
  }
19511
19449
 
19512
- var formkitVersion = '202606192121';
19450
+ var formkitVersion = '202607010100';
19513
19451
 
19514
19452
  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}`;
19515
19453
 
@@ -19877,12 +19815,23 @@ function getOptionLabel(option) {
19877
19815
  if (!option) {
19878
19816
  return '';
19879
19817
  }
19880
- const clone = option.cloneNode(true);
19881
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19882
- if (displayValueEl) {
19883
- displayValueEl.remove();
19818
+
19819
+ // Consumer-provided override: short-circuit the DOM walk entirely.
19820
+ if (option.dataset && option.dataset.label) {
19821
+ return option.dataset.label;
19822
+ }
19823
+
19824
+ // Walk direct children — the `slot` attribute only applies to direct children
19825
+ // of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
19826
+ // querySelector allocation that ran on every keystroke via syncValuesAndStates.
19827
+ let text = '';
19828
+ for (const node of option.childNodes) {
19829
+ const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
19830
+ if (!isDisplayValueSlot) {
19831
+ text += node.textContent || '';
19832
+ }
19884
19833
  }
19885
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19834
+ return text.replace(/\s+/gu, ' ').trim();
19886
19835
  }
19887
19836
 
19888
19837
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
@@ -19957,8 +19906,6 @@ class AuroCombobox extends AuroElement {
19957
19906
  this.availableOptions = [];
19958
19907
  this.dropdownId = undefined;
19959
19908
  this.dropdownOpen = false;
19960
- this.triggerExpandedState = false;
19961
- this._expandedTimeout = null;
19962
19909
  this._inFullscreenTransition = false;
19963
19910
  this.errorMessage = null;
19964
19911
  this.isHiddenWhileLoading = false;
@@ -19969,6 +19916,30 @@ class AuroCombobox extends AuroElement {
19969
19916
  this.touched = false;
19970
19917
  this.validation = new AuroFormValidation$1();
19971
19918
  this.validity = undefined;
19919
+ this._userTyped = false;
19920
+ // Tracks every setTimeout scheduled via _scheduleTimer so
19921
+ // disconnectedCallback can cancel them. Without this, a detached
19922
+ // combobox's pending timers still fire — most are no-ops, but
19923
+ // configureMenu's racing-condition retry would otherwise loop.
19924
+ this._pendingTimers = new Set();
19925
+ }
19926
+
19927
+ /**
19928
+ * setTimeout wrapper that records the timer id so disconnectedCallback
19929
+ * can cancel any outstanding callbacks. The id is removed from the set
19930
+ * once the callback fires so the set doesn't grow unbounded.
19931
+ * @param {Function} fn - Callback to run.
19932
+ * @param {number} ms - Delay in milliseconds.
19933
+ * @returns {number} The timer id.
19934
+ * @private
19935
+ */
19936
+ _scheduleTimer(fn, ms) {
19937
+ const id = setTimeout(() => {
19938
+ this._pendingTimers.delete(id);
19939
+ fn();
19940
+ }, ms);
19941
+ this._pendingTimers.add(id);
19942
+ return id;
19972
19943
  }
19973
19944
 
19974
19945
  // This function is to define props used within the scope of this component
@@ -20330,17 +20301,6 @@ class AuroCombobox extends AuroElement {
20330
20301
  attribute: false
20331
20302
  },
20332
20303
 
20333
- /**
20334
- * Deferred aria-expanded state for the trigger input.
20335
- * Delays the "true" transition so VoiceOver finishes its character echo
20336
- * before announcing "expanded".
20337
- * @private
20338
- */
20339
- triggerExpandedState: {
20340
- type: Boolean,
20341
- reflect: false,
20342
- attribute: false
20343
- },
20344
20304
  };
20345
20305
  }
20346
20306
 
@@ -20363,13 +20323,6 @@ class AuroCombobox extends AuroElement {
20363
20323
  return this.input.value;
20364
20324
  }
20365
20325
 
20366
- // /**
20367
- // * Sets the value of the input element within the combobox.
20368
- // */
20369
- // set inputValue(value) {
20370
- // this.input.value = value;
20371
- // }
20372
-
20373
20326
  /**
20374
20327
  * Checks if the element is valid.
20375
20328
  * @returns {boolean} - Returns true if the element is valid, false otherwise.
@@ -20496,7 +20449,7 @@ class AuroCombobox extends AuroElement {
20496
20449
  if (this.menu) {
20497
20450
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20498
20451
  }
20499
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20452
+ const label = getOptionLabel(this.menu.optionSelected);
20500
20453
  this.updateTriggerTextDisplay(label || this.value);
20501
20454
  }
20502
20455
 
@@ -20510,44 +20463,16 @@ class AuroCombobox extends AuroElement {
20510
20463
  // in suggestion mode, do not override input value if no selection has been made and the input currently has focus
20511
20464
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20512
20465
 
20513
- if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20514
- const nextValue = label || this.value;
20515
- // Only set the flag when there's an actual write to suppress —
20516
- // syncValuesAndStates re-enters here during typing when both inputs
20517
- // already match, and a no-op flag flip would make the bib branch's
20518
- // bail eat legitimate user-input events.
20519
- const triggerNeedsSync = this.input.value !== nextValue;
20520
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20521
- if (triggerNeedsSync || bibNeedsSync) {
20522
- this._syncingDisplayValue = true;
20523
- if (triggerNeedsSync) {
20524
- this.input.value = nextValue;
20525
- }
20526
- if (bibNeedsSync) {
20527
- this.inputInBib.value = nextValue;
20528
- }
20529
- const pending = [];
20530
- if (triggerNeedsSync) {
20531
- pending.push(this.input.updateComplete);
20532
- }
20533
- if (bibNeedsSync) {
20534
- pending.push(this.inputInBib.updateComplete);
20535
- }
20536
- Promise.all(pending).then(() => {
20537
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20538
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20539
- this.input.maskInstance.updateValue();
20540
- }
20541
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20542
- this.inputInBib.maskInstance.updateValue();
20543
- }
20544
- this._syncingDisplayValue = false;
20545
- });
20546
- }
20466
+ const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
20467
+ if (!suppressed) {
20468
+ this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20547
20469
  }
20548
20470
 
20549
- // update the displayValue in the trigger if displayValue slot content is present
20550
- const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]');
20471
+ // Replace any previously appended displayValue clone in the trigger.
20472
+ // :not(slot) excludes the template's <slot name="displayValue"
20473
+ // slot="displayValue"> forwarder, which also has slot="displayValue"
20474
+ // and would otherwise be matched first and removed.
20475
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
20551
20476
 
20552
20477
  if (displayValueInTrigger) {
20553
20478
  displayValueInTrigger.remove();
@@ -20571,6 +20496,53 @@ class AuroCombobox extends AuroElement {
20571
20496
  this.requestUpdate();
20572
20497
  }
20573
20498
 
20499
+ /**
20500
+ * Writes nextValue to the trigger input and the bib input when their current
20501
+ * value differs, then re-asserts imask after Lit's update flushes.
20502
+ * @param {string} nextValue - The value to write to both inputs.
20503
+ * @returns {Promise<void>} Resolves after both inputs flush and imask
20504
+ * re-asserts; resolves immediately when no sync is needed.
20505
+ * @private
20506
+ */
20507
+ async syncInputValuesAcrossTriggerAndBib(nextValue) {
20508
+ // Only set the flag when there's an actual write to suppress —
20509
+ // syncValuesAndStates re-enters here during typing when both inputs
20510
+ // already match, and a no-op flag flip would make the bib branch's
20511
+ // bail eat legitimate user-input events.
20512
+ const triggerNeedsSync = this.input.value !== nextValue;
20513
+ const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
20514
+ if (!triggerNeedsSync && !bibNeedsSync) {
20515
+ return;
20516
+ }
20517
+
20518
+ this._syncingDisplayValue = true;
20519
+
20520
+ const pending = [];
20521
+ if (triggerNeedsSync) {
20522
+ this.input.value = nextValue;
20523
+ pending.push(this.input.updateComplete);
20524
+ }
20525
+ if (bibNeedsSync) {
20526
+ this.inputInBib.value = nextValue;
20527
+ pending.push(this.inputInBib.updateComplete);
20528
+ }
20529
+ // finally — not a bare .then — so that an imask throw (see commit
20530
+ // d1857401c: imask can throw on credit-card format change) doesn't strand
20531
+ // _syncingDisplayValue=true and silently swallow every subsequent input.
20532
+ try {
20533
+ await Promise.all(pending);
20534
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
20535
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20536
+ this.input.maskInstance.updateValue();
20537
+ }
20538
+ if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20539
+ this.inputInBib.maskInstance.updateValue();
20540
+ }
20541
+ } finally {
20542
+ this._syncingDisplayValue = false;
20543
+ }
20544
+ }
20545
+
20574
20546
  /**
20575
20547
  * Processes hidden state of all menu options and determines if there are any available options not hidden.
20576
20548
  * @private
@@ -20579,6 +20551,13 @@ class AuroCombobox extends AuroElement {
20579
20551
  handleMenuOptions() {
20580
20552
  this.generateOptionsArray();
20581
20553
  this.availableOptions = [];
20554
+ // Single source of truth for the menu's filter/highlight token per call.
20555
+ // syncValuesAndStates re-writes the same value on exact-match keystrokes —
20556
+ // Lit's hasChanged makes that a no-op — and the prior duplicate set inside
20557
+ // handleTriggerInputValueChange is gone.
20558
+ if (this.menu) {
20559
+ this.menu.matchWord = normalizeFilterValue(this.input.value);
20560
+ }
20582
20561
  this.updateFilter();
20583
20562
 
20584
20563
  // Set aria-setsize/aria-posinset on each visible option so screen readers
@@ -20590,17 +20569,14 @@ class AuroCombobox extends AuroElement {
20590
20569
  option.setAttribute('aria-posinset', index + 1);
20591
20570
  });
20592
20571
 
20593
- if (this.value && this.input.value && !this.menu.value) {
20594
- if (this.behavior === 'suggestion' && this.menu.options && this.menu.options.some((opt) => opt.value === this.value)) {
20595
- this.setMenuValue(this.value);
20596
- }
20597
-
20572
+ if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
20573
+ this.setMenuValue(this.input.value);
20598
20574
  this.syncValuesAndStates();
20599
20575
  }
20600
20576
 
20601
- // Re-activate when optionActive is no longer visible, or when _index has
20602
- // been reset (e.g. by clearSelection in an async update) so that
20603
- // makeSelection() can find the correct option.
20577
+ // Re-activate when optionActive is not in the current scrollable viewport,
20578
+ // or when _index has been reset (e.g. by clearSelection in an async update)
20579
+ // so that makeSelection() can find the correct option.
20604
20580
  if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20605
20581
  this.activateFirstEnabledAvailableOption();
20606
20582
  }
@@ -20673,18 +20649,6 @@ class AuroCombobox extends AuroElement {
20673
20649
  this.dropdownOpen = ev.detail.expanded;
20674
20650
  this.updateMenuShapeSize();
20675
20651
 
20676
- // Defer aria-expanded "true" so VoiceOver finishes character echo
20677
- // before announcing "expanded". Set "false" immediately on close.
20678
- clearTimeout(this._expandedTimeout);
20679
- if (this.dropdownOpen) {
20680
- const expandedDelay = 150;
20681
- this._expandedTimeout = setTimeout(() => {
20682
- this.triggerExpandedState = true;
20683
- }, expandedDelay);
20684
- } else {
20685
- this.triggerExpandedState = false;
20686
- }
20687
-
20688
20652
  // Clear aria-activedescendant when dropdown closes
20689
20653
  if (!this.dropdownOpen && this.input) {
20690
20654
  this.input.setActiveDescendant(null);
@@ -20734,28 +20698,25 @@ class AuroCombobox extends AuroElement {
20734
20698
 
20735
20699
  guardTouchPassthrough(this.menu);
20736
20700
 
20737
- // The dialog's showModal() steals focus from the trigger.
20738
- // A single rAF is enough for the dialog DOM to be painted and
20739
- // focusable, then doubleRaf finalizes the transition.
20701
+ // showModal() takes focus away from the trigger. Early focus once
20702
+ // the dialog has painted; the shared doubleRaf below is the fallback
20703
+ // for when the dialog needs an extra frame and also clears the
20704
+ // validation guard.
20740
20705
  requestAnimationFrame(() => {
20741
20706
  this.setInputFocus();
20742
20707
  });
20743
-
20744
- doubleRaf(() => {
20745
- this.setInputFocus();
20746
- this._inFullscreenTransition = false;
20747
- });
20748
- } else {
20749
- // Desktop popover-open: restore the trigger caret to end-of-text.
20750
- // Clicking the trigger lands on auro-input's floating <label for="…">
20751
- // overlay; Chrome resets the native input's selection to [0, 0] on
20752
- // label-focus before any JS runs. setInputFocus()'s non-fullscreen
20753
- // branch parks the caret at end. doubleRaf lets the dropdown layout
20754
- // settle first, matching the fullscreen branch timing.
20755
- doubleRaf(() => {
20756
- this.setInputFocus();
20757
- });
20758
20708
  }
20709
+ // else (desktop popover-open): Chrome resets the trigger caret to
20710
+ // [0, 0] when its floating <label for="…"> overlay receives focus.
20711
+ // The shared doubleRaf below parks the caret back at end-of-text
20712
+ // after the dropdown layout settles.
20713
+
20714
+ doubleRaf(() => {
20715
+ this.setInputFocus();
20716
+ if (this._inFullscreenTransition) {
20717
+ this._inFullscreenTransition = false;
20718
+ }
20719
+ });
20759
20720
  }
20760
20721
  });
20761
20722
 
@@ -20792,7 +20753,7 @@ class AuroCombobox extends AuroElement {
20792
20753
  this.dropdown.trigger.inert = false;
20793
20754
  }
20794
20755
 
20795
- setTimeout(() => {
20756
+ this._scheduleTimer(() => {
20796
20757
  this.setInputFocus();
20797
20758
  }, 0);
20798
20759
  });
@@ -20860,24 +20821,7 @@ class AuroCombobox extends AuroElement {
20860
20821
  nativeInput.setSelectionRange(len, len);
20861
20822
  }
20862
20823
  } else {
20863
- // Safety net for the strategy-change setTimeout: a viewport-crossing
20864
- // fullscreen→floating switch can close the dialog and leave focus on
20865
- // body. Every other Branch 2 callsite already has focus on the input
20866
- // via the focusin → this.focus() redirect (L1448-1452), so this is a
20867
- // no-op in the common case.
20868
- if (!this.input.componentHasFocus) {
20869
- this.input.focus();
20870
- }
20871
-
20872
- // Park the trigger native input's caret at end-of-text every time the
20873
- // popover opens. Chrome focuses the native via the floating <label for="…">
20874
- // overlay on click and resets selection to [0, 0]; no JS fires between
20875
- // mousedown and selectionchange, so we restore the caret here.
20876
- const triggerNativeInput = this.input.inputElement;
20877
- if (triggerNativeInput && triggerNativeInput.value) {
20878
- const len = triggerNativeInput.value.length;
20879
- triggerNativeInput.setSelectionRange(len, len);
20880
- }
20824
+ this.input.focus();
20881
20825
  }
20882
20826
  }
20883
20827
 
@@ -20953,7 +20897,7 @@ class AuroCombobox extends AuroElement {
20953
20897
 
20954
20898
  // racing condition on custom-combobox with custom-menu
20955
20899
  if (!this.menu) {
20956
- setTimeout(() => {
20900
+ this._scheduleTimer(() => {
20957
20901
  this.configureMenu();
20958
20902
  }, 0);
20959
20903
  return;
@@ -20983,7 +20927,7 @@ class AuroCombobox extends AuroElement {
20983
20927
  if (this.menu.optionSelected) {
20984
20928
  const selected = this.menu.optionSelected;
20985
20929
 
20986
- if (!this.optionSelected || this.optionSelected !== selected) {
20930
+ if (this.optionSelected !== selected) {
20987
20931
  this.optionSelected = selected;
20988
20932
  }
20989
20933
 
@@ -20996,7 +20940,7 @@ class AuroCombobox extends AuroElement {
20996
20940
  }
20997
20941
 
20998
20942
  // Update display
20999
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
20943
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
21000
20944
 
21001
20945
  // Update match word for filtering
21002
20946
  const trimmedInput = normalizeFilterValue(this.input.value);
@@ -21008,17 +20952,24 @@ class AuroCombobox extends AuroElement {
21008
20952
  // Hide dropdown on selection (except during slot changes)
21009
20953
  if (evt.detail && evt.detail.source !== 'slotchange') {
21010
20954
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21011
- if (this.menu.value || this.behavior !== 'suggestion') {
21012
- this.hideBib();
20955
+ this.hideBib();
20956
+
20957
+ // Move focus to the clear button when the user makes a selection.
20958
+ if (!isEcho && this.menu.value !== undefined) {
20959
+ this.setClearBtnFocus();
21013
20960
  }
21014
20961
 
21015
20962
  // Announce the selection after the dropdown closes so it isn't
21016
20963
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
20964
+ // Skip when there's no selected value (e.g. menu.clearSelection() from
20965
+ // the unmatched-value path), otherwise VoiceOver reads "undefined".
21017
20966
  const selectedValue = this.menu.value;
21018
- const announcementDelay = 300;
21019
- setTimeout(() => {
21020
- announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21021
- }, announcementDelay);
20967
+ if (selectedValue) {
20968
+ const announcementDelay = 300;
20969
+ this._scheduleTimer(() => {
20970
+ announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
20971
+ }, announcementDelay);
20972
+ }
21022
20973
  }
21023
20974
 
21024
20975
  // Programmatic value syncs leave availableOptions stale because
@@ -21027,22 +20978,6 @@ class AuroCombobox extends AuroElement {
21027
20978
  // only — fresh user selections take the existing hideBib path.
21028
20979
  if (isEcho && this.menu.optionSelected) {
21029
20980
  this._programmaticFilterRefresh = true;
21030
- this.handleMenuOptions();
21031
- setTimeout(() => {
21032
- this._programmaticFilterRefresh = false;
21033
- }, 0);
21034
- }
21035
-
21036
- // base-input skips auto-validate when focus is inside its own shadow,
21037
- // which it is after setTriggerInputFocus — re-run so prior tooShort
21038
- // clears. processCreditCard reasserts errorMessage on every render.
21039
- if (!isEcho && this.menu.optionSelected && this.input.validate) {
21040
- this.input.updateComplete.then(() => {
21041
- this.input.validate(true);
21042
- if (this.input.validity === 'valid') {
21043
- this.input.errorMessage = '';
21044
- }
21045
- });
21046
20981
  }
21047
20982
  });
21048
20983
 
@@ -21055,6 +20990,14 @@ class AuroCombobox extends AuroElement {
21055
20990
  // stale option. Safe from re-entrancy because any resulting
21056
20991
  // input.value changes dispatch isProgrammatic events.
21057
20992
  this.menu.addEventListener('auroMenu-selectValueFailure', () => {
20993
+ // Announce the rejection BEFORE we clear `this.value` so the live
20994
+ // region carries the attempted value — without this the bib closes
20995
+ // silently and screen-reader users get no signal that their request
20996
+ // (e.g. a direct setMenuValue() call) was dropped.
20997
+ const attemptedValue = this.value;
20998
+ if (attemptedValue) {
20999
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
21000
+ }
21058
21001
  this.value = undefined;
21059
21002
  this.optionSelected = undefined;
21060
21003
  });
@@ -21066,10 +21009,13 @@ class AuroCombobox extends AuroElement {
21066
21009
  this.input.setActiveDescendant(this.optionActive);
21067
21010
  }
21068
21011
 
21069
- // Announce the active option for screen readers including position,
21070
- // since shadow DOM boundaries prevent native reading of
21071
- // aria-setsize/aria-posinset via aria-activedescendant.
21072
- if (this.optionActive) {
21012
+ // In fullscreen mode the menu sits inside a nested <dialog> shadow root,
21013
+ // and aria-activedescendant references across that boundary are lost —
21014
+ // VoiceOver/NVDA don't read the active option natively, so we mirror it
21015
+ // into the polite live region. In popover mode aria-activedescendant on
21016
+ // the trigger input is read natively; double-announcing would flood the
21017
+ // queue on arrow-key repeat.
21018
+ if (this.optionActive && this.dropdown.isBibFullscreen) {
21073
21019
  const optionText = this.optionActive.textContent.trim();
21074
21020
  const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
21075
21021
  const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
@@ -21102,7 +21048,12 @@ class AuroCombobox extends AuroElement {
21102
21048
  * Validate every time we remove focus from the combo box.
21103
21049
  */
21104
21050
  this.addEventListener('focusout', () => {
21105
- if (!this.componentHasFocus && !this._inFullscreenTransition) {
21051
+ // Skip while the dropdown is open — focus transits out briefly on
21052
+ // mousedown of a menu option (popover top-layer breaks :focus-within),
21053
+ // and validating against the pre-selection value flashes a stale error
21054
+ // between mousedown and mouseup. The next focusout fires after the
21055
+ // dropdown closes and validates against the post-selection value.
21056
+ if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
21106
21057
  this.validate();
21107
21058
  }
21108
21059
  });
@@ -21165,49 +21116,40 @@ class AuroCombobox extends AuroElement {
21165
21116
  if (this._syncingDisplayValue) {
21166
21117
  return;
21167
21118
  }
21168
- this._syncingBibValue = true;
21169
- this.input.value = this.inputInBib.value;
21170
- this.input.updateComplete.then(() => {
21171
- this._syncingBibValue = false;
21172
- });
21173
21119
 
21174
- // Run filtering inline — the re-entrant event won't reach this code.
21175
- this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21176
- this.optionActive = null;
21177
-
21178
- // In suggestion mode, keep the combobox value in sync with the typed
21179
- // text so that freeform values are captured (mirroring the non-fullscreen
21180
- // path). Clear the selection when the input is emptied.
21181
- if (this.behavior === 'suggestion') {
21182
- this.value = this.inputInBib.value || undefined;
21120
+ // Filtering runs via re-entrance: writing this.input.value below
21121
+ // dispatches an 'input' event that the trigger's listener routes to
21122
+ // handleTriggerInputValueChange, which refreshes the menu filter.
21123
+ if (this.input.value !== this.inputInBib.value) {
21124
+ this._syncingBibValue = true;
21125
+ this.input.value = this.inputInBib.value;
21126
+ this.input.updateComplete.then(() => {
21127
+ this._syncingBibValue = false;
21128
+ });
21183
21129
  }
21184
21130
 
21185
- this.handleMenuOptions();
21186
21131
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21187
- return;
21132
+ } else if (event.target === this.input) {
21133
+
21134
+ // Also sync the native input immediately so keystrokes arriving
21135
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
21136
+ // fullscreen transition) operate on the correct content.
21137
+ // Skip the next programmatic input event to prevent the patched setter
21138
+ // from re-entering handleInputValueChange via the bib path.
21139
+ const bibNativeInput = this.inputInBib.inputElement;
21140
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21141
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
21142
+ bibNativeInput.value = this.input.value || '';
21143
+ }
21188
21144
  }
21189
21145
 
21146
+ this.value = this.input.value;
21147
+
21190
21148
  // Ignore re-entrant input events caused by programmatic value sets.
21191
21149
  if (this._syncingBibValue || this._syncingDisplayValue) {
21192
21150
  return;
21193
21151
  }
21194
21152
 
21195
- this.inputInBib.value = this.input.value;
21196
-
21197
- // Also sync the native input immediately so keystrokes arriving
21198
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21199
- // fullscreen transition) operate on the correct content.
21200
- // Skip the next programmatic input event to prevent the patched setter
21201
- // from re-entering handleInputValueChange via the bib path.
21202
- const bibNativeInput = this.inputInBib.inputElement;
21203
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21204
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21205
- bibNativeInput.value = this.input.value || '';
21206
- }
21207
-
21208
- this.menu.matchWord = normalizeFilterValue(this.input.value);
21209
- this.optionActive = null;
21210
-
21211
21153
  if (this.behavior === 'suggestion') {
21212
21154
  this.value = this.input.value;
21213
21155
  }
@@ -21215,7 +21157,6 @@ class AuroCombobox extends AuroElement {
21215
21157
  if (!this.input.value && !this._clearing) {
21216
21158
  this.clear();
21217
21159
  }
21218
- this.handleMenuOptions();
21219
21160
 
21220
21161
  // Validate only if the value was set programmatically (not during user
21221
21162
  // interaction). In fullscreen dialog mode, componentHasFocus returns false
@@ -21225,34 +21166,44 @@ class AuroCombobox extends AuroElement {
21225
21166
  this.validate();
21226
21167
  }
21227
21168
 
21228
- if (this.input.value && this.input.value.length === 0) {
21229
- // Hide menu if value is empty, otherwise show if there are available suggestions
21230
- this.hideBib();
21231
- } else if (this.menu.loading) {
21232
- // if input has value but menu is loading, show bib immediately
21233
- this.showBib();
21234
- } else if (this.availableOptions.length === 0 && !this.dropdown.isBibFullscreen) {
21235
- // Force dropdown bib to hide if input value has no matching suggestions
21236
- this.hideBib();
21169
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21170
+ }
21171
+
21172
+ /**
21173
+ * Handles input value changes originating from the trigger input.
21174
+ * Refreshes menu options and filtering, delegates to handleInputValueChange
21175
+ * for value synchronization, and manages fullscreen bib focus.
21176
+ * @private
21177
+ * @param {Event} event - The input event from the trigger input element.
21178
+ * @returns {void}
21179
+ */
21180
+ handleTriggerInputValueChange(event) {
21181
+ // 'input' fires for every user-initiated value change — typing, paste,
21182
+ // IME composition end, dead-key composition, drag-drop. Flip _userTyped
21183
+ // here so updated('availableOptions') auto-opens the bib for sources
21184
+ // that keydown alone misses: paste fires no keydown, IME uses
21185
+ // key='Process', and dead keys produce multi-char keys (all bypass the
21186
+ // prior keydown.key.length===1 gate). Skip programmatic syncs.
21187
+ if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
21188
+ this._userTyped = true;
21237
21189
  }
21238
21190
 
21239
- // iOS virtual keyboard retention: when in fullscreen mode, ensure the
21240
- // dialog opens and the bib input is focused synchronously within the
21241
- // input event (user gesture) chain. Without this, Lit's async update
21242
- // cycle delays showModal() past the user activation window, causing
21243
- // iOS Safari to dismiss the virtual keyboard when the fullscreen
21244
- // dialog opens — the user then has to tap the input again to resume
21245
- // typing.
21246
- if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
21247
- if (!this.dropdown.isPopoverVisible) {
21248
- this.showBib();
21249
- }
21250
- if (this.dropdown.isPopoverVisible) {
21251
- this.setInputFocus();
21252
- }
21191
+ this.handleMenuOptions();
21192
+ this.optionActive = null;
21193
+
21194
+ if (this.value === this.input.value) {
21195
+ return;
21253
21196
  }
21254
21197
 
21255
- this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21198
+ this.handleInputValueChange(event);
21199
+
21200
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
21201
+ this.setInputFocus();
21202
+ }
21203
+
21204
+ if (this._programmaticFilterRefresh) {
21205
+ this._programmaticFilterRefresh = false;
21206
+ }
21256
21207
  }
21257
21208
 
21258
21209
  /**
@@ -21320,6 +21271,11 @@ class AuroCombobox extends AuroElement {
21320
21271
  disconnectedCallback() {
21321
21272
  super.disconnectedCallback();
21322
21273
  this._inFullscreenTransition = false;
21274
+ // Cancel any outstanding timers so detached callbacks don't fire on
21275
+ // disposed DOM — most are no-ops, but configureMenu's racing-condition
21276
+ // retry would otherwise keep rescheduling itself indefinitely.
21277
+ this._pendingTimers.forEach((id) => clearTimeout(id));
21278
+ this._pendingTimers.clear();
21323
21279
  }
21324
21280
 
21325
21281
  firstUpdated() {
@@ -21361,7 +21317,7 @@ class AuroCombobox extends AuroElement {
21361
21317
  this.menu.value = value;
21362
21318
  // Backup clear: if menu.value === menu.optionSelected.value already, the
21363
21319
  // listener won't fire and the flag would swallow the next user click.
21364
- setTimeout(() => {
21320
+ this._scheduleTimer(() => {
21365
21321
  this._pendingMenuValueSync = false;
21366
21322
  }, 0);
21367
21323
  }
@@ -21378,15 +21334,7 @@ class AuroCombobox extends AuroElement {
21378
21334
  this.menu.value = undefined;
21379
21335
  this.validation.reset(this);
21380
21336
  this.touched = false;
21381
- // Force validity back to the cleared state. validation.reset() calls
21382
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21383
- // residual validity may still be copied into the combobox by the
21384
- // auroInputElements loop. Explicitly clear here so reset() actually
21385
- // leaves the component in a "no validity" state.
21386
21337
  this.validity = undefined;
21387
- this.errorMessage = '';
21388
- this.input.validity = undefined;
21389
- this.input.errorMessage = '';
21390
21338
  }
21391
21339
 
21392
21340
  /**
@@ -21404,6 +21352,17 @@ class AuroCombobox extends AuroElement {
21404
21352
  this.optionSelected = undefined;
21405
21353
  this.value = undefined;
21406
21354
 
21355
+ // Clear the appended displayValue clone in the trigger if present.
21356
+ // :not(slot) excludes the template's <slot name="displayValue"
21357
+ // slot="displayValue"> forwarder (line 1816), which also has
21358
+ // slot="displayValue" and would otherwise be matched first and removed,
21359
+ // permanently breaking the consumer-provided displayValue slot.
21360
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
21361
+
21362
+ if (displayValueInTrigger) {
21363
+ displayValueInTrigger.remove();
21364
+ }
21365
+
21407
21366
  if (this.input.value) {
21408
21367
  this.input.clear();
21409
21368
  }
@@ -21427,81 +21386,50 @@ class AuroCombobox extends AuroElement {
21427
21386
  }
21428
21387
 
21429
21388
  updated(changedProperties) {
21430
- // After the component is ready, send direct value changes to auro-menu.
21389
+ // After the component is ready, propagate direct changes down to child components.
21431
21390
  if (changedProperties.has('value')) {
21432
- if (this.value && this.value.length > 0) {
21433
- this.hasValue = true;
21434
- } else {
21435
- this.hasValue = false;
21391
+ // Only flag programmatic refreshes — user-typed value changes must not
21392
+ // suppress the availableOptions branch's showBib(). Firefox batches
21393
+ // 'value' and 'availableOptions' into the same updated() call, so
21394
+ // setting the flag unconditionally here masks the user-typed open path.
21395
+ if (!this._userTyped) {
21396
+ this._programmaticFilterRefresh = true;
21436
21397
  }
21437
21398
 
21438
- if (this.hasValue && !this.input.value && (!this.menu.options || this.menu.options.length === 0)) {
21439
- this.input.value = this.value;
21440
- }
21399
+ if (this.input.value !== this.value) {
21400
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21401
+ // menu.value leaves the previously-selected option element pinned
21402
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21403
+ // would then write its stale .value back into combobox.value
21404
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21405
+ if (this.menu.value || this.menu.optionSelected) {
21406
+ this.menu.clearSelection();
21407
+ }
21441
21408
 
21442
- // Sync menu.value only when an option actually matches; otherwise clear it.
21443
- if (this.menu.options && this.menu.options.length > 0) {
21444
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21445
- this.setMenuValue(this.value);
21446
- } else if (this.behavior === 'filter') {
21447
- // In filter mode, freeform values aren't allowed. Push the unmatched
21448
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21449
- // and the listener at line 1206 clears combobox.value (mirrors select's
21450
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21451
- this.setMenuValue(this.value);
21452
- } else {
21453
- if (this.menu.value) {
21454
- this.menu.value = undefined;
21455
- }
21456
- // Sync the display for programmatic freeform value changes (e.g. the
21457
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21458
- // re-entrant input event from base-input.notifyValueChanged() is
21459
- // ignored by handleInputValueChange. Skip the sync when input already
21460
- // matches to avoid spurious events during the normal typing path.
21461
- const nextValue = this.value || '';
21462
- const triggerNeedsSync = this.input.value !== nextValue;
21463
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21464
- if (triggerNeedsSync || bibNeedsSync) {
21465
- this._syncingDisplayValue = true;
21466
- if (triggerNeedsSync) {
21467
- this.input.value = nextValue;
21468
- }
21469
- if (bibNeedsSync) {
21470
- this.inputInBib.value = nextValue;
21471
- }
21472
- const pending = [];
21473
- if (triggerNeedsSync) {
21474
- pending.push(this.input.updateComplete);
21475
- }
21476
- if (bibNeedsSync) {
21477
- pending.push(this.inputInBib.updateComplete);
21478
- }
21479
- Promise.all(pending).then(() => {
21480
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21481
- this.input.maskInstance.updateValue();
21482
- }
21483
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21484
- this.inputInBib.maskInstance.updateValue();
21485
- }
21486
- this._syncingDisplayValue = false;
21487
- // handleInputValueChange bailed on the flag — refresh the filter.
21488
- this._programmaticFilterRefresh = true;
21489
- this.handleMenuOptions();
21490
- setTimeout(() => {
21491
- this._programmaticFilterRefresh = false;
21492
- }, 0);
21493
- });
21494
- }
21409
+ if (!this.persistInput) {
21410
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21411
+ }
21412
+
21413
+ // Programmatic value with no matching option: updateFilter will close
21414
+ // the bib silently (see line 648 no noMatchOption + 0 results
21415
+ // hides). Announce so screen-reader users hear the request was
21416
+ // dropped. Gated on `input.value !== this.value` so this never fires
21417
+ // for user typing that path always reconciles input.value to
21418
+ // this.value before updated() runs.
21419
+ if (
21420
+ this.value &&
21421
+ this.menu &&
21422
+ this.menu.options &&
21423
+ this.menu.options.length > 0 &&
21424
+ !this.menu.options.some((opt) => opt.value === this.value)
21425
+ ) {
21426
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21495
21427
  }
21496
21428
  }
21429
+
21497
21430
  if (!this.value) {
21498
21431
  this.clear();
21499
21432
  }
21500
- if (this.value && !this.componentHasFocus) {
21501
- // If the value got set programmatically make sure we hide the bib
21502
- // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
21503
- this.hideBib();
21504
- }
21505
21433
 
21506
21434
  // Sync the input and match word, but don't directly set menu.value again
21507
21435
  if (this.menu) {
@@ -21514,7 +21442,7 @@ class AuroCombobox extends AuroElement {
21514
21442
  composed: true,
21515
21443
  detail: {
21516
21444
  optionSelected: this.menu.optionSelected,
21517
- value: this.menu.value
21445
+ value: this.value
21518
21446
  }
21519
21447
  }));
21520
21448
 
@@ -21537,16 +21465,30 @@ class AuroCombobox extends AuroElement {
21537
21465
  // from a programmatic filter refresh after a value swap — the user
21538
21466
  // didn't interact, so we shouldn't auto-open the bib (especially for
21539
21467
  // the no-match path where the existing condition unconditionally fires).
21540
- if (this._programmaticFilterRefresh) {
21541
- this._programmaticFilterRefresh = false;
21542
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21543
- this.showBib();
21468
+ if ((this.menu && !this._programmaticFilterRefresh)) {
21469
+ if (
21470
+ this.availableOptions.length > 0 ||
21471
+ this.menu.loading ||
21472
+ this.noMatchOption
21473
+ ) {
21474
+ if (this._userTyped) {
21475
+ if (!this.dropdownOpen) {
21476
+ this.showBib();
21477
+ }
21478
+ this._userTyped = false;
21479
+ }
21480
+ }
21481
+
21544
21482
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21545
21483
  this.activateFirstEnabledAvailableOption();
21546
21484
  }
21547
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21485
+ } else if (!this.dropdown.isBibFullscreen) {
21548
21486
  this.hideBib();
21549
21487
  }
21488
+
21489
+ if (this._programmaticFilterRefresh) {
21490
+ this._programmaticFilterRefresh = false;
21491
+ }
21550
21492
  }
21551
21493
 
21552
21494
  if (changedProperties.has('error')) {
@@ -21688,10 +21630,10 @@ class AuroCombobox extends AuroElement {
21688
21630
  shape="${this.shape}"
21689
21631
  size="${this.size}">
21690
21632
  <${this.inputTag}
21691
- @input="${this.handleInputValueChange}"
21633
+ @input="${this.handleTriggerInputValueChange}"
21692
21634
  appearance="${this.onDark ? 'inverse' : this.appearance}"
21693
21635
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
21694
- .a11yExpanded="${this.triggerExpandedState}"
21636
+ .a11yExpanded="${this.dropdownOpen}"
21695
21637
  .a11yControls="${this.dropdownId}"
21696
21638
  .autocomplete="${this.autocomplete}"
21697
21639
  .inputmode="${this.inputmode}"