diff --git a/packages/@react-aria/gridlist/src/useGridList.ts b/packages/@react-aria/gridlist/src/useGridList.ts index 055602c8146..d5f3613e505 100644 --- a/packages/@react-aria/gridlist/src/useGridList.ts +++ b/packages/@react-aria/gridlist/src/useGridList.ts @@ -96,7 +96,12 @@ export interface AriaGridListOptions extends Omit, 'chil * - 'override': links override all other interactions (link items are not selectable). * @default 'action' */ - linkBehavior?: 'action' | 'selection' | 'override' + linkBehavior?: 'action' | 'selection' | 'override', + /** + * Whether selection should occur automatically on focus. + * @default true (when selectionBehavior is 'replace') + */ + selectOnFocus?: boolean } export interface GridListAria { @@ -121,7 +126,8 @@ export function useGridList(props: AriaGridListOptions, state: ListState(props: AriaGridListOptions, state: ListState extends Omit, 'children'> * Whether the items are arranged in a stack or grid. * @default 'stack' */ - layout?: 'stack' | 'grid' + layout?: 'stack' | 'grid', + /** + * Whether selection should occur automatically on focus. + * @default true (when selectionBehavior is 'replace') + */ + selectOnFocus?: boolean } diff --git a/packages/react-aria-components/test/GridList.test.js b/packages/react-aria-components/test/GridList.test.js index be82e37b791..a8f63d34091 100644 --- a/packages/react-aria-components/test/GridList.test.js +++ b/packages/react-aria-components/test/GridList.test.js @@ -1116,6 +1116,79 @@ describe('GridList', () => { }); }); + describe('selectOnFocus', () => { + it('should select on focus by default when selectionBehavior="replace"', async () => { + let onSelectionChange = jest.fn(); + let {getAllByRole} = render( + + Cat + Dog + Kangaroo + + ); + let items = getAllByRole('row'); + + await user.tab(); + expect(document.activeElement).toBe(items[0]); + expect(onSelectionChange).toHaveBeenCalledTimes(1); + expect(new Set(onSelectionChange.mock.calls[0][0])).toEqual(new Set(['cat'])); + + await user.keyboard('{ArrowDown}'); + expect(document.activeElement).toBe(items[1]); + expect(onSelectionChange).toHaveBeenCalledTimes(2); + expect(new Set(onSelectionChange.mock.calls[1][0])).toEqual(new Set(['dog'])); + }); + + it('should not select on focus when selectOnFocus={false}', async () => { + let onSelectionChange = jest.fn(); + let {getAllByRole} = render( + + Cat + Dog + Kangaroo + + ); + let items = getAllByRole('row'); + + await user.tab(); + expect(document.activeElement).toBe(items[0]); + expect(onSelectionChange).not.toHaveBeenCalled(); + + await user.keyboard('{ArrowDown}'); + expect(document.activeElement).toBe(items[1]); + expect(onSelectionChange).not.toHaveBeenCalled(); + + // Selection should still work on explicit press + await user.keyboard(' '); + expect(onSelectionChange).toHaveBeenCalledTimes(1); + expect(new Set(onSelectionChange.mock.calls[0][0])).toEqual(new Set(['dog'])); + }); + + it('should not select on focus when selectOnFocus={false} with multiple selection', async () => { + let onSelectionChange = jest.fn(); + let {getAllByRole} = render( + + Cat + Dog + Kangaroo + + ); + let items = getAllByRole('row'); + + await user.tab(); + expect(document.activeElement).toBe(items[0]); + expect(onSelectionChange).not.toHaveBeenCalled(); + + await user.keyboard('{ArrowDown}'); + expect(document.activeElement).toBe(items[1]); + expect(onSelectionChange).not.toHaveBeenCalled(); + + await user.keyboard('{ArrowDown}'); + expect(document.activeElement).toBe(items[2]); + expect(onSelectionChange).not.toHaveBeenCalled(); + }); + }); + describe('shouldSelectOnPressUp', () => { it('should select an item on pressing down when shouldSelectOnPressUp is not provided', async () => { let onSelectionChange = jest.fn();