Skip to content

Commit 7df2296

Browse files
authored
fix: Focus behaviour on inputs inside a FocusScope (#8498)
* fix: Focus behaviour on inputs inside a FocusScope * fix combobox tests * only do the select for html input elements
1 parent 4c2d52d commit 7df2296

File tree

5 files changed

+30
-6
lines changed

5 files changed

+30
-6
lines changed

packages/@react-aria/autocomplete/src/useAutocomplete.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaLabelingProps, BaseEvent, DOMProps, FocusableElement, FocusEvents, KeyboardEvents, Node, RefObject, ValueBase} from '@react-types/shared';
1414
import {AriaTextFieldProps} from '@react-aria/textfield';
1515
import {AutocompleteProps, AutocompleteState} from '@react-stately/autocomplete';
16-
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useLabels, useObjectRef, useSlotId} from '@react-aria/utils';
16+
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, getOwnerWindow, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useLabels, useObjectRef, useSlotId} from '@react-aria/utils';
1717
import {dispatchVirtualBlur, dispatchVirtualFocus, getVirtuallyFocusedElement, moveVirtualFocus} from '@react-aria/focus';
1818
import {getInteractionModality} from '@react-aria/interactions';
1919
// @ts-ignore
@@ -106,6 +106,9 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
106106
// Ensure input is focused if the user clicks on the collection directly.
107107
if (!e.isTrusted && shouldUseVirtualFocus && inputRef.current && getActiveElement(getOwnerDocument(inputRef.current)) !== inputRef.current) {
108108
inputRef.current.focus();
109+
if (inputRef.current instanceof getOwnerWindow(inputRef.current).HTMLInputElement) {
110+
inputRef.current.select();
111+
}
109112
}
110113

111114
let target = e.target as Element | null;

packages/@react-aria/focus/src/FocusScope.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getActiveElement,
1616
getEventTarget,
1717
getOwnerDocument,
18+
getOwnerWindow,
1819
isAndroid,
1920
isChrome,
2021
isFocusable,
@@ -371,6 +372,10 @@ function useFocusContainment(scopeRef: RefObject<Element[] | null>, contain?: bo
371372
// restore focus to the previously focused node or the first tabbable element in the active scope.
372373
if (focusedNode.current) {
373374
focusedNode.current.focus();
375+
376+
if (focusedNode.current instanceof getOwnerWindow(focusedNode.current).HTMLInputElement) {
377+
focusedNode.current.select();
378+
}
374379
} else if (activeScope && activeScope.current) {
375380
focusFirstInScope(activeScope.current);
376381
}
@@ -399,6 +404,9 @@ function useFocusContainment(scopeRef: RefObject<Element[] | null>, contain?: bo
399404
if (target && target.isConnected) {
400405
focusedNode.current = target;
401406
focusedNode.current?.focus();
407+
if (focusedNode.current instanceof getOwnerWindow(focusedNode.current).HTMLInputElement) {
408+
focusedNode.current.select();
409+
}
402410
} else if (activeScope.current) {
403411
focusFirstInScope(activeScope.current);
404412
}
@@ -486,6 +494,9 @@ function focusElement(element: FocusableElement | null, scroll = false) {
486494
} else if (element != null) {
487495
try {
488496
element.focus();
497+
if (element instanceof getOwnerWindow(element).HTMLInputElement) {
498+
element.select();
499+
}
489500
} catch {
490501
// ignore
491502
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
focusWithoutScrolling,
1616
getActiveElement,
1717
getOwnerDocument,
18+
getOwnerWindow,
1819
runAfterTransition
1920
} from '@react-aria/utils';
2021
import {getInteractionModality} from './useFocusVisible';
@@ -37,9 +38,15 @@ export function focusSafely(element: FocusableElement): void {
3738
// If focus did not move and the element is still in the document, focus it.
3839
if (getActiveElement(ownerDocument) === lastFocusedElement && element.isConnected) {
3940
focusWithoutScrolling(element);
41+
if (element instanceof getOwnerWindow(element).HTMLInputElement) {
42+
element.select();
43+
}
4044
}
4145
});
4246
} else {
4347
focusWithoutScrolling(element);
48+
if (element instanceof getOwnerWindow(element).HTMLInputElement) {
49+
element.select();
50+
}
4451
}
4552
}

packages/@react-spectrum/combobox/test/ComboBox.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4398,7 +4398,7 @@ describe('ComboBox', function () {
43984398
if (Method === 'escape key') {
43994399
expect(button).toHaveAttribute('aria-labelledby', `${tree.getByText('Test').id} ${tree.getByText('Two').id}`);
44004400
} else {
4401-
expect(button).toHaveAttribute('aria-labelledby', `${tree.getByText('Test').id} ${tree.getByText('Twor').id}`);
4401+
expect(button).toHaveAttribute('aria-labelledby', `${tree.getByText('Test').id} ${tree.getByText('r').id}`);
44024402
}
44034403
tree.unmount();
44044404

@@ -4412,7 +4412,7 @@ describe('ComboBox', function () {
44124412

44134413
await performInteractions(tree);
44144414
expect(() => tree.getByTestId('tray')).toThrow();
4415-
expect(button).toHaveAttribute('aria-labelledby', `${tree.getByText('Test').id} ${tree.getByText('Twor').id}`);
4415+
expect(button).toHaveAttribute('aria-labelledby', `${tree.getByText('Test').id} ${tree.getByText('r').id}`);
44164416
});
44174417

44184418
it('menutrigger=focus doesn\'t reopen the tray on close', async function () {

packages/@react-spectrum/s2/stories/Dialog.stories.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ const ExampleRender = (args: ExampleRenderProps): ReactElement => (
4848
<Heading slot="title">Dialog title</Heading>
4949
<Header>Header</Header>
5050
<Content>
51-
{[...Array(args.paragraphs)].map((_, i) =>
52-
<p key={i} style={{marginTop: i === 0 ? 0 : undefined, marginBottom: i === args.paragraphs - 1 ? 0 : undefined}}>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in</p>
53-
)}
51+
<>
52+
{[...Array(args.paragraphs)].map((_, i) =>
53+
<p key={i} style={{marginTop: i === 0 ? 0 : undefined, marginBottom: i === args.paragraphs - 1 ? 0 : undefined}}>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in</p>
54+
)}
55+
<input type="text" defaultValue="Hello" />
56+
</>
5457
</Content>
5558
<Footer><Checkbox>Don't show this again</Checkbox></Footer>
5659
<ButtonGroup>

0 commit comments

Comments
 (0)