@aurodesignsystem-dev/auro-formkit 0.0.0-pr1346.2 → 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 +252 -65
  6. package/components/combobox/demo/index.min.js +252 -65
  7. package/components/combobox/dist/auro-combobox.d.ts +2 -1
  8. package/components/combobox/dist/index.js +245 -62
  9. package/components/combobox/dist/registered.js +245 -62
  10. package/components/counter/demo/api.min.js +124 -12
  11. package/components/counter/demo/index.min.js +124 -12
  12. package/components/counter/dist/index.js +124 -12
  13. package/components/counter/dist/registered.js +124 -12
  14. package/components/datepicker/demo/api.min.js +161 -40
  15. package/components/datepicker/demo/index.min.js +161 -40
  16. package/components/datepicker/dist/index.js +161 -40
  17. package/components/datepicker/dist/registered.js +161 -40
  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 +38 -29
  24. package/components/input/demo/index.min.js +38 -29
  25. package/components/input/dist/index.js +38 -29
  26. package/components/input/dist/registered.js +38 -29
  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 = '202602201708';
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.
@@ -11375,31 +11488,34 @@ class BaseInput extends AuroElement$1 {
11375
11488
  // Process credit card type detection and formatting during input
11376
11489
  if (this.type === 'credit-card') {
11377
11490
  this.processCreditCard();
11378
- }
11491
+ this.touched = true;
11492
+ this.validation.validate(this);
11493
+ } else {
11379
11494
 
11380
- // Sets value property to value of element value (el.value).
11381
- this.value = this.inputElement.value;
11495
+ // Sets value property to value of element value (el.value).
11496
+ this.value = this.inputElement.value;
11382
11497
 
11383
- // Determine if the value change was programmatic, including autofill.
11384
- const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11498
+ // Determine if the value change was programmatic, including autofill.
11499
+ const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11385
11500
 
11386
- // Validation on input or programmatic value change (including autofill).
11387
- if (this.validateOnInput || inputWasProgrammatic) {
11388
- this.touched = true;
11389
- this.validation.validate(this);
11390
- }
11501
+ // Validation on input or programmatic value change (including autofill).
11502
+ if (this.validateOnInput || inputWasProgrammatic) {
11503
+ this.touched = true;
11504
+ this.validation.validate(this);
11505
+ }
11391
11506
 
11392
- // Prevents cursor jumping in Safari.
11393
- const { selectionStart } = this.inputElement;
11507
+ // Prevents cursor jumping in Safari.
11508
+ const { selectionStart } = this.inputElement;
11394
11509
 
11395
- if (this.setSelectionInputTypes.includes(this.type)) {
11396
- this.updateComplete.then(() => {
11397
- try {
11398
- this.inputElement.setSelectionRange(selectionStart, selectionStart);
11399
- } catch (error) { // eslint-disable-line
11400
- // do nothing
11401
- }
11402
- });
11510
+ if (this.setSelectionInputTypes.includes(this.type)) {
11511
+ this.updateComplete.then(() => {
11512
+ try {
11513
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
11514
+ } catch (error) { // eslint-disable-line
11515
+ // do nothing
11516
+ }
11517
+ });
11518
+ }
11403
11519
  }
11404
11520
  }
11405
11521
 
@@ -11432,6 +11548,11 @@ class BaseInput extends AuroElement$1 {
11432
11548
  this.inputElement.scrollLeft = 100;
11433
11549
 
11434
11550
  if (!this.noValidate) {
11551
+ // For credit card inputs with mask, ensure value is synced from mask instance
11552
+ if (this.type === 'credit-card' && this.maskInstance) {
11553
+ this.value = this.maskInstance.value;
11554
+ }
11555
+
11435
11556
  this.validation.validate(this);
11436
11557
  }
11437
11558
  }
@@ -12094,7 +12215,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12094
12215
  }
12095
12216
  };
12096
12217
 
12097
- var formkitVersion$1 = '202602201708';
12218
+ var formkitVersion$1 = '202602260152';
12098
12219
 
12099
12220
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12100
12221
  // See LICENSE in the project root for license information.
@@ -13121,7 +13242,7 @@ class AuroBibtemplate extends i$4 {
13121
13242
  }
13122
13243
  }
13123
13244
 
13124
- var formkitVersion = '202602201708';
13245
+ var formkitVersion = '202602260152';
13125
13246
 
13126
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}`;
13127
13248
 
@@ -13539,6 +13660,7 @@ class AuroCombobox extends AuroElement {
13539
13660
  this.dropdownOpen = false;
13540
13661
  this.triggerExpandedState = false;
13541
13662
  this._expandedTimeout = null;
13663
+ this._inFullscreenTransition = false;
13542
13664
  this.errorMessage = null;
13543
13665
  this.isHiddenWhileLoading = false;
13544
13666
  this.largeFullscreenHeadline = false;
@@ -13559,7 +13681,7 @@ class AuroCombobox extends AuroElement {
13559
13681
 
13560
13682
  /**
13561
13683
  * Defines whether the component will be on lighter or darker backgrounds.
13562
- * @property {'default' | 'inverse'}
13684
+ * @property {'default' | 'inverse'} appearance - The visual appearance of the component.
13563
13685
  * @default 'default'
13564
13686
  */
13565
13687
  appearance: {
@@ -14114,7 +14236,7 @@ class AuroCombobox extends AuroElement {
14114
14236
  * @returns {void}
14115
14237
  */
14116
14238
  showBib() {
14117
- if (!this.input.value) {
14239
+ if (!this.input.value && !this.dropdown.isBibFullscreen) {
14118
14240
  this.dropdown.hide();
14119
14241
  return;
14120
14242
  }
@@ -14183,6 +14305,18 @@ class AuroCombobox extends AuroElement {
14183
14305
 
14184
14306
  // Restore trigger accessibility when closing fullscreen
14185
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
+ }
14186
14320
  }
14187
14321
 
14188
14322
  if (this.dropdownOpen) {
@@ -14192,6 +14326,19 @@ class AuroCombobox extends AuroElement {
14192
14326
  this.updateBibDialogRole();
14193
14327
 
14194
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
+
14195
14342
  // Hide the trigger from assistive technology so VoiceOver cannot reach it
14196
14343
  // behind the fullscreen dialog
14197
14344
  this.dropdown.trigger.inert = true;
@@ -14223,6 +14370,7 @@ class AuroCombobox extends AuroElement {
14223
14370
  requestAnimationFrame(() => {
14224
14371
  requestAnimationFrame(() => {
14225
14372
  this.setInputFocus();
14373
+ this._inFullscreenTransition = false;
14226
14374
  });
14227
14375
  });
14228
14376
  } else {
@@ -14317,7 +14465,7 @@ class AuroCombobox extends AuroElement {
14317
14465
  * @private
14318
14466
  */
14319
14467
  updateBibDialogRole() {
14320
- const bibEl = this.dropdown.bibElement?.value;
14468
+ const bibEl = this.dropdown.bibElement && this.dropdown.bibElement.value;
14321
14469
  if (!bibEl) {
14322
14470
  return;
14323
14471
  }
@@ -14446,9 +14594,10 @@ class AuroCombobox extends AuroElement {
14446
14594
  // Announce the selection after the dropdown closes so it isn't
14447
14595
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
14448
14596
  const selectedValue = event.detail.stringValue;
14597
+ const announcementDelay = 300;
14449
14598
  setTimeout(() => {
14450
14599
  this.announceToScreenReader(`${selectedValue}, selected`);
14451
- }, 300);
14600
+ }, announcementDelay);
14452
14601
  }
14453
14602
  });
14454
14603
 
@@ -14496,7 +14645,7 @@ class AuroCombobox extends AuroElement {
14496
14645
  * Validate every time we remove focus from the combo box.
14497
14646
  */
14498
14647
  this.addEventListener('focusout', () => {
14499
- if (!this.componentHasFocus) {
14648
+ if (!this.componentHasFocus && !this._inFullscreenTransition) {
14500
14649
  this.validate();
14501
14650
  }
14502
14651
  });
@@ -14547,8 +14696,11 @@ class AuroCombobox extends AuroElement {
14547
14696
  }
14548
14697
  this.handleMenuOptions();
14549
14698
 
14550
- // Validate only if the value was set programmatically
14551
- 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) {
14552
14704
  this.validate();
14553
14705
  }
14554
14706
 
@@ -14563,6 +14715,22 @@ class AuroCombobox extends AuroElement {
14563
14715
  this.hideBib();
14564
14716
  }
14565
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
+
14566
14734
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
14567
14735
  }
14568
14736
 
@@ -14588,23 +14756,20 @@ class AuroCombobox extends AuroElement {
14588
14756
  }
14589
14757
 
14590
14758
  if (evt.key === 'Tab' && this.dropdown.isPopoverVisible) {
14591
- if (this.dropdown.isBibFullscreen) {
14592
-
14593
- // when focus is on the input, move focus back to close button with Tab key
14594
- if (document.activeElement.shadowRoot.activeElement === this.inputInBib) {
14595
- evt.preventDefault();
14596
- this.dropdown.focus();
14597
- }
14598
- } 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) {
14599
14769
  if (this.menu.optionActive && this.menu.optionActive.value) {
14600
14770
  this.menu.value = this.menu.optionActive.value;
14601
14771
  }
14602
-
14603
- setTimeout(() => {
14604
- if (!this.componentHasFocus) {
14605
- this.hideBib();
14606
- }
14607
- }, 0);
14772
+ this.hideBib();
14608
14773
  }
14609
14774
  }
14610
14775
 
@@ -14635,6 +14800,14 @@ class AuroCombobox extends AuroElement {
14635
14800
  });
14636
14801
 
14637
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
+
14638
14811
  this.input.validity = evt.detail.validity;
14639
14812
  this.input.errorMessage = evt.detail.message;
14640
14813
  this.validity = evt.detail.validity;
@@ -14738,6 +14911,9 @@ class AuroCombobox extends AuroElement {
14738
14911
  * @param {boolean} [force=false] - Whether to force validation.
14739
14912
  */
14740
14913
  validate(force = false) {
14914
+ if (this._inFullscreenTransition) {
14915
+ return;
14916
+ }
14741
14917
  this.validation.validate(this, force);
14742
14918
  }
14743
14919
 
@@ -14791,7 +14967,13 @@ class AuroCombobox extends AuroElement {
14791
14967
  }
14792
14968
 
14793
14969
  if (changedProperties.has('availableOptions')) {
14794
- 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)) {
14795
14977
  this.showBib();
14796
14978
  } else {
14797
14979
  this.hideBib();
@@ -14966,6 +15148,7 @@ class AuroCombobox extends AuroElement {
14966
15148
  <slot @slotchange="${this.handleSlotChange}"></slot>
14967
15149
  <${this.inputTag}
14968
15150
  id="inputInBib"
15151
+ autofocus
14969
15152
  @input="${this.handleInputValueChange}"
14970
15153
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
14971
15154
  .a11yControls=${`${this.dropdownId}-floater-bib`}
@@ -15304,7 +15487,11 @@ class AuroMenuOption extends AuroElement {
15304
15487
 
15305
15488
  // Generate unique ID if not already set (required for aria-activedescendant)
15306
15489
  if (!this.id) {
15307
- 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)}`;
15308
15495
  }
15309
15496
 
15310
15497
  this.setAttribute('role', 'option');
@@ -16211,7 +16398,7 @@ class MenuService {
16211
16398
 
16212
16399
  const MenuContext = n('menu-context');
16213
16400
 
16214
- /* eslint-disable no-underscore-dangle, curly */
16401
+ /* eslint-disable no-underscore-dangle */
16215
16402
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16216
16403
  // See LICENSE in the project root for license information.
16217
16404
 
@@ -16232,7 +16419,7 @@ const MenuContext = n('menu-context');
16232
16419
  * @slot - Slot for insertion of menu options.
16233
16420
  */
16234
16421
 
16235
- /* eslint-disable no-magic-numbers, max-lines, no-extra-parens */
16422
+ /* eslint-disable max-lines */
16236
16423
 
16237
16424
  class AuroMenu extends AuroElement {
16238
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;