@aurodesignsystem-dev/auro-formkit 0.0.0-pr1503.3 → 0.0.0-pr1503.5

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 (190) hide show
  1. package/components/bibtemplate/dist/index.js +1 -1
  2. package/components/bibtemplate/dist/registered.js +1 -1
  3. package/components/checkbox/demo/accessibility.md +1 -1
  4. package/components/checkbox/demo/customize.html +1 -2
  5. package/components/checkbox/demo/customize.min.js +23 -8
  6. package/components/checkbox/demo/getting-started.min.js +23 -8
  7. package/components/checkbox/demo/index.min.js +23 -8
  8. package/components/checkbox/dist/index.js +23 -8
  9. package/components/checkbox/dist/registered.js +23 -8
  10. package/components/combobox/README.md +1 -1
  11. package/components/combobox/demo/accessibility.md +1 -1
  12. package/components/combobox/demo/api.md +2 -2
  13. package/components/combobox/demo/customize.html +1 -2
  14. package/components/combobox/demo/customize.md +166 -142
  15. package/components/combobox/demo/customize.min.js +2515 -2316
  16. package/components/combobox/demo/getting-started.min.js +2515 -2312
  17. package/components/combobox/demo/index.md +2 -1
  18. package/components/combobox/demo/index.min.js +2515 -2312
  19. package/components/combobox/demo/keyboard-behavior.md +3 -146
  20. package/components/combobox/demo/readme.md +1 -1
  21. package/components/combobox/demo/why-combobox.md +2 -2
  22. package/components/combobox/dist/auro-combobox.d.ts +49 -15
  23. package/components/combobox/dist/index.js +1462 -753
  24. package/components/combobox/dist/registered.js +1462 -753
  25. package/components/counter/demo/customize.min.js +188 -244
  26. package/components/counter/demo/index.min.js +188 -244
  27. package/components/counter/dist/auro-counter.d.ts +0 -8
  28. package/components/counter/dist/index.js +188 -244
  29. package/components/counter/dist/registered.js +188 -244
  30. package/components/datepicker/demo/accessibility.md +20 -10
  31. package/components/datepicker/demo/api.md +65 -62
  32. package/components/datepicker/demo/customize.md +180 -40
  33. package/components/datepicker/demo/customize.min.js +1690 -777
  34. package/components/datepicker/demo/getting-started.md +118 -2
  35. package/components/datepicker/demo/index.min.js +1690 -759
  36. package/components/datepicker/demo/keyboard-behavior.md +3 -3
  37. package/components/datepicker/demo/voiceover.md +4 -4
  38. package/components/datepicker/demo/why-datepicker.md +2 -2
  39. package/components/datepicker/dist/{src/auro-calendar-cell.d.ts → auro-calendar-cell.d.ts} +48 -3
  40. package/components/datepicker/dist/{src/auro-calendar.d.ts → auro-calendar.d.ts} +188 -10
  41. package/components/datepicker/dist/{src/auro-datepicker.d.ts → auro-datepicker.d.ts} +89 -6
  42. package/components/datepicker/dist/blackoutUtils.d.ts +43 -0
  43. package/components/datepicker/dist/index.js +1690 -759
  44. package/components/datepicker/dist/registered.js +1690 -759
  45. package/components/dropdown/demo/customize.html +3 -0
  46. package/components/dropdown/demo/customize.min.js +142 -208
  47. package/components/dropdown/demo/getting-started.min.js +131 -207
  48. package/components/dropdown/demo/index.min.js +131 -207
  49. package/components/dropdown/dist/auro-dropdown.d.ts +17 -1
  50. package/components/dropdown/dist/index.js +131 -207
  51. package/components/dropdown/dist/registered.js +131 -207
  52. package/components/form/demo/api.md +3 -3
  53. package/components/form/demo/customize.html +6 -6
  54. package/components/form/demo/customize.md +535 -135
  55. package/components/form/demo/customize.min.js +6307 -4378
  56. package/components/form/demo/getting-started.md +344 -96
  57. package/components/form/demo/getting-started.min.js +6255 -4378
  58. package/components/form/demo/index.min.js +6206 -4378
  59. package/components/form/demo/registerDemoDeps.min.js +5827 -4333
  60. package/components/form/dist/auro-form.d.ts +128 -10
  61. package/components/form/dist/index.js +377 -43
  62. package/components/form/dist/registered.js +377 -43
  63. package/components/input/demo/accessibility.md +1 -1
  64. package/components/input/demo/api.md +58 -57
  65. package/components/input/demo/customize.html +1 -2
  66. package/components/input/demo/customize.md +62 -61
  67. package/components/input/demo/customize.min.js +396 -275
  68. package/components/input/demo/getting-started.min.js +396 -275
  69. package/components/input/demo/index.min.js +396 -275
  70. package/components/input/dist/auro-input.d.ts +1 -1
  71. package/components/input/dist/base-input.d.ts +60 -81
  72. package/components/input/dist/index.js +409 -276
  73. package/components/input/dist/registered.js +396 -275
  74. package/components/input/dist/utilities.d.ts +10 -1
  75. package/components/menu/demo/accessibility.md +9 -4
  76. package/components/menu/demo/api.md +48 -45
  77. package/components/menu/demo/css-only.md +26 -19
  78. package/components/menu/demo/customize.md +307 -75
  79. package/components/menu/demo/design.md +1 -1
  80. package/components/menu/demo/getting-started.md +144 -7
  81. package/components/menu/demo/index.min.js +961 -1341
  82. package/components/menu/demo/keyboard-behavior.md +83 -4
  83. package/components/menu/demo/voiceover.md +21 -14
  84. package/components/menu/demo/why-menu.md +8 -9
  85. package/components/menu/dist/auro-menu-utils.d.ts +19 -0
  86. package/components/menu/dist/auro-menu.d.ts +97 -116
  87. package/components/menu/dist/auro-menuoption.d.ts +39 -133
  88. package/components/menu/dist/index.js +823 -1309
  89. package/components/menu/dist/registered.js +835 -1309
  90. package/components/radio/demo/accessibility.md +1 -1
  91. package/components/radio/demo/customize.min.js +23 -8
  92. package/components/radio/demo/getting-started.min.js +23 -8
  93. package/components/radio/demo/index.min.js +23 -8
  94. package/components/radio/dist/index.js +23 -8
  95. package/components/radio/dist/registered.js +23 -8
  96. package/components/select/demo/accessibility.md +6 -1
  97. package/components/select/demo/api.md +3 -2
  98. package/components/select/demo/customize.html +1 -2
  99. package/components/select/demo/customize.md +210 -5
  100. package/components/select/demo/customize.min.js +1613 -1854
  101. package/components/select/demo/design.md +10 -10
  102. package/components/select/demo/getting-started.md +1 -1
  103. package/components/select/demo/getting-started.min.js +1617 -1903
  104. package/components/select/demo/index.md +2 -2
  105. package/components/select/demo/index.min.js +1613 -1854
  106. package/components/select/demo/keyboard-behavior.md +81 -54
  107. package/components/select/demo/voiceover.md +28 -15
  108. package/components/select/dist/auro-select.d.ts +70 -12
  109. package/components/select/dist/index.js +580 -315
  110. package/components/select/dist/registered.js +580 -315
  111. package/components/select/dist/selectUtils.d.ts +24 -0
  112. package/custom-elements.json +8071 -7919
  113. package/package.json +7 -3
  114. package/components/checkbox/demo/customize.js +0 -22
  115. package/components/checkbox/demo/demo-support.js +0 -1
  116. package/components/checkbox/demo/getting-started.js +0 -22
  117. package/components/checkbox/demo/index.js +0 -8
  118. package/components/checkbox/demo/styles.scss +0 -1
  119. package/components/combobox/demo/customize.js +0 -24
  120. package/components/combobox/demo/demo-support.js +0 -1
  121. package/components/combobox/demo/getting-started.js +0 -21
  122. package/components/combobox/demo/index.js +0 -23
  123. package/components/combobox/demo/styles.scss +0 -1
  124. package/components/counter/demo/customize.js +0 -21
  125. package/components/counter/demo/demo-support.js +0 -1
  126. package/components/counter/demo/index.js +0 -21
  127. package/components/counter/demo/styles.scss +0 -1
  128. package/components/datepicker/demo/customize.js +0 -19
  129. package/components/datepicker/demo/demo-support.js +0 -1
  130. package/components/datepicker/demo/index.js +0 -23
  131. package/components/datepicker/demo/styles.scss +0 -1
  132. package/components/dropdown/demo/customize.js +0 -9
  133. package/components/dropdown/demo/demo-support.js +0 -1
  134. package/components/dropdown/demo/getting-started.js +0 -9
  135. package/components/dropdown/demo/index.js +0 -16
  136. package/components/dropdown/demo/styles.scss +0 -1
  137. package/components/form/demo/customize.js +0 -9
  138. package/components/form/demo/demo-support.js +0 -1
  139. package/components/form/demo/getting-started.js +0 -9
  140. package/components/form/demo/index.js +0 -5
  141. package/components/form/demo/registerDemoDeps.js +0 -23
  142. package/components/form/demo/styles.scss +0 -1
  143. package/components/input/demo/customize.js +0 -25
  144. package/components/input/demo/demo-support.js +0 -1
  145. package/components/input/demo/getting-started.js +0 -8
  146. package/components/input/demo/index.js +0 -8
  147. package/components/input/demo/styles.css +0 -974
  148. package/components/input/demo/styles.scss +0 -1
  149. package/components/menu/demo/demo-support.js +0 -1
  150. package/components/menu/demo/index.js +0 -25
  151. package/components/menu/demo/styles.scss +0 -1
  152. package/components/menu/dist/auro-menu.context.d.ts +0 -238
  153. package/components/radio/demo/customize.js +0 -22
  154. package/components/radio/demo/demo-support.js +0 -1
  155. package/components/radio/demo/getting-started.js +0 -24
  156. package/components/radio/demo/index.js +0 -8
  157. package/components/radio/demo/styles.scss +0 -1
  158. package/components/select/demo/customize.js +0 -11
  159. package/components/select/demo/demo-support.js +0 -1
  160. package/components/select/demo/getting-started.js +0 -26
  161. package/components/select/demo/index.js +0 -11
  162. package/components/select/demo/styles.scss +0 -1
  163. /package/components/counter/dist/styles/{counter-wrapper-color-css.d.ts → counter-group-color-css.d.ts} +0 -0
  164. /package/components/datepicker/dist/{src/auro-calendar-month.d.ts → auro-calendar-month.d.ts} +0 -0
  165. /package/components/datepicker/dist/{src/buttonVersion.d.ts → buttonVersion.d.ts} +0 -0
  166. /package/components/datepicker/dist/{src/datepickerKeyboardStrategy.d.ts → datepickerKeyboardStrategy.d.ts} +0 -0
  167. /package/components/datepicker/dist/{src/iconVersion.d.ts → iconVersion.d.ts} +0 -0
  168. /package/components/datepicker/dist/{src/index.d.ts → index.d.ts} +0 -0
  169. /package/components/datepicker/dist/{src/popoverVersion.d.ts → popoverVersion.d.ts} +0 -0
  170. /package/components/datepicker/dist/{src/styles → styles}/classic/color-css.d.ts +0 -0
  171. /package/components/datepicker/dist/{src/styles → styles}/classic/style-css.d.ts +0 -0
  172. /package/components/datepicker/dist/{src/styles → styles}/color-calendar-css.d.ts +0 -0
  173. /package/components/datepicker/dist/{src/styles → styles}/color-cell-css.d.ts +0 -0
  174. /package/components/datepicker/dist/{src/styles → styles}/color-css.d.ts +0 -0
  175. /package/components/datepicker/dist/{src/styles → styles}/color-month-css.d.ts +0 -0
  176. /package/components/datepicker/dist/{src/styles → styles}/shapeSize-css.d.ts +0 -0
  177. /package/components/datepicker/dist/{src/styles → styles}/snowflake/color-css.d.ts +0 -0
  178. /package/components/datepicker/dist/{src/styles → styles}/snowflake/style-css.d.ts +0 -0
  179. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-cell-css.d.ts +0 -0
  180. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-css.d.ts +0 -0
  181. /package/components/datepicker/dist/{src/styles → styles}/style-auro-calendar-month-css.d.ts +0 -0
  182. /package/components/datepicker/dist/{src/styles → styles}/style-css.d.ts +0 -0
  183. /package/components/datepicker/dist/{src/styles → styles}/tokens-css.d.ts +0 -0
  184. /package/components/datepicker/dist/{src/utilities.d.ts → utilities.d.ts} +0 -0
  185. /package/components/datepicker/dist/{src/utilitiesCalendar.d.ts → utilitiesCalendar.d.ts} +0 -0
  186. /package/components/datepicker/dist/{src/utilitiesCalendarRender.d.ts → utilitiesCalendarRender.d.ts} +0 -0
  187. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/day.d.ts +0 -0
  188. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-calendar.d.ts +0 -0
  189. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker-cell.d.ts +0 -0
  190. /package/components/datepicker/dist/{src/vendor → vendor}/wc-range-datepicker/range-datepicker.d.ts +0 -0
@@ -2322,12 +2322,40 @@ class AuroFloatingUI {
2322
2322
  return;
2323
2323
  }
2324
2324
 
2325
+ // Chrome-specific: during popover top-layer promotion after a click on a
2326
+ // slotted focusable, :focus-within can briefly evaluate false while the
2327
+ // active element is still structurally inside the trigger/bib. Fall back
2328
+ // to a shadow-piercing ancestry walk from the deep active element before
2329
+ // treating this as a real focus loss.
2330
+ try {
2331
+ let active = document.activeElement;
2332
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) {
2333
+ active = active.shadowRoot.activeElement;
2334
+ }
2335
+ const targets = [element, element.trigger, element.bib].filter(Boolean);
2336
+ let node = active;
2337
+ while (node) {
2338
+ if (targets.includes(node)) {
2339
+ return;
2340
+ }
2341
+ node =
2342
+ node.parentElement ||
2343
+ (node.getRootNode && node.getRootNode().host) ||
2344
+ null;
2345
+ }
2346
+ } catch (e) {
2347
+ // Defensive: fall through to the existing close path if traversal fails.
2348
+ }
2349
+
2325
2350
  // if fullscreen bib is in fullscreen mode, do not close
2326
2351
  if (element.bib.hasAttribute("isfullscreen")) {
2327
2352
  return;
2328
2353
  }
2329
2354
 
2330
- this.hideBib("keydown");
2355
+ // eventType "focusloss" distinguishes a Tab/click-driven close from an
2356
+ // Escape keydown close. Consumers use this to decide whether to restore
2357
+ // focus to the trigger (Escape) or let it advance naturally (Tab).
2358
+ this.hideBib("focusloss");
2331
2359
  }
2332
2360
 
2333
2361
  setupHideHandlers() {
@@ -2997,199 +3025,6 @@ function getFocusableElements(container) {
2997
3025
  return tabIndexedUniqueElements;
2998
3026
  }
2999
3027
 
3000
- /**
3001
- * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
3002
- * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
3003
- */
3004
- class FocusTrap {
3005
- /**
3006
- * Creates a new FocusTrap instance for the given container element.
3007
- * Initializes event listeners and prepares the container for focus management.
3008
- *
3009
- * @param {HTMLElement} container The DOM element to trap focus within.
3010
- * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
3011
- * @throws {Error} If the provided container is not a valid HTMLElement.
3012
- */
3013
- constructor(container, controlTabOrder = false) {
3014
- if (!container || !(container instanceof HTMLElement)) {
3015
- throw new Error("FocusTrap requires a valid HTMLElement.");
3016
- }
3017
-
3018
- this.container = container;
3019
- this.tabDirection = "forward"; // or 'backward';
3020
- this.controlTabOrder = controlTabOrder;
3021
-
3022
- this._init();
3023
- }
3024
-
3025
- /**
3026
- * Initializes the focus trap by setting up event listeners and attributes on the container.
3027
- * Prepares the container for focus management, including support for shadow DOM and inert attributes.
3028
- *
3029
- * @private
3030
- */
3031
- _init() {
3032
- // Add inert attribute to prevent focusing programmatically as well (if supported)
3033
- if ("inert" in HTMLElement.prototype) {
3034
- this.container.inert = false; // Ensure the container isn't inert
3035
- this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
3036
- }
3037
-
3038
- // Track tab direction
3039
- this.container.addEventListener("keydown", this._onKeydown);
3040
- }
3041
-
3042
- /**
3043
- * Gets an array of currently active (focused) elements in the document and shadow DOM.
3044
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3045
- * @private
3046
- */
3047
- _getActiveElements() {
3048
- // Get the active element(s) in the document and shadow root
3049
- // This will include the active element in the shadow DOM if it exists
3050
- // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
3051
- let { activeElement } = document;
3052
- const actives = [activeElement];
3053
- while (activeElement?.shadowRoot?.activeElement) {
3054
- actives.push(activeElement.shadowRoot.activeElement);
3055
- activeElement = activeElement.shadowRoot.activeElement;
3056
- }
3057
- return actives;
3058
- }
3059
-
3060
- /**
3061
- * Gets the next focus index based on the current index and focusable elements.
3062
- * @param {number} currentIndex The current index of the focused element.
3063
- * @param {Array<HTMLElement>} focusables The array of focusable elements.
3064
- * @returns {number|null} The next focus index or null if not determined.
3065
- */
3066
- _getNextFocusIndex(currentIndex, focusables, actives) {
3067
- if (this.controlTabOrder) {
3068
- // Calculate the new index based on the current index and tab direction
3069
- let newFocusIndex =
3070
- currentIndex + (this.tabDirection === "forward" ? 1 : -1);
3071
-
3072
- // Wrap-around logic
3073
- if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
3074
- if (newFocusIndex >= focusables.length) newFocusIndex = 0;
3075
-
3076
- // Early return with the new index
3077
- return newFocusIndex;
3078
- }
3079
-
3080
- // Determine if we need to wrap
3081
- const atFirst =
3082
- actives.includes(focusables[0]) || actives.includes(this.container);
3083
- const atLast = actives.includes(focusables[focusables.length - 1]);
3084
-
3085
- // Only wrap if at the ends
3086
- if (this.tabDirection === "backward" && atFirst) {
3087
- return focusables.length - 1;
3088
- }
3089
-
3090
- if (this.tabDirection === "forward" && atLast) {
3091
- return 0;
3092
- }
3093
-
3094
- // No wrap, so don't change focus, return early
3095
- return null;
3096
- }
3097
-
3098
- /**
3099
- * Handles the Tab key press event to manage focus within the container.
3100
- * @param {KeyboardEvent} e The keyboard event triggered by the user.
3101
- * @returns {void}
3102
- */
3103
- _handleTabKey(e) {
3104
- // Update the focusable elements
3105
- const focusables = this._getFocusableElements();
3106
-
3107
- // If there are no focusable elements, exit
3108
- if (!focusables.length) return;
3109
-
3110
- // Set the tab direction based on the key pressed
3111
- this.tabDirection = e.shiftKey ? "backward" : "forward";
3112
-
3113
- // Get the active elements that are currently focused
3114
- const actives = this._getActiveElements();
3115
-
3116
- // If we're at either end of the focusable elements, wrap around to the other end
3117
- let focusIndex = focusables.findIndex((el) => actives.includes(el));
3118
-
3119
- // Fallback if we have no focused element
3120
- if (focusIndex === -1) focusIndex = 0;
3121
-
3122
- // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
3123
- // Is null if no new focus index is determined
3124
- const newFocusIndex = this._getNextFocusIndex(
3125
- focusIndex,
3126
- focusables,
3127
- actives,
3128
- );
3129
-
3130
- // If we have a new focus index, set focus to that element
3131
- if (newFocusIndex !== null) {
3132
- e.preventDefault();
3133
- focusables[newFocusIndex].focus();
3134
- }
3135
- }
3136
-
3137
- /**
3138
- * Catches the keydown event
3139
- * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
3140
- * @private
3141
- */
3142
- _onKeydown = (e) => {
3143
- // Handle tab
3144
- if (e.key === "Tab") this._handleTabKey(e);
3145
- };
3146
-
3147
- /**
3148
- * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
3149
- * Returns a unique, ordered array of elements that can receive focus.
3150
- *
3151
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3152
- * @private
3153
- */
3154
- _getFocusableElements() {
3155
- // Use the imported utility function to get focusable elements
3156
- const elements = getFocusableElements(this.container);
3157
-
3158
- // Return the elements found
3159
- return elements;
3160
- }
3161
-
3162
- /**
3163
- * Moves focus to the first focusable element within the container.
3164
- * Useful for setting initial focus when activating the focus trap.
3165
- */
3166
- focusFirstElement() {
3167
- const focusables = this._getFocusableElements();
3168
- if (focusables.length) focusables[0].focus();
3169
- }
3170
-
3171
- /**
3172
- * Moves focus to the last focusable element within the container.
3173
- * Useful for setting focus when deactivating or cycling focus in reverse.
3174
- */
3175
- focusLastElement() {
3176
- const focusables = this._getFocusableElements();
3177
- if (focusables.length) focusables[focusables.length - 1].focus();
3178
- }
3179
-
3180
- /**
3181
- * Removes event listeners and attributes added by the focus trap.
3182
- * Call this method to clean up when the focus trap is no longer needed.
3183
- */
3184
- disconnect() {
3185
- if (this.container.hasAttribute("data-focus-trap-container")) {
3186
- this.container.removeAttribute("data-focus-trap-container");
3187
- }
3188
-
3189
- this.container.removeEventListener("keydown", this._onKeydown);
3190
- }
3191
- }
3192
-
3193
3028
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
3194
3029
  // See LICENSE in the project root for license information.
3195
3030
 
@@ -3721,7 +3556,7 @@ class AuroDropdownBib extends i {
3721
3556
  classes[`shape-${this.shape}`] = true;
3722
3557
 
3723
3558
  return u$2`
3724
- <dialog class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3559
+ <dialog tabindex="-1" class="${e$4(classes)}" part="bibContainer" role="${o(this.dialogRole)}" aria-labelledby="${o(this.dialogLabel ? 'dialogLabel' : undefined)}">
3725
3560
  ${this.dialogLabel ? u$2`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
3726
3561
  <slot></slot>
3727
3562
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -3972,7 +3807,7 @@ class AuroHelpText extends i {
3972
3807
  }
3973
3808
  }
3974
3809
 
3975
- var formkitVersion = '202606191810';
3810
+ var formkitVersion = '202607221815';
3976
3811
 
3977
3812
  class AuroElement extends i {
3978
3813
  static get properties() {
@@ -4101,7 +3936,6 @@ class AuroDropdown extends AuroElement {
4101
3936
  static get shadowRootOptions() {
4102
3937
  return {
4103
3938
  ...AuroElement.shadowRootOptions,
4104
- delegatesFocus: true,
4105
3939
  };
4106
3940
  }
4107
3941
 
@@ -4113,6 +3947,15 @@ class AuroDropdown extends AuroElement {
4113
3947
  this.matchWidth = false;
4114
3948
  this.noHideOnThisFocusLoss = false;
4115
3949
 
3950
+ /**
3951
+ * When true, the dropdown skips its generic focus restoration on close.
3952
+ * Set by consumers (e.g. combobox) that manage their own focus routing
3953
+ * via setClearBtnFocus / setInputFocus / keyboard strategy.
3954
+ * Separate from noHideOnThisFocusLoss (which controls auto-close behavior).
3955
+ * @private
3956
+ */
3957
+ this.noFocusRestoreOnClose = false;
3958
+
4116
3959
  this.errorMessage = undefined; // TODO - check with Doug if there is still more to do here
4117
3960
 
4118
3961
  // Layout Config
@@ -4268,7 +4111,49 @@ class AuroDropdown extends AuroElement {
4268
4111
  focusables[0].focus();
4269
4112
  }
4270
4113
  } else {
4114
+ this.focusTrigger();
4115
+ }
4116
+ }
4117
+
4118
+ /**
4119
+ * Focus the trigger content. When the trigger wrapper itself is focusable
4120
+ * (has tabindex), focus it directly. Otherwise, focus the first focusable
4121
+ * element slotted into the trigger slot (e.g. the auro-input).
4122
+ * @private
4123
+ */
4124
+ focusTrigger() {
4125
+ if (this.trigger.hasAttribute('tabindex')) {
4271
4126
  this.trigger.focus();
4127
+ } else {
4128
+ // Slotted content isn't a DOM child of the trigger div, so
4129
+ // getFocusableElements can't find it. Query assigned nodes directly.
4130
+ const slot = this.trigger.querySelector('slot[name="trigger"]');
4131
+ if (slot) {
4132
+ const assigned = slot.assignedElements();
4133
+ for (const el of assigned) {
4134
+ if (el.hasAttribute('disabled')) {
4135
+ continue; // eslint-disable-line no-continue
4136
+ }
4137
+ // Try finding a focusable descendant first (handles non-focusable
4138
+ // wrappers like <div> containing a <button>). If none found, try
4139
+ // focusing the element directly (works for custom elements like
4140
+ // auro-input that have delegatesFocus or a custom focus() method).
4141
+ const descendants = getFocusableElements(el);
4142
+ if (descendants.length > 0) {
4143
+ descendants[0].focus();
4144
+ return;
4145
+ }
4146
+ el.focus();
4147
+ if (document.activeElement === el) {
4148
+ return;
4149
+ }
4150
+ }
4151
+ }
4152
+ // Fallback: try DOM children (non-slotted content)
4153
+ const focusables = getFocusableElements(this.trigger);
4154
+ if (focusables.length > 0) {
4155
+ focusables[0].focus();
4156
+ }
4272
4157
  }
4273
4158
  }
4274
4159
 
@@ -4644,10 +4529,17 @@ class AuroDropdown extends AuroElement {
4644
4529
  this.clearTriggerFocusEventBinding();
4645
4530
  }
4646
4531
 
4532
+ // eslint-disable-next-line complexity
4647
4533
  updated(changedProperties) {
4648
4534
  super.updated(changedProperties);
4649
4535
  this.floater.handleUpdate(changedProperties);
4650
4536
 
4537
+ // Close the dropdown if the disabled property is set to true
4538
+ if (changedProperties.has('disabled') && this.disabled && this.isPopoverVisible) {
4539
+ this.hide();
4540
+ this.isPopoverVisible = false;
4541
+ }
4542
+
4651
4543
  // Note: `disabled` is not a breakpoint (it is not a screen size),
4652
4544
  // so it looks like we never consume this - however, dropdownBib handles this in the setter as "undefined"
4653
4545
  if (changedProperties.has('fullscreenBreakpoint')) {
@@ -4721,8 +4613,30 @@ class AuroDropdown extends AuroElement {
4721
4613
  }
4722
4614
 
4723
4615
  const eventType = event.detail.eventType || "unknown";
4724
- if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
4725
- this.trigger.focus();
4616
+ if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown" && !this.noFocusRestoreOnClose) {
4617
+ this.focusTrigger();
4618
+ }
4619
+
4620
+
4621
+ // On Tab-driven close (eventType "focusloss"), let focus advance naturally
4622
+ // — restoring to the trigger would trap the user on this dropdown, forcing
4623
+ // an extra Tab to move on. Escape and outside-click still restore.
4624
+ // When noFocusRestoreOnClose is true, the consumer (e.g. combobox) manages
4625
+ // its own focus restoration — skip the generic trigger focus to avoid races.
4626
+ if (!this.isPopoverVisible && eventType !== "focusloss" && !this.noFocusRestoreOnClose) {
4627
+ // wait til the bib gets fully closed and rendered
4628
+ setTimeout(() => {
4629
+ // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
4630
+ // Restore focus to trigger when focus is still inside the bib (:focus-within) or fell to body.
4631
+ if (this.isPopoverVisible ||
4632
+ // eslint-disable-next-line no-extra-parens
4633
+ (!this.bibContent?.matches(':focus-within') &&
4634
+ document.activeElement !== document.body)) {
4635
+ return;
4636
+ }
4637
+ // Restore focus to the trigger.
4638
+ this.focusTrigger();
4639
+ });
4726
4640
  }
4727
4641
  }
4728
4642
 
@@ -4923,6 +4837,14 @@ class AuroDropdown extends AuroElement {
4923
4837
  };
4924
4838
  this.addEventListener('keydown', this._bibTabHandler);
4925
4839
 
4840
+ // Suppress AuroFloatingUI's auto-hide-on-focus-loss while the
4841
+ // desktopModal trap owns focus management. Without this, the very
4842
+ // first focus move into the bib (from the RAF below) triggers
4843
+ // handleFocusLoss → hideBib, tearing down the trap before the
4844
+ // user can press Tab.
4845
+ this._priorNoHideOnFocusLoss = this.noHideOnThisFocusLoss;
4846
+ this.noHideOnThisFocusLoss = true;
4847
+
4926
4848
  // Move initial focus into the bib content, matching FocusTrap behavior
4927
4849
  requestAnimationFrame(() => {
4928
4850
  const focusables = getFocusableElements(this.bibContent);
@@ -4931,15 +4853,16 @@ class AuroDropdown extends AuroElement {
4931
4853
  }
4932
4854
  });
4933
4855
  } else {
4934
- // Normal desktop: use FocusTrap on the bib element.
4935
- // Defer focusFirstElement to the next frame because the popover
4936
- // is positioned asynchronously by Floating UI — calling focus()
4937
- // synchronously here would target elements with zero dimensions
4938
- // and the focus call would be silently ignored.
4939
- this.focusTrap = new FocusTrap(this.bibContent);
4856
+ // Normal desktop (non-modal): move initial focus into the bib but
4857
+ // don't trap Tab. Tab should exit the bib and let the floater's
4858
+ // handleFocusLoss close it, matching native <select>/<details>
4859
+ // behavior. Deferred one frame because Floating UI positions the
4860
+ // popover asynchronously — a synchronous focus() would target
4861
+ // zero-dimension elements and be silently ignored.
4940
4862
  requestAnimationFrame(() => {
4941
- if (this.focusTrap) {
4942
- this.focusTrap.focusFirstElement();
4863
+ const focusables = getFocusableElements(this.bibContent);
4864
+ if (focusables.length) {
4865
+ focusables[0].focus();
4943
4866
  }
4944
4867
  });
4945
4868
  }
@@ -5236,6 +5159,7 @@ class AuroDropdown extends AuroElement {
5236
5159
  role="${o(this.triggerContentFocusable ? undefined : this.a11yRole)}"
5237
5160
  aria-expanded="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
5238
5161
  aria-controls="${o(this.a11yRole === 'button' || this.triggerContentFocusable ? undefined : this.dropdownId)}"
5162
+ aria-haspopup="${o(this.a11yRole === 'combobox' && !this.triggerContentFocusable ? 'listbox' : undefined)}"
5239
5163
  aria-labelledby="${o(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
5240
5164
  aria-disabled="${o(this.disabled ? 'true' : undefined)}"
5241
5165
  @focusin="${this.handleFocusin}"
@@ -15,9 +15,9 @@
15
15
  */
16
16
  export class AuroDropdown extends AuroElement {
17
17
  static get shadowRootOptions(): {
18
- delegatesFocus: boolean;
19
18
  clonable?: boolean;
20
19
  customElementRegistry?: CustomElementRegistry | null;
20
+ delegatesFocus?: boolean;
21
21
  mode: ShadowRootMode;
22
22
  serializable?: boolean;
23
23
  slotAssignment?: SlotAssignmentMode;
@@ -267,6 +267,14 @@ export class AuroDropdown extends AuroElement {
267
267
  isBibFullscreen: boolean;
268
268
  matchWidth: boolean;
269
269
  noHideOnThisFocusLoss: boolean;
270
+ /**
271
+ * When true, the dropdown skips its generic focus restoration on close.
272
+ * Set by consumers (e.g. combobox) that manage their own focus routing
273
+ * via setClearBtnFocus / setInputFocus / keyboard strategy.
274
+ * Separate from noHideOnThisFocusLoss (which controls auto-close behavior).
275
+ * @private
276
+ */
277
+ private noFocusRestoreOnClose;
270
278
  errorMessage: any;
271
279
  layout: any;
272
280
  shape: any;
@@ -366,6 +374,13 @@ export class AuroDropdown extends AuroElement {
366
374
  * If not, trigger element will get focus.
367
375
  */
368
376
  focus(): void;
377
+ /**
378
+ * Focus the trigger content. When the trigger wrapper itself is focusable
379
+ * (has tabindex), focus it directly. Otherwise, focus the first focusable
380
+ * element slotted into the trigger slot (e.g. the auro-input).
381
+ * @private
382
+ */
383
+ private focusTrigger;
369
384
  /**
370
385
  * Sets the active descendant element for accessibility.
371
386
  * Uses ariaActiveDescendantElement to cross shadow DOM boundaries.
@@ -412,6 +427,7 @@ export class AuroDropdown extends AuroElement {
412
427
  private updateFocusTrap;
413
428
  _noHideOverridden: boolean | undefined;
414
429
  _priorNoHide: any;
430
+ _priorNoHideOnFocusLoss: boolean | undefined;
415
431
  /**
416
432
  * Returns the chain of active (focused) elements through shadow roots.
417
433
  * @private