@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
@@ -782,6 +782,188 @@ function navigateArrow(component, direction, options = {}) {
782
782
  }
783
783
  }
784
784
 
785
+ // Selectors for focusable elements
786
+ const FOCUSABLE_SELECTORS$1 = [
787
+ "a[href]",
788
+ "button:not([disabled])",
789
+ "textarea:not([disabled])",
790
+ "input:not([disabled])",
791
+ "select:not([disabled])",
792
+ '[role="tab"]:not([disabled])',
793
+ '[role="link"]:not([disabled])',
794
+ '[role="button"]:not([disabled])',
795
+ '[tabindex]:not([tabindex="-1"])',
796
+ '[contenteditable]:not([contenteditable="false"])',
797
+ ];
798
+
799
+ // List of custom components that are known to be focusable
800
+ const FOCUSABLE_COMPONENTS$1 = [
801
+ "auro-checkbox",
802
+ "auro-radio",
803
+ "auro-dropdown",
804
+ "auro-button",
805
+ "auro-combobox",
806
+ "auro-input",
807
+ "auro-counter",
808
+ // 'auro-menu', // Auro menu is not focusable by default, it uses a different interaction model
809
+ "auro-select",
810
+ "auro-datepicker",
811
+ "auro-hyperlink",
812
+ "auro-accordion",
813
+ ];
814
+
815
+ /**
816
+ * Determines if a given element is a custom focusable component.
817
+ * Returns true if the element matches a known focusable component and is not disabled.
818
+ *
819
+ * @param {HTMLElement} element The element to check for focusability.
820
+ * @returns {boolean} True if the element is a focusable custom component, false otherwise.
821
+ */
822
+ function isFocusableComponent$1(element) {
823
+ const componentName = element.tagName.toLowerCase();
824
+
825
+ // Guard Clause: Element is a focusable component
826
+ if (
827
+ !FOCUSABLE_COMPONENTS$1.some(
828
+ (name) => element.hasAttribute(name) || componentName === name,
829
+ )
830
+ )
831
+ return false;
832
+
833
+ // Guard Clause: Element is not disabled
834
+ if (element.hasAttribute("disabled")) return false;
835
+
836
+ // Guard Clause: The element is a hyperlink and has no href attribute
837
+ if (componentName.match("hyperlink") && !element.hasAttribute("href"))
838
+ return false;
839
+
840
+ // If all guard clauses pass, the element is a focusable component
841
+ return true;
842
+ }
843
+
844
+ /**
845
+ * Safely get a numeric tabindex for an element.
846
+ * Returns a number if the tabindex is a valid integer, otherwise null.
847
+ *
848
+ * @param {HTMLElement} element The element whose tabindex to read.
849
+ * @returns {?number} The numeric tabindex or null if missing/invalid.
850
+ */
851
+ function getNumericTabIndex$1(element) {
852
+ const raw = element.getAttribute("tabindex");
853
+ if (raw == null) return null;
854
+
855
+ const value = Number.parseInt(raw, 10);
856
+ return Number.isNaN(value) ? null : value;
857
+ }
858
+
859
+ /**
860
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
861
+ * Returns a unique, ordered array of elements that can receive focus.
862
+ * Also sorts elements with tabindex first, preserving their order.
863
+ *
864
+ * @param {HTMLElement} container The container to search within
865
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
866
+ */
867
+ function getFocusableElements$1(container) {
868
+ // Get elements in DOM order by walking the tree
869
+ const orderedFocusableElements = [];
870
+
871
+ // Define a recursive function to collect focusable elements in DOM order
872
+ const collectFocusableElements = (root) => {
873
+ // Check if current element is focusable
874
+ if (root.nodeType === Node.ELEMENT_NODE) {
875
+ // Check if this is a custom component that is focusable
876
+ const isComponentFocusable = isFocusableComponent$1(root);
877
+
878
+ if (isComponentFocusable) {
879
+ // Add the component itself as a focusable element and don't traverse its shadow DOM
880
+ orderedFocusableElements.push(root);
881
+ return; // Skip traversing inside this component
882
+ }
883
+
884
+ // Check if the element itself matches any selector
885
+ for (const selector of FOCUSABLE_SELECTORS$1) {
886
+ if (root.matches?.(selector)) {
887
+ orderedFocusableElements.push(root);
888
+ break; // Once we know it's focusable, no need to check other selectors
889
+ }
890
+ }
891
+
892
+ // Process shadow DOM only for non-Auro components
893
+ if (root.shadowRoot) {
894
+ // Process shadow DOM children in order
895
+ if (root.shadowRoot.children) {
896
+ Array.from(root.shadowRoot.children).forEach((child) => {
897
+ collectFocusableElements(child);
898
+ });
899
+ }
900
+ }
901
+
902
+ // Process slots and their assigned nodes in order
903
+ if (root.tagName === "SLOT") {
904
+ const assignedNodes = root.assignedNodes({ flatten: true });
905
+ for (const node of assignedNodes) {
906
+ collectFocusableElements(node);
907
+ }
908
+ } else {
909
+ // Process light DOM children in order
910
+ if (root.children) {
911
+ Array.from(root.children).forEach((child) => {
912
+ collectFocusableElements(child);
913
+ });
914
+ }
915
+ }
916
+ }
917
+ };
918
+
919
+ // Start the traversal from the container
920
+ collectFocusableElements(container);
921
+
922
+ // Remove duplicates that might have been collected through different paths
923
+ // while preserving order
924
+ const uniqueElements = [];
925
+ const seen = new Set();
926
+
927
+ for (const element of orderedFocusableElements) {
928
+ if (!seen.has(element)) {
929
+ seen.add(element);
930
+ uniqueElements.push(element);
931
+ }
932
+ }
933
+
934
+ // Move tab-indexed elements to the front while preserving their order
935
+ // This ensures that elements with tabindex are prioritized in the focus order
936
+
937
+ // First extract elements with valid positive tabindex
938
+ const elementsWithTabindex = uniqueElements.filter((el) => {
939
+ const tabindex = getNumericTabIndex$1(el);
940
+ return tabindex !== null && tabindex > 0;
941
+ });
942
+
943
+ // Sort these elements by their tabindex value
944
+ elementsWithTabindex.sort((a, b) => {
945
+ const aIndex = getNumericTabIndex$1(a) ?? 0;
946
+ const bIndex = getNumericTabIndex$1(b) ?? 0;
947
+ return aIndex - bIndex;
948
+ });
949
+
950
+ // Elements without tabindex (preserving their original order)
951
+ const elementsWithoutTabindex = uniqueElements.filter((el) => {
952
+ const tabindex = getNumericTabIndex$1(el);
953
+
954
+ // Elements without tabindex or with tabindex of 0 stay in DOM order
955
+ return tabindex === null || tabindex === 0;
956
+ });
957
+
958
+ // Combine both arrays with tabindex elements first
959
+ const tabIndexedUniqueElements = [
960
+ ...elementsWithTabindex,
961
+ ...elementsWithoutTabindex,
962
+ ];
963
+
964
+ return tabIndexedUniqueElements;
965
+ }
966
+
785
967
  /* eslint-disable no-underscore-dangle */
786
968
 
787
969
  /**
@@ -827,6 +1009,82 @@ function reconcileMenuIndex(menu) {
827
1009
  }
828
1010
  }
829
1011
 
1012
+ /**
1013
+ * Commit the highlighted option and close the bib.
1014
+ * @param {Element} component - The auro-combobox host element.
1015
+ */
1016
+ function selectAndClose(component) {
1017
+ reconcileMenuIndex(component.menu);
1018
+ component.menu.makeSelection();
1019
+ component.hideBib();
1020
+ }
1021
+
1022
+ /**
1023
+ * Whether `el` is currently visible enough to receive focus. Prefers the
1024
+ * browser's `checkVisibility()` (Chrome/Safari; Firefox behind a flag until
1025
+ * recently); otherwise combines a display/visibility check with an
1026
+ * ancestor-hidden probe that keeps position:fixed elements visible.
1027
+ * @param {Element} el - The element to test.
1028
+ * @returns {boolean}
1029
+ */
1030
+ function isVisibleForFocus(el) {
1031
+ if (typeof el.checkVisibility === 'function') {
1032
+ return el.checkVisibility();
1033
+ }
1034
+ const style = el.ownerDocument.defaultView.getComputedStyle(el);
1035
+ if (style.display === 'none' || style.visibility === 'hidden') {
1036
+ return false;
1037
+ }
1038
+ return el.offsetParent !== null || style.position === 'fixed';
1039
+ }
1040
+
1041
+ /**
1042
+ * Return the tab stop that comes before `component` in page tab order,
1043
+ * skipping any hidden entries the walker doesn't filter itself.
1044
+ * @param {Element} component - The auro-combobox host element.
1045
+ * @returns {Element|null}
1046
+ */
1047
+ function getPreviousTabStop(component) {
1048
+ const tabStops = getFocusableElements$1(component.ownerDocument.body);
1049
+ const componentIndex = tabStops.indexOf(component);
1050
+ for (let index = componentIndex - 1; index >= 0; index -= 1) {
1051
+ if (isVisibleForFocus(tabStops[index])) {
1052
+ return tabStops[index];
1053
+ }
1054
+ }
1055
+ return null;
1056
+ }
1057
+
1058
+ /**
1059
+ * Commit the highlighted option, close the bib, and move focus to the tab
1060
+ * stop before the combobox — the Shift+Tab exit path (AB#1592239).
1061
+ * Returns true when this function moved focus and the caller should
1062
+ * preventDefault; false when no previous tab stop was found and the browser's
1063
+ * native traversal should run.
1064
+ * @param {Element} component - The auro-combobox host element.
1065
+ * @returns {boolean}
1066
+ */
1067
+ function selectAndExitBackward(component) {
1068
+ const previousTabStop = getPreviousTabStop(component);
1069
+
1070
+ // Opts this selection out of setClearBtnFocus so the focus move below
1071
+ // isn't clobbered. Consumed by auro-combobox's selection listener; also
1072
+ // cleared via microtask in case makeSelection is a no-op and the listener
1073
+ // never fires (microtasks run before the next user event, so this can't
1074
+ // leak into an unrelated selection).
1075
+ component._suppressClearBtnFocusOnSelection = true;
1076
+ selectAndClose(component);
1077
+ queueMicrotask(() => {
1078
+ component._suppressClearBtnFocusOnSelection = false;
1079
+ });
1080
+
1081
+ if (!previousTabStop) {
1082
+ return false;
1083
+ }
1084
+ doubleRaf(() => previousTabStop.focus());
1085
+ return true;
1086
+ }
1087
+
830
1088
  const comboboxKeyboardStrategy = {
831
1089
  ArrowDown(component, evt, ctx) {
832
1090
  // If the clear button has focus, let the browser handle ArrowDown normally.
@@ -926,18 +1184,17 @@ const comboboxKeyboardStrategy = {
926
1184
 
927
1185
  Tab(component, evt, ctx) {
928
1186
  if (ctx.isExpanded && !isClearBtnFocused(ctx)) {
929
- // When the clear button is focused, Tab events do not bubble out of
930
- // its shadow DOM, so this handler only fires when the clear button
931
- // is NOT focused. In that case, select the active option and close.
932
- reconcileMenuIndex(component.menu);
933
- component.menu.makeSelection();
934
- component.hideBib();
1187
+ if (evt.shiftKey) {
1188
+ if (selectAndExitBackward(component)) {
1189
+ evt.preventDefault();
1190
+ }
1191
+ return;
1192
+ }
1193
+
1194
+ selectAndClose(component);
935
1195
 
936
- // In fullscreen modal mode, closing the dialog does not
937
- // automatically restores focus to the input. In the tab case,
938
- // Explicitly move focus to the trigger's clear button so the
939
- // user can continues tabbing through the page normally.
940
- if (ctx.isModal && !evt.shiftKey) {
1196
+ if (ctx.isModal) {
1197
+ // Fullscreen close does not automatically restore focus to the input.
941
1198
  component.setClearBtnFocus();
942
1199
  }
943
1200
  }
@@ -3196,12 +3453,40 @@ class AuroFloatingUI {
3196
3453
  return;
3197
3454
  }
3198
3455
 
3456
+ // Chrome-specific: during popover top-layer promotion after a click on a
3457
+ // slotted focusable, :focus-within can briefly evaluate false while the
3458
+ // active element is still structurally inside the trigger/bib. Fall back
3459
+ // to a shadow-piercing ancestry walk from the deep active element before
3460
+ // treating this as a real focus loss.
3461
+ try {
3462
+ let active = document.activeElement;
3463
+ while (active && active.shadowRoot && active.shadowRoot.activeElement) {
3464
+ active = active.shadowRoot.activeElement;
3465
+ }
3466
+ const targets = [element, element.trigger, element.bib].filter(Boolean);
3467
+ let node = active;
3468
+ while (node) {
3469
+ if (targets.includes(node)) {
3470
+ return;
3471
+ }
3472
+ node =
3473
+ node.parentElement ||
3474
+ (node.getRootNode && node.getRootNode().host) ||
3475
+ null;
3476
+ }
3477
+ } catch (e) {
3478
+ // Defensive: fall through to the existing close path if traversal fails.
3479
+ }
3480
+
3199
3481
  // if fullscreen bib is in fullscreen mode, do not close
3200
3482
  if (element.bib.hasAttribute("isfullscreen")) {
3201
3483
  return;
3202
3484
  }
3203
3485
 
3204
- this.hideBib("keydown");
3486
+ // eventType "focusloss" distinguishes a Tab/click-driven close from an
3487
+ // Escape keydown close. Consumers use this to decide whether to restore
3488
+ // focus to the trigger (Escape) or let it advance naturally (Tab).
3489
+ this.hideBib("focusloss");
3205
3490
  }
3206
3491
 
3207
3492
  setupHideHandlers() {
@@ -3871,199 +4156,6 @@ function getFocusableElements(container) {
3871
4156
  return tabIndexedUniqueElements;
3872
4157
  }
3873
4158
 
3874
- /**
3875
- * FocusTrap manages keyboard focus within a specified container element, ensuring that focus does not leave the container when tabbing.
3876
- * It is commonly used for modal dialogs or overlays to improve accessibility by trapping focus within interactive UI components.
3877
- */
3878
- class FocusTrap {
3879
- /**
3880
- * Creates a new FocusTrap instance for the given container element.
3881
- * Initializes event listeners and prepares the container for focus management.
3882
- *
3883
- * @param {HTMLElement} container The DOM element to trap focus within.
3884
- * @param {boolean} [controlTabOrder=false] If true enables manual control of the tab order by the FocusTrap.
3885
- * @throws {Error} If the provided container is not a valid HTMLElement.
3886
- */
3887
- constructor(container, controlTabOrder = false) {
3888
- if (!container || !(container instanceof HTMLElement)) {
3889
- throw new Error("FocusTrap requires a valid HTMLElement.");
3890
- }
3891
-
3892
- this.container = container;
3893
- this.tabDirection = "forward"; // or 'backward';
3894
- this.controlTabOrder = controlTabOrder;
3895
-
3896
- this._init();
3897
- }
3898
-
3899
- /**
3900
- * Initializes the focus trap by setting up event listeners and attributes on the container.
3901
- * Prepares the container for focus management, including support for shadow DOM and inert attributes.
3902
- *
3903
- * @private
3904
- */
3905
- _init() {
3906
- // Add inert attribute to prevent focusing programmatically as well (if supported)
3907
- if ("inert" in HTMLElement.prototype) {
3908
- this.container.inert = false; // Ensure the container isn't inert
3909
- this.container.setAttribute("data-focus-trap-container", true); // Mark for identification
3910
- }
3911
-
3912
- // Track tab direction
3913
- this.container.addEventListener("keydown", this._onKeydown);
3914
- }
3915
-
3916
- /**
3917
- * Gets an array of currently active (focused) elements in the document and shadow DOM.
3918
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
3919
- * @private
3920
- */
3921
- _getActiveElements() {
3922
- // Get the active element(s) in the document and shadow root
3923
- // This will include the active element in the shadow DOM if it exists
3924
- // Active element may be inside the shadow DOM depending on delegatesFocus, so we need to check both
3925
- let { activeElement } = document;
3926
- const actives = [activeElement];
3927
- while (activeElement?.shadowRoot?.activeElement) {
3928
- actives.push(activeElement.shadowRoot.activeElement);
3929
- activeElement = activeElement.shadowRoot.activeElement;
3930
- }
3931
- return actives;
3932
- }
3933
-
3934
- /**
3935
- * Gets the next focus index based on the current index and focusable elements.
3936
- * @param {number} currentIndex The current index of the focused element.
3937
- * @param {Array<HTMLElement>} focusables The array of focusable elements.
3938
- * @returns {number|null} The next focus index or null if not determined.
3939
- */
3940
- _getNextFocusIndex(currentIndex, focusables, actives) {
3941
- if (this.controlTabOrder) {
3942
- // Calculate the new index based on the current index and tab direction
3943
- let newFocusIndex =
3944
- currentIndex + (this.tabDirection === "forward" ? 1 : -1);
3945
-
3946
- // Wrap-around logic
3947
- if (newFocusIndex < 0) newFocusIndex = focusables.length - 1;
3948
- if (newFocusIndex >= focusables.length) newFocusIndex = 0;
3949
-
3950
- // Early return with the new index
3951
- return newFocusIndex;
3952
- }
3953
-
3954
- // Determine if we need to wrap
3955
- const atFirst =
3956
- actives.includes(focusables[0]) || actives.includes(this.container);
3957
- const atLast = actives.includes(focusables[focusables.length - 1]);
3958
-
3959
- // Only wrap if at the ends
3960
- if (this.tabDirection === "backward" && atFirst) {
3961
- return focusables.length - 1;
3962
- }
3963
-
3964
- if (this.tabDirection === "forward" && atLast) {
3965
- return 0;
3966
- }
3967
-
3968
- // No wrap, so don't change focus, return early
3969
- return null;
3970
- }
3971
-
3972
- /**
3973
- * Handles the Tab key press event to manage focus within the container.
3974
- * @param {KeyboardEvent} e The keyboard event triggered by the user.
3975
- * @returns {void}
3976
- */
3977
- _handleTabKey(e) {
3978
- // Update the focusable elements
3979
- const focusables = this._getFocusableElements();
3980
-
3981
- // If there are no focusable elements, exit
3982
- if (!focusables.length) return;
3983
-
3984
- // Set the tab direction based on the key pressed
3985
- this.tabDirection = e.shiftKey ? "backward" : "forward";
3986
-
3987
- // Get the active elements that are currently focused
3988
- const actives = this._getActiveElements();
3989
-
3990
- // If we're at either end of the focusable elements, wrap around to the other end
3991
- let focusIndex = focusables.findIndex((el) => actives.includes(el));
3992
-
3993
- // Fallback if we have no focused element
3994
- if (focusIndex === -1) focusIndex = 0;
3995
-
3996
- // Get the next focus index based on the current focus index, tab direction, and controlTabOrder setting
3997
- // Is null if no new focus index is determined
3998
- const newFocusIndex = this._getNextFocusIndex(
3999
- focusIndex,
4000
- focusables,
4001
- actives,
4002
- );
4003
-
4004
- // If we have a new focus index, set focus to that element
4005
- if (newFocusIndex !== null) {
4006
- e.preventDefault();
4007
- focusables[newFocusIndex].focus();
4008
- }
4009
- }
4010
-
4011
- /**
4012
- * Catches the keydown event
4013
- * @param {KeyboardEvent} e The keyboard event triggered by user interaction.
4014
- * @private
4015
- */
4016
- _onKeydown = (e) => {
4017
- // Handle tab
4018
- if (e.key === "Tab") this._handleTabKey(e);
4019
- };
4020
-
4021
- /**
4022
- * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
4023
- * Returns a unique, ordered array of elements that can receive focus.
4024
- *
4025
- * @returns {Array<HTMLElement>} An array of focusable elements within the container.
4026
- * @private
4027
- */
4028
- _getFocusableElements() {
4029
- // Use the imported utility function to get focusable elements
4030
- const elements = getFocusableElements(this.container);
4031
-
4032
- // Return the elements found
4033
- return elements;
4034
- }
4035
-
4036
- /**
4037
- * Moves focus to the first focusable element within the container.
4038
- * Useful for setting initial focus when activating the focus trap.
4039
- */
4040
- focusFirstElement() {
4041
- const focusables = this._getFocusableElements();
4042
- if (focusables.length) focusables[0].focus();
4043
- }
4044
-
4045
- /**
4046
- * Moves focus to the last focusable element within the container.
4047
- * Useful for setting focus when deactivating or cycling focus in reverse.
4048
- */
4049
- focusLastElement() {
4050
- const focusables = this._getFocusableElements();
4051
- if (focusables.length) focusables[focusables.length - 1].focus();
4052
- }
4053
-
4054
- /**
4055
- * Removes event listeners and attributes added by the focus trap.
4056
- * Call this method to clean up when the focus trap is no longer needed.
4057
- */
4058
- disconnect() {
4059
- if (this.container.hasAttribute("data-focus-trap-container")) {
4060
- this.container.removeAttribute("data-focus-trap-container");
4061
- }
4062
-
4063
- this.container.removeEventListener("keydown", this._onKeydown);
4064
- }
4065
- }
4066
-
4067
4159
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
4068
4160
  // See LICENSE in the project root for license information.
4069
4161
 
@@ -4561,7 +4653,7 @@ class AuroDropdownBib extends LitElement {
4561
4653
  classes[`shape-${this.shape}`] = true;
4562
4654
 
4563
4655
  return html`
4564
- <dialog class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
4656
+ <dialog tabindex="-1" class="${classMap(classes)}" part="bibContainer" role="${ifDefined(this.dialogRole)}" aria-labelledby="${ifDefined(this.dialogLabel ? 'dialogLabel' : undefined)}">
4565
4657
  ${this.dialogLabel ? html`<span id="dialogLabel" class="util_displayHiddenVisually">${this.dialogLabel}</span>` : ''}
4566
4658
  <slot></slot>
4567
4659
  <span id="srAnnouncement" class="util_displayHiddenVisually" aria-live="polite" role="status"></span>
@@ -4812,7 +4904,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4812
4904
  }
4813
4905
  };
4814
4906
 
4815
- var formkitVersion$2 = '202607072023';
4907
+ var formkitVersion$2 = '202607102214';
4816
4908
 
4817
4909
  let AuroElement$2 = class AuroElement extends LitElement {
4818
4910
  static get properties() {
@@ -5566,7 +5658,10 @@ class AuroDropdown extends AuroElement$2 {
5566
5658
  }
5567
5659
 
5568
5660
 
5569
- if (!this.isPopoverVisible) {
5661
+ // On Tab-driven close (eventType "focusloss"), let focus advance naturally
5662
+ // — restoring to the trigger would trap the user on this dropdown, forcing
5663
+ // an extra Tab to move on. Escape and outside-click still restore.
5664
+ if (!this.isPopoverVisible && eventType !== "focusloss") {
5570
5665
  // wait til the bib gets fully closed and rendered
5571
5666
  setTimeout(() => {
5572
5667
  // Skip if the bib re-opened, or if focus moved intentionally outside the dropdown (not to body).
@@ -5796,15 +5891,16 @@ class AuroDropdown extends AuroElement$2 {
5796
5891
  }
5797
5892
  });
5798
5893
  } else {
5799
- // Normal desktop: use FocusTrap on the bib element.
5800
- // Defer focusFirstElement to the next frame because the popover
5801
- // is positioned asynchronously by Floating UI — calling focus()
5802
- // synchronously here would target elements with zero dimensions
5803
- // and the focus call would be silently ignored.
5804
- this.focusTrap = new FocusTrap(this.bibContent);
5894
+ // Normal desktop (non-modal): move initial focus into the bib but
5895
+ // don't trap Tab. Tab should exit the bib and let the floater's
5896
+ // handleFocusLoss close it, matching native <select>/<details>
5897
+ // behavior. Deferred one frame because Floating UI positions the
5898
+ // popover asynchronously a synchronous focus() would target
5899
+ // zero-dimension elements and be silently ignored.
5805
5900
  requestAnimationFrame(() => {
5806
- if (this.focusTrap) {
5807
- this.focusTrap.focusFirstElement();
5901
+ const focusables = getFocusableElements(this.bibContent);
5902
+ if (focusables.length) {
5903
+ focusables[0].focus();
5808
5904
  }
5809
5905
  });
5810
5906
  }
@@ -18430,7 +18526,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18430
18526
  }
18431
18527
  };
18432
18528
 
18433
- var formkitVersion$1 = '202607072023';
18529
+ var formkitVersion$1 = '202607102214';
18434
18530
 
18435
18531
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18436
18532
  // See LICENSE in the project root for license information.
@@ -19551,7 +19647,7 @@ class AuroBibtemplate extends LitElement {
19551
19647
  }
19552
19648
  }
19553
19649
 
19554
- var formkitVersion = '202607072023';
19650
+ var formkitVersion = '202607102214';
19555
19651
 
19556
19652
  var styleCss$1 = css`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
19557
19653
 
@@ -20334,6 +20430,8 @@ class AuroCombobox extends AuroElement {
20334
20430
 
20335
20431
  /**
20336
20432
  * If set, the `icon` attribute will be applied to the trigger `auro-input` element.
20433
+ * Icon rendering is currently limited to `type="credit-card"`. Setting `triggerIcon`
20434
+ * without a supported `type` propagates the attribute but does not render a visible icon.
20337
20435
  */
20338
20436
  triggerIcon: {
20339
20437
  type: Boolean,
@@ -20365,7 +20463,7 @@ class AuroCombobox extends AuroElement {
20365
20463
  },
20366
20464
 
20367
20465
  /**
20368
- * Value selected for the dropdown menu.
20466
+ * Value selected for the dropdown menu. When set programmatically or as a preset attribute, the value must match a selectable option. If it matches an option marked `disabled` or `static`, the selection is rejected: `value` and `optionSelected` are cleared to `undefined`. `hidden` options (including those filtered out by type-ahead) remain selectable by value.
20369
20467
  */
20370
20468
  value: {
20371
20469
  type: String
@@ -20861,7 +20959,12 @@ class AuroCombobox extends AuroElement {
20861
20959
  this.dropdown.bibDialogLabel = labelElement.textContent.trim() || undefined;
20862
20960
  }
20863
20961
 
20864
- // Exposes the CSS parts from the bibtemplate for styling
20962
+ if (this.customBibWidth) {
20963
+ this.dropdown.dropdownWidth = this.customBibWidth;
20964
+ }
20965
+
20966
+ // Exposes the CSS parts from the dropdown and bibtemplate for styling
20967
+ this.dropdown.exposeCssParts();
20865
20968
  this.bibtemplate.exposeCssParts();
20866
20969
 
20867
20970
  this.hideBib = this.hideBib.bind(this);
@@ -21081,8 +21184,11 @@ class AuroCombobox extends AuroElement {
21081
21184
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21082
21185
  this.hideBib();
21083
21186
 
21084
- // Move focus to the clear button when the user makes a selection.
21085
- if (!isEcho && this.menu.value !== undefined) {
21187
+ // Move focus to the clear button when the user makes a selection,
21188
+ // unless the Shift+Tab handler opted this selection out.
21189
+ if (this._suppressClearBtnFocusOnSelection) {
21190
+ this._suppressClearBtnFocusOnSelection = false;
21191
+ } else if (!isEcho && this.menu.value !== undefined) {
21086
21192
  this.setClearBtnFocus();
21087
21193
  }
21088
21194
 
@@ -21488,6 +21594,12 @@ class AuroCombobox extends AuroElement {
21488
21594
  this.value = undefined;
21489
21595
  this.typedValue = undefined;
21490
21596
  this.input.value = undefined;
21597
+ // Fullscreen bib mounts a second auro-input that holds the user-facing
21598
+ // native <input> on mobile; clearing only the trigger leaves stale text
21599
+ // there when the bib is reopened after reset.
21600
+ if (this.inputInBib) {
21601
+ this.inputInBib.value = undefined;
21602
+ }
21491
21603
  this.menu.value = undefined;
21492
21604
  this.validation.reset(this);
21493
21605
  this.touched = false;