From df0340075cffd20196744fa7188fb517c1ce1480 Mon Sep 17 00:00:00 2001 From: Daniel Lu Date: Mon, 30 Mar 2026 16:54:59 -0700 Subject: [PATCH] fix: prevent double focus rings in table when clicking on expand buttons include visually hidden elements in preventFocus so we get the right target the browser tries to focus. this allows us to properly refocus the original element when the user presses on a preventFocusOnPress element, fixing the broken flow that useFocusRing and useFocus had when trying to update the focused state of a row when focus moves due to a click on the expand button. --- packages/react-aria/src/interactions/utils.ts | 2 +- packages/react-aria/src/utils/isFocusable.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-aria/src/interactions/utils.ts b/packages/react-aria/src/interactions/utils.ts index 54d10330d02..82d92ed3a9b 100644 --- a/packages/react-aria/src/interactions/utils.ts +++ b/packages/react-aria/src/interactions/utils.ts @@ -109,7 +109,7 @@ export let ignoreFocusEvent = false; */ export function preventFocus(target: FocusableElement | null): (() => void) | undefined { // The browser will focus the nearest focusable ancestor of our target. - while (target && !isFocusable(target)) { + while (target && !isFocusable(target, {skipVisibilityCheck: true})) { target = target.parentElement; } diff --git a/packages/react-aria/src/utils/isFocusable.ts b/packages/react-aria/src/utils/isFocusable.ts index 9f8f6114b99..f9fae7cafbb 100644 --- a/packages/react-aria/src/utils/isFocusable.ts +++ b/packages/react-aria/src/utils/isFocusable.ts @@ -34,8 +34,8 @@ const FOCUSABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]),') + ' focusableElements.push('[tabindex]:not([tabindex="-1"]):not([disabled])'); const TABBABLE_ELEMENT_SELECTOR = focusableElements.join(':not([hidden]):not([tabindex="-1"]),'); -export function isFocusable(element: Element): boolean { - return element.matches(FOCUSABLE_ELEMENT_SELECTOR) && isElementVisible(element) && !isInert(element); +export function isFocusable(element: Element, options?: {skipVisibilityCheck?: boolean}): boolean { + return element.matches(FOCUSABLE_ELEMENT_SELECTOR) && !isInert(element) && (options?.skipVisibilityCheck || isElementVisible(element)); } export function isTabbable(element: Element): boolean {