@aurodesignsystem-dev/auro-formkit 0.0.0-pr1551.0 → 0.0.0-pr1552.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.min.js +207 -46
  7. package/components/combobox/demo/getting-started.min.js +207 -46
  8. package/components/combobox/demo/index.min.js +207 -46
  9. package/components/combobox/dist/index.js +207 -46
  10. package/components/combobox/dist/registered.js +207 -46
  11. package/components/counter/demo/customize.min.js +206 -45
  12. package/components/counter/demo/index.min.js +206 -45
  13. package/components/counter/dist/index.js +206 -45
  14. package/components/counter/dist/registered.js +206 -45
  15. package/components/datepicker/demo/customize.min.js +207 -46
  16. package/components/datepicker/demo/index.min.js +207 -46
  17. package/components/datepicker/dist/index.js +207 -46
  18. package/components/datepicker/dist/registered.js +207 -46
  19. package/components/dropdown/demo/customize.min.js +205 -44
  20. package/components/dropdown/demo/getting-started.min.js +205 -44
  21. package/components/dropdown/demo/index.min.js +205 -44
  22. package/components/dropdown/dist/index.js +205 -44
  23. package/components/dropdown/dist/registered.js +205 -44
  24. package/components/form/demo/customize.min.js +829 -185
  25. package/components/form/demo/getting-started.min.js +829 -185
  26. package/components/form/demo/index.min.js +829 -185
  27. package/components/form/demo/registerDemoDeps.min.js +829 -185
  28. package/components/input/demo/customize.min.js +1 -1
  29. package/components/input/demo/getting-started.min.js +1 -1
  30. package/components/input/demo/index.min.js +1 -1
  31. package/components/input/dist/index.js +1 -1
  32. package/components/input/dist/registered.js +1 -1
  33. package/components/radio/demo/customize.min.js +1 -1
  34. package/components/radio/demo/getting-started.min.js +1 -1
  35. package/components/radio/demo/index.min.js +1 -1
  36. package/components/radio/dist/index.js +1 -1
  37. package/components/radio/dist/registered.js +1 -1
  38. package/components/select/demo/customize.min.js +206 -45
  39. package/components/select/demo/getting-started.min.js +206 -45
  40. package/components/select/demo/index.min.js +206 -45
  41. package/components/select/dist/index.js +206 -45
  42. package/components/select/dist/registered.js +206 -45
  43. package/package.json +2 -2
@@ -1064,7 +1064,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
1064
1064
  }
1065
1065
  };
1066
1066
 
1067
- var formkitVersion$1 = '202607100029';
1067
+ var formkitVersion$1 = '202607092329';
1068
1068
 
1069
1069
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
1070
1070
  // See LICENSE in the project root for license information.
@@ -3776,40 +3776,12 @@ class AuroFloatingUI {
3776
3776
  return;
3777
3777
  }
3778
3778
 
3779
- // Chrome-specific: during popover top-layer promotion after a click on a
3780
- // slotted focusable, :focus-within can briefly evaluate false while the
3781
- // active element is still structurally inside the trigger/bib. Fall back
3782
- // to a shadow-piercing ancestry walk from the deep active element before
3783
- // treating this as a real focus loss.
3784
- try {
3785
- let active = document.activeElement;
3786
- while (active && active.shadowRoot && active.shadowRoot.activeElement) {
3787
- active = active.shadowRoot.activeElement;
3788
- }
3789
- const targets = [element, element.trigger, element.bib].filter(Boolean);
3790
- let node = active;
3791
- while (node) {
3792
- if (targets.includes(node)) {
3793
- return;
3794
- }
3795
- node =
3796
- node.parentElement ||
3797
- (node.getRootNode && node.getRootNode().host) ||
3798
- null;
3799
- }
3800
- } catch (e) {
3801
- // Defensive: fall through to the existing close path if traversal fails.
3802
- }
3803
-
3804
3779
  // if fullscreen bib is in fullscreen mode, do not close
3805
3780
  if (element.bib.hasAttribute("isfullscreen")) {
3806
3781
  return;
3807
3782
  }
3808
3783
 
3809
- // eventType "focusloss" distinguishes a Tab/click-driven close from an
3810
- // Escape keydown close. Consumers use this to decide whether to restore
3811
- // focus to the trigger (Escape) or let it advance naturally (Tab).
3812
- this.hideBib("focusloss");
3784
+ this.hideBib("keydown");
3813
3785
  }
3814
3786
 
3815
3787
  setupHideHandlers() {
@@ -4479,6 +4451,199 @@ function getFocusableElements(container) {
4479
4451
  return tabIndexedUniqueElements;
4480
4452
  }
4481
4453
 
4454
+ /**
4455
+ * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
4456
+ * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
4457
+ */
4458
+ class FocusTrap {
4459
+ /**
4460
+ * Creates a new FocusTrap instance for the given container element.
4461
+ * Initializes event listeners and prepares the container for focus management.
4462
+ *
4463
+ * @param {HTMLElement} container The DOM element to trap focus within.
4464
+ * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
4465
+ * @throws {Error} If the provided container is not a valid HTMLElement.
4466
+ */
4467
+ constructor(container, controlTabOrder = false) {
4468
+ if (!container || !(container instanceof HTMLElement)) {
4469
+ throw new Error("FocusTrap requires a valid HTMLElement.");
4470
+ }
4471
+
4472
+ this.container = container;
4473
+ this.tabDirection = "forward"; // or 'backward';
4474
+ this.controlTabOrder = controlTabOrder;
4475
+
4476
+ this._init();
4477
+ }
4478
+
4479
+ /**
4480
+ * Initializes the focus trap by setting up event listeners and attributes on the container.
4481
+ * Prepares the container for focus management, including support for shadow DOM and inert attributes.
4482
+ *
4483
+ * @private
4484
+ */
4485
+ _init() {
4486
+ // Add inert attribute to prevent focusing programmatically as well (if supported)
4487
+ if ("inert" in HTMLElement.prototype) {
4488
+ this.container.inert = false; // Ensure the container isn't inert
4489
+ this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
4490
+ }
4491
+
4492
+ // Track tab direction
4493
+ this.container.addEventListener("keydown", this._onKeydown);
4494
+ }
4495
+
4496
+ /**
4497
+ * Gets an array of currently active (focused) elements in the document and shadow DOM.
4498
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
4499
+ * @private
4500
+ */
4501
+ _getActiveElements() {
4502
+ // Get the active element(s) in the document and shadow root
4503
+ // This will include the active element in the shadow DOM if it exists
4504
+ // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
4505
+ let { activeElement } = document;
4506
+ const actives = [activeElement];
4507
+ while (activeElement?.shadowRoot?.activeElement) {
4508
+ actives.push(activeElement.shadowRoot.activeElement);
4509
+ activeElement = activeElement.shadowRoot.activeElement;
4510
+ }
4511
+ return actives;
4512
+ }
4513
+
4514
+ /**
4515
+ * Gets the next focus index based on the current index and focusable elements.
4516
+ * @param {number} currentIndex The current index of the focused element.
4517
+ * @param {Array<HTMLElement>} focusables The array of focusable elements.
4518
+ * @returns {number|null} The next focus index or null if not determined.
4519
+ */
4520
+ _getNextFocusIndex(currentIndex, focusables, actives) {
4521
+ if (this.controlTabOrder) {
4522
+ // Calculate the new index based on the current index and tab direction
4523
+ let newFocusIndex =
4524
+ currentIndex + (this.tabDirection === "forward" ? 1 : -1);
4525
+
4526
+ // Wrap-around logic
4527
+ if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
4528
+ if (newFocusIndex >= focusables.length) newFocusIndex = 0;
4529
+
4530
+ // Early return with the new index
4531
+ return newFocusIndex;
4532
+ }
4533
+
4534
+ // Determine if we need to wrap
4535
+ const atFirst =
4536
+ actives.includes(focusables[0]) || actives.includes(this.container);
4537
+ const atLast = actives.includes(focusables[focusables.length - 1]);
4538
+
4539
+ // Only wrap if at the ends
4540
+ if (this.tabDirection === "backward" && atFirst) {
4541
+ return focusables.length - 1;
4542
+ }
4543
+
4544
+ if (this.tabDirection === "forward" && atLast) {
4545
+ return 0;
4546
+ }
4547
+
4548
+ // No wrap, so don't change focus, return early
4549
+ return null;
4550
+ }
4551
+
4552
+ /**
4553
+ * Handles the Tab key press event to manage focus within the container.
4554
+ * @param {KeyboardEvent} e The keyboard event triggered by the user.
4555
+ * @returns {void}
4556
+ */
4557
+ _handleTabKey(e) {
4558
+ // Update the focusable elements
4559
+ const focusables = this._getFocusableElements();
4560
+
4561
+ // If there are no focusable elements, exit
4562
+ if (!focusables.length) return;
4563
+
4564
+ // Set the tab direction based on the key pressed
4565
+ this.tabDirection = e.shiftKey ? "backward" : "forward";
4566
+
4567
+ // Get the active elements that are currently focused
4568
+ const actives = this._getActiveElements();
4569
+
4570
+ // If we're at either end of the focusable elements, wrap around to the other end
4571
+ let focusIndex = focusables.findIndex((el) => actives.includes(el));
4572
+
4573
+ // Fallback if we have no focused element
4574
+ if (focusIndex === -1) focusIndex = 0;
4575
+
4576
+ // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
4577
+ // Is null if no new focus index is determined
4578
+ const newFocusIndex = this._getNextFocusIndex(
4579
+ focusIndex,
4580
+ focusables,
4581
+ actives,
4582
+ );
4583
+
4584
+ // If we have a new focus index, set focus to that element
4585
+ if (newFocusIndex !== null) {
4586
+ e.preventDefault();
4587
+ focusables[newFocusIndex].focus();
4588
+ }
4589
+ }
4590
+
4591
+ /**
4592
+ * Catches the keydown event
4593
+ * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
4594
+ * @private
4595
+ */
4596
+ _onKeydown = (e) => {
4597
+ // Handle tab
4598
+ if (e.key === "Tab") this._handleTabKey(e);
4599
+ };
4600
+
4601
+ /**
4602
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
4603
+ * Returns a unique, ordered array of elements that can receive focus.
4604
+ *
4605
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
4606
+ * @private
4607
+ */
4608
+ _getFocusableElements() {
4609
+ // Use the imported utility function to get focusable elements
4610
+ const elements = getFocusableElements(this.container);
4611
+
4612
+ // Return the elements found
4613
+ return elements;
4614
+ }
4615
+
4616
+ /**
4617
+ * Moves focus to the first focusable element within the container.
4618
+ * Useful for setting initial focus when activating the focus trap.
4619
+ */
4620
+ focusFirstElement() {
4621
+ const focusables = this._getFocusableElements();
4622
+ if (focusables.length) focusables[0].focus();
4623
+ }
4624
+
4625
+ /**
4626
+ * Moves focus to the last focusable element within the container.
4627
+ * Useful for setting focus when deactivating or cycling focus in reverse.
4628
+ */
4629
+ focusLastElement() {
4630
+ const focusables = this._getFocusableElements();
4631
+ if (focusables.length) focusables[focusables.length - 1].focus();
4632
+ }
4633
+
4634
+ /**
4635
+ * Removes event listeners and attributes added by the focus trap.
4636
+ * Call this method to clean up when the focus trap is no longer needed.
4637
+ */
4638
+ disconnect() {
4639
+ if (this.container.hasAttribute("data-focus-trap-container")) {
4640
+ this.container.removeAttribute("data-focus-trap-container");
4641
+ }
4642
+
4643
+ this.container.removeEventListener("keydown", this._onKeydown);
4644
+ }
4645
+ }
4646
+
4482
4647
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
4483
4648
  // See LICENSE in the project root for license information.
4484
4649
 
@@ -4976,7 +5141,7 @@ class AuroDropdownBib extends LitElement {
4976
5141
  classes[`shape-${this.shape}`] = true;
4977
5142
 
4978
5143
  return html`
4979
- <dialog tabindex="-1" class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
5144
+ <dialog class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
4980
5145
  ${this.dialogLabel ? html`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
4981
5146
  <slot></slot>
4982
5147
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -5227,7 +5392,7 @@ class AuroHelpText extends LitElement {
5227
5392
  }
5228
5393
  }
5229
5394
 
5230
- var formkitVersion = '202607100029';
5395
+ var formkitVersion = '202607092329';
5231
5396
 
5232
5397
  let AuroElement$1 = class AuroElement extends LitElement {
5233
5398
  static get properties() {
@@ -5981,10 +6146,7 @@ class AuroDropdown extends AuroElement$1 {
5981
6146
  }
5982
6147
 
5983
6148
 
5984
- // On Tab-driven close (eventType "focusloss"), let focus advance naturally
5985
- // — restoring to the trigger would trap the user on this dropdown, forcing
5986
- // an extra Tab to move on. Escape and outside-click still restore.
5987
- if (!this.isPopoverVisible && eventType !== "focusloss") {
6149
+ if (!this.isPopoverVisible) {
5988
6150
  // wait til the bib gets fully closed and rendered
5989
6151
  setTimeout(() => {
5990
6152
  // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
@@ -6214,16 +6376,15 @@ class AuroDropdown extends AuroElement$1 {
6214
6376
  }
6215
6377
  });
6216
6378
  } else {
6217
- // Normal desktop (non-modal): move initial focus into the bib but
6218
- // don't trap Tab. Tab should exit the bib and let the floater's
6219
- // handleFocusLoss close it, matching native <select>/<details>
6220
- // behavior. Deferred one frame because Floating UI positions the
6221
- // popover asynchronously a synchronous focus() would target
6222
- // zero-dimension elements and be silently ignored.
6379
+ // Normal desktop: use FocusTrap on the bib element.
6380
+ // Defer focusFirstElement to the next frame because the popover
6381
+ // is positioned asynchronously by Floating UI — calling focus()
6382
+ // synchronously here would target elements with zero dimensions
6383
+ // and the focus call would be silently ignored.
6384
+ this.focusTrap = new FocusTrap(this.bibContent);
6223
6385
  requestAnimationFrame(() => {
6224
- const focusables = getFocusableElements(this.bibContent);
6225
- if (focusables.length) {
6226
- focusables[0].focus();
6386
+ if (this.focusTrap) {
6387
+ this.focusTrap.focusFirstElement();
6227
6388
  }
6228
6389
  });
6229
6390
  }
@@ -13717,7 +13717,7 @@ class AuroBibtemplate extends i$1 {
13717
13717
  }
13718
13718
  }
13719
13719
 
13720
- var formkitVersion$2 = '202607100029';
13720
+ var formkitVersion$2 = '202607092329';
13721
13721
 
13722
13722
  let l$1 = class l{generateElementName(t,e){let o=t;return o+="-",o+=e.replace(/[.]/g,"_"),o}generateTag(o,s,a){const r=this.generateElementName(o,s),i=i$5`${s$5(r)}`;return customElements.get(r)||customElements.define(r,class extends a{}),i}};let d$1 = class d{registerComponent(t,e){customElements.get(t)||customElements.define(t,class extends e{});}closestElement(t,e=this,o=(e,s=e&&e.closest(t))=>e&&e!==document&&e!==window?s||o(e.getRootNode().host):null){return o(e)}handleComponentTagRename(t,e){const o=e.toLowerCase();t.tagName.toLowerCase()!==o&&t.setAttribute(o,true);}elementMatch(t,e){const o=e.toLowerCase();return t.tagName.toLowerCase()===o||t.hasAttribute(o)}getSlotText(t,e){const o=t.shadowRoot?.querySelector(`slot[name="${e}"]`),s=(o?.assignedNodes({flatten:true})||[]).map(t=>t.textContent?.trim()).join(" ").trim();return s||null}};let h$4 = class h{registerComponent(t,e){customElements.get(t)||customElements.define(t,class extends e{});}closestElement(t,e=this,o=(e,s=e&&e.closest(t))=>e&&e!==document&&e!==window?s||o(e.getRootNode().host):null){return o(e)}handleComponentTagRename(t,e){const o=e.toLowerCase();t.tagName.toLowerCase()!==o&&t.setAttribute(o,true);}elementMatch(t,e){const o=e.toLowerCase();return t.tagName.toLowerCase()===o||t.hasAttribute(o)}};var c$3=i$3`:host{color:var(--ds-auro-loader-color)}:host>span{background-color:var(--ds-auro-loader-background-color);border-color:var(--ds-auro-loader-border-color)}:host([onlight]),:host([appearance=brand]){--ds-auro-loader-color: var(--ds-basic-color-brand-primary, #01426a)}:host([ondark]),:host([appearance=inverse]){--ds-auro-loader-color: var(--ds-basic-color-texticon-inverse, #ffffff)}:host([orbit])>span{--ds-auro-loader-background-color: transparent}:host([orbit])>span:nth-child(1){--ds-auro-loader-border-color: currentcolor;opacity:.25}:host([orbit])>span:nth-child(2){--ds-auro-loader-border-color: currentcolor;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}
13723
13723
  `,u$6=i$3`.body-default{font-size:var(--wcss-body-default-font-size, 1rem);line-height:var(--wcss-body-default-line-height, 1.5rem)}.body-default,.body-lg{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-lg{font-size:var(--wcss-body-lg-font-size, 1.125rem);line-height:var(--wcss-body-lg-line-height, 1.625rem)}.body-sm{font-size:var(--wcss-body-sm-font-size, .875rem);line-height:var(--wcss-body-sm-line-height, 1.25rem)}.body-sm,.body-xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0)}.body-xs{font-size:var(--wcss-body-xs-font-size, .75rem);line-height:var(--wcss-body-xs-line-height, 1rem)}.body-2xs{font-family:var(--wcss-body-family, "AS Circular"),system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:var(--wcss-body-2xs-font-size, .625rem);font-weight:var(--wcss-body-weight, 450);letter-spacing:var(--wcss-body-letter-spacing, 0);line-height:var(--wcss-body-2xs-line-height, .875rem)}.display-2xl{font-family:var(--wcss-display-2xl-family, "AS Circular"),var(--wcss-display-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-2xl-font-size, clamp(3.5rem, 6vw, 5.375rem));font-weight:var(--wcss-display-2xl-weight, 300);letter-spacing:var(--wcss-display-2xl-letter-spacing, 0);line-height:var(--wcss-display-2xl-line-height, 1.3)}.display-xl{font-family:var(--wcss-display-xl-family, "AS Circular"),var(--wcss-display-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xl-font-size, clamp(3rem, 5.3333333333vw, 4.5rem));font-weight:var(--wcss-display-xl-weight, 300);letter-spacing:var(--wcss-display-xl-letter-spacing, 0);line-height:var(--wcss-display-xl-line-height, 1.3)}.display-lg{font-family:var(--wcss-display-lg-family, "AS Circular"),var(--wcss-display-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-lg-font-size, clamp(2.75rem, 4.6666666667vw, 4rem));font-weight:var(--wcss-display-lg-weight, 300);letter-spacing:var(--wcss-display-lg-letter-spacing, 0);line-height:var(--wcss-display-lg-line-height, 1.3)}.display-md{font-family:var(--wcss-display-md-family, "AS Circular"),var(--wcss-display-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-md-font-size, clamp(2.5rem, 4vw, 3.5rem));font-weight:var(--wcss-display-md-weight, 300);letter-spacing:var(--wcss-display-md-letter-spacing, 0);line-height:var(--wcss-display-md-line-height, 1.3)}.display-sm{font-family:var(--wcss-display-sm-family, "AS Circular"),var(--wcss-display-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-sm-font-size, clamp(2rem, 3.6666666667vw, 3rem));font-weight:var(--wcss-display-sm-weight, 300);letter-spacing:var(--wcss-display-sm-letter-spacing, 0);line-height:var(--wcss-display-sm-line-height, 1.3)}.display-xs{font-family:var(--wcss-display-xs-family, "AS Circular"),var(--wcss-display-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-display-xs-font-size, clamp(1.75rem, 3vw, 2.375rem));font-weight:var(--wcss-display-xs-weight, 300);letter-spacing:var(--wcss-display-xs-letter-spacing, 0);line-height:var(--wcss-display-xs-line-height, 1.3)}.heading-xl{font-family:var(--wcss-heading-xl-family, "AS Circular"),var(--wcss-heading-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xl-font-size, clamp(2rem, 3vw, 2.5rem));font-weight:var(--wcss-heading-xl-weight, 300);letter-spacing:var(--wcss-heading-xl-letter-spacing, 0);line-height:var(--wcss-heading-xl-line-height, 1.3)}.heading-lg{font-family:var(--wcss-heading-lg-family, "AS Circular"),var(--wcss-heading-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-lg-font-size, clamp(1.75rem, 2.6666666667vw, 2.25rem));font-weight:var(--wcss-heading-lg-weight, 300);letter-spacing:var(--wcss-heading-lg-letter-spacing, 0);line-height:var(--wcss-heading-lg-line-height, 1.3)}.heading-md{font-family:var(--wcss-heading-md-family, "AS Circular"),var(--wcss-heading-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-md-font-size, clamp(1.625rem, 2.3333333333vw, 1.75rem));font-weight:var(--wcss-heading-md-weight, 300);letter-spacing:var(--wcss-heading-md-letter-spacing, 0);line-height:var(--wcss-heading-md-line-height, 1.3)}.heading-sm{font-family:var(--wcss-heading-sm-family, "AS Circular"),var(--wcss-heading-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-sm-font-size, clamp(1.375rem, 2vw, 1.5rem));font-weight:var(--wcss-heading-sm-weight, 300);letter-spacing:var(--wcss-heading-sm-letter-spacing, 0);line-height:var(--wcss-heading-sm-line-height, 1.3)}.heading-xs{font-family:var(--wcss-heading-xs-family, "AS Circular"),var(--wcss-heading-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-xs-font-size, clamp(1.25rem, 1.6666666667vw, 1.25rem));font-weight:var(--wcss-heading-xs-weight, 450);letter-spacing:var(--wcss-heading-xs-letter-spacing, 0);line-height:var(--wcss-heading-xs-line-height, 1.3)}.heading-2xs{font-family:var(--wcss-heading-2xs-family, "AS Circular"),var(--wcss-heading-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-heading-2xs-font-size, clamp(1.125rem, 1.5vw, 1.125rem));font-weight:var(--wcss-heading-2xs-weight, 450);letter-spacing:var(--wcss-heading-2xs-letter-spacing, 0);line-height:var(--wcss-heading-2xs-line-height, 1.3)}.accent-2xl{font-family:var(--wcss-accent-2xl-family, "Good OT"),var(--wcss-accent-2xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xl-font-size, clamp(2rem, 3.1666666667vw, 2.375rem));font-weight:var(--wcss-accent-2xl-weight, 450);letter-spacing:var(--wcss-accent-2xl-letter-spacing, .05em);line-height:var(--wcss-accent-2xl-line-height, 1)}.accent-2xl,.accent-xl{text-transform:uppercase}.accent-xl{font-family:var(--wcss-accent-xl-family, "Good OT"),var(--wcss-accent-xl-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xl-font-size, clamp(1.625rem, 2.3333333333vw, 2rem));font-weight:var(--wcss-accent-xl-weight, 450);letter-spacing:var(--wcss-accent-xl-letter-spacing, .05em);line-height:var(--wcss-accent-xl-line-height, 1.3)}.accent-lg{font-family:var(--wcss-accent-lg-family, "Good OT"),var(--wcss-accent-lg-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-lg-font-size, clamp(1.5rem, 2.1666666667vw, 1.75rem));font-weight:var(--wcss-accent-lg-weight, 450);letter-spacing:var(--wcss-accent-lg-letter-spacing, .05em);line-height:var(--wcss-accent-lg-line-height, 1.3)}.accent-lg,.accent-md{text-transform:uppercase}.accent-md{font-family:var(--wcss-accent-md-family, "Good OT"),var(--wcss-accent-md-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-md-font-size, clamp(1.375rem, 1.8333333333vw, 1.5rem));font-weight:var(--wcss-accent-md-weight, 500);letter-spacing:var(--wcss-accent-md-letter-spacing, .05em);line-height:var(--wcss-accent-md-line-height, 1.3)}.accent-sm{font-family:var(--wcss-accent-sm-family, "Good OT"),var(--wcss-accent-sm-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-sm-font-size, clamp(1.125rem, 1.5vw, 1.25rem));font-weight:var(--wcss-accent-sm-weight, 500);letter-spacing:var(--wcss-accent-sm-letter-spacing, .05em);line-height:var(--wcss-accent-sm-line-height, 1.3)}.accent-sm,.accent-xs{text-transform:uppercase}.accent-xs{font-family:var(--wcss-accent-xs-family, "Good OT"),var(--wcss-accent-xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-xs-font-size, clamp(1rem, 1.3333333333vw, 1rem));font-weight:var(--wcss-accent-xs-weight, 500);letter-spacing:var(--wcss-accent-xs-letter-spacing, .1em);line-height:var(--wcss-accent-xs-line-height, 1.3)}.accent-2xs{font-family:var(--wcss-accent-2xs-family, "Good OT"),var(--wcss-accent-2xs-family-fallback, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);font-size:var(--wcss-accent-2xs-font-size, clamp(.875rem, 1.1666666667vw, .875rem));font-weight:var(--wcss-accent-2xs-weight, 450);letter-spacing:var(--wcss-accent-2xs-letter-spacing, .1em);line-height:var(--wcss-accent-2xs-line-height, 1.3);text-transform:uppercase}:focus:not(:focus-visible){outline:3px solid transparent}:host,:host>span{position:relative}:host{width:2rem;height:2rem;display:inline-block;font-size:0}:host>span{position:absolute;display:inline-block;float:none;top:0;left:0;width:2rem;height:2rem;border-radius:100%;border-style:solid;border-width:0;box-sizing:border-box}:host([xs]),:host([xs])>span{width:1.2rem;height:1.2rem}:host([sm]),:host([sm])>span{width:3rem;height:3rem}:host([md]),:host([md])>span{width:5rem;height:5rem}:host([lg]),:host([lg])>span{width:8rem;height:8rem}:host{--margin: .375rem;--margin-xs: .2rem;--margin-sm: .5rem;--margin-md: .75rem;--margin-lg: 1rem}:host([pulse]),:host([pulse])>span{position:relative}:host([pulse]){width:calc(3rem + var(--margin) * 6);height:calc(1rem + var(--margin) * 2)}:host([pulse])>span{width:1rem;height:1rem;margin:var(--margin);animation:pulse 1.5s ease infinite}:host([pulse][xs]){width:calc(1.95rem + var(--margin-xs) * 6);height:calc(.65rem + var(--margin-xs) * 2)}:host([pulse][xs])>span{margin:var(--margin-xs);width:.65rem;height:.65rem}:host([pulse][sm]){width:calc(6rem + var(--margin-sm) * 6);height:calc(2rem + var(--margin-sm) * 2)}:host([pulse][sm])>span{margin:var(--margin-sm);width:2rem;height:2rem}:host([pulse][md]){width:calc(9rem + var(--margin-md) * 6);height:calc(3rem + var(--margin-md) * 2)}:host([pulse][md])>span{margin:var(--margin-md);width:3rem;height:3rem}:host([pulse][lg]){width:calc(15rem + var(--margin-lg) * 6);height:calc(5rem + var(--margin-lg) * 2)}:host([pulse][lg])>span{margin:var(--margin-lg);width:5rem;height:5rem}:host([pulse])>span:nth-child(1){animation-delay:-.4s}:host([pulse])>span:nth-child(2){animation-delay:-.2s}:host([pulse])>span:nth-child(3){animation-delay:0ms}@keyframes pulse{0%,to{opacity:.1;transform:scale(.9)}50%{opacity:1;transform:scale(1.1)}}:host([orbit]),:host([orbit])>span{opacity:1}:host([orbit])>span{border-width:5px}:host([orbit])>span:nth-child(2){animation:orbit 2s linear infinite}:host([orbit][sm])>span{border-width:8px}:host([orbit][md])>span{border-width:13px}:host([orbit][lg])>span{border-width:21px}@keyframes orbit{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:host([ringworm])>svg{animation:rotate 2s linear infinite;height:100%;width:100%;stroke:currentcolor;stroke-width:8}:host([ringworm]) .path{stroke-dashoffset:0;animation:ringworm 1.5s ease-in-out infinite;stroke-linecap:round}@keyframes rotate{to{transform:rotate(360deg)}}@keyframes ringworm{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}to{stroke-dasharray:89,200;stroke-dashoffset:-124px}}:host([laser]){position:static;width:100%;display:block;height:0;overflow:hidden;font-size:unset}:host([laser])>span{position:fixed;width:100%;height:.25rem;border-radius:0;z-index:100}:host([laser])>span:nth-child(1){border-color:currentcolor;opacity:.25}:host([laser])>span:nth-child(2){border-color:currentcolor;animation:laser 2s linear infinite;opacity:1;width:50%}:host([laser][sm])>span:nth-child(2){width:20%}:host([laser][md])>span:nth-child(2){width:30%}:host([laser][lg])>span:nth-child(2){width:50%;animation-duration:1.5s}:host([laser][xl])>span:nth-child(2){width:80%;animation-duration:1.5s}@keyframes laser{0%{left:-100%}to{left:110%}}:host>.no-animation{display:none}@media (prefers-reduced-motion: reduce){:host{display:flex;align-items:center;justify-content:center}:host>span{opacity:1}:host>.loader{display:none}:host>svg{display:none}:host>.no-animation{display:block}}
@@ -18018,40 +18018,12 @@ class AuroFloatingUI {
18018
18018
  return;
18019
18019
  }
18020
18020
 
18021
- // Chrome-specific: during popover top-layer promotion after a click on a
18022
- // slotted focusable, :focus-within can briefly evaluate false while the
18023
- // active element is still structurally inside the trigger/bib. Fall back
18024
- // to a shadow-piercing ancestry walk from the deep active element before
18025
- // treating this as a real focus loss.
18026
- try {
18027
- let active = document.activeElement;
18028
- while (active && active.shadowRoot && active.shadowRoot.activeElement) {
18029
- active = active.shadowRoot.activeElement;
18030
- }
18031
- const targets = [element, element.trigger, element.bib].filter(Boolean);
18032
- let node = active;
18033
- while (node) {
18034
- if (targets.includes(node)) {
18035
- return;
18036
- }
18037
- node =
18038
- node.parentElement ||
18039
- (node.getRootNode && node.getRootNode().host) ||
18040
- null;
18041
- }
18042
- } catch (e) {
18043
- // Defensive: fall through to the existing close path if traversal fails.
18044
- }
18045
-
18046
18021
  // if fullscreen bib is in fullscreen mode, do not close
18047
18022
  if (element.bib.hasAttribute("isfullscreen")) {
18048
18023
  return;
18049
18024
  }
18050
18025
 
18051
- // eventType "focusloss" distinguishes a Tab/click-driven close from an
18052
- // Escape keydown close. Consumers use this to decide whether to restore
18053
- // focus to the trigger (Escape) or let it advance naturally (Tab).
18054
- this.hideBib("focusloss");
18026
+ this.hideBib("keydown");
18055
18027
  }
18056
18028
 
18057
18029
  setupHideHandlers() {
@@ -18721,6 +18693,199 @@ function getFocusableElements(container) {
18721
18693
  return tabIndexedUniqueElements;
18722
18694
  }
18723
18695
 
18696
+ /**
18697
+ * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
18698
+ * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
18699
+ */
18700
+ class FocusTrap {
18701
+ /**
18702
+ * Creates a new FocusTrap instance for the given container element.
18703
+ * Initializes event listeners and prepares the container for focus management.
18704
+ *
18705
+ * @param {HTMLElement} container The DOM element to trap focus within.
18706
+ * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
18707
+ * @throws {Error} If the provided container is not a valid HTMLElement.
18708
+ */
18709
+ constructor(container, controlTabOrder = false) {
18710
+ if (!container || !(container instanceof HTMLElement)) {
18711
+ throw new Error("FocusTrap requires a valid HTMLElement.");
18712
+ }
18713
+
18714
+ this.container = container;
18715
+ this.tabDirection = "forward"; // or 'backward';
18716
+ this.controlTabOrder = controlTabOrder;
18717
+
18718
+ this._init();
18719
+ }
18720
+
18721
+ /**
18722
+ * Initializes the focus trap by setting up event listeners and attributes on the container.
18723
+ * Prepares the container for focus management, including support for shadow DOM and inert attributes.
18724
+ *
18725
+ * @private
18726
+ */
18727
+ _init() {
18728
+ // Add inert attribute to prevent focusing programmatically as well (if supported)
18729
+ if ("inert" in HTMLElement.prototype) {
18730
+ this.container.inert = false; // Ensure the container isn't inert
18731
+ this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
18732
+ }
18733
+
18734
+ // Track tab direction
18735
+ this.container.addEventListener("keydown", this._onKeydown);
18736
+ }
18737
+
18738
+ /**
18739
+ * Gets an array of currently active (focused) elements in the document and shadow DOM.
18740
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
18741
+ * @private
18742
+ */
18743
+ _getActiveElements() {
18744
+ // Get the active element(s) in the document and shadow root
18745
+ // This will include the active element in the shadow DOM if it exists
18746
+ // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
18747
+ let { activeElement } = document;
18748
+ const actives = [activeElement];
18749
+ while (activeElement?.shadowRoot?.activeElement) {
18750
+ actives.push(activeElement.shadowRoot.activeElement);
18751
+ activeElement = activeElement.shadowRoot.activeElement;
18752
+ }
18753
+ return actives;
18754
+ }
18755
+
18756
+ /**
18757
+ * Gets the next focus index based on the current index and focusable elements.
18758
+ * @param {number} currentIndex The current index of the focused element.
18759
+ * @param {Array<HTMLElement>} focusables The array of focusable elements.
18760
+ * @returns {number|null} The next focus index or null if not determined.
18761
+ */
18762
+ _getNextFocusIndex(currentIndex, focusables, actives) {
18763
+ if (this.controlTabOrder) {
18764
+ // Calculate the new index based on the current index and tab direction
18765
+ let newFocusIndex =
18766
+ currentIndex + (this.tabDirection === "forward" ? 1 : -1);
18767
+
18768
+ // Wrap-around logic
18769
+ if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
18770
+ if (newFocusIndex >= focusables.length) newFocusIndex = 0;
18771
+
18772
+ // Early return with the new index
18773
+ return newFocusIndex;
18774
+ }
18775
+
18776
+ // Determine if we need to wrap
18777
+ const atFirst =
18778
+ actives.includes(focusables[0]) || actives.includes(this.container);
18779
+ const atLast = actives.includes(focusables[focusables.length - 1]);
18780
+
18781
+ // Only wrap if at the ends
18782
+ if (this.tabDirection === "backward" && atFirst) {
18783
+ return focusables.length - 1;
18784
+ }
18785
+
18786
+ if (this.tabDirection === "forward" && atLast) {
18787
+ return 0;
18788
+ }
18789
+
18790
+ // No wrap, so don't change focus, return early
18791
+ return null;
18792
+ }
18793
+
18794
+ /**
18795
+ * Handles the Tab key press event to manage focus within the container.
18796
+ * @param {KeyboardEvent} e The keyboard event triggered by the user.
18797
+ * @returns {void}
18798
+ */
18799
+ _handleTabKey(e) {
18800
+ // Update the focusable elements
18801
+ const focusables = this._getFocusableElements();
18802
+
18803
+ // If there are no focusable elements, exit
18804
+ if (!focusables.length) return;
18805
+
18806
+ // Set the tab direction based on the key pressed
18807
+ this.tabDirection = e.shiftKey ? "backward" : "forward";
18808
+
18809
+ // Get the active elements that are currently focused
18810
+ const actives = this._getActiveElements();
18811
+
18812
+ // If we're at either end of the focusable elements, wrap around to the other end
18813
+ let focusIndex = focusables.findIndex((el) => actives.includes(el));
18814
+
18815
+ // Fallback if we have no focused element
18816
+ if (focusIndex === -1) focusIndex = 0;
18817
+
18818
+ // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
18819
+ // Is null if no new focus index is determined
18820
+ const newFocusIndex = this._getNextFocusIndex(
18821
+ focusIndex,
18822
+ focusables,
18823
+ actives,
18824
+ );
18825
+
18826
+ // If we have a new focus index, set focus to that element
18827
+ if (newFocusIndex !== null) {
18828
+ e.preventDefault();
18829
+ focusables[newFocusIndex].focus();
18830
+ }
18831
+ }
18832
+
18833
+ /**
18834
+ * Catches the keydown event
18835
+ * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
18836
+ * @private
18837
+ */
18838
+ _onKeydown = (e) => {
18839
+ // Handle tab
18840
+ if (e.key === "Tab") this._handleTabKey(e);
18841
+ };
18842
+
18843
+ /**
18844
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
18845
+ * Returns a unique, ordered array of elements that can receive focus.
18846
+ *
18847
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
18848
+ * @private
18849
+ */
18850
+ _getFocusableElements() {
18851
+ // Use the imported utility function to get focusable elements
18852
+ const elements = getFocusableElements(this.container);
18853
+
18854
+ // Return the elements found
18855
+ return elements;
18856
+ }
18857
+
18858
+ /**
18859
+ * Moves focus to the first focusable element within the container.
18860
+ * Useful for setting initial focus when activating the focus trap.
18861
+ */
18862
+ focusFirstElement() {
18863
+ const focusables = this._getFocusableElements();
18864
+ if (focusables.length) focusables[0].focus();
18865
+ }
18866
+
18867
+ /**
18868
+ * Moves focus to the last focusable element within the container.
18869
+ * Useful for setting focus when deactivating or cycling focus in reverse.
18870
+ */
18871
+ focusLastElement() {
18872
+ const focusables = this._getFocusableElements();
18873
+ if (focusables.length) focusables[focusables.length - 1].focus();
18874
+ }
18875
+
18876
+ /**
18877
+ * Removes event listeners and attributes added by the focus trap.
18878
+ * Call this method to clean up when the focus trap is no longer needed.
18879
+ */
18880
+ disconnect() {
18881
+ if (this.container.hasAttribute("data-focus-trap-container")) {
18882
+ this.container.removeAttribute("data-focus-trap-container");
18883
+ }
18884
+
18885
+ this.container.removeEventListener("keydown", this._onKeydown);
18886
+ }
18887
+ }
18888
+
18724
18889
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
18725
18890
  // See LICENSE in the project root for license information.
18726
18891
 
@@ -19218,7 +19383,7 @@ class AuroDropdownBib extends i$1 {
19218
19383
  classes[`shape-${this.shape}`] = true;
19219
19384
 
19220
19385
  return u$a`
19221
- <dialog tabindex="-1" class="${e$4(classes)}" part="bibContainer" role="${o$2(this.dialogRole)}" aria-labelledby="${o$2(this.dialogLabel ? 'dialogLabel' : undefined)}">
19386
+ <dialog class="${e$4(classes)}" part="bibContainer" role="${o$2(this.dialogRole)}" aria-labelledby="${o$2(this.dialogLabel ? 'dialogLabel' : undefined)}">
19222
19387
  ${this.dialogLabel ? u$a`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
19223
19388
  <slot></slot>
19224
19389
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -19469,7 +19634,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$1 {
19469
19634
  }
19470
19635
  };
19471
19636
 
19472
- var formkitVersion$1 = '202607100029';
19637
+ var formkitVersion$1 = '202607092329';
19473
19638
 
19474
19639
  let AuroElement$2 = class AuroElement extends i$1 {
19475
19640
  static get properties() {
@@ -20223,10 +20388,7 @@ class AuroDropdown extends AuroElement$2 {
20223
20388
  }
20224
20389
 
20225
20390
 
20226
- // On Tab-driven close (eventType "focusloss"), let focus advance naturally
20227
- // — restoring to the trigger would trap the user on this dropdown, forcing
20228
- // an extra Tab to move on. Escape and outside-click still restore.
20229
- if (!this.isPopoverVisible && eventType !== "focusloss") {
20391
+ if (!this.isPopoverVisible) {
20230
20392
  // wait til the bib gets fully closed and rendered
20231
20393
  setTimeout(() => {
20232
20394
  // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
@@ -20456,16 +20618,15 @@ class AuroDropdown extends AuroElement$2 {
20456
20618
  }
20457
20619
  });
20458
20620
  } else {
20459
- // Normal desktop (non-modal): move initial focus into the bib but
20460
- // don't trap Tab. Tab should exit the bib and let the floater's
20461
- // handleFocusLoss close it, matching native <select>/<details>
20462
- // behavior. Deferred one frame because Floating UI positions the
20463
- // popover asynchronously a synchronous focus() would target
20464
- // zero-dimension elements and be silently ignored.
20621
+ // Normal desktop: use FocusTrap on the bib element.
20622
+ // Defer focusFirstElement to the next frame because the popover
20623
+ // is positioned asynchronously by Floating UI — calling focus()
20624
+ // synchronously here would target elements with zero dimensions
20625
+ // and the focus call would be silently ignored.
20626
+ this.focusTrap = new FocusTrap(this.bibContent);
20465
20627
  requestAnimationFrame(() => {
20466
- const focusables = getFocusableElements(this.bibContent);
20467
- if (focusables.length) {
20468
- focusables[0].focus();
20628
+ if (this.focusTrap) {
20629
+ this.focusTrap.focusFirstElement();
20469
20630
  }
20470
20631
  });
20471
20632
  }
@@ -33098,7 +33259,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$1 {
33098
33259
  }
33099
33260
  };
33100
33261
 
33101
- var formkitVersion = '202607100029';
33262
+ var formkitVersion = '202607092329';
33102
33263
 
33103
33264
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
33104
33265
  // See LICENSE in the project root for license information.