Skip to content

Commit 47f079d

Browse files
committed
[popups] Fix programmatic focus return
1 parent 09ce83b commit 47f079d

2 files changed

Lines changed: 25 additions & 8 deletions

File tree

packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe('<Dialog.Root />', () => {
183183

184184
return (
185185
<div>
186-
<Dialog.Root open={open} triggerId={triggerId}>
186+
<Dialog.Root open={open} triggerId={triggerId} onOpenChange={setOpen}>
187187
{({ payload }: NumberPayload) => (
188188
<React.Fragment>
189189
<Dialog.Trigger id="trigger-1" payload={1}>
@@ -196,6 +196,7 @@ describe('<Dialog.Root />', () => {
196196
<Dialog.Portal>
197197
<Dialog.Popup>
198198
<span data-testid="content">{payload}</span>
199+
<Dialog.Close>Close</Dialog.Close>
199200
</Dialog.Popup>
200201
</Dialog.Portal>
201202
</React.Fragment>
@@ -223,6 +224,13 @@ describe('<Dialog.Root />', () => {
223224
await waitFor(() => {
224225
expect(screen.getByTestId('content').textContent).toBe('2');
225226
});
227+
228+
await user.click(screen.getByRole('button', { name: 'Close' }));
229+
230+
await waitFor(() => {
231+
expect(screen.queryByTestId('content')).toBe(null);
232+
});
233+
expect(openButton).toHaveFocus();
226234
});
227235

228236
it('keeps the payload reactive', async () => {

packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS
731731

732732
const doc = ownerDocument(floatingFocusElement);
733733
const previouslyFocusedElement = activeElement(doc);
734+
const openedProgrammatically = openInteractionTypeRef.current == null;
734735

735736
addPreviouslyFocusedElement(previouslyFocusedElement);
736737

@@ -793,15 +794,22 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS
793794
resolvedReturnFocusValue = true;
794795
}
795796

796-
if (typeof resolvedReturnFocusValue === 'boolean') {
797-
if (domReference?.isConnected) {
798-
return domReference;
799-
}
800-
801-
return getPreviouslyFocusedElement() || null;
797+
const connectedReference = domReference?.isConnected ? domReference : null;
798+
let previouslyFocusedReturnElement = previouslyFocusedElement;
799+
if (
800+
!previouslyFocusedReturnElement?.isConnected ||
801+
getNodeName(previouslyFocusedReturnElement) === 'body'
802+
) {
803+
previouslyFocusedReturnElement = getPreviouslyFocusedElement() || null;
802804
}
803805

804-
const fallback = domReference?.isConnected ? domReference : getPreviouslyFocusedElement();
806+
const fallback = openedProgrammatically
807+
? previouslyFocusedReturnElement || connectedReference
808+
: connectedReference || previouslyFocusedReturnElement;
809+
810+
if (typeof resolvedReturnFocusValue === 'boolean') {
811+
return fallback;
812+
}
805813

806814
return resolveRef(resolvedReturnFocusValue) || fallback || null;
807815
}
@@ -850,6 +858,7 @@ export function FloatingFocusManager(props: FloatingFocusManagerProps): React.JS
850858
floating,
851859
floatingFocusElement,
852860
returnFocusRef,
861+
openInteractionTypeRef,
853862
events,
854863
tree,
855864
domReference,

0 commit comments

Comments
 (0)