Skip to content

Commit c37aeac

Browse files
chirokasLFDanLu
andauthored
fix(RAC): ensure Input autoFocus works when opening Modal from MenuItem via keyboard (#8881)
* fix(RAC): ensure Input autoFocus works when opening Modal from MenuItem via keyboard * fix: react.act is not a function --------- Co-authored-by: Daniel Lu <[email protected]>
1 parent ee53a79 commit c37aeac

File tree

2 files changed

+68
-6
lines changed

2 files changed

+68
-6
lines changed

packages/@react-aria/interactions/src/focusSafely.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export function focusSafely(element: FocusableElement): void {
3030
// causing the page to scroll when moving focus if the element is transitioning
3131
// from off the screen.
3232
const ownerDocument = getOwnerDocument(element);
33-
const activeElement = getActiveElement(ownerDocument);
3433
if (getInteractionModality() === 'virtual') {
35-
let lastFocusedElement = activeElement;
34+
let lastFocusedElement = getActiveElement(ownerDocument);
3635
runAfterTransition(() => {
37-
// If focus did not move and the element is still in the document, focus it.
38-
if (getActiveElement(ownerDocument) === lastFocusedElement && element.isConnected) {
36+
const activeElement = getActiveElement(ownerDocument);
37+
// If focus did not move or focus was lost to the body, and the element is still in the document, focus it.
38+
if ((activeElement === lastFocusedElement || activeElement === ownerDocument.body) && element.isConnected) {
3939
focusWithoutScrolling(element);
4040
}
4141
});

packages/react-aria-components/test/Dialog.test.js

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,23 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
import {act, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
1314
import {
1415
Button,
1516
Dialog,
1617
DialogTrigger,
1718
Heading,
19+
Input,
20+
Label,
21+
Menu,
22+
MenuItem,
23+
MenuTrigger,
1824
Modal,
1925
ModalOverlay,
2026
OverlayArrow,
21-
Popover
27+
Popover,
28+
TextField
2229
} from '../';
23-
import {pointerMap, render, within} from '@react-spectrum/test-utils-internal';
2430
import React, {useRef} from 'react';
2531
import {UNSAFE_PortalProvider} from '@react-aria/overlays';
2632
import userEvent from '@testing-library/user-event';
@@ -29,6 +35,7 @@ describe('Dialog', () => {
2935
let user;
3036
beforeAll(() => {
3137
user = userEvent.setup({delay: null, pointerMap});
38+
jest.useFakeTimers();
3239
});
3340

3441
it('should have a base default set of attributes', () => {
@@ -379,4 +386,59 @@ describe('Dialog', () => {
379386
await user.click(document.body);
380387
});
381388
});
389+
390+
it('ensure Input autoFocus works when opening Modal from MenuItem via keyboard', async () => {
391+
function App() {
392+
const [isOpen, setOpen] = React.useState(false);
393+
return (
394+
<>
395+
<MenuTrigger>
396+
<Button>Open menu</Button>
397+
<Popover>
398+
<Menu>
399+
<MenuItem onAction={() => setOpen(true)}>Add account</MenuItem>
400+
<MenuItem>Sign out</MenuItem>
401+
</Menu>
402+
</Popover>
403+
</MenuTrigger>
404+
<ModalOverlay isDismissable isOpen={isOpen} onOpenChange={setOpen}>
405+
<Modal>
406+
<Dialog>
407+
<form>
408+
<Heading slot="title">Sign up</Heading>
409+
<TextField autoFocus>
410+
<Label>Email</Label>
411+
<Input data-testid="email" />
412+
</TextField>
413+
<TextField>
414+
<Label>Password</Label>
415+
<Input />
416+
</TextField>
417+
</form>
418+
</Dialog>
419+
</Modal>
420+
</ModalOverlay>
421+
</>
422+
);
423+
}
424+
425+
const {getAllByRole, getByRole, getByTestId} = render(<App />);
426+
const button = getByRole('button');
427+
await user.tab();
428+
expect(document.activeElement).toBe(button);
429+
await user.keyboard('{Enter}');
430+
act(() => {
431+
jest.runAllTimers();
432+
});
433+
434+
const menuitem = getAllByRole('menuitem')[0];
435+
expect(document.activeElement).toBe(menuitem);
436+
await user.keyboard('{Enter}');
437+
act(() => {
438+
jest.runAllTimers();
439+
});
440+
441+
const input = getByTestId('email');
442+
expect(document.activeElement).toBe(input);
443+
});
382444
});

0 commit comments

Comments
 (0)