@aurodesignsystem-dev/auro-formkit 0.0.0-pr1346.3 → 0.0.0-pr1346.4

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 (40) hide show
  1. package/components/checkbox/demo/api.min.js +1 -1
  2. package/components/checkbox/demo/index.min.js +1 -1
  3. package/components/checkbox/dist/index.js +1 -1
  4. package/components/checkbox/dist/registered.js +1 -1
  5. package/components/combobox/demo/api.min.js +224 -45
  6. package/components/combobox/demo/index.min.js +224 -45
  7. package/components/combobox/dist/auro-combobox.d.ts +2 -1
  8. package/components/combobox/dist/index.js +217 -42
  9. package/components/combobox/dist/registered.js +217 -42
  10. package/components/counter/demo/api.min.js +123 -11
  11. package/components/counter/demo/index.min.js +123 -11
  12. package/components/counter/dist/index.js +123 -11
  13. package/components/counter/dist/registered.js +123 -11
  14. package/components/datepicker/demo/api.min.js +133 -20
  15. package/components/datepicker/demo/index.min.js +133 -20
  16. package/components/datepicker/dist/index.js +133 -20
  17. package/components/datepicker/dist/registered.js +133 -20
  18. package/components/dropdown/demo/api.min.js +122 -10
  19. package/components/dropdown/demo/index.min.js +122 -10
  20. package/components/dropdown/dist/auro-dropdownBib.d.ts +18 -2
  21. package/components/dropdown/dist/index.js +122 -10
  22. package/components/dropdown/dist/registered.js +122 -10
  23. package/components/input/demo/api.min.js +10 -9
  24. package/components/input/demo/index.min.js +10 -9
  25. package/components/input/dist/index.js +10 -9
  26. package/components/input/dist/registered.js +10 -9
  27. package/components/menu/demo/api.min.js +7 -3
  28. package/components/menu/demo/index.min.js +7 -3
  29. package/components/menu/dist/index.js +7 -3
  30. package/components/menu/dist/registered.js +7 -3
  31. package/components/radio/demo/api.min.js +1 -1
  32. package/components/radio/demo/index.min.js +1 -1
  33. package/components/radio/dist/index.js +1 -1
  34. package/components/radio/dist/registered.js +1 -1
  35. package/components/select/demo/api.min.js +159 -17
  36. package/components/select/demo/index.min.js +159 -17
  37. package/components/select/dist/index.js +152 -14
  38. package/components/select/dist/registered.js +152 -14
  39. package/custom-elements.json +35 -10
  40. package/package.json +1 -1
@@ -3873,7 +3873,7 @@ let p$4 = class p{registerComponent(t,a){customElements.get(t)||customElements.d
3873
3873
 
3874
3874
  var iconVersion$3 = '9.1.2';
3875
3875
 
3876
- var styleCss$2$1 = i$7`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{max-width:none;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:absolute;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([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}`;
3876
+ var styleCss$2$1 = i$7`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{max-width:none;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:absolute;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([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}`;
3877
3877
 
3878
3878
  var colorCss$2$1 = i$7`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`;
3879
3879
 
@@ -3881,6 +3881,8 @@ var tokensCss$1$2 = i$7`:host(:not([ondark])),:host(:not([appearance=inverse])){
3881
3881
 
3882
3882
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
3883
3883
  // See LICENSE in the project root for license information.
3884
+ /* eslint-disable max-lines */
3885
+ // ---------------------------------------------------------------------
3884
3886
 
3885
3887
 
3886
3888
  const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-';
@@ -4056,22 +4058,63 @@ class AuroDropdownBib extends i$4 {
4056
4058
  // Handle ESC key via dialog's cancel event
4057
4059
  const dialog = this.shadowRoot.querySelector('dialog');
4058
4060
  dialog.addEventListener('cancel', (event) => {
4059
- event.preventDefault(); // Let parent handle closing
4061
+ // Let parent handle closing
4062
+ event.preventDefault();
4060
4063
  this.dispatchEvent(new CustomEvent('auro-bib-cancel', {
4061
4064
  bubbles: true,
4062
4065
  composed: true
4063
4066
  }));
4064
4067
  });
4065
4068
 
4066
- // Re-dispatch navigation keyboard events so they cross the shadow DOM
4067
- // boundary and reach the combobox/select key handlers.
4068
- // Only intercept keys used for menu navigation let all other keys
4069
- // (characters, Backspace, etc.) through so typing in inputs works.
4070
- const navKeys = new Set(['ArrowUp', 'ArrowDown', 'Enter', 'Escape']);
4069
+ // showModal() creates a closed focus scope keyboard events inside
4070
+ // the dialog's shadow DOM do NOT bubble out to the combobox/select
4071
+ // keydown handlers in the parent shadow DOM. This handler bridges
4072
+ // that gap by re-dispatching navigation keys so they cross the
4073
+ // shadow boundary and reach the menu navigation logic in the parent
4074
+ // component.
4075
+ //
4076
+ // The trade-off: intercepting these keys means native keyboard
4077
+ // behaviors that would normally "just work" must be manually
4078
+ // re-implemented here:
4079
+ //
4080
+ // - Enter on buttons: Custom elements (auro-button) don't get the
4081
+ // native Enter→click that <button> provides, so we call .click()
4082
+ // directly when Enter is pressed on a button-like element.
4083
+ //
4084
+ // - Tab: NOT intercepted — left to the browser's native focus trap
4085
+ // provided by showModal(), which cycles Tab between focusable
4086
+ // elements inside the dialog (e.g. the input and close button).
4087
+ // Intercepting Tab would kill the native focus trap and break
4088
+ // focus management inside the dialog.
4089
+ //
4090
+ // - Escape: The native <dialog> fires a `cancel` event on ESC
4091
+ // (handled above), so the re-dispatched Escape is a secondary
4092
+ // path for parent components that also listen for Escape keydown.
4093
+ const navKeys = new Set([
4094
+ 'ArrowUp',
4095
+ 'ArrowDown',
4096
+ 'Enter',
4097
+ 'Escape'
4098
+ ]);
4071
4099
  dialog.addEventListener('keydown', (event) => {
4072
4100
  if (!navKeys.has(event.key)) {
4073
4101
  return;
4074
4102
  }
4103
+
4104
+ // Custom elements (auro-button) don't get the native Enter→click
4105
+ // behavior that <button> has. Find the button in the composed path
4106
+ // and click it directly.
4107
+ if (event.key === 'Enter') {
4108
+ const buttonSelector = 'button, [role="button"], auro-button, [auro-button]';
4109
+ const btn = event.composedPath().find((el) => el.matches && el.matches(buttonSelector));
4110
+ if (btn) {
4111
+ event.preventDefault();
4112
+ event.stopPropagation();
4113
+ btn.click();
4114
+ return;
4115
+ }
4116
+ }
4117
+
4075
4118
  event.preventDefault();
4076
4119
  event.stopPropagation();
4077
4120
  const newEvent = new KeyboardEvent('keydown', {
@@ -4095,9 +4138,55 @@ class AuroDropdownBib extends i$4 {
4095
4138
  }
4096
4139
 
4097
4140
  /**
4098
- * Opens the dialog using showModal() for accessibility.
4099
- * @param {boolean} modal - If true, uses showModal() (default). If false, uses show().
4141
+ * Blocks touch-driven page scroll while a fullscreen modal dialog is open.
4142
+ *
4143
+ * The showModal() function places the dialog in the browser's **top layer**,
4144
+ * which is a separate rendering layer above the normal DOM. On mobile, the
4145
+ * compositor processes visual-viewport panning before top-layer touch
4146
+ * handling. This means the entire viewport — including the top-layer dialog
4147
+ * — can be panned by a touch gesture, causing the page behind the dialog to
4148
+ * scroll into view. To prevent this, we add a touchmove listener that cancels
4149
+ * the event if the touch started outside the dialog or any scrollable child within it.
4150
+ *
4151
+ * @private
4152
+ */
4153
+ _lockTouchScroll() {
4154
+ const dialog = this.shadowRoot.querySelector('dialog');
4155
+
4156
+ this._touchMoveHandler = (event) => {
4157
+ // Walk the composed path (which crosses shadow DOM boundaries) to
4158
+ // check whether the touch started inside a scrollable element that
4159
+ // lives within the dialog. If so, allow the scroll.
4160
+ for (const el of event.composedPath()) {
4161
+ if (el === dialog) {
4162
+ // Reached the dialog boundary without finding a scrollable child.
4163
+ break;
4164
+ }
4165
+ if (el instanceof HTMLElement && el.scrollHeight > el.clientHeight) {
4166
+ const { overflowY } = getComputedStyle(el);
4167
+ if (overflowY === 'auto' || overflowY === 'scroll') {
4168
+ return;
4169
+ }
4170
+ }
4171
+ }
4172
+
4173
+ event.preventDefault();
4174
+ };
4175
+
4176
+ document.addEventListener('touchmove', this._touchMoveHandler, { passive: false });
4177
+ }
4178
+
4179
+ /**
4180
+ * Removes the touchmove listener added by _lockTouchScroll().
4181
+ * @private
4100
4182
  */
4183
+ _unlockTouchScroll() {
4184
+ if (this._touchMoveHandler) {
4185
+ document.removeEventListener('touchmove', this._touchMoveHandler);
4186
+ this._touchMoveHandler = undefined;
4187
+ }
4188
+ }
4189
+
4101
4190
  open(modal = true) {
4102
4191
  const dialog = this.shadowRoot.querySelector('dialog');
4103
4192
  if (dialog && !dialog.open) {
@@ -4114,6 +4203,8 @@ class AuroDropdownBib extends i$4 {
4114
4203
 
4115
4204
  documentElement.style.overflow = prevOverflow;
4116
4205
 
4206
+ this._lockTouchScroll();
4207
+
4117
4208
  } else {
4118
4209
  // Use setAttribute instead of dialog.show() to avoid the dialog
4119
4210
  // focusing steps which steal focus from the trigger and cause
@@ -4129,6 +4220,7 @@ class AuroDropdownBib extends i$4 {
4129
4220
  close() {
4130
4221
  const dialog = this.shadowRoot.querySelector('dialog');
4131
4222
  if (dialog && dialog.open) {
4223
+ this._unlockTouchScroll();
4132
4224
  dialog.close();
4133
4225
  }
4134
4226
  }
@@ -4394,7 +4486,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$4 {
4394
4486
  }
4395
4487
  };
4396
4488
 
4397
- var formkitVersion$2 = '202602232116';
4489
+ var formkitVersion$2 = '202602260152';
4398
4490
 
4399
4491
  let AuroElement$2 = class AuroElement extends i$4 {
4400
4492
  static get properties() {
@@ -4667,6 +4759,18 @@ class AuroDropdown extends AuroElement$2 {
4667
4759
  */
4668
4760
  show() {
4669
4761
  this.floater.showBib();
4762
+
4763
+ // Open dialog synchronously so callers remain in the user gesture
4764
+ // chain. This is critical for mobile browsers (iOS Safari) to keep
4765
+ // the virtual keyboard open when transitioning from the trigger
4766
+ // input to an input inside the fullscreen dialog. Without this,
4767
+ // showModal() fires asynchronously via Lit's update cycle, which
4768
+ // falls outside the user activation window and causes iOS to
4769
+ // dismiss the keyboard.
4770
+ if (this.isBibFullscreen && this.bibElement && this.bibElement.value) {
4771
+ const useModal = !this.disableFocusTrap;
4772
+ this.bibElement.value.open(useModal);
4773
+ }
4670
4774
  }
4671
4775
 
4672
4776
  /**
@@ -5053,6 +5157,14 @@ class AuroDropdown extends AuroElement$2 {
5053
5157
  this.bibElement.value.close();
5054
5158
  }
5055
5159
  }
5160
+
5161
+ // When fullscreen strategy changes while open, re-open dialog with correct mode
5162
+ // (e.g. resizing from desktop → mobile while dropdown is open)
5163
+ if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
5164
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5165
+ this.bibElement.value.close();
5166
+ this.bibElement.value.open(useModal);
5167
+ }
5056
5168
  }
5057
5169
 
5058
5170
  /**
@@ -10554,6 +10666,15 @@ class BaseInput extends AuroElement$1 {
10554
10666
  constructor() {
10555
10667
  super();
10556
10668
 
10669
+ // Delegate focus to the native <input> inside the shadow root so that
10670
+ // showModal()'s dialog focusing steps reach the input element.
10671
+ // This keeps the mobile virtual keyboard open when the fullscreen dialog
10672
+ // opens, because the browser sees an input-to-input focus transfer.
10673
+ this.constructor.shadowRootOptions = {
10674
+ ...AuroElement$1.shadowRootOptions,
10675
+ delegatesFocus: true,
10676
+ };
10677
+
10557
10678
  this._initializeDefaults();
10558
10679
  }
10559
10680
 
@@ -10675,14 +10796,6 @@ class BaseInput extends AuroElement$1 {
10675
10796
  reflect: true
10676
10797
  },
10677
10798
 
10678
- /**
10679
- * The value for the aria-controls attribute.
10680
- */
10681
- a11yControls: {
10682
- type: String,
10683
- reflect: true
10684
- },
10685
-
10686
10799
  /**
10687
10800
  * The value for the aria-activedescendant attribute.
10688
10801
  * Points to the ID of the currently active/highlighted option in a listbox.
@@ -12102,7 +12215,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12102
12215
  }
12103
12216
  };
12104
12217
 
12105
- var formkitVersion$1 = '202602232116';
12218
+ var formkitVersion$1 = '202602260152';
12106
12219
 
12107
12220
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12108
12221
  // See LICENSE in the project root for license information.
@@ -13129,7 +13242,7 @@ class AuroBibtemplate extends i$4 {
13129
13242
  }
13130
13243
  }
13131
13244
 
13132
- var formkitVersion = '202602232116';
13245
+ var formkitVersion = '202602260152';
13133
13246
 
13134
13247
  var styleCss$3 = i$7`.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}`;
13135
13248
 
@@ -13547,6 +13660,7 @@ class AuroCombobox extends AuroElement {
13547
13660
  this.dropdownOpen = false;
13548
13661
  this.triggerExpandedState = false;
13549
13662
  this._expandedTimeout = null;
13663
+ this._inFullscreenTransition = false;
13550
13664
  this.errorMessage = null;
13551
13665
  this.isHiddenWhileLoading = false;
13552
13666
  this.largeFullscreenHeadline = false;
@@ -13567,7 +13681,7 @@ class AuroCombobox extends AuroElement {
13567
13681
 
13568
13682
  /**
13569
13683
  * Defines whether the component will be on lighter or darker backgrounds.
13570
- * @property {'default' | 'inverse'}
13684
+ * @property {'default' | 'inverse'} appearance - The visual appearance of the component.
13571
13685
  * @default 'default'
13572
13686
  */
13573
13687
  appearance: {
@@ -14122,7 +14236,7 @@ class AuroCombobox extends AuroElement {
14122
14236
  * @returns {void}
14123
14237
  */
14124
14238
  showBib() {
14125
- if (!this.input.value) {
14239
+ if (!this.input.value && !this.dropdown.isBibFullscreen) {
14126
14240
  this.dropdown.hide();
14127
14241
  return;
14128
14242
  }
@@ -14191,6 +14305,18 @@ class AuroCombobox extends AuroElement {
14191
14305
 
14192
14306
  // Restore trigger accessibility when closing fullscreen
14193
14307
  this.dropdown.trigger.inert = false;
14308
+
14309
+ // Restore focus to the trigger input after closing the
14310
+ // fullscreen dialog. The browser's native dialog focus restoration
14311
+ // fails because the trigger was set to inert before showModal().
14312
+ // Use rAF to run after Lit's microtask update cycle calls dialog.close().
14313
+ if (this.dropdown.isBibFullscreen) {
14314
+ requestAnimationFrame(() => {
14315
+ if (!this.dropdown.isPopoverVisible) {
14316
+ this.input.focus();
14317
+ }
14318
+ });
14319
+ }
14194
14320
  }
14195
14321
 
14196
14322
  if (this.dropdownOpen) {
@@ -14200,6 +14326,19 @@ class AuroCombobox extends AuroElement {
14200
14326
  this.updateBibDialogRole();
14201
14327
 
14202
14328
  if (this.dropdown.isBibFullscreen) {
14329
+ // Guard against spurious validation during the focus transition
14330
+ // from trigger to bib input. Setting trigger.inert = true removes
14331
+ // focus, which fires focusout on the child auro-input before the
14332
+ // bib input receives focus. That focusout triggers the input's own
14333
+ // validate(), which dispatches a composed auroFormElement-validated
14334
+ // event. Because composed events are retargetted at each shadow DOM
14335
+ // boundary, the event appears to originate from the combobox itself
14336
+ // and its listener unconditionally sets this.validity — causing
14337
+ // premature validation. This flag suppresses all validation paths
14338
+ // (focusout handler, handleInputValueChange, validate(), and the
14339
+ // auroFormElement-validated listener) until focus settles in the bib.
14340
+ this._inFullscreenTransition = true;
14341
+
14203
14342
  // Hide the trigger from assistive technology so VoiceOver cannot reach it
14204
14343
  // behind the fullscreen dialog
14205
14344
  this.dropdown.trigger.inert = true;
@@ -14231,6 +14370,7 @@ class AuroCombobox extends AuroElement {
14231
14370
  requestAnimationFrame(() => {
14232
14371
  requestAnimationFrame(() => {
14233
14372
  this.setInputFocus();
14373
+ this._inFullscreenTransition = false;
14234
14374
  });
14235
14375
  });
14236
14376
  } else {
@@ -14325,7 +14465,7 @@ class AuroCombobox extends AuroElement {
14325
14465
  * @private
14326
14466
  */
14327
14467
  updateBibDialogRole() {
14328
- const bibEl = this.dropdown.bibElement?.value;
14468
+ const bibEl = this.dropdown.bibElement && this.dropdown.bibElement.value;
14329
14469
  if (!bibEl) {
14330
14470
  return;
14331
14471
  }
@@ -14454,9 +14594,10 @@ class AuroCombobox extends AuroElement {
14454
14594
  // Announce the selection after the dropdown closes so it isn't
14455
14595
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
14456
14596
  const selectedValue = event.detail.stringValue;
14597
+ const announcementDelay = 300;
14457
14598
  setTimeout(() => {
14458
14599
  this.announceToScreenReader(`${selectedValue}, selected`);
14459
- }, 300);
14600
+ }, announcementDelay);
14460
14601
  }
14461
14602
  });
14462
14603
 
@@ -14504,7 +14645,7 @@ class AuroCombobox extends AuroElement {
14504
14645
  * Validate every time we remove focus from the combo box.
14505
14646
  */
14506
14647
  this.addEventListener('focusout', () => {
14507
- if (!this.componentHasFocus) {
14648
+ if (!this.componentHasFocus && !this._inFullscreenTransition) {
14508
14649
  this.validate();
14509
14650
  }
14510
14651
  });
@@ -14555,8 +14696,11 @@ class AuroCombobox extends AuroElement {
14555
14696
  }
14556
14697
  this.handleMenuOptions();
14557
14698
 
14558
- // Validate only if the value was set programmatically
14559
- if (!this.componentHasFocus) {
14699
+ // Validate only if the value was set programmatically (not during user
14700
+ // interaction). In fullscreen dialog mode, componentHasFocus returns false
14701
+ // because focus is inside the top-layer dialog, so also check
14702
+ // dropdownOpen and the fullscreen transition flag.
14703
+ if (!this.componentHasFocus && !this.dropdownOpen && !this._inFullscreenTransition) {
14560
14704
  this.validate();
14561
14705
  }
14562
14706
 
@@ -14571,6 +14715,22 @@ class AuroCombobox extends AuroElement {
14571
14715
  this.hideBib();
14572
14716
  }
14573
14717
 
14718
+ // iOS virtual keyboard retention: when in fullscreen mode, ensure the
14719
+ // dialog opens and the bib input is focused synchronously within the
14720
+ // input event (user gesture) chain. Without this, Lit's async update
14721
+ // cycle delays showModal() past the user activation window, causing
14722
+ // iOS Safari to dismiss the virtual keyboard when the fullscreen
14723
+ // dialog opens — the user then has to tap the input again to resume
14724
+ // typing.
14725
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
14726
+ if (!this.dropdown.isPopoverVisible) {
14727
+ this.showBib();
14728
+ }
14729
+ if (this.dropdown.isPopoverVisible) {
14730
+ this.setInputFocus();
14731
+ }
14732
+ }
14733
+
14574
14734
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
14575
14735
  }
14576
14736
 
@@ -14596,23 +14756,20 @@ class AuroCombobox extends AuroElement {
14596
14756
  }
14597
14757
 
14598
14758
  if (evt.key === 'Tab' && this.dropdown.isPopoverVisible) {
14599
- if (this.dropdown.isBibFullscreen) {
14600
-
14601
- // when focus is on the input, move focus back to close button with Tab key
14602
- if (document.activeElement.shadowRoot.activeElement === this.inputInBib) {
14603
- evt.preventDefault();
14604
- this.dropdown.focus();
14605
- }
14606
- } else {
14759
+ // Non-fullscreen (combobox pattern per WAI-ARIA APG):
14760
+ // Tab accepts the focused option and closes the popup, moving focus
14761
+ // to the next focusable element on the page.
14762
+ // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
14763
+ //
14764
+ // Fullscreen (dialog / modal pattern): Tab navigates between
14765
+ // focusable elements inside the modal (e.g. the close button) via
14766
+ // the native focus trap provided by showModal(). The dropdown is
14767
+ // closed by the close button or Escape instead.
14768
+ if (!this.dropdown.isBibFullscreen) {
14607
14769
  if (this.menu.optionActive && this.menu.optionActive.value) {
14608
14770
  this.menu.value = this.menu.optionActive.value;
14609
14771
  }
14610
-
14611
- setTimeout(() => {
14612
- if (!this.componentHasFocus) {
14613
- this.hideBib();
14614
- }
14615
- }, 0);
14772
+ this.hideBib();
14616
14773
  }
14617
14774
  }
14618
14775
 
@@ -14643,6 +14800,14 @@ class AuroCombobox extends AuroElement {
14643
14800
  });
14644
14801
 
14645
14802
  this.addEventListener('auroFormElement-validated', (evt) => {
14803
+ // During the fullscreen transition, child elements (auro-input) may fire
14804
+ // their own validation events when the trigger becomes inert and loses
14805
+ // focus. Those composed events bubble up through shadow DOM boundaries
14806
+ // and would incorrectly set combobox validity. Ignore them.
14807
+ if (this._inFullscreenTransition) {
14808
+ return;
14809
+ }
14810
+
14646
14811
  this.input.validity = evt.detail.validity;
14647
14812
  this.input.errorMessage = evt.detail.message;
14648
14813
  this.validity = evt.detail.validity;
@@ -14746,6 +14911,9 @@ class AuroCombobox extends AuroElement {
14746
14911
  * @param {boolean} [force=false] - Whether to force validation.
14747
14912
  */
14748
14913
  validate(force = false) {
14914
+ if (this._inFullscreenTransition) {
14915
+ return;
14916
+ }
14749
14917
  this.validation.validate(this, force);
14750
14918
  }
14751
14919
 
@@ -14799,7 +14967,13 @@ class AuroCombobox extends AuroElement {
14799
14967
  }
14800
14968
 
14801
14969
  if (changedProperties.has('availableOptions')) {
14802
- if ((this.availableOptions.length > 0 && this.componentHasFocus) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
14970
+ // dropdownOpen is set synchronously by the auroDropdown-toggled event
14971
+ // handler during showBib() → floater.showBib() → dispatchEventDropdownToggle(),
14972
+ // so it's already true by the time updated() runs. This prevents the else
14973
+ // branch from calling hideBib() when the dropdown was just opened but
14974
+ // :focus-within hasn't propagated through the top-layer dialog's nested
14975
+ // shadow DOM boundaries.
14976
+ if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
14803
14977
  this.showBib();
14804
14978
  } else {
14805
14979
  this.hideBib();
@@ -14974,6 +15148,7 @@ class AuroCombobox extends AuroElement {
14974
15148
  <slot @slotchange="${this.handleSlotChange}"></slot>
14975
15149
  <${this.inputTag}
14976
15150
  id="inputInBib"
15151
+ autofocus
14977
15152
  @input="${this.handleInputValueChange}"
14978
15153
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
14979
15154
  .a11yControls=${`${this.dropdownId}-floater-bib`}
@@ -15312,7 +15487,11 @@ class AuroMenuOption extends AuroElement {
15312
15487
 
15313
15488
  // Generate unique ID if not already set (required for aria-activedescendant)
15314
15489
  if (!this.id) {
15315
- this.id = `menuoption-${Math.random().toString(36).slice(2, 8)}`;
15490
+ const idBase = 36;
15491
+ const sliceStart = 2;
15492
+ const sliceEnd = 8;
15493
+ this.id = `menuoption-${Math.random().toString(idBase).
15494
+ slice(sliceStart, sliceEnd)}`;
15316
15495
  }
15317
15496
 
15318
15497
  this.setAttribute('role', 'option');
@@ -16219,7 +16398,7 @@ class MenuService {
16219
16398
 
16220
16399
  const MenuContext = n('menu-context');
16221
16400
 
16222
- /* eslint-disable no-underscore-dangle, curly */
16401
+ /* eslint-disable no-underscore-dangle */
16223
16402
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16224
16403
  // See LICENSE in the project root for license information.
16225
16404
 
@@ -16240,7 +16419,7 @@ const MenuContext = n('menu-context');
16240
16419
  * @slot - Slot for insertion of menu options.
16241
16420
  */
16242
16421
 
16243
- /* eslint-disable no-magic-numbers, max-lines, no-extra-parens */
16422
+ /* eslint-disable max-lines */
16244
16423
 
16245
16424
  class AuroMenu extends AuroElement {
16246
16425
 
@@ -19,7 +19,7 @@ export class AuroCombobox extends AuroElement {
19
19
  static get properties(): {
20
20
  /**
21
21
  * Defines whether the component will be on lighter or darker backgrounds.
22
- * @property {'default' | 'inverse'}
22
+ * @property {'default' | 'inverse'} appearance - The visual appearance of the component.
23
23
  * @default 'default'
24
24
  */
25
25
  appearance: {
@@ -360,6 +360,7 @@ export class AuroCombobox extends AuroElement {
360
360
  dropdownOpen: any;
361
361
  triggerExpandedState: boolean;
362
362
  _expandedTimeout: NodeJS.Timeout;
363
+ _inFullscreenTransition: boolean;
363
364
  errorMessage: any;
364
365
  isHiddenWhileLoading: boolean;
365
366
  largeFullscreenHeadline: boolean;