Skip to content

Commit 5a2ed7e

Browse files
Copilotdmytrokirpa
andauthored
fix(react-accordion): prevent keyboard focus from entering a collapsing panel (#35960)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dmytrokirpa <1492102+dmytrokirpa@users.noreply.github.com>
1 parent 26c670a commit 5a2ed7e

3 files changed

Lines changed: 33 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "fix: prevent keyboard focus from entering a collapsing panel",
4+
"packageName": "@fluentui/react-accordion",
5+
"email": "198982749+Copilot@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-accordion/library/src/components/AccordionPanel/AccordionPanel.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as React from 'react';
22
import { render } from '@testing-library/react';
3+
import { renderHook } from '@testing-library/react-hooks';
34
import { AccordionPanel } from './AccordionPanel';
45
import { isConformant } from '../../testing/isConformant';
56
import { AccordionItemProvider } from '../../contexts/accordionItem';
67
import { mockAccordionItemContextValue } from '../../testing/mockContextValue';
8+
import { useAccordionPanelBase_unstable } from './useAccordionPanel';
79

810
describe('AccordionPanel', () => {
911
const Wrapper: React.FC<{ children?: React.ReactNode }> = props => (
@@ -29,4 +31,24 @@ describe('AccordionPanel', () => {
2931
const { container } = render(<AccordionPanel>Default AccordionPanel</AccordionPanel>);
3032
expect(container).toMatchSnapshot();
3133
});
34+
35+
it('sets inert and tabIndex -1 on root when closed to prevent keyboard focus entering the panel', () => {
36+
const ref = React.createRef<HTMLElement>();
37+
const wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
38+
<AccordionItemProvider value={mockAccordionItemContextValue({ open: false })}>{children}</AccordionItemProvider>
39+
);
40+
const { result } = renderHook(() => useAccordionPanelBase_unstable({}, ref), { wrapper });
41+
expect(result.current.root.inert).toBe(true);
42+
expect(result.current.root.tabIndex).toBe(-1);
43+
});
44+
45+
it('does not set inert or tabIndex -1 on root when open', () => {
46+
const ref = React.createRef<HTMLElement>();
47+
const wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
48+
<AccordionItemProvider value={mockAccordionItemContextValue({ open: true })}>{children}</AccordionItemProvider>
49+
);
50+
const { result } = renderHook(() => useAccordionPanelBase_unstable({}, ref), { wrapper });
51+
expect(result.current.root.inert).toBeUndefined();
52+
expect(result.current.root.tabIndex).toBeUndefined();
53+
});
3254
});

packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export const useAccordionPanelBase_unstable = (
7070
{
7171
ref: ref as React.Ref<HTMLDivElement>,
7272
...props,
73+
// Prevent keyboard focus from entering the panel while it is closed/collapsing.
74+
// tabIndex: -1 prevents the panel itself from being focused, and inert prevents
75+
// all focusable descendants from being reachable via keyboard navigation.
76+
...(open ? {} : { tabIndex: -1, inert: true }),
7377
},
7478
{ elementType: 'div' },
7579
),

0 commit comments

Comments
 (0)