@aurodesignsystem-dev/auro-formkit 0.0.0-pr1545.0 → 0.0.0-pr1546.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.min.js +276 -33
  7. package/components/combobox/demo/getting-started.min.js +276 -33
  8. package/components/combobox/demo/index.min.js +276 -33
  9. package/components/combobox/demo/keyboard-behavior.md +1 -1
  10. package/components/combobox/dist/auro-combobox.d.ts +1 -0
  11. package/components/combobox/dist/index.js +276 -33
  12. package/components/combobox/dist/registered.js +276 -33
  13. package/components/counter/demo/customize.min.js +2 -2
  14. package/components/counter/demo/index.min.js +2 -2
  15. package/components/counter/dist/index.js +2 -2
  16. package/components/counter/dist/registered.js +2 -2
  17. package/components/datepicker/demo/customize.min.js +3 -3
  18. package/components/datepicker/demo/index.min.js +3 -3
  19. package/components/datepicker/dist/index.js +3 -3
  20. package/components/datepicker/dist/registered.js +3 -3
  21. package/components/dropdown/demo/customize.min.js +1 -1
  22. package/components/dropdown/demo/getting-started.min.js +1 -1
  23. package/components/dropdown/demo/index.min.js +1 -1
  24. package/components/dropdown/dist/index.js +1 -1
  25. package/components/dropdown/dist/registered.js +1 -1
  26. package/components/form/demo/customize.min.js +340 -97
  27. package/components/form/demo/getting-started.min.js +340 -97
  28. package/components/form/demo/index.min.js +340 -97
  29. package/components/form/demo/registerDemoDeps.min.js +340 -97
  30. package/components/input/demo/customize.min.js +1 -1
  31. package/components/input/demo/getting-started.min.js +1 -1
  32. package/components/input/demo/index.min.js +1 -1
  33. package/components/input/dist/index.js +1 -1
  34. package/components/input/dist/registered.js +1 -1
  35. package/components/radio/demo/customize.min.js +1 -1
  36. package/components/radio/demo/getting-started.min.js +1 -1
  37. package/components/radio/demo/index.min.js +1 -1
  38. package/components/radio/dist/index.js +1 -1
  39. package/components/radio/dist/registered.js +1 -1
  40. package/components/select/demo/customize.min.js +2 -2
  41. package/components/select/demo/getting-started.min.js +2 -2
  42. package/components/select/demo/index.min.js +2 -2
  43. package/components/select/dist/index.js +2 -2
  44. package/components/select/dist/registered.js +2 -2
  45. package/custom-elements.json +1507 -1507
  46. package/package.json +1 -1
@@ -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.
@@ -925,36 +1183,18 @@ const comboboxKeyboardStrategy = {
925
1183
  },
926
1184
 
927
1185
  Tab(component, evt, ctx) {
928
- // Runs for both Tab and Shift+Tab.
929
- //
930
- // Current behavior:
931
- // Tab — select the active option, close the bib, and (in fullscreen
932
- // modal mode only) explicitly move focus to the trigger's
933
- // clear button. In desktop popover mode the browser's native
934
- // tab traversal takes focus forward from the input.
935
- // Shift+Tab — select the active option and close the bib. Focus then
936
- // lands on the trigger's clear button as a byproduct of the
937
- // shadow-DOM tab order, so keyboard users must press
938
- // Shift+Tab three times to exit the component (clear button
939
- // → input → previous element on the page).
940
- //
941
- // Intended behavior for Shift+Tab (per team decision, tracked in
942
- // AB#1590650): a single Shift+Tab should select the active option, close
943
- // the bib, and move focus directly to the previous focusable element on
944
- // the page — symmetric with Tab.
945
1186
  if (ctx.isExpanded && !isClearBtnFocused(ctx)) {
946
- // When the clear button is focused, Tab events do not bubble out of
947
- // its shadow DOM, so this handler only fires when the clear button
948
- // is NOT focused. In that case, select the active option and close.
949
- reconcileMenuIndex(component.menu);
950
- component.menu.makeSelection();
951
- component.hideBib();
1187
+ if (evt.shiftKey) {
1188
+ if (selectAndExitBackward(component)) {
1189
+ evt.preventDefault();
1190
+ }
1191
+ return;
1192
+ }
1193
+
1194
+ selectAndClose(component);
952
1195
 
953
- // In fullscreen modal mode, closing the dialog does not
954
- // automatically restores focus to the input. In the tab case,
955
- // Explicitly move focus to the trigger's clear button so the
956
- // user can continues tabbing through the page normally.
957
- if (ctx.isModal && !evt.shiftKey) {
1196
+ if (ctx.isModal) {
1197
+ // Fullscreen close does not automatically restore focus to the input.
958
1198
  component.setClearBtnFocus();
959
1199
  }
960
1200
  }
@@ -4829,7 +5069,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
4829
5069
  }
4830
5070
  };
4831
5071
 
4832
- var formkitVersion$2 = '202607082244';
5072
+ var formkitVersion$2 = '202607090129';
4833
5073
 
4834
5074
  let AuroElement$2 = class AuroElement extends LitElement {
4835
5075
  static get properties() {
@@ -18447,7 +18687,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
18447
18687
  }
18448
18688
  };
18449
18689
 
18450
- var formkitVersion$1 = '202607082244';
18690
+ var formkitVersion$1 = '202607090129';
18451
18691
 
18452
18692
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18453
18693
  // See LICENSE in the project root for license information.
@@ -19568,7 +19808,7 @@ class AuroBibtemplate extends LitElement {
19568
19808
  }
19569
19809
  }
19570
19810
 
19571
- var formkitVersion = '202607082244';
19811
+ var formkitVersion = '202607090129';
19572
19812
 
19573
19813
  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}`;
19574
19814
 
@@ -21100,8 +21340,11 @@ class AuroCombobox extends AuroElement {
21100
21340
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21101
21341
  this.hideBib();
21102
21342
 
21103
- // Move focus to the clear button when the user makes a selection.
21104
- if (!isEcho && this.menu.value !== undefined) {
21343
+ // Move focus to the clear button when the user makes a selection,
21344
+ // unless the Shift+Tab handler opted this selection out.
21345
+ if (this._suppressClearBtnFocusOnSelection) {
21346
+ this._suppressClearBtnFocusOnSelection = false;
21347
+ } else if (!isEcho && this.menu.value !== undefined) {
21105
21348
  this.setClearBtnFocus();
21106
21349
  }
21107
21350