Skip to content

Commit 6806410

Browse files
alirezamiriansnowystingerreidbarber
authored
fix(@react-aria/selection): don't mutate non-empty selection upon focus (#7513)
* fix(@react-aria/selection): don't mutate non-empty selection upon focus closes #7512 * add some assertions --------- Co-authored-by: GitHub <[email protected]> Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Reid Barber <[email protected]>
1 parent 55dc352 commit 6806410

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

packages/@react-aria/selection/src/useSelectableCollection.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,11 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
342342
}
343343

344344
manager.setFocused(true);
345-
346345
if (manager.focusedKey == null) {
347-
let navigateToFirstKey = (key: Key | undefined | null) => {
346+
let navigateToKey = (key: Key | undefined | null) => {
348347
if (key != null) {
349348
manager.setFocusedKey(key);
350-
if (selectOnFocus) {
349+
if (selectOnFocus && !manager.isSelected(key)) {
351350
manager.replaceSelection(key);
352351
}
353352
}
@@ -357,9 +356,9 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
357356
// and either focus the first or last item accordingly.
358357
let relatedTarget = e.relatedTarget as Element;
359358
if (relatedTarget && (e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING)) {
360-
navigateToFirstKey(manager.lastSelectedKey ?? delegate.getLastKey?.());
359+
navigateToKey(manager.lastSelectedKey ?? delegate.getLastKey?.());
361360
} else {
362-
navigateToFirstKey(manager.firstSelectedKey ?? delegate.getFirstKey?.());
361+
navigateToKey(manager.firstSelectedKey ?? delegate.getFirstKey?.());
363362
}
364363
} else if (!isVirtualized && scrollRef.current) {
365364
// Restore the scroll position to what it was before.

packages/@react-aria/selection/test/useSelectableCollection.test.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {fireEvent, installPointerEvent, pointerMap, render, simulateDesktop, simulateMobile} from '@react-spectrum/test-utils-internal';
13+
import {fireEvent, installPointerEvent, pointerMap, render, simulateDesktop, simulateMobile, within} from '@react-spectrum/test-utils-internal';
1414
import {Item} from '@react-stately/collections';
1515
import {List} from '../stories/List';
1616
import React from 'react';
@@ -107,5 +107,43 @@ describe('useSelectableCollection', () => {
107107
expect(options[1]).not.toHaveAttribute('aria-selected');
108108
expect(options[2]).toHaveAttribute('aria-selected', 'true');
109109
});
110+
111+
it('focuses first/last selected item on focus enter and does not change the selection', async () => {
112+
let onSelectionChange1 = jest.fn();
113+
let onSelectionChange2 = jest.fn();
114+
let {getByText, getAllByRole} = render(
115+
<>
116+
<List
117+
selectionMode="multiple"
118+
selectionBehavior="replace"
119+
defaultSelectedKeys={['i2', 'i3']}
120+
onSelectionChange={onSelectionChange1}>
121+
<Item key="i1">Paco de Lucia</Item>
122+
<Item key="i2">Vicente Amigo</Item>
123+
<Item key="i3">Gerardo Nunez</Item>
124+
</List>
125+
<button>focus stop</button>
126+
<List
127+
selectionMode="multiple"
128+
selectionBehavior="replace"
129+
defaultSelectedKeys={['i2', 'i3']}
130+
onSelectionChange={onSelectionChange2}>
131+
<Item key="i1">Paco de Lucia</Item>
132+
<Item key="i2">Vicente Amigo</Item>
133+
<Item key="i3">Gerardo Nunez</Item>
134+
</List>
135+
</>
136+
);
137+
let [firstList, secondList] = getAllByRole('listbox');
138+
await user.click(getByText('focus stop'));
139+
await user.tab();
140+
expect(document.activeElement).toBe(within(secondList).getByRole('option', {name: 'Vicente Amigo'}));
141+
await user.click(getByText('focus stop'));
142+
await user.tab({shift: true});
143+
expect(document.activeElement).toBe(within(firstList).getByRole('option', {name: 'Gerardo Nunez'}));
144+
145+
expect(onSelectionChange1).not.toHaveBeenCalled();
146+
expect(onSelectionChange2).not.toHaveBeenCalled();
147+
});
110148
});
111149
});

0 commit comments

Comments
 (0)