Skip to content

Commit b45d46f

Browse files
committed
wip: cdk selection
1 parent 6bd2775 commit b45d46f

File tree

14 files changed

+734
-25
lines changed

14 files changed

+734
-25
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package(default_visibility=["//visibility:public"])
2+
load("@angular//:index.bzl", "ng_module")
3+
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test")
4+
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary")
5+
6+
7+
ng_module(
8+
name = "selection",
9+
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
10+
module_name = "@angular/cdk-experimental/selection",
11+
assets = [] + glob(["**/*.html"]),
12+
deps = [
13+
"//src/cdk/coercion",
14+
"//src/cdk/collections",
15+
"@rxjs",
16+
],
17+
tsconfig = "//src/cdk-experimental:tsconfig-build.json",
18+
)
19+
20+
21+
ts_library(
22+
name = "selection_test_sources",
23+
testonly = 1,
24+
srcs = glob(["**/*.spec.ts"]),
25+
deps = [
26+
":selection",
27+
"//src/cdk/collections",
28+
"//src/cdk/testing",
29+
"@rxjs",
30+
],
31+
tsconfig = "//src/cdk-experimental:tsconfig-build.json",
32+
)
33+
34+
ts_web_test(
35+
name = "unit_tests",
36+
bootstrap = [
37+
"//:web_test_bootstrap_scripts",
38+
],
39+
tags = ["manual"],
40+
41+
# Do not sort
42+
deps = [
43+
"//:tslib_bundle",
44+
"//:angular_bundles",
45+
"//:angular_test_bundles",
46+
"//test:angular_test_init",
47+
":selection_test_sources",
48+
],
49+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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';

src/cdk-experimental/selection/public-api.ts

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 { NgModule } from '@angular/core';
10+
import { CdkSelection } from './selection';
11+
import { CdkSelectionToggle } from './selection-toggle';
12+
13+
14+
@NgModule({
15+
declarations: [CdkSelection, CdkSelectionToggle],
16+
exports: [CdkSelection, CdkSelectionToggle]
17+
})
18+
export class CdkSelectionModule {}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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, Input, OnInit, OnDestroy, ElementRef, ChangeDetectorRef} from '@angular/core';
10+
import {CdkSelection} from './selection';
11+
import {Subject} from 'rxjs';
12+
import {takeUntil} from 'rxjs/operators';
13+
14+
15+
@Directive({
16+
selector: '[cdkSelectionToggle]',
17+
host: {
18+
'class': 'cdk-selection-toggle',
19+
'role': 'option',
20+
'tabindex': '-1',
21+
'[attr.aria-selected]': 'selected',
22+
'[class.cdk-selection-selected]': 'selected',
23+
'(mousedown)': '_onMouseDown($event)',
24+
'(click)': '_onToggle()',
25+
'(contextmenu)': '_onToggle()',
26+
'(keydown.enter)': '_onToggle()',
27+
}
28+
})
29+
export class CdkSelectionToggle<T> implements OnInit, OnDestroy {
30+
31+
/** Model of the item to toggle selection. */
32+
@Input('cdkSelectionToggle') model: T | T[];
33+
34+
/** Whether the selection is disabled or not. */
35+
@Input() disabled: boolean;
36+
37+
/** Whether the toggle is selected or not. */
38+
selected: boolean;
39+
40+
/** The modifier that was invoked. */
41+
modifier: 'shift' | 'meta' | null;
42+
43+
private _destroy = new Subject();
44+
45+
constructor(
46+
public elementRef: ElementRef,
47+
private _selection: CdkSelection<T>,
48+
private _cd: ChangeDetectorRef
49+
) {}
50+
51+
ngOnInit() {
52+
this._selection.selectionChange
53+
.pipe(takeUntil(this._destroy))
54+
.subscribe(() => this._checkSelected());
55+
56+
this._checkSelected();
57+
this._selection.register(this);
58+
}
59+
60+
ngOnDestroy() {
61+
this._destroy.next();
62+
this._destroy.complete();
63+
64+
this._selection.deregister(this);
65+
}
66+
67+
/** Mousedown even to capture modifiers. */
68+
_onMouseDown(event: MouseEvent) {
69+
this._setModifiers(event);
70+
}
71+
72+
/** Invoke toggle on the parent selection directive. */
73+
_onToggle() {
74+
if (!this.disabled) {
75+
this._selection.toggle(this);
76+
}
77+
}
78+
79+
/** Set the modifiers for the mouse event. */
80+
_setModifiers(event) {
81+
if (event.metaKey || event.ctrlKey) {
82+
this.modifier = 'meta';
83+
} else if (event.shiftKey) {
84+
this.modifier = 'shift';
85+
}
86+
87+
// Clear the modifier if we don't use it
88+
setTimeout(() => this.modifier = null, 200);
89+
}
90+
91+
/** Check whether the toggle's model(s) are selected and set state. */
92+
_checkSelected() {
93+
if (Array.isArray(this.model)) {
94+
let has = true;
95+
for (const model of this.model) {
96+
if (!this._selection._selectionModel.isSelected(model)) {
97+
has = false;
98+
break;
99+
}
100+
}
101+
this.selected = has;
102+
} else {
103+
this.selected = this._selection._selectionModel.isSelected(this.model);
104+
}
105+
106+
this._cd.markForCheck();
107+
}
108+
109+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
The `selection` package handles managing selection state on components
2+
with support for single and multi select with keyboard modifiers.
3+
4+
Components such as buttons, checkboxes, etc can all be decorated with
5+
the `cdkSelectionToggle` directive. Once decorated, the directive
6+
will listen for mouse and keyboard events and coordinate the
7+
selection options with the parent `cdkSelection` directive.
8+
9+
In the example below, the div container of the buttons is decorated with `cdkSelection`. To populate
10+
default selections pass an array of the selections to this option like:
11+
`[cdkSelection]="['yellow']"`. Nested beneath the selection directive is a button decorated
12+
with the `cdkSelectionToggle` directive with an argument of the item's value property.
13+
14+
```html
15+
<ul cdkSelection cdkSelectionStrategy="single">
16+
<li *ngFor="let item of items">
17+
<button [cdkSelectionToggle]="item.value">
18+
{{item.label}}
19+
</button>
20+
</li>
21+
</ul>
22+
```
23+
24+
### Strategies
25+
The selection directive has 3 different strategies for selection:
26+
27+
- Single: Only one item can be selected at a time.
28+
- Multiple: Multiple items can be selected by clicking
29+
- Modifier Multiple: Multiple items can be select using keyboard modifiers such as shift or ctrl.
30+
31+
### Clearable
32+
The ability to clear a selection that has been made can be prevented using the `cdkSelectionClearable`.
33+
This means that after a selection (or many selections) have been made users can deselect
34+
all but one of the selections. This concept can be related to radio buttons selection strategy.
35+
36+
### Tracking
37+
When using complex objects with the selection toggle, custom tracking strategies
38+
can ensure correct identification of the values. The `cdkSelectionTrackBy` accepts
39+
a function that will be invoked with the values of the toggle. This can be used to
40+
return a identifying attribute for the object.
41+
42+
```TS
43+
@Component({
44+
template: `
45+
<ul cdkSelection cdkSelectionStrategy="multiple" [cdkSelectionTrackBy]=""trackBy>
46+
<li *ngFor="let item of items">
47+
<button [cdkSelectionToggle]="item">
48+
{{item.label}}
49+
</button>
50+
</li>
51+
</ul>
52+
`
53+
})
54+
export class MyComponent {
55+
trackBy(model) {
56+
return model.value;
57+
}
58+
}
59+
```

0 commit comments

Comments
 (0)