Skip to content

Commit e241402

Browse files
authored
fix: allow user to move into Autocomplete wrapped 2d grid via ArrowLeft/Right (adobe#9755)
also prevents internally tracked focused key from sticking if the inner collection of a Autocomplete is switched/changed to another collection
1 parent 80ef0d8 commit e241402

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
231231
}
232232

233233
let focusedNodeId = queuedActiveDescendant.current;
234+
if (focusedNodeId !== null && getOwnerDocument(inputRef.current).getElementById(focusedNodeId) == null) {
235+
// if the focused id doesn't exist in document, then we need to clear the tracked focused node, otherwise
236+
// we will be attempting to fire key events on a non-existing node instead of trying to focus the newly swapped wrapped collection.
237+
// This can happen if you are swapping out the Autocomplete wrapped collection component like in the docs search.
238+
queuedActiveDescendant.current = null;
239+
focusedNodeId = null;
240+
}
241+
234242
switch (e.key) {
235243
case 'a':
236244
if (isCtrlKeyPressed(e)) {
@@ -260,11 +268,28 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
260268
case 'PageDown':
261269
case 'PageUp':
262270
case 'ArrowUp':
263-
case 'ArrowDown': {
271+
case 'ArrowDown':
272+
case 'ArrowRight':
273+
case 'ArrowLeft': {
264274
if ((e.key === 'Home' || e.key === 'End') && focusedNodeId == null && e.shiftKey) {
265275
return;
266276
}
267277

278+
// If there is text within the input field, we'll want continue propagating events down
279+
// to the wrapped collection if there is a focused node so that a user can continue moving the
280+
// virtual focus. However, if the user doesn't have a focus in the collection, just move the text
281+
// cursor instead. They can move focus down into the collection via down/up arrow if need be
282+
if ((e.key === 'ArrowRight' || e.key === 'ArrowLeft') && state.inputValue.length > 0) {
283+
if (focusedNodeId == null) {
284+
if (!e.isPropagationStopped()) {
285+
e.stopPropagation();
286+
}
287+
return;
288+
}
289+
290+
break;
291+
}
292+
268293
// Prevent these keys from moving the text cursor in the input
269294
e.preventDefault();
270295
// Move virtual focus into the wrapped collection

packages/@react-aria/selection/src/useSelectableCollection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
201201
}
202202
case 'ArrowLeft': {
203203
if (delegate.getKeyLeftOf) {
204-
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyLeftOf?.(manager.focusedKey) : null;
204+
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyLeftOf?.(manager.focusedKey) : delegate.getFirstKey?.();
205205
if (nextKey == null && shouldFocusWrap) {
206206
nextKey = direction === 'rtl' ? delegate.getFirstKey?.(manager.focusedKey) : delegate.getLastKey?.(manager.focusedKey);
207207
}
@@ -214,7 +214,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
214214
}
215215
case 'ArrowRight': {
216216
if (delegate.getKeyRightOf) {
217-
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyRightOf?.(manager.focusedKey) : null;
217+
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyRightOf?.(manager.focusedKey) : delegate.getFirstKey?.();
218218
if (nextKey == null && shouldFocusWrap) {
219219
nextKey = direction === 'rtl' ? delegate.getLastKey?.(manager.focusedKey) : delegate.getFirstKey?.(manager.focusedKey);
220220
}

packages/react-aria-components/stories/Autocomplete.stories.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,3 +1208,39 @@ export const AutocompleteUserCustomFiltering: AutocompleteStory = {
12081208
}
12091209
}
12101210
};
1211+
1212+
export function AutocompleteGrid() {
1213+
return (
1214+
<AutocompleteWrapper>
1215+
<div>
1216+
<TextField autoFocus data-testid="autocomplete-example">
1217+
<Label style={{display: 'block'}}>Test</Label>
1218+
<Input />
1219+
<Text style={{display: 'block'}} slot="description">Please select an option below.</Text>
1220+
</TextField>
1221+
<ListBox
1222+
className={styles.menu}
1223+
aria-label="test listbox"
1224+
layout="grid"
1225+
orientation="vertical"
1226+
style={{
1227+
width: 300,
1228+
height: 300,
1229+
display: 'grid',
1230+
gridTemplate: 'repeat(3, 1fr) / repeat(3, 1fr)',
1231+
gridAutoFlow: 'row'
1232+
}}>
1233+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>1,1</MyListBoxItem>
1234+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>1,2</MyListBoxItem>
1235+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>1,3</MyListBoxItem>
1236+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>2,1</MyListBoxItem>
1237+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>2,2</MyListBoxItem>
1238+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>2,3</MyListBoxItem>
1239+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>3,1</MyListBoxItem>
1240+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>3,2</MyListBoxItem>
1241+
<MyListBoxItem style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}>3,3</MyListBoxItem>
1242+
</ListBox>
1243+
</div>
1244+
</AutocompleteWrapper>
1245+
);
1246+
};

0 commit comments

Comments
 (0)