-
Notifications
You must be signed in to change notification settings - Fork 12
Fix Popover focus management when used with Tabs #2863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fad9b9b
1ff1233
188329d
209b1af
9eec570
61224bd
0976e3d
412fed1
1b2f733
a28c480
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@khanacademy/wonder-blocks-tabs": patch | ||
| --- | ||
|
|
||
| Fix issue where TabPanel is set with tabIndex=0 before it has determined if it has focusable elements within it (related to a focus management bug when Tabs are used inside a Popover) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@khanacademy/wonder-blocks-popover": patch | ||
| --- | ||
|
|
||
| Fix issue where Popover focus management was interfering with Tabs focus management |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||
| import * as React from "react"; | ||||||||||||||||||||||||||||||||||||||
| import {addStyle, StyleType} from "@khanacademy/wonder-blocks-core"; | ||||||||||||||||||||||||||||||||||||||
| import {StyleSheet} from "aphrodite"; | ||||||||||||||||||||||||||||||||||||||
| import {focusStyles} from "@khanacademy/wonder-blocks-styles"; | ||||||||||||||||||||||||||||||||||||||
| import {findFocusableNodes} from "../../../wonder-blocks-core/src/util/focus"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| type Props = { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -48,12 +49,16 @@ export const TabPanel = (props: Props) => { | |||||||||||||||||||||||||||||||||||||
| } = props; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const ref = React.useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||
| const [hasFocusableElement, setHasFocusableElement] = React.useState(false); | ||||||||||||||||||||||||||||||||||||||
| // Initialize to null to indicate that the focusable element status is not | ||||||||||||||||||||||||||||||||||||||
| // yet determined | ||||||||||||||||||||||||||||||||||||||
| const [hasFocusableElement, setHasFocusableElement] = React.useState< | ||||||||||||||||||||||||||||||||||||||
| boolean | null | ||||||||||||||||||||||||||||||||||||||
| >(null); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| React.useEffect(() => { | ||||||||||||||||||||||||||||||||||||||
| // Whenever tab panel contents change, determine if the panel has a | ||||||||||||||||||||||||||||||||||||||
| // focusable element | ||||||||||||||||||||||||||||||||||||||
| if (ref.current) { | ||||||||||||||||||||||||||||||||||||||
| // focusable element (only if the tab panel has children) | ||||||||||||||||||||||||||||||||||||||
| if (ref.current && children) { | ||||||||||||||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If wonder-blocks/packages/wonder-blocks-tabs/src/components/tabs.tsx Lines 395 to 412 in 0bb9648
This additional check makes it so the tab index on the tabpanel is not initialized until the (related to https://github.com/Khan/wonder-blocks/pull/2863/files#r2528858860) |
||||||||||||||||||||||||||||||||||||||
| setHasFocusableElement(findFocusableNodes(ref.current).length > 0); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| }, [active, ref, children]); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -64,9 +69,10 @@ export const TabPanel = (props: Props) => { | |||||||||||||||||||||||||||||||||||||
| role="tabpanel" | ||||||||||||||||||||||||||||||||||||||
| id={id} | ||||||||||||||||||||||||||||||||||||||
| aria-labelledby={ariaLabelledby} | ||||||||||||||||||||||||||||||||||||||
| // If the tab panel doesn't have focusable elements, it should be | ||||||||||||||||||||||||||||||||||||||
| // focusable so that it is included in the tab sequence of the page | ||||||||||||||||||||||||||||||||||||||
| tabIndex={hasFocusableElement ? undefined : 0} | ||||||||||||||||||||||||||||||||||||||
| // If the tab panel doesn't have focusable elements after being | ||||||||||||||||||||||||||||||||||||||
| // determined, it should be focusable so that it is included in the | ||||||||||||||||||||||||||||||||||||||
| // tab sequence of the page | ||||||||||||||||||||||||||||||||||||||
| tabIndex={hasFocusableElement === false ? 0 : undefined} | ||||||||||||||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Previously, TLDR: By making sure we don't initially set the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: There are some edge cases still with focus management in popovers + tabs. The long term fix would be to update FocusManager so it can keep track of whenever any descendant element becomes focusable/non-focusable. The focus management in Popover is going to be re-visited as part of the work to switch to floating-ui, which can hopefully address these issues! (related Slack thread) For now, this PR addresses the main issues! |
||||||||||||||||||||||||||||||||||||||
| // Only show the tab panel if it is active | ||||||||||||||||||||||||||||||||||||||
| hidden={!active} | ||||||||||||||||||||||||||||||||||||||
| data-testid={testId} | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -82,5 +88,6 @@ const styles = StyleSheet.create({ | |||||||||||||||||||||||||||||||||||||
| tabPanel: { | ||||||||||||||||||||||||||||||||||||||
| // Apply flex so that panel supports rtl | ||||||||||||||||||||||||||||||||||||||
| display: "flex", | ||||||||||||||||||||||||||||||||||||||
| ...focusStyles.focus, | ||||||||||||||||||||||||||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the WB focus styles to tab panel so that it is compatible with dark backgrounds later on. The tab panel becomes focusable if there are no interactive elements within it |
||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes it so that the tabs (which are
buttonelements withrole=tab) with tabindex="-1" aren't included in FocusManager's logic for determining focusable nodesThis prevents the issue where all the tabs are focusable by default