Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Charts-DonutChart 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 7530 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic.default.chromium.png 5581 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 404 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 852 Changed
vr-tests-react-components/Positioning.Positioning end.chromium.png 778 Changed
vr-tests-react-components/ProgressBar converged 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 32 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 26 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled.chromium.png 677 Changed

There were 1 duplicate changes discarded. Check the build logs for more information.

"type": "patch",
"comment": "fix: prefer autofocus element over first focusable in Dialog/Drawer (#35749)",
"packageName": "@fluentui/react-dialog",
"email": "198982749+Copilot@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type * as React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { useFocusFinders } from '@fluentui/react-tabster';
import { useFluent_unstable } from '@fluentui/react-shared-contexts';
import { useFocusFirstElement } from './useFocusFirstElement';

jest.mock('@fluentui/react-tabster');
jest.mock('@fluentui/react-shared-contexts');

const mockFindFirstFocusable = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
(useFocusFinders as jest.Mock).mockReturnValue({ findFirstFocusable: mockFindFirstFocusable });
(useFluent_unstable as jest.Mock).mockReturnValue({ targetDocument: document });
});

describe('useFocusFirstElement', () => {
it('focuses the first focusable element when dialog opens and no autofocus element is present', () => {
const container = document.createElement('div');
const firstButton = document.createElement('button');
container.appendChild(firstButton);
document.body.appendChild(container);

mockFindFirstFocusable.mockReturnValue(firstButton);
const focusSpy = jest.spyOn(firstButton, 'focus');

const { result, rerender } = renderHook(({ open }) => useFocusFirstElement(open, 'modal'), {
initialProps: { open: false },
});

// Attach the ref to the container
(result.current as React.RefObject<HTMLElement | null>).current = container;

act(() => {
rerender({ open: true });
});

expect(mockFindFirstFocusable).toHaveBeenCalledWith(container);
expect(focusSpy).toHaveBeenCalledTimes(1);

document.body.removeChild(container);
});

it('focuses the autofocus element instead of the first focusable element when autofocus element is present', () => {
const container = document.createElement('div');
const firstButton = document.createElement('button');
const autoFocusInput = document.createElement('input');
autoFocusInput.setAttribute('autofocus', '');

container.appendChild(firstButton);
container.appendChild(autoFocusInput);
document.body.appendChild(container);

mockFindFirstFocusable.mockReturnValue(firstButton);
const firstButtonFocusSpy = jest.spyOn(firstButton, 'focus');
const autoFocusInputFocusSpy = jest.spyOn(autoFocusInput, 'focus');

const { result, rerender } = renderHook(({ open }) => useFocusFirstElement(open, 'modal'), {
initialProps: { open: false },
});

// Attach the ref to the container
(result.current as React.RefObject<HTMLElement | null>).current = container;

act(() => {
rerender({ open: true });
});

// findFirstFocusable should not be called since autofocus element was found
expect(mockFindFirstFocusable).not.toHaveBeenCalled();
// The autofocus element should be focused, not the first button
expect(autoFocusInputFocusSpy).toHaveBeenCalledTimes(1);
expect(firstButtonFocusSpy).not.toHaveBeenCalled();

document.body.removeChild(container);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export function useFocusFirstElement(
if (!open) {
return;
}
const element = dialogRef.current && findFirstFocusable(dialogRef.current);
const autoFocusElement = dialogRef.current?.querySelector<HTMLElement>('[autofocus]');
const element = autoFocusElement || (dialogRef.current && findFirstFocusable(dialogRef.current));
if (element) {
element.focus();
} else {
Expand Down
Loading