fix(utils): update focus visible to work with keydown again

This commit is contained in:
Brandy Smith
2025-07-01 18:53:03 -04:00
parent b09caeb053
commit 0ded8ba19f

View File

@@ -1,5 +1,19 @@
const ION_FOCUSED = 'ion-focused';
const ION_FOCUSABLE = 'ion-focusable';
const FOCUS_KEYS = [
'Tab',
'ArrowDown',
'Space',
'Escape',
' ',
'Shift',
'Enter',
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'Home',
'End',
];
export interface FocusVisibleUtility {
destroy: () => void;
@@ -8,6 +22,8 @@ export interface FocusVisibleUtility {
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
let currentFocus: Element[] = [];
let keyboardMode = false;
let lastPointerType: string | null = null;
const ref = rootEl ? rootEl.shadowRoot! : document;
const root = rootEl ? rootEl : document.body;
@@ -18,16 +34,40 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
currentFocus = elements;
};
const pointerDown = () => {
const pointerDown = (ev: Event) => {
const pointerEvent = ev as PointerEvent;
lastPointerType = pointerEvent.pointerType;
keyboardMode = false;
setFocus([]);
};
const onFocusin = (ev: Event) => {
const toFocus = ev.composedPath().filter((el): el is HTMLElement => {
return el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE);
});
const onKeydown = (ev: Event) => {
const keyboardEvent = ev as KeyboardEvent;
// Always set keyboard mode to true when any key is pressed
// This handles the WebKit Tab key bug where keydown might not fire
keyboardMode = true;
setFocus(toFocus);
// If it's not a focus key, clear focus immediately
if (!FOCUS_KEYS.includes(keyboardEvent.key)) {
setFocus([]);
}
};
const onFocusin = (ev: Event) => {
// Check if this focus event is likely from keyboard navigation
// We can detect this by checking if there was a recent keydown event
// or if the focus target is a focusable element that typically gets focus via keyboard
const target = ev.target as HTMLElement;
if (target.classList.contains(ION_FOCUSABLE)) {
// If we're in keyboard mode or this looks like keyboard navigation
if (keyboardMode || !lastPointerType) {
const toFocus = ev.composedPath().filter((el): el is HTMLElement => {
return el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE);
});
setFocus(toFocus);
}
}
};
const onFocusout = () => {
@@ -36,14 +76,18 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
}
};
ref.addEventListener('keydown', onKeydown);
ref.addEventListener('focusin', onFocusin);
ref.addEventListener('focusout', onFocusout);
ref.addEventListener('pointerdown', pointerDown, { passive: true });
ref.addEventListener('touchstart', pointerDown, { passive: true });
ref.addEventListener('mousedown', pointerDown);
const destroy = () => {
ref.removeEventListener('keydown', onKeydown);
ref.removeEventListener('focusin', onFocusin);
ref.removeEventListener('focusout', onFocusout);
ref.removeEventListener('pointerdown', pointerDown);
ref.removeEventListener('touchstart', pointerDown);
ref.removeEventListener('mousedown', pointerDown);
};