Skip to content

fix: #9239 Focus on the first available option in the dropdown#10236

Closed
jsmitrah wants to merge 12 commits into
adobe:mainfrom
jsmitrah:fix/9239/focus-on-the-first-available-option
Closed

fix: #9239 Focus on the first available option in the dropdown#10236
jsmitrah wants to merge 12 commits into
adobe:mainfrom
jsmitrah:fix/9239/focus-on-the-first-available-option

Conversation

@jsmitrah

@jsmitrah jsmitrah commented Jun 19, 2026

Copy link
Copy Markdown

Closes #9239

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

  • Configure the component data so that the first 2 or 3 items in the list are disabled.
  • Open the component and verify that the initial focus outline snaps straight to the first available option automatically, without needing an extra keypress.

🧢 Your Project:

@jsmitrah jsmitrah closed this Jun 19, 2026
@jsmitrah jsmitrah reopened this Jun 22, 2026

@snowystinger snowystinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I'd like to see a storybook story which recreates the bug that was reported in the issue which included a Combobox and a long list of options which scrolled where the first non-disabled element was past the scrolling region.

getFirstKey(): Key | null {
let key = this.collection.getFirstKey();
return this.findNextNonDisabled(key, key => this.collection.getKeyAfter(key));
getFirstKey(key?: Key, includeDisabled?: boolean): Key | null {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these arguments for? From what I can see, key isn't used in this function, and nothing in this PR calls getFirstKey with includeDisabled set.

The only place I see it set is in useSelectableCollection, but it appears to be more of an accident, as it would only "include" if the Ctrl key is pressed, which I don't think we want to be a condition for Home/End as it has to do with selection and something called "global" that determines row vs cell focus

finally, reverting the changes in this file still pass the added tests

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're completely right. I have reverted ListKeyboardDelegate.ts to its original state and kept the check entirely within useSelectableCollection.ts by checking "manager.disabledKeys" directly during the autofocus lifecycle. Thanks for the guidance


manager.setFocused(true);
manager.setFocusedKey(focusedKey);
manager.setFocusedKey(focusedKey); // Fires consistently now (even if null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this code comment mean? what is it referencing? when did it not fire?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That inline comment was a leftover trace from a temporary intermediate change. Now I have removed that


// Safety check: If the resolved target item is explicitly disabled,
// fallback immediately to the first available enabled option.
if (focusedKey && manager.disabledKeys.has(focusedKey)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this block of code for? all tests pass without it.

Looking at the loop above, this line should make sure that the item isn't disabled. Did you see otherwise? please include a test.

          if (manager.canSelectItem(key)) {

Comment on lines +196 to +198
// =========================================================================
// ADDED: Tests for skipping disabled items automatically on mount/focus
// =========================================================================

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// =========================================================================
// ADDED: Tests for skipping disabled items automatically on mount/focus
// =========================================================================

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the suggestion

Comment on lines +212 to +213
// Let any asynchronous layout microtasks complete smoothly
await new Promise(resolve => setTimeout(resolve, 0));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this about? the test passes without it from what i can see

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I originally included this timeout helper to address a microtask timing race condition during an earlier iteration of the fix. Now I removed that.

Comment on lines +231 to +232
// Wrap the simulated user tabbing action cleanly in an async act block
// to handle the synchronous JSDOM focus state mutation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Wrap the simulated user tabbing action cleanly in an async act block
// to handle the synchronous JSDOM focus state mutation

This comment means nothing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I have removed that.

manager.setFocusedKey(focusedKey); // Fires consistently now (even if null)

// If no default focus key is selected, focus the collection itself.
// If no default focus key is selected, focus the collection container itself.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If no default focus key is selected, focus the collection container itself.
// If no default focus key is selected, focus the collection itself.

@jsmitrah jsmitrah Jun 25, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the suggestion for the comment

navigateToKey(manager.lastSelectedKey ?? delegate.getLastKey?.());
} else {
navigateToKey(manager.firstSelectedKey ?? delegate.getFirstKey?.());
let firstKey = manager.firstSelectedKey ?? delegate.getFirstKey?.();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something is wrong the tests, they pass without this change as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this block was redundant, so I have removed it and reverted this section.

@jsmitrah jsmitrah requested a review from snowystinger June 25, 2026 15:05
@snowystinger

Copy link
Copy Markdown
Member

I'm not sure there is actually a bug to fix anymore. I can't run the original authors codesandbox, and in the repro I made here, https://stackblitz.com/edit/adpc19dw?file=src%2FExample.tsx it's scrolling the first non-disabled item into view. Were you able to reproduce the initial issue?

@jsmitrah jsmitrah closed this Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Focus on the first available (not disabled) option

2 participants