Skip to content

Commit 5c0e4a2

Browse files
reidbarberLFDanLu
andauthored
fix(SelectionManager): support full collection for select all functionality (adobe#9621)
* fix(SelectionManager): support full collection for select all functionality * add story * more unit tests * add test to confirm external control behavior --------- Co-authored-by: Daniel Lu <dl1644@gmail.com>
1 parent bf87614 commit 5c0e4a2

File tree

3 files changed

+414
-8
lines changed

3 files changed

+414
-8
lines changed

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {Selection} from './Selection';
2828

2929
interface SelectionManagerOptions {
3030
allowsCellSelection?: boolean,
31-
layoutDelegate?: LayoutDelegate
31+
layoutDelegate?: LayoutDelegate,
32+
fullCollection?: Collection<Node<unknown>>
3233
}
3334

3435
/**
@@ -40,13 +41,15 @@ export class SelectionManager implements MultipleSelectionManager {
4041
private allowsCellSelection: boolean;
4142
private _isSelectAll: boolean | null;
4243
private layoutDelegate: LayoutDelegate | null;
44+
private fullCollection: Collection<Node<unknown>> | null;
4345

4446
constructor(collection: Collection<Node<unknown>>, state: MultipleSelectionState, options?: SelectionManagerOptions) {
4547
this.collection = collection;
4648
this.state = state;
4749
this.allowsCellSelection = options?.allowsCellSelection ?? false;
4850
this._isSelectAll = null;
4951
this.layoutDelegate = options?.layoutDelegate || null;
52+
this.fullCollection = options?.fullCollection || null;
5053
}
5154

5255
/**
@@ -388,26 +391,29 @@ export class SelectionManager implements MultipleSelectionManager {
388391
}
389392

390393
private getSelectAllKeys() {
394+
// Use the full (unfiltered) collection when available so that materializing
395+
// the 'all' selection includes items that are currently filtered out (e.g. by Autocomplete).
396+
let collection = this.fullCollection ?? this.collection;
391397
let keys: Key[] = [];
392398
let addKeys = (key: Key | null) => {
393399
while (key != null) {
394-
if (this.canSelectItem(key)) {
395-
let item = this.collection.getItem(key);
400+
if (this.canSelectItemIn(key, collection)) {
401+
let item = collection.getItem(key);
396402
if (item?.type === 'item') {
397403
keys.push(key);
398404
}
399405

400406
// Add child keys. If cell selection is allowed, then include item children too.
401407
if (item?.hasChildNodes && (this.allowsCellSelection || item.type !== 'item')) {
402-
addKeys(getFirstItem(getChildNodes(item, this.collection))?.key ?? null);
408+
addKeys(getFirstItem(getChildNodes(item, collection))?.key ?? null);
403409
}
404410
}
405411

406-
key = this.collection.getKeyAfter(key);
412+
key = collection.getKeyAfter(key);
407413
}
408414
};
409415

410-
addKeys(this.collection.getFirstKey());
416+
addKeys(collection.getFirstKey());
411417
return keys;
412418
}
413419

@@ -489,11 +495,15 @@ export class SelectionManager implements MultipleSelectionManager {
489495
}
490496

491497
canSelectItem(key: Key): boolean {
498+
return this.canSelectItemIn(key, this.collection);
499+
}
500+
501+
private canSelectItemIn(key: Key, collection: Collection<Node<unknown>>): boolean {
492502
if (this.state.selectionMode === 'none' || this.state.disabledKeys.has(key)) {
493503
return false;
494504
}
495505

496-
let item = this.collection.getItem(key);
506+
let item = collection.getItem(key);
497507
if (!item || item?.props?.isDisabled || (item.type === 'cell' && !this.allowsCellSelection)) {
498508
return false;
499509
}
@@ -516,7 +526,8 @@ export class SelectionManager implements MultipleSelectionManager {
516526
withCollection(collection: Collection<Node<unknown>>): SelectionManager {
517527
return new SelectionManager(collection, this.state, {
518528
allowsCellSelection: this.allowsCellSelection,
519-
layoutDelegate: this.layoutDelegate || undefined
529+
layoutDelegate: this.layoutDelegate || undefined,
530+
fullCollection: this.fullCollection ?? this.collection
520531
});
521532
}
522533
}

packages/react-aria-components/stories/Autocomplete.stories.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,30 @@ export const AutocompleteWithListbox: AutocompleteStory = {
513513
name: 'Autocomplete with ListBox + Popover'
514514
};
515515

516+
export const AutocompleteSelectAllFiltering: AutocompleteStory = {
517+
render: (args) => {
518+
return (
519+
<AutocompleteWrapper disableVirtualFocus={args.disableVirtualFocus}>
520+
<div>
521+
<SearchField autoFocus>
522+
<Label style={{display: 'block'}}>Test</Label>
523+
<Input />
524+
</SearchField>
525+
<ListBox<AutocompleteItem>
526+
className={styles.menu}
527+
items={items}
528+
selectionMode="multiple"
529+
defaultSelectedKeys="all"
530+
onSelectionChange={action('onSelectionChange')}>
531+
{(item: AutocompleteItem) => <MyListBoxItem id={item.id}>{item.name}</MyListBoxItem>}
532+
</ListBox>
533+
</div>
534+
</AutocompleteWrapper>
535+
);
536+
},
537+
name: 'Autocomplete, select all with filtering'
538+
};
539+
516540
function VirtualizedListBox(props) {
517541
let items: {id: number, name: string}[] = [];
518542
for (let i = 0; i < 10000; i++) {

0 commit comments

Comments
 (0)