Skip to content

Commit 246225b

Browse files
authored
fix selection with falsy keys (#7079)
* fix selection with falsy keys * fix falsy check if Selection class * add unit tests * revert additional change
1 parent 7c0e286 commit 246225b

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

packages/@react-stately/selection/src/Selection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export class Selection extends Set<Key> {
2323
constructor(keys?: Iterable<Key> | Selection, anchorKey?: Key, currentKey?: Key) {
2424
super(keys);
2525
if (keys instanceof Selection) {
26-
this.anchorKey = anchorKey || keys.anchorKey;
27-
this.currentKey = currentKey || keys.currentKey;
26+
this.anchorKey = anchorKey ?? keys.anchorKey;
27+
this.currentKey = currentKey ?? keys.currentKey;
2828
} else {
2929
this.anchorKey = anchorKey;
3030
this.currentKey = currentKey;

packages/@react-stately/selection/src/SelectionManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ export class SelectionManager implements MultipleSelectionManager {
226226
selection = new Selection([toKey], toKey, toKey);
227227
} else {
228228
let selectedKeys = this.state.selectedKeys as Selection;
229-
let anchorKey = selectedKeys.anchorKey || toKey;
229+
let anchorKey = selectedKeys.anchorKey ?? toKey;
230230
selection = new Selection(selectedKeys, anchorKey, toKey);
231-
for (let key of this.getKeyRange(anchorKey, selectedKeys.currentKey || toKey)) {
231+
for (let key of this.getKeyRange(anchorKey, selectedKeys.currentKey ?? toKey)) {
232232
selection.delete(key);
233233
}
234234

@@ -263,7 +263,7 @@ export class SelectionManager implements MultipleSelectionManager {
263263

264264
let keys: Key[] = [];
265265
let key = from;
266-
while (key) {
266+
while (key != null) {
267267
let item = this.collection.getItem(key);
268268
if (item && item.type === 'item' || (item.type === 'cell' && this.allowsCellSelection)) {
269269
keys.push(key);

packages/react-aria-components/test/ListBox.test.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,4 +1070,94 @@ describe('ListBox', () => {
10701070
});
10711071
});
10721072
});
1073+
1074+
const FalsyExample = () => (
1075+
<ListBox aria-label="Test" selectionMode="multiple" selectionBehavior="replace">
1076+
<ListBoxItem id="0">Item 0</ListBoxItem>
1077+
<ListBoxItem id="1">Item 1</ListBoxItem>
1078+
<ListBoxItem id="2">Item 2</ListBoxItem>
1079+
</ListBox>
1080+
);
1081+
1082+
describe('selection with falsy keys', () => {
1083+
describe('keyboard', () => {
1084+
it('should deselect item 0 when navigating back in replace selection mode', async () => {
1085+
let {getAllByRole} = render(<FalsyExample />);
1086+
1087+
let items = getAllByRole('option');
1088+
1089+
await user.click(items[1]);
1090+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1091+
1092+
// Hold Shift and press ArrowUp to select item 0
1093+
await user.keyboard('{Shift>}{ArrowUp}{/Shift}');
1094+
1095+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1096+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1097+
expect(items[2]).toHaveAttribute('aria-selected', 'false');
1098+
1099+
// Hold Shift and press ArrowDown to navigate back to item 1
1100+
await user.keyboard('{Shift>}{ArrowDown}{/Shift}');
1101+
1102+
expect(items[0]).toHaveAttribute('aria-selected', 'false');
1103+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1104+
expect(items[2]).toHaveAttribute('aria-selected', 'false');
1105+
});
1106+
1107+
it('should correctly handle starting selection at item 0 and extending to item 2', async () => {
1108+
let {getAllByRole} = render(<FalsyExample />);
1109+
1110+
let items = getAllByRole('option');
1111+
1112+
await user.click(items[0]);
1113+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1114+
1115+
// Hold Shift and press ArrowDown to select item 1
1116+
await user.keyboard('{Shift>}{ArrowDown}{/Shift}');
1117+
1118+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1119+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1120+
expect(items[2]).toHaveAttribute('aria-selected', 'false');
1121+
1122+
// Hold Shift and press ArrowDown to select item 2
1123+
await user.keyboard('{Shift>}{ArrowDown}{/Shift}');
1124+
1125+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1126+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1127+
expect(items[2]).toHaveAttribute('aria-selected', 'true');
1128+
});
1129+
});
1130+
1131+
describe('mouse', () => {
1132+
it('should deselect item 0 when clicking another item in replace selection mode', async () => {
1133+
let {getAllByRole} = render(<FalsyExample />);
1134+
1135+
let items = getAllByRole('option');
1136+
1137+
await user.click(items[1]);
1138+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1139+
1140+
await user.click(items[0]);
1141+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1142+
expect(items[1]).toHaveAttribute('aria-selected', 'false');
1143+
1144+
await user.click(items[1]);
1145+
expect(items[1]).toHaveAttribute('aria-selected', 'true');
1146+
expect(items[0]).toHaveAttribute('aria-selected', 'false');
1147+
});
1148+
1149+
it('should correctly handle mouse selection starting at item 0 and extending to item 2', async () => {
1150+
let {getAllByRole} = render(<FalsyExample />);
1151+
1152+
let items = getAllByRole('option');
1153+
1154+
await user.click(items[0]);
1155+
expect(items[0]).toHaveAttribute('aria-selected', 'true');
1156+
1157+
await user.click(items[2]);
1158+
expect(items[0]).toHaveAttribute('aria-selected', 'false');
1159+
expect(items[2]).toHaveAttribute('aria-selected', 'true');
1160+
});
1161+
});
1162+
});
10731163
});

0 commit comments

Comments
 (0)