Skip to content

Commit ebb3998

Browse files
authored
feat(cdk-experimental/selection): Merge cdk–selection to the master (#20229)
* feat(cdk-experimental/selection): add selection state to a list of items (#18424) * feat(material-experimental/selection): add mat APIs for cdk-experimental/selection (#18620) * CdkSelection: Add unit tests (#19945) * feat(cdk-experiment/selection): Merge to master
1 parent 5526ab9 commit ebb3998

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2451
-11
lines changed

.github/CODEOWNERS

+3
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
/src/material-experimental/mdc-typography/** @mmalerba
123123
/src/material-experimental/menubar/** @jelbourn @andy9775
124124
/src/material-experimental/popover-edit/** @kseamon @andrewseguin
125+
/src/material-experimental/selection/** @yifange @jelbourn
125126

126127
# CDK experimental package
127128
/src/cdk-experimental/* @jelbourn
@@ -132,6 +133,7 @@
132133
/src/cdk-experimental/popover-edit/** @kseamon @andrewseguin
133134
/src/cdk-experimental/scrolling/** @mmalerba
134135
/src/cdk-experimental/listbox/** @nielsr98 @jelbourn
136+
/src/cdk-experimental/selection/** @yifange @jelbourn
135137

136138
# Docs examples & guides
137139
/guides/** @jelbourn
@@ -217,6 +219,7 @@
217219
/src/dev-app/typography/** @crisbeto
218220
/src/dev-app/virtual-scroll/** @mmalerba
219221
/src/dev-app/youtube-player/** @nathantate
222+
/src/dev-app/selection/** @yifange @jelbourn
220223

221224
# E2E app
222225
/src/e2e-app/* @jelbourn

src/cdk-experimental/config.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CDK_EXPERIMENTAL_ENTRYPOINTS = [
77
"listbox",
88
"popover-edit",
99
"scrolling",
10+
"selection",
1011
]
1112

1213
# List of all entry-point targets of the Angular cdk-experimental package.
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
load("//tools:defaults.bzl", "ng_module", "ng_test_library", "ng_web_test_suite")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_module(
6+
name = "selection",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/cdk-experimental/selection",
12+
deps = [
13+
"//src/cdk/coercion",
14+
"//src/cdk/collections",
15+
"//src/cdk/table",
16+
"@npm//@angular/core",
17+
"@npm//@angular/forms",
18+
"@npm//rxjs",
19+
],
20+
)
21+
22+
ng_test_library(
23+
name = "unit_test_sources",
24+
srcs = glob(
25+
["**/*.spec.ts"],
26+
exclude = ["**/*.e2e.spec.ts"],
27+
),
28+
deps = [
29+
":selection",
30+
"//src/cdk/table",
31+
"//src/cdk/testing/private",
32+
],
33+
)
34+
35+
ng_web_test_suite(
36+
name = "unit_tests",
37+
deps = [":unit_test_sources"],
38+
)
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './public-api';
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './selection';
10+
export * from './select-all';
11+
export * from './selection-toggle';
12+
export * from './selection-column';
13+
export * from './row-selection';
14+
export * from './selection-set';
15+
export * from './selection-module';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {coerceNumberProperty, NumberInput} from '@angular/cdk/coercion';
10+
import {Directive, Input} from '@angular/core';
11+
12+
import {CdkSelection} from './selection';
13+
14+
/**
15+
* Applies `cdk-selected` class and `aria-selected` to an element.
16+
*
17+
* Must be used within a parent `CdkSelection` directive.
18+
* Must be provided with the value. The index is required if `trackBy` is used on the `CdkSelection`
19+
* directive.
20+
*/
21+
@Directive({
22+
selector: '[cdkRowSelection]',
23+
host: {
24+
'[class.cdk-selected]': '_selection.isSelected(this.value, this.index)',
25+
'[attr.aria-selected]': '_selection.isSelected(this.value, this.index)',
26+
},
27+
})
28+
export class CdkRowSelection<T> {
29+
@Input('cdkRowSelectionValue') value: T;
30+
31+
@Input('cdkRowSelectionIndex')
32+
get index(): number|undefined { return this._index; }
33+
set index(index: number|undefined) { this._index = coerceNumberProperty(index); }
34+
private _index?: number;
35+
36+
constructor(readonly _selection: CdkSelection<T>) {}
37+
38+
static ngAcceptInputType_index: NumberInput;
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Directive, Inject, isDevMode, OnDestroy, OnInit, Optional, Self} from '@angular/core';
10+
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
11+
import {Observable, of as observableOf, Subject} from 'rxjs';
12+
import {switchMap, takeUntil} from 'rxjs/operators';
13+
14+
import {CdkSelection} from './selection';
15+
16+
/**
17+
* Makes the element a select-all toggle.
18+
*
19+
* Must be used within a parent `CdkSelection` directive. It toggles the selection states
20+
* of all the selection toggles connected with the `CdkSelection` directive.
21+
* If the element implements `ControlValueAccessor`, e.g. `MatCheckbox`, the directive
22+
* automatically connects it with the select-all state provided by the `CdkSelection` directive. If
23+
* not, use `checked$` to get the checked state, `indeterminate$` to get the indeterminate state,
24+
* and `toggle()` to change the selection state.
25+
*/
26+
@Directive({
27+
selector: '[cdkSelectAll]',
28+
exportAs: 'cdkSelectAll',
29+
})
30+
export class CdkSelectAll<T> implements OnDestroy, OnInit {
31+
/**
32+
* The checked state of the toggle.
33+
* Resolves to `true` if all the values are selected, `false` if no value is selected.
34+
*/
35+
readonly checked: Observable<boolean> = this._selection.change.pipe(
36+
switchMap(() => observableOf(this._selection.isAllSelected())),
37+
);
38+
39+
/**
40+
* The indeterminate state of the toggle.
41+
* Resolves to `true` if part (not all) of the values are selected, `false` if all values or no
42+
* value at all are selected.
43+
*/
44+
readonly indeterminate: Observable<boolean> = this._selection.change.pipe(
45+
switchMap(() => observableOf(this._selection.isPartialSelected())),
46+
);
47+
48+
/**
49+
* Toggles the select-all state.
50+
* @param event The click event if the toggle is triggered by a (mouse or keyboard) click. If
51+
* using with a native `<input type="checkbox">`, the parameter is required for the
52+
* indeterminate state to work properly.
53+
*/
54+
toggle(event?: MouseEvent) {
55+
// This is needed when applying the directive on a native <input type="checkbox">
56+
// checkbox. The default behavior needs to be prevented in order to support the indeterminate
57+
// state. The timeout is also needed so the checkbox can show the latest state.
58+
if (event) {
59+
event.preventDefault();
60+
}
61+
62+
setTimeout(() => {
63+
this._selection.toggleSelectAll();
64+
});
65+
}
66+
67+
private readonly _destroyed = new Subject<void>();
68+
69+
constructor(
70+
@Optional() @Inject(CdkSelection) private readonly _selection: CdkSelection<T>,
71+
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) private readonly _controlValueAccessor:
72+
ControlValueAccessor[]) {}
73+
74+
ngOnInit() {
75+
this._assertValidParentSelection();
76+
this._configureControlValueAccessor();
77+
}
78+
79+
private _configureControlValueAccessor() {
80+
if (this._controlValueAccessor && this._controlValueAccessor.length) {
81+
this._controlValueAccessor[0].registerOnChange((e: unknown) => {
82+
if (e === true || e === false) {
83+
this.toggle();
84+
}
85+
});
86+
this.checked.pipe(takeUntil(this._destroyed)).subscribe((state) => {
87+
this._controlValueAccessor[0].writeValue(state);
88+
});
89+
}
90+
}
91+
92+
private _assertValidParentSelection() {
93+
if (!this._selection && isDevMode()) {
94+
throw Error('CdkSelectAll: missing CdkSelection in the parent');
95+
}
96+
97+
if (!this._selection.multiple && isDevMode()) {
98+
throw Error('CdkSelectAll: CdkSelection must have cdkSelectionMultiple set to true');
99+
}
100+
}
101+
102+
ngOnDestroy() {
103+
this._destroyed.next();
104+
this._destroyed.complete();
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CdkCellDef, CdkColumnDef, CdkHeaderCellDef, CdkTable} from '@angular/cdk/table';
10+
import {
11+
Component,
12+
Input,
13+
isDevMode,
14+
OnDestroy,
15+
OnInit,
16+
Optional,
17+
ViewChild,
18+
ChangeDetectionStrategy,
19+
ViewEncapsulation,
20+
Inject,
21+
} from '@angular/core';
22+
23+
import {CdkSelection} from './selection';
24+
25+
/**
26+
* Column that adds row selecting checkboxes and a select-all checkbox if `cdkSelectionMultiple` is
27+
* `true`.
28+
*
29+
* Must be used within a parent `CdkSelection` directive.
30+
*/
31+
@Component({
32+
selector: 'cdk-selection-column',
33+
template: `
34+
<ng-container cdkColumnDef>
35+
<th cdkHeaderCell *cdkHeaderCellDef>
36+
<input type="checkbox" *ngIf="selection.multiple"
37+
cdkSelectAll
38+
#allToggler="cdkSelectAll"
39+
[checked]="allToggler.checked | async"
40+
[indeterminate]="allToggler.indeterminate | async"
41+
(click)="allToggler.toggle($event)">
42+
</th>
43+
<td cdkCell *cdkCellDef="let row; let i = $index">
44+
<input type="checkbox"
45+
#toggler="cdkSelectionToggle"
46+
cdkSelectionToggle
47+
[cdkSelectionToggleValue]="row"
48+
[cdkSelectionToggleIndex]="i"
49+
(click)="toggler.toggle()"
50+
[checked]="toggler.checked | async">
51+
</td>
52+
</ng-container>
53+
`,
54+
changeDetection: ChangeDetectionStrategy.OnPush,
55+
encapsulation: ViewEncapsulation.None,
56+
})
57+
export class CdkSelectionColumn<T> implements OnInit, OnDestroy {
58+
/** Column name that should be used to reference this column. */
59+
@Input('cdkSelectionColumnName')
60+
get name(): string {
61+
return this._name;
62+
}
63+
set name(name: string) {
64+
this._name = name;
65+
66+
this._syncColumnDefName();
67+
}
68+
private _name: string;
69+
70+
@ViewChild(CdkColumnDef, {static: true}) private readonly _columnDef: CdkColumnDef;
71+
@ViewChild(CdkCellDef, {static: true}) private readonly _cell: CdkCellDef;
72+
@ViewChild(CdkHeaderCellDef, {static: true}) private readonly _headerCell: CdkHeaderCellDef;
73+
74+
constructor(
75+
@Optional() @Inject(CdkTable) private _table: CdkTable<T>,
76+
@Optional() @Inject(CdkSelection) readonly selection: CdkSelection<T>,
77+
) {}
78+
79+
ngOnInit() {
80+
if (!this.selection && isDevMode()) {
81+
throw Error('CdkSelectionColumn: missing CdkSelection in the parent');
82+
}
83+
84+
this._syncColumnDefName();
85+
86+
if (this._table) {
87+
this._columnDef.cell = this._cell;
88+
this._columnDef.headerCell = this._headerCell;
89+
this._table.addColumnDef(this._columnDef);
90+
} else {
91+
if (isDevMode()) {
92+
throw Error('CdkSelectionColumn: missing parent table');
93+
}
94+
}
95+
}
96+
97+
ngOnDestroy() {
98+
if (this._table) {
99+
this._table.removeColumnDef(this._columnDef);
100+
}
101+
}
102+
103+
private _syncColumnDefName() {
104+
if (this._columnDef) {
105+
this._columnDef.name = this._name;
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CdkTableModule} from '@angular/cdk/table';
10+
import {CommonModule} from '@angular/common';
11+
import {NgModule} from '@angular/core';
12+
13+
import {CdkRowSelection} from './row-selection';
14+
import {CdkSelectAll} from './select-all';
15+
import {CdkSelection} from './selection';
16+
import {CdkSelectionColumn} from './selection-column';
17+
import {CdkSelectionToggle} from './selection-toggle';
18+
19+
@NgModule({
20+
imports: [
21+
CommonModule,
22+
CdkTableModule,
23+
],
24+
exports: [
25+
CdkSelection,
26+
CdkSelectionToggle,
27+
CdkSelectAll,
28+
CdkSelectionColumn,
29+
CdkRowSelection,
30+
],
31+
declarations: [
32+
CdkSelection,
33+
CdkSelectionToggle,
34+
CdkSelectAll,
35+
CdkSelectionColumn,
36+
CdkRowSelection,
37+
],
38+
})
39+
export class CdkSelectionModule {
40+
}

0 commit comments

Comments
 (0)