Skip to content

Commit 9ec9d9d

Browse files
authored
fix: Layer sets focus visible classname for its FocusRectsProvider (#28157)
1 parent 1b5b7a1 commit 9ec9d9d

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed
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: Layer propagates focus visible classname to its contents",
4+
"packageName": "@fluentui/react",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/react/src/components/Layer/Layer.base.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
setPortalAttribute,
1212
setVirtualParent,
1313
FocusRectsProvider,
14+
FocusRectsContext,
15+
IsFocusVisibleClassName,
1416
} from '../../Utilities';
1517
import {
1618
registerLayer,
@@ -24,6 +26,14 @@ import type { ILayerProps, ILayerStyleProps, ILayerStyles } from './Layer.types'
2426

2527
const getClassNames = classNamesFunction<ILayerStyleProps, ILayerStyles>();
2628

29+
const getFocusVisibility = (providerRef?: React.RefObject<HTMLElement>) => {
30+
if (providerRef?.current) {
31+
return providerRef.current.classList.contains(IsFocusVisibleClassName);
32+
}
33+
34+
return false;
35+
};
36+
2737
export const LayerBase: React.FunctionComponent<ILayerProps> = React.forwardRef<HTMLDivElement, ILayerProps>(
2838
(props, ref) => {
2939
const registerPortalEl = usePortalCompat();
@@ -32,11 +42,24 @@ export const LayerBase: React.FunctionComponent<ILayerProps> = React.forwardRef<
3242
const mergedRef = useMergedRefs(rootRef, ref);
3343
const layerRef = React.useRef<HTMLDivElement>();
3444
const fabricElementRef = React.useRef<HTMLDivElement>(null);
45+
const focusContext = React.useContext(FocusRectsContext);
3546

3647
// Tracks if the layer mount events need to be raised.
3748
// Required to allow the DOM to render after the layer element is added.
3849
const [needRaiseLayerMount, setNeedRaiseLayerMount] = React.useState(false);
3950

51+
// Sets the focus visible className when the FocusRectsProvider for the layer is rendered
52+
// This allows the current focus visibility style to be carried over to the layer content
53+
const focusRectsRef = React.useCallback(
54+
el => {
55+
const isFocusVisible = getFocusVisibility(focusContext?.providerRef);
56+
if (el && isFocusVisible) {
57+
el.classList.add(IsFocusVisibleClassName);
58+
}
59+
},
60+
[focusContext],
61+
);
62+
4063
const {
4164
children,
4265
className,
@@ -52,7 +75,7 @@ export const LayerBase: React.FunctionComponent<ILayerProps> = React.forwardRef<
5275
theme,
5376
} = props;
5477

55-
const fabricRef = useMergedRefs(fabricElementRef, fabricProps?.ref);
78+
const fabricRef = useMergedRefs(fabricElementRef, fabricProps?.ref, focusRectsRef);
5679

5780
const classNames = getClassNames(styles!, {
5881
theme: theme!,

packages/react/src/components/Layer/Layer.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22
import * as ReactTestUtils from 'react-dom/test-utils';
33
import { Layer } from './Layer';
44
import { LayerHost } from './LayerHost';
5+
import { FocusRectsProvider, IsFocusVisibleClassName } from '../../Utilities';
56
import { mount } from 'enzyme';
67
import { safeCreate } from '@fluentui/test-utilities';
78
import { render } from '@testing-library/react';
@@ -263,6 +264,41 @@ describe('Layer', () => {
263264
expect(onLayerDidMountSpy).toHaveBeenCalledTimes(1);
264265
});
265266

267+
it('sets focus visibility className from parent context', () => {
268+
const parentFocusEl = document.createElement('div');
269+
parentFocusEl.classList.add(IsFocusVisibleClassName);
270+
const parentFocusRef = { current: parentFocusEl };
271+
const FocusProviderTest = () => (
272+
<div id="app">
273+
<FocusRectsProvider providerRef={parentFocusRef}>
274+
<div id="parent">
275+
<Layer hostId="focusTest" fabricProps={{ className: 'innerFocusProvider' }}>
276+
content
277+
</Layer>
278+
</div>
279+
</FocusRectsProvider>
280+
<LayerHost id="focusTest" />
281+
</div>
282+
);
283+
284+
const appElement = document.createElement('div');
285+
286+
try {
287+
document.body.appendChild(appElement);
288+
289+
ReactTestUtils.act(() => {
290+
ReactDOM.render(<FocusProviderTest />, appElement);
291+
});
292+
293+
const focusProvider = appElement.querySelector('.innerFocusProvider');
294+
expect(focusProvider).toBeTruthy();
295+
expect(focusProvider?.classList.contains(IsFocusVisibleClassName)).toBeTruthy();
296+
} finally {
297+
ReactDOM.unmountComponentAtNode(appElement);
298+
appElement.remove();
299+
}
300+
});
301+
266302
describe('compat', () => {
267303
it('calls "register" from "react-portal-compat"', () => {
268304
const unregister = jest.fn();

0 commit comments

Comments
 (0)