diff --git a/packages/@react-aria/interactions/src/focusSafely.ts b/packages/@react-aria/interactions/src/focusSafely.ts
index 384f3f363f7..cd171d34950 100644
--- a/packages/@react-aria/interactions/src/focusSafely.ts
+++ b/packages/@react-aria/interactions/src/focusSafely.ts
@@ -30,12 +30,12 @@ export function focusSafely(element: FocusableElement): void {
// causing the page to scroll when moving focus if the element is transitioning
// from off the screen.
const ownerDocument = getOwnerDocument(element);
- const activeElement = getActiveElement(ownerDocument);
if (getInteractionModality() === 'virtual') {
- let lastFocusedElement = activeElement;
+ let lastFocusedElement = getActiveElement(ownerDocument);
runAfterTransition(() => {
- // If focus did not move and the element is still in the document, focus it.
- if (getActiveElement(ownerDocument) === lastFocusedElement && element.isConnected) {
+ const activeElement = getActiveElement(ownerDocument);
+ // If focus did not move or focus was lost to the body, and the element is still in the document, focus it.
+ if ((activeElement === lastFocusedElement || activeElement === ownerDocument.body) && element.isConnected) {
focusWithoutScrolling(element);
}
});
diff --git a/packages/react-aria-components/test/Dialog.test.js b/packages/react-aria-components/test/Dialog.test.js
index c047f886513..4bd6c76f1a6 100644
--- a/packages/react-aria-components/test/Dialog.test.js
+++ b/packages/react-aria-components/test/Dialog.test.js
@@ -10,17 +10,23 @@
* governing permissions and limitations under the License.
*/
+import {act, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
import {
Button,
Dialog,
DialogTrigger,
Heading,
+ Input,
+ Label,
+ Menu,
+ MenuItem,
+ MenuTrigger,
Modal,
ModalOverlay,
OverlayArrow,
- Popover
+ Popover,
+ TextField
} from '../';
-import {pointerMap, render, within} from '@react-spectrum/test-utils-internal';
import React, {useRef} from 'react';
import {UNSAFE_PortalProvider} from '@react-aria/overlays';
import userEvent from '@testing-library/user-event';
@@ -29,6 +35,7 @@ describe('Dialog', () => {
let user;
beforeAll(() => {
user = userEvent.setup({delay: null, pointerMap});
+ jest.useFakeTimers();
});
it('should have a base default set of attributes', () => {
@@ -379,4 +386,59 @@ describe('Dialog', () => {
await user.click(document.body);
});
});
+
+ it('ensure Input autoFocus works when opening Modal from MenuItem via keyboard', async () => {
+ function App() {
+ const [isOpen, setOpen] = React.useState(false);
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ const {getAllByRole, getByRole, getByTestId} = render();
+ const button = getByRole('button');
+ await user.tab();
+ expect(document.activeElement).toBe(button);
+ await user.keyboard('{Enter}');
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ const menuitem = getAllByRole('menuitem')[0];
+ expect(document.activeElement).toBe(menuitem);
+ await user.keyboard('{Enter}');
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ const input = getByTestId('email');
+ expect(document.activeElement).toBe(input);
+ });
});