Skip to content

Commit 688ec4f

Browse files
committed
fix: skip focused item on new typeahead search
1 parent 8ab651b commit 688ec4f

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,47 @@ describe('Select', () => {
503503
expect(trigger).toHaveTextContent('Northern Territory');
504504
expect(trigger).not.toHaveAttribute('data-pressed');
505505
});
506+
507+
it('should handle typeahead transitions correctly with and without debounce', async () => {
508+
let {getByTestId} = render(
509+
<Select data-testid="select">
510+
<Label>Test</Label>
511+
<Button>
512+
<SelectValue />
513+
</Button>
514+
<Popover>
515+
<ListBox>
516+
<ListBoxItem id="a">a</ListBoxItem>
517+
<ListBoxItem id="aa">aa</ListBoxItem>
518+
<ListBoxItem id="ab">ab</ListBoxItem>
519+
</ListBox>
520+
</Popover>
521+
</Select>
522+
);
523+
524+
let wrapper = getByTestId('select');
525+
let selectTester = testUtilUser.createTester('Select', {root: wrapper});
526+
let trigger = selectTester.trigger;
527+
528+
await user.tab();
529+
530+
await user.keyboard('a');
531+
expect(trigger).toHaveTextContent('a');
532+
533+
// continuing search narrows — 'aa' matches 'aa'
534+
await user.keyboard('a');
535+
expect(trigger).toHaveTextContent('aa');
536+
537+
// no match on 'aaa' — selection unchanged
538+
await user.keyboard('a');
539+
expect(trigger).toHaveTextContent('aa');
540+
541+
act(() => { jest.runAllTimers(); });
542+
543+
// new search after debounce skips focused item and cycles to next match
544+
await user.keyboard('a');
545+
expect(trigger).toHaveTextContent('ab');
546+
});
506547
});
507548

508549
it('should support autoFocus', () => {

packages/react-aria/src/selection/useTypeSelect.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,15 @@ export function useTypeSelect(options: AriaTypeSelectOptions): TypeSelectAria {
7272
state.search += character;
7373

7474
if (keyboardDelegate.getKeyForSearch != null) {
75-
// Use the delegate to find a key to focus.
76-
// Prioritize items after the currently focused item, falling back to searching the whole list.
77-
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey);
75+
// On a new search, skip the focused item; when continuing a search, include it.
76+
// Falls back to searching the whole list if no match is found.
77+
let fromKey = selectionManager.focusedKey;
78+
const isNewSearch = state.search === character;
79+
if (isNewSearch && fromKey != null && keyboardDelegate.getKeyBelow) {
80+
fromKey = keyboardDelegate.getKeyBelow(fromKey);
81+
}
7882

83+
let key = keyboardDelegate.getKeyForSearch(state.search, fromKey);
7984
// If no key found, search from the top.
8085
if (key == null) {
8186
key = keyboardDelegate.getKeyForSearch(state.search);

0 commit comments

Comments
 (0)