Compare commits

...

5 Commits

Author SHA1 Message Date
Brandy Smith
cb7436f051 chore: build & lint 2025-07-02 13:32:26 -04:00
Brandy Smith
f1dc0b2b06 fix(utils): update to track pointer instead of keyboard 2025-07-02 13:30:38 -04:00
Brandy Smith
0ded8ba19f fix(utils): update focus visible to work with keydown again 2025-07-02 13:30:38 -04:00
Brandy Carney
b09caeb053 fix(utils): update focus visible to remove keydown approach 2025-07-02 13:30:37 -04:00
Brandy Carney
42c9db52a7 test(angular): add new pages to navigate between lazy and standalone tests 2025-07-02 13:29:42 -04:00

View File

@@ -22,39 +22,54 @@ export interface FocusVisibleUtility {
export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility => {
let currentFocus: Element[] = [];
let keyboardMode = true;
// 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;
};
const pointerDown = () => {
keyboardMode = false;
setFocus([]);
// Do not set focus on pointer interactions
const pointerDown = (ev: Event) => {
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) => {
keyboardMode = FOCUS_KEYS.includes((ev as KeyboardEvent).key);
if (!keyboardMode) {
hadPointerEvent = false;
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) => {
if (keyboardMode && ev.composedPath !== undefined) {
const toFocus = ev.composedPath().filter((el: any) => {
// TODO(FW-2832): type
if (el.classList) {
return el.classList.contains(ION_FOCUSABLE);
}
return false;
}) as Element[];
const target = ev.target as HTMLElement;
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);
}
};
const onFocusout = () => {
if (ref.activeElement === root) {
setFocus([]);
@@ -64,15 +79,13 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
ref.addEventListener('keydown', onKeydown);
ref.addEventListener('focusin', onFocusin);
ref.addEventListener('focusout', onFocusout);
ref.addEventListener('touchstart', pointerDown, { passive: true });
ref.addEventListener('mousedown', pointerDown);
ref.addEventListener('pointerdown', pointerDown, { passive: true });
const destroy = () => {
ref.removeEventListener('keydown', onKeydown);
ref.removeEventListener('focusin', onFocusin);
ref.removeEventListener('focusout', onFocusout);
ref.removeEventListener('touchstart', pointerDown);
ref.removeEventListener('mousedown', pointerDown);
ref.removeEventListener('pointerdown', pointerDown);
};
return {