Skip to content

Commit c205d56

Browse files
committed
Add CdkSelection and examples
No test coverage so far
1 parent 173f5fa commit c205d56

28 files changed

+1171
-12
lines changed

src/cdk-experimental/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CDK_EXPERIMENTAL_ENTRYPOINTS = [
33
"dialog",
44
"popover-edit",
55
"scrolling",
6+
"selection",
67
]
78

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