@aurodesignsystem-dev/auro-formkit 0.0.0-pr1540.1 → 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.
- package/components/checkbox/demo/accessibility.md +1 -1
- package/components/checkbox/demo/customize.min.js +1 -1
- package/components/checkbox/demo/getting-started.min.js +1 -1
- package/components/checkbox/demo/index.min.js +1 -1
- package/components/checkbox/dist/index.js +1 -1
- package/components/checkbox/dist/registered.js +1 -1
- package/components/combobox/demo/accessibility.md +1 -1
- package/components/combobox/demo/api.md +2 -2
- package/components/combobox/demo/customize.md +54 -2
- package/components/combobox/demo/customize.min.js +421 -240
- package/components/combobox/demo/getting-started.min.js +421 -240
- package/components/combobox/demo/index.md +1 -0
- package/components/combobox/demo/index.min.js +421 -240
- package/components/combobox/demo/keyboard-behavior.md +1 -4
- package/components/combobox/dist/auro-combobox.d.ts +4 -1
- package/components/combobox/dist/index.js +334 -222
- package/components/combobox/dist/registered.js +334 -222
- package/components/counter/demo/customize.min.js +59 -222
- package/components/counter/demo/index.min.js +59 -222
- package/components/counter/dist/auro-counter.d.ts +0 -8
- package/components/counter/dist/index.js +59 -222
- package/components/counter/dist/registered.js +59 -222
- package/components/datepicker/demo/accessibility.md +1 -1
- package/components/datepicker/demo/customize.md +30 -1
- package/components/datepicker/demo/customize.min.js +84 -220
- package/components/datepicker/demo/getting-started.md +118 -2
- package/components/datepicker/demo/index.min.js +84 -220
- package/components/datepicker/dist/index.js +84 -220
- package/components/datepicker/dist/registered.js +84 -220
- package/components/dropdown/demo/customize.html +3 -0
- package/components/dropdown/demo/customize.min.js +55 -206
- package/components/dropdown/demo/getting-started.min.js +44 -205
- package/components/dropdown/demo/index.min.js +44 -205
- package/components/dropdown/dist/index.js +44 -205
- package/components/dropdown/dist/registered.js +44 -205
- package/components/form/demo/customize.min.js +670 -945
- package/components/form/demo/getting-started.min.js +670 -945
- package/components/form/demo/index.min.js +670 -945
- package/components/form/demo/registerDemoDeps.min.js +670 -945
- package/components/input/demo/accessibility.md +1 -1
- package/components/input/demo/customize.min.js +1 -1
- package/components/input/demo/getting-started.min.js +1 -1
- package/components/input/demo/index.min.js +1 -1
- package/components/input/dist/index.js +1 -1
- package/components/input/dist/registered.js +1 -1
- package/components/menu/demo/accessibility.md +9 -4
- package/components/menu/demo/api.md +26 -19
- package/components/menu/demo/css-only.md +26 -19
- package/components/menu/demo/customize.md +307 -47
- package/components/menu/demo/design.md +1 -1
- package/components/menu/demo/getting-started.md +144 -7
- package/components/menu/demo/index.min.js +214 -19
- package/components/menu/demo/keyboard-behavior.md +83 -4
- package/components/menu/demo/voiceover.md +21 -14
- package/components/menu/demo/why-menu.md +8 -9
- package/components/menu/dist/auro-menu-utils.d.ts +19 -0
- package/components/menu/dist/auro-menu.d.ts +4 -8
- package/components/menu/dist/auro-menuoption.d.ts +12 -0
- package/components/menu/dist/index.js +87 -18
- package/components/menu/dist/registered.js +87 -18
- package/components/radio/demo/accessibility.md +1 -1
- package/components/radio/demo/customize.min.js +1 -1
- package/components/radio/demo/getting-started.min.js +1 -1
- package/components/radio/demo/index.min.js +1 -1
- package/components/radio/dist/index.js +1 -1
- package/components/radio/dist/registered.js +1 -1
- package/components/select/demo/accessibility.md +6 -1
- package/components/select/demo/api.md +2 -2
- package/components/select/demo/customize.md +6 -3
- package/components/select/demo/customize.min.js +139 -227
- package/components/select/demo/design.md +10 -10
- package/components/select/demo/getting-started.md +1 -1
- package/components/select/demo/getting-started.min.js +139 -227
- package/components/select/demo/index.md +2 -2
- package/components/select/demo/index.min.js +139 -227
- package/components/select/demo/keyboard-behavior.md +53 -58
- package/components/select/demo/voiceover.md +28 -15
- package/components/select/dist/auro-select.d.ts +6 -2
- package/components/select/dist/index.js +52 -209
- package/components/select/dist/registered.js +52 -209
- package/custom-elements.json +1611 -1552
- 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
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1187
|
+
if (evt.shiftKey) {
|
|
1188
|
+
if (selectAndExitBackward(component)) {
|
|
1189
|
+
evt.preventDefault();
|
|
1190
|
+
}
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
selectAndClose(component);
|
|
935
1195
|
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
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 = '
|
|
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
|
-
|
|
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:
|
|
5800
|
-
//
|
|
5801
|
-
//
|
|
5802
|
-
//
|
|
5803
|
-
//
|
|
5804
|
-
|
|
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
|
-
|
|
5807
|
-
|
|
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 = '
|
|
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 = '
|
|
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
|
-
|
|
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
|
-
|
|
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;
|