@aurodesignsystem-dev/auro-formkit 0.0.0-pr1540.0 → 0.0.0-pr1540.2

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 (82) hide show
  1. package/components/checkbox/demo/accessibility.md +1 -1
  2. package/components/checkbox/demo/customize.min.js +1 -1
  3. package/components/checkbox/demo/getting-started.min.js +1 -1
  4. package/components/checkbox/demo/index.min.js +1 -1
  5. package/components/checkbox/dist/index.js +1 -1
  6. package/components/checkbox/dist/registered.js +1 -1
  7. package/components/combobox/demo/accessibility.md +1 -1
  8. package/components/combobox/demo/api.md +2 -2
  9. package/components/combobox/demo/customize.md +54 -2
  10. package/components/combobox/demo/customize.min.js +421 -240
  11. package/components/combobox/demo/getting-started.min.js +421 -240
  12. package/components/combobox/demo/index.md +1 -0
  13. package/components/combobox/demo/index.min.js +421 -240
  14. package/components/combobox/demo/keyboard-behavior.md +1 -4
  15. package/components/combobox/dist/auro-combobox.d.ts +4 -1
  16. package/components/combobox/dist/index.js +334 -222
  17. package/components/combobox/dist/registered.js +334 -222
  18. package/components/counter/demo/customize.min.js +59 -222
  19. package/components/counter/demo/index.min.js +59 -222
  20. package/components/counter/dist/auro-counter.d.ts +0 -8
  21. package/components/counter/dist/index.js +59 -222
  22. package/components/counter/dist/registered.js +59 -222
  23. package/components/datepicker/demo/accessibility.md +1 -1
  24. package/components/datepicker/demo/customize.md +30 -1
  25. package/components/datepicker/demo/customize.min.js +84 -220
  26. package/components/datepicker/demo/getting-started.md +118 -2
  27. package/components/datepicker/demo/index.min.js +84 -220
  28. package/components/datepicker/dist/index.js +84 -220
  29. package/components/datepicker/dist/registered.js +84 -220
  30. package/components/dropdown/demo/customize.html +3 -0
  31. package/components/dropdown/demo/customize.min.js +55 -206
  32. package/components/dropdown/demo/getting-started.min.js +44 -205
  33. package/components/dropdown/demo/index.min.js +44 -205
  34. package/components/dropdown/dist/index.js +44 -205
  35. package/components/dropdown/dist/registered.js +44 -205
  36. package/components/form/demo/customize.min.js +670 -945
  37. package/components/form/demo/getting-started.min.js +670 -945
  38. package/components/form/demo/index.min.js +670 -945
  39. package/components/form/demo/registerDemoDeps.min.js +670 -945
  40. package/components/input/demo/accessibility.md +1 -1
  41. package/components/input/demo/customize.min.js +1 -1
  42. package/components/input/demo/getting-started.min.js +1 -1
  43. package/components/input/demo/index.min.js +1 -1
  44. package/components/input/dist/index.js +1 -1
  45. package/components/input/dist/registered.js +1 -1
  46. package/components/menu/demo/accessibility.md +9 -4
  47. package/components/menu/demo/api.md +26 -19
  48. package/components/menu/demo/css-only.md +26 -19
  49. package/components/menu/demo/customize.md +307 -47
  50. package/components/menu/demo/design.md +1 -1
  51. package/components/menu/demo/getting-started.md +144 -7
  52. package/components/menu/demo/index.min.js +214 -19
  53. package/components/menu/demo/keyboard-behavior.md +83 -4
  54. package/components/menu/demo/voiceover.md +21 -14
  55. package/components/menu/demo/why-menu.md +8 -9
  56. package/components/menu/dist/auro-menu-utils.d.ts +19 -0
  57. package/components/menu/dist/auro-menu.d.ts +4 -8
  58. package/components/menu/dist/auro-menuoption.d.ts +12 -0
  59. package/components/menu/dist/index.js +87 -18
  60. package/components/menu/dist/registered.js +87 -18
  61. package/components/radio/demo/accessibility.md +1 -1
  62. package/components/radio/demo/customize.min.js +1 -1
  63. package/components/radio/demo/getting-started.min.js +1 -1
  64. package/components/radio/demo/index.min.js +1 -1
  65. package/components/radio/dist/index.js +1 -1
  66. package/components/radio/dist/registered.js +1 -1
  67. package/components/select/demo/accessibility.md +6 -1
  68. package/components/select/demo/api.md +2 -2
  69. package/components/select/demo/customize.md +6 -3
  70. package/components/select/demo/customize.min.js +139 -227
  71. package/components/select/demo/design.md +10 -10
  72. package/components/select/demo/getting-started.md +1 -1
  73. package/components/select/demo/getting-started.min.js +139 -227
  74. package/components/select/demo/index.md +2 -2
  75. package/components/select/demo/index.min.js +139 -227
  76. package/components/select/demo/keyboard-behavior.md +53 -58
  77. package/components/select/demo/voiceover.md +28 -15
  78. package/components/select/dist/auro-select.d.ts +6 -2
  79. package/components/select/dist/index.js +52 -209
  80. package/components/select/dist/registered.js +52 -209
  81. package/custom-elements.json +1611 -1552
  82. package/package.json +2 -2
@@ -2295,12 +2295,40 @@ class AuroFloatingUI {
2295
2295
  return;
2296
2296
  }
2297
2297
 
2298
+ // Chrome-specific: during popover top-layer promotion after a click on a
2299
+ // slotted focusable, :focus-within can briefly evaluate false while the
2300
+ // active element is still structurally inside the trigger/bib. Fall back
2301
+ // to a shadow-piercing ancestry walk from the deep active element before
2302
+ // treating this as a real focus loss.
2303
+ try {
2304
+ let active = document.activeElement;
2305
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) {
2306
+ active = active.shadowRoot.activeElement;
2307
+ }
2308
+ const targets = [element, element.trigger, element.bib].filter(Boolean);
2309
+ let node = active;
2310
+ while (node) {
2311
+ if (targets.includes(node)) {
2312
+ return;
2313
+ }
2314
+ node =
2315
+ node.parentElement ||
2316
+ (node.getRootNode && node.getRootNode().host) ||
2317
+ null;
2318
+ }
2319
+ } catch (e) {
2320
+ // Defensive: fall through to the existing close path if traversal fails.
2321
+ }
2322
+
2298
2323
  // if fullscreen bib is in fullscreen mode, do not close
2299
2324
  if (element.bib.hasAttribute("isfullscreen")) {
2300
2325
  return;
2301
2326
  }
2302
2327
 
2303
- this.hideBib("keydown");
2328
+ // eventType "focusloss" distinguishes a Tab/click-driven close from an
2329
+ // Escape keydown close. Consumers use this to decide whether to restore
2330
+ // focus to the trigger (Escape) or let it advance naturally (Tab).
2331
+ this.hideBib("focusloss");
2304
2332
  }
2305
2333
 
2306
2334
  setupHideHandlers() {
@@ -2970,199 +2998,6 @@ function getFocusableElements(container) {
2970
2998
  return tabIndexedUniqueElements;
2971
2999
  }
2972
3000
 
2973
- /**
2974
- * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
2975
- * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
2976
- */
2977
- class FocusTrap {
2978
- /**
2979
- * Creates a new FocusTrap instance for the given container element.
2980
- * Initializes event listeners and prepares the container for focus management.
2981
- *
2982
- * @param {HTMLElement} container The DOM element to trap focus within.
2983
- * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
2984
- * @throws {Error} If the provided container is not a valid HTMLElement.
2985
- */
2986
- constructor(container, controlTabOrder = false) {
2987
- if (!container || !(container instanceof HTMLElement)) {
2988
- throw new Error("FocusTrap requires a valid HTMLElement.");
2989
- }
2990
-
2991
- this.container = container;
2992
- this.tabDirection = "forward"; // or 'backward';
2993
- this.controlTabOrder = controlTabOrder;
2994
-
2995
- this._init();
2996
- }
2997
-
2998
- /**
2999
- * Initializes the focus trap by setting up event listeners and attributes on the container.
3000
- * Prepares the container for focus management, including support for shadow DOM and inert attributes.
3001
- *
3002
- * @private
3003
- */
3004
- _init() {
3005
- // Add inert attribute to prevent focusing programmatically as well (if supported)
3006
- if ("inert" in HTMLElement.prototype) {
3007
- this.container.inert = false; // Ensure the container isn't inert
3008
- this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
3009
- }
3010
-
3011
- // Track tab direction
3012
- this.container.addEventListener("keydown", this._onKeydown);
3013
- }
3014
-
3015
- /**
3016
- * Gets an array of currently active (focused) elements in the document and shadow DOM.
3017
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3018
- * @private
3019
- */
3020
- _getActiveElements() {
3021
- // Get the active element(s) in the document and shadow root
3022
- // This will include the active element in the shadow DOM if it exists
3023
- // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
3024
- let { activeElement } = document;
3025
- const actives = [activeElement];
3026
- while (activeElement?.shadowRoot?.activeElement) {
3027
- actives.push(activeElement.shadowRoot.activeElement);
3028
- activeElement = activeElement.shadowRoot.activeElement;
3029
- }
3030
- return actives;
3031
- }
3032
-
3033
- /**
3034
- * Gets the next focus index based on the current index and focusable elements.
3035
- * @param {number} currentIndex The current index of the focused element.
3036
- * @param {Array<HTMLElement>} focusables The array of focusable elements.
3037
- * @returns {number|null} The next focus index or null if not determined.
3038
- */
3039
- _getNextFocusIndex(currentIndex, focusables, actives) {
3040
- if (this.controlTabOrder) {
3041
- // Calculate the new index based on the current index and tab direction
3042
- let newFocusIndex =
3043
- currentIndex + (this.tabDirection === "forward" ? 1 : -1);
3044
-
3045
- // Wrap-around logic
3046
- if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
3047
- if (newFocusIndex >= focusables.length) newFocusIndex = 0;
3048
-
3049
- // Early return with the new index
3050
- return newFocusIndex;
3051
- }
3052
-
3053
- // Determine if we need to wrap
3054
- const atFirst =
3055
- actives.includes(focusables[0]) || actives.includes(this.container);
3056
- const atLast = actives.includes(focusables[focusables.length - 1]);
3057
-
3058
- // Only wrap if at the ends
3059
- if (this.tabDirection === "backward" && atFirst) {
3060
- return focusables.length - 1;
3061
- }
3062
-
3063
- if (this.tabDirection === "forward" && atLast) {
3064
- return 0;
3065
- }
3066
-
3067
- // No wrap, so don't change focus, return early
3068
- return null;
3069
- }
3070
-
3071
- /**
3072
- * Handles the Tab key press event to manage focus within the container.
3073
- * @param {KeyboardEvent} e The keyboard event triggered by the user.
3074
- * @returns {void}
3075
- */
3076
- _handleTabKey(e) {
3077
- // Update the focusable elements
3078
- const focusables = this._getFocusableElements();
3079
-
3080
- // If there are no focusable elements, exit
3081
- if (!focusables.length) return;
3082
-
3083
- // Set the tab direction based on the key pressed
3084
- this.tabDirection = e.shiftKey ? "backward" : "forward";
3085
-
3086
- // Get the active elements that are currently focused
3087
- const actives = this._getActiveElements();
3088
-
3089
- // If we're at either end of the focusable elements, wrap around to the other end
3090
- let focusIndex = focusables.findIndex((el) => actives.includes(el));
3091
-
3092
- // Fallback if we have no focused element
3093
- if (focusIndex === -1) focusIndex = 0;
3094
-
3095
- // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
3096
- // Is null if no new focus index is determined
3097
- const newFocusIndex = this._getNextFocusIndex(
3098
- focusIndex,
3099
- focusables,
3100
- actives,
3101
- );
3102
-
3103
- // If we have a new focus index, set focus to that element
3104
- if (newFocusIndex !== null) {
3105
- e.preventDefault();
3106
- focusables[newFocusIndex].focus();
3107
- }
3108
- }
3109
-
3110
- /**
3111
- * Catches the keydown event
3112
- * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
3113
- * @private
3114
- */
3115
- _onKeydown = (e) => {
3116
- // Handle tab
3117
- if (e.key === "Tab") this._handleTabKey(e);
3118
- };
3119
-
3120
- /**
3121
- * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
3122
- * Returns a unique, ordered array of elements that can receive focus.
3123
- *
3124
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3125
- * @private
3126
- */
3127
- _getFocusableElements() {
3128
- // Use the imported utility function to get focusable elements
3129
- const elements = getFocusableElements(this.container);
3130
-
3131
- // Return the elements found
3132
- return elements;
3133
- }
3134
-
3135
- /**
3136
- * Moves focus to the first focusable element within the container.
3137
- * Useful for setting initial focus when activating the focus trap.
3138
- */
3139
- focusFirstElement() {
3140
- const focusables = this._getFocusableElements();
3141
- if (focusables.length) focusables[0].focus();
3142
- }
3143
-
3144
- /**
3145
- * Moves focus to the last focusable element within the container.
3146
- * Useful for setting focus when deactivating or cycling focus in reverse.
3147
- */
3148
- focusLastElement() {
3149
- const focusables = this._getFocusableElements();
3150
- if (focusables.length) focusables[focusables.length - 1].focus();
3151
- }
3152
-
3153
- /**
3154
- * Removes event listeners and attributes added by the focus trap.
3155
- * Call this method to clean up when the focus trap is no longer needed.
3156
- */
3157
- disconnect() {
3158
- if (this.container.hasAttribute("data-focus-trap-container")) {
3159
- this.container.removeAttribute("data-focus-trap-container");
3160
- }
3161
-
3162
- this.container.removeEventListener("keydown", this._onKeydown);
3163
- }
3164
- }
3165
-
3166
3001
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
3167
3002
  // See LICENSE in the project root for license information.
3168
3003
 
@@ -3691,7 +3526,7 @@ class AuroDropdownBib extends i {
3691
3526
  classes[`shape-${this.shape}`] = true;
3692
3527
 
3693
3528
  return u$2`
3694
- <dialog class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3529
+ <dialog tabindex="-1" class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3695
3530
  ${this.dialogLabel ? u$2`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
3696
3531
  <slot></slot>
3697
3532
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -3942,7 +3777,7 @@ class AuroHelpText extends i {
3942
3777
  }
3943
3778
  }
3944
3779
 
3945
- var formkitVersion = '202607072023';
3780
+ var formkitVersion = '202607102214';
3946
3781
 
3947
3782
  class AuroElement extends i {
3948
3783
  static get properties() {
@@ -4696,7 +4531,10 @@ class AuroDropdown extends AuroElement {
4696
4531
  }
4697
4532
 
4698
4533
 
4699
- if (!this.isPopoverVisible) {
4534
+ // On Tab-driven close (eventType "focusloss"), let focus advance naturally
4535
+ // — restoring to the trigger would trap the user on this dropdown, forcing
4536
+ // an extra Tab to move on. Escape and outside-click still restore.
4537
+ if (!this.isPopoverVisible && eventType !== "focusloss") {
4700
4538
  // wait til the bib gets fully closed and rendered
4701
4539
  setTimeout(() => {
4702
4540
  // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
@@ -4926,15 +4764,16 @@ class AuroDropdown extends AuroElement {
4926
4764
  }
4927
4765
  });
4928
4766
  } else {
4929
- // Normal desktop: use FocusTrap on the bib element.
4930
- // Defer focusFirstElement to the next frame because the popover
4931
- // is positioned asynchronously by Floating UI — calling focus()
4932
- // synchronously here would target elements with zero dimensions
4933
- // and the focus call would be silently ignored.
4934
- this.focusTrap = new FocusTrap(this.bibContent);
4767
+ // Normal desktop (non-modal): move initial focus into the bib but
4768
+ // don't trap Tab. Tab should exit the bib and let the floater's
4769
+ // handleFocusLoss close it, matching native <select>/<details>
4770
+ // behavior. Deferred one frame because Floating UI positions the
4771
+ // popover asynchronously a synchronous focus() would target
4772
+ // zero-dimension elements and be silently ignored.
4935
4773
  requestAnimationFrame(() => {
4936
- if (this.focusTrap) {
4937
- this.focusTrap.focusFirstElement();
4774
+ const focusables = getFocusableElements(this.bibContent);
4775
+ if (focusables.length) {
4776
+ focusables[0].focus();
4938
4777
  }
4939
4778
  });
4940
4779
  }
@@ -5356,13 +5195,23 @@ class AuroDropdown extends AuroElement {
5356
5195
  }
5357
5196
  }
5358
5197
 
5198
+ function fullscreenBreakpointExample() {
5199
+ const btn = document.querySelector('#fullscreenButton');
5200
+ const dropdown = document.querySelector('#fullscreen');
5201
+
5202
+ btn?.addEventListener('click', () => {
5203
+ dropdown?.hide();
5204
+ });
5205
+ }
5206
+
5359
5207
  /* eslint-disable jsdoc/require-jsdoc, no-magic-numbers, no-param-reassign */
5360
5208
 
5361
5209
  AuroDropdown.register();
5362
5210
  AuroDropdown.register('custom-dropdown');
5363
-
5364
5211
  function initExamples() {
5365
5212
  // javascript example function calls to be added here upon creation to test examples
5213
+
5214
+ fullscreenBreakpointExample();
5366
5215
  }
5367
5216
 
5368
5217
  export { initExamples };
@@ -2295,12 +2295,40 @@ class AuroFloatingUI {
2295
2295
  return;
2296
2296
  }
2297
2297
 
2298
+ // Chrome-specific: during popover top-layer promotion after a click on a
2299
+ // slotted focusable, :focus-within can briefly evaluate false while the
2300
+ // active element is still structurally inside the trigger/bib. Fall back
2301
+ // to a shadow-piercing ancestry walk from the deep active element before
2302
+ // treating this as a real focus loss.
2303
+ try {
2304
+ let active = document.activeElement;
2305
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) {
2306
+ active = active.shadowRoot.activeElement;
2307
+ }
2308
+ const targets = [element, element.trigger, element.bib].filter(Boolean);
2309
+ let node = active;
2310
+ while (node) {
2311
+ if (targets.includes(node)) {
2312
+ return;
2313
+ }
2314
+ node =
2315
+ node.parentElement ||
2316
+ (node.getRootNode && node.getRootNode().host) ||
2317
+ null;
2318
+ }
2319
+ } catch (e) {
2320
+ // Defensive: fall through to the existing close path if traversal fails.
2321
+ }
2322
+
2298
2323
  // if fullscreen bib is in fullscreen mode, do not close
2299
2324
  if (element.bib.hasAttribute("isfullscreen")) {
2300
2325
  return;
2301
2326
  }
2302
2327
 
2303
- this.hideBib("keydown");
2328
+ // eventType "focusloss" distinguishes a Tab/click-driven close from an
2329
+ // Escape keydown close. Consumers use this to decide whether to restore
2330
+ // focus to the trigger (Escape) or let it advance naturally (Tab).
2331
+ this.hideBib("focusloss");
2304
2332
  }
2305
2333
 
2306
2334
  setupHideHandlers() {
@@ -2970,199 +2998,6 @@ function getFocusableElements(container) {
2970
2998
  return tabIndexedUniqueElements;
2971
2999
  }
2972
3000
 
2973
- /**
2974
- * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
2975
- * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
2976
- */
2977
- class FocusTrap {
2978
- /**
2979
- * Creates a new FocusTrap instance for the given container element.
2980
- * Initializes event listeners and prepares the container for focus management.
2981
- *
2982
- * @param {HTMLElement} container The DOM element to trap focus within.
2983
- * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
2984
- * @throws {Error} If the provided container is not a valid HTMLElement.
2985
- */
2986
- constructor(container, controlTabOrder = false) {
2987
- if (!container || !(container instanceof HTMLElement)) {
2988
- throw new Error("FocusTrap requires a valid HTMLElement.");
2989
- }
2990
-
2991
- this.container = container;
2992
- this.tabDirection = "forward"; // or 'backward';
2993
- this.controlTabOrder = controlTabOrder;
2994
-
2995
- this._init();
2996
- }
2997
-
2998
- /**
2999
- * Initializes the focus trap by setting up event listeners and attributes on the container.
3000
- * Prepares the container for focus management, including support for shadow DOM and inert attributes.
3001
- *
3002
- * @private
3003
- */
3004
- _init() {
3005
- // Add inert attribute to prevent focusing programmatically as well (if supported)
3006
- if ("inert" in HTMLElement.prototype) {
3007
- this.container.inert = false; // Ensure the container isn't inert
3008
- this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
3009
- }
3010
-
3011
- // Track tab direction
3012
- this.container.addEventListener("keydown", this._onKeydown);
3013
- }
3014
-
3015
- /**
3016
- * Gets an array of currently active (focused) elements in the document and shadow DOM.
3017
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3018
- * @private
3019
- */
3020
- _getActiveElements() {
3021
- // Get the active element(s) in the document and shadow root
3022
- // This will include the active element in the shadow DOM if it exists
3023
- // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
3024
- let { activeElement } = document;
3025
- const actives = [activeElement];
3026
- while (activeElement?.shadowRoot?.activeElement) {
3027
- actives.push(activeElement.shadowRoot.activeElement);
3028
- activeElement = activeElement.shadowRoot.activeElement;
3029
- }
3030
- return actives;
3031
- }
3032
-
3033
- /**
3034
- * Gets the next focus index based on the current index and focusable elements.
3035
- * @param {number} currentIndex The current index of the focused element.
3036
- * @param {Array<HTMLElement>} focusables The array of focusable elements.
3037
- * @returns {number|null} The next focus index or null if not determined.
3038
- */
3039
- _getNextFocusIndex(currentIndex, focusables, actives) {
3040
- if (this.controlTabOrder) {
3041
- // Calculate the new index based on the current index and tab direction
3042
- let newFocusIndex =
3043
- currentIndex + (this.tabDirection === "forward" ? 1 : -1);
3044
-
3045
- // Wrap-around logic
3046
- if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
3047
- if (newFocusIndex >= focusables.length) newFocusIndex = 0;
3048
-
3049
- // Early return with the new index
3050
- return newFocusIndex;
3051
- }
3052
-
3053
- // Determine if we need to wrap
3054
- const atFirst =
3055
- actives.includes(focusables[0]) || actives.includes(this.container);
3056
- const atLast = actives.includes(focusables[focusables.length - 1]);
3057
-
3058
- // Only wrap if at the ends
3059
- if (this.tabDirection === "backward" && atFirst) {
3060
- return focusables.length - 1;
3061
- }
3062
-
3063
- if (this.tabDirection === "forward" && atLast) {
3064
- return 0;
3065
- }
3066
-
3067
- // No wrap, so don't change focus, return early
3068
- return null;
3069
- }
3070
-
3071
- /**
3072
- * Handles the Tab key press event to manage focus within the container.
3073
- * @param {KeyboardEvent} e The keyboard event triggered by the user.
3074
- * @returns {void}
3075
- */
3076
- _handleTabKey(e) {
3077
- // Update the focusable elements
3078
- const focusables = this._getFocusableElements();
3079
-
3080
- // If there are no focusable elements, exit
3081
- if (!focusables.length) return;
3082
-
3083
- // Set the tab direction based on the key pressed
3084
- this.tabDirection = e.shiftKey ? "backward" : "forward";
3085
-
3086
- // Get the active elements that are currently focused
3087
- const actives = this._getActiveElements();
3088
-
3089
- // If we're at either end of the focusable elements, wrap around to the other end
3090
- let focusIndex = focusables.findIndex((el) => actives.includes(el));
3091
-
3092
- // Fallback if we have no focused element
3093
- if (focusIndex === -1) focusIndex = 0;
3094
-
3095
- // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
3096
- // Is null if no new focus index is determined
3097
- const newFocusIndex = this._getNextFocusIndex(
3098
- focusIndex,
3099
- focusables,
3100
- actives,
3101
- );
3102
-
3103
- // If we have a new focus index, set focus to that element
3104
- if (newFocusIndex !== null) {
3105
- e.preventDefault();
3106
- focusables[newFocusIndex].focus();
3107
- }
3108
- }
3109
-
3110
- /**
3111
- * Catches the keydown event
3112
- * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
3113
- * @private
3114
- */
3115
- _onKeydown = (e) => {
3116
- // Handle tab
3117
- if (e.key === "Tab") this._handleTabKey(e);
3118
- };
3119
-
3120
- /**
3121
- * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
3122
- * Returns a unique, ordered array of elements that can receive focus.
3123
- *
3124
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3125
- * @private
3126
- */
3127
- _getFocusableElements() {
3128
- // Use the imported utility function to get focusable elements
3129
- const elements = getFocusableElements(this.container);
3130
-
3131
- // Return the elements found
3132
- return elements;
3133
- }
3134
-
3135
- /**
3136
- * Moves focus to the first focusable element within the container.
3137
- * Useful for setting initial focus when activating the focus trap.
3138
- */
3139
- focusFirstElement() {
3140
- const focusables = this._getFocusableElements();
3141
- if (focusables.length) focusables[0].focus();
3142
- }
3143
-
3144
- /**
3145
- * Moves focus to the last focusable element within the container.
3146
- * Useful for setting focus when deactivating or cycling focus in reverse.
3147
- */
3148
- focusLastElement() {
3149
- const focusables = this._getFocusableElements();
3150
- if (focusables.length) focusables[focusables.length - 1].focus();
3151
- }
3152
-
3153
- /**
3154
- * Removes event listeners and attributes added by the focus trap.
3155
- * Call this method to clean up when the focus trap is no longer needed.
3156
- */
3157
- disconnect() {
3158
- if (this.container.hasAttribute("data-focus-trap-container")) {
3159
- this.container.removeAttribute("data-focus-trap-container");
3160
- }
3161
-
3162
- this.container.removeEventListener("keydown", this._onKeydown);
3163
- }
3164
- }
3165
-
3166
3001
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
3167
3002
  // See LICENSE in the project root for license information.
3168
3003
 
@@ -3691,7 +3526,7 @@ class AuroDropdownBib extends i {
3691
3526
  classes[`shape-${this.shape}`] = true;
3692
3527
 
3693
3528
  return u$2`
3694
- <dialog class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3529
+ <dialog tabindex="-1" class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3695
3530
  ${this.dialogLabel ? u$2`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
3696
3531
  <slot></slot>
3697
3532
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -3942,7 +3777,7 @@ class AuroHelpText extends i {
3942
3777
  }
3943
3778
  }
3944
3779
 
3945
- var formkitVersion = '202607072023';
3780
+ var formkitVersion = '202607102214';
3946
3781
 
3947
3782
  class AuroElement extends i {
3948
3783
  static get properties() {
@@ -4696,7 +4531,10 @@ class AuroDropdown extends AuroElement {
4696
4531
  }
4697
4532
 
4698
4533
 
4699
- if (!this.isPopoverVisible) {
4534
+ // On Tab-driven close (eventType "focusloss"), let focus advance naturally
4535
+ // — restoring to the trigger would trap the user on this dropdown, forcing
4536
+ // an extra Tab to move on. Escape and outside-click still restore.
4537
+ if (!this.isPopoverVisible && eventType !== "focusloss") {
4700
4538
  // wait til the bib gets fully closed and rendered
4701
4539
  setTimeout(() => {
4702
4540
  // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
@@ -4926,15 +4764,16 @@ class AuroDropdown extends AuroElement {
4926
4764
  }
4927
4765
  });
4928
4766
  } else {
4929
- // Normal desktop: use FocusTrap on the bib element.
4930
- // Defer focusFirstElement to the next frame because the popover
4931
- // is positioned asynchronously by Floating UI — calling focus()
4932
- // synchronously here would target elements with zero dimensions
4933
- // and the focus call would be silently ignored.
4934
- this.focusTrap = new FocusTrap(this.bibContent);
4767
+ // Normal desktop (non-modal): move initial focus into the bib but
4768
+ // don't trap Tab. Tab should exit the bib and let the floater's
4769
+ // handleFocusLoss close it, matching native <select>/<details>
4770
+ // behavior. Deferred one frame because Floating UI positions the
4771
+ // popover asynchronously a synchronous focus() would target
4772
+ // zero-dimension elements and be silently ignored.
4935
4773
  requestAnimationFrame(() => {
4936
- if (this.focusTrap) {
4937
- this.focusTrap.focusFirstElement();
4774
+ const focusables = getFocusableElements(this.bibContent);
4775
+ if (focusables.length) {
4776
+ focusables[0].focus();
4938
4777
  }
4939
4778
  });
4940
4779
  }