Skip to content

feat: add focus events to GridListItem - #10822

Merged
yihuiliao merged 3 commits into
adobe:mainfrom
dagemawinegash:feat/grid-list-item-focus-events
Oct 22, 2026
Merged

yihuiliao merged 3 commits into
adobe:mainfrom
dagemawinegash:feat/grid-list-item-focus-events

Conversation

@dagemawinegash

Copy link
Copy Markdown
Contributor

Adds onFocus, onBlur, and onFocusChange to GridListItem for mouse and keyboard interaction, using the existing useFocus hook. Includes regression tests and an interactive Storybook example.

Part of #6460 (GridListItem only; table Row is outside this change).

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub issue.
  • Added unit tests and a Storybook example.
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the ARIA grid practices.
  • I understand every change in this PR and can explain why it's there.

API prop documentation derives from the inherited FocusEvents type; no separate documentation files were changed.

📝 Test Instructions:

  1. Run yarn start and open React Aria Components → GridList → Item Focus Events.
  2. Click different items, then use Tab and arrow keys. Verify the Actions panel reports focus, blur, and focus-change callbacks while selection and focus rings continue to work.
  3. Run yarn test packages/react-aria-components/test/GridList.test.js --runInBand.

Validation: all 110 GridList tests, repository lint, and all 74 SSR tests passed (SSR used a 120-second local timeout). The full unit run had 16 failing tests and the browser run had two failures. All reproduced with the original GridList source: unit failures involve Windows locale/path handling and an interactive codemod fixture; browser failures involve TokenField word deletion in Chromium and Firefox.

🧢 Your Project:

Independent open-source contribution.

@yihuiliao

yihuiliao commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

seems like some tests are failing, could you take a look?

@dagemawinegash

Copy link
Copy Markdown
Contributor Author

Thanks for mentioning that
I reproduced both failures in the React 16 job. The new tests were reading focus events after React 16 had cleared them for reuse
I’ve fixed the tests locally to capture the target inside the callback. All 110 GridList tests now pass on both React 16 and React 19
I’ll push the update shortly

@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.

In general looks good, couple nitpicks

Comment thread packages/react-aria-components/test/GridList.test.js
Comment on lines +2009 to +2028

it.each(['onFocus', 'onBlur', 'onFocusChange'])(
'should support %s independently',
async prop => {
let handler = jest.fn();
let {getByRole} = render(
<>
<GridList aria-label="Test">
<GridListItem {...{[prop]: handler}}>Cat</GridListItem>
</GridList>
<button>After</button>
</>
);
await user.tab();
expect(document.activeElement).toBe(getByRole('row'));
await user.tab();
expect(document.activeElement).toBe(getByRole('button', {name: 'After'}));
expect(handler).toHaveBeenCalledTimes(prop === 'onFocusChange' ? 2 : 1);
}
);

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
it.each(['onFocus', 'onBlur', 'onFocusChange'])(
'should support %s independently',
async prop => {
let handler = jest.fn();
let {getByRole} = render(
<>
<GridList aria-label="Test">
<GridListItem {...{[prop]: handler}}>Cat</GridListItem>
</GridList>
<button>After</button>
</>
);
await user.tab();
expect(document.activeElement).toBe(getByRole('row'));
await user.tab();
expect(document.activeElement).toBe(getByRole('button', {name: 'After'}));
expect(handler).toHaveBeenCalledTimes(prop === 'onFocusChange' ? 2 : 1);
}
);

This isn't really providing value in addition to the first test, unless you ran into a case where this wasn't working, in which case make it closer to the real life case that caused it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in b21c5a6

Comment on lines +2076 to +2099

it('should not report focus for disabled items skipped during navigation', async () => {
let onDisabledFocusChange = jest.fn();
let onFocusChange = jest.fn();
let {getAllByRole} = render(
<GridList aria-label="Test" disabledKeys={['dog']}>
<GridListItem id="cat" onFocusChange={onFocusChange}>
Cat
</GridListItem>
<GridListItem id="dog" onFocusChange={onDisabledFocusChange}>
Dog
</GridListItem>
<GridListItem id="kangaroo" onFocusChange={onFocusChange}>
Kangaroo
</GridListItem>
</GridList>
);
let rows = getAllByRole('row');
await user.tab();
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(rows[2]);
expect(onFocusChange.mock.calls).toEqual([[true], [false], [true]]);
expect(onDisabledFocusChange).not.toHaveBeenCalled();
});

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
it('should not report focus for disabled items skipped during navigation', async () => {
let onDisabledFocusChange = jest.fn();
let onFocusChange = jest.fn();
let {getAllByRole} = render(
<GridList aria-label="Test" disabledKeys={['dog']}>
<GridListItem id="cat" onFocusChange={onFocusChange}>
Cat
</GridListItem>
<GridListItem id="dog" onFocusChange={onDisabledFocusChange}>
Dog
</GridListItem>
<GridListItem id="kangaroo" onFocusChange={onFocusChange}>
Kangaroo
</GridListItem>
</GridList>
);
let rows = getAllByRole('row');
await user.tab();
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(rows[2]);
expect(onFocusChange.mock.calls).toEqual([[true], [false], [true]]);
expect(onDisabledFocusChange).not.toHaveBeenCalled();
});

not providing value, we have tests that show that disabled elements are skipped

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in b21c5a6

@dagemawinegash

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback
I’ll add the mouse focus-visible assertion and remove the redundant callback and disabled-item tests, then rerun the checks and push an update

@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.

lgtm

@yihuiliao
yihuiliao added this pull request to the merge queue Oct 22, 2026
Merged via the queue into adobe:main with commit 20bb8e5 Oct 22, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants