@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
@@ -1687,7 +1687,7 @@ class AuroHelpText extends i$2 {
1687
1687
  }
1688
1688
  }
1689
1689
 
1690
- var formkitVersion = '202602201708';
1690
+ var formkitVersion = '202602260152';
1691
1691
 
1692
1692
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1693
1693
  // See LICENSE in the project root for license information.
@@ -1679,7 +1679,7 @@ class AuroHelpText extends i$2 {
1679
1679
  }
1680
1680
  }
1681
1681
 
1682
- var formkitVersion = '202602201708';
1682
+ var formkitVersion = '202602260152';
1683
1683
 
1684
1684
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1685
1685
  // See LICENSE in the project root for license information.
@@ -1632,7 +1632,7 @@ class AuroHelpText extends LitElement {
1632
1632
  }
1633
1633
  }
1634
1634
 
1635
- var formkitVersion = '202602201708';
1635
+ var formkitVersion = '202602260152';
1636
1636
 
1637
1637
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1638
1638
  // See LICENSE in the project root for license information.
@@ -1632,7 +1632,7 @@ class AuroHelpText extends LitElement {
1632
1632
  }
1633
1633
  }
1634
1634
 
1635
- var formkitVersion = '202602201708';
1635
+ var formkitVersion = '202602260152';
1636
1636
 
1637
1637
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1638
1638
  // See LICENSE in the project root for license information.
@@ -3950,7 +3950,7 @@ let p$4 = class p{registerComponent(t,a){customElements.get(t)||customElements.d
3950
3950
 
3951
3951
  var iconVersion$3 = '9.1.2';
3952
3952
 
3953
- 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}`;
3953
+ 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}`;
3954
3954
 
3955
3955
  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)}`;
3956
3956
 
@@ -3958,6 +3958,8 @@ var tokensCss$1$2 = i$7`:host(:not([ondark])),:host(:not([appearance=inverse])){
3958
3958
 
3959
3959
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
3960
3960
  // See LICENSE in the project root for license information.
3961
+ /* eslint-disable max-lines */
3962
+ // ---------------------------------------------------------------------
3961
3963
 
3962
3964
 
3963
3965
  const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-';
@@ -4133,22 +4135,63 @@ class AuroDropdownBib extends i$4 {
4133
4135
  // Handle ESC key via dialog's cancel event
4134
4136
  const dialog = this.shadowRoot.querySelector('dialog');
4135
4137
  dialog.addEventListener('cancel', (event) => {
4136
- event.preventDefault(); // Let parent handle closing
4138
+ // Let parent handle closing
4139
+ event.preventDefault();
4137
4140
  this.dispatchEvent(new CustomEvent('auro-bib-cancel', {
4138
4141
  bubbles: true,
4139
4142
  composed: true
4140
4143
  }));
4141
4144
  });
4142
4145
 
4143
- // Re-dispatch navigation keyboard events so they cross the shadow DOM
4144
- // boundary and reach the combobox/select key handlers.
4145
- // Only intercept keys used for menu navigation let all other keys
4146
- // (characters, Backspace, etc.) through so typing in inputs works.
4147
- const navKeys = new Set(['ArrowUp', 'ArrowDown', 'Enter', 'Escape']);
4146
+ // showModal() creates a closed focus scope keyboard events inside
4147
+ // the dialog's shadow DOM do NOT bubble out to the combobox/select
4148
+ // keydown handlers in the parent shadow DOM. This handler bridges
4149
+ // that gap by re-dispatching navigation keys so they cross the
4150
+ // shadow boundary and reach the menu navigation logic in the parent
4151
+ // component.
4152
+ //
4153
+ // The trade-off: intercepting these keys means native keyboard
4154
+ // behaviors that would normally "just work" must be manually
4155
+ // re-implemented here:
4156
+ //
4157
+ // - Enter on buttons: Custom elements (auro-button) don't get the
4158
+ // native Enter→click that <button> provides, so we call .click()
4159
+ // directly when Enter is pressed on a button-like element.
4160
+ //
4161
+ // - Tab: NOT intercepted — left to the browser's native focus trap
4162
+ // provided by showModal(), which cycles Tab between focusable
4163
+ // elements inside the dialog (e.g. the input and close button).
4164
+ // Intercepting Tab would kill the native focus trap and break
4165
+ // focus management inside the dialog.
4166
+ //
4167
+ // - Escape: The native <dialog> fires a `cancel` event on ESC
4168
+ // (handled above), so the re-dispatched Escape is a secondary
4169
+ // path for parent components that also listen for Escape keydown.
4170
+ const navKeys = new Set([
4171
+ 'ArrowUp',
4172
+ 'ArrowDown',
4173
+ 'Enter',
4174
+ 'Escape'
4175
+ ]);
4148
4176
  dialog.addEventListener('keydown', (event) => {
4149
4177
  if (!navKeys.has(event.key)) {
4150
4178
  return;
4151
4179
  }
4180
+
4181
+ // Custom elements (auro-button) don't get the native Enter→click
4182
+ // behavior that <button> has. Find the button in the composed path
4183
+ // and click it directly.
4184
+ if (event.key === 'Enter') {
4185
+ const buttonSelector = 'button, [role="button"], auro-button, [auro-button]';
4186
+ const btn = event.composedPath().find((el) => el.matches && el.matches(buttonSelector));
4187
+ if (btn) {
4188
+ event.preventDefault();
4189
+ event.stopPropagation();
4190
+ btn.click();
4191
+ return;
4192
+ }
4193
+ }
4194
+
4152
4195
  event.preventDefault();
4153
4196
  event.stopPropagation();
4154
4197
  const newEvent = new KeyboardEvent('keydown', {
@@ -4172,9 +4215,55 @@ class AuroDropdownBib extends i$4 {
4172
4215
  }
4173
4216
 
4174
4217
  /**
4175
- * Opens the dialog using showModal() for accessibility.
4176
- * @param {boolean} modal - If true, uses showModal() (default). If false, uses show().
4218
+ * Blocks touch-driven page scroll while a fullscreen modal dialog is open.
4219
+ *
4220
+ * The showModal() function places the dialog in the browser's **top layer**,
4221
+ * which is a separate rendering layer above the normal DOM. On mobile, the
4222
+ * compositor processes visual-viewport panning before top-layer touch
4223
+ * handling. This means the entire viewport — including the top-layer dialog
4224
+ * — can be panned by a touch gesture, causing the page behind the dialog to
4225
+ * scroll into view. To prevent this, we add a touchmove listener that cancels
4226
+ * the event if the touch started outside the dialog or any scrollable child within it.
4227
+ *
4228
+ * @private
4229
+ */
4230
+ _lockTouchScroll() {
4231
+ const dialog = this.shadowRoot.querySelector('dialog');
4232
+
4233
+ this._touchMoveHandler = (event) => {
4234
+ // Walk the composed path (which crosses shadow DOM boundaries) to
4235
+ // check whether the touch started inside a scrollable element that
4236
+ // lives within the dialog. If so, allow the scroll.
4237
+ for (const el of event.composedPath()) {
4238
+ if (el === dialog) {
4239
+ // Reached the dialog boundary without finding a scrollable child.
4240
+ break;
4241
+ }
4242
+ if (el instanceof HTMLElement && el.scrollHeight > el.clientHeight) {
4243
+ const { overflowY } = getComputedStyle(el);
4244
+ if (overflowY === 'auto' || overflowY === 'scroll') {
4245
+ return;
4246
+ }
4247
+ }
4248
+ }
4249
+
4250
+ event.preventDefault();
4251
+ };
4252
+
4253
+ document.addEventListener('touchmove', this._touchMoveHandler, { passive: false });
4254
+ }
4255
+
4256
+ /**
4257
+ * Removes the touchmove listener added by _lockTouchScroll().
4258
+ * @private
4177
4259
  */
4260
+ _unlockTouchScroll() {
4261
+ if (this._touchMoveHandler) {
4262
+ document.removeEventListener('touchmove', this._touchMoveHandler);
4263
+ this._touchMoveHandler = undefined;
4264
+ }
4265
+ }
4266
+
4178
4267
  open(modal = true) {
4179
4268
  const dialog = this.shadowRoot.querySelector('dialog');
4180
4269
  if (dialog && !dialog.open) {
@@ -4191,6 +4280,8 @@ class AuroDropdownBib extends i$4 {
4191
4280
 
4192
4281
  documentElement.style.overflow = prevOverflow;
4193
4282
 
4283
+ this._lockTouchScroll();
4284
+
4194
4285
  } else {
4195
4286
  // Use setAttribute instead of dialog.show() to avoid the dialog
4196
4287
  // focusing steps which steal focus from the trigger and cause
@@ -4206,6 +4297,7 @@ class AuroDropdownBib extends i$4 {
4206
4297
  close() {
4207
4298
  const dialog = this.shadowRoot.querySelector('dialog');
4208
4299
  if (dialog && dialog.open) {
4300
+ this._unlockTouchScroll();
4209
4301
  dialog.close();
4210
4302
  }
4211
4303
  }
@@ -4471,7 +4563,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$4 {
4471
4563
  }
4472
4564
  };
4473
4565
 
4474
- var formkitVersion$2 = '202602201708';
4566
+ var formkitVersion$2 = '202602260152';
4475
4567
 
4476
4568
  let AuroElement$2 = class AuroElement extends i$4 {
4477
4569
  static get properties() {
@@ -4744,6 +4836,18 @@ class AuroDropdown extends AuroElement$2 {
4744
4836
  */
4745
4837
  show() {
4746
4838
  this.floater.showBib();
4839
+
4840
+ // Open dialog synchronously so callers remain in the user gesture
4841
+ // chain. This is critical for mobile browsers (iOS Safari) to keep
4842
+ // the virtual keyboard open when transitioning from the trigger
4843
+ // input to an input inside the fullscreen dialog. Without this,
4844
+ // showModal() fires asynchronously via Lit's update cycle, which
4845
+ // falls outside the user activation window and causes iOS to
4846
+ // dismiss the keyboard.
4847
+ if (this.isBibFullscreen && this.bibElement && this.bibElement.value) {
4848
+ const useModal = !this.disableFocusTrap;
4849
+ this.bibElement.value.open(useModal);
4850
+ }
4747
4851
  }
4748
4852
 
4749
4853
  /**
@@ -5130,6 +5234,14 @@ class AuroDropdown extends AuroElement$2 {
5130
5234
  this.bibElement.value.close();
5131
5235
  }
5132
5236
  }
5237
+
5238
+ // When fullscreen strategy changes while open, re-open dialog with correct mode
5239
+ // (e.g. resizing from desktop → mobile while dropdown is open)
5240
+ if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
5241
+ const useModal = this.isBibFullscreen && !this.disableFocusTrap;
5242
+ this.bibElement.value.close();
5243
+ this.bibElement.value.open(useModal);
5244
+ }
5133
5245
  }
5134
5246
 
5135
5247
  /**
@@ -10631,6 +10743,15 @@ class BaseInput extends AuroElement$1 {
10631
10743
  constructor() {
10632
10744
  super();
10633
10745
 
10746
+ // Delegate focus to the native <input> inside the shadow root so that
10747
+ // showModal()'s dialog focusing steps reach the input element.
10748
+ // This keeps the mobile virtual keyboard open when the fullscreen dialog
10749
+ // opens, because the browser sees an input-to-input focus transfer.
10750
+ this.constructor.shadowRootOptions = {
10751
+ ...AuroElement$1.shadowRootOptions,
10752
+ delegatesFocus: true,
10753
+ };
10754
+
10634
10755
  this._initializeDefaults();
10635
10756
  }
10636
10757
 
@@ -10752,14 +10873,6 @@ class BaseInput extends AuroElement$1 {
10752
10873
  reflect: true
10753
10874
  },
10754
10875
 
10755
- /**
10756
- * The value for the aria-controls attribute.
10757
- */
10758
- a11yControls: {
10759
- type: String,
10760
- reflect: true
10761
- },
10762
-
10763
10876
  /**
10764
10877
  * The value for the aria-activedescendant attribute.
10765
10878
  * Points to the ID of the currently active/highlighted option in a listbox.
@@ -11452,31 +11565,34 @@ class BaseInput extends AuroElement$1 {
11452
11565
  // Process credit card type detection and formatting during input
11453
11566
  if (this.type === 'credit-card') {
11454
11567
  this.processCreditCard();
11455
- }
11568
+ this.touched = true;
11569
+ this.validation.validate(this);
11570
+ } else {
11456
11571
 
11457
- // Sets value property to value of element value (el.value).
11458
- this.value = this.inputElement.value;
11572
+ // Sets value property to value of element value (el.value).
11573
+ this.value = this.inputElement.value;
11459
11574
 
11460
- // Determine if the value change was programmatic, including autofill.
11461
- const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11575
+ // Determine if the value change was programmatic, including autofill.
11576
+ const inputWasProgrammatic = !this.matches(":focus") || event.isProgrammatic;
11462
11577
 
11463
- // Validation on input or programmatic value change (including autofill).
11464
- if (this.validateOnInput || inputWasProgrammatic) {
11465
- this.touched = true;
11466
- this.validation.validate(this);
11467
- }
11578
+ // Validation on input or programmatic value change (including autofill).
11579
+ if (this.validateOnInput || inputWasProgrammatic) {
11580
+ this.touched = true;
11581
+ this.validation.validate(this);
11582
+ }
11468
11583
 
11469
- // Prevents cursor jumping in Safari.
11470
- const { selectionStart } = this.inputElement;
11584
+ // Prevents cursor jumping in Safari.
11585
+ const { selectionStart } = this.inputElement;
11471
11586
 
11472
- if (this.setSelectionInputTypes.includes(this.type)) {
11473
- this.updateComplete.then(() => {
11474
- try {
11475
- this.inputElement.setSelectionRange(selectionStart, selectionStart);
11476
- } catch (error) { // eslint-disable-line
11477
- // do nothing
11478
- }
11479
- });
11587
+ if (this.setSelectionInputTypes.includes(this.type)) {
11588
+ this.updateComplete.then(() => {
11589
+ try {
11590
+ this.inputElement.setSelectionRange(selectionStart, selectionStart);
11591
+ } catch (error) { // eslint-disable-line
11592
+ // do nothing
11593
+ }
11594
+ });
11595
+ }
11480
11596
  }
11481
11597
  }
11482
11598
 
@@ -11509,6 +11625,11 @@ class BaseInput extends AuroElement$1 {
11509
11625
  this.inputElement.scrollLeft = 100;
11510
11626
 
11511
11627
  if (!this.noValidate) {
11628
+ // For credit card inputs with mask, ensure value is synced from mask instance
11629
+ if (this.type === 'credit-card' && this.maskInstance) {
11630
+ this.value = this.maskInstance.value;
11631
+ }
11632
+
11512
11633
  this.validation.validate(this);
11513
11634
  }
11514
11635
  }
@@ -12171,7 +12292,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12171
12292
  }
12172
12293
  };
12173
12294
 
12174
- var formkitVersion$1 = '202602201708';
12295
+ var formkitVersion$1 = '202602260152';
12175
12296
 
12176
12297
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12177
12298
  // See LICENSE in the project root for license information.
@@ -13198,7 +13319,7 @@ class AuroBibtemplate extends i$4 {
13198
13319
  }
13199
13320
  }
13200
13321
 
13201
- var formkitVersion = '202602201708';
13322
+ var formkitVersion = '202602260152';
13202
13323
 
13203
13324
  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}`;
13204
13325
 
@@ -13616,6 +13737,7 @@ class AuroCombobox extends AuroElement {
13616
13737
  this.dropdownOpen = false;
13617
13738
  this.triggerExpandedState = false;
13618
13739
  this._expandedTimeout = null;
13740
+ this._inFullscreenTransition = false;
13619
13741
  this.errorMessage = null;
13620
13742
  this.isHiddenWhileLoading = false;
13621
13743
  this.largeFullscreenHeadline = false;
@@ -13636,7 +13758,7 @@ class AuroCombobox extends AuroElement {
13636
13758
 
13637
13759
  /**
13638
13760
  * Defines whether the component will be on lighter or darker backgrounds.
13639
- * @property {'default' | 'inverse'}
13761
+ * @property {'default' | 'inverse'} appearance - The visual appearance of the component.
13640
13762
  * @default 'default'
13641
13763
  */
13642
13764
  appearance: {
@@ -14191,7 +14313,7 @@ class AuroCombobox extends AuroElement {
14191
14313
  * @returns {void}
14192
14314
  */
14193
14315
  showBib() {
14194
- if (!this.input.value) {
14316
+ if (!this.input.value && !this.dropdown.isBibFullscreen) {
14195
14317
  this.dropdown.hide();
14196
14318
  return;
14197
14319
  }
@@ -14260,6 +14382,18 @@ class AuroCombobox extends AuroElement {
14260
14382
 
14261
14383
  // Restore trigger accessibility when closing fullscreen
14262
14384
  this.dropdown.trigger.inert = false;
14385
+
14386
+ // Restore focus to the trigger input after closing the
14387
+ // fullscreen dialog. The browser's native dialog focus restoration
14388
+ // fails because the trigger was set to inert before showModal().
14389
+ // Use rAF to run after Lit's microtask update cycle calls dialog.close().
14390
+ if (this.dropdown.isBibFullscreen) {
14391
+ requestAnimationFrame(() => {
14392
+ if (!this.dropdown.isPopoverVisible) {
14393
+ this.input.focus();
14394
+ }
14395
+ });
14396
+ }
14263
14397
  }
14264
14398
 
14265
14399
  if (this.dropdownOpen) {
@@ -14269,6 +14403,19 @@ class AuroCombobox extends AuroElement {
14269
14403
  this.updateBibDialogRole();
14270
14404
 
14271
14405
  if (this.dropdown.isBibFullscreen) {
14406
+ // Guard against spurious validation during the focus transition
14407
+ // from trigger to bib input. Setting trigger.inert = true removes
14408
+ // focus, which fires focusout on the child auro-input before the
14409
+ // bib input receives focus. That focusout triggers the input's own
14410
+ // validate(), which dispatches a composed auroFormElement-validated
14411
+ // event. Because composed events are retargetted at each shadow DOM
14412
+ // boundary, the event appears to originate from the combobox itself
14413
+ // and its listener unconditionally sets this.validity — causing
14414
+ // premature validation. This flag suppresses all validation paths
14415
+ // (focusout handler, handleInputValueChange, validate(), and the
14416
+ // auroFormElement-validated listener) until focus settles in the bib.
14417
+ this._inFullscreenTransition = true;
14418
+
14272
14419
  // Hide the trigger from assistive technology so VoiceOver cannot reach it
14273
14420
  // behind the fullscreen dialog
14274
14421
  this.dropdown.trigger.inert = true;
@@ -14300,6 +14447,7 @@ class AuroCombobox extends AuroElement {
14300
14447
  requestAnimationFrame(() => {
14301
14448
  requestAnimationFrame(() => {
14302
14449
  this.setInputFocus();
14450
+ this._inFullscreenTransition = false;
14303
14451
  });
14304
14452
  });
14305
14453
  } else {
@@ -14394,7 +14542,7 @@ class AuroCombobox extends AuroElement {
14394
14542
  * @private
14395
14543
  */
14396
14544
  updateBibDialogRole() {
14397
- const bibEl = this.dropdown.bibElement?.value;
14545
+ const bibEl = this.dropdown.bibElement && this.dropdown.bibElement.value;
14398
14546
  if (!bibEl) {
14399
14547
  return;
14400
14548
  }
@@ -14523,9 +14671,10 @@ class AuroCombobox extends AuroElement {
14523
14671
  // Announce the selection after the dropdown closes so it isn't
14524
14672
  // overridden by VoiceOver's "collapsed" announcement from aria-expanded.
14525
14673
  const selectedValue = event.detail.stringValue;
14674
+ const announcementDelay = 300;
14526
14675
  setTimeout(() => {
14527
14676
  this.announceToScreenReader(`${selectedValue}, selected`);
14528
- }, 300);
14677
+ }, announcementDelay);
14529
14678
  }
14530
14679
  });
14531
14680
 
@@ -14573,7 +14722,7 @@ class AuroCombobox extends AuroElement {
14573
14722
  * Validate every time we remove focus from the combo box.
14574
14723
  */
14575
14724
  this.addEventListener('focusout', () => {
14576
- if (!this.componentHasFocus) {
14725
+ if (!this.componentHasFocus && !this._inFullscreenTransition) {
14577
14726
  this.validate();
14578
14727
  }
14579
14728
  });
@@ -14624,8 +14773,11 @@ class AuroCombobox extends AuroElement {
14624
14773
  }
14625
14774
  this.handleMenuOptions();
14626
14775
 
14627
- // Validate only if the value was set programmatically
14628
- if (!this.componentHasFocus) {
14776
+ // Validate only if the value was set programmatically (not during user
14777
+ // interaction). In fullscreen dialog mode, componentHasFocus returns false
14778
+ // because focus is inside the top-layer dialog, so also check
14779
+ // dropdownOpen and the fullscreen transition flag.
14780
+ if (!this.componentHasFocus && !this.dropdownOpen && !this._inFullscreenTransition) {
14629
14781
  this.validate();
14630
14782
  }
14631
14783
 
@@ -14640,6 +14792,22 @@ class AuroCombobox extends AuroElement {
14640
14792
  this.hideBib();
14641
14793
  }
14642
14794
 
14795
+ // iOS virtual keyboard retention: when in fullscreen mode, ensure the
14796
+ // dialog opens and the bib input is focused synchronously within the
14797
+ // input event (user gesture) chain. Without this, Lit's async update
14798
+ // cycle delays showModal() past the user activation window, causing
14799
+ // iOS Safari to dismiss the virtual keyboard when the fullscreen
14800
+ // dialog opens — the user then has to tap the input again to resume
14801
+ // typing.
14802
+ if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
14803
+ if (!this.dropdown.isPopoverVisible) {
14804
+ this.showBib();
14805
+ }
14806
+ if (this.dropdown.isPopoverVisible) {
14807
+ this.setInputFocus();
14808
+ }
14809
+ }
14810
+
14643
14811
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
14644
14812
  }
14645
14813
 
@@ -14665,23 +14833,20 @@ class AuroCombobox extends AuroElement {
14665
14833
  }
14666
14834
 
14667
14835
  if (evt.key === 'Tab' && this.dropdown.isPopoverVisible) {
14668
- if (this.dropdown.isBibFullscreen) {
14669
-
14670
- // when focus is on the input, move focus back to close button with Tab key
14671
- if (document.activeElement.shadowRoot.activeElement === this.inputInBib) {
14672
- evt.preventDefault();
14673
- this.dropdown.focus();
14674
- }
14675
- } else {
14836
+ // Non-fullscreen (combobox pattern per WAI-ARIA APG):
14837
+ // Tab accepts the focused option and closes the popup, moving focus
14838
+ // to the next focusable element on the page.
14839
+ // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
14840
+ //
14841
+ // Fullscreen (dialog / modal pattern): Tab navigates between
14842
+ // focusable elements inside the modal (e.g. the close button) via
14843
+ // the native focus trap provided by showModal(). The dropdown is
14844
+ // closed by the close button or Escape instead.
14845
+ if (!this.dropdown.isBibFullscreen) {
14676
14846
  if (this.menu.optionActive && this.menu.optionActive.value) {
14677
14847
  this.menu.value = this.menu.optionActive.value;
14678
14848
  }
14679
-
14680
- setTimeout(() => {
14681
- if (!this.componentHasFocus) {
14682
- this.hideBib();
14683
- }
14684
- }, 0);
14849
+ this.hideBib();
14685
14850
  }
14686
14851
  }
14687
14852
 
@@ -14712,6 +14877,14 @@ class AuroCombobox extends AuroElement {
14712
14877
  });
14713
14878
 
14714
14879
  this.addEventListener('auroFormElement-validated', (evt) => {
14880
+ // During the fullscreen transition, child elements (auro-input) may fire
14881
+ // their own validation events when the trigger becomes inert and loses
14882
+ // focus. Those composed events bubble up through shadow DOM boundaries
14883
+ // and would incorrectly set combobox validity. Ignore them.
14884
+ if (this._inFullscreenTransition) {
14885
+ return;
14886
+ }
14887
+
14715
14888
  this.input.validity = evt.detail.validity;
14716
14889
  this.input.errorMessage = evt.detail.message;
14717
14890
  this.validity = evt.detail.validity;
@@ -14815,6 +14988,9 @@ class AuroCombobox extends AuroElement {
14815
14988
  * @param {boolean} [force=false] - Whether to force validation.
14816
14989
  */
14817
14990
  validate(force = false) {
14991
+ if (this._inFullscreenTransition) {
14992
+ return;
14993
+ }
14818
14994
  this.validation.validate(this, force);
14819
14995
  }
14820
14996
 
@@ -14868,7 +15044,13 @@ class AuroCombobox extends AuroElement {
14868
15044
  }
14869
15045
 
14870
15046
  if (changedProperties.has('availableOptions')) {
14871
- if ((this.availableOptions.length > 0 && this.componentHasFocus) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
15047
+ // dropdownOpen is set synchronously by the auroDropdown-toggled event
15048
+ // handler during showBib() → floater.showBib() → dispatchEventDropdownToggle(),
15049
+ // so it's already true by the time updated() runs. This prevents the else
15050
+ // branch from calling hideBib() when the dropdown was just opened but
15051
+ // :focus-within hasn't propagated through the top-layer dialog's nested
15052
+ // shadow DOM boundaries.
15053
+ if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || this.menu.loading || (this.availableOptions.length === 0 && this.noMatchOption)) {
14872
15054
  this.showBib();
14873
15055
  } else {
14874
15056
  this.hideBib();
@@ -15043,6 +15225,7 @@ class AuroCombobox extends AuroElement {
15043
15225
  <slot @slotchange="${this.handleSlotChange}"></slot>
15044
15226
  <${this.inputTag}
15045
15227
  id="inputInBib"
15228
+ autofocus
15046
15229
  @input="${this.handleInputValueChange}"
15047
15230
  .a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
15048
15231
  .a11yControls=${`${this.dropdownId}-floater-bib`}
@@ -15381,7 +15564,11 @@ class AuroMenuOption extends AuroElement {
15381
15564
 
15382
15565
  // Generate unique ID if not already set (required for aria-activedescendant)
15383
15566
  if (!this.id) {
15384
- this.id = `menuoption-${Math.random().toString(36).slice(2, 8)}`;
15567
+ const idBase = 36;
15568
+ const sliceStart = 2;
15569
+ const sliceEnd = 8;
15570
+ this.id = `menuoption-${Math.random().toString(idBase).
15571
+ slice(sliceStart, sliceEnd)}`;
15385
15572
  }
15386
15573
 
15387
15574
  this.setAttribute('role', 'option');
@@ -16288,7 +16475,7 @@ class MenuService {
16288
16475
 
16289
16476
  const MenuContext = n('menu-context');
16290
16477
 
16291
- /* eslint-disable no-underscore-dangle, curly */
16478
+ /* eslint-disable no-underscore-dangle */
16292
16479
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
16293
16480
  // See LICENSE in the project root for license information.
16294
16481
 
@@ -16309,7 +16496,7 @@ const MenuContext = n('menu-context');
16309
16496
  * @slot - Slot for insertion of menu options.
16310
16497
  */
16311
16498
 
16312
- /* eslint-disable no-magic-numbers, max-lines, no-extra-parens */
16499
+ /* eslint-disable max-lines */
16313
16500
 
16314
16501
  class AuroMenu extends AuroElement {
16315
16502