@aurodesignsystem-dev/auro-formkit 0.0.0-pr1508.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 (138) 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.min.js +8 -6
  4. package/components/checkbox/demo/getting-started.min.js +8 -6
  5. package/components/checkbox/demo/index.min.js +8 -6
  6. package/components/checkbox/dist/index.js +8 -6
  7. package/components/checkbox/dist/registered.js +8 -6
  8. package/components/combobox/demo/customize.md +2 -10
  9. package/components/combobox/demo/customize.min.js +386 -305
  10. package/components/combobox/demo/getting-started.min.js +386 -301
  11. package/components/combobox/demo/index.min.js +386 -301
  12. package/components/combobox/demo/keyboard-behavior.md +2 -142
  13. package/components/combobox/demo/why-combobox.md +1 -1
  14. package/components/combobox/dist/auro-combobox.d.ts +23 -16
  15. package/components/combobox/dist/index.js +385 -300
  16. package/components/combobox/dist/registered.js +385 -300
  17. package/components/counter/demo/customize.min.js +18 -8
  18. package/components/counter/demo/index.min.js +18 -8
  19. package/components/counter/dist/index.js +18 -8
  20. package/components/counter/dist/registered.js +18 -8
  21. package/components/datepicker/demo/accessibility.md +2 -1
  22. package/components/datepicker/demo/api.md +63 -62
  23. package/components/datepicker/demo/customize.md +12 -1
  24. package/components/datepicker/demo/customize.min.js +932 -335
  25. package/components/datepicker/demo/index.min.js +932 -335
  26. package/components/datepicker/dist/{src/auro-calendar-cell.d.ts → auro-calendar-cell.d.ts} +20 -3
  27. package/components/datepicker/dist/{src/auro-calendar.d.ts → auro-calendar.d.ts} +121 -7
  28. package/components/datepicker/dist/{src/auro-datepicker.d.ts → auro-datepicker.d.ts} +78 -5
  29. package/components/datepicker/dist/blackoutUtils.d.ts +43 -0
  30. package/components/datepicker/dist/index.js +932 -335
  31. package/components/datepicker/dist/registered.js +932 -335
  32. package/components/dropdown/demo/customize.min.js +2 -1
  33. package/components/dropdown/demo/getting-started.min.js +2 -1
  34. package/components/dropdown/demo/index.min.js +2 -1
  35. package/components/dropdown/dist/index.js +2 -1
  36. package/components/dropdown/dist/registered.js +2 -1
  37. package/components/form/demo/customize.min.js +1690 -826
  38. package/components/form/demo/getting-started.min.js +1690 -826
  39. package/components/form/demo/index.min.js +1690 -826
  40. package/components/form/demo/registerDemoDeps.min.js +1690 -826
  41. package/components/input/demo/api.md +5 -5
  42. package/components/input/demo/customize.md +8 -8
  43. package/components/input/demo/customize.min.js +137 -95
  44. package/components/input/demo/getting-started.min.js +137 -95
  45. package/components/input/demo/index.min.js +137 -95
  46. package/components/input/dist/base-input.d.ts +33 -44
  47. package/components/input/dist/index.js +150 -96
  48. package/components/input/dist/registered.js +137 -95
  49. package/components/radio/demo/customize.min.js +8 -6
  50. package/components/radio/demo/getting-started.min.js +8 -6
  51. package/components/radio/demo/index.min.js +8 -6
  52. package/components/radio/dist/index.js +8 -6
  53. package/components/radio/dist/registered.js +8 -6
  54. package/components/select/demo/customize.md +86 -0
  55. package/components/select/demo/customize.min.js +194 -68
  56. package/components/select/demo/getting-started.min.js +196 -115
  57. package/components/select/demo/index.min.js +194 -68
  58. package/components/select/demo/keyboard-behavior.md +18 -4
  59. package/components/select/dist/auro-select.d.ts +20 -8
  60. package/components/select/dist/index.js +194 -68
  61. package/components/select/dist/registered.js +194 -68
  62. package/custom-elements.json +2365 -1819
  63. package/package.json +6 -2
  64. package/components/checkbox/demo/customize.js +0 -22
  65. package/components/checkbox/demo/demo-support.js +0 -1
  66. package/components/checkbox/demo/getting-started.js +0 -22
  67. package/components/checkbox/demo/index.js +0 -8
  68. package/components/checkbox/demo/styles.scss +0 -1
  69. package/components/combobox/demo/customize.js +0 -24
  70. package/components/combobox/demo/demo-support.js +0 -1
  71. package/components/combobox/demo/getting-started.js +0 -21
  72. package/components/combobox/demo/index.js +0 -23
  73. package/components/combobox/demo/styles.scss +0 -1
  74. package/components/counter/demo/customize.js +0 -21
  75. package/components/counter/demo/demo-support.js +0 -1
  76. package/components/counter/demo/index.js +0 -21
  77. package/components/counter/demo/styles.scss +0 -1
  78. package/components/datepicker/demo/customize.js +0 -17
  79. package/components/datepicker/demo/demo-support.js +0 -1
  80. package/components/datepicker/demo/index.js +0 -23
  81. package/components/datepicker/demo/styles.scss +0 -1
  82. package/components/dropdown/demo/customize.js +0 -9
  83. package/components/dropdown/demo/demo-support.js +0 -1
  84. package/components/dropdown/demo/getting-started.js +0 -9
  85. package/components/dropdown/demo/index.js +0 -16
  86. package/components/dropdown/demo/styles.scss +0 -1
  87. package/components/form/demo/customize.js +0 -28
  88. package/components/form/demo/demo-support.js +0 -1
  89. package/components/form/demo/getting-started.js +0 -9
  90. package/components/form/demo/index.js +0 -5
  91. package/components/form/demo/registerDemoDeps.js +0 -23
  92. package/components/form/demo/styles.scss +0 -1
  93. package/components/input/demo/customize.js +0 -25
  94. package/components/input/demo/demo-support.js +0 -1
  95. package/components/input/demo/getting-started.js +0 -8
  96. package/components/input/demo/index.js +0 -8
  97. package/components/input/demo/styles.css +0 -974
  98. package/components/input/demo/styles.scss +0 -1
  99. package/components/menu/demo/demo-support.js +0 -1
  100. package/components/menu/demo/index.js +0 -25
  101. package/components/menu/demo/styles.scss +0 -1
  102. package/components/radio/demo/customize.js +0 -22
  103. package/components/radio/demo/demo-support.js +0 -1
  104. package/components/radio/demo/getting-started.js +0 -24
  105. package/components/radio/demo/index.js +0 -8
  106. package/components/radio/demo/styles.scss +0 -1
  107. package/components/select/demo/customize.js +0 -11
  108. package/components/select/demo/demo-support.js +0 -1
  109. package/components/select/demo/getting-started.js +0 -26
  110. package/components/select/demo/index.js +0 -11
  111. package/components/select/demo/styles.scss +0 -1
  112. /package/components/datepicker/dist/{src/auro-calendar-month.d.ts → auro-calendar-month.d.ts} +0 -0
  113. /package/components/datepicker/dist/{src/buttonVersion.d.ts → buttonVersion.d.ts} +0 -0
  114. /package/components/datepicker/dist/{src/datepickerKeyboardStrategy.d.ts → datepickerKeyboardStrategy.d.ts} +0 -0
  115. /package/components/datepicker/dist/{src/iconVersion.d.ts → iconVersion.d.ts} +0 -0
  116. /package/components/datepicker/dist/{src/index.d.ts → index.d.ts} +0 -0
  117. /package/components/datepicker/dist/{src/popoverVersion.d.ts → popoverVersion.d.ts} +0 -0
  118. /package/components/datepicker/dist/{src/styles → styles}/classic/color-css.d.ts +0 -0
  119. /package/components/datepicker/dist/{src/styles → styles}/classic/style-css.d.ts +0 -0
  120. /package/components/datepicker/dist/{src/styles → styles}/color-calendar-css.d.ts +0 -0
  121. /package/components/datepicker/dist/{src/styles → styles}/color-cell-css.d.ts +0 -0
  122. /package/components/datepicker/dist/{src/styles → styles}/color-css.d.ts +0 -0
  123. /package/components/datepicker/dist/{src/styles → styles}/color-month-css.d.ts +0 -0
  124. /package/components/datepicker/dist/{src/styles → styles}/shapeSize-css.d.ts +0 -0
  125. /package/components/datepicker/dist/{src/styles → styles}/snowflake/color-css.d.ts +0 -0
  126. /package/components/datepicker/dist/{src/styles → styles}/snowflake/style-css.d.ts +0 -0
  127. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-cell-css.d.ts +0 -0
  128. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-css.d.ts +0 -0
  129. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-month-css.d.ts +0 -0
  130. /package/components/datepicker/dist/{src/styles → styles}/style-css.d.ts +0 -0
  131. /package/components/datepicker/dist/{src/styles → styles}/tokens-css.d.ts +0 -0
  132. /package/components/datepicker/dist/{src/utilities.d.ts → utilities.d.ts} +0 -0
  133. /package/components/datepicker/dist/{src/utilitiesCalendar.d.ts → utilitiesCalendar.d.ts} +0 -0
  134. /package/components/datepicker/dist/{src/utilitiesCalendarRender.d.ts → utilitiesCalendarRender.d.ts} +0 -0
  135. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/day.d.ts +0 -0
  136. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-calendar.d.ts +0 -0
  137. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-cell.d.ts +0 -0
  138. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker.d.ts +0 -0
@@ -485,15 +485,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
485
485
  );
486
486
  }
487
487
 
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) {
488
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
489
+
490
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
491
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
492
+ // field (datepicker is the intended consumer — start/end are independently required).
493
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
494
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
491
495
  hasValue = false;
492
496
  }
493
497
  }
494
498
 
495
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
496
-
497
499
  if (isCombobox) {
498
500
 
499
501
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -868,11 +870,7 @@ const comboboxKeyboardStrategy = {
868
870
 
869
871
  // navigate if bib is open otherwise open it
870
872
  if (component.dropdown.isPopoverVisible) {
871
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
872
- component.activateLastEnabledAvailableOption();
873
- } else {
874
- navigateArrow(component, 'down');
875
- }
873
+ navigateArrow(component, 'down');
876
874
  } else {
877
875
  component.showBib();
878
876
  }
@@ -891,11 +889,7 @@ const comboboxKeyboardStrategy = {
891
889
 
892
890
  // navigate if bib is open otherwise open it
893
891
  if (component.dropdown.isPopoverVisible) {
894
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
895
- component.activateFirstEnabledAvailableOption();
896
- } else {
897
- navigateArrow(component, 'up');
898
- }
892
+ navigateArrow(component, 'up');
899
893
  } else {
900
894
  component.showBib();
901
895
  }
@@ -911,13 +905,18 @@ const comboboxKeyboardStrategy = {
911
905
  },
912
906
 
913
907
  Enter(component, evt, ctx) {
908
+ // Forms should not submit on Enter from a combobox, regardless of which
909
+ // child element (input, clear button, menu) is focused.
910
+ evt.stopPropagation();
911
+
914
912
  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) {
913
+ // Let the browser dispatch Enter to the focused clear button so its
914
+ // built-in activation fires and clears the selection. Do NOT call
915
+ // preventDefault — that would block the activation.
916
+ return;
917
+ }
918
+
919
+ if (ctx.isExpanded && component.menu.optionActive) {
921
920
  reconcileMenuIndex(component.menu);
922
921
  component.menu.makeSelection();
923
922
 
@@ -926,14 +925,8 @@ const comboboxKeyboardStrategy = {
926
925
  }
927
926
 
928
927
  evt.preventDefault();
929
- evt.stopPropagation();
930
928
  } 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
929
  evt.preventDefault();
936
- evt.stopPropagation();
937
930
  component.showBib();
938
931
  }
939
932
  },
@@ -978,7 +971,7 @@ const comboboxKeyboardStrategy = {
978
971
  component.setClearBtnFocus();
979
972
  }
980
973
  }
981
- },
974
+ }
982
975
  };
983
976
 
984
977
  /**
@@ -4873,7 +4866,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4873
4866
  }
4874
4867
  };
4875
4868
 
4876
- var formkitVersion$2 = '202606231948';
4869
+ var formkitVersion$2 = '202607010100';
4877
4870
 
4878
4871
  let AuroElement$2 = class AuroElement extends i$3 {
4879
4872
  static get properties() {
@@ -6145,6 +6138,7 @@ class AuroDropdown extends AuroElement$2 {
6145
6138
  role="${o(this.triggerContentFocusable ? undefined : this.a11yRole)}"
6146
6139
  aria-expanded="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
6147
6140
  aria-controls="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.dropdownId)}"
6141
+ aria-haspopup="${o(this.a11yRole === 'combobox' && !this.triggerContentFocusable ? 'listbox' : undefined)}"
6148
6142
  aria-labelledby="${o(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
6149
6143
  aria-disabled="${o(this.disabled ? 'true' : undefined)}"
6150
6144
  @focusin="${this.handleFocusin}"
@@ -10654,15 +10648,17 @@ class AuroFormValidation {
10654
10648
  );
10655
10649
  }
10656
10650
 
10657
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
10658
- if (this.auroInputElements?.length === 2) {
10659
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
10651
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10652
+
10653
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
10654
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
10655
+ // field (datepicker is the intended consumer — start/end are independently required).
10656
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
10657
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
10660
10658
  hasValue = false;
10661
10659
  }
10662
10660
  }
10663
10661
 
10664
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10665
-
10666
10662
  if (isCombobox) {
10667
10663
 
10668
10664
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -16598,6 +16594,13 @@ class AuroInputUtilities {
16598
16594
  * @returns {string | undefined}
16599
16595
  */
16600
16596
  toFormattedValue(valueObject, format) {
16597
+ // Exposed publicly via `AuroInputUtil.toFormattedValue`, so a missing
16598
+ // `format` argument has to fail gracefully rather than throw on
16599
+ // `format.toLowerCase()`. Treat any falsy value (undefined, null, '')
16600
+ // as "no format → no display value".
16601
+ if (!format) {
16602
+ return undefined;
16603
+ }
16601
16604
  const normalizedFormat = format.toLowerCase();
16602
16605
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16603
16606
 
@@ -16749,76 +16752,19 @@ class BaseInput extends AuroElement$1 {
16749
16752
  constructor() {
16750
16753
  super();
16751
16754
 
16752
- this.appearance = "default";
16753
- this.disabled = false;
16754
- this.layout = 'classic';
16755
- this.locale = 'en-US';
16756
- this.max = undefined;
16757
- this.maxLength = undefined;
16758
- this.min = undefined;
16759
- this.minLength = undefined;
16760
- this.required = false;
16761
- this.onDark = false;
16762
- this.setCustomValidityForType = undefined;
16763
- this.size = 'lg';
16764
- this.shape = 'classic';
16765
- this.value = undefined;
16766
-
16767
- this._initializePrivateDefaults();
16768
- }
16769
-
16770
- /**
16771
- * Internal Defaults.
16772
- * @private
16773
- * @returns {void}
16774
- */
16775
- _initializePrivateDefaults() {
16755
+ // Single-source initialization. Alphabetized so duplicate or stale
16756
+ // defaults are immediately obvious on a diff. Every field is assigned
16757
+ // exactly once here (previously the constructor + the old
16758
+ // `_initializePrivateDefaults` overlapped — both wrote ~14 of the same
16759
+ // fields and double-allocated `util` and `validation`, discarding the
16760
+ // first instance). `validation` is now allocated exactly once; `util`
16761
+ // is seeded here with an en-US default and then rebuilt in
16762
+ // `connectedCallback` once the consumer-resolved locale is available,
16763
+ // so a parent (datepicker/combobox) calling `validate()` synchronously
16764
+ // during its own update cycle sees a populated util instance.
16776
16765
  this.activeLabel = false;
16777
- this.appearance = "default";
16778
- this.icon = false;
16779
- this.disabled = false;
16780
- this.dvInputOnly = false;
16781
- this.hideLabelVisually = false;
16782
- this.max = undefined;
16783
- this.maxLength = undefined;
16784
- this.min = undefined;
16785
- this.minLength = undefined;
16786
- this.noValidate = false;
16787
- this.onDark = false;
16788
- this.required = false;
16789
- this.setCustomValidityForType = undefined;
16790
-
16791
- // Used for storing raw values returned from input mask.
16792
- this._rawMaskValue = undefined;
16793
-
16794
- /**
16795
- * @private
16796
- */
16797
- this.layout = 'classic';
16798
-
16799
- /**
16800
- * @private
16801
- */
16802
- this.shape = 'classic';
16803
-
16804
- /**
16805
- * @private
16806
- */
16807
- this.size = 'lg';
16808
-
16809
- this.touched = false;
16810
- this.util = new AuroInputUtilities({
16811
- locale: "en-US",
16812
- format: this.format
16813
- });
16814
- this.validation = new AuroFormValidation();
16815
- this.inputIconName = undefined;
16816
- this.showPassword = false;
16817
- this.validationCCLength = undefined;
16818
- this.hasValue = false;
16819
- this.label = 'Input label is undefined';
16820
-
16821
16766
 
16767
+ /** @private */
16822
16768
  this.allowedInputTypes = [
16823
16769
  "text",
16824
16770
  "number",
@@ -16827,7 +16773,9 @@ class BaseInput extends AuroElement$1 {
16827
16773
  "credit-card",
16828
16774
  "tel"
16829
16775
  ];
16830
- this.icon = false;
16776
+ this.appearance = "default";
16777
+
16778
+ /** @private */
16831
16779
  this.dateFormatMap = {
16832
16780
  'mm/dd/yyyy': 'dateMMDDYYYY',
16833
16781
  'dd/mm/yyyy': 'dateDDMMYYYY',
@@ -16844,27 +16792,62 @@ class BaseInput extends AuroElement$1 {
16844
16792
  'dd/mm': 'dateDDMM',
16845
16793
  'mm/dd': 'dateMMDD'
16846
16794
  };
16795
+ this.disabled = false;
16796
+
16797
+ /** @private */
16847
16798
  this.domHandler = new DomHandler();
16848
16799
  this.dvInputOnly = false;
16849
16800
  this.hasValue = false;
16801
+ this.hideLabelVisually = false;
16802
+ this.icon = false;
16803
+
16804
+ /** @private */
16850
16805
  this.inputIconName = undefined;
16806
+
16807
+ /** @private */
16851
16808
  this.label = 'Input label is undefined';
16809
+ this.layout = 'classic';
16810
+ this.locale = 'en-US';
16811
+ this.max = undefined;
16812
+ this.maxLength = undefined;
16813
+ this.min = undefined;
16814
+ this.minLength = undefined;
16852
16815
  this.noValidate = false;
16853
- this._rawMaskValue = undefined; // Used for storing raw values returned from input mask.
16816
+ this.onDark = false;
16817
+ // Raw values returned from the input mask before model normalization.
16818
+ this._rawMaskValue = undefined;
16819
+ this.required = false;
16820
+ this.setCustomValidityForType = undefined;
16821
+ // Credit Card is intentionally excluded — its mask manages the cursor
16822
+ // itself, and listing it here caused cursor placement issues in Safari.
16823
+ /** @private */
16854
16824
  this.setSelectionInputTypes = [
16855
16825
  "text",
16856
16826
  "password",
16857
16827
  "email"
16858
- ]; // Credit Card is not included as this caused cursor placement issues in Safari.
16828
+ ];
16829
+ this.shape = 'classic';
16830
+
16831
+ /** @private */
16859
16832
  this.showPassword = false;
16833
+ this.size = 'lg';
16860
16834
  this.touched = false;
16835
+
16836
+ /** @private */
16861
16837
  this.uniqueId = new UniqueId().create();
16838
+
16839
+ /** @private */
16862
16840
  this.util = new AuroInputUtilities({
16863
16841
  locale: this.locale,
16864
16842
  format: this.format
16865
16843
  });
16844
+
16845
+ /** @private */
16866
16846
  this.validation = new AuroFormValidation();
16847
+
16848
+ /** @private */
16867
16849
  this.validationCCLength = undefined;
16850
+ this.value = undefined;
16868
16851
  }
16869
16852
 
16870
16853
  // function to define props used within the scope of this component
@@ -17368,6 +17351,15 @@ class BaseInput extends AuroElement$1 {
17368
17351
 
17369
17352
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17370
17353
 
17354
+ // Normalize the format token to lowercase so case-mixed values supplied
17355
+ // via attribute (e.g. `format="MM/DD/YYYY"`) hit the `dateFormatMap`
17356
+ // lookup inside `setCustomHelpTextMessage`. Without this, an uppercase
17357
+ // format silently misses the map and leaves `setCustomValidityForType`
17358
+ // unset.
17359
+ if (this.format) {
17360
+ this.format = this.format.toLowerCase();
17361
+ }
17362
+
17371
17363
  // use validity message override if declared when initializing the component
17372
17364
  if (this.hasAttribute('setCustomValidity')) {
17373
17365
  this.ValidityMessageOverride = this.setCustomValidity;
@@ -17451,6 +17443,13 @@ class BaseInput extends AuroElement$1 {
17451
17443
 
17452
17444
  if (typeToI18n.includes(this.type)) {
17453
17445
  this.setCustomValidityForType = i18n(this.lang, this.type);
17446
+ // COVERAGE: this `else if` branch is unreachable in WTR. connectedCallback
17447
+ // (L682) calls configureDataForType, which at L1266-1268 assigns
17448
+ // `this.format = this.util.getDateMaskFromLocale().toLowerCase()` for any
17449
+ // type=date input whose `format` attribute is unset. That runs before
17450
+ // firstUpdated invokes setCustomHelpTextMessage, so `!this.format` is
17451
+ // always false here. Retained as a defensive fallback; the whole function
17452
+ // is @deprecated per AB#1557296 and slated for removal.
17454
17453
  } else if (!this.format && this.type === 'date') {
17455
17454
  this.setCustomValidityForType = i18n(this.lang, 'dateMMDDYYYY');
17456
17455
  } else if (this.dateFormatMap[this.format]) {
@@ -17740,6 +17739,38 @@ class BaseInput extends AuroElement$1 {
17740
17739
  this.touched = true;
17741
17740
  this.validation.validate(this);
17742
17741
  }
17742
+
17743
+ // Prevents cursor jumping in Safari. Setting `this.value` triggers a Lit
17744
+ // update that can re-render the input and reset the native cursor; we
17745
+ // capture the caret position before that update commits and restore it
17746
+ // via `setSelectionRange` once the update has flushed. Gated on
17747
+ // `setSelectionInputTypes` so credit-card (and other masked types whose
17748
+ // formatter manages the cursor itself) doesn't get a competing write.
17749
+ // Capture the caret position INSIDE the gate — reading `selectionStart`
17750
+ // on input types that don't support text selection (number, email in
17751
+ // some browsers) throws InvalidStateError, which would crash all input
17752
+ // handling. Wrap the read in try/catch belt-and-suspenders even though
17753
+ // the gated types currently support it, since the list is a public
17754
+ // property a consumer could mutate.
17755
+ if (this.setSelectionInputTypes.includes(this.type)) {
17756
+ let selectionStart = null;
17757
+ try {
17758
+ ({ selectionStart } = this.inputElement);
17759
+ } catch (error) { // eslint-disable-line no-unused-vars
17760
+ return;
17761
+ }
17762
+ if (typeof selectionStart !== 'number') {
17763
+ return;
17764
+ }
17765
+ this.updateComplete.then(() => {
17766
+ try {
17767
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
17768
+ } catch (error) { // eslint-disable-line no-unused-vars
17769
+ // Some input types (number/email in certain UAs) throw on
17770
+ // setSelectionRange; swallow and let the native cursor stand.
17771
+ }
17772
+ });
17773
+ }
17743
17774
  }
17744
17775
 
17745
17776
  /**
@@ -17873,7 +17904,7 @@ class BaseInput extends AuroElement$1 {
17873
17904
  */
17874
17905
  get placeholderStr() {
17875
17906
  if (!this.placeholder && this.type === 'date') {
17876
- return this.format ? this.format.toUpperCase() : 'MM/DD/YYYY';
17907
+ return this.format.toUpperCase();
17877
17908
  }
17878
17909
  return this.placeholder || "";
17879
17910
  }
@@ -18364,7 +18395,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18364
18395
  }
18365
18396
  };
18366
18397
 
18367
- var formkitVersion$1 = '202606231948';
18398
+ var formkitVersion$1 = '202607010100';
18368
18399
 
18369
18400
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18370
18401
  // See LICENSE in the project root for license information.
@@ -18867,6 +18898,7 @@ class AuroInput extends BaseInput {
18867
18898
  <${this.buttonTag}
18868
18899
  @click="${this.handleClickShowPassword}"
18869
18900
  appearance="${this.onDark ? 'inverse' : this.appearance}"
18901
+ aria-pressed="${this.showPassword ? 'true' : 'false'}"
18870
18902
  class="notificationBtn passwordBtn"
18871
18903
  shape="circle"
18872
18904
  size="sm"
@@ -18940,25 +18972,29 @@ class AuroInput extends BaseInput {
18940
18972
  * @returns {html} - Returns HTML for the help text and error message.
18941
18973
  */
18942
18974
  renderHtmlHelpText() {
18975
+ // Single `<p>` with stable identity across validity transitions —
18976
+ // previously two distinct templates (valid vs invalid) caused Lit to
18977
+ // replace the node entirely on a flip, and VoiceOver wouldn't
18978
+ // re-announce because the live-region element it was watching had been
18979
+ // removed and a new one inserted. Keeping one node means the `role`,
18980
+ // `aria-live`, and text content all change in-place, which AT does
18981
+ // observe and announce.
18982
+ const isError = this.validity && this.validity !== 'valid';
18943
18983
  return u$7`
18944
- ${!this.validity || this.validity === undefined || this.validity === 'valid'
18945
- ? u$7`
18946
- <${this.helpTextTag}
18947
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18948
- <p id="${this.uniqueId}" part="helpText">
18949
- <slot name="helpText">${this.getHelpText()}</slot>
18950
- </p>
18951
- </${this.helpTextTag}>
18952
- `
18953
- : u$7`
18954
- <${this.helpTextTag} error
18955
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18956
- <p id="${this.uniqueId}" role="alert" aria-live="assertive" part="helpText">
18957
- ${this.errorMessage}
18958
- </p>
18959
- </${this.helpTextTag}>
18960
- `
18961
- }
18984
+ <${this.helpTextTag}
18985
+ appearance="${this.onDark ? 'inverse' : this.appearance}"
18986
+ ?error=${isError}>
18987
+ <p
18988
+ id="${this.uniqueId}"
18989
+ part="helpText"
18990
+ role="${o(isError ? 'alert' : undefined)}"
18991
+ aria-live="${o(isError ? 'assertive' : undefined)}">
18992
+ ${isError
18993
+ ? this.errorMessage
18994
+ : u$7`<slot name="helpText">${this.getHelpText()}</slot>`
18995
+ }
18996
+ </p>
18997
+ </${this.helpTextTag}>
18962
18998
  `;
18963
18999
  }
18964
19000
 
@@ -19090,7 +19126,7 @@ class AuroInput extends BaseInput {
19090
19126
 
19091
19127
  var colorCss$3 = i$6`#headerContainer{box-shadow:var(--ds-auro-dropdownbib-header-boxshadow-color)}#footerContainer{background-color:var(--ds-auro-dropdownbib-footer-container-color)}`;
19092
19128
 
19093
- var styleCss$4 = i$6`#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)}`;
19129
+ var styleCss$4 = i$6`#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)}`;
19094
19130
 
19095
19131
  var tokenCss = i$6`: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)}`;
19096
19132
 
@@ -19485,7 +19521,7 @@ class AuroBibtemplate extends i$3 {
19485
19521
  }
19486
19522
  }
19487
19523
 
19488
- var formkitVersion = '202606231948';
19524
+ var formkitVersion = '202607010100';
19489
19525
 
19490
19526
  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}`;
19491
19527
 
@@ -19853,12 +19889,23 @@ function getOptionLabel(option) {
19853
19889
  if (!option) {
19854
19890
  return '';
19855
19891
  }
19856
- const clone = option.cloneNode(true);
19857
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19858
- if (displayValueEl) {
19859
- displayValueEl.remove();
19892
+
19893
+ // Consumer-provided override: short-circuit the DOM walk entirely.
19894
+ if (option.dataset && option.dataset.label) {
19895
+ return option.dataset.label;
19896
+ }
19897
+
19898
+ // Walk direct children — the `slot` attribute only applies to direct children
19899
+ // of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
19900
+ // querySelector allocation that ran on every keystroke via syncValuesAndStates.
19901
+ let text = '';
19902
+ for (const node of option.childNodes) {
19903
+ const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
19904
+ if (!isDisplayValueSlot) {
19905
+ text += node.textContent || '';
19906
+ }
19860
19907
  }
19861
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19908
+ return text.replace(/\s+/gu, ' ').trim();
19862
19909
  }
19863
19910
 
19864
19911
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
@@ -19933,8 +19980,6 @@ class AuroCombobox extends AuroElement {
19933
19980
  this.availableOptions = [];
19934
19981
  this.dropdownId = undefined;
19935
19982
  this.dropdownOpen = false;
19936
- this.triggerExpandedState = false;
19937
- this._expandedTimeout = null;
19938
19983
  this._inFullscreenTransition = false;
19939
19984
  this.errorMessage = null;
19940
19985
  this.isHiddenWhileLoading = false;
@@ -19945,6 +19990,30 @@ class AuroCombobox extends AuroElement {
19945
19990
  this.touched = false;
19946
19991
  this.validation = new AuroFormValidation$1();
19947
19992
  this.validity = undefined;
19993
+ this._userTyped = false;
19994
+ // Tracks every setTimeout scheduled via _scheduleTimer so
19995
+ // disconnectedCallback can cancel them. Without this, a detached
19996
+ // combobox's pending timers still fire — most are no-ops, but
19997
+ // configureMenu's racing-condition retry would otherwise loop.
19998
+ this._pendingTimers = new Set();
19999
+ }
20000
+
20001
+ /**
20002
+ * setTimeout wrapper that records the timer id so disconnectedCallback
20003
+ * can cancel any outstanding callbacks. The id is removed from the set
20004
+ * once the callback fires so the set doesn't grow unbounded.
20005
+ * @param {Function} fn - Callback to run.
20006
+ * @param {number} ms - Delay in milliseconds.
20007
+ * @returns {number} The timer id.
20008
+ * @private
20009
+ */
20010
+ _scheduleTimer(fn, ms) {
20011
+ const id = setTimeout(() => {
20012
+ this._pendingTimers.delete(id);
20013
+ fn();
20014
+ }, ms);
20015
+ this._pendingTimers.add(id);
20016
+ return id;
19948
20017
  }
19949
20018
 
19950
20019
  // This function is to define props used within the scope of this component
@@ -20306,17 +20375,6 @@ class AuroCombobox extends AuroElement {
20306
20375
  attribute: false
20307
20376
  },
20308
20377
 
20309
- /**
20310
- * Deferred aria-expanded state for the trigger input.
20311
- * Delays the "true" transition so VoiceOver finishes its character echo
20312
- * before announcing "expanded".
20313
- * @private
20314
- */
20315
- triggerExpandedState: {
20316
- type: Boolean,
20317
- reflect: false,
20318
- attribute: false
20319
- },
20320
20378
  };
20321
20379
  }
20322
20380
 
@@ -20339,13 +20397,6 @@ class AuroCombobox extends AuroElement {
20339
20397
  return this.input.value;
20340
20398
  }
20341
20399
 
20342
- // /**
20343
- // * Sets the value of the input element within the combobox.
20344
- // */
20345
- // set inputValue(value) {
20346
- // this.input.value = value;
20347
- // }
20348
-
20349
20400
  /**
20350
20401
  * Checks if the element is valid.
20351
20402
  * @returns {boolean} - Returns true if the element is valid, false otherwise.
@@ -20491,8 +20542,11 @@ class AuroCombobox extends AuroElement {
20491
20542
  this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20492
20543
  }
20493
20544
 
20494
- // update the displayValue in the trigger if displayValue slot content is present
20495
- const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]');
20545
+ // Replace any previously appended displayValue clone in the trigger.
20546
+ // :not(slot) excludes the template's <slot name="displayValue"
20547
+ // slot="displayValue"> forwarder, which also has slot="displayValue"
20548
+ // and would otherwise be matched first and removed.
20549
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
20496
20550
 
20497
20551
  if (displayValueInTrigger) {
20498
20552
  displayValueInTrigger.remove();
@@ -20520,19 +20574,19 @@ class AuroCombobox extends AuroElement {
20520
20574
  * Writes nextValue to the trigger input and the bib input when their current
20521
20575
  * value differs, then re-asserts imask after Lit's update flushes.
20522
20576
  * @param {string} nextValue - The value to write to both inputs.
20523
- * @returns {Promise<void> | null} Promise that resolves after the inputs flush,
20524
- * or null when no sync was needed (so callers can skip post-flush work).
20577
+ * @returns {Promise<void>} Resolves after both inputs flush and imask
20578
+ * re-asserts; resolves immediately when no sync is needed.
20525
20579
  * @private
20526
20580
  */
20527
- syncInputValuesAcrossTriggerAndBib(nextValue) {
20581
+ async syncInputValuesAcrossTriggerAndBib(nextValue) {
20528
20582
  // Only set the flag when there's an actual write to suppress —
20529
20583
  // syncValuesAndStates re-enters here during typing when both inputs
20530
20584
  // already match, and a no-op flag flip would make the bib branch's
20531
20585
  // bail eat legitimate user-input events.
20532
20586
  const triggerNeedsSync = this.input.value !== nextValue;
20533
- const bibNeedsSync = this.inputInBib.value !== nextValue;
20587
+ const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
20534
20588
  if (!triggerNeedsSync && !bibNeedsSync) {
20535
- return null;
20589
+ return;
20536
20590
  }
20537
20591
 
20538
20592
  this._syncingDisplayValue = true;
@@ -20546,16 +20600,21 @@ class AuroCombobox extends AuroElement {
20546
20600
  this.inputInBib.value = nextValue;
20547
20601
  pending.push(this.inputInBib.updateComplete);
20548
20602
  }
20549
- return Promise.all(pending).then(() => {
20603
+ // finally — not a bare .then — so that an imask throw (see commit
20604
+ // d1857401c: imask can throw on credit-card format change) doesn't strand
20605
+ // _syncingDisplayValue=true and silently swallow every subsequent input.
20606
+ try {
20607
+ await Promise.all(pending);
20550
20608
  // imask reasserts otherwise, and handleBlur reverts to its stale value.
20551
20609
  if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20552
20610
  this.input.maskInstance.updateValue();
20553
20611
  }
20554
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20612
+ if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20555
20613
  this.inputInBib.maskInstance.updateValue();
20556
20614
  }
20615
+ } finally {
20557
20616
  this._syncingDisplayValue = false;
20558
- });
20617
+ }
20559
20618
  }
20560
20619
 
20561
20620
  /**
@@ -20566,6 +20625,13 @@ class AuroCombobox extends AuroElement {
20566
20625
  handleMenuOptions() {
20567
20626
  this.generateOptionsArray();
20568
20627
  this.availableOptions = [];
20628
+ // Single source of truth for the menu's filter/highlight token per call.
20629
+ // syncValuesAndStates re-writes the same value on exact-match keystrokes —
20630
+ // Lit's hasChanged makes that a no-op — and the prior duplicate set inside
20631
+ // handleTriggerInputValueChange is gone.
20632
+ if (this.menu) {
20633
+ this.menu.matchWord = normalizeFilterValue(this.input.value);
20634
+ }
20569
20635
  this.updateFilter();
20570
20636
 
20571
20637
  // Set aria-setsize/aria-posinset on each visible option so screen readers
@@ -20577,11 +20643,8 @@ class AuroCombobox extends AuroElement {
20577
20643
  option.setAttribute('aria-posinset', index + 1);
20578
20644
  });
20579
20645
 
20580
- if (this.value && this.input.value && !this.menu.value) {
20581
- if (this.behavior === 'suggestion' && this.menu.options && this.menu.options.some((opt) => opt.value === this.value)) {
20582
- this.setMenuValue(this.value);
20583
- }
20584
-
20646
+ if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
20647
+ this.setMenuValue(this.input.value);
20585
20648
  this.syncValuesAndStates();
20586
20649
  }
20587
20650
 
@@ -20660,18 +20723,6 @@ class AuroCombobox extends AuroElement {
20660
20723
  this.dropdownOpen = ev.detail.expanded;
20661
20724
  this.updateMenuShapeSize();
20662
20725
 
20663
- // Defer aria-expanded "true" so VoiceOver finishes character echo
20664
- // before announcing "expanded". Set "false" immediately on close.
20665
- clearTimeout(this._expandedTimeout);
20666
- if (this.dropdownOpen) {
20667
- const expandedDelay = 150;
20668
- this._expandedTimeout = setTimeout(() => {
20669
- this.triggerExpandedState = true;
20670
- }, expandedDelay);
20671
- } else {
20672
- this.triggerExpandedState = false;
20673
- }
20674
-
20675
20726
  // Clear aria-activedescendant when dropdown closes
20676
20727
  if (!this.dropdownOpen && this.input) {
20677
20728
  this.input.setActiveDescendant(null);
@@ -20776,7 +20827,7 @@ class AuroCombobox extends AuroElement {
20776
20827
  this.dropdown.trigger.inert = false;
20777
20828
  }
20778
20829
 
20779
- setTimeout(() => {
20830
+ this._scheduleTimer(() => {
20780
20831
  this.setInputFocus();
20781
20832
  }, 0);
20782
20833
  });
@@ -20920,7 +20971,7 @@ class AuroCombobox extends AuroElement {
20920
20971
 
20921
20972
  // racing condition on custom-combobox with custom-menu
20922
20973
  if (!this.menu) {
20923
- setTimeout(() => {
20974
+ this._scheduleTimer(() => {
20924
20975
  this.configureMenu();
20925
20976
  }, 0);
20926
20977
  return;
@@ -20975,9 +21026,7 @@ class AuroCombobox extends AuroElement {
20975
21026
  // Hide dropdown on selection (except during slot changes)
20976
21027
  if (evt.detail && evt.detail.source !== 'slotchange') {
20977
21028
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
20978
- if (this.menu.value || this.behavior !== 'suggestion') {
20979
- this.hideBib();
20980
- }
21029
+ this.hideBib();
20981
21030
 
20982
21031
  // Move focus to the clear button when the user makes a selection.
20983
21032
  if (!isEcho && this.menu.value !== undefined) {
@@ -20991,7 +21040,7 @@ class AuroCombobox extends AuroElement {
20991
21040
  const selectedValue = this.menu.value;
20992
21041
  if (selectedValue) {
20993
21042
  const announcementDelay = 300;
20994
- setTimeout(() => {
21043
+ this._scheduleTimer(() => {
20995
21044
  announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
20996
21045
  }, announcementDelay);
20997
21046
  }
@@ -21003,10 +21052,6 @@ class AuroCombobox extends AuroElement {
21003
21052
  // only — fresh user selections take the existing hideBib path.
21004
21053
  if (isEcho && this.menu.optionSelected) {
21005
21054
  this._programmaticFilterRefresh = true;
21006
- this.handleMenuOptions();
21007
- setTimeout(() => {
21008
- this._programmaticFilterRefresh = false;
21009
- }, 0);
21010
21055
  }
21011
21056
  });
21012
21057
 
@@ -21019,6 +21064,14 @@ class AuroCombobox extends AuroElement {
21019
21064
  // stale option. Safe from re-entrancy because any resulting
21020
21065
  // input.value changes dispatch isProgrammatic events.
21021
21066
  this.menu.addEventListener('auroMenu-selectValueFailure', () => {
21067
+ // Announce the rejection BEFORE we clear `this.value` so the live
21068
+ // region carries the attempted value — without this the bib closes
21069
+ // silently and screen-reader users get no signal that their request
21070
+ // (e.g. a direct setMenuValue() call) was dropped.
21071
+ const attemptedValue = this.value;
21072
+ if (attemptedValue) {
21073
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
21074
+ }
21022
21075
  this.value = undefined;
21023
21076
  this.optionSelected = undefined;
21024
21077
  });
@@ -21030,10 +21083,13 @@ class AuroCombobox extends AuroElement {
21030
21083
  this.input.setActiveDescendant(this.optionActive);
21031
21084
  }
21032
21085
 
21033
- // Announce the active option for screen readers including position,
21034
- // since shadow DOM boundaries prevent native reading of
21035
- // aria-setsize/aria-posinset via aria-activedescendant.
21036
- if (this.optionActive) {
21086
+ // In fullscreen mode the menu sits inside a nested <dialog> shadow root,
21087
+ // and aria-activedescendant references across that boundary are lost —
21088
+ // VoiceOver/NVDA don't read the active option natively, so we mirror it
21089
+ // into the polite live region. In popover mode aria-activedescendant on
21090
+ // the trigger input is read natively; double-announcing would flood the
21091
+ // queue on arrow-key repeat.
21092
+ if (this.optionActive && this.dropdown.isBibFullscreen) {
21037
21093
  const optionText = this.optionActive.textContent.trim();
21038
21094
  const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
21039
21095
  const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
@@ -21066,7 +21122,12 @@ class AuroCombobox extends AuroElement {
21066
21122
  * Validate every time we remove focus from the combo box.
21067
21123
  */
21068
21124
  this.addEventListener('focusout', () => {
21069
- if (!this.componentHasFocus && !this._inFullscreenTransition) {
21125
+ // Skip while the dropdown is open — focus transits out briefly on
21126
+ // mousedown of a menu option (popover top-layer breaks :focus-within),
21127
+ // and validating against the pre-selection value flashes a stale error
21128
+ // between mousedown and mouseup. The next focusout fires after the
21129
+ // dropdown closes and validates against the post-selection value.
21130
+ if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
21070
21131
  this.validate();
21071
21132
  }
21072
21133
  });
@@ -21129,49 +21190,40 @@ class AuroCombobox extends AuroElement {
21129
21190
  if (this._syncingDisplayValue) {
21130
21191
  return;
21131
21192
  }
21132
- this._syncingBibValue = true;
21133
- this.input.value = this.inputInBib.value;
21134
- this.input.updateComplete.then(() => {
21135
- this._syncingBibValue = false;
21136
- });
21137
21193
 
21138
- // Run filtering inline — the re-entrant event won't reach this code.
21139
- this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21140
- this.optionActive = null;
21141
-
21142
- // In suggestion mode, keep the combobox value in sync with the typed
21143
- // text so that freeform values are captured (mirroring the non-fullscreen
21144
- // path). Clear the selection when the input is emptied.
21145
- if (this.behavior === 'suggestion') {
21146
- this.value = this.inputInBib.value || undefined;
21194
+ // Filtering runs via re-entrance: writing this.input.value below
21195
+ // dispatches an 'input' event that the trigger's listener routes to
21196
+ // handleTriggerInputValueChange, which refreshes the menu filter.
21197
+ if (this.input.value !== this.inputInBib.value) {
21198
+ this._syncingBibValue = true;
21199
+ this.input.value = this.inputInBib.value;
21200
+ this.input.updateComplete.then(() => {
21201
+ this._syncingBibValue = false;
21202
+ });
21147
21203
  }
21148
21204
 
21149
- this.handleMenuOptions();
21150
21205
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21151
- return;
21206
+ } else if (event.target === this.input) {
21207
+
21208
+ // Also sync the native input immediately so keystrokes arriving
21209
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
21210
+ // fullscreen transition) operate on the correct content.
21211
+ // Skip the next programmatic input event to prevent the patched setter
21212
+ // from re-entering handleInputValueChange via the bib path.
21213
+ const bibNativeInput = this.inputInBib.inputElement;
21214
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21215
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
21216
+ bibNativeInput.value = this.input.value || '';
21217
+ }
21152
21218
  }
21153
21219
 
21220
+ this.value = this.input.value;
21221
+
21154
21222
  // Ignore re-entrant input events caused by programmatic value sets.
21155
21223
  if (this._syncingBibValue || this._syncingDisplayValue) {
21156
21224
  return;
21157
21225
  }
21158
21226
 
21159
- this.inputInBib.value = this.input.value;
21160
-
21161
- // Also sync the native input immediately so keystrokes arriving
21162
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21163
- // fullscreen transition) operate on the correct content.
21164
- // Skip the next programmatic input event to prevent the patched setter
21165
- // from re-entering handleInputValueChange via the bib path.
21166
- const bibNativeInput = this.inputInBib.inputElement;
21167
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21168
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21169
- bibNativeInput.value = this.input.value || '';
21170
- }
21171
-
21172
- this.menu.matchWord = normalizeFilterValue(this.input.value);
21173
- this.optionActive = null;
21174
-
21175
21227
  if (this.behavior === 'suggestion') {
21176
21228
  this.value = this.input.value;
21177
21229
  }
@@ -21179,7 +21231,6 @@ class AuroCombobox extends AuroElement {
21179
21231
  if (!this.input.value && !this._clearing) {
21180
21232
  this.clear();
21181
21233
  }
21182
- this.handleMenuOptions();
21183
21234
 
21184
21235
  // Validate only if the value was set programmatically (not during user
21185
21236
  // interaction). In fullscreen dialog mode, componentHasFocus returns false
@@ -21189,34 +21240,44 @@ class AuroCombobox extends AuroElement {
21189
21240
  this.validate();
21190
21241
  }
21191
21242
 
21192
- if (this.input.value && this.input.value.length === 0) {
21193
- // Hide menu if value is empty, otherwise show if there are available suggestions
21194
- this.hideBib();
21195
- } else if (this.menu.loading) {
21196
- // if input has value but menu is loading, show bib immediately
21197
- this.showBib();
21198
- } else if (this.availableOptions.length === 0 && !this.dropdown.isBibFullscreen) {
21199
- // Force dropdown bib to hide if input value has no matching suggestions
21200
- this.hideBib();
21243
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21244
+ }
21245
+
21246
+ /**
21247
+ * Handles input value changes originating from the trigger input.
21248
+ * Refreshes menu options and filtering, delegates to handleInputValueChange
21249
+ * for value synchronization, and manages fullscreen bib focus.
21250
+ * @private
21251
+ * @param {Event} event - The input event from the trigger input element.
21252
+ * @returns {void}
21253
+ */
21254
+ handleTriggerInputValueChange(event) {
21255
+ // 'input' fires for every user-initiated value change — typing, paste,
21256
+ // IME composition end, dead-key composition, drag-drop. Flip _userTyped
21257
+ // here so updated('availableOptions') auto-opens the bib for sources
21258
+ // that keydown alone misses: paste fires no keydown, IME uses
21259
+ // key='Process', and dead keys produce multi-char keys (all bypass the
21260
+ // prior keydown.key.length===1 gate). Skip programmatic syncs.
21261
+ if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
21262
+ this._userTyped = true;
21201
21263
  }
21202
21264
 
21203
- // iOS virtual keyboard retention: when in fullscreen mode, ensure the
21204
- // dialog opens and the bib input is focused synchronously within the
21205
- // input event (user gesture) chain. Without this, Lit's async update
21206
- // cycle delays showModal() past the user activation window, causing
21207
- // iOS Safari to dismiss the virtual keyboard when the fullscreen
21208
- // dialog opens — the user then has to tap the input again to resume
21209
- // typing.
21210
- if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
21211
- if (!this.dropdown.isPopoverVisible) {
21212
- this.showBib();
21213
- }
21214
- if (this.dropdown.isPopoverVisible) {
21215
- this.setInputFocus();
21216
- }
21265
+ this.handleMenuOptions();
21266
+ this.optionActive = null;
21267
+
21268
+ if (this.value === this.input.value) {
21269
+ return;
21217
21270
  }
21218
21271
 
21219
- this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21272
+ this.handleInputValueChange(event);
21273
+
21274
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
21275
+ this.setInputFocus();
21276
+ }
21277
+
21278
+ if (this._programmaticFilterRefresh) {
21279
+ this._programmaticFilterRefresh = false;
21280
+ }
21220
21281
  }
21221
21282
 
21222
21283
  /**
@@ -21284,6 +21345,11 @@ class AuroCombobox extends AuroElement {
21284
21345
  disconnectedCallback() {
21285
21346
  super.disconnectedCallback();
21286
21347
  this._inFullscreenTransition = false;
21348
+ // Cancel any outstanding timers so detached callbacks don't fire on
21349
+ // disposed DOM — most are no-ops, but configureMenu's racing-condition
21350
+ // retry would otherwise keep rescheduling itself indefinitely.
21351
+ this._pendingTimers.forEach((id) => clearTimeout(id));
21352
+ this._pendingTimers.clear();
21287
21353
  }
21288
21354
 
21289
21355
  firstUpdated() {
@@ -21325,7 +21391,7 @@ class AuroCombobox extends AuroElement {
21325
21391
  this.menu.value = value;
21326
21392
  // Backup clear: if menu.value === menu.optionSelected.value already, the
21327
21393
  // listener won't fire and the flag would swallow the next user click.
21328
- setTimeout(() => {
21394
+ this._scheduleTimer(() => {
21329
21395
  this._pendingMenuValueSync = false;
21330
21396
  }, 0);
21331
21397
  }
@@ -21360,6 +21426,17 @@ class AuroCombobox extends AuroElement {
21360
21426
  this.optionSelected = undefined;
21361
21427
  this.value = undefined;
21362
21428
 
21429
+ // Clear the appended displayValue clone in the trigger if present.
21430
+ // :not(slot) excludes the template's <slot name="displayValue"
21431
+ // slot="displayValue"> forwarder (line 1816), which also has
21432
+ // slot="displayValue" and would otherwise be matched first and removed,
21433
+ // permanently breaking the consumer-provided displayValue slot.
21434
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
21435
+
21436
+ if (displayValueInTrigger) {
21437
+ displayValueInTrigger.remove();
21438
+ }
21439
+
21363
21440
  if (this.input.value) {
21364
21441
  this.input.clear();
21365
21442
  }
@@ -21383,56 +21460,50 @@ class AuroCombobox extends AuroElement {
21383
21460
  }
21384
21461
 
21385
21462
  updated(changedProperties) {
21386
- // After the component is ready, send direct value changes to auro-menu.
21463
+ // After the component is ready, propagate direct changes down to child components.
21387
21464
  if (changedProperties.has('value')) {
21388
- if (this.value && this.value.length > 0) {
21389
- this.hasValue = true;
21390
- } else {
21391
- this.hasValue = false;
21465
+ // Only flag programmatic refreshes — user-typed value changes must not
21466
+ // suppress the availableOptions branch's showBib(). Firefox batches
21467
+ // 'value' and 'availableOptions' into the same updated() call, so
21468
+ // setting the flag unconditionally here masks the user-typed open path.
21469
+ if (!this._userTyped) {
21470
+ this._programmaticFilterRefresh = true;
21392
21471
  }
21393
21472
 
21394
- if (this.hasValue && !this.input.value && (!this.menu.options || this.menu.options.length === 0)) {
21395
- this.input.value = this.value;
21396
- }
21473
+ if (this.input.value !== this.value) {
21474
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21475
+ // menu.value leaves the previously-selected option element pinned
21476
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21477
+ // would then write its stale .value back into combobox.value
21478
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21479
+ if (this.menu.value || this.menu.optionSelected) {
21480
+ this.menu.clearSelection();
21481
+ }
21397
21482
 
21398
- // Sync menu.value only when an option actually matches, or when filter
21399
- // mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
21400
- // listener at line 1206 clears combobox.value, mirroring select's
21401
- // pattern). Otherwise sync the input display for freeform values.
21402
- if (this.menu.options && this.menu.options.length > 0) {
21403
- if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
21404
- this.setMenuValue(this.value);
21405
- } else {
21406
- // Clear menu.value AND menu.optionSelected together. Clearing only
21407
- // menu.value leaves the previously-selected option element pinned
21408
- // as menu.optionSelected; a later auroMenu-selectedOption event
21409
- // would then write its stale .value back into combobox.value.
21410
- if (this.menu.value || this.menu.optionSelected) {
21411
- this.menu.clearSelection();
21412
- }
21413
- // Suggestion-mode freeform value: sync the trigger + bib to show it,
21414
- // then refresh the filter once the inputs flush (handleInputValueChange
21415
- // bailed on _syncingDisplayValue).
21416
- const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21417
- if (syncPromise) {
21418
- syncPromise.then(() => {
21419
- this._programmaticFilterRefresh = true;
21420
- this.handleMenuOptions();
21421
- setTimeout(() => {
21422
- this._programmaticFilterRefresh = false;
21423
- }, 0);
21424
- });
21425
- }
21483
+ if (!this.persistInput) {
21484
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21485
+ }
21486
+
21487
+ // Programmatic value with no matching option: updateFilter will close
21488
+ // the bib silently (see line 648 no noMatchOption + 0 results
21489
+ // hides). Announce so screen-reader users hear the request was
21490
+ // dropped. Gated on `input.value !== this.value` so this never fires
21491
+ // for user typing that path always reconciles input.value to
21492
+ // this.value before updated() runs.
21493
+ if (
21494
+ this.value &&
21495
+ this.menu &&
21496
+ this.menu.options &&
21497
+ this.menu.options.length > 0 &&
21498
+ !this.menu.options.some((opt) => opt.value === this.value)
21499
+ ) {
21500
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21426
21501
  }
21427
21502
  }
21503
+
21428
21504
  if (!this.value) {
21429
21505
  this.clear();
21430
21506
  }
21431
- if (this.value && !this.componentHasFocus) {
21432
- // If the value got set programmatically make sure we hide the bib
21433
- // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
21434
- this.hideBib();
21435
- }
21436
21507
 
21437
21508
  // Sync the input and match word, but don't directly set menu.value again
21438
21509
  if (this.menu) {
@@ -21445,7 +21516,7 @@ class AuroCombobox extends AuroElement {
21445
21516
  composed: true,
21446
21517
  detail: {
21447
21518
  optionSelected: this.menu.optionSelected,
21448
- value: this.menu.value
21519
+ value: this.value
21449
21520
  }
21450
21521
  }));
21451
21522
 
@@ -21468,16 +21539,30 @@ class AuroCombobox extends AuroElement {
21468
21539
  // from a programmatic filter refresh after a value swap — the user
21469
21540
  // didn't interact, so we shouldn't auto-open the bib (especially for
21470
21541
  // the no-match path where the existing condition unconditionally fires).
21471
- if (this._programmaticFilterRefresh) {
21472
- this._programmaticFilterRefresh = false;
21473
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21474
- this.showBib();
21542
+ if ((this.menu && !this._programmaticFilterRefresh)) {
21543
+ if (
21544
+ this.availableOptions.length > 0 ||
21545
+ this.menu.loading ||
21546
+ this.noMatchOption
21547
+ ) {
21548
+ if (this._userTyped) {
21549
+ if (!this.dropdownOpen) {
21550
+ this.showBib();
21551
+ }
21552
+ this._userTyped = false;
21553
+ }
21554
+ }
21555
+
21475
21556
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21476
21557
  this.activateFirstEnabledAvailableOption();
21477
21558
  }
21478
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21559
+ } else if (!this.dropdown.isBibFullscreen) {
21479
21560
  this.hideBib();
21480
21561
  }
21562
+
21563
+ if (this._programmaticFilterRefresh) {
21564
+ this._programmaticFilterRefresh = false;
21565
+ }
21481
21566
  }
21482
21567
 
21483
21568
  if (changedProperties.has('error')) {
@@ -21619,10 +21704,10 @@ class AuroCombobox extends AuroElement {
21619
21704
  shape="${this.shape}"
21620
21705
  size="${this.size}">
21621
21706
  <${this.inputTag}
21622
- @input="${this.handleInputValueChange}"
21707
+ @input="${this.handleTriggerInputValueChange}"
21623
21708
  appearance="${this.onDark ? 'inverse' : this.appearance}"
21624
21709
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
21625
- .a11yExpanded="${this.triggerExpandedState}"
21710
+ .a11yExpanded="${this.dropdownOpen}"
21626
21711
  .a11yControls="${this.dropdownId}"
21627
21712
  .autocomplete="${this.autocomplete}"
21628
21713
  .inputmode="${this.inputmode}"
@@ -23227,14 +23312,10 @@ async function dynamicMenuExample() {
23227
23312
  // Helper function that generates HTML for menuoptions
23228
23313
  function generateMenuOptionHtml(menu, label, value) {
23229
23314
  const option = document.createElement('auro-menuoption');
23230
- const displayValue = document.createElement('div');
23231
- displayValue.setAttribute("slot", "displayValue");
23232
- displayValue.innerHTML = value;
23233
23315
 
23234
23316
  option.value = value;
23235
23317
  option.innerHTML = label;
23236
23318
  menu.appendChild(option);
23237
- option.appendChild(displayValue);
23238
23319
  }
23239
23320
 
23240
23321
  // Main javascript that runs all JS to create example