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

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 (141) 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/README.md +1 -1
  9. package/components/combobox/demo/customize.md +2 -10
  10. package/components/combobox/demo/customize.min.js +386 -305
  11. package/components/combobox/demo/getting-started.min.js +386 -301
  12. package/components/combobox/demo/index.md +1 -1
  13. package/components/combobox/demo/index.min.js +386 -301
  14. package/components/combobox/demo/keyboard-behavior.md +2 -142
  15. package/components/combobox/demo/readme.md +1 -1
  16. package/components/combobox/demo/why-combobox.md +2 -2
  17. package/components/combobox/dist/auro-combobox.d.ts +23 -16
  18. package/components/combobox/dist/index.js +385 -300
  19. package/components/combobox/dist/registered.js +385 -300
  20. package/components/counter/demo/customize.min.js +18 -8
  21. package/components/counter/demo/index.min.js +18 -8
  22. package/components/counter/dist/index.js +18 -8
  23. package/components/counter/dist/registered.js +18 -8
  24. package/components/datepicker/demo/accessibility.md +2 -1
  25. package/components/datepicker/demo/api.md +63 -62
  26. package/components/datepicker/demo/customize.md +12 -1
  27. package/components/datepicker/demo/customize.min.js +932 -335
  28. package/components/datepicker/demo/index.min.js +932 -335
  29. package/components/datepicker/dist/{src/auro-calendar-cell.d.ts → auro-calendar-cell.d.ts} +20 -3
  30. package/components/datepicker/dist/{src/auro-calendar.d.ts → auro-calendar.d.ts} +121 -7
  31. package/components/datepicker/dist/{src/auro-datepicker.d.ts → auro-datepicker.d.ts} +78 -5
  32. package/components/datepicker/dist/blackoutUtils.d.ts +43 -0
  33. package/components/datepicker/dist/index.js +932 -335
  34. package/components/datepicker/dist/registered.js +932 -335
  35. package/components/dropdown/demo/customize.min.js +2 -1
  36. package/components/dropdown/demo/getting-started.min.js +2 -1
  37. package/components/dropdown/demo/index.min.js +2 -1
  38. package/components/dropdown/dist/index.js +2 -1
  39. package/components/dropdown/dist/registered.js +2 -1
  40. package/components/form/demo/customize.min.js +1690 -826
  41. package/components/form/demo/getting-started.min.js +1690 -826
  42. package/components/form/demo/index.min.js +1690 -826
  43. package/components/form/demo/registerDemoDeps.min.js +1690 -826
  44. package/components/input/demo/api.md +5 -5
  45. package/components/input/demo/customize.md +8 -8
  46. package/components/input/demo/customize.min.js +137 -95
  47. package/components/input/demo/getting-started.min.js +137 -95
  48. package/components/input/demo/index.min.js +137 -95
  49. package/components/input/dist/base-input.d.ts +33 -44
  50. package/components/input/dist/index.js +150 -96
  51. package/components/input/dist/registered.js +137 -95
  52. package/components/radio/demo/customize.min.js +8 -6
  53. package/components/radio/demo/getting-started.min.js +8 -6
  54. package/components/radio/demo/index.min.js +8 -6
  55. package/components/radio/dist/index.js +8 -6
  56. package/components/radio/dist/registered.js +8 -6
  57. package/components/select/demo/customize.md +86 -0
  58. package/components/select/demo/customize.min.js +194 -68
  59. package/components/select/demo/getting-started.min.js +196 -115
  60. package/components/select/demo/index.min.js +194 -68
  61. package/components/select/demo/keyboard-behavior.md +18 -4
  62. package/components/select/dist/auro-select.d.ts +20 -8
  63. package/components/select/dist/index.js +194 -68
  64. package/components/select/dist/registered.js +194 -68
  65. package/custom-elements.json +2365 -1819
  66. package/package.json +6 -2
  67. package/components/checkbox/demo/customize.js +0 -22
  68. package/components/checkbox/demo/demo-support.js +0 -1
  69. package/components/checkbox/demo/getting-started.js +0 -22
  70. package/components/checkbox/demo/index.js +0 -8
  71. package/components/checkbox/demo/styles.scss +0 -1
  72. package/components/combobox/demo/customize.js +0 -24
  73. package/components/combobox/demo/demo-support.js +0 -1
  74. package/components/combobox/demo/getting-started.js +0 -21
  75. package/components/combobox/demo/index.js +0 -23
  76. package/components/combobox/demo/styles.scss +0 -1
  77. package/components/counter/demo/customize.js +0 -21
  78. package/components/counter/demo/demo-support.js +0 -1
  79. package/components/counter/demo/index.js +0 -21
  80. package/components/counter/demo/styles.scss +0 -1
  81. package/components/datepicker/demo/customize.js +0 -17
  82. package/components/datepicker/demo/demo-support.js +0 -1
  83. package/components/datepicker/demo/index.js +0 -23
  84. package/components/datepicker/demo/styles.scss +0 -1
  85. package/components/dropdown/demo/customize.js +0 -9
  86. package/components/dropdown/demo/demo-support.js +0 -1
  87. package/components/dropdown/demo/getting-started.js +0 -9
  88. package/components/dropdown/demo/index.js +0 -16
  89. package/components/dropdown/demo/styles.scss +0 -1
  90. package/components/form/demo/customize.js +0 -28
  91. package/components/form/demo/demo-support.js +0 -1
  92. package/components/form/demo/getting-started.js +0 -9
  93. package/components/form/demo/index.js +0 -5
  94. package/components/form/demo/registerDemoDeps.js +0 -23
  95. package/components/form/demo/styles.scss +0 -1
  96. package/components/input/demo/customize.js +0 -25
  97. package/components/input/demo/demo-support.js +0 -1
  98. package/components/input/demo/getting-started.js +0 -8
  99. package/components/input/demo/index.js +0 -8
  100. package/components/input/demo/styles.css +0 -974
  101. package/components/input/demo/styles.scss +0 -1
  102. package/components/menu/demo/demo-support.js +0 -1
  103. package/components/menu/demo/index.js +0 -25
  104. package/components/menu/demo/styles.scss +0 -1
  105. package/components/radio/demo/customize.js +0 -22
  106. package/components/radio/demo/demo-support.js +0 -1
  107. package/components/radio/demo/getting-started.js +0 -24
  108. package/components/radio/demo/index.js +0 -8
  109. package/components/radio/demo/styles.scss +0 -1
  110. package/components/select/demo/customize.js +0 -11
  111. package/components/select/demo/demo-support.js +0 -1
  112. package/components/select/demo/getting-started.js +0 -26
  113. package/components/select/demo/index.js +0 -11
  114. package/components/select/demo/styles.scss +0 -1
  115. /package/components/datepicker/dist/{src/auro-calendar-month.d.ts → auro-calendar-month.d.ts} +0 -0
  116. /package/components/datepicker/dist/{src/buttonVersion.d.ts → buttonVersion.d.ts} +0 -0
  117. /package/components/datepicker/dist/{src/datepickerKeyboardStrategy.d.ts → datepickerKeyboardStrategy.d.ts} +0 -0
  118. /package/components/datepicker/dist/{src/iconVersion.d.ts → iconVersion.d.ts} +0 -0
  119. /package/components/datepicker/dist/{src/index.d.ts → index.d.ts} +0 -0
  120. /package/components/datepicker/dist/{src/popoverVersion.d.ts → popoverVersion.d.ts} +0 -0
  121. /package/components/datepicker/dist/{src/styles → styles}/classic/color-css.d.ts +0 -0
  122. /package/components/datepicker/dist/{src/styles → styles}/classic/style-css.d.ts +0 -0
  123. /package/components/datepicker/dist/{src/styles → styles}/color-calendar-css.d.ts +0 -0
  124. /package/components/datepicker/dist/{src/styles → styles}/color-cell-css.d.ts +0 -0
  125. /package/components/datepicker/dist/{src/styles → styles}/color-css.d.ts +0 -0
  126. /package/components/datepicker/dist/{src/styles → styles}/color-month-css.d.ts +0 -0
  127. /package/components/datepicker/dist/{src/styles → styles}/shapeSize-css.d.ts +0 -0
  128. /package/components/datepicker/dist/{src/styles → styles}/snowflake/color-css.d.ts +0 -0
  129. /package/components/datepicker/dist/{src/styles → styles}/snowflake/style-css.d.ts +0 -0
  130. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-cell-css.d.ts +0 -0
  131. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-css.d.ts +0 -0
  132. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-month-css.d.ts +0 -0
  133. /package/components/datepicker/dist/{src/styles → styles}/style-css.d.ts +0 -0
  134. /package/components/datepicker/dist/{src/styles → styles}/tokens-css.d.ts +0 -0
  135. /package/components/datepicker/dist/{src/utilities.d.ts → utilities.d.ts} +0 -0
  136. /package/components/datepicker/dist/{src/utilitiesCalendar.d.ts → utilitiesCalendar.d.ts} +0 -0
  137. /package/components/datepicker/dist/{src/utilitiesCalendarRender.d.ts → utilitiesCalendarRender.d.ts} +0 -0
  138. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/day.d.ts +0 -0
  139. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-calendar.d.ts +0 -0
  140. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-cell.d.ts +0 -0
  141. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker.d.ts +0 -0
@@ -500,15 +500,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
500
500
  );
501
501
  }
502
502
 
503
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
504
- if (this.auroInputElements?.length === 2) {
505
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
503
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
504
+
505
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
506
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
507
+ // field (datepicker is the intended consumer — start/end are independently required).
508
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
509
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
506
510
  hasValue = false;
507
511
  }
508
512
  }
509
513
 
510
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
511
-
512
514
  if (isCombobox) {
513
515
 
514
516
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -883,11 +885,7 @@ const comboboxKeyboardStrategy = {
883
885
 
884
886
  // navigate if bib is open otherwise open it
885
887
  if (component.dropdown.isPopoverVisible) {
886
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
887
- component.activateLastEnabledAvailableOption();
888
- } else {
889
- navigateArrow(component, 'down');
890
- }
888
+ navigateArrow(component, 'down');
891
889
  } else {
892
890
  component.showBib();
893
891
  }
@@ -906,11 +904,7 @@ const comboboxKeyboardStrategy = {
906
904
 
907
905
  // navigate if bib is open otherwise open it
908
906
  if (component.dropdown.isPopoverVisible) {
909
- if (evt.altKey || evt.ctrlKey || evt.metaKey) {
910
- component.activateFirstEnabledAvailableOption();
911
- } else {
912
- navigateArrow(component, 'up');
913
- }
907
+ navigateArrow(component, 'up');
914
908
  } else {
915
909
  component.showBib();
916
910
  }
@@ -926,13 +920,18 @@ const comboboxKeyboardStrategy = {
926
920
  },
927
921
 
928
922
  Enter(component, evt, ctx) {
923
+ // Forms should not submit on Enter from a combobox, regardless of which
924
+ // child element (input, clear button, menu) is focused.
925
+ evt.stopPropagation();
926
+
929
927
  if (isClearBtnFocused(ctx)) {
930
- // If the clear button has focus, let the browser activate it normally.
931
- // stopPropagation prevents parent containers (e.g., forms) from treating
932
- // Enter as a submit, but we must NOT call preventDefault — that would
933
- // block the browser's built-in "Enter activates focused button" behavior.
934
- evt.stopPropagation();
935
- } else if (ctx.isExpanded && component.menu.optionActive) {
928
+ // Let the browser dispatch Enter to the focused clear button so its
929
+ // built-in activation fires and clears the selection. Do NOT call
930
+ // preventDefault — that would block the activation.
931
+ return;
932
+ }
933
+
934
+ if (ctx.isExpanded && component.menu.optionActive) {
936
935
  reconcileMenuIndex(component.menu);
937
936
  component.menu.makeSelection();
938
937
 
@@ -941,14 +940,8 @@ const comboboxKeyboardStrategy = {
941
940
  }
942
941
 
943
942
  evt.preventDefault();
944
- evt.stopPropagation();
945
943
  } else {
946
- // Prevent the keypress from bubbling to parent containers (e.g., forms)
947
- // which could interpret Enter as a submit or trigger other unintended behavior.
948
- // This is safe because showBib() opens the dialog programmatically,
949
- // not via event propagation.
950
944
  evt.preventDefault();
951
- evt.stopPropagation();
952
945
  component.showBib();
953
946
  }
954
947
  },
@@ -993,7 +986,7 @@ const comboboxKeyboardStrategy = {
993
986
  component.setClearBtnFocus();
994
987
  }
995
988
  }
996
- },
989
+ }
997
990
  };
998
991
 
999
992
  /**
@@ -4888,7 +4881,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4888
4881
  }
4889
4882
  };
4890
4883
 
4891
- var formkitVersion$2 = '202606231948';
4884
+ var formkitVersion$2 = '202607010104';
4892
4885
 
4893
4886
  let AuroElement$2 = class AuroElement extends i$3 {
4894
4887
  static get properties() {
@@ -6160,6 +6153,7 @@ class AuroDropdown extends AuroElement$2 {
6160
6153
  role="${o(this.triggerContentFocusable ? undefined : this.a11yRole)}"
6161
6154
  aria-expanded="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
6162
6155
  aria-controls="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.dropdownId)}"
6156
+ aria-haspopup="${o(this.a11yRole === 'combobox' && !this.triggerContentFocusable ? 'listbox' : undefined)}"
6163
6157
  aria-labelledby="${o(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
6164
6158
  aria-disabled="${o(this.disabled ? 'true' : undefined)}"
6165
6159
  @focusin="${this.handleFocusin}"
@@ -10669,15 +10663,17 @@ class AuroFormValidation {
10669
10663
  );
10670
10664
  }
10671
10665
 
10672
- // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false;
10673
- if (this.auroInputElements?.length === 2) {
10674
- if (!this.auroInputElements[1].value || this.auroInputElements[1].length === 0) {
10666
+ const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10667
+
10668
+ // If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
10669
+ // Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
10670
+ // field (datepicker is the intended consumer — start/end are independently required).
10671
+ if (this.auroInputElements?.length === 2 && !isCombobox) {
10672
+ if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
10675
10673
  hasValue = false;
10676
10674
  }
10677
10675
  }
10678
10676
 
10679
- const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
10680
-
10681
10677
  if (isCombobox) {
10682
10678
 
10683
10679
  if (!elem.persistInput || elem.behavior === "filter") {
@@ -16613,6 +16609,13 @@ class AuroInputUtilities {
16613
16609
  * @returns {string | undefined}
16614
16610
  */
16615
16611
  toFormattedValue(valueObject, format) {
16612
+ // Exposed publicly via `AuroInputUtil.toFormattedValue`, so a missing
16613
+ // `format` argument has to fail gracefully rather than throw on
16614
+ // `format.toLowerCase()`. Treat any falsy value (undefined, null, '')
16615
+ // as "no format → no display value".
16616
+ if (!format) {
16617
+ return undefined;
16618
+ }
16616
16619
  const normalizedFormat = format.toLowerCase();
16617
16620
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16618
16621
 
@@ -16764,76 +16767,19 @@ class BaseInput extends AuroElement$1 {
16764
16767
  constructor() {
16765
16768
  super();
16766
16769
 
16767
- this.appearance = "default";
16768
- this.disabled = false;
16769
- this.layout = 'classic';
16770
- this.locale = 'en-US';
16771
- this.max = undefined;
16772
- this.maxLength = undefined;
16773
- this.min = undefined;
16774
- this.minLength = undefined;
16775
- this.required = false;
16776
- this.onDark = false;
16777
- this.setCustomValidityForType = undefined;
16778
- this.size = 'lg';
16779
- this.shape = 'classic';
16780
- this.value = undefined;
16781
-
16782
- this._initializePrivateDefaults();
16783
- }
16784
-
16785
- /**
16786
- * Internal Defaults.
16787
- * @private
16788
- * @returns {void}
16789
- */
16790
- _initializePrivateDefaults() {
16770
+ // Single-source initialization. Alphabetized so duplicate or stale
16771
+ // defaults are immediately obvious on a diff. Every field is assigned
16772
+ // exactly once here (previously the constructor + the old
16773
+ // `_initializePrivateDefaults` overlapped — both wrote ~14 of the same
16774
+ // fields and double-allocated `util` and `validation`, discarding the
16775
+ // first instance). `validation` is now allocated exactly once; `util`
16776
+ // is seeded here with an en-US default and then rebuilt in
16777
+ // `connectedCallback` once the consumer-resolved locale is available,
16778
+ // so a parent (datepicker/combobox) calling `validate()` synchronously
16779
+ // during its own update cycle sees a populated util instance.
16791
16780
  this.activeLabel = false;
16792
- this.appearance = "default";
16793
- this.icon = false;
16794
- this.disabled = false;
16795
- this.dvInputOnly = false;
16796
- this.hideLabelVisually = false;
16797
- this.max = undefined;
16798
- this.maxLength = undefined;
16799
- this.min = undefined;
16800
- this.minLength = undefined;
16801
- this.noValidate = false;
16802
- this.onDark = false;
16803
- this.required = false;
16804
- this.setCustomValidityForType = undefined;
16805
-
16806
- // Used for storing raw values returned from input mask.
16807
- this._rawMaskValue = undefined;
16808
-
16809
- /**
16810
- * @private
16811
- */
16812
- this.layout = 'classic';
16813
-
16814
- /**
16815
- * @private
16816
- */
16817
- this.shape = 'classic';
16818
-
16819
- /**
16820
- * @private
16821
- */
16822
- this.size = 'lg';
16823
-
16824
- this.touched = false;
16825
- this.util = new AuroInputUtilities({
16826
- locale: "en-US",
16827
- format: this.format
16828
- });
16829
- this.validation = new AuroFormValidation();
16830
- this.inputIconName = undefined;
16831
- this.showPassword = false;
16832
- this.validationCCLength = undefined;
16833
- this.hasValue = false;
16834
- this.label = 'Input label is undefined';
16835
-
16836
16781
 
16782
+ /** @private */
16837
16783
  this.allowedInputTypes = [
16838
16784
  "text",
16839
16785
  "number",
@@ -16842,7 +16788,9 @@ class BaseInput extends AuroElement$1 {
16842
16788
  "credit-card",
16843
16789
  "tel"
16844
16790
  ];
16845
- this.icon = false;
16791
+ this.appearance = "default";
16792
+
16793
+ /** @private */
16846
16794
  this.dateFormatMap = {
16847
16795
  'mm/dd/yyyy': 'dateMMDDYYYY',
16848
16796
  'dd/mm/yyyy': 'dateDDMMYYYY',
@@ -16859,27 +16807,62 @@ class BaseInput extends AuroElement$1 {
16859
16807
  'dd/mm': 'dateDDMM',
16860
16808
  'mm/dd': 'dateMMDD'
16861
16809
  };
16810
+ this.disabled = false;
16811
+
16812
+ /** @private */
16862
16813
  this.domHandler = new DomHandler();
16863
16814
  this.dvInputOnly = false;
16864
16815
  this.hasValue = false;
16816
+ this.hideLabelVisually = false;
16817
+ this.icon = false;
16818
+
16819
+ /** @private */
16865
16820
  this.inputIconName = undefined;
16821
+
16822
+ /** @private */
16866
16823
  this.label = 'Input label is undefined';
16824
+ this.layout = 'classic';
16825
+ this.locale = 'en-US';
16826
+ this.max = undefined;
16827
+ this.maxLength = undefined;
16828
+ this.min = undefined;
16829
+ this.minLength = undefined;
16867
16830
  this.noValidate = false;
16868
- this._rawMaskValue = undefined; // Used for storing raw values returned from input mask.
16831
+ this.onDark = false;
16832
+ // Raw values returned from the input mask before model normalization.
16833
+ this._rawMaskValue = undefined;
16834
+ this.required = false;
16835
+ this.setCustomValidityForType = undefined;
16836
+ // Credit Card is intentionally excluded — its mask manages the cursor
16837
+ // itself, and listing it here caused cursor placement issues in Safari.
16838
+ /** @private */
16869
16839
  this.setSelectionInputTypes = [
16870
16840
  "text",
16871
16841
  "password",
16872
16842
  "email"
16873
- ]; // Credit Card is not included as this caused cursor placement issues in Safari.
16843
+ ];
16844
+ this.shape = 'classic';
16845
+
16846
+ /** @private */
16874
16847
  this.showPassword = false;
16848
+ this.size = 'lg';
16875
16849
  this.touched = false;
16850
+
16851
+ /** @private */
16876
16852
  this.uniqueId = new UniqueId().create();
16853
+
16854
+ /** @private */
16877
16855
  this.util = new AuroInputUtilities({
16878
16856
  locale: this.locale,
16879
16857
  format: this.format
16880
16858
  });
16859
+
16860
+ /** @private */
16881
16861
  this.validation = new AuroFormValidation();
16862
+
16863
+ /** @private */
16882
16864
  this.validationCCLength = undefined;
16865
+ this.value = undefined;
16883
16866
  }
16884
16867
 
16885
16868
  // function to define props used within the scope of this component
@@ -17383,6 +17366,15 @@ class BaseInput extends AuroElement$1 {
17383
17366
 
17384
17367
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17385
17368
 
17369
+ // Normalize the format token to lowercase so case-mixed values supplied
17370
+ // via attribute (e.g. `format="MM/DD/YYYY"`) hit the `dateFormatMap`
17371
+ // lookup inside `setCustomHelpTextMessage`. Without this, an uppercase
17372
+ // format silently misses the map and leaves `setCustomValidityForType`
17373
+ // unset.
17374
+ if (this.format) {
17375
+ this.format = this.format.toLowerCase();
17376
+ }
17377
+
17386
17378
  // use validity message override if declared when initializing the component
17387
17379
  if (this.hasAttribute('setCustomValidity')) {
17388
17380
  this.ValidityMessageOverride = this.setCustomValidity;
@@ -17466,6 +17458,13 @@ class BaseInput extends AuroElement$1 {
17466
17458
 
17467
17459
  if (typeToI18n.includes(this.type)) {
17468
17460
  this.setCustomValidityForType = i18n(this.lang, this.type);
17461
+ // COVERAGE: this `else if` branch is unreachable in WTR. connectedCallback
17462
+ // (L682) calls configureDataForType, which at L1266-1268 assigns
17463
+ // `this.format = this.util.getDateMaskFromLocale().toLowerCase()` for any
17464
+ // type=date input whose `format` attribute is unset. That runs before
17465
+ // firstUpdated invokes setCustomHelpTextMessage, so `!this.format` is
17466
+ // always false here. Retained as a defensive fallback; the whole function
17467
+ // is @deprecated per AB#1557296 and slated for removal.
17469
17468
  } else if (!this.format && this.type === 'date') {
17470
17469
  this.setCustomValidityForType = i18n(this.lang, 'dateMMDDYYYY');
17471
17470
  } else if (this.dateFormatMap[this.format]) {
@@ -17755,6 +17754,38 @@ class BaseInput extends AuroElement$1 {
17755
17754
  this.touched = true;
17756
17755
  this.validation.validate(this);
17757
17756
  }
17757
+
17758
+ // Prevents cursor jumping in Safari. Setting `this.value` triggers a Lit
17759
+ // update that can re-render the input and reset the native cursor; we
17760
+ // capture the caret position before that update commits and restore it
17761
+ // via `setSelectionRange` once the update has flushed. Gated on
17762
+ // `setSelectionInputTypes` so credit-card (and other masked types whose
17763
+ // formatter manages the cursor itself) doesn't get a competing write.
17764
+ // Capture the caret position INSIDE the gate — reading `selectionStart`
17765
+ // on input types that don't support text selection (number, email in
17766
+ // some browsers) throws InvalidStateError, which would crash all input
17767
+ // handling. Wrap the read in try/catch belt-and-suspenders even though
17768
+ // the gated types currently support it, since the list is a public
17769
+ // property a consumer could mutate.
17770
+ if (this.setSelectionInputTypes.includes(this.type)) {
17771
+ let selectionStart = null;
17772
+ try {
17773
+ ({ selectionStart } = this.inputElement);
17774
+ } catch (error) { // eslint-disable-line no-unused-vars
17775
+ return;
17776
+ }
17777
+ if (typeof selectionStart !== 'number') {
17778
+ return;
17779
+ }
17780
+ this.updateComplete.then(() => {
17781
+ try {
17782
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
17783
+ } catch (error) { // eslint-disable-line no-unused-vars
17784
+ // Some input types (number/email in certain UAs) throw on
17785
+ // setSelectionRange; swallow and let the native cursor stand.
17786
+ }
17787
+ });
17788
+ }
17758
17789
  }
17759
17790
 
17760
17791
  /**
@@ -17888,7 +17919,7 @@ class BaseInput extends AuroElement$1 {
17888
17919
  */
17889
17920
  get placeholderStr() {
17890
17921
  if (!this.placeholder && this.type === 'date') {
17891
- return this.format ? this.format.toUpperCase() : 'MM/DD/YYYY';
17922
+ return this.format.toUpperCase();
17892
17923
  }
17893
17924
  return this.placeholder || "";
17894
17925
  }
@@ -18379,7 +18410,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18379
18410
  }
18380
18411
  };
18381
18412
 
18382
- var formkitVersion$1 = '202606231948';
18413
+ var formkitVersion$1 = '202607010104';
18383
18414
 
18384
18415
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18385
18416
  // See LICENSE in the project root for license information.
@@ -18882,6 +18913,7 @@ class AuroInput extends BaseInput {
18882
18913
  <${this.buttonTag}
18883
18914
  @click="${this.handleClickShowPassword}"
18884
18915
  appearance="${this.onDark ? 'inverse' : this.appearance}"
18916
+ aria-pressed="${this.showPassword ? 'true' : 'false'}"
18885
18917
  class="notificationBtn passwordBtn"
18886
18918
  shape="circle"
18887
18919
  size="sm"
@@ -18955,25 +18987,29 @@ class AuroInput extends BaseInput {
18955
18987
  * @returns {html} - Returns HTML for the help text and error message.
18956
18988
  */
18957
18989
  renderHtmlHelpText() {
18990
+ // Single `<p>` with stable identity across validity transitions —
18991
+ // previously two distinct templates (valid vs invalid) caused Lit to
18992
+ // replace the node entirely on a flip, and VoiceOver wouldn't
18993
+ // re-announce because the live-region element it was watching had been
18994
+ // removed and a new one inserted. Keeping one node means the `role`,
18995
+ // `aria-live`, and text content all change in-place, which AT does
18996
+ // observe and announce.
18997
+ const isError = this.validity && this.validity !== 'valid';
18958
18998
  return u$7`
18959
- ${!this.validity || this.validity === undefined || this.validity === 'valid'
18960
- ? u$7`
18961
- <${this.helpTextTag}
18962
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18963
- <p id="${this.uniqueId}" part="helpText">
18964
- <slot name="helpText">${this.getHelpText()}</slot>
18965
- </p>
18966
- </${this.helpTextTag}>
18967
- `
18968
- : u$7`
18969
- <${this.helpTextTag} error
18970
- appearance="${this.onDark ? 'inverse' : this.appearance}">
18971
- <p id="${this.uniqueId}" role="alert" aria-live="assertive" part="helpText">
18972
- ${this.errorMessage}
18973
- </p>
18974
- </${this.helpTextTag}>
18975
- `
18976
- }
18999
+ <${this.helpTextTag}
19000
+ appearance="${this.onDark ? 'inverse' : this.appearance}"
19001
+ ?error=${isError}>
19002
+ <p
19003
+ id="${this.uniqueId}"
19004
+ part="helpText"
19005
+ role="${o(isError ? 'alert' : undefined)}"
19006
+ aria-live="${o(isError ? 'assertive' : undefined)}">
19007
+ ${isError
19008
+ ? this.errorMessage
19009
+ : u$7`<slot name="helpText">${this.getHelpText()}</slot>`
19010
+ }
19011
+ </p>
19012
+ </${this.helpTextTag}>
18977
19013
  `;
18978
19014
  }
18979
19015
 
@@ -19105,7 +19141,7 @@ class AuroInput extends BaseInput {
19105
19141
 
19106
19142
  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)}`;
19107
19143
 
19108
- 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)}`;
19144
+ 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)}`;
19109
19145
 
19110
19146
  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)}`;
19111
19147
 
@@ -19500,7 +19536,7 @@ class AuroBibtemplate extends i$3 {
19500
19536
  }
19501
19537
  }
19502
19538
 
19503
- var formkitVersion = '202606231948';
19539
+ var formkitVersion = '202607010104';
19504
19540
 
19505
19541
  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}`;
19506
19542
 
@@ -19868,12 +19904,23 @@ function getOptionLabel(option) {
19868
19904
  if (!option) {
19869
19905
  return '';
19870
19906
  }
19871
- const clone = option.cloneNode(true);
19872
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19873
- if (displayValueEl) {
19874
- displayValueEl.remove();
19907
+
19908
+ // Consumer-provided override: short-circuit the DOM walk entirely.
19909
+ if (option.dataset && option.dataset.label) {
19910
+ return option.dataset.label;
19911
+ }
19912
+
19913
+ // Walk direct children — the `slot` attribute only applies to direct children
19914
+ // of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
19915
+ // querySelector allocation that ran on every keystroke via syncValuesAndStates.
19916
+ let text = '';
19917
+ for (const node of option.childNodes) {
19918
+ const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
19919
+ if (!isDisplayValueSlot) {
19920
+ text += node.textContent || '';
19921
+ }
19875
19922
  }
19876
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19923
+ return text.replace(/\s+/gu, ' ').trim();
19877
19924
  }
19878
19925
 
19879
19926
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
@@ -19948,8 +19995,6 @@ class AuroCombobox extends AuroElement {
19948
19995
  this.availableOptions = [];
19949
19996
  this.dropdownId = undefined;
19950
19997
  this.dropdownOpen = false;
19951
- this.triggerExpandedState = false;
19952
- this._expandedTimeout = null;
19953
19998
  this._inFullscreenTransition = false;
19954
19999
  this.errorMessage = null;
19955
20000
  this.isHiddenWhileLoading = false;
@@ -19960,6 +20005,30 @@ class AuroCombobox extends AuroElement {
19960
20005
  this.touched = false;
19961
20006
  this.validation = new AuroFormValidation$1();
19962
20007
  this.validity = undefined;
20008
+ this._userTyped = false;
20009
+ // Tracks every setTimeout scheduled via _scheduleTimer so
20010
+ // disconnectedCallback can cancel them. Without this, a detached
20011
+ // combobox's pending timers still fire — most are no-ops, but
20012
+ // configureMenu's racing-condition retry would otherwise loop.
20013
+ this._pendingTimers = new Set();
20014
+ }
20015
+
20016
+ /**
20017
+ * setTimeout wrapper that records the timer id so disconnectedCallback
20018
+ * can cancel any outstanding callbacks. The id is removed from the set
20019
+ * once the callback fires so the set doesn't grow unbounded.
20020
+ * @param {Function} fn - Callback to run.
20021
+ * @param {number} ms - Delay in milliseconds.
20022
+ * @returns {number} The timer id.
20023
+ * @private
20024
+ */
20025
+ _scheduleTimer(fn, ms) {
20026
+ const id = setTimeout(() => {
20027
+ this._pendingTimers.delete(id);
20028
+ fn();
20029
+ }, ms);
20030
+ this._pendingTimers.add(id);
20031
+ return id;
19963
20032
  }
19964
20033
 
19965
20034
  // This function is to define props used within the scope of this component
@@ -20321,17 +20390,6 @@ class AuroCombobox extends AuroElement {
20321
20390
  attribute: false
20322
20391
  },
20323
20392
 
20324
- /**
20325
- * Deferred aria-expanded state for the trigger input.
20326
- * Delays the "true" transition so VoiceOver finishes its character echo
20327
- * before announcing "expanded".
20328
- * @private
20329
- */
20330
- triggerExpandedState: {
20331
- type: Boolean,
20332
- reflect: false,
20333
- attribute: false
20334
- },
20335
20393
  };
20336
20394
  }
20337
20395
 
@@ -20354,13 +20412,6 @@ class AuroCombobox extends AuroElement {
20354
20412
  return this.input.value;
20355
20413
  }
20356
20414
 
20357
- // /**
20358
- // * Sets the value of the input element within the combobox.
20359
- // */
20360
- // set inputValue(value) {
20361
- // this.input.value = value;
20362
- // }
20363
-
20364
20415
  /**
20365
20416
  * Checks if the element is valid.
20366
20417
  * @returns {boolean} - Returns true if the element is valid, false otherwise.
@@ -20506,8 +20557,11 @@ class AuroCombobox extends AuroElement {
20506
20557
  this.syncInputValuesAcrossTriggerAndBib(label || this.value);
20507
20558
  }
20508
20559
 
20509
- // update the displayValue in the trigger if displayValue slot content is present
20510
- const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]');
20560
+ // Replace any previously appended displayValue clone in the trigger.
20561
+ // :not(slot) excludes the template's <slot name="displayValue"
20562
+ // slot="displayValue"> forwarder, which also has slot="displayValue"
20563
+ // and would otherwise be matched first and removed.
20564
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
20511
20565
 
20512
20566
  if (displayValueInTrigger) {
20513
20567
  displayValueInTrigger.remove();
@@ -20535,19 +20589,19 @@ class AuroCombobox extends AuroElement {
20535
20589
  * Writes nextValue to the trigger input and the bib input when their current
20536
20590
  * value differs, then re-asserts imask after Lit's update flushes.
20537
20591
  * @param {string} nextValue - The value to write to both inputs.
20538
- * @returns {Promise<void> | null} Promise that resolves after the inputs flush,
20539
- * or null when no sync was needed (so callers can skip post-flush work).
20592
+ * @returns {Promise<void>} Resolves after both inputs flush and imask
20593
+ * re-asserts; resolves immediately when no sync is needed.
20540
20594
  * @private
20541
20595
  */
20542
- syncInputValuesAcrossTriggerAndBib(nextValue) {
20596
+ async syncInputValuesAcrossTriggerAndBib(nextValue) {
20543
20597
  // Only set the flag when there's an actual write to suppress —
20544
20598
  // syncValuesAndStates re-enters here during typing when both inputs
20545
20599
  // already match, and a no-op flag flip would make the bib branch's
20546
20600
  // bail eat legitimate user-input events.
20547
20601
  const triggerNeedsSync = this.input.value !== nextValue;
20548
- const bibNeedsSync = this.inputInBib.value !== nextValue;
20602
+ const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
20549
20603
  if (!triggerNeedsSync && !bibNeedsSync) {
20550
- return null;
20604
+ return;
20551
20605
  }
20552
20606
 
20553
20607
  this._syncingDisplayValue = true;
@@ -20561,16 +20615,21 @@ class AuroCombobox extends AuroElement {
20561
20615
  this.inputInBib.value = nextValue;
20562
20616
  pending.push(this.inputInBib.updateComplete);
20563
20617
  }
20564
- return Promise.all(pending).then(() => {
20618
+ // finally — not a bare .then — so that an imask throw (see commit
20619
+ // d1857401c: imask can throw on credit-card format change) doesn't strand
20620
+ // _syncingDisplayValue=true and silently swallow every subsequent input.
20621
+ try {
20622
+ await Promise.all(pending);
20565
20623
  // imask reasserts otherwise, and handleBlur reverts to its stale value.
20566
20624
  if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20567
20625
  this.input.maskInstance.updateValue();
20568
20626
  }
20569
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20627
+ if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20570
20628
  this.inputInBib.maskInstance.updateValue();
20571
20629
  }
20630
+ } finally {
20572
20631
  this._syncingDisplayValue = false;
20573
- });
20632
+ }
20574
20633
  }
20575
20634
 
20576
20635
  /**
@@ -20581,6 +20640,13 @@ class AuroCombobox extends AuroElement {
20581
20640
  handleMenuOptions() {
20582
20641
  this.generateOptionsArray();
20583
20642
  this.availableOptions = [];
20643
+ // Single source of truth for the menu's filter/highlight token per call.
20644
+ // syncValuesAndStates re-writes the same value on exact-match keystrokes —
20645
+ // Lit's hasChanged makes that a no-op — and the prior duplicate set inside
20646
+ // handleTriggerInputValueChange is gone.
20647
+ if (this.menu) {
20648
+ this.menu.matchWord = normalizeFilterValue(this.input.value);
20649
+ }
20584
20650
  this.updateFilter();
20585
20651
 
20586
20652
  // Set aria-setsize/aria-posinset on each visible option so screen readers
@@ -20592,11 +20658,8 @@ class AuroCombobox extends AuroElement {
20592
20658
  option.setAttribute('aria-posinset', index + 1);
20593
20659
  });
20594
20660
 
20595
- if (this.value && this.input.value && !this.menu.value) {
20596
- if (this.behavior === 'suggestion' && this.menu.options && this.menu.options.some((opt) => opt.value === this.value)) {
20597
- this.setMenuValue(this.value);
20598
- }
20599
-
20661
+ if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
20662
+ this.setMenuValue(this.input.value);
20600
20663
  this.syncValuesAndStates();
20601
20664
  }
20602
20665
 
@@ -20675,18 +20738,6 @@ class AuroCombobox extends AuroElement {
20675
20738
  this.dropdownOpen = ev.detail.expanded;
20676
20739
  this.updateMenuShapeSize();
20677
20740
 
20678
- // Defer aria-expanded "true" so VoiceOver finishes character echo
20679
- // before announcing "expanded". Set "false" immediately on close.
20680
- clearTimeout(this._expandedTimeout);
20681
- if (this.dropdownOpen) {
20682
- const expandedDelay = 150;
20683
- this._expandedTimeout = setTimeout(() => {
20684
- this.triggerExpandedState = true;
20685
- }, expandedDelay);
20686
- } else {
20687
- this.triggerExpandedState = false;
20688
- }
20689
-
20690
20741
  // Clear aria-activedescendant when dropdown closes
20691
20742
  if (!this.dropdownOpen && this.input) {
20692
20743
  this.input.setActiveDescendant(null);
@@ -20791,7 +20842,7 @@ class AuroCombobox extends AuroElement {
20791
20842
  this.dropdown.trigger.inert = false;
20792
20843
  }
20793
20844
 
20794
- setTimeout(() => {
20845
+ this._scheduleTimer(() => {
20795
20846
  this.setInputFocus();
20796
20847
  }, 0);
20797
20848
  });
@@ -20935,7 +20986,7 @@ class AuroCombobox extends AuroElement {
20935
20986
 
20936
20987
  // racing condition on custom-combobox with custom-menu
20937
20988
  if (!this.menu) {
20938
- setTimeout(() => {
20989
+ this._scheduleTimer(() => {
20939
20990
  this.configureMenu();
20940
20991
  }, 0);
20941
20992
  return;
@@ -20990,9 +21041,7 @@ class AuroCombobox extends AuroElement {
20990
21041
  // Hide dropdown on selection (except during slot changes)
20991
21042
  if (evt.detail && evt.detail.source !== 'slotchange') {
20992
21043
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
20993
- if (this.menu.value || this.behavior !== 'suggestion') {
20994
- this.hideBib();
20995
- }
21044
+ this.hideBib();
20996
21045
 
20997
21046
  // Move focus to the clear button when the user makes a selection.
20998
21047
  if (!isEcho && this.menu.value !== undefined) {
@@ -21006,7 +21055,7 @@ class AuroCombobox extends AuroElement {
21006
21055
  const selectedValue = this.menu.value;
21007
21056
  if (selectedValue) {
21008
21057
  const announcementDelay = 300;
21009
- setTimeout(() => {
21058
+ this._scheduleTimer(() => {
21010
21059
  announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
21011
21060
  }, announcementDelay);
21012
21061
  }
@@ -21018,10 +21067,6 @@ class AuroCombobox extends AuroElement {
21018
21067
  // only — fresh user selections take the existing hideBib path.
21019
21068
  if (isEcho && this.menu.optionSelected) {
21020
21069
  this._programmaticFilterRefresh = true;
21021
- this.handleMenuOptions();
21022
- setTimeout(() => {
21023
- this._programmaticFilterRefresh = false;
21024
- }, 0);
21025
21070
  }
21026
21071
  });
21027
21072
 
@@ -21034,6 +21079,14 @@ class AuroCombobox extends AuroElement {
21034
21079
  // stale option. Safe from re-entrancy because any resulting
21035
21080
  // input.value changes dispatch isProgrammatic events.
21036
21081
  this.menu.addEventListener('auroMenu-selectValueFailure', () => {
21082
+ // Announce the rejection BEFORE we clear `this.value` so the live
21083
+ // region carries the attempted value — without this the bib closes
21084
+ // silently and screen-reader users get no signal that their request
21085
+ // (e.g. a direct setMenuValue() call) was dropped.
21086
+ const attemptedValue = this.value;
21087
+ if (attemptedValue) {
21088
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
21089
+ }
21037
21090
  this.value = undefined;
21038
21091
  this.optionSelected = undefined;
21039
21092
  });
@@ -21045,10 +21098,13 @@ class AuroCombobox extends AuroElement {
21045
21098
  this.input.setActiveDescendant(this.optionActive);
21046
21099
  }
21047
21100
 
21048
- // Announce the active option for screen readers including position,
21049
- // since shadow DOM boundaries prevent native reading of
21050
- // aria-setsize/aria-posinset via aria-activedescendant.
21051
- if (this.optionActive) {
21101
+ // In fullscreen mode the menu sits inside a nested <dialog> shadow root,
21102
+ // and aria-activedescendant references across that boundary are lost —
21103
+ // VoiceOver/NVDA don't read the active option natively, so we mirror it
21104
+ // into the polite live region. In popover mode aria-activedescendant on
21105
+ // the trigger input is read natively; double-announcing would flood the
21106
+ // queue on arrow-key repeat.
21107
+ if (this.optionActive && this.dropdown.isBibFullscreen) {
21052
21108
  const optionText = this.optionActive.textContent.trim();
21053
21109
  const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
21054
21110
  const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
@@ -21081,7 +21137,12 @@ class AuroCombobox extends AuroElement {
21081
21137
  * Validate every time we remove focus from the combo box.
21082
21138
  */
21083
21139
  this.addEventListener('focusout', () => {
21084
- if (!this.componentHasFocus && !this._inFullscreenTransition) {
21140
+ // Skip while the dropdown is open — focus transits out briefly on
21141
+ // mousedown of a menu option (popover top-layer breaks :focus-within),
21142
+ // and validating against the pre-selection value flashes a stale error
21143
+ // between mousedown and mouseup. The next focusout fires after the
21144
+ // dropdown closes and validates against the post-selection value.
21145
+ if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
21085
21146
  this.validate();
21086
21147
  }
21087
21148
  });
@@ -21144,49 +21205,40 @@ class AuroCombobox extends AuroElement {
21144
21205
  if (this._syncingDisplayValue) {
21145
21206
  return;
21146
21207
  }
21147
- this._syncingBibValue = true;
21148
- this.input.value = this.inputInBib.value;
21149
- this.input.updateComplete.then(() => {
21150
- this._syncingBibValue = false;
21151
- });
21152
21208
 
21153
- // Run filtering inline — the re-entrant event won't reach this code.
21154
- this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21155
- this.optionActive = null;
21156
-
21157
- // In suggestion mode, keep the combobox value in sync with the typed
21158
- // text so that freeform values are captured (mirroring the non-fullscreen
21159
- // path). Clear the selection when the input is emptied.
21160
- if (this.behavior === 'suggestion') {
21161
- this.value = this.inputInBib.value || undefined;
21209
+ // Filtering runs via re-entrance: writing this.input.value below
21210
+ // dispatches an 'input' event that the trigger's listener routes to
21211
+ // handleTriggerInputValueChange, which refreshes the menu filter.
21212
+ if (this.input.value !== this.inputInBib.value) {
21213
+ this._syncingBibValue = true;
21214
+ this.input.value = this.inputInBib.value;
21215
+ this.input.updateComplete.then(() => {
21216
+ this._syncingBibValue = false;
21217
+ });
21162
21218
  }
21163
21219
 
21164
- this.handleMenuOptions();
21165
21220
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21166
- return;
21221
+ } else if (event.target === this.input) {
21222
+
21223
+ // Also sync the native input immediately so keystrokes arriving
21224
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
21225
+ // fullscreen transition) operate on the correct content.
21226
+ // Skip the next programmatic input event to prevent the patched setter
21227
+ // from re-entering handleInputValueChange via the bib path.
21228
+ const bibNativeInput = this.inputInBib.inputElement;
21229
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21230
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
21231
+ bibNativeInput.value = this.input.value || '';
21232
+ }
21167
21233
  }
21168
21234
 
21235
+ this.value = this.input.value;
21236
+
21169
21237
  // Ignore re-entrant input events caused by programmatic value sets.
21170
21238
  if (this._syncingBibValue || this._syncingDisplayValue) {
21171
21239
  return;
21172
21240
  }
21173
21241
 
21174
- this.inputInBib.value = this.input.value;
21175
-
21176
- // Also sync the native input immediately so keystrokes arriving
21177
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21178
- // fullscreen transition) operate on the correct content.
21179
- // Skip the next programmatic input event to prevent the patched setter
21180
- // from re-entering handleInputValueChange via the bib path.
21181
- const bibNativeInput = this.inputInBib.inputElement;
21182
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21183
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21184
- bibNativeInput.value = this.input.value || '';
21185
- }
21186
-
21187
- this.menu.matchWord = normalizeFilterValue(this.input.value);
21188
- this.optionActive = null;
21189
-
21190
21242
  if (this.behavior === 'suggestion') {
21191
21243
  this.value = this.input.value;
21192
21244
  }
@@ -21194,7 +21246,6 @@ class AuroCombobox extends AuroElement {
21194
21246
  if (!this.input.value && !this._clearing) {
21195
21247
  this.clear();
21196
21248
  }
21197
- this.handleMenuOptions();
21198
21249
 
21199
21250
  // Validate only if the value was set programmatically (not during user
21200
21251
  // interaction). In fullscreen dialog mode, componentHasFocus returns false
@@ -21204,34 +21255,44 @@ class AuroCombobox extends AuroElement {
21204
21255
  this.validate();
21205
21256
  }
21206
21257
 
21207
- if (this.input.value && this.input.value.length === 0) {
21208
- // Hide menu if value is empty, otherwise show if there are available suggestions
21209
- this.hideBib();
21210
- } else if (this.menu.loading) {
21211
- // if input has value but menu is loading, show bib immediately
21212
- this.showBib();
21213
- } else if (this.availableOptions.length === 0 && !this.dropdown.isBibFullscreen) {
21214
- // Force dropdown bib to hide if input value has no matching suggestions
21215
- this.hideBib();
21258
+ this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21259
+ }
21260
+
21261
+ /**
21262
+ * Handles input value changes originating from the trigger input.
21263
+ * Refreshes menu options and filtering, delegates to handleInputValueChange
21264
+ * for value synchronization, and manages fullscreen bib focus.
21265
+ * @private
21266
+ * @param {Event} event - The input event from the trigger input element.
21267
+ * @returns {void}
21268
+ */
21269
+ handleTriggerInputValueChange(event) {
21270
+ // 'input' fires for every user-initiated value change — typing, paste,
21271
+ // IME composition end, dead-key composition, drag-drop. Flip _userTyped
21272
+ // here so updated('availableOptions') auto-opens the bib for sources
21273
+ // that keydown alone misses: paste fires no keydown, IME uses
21274
+ // key='Process', and dead keys produce multi-char keys (all bypass the
21275
+ // prior keydown.key.length===1 gate). Skip programmatic syncs.
21276
+ if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
21277
+ this._userTyped = true;
21216
21278
  }
21217
21279
 
21218
- // iOS virtual keyboard retention: when in fullscreen mode, ensure the
21219
- // dialog opens and the bib input is focused synchronously within the
21220
- // input event (user gesture) chain. Without this, Lit's async update
21221
- // cycle delays showModal() past the user activation window, causing
21222
- // iOS Safari to dismiss the virtual keyboard when the fullscreen
21223
- // dialog opens — the user then has to tap the input again to resume
21224
- // typing.
21225
- if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
21226
- if (!this.dropdown.isPopoverVisible) {
21227
- this.showBib();
21228
- }
21229
- if (this.dropdown.isPopoverVisible) {
21230
- this.setInputFocus();
21231
- }
21280
+ this.handleMenuOptions();
21281
+ this.optionActive = null;
21282
+
21283
+ if (this.value === this.input.value) {
21284
+ return;
21232
21285
  }
21233
21286
 
21234
- this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21287
+ this.handleInputValueChange(event);
21288
+
21289
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
21290
+ this.setInputFocus();
21291
+ }
21292
+
21293
+ if (this._programmaticFilterRefresh) {
21294
+ this._programmaticFilterRefresh = false;
21295
+ }
21235
21296
  }
21236
21297
 
21237
21298
  /**
@@ -21299,6 +21360,11 @@ class AuroCombobox extends AuroElement {
21299
21360
  disconnectedCallback() {
21300
21361
  super.disconnectedCallback();
21301
21362
  this._inFullscreenTransition = false;
21363
+ // Cancel any outstanding timers so detached callbacks don't fire on
21364
+ // disposed DOM — most are no-ops, but configureMenu's racing-condition
21365
+ // retry would otherwise keep rescheduling itself indefinitely.
21366
+ this._pendingTimers.forEach((id) => clearTimeout(id));
21367
+ this._pendingTimers.clear();
21302
21368
  }
21303
21369
 
21304
21370
  firstUpdated() {
@@ -21340,7 +21406,7 @@ class AuroCombobox extends AuroElement {
21340
21406
  this.menu.value = value;
21341
21407
  // Backup clear: if menu.value === menu.optionSelected.value already, the
21342
21408
  // listener won't fire and the flag would swallow the next user click.
21343
- setTimeout(() => {
21409
+ this._scheduleTimer(() => {
21344
21410
  this._pendingMenuValueSync = false;
21345
21411
  }, 0);
21346
21412
  }
@@ -21375,6 +21441,17 @@ class AuroCombobox extends AuroElement {
21375
21441
  this.optionSelected = undefined;
21376
21442
  this.value = undefined;
21377
21443
 
21444
+ // Clear the appended displayValue clone in the trigger if present.
21445
+ // :not(slot) excludes the template's <slot name="displayValue"
21446
+ // slot="displayValue"> forwarder (line 1816), which also has
21447
+ // slot="displayValue" and would otherwise be matched first and removed,
21448
+ // permanently breaking the consumer-provided displayValue slot.
21449
+ const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
21450
+
21451
+ if (displayValueInTrigger) {
21452
+ displayValueInTrigger.remove();
21453
+ }
21454
+
21378
21455
  if (this.input.value) {
21379
21456
  this.input.clear();
21380
21457
  }
@@ -21398,56 +21475,50 @@ class AuroCombobox extends AuroElement {
21398
21475
  }
21399
21476
 
21400
21477
  updated(changedProperties) {
21401
- // After the component is ready, send direct value changes to auro-menu.
21478
+ // After the component is ready, propagate direct changes down to child components.
21402
21479
  if (changedProperties.has('value')) {
21403
- if (this.value && this.value.length > 0) {
21404
- this.hasValue = true;
21405
- } else {
21406
- this.hasValue = false;
21480
+ // Only flag programmatic refreshes — user-typed value changes must not
21481
+ // suppress the availableOptions branch's showBib(). Firefox batches
21482
+ // 'value' and 'availableOptions' into the same updated() call, so
21483
+ // setting the flag unconditionally here masks the user-typed open path.
21484
+ if (!this._userTyped) {
21485
+ this._programmaticFilterRefresh = true;
21407
21486
  }
21408
21487
 
21409
- if (this.hasValue && !this.input.value && (!this.menu.options || this.menu.options.length === 0)) {
21410
- this.input.value = this.value;
21411
- }
21488
+ if (this.input.value !== this.value) {
21489
+ // Clear menu.value AND menu.optionSelected together. Clearing only
21490
+ // menu.value leaves the previously-selected option element pinned
21491
+ // as menu.optionSelected; a later auroMenu-selectedOption event
21492
+ // would then write its stale .value back into combobox.value
21493
+ // (e.g. Tab-after-Backspace re-selecting the prior option).
21494
+ if (this.menu.value || this.menu.optionSelected) {
21495
+ this.menu.clearSelection();
21496
+ }
21412
21497
 
21413
- // Sync menu.value only when an option actually matches, or when filter
21414
- // mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
21415
- // listener at line 1206 clears combobox.value, mirroring select's
21416
- // pattern). Otherwise sync the input display for freeform values.
21417
- if (this.menu.options && this.menu.options.length > 0) {
21418
- if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
21419
- this.setMenuValue(this.value);
21420
- } else {
21421
- // Clear menu.value AND menu.optionSelected together. Clearing only
21422
- // menu.value leaves the previously-selected option element pinned
21423
- // as menu.optionSelected; a later auroMenu-selectedOption event
21424
- // would then write its stale .value back into combobox.value.
21425
- if (this.menu.value || this.menu.optionSelected) {
21426
- this.menu.clearSelection();
21427
- }
21428
- // Suggestion-mode freeform value: sync the trigger + bib to show it,
21429
- // then refresh the filter once the inputs flush (handleInputValueChange
21430
- // bailed on _syncingDisplayValue).
21431
- const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21432
- if (syncPromise) {
21433
- syncPromise.then(() => {
21434
- this._programmaticFilterRefresh = true;
21435
- this.handleMenuOptions();
21436
- setTimeout(() => {
21437
- this._programmaticFilterRefresh = false;
21438
- }, 0);
21439
- });
21440
- }
21498
+ if (!this.persistInput) {
21499
+ this.syncInputValuesAcrossTriggerAndBib(this.value || '');
21500
+ }
21501
+
21502
+ // Programmatic value with no matching option: updateFilter will close
21503
+ // the bib silently (see line 648 no noMatchOption + 0 results
21504
+ // hides). Announce so screen-reader users hear the request was
21505
+ // dropped. Gated on `input.value !== this.value` so this never fires
21506
+ // for user typing that path always reconciles input.value to
21507
+ // this.value before updated() runs.
21508
+ if (
21509
+ this.value &&
21510
+ this.menu &&
21511
+ this.menu.options &&
21512
+ this.menu.options.length > 0 &&
21513
+ !this.menu.options.some((opt) => opt.value === this.value)
21514
+ ) {
21515
+ announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
21441
21516
  }
21442
21517
  }
21518
+
21443
21519
  if (!this.value) {
21444
21520
  this.clear();
21445
21521
  }
21446
- if (this.value && !this.componentHasFocus) {
21447
- // If the value got set programmatically make sure we hide the bib
21448
- // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
21449
- this.hideBib();
21450
- }
21451
21522
 
21452
21523
  // Sync the input and match word, but don't directly set menu.value again
21453
21524
  if (this.menu) {
@@ -21460,7 +21531,7 @@ class AuroCombobox extends AuroElement {
21460
21531
  composed: true,
21461
21532
  detail: {
21462
21533
  optionSelected: this.menu.optionSelected,
21463
- value: this.menu.value
21534
+ value: this.value
21464
21535
  }
21465
21536
  }));
21466
21537
 
@@ -21483,16 +21554,30 @@ class AuroCombobox extends AuroElement {
21483
21554
  // from a programmatic filter refresh after a value swap — the user
21484
21555
  // didn't interact, so we shouldn't auto-open the bib (especially for
21485
21556
  // the no-match path where the existing condition unconditionally fires).
21486
- if (this._programmaticFilterRefresh) {
21487
- this._programmaticFilterRefresh = false;
21488
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21489
- this.showBib();
21557
+ if ((this.menu && !this._programmaticFilterRefresh)) {
21558
+ if (
21559
+ this.availableOptions.length > 0 ||
21560
+ this.menu.loading ||
21561
+ this.noMatchOption
21562
+ ) {
21563
+ if (this._userTyped) {
21564
+ if (!this.dropdownOpen) {
21565
+ this.showBib();
21566
+ }
21567
+ this._userTyped = false;
21568
+ }
21569
+ }
21570
+
21490
21571
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21491
21572
  this.activateFirstEnabledAvailableOption();
21492
21573
  }
21493
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21574
+ } else if (!this.dropdown.isBibFullscreen) {
21494
21575
  this.hideBib();
21495
21576
  }
21577
+
21578
+ if (this._programmaticFilterRefresh) {
21579
+ this._programmaticFilterRefresh = false;
21580
+ }
21496
21581
  }
21497
21582
 
21498
21583
  if (changedProperties.has('error')) {
@@ -21634,10 +21719,10 @@ class AuroCombobox extends AuroElement {
21634
21719
  shape="${this.shape}"
21635
21720
  size="${this.size}">
21636
21721
  <${this.inputTag}
21637
- @input="${this.handleInputValueChange}"
21722
+ @input="${this.handleTriggerInputValueChange}"
21638
21723
  appearance="${this.onDark ? 'inverse' : this.appearance}"
21639
21724
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
21640
- .a11yExpanded="${this.triggerExpandedState}"
21725
+ .a11yExpanded="${this.dropdownOpen}"
21641
21726
  .a11yControls="${this.dropdownId}"
21642
21727
  .autocomplete="${this.autocomplete}"
21643
21728
  .inputmode="${this.inputmode}"