Skip to content

Commit a42e1a2

Browse files
committed
fix(utils): update focus visible to work with keydown again
1 parent f38a078 commit a42e1a2

File tree

1 file changed

+49
-5
lines changed

1 file changed

+49
-5
lines changed

core/src/utils/focus-visible.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
const ION_FOCUSED = 'ion-focused';
22
const ION_FOCUSABLE = 'ion-focusable';
3+
const FOCUS_KEYS = [
4+
'Tab',
5+
'ArrowDown',
6+
'Space',
7+
'Escape',
8+
' ',
9+
'Shift',
10+
'Enter',
11+
'ArrowLeft',
12+
'ArrowRight',
13+
'ArrowUp',
14+
'Home',
15+
'End',
16+
];
317

418
export interface FocusVisibleUtility {
519
destroy: () => void;
@@ -8,6 +22,8 @@ export interface FocusVisibleUtility {
822

923
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
1024
let currentFocus: Element[] = [];
25+
let keyboardMode = false;
26+
let lastPointerType: string | null = null;
1127

1228
const ref = rootEl ? rootEl.shadowRoot! : document;
1329
const root = rootEl ? rootEl : document.body;
@@ -18,16 +34,40 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
1834
currentFocus = elements;
1935
};
2036

21-
const pointerDown = () => {
37+
const pointerDown = (ev: Event) => {
38+
const pointerEvent = ev as PointerEvent;
39+
lastPointerType = pointerEvent.pointerType;
40+
keyboardMode = false;
2241
setFocus([]);
2342
};
2443

44+
const onKeydown = (ev: Event) => {
45+
const keyboardEvent = ev as KeyboardEvent;
46+
// Always set keyboard mode to true when any key is pressed
47+
// This handles the WebKit Tab key bug where keydown might not fire
48+
keyboardMode = true;
49+
50+
// If it's not a focus key, clear focus immediately
51+
if (!FOCUS_KEYS.includes(keyboardEvent.key)) {
52+
setFocus([]);
53+
}
54+
};
55+
2556
const onFocusin = (ev: Event) => {
26-
const toFocus = ev.composedPath().filter((el): el is HTMLElement => {
27-
return el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE);
28-
});
57+
// Check if this focus event is likely from keyboard navigation
58+
// We can detect this by checking if there was a recent keydown event
59+
// or if the focus target is a focusable element that typically gets focus via keyboard
60+
const target = ev.target as HTMLElement;
2961

30-
setFocus(toFocus);
62+
if (target.classList.contains(ION_FOCUSABLE)) {
63+
// If we're in keyboard mode or this looks like keyboard navigation
64+
if (keyboardMode || !lastPointerType) {
65+
const toFocus = ev.composedPath().filter((el): el is HTMLElement => {
66+
return el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE);
67+
});
68+
setFocus(toFocus);
69+
}
70+
}
3171
};
3272

3373
const onFocusout = () => {
@@ -36,14 +76,18 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
3676
}
3777
};
3878

79+
ref.addEventListener('keydown', onKeydown);
3980
ref.addEventListener('focusin', onFocusin);
4081
ref.addEventListener('focusout', onFocusout);
82+
ref.addEventListener('pointerdown', pointerDown, { passive: true });
4183
ref.addEventListener('touchstart', pointerDown, { passive: true });
4284
ref.addEventListener('mousedown', pointerDown);
4385

4486
const destroy = () => {
87+
ref.removeEventListener('keydown', onKeydown);
4588
ref.removeEventListener('focusin', onFocusin);
4689
ref.removeEventListener('focusout', onFocusout);
90+
ref.removeEventListener('pointerdown', pointerDown);
4791
ref.removeEventListener('touchstart', pointerDown);
4892
ref.removeEventListener('mousedown', pointerDown);
4993
};

0 commit comments

Comments
 (0)