@aurodesignsystem-dev/auro-formkit 0.0.0-pr1498.0 → 0.0.0-pr1499.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 (62) hide show
  1. package/components/checkbox/demo/customize.min.js +2 -2
  2. package/components/checkbox/demo/getting-started.min.js +2 -2
  3. package/components/checkbox/demo/index.min.js +2 -2
  4. package/components/checkbox/dist/index.js +2 -2
  5. package/components/checkbox/dist/registered.js +2 -2
  6. package/components/combobox/demo/customize.md +3 -7
  7. package/components/combobox/demo/customize.min.js +1705 -1459
  8. package/components/combobox/demo/getting-started.min.js +1705 -1459
  9. package/components/combobox/demo/index.min.js +1705 -1459
  10. package/components/combobox/dist/auro-combobox.d.ts +0 -5
  11. package/components/combobox/dist/index.js +162 -491
  12. package/components/combobox/dist/registered.js +162 -491
  13. package/components/counter/demo/customize.min.js +19 -18
  14. package/components/counter/demo/index.min.js +19 -18
  15. package/components/counter/dist/index.js +187 -5442
  16. package/components/counter/dist/registered.js +187 -5442
  17. package/components/datepicker/demo/customize.md +477 -384
  18. package/components/datepicker/demo/customize.min.js +96 -161
  19. package/components/datepicker/demo/index.md +0 -1
  20. package/components/datepicker/demo/index.min.js +96 -161
  21. package/components/datepicker/dist/index.js +96 -161
  22. package/components/datepicker/dist/registered.js +96 -161
  23. package/components/datepicker/dist/src/auro-datepicker.d.ts +2 -2
  24. package/components/dropdown/demo/customize.min.js +17 -16
  25. package/components/dropdown/demo/getting-started.min.js +17 -16
  26. package/components/dropdown/demo/index.min.js +17 -16
  27. package/components/dropdown/dist/auro-dropdown.d.ts +2 -1
  28. package/components/dropdown/dist/index.js +17 -16
  29. package/components/dropdown/dist/registered.js +17 -16
  30. package/components/form/demo/customize.min.js +7965 -7884
  31. package/components/form/demo/getting-started.min.js +7965 -7884
  32. package/components/form/demo/index.min.js +7965 -7884
  33. package/components/form/demo/registerDemoDeps.min.js +7934 -7853
  34. package/components/input/demo/customize.md +539 -472
  35. package/components/input/demo/customize.min.js +61 -132
  36. package/components/input/demo/getting-started.min.js +61 -132
  37. package/components/input/demo/index.min.js +61 -132
  38. package/components/input/dist/base-input.d.ts +0 -16
  39. package/components/input/dist/index.js +61 -132
  40. package/components/input/dist/registered.js +61 -132
  41. package/components/menu/demo/api.md +45 -41
  42. package/components/menu/demo/customize.md +28 -0
  43. package/components/menu/demo/index.min.js +1358 -783
  44. package/components/menu/dist/auro-menu.context.d.ts +238 -0
  45. package/components/menu/dist/auro-menu.d.ts +110 -95
  46. package/components/menu/dist/auro-menuoption.d.ts +138 -32
  47. package/components/menu/dist/index.js +1312 -757
  48. package/components/menu/dist/registered.js +1312 -769
  49. package/components/radio/demo/customize.min.js +2 -2
  50. package/components/radio/demo/getting-started.min.js +2 -2
  51. package/components/radio/demo/index.min.js +2 -2
  52. package/components/radio/dist/index.js +2 -2
  53. package/components/radio/dist/registered.js +2 -2
  54. package/components/select/demo/customize.md +3 -7
  55. package/components/select/demo/customize.min.js +1634 -1089
  56. package/components/select/demo/getting-started.min.js +1634 -1089
  57. package/components/select/demo/index.min.js +1634 -1089
  58. package/components/select/dist/index.js +31 -61
  59. package/components/select/dist/registered.js +31 -61
  60. package/custom-elements.json +2085 -1439
  61. package/package.json +1 -1
  62. package/components/select/dist/selectUtils.d.ts +0 -12
@@ -555,7 +555,7 @@ let AuroFormValidation$1 = class AuroFormValidation {
555
555
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-input') && elem.errorMessage === '') {
556
556
  const input = elem.renderRoot.querySelector('input');
557
557
 
558
- if (input && input.validationMessage.length > 0) {
558
+ if (input.validationMessage.length > 0) {
559
559
  elem.errorMessage = input.validationMessage;
560
560
  }
561
561
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-combobox') && elem.errorMessage === '') {
@@ -797,21 +797,6 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
797
797
  return isFocused;
798
798
  }
799
799
 
800
- /**
801
- * Reconcile the menu `_index` from `optionActive` so subsequent `makeSelection` calls find the correct option after async clearSelection has reset it.
802
- * @param {Object} menu - The menu component.
803
- */
804
- function reconcileMenuIndex(menu) {
805
- // eslint-disable-next-line no-underscore-dangle
806
- if (menu._index < 0 && menu.optionActive && menu.items) {
807
- const idx = menu.items.indexOf(menu.optionActive);
808
- if (idx >= 0) {
809
- // eslint-disable-next-line no-underscore-dangle
810
- menu._index = idx;
811
- }
812
- }
813
- }
814
-
815
800
  const comboboxKeyboardStrategy = {
816
801
  ArrowDown(component, evt, ctx) {
817
802
  // If the clear button has focus, let the browser handle ArrowDown normally.
@@ -875,7 +860,6 @@ const comboboxKeyboardStrategy = {
875
860
  // block the browser's built-in "Enter activates focused button" behavior.
876
861
  evt.stopPropagation();
877
862
  } else if (ctx.isExpanded && component.menu.optionActive) {
878
- reconcileMenuIndex(component.menu);
879
863
  component.menu.makeSelection();
880
864
 
881
865
  if (ctx.isModal) {
@@ -923,7 +907,6 @@ const comboboxKeyboardStrategy = {
923
907
  // When the clear button is focused, Tab events do not bubble out of
924
908
  // its shadow DOM, so this handler only fires when the clear button
925
909
  // is NOT focused. In that case, select the active option and close.
926
- reconcileMenuIndex(component.menu);
927
910
  component.menu.makeSelection();
928
911
  component.hideBib();
929
912
 
@@ -4806,7 +4789,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4806
4789
  }
4807
4790
  };
4808
4791
 
4809
- var formkitVersion$2 = '202606100232';
4792
+ var formkitVersion$2 = '202606101857';
4810
4793
 
4811
4794
  let AuroElement$2 = class AuroElement extends LitElement {
4812
4795
  static get properties() {
@@ -5664,18 +5647,27 @@ class AuroDropdown extends AuroElement$2 {
5664
5647
  this.focusTrap = undefined;
5665
5648
  }
5666
5649
 
5667
- // Restore any focus-loss suppression we installed for a prior trap state.
5668
- // The desktopModal trap moves focus into the bib via RAF, which would
5669
- // otherwise trip AuroFloatingUI.handleFocusLoss (a popover-top-layer
5670
- // timing issue makes its :focus-within guard miss the focused button).
5671
- if (this._priorNoHideOnFocusLoss !== undefined) {
5672
- this.noHideOnThisFocusLoss = this._priorNoHideOnFocusLoss;
5673
- this._priorNoHideOnFocusLoss = undefined;
5650
+ // Restore the user-set noHideOnThisFocusLoss value, if we overrode it.
5651
+ if (this._noHideOverridden) {
5652
+ this.noHideOnThisFocusLoss = this._priorNoHide;
5653
+ this._noHideOverridden = false;
5654
+ this._priorNoHide = undefined;
5674
5655
  }
5675
5656
 
5676
5657
  if (this.isPopoverVisible) {
5677
5658
  if (!this.isBibFullscreen) {
5678
5659
  if (this.desktopModal) {
5660
+ // The floater's focus-loss check uses :focus-within, which does not
5661
+ // match the host when focus is on a slotted element projected through
5662
+ // multiple shadow roots. Without this override, focusing a button
5663
+ // inside the bib would be misread as focus leaving the dropdown and
5664
+ // close the bib immediately. desktopModal traps focus inside the
5665
+ // bib and inerts siblings, so suppressing focus-loss dismissal here
5666
+ // is safe — Escape and outside-click still close the bib.
5667
+ this._priorNoHide = this.noHideOnThisFocusLoss;
5668
+ this._noHideOverridden = true;
5669
+ this.noHideOnThisFocusLoss = true;
5670
+
5679
5671
  // Desktop modal: trap focus only within the bib content.
5680
5672
  // Can't use FocusTrap on the bib element because keydown events
5681
5673
  // from slotted content bubble through the dropdown host (light DOM),
@@ -5748,14 +5740,6 @@ class AuroDropdown extends AuroElement$2 {
5748
5740
  };
5749
5741
  this.addEventListener('keydown', this._bibTabHandler);
5750
5742
 
5751
- // Suppress AuroFloatingUI's auto-hide-on-focus-loss while the
5752
- // desktopModal trap owns focus management. Without this, the very
5753
- // first focus move into the bib (from the RAF below) triggers
5754
- // handleFocusLoss → hideBib, tearing down the trap before the
5755
- // user can press Tab.
5756
- this._priorNoHideOnFocusLoss = this.noHideOnThisFocusLoss;
5757
- this.noHideOnThisFocusLoss = true;
5758
-
5759
5743
  // Move initial focus into the bib content, matching FocusTrap behavior
5760
5744
  requestAnimationFrame(() => {
5761
5745
  const focusables = getFocusableElements(this.bibContent);
@@ -10678,7 +10662,7 @@ class AuroFormValidation {
10678
10662
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-input') && elem.errorMessage === '') {
10679
10663
  const input = elem.renderRoot.querySelector('input');
10680
10664
 
10681
- if (input && input.validationMessage.length > 0) {
10665
+ if (input.validationMessage.length > 0) {
10682
10666
  elem.errorMessage = input.validationMessage;
10683
10667
  }
10684
10668
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-combobox') && elem.errorMessage === '') {
@@ -16350,7 +16334,11 @@ class AuroInputUtilities {
16350
16334
  return part.value;
16351
16335
  }
16352
16336
  })
16353
- .join("");
16337
+ .join("")
16338
+ // Remove whitespace and leading/trailing special characters for IMask compatibility
16339
+ // for example, ko-KR's format is `YYYY. MM. DD.` which causes issues with IMask if we don't remove the trailing period.
16340
+ .replace(/\s/gu, '')
16341
+ .replace(/^[^A-Z0-9]+|[^A-Z0-9]+$/gu, '');
16354
16342
  }
16355
16343
 
16356
16344
  /**
@@ -16515,6 +16503,7 @@ class AuroInputUtilities {
16515
16503
  const maskOptions = this.getMaskOptions('date', normalizedFormat);
16516
16504
 
16517
16505
  if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
16506
+ console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
16518
16507
  return undefined;
16519
16508
  }
16520
16509
 
@@ -17217,7 +17206,7 @@ class BaseInput extends AuroElement$1 {
17217
17206
  * @returns {Date|undefined}
17218
17207
  */
17219
17208
  get valueObject() {
17220
- return this._valueObject || this._computeDateObjectFallback(this.value);
17209
+ return this._valueObject;
17221
17210
  }
17222
17211
 
17223
17212
  /**
@@ -17225,7 +17214,7 @@ class BaseInput extends AuroElement$1 {
17225
17214
  * @returns {Date|undefined}
17226
17215
  */
17227
17216
  get minObject() {
17228
- return this._minObject || this._computeDateObjectFallback(this.min);
17217
+ return this._minObject;
17229
17218
  }
17230
17219
 
17231
17220
  /**
@@ -17233,31 +17222,7 @@ class BaseInput extends AuroElement$1 {
17233
17222
  * @returns {Date|undefined}
17234
17223
  */
17235
17224
  get maxObject() {
17236
- return this._maxObject || this._computeDateObjectFallback(this.max);
17237
- }
17238
-
17239
- /**
17240
- * Parses a date string into a Date object when the corresponding `_*Object`
17241
- * field hasn't been synced yet by `updated()`. Returns undefined when the
17242
- * input type/format isn't a full date or the string is not a valid date.
17243
- *
17244
- * Why this exists: a parent (datepicker) can call `inputN.validate()` from
17245
- * inside its own `updated()` before this input's `updated()` has run
17246
- * `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
17247
- * and range checks would otherwise silently no-op (flipping the result to
17248
- * `valid` or `patternMismatch`).
17249
- * @private
17250
- * @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
17251
- * @returns {Date|undefined}
17252
- */
17253
- _computeDateObjectFallback(dateStr) {
17254
- if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
17255
- return undefined;
17256
- }
17257
- if (!dateFormatter.isValidDate(dateStr)) {
17258
- return undefined;
17259
- }
17260
- return dateFormatter.stringToDateInstance(dateStr);
17225
+ return this._maxObject;
17261
17226
  }
17262
17227
 
17263
17228
  /**
@@ -17282,30 +17247,8 @@ class BaseInput extends AuroElement$1 {
17282
17247
  connectedCallback() {
17283
17248
  super.connectedCallback();
17284
17249
 
17285
- // Mark for query selectors when registered under a versioned tag (e.g. inside
17286
- // a datepicker/combobox). Must run before parent components call validate() on
17287
- // their inner inputs — the validation framework matches via tag-or-attribute,
17288
- // and parents trigger validation during their own updated() cycle, which can
17289
- // precede this input's firstUpdated().
17290
- if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17291
- this.setAttribute('auro-input', '');
17292
- }
17293
-
17294
17250
  this.locale = this.domHandler.getLocale(this);
17295
17251
  notifyOnLangChange(this);
17296
-
17297
- // Pre-compute lengthForType and date-object fields so a parent (e.g. datepicker)
17298
- // calling our validate() synchronously during its own updated() cycle sees
17299
- // populated values. Without this, range validation silently no-ops because
17300
- // `max.length === lengthForType` fails when lengthForType is still undefined.
17301
- // Rebuild util here (constructor seeded with en-US default) so configureDataForType's
17302
- // locale-derived format lookup uses the actual locale just resolved above.
17303
- this.util = new AuroInputUtilities({
17304
- locale: this.locale,
17305
- format: this.format
17306
- });
17307
- this.configureDataForType();
17308
- this.syncDateValues();
17309
17252
  }
17310
17253
 
17311
17254
  disconnectedCallback() {
@@ -17326,8 +17269,8 @@ class BaseInput extends AuroElement$1 {
17326
17269
  }
17327
17270
 
17328
17271
  // add attribute for query selectors when auro-input is registered under a custom name
17329
- if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
17330
- this.setAttribute('auro-input', '');
17272
+ if (this.tagName.toLowerCase() !== 'auro-input') {
17273
+ this.setAttribute('auro-input', true);
17331
17274
  }
17332
17275
  this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
17333
17276
 
@@ -17375,12 +17318,6 @@ class BaseInput extends AuroElement$1 {
17375
17318
  return;
17376
17319
  }
17377
17320
 
17378
- // While configureAutoFormatting is running, imask's internal updateControl
17379
- // writes el.value to align display with the masked value. A synthetic input
17380
- // event from that write would re-enter handleInput → processCreditCard mid-setup
17381
- // and clobber a Lit value that was just pushed by the parent combobox.
17382
- if (component._configuringMask) return;
17383
-
17384
17321
  // If all guard clauses are passed, dispatch the event
17385
17322
  const inputEvent = new InputEvent('input', {
17386
17323
  bubbles: true,
@@ -17499,16 +17436,7 @@ class BaseInput extends AuroElement$1 {
17499
17436
 
17500
17437
  if (formattedValue !== this.inputElement.value) {
17501
17438
  this.skipNextProgrammaticInputEvent = true;
17502
- if (this.maskInstance && this.type === 'credit-card') {
17503
- // Route through the mask so its _value and el.value stay in lock-step
17504
- // (set value calls updateControl which writes el.value = displayValue).
17505
- // Writing el.value directly leaves the mask thinking displayValue is
17506
- // stale; _saveSelection on the next focus/click then warns. Scoped to
17507
- // credit-card so date's own formattedValue (raw ISO when calendar-invalid)
17508
- // isn't re-masked through the mm/dd/yyyy mask, which would truncate it
17509
- // and flip validity from patternMismatch to tooShort.
17510
- this.maskInstance.value = formattedValue || '';
17511
- } else if (formattedValue) {
17439
+ if (formattedValue) {
17512
17440
  this.inputElement.value = formattedValue;
17513
17441
  } else {
17514
17442
  this.inputElement.value = '';
@@ -17617,73 +17545,58 @@ class BaseInput extends AuroElement$1 {
17617
17545
  * @returns {void}
17618
17546
  */
17619
17547
  configureAutoFormatting() {
17620
- // Re-entrancy guard. The patched-setter's synthetic input event (suppressed
17621
- // by _configuringMask above) could otherwise trigger handleInput →
17622
- // processCreditCard → configureAutoFormatting before the outer call's
17623
- // set value has finished its alignCursor pass.
17624
- if (this._configuringMask) return;
17625
- this._configuringMask = true;
17626
- try {
17627
- if (this.maskInstance) {
17628
- this.maskInstance.destroy();
17629
- }
17548
+ if (this.maskInstance) {
17549
+ this.maskInstance.destroy();
17550
+ }
17630
17551
 
17631
- // Pass new format to util
17632
- this.util.updateFormat(this.format);
17552
+ // Pass new format to util
17553
+ this.util.updateFormat(this.format);
17633
17554
 
17634
- const maskOptions = this.util.getMaskOptions(this.type, this.format);
17555
+ const maskOptions = this.util.getMaskOptions(this.type, this.format);
17635
17556
 
17636
- if (this.inputElement && maskOptions.mask) {
17557
+ if (this.inputElement && maskOptions.mask) {
17637
17558
 
17638
- // Stash and clear any existing value before IMask init.
17639
- // IMask's constructor processes the current input value which requires
17640
- // selection state — clearing first avoids that scenario entirely.
17641
- // When the format changes (e.g. locale switch) and we have a valid ISO
17642
- // model value, compute the display string for the NEW format instead of
17643
- // re-using the old display string, which may be invalid in the new mask.
17644
- let existingValue = this.inputElement.value;
17645
- if (
17646
- this.util.isFullDateFormat(this.type, this.format) &&
17647
- this.value &&
17648
- dateFormatter.isValidDate(this.value) &&
17649
- this.valueObject instanceof Date &&
17650
- !Number.isNaN(this.valueObject.getTime()) &&
17651
- typeof maskOptions.format === 'function'
17652
- ) {
17653
- existingValue = maskOptions.format(this.valueObject);
17654
- }
17559
+ // Stash and clear any existing value before IMask init.
17560
+ // IMask's constructor processes the current input value which requires
17561
+ // selection state — clearing first avoids that scenario entirely.
17562
+ // When the format changes (e.g. locale switch) and we have a valid ISO
17563
+ // model value, compute the display string for the NEW format instead of
17564
+ // re-using the old display string, which may be invalid in the new mask.
17565
+ let existingValue = this.inputElement.value;
17566
+ if (
17567
+ this.util.isFullDateFormat(this.type, this.format) &&
17568
+ this.value &&
17569
+ dateFormatter.isValidDate(this.value) &&
17570
+ this.valueObject instanceof Date &&
17571
+ !Number.isNaN(this.valueObject.getTime()) &&
17572
+ typeof maskOptions.format === 'function'
17573
+ ) {
17574
+ existingValue = maskOptions.format(this.valueObject);
17575
+ }
17655
17576
 
17656
- this.skipNextProgrammaticInputEvent = true;
17657
- this.inputElement.value = '';
17658
-
17659
- this.maskInstance = IMask(this.inputElement, maskOptions);
17660
-
17661
- this.maskInstance.on('accept', () => {
17662
- // Suppress propagation during configureAutoFormatting's own value-restoration
17663
- // (line below) — the mask emits 'accept' on every value-set, including ours,
17664
- // and we don't want to overwrite a value the parent just pushed.
17665
- if (this._configuringMask) return;
17666
- this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17667
- if (this.type === "date") {
17668
- this._rawMaskValue = this.maskInstance.value;
17669
- }
17670
- });
17577
+ this.skipNextProgrammaticInputEvent = true;
17578
+ this.inputElement.value = '';
17671
17579
 
17672
- this.maskInstance.on('complete', () => {
17673
- if (this._configuringMask) return;
17674
- this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17675
- if (this.type === "date") {
17676
- this._rawMaskValue = this.maskInstance.value;
17677
- }
17678
- });
17580
+ this.maskInstance = IMask(this.inputElement, maskOptions);
17581
+
17582
+ this.maskInstance.on('accept', () => {
17583
+ this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17584
+ if (this.type === "date") {
17585
+ this._rawMaskValue = this.maskInstance.value;
17586
+ }
17587
+ });
17679
17588
 
17680
- // Restore the stashed value through IMask so it's properly masked
17681
- if (existingValue) {
17682
- this.maskInstance.value = existingValue;
17589
+ this.maskInstance.on('complete', () => {
17590
+ this.value = this.util.toModelValue(this.maskInstance.value, this.format);
17591
+ if (this.type === "date") {
17592
+ this._rawMaskValue = this.maskInstance.value;
17683
17593
  }
17594
+ });
17595
+
17596
+ // Restore the stashed value through IMask so it's properly masked
17597
+ if (existingValue) {
17598
+ this.maskInstance.value = existingValue;
17684
17599
  }
17685
- } finally {
17686
- this._configuringMask = false;
17687
17600
  }
17688
17601
  }
17689
17602
 
@@ -17970,10 +17883,10 @@ class BaseInput extends AuroElement$1 {
17970
17883
  },
17971
17884
  {
17972
17885
  name: 'Diners club',
17973
- regex: /^(?<num>30[0-5]|36|38)\d{0}/u,
17886
+ regex: /^(?<num>36|38)\d{0}/u,
17974
17887
  formatLength: 16,
17975
17888
  errorMessage: CreditCardValidationMessage,
17976
- cardIcon: 'cc-dinersclub',
17889
+ cardIcon: 'credit-card',
17977
17890
  maskFormat: "0000 000000 0000"
17978
17891
  },
17979
17892
  {
@@ -18383,7 +18296,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18383
18296
  }
18384
18297
  };
18385
18298
 
18386
- var formkitVersion$1 = '202606100232';
18299
+ var formkitVersion$1 = '202606101857';
18387
18300
 
18388
18301
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18389
18302
  // See LICENSE in the project root for license information.
@@ -19508,7 +19421,7 @@ class AuroBibtemplate extends LitElement {
19508
19421
  }
19509
19422
  }
19510
19423
 
19511
- var formkitVersion = '202606100232';
19424
+ var formkitVersion = '202606101857';
19512
19425
 
19513
19426
  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}`;
19514
19427
 
@@ -19865,25 +19778,6 @@ function normalizeFilterValue(value) {
19865
19778
  return raw.trim() ? raw.trimEnd() : raw;
19866
19779
  }
19867
19780
 
19868
- /**
19869
- * Returns the option's plain-text label suitable for input.value, with
19870
- * HTML-indentation whitespace collapsed and the displayValue slot text
19871
- * removed (the slot renders separately in the trigger).
19872
- * @param {HTMLElement} option - An auro-menuoption element.
19873
- * @returns {string} Normalized label.
19874
- */
19875
- function getOptionLabel(option) {
19876
- if (!option) {
19877
- return '';
19878
- }
19879
- const clone = option.cloneNode(true);
19880
- const displayValueEl = clone.querySelector('[slot="displayValue"]');
19881
- if (displayValueEl) {
19882
- displayValueEl.remove();
19883
- }
19884
- return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
19885
- }
19886
-
19887
19781
  // See https://git.io/JJ6SJ for "How to document your components using JSDoc"
19888
19782
  /**
19889
19783
  * 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.
@@ -20011,19 +19905,7 @@ class AuroCombobox extends AuroElement {
20011
19905
  availableOptions: {
20012
19906
  state: true,
20013
19907
  type: Array,
20014
- reflect: false,
20015
- hasChanged(newVal, oldVal) {
20016
- if (!oldVal && !newVal) {
20017
- return false;
20018
- }
20019
- if (!oldVal || !newVal) {
20020
- return true;
20021
- }
20022
- if (newVal.length !== oldVal.length) {
20023
- return true;
20024
- }
20025
- return newVal.some((opt, idx) => opt !== oldVal[idx]);
20026
- }
19908
+ reflect: false
20027
19909
  },
20028
19910
 
20029
19911
  /**
@@ -20454,15 +20336,18 @@ class AuroCombobox extends AuroElement {
20454
20336
  matchString = `${matchString} ${option.getAttribute('suggest')}`.toLowerCase();
20455
20337
  }
20456
20338
 
20457
- if (!filterValue && option.hasAttribute('static')) {
20458
- // Static options are shown when the input is empty so consumers can
20459
- // surface headers / "add new" rows when no filter is active.
20460
- option.removeAttribute('hidden');
20461
- this.availableOptions.push(option);
20462
- } else if (filterValue && matchString.includes(filterValue) && !option.hasAttribute('static')) {
20339
+ // If input is empty, show all options (except static ones)
20340
+ if (!filterValue) {
20341
+ if (!option.hasAttribute('static')) {
20342
+ option.removeAttribute('hidden');
20343
+ this.availableOptions.push(option);
20344
+ }
20345
+ } else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
20346
+ // only count options that match the typed input value AND are not static
20463
20347
  option.removeAttribute('hidden');
20464
20348
  this.availableOptions.push(option);
20465
20349
  } else if (!option.hasAttribute('persistent')) {
20350
+ // Hide all other non-persistent options
20466
20351
  option.setAttribute('hidden', '');
20467
20352
  option.removeAttribute('aria-setsize');
20468
20353
  option.removeAttribute('aria-posinset');
@@ -20470,9 +20355,7 @@ class AuroCombobox extends AuroElement {
20470
20355
  });
20471
20356
 
20472
20357
  if (this.availableOptions.length === 0) {
20473
- // Only surface the nomatch option once the user has actually typed
20474
- // something. Empty input is handled by the bib being closed.
20475
- if (this.noMatchOption && filterValue) {
20358
+ if (this.noMatchOption) {
20476
20359
  this.noMatchOption.removeAttribute('hidden');
20477
20360
  } else if (!this.menu.loading || this.isHiddenWhileLoading) {
20478
20361
  this.hideBib();
@@ -20488,14 +20371,11 @@ class AuroCombobox extends AuroElement {
20488
20371
  * @returns {void}
20489
20372
  */
20490
20373
  syncValuesAndStates() {
20491
- // Do NOT call setMenuValue here the caller (handleMenuOptions) already
20492
- // guards setMenuValue with an option-match check. Calling it unconditionally
20493
- // sets free-form values on the menu, triggering clearSelection which resets
20494
- // _index and breaks subsequent makeSelection calls.
20374
+ // Only sync matchWord, don't set menu.value here since setMenuValue should handle that
20495
20375
  if (this.menu) {
20496
20376
  this.menu.matchWord = normalizeFilterValue(this.input.value);
20497
20377
  }
20498
- const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
20378
+ const label = this.menu.currentLabel;
20499
20379
  this.updateTriggerTextDisplay(label || this.value);
20500
20380
  }
20501
20381
 
@@ -20510,39 +20390,7 @@ class AuroCombobox extends AuroElement {
20510
20390
  const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
20511
20391
 
20512
20392
  if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
20513
- const nextValue = label || this.value;
20514
- // Only set the flag when there's an actual write to suppress —
20515
- // syncValuesAndStates re-enters here during typing when both inputs
20516
- // already match, and a no-op flag flip would make the bib branch's
20517
- // bail eat legitimate user-input events.
20518
- const triggerNeedsSync = this.input.value !== nextValue;
20519
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
20520
- if (triggerNeedsSync || bibNeedsSync) {
20521
- this._syncingDisplayValue = true;
20522
- if (triggerNeedsSync) {
20523
- this.input.value = nextValue;
20524
- }
20525
- if (bibNeedsSync) {
20526
- this.inputInBib.value = nextValue;
20527
- }
20528
- const pending = [];
20529
- if (triggerNeedsSync) {
20530
- pending.push(this.input.updateComplete);
20531
- }
20532
- if (bibNeedsSync) {
20533
- pending.push(this.inputInBib.updateComplete);
20534
- }
20535
- Promise.all(pending).then(() => {
20536
- // imask reasserts otherwise, and handleBlur reverts to its stale value.
20537
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
20538
- this.input.maskInstance.updateValue();
20539
- }
20540
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
20541
- this.inputInBib.maskInstance.updateValue();
20542
- }
20543
- this._syncingDisplayValue = false;
20544
- });
20545
- }
20393
+ this.input.value = label || this.value;
20546
20394
  }
20547
20395
 
20548
20396
  // update the displayValue in the trigger if displayValue slot content is present
@@ -20556,14 +20404,6 @@ class AuroCombobox extends AuroElement {
20556
20404
  const displayValueEl = this.menu.optionSelected.querySelector("[slot='displayValue']");
20557
20405
  if (displayValueEl) {
20558
20406
  this.input.appendChild(displayValueEl.cloneNode(true));
20559
- // auro-input's hasDisplayValueContent is non-reactive; nudge it to
20560
- // re-evaluate the slot and re-render so the displayValue wrapper
20561
- // gets its hasContent class. Without this, the apple/icon stays
20562
- // hidden when input.value is set in the same tick as the append.
20563
- if (typeof this.input.checkDisplayValueSlotChange === 'function') {
20564
- this.input.checkDisplayValueSlotChange();
20565
- this.input.requestUpdate();
20566
- }
20567
20407
  }
20568
20408
  }
20569
20409
 
@@ -20597,10 +20437,7 @@ class AuroCombobox extends AuroElement {
20597
20437
  this.syncValuesAndStates();
20598
20438
  }
20599
20439
 
20600
- // Re-activate when optionActive is no longer visible, or when _index has
20601
- // been reset (e.g. by clearSelection in an async update) so that
20602
- // makeSelection() can find the correct option.
20603
- if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
20440
+ if (!this.availableOptions.includes(this.menu.optionActive)) {
20604
20441
  this.activateFirstEnabledAvailableOption();
20605
20442
  }
20606
20443
  }
@@ -20725,13 +20562,8 @@ class AuroCombobox extends AuroElement {
20725
20562
 
20726
20563
  guardTouchPassthrough(this.menu);
20727
20564
 
20728
- // The dialog's showModal() steals focus from the trigger.
20729
- // A single rAF is enough for the dialog DOM to be painted and
20730
- // focusable, then doubleRaf finalizes the transition.
20731
- requestAnimationFrame(() => {
20732
- this.setInputFocus();
20733
- });
20734
-
20565
+ // Wait for the bibtemplate to fully render across
20566
+ // multiple Lit update cycles before moving focus into the bib
20735
20567
  doubleRaf(() => {
20736
20568
  this.setInputFocus();
20737
20569
  this._inFullscreenTransition = false;
@@ -20812,42 +20644,16 @@ class AuroCombobox extends AuroElement {
20812
20644
  */
20813
20645
  setInputFocus() {
20814
20646
  if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
20815
- // Sync the native input value synchronously before focusing.
20816
- // Lit's property-to-attribute sync is async (microtask), so the
20817
- // native <input> inside inputInBib may still hold a stale value
20818
- // when focus moves here during the fullscreen transition. Without
20819
- // this, keystrokes (e.g. Backspace) operate on the wrong content.
20820
- const nativeInput = this.inputInBib.inputElement;
20821
- const triggerNativeInput = this.input.inputElement;
20822
- if (nativeInput && triggerNativeInput && nativeInput.value !== triggerNativeInput.value) {
20823
- this.inputInBib.skipNextProgrammaticInputEvent = true;
20824
- nativeInput.value = triggerNativeInput.value || '';
20825
- }
20826
-
20827
- // Focus the native input directly to ensure it receives keystrokes
20828
- // after the fullscreen dialog opens. The dialog's showModal() may
20829
- // have moved focus to the dialog element itself; focusing the
20830
- // auro-input custom element doesn't always reach the native input
20831
- // when the dialog DOM isn't fully painted.
20832
- if (nativeInput) {
20833
- nativeInput.focus();
20834
- } else {
20835
- this.inputInBib.focus();
20836
- }
20647
+ this.inputInBib.focus();
20837
20648
 
20838
20649
  // Place cursor at end of existing text so the user can continue editing
20650
+ const nativeInput = this.inputInBib.inputElement;
20839
20651
  if (nativeInput && nativeInput.value) {
20840
20652
  const len = nativeInput.value.length;
20841
20653
  nativeInput.setSelectionRange(len, len);
20842
20654
  }
20843
- } else if (!this.input.componentHasFocus) {
20844
- const focusedEl = this.querySelector(":focus");
20655
+ } else {
20845
20656
  this.input.focus();
20846
- // current focus is on a menuoption, after clicking on it.
20847
- if (this.persistInput && focusedEl && (focusedEl.tagName.toLowerCase() === 'auro-menuoption' || focusedEl.hasAttribute('auro-menuoption'))) {
20848
- this.setClearBtnFocus();
20849
- this.validate(true);
20850
- }
20851
20657
  }
20852
20658
  }
20853
20659
 
@@ -20943,92 +20749,53 @@ class AuroCombobox extends AuroElement {
20943
20749
  this.menu.setAttribute('nocheckmark', '');
20944
20750
  }
20945
20751
 
20946
- // Handle menu option selection
20947
- this.menu.addEventListener('auroMenu-selectedOption', (evt) => {
20948
- // Capture before the consumption below — true means this event is the
20949
- // echo of our own setMenuValue (programmatic value sync), false means
20950
- // a fresh user selection.
20951
- const isEcho = this._pendingMenuValueSync === true;
20952
-
20953
- if (this.menu.optionSelected) {
20954
- const selected = this.menu.optionSelected;
20752
+ this.menu.addEventListener("auroMenu-deselectPrevented", () => {
20753
+ this.hideBib();
20754
+ });
20955
20755
 
20956
- if (!this.optionSelected || this.optionSelected !== selected) {
20957
- this.optionSelected = selected;
20958
- }
20756
+ // Handle menu option selection like select does
20757
+ this.menu.addEventListener('auroMenu-selectedOption', (event) => {
20758
+ // Update the optionSelected from the event details, not manually
20759
+ [this.optionSelected] = event.detail.options;
20959
20760
 
20960
- // Skip writeback when this event is the echo of our own setMenuValue —
20961
- // otherwise it cascades against handleInputValueChange in suggestion mode.
20962
- if (this._pendingMenuValueSync) {
20963
- this._pendingMenuValueSync = false;
20964
- } else if (!this.value || this.value !== this.optionSelected.value) {
20965
- this.value = this.optionSelected.value;
20966
- }
20761
+ // Update the internal value to match the menu's value
20762
+ this.value = event.detail.stringValue;
20967
20763
 
20968
- // Update display
20969
- this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
20764
+ // Update display
20765
+ this.updateTriggerTextDisplay(event.detail.label || event.detail.value);
20970
20766
 
20971
- // Update match word for filtering
20972
- const trimmedInput = normalizeFilterValue(this.input.value);
20973
- if (this.menu.matchWord !== trimmedInput) {
20974
- this.menu.matchWord = trimmedInput;
20975
- }
20767
+ // Update match word for filtering
20768
+ const trimmedInput = normalizeFilterValue(this.input.value);
20769
+ if (this.menu.matchWord !== trimmedInput) {
20770
+ this.menu.matchWord = trimmedInput;
20976
20771
  }
20977
20772
 
20773
+ // Update available options based on selection
20774
+ this.handleMenuOptions();
20775
+
20978
20776
  // Hide dropdown on selection (except during slot changes)
20979
- if (evt.detail && evt.detail.source !== 'slotchange') {
20980
- // do not close while typing in suggestion mode with no value selected, to allow freeform input
20981
- if (this.menu.value || this.behavior !== 'suggestion') {
20982
- this.hideBib();
20983
- }
20777
+ if (event.detail && event.detail.source !== 'slotchange') {
20778
+ setTimeout(() => {
20779
+ // do not close while typing in suggestion mode with no value selected, to allow freeform input
20780
+ if (this.menu.value || this.behavior !== 'suggestion') {
20781
+ this.hideBib();
20782
+ }
20783
+ }, 0);
20984
20784
 
20985
20785
  // Announce the selection after the dropdown closes so it isn't
20986
20786
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
20987
- const selectedValue = this.menu.value;
20787
+ const selectedValue = event.detail.stringValue;
20988
20788
  const announcementDelay = 300;
20989
20789
  setTimeout(() => {
20990
20790
  announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
20991
20791
  }, announcementDelay);
20992
20792
  }
20993
-
20994
- // Programmatic value syncs leave availableOptions stale because
20995
- // updateTriggerTextDisplay sets input.value with _syncingDisplayValue
20996
- // and handleInputValueChange bails. Refresh the filter on echo events
20997
- // only — fresh user selections take the existing hideBib path.
20998
- if (isEcho && this.menu.optionSelected) {
20999
- this._programmaticFilterRefresh = true;
21000
- this.handleMenuOptions();
21001
- setTimeout(() => {
21002
- this._programmaticFilterRefresh = false;
21003
- }, 0);
21004
- }
21005
-
21006
- // base-input skips auto-validate when focus is inside its own shadow,
21007
- // which it is after setTriggerInputFocus — re-run so prior tooShort
21008
- // clears. processCreditCard reasserts errorMessage on every render.
21009
- if (!isEcho && this.menu.optionSelected && this.input.validate) {
21010
- this.input.updateComplete.then(() => {
21011
- this.input.validate(true);
21012
- if (this.input.validity === 'valid') {
21013
- this.input.errorMessage = '';
21014
- }
21015
- });
21016
- }
21017
20793
  });
21018
20794
 
21019
20795
  this.menu.addEventListener('auroMenu-customEventFired', () => {
21020
20796
  this.hideBib();
21021
20797
  });
21022
20798
 
21023
- // When the menu cannot match a programmatic value to any option,
21024
- // clear the combobox selection state so it doesn't reference a
21025
- // stale option. Safe from re-entrancy because any resulting
21026
- // input.value changes dispatch isProgrammatic events.
21027
- this.menu.addEventListener('auroMenu-selectValueFailure', () => {
21028
- this.value = undefined;
21029
- this.optionSelected = undefined;
21030
- });
21031
-
21032
20799
  this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
21033
20800
  this.optionActive = evt.detail;
21034
20801
 
@@ -21129,12 +20896,11 @@ class AuroCombobox extends AuroElement {
21129
20896
  // event from the trigger. The _syncingBibValue guard persists across the
21130
20897
  // async boundary and prevents that re-entrant event from running the
21131
20898
  // non-fullscreen path (which would call clear() → hideBib()).
20899
+ // When the event comes from the fullscreen bib input, sync the value to
20900
+ // the trigger and run filtering, but suppress the re-entrant input event
20901
+ // that the trigger fires (via Lit updated() → notifyValueChanged()) so
20902
+ // the non-fullscreen hide/clear logic doesn't close the dialog.
21132
20903
  if (event.target === this.inputInBib) {
21133
- // Mirror the trigger-branch bail below — programmatic inputInBib writes
21134
- // notify back into this branch via Lit's notifyValueChanged.
21135
- if (this._syncingDisplayValue) {
21136
- return;
21137
- }
21138
20904
  this._syncingBibValue = true;
21139
20905
  this.input.value = this.inputInBib.value;
21140
20906
  this.input.updateComplete.then(() => {
@@ -21144,37 +20910,18 @@ class AuroCombobox extends AuroElement {
21144
20910
  // Run filtering inline — the re-entrant event won't reach this code.
21145
20911
  this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
21146
20912
  this.optionActive = null;
21147
-
21148
- // In suggestion mode, keep the combobox value in sync with the typed
21149
- // text so that freeform values are captured (mirroring the non-fullscreen
21150
- // path). Clear the selection when the input is emptied.
21151
- if (this.behavior === 'suggestion') {
21152
- this.value = this.inputInBib.value || undefined;
21153
- }
21154
-
21155
20913
  this.handleMenuOptions();
21156
20914
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
21157
20915
  return;
21158
20916
  }
21159
20917
 
21160
- // Ignore re-entrant input events caused by programmatic value sets.
21161
- if (this._syncingBibValue || this._syncingDisplayValue) {
20918
+ // Ignore re-entrant input events caused by the bib→trigger sync above.
20919
+ if (this._syncingBibValue) {
21162
20920
  return;
21163
20921
  }
21164
20922
 
21165
20923
  this.inputInBib.value = this.input.value;
21166
20924
 
21167
- // Also sync the native input immediately so keystrokes arriving
21168
- // before Lit's async update cycle (e.g. rapid Backspaces during a
21169
- // fullscreen transition) operate on the correct content.
21170
- // Skip the next programmatic input event to prevent the patched setter
21171
- // from re-entering handleInputValueChange via the bib path.
21172
- const bibNativeInput = this.inputInBib.inputElement;
21173
- if (bibNativeInput && bibNativeInput.value !== this.input.value) {
21174
- this.inputInBib.skipNextProgrammaticInputEvent = true;
21175
- bibNativeInput.value = this.input.value || '';
21176
- }
21177
-
21178
20925
  this.menu.matchWord = normalizeFilterValue(this.input.value);
21179
20926
  this.optionActive = null;
21180
20927
 
@@ -21182,7 +20929,7 @@ class AuroCombobox extends AuroElement {
21182
20929
  this.value = this.input.value;
21183
20930
  }
21184
20931
 
21185
- if (!this.input.value && !this._clearing) {
20932
+ if (!this.input.value) {
21186
20933
  this.clear();
21187
20934
  }
21188
20935
  this.handleMenuOptions();
@@ -21251,16 +20998,6 @@ class AuroCombobox extends AuroElement {
21251
20998
  return;
21252
20999
  }
21253
21000
 
21254
- // Ignore dispatches from the bib (fullscreen) input. It's re-validated
21255
- // inside this.validate()'s auroInputElements loop with its own
21256
- // (often undefined) validity, and the event is composed/bubbles up to
21257
- // this listener with `target` retargeted to the combobox. Letting it
21258
- // through would overwrite the trigger input's correct validity with
21259
- // the bib input's stale one (e.g. wiping `tooShort` during typing).
21260
- if (this.inputInBib && evt.composedPath()[0] === this.inputInBib && this.inputInBib !== this.input) {
21261
- return;
21262
- }
21263
-
21264
21001
  this.input.validity = evt.detail.validity;
21265
21002
  this.input.errorMessage = evt.detail.message;
21266
21003
  this.validity = evt.detail.validity;
@@ -21322,18 +21059,10 @@ class AuroCombobox extends AuroElement {
21322
21059
  * @returns {void}
21323
21060
  */
21324
21061
  setMenuValue(value) {
21325
- if (!this.menu || this.menu.value === value) {
21062
+ if (!this.menu) {
21326
21063
  return;
21327
21064
  }
21328
- // One-shot flag consumed by the auroMenu-selectedOption listener so the
21329
- // echo of this sync doesn't propagate back to this.value.
21330
- this._pendingMenuValueSync = true;
21331
21065
  this.menu.value = value;
21332
- // Backup clear: if menu.value === menu.optionSelected.value already, the
21333
- // listener won't fire and the flag would swallow the next user click.
21334
- setTimeout(() => {
21335
- this._pendingMenuValueSync = false;
21336
- }, 0);
21337
21066
  }
21338
21067
 
21339
21068
  /**
@@ -21348,15 +21077,6 @@ class AuroCombobox extends AuroElement {
21348
21077
  this.menu.value = undefined;
21349
21078
  this.validation.reset(this);
21350
21079
  this.touched = false;
21351
- // Force validity back to the cleared state. validation.reset() calls
21352
- // validate() at the end, and (post-credit-card-fix) the trigger input's
21353
- // residual validity may still be copied into the combobox by the
21354
- // auroInputElements loop. Explicitly clear here so reset() actually
21355
- // leaves the component in a "no validity" state.
21356
- this.validity = undefined;
21357
- this.errorMessage = '';
21358
- this.input.validity = undefined;
21359
- this.input.errorMessage = '';
21360
21080
  }
21361
21081
 
21362
21082
  /**
@@ -21364,24 +21084,16 @@ class AuroCombobox extends AuroElement {
21364
21084
  * @returns {void}
21365
21085
  */
21366
21086
  clear() {
21367
- // input.clear() fires an input event that re-enters handleInputValueChange
21368
- // → which calls clear() again on empty input. Guard against that loop.
21369
- if (this._clearing) {
21370
- return;
21371
- }
21372
- this._clearing = true;
21373
- try {
21374
- this.optionSelected = undefined;
21375
- this.value = undefined;
21087
+ // Clear combobox state first
21088
+ this.optionSelected = undefined;
21089
+ this.value = undefined;
21376
21090
 
21377
- if (this.input.value) {
21378
- this.input.clear();
21379
- }
21380
- if (this.menu.value || this.menu.optionSelected) {
21381
- this.menu.reset();
21382
- }
21383
- } finally {
21384
- this._clearing = false;
21091
+ // Then clear input and menu
21092
+ if (this.input.value) {
21093
+ this.input.clear();
21094
+ }
21095
+ if (this.menu.value || this.menu.optionSelected) {
21096
+ this.menu.reset();
21385
21097
  }
21386
21098
  }
21387
21099
 
@@ -21409,63 +21121,28 @@ class AuroCombobox extends AuroElement {
21409
21121
  this.input.value = this.value;
21410
21122
  }
21411
21123
 
21412
- // Sync menu.value only when an option actually matches; otherwise clear it.
21413
- if (this.menu.options && this.menu.options.length > 0) {
21414
- if (this.menu.options.some((opt) => opt.value === this.value)) {
21415
- this.setMenuValue(this.value);
21416
- } else if (this.behavior === 'filter') {
21417
- // In filter mode, freeform values aren't allowed. Push the unmatched
21418
- // value through setMenuValue so menu dispatches auroMenu-selectValueFailure
21419
- // and the listener at line 1206 clears combobox.value (mirrors select's
21420
- // pattern). Bypassing setMenuValue here would skip the failure cascade.
21124
+ if (this.menu && this.hasValue && this.menu.options) {
21125
+ this.menu.options.forEach((opt) => {
21126
+ if (!opt.hasAttribute('static')) {
21127
+ opt.removeAttribute('hidden');
21128
+ }
21129
+ });
21130
+ }
21131
+
21132
+ if (this.behavior === 'suggestion') {
21133
+ if (!this.menu.options || this.menu.options.length === 0) ; else if (this.menu.options.filter((opt) => opt.value === this.value).length > 0) {
21421
21134
  this.setMenuValue(this.value);
21422
21135
  } else {
21423
- if (this.menu.value) {
21424
- this.menu.value = undefined;
21425
- }
21426
- // Sync the display for programmatic freeform value changes (e.g. the
21427
- // swap-values pattern). Guard with _syncingDisplayValue so that the
21428
- // re-entrant input event from base-input.notifyValueChanged() is
21429
- // ignored by handleInputValueChange. Skip the sync when input already
21430
- // matches to avoid spurious events during the normal typing path.
21431
- const nextValue = this.value || '';
21432
- const triggerNeedsSync = this.input.value !== nextValue;
21433
- const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
21434
- if (triggerNeedsSync || bibNeedsSync) {
21435
- this._syncingDisplayValue = true;
21436
- if (triggerNeedsSync) {
21437
- this.input.value = nextValue;
21438
- }
21439
- if (bibNeedsSync) {
21440
- this.inputInBib.value = nextValue;
21441
- }
21442
- const pending = [];
21443
- if (triggerNeedsSync) {
21444
- pending.push(this.input.updateComplete);
21445
- }
21446
- if (bibNeedsSync) {
21447
- pending.push(this.inputInBib.updateComplete);
21448
- }
21449
- Promise.all(pending).then(() => {
21450
- if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
21451
- this.input.maskInstance.updateValue();
21452
- }
21453
- if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
21454
- this.inputInBib.maskInstance.updateValue();
21455
- }
21456
- this._syncingDisplayValue = false;
21457
- // handleInputValueChange bailed on the flag — refresh the filter.
21458
- this._programmaticFilterRefresh = true;
21459
- this.handleMenuOptions();
21460
- setTimeout(() => {
21461
- this._programmaticFilterRefresh = false;
21462
- }, 0);
21463
- });
21464
- }
21136
+ this.menu.value = undefined;
21137
+ // Sync the input display for freeform values that don't match any option
21138
+ this.input.value = this.value;
21139
+ }
21140
+ } else {
21141
+ // Use setMenuValue like select does instead of direct assignment
21142
+ this.setMenuValue(this.value);
21143
+ if (!this.value) {
21144
+ this.clear();
21465
21145
  }
21466
- }
21467
- if (!this.value) {
21468
- this.clear();
21469
21146
  }
21470
21147
  if (this.value && !this.componentHasFocus) {
21471
21148
  // If the value got set programmatically make sure we hide the bib
@@ -21503,18 +21180,12 @@ class AuroCombobox extends AuroElement {
21503
21180
  // branch from calling hideBib() when the dropdown was just opened but
21504
21181
  // :focus-within hasn't propagated through the top-layer dialog's nested
21505
21182
  // shadow DOM boundaries.
21506
- // Skip the show/hide side effects when the availableOptions change came
21507
- // from a programmatic filter refresh after a value swap — the user
21508
- // didn't interact, so we shouldn't auto-open the bib (especially for
21509
- // the no-match path where the existing condition unconditionally fires).
21510
- if (this._programmaticFilterRefresh) {
21511
- this._programmaticFilterRefresh = false;
21512
- } else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21183
+ if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
21513
21184
  this.showBib();
21514
21185
  if (!this.availableOptions.includes(this.menu.optionActive)) {
21515
21186
  this.activateFirstEnabledAvailableOption();
21516
21187
  }
21517
- } else if (this.dropdown && this.dropdown.isPopoverVisible) {
21188
+ } else {
21518
21189
  this.hideBib();
21519
21190
  }
21520
21191
  }