Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,15 @@ export class ChipsAutocompleteExample {
}

add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
const value = (event.value || '').trim();

// Add our fruit
if ((value || '').trim()) {
this.fruits.push(value.trim());
if (value) {
this.fruits.push(value);
}

// Reset the input value
if (input) {
input.value = '';
}
// Clear the input value
event.clearInput();

this.fruitCtrl.setValue(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,15 @@ export class ChipsInputExample {
];

add(event: MatChipInputEvent): void {
const input = event.input;
const value = event.value;
const value = (event.value || '').trim();

// Add our fruit
if ((value || '').trim()) {
this.fruits.push({name: value.trim()});
if (value) {
this.fruits.push({name: value});
}

// Reset the input value
if (input) {
input.value = '';
}
// Clear the input value
event.clearInput();
}

remove(fruit: Fruit): void {
Expand Down
13 changes: 5 additions & 8 deletions src/dev-app/chips/chips-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {Component} from '@angular/core';
import {MatChipInputEvent} from '@angular/material/chips';
import {ThemePalette} from '@angular/material/core';


export interface Person {
name: string;
}
Expand Down Expand Up @@ -61,17 +60,15 @@ export class ChipsDemo {
}

add(event: MatChipInputEvent): void {
const {input, value} = event;
const value = (event.value || '').trim();

// Add our person
if ((value || '').trim()) {
this.people.push({ name: value.trim() });
if (value) {
this.people.push({ name: value });
}

// Reset the input value
if (input) {
input.value = '';
}
// Clear the input value
event.clearInput();
}

remove(person: Person): void {
Expand Down
79 changes: 65 additions & 14 deletions src/material/chips/chip-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,32 @@
*/

import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {hasModifierKey, TAB} from '@angular/cdk/keycodes';
import {Directive, ElementRef, EventEmitter, Inject, Input, OnChanges, Output} from '@angular/core';
import {BACKSPACE, hasModifierKey, TAB} from '@angular/cdk/keycodes';
import {
AfterContentInit,
Directive,
ElementRef,
EventEmitter,
Inject,
Input,
OnChanges,
OnDestroy,
Output
} from '@angular/core';
import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './chip-default-options';
import {MatChipList} from './chip-list';
import {MatChipTextControl} from './chip-text-control';


/** Represents an input event on a `matChipInput`. */
export interface MatChipInputEvent {
/** The native `<input>` element that the event is being fired for. */
input: HTMLInputElement;

/** The value of the input. */
value: string;

/** Call to clear the value of the input */
clearInput(): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than having the clearInput method on the event, how about adding a reference to the MatChipInput and putting the method on the directive instead. Then people would do something like this: event.chipInput.clear()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! will change it 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the input itself? the HTMLInputElement... should it still be part of the event?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't remove it without going through a deprecation period. You can mark it as deprecated:

/**
 * @deprecated use `chipInput.inputElement`
 * @breaking-change 13.0.0 remove this property.
 */

(You'll need to change _inputElement on MatChipInput to public and remove the underscore)

}

// Increasing integer for generating unique ids.
Expand All @@ -36,6 +48,7 @@ let nextUniqueId = 0;
host: {
'class': 'mat-chip-input mat-input-element',
'(keydown)': '_keydown($event)',
'(keyup)': '_keyup($event)',
'(blur)': '_blur()',
'(focus)': '_focus()',
'(input)': '_onInput()',
Expand All @@ -46,7 +59,10 @@ let nextUniqueId = 0;
'[attr.aria-required]': '_chipList && _chipList.required || null',
}
})
export class MatChipInput implements MatChipTextControl, OnChanges {
export class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy, AfterContentInit {
/** Used to prevent focus moving to chips while user is holding backspace */
private _focusLastChipOnBackspace: boolean;

/** Whether the control is focused. */
focused: boolean = false;
_chipList: MatChipList;
Expand Down Expand Up @@ -105,21 +121,50 @@ export class MatChipInput implements MatChipTextControl, OnChanges {
this._inputElement = this._elementRef.nativeElement as HTMLInputElement;
}

ngOnChanges() {
ngOnChanges(): void {
this._chipList.stateChanges.next();
}

ngOnDestroy(): void {
this.chipEnd.complete();
}

ngAfterContentInit(): void {
this._focusLastChipOnBackspace = this.empty;
}

/** Utility method to make host definition/tests more clear. */
_keydown(event?: KeyboardEvent) {
// Allow the user's focus to escape when they're tabbing forward. Note that we don't
// want to do this when going backwards, because focus should go back to the first chip.
if (event && event.keyCode === TAB && !hasModifierKey(event, 'shiftKey')) {
this._chipList._allowFocusEscape();
if (event) {
// Allow the user's focus to escape when they're tabbing forward. Note that we don't
// want to do this when going backwards, because focus should go back to the first chip.
if (event.keyCode === TAB && !hasModifierKey(event, 'shiftKey')) {
this._chipList._allowFocusEscape();
}

if (event.keyCode === BACKSPACE && this._focusLastChipOnBackspace) {
this._chipList._keyManager.setLastItemActive();
event.preventDefault();
return;
} else {
this._focusLastChipOnBackspace = false;
}
}

this._emitChipEnd(event);
}

/**
* Pass events to the keyboard manager. Available here for tests.
*/
_keyup(event: KeyboardEvent) {
// Allow user to move focus to chips next time he presses backspace
if (!this._focusLastChipOnBackspace && event.keyCode === BACKSPACE && this.empty) {
this._focusLastChipOnBackspace = true;
event.preventDefault();
}
}

/** Checks to see if the blur should emit the (chipEnd) event. */
_blur() {
if (this.addOnBlur) {
Expand All @@ -143,12 +188,18 @@ export class MatChipInput implements MatChipTextControl, OnChanges {
if (!this._inputElement.value && !!event) {
this._chipList._keydown(event);
}
if (!event || this._isSeparatorKey(event)) {
this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value });

if (event) {
event.preventDefault();
}
if (!event || this._isSeparatorKey(event)) {
this.chipEnd.emit({
input: this._inputElement,
value: this._inputElement.value,
clearInput: () => {
this._inputElement.value = '';
this._focusLastChipOnBackspace = true;
}
});

event?.preventDefault();
}
}

Expand Down
15 changes: 6 additions & 9 deletions src/material/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1516,21 +1516,18 @@ class InputChipList {
isRequired: boolean;

add(event: MatChipInputEvent): void {
let input = event.input;
let value = event.value;
const value = (event.value || '').trim();

// Add our foods
if ((value || '').trim()) {
if (value) {
this.foods.push({
value: `${value.trim().toLowerCase()}-${this.foods.length}`,
viewValue: value.trim()
value: `${value.toLowerCase()}-${this.foods.length}`,
viewValue: value
});
}

// Reset the input value
if (input) {
input.value = '';
}
// Clear the input value
event.clearInput();
}

remove(food: any): void {
Expand Down
20 changes: 1 addition & 19 deletions src/material/chips/chip-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {FocusKeyManager} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {SelectionModel} from '@angular/cdk/collections';
import {BACKSPACE} from '@angular/cdk/keycodes';
import {
AfterContentInit,
ChangeDetectionStrategy,
Expand Down Expand Up @@ -42,7 +41,6 @@ import {startWith, takeUntil} from 'rxjs/operators';
import {MatChip, MatChipEvent, MatChipSelectionChange} from './chip';
import {MatChipTextControl} from './chip-text-control';


// Boilerplate for applying mixins to MatChipList.
/** @docs-private */
class MatChipListBase {
Expand All @@ -68,7 +66,6 @@ export class MatChipListChange {
public value: any) { }
}


/**
* A material design chips component (named ChipList for its similarity to the List component).
*/
Expand Down Expand Up @@ -415,7 +412,6 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
this._dropSubscriptions();
}


/** Associates an HTML input element with this chip list. */
registerInput(inputElement: MatChipTextControl): void {
this._chipInput = inputElement;
Expand Down Expand Up @@ -499,17 +495,12 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
_keydown(event: KeyboardEvent) {
const target = event.target as HTMLElement;

// If they are on an empty input and hit backspace, focus the last chip
if (event.keyCode === BACKSPACE && this._isInputEmpty(target)) {
this._keyManager.setLastItemActive();
event.preventDefault();
} else if (target && target.classList.contains('mat-chip')) {
if (target && target.classList.contains('mat-chip')) {
this._keyManager.onKeydown(event);
this.stateChanges.next();
}
}


/**
* Check the tab index as you should not be allowed to focus an empty list.
*/
Expand Down Expand Up @@ -546,15 +537,6 @@ export class MatChipList extends _MatChipListMixinBase implements MatFormFieldCo
return index >= 0 && index < this.chips.length;
}

private _isInputEmpty(element: HTMLElement): boolean {
if (element && element.nodeName.toLowerCase() === 'input') {
let input = element as HTMLInputElement;
return !input.value;
}

return false;
}

_setSelectionByValue(value: any, isUserInput: boolean = true) {
this._clearSelection();
this.chips.forEach(chip => chip.deselect());
Expand Down
6 changes: 5 additions & 1 deletion tools/public_api_guard/material/chips.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export interface MatChipEvent {
chip: MatChip;
}

export declare class MatChipInput implements MatChipTextControl, OnChanges {
export declare class MatChipInput implements MatChipTextControl, OnChanges, OnDestroy, AfterContentInit {
_addOnBlur: boolean;
_chipList: MatChipList;
protected _elementRef: ElementRef<HTMLInputElement>;
Expand All @@ -91,9 +91,12 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges {
_emitChipEnd(event?: KeyboardEvent): void;
_focus(): void;
_keydown(event?: KeyboardEvent): void;
_keyup(event: KeyboardEvent): void;
_onInput(): void;
focus(options?: FocusOptions): void;
ngAfterContentInit(): void;
ngOnChanges(): void;
ngOnDestroy(): void;
static ngAcceptInputType_addOnBlur: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatChipInput, "input[matChipInputFor]", ["matChipInput", "matChipInputFor"], { "chipList": "matChipInputFor"; "addOnBlur": "matChipInputAddOnBlur"; "separatorKeyCodes": "matChipInputSeparatorKeyCodes"; "placeholder": "placeholder"; "id": "id"; "disabled": "disabled"; }, { "chipEnd": "matChipInputTokenEnd"; }, never>;
Expand All @@ -103,6 +106,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges {
export interface MatChipInputEvent {
input: HTMLInputElement;
value: string;
clearInput(): void;
}

export declare class MatChipList extends _MatChipListMixinBase implements MatFormFieldControl<any>, ControlValueAccessor, AfterContentInit, DoCheck, OnInit, OnDestroy, CanUpdateErrorState {
Expand Down