@aurodesignsystem-dev/auro-formkit 0.0.0-pr1480.9 → 0.0.0-pr1482.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/components/bibtemplate/dist/auro-bibtemplate.d.ts +0 -7
  2. package/components/bibtemplate/dist/index.js +1 -9
  3. package/components/bibtemplate/dist/registered.js +1 -9
  4. package/components/checkbox/demo/customize.min.js +1 -1
  5. package/components/checkbox/demo/getting-started.min.js +1 -1
  6. package/components/checkbox/demo/index.min.js +1 -1
  7. package/components/checkbox/demo/pages.json +1 -1
  8. package/components/checkbox/dist/index.js +1 -1
  9. package/components/checkbox/dist/registered.js +1 -1
  10. package/components/combobox/demo/customize.md +7 -3
  11. package/components/combobox/demo/customize.min.js +1257 -1816
  12. package/components/combobox/demo/getting-started.min.js +1257 -1816
  13. package/components/combobox/demo/index.min.js +1257 -1816
  14. package/components/combobox/demo/keyboard-behavior.md +68 -8
  15. package/components/combobox/demo/pages.json +1 -1
  16. package/components/combobox/dist/auro-combobox.d.ts +5 -0
  17. package/components/combobox/dist/index.js +335 -319
  18. package/components/combobox/dist/registered.js +335 -319
  19. package/components/counter/demo/customize.min.js +15 -233
  20. package/components/counter/demo/index.min.js +15 -233
  21. package/components/counter/demo/keyboard-behavior.md +0 -1
  22. package/components/counter/demo/pages.json +1 -1
  23. package/components/counter/dist/index.js +5204 -208
  24. package/components/counter/dist/registered.js +5204 -208
  25. package/components/datepicker/demo/accessibility.md +3 -54
  26. package/components/datepicker/demo/api.md +2 -11
  27. package/components/datepicker/demo/customize.html +0 -2
  28. package/components/datepicker/demo/customize.md +8 -72
  29. package/components/datepicker/demo/design.md +1 -3
  30. package/components/datepicker/demo/index.js +1 -5
  31. package/components/datepicker/demo/index.md +2 -85
  32. package/components/datepicker/demo/index.min.js +183 -2288
  33. package/components/datepicker/demo/keyboard-behavior.md +2 -201
  34. package/components/datepicker/demo/pages.json +1 -1
  35. package/components/datepicker/demo/voiceover.md +12 -21
  36. package/components/datepicker/dist/index.js +156 -2174
  37. package/components/datepicker/dist/registered.js +156 -2174
  38. package/components/datepicker/dist/src/auro-calendar-cell.d.ts +11 -111
  39. package/components/datepicker/dist/src/auro-calendar-month.d.ts +0 -37
  40. package/components/datepicker/dist/src/auro-calendar.d.ts +0 -246
  41. package/components/datepicker/dist/src/auro-datepicker.d.ts +0 -88
  42. package/components/datepicker/dist/src/datepickerKeyboardStrategy.d.ts +3 -5
  43. package/components/dropdown/demo/accessibility.md +0 -11
  44. package/components/dropdown/demo/api.md +0 -1
  45. package/components/dropdown/demo/customize.md +0 -3
  46. package/components/dropdown/demo/customize.min.js +13 -223
  47. package/components/dropdown/demo/getting-started.min.js +13 -223
  48. package/components/dropdown/demo/index.min.js +13 -223
  49. package/components/dropdown/demo/keyboard-behavior.md +0 -1
  50. package/components/dropdown/demo/pages.json +1 -1
  51. package/components/dropdown/dist/auro-dropdown.d.ts +1 -33
  52. package/components/dropdown/dist/index.js +13 -223
  53. package/components/dropdown/dist/registered.js +13 -223
  54. package/components/form/demo/customize.min.js +7765 -10745
  55. package/components/form/demo/getting-started.min.js +7765 -10745
  56. package/components/form/demo/index.min.js +7765 -10745
  57. package/components/form/demo/pages.json +1 -1
  58. package/components/form/demo/registerDemoDeps.min.js +7764 -10744
  59. package/components/input/demo/customize.min.js +1 -1
  60. package/components/input/demo/getting-started.min.js +1 -1
  61. package/components/input/demo/index.min.js +1 -1
  62. package/components/input/demo/pages.json +1 -1
  63. package/components/input/dist/index.js +1 -1
  64. package/components/input/dist/registered.js +1 -1
  65. package/components/menu/demo/api.md +41 -45
  66. package/components/menu/demo/customize.md +0 -28
  67. package/components/menu/demo/index.min.js +779 -1354
  68. package/components/menu/demo/pages.json +1 -1
  69. package/components/menu/dist/auro-menu.d.ts +95 -110
  70. package/components/menu/dist/auro-menuoption.d.ts +32 -138
  71. package/components/menu/dist/index.js +753 -1308
  72. package/components/menu/dist/registered.js +765 -1308
  73. package/components/radio/demo/getting-started.js +1 -1
  74. package/components/radio/demo/getting-started.md +1 -1
  75. package/components/radio/demo/index.min.js +1 -1
  76. package/components/radio/demo/pages.json +1 -1
  77. package/components/radio/dist/index.js +1 -1
  78. package/components/radio/dist/registered.js +1 -1
  79. package/components/select/demo/customize.md +7 -3
  80. package/components/select/demo/customize.min.js +999 -1759
  81. package/components/select/demo/getting-started.min.js +999 -1759
  82. package/components/select/demo/index.min.js +999 -1759
  83. package/components/select/demo/keyboard-behavior.md +54 -9
  84. package/components/select/demo/pages.json +1 -1
  85. package/components/select/dist/index.js +76 -261
  86. package/components/select/dist/registered.js +76 -261
  87. package/components/select/dist/selectUtils.d.ts +12 -0
  88. package/custom-elements.json +2406 -4134
  89. package/package.json +4 -4
  90. package/components/checkbox/demo/why-checkbox.html +0 -57
  91. package/components/checkbox/demo/why-checkbox.md +0 -86
  92. package/components/combobox/demo/why-combobox.html +0 -57
  93. package/components/combobox/demo/why-combobox.md +0 -113
  94. package/components/counter/demo/why-counter.html +0 -57
  95. package/components/counter/demo/why-counter.md +0 -108
  96. package/components/datepicker/demo/customize.js +0 -19
  97. package/components/datepicker/demo/customize.min.js +0 -26655
  98. package/components/datepicker/demo/why-datepicker.html +0 -57
  99. package/components/datepicker/demo/why-datepicker.md +0 -133
  100. package/components/dropdown/demo/why-dropdown.html +0 -57
  101. package/components/dropdown/demo/why-dropdown.md +0 -97
  102. package/components/form/demo/why-form.html +0 -57
  103. package/components/form/demo/why-form.md +0 -101
  104. package/components/input/demo/why-input.html +0 -57
  105. package/components/input/demo/why-input.md +0 -121
  106. package/components/menu/demo/why-menu.html +0 -57
  107. package/components/menu/demo/why-menu.md +0 -104
  108. package/components/menu/dist/auro-menu.context.d.ts +0 -238
  109. package/components/radio/demo/customize.min.js +0 -2186
  110. package/components/radio/demo/demo-support.min.js +0 -55807
  111. package/components/radio/demo/getting-started.min.js +0 -2205
  112. package/components/radio/demo/why-radio.html +0 -57
  113. package/components/radio/demo/why-radio.md +0 -92
  114. package/components/select/demo/why-select.html +0 -57
  115. package/components/select/demo/why-select.md +0 -128
@@ -1219,6 +1219,21 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
1219
1219
  return isFocused;
1220
1220
  }
1221
1221
 
1222
+ /**
1223
+ * Reconcile the menu `_index` from `optionActive` so subsequent `makeSelection` calls find the correct option after async clearSelection has reset it.
1224
+ * @param {Object} menu - The menu component.
1225
+ */
1226
+ function reconcileMenuIndex(menu) {
1227
+ // eslint-disable-next-line no-underscore-dangle
1228
+ if (menu._index < 0 && menu.optionActive && menu.items) {
1229
+ const idx = menu.items.indexOf(menu.optionActive);
1230
+ if (idx >= 0) {
1231
+ // eslint-disable-next-line no-underscore-dangle
1232
+ menu._index = idx;
1233
+ }
1234
+ }
1235
+ }
1236
+
1222
1237
  const comboboxKeyboardStrategy = {
1223
1238
  ArrowDown(component, evt, ctx) {
1224
1239
  // If the clear button has focus, let the browser handle ArrowDown normally.
@@ -1232,7 +1247,7 @@ const comboboxKeyboardStrategy = {
1232
1247
 
1233
1248
  // navigate if bib is open otherwise open it
1234
1249
  if (component.dropdown.isPopoverVisible) {
1235
- if (evt.altKey || evt.metaKey) {
1250
+ if (evt.altKey || evt.ctrlKey || evt.metaKey) {
1236
1251
  component.activateLastEnabledAvailableOption();
1237
1252
  } else {
1238
1253
  navigateArrow(component, 'down');
@@ -1255,7 +1270,7 @@ const comboboxKeyboardStrategy = {
1255
1270
 
1256
1271
  // navigate if bib is open otherwise open it
1257
1272
  if (component.dropdown.isPopoverVisible) {
1258
- if (evt.altKey || evt.metaKey) {
1273
+ if (evt.altKey || evt.ctrlKey || evt.metaKey) {
1259
1274
  component.activateFirstEnabledAvailableOption();
1260
1275
  } else {
1261
1276
  navigateArrow(component, 'up');
@@ -1282,6 +1297,7 @@ const comboboxKeyboardStrategy = {
1282
1297
  // block the browser's built-in "Enter activates focused button" behavior.
1283
1298
  evt.stopPropagation();
1284
1299
  } else if (ctx.isExpanded && component.menu.optionActive) {
1300
+ reconcileMenuIndex(component.menu);
1285
1301
  component.menu.makeSelection();
1286
1302
 
1287
1303
  if (ctx.isModal) {
@@ -1329,6 +1345,7 @@ const comboboxKeyboardStrategy = {
1329
1345
  // When the clear button is focused, Tab events do not bubble out of
1330
1346
  // its shadow DOM, so this handler only fires when the clear button
1331
1347
  // is NOT focused. In that case, select the active option and close.
1348
+ reconcileMenuIndex(component.menu);
1332
1349
  component.menu.makeSelection();
1333
1350
  component.hideBib();
1334
1351
 
@@ -4580,7 +4597,7 @@ function applyKeyboardStrategy(component, strategy, options = {}) {
4580
4597
  });
4581
4598
  }
4582
4599
 
4583
- var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{width:auto;max-width:none;height:auto;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:relative;inset:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}:host([desktopmodal]:popover-open)::backdrop{background:transparent}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([isfullscreen]) .container::backdrop{background:var(--ds-color-background-primary, #fff)}:host(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}.util_displayHiddenVisually{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip-path:inset(50%);white-space:nowrap}`;
4600
+ var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{width:auto;max-width:none;height:auto;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:relative;inset:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([isfullscreen]) .container::backdrop{background:var(--ds-color-background-primary, #fff)}:host(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}.util_displayHiddenVisually{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip-path:inset(50%);white-space:nowrap}`;
4584
4601
 
4585
4602
  var colorCss$2$1 = css`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`;
4586
4603
 
@@ -5211,7 +5228,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
5211
5228
  }
5212
5229
  };
5213
5230
 
5214
- var formkitVersion$2 = '202605262025';
5231
+ var formkitVersion$2 = '202606082059';
5215
5232
 
5216
5233
  let AuroElement$2 = class AuroElement extends LitElement {
5217
5234
  static get properties() {
@@ -5391,7 +5408,6 @@ class AuroDropdown extends AuroElement$2 {
5391
5408
  _intializeDefaults() {
5392
5409
  this.appearance = 'default';
5393
5410
  this.chevron = false;
5394
- this.desktopModal = false;
5395
5411
  this.disabled = false;
5396
5412
  this.disableKeyboardHandling = false;
5397
5413
  this.error = false;
@@ -5572,14 +5588,6 @@ class AuroDropdown extends AuroElement$2 {
5572
5588
  reflect: true
5573
5589
  },
5574
5590
 
5575
- /**
5576
- * If declared, the dropdown will behave as a modal dialog when in a desktop viewport size.
5577
- */
5578
- desktopModal: {
5579
- type: Boolean,
5580
- reflect: true
5581
- },
5582
-
5583
5591
  /**
5584
5592
  * If declared, the dropdown will only show by calling the API .show() public method.
5585
5593
  */
@@ -5867,15 +5875,6 @@ class AuroDropdown extends AuroElement$2 {
5867
5875
 
5868
5876
  disconnectedCallback() {
5869
5877
  super.disconnectedCallback();
5870
- this._clearPageInert();
5871
- if (this._bibTabHandler) {
5872
- this.removeEventListener('keydown', this._bibTabHandler);
5873
- this._bibTabHandler = undefined;
5874
- }
5875
- if (this.focusTrap) {
5876
- this.focusTrap.disconnect();
5877
- this.focusTrap = undefined;
5878
- }
5879
5878
  if (this.floater) {
5880
5879
  this.floater.hideBib('disconnect');
5881
5880
  this.floater.disconnect();
@@ -5903,45 +5902,19 @@ class AuroDropdown extends AuroElement$2 {
5903
5902
  if (this.isPopoverVisible) {
5904
5903
  // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
5905
5904
  // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
5906
- this.bibElement.value.open(this.isBibFullscreen);
5907
- this.updateFocusTrap();
5908
-
5909
- // Desktop modal: make siblings inert so content outside is not interactive
5910
- if (this.desktopModal && !this.isBibFullscreen) {
5911
- this._setPageInert();
5912
- }
5905
+ const useModal = this.isBibFullscreen;
5906
+ this.bibElement.value.open(useModal);
5913
5907
  } else {
5914
5908
  this.bibElement.value.close();
5915
- this._clearPageInert();
5916
5909
  }
5917
5910
  }
5918
5911
 
5919
5912
  // When fullscreen strategy changes while open, re-open dialog with correct mode
5920
5913
  // (e.g. resizing from desktop → mobile while dropdown is open)
5921
5914
  if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
5915
+ const useModal = this.isBibFullscreen;
5922
5916
  this.bibElement.value.close();
5923
- this.bibElement.value.open(this.isBibFullscreen);
5924
-
5925
- // Re-initialize focus management for the new strategy
5926
- this.updateFocusTrap();
5927
-
5928
- // Toggle inert: desktop modal needs it, fullscreen showModal() handles it natively
5929
- if (this.desktopModal && !this.isBibFullscreen) {
5930
- this._setPageInert();
5931
- } else {
5932
- this._clearPageInert();
5933
- }
5934
- }
5935
-
5936
- // Handle desktopModal toggled while the dropdown is already open.
5937
- // Re-initialize focus trapping and page inert state to match the new mode.
5938
- if (changedProperties.has('desktopModal') && this.isPopoverVisible && !this.isBibFullscreen) {
5939
- this.updateFocusTrap();
5940
- if (this.desktopModal) {
5941
- this._setPageInert();
5942
- } else {
5943
- this._clearPageInert();
5944
- }
5917
+ this.bibElement.value.open(useModal);
5945
5918
  }
5946
5919
  }
5947
5920
 
@@ -5951,14 +5924,8 @@ class AuroDropdown extends AuroElement$2 {
5951
5924
  * @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
5952
5925
  */
5953
5926
  handleDropdownToggle(event) {
5927
+ this.updateFocusTrap();
5954
5928
  this.isPopoverVisible = event.detail.expanded;
5955
-
5956
- // Tear down FocusTrap when closing. Creation happens in updated()
5957
- // after the dialog is open so getFocusableElements can find content.
5958
- if (!this.isPopoverVisible) {
5959
- this.updateFocusTrap();
5960
- }
5961
-
5962
5929
  const eventType = event.detail.eventType || "unknown";
5963
5930
  if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
5964
5931
  this.trigger.focus();
@@ -6057,178 +6024,19 @@ class AuroDropdown extends AuroElement$2 {
6057
6024
  * @private
6058
6025
  */
6059
6026
  updateFocusTrap() {
6060
- // Always clean up existing handlers/traps before setting up new ones
6061
- // to prevent duplicate listeners on repeated calls.
6062
- if (this._bibTabHandler) {
6063
- this.removeEventListener('keydown', this._bibTabHandler);
6064
- this._bibTabHandler = undefined;
6065
- }
6066
-
6067
- if (this.focusTrap) {
6068
- this.focusTrap.disconnect();
6069
- this.focusTrap = undefined;
6070
- }
6071
-
6072
6027
  if (this.isPopoverVisible) {
6073
6028
  if (!this.isBibFullscreen) {
6074
- if (this.desktopModal) {
6075
- // Desktop modal: trap focus only within the bib content.
6076
- // Can't use FocusTrap on the bib element because keydown events
6077
- // from slotted content bubble through the dropdown host (light DOM),
6078
- // not through the bib (shadow projection target). Using FocusTrap
6079
- // on the dropdown would include the trigger in the tab cycle.
6080
- // Instead, listen for Tab on the dropdown and manually wrap focus
6081
- // within the bib's focusable elements.
6082
- this._bibTabHandler = (event) => {
6083
- if (event.key !== 'Tab') {
6084
- return;
6085
- }
6086
-
6087
- // Collect focusable elements from the bib content.
6088
- const focusables = getFocusableElements(this.bibContent);
6089
-
6090
- // Fallback: try from slotted content directly
6091
- if (!focusables.length) {
6092
- const slot = this.shadowRoot.querySelector('.slotContent slot');
6093
- const assignedNodes = slot ? slot.assignedNodes({ flatten: true }) : [];
6094
-
6095
- for (const node of assignedNodes) {
6096
- if (node.nodeType === Node.ELEMENT_NODE) {
6097
- focusables.push(...getFocusableElements(node));
6098
- }
6099
- }
6100
- }
6101
-
6102
- if (!focusables.length) {
6103
- return;
6104
- }
6105
-
6106
- event.preventDefault();
6107
-
6108
- const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
6109
-
6110
- // Walk the active element chain through shadow roots
6111
- const actives = this._getActiveElements();
6112
-
6113
- let idx = focusables.findIndex((el) => actives.includes(el));
6114
-
6115
- if (idx === -1) { // eslint-disable-line no-magic-numbers
6116
- // Focus is not on a known element — move to first/last
6117
- idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
6118
- }
6119
-
6120
- // Try each element in order, skipping any that can't receive focus
6121
- // (e.g. hidden elements, elements in collapsed sections)
6122
- for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
6123
- let nextIdx = idx + direction;
6124
-
6125
- // Wrap around
6126
- if (nextIdx < 0) {
6127
- nextIdx = focusables.length - 1;
6128
- } else if (nextIdx >= focusables.length) {
6129
- nextIdx = 0;
6130
- }
6131
-
6132
- focusables[nextIdx].focus();
6133
-
6134
- // Verify focus actually moved to the target
6135
- const newActives = this._getActiveElements();
6136
-
6137
- if (newActives.includes(focusables[nextIdx])) {
6138
- return;
6139
- }
6140
-
6141
- // Focus didn't stick — skip this element and try the next
6142
- idx = nextIdx;
6143
- }
6144
- };
6145
- this.addEventListener('keydown', this._bibTabHandler);
6146
-
6147
- // Move initial focus into the bib content, matching FocusTrap behavior
6148
- requestAnimationFrame(() => {
6149
- const focusables = getFocusableElements(this.bibContent);
6150
- if (focusables.length) {
6151
- focusables[0].focus();
6152
- }
6153
- });
6154
- } else {
6155
- // Normal desktop: use FocusTrap on the bib element
6156
- this.focusTrap = new FocusTrap(this.bibContent);
6157
- this.focusTrap.focusFirstElement();
6158
- }
6029
+ // Desktop: show() doesn't trap focus, so use FocusTrap
6030
+ this.focusTrap = new FocusTrap(this.bibContent);
6031
+ this.focusTrap.focusFirstElement();
6159
6032
  }
6160
6033
  // Fullscreen: showModal() provides native focus trapping
6161
- }
6162
- }
6163
-
6164
- /**
6165
- * Returns the chain of active (focused) elements through shadow roots.
6166
- * @private
6167
- * @returns {Array<HTMLElement>}
6168
- */
6169
- _getActiveElements() {
6170
- let { activeElement } = document;
6171
- const actives = [activeElement];
6172
-
6173
- while (activeElement?.shadowRoot?.activeElement) {
6174
- activeElement = activeElement.shadowRoot.activeElement;
6175
- actives.push(activeElement);
6176
- }
6177
-
6178
- return actives;
6179
- }
6180
-
6181
- /**
6182
- * Sets `inert` on sibling elements of the dropdown's top-level host
6183
- * so that content outside the dropdown is not interactive while the modal is open.
6184
- * Walks up through shadow DOM boundaries to find the outermost host element
6185
- * in the light DOM, then sets `inert` on siblings at each ancestor level
6186
- * to ensure all page content outside the host subtree is inert.
6187
- * @private
6188
- */
6189
- _setPageInert() {
6190
- if (this._inertSiblings) {
6191
6034
  return;
6192
6035
  }
6193
6036
 
6194
- this._inertSiblings = [];
6195
-
6196
- // Walk up through shadow DOM boundaries to find the topmost host
6197
- // element in the light DOM. For example, if this dropdown is inside
6198
- // auro-datepicker's shadow DOM, we walk up to the datepicker element
6199
- // so we set inert on its siblings — not on the datepicker itself.
6200
- let host = this;
6201
- while (host.getRootNode() instanceof ShadowRoot) {
6202
- host = host.getRootNode().host;
6203
- }
6204
-
6205
- // Walk up the ancestor chain, inerting siblings at each level
6206
- // to ensure the entire page outside the host subtree is inert.
6207
- let current = host;
6208
- while (current.parentElement) {
6209
- const parent = current.parentElement;
6210
- for (const sibling of parent.children) {
6211
- if (sibling !== current) {
6212
- this._inertSiblings.push({ element: sibling, wasInert: sibling.inert });
6213
- sibling.inert = true;
6214
- }
6215
- }
6216
- current = parent;
6217
- }
6218
- }
6219
-
6220
- /**
6221
- * Restores `inert` state on siblings that were tracked by `_setPageInert`.
6222
- * Preserves the previous inert state so externally-inerted elements are
6223
- * not inadvertently re-enabled.
6224
- * @private
6225
- */
6226
- _clearPageInert() {
6227
- if (this._inertSiblings) {
6228
- for (const entry of this._inertSiblings) {
6229
- entry.element.inert = entry.wasInert;
6230
- }
6231
- this._inertSiblings = undefined;
6037
+ if (this.focusTrap) {
6038
+ this.focusTrap.disconnect();
6039
+ this.focusTrap = undefined;
6232
6040
  }
6233
6041
  }
6234
6042
 
@@ -6467,7 +6275,6 @@ class AuroDropdown extends AuroElement$2 {
6467
6275
  shape="${this.shape}"
6468
6276
  ?data-show="${this.isPopoverVisible}"
6469
6277
  ?isfullscreen="${this.isBibFullscreen}"
6470
- ?desktopmodal="${this.desktopModal}"
6471
6278
  .dialogLabel="${this.bibDialogLabel}"
6472
6279
  ${ref(this.bibElement)}
6473
6280
  >
@@ -13166,7 +12973,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
13166
12973
  }
13167
12974
  };
13168
12975
 
13169
- var formkitVersion$1 = '202605262025';
12976
+ var formkitVersion$1 = '202606082059';
13170
12977
 
13171
12978
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
13172
12979
  // See LICENSE in the project root for license information.
@@ -14120,14 +13927,6 @@ class AuroBibtemplate extends LitElement {
14120
13927
  large: {
14121
13928
  type: Boolean,
14122
13929
  reflect: true
14123
- },
14124
-
14125
- /**
14126
- * If declared, the footer slot will be rendered even when not in fullscreen mode.
14127
- */
14128
- showFooter: {
14129
- type: Boolean,
14130
- reflect: true
14131
13930
  }
14132
13931
  };
14133
13932
  }
@@ -14230,7 +14029,7 @@ class AuroBibtemplate extends LitElement {
14230
14029
  <slot></slot>
14231
14030
  </div>
14232
14031
 
14233
- ${this.isFullscreen || this.showFooter ? html`
14032
+ ${this.isFullscreen ? html`
14234
14033
  <div id="footerContainer">
14235
14034
  <slot name="footer"></slot>
14236
14035
  </div>` : null}
@@ -14239,7 +14038,7 @@ class AuroBibtemplate extends LitElement {
14239
14038
  }
14240
14039
  }
14241
14040
 
14242
- var formkitVersion = '202605262025';
14041
+ var formkitVersion = '202606082059';
14243
14042
 
14244
14043
  var styleCss$1 = css`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
14245
14044
 
@@ -14596,6 +14395,25 @@ function normalizeFilterValue(value) {
14596
14395
  return raw.trim() ? raw.trimEnd() : raw;
14597
14396
  }
14598
14397
 
14398
+ /**
14399
+ * Returns the option's plain-text label suitable for input.value, with
14400
+ * HTML-indentation whitespace collapsed and the displayValue slot text
14401
+ * removed (the slot renders separately in the trigger).
14402
+ * @param {HTMLElement} option - An auro-menuoption element.
14403
+ * @returns {string} Normalized label.
14404
+ */
14405
+ function getOptionLabel(option) {
14406
+ if (!option) {
14407
+ return '';
14408
+ }
14409
+ const clone = option.cloneNode(true);
14410
+ const displayValueEl = clone.querySelector('[slot="displayValue"]');
14411
+ if (displayValueEl) {
14412
+ displayValueEl.remove();
14413
+ }
14414
+ return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
14415
+ }
14416
+
14599
14417
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
14600
14418
  /**
14601
14419
  * The `auro-combobox` element provides users with a way to select an option from a list of filtered or suggested options based on user input.
@@ -14723,7 +14541,19 @@ class AuroCombobox extends AuroElement {
14723
14541
  availableOptions: {
14724
14542
  state: true,
14725
14543
  type: Array,
14726
- reflect: false
14544
+ reflect: false,
14545
+ hasChanged(newVal, oldVal) {
14546
+ if (!oldVal && !newVal) {
14547
+ return false;
14548
+ }
14549
+ if (!oldVal || !newVal) {
14550
+ return true;
14551
+ }
14552
+ if (newVal.length !== oldVal.length) {
14553
+ return true;
14554
+ }
14555
+ return newVal.some((opt, idx) => opt !== oldVal[idx]);
14556
+ }
14727
14557
  },
14728
14558
 
14729
14559
  /**
@@ -15154,18 +14984,15 @@ class AuroCombobox extends AuroElement {
15154
14984
  matchString = `${matchString} ${option.getAttribute('suggest')}`.toLowerCase();
15155
14985
  }
15156
14986
 
15157
- // If input is empty, show all options (except static ones)
15158
- if (!filterValue) {
15159
- if (!option.hasAttribute('static')) {
15160
- option.removeAttribute('hidden');
15161
- this.availableOptions.push(option);
15162
- }
15163
- } else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
15164
- // only count options that match the typed input value AND are not static
14987
+ if (!filterValue && option.hasAttribute('static')) {
14988
+ // Static options are shown when the input is empty so consumers can
14989
+ // surface headers / "add new" rows when no filter is active.
14990
+ option.removeAttribute('hidden');
14991
+ this.availableOptions.push(option);
14992
+ } else if (filterValue && matchString.includes(filterValue) && !option.hasAttribute('static')) {
15165
14993
  option.removeAttribute('hidden');
15166
14994
  this.availableOptions.push(option);
15167
14995
  } else if (!option.hasAttribute('persistent')) {
15168
- // Hide all other non-persistent options
15169
14996
  option.setAttribute('hidden', '');
15170
14997
  option.removeAttribute('aria-setsize');
15171
14998
  option.removeAttribute('aria-posinset');
@@ -15173,7 +15000,9 @@ class AuroCombobox extends AuroElement {
15173
15000
  });
15174
15001
 
15175
15002
  if (this.availableOptions.length === 0) {
15176
- if (this.noMatchOption) {
15003
+ // Only surface the nomatch option once the user has actually typed
15004
+ // something. Empty input is handled by the bib being closed.
15005
+ if (this.noMatchOption && filterValue) {
15177
15006
  this.noMatchOption.removeAttribute('hidden');
15178
15007
  } else if (!this.menu.loading || this.isHiddenWhileLoading) {
15179
15008
  this.hideBib();
@@ -15189,11 +15018,14 @@ class AuroCombobox extends AuroElement {
15189
15018
  * @returns {void}
15190
15019
  */
15191
15020
  syncValuesAndStates() {
15192
- // Only sync matchWord, don't set menu.value here since setMenuValue should handle that
15021
+ // Do NOT call setMenuValue here the caller (handleMenuOptions) already
15022
+ // guards setMenuValue with an option-match check. Calling it unconditionally
15023
+ // sets free-form values on the menu, triggering clearSelection which resets
15024
+ // _index and breaks subsequent makeSelection calls.
15193
15025
  if (this.menu) {
15194
15026
  this.menu.matchWord = normalizeFilterValue(this.input.value);
15195
15027
  }
15196
- const label = this.menu.currentLabel;
15028
+ const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
15197
15029
  this.updateTriggerTextDisplay(label || this.value);
15198
15030
  }
15199
15031
 
@@ -15208,7 +15040,39 @@ class AuroCombobox extends AuroElement {
15208
15040
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
15209
15041
 
15210
15042
  if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
15211
- this.input.value = label || this.value;
15043
+ const nextValue = label || this.value;
15044
+ // Only set the flag when there's an actual write to suppress —
15045
+ // syncValuesAndStates re-enters here during typing when both inputs
15046
+ // already match, and a no-op flag flip would make the bib branch's
15047
+ // bail eat legitimate user-input events.
15048
+ const triggerNeedsSync = this.input.value !== nextValue;
15049
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
15050
+ if (triggerNeedsSync || bibNeedsSync) {
15051
+ this._syncingDisplayValue = true;
15052
+ if (triggerNeedsSync) {
15053
+ this.input.value = nextValue;
15054
+ }
15055
+ if (bibNeedsSync) {
15056
+ this.inputInBib.value = nextValue;
15057
+ }
15058
+ const pending = [];
15059
+ if (triggerNeedsSync) {
15060
+ pending.push(this.input.updateComplete);
15061
+ }
15062
+ if (bibNeedsSync) {
15063
+ pending.push(this.inputInBib.updateComplete);
15064
+ }
15065
+ Promise.all(pending).then(() => {
15066
+ // imask reasserts otherwise, and handleBlur reverts to its stale value.
15067
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
15068
+ this.input.maskInstance.updateValue();
15069
+ }
15070
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
15071
+ this.inputInBib.maskInstance.updateValue();
15072
+ }
15073
+ this._syncingDisplayValue = false;
15074
+ });
15075
+ }
15212
15076
  }
15213
15077
 
15214
15078
  // update the displayValue in the trigger if displayValue slot content is present
@@ -15222,6 +15086,14 @@ class AuroCombobox extends AuroElement {
15222
15086
  const displayValueEl = this.menu.optionSelected.querySelector("[slot='displayValue']");
15223
15087
  if (displayValueEl) {
15224
15088
  this.input.appendChild(displayValueEl.cloneNode(true));
15089
+ // auro-input's hasDisplayValueContent is non-reactive; nudge it to
15090
+ // re-evaluate the slot and re-render so the displayValue wrapper
15091
+ // gets its hasContent class. Without this, the apple/icon stays
15092
+ // hidden when input.value is set in the same tick as the append.
15093
+ if (typeof this.input.checkDisplayValueSlotChange === 'function') {
15094
+ this.input.checkDisplayValueSlotChange();
15095
+ this.input.requestUpdate();
15096
+ }
15225
15097
  }
15226
15098
  }
15227
15099
 
@@ -15255,7 +15127,10 @@ class AuroCombobox extends AuroElement {
15255
15127
  this.syncValuesAndStates();
15256
15128
  }
15257
15129
 
15258
- if (!this.availableOptions.includes(this.menu.optionActive)) {
15130
+ // Re-activate when optionActive is no longer visible, or when _index has
15131
+ // been reset (e.g. by clearSelection in an async update) so that
15132
+ // makeSelection() can find the correct option.
15133
+ if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
15259
15134
  this.activateFirstEnabledAvailableOption();
15260
15135
  }
15261
15136
  }
@@ -15380,8 +15255,13 @@ class AuroCombobox extends AuroElement {
15380
15255
 
15381
15256
  guardTouchPassthrough(this.menu);
15382
15257
 
15383
- // Wait for the bibtemplate to fully render across
15384
- // multiple Lit update cycles before moving focus into the bib
15258
+ // The dialog's showModal() steals focus from the trigger.
15259
+ // A single rAF is enough for the dialog DOM to be painted and
15260
+ // focusable, then doubleRaf finalizes the transition.
15261
+ requestAnimationFrame(() => {
15262
+ this.setInputFocus();
15263
+ });
15264
+
15385
15265
  doubleRaf(() => {
15386
15266
  this.setInputFocus();
15387
15267
  this._inFullscreenTransition = false;
@@ -15462,10 +15342,30 @@ class AuroCombobox extends AuroElement {
15462
15342
  */
15463
15343
  setInputFocus() {
15464
15344
  if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
15465
- this.inputInBib.focus();
15345
+ // Sync the native input value synchronously before focusing.
15346
+ // Lit's property-to-attribute sync is async (microtask), so the
15347
+ // native <input> inside inputInBib may still hold a stale value
15348
+ // when focus moves here during the fullscreen transition. Without
15349
+ // this, keystrokes (e.g. Backspace) operate on the wrong content.
15350
+ const nativeInput = this.inputInBib.inputElement;
15351
+ const triggerNativeInput = this.input.inputElement;
15352
+ if (nativeInput && triggerNativeInput && nativeInput.value !== triggerNativeInput.value) {
15353
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
15354
+ nativeInput.value = triggerNativeInput.value || '';
15355
+ }
15356
+
15357
+ // Focus the native input directly to ensure it receives keystrokes
15358
+ // after the fullscreen dialog opens. The dialog's showModal() may
15359
+ // have moved focus to the dialog element itself; focusing the
15360
+ // auro-input custom element doesn't always reach the native input
15361
+ // when the dialog DOM isn't fully painted.
15362
+ if (nativeInput) {
15363
+ nativeInput.focus();
15364
+ } else {
15365
+ this.inputInBib.focus();
15366
+ }
15466
15367
 
15467
15368
  // Place cursor at end of existing text so the user can continue editing
15468
- const nativeInput = this.inputInBib.inputElement;
15469
15369
  if (nativeInput && nativeInput.value) {
15470
15370
  const len = nativeInput.value.length;
15471
15371
  nativeInput.setSelectionRange(len, len);
@@ -15573,53 +15473,92 @@ class AuroCombobox extends AuroElement {
15573
15473
  this.menu.setAttribute('nocheckmark', '');
15574
15474
  }
15575
15475
 
15576
- this.menu.addEventListener("auroMenu-deselectPrevented", () => {
15577
- this.hideBib();
15578
- });
15476
+ // Handle menu option selection
15477
+ this.menu.addEventListener('auroMenu-selectedOption', (evt) => {
15478
+ // Capture before the consumption below — true means this event is the
15479
+ // echo of our own setMenuValue (programmatic value sync), false means
15480
+ // a fresh user selection.
15481
+ const isEcho = this._pendingMenuValueSync === true;
15579
15482
 
15580
- // Handle menu option selection like select does
15581
- this.menu.addEventListener('auroMenu-selectedOption', (event) => {
15582
- // Update the optionSelected from the event details, not manually
15583
- [this.optionSelected] = event.detail.options;
15483
+ if (this.menu.optionSelected) {
15484
+ const selected = this.menu.optionSelected;
15584
15485
 
15585
- // Update the internal value to match the menu's value
15586
- this.value = event.detail.stringValue;
15486
+ if (!this.optionSelected || this.optionSelected !== selected) {
15487
+ this.optionSelected = selected;
15488
+ }
15587
15489
 
15588
- // Update display
15589
- this.updateTriggerTextDisplay(event.detail.label || event.detail.value);
15490
+ // Skip writeback when this event is the echo of our own setMenuValue —
15491
+ // otherwise it cascades against handleInputValueChange in suggestion mode.
15492
+ if (this._pendingMenuValueSync) {
15493
+ this._pendingMenuValueSync = false;
15494
+ } else if (!this.value || this.value !== this.optionSelected.value) {
15495
+ this.value = this.optionSelected.value;
15496
+ }
15590
15497
 
15591
- // Update match word for filtering
15592
- const trimmedInput = normalizeFilterValue(this.input.value);
15593
- if (this.menu.matchWord !== trimmedInput) {
15594
- this.menu.matchWord = trimmedInput;
15595
- }
15498
+ // Update display
15499
+ this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
15596
15500
 
15597
- // Update available options based on selection
15598
- this.handleMenuOptions();
15501
+ // Update match word for filtering
15502
+ const trimmedInput = normalizeFilterValue(this.input.value);
15503
+ if (this.menu.matchWord !== trimmedInput) {
15504
+ this.menu.matchWord = trimmedInput;
15505
+ }
15506
+ }
15599
15507
 
15600
15508
  // Hide dropdown on selection (except during slot changes)
15601
- if (event.detail && event.detail.source !== 'slotchange') {
15602
- setTimeout(() => {
15603
- // do not close while typing in suggestion mode with no value selected, to allow freeform input
15604
- if (this.menu.value || this.behavior !== 'suggestion') {
15605
- this.hideBib();
15606
- }
15607
- }, 0);
15509
+ if (evt.detail && evt.detail.source !== 'slotchange') {
15510
+ // do not close while typing in suggestion mode with no value selected, to allow freeform input
15511
+ if (this.menu.value || this.behavior !== 'suggestion') {
15512
+ this.hideBib();
15513
+ }
15608
15514
 
15609
15515
  // Announce the selection after the dropdown closes so it isn't
15610
15516
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
15611
- const selectedValue = event.detail.stringValue;
15517
+ const selectedValue = this.menu.value;
15612
15518
  const announcementDelay = 300;
15613
15519
  setTimeout(() => {
15614
15520
  announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
15615
15521
  }, announcementDelay);
15616
15522
  }
15523
+
15524
+ // Programmatic value syncs leave availableOptions stale because
15525
+ // updateTriggerTextDisplay sets input.value with _syncingDisplayValue
15526
+ // and handleInputValueChange bails. Refresh the filter on echo events
15527
+ // only — fresh user selections take the existing hideBib path.
15528
+ if (isEcho && this.menu.optionSelected) {
15529
+ this._programmaticFilterRefresh = true;
15530
+ this.handleMenuOptions();
15531
+ setTimeout(() => {
15532
+ this._programmaticFilterRefresh = false;
15533
+ }, 0);
15534
+ }
15535
+
15536
+ // base-input skips auto-validate when focus is inside its own shadow,
15537
+ // which it is after setTriggerInputFocus — re-run so prior tooShort
15538
+ // clears. processCreditCard reasserts errorMessage on every render.
15539
+ if (!isEcho && this.menu.optionSelected && this.input.validate) {
15540
+ this.input.updateComplete.then(() => {
15541
+ this.input.validate(true);
15542
+ if (this.input.validity === 'valid') {
15543
+ this.input.errorMessage = '';
15544
+ }
15545
+ });
15546
+ }
15617
15547
  });
15618
15548
 
15619
15549
  this.menu.addEventListener('auroMenu-customEventFired', () => {
15620
15550
  this.hideBib();
15621
15551
  });
15622
15552
 
15553
+ // When the menu cannot match a programmatic value to any option,
15554
+ // clear the combobox selection state so it doesn't reference a
15555
+ // stale option. Safe from re-entrancy because any resulting
15556
+ // input.value changes dispatch isProgrammatic events.
15557
+ this.menu.addEventListener('auroMenu-selectValueFailure', () => {
15558
+ this.value = undefined;
15559
+ this.optionSelected = undefined;
15560
+ });
15561
+
15623
15562
  this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
15624
15563
  this.optionActive = evt.detail;
15625
15564
 
@@ -15720,11 +15659,12 @@ class AuroCombobox extends AuroElement {
15720
15659
  // event from the trigger. The _syncingBibValue guard persists across the
15721
15660
  // async boundary and prevents that re-entrant event from running the
15722
15661
  // non-fullscreen path (which would call clear() → hideBib()).
15723
- // When the event comes from the fullscreen bib input, sync the value to
15724
- // the trigger and run filtering, but suppress the re-entrant input event
15725
- // that the trigger fires (via Lit updated() → notifyValueChanged()) so
15726
- // the non-fullscreen hide/clear logic doesn't close the dialog.
15727
15662
  if (event.target === this.inputInBib) {
15663
+ // Mirror the trigger-branch bail below — programmatic inputInBib writes
15664
+ // notify back into this branch via Lit's notifyValueChanged.
15665
+ if (this._syncingDisplayValue) {
15666
+ return;
15667
+ }
15728
15668
  this._syncingBibValue = true;
15729
15669
  this.input.value = this.inputInBib.value;
15730
15670
  this.input.updateComplete.then(() => {
@@ -15734,18 +15674,37 @@ class AuroCombobox extends AuroElement {
15734
15674
  // Run filtering inline — the re-entrant event won't reach this code.
15735
15675
  this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
15736
15676
  this.optionActive = null;
15677
+
15678
+ // In suggestion mode, keep the combobox value in sync with the typed
15679
+ // text so that freeform values are captured (mirroring the non-fullscreen
15680
+ // path). Clear the selection when the input is emptied.
15681
+ if (this.behavior === 'suggestion') {
15682
+ this.value = this.inputInBib.value || undefined;
15683
+ }
15684
+
15737
15685
  this.handleMenuOptions();
15738
15686
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
15739
15687
  return;
15740
15688
  }
15741
15689
 
15742
- // Ignore re-entrant input events caused by the bib→trigger sync above.
15743
- if (this._syncingBibValue) {
15690
+ // Ignore re-entrant input events caused by programmatic value sets.
15691
+ if (this._syncingBibValue || this._syncingDisplayValue) {
15744
15692
  return;
15745
15693
  }
15746
15694
 
15747
15695
  this.inputInBib.value = this.input.value;
15748
15696
 
15697
+ // Also sync the native input immediately so keystrokes arriving
15698
+ // before Lit's async update cycle (e.g. rapid Backspaces during a
15699
+ // fullscreen transition) operate on the correct content.
15700
+ // Skip the next programmatic input event to prevent the patched setter
15701
+ // from re-entering handleInputValueChange via the bib path.
15702
+ const bibNativeInput = this.inputInBib.inputElement;
15703
+ if (bibNativeInput && bibNativeInput.value !== this.input.value) {
15704
+ this.inputInBib.skipNextProgrammaticInputEvent = true;
15705
+ bibNativeInput.value = this.input.value || '';
15706
+ }
15707
+
15749
15708
  this.menu.matchWord = normalizeFilterValue(this.input.value);
15750
15709
  this.optionActive = null;
15751
15710
 
@@ -15753,7 +15712,7 @@ class AuroCombobox extends AuroElement {
15753
15712
  this.value = this.input.value;
15754
15713
  }
15755
15714
 
15756
- if (!this.input.value) {
15715
+ if (!this.input.value && !this._clearing) {
15757
15716
  this.clear();
15758
15717
  }
15759
15718
  this.handleMenuOptions();
@@ -15883,10 +15842,18 @@ class AuroCombobox extends AuroElement {
15883
15842
  * @returns {void}
15884
15843
  */
15885
15844
  setMenuValue(value) {
15886
- if (!this.menu) {
15845
+ if (!this.menu || this.menu.value === value) {
15887
15846
  return;
15888
15847
  }
15848
+ // One-shot flag consumed by the auroMenu-selectedOption listener so the
15849
+ // echo of this sync doesn't propagate back to this.value.
15850
+ this._pendingMenuValueSync = true;
15889
15851
  this.menu.value = value;
15852
+ // Backup clear: if menu.value === menu.optionSelected.value already, the
15853
+ // listener won't fire and the flag would swallow the next user click.
15854
+ setTimeout(() => {
15855
+ this._pendingMenuValueSync = false;
15856
+ }, 0);
15890
15857
  }
15891
15858
 
15892
15859
  /**
@@ -15908,16 +15875,24 @@ class AuroCombobox extends AuroElement {
15908
15875
  * @returns {void}
15909
15876
  */
15910
15877
  clear() {
15911
- // Clear combobox state first
15912
- this.optionSelected = undefined;
15913
- this.value = undefined;
15914
-
15915
- // Then clear input and menu
15916
- if (this.input.value) {
15917
- this.input.clear();
15878
+ // input.clear() fires an input event that re-enters handleInputValueChange
15879
+ // → which calls clear() again on empty input. Guard against that loop.
15880
+ if (this._clearing) {
15881
+ return;
15918
15882
  }
15919
- if (this.menu.value || this.menu.optionSelected) {
15920
- this.menu.reset();
15883
+ this._clearing = true;
15884
+ try {
15885
+ this.optionSelected = undefined;
15886
+ this.value = undefined;
15887
+
15888
+ if (this.input.value) {
15889
+ this.input.clear();
15890
+ }
15891
+ if (this.menu.value || this.menu.optionSelected) {
15892
+ this.menu.reset();
15893
+ }
15894
+ } finally {
15895
+ this._clearing = false;
15921
15896
  }
15922
15897
  }
15923
15898
 
@@ -15945,29 +15920,64 @@ class AuroCombobox extends AuroElement {
15945
15920
  this.input.value = this.value;
15946
15921
  }
15947
15922
 
15948
- if (this.menu && this.hasValue && this.menu.options) {
15949
- this.menu.options.forEach((opt) => {
15950
- if (!opt.hasAttribute('static')) {
15951
- opt.removeAttribute('hidden');
15952
- }
15953
- });
15954
- }
15955
-
15956
- if (this.behavior === 'suggestion') {
15957
- if (!this.menu.options || this.menu.options.length === 0) ; else if (this.menu.options.filter((opt) => opt.value === this.value).length > 0) {
15923
+ // Sync menu.value only when an option actually matches; otherwise clear it.
15924
+ if (this.menu.options && this.menu.options.length > 0) {
15925
+ if (this.menu.options.some((opt) => opt.value === this.value)) {
15926
+ this.setMenuValue(this.value);
15927
+ } else if (this.behavior === 'filter') {
15928
+ // In filter mode, freeform values aren't allowed. Push the unmatched
15929
+ // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
15930
+ // and the listener at line 1206 clears combobox.value (mirrors select's
15931
+ // pattern). Bypassing setMenuValue here would skip the failure cascade.
15958
15932
  this.setMenuValue(this.value);
15959
15933
  } else {
15960
- this.menu.value = undefined;
15961
- // Sync the input display for freeform values that don't match any option
15962
- this.input.value = this.value;
15963
- }
15964
- } else {
15965
- // Use setMenuValue like select does instead of direct assignment
15966
- this.setMenuValue(this.value);
15967
- if (!this.value) {
15968
- this.clear();
15934
+ if (this.menu.value) {
15935
+ this.menu.value = undefined;
15936
+ }
15937
+ // Sync the display for programmatic freeform value changes (e.g. the
15938
+ // swap-values pattern). Guard with _syncingDisplayValue so that the
15939
+ // re-entrant input event from base-input.notifyValueChanged() is
15940
+ // ignored by handleInputValueChange. Skip the sync when input already
15941
+ // matches to avoid spurious events during the normal typing path.
15942
+ const nextValue = this.value || '';
15943
+ const triggerNeedsSync = this.input.value !== nextValue;
15944
+ const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
15945
+ if (triggerNeedsSync || bibNeedsSync) {
15946
+ this._syncingDisplayValue = true;
15947
+ if (triggerNeedsSync) {
15948
+ this.input.value = nextValue;
15949
+ }
15950
+ if (bibNeedsSync) {
15951
+ this.inputInBib.value = nextValue;
15952
+ }
15953
+ const pending = [];
15954
+ if (triggerNeedsSync) {
15955
+ pending.push(this.input.updateComplete);
15956
+ }
15957
+ if (bibNeedsSync) {
15958
+ pending.push(this.inputInBib.updateComplete);
15959
+ }
15960
+ Promise.all(pending).then(() => {
15961
+ if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
15962
+ this.input.maskInstance.updateValue();
15963
+ }
15964
+ if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
15965
+ this.inputInBib.maskInstance.updateValue();
15966
+ }
15967
+ this._syncingDisplayValue = false;
15968
+ // handleInputValueChange bailed on the flag — refresh the filter.
15969
+ this._programmaticFilterRefresh = true;
15970
+ this.handleMenuOptions();
15971
+ setTimeout(() => {
15972
+ this._programmaticFilterRefresh = false;
15973
+ }, 0);
15974
+ });
15975
+ }
15969
15976
  }
15970
15977
  }
15978
+ if (!this.value) {
15979
+ this.clear();
15980
+ }
15971
15981
  if (this.value && !this.componentHasFocus) {
15972
15982
  // If the value got set programmatically make sure we hide the bib
15973
15983
  // when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
@@ -16004,12 +16014,18 @@ class AuroCombobox extends AuroElement {
16004
16014
  // branch from calling hideBib() when the dropdown was just opened but
16005
16015
  // :focus-within hasn't propagated through the top-layer dialog's nested
16006
16016
  // shadow DOM boundaries.
16007
- if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
16017
+ // Skip the show/hide side effects when the availableOptions change came
16018
+ // from a programmatic filter refresh after a value swap — the user
16019
+ // didn't interact, so we shouldn't auto-open the bib (especially for
16020
+ // the no-match path where the existing condition unconditionally fires).
16021
+ if (this._programmaticFilterRefresh) {
16022
+ this._programmaticFilterRefresh = false;
16023
+ } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
16008
16024
  this.showBib();
16009
16025
  if (!this.availableOptions.includes(this.menu.optionActive)) {
16010
16026
  this.activateFirstEnabledAvailableOption();
16011
16027
  }
16012
- } else {
16028
+ } else if (this.dropdown && this.dropdown.isPopoverVisible) {
16013
16029
  this.hideBib();
16014
16030
  }
16015
16031
  }