diff --git a/core/src/utils/focus-visible.ts b/core/src/utils/focus-visible.ts index f1411e6996..91284109c6 100644 --- a/core/src/utils/focus-visible.ts +++ b/core/src/utils/focus-visible.ts @@ -22,51 +22,49 @@ export interface FocusVisibleUtility { export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => { let currentFocus: Element[] = []; - let keyboardMode = false; - let lastPointerType: string | null = null; + // Tracks if the last interaction was a pointer event (mouse, touch, pen) + // Used to distinguish between pointer and keyboard navigation for focus styling + let hadPointerEvent = false; const ref = rootEl ? rootEl.shadowRoot! : document; const root = rootEl ? rootEl : document.body; + // Adds or removes the focused class for styling const setFocus = (elements: Element[]) => { currentFocus.forEach((el) => el.classList.remove(ION_FOCUSED)); elements.forEach((el) => el.classList.add(ION_FOCUSED)); currentFocus = elements; }; + // Do not set focus on pointer interactions const pointerDown = (ev: Event) => { - const pointerEvent = ev as PointerEvent; - lastPointerType = pointerEvent.pointerType; - keyboardMode = false; - setFocus([]); + if (ev instanceof PointerEvent && ev.pointerType !== '') { + hadPointerEvent = true; + // Reset after the event loop so only the immediate focusin is suppressed + setTimeout(() => { hadPointerEvent = false; }, 0); + } }; + // Clear hadPointerEvent so keyboard navigation shows focus + // Also, clear focus if the key is not a navigation key 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; + hadPointerEvent = false; - // If it's not a focus key, clear focus immediately + const keyboardEvent = ev as KeyboardEvent; if (!FOCUS_KEYS.includes(keyboardEvent.key)) { setFocus([]); } }; + // Set focus if the last interaction was NOT a pointer event + // This works around iOS/Safari bugs where keydown is not fired for Tab 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); - } + if (target.classList.contains(ION_FOCUSABLE) && !hadPointerEvent) { + const toFocus = ev.composedPath().filter((el): el is HTMLElement => + el instanceof HTMLElement && el.classList.contains(ION_FOCUSABLE) + ); + setFocus(toFocus); } }; @@ -80,16 +78,12 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => 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); }; return {