@aurodesignsystem-dev/auro-formkit 0.0.0-pr1545.1 → 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 +195 -195
  46. package/package.json +1 -1
@@ -840,6 +840,188 @@ function navigateArrow(component, direction, options = {}) {
840
840
  }
841
841
  }
842
842
 
843
+ // Selectors for focusable elements
844
+ const FOCUSABLE_SELECTORS$1 = [
845
+ "a[href]",
846
+ "button:not([disabled])",
847
+ "textarea:not([disabled])",
848
+ "input:not([disabled])",
849
+ "select:not([disabled])",
850
+ '[role="tab"]:not([disabled])',
851
+ '[role="link"]:not([disabled])',
852
+ '[role="button"]:not([disabled])',
853
+ '[tabindex]:not([tabindex="-1"])',
854
+ '[contenteditable]:not([contenteditable="false"])',
855
+ ];
856
+
857
+ // List of custom components that are known to be focusable
858
+ const FOCUSABLE_COMPONENTS$1 = [
859
+ "auro-checkbox",
860
+ "auro-radio",
861
+ "auro-dropdown",
862
+ "auro-button",
863
+ "auro-combobox",
864
+ "auro-input",
865
+ "auro-counter",
866
+ // 'auro-menu', // Auro menu is not focusable by default, it uses a different interaction model
867
+ "auro-select",
868
+ "auro-datepicker",
869
+ "auro-hyperlink",
870
+ "auro-accordion",
871
+ ];
872
+
873
+ /**
874
+ * Determines if a given element is a custom focusable component.
875
+ * Returns true if the element matches a known focusable component and is not disabled.
876
+ *
877
+ * @param {HTMLElement} element The element to check for focusability.
878
+ * @returns {boolean} True if the element is a focusable custom component, false otherwise.
879
+ */
880
+ function isFocusableComponent$1(element) {
881
+ const componentName = element.tagName.toLowerCase();
882
+
883
+ // Guard Clause: Element is a focusable component
884
+ if (
885
+ !FOCUSABLE_COMPONENTS$1.some(
886
+ (name) => element.hasAttribute(name) || componentName === name,
887
+ )
888
+ )
889
+ return false;
890
+
891
+ // Guard Clause: Element is not disabled
892
+ if (element.hasAttribute("disabled")) return false;
893
+
894
+ // Guard Clause: The element is a hyperlink and has no href attribute
895
+ if (componentName.match("hyperlink") && !element.hasAttribute("href"))
896
+ return false;
897
+
898
+ // If all guard clauses pass, the element is a focusable component
899
+ return true;
900
+ }
901
+
902
+ /**
903
+ * Safely get a numeric tabindex for an element.
904
+ * Returns a number if the tabindex is a valid integer, otherwise null.
905
+ *
906
+ * @param {HTMLElement} element The element whose tabindex to read.
907
+ * @returns {?number} The numeric tabindex or null if missing/invalid.
908
+ */
909
+ function getNumericTabIndex$1(element) {
910
+ const raw = element.getAttribute("tabindex");
911
+ if (raw == null) return null;
912
+
913
+ const value = Number.parseInt(raw, 10);
914
+ return Number.isNaN(value) ? null : value;
915
+ }
916
+
917
+ /**
918
+ * Retrieves all focusable elements within the container in DOM order, including those in shadow DOM and slots.
919
+ * Returns a unique, ordered array of elements that can receive focus.
920
+ * Also sorts elements with tabindex first, preserving their order.
921
+ *
922
+ * @param {HTMLElement} container The container to search within
923
+ * @returns {Array<HTMLElement>} An array of focusable elements within the container.
924
+ */
925
+ function getFocusableElements$1(container) {
926
+ // Get elements in DOM order by walking the tree
927
+ const orderedFocusableElements = [];
928
+
929
+ // Define a recursive function to collect focusable elements in DOM order
930
+ const collectFocusableElements = (root) => {
931
+ // Check if current element is focusable
932
+ if (root.nodeType === Node.ELEMENT_NODE) {
933
+ // Check if this is a custom component that is focusable
934
+ const isComponentFocusable = isFocusableComponent$1(root);
935
+
936
+ if (isComponentFocusable) {
937
+ // Add the component itself as a focusable element and don't traverse its shadow DOM
938
+ orderedFocusableElements.push(root);
939
+ return; // Skip traversing inside this component
940
+ }
941
+
942
+ // Check if the element itself matches any selector
943
+ for (const selector of FOCUSABLE_SELECTORS$1) {
944
+ if (root.matches?.(selector)) {
945
+ orderedFocusableElements.push(root);
946
+ break; // Once we know it's focusable, no need to check other selectors
947
+ }
948
+ }
949
+
950
+ // Process shadow DOM only for non-Auro components
951
+ if (root.shadowRoot) {
952
+ // Process shadow DOM children in order
953
+ if (root.shadowRoot.children) {
954
+ Array.from(root.shadowRoot.children).forEach((child) => {
955
+ collectFocusableElements(child);
956
+ });
957
+ }
958
+ }
959
+
960
+ // Process slots and their assigned nodes in order
961
+ if (root.tagName === "SLOT") {
962
+ const assignedNodes = root.assignedNodes({ flatten: true });
963
+ for (const node of assignedNodes) {
964
+ collectFocusableElements(node);
965
+ }
966
+ } else {
967
+ // Process light DOM children in order
968
+ if (root.children) {
969
+ Array.from(root.children).forEach((child) => {
970
+ collectFocusableElements(child);
971
+ });
972
+ }
973
+ }
974
+ }
975
+ };
976
+
977
+ // Start the traversal from the container
978
+ collectFocusableElements(container);
979
+
980
+ // Remove duplicates that might have been collected through different paths
981
+ // while preserving order
982
+ const uniqueElements = [];
983
+ const seen = new Set();
984
+
985
+ for (const element of orderedFocusableElements) {
986
+ if (!seen.has(element)) {
987
+ seen.add(element);
988
+ uniqueElements.push(element);
989
+ }
990
+ }
991
+
992
+ // Move tab-indexed elements to the front while preserving their order
993
+ // This ensures that elements with tabindex are prioritized in the focus order
994
+
995
+ // First extract elements with valid positive tabindex
996
+ const elementsWithTabindex = uniqueElements.filter((el) => {
997
+ const tabindex = getNumericTabIndex$1(el);
998
+ return tabindex !== null && tabindex > 0;
999
+ });
1000
+
1001
+ // Sort these elements by their tabindex value
1002
+ elementsWithTabindex.sort((a, b) => {
1003
+ const aIndex = getNumericTabIndex$1(a) ?? 0;
1004
+ const bIndex = getNumericTabIndex$1(b) ?? 0;
1005
+ return aIndex - bIndex;
1006
+ });
1007
+
1008
+ // Elements without tabindex (preserving their original order)
1009
+ const elementsWithoutTabindex = uniqueElements.filter((el) => {
1010
+ const tabindex = getNumericTabIndex$1(el);
1011
+
1012
+ // Elements without tabindex or with tabindex of 0 stay in DOM order
1013
+ return tabindex === null || tabindex === 0;
1014
+ });
1015
+
1016
+ // Combine both arrays with tabindex elements first
1017
+ const tabIndexedUniqueElements = [
1018
+ ...elementsWithTabindex,
1019
+ ...elementsWithoutTabindex,
1020
+ ];
1021
+
1022
+ return tabIndexedUniqueElements;
1023
+ }
1024
+
843
1025
  /* eslint-disable no-underscore-dangle */
844
1026
 
845
1027
  /**
@@ -885,6 +1067,82 @@ function reconcileMenuIndex(menu) {
885
1067
  }
886
1068
  }
887
1069
 
1070
+ /**
1071
+ * Commit the highlighted option and close the bib.
1072
+ * @param {Element} component - The auro-combobox host element.
1073
+ */
1074
+ function selectAndClose(component) {
1075
+ reconcileMenuIndex(component.menu);
1076
+ component.menu.makeSelection();
1077
+ component.hideBib();
1078
+ }
1079
+
1080
+ /**
1081
+ * Whether `el` is currently visible enough to receive focus. Prefers the
1082
+ * browser's `checkVisibility()` (Chrome/Safari; Firefox behind a flag until
1083
+ * recently); otherwise combines a display/visibility check with an
1084
+ * ancestor-hidden probe that keeps position:fixed elements visible.
1085
+ * @param {Element} el - The element to test.
1086
+ * @returns {boolean}
1087
+ */
1088
+ function isVisibleForFocus(el) {
1089
+ if (typeof el.checkVisibility === 'function') {
1090
+ return el.checkVisibility();
1091
+ }
1092
+ const style = el.ownerDocument.defaultView.getComputedStyle(el);
1093
+ if (style.display === 'none' || style.visibility === 'hidden') {
1094
+ return false;
1095
+ }
1096
+ return el.offsetParent !== null || style.position === 'fixed';
1097
+ }
1098
+
1099
+ /**
1100
+ * Return the tab stop that comes before `component` in page tab order,
1101
+ * skipping any hidden entries the walker doesn't filter itself.
1102
+ * @param {Element} component - The auro-combobox host element.
1103
+ * @returns {Element|null}
1104
+ */
1105
+ function getPreviousTabStop(component) {
1106
+ const tabStops = getFocusableElements$1(component.ownerDocument.body);
1107
+ const componentIndex = tabStops.indexOf(component);
1108
+ for (let index = componentIndex - 1; index >= 0; index -= 1) {
1109
+ if (isVisibleForFocus(tabStops[index])) {
1110
+ return tabStops[index];
1111
+ }
1112
+ }
1113
+ return null;
1114
+ }
1115
+
1116
+ /**
1117
+ * Commit the highlighted option, close the bib, and move focus to the tab
1118
+ * stop before the combobox — the Shift+Tab exit path (AB#1592239).
1119
+ * Returns true when this function moved focus and the caller should
1120
+ * preventDefault; false when no previous tab stop was found and the browser's
1121
+ * native traversal should run.
1122
+ * @param {Element} component - The auro-combobox host element.
1123
+ * @returns {boolean}
1124
+ */
1125
+ function selectAndExitBackward(component) {
1126
+ const previousTabStop = getPreviousTabStop(component);
1127
+
1128
+ // Opts this selection out of setClearBtnFocus so the focus move below
1129
+ // isn't clobbered. Consumed by auro-combobox's selection listener; also
1130
+ // cleared via microtask in case makeSelection is a no-op and the listener
1131
+ // never fires (microtasks run before the next user event, so this can't
1132
+ // leak into an unrelated selection).
1133
+ component._suppressClearBtnFocusOnSelection = true;
1134
+ selectAndClose(component);
1135
+ queueMicrotask(() => {
1136
+ component._suppressClearBtnFocusOnSelection = false;
1137
+ });
1138
+
1139
+ if (!previousTabStop) {
1140
+ return false;
1141
+ }
1142
+ doubleRaf(() => previousTabStop.focus());
1143
+ return true;
1144
+ }
1145
+
888
1146
  const comboboxKeyboardStrategy = {
889
1147
  ArrowDown(component, evt, ctx) {
890
1148
  // If the clear button has focus, let the browser handle ArrowDown normally.
@@ -983,36 +1241,18 @@ const comboboxKeyboardStrategy = {
983
1241
  },
984
1242
 
985
1243
  Tab(component, evt, ctx) {
986
- // Runs for both Tab and Shift+Tab.
987
- //
988
- // Current behavior:
989
- // Tab — select the active option, close the bib, and (in fullscreen
990
- // modal mode only) explicitly move focus to the trigger's
991
- // clear button. In desktop popover mode the browser's native
992
- // tab traversal takes focus forward from the input.
993
- // Shift+Tab — select the active option and close the bib. Focus then
994
- // lands on the trigger's clear button as a byproduct of the
995
- // shadow-DOM tab order, so keyboard users must press
996
- // Shift+Tab three times to exit the component (clear button
997
- // → input → previous element on the page).
998
- //
999
- // Intended behavior for Shift+Tab (per team decision, tracked in
1000
- // AB#1590650): a single Shift+Tab should select the active option, close
1001
- // the bib, and move focus directly to the previous focusable element on
1002
- // the page — symmetric with Tab.
1003
1244
  if (ctx.isExpanded && !isClearBtnFocused(ctx)) {
1004
- // When the clear button is focused, Tab events do not bubble out of
1005
- // its shadow DOM, so this handler only fires when the clear button
1006
- // is NOT focused. In that case, select the active option and close.
1007
- reconcileMenuIndex(component.menu);
1008
- component.menu.makeSelection();
1009
- component.hideBib();
1245
+ if (evt.shiftKey) {
1246
+ if (selectAndExitBackward(component)) {
1247
+ evt.preventDefault();
1248
+ }
1249
+ return;
1250
+ }
1251
+
1252
+ selectAndClose(component);
1010
1253
 
1011
- // In fullscreen modal mode, closing the dialog does not
1012
- // automatically restores focus to the input. In the tab case,
1013
- // Explicitly move focus to the trigger's clear button so the
1014
- // user can continues tabbing through the page normally.
1015
- if (ctx.isModal && !evt.shiftKey) {
1254
+ if (ctx.isModal) {
1255
+ // Fullscreen close does not automatically restore focus to the input.
1016
1256
  component.setClearBtnFocus();
1017
1257
  }
1018
1258
  }
@@ -4911,7 +5151,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
4911
5151
  }
4912
5152
  };
4913
5153
 
4914
- var formkitVersion$2 = '202607082302';
5154
+ var formkitVersion$2 = '202607090129';
4915
5155
 
4916
5156
  let AuroElement$2 = class AuroElement extends i$3 {
4917
5157
  static get properties() {
@@ -18536,7 +18776,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
18536
18776
  }
18537
18777
  };
18538
18778
 
18539
- var formkitVersion$1 = '202607082302';
18779
+ var formkitVersion$1 = '202607090129';
18540
18780
 
18541
18781
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
18542
18782
  // See LICENSE in the project root for license information.
@@ -19657,7 +19897,7 @@ class AuroBibtemplate extends i$3 {
19657
19897
  }
19658
19898
  }
19659
19899
 
19660
- var formkitVersion = '202607082302';
19900
+ var formkitVersion = '202607090129';
19661
19901
 
19662
19902
  var styleCss$3 = i$6`.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}`;
19663
19903
 
@@ -21189,8 +21429,11 @@ class AuroCombobox extends AuroElement {
21189
21429
  // do not close while typing in suggestion mode with no value selected, to allow freeform input
21190
21430
  this.hideBib();
21191
21431
 
21192
- // Move focus to the clear button when the user makes a selection.
21193
- if (!isEcho && this.menu.value !== undefined) {
21432
+ // Move focus to the clear button when the user makes a selection,
21433
+ // unless the Shift+Tab handler opted this selection out.
21434
+ if (this._suppressClearBtnFocusOnSelection) {
21435
+ this._suppressClearBtnFocusOnSelection = false;
21436
+ } else if (!isEcho && this.menu.value !== undefined) {
21194
21437
  this.setClearBtnFocus();
21195
21438
  }
21196
21439
 
@@ -175,7 +175,7 @@
175
175
  </div>
176
176
  </td>
177
177
  <td>
178
- The current <code>focused</code> option is selected, the bib is closed and <strong>focus</strong> is moved to the <strong>clear button</strong> in the component trigger. A subsequent Shift+Tab moves focus back to the input, and another Shift+Tab exits the component to the previous focusable element on the page.
178
+ The current <code>focused</code> option is selected, the bib is closed and <strong>focus</strong> is moved to the previous focusable element on the page.
179
179
  </td>
180
180
  </tr>
181
181
  </tbody>
@@ -507,6 +507,7 @@ export class AuroCombobox extends AuroElement {
507
507
  defaultMenuShape: string | null | undefined;
508
508
  _pendingMenuValueSync: boolean | undefined;
509
509
  value: any;
510
+ _suppressClearBtnFocusOnSelection: boolean | undefined;
510
511
  _programmaticFilterRefresh: boolean | undefined;
511
512
  /**
512
513
  * Binds all behavior needed to the input after rendering.