Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/@react-aria/gridlist/src/useGridList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ export interface AriaGridListOptions<T> extends Omit<AriaGridListProps<T>, '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')
Copy link
Member

Choose a reason for hiding this comment

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

should this be disregarded if selectionBehaviour is not replace?

*/
selectOnFocus?: boolean
}

export interface GridListAria {
Expand All @@ -121,7 +126,8 @@ export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T
linkBehavior = 'action',
keyboardNavigationBehavior = 'arrow',
escapeKeyBehavior = 'clearSelection',
shouldSelectOnPressUp
shouldSelectOnPressUp,
selectOnFocus
} = props;

if (!props['aria-label'] && !props['aria-labelledby']) {
Expand All @@ -136,7 +142,7 @@ export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T
keyboardDelegate,
layoutDelegate,
isVirtualized,
selectOnFocus: state.selectionManager.selectionBehavior === 'replace',
selectOnFocus: selectOnFocus ?? state.selectionManager.selectionBehavior === 'replace',
shouldFocusWrap: props.shouldFocusWrap,
linkBehavior,
disallowTypeAhead,
Expand Down
7 changes: 6 additions & 1 deletion packages/react-aria-components/src/GridList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,12 @@ export interface GridListProps<T> extends Omit<AriaGridListProps<T>, '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
}


Expand Down
73 changes: 73 additions & 0 deletions packages/react-aria-components/test/GridList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<GridList aria-label="Test" selectionMode="single" selectionBehavior="replace" onSelectionChange={onSelectionChange}>
<GridListItem id="cat">Cat</GridListItem>
<GridListItem id="dog">Dog</GridListItem>
<GridListItem id="kangaroo">Kangaroo</GridListItem>
</GridList>
);
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(
<GridList aria-label="Test" selectionMode="single" selectionBehavior="replace" selectOnFocus={false} onSelectionChange={onSelectionChange}>
<GridListItem id="cat">Cat</GridListItem>
<GridListItem id="dog">Dog</GridListItem>
<GridListItem id="kangaroo">Kangaroo</GridListItem>
</GridList>
);
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(
<GridList aria-label="Test" selectionMode="multiple" selectionBehavior="replace" selectOnFocus={false} onSelectionChange={onSelectionChange}>
Copy link
Member

Choose a reason for hiding this comment

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

how does one select multiple in replace? does shift+arrow down work?

<GridListItem id="cat">Cat</GridListItem>
<GridListItem id="dog">Dog</GridListItem>
<GridListItem id="kangaroo">Kangaroo</GridListItem>
</GridList>
);
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();
Expand Down