Skip to content

Commit bf5e72f

Browse files
WIP Start adding forms demo, matChipInputFor directive
1 parent f0d4c46 commit bf5e72f

File tree

10 files changed

+419
-13
lines changed

10 files changed

+419
-13
lines changed

src/dev-app/mdc-chips/mdc-chips-demo-module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
import {CommonModule} from '@angular/common';
1010
import {NgModule} from '@angular/core';
11+
import {FormsModule} from '@angular/forms';
1112
import {MatCardModule} from '@angular/material/card';
13+
import {MatFormFieldModule} from '@angular/material/form-field';
1214
import {MatToolbarModule} from '@angular/material/toolbar';
1315
import {MatChipsModule} from '@angular/material-experimental/mdc-chips';
1416
import {MatIconModule} from '@angular/material/icon';
@@ -18,8 +20,10 @@ import {MdcChipsDemo} from './mdc-chips-demo';
1820
@NgModule({
1921
imports: [
2022
CommonModule,
23+
FormsModule,
2124
MatCardModule,
2225
MatChipsModule,
26+
MatFormFieldModule,
2327
MatIconModule,
2428
MatToolbarModule,
2529
RouterModule.forChild([{path: '', component: MdcChipsDemo}]),

src/dev-app/mdc-chips/mdc-chips-demo.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,32 @@ <h4>With avatar and icons</h4>
8484
</mat-chip-set>
8585
</mat-card-content>
8686
</mat-card>
87+
88+
<mat-card>
89+
<mat-toolbar color="primary">Dynamic Chips</mat-toolbar>
90+
91+
<mat-card-content>
92+
<h4>Form Field</h4>
93+
94+
<p>
95+
You can easily put the <code>&lt;mat-chip-list&gt;</code> inside of an
96+
<code>&lt;mat-form-field&gt;</code>.
97+
</p>
98+
99+
<mat-form-field class="demo-has-chip-list">
100+
<mat-chip-list #chipList1 [(ngModel)]="selectedPeople" required>
101+
<mat-chip-option *ngFor="let person of people"
102+
(removed)="remove(person)">
103+
{{person.name}}
104+
<mat-icon matChipRemove>cancel</mat-icon>
105+
</mat-chip-option>
106+
<input placeholder="New Contributor..."
107+
[matChipInputFor]="chipList1"
108+
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
109+
[matChipInputAddOnBlur]="addOnBlur"
110+
(matChipInputTokenEnd)="add($event)" />
111+
</mat-chip-list>
112+
</mat-form-field>
113+
</mat-card-content>
114+
</mat-card>
87115
</div>

src/dev-app/mdc-chips/mdc-chips-demo.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {COMMA, ENTER} from '@angular/cdk/keycodes';
910
import {Component} from '@angular/core';
11+
import {MatChipInputEvent} from '@angular/material-experimental/mdc-chips';
12+
13+
export interface Person {
14+
name: string;
15+
}
1016

1117
@Component({
1218
moduleId: module.id,
@@ -16,12 +22,51 @@ import {Component} from '@angular/core';
1622
})
1723
export class MdcChipsDemo {
1824
visible = true;
25+
selectable = true;
26+
removable = true;
27+
addOnBlur = true;
1928
message = '';
2029

30+
// Enter, comma, semi-colon
31+
separatorKeysCodes = [ENTER, COMMA, 186];
32+
33+
selectedPeople = null;
34+
35+
people: Person[] = [
36+
{name: 'Kara'},
37+
{name: 'Jeremy'},
38+
{name: 'Topher'},
39+
{name: 'Elad'},
40+
{name: 'Kristiyan'},
41+
{name: 'Paul'}
42+
];
43+
2144
displayMessage(message: string): void {
2245
this.message = message;
2346
}
2447

48+
add(event: MatChipInputEvent): void {
49+
const {input, value} = event;
50+
51+
// Add our person
52+
if ((value || '').trim()) {
53+
this.people.push({ name: value.trim() });
54+
}
55+
56+
// Reset the input value
57+
if (input) {
58+
input.value = '';
59+
}
60+
}
61+
62+
remove(person: Person): void {
63+
const index = this.people.indexOf(person);
64+
65+
if (index >= 0) {
66+
this.people.splice(index, 1);
67+
}
68+
}
69+
2570
toggleVisible(): void {
2671
this.visible = false;
2772
}

src/material-experimental/mdc-chips/_mdc-chips.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,9 @@
5151
}
5252
}
5353
}
54+
55+
$mat-chip-input-width: 150px;
56+
57+
input.mat-chip-input {
58+
flex: 1 0 $mat-chip-input-width;
59+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 {InjectionToken} from '@angular/core';
10+
11+
/** Default options, for the chips module, that can be overridden. */
12+
export interface MatChipsDefaultOptions {
13+
/** The list of key codes that will trigger a chipEnd event. */
14+
separatorKeyCodes: number[] | Set<number>;
15+
}
16+
17+
/** Injection token to be used to override the default options for the chips module. */
18+
export const MAT_CHIPS_DEFAULT_OPTIONS =
19+
new InjectionToken<MatChipsDefaultOptions>('mat-chips-default-options');
20+
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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 {coerceBooleanProperty} from '@angular/cdk/coercion';
10+
import {Directive, ElementRef, EventEmitter, Inject, Input, OnChanges, Output} from '@angular/core';
11+
import {hasModifierKey, TAB} from '@angular/cdk/keycodes';
12+
import {MAT_CHIPS_DEFAULT_OPTIONS, MatChipsDefaultOptions} from './chip-default-options';
13+
import {MatChipList} from './chip-set';
14+
import {MatChipTextControl} from './chip-text-control';
15+
16+
/** Represents an input event on a `matChipInput`. */
17+
export interface MatChipInputEvent {
18+
/** The native `<input>` element that the event is being fired for. */
19+
input: HTMLInputElement;
20+
21+
/** The value of the input. */
22+
value: string;
23+
}
24+
25+
// Increasing integer for generating unique ids.
26+
let nextUniqueId = 0;
27+
28+
/**
29+
* Directive that adds chip-specific behaviors to an input element inside `<mat-form-field>`.
30+
* May be placed inside or outside of an `<mat-chip-list>`.
31+
*/
32+
@Directive({
33+
selector: 'input[matChipInputFor]',
34+
exportAs: 'matChipInput, matChipInputFor',
35+
host: {
36+
'class': 'mat-chip-input mat-input-element',
37+
'(keydown)': '_keydown($event)',
38+
'(blur)': '_blur()',
39+
'(focus)': '_focus()',
40+
'(input)': '_onInput()',
41+
'[id]': 'id',
42+
'[attr.disabled]': 'disabled || null',
43+
'[attr.placeholder]': 'placeholder || null',
44+
'[attr.aria-invalid]': '_chipList && _chipList.ngControl ? _chipList.ngControl.invalid : null',
45+
}
46+
})
47+
export class MatChipInput implements MatChipTextControl, OnChanges {
48+
/** Whether the control is focused. */
49+
focused: boolean = false;
50+
_chipList: MatChipList;
51+
52+
/** Register input for chip list */
53+
@Input('matChipInputFor')
54+
set chipList(value: MatChipList) {
55+
if (value) {
56+
this._chipList = value;
57+
this._chipList.registerInput(this);
58+
}
59+
}
60+
61+
/**
62+
* Whether or not the chipEnd event will be emitted when the input is blurred.
63+
*/
64+
@Input('matChipInputAddOnBlur')
65+
get addOnBlur(): boolean { return this._addOnBlur; }
66+
set addOnBlur(value: boolean) { this._addOnBlur = coerceBooleanProperty(value); }
67+
_addOnBlur: boolean = false;
68+
69+
/**
70+
* The list of key codes that will trigger a chipEnd event.
71+
*
72+
* Defaults to `[ENTER]`.
73+
*/
74+
@Input('matChipInputSeparatorKeyCodes')
75+
separatorKeyCodes: number[] | Set<number> = this._defaultOptions.separatorKeyCodes;
76+
77+
/** Emitted when a chip is to be added. */
78+
@Output('matChipInputTokenEnd')
79+
chipEnd: EventEmitter<MatChipInputEvent> = new EventEmitter<MatChipInputEvent>();
80+
81+
/** The input's placeholder text. */
82+
@Input() placeholder: string = '';
83+
84+
/** Unique id for the input. */
85+
@Input() id: string = `mat-chip-list-input-${nextUniqueId++}`;
86+
87+
/** Whether the input is disabled. */
88+
@Input()
89+
get disabled(): boolean { return this._disabled || (this._chipList && this._chipList.disabled); }
90+
set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value); }
91+
private _disabled: boolean = false;
92+
93+
/** Whether the input is empty. */
94+
get empty(): boolean { return !this._inputElement.value; }
95+
96+
/** The native input element to which this directive is attached. */
97+
protected _inputElement: HTMLInputElement;
98+
99+
constructor(
100+
protected _elementRef: ElementRef<HTMLInputElement>,
101+
@Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions) {
102+
this._inputElement = this._elementRef.nativeElement as HTMLInputElement;
103+
}
104+
105+
ngOnChanges() {
106+
this._chipList.stateChanges.next();
107+
}
108+
109+
/** Utility method to make host definition/tests more clear. */
110+
_keydown(event?: KeyboardEvent) {
111+
// Allow the user's focus to escape when they're tabbing forward. Note that we don't
112+
// want to do this when going backwards, because focus should go back to the first chip.
113+
if (event && event.keyCode === TAB && !hasModifierKey(event, 'shiftKey')) {
114+
this._chipList._allowFocusEscape();
115+
}
116+
117+
this._emitChipEnd(event);
118+
}
119+
120+
/** Checks to see if the blur should emit the (chipEnd) event. */
121+
_blur() {
122+
if (this.addOnBlur) {
123+
this._emitChipEnd();
124+
}
125+
this.focused = false;
126+
// Blur the chip list if it is not focused
127+
if (!this._chipList.focused) {
128+
this._chipList._blur();
129+
}
130+
this._chipList.stateChanges.next();
131+
}
132+
133+
_focus() {
134+
this.focused = true;
135+
this._chipList.stateChanges.next();
136+
}
137+
138+
/** Checks to see if the (chipEnd) event needs to be emitted. */
139+
_emitChipEnd(event?: KeyboardEvent) {
140+
if (!this._inputElement.value && !!event) {
141+
this._chipList._keydown(event);
142+
}
143+
if (!event || this._isSeparatorKey(event)) {
144+
this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value });
145+
146+
if (event) {
147+
event.preventDefault();
148+
}
149+
}
150+
}
151+
152+
_onInput() {
153+
// Let chip list know whenever the value changes.
154+
this._chipList.stateChanges.next();
155+
}
156+
157+
/** Focuses the input. */
158+
focus(): void {
159+
this._inputElement.focus();
160+
}
161+
162+
/** Checks whether a keycode is one of the configured separators. */
163+
private _isSeparatorKey(event: KeyboardEvent) {
164+
if (hasModifierKey(event)) {
165+
return false;
166+
}
167+
168+
const separators = this.separatorKeyCodes;
169+
const keyCode = event.keyCode;
170+
return Array.isArray(separators) ? separators.indexOf(keyCode) > -1 : separators.has(keyCode);
171+
}
172+
}
173+

0 commit comments

Comments
 (0)