diff --git a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts index 9ebe89ec9ed0..8939f31f5b31 100644 --- a/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts +++ b/src/components-examples/material/chips/chips-autocomplete/chips-autocomplete-example.ts @@ -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.chipInput!.clear(); this.fruitCtrl.setValue(null); } diff --git a/src/components-examples/material/chips/chips-input/chips-input-example.ts b/src/components-examples/material/chips/chips-input/chips-input-example.ts index eaedd5100652..cec9e34f8348 100644 --- a/src/components-examples/material/chips/chips-input/chips-input-example.ts +++ b/src/components-examples/material/chips/chips-input/chips-input-example.ts @@ -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.chipInput!.clear(); } remove(fruit: Fruit): void { diff --git a/src/dev-app/chips/chips-demo.ts b/src/dev-app/chips/chips-demo.ts index 0702a41aea22..a4ee39c19988 100644 --- a/src/dev-app/chips/chips-demo.ts +++ b/src/dev-app/chips/chips-demo.ts @@ -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; } @@ -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.chipInput!.clear(); } remove(person: Person): void { diff --git a/src/dev-app/mdc-chips/mdc-chips-demo.ts b/src/dev-app/mdc-chips/mdc-chips-demo.ts index c93da13d7bd7..24639042c4fc 100644 --- a/src/dev-app/mdc-chips/mdc-chips-demo.ts +++ b/src/dev-app/mdc-chips/mdc-chips-demo.ts @@ -61,17 +61,15 @@ export class MdcChipsDemo { } 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.chipInput!.clear(); } remove(person: Person): void { diff --git a/src/material-experimental/mdc-chips/chip-grid.spec.ts b/src/material-experimental/mdc-chips/chip-grid.spec.ts index 7488bb090da3..ab29237b694f 100644 --- a/src/material-experimental/mdc-chips/chip-grid.spec.ts +++ b/src/material-experimental/mdc-chips/chip-grid.spec.ts @@ -1,6 +1,7 @@ import {animate, style, transition, trigger} from '@angular/animations'; import {Direction, Directionality} from '@angular/cdk/bidi'; import { + A, BACKSPACE, DELETE, END, @@ -49,7 +50,6 @@ import { describe('MDC-based MatChipGrid', () => { - let fixture: ComponentFixture; let chipGridDebugElement: DebugElement; let chipGridNativeElement: HTMLElement; let chipGridInstance: MatChipGrid; @@ -59,10 +59,22 @@ describe('MDC-based MatChipGrid', () => { let testComponent: StandardChipGrid; let dirChange: Subject; + const expectNoCellFocused = () => { + expect(manager.activeRowIndex).toBe(-1); + expect(manager.activeColumnIndex).toBe(-1); + }; + + const expectLastCellFocused = () => { + expect(manager.activeRowIndex).toBe(chips.length - 1); + expect(manager.activeColumnIndex).toBe(0); + }; + describe('StandardChipGrid', () => { describe('basic behaviors', () => { + let fixture: ComponentFixture; + beforeEach(() => { - setupStandardGrid(); + fixture = setupStandardGrid(); }); it('should add the `mat-mdc-chip-set` class', () => { @@ -108,9 +120,11 @@ describe('MDC-based MatChipGrid', () => { }); describe('focus behaviors', () => { + let fixture: ComponentFixture | + ComponentFixture; + beforeEach(() => { - setupStandardGrid(); - manager = chipGridInstance._keyManager; + fixture = setupStandardGrid(); }); it('should focus the first chip on focus', () => { @@ -122,11 +136,9 @@ describe('MDC-based MatChipGrid', () => { }); it('should watch for chip focus', () => { - let array = chips.toArray(); - let lastIndex = array.length - 1; - let lastItem = array[lastIndex]; + const lastIndex = chips.length - 1; - lastItem.focus(); + chips.last.focus(); fixture.detectChanges(); expect(manager.activeRowIndex).toBe(lastIndex); @@ -155,8 +167,7 @@ describe('MDC-based MatChipGrid', () => { describe('on chip destroy', () => { it('should focus the next item', () => { - let array = chips.toArray(); - let midItem = array[2]; + const midItem = chips.get(2)!; // Focus the middle item midItem.focus(); @@ -170,12 +181,10 @@ describe('MDC-based MatChipGrid', () => { }); it('should focus the previous item', () => { - let array = chips.toArray(); - let lastIndex = array.length - 1; - let lastItem = array[lastIndex]; + const lastIndex = chips.length - 1; // Focus the last item - lastItem.focus(); + chips.last.focus(); // Destroy the last item testComponent.chips.pop(); @@ -186,8 +195,7 @@ describe('MDC-based MatChipGrid', () => { }); it('should not focus if chip grid is not focused', fakeAsync(() => { - let array = chips.toArray(); - let midItem = array[2]; + const midItem = chips.get(2)!; // Focus and blur the middle item midItem.focus(); @@ -219,14 +227,8 @@ describe('MDC-based MatChipGrid', () => { 'component with animations', fakeAsync(() => { fixture.destroy(); TestBed.resetTestingModule(); - fixture = createComponent(StandardChipGridWithAnimations, [], BrowserAnimationsModule); - fixture.detectChanges(); - chipGridDebugElement = fixture.debugElement.query(By.directive(MatChipGrid))!; - chipGridNativeElement = chipGridDebugElement.nativeElement; - chipGridInstance = chipGridDebugElement.componentInstance; - testComponent = fixture.debugElement.componentInstance; - chips = chipGridInstance._chips; + fixture = createComponent(StandardChipGridWithAnimations, [], BrowserAnimationsModule); chips.last.focus(); fixture.detectChanges(); @@ -252,30 +254,23 @@ describe('MDC-based MatChipGrid', () => { }); describe('keyboard behavior', () => { + describe('LTR (default)', () => { + let fixture: ComponentFixture; + beforeEach(() => { fixture = createComponent(ChipGridWithRemove); - fixture.detectChanges(); - - chipGridDebugElement = fixture.debugElement.query(By.directive(MatChipGrid))!; - chipGridInstance = chipGridDebugElement.componentInstance; - chipGridNativeElement = chipGridDebugElement.nativeElement; - chips = chipGridInstance._chips; - manager = chipGridInstance._keyManager; }); it('should focus previous column when press LEFT ARROW', () => { let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); let lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement; - let array = chips.toArray(); - let lastRowIndex = array.length - 1; - let lastChip = array[lastRowIndex]; + const lastRowIndex = chips.length - 1; // Focus the first column of the last chip in the array - lastChip.focus(); - expect(manager.activeRowIndex).toEqual(lastRowIndex); - expect(manager.activeColumnIndex).toEqual(0); + chips.last.focus(); + expectLastCellFocused(); // Press the LEFT arrow dispatchKeyboardEvent(lastNativeChip, 'keydown', LEFT_ARROW); @@ -292,11 +287,8 @@ describe('MDC-based MatChipGrid', () => { let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); let firstNativeChip = nativeChips[0] as HTMLElement; - let array = chips.toArray(); - let firstItem = array[0]; - // Focus the first column of the first chip in the array - firstItem.focus(); + chips.first.focus(); expect(manager.activeRowIndex).toEqual(0); expect(manager.activeColumnIndex).toEqual(0); @@ -322,24 +314,21 @@ describe('MDC-based MatChipGrid', () => { }); describe('RTL', () => { + let fixture: ComponentFixture; + beforeEach(() => { - setupStandardGrid('rtl'); - manager = chipGridInstance._keyManager; + fixture = setupStandardGrid('rtl'); }); it('should focus previous column when press RIGHT ARROW', () => { let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); let lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement; - let array = chips.toArray(); - let lastRowIndex = array.length - 1; - let lastItem = array[lastRowIndex]; + const lastRowIndex = chips.length - 1; // Focus the first column of the last chip in the array - lastItem.focus(); - expect(manager.activeRowIndex).toEqual(lastRowIndex); - expect(manager.activeColumnIndex).toEqual(0); - + chips.last.focus(); + expectLastCellFocused(); // Press the RIGHT arrow dispatchKeyboardEvent(lastNativeChip, 'keydown', RIGHT_ARROW); @@ -355,15 +344,11 @@ describe('MDC-based MatChipGrid', () => { let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); let firstNativeChip = nativeChips[0] as HTMLElement; - let array = chips.toArray(); - let firstItem = array[0]; - // Focus the first column of the first chip in the array - firstItem.focus(); + chips.first.focus(); expect(manager.activeRowIndex).toEqual(0); expect(manager.activeColumnIndex).toEqual(0); - // Press the LEFT arrow dispatchKeyboardEvent(firstNativeChip, 'keydown', LEFT_ARROW); chipGridInstance._blur(); // Simulate focus leaving the list and going to the chip. @@ -409,123 +394,110 @@ describe('MDC-based MatChipGrid', () => { })); }); - it('should account for the direction changing', () => { - setupStandardGrid(); - manager = chipGridInstance._keyManager; + describe('keydown behavior', () => { + let fixture: ComponentFixture; - let nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); - let firstNativeChip = nativeChips[0] as HTMLElement; + beforeEach(() => { + fixture = setupStandardGrid(); + }); - let RIGHT_EVENT = createKeyboardEvent('keydown', RIGHT_ARROW); - let array = chips.toArray(); - let firstItem = array[0]; + it('should account for the direction changing', () => { + const firstNativeChip = + chipGridNativeElement.querySelectorAll('mat-chip-row')[0] as HTMLElement; - firstItem.focus(); - expect(manager.activeRowIndex).toBe(0); - expect(manager.activeColumnIndex).toBe(0); + const RIGHT_EVENT = createKeyboardEvent('keydown', RIGHT_ARROW); - dispatchEvent(firstNativeChip, RIGHT_EVENT); - chipGridInstance._blur(); - fixture.detectChanges(); - - expect(manager.activeRowIndex).toBe(1); - expect(manager.activeColumnIndex).toBe(0); + chips.first.focus(); + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); - dirChange.next('rtl'); - fixture.detectChanges(); + dispatchEvent(firstNativeChip, RIGHT_EVENT); + chipGridInstance._blur(); + fixture.detectChanges(); - chipGridInstance._keydown(RIGHT_EVENT); - chipGridInstance._blur(); - fixture.detectChanges(); + expect(manager.activeRowIndex).toBe(1); + expect(manager.activeColumnIndex).toBe(0); - expect(manager.activeRowIndex).toBe(0); - expect(manager.activeColumnIndex).toBe(0); - }); + dirChange.next('rtl'); + fixture.detectChanges(); - it('should move focus to the first chip when pressing HOME', () => { - setupStandardGrid(); - manager = chipGridInstance._keyManager; + chipGridInstance._keydown(RIGHT_EVENT); + chipGridInstance._blur(); + fixture.detectChanges(); - const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); - const lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement; + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); + }); - const HOME_EVENT = createKeyboardEvent('keydown', HOME); - const array = chips.toArray(); - const lastItem = array[array.length - 1]; + it('should move focus to the first chip when pressing HOME', () => { + const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); + const lastNativeChip = nativeChips[nativeChips.length - 1] as HTMLElement; - lastItem.focus(); - expect(manager.activeRowIndex).toBe(4); - expect(manager.activeColumnIndex).toBe(0); + const HOME_EVENT = createKeyboardEvent('keydown', HOME); + chips.last.focus(); - dispatchEvent(lastNativeChip, HOME_EVENT); - fixture.detectChanges(); + expect(manager.activeRowIndex).toBe(4); + expect(manager.activeColumnIndex).toBe(0); - expect(HOME_EVENT.defaultPrevented).toBe(true); - expect(manager.activeRowIndex).toBe(0); - expect(manager.activeColumnIndex).toBe(0); - }); + dispatchEvent(lastNativeChip, HOME_EVENT); + fixture.detectChanges(); - it('should move focus to the last chip when pressing END', () => { - setupStandardGrid(); - manager = chipGridInstance._keyManager; + expect(HOME_EVENT.defaultPrevented).toBe(true); + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); + }); - const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); - const firstNativeChip = nativeChips[0] as HTMLElement; + it('should move focus to the last chip when pressing END', () => { + const nativeChips = chipGridNativeElement.querySelectorAll('mat-chip-row'); + const firstNativeChip = nativeChips[0] as HTMLElement; - const END_EVENT = createKeyboardEvent('keydown', END); - const array = chips.toArray(); - const firstItem = array[0]; + const END_EVENT = createKeyboardEvent('keydown', END); + chips.first.focus(); - firstItem.focus(); - expect(manager.activeRowIndex).toBe(0); - expect(manager.activeColumnIndex).toBe(0); + expect(manager.activeRowIndex).toBe(0); + expect(manager.activeColumnIndex).toBe(0); - dispatchEvent(firstNativeChip, END_EVENT); - fixture.detectChanges(); + dispatchEvent(firstNativeChip, END_EVENT); + fixture.detectChanges(); - expect(END_EVENT.defaultPrevented).toBe(true); - expect(manager.activeRowIndex).toBe(4); - expect(manager.activeColumnIndex).toBe(0); - }); + expect(END_EVENT.defaultPrevented).toBe(true); + expect(manager.activeRowIndex).toBe(4); + expect(manager.activeColumnIndex).toBe(0); + }); - it('should ignore all non-tab navigation keyboard events from an editing chip', () => { - setupStandardGrid(); - manager = chipGridInstance._keyManager; - testComponent.editable = true; - fixture.detectChanges(); + it('should ignore all non-tab navigation keyboard events from an editing chip', () => { + testComponent.editable = true; + fixture.detectChanges(); - const array = chips.toArray(); - const firstItem = array[0]; + chips.first.focus(); - firstItem.focus(); - dispatchKeyboardEvent(document.activeElement!, 'keydown', ENTER, 'Enter'); - fixture.detectChanges(); + dispatchKeyboardEvent(document.activeElement!, 'keydown', ENTER, 'Enter'); + fixture.detectChanges(); - const activeRowIndex = manager.activeRowIndex; - const activeColumnIndex = manager.activeColumnIndex; + const activeRowIndex = manager.activeRowIndex; + const activeColumnIndex = manager.activeColumnIndex; - const KEYS_TO_IGNORE = [HOME, END, LEFT_ARROW, RIGHT_ARROW]; - for (const key of KEYS_TO_IGNORE) { - dispatchKeyboardEvent(document.activeElement!, 'keydown', key); - fixture.detectChanges(); + const KEYS_TO_IGNORE = [HOME, END, LEFT_ARROW, RIGHT_ARROW]; + for (const key of KEYS_TO_IGNORE) { + dispatchKeyboardEvent(document.activeElement!, 'keydown', key); + fixture.detectChanges(); - expect(manager.activeRowIndex).toBe(activeRowIndex); - expect(manager.activeColumnIndex).toBe(activeColumnIndex); - } + expect(manager.activeRowIndex).toBe(activeRowIndex); + expect(manager.activeColumnIndex).toBe(activeColumnIndex); + } + }); }); }); }); describe('FormFieldChipGrid', () => { + let fixture: ComponentFixture; + beforeEach(() => { - setupInputGrid(); + fixture = setupInputGrid(); }); describe('keyboard behavior', () => { - beforeEach(() => { - manager = chipGridInstance._keyManager; - }); - it('should maintain focus if the active chip is deleted', () => { const secondChip = fixture.nativeElement.querySelectorAll('.mat-mdc-chip')[1]; @@ -541,22 +513,19 @@ describe('MDC-based MatChipGrid', () => { }); describe('when the input has focus', () => { - it('should not focus the last chip when press DELETE', () => { let nativeInput = fixture.nativeElement.querySelector('input'); // Focus the input nativeInput.focus(); - expect(manager.activeRowIndex).toBe(-1); - expect(manager.activeColumnIndex).toBe(-1); + expectNoCellFocused(); // Press the DELETE key dispatchKeyboardEvent(nativeInput, 'keydown', DELETE); fixture.detectChanges(); // It doesn't focus the last chip - expect(manager.activeRowIndex).toEqual(-1); - expect(manager.activeColumnIndex).toBe(-1); + expectNoCellFocused(); }); it('should focus the last chip when press BACKSPACE', () => { @@ -564,16 +533,14 @@ describe('MDC-based MatChipGrid', () => { // Focus the input nativeInput.focus(); - expect(manager.activeRowIndex).toBe(-1); - expect(manager.activeColumnIndex).toBe(-1); + expectNoCellFocused(); // Press the BACKSPACE key dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); fixture.detectChanges(); // It focuses the last chip - expect(manager.activeRowIndex).toEqual(chips.length - 1); - expect(manager.activeColumnIndex).toBe(0); + expectLastCellFocused(); }); }); }); @@ -600,40 +567,45 @@ describe('MDC-based MatChipGrid', () => { }); describe('with chip remove', () => { + let fixture: ComponentFixture; let chipGrid: MatChipGrid; let chipRemoveDebugElements: DebugElement[]; beforeEach(() => { fixture = createComponent(ChipGridWithRemove); - fixture.detectChanges(); chipGrid = fixture.debugElement.query(By.directive(MatChipGrid))!.componentInstance; chipRemoveDebugElements = fixture.debugElement.queryAll(By.directive(MatChipRemove)); - chips = chipGrid._chips; }); it('should properly focus next item if chip is removed through click', () => { - chips.toArray()[2].focus(); + chips.get(2)!.focus(); // Destroy the third focused chip by dispatching a bubbling click event on the // associated chip remove element. dispatchMouseEvent(chipRemoveDebugElements[2].nativeElement, 'click'); fixture.detectChanges(); - expect(chips.toArray()[2].value).not.toBe(2, 'Expected the third chip to be removed.'); + expect(chips.get(2)!.value).not.toBe(2, 'Expected the third chip to be removed.'); expect(chipGrid._keyManager.activeRowIndex).toBe(2); }); }); describe('chip grid with chip input', () => { + let fixture: ComponentFixture; let nativeChips: HTMLElement[]; + let nativeInput: HTMLInputElement; + let nativeChipGrid: HTMLElement; beforeEach(() => { fixture = createComponent(InputChipGrid); - fixture.detectChanges(); nativeChips = fixture.debugElement.queryAll(By.css('mat-chip-row')) .map((chip) => chip.nativeElement); + + nativeChipGrid = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement; + + nativeInput = fixture.nativeElement.querySelector('input'); }); it('should take an initial view value with reactive forms', () => { @@ -658,7 +630,6 @@ describe('MDC-based MatChipGrid', () => { expect(fixture.componentInstance.control.value) .toEqual(null, `Expected the control's value to be empty initially.`); - const nativeInput = fixture.nativeElement.querySelector('input'); nativeInput.focus(); typeInElement(nativeInput, '123'); @@ -687,7 +658,6 @@ describe('MDC-based MatChipGrid', () => { expect(fixture.componentInstance.control.touched) .toBe(false, 'Expected the control to start off as untouched.'); - const nativeChipGrid = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement; dispatchFakeEvent(nativeChipGrid, 'blur'); tick(); @@ -700,7 +670,6 @@ describe('MDC-based MatChipGrid', () => { .toBe(false, 'Expected the control to start off as untouched.'); fixture.componentInstance.control.disable(); - const nativeChipGrid = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement; dispatchFakeEvent(nativeChipGrid, 'blur'); tick(); @@ -713,7 +682,6 @@ describe('MDC-based MatChipGrid', () => { expect(fixture.componentInstance.control.dirty) .toEqual(false, `Expected control to start out pristine.`); - const nativeInput = fixture.nativeElement.querySelector('input'); nativeInput.focus(); typeInElement(nativeInput, '123'); @@ -770,7 +738,6 @@ describe('MDC-based MatChipGrid', () => { })); it('should keep focus on the input after adding the first chip', fakeAsync(() => { - const nativeInput = fixture.nativeElement.querySelector('input'); const chipEls = Array.from( fixture.nativeElement.querySelectorAll('mat-chip-row')).reverse(); @@ -815,16 +782,64 @@ describe('MDC-based MatChipGrid', () => { fixture.detectChanges(); expect(input.getAttribute('aria-invalid')).toBe('false'); })); + + describe('when the input has focus', () => { + beforeEach(() => { + nativeInput.focus(); + expectNoCellFocused(); + }); + + it('should not focus the last chip when pressing DELETE', () => { + dispatchKeyboardEvent(nativeInput, 'keydown', DELETE); + expectNoCellFocused(); + }); + + it('should focus the last chip when pressing BACKSPACE when input is empty', () => { + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastCellFocused(); + }); + + it('should not focus the last chip when pressing BACKSPACE after changing input, ' + + 'until BACKSPACE is released and pressed again', () => { + // Change the input + dispatchKeyboardEvent(nativeInput, 'keydown', A); + + // It shouldn't focus until backspace is released and pressed again + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectNoCellFocused(); + + // Still not focused + dispatchKeyboardEvent(nativeInput, 'keyup', BACKSPACE); + expectNoCellFocused(); + + // Only now should it focus the last element + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastCellFocused(); + }); + + it('should focus last chip after pressing BACKSPACE after creating a chip', () => { + // Create a chip + typeInElement(nativeInput, '123'); + dispatchKeyboardEvent(nativeInput, 'keydown', ENTER); + + expectNoCellFocused(); + + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastCellFocused(); + }); + }); }); describe('error messages', () => { + let fixture: ComponentFixture; let errorTestComponent: ChipGridWithFormErrorMessages; let containerEl: HTMLElement; let chipGridEl: HTMLElement; beforeEach(() => { fixture = createComponent(ChipGridWithFormErrorMessages); - fixture.detectChanges(); errorTestComponent = fixture.componentInstance; containerEl = fixture.debugElement.query(By.css('mat-form-field'))!.nativeElement; chipGridEl = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement; @@ -932,9 +947,12 @@ describe('MDC-based MatChipGrid', () => { }); }); - function createComponent(component: Type, providers: Provider[] = [], animationsModule: - Type | Type = NoopAnimationsModule): - ComponentFixture { + function createComponent( + component: Type, + providers: Provider[] = [], + animationsModule: + Type | Type = NoopAnimationsModule + ): ComponentFixture { TestBed.configureTestingModule({ imports: [ FormsModule, @@ -946,22 +964,15 @@ describe('MDC-based MatChipGrid', () => { ], declarations: [component], providers: [ - {provide: NgZone, useFactory: () => zone = new MockNgZone()}, + { + provide: NgZone, + useFactory: () => zone = new MockNgZone() + }, ...providers ] }).compileComponents(); - return TestBed.createComponent(component); - } - - function setupStandardGrid(direction: Direction = 'ltr') { - dirChange = new Subject(); - fixture = createComponent(StandardChipGrid, [{ - provide: Directionality, useFactory: () => ({ - value: direction.toLowerCase(), - change: dirChange - }) - }]); + const fixture = TestBed.createComponent(component); fixture.detectChanges(); chipGridDebugElement = fixture.debugElement.query(By.directive(MatChipGrid))!; @@ -969,17 +980,27 @@ describe('MDC-based MatChipGrid', () => { chipGridInstance = chipGridDebugElement.componentInstance; testComponent = fixture.debugElement.componentInstance; chips = chipGridInstance._chips; + manager = chipGridInstance._keyManager; + + return fixture; } - function setupInputGrid() { - fixture = createComponent(FormFieldChipGrid); - fixture.detectChanges(); + function setupStandardGrid(direction: Direction = 'ltr') { + dirChange = new Subject(); - chipGridDebugElement = fixture.debugElement.query(By.directive(MatChipGrid))!; - chipGridNativeElement = chipGridDebugElement.nativeElement; - chipGridInstance = chipGridDebugElement.componentInstance; - testComponent = fixture.debugElement.componentInstance; - chips = chipGridInstance._chips; + return createComponent(StandardChipGrid, [ + { + provide: Directionality, + useFactory: () => ({ + value: direction.toLowerCase(), + change: dirChange + }) + } + ]); + } + + function setupInputGrid() { + return createComponent(FormFieldChipGrid); } }); @@ -1059,21 +1080,18 @@ class InputChipGrid { 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 = ''; - } + event.chipInput!.clear(); } remove(food: any): void { diff --git a/src/material-experimental/mdc-chips/chip-grid.ts b/src/material-experimental/mdc-chips/chip-grid.ts index dac73e8aee22..6046fef793c7 100644 --- a/src/material-experimental/mdc-chips/chip-grid.ts +++ b/src/material-experimental/mdc-chips/chip-grid.ts @@ -8,7 +8,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; -import {BACKSPACE, TAB} from '@angular/cdk/keycodes'; +import {TAB} from '@angular/cdk/keycodes'; import { AfterContentInit, AfterViewInit, @@ -43,7 +43,6 @@ import {MatChipRow} from './chip-row'; import {MatChipSet} from './chip-set'; import {GridFocusKeyManager} from './grid-focus-key-manager'; - /** Change event object that is emitted when the chip grid value has changed. */ export class MatChipGridChange { constructor( @@ -109,12 +108,12 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn */ readonly controlType: string = 'mat-chip-grid'; - /** Subscription to blur changes in the chips. */ - private _chipBlurSubscription: Subscription | null; - /** Subscription to focus changes in the chips. */ private _chipFocusSubscription: Subscription | null; + /** Subscription to blur changes in the chips. */ + private _chipBlurSubscription: Subscription | null; + /** The chip input to add more chips */ protected _chipInput: MatChipTextControl; @@ -408,19 +407,15 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn const target = event.target as HTMLElement; const keyCode = event.keyCode; const manager = this._keyManager; + if (keyCode === TAB && target.id !== this._chipInput!.id) { this._allowFocusEscape(); } else if (this._originatesFromEditingChip(event)) { // No-op, let the editing chip handle all keyboard events except for Tab. - } else if (keyCode === BACKSPACE && this._isEmptyInput(target)) { - // If they are on an empty input and hit backspace, focus the last chip - if (this._chips.length) { - manager.setLastCellActive(); - } - event.preventDefault(); } else if (this._originatesFromChip(event)) { manager.onKeydown(event); } + this.stateChanges.next(); } @@ -458,7 +453,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn } } - /** Subscribes to chip focus events. */ + /** Subscribes to chip focus events. */ private _listenToChipsFocus(): void { this._chipFocusSubscription = this.chipFocusChanges.subscribe((event: MatChipEvent) => { let chipIndex: number = this._chips.toArray().indexOf(event.chip as MatChipRow); @@ -477,7 +472,7 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn }); } - /** Emits change event to set the model value. */ + /** Emits change event to set the model value. */ private _propagateChanges(): void { const valueToEmit = this._chips.length ? this._chips.toArray().map( chip => chip.value) : []; @@ -520,15 +515,6 @@ export class MatChipGrid extends _MatChipGridMixinBase implements AfterContentIn this._chipInput.focus(); } - /** Returns true if element is an input with no value. */ - private _isEmptyInput(element: HTMLElement): boolean { - if (element && element.id === this._chipInput!.id) { - return this._chipInput.empty; - } - - return false; - } - static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_required: BooleanInput; } diff --git a/src/material-experimental/mdc-chips/chip-icons.ts b/src/material-experimental/mdc-chips/chip-icons.ts index aecfef8a4507..6f48b4c8610c 100644 --- a/src/material-experimental/mdc-chips/chip-icons.ts +++ b/src/material-experimental/mdc-chips/chip-icons.ts @@ -203,6 +203,10 @@ export class MatChipRemove extends _MatChipRemoveMixinBase implements CanDisable event.stopPropagation(); } + focus() { + this._elementRef.nativeElement.focus(); + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_tabIndex: NumberInput; } diff --git a/src/material-experimental/mdc-chips/chip-input.ts b/src/material-experimental/mdc-chips/chip-input.ts index aa3faafd5bdf..489ec4e85dca 100644 --- a/src/material-experimental/mdc-chips/chip-input.ts +++ b/src/material-experimental/mdc-chips/chip-input.ts @@ -7,20 +7,39 @@ */ 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 {MatChipGrid} from './chip-grid'; import {MatChipTextControl} from './chip-text-control'; - /** Represents an input event on a `matChipInput`. */ export interface MatChipInputEvent { - /** The native `` element that the event is being fired for. */ + /** + * The native `` element that the event is being fired for. + * @deprecated Use `MatChipInputEvent#chipInput.inputElement` instead. + * @breaking-change 13.0.0 This property will be removed. + */ input: HTMLInputElement; /** The value of the input. */ value: string; + + /** + * Reference to the chip input that emitted the event. + * @breaking-change 13.0.0 This property will be made required. + */ + chipInput?: MatChipInput; } // Increasing integer for generating unique ids. @@ -39,6 +58,7 @@ let nextUniqueId = 0; // the MDC chips were landed initially with it. 'class': 'mat-mdc-chip-input mat-mdc-input-element mdc-text-field__input mat-input-element', '(keydown)': '_keydown($event)', + '(keyup)': '_keyup($event)', '(blur)': '_blur()', '(focus)': '_focus()', '(input)': '_onInput()', @@ -49,7 +69,10 @@ let nextUniqueId = 0; '[attr.aria-required]': '_chipGrid && _chipGrid.required || null', } }) -export class MatChipInput implements MatChipTextControl, OnChanges { +export class MatChipInput implements MatChipTextControl, AfterContentInit, OnChanges, OnDestroy { + /** Used to prevent focus moving to chips while user is holding backspace */ + private _focusLastChipOnBackspace: boolean; + /** Whether the control is focused. */ focused: boolean = false; _chipGrid: MatChipGrid; @@ -97,32 +120,66 @@ export class MatChipInput implements MatChipTextControl, OnChanges { private _disabled: boolean = false; /** Whether the input is empty. */ - get empty(): boolean { return !this._inputElement.value; } + get empty(): boolean { return !this.inputElement.value; } /** The native input element to which this directive is attached. */ - protected _inputElement: HTMLInputElement; + readonly inputElement: HTMLInputElement; constructor( protected _elementRef: ElementRef, @Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions) { - this._inputElement = this._elementRef.nativeElement as HTMLInputElement; + this.inputElement = this._elementRef.nativeElement as HTMLInputElement; } ngOnChanges() { this._chipGrid.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._chipGrid._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._chipGrid._allowFocusEscape(); + } + + // To prevent the user from accidentally deleting chips when pressing BACKSPACE continuously, + // We focus the last chip on backspace only after the user has released the backspace button, + // And the input is empty (see behaviour in _keyup) + if (event.keyCode === BACKSPACE && this._focusLastChipOnBackspace) { + if (this._chipGrid._chips.length) { + this._chipGrid._keyManager.setLastCellActive(); + } + 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) { @@ -143,15 +200,18 @@ export class MatChipInput implements MatChipTextControl, OnChanges { /** Checks to see if the (chipEnd) event needs to be emitted. */ _emitChipEnd(event?: KeyboardEvent) { - if (!this._inputElement.value && !!event) { + if (!this.inputElement.value && !!event) { this._chipGrid._keydown(event); } + if (!event || this._isSeparatorKey(event)) { - this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value }); + this.chipEnd.emit({ + input: this.inputElement, + value: this.inputElement.value, + chipInput: this, + }); - if (event) { - event.preventDefault(); - } + event?.preventDefault(); } } @@ -162,7 +222,13 @@ export class MatChipInput implements MatChipTextControl, OnChanges { /** Focuses the input. */ focus(): void { - this._inputElement.focus(); + this.inputElement.focus(); + } + + /** Clears the input */ + clear(): void { + this.inputElement.value = ''; + this._focusLastChipOnBackspace = true; } /** Checks whether a keycode is one of the configured separators. */ diff --git a/src/material/chips/chip-input.ts b/src/material/chips/chip-input.ts index ae5ae364d5ee..61b42772e313 100644 --- a/src/material/chips/chip-input.ts +++ b/src/material/chips/chip-input.ts @@ -7,20 +7,39 @@ */ 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 `` element that the event is being fired for. */ + /** + * The native `` element that the event is being fired for. + * @deprecated Use `MatChipInputEvent#chipInput.inputElement` instead. + * @breaking-change 13.0.0 This property will be removed. + */ input: HTMLInputElement; /** The value of the input. */ value: string; + + /** + * Reference to the chip input that emitted the event. + * @breaking-change 13.0.0 This property will be made required. + */ + chipInput?: MatChipInput; } // Increasing integer for generating unique ids. @@ -36,6 +55,7 @@ let nextUniqueId = 0; host: { 'class': 'mat-chip-input mat-input-element', '(keydown)': '_keydown($event)', + '(keyup)': '_keyup($event)', '(blur)': '_blur()', '(focus)': '_focus()', '(input)': '_onInput()', @@ -46,7 +66,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; @@ -94,32 +117,64 @@ export class MatChipInput implements MatChipTextControl, OnChanges { private _disabled: boolean = false; /** Whether the input is empty. */ - get empty(): boolean { return !this._inputElement.value; } + get empty(): boolean { return !this.inputElement.value; } /** The native input element to which this directive is attached. */ - protected _inputElement: HTMLInputElement; + readonly inputElement: HTMLInputElement; constructor( protected _elementRef: ElementRef, @Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions) { - this._inputElement = this._elementRef.nativeElement as HTMLInputElement; + 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(); + } + + // To prevent the user from accidentally deleting chips when pressing BACKSPACE continuously, + // We focus the last chip on backspace only after the user has released the backspace button, + // and the input is empty (see behaviour in _keyup) + 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) { @@ -140,15 +195,18 @@ export class MatChipInput implements MatChipTextControl, OnChanges { /** Checks to see if the (chipEnd) event needs to be emitted. */ _emitChipEnd(event?: KeyboardEvent) { - if (!this._inputElement.value && !!event) { + if (!this.inputElement.value && !!event) { this._chipList._keydown(event); } + if (!event || this._isSeparatorKey(event)) { - this.chipEnd.emit({ input: this._inputElement, value: this._inputElement.value }); + this.chipEnd.emit({ + input: this.inputElement, + value: this.inputElement.value, + chipInput: this, + }); - if (event) { - event.preventDefault(); - } + event?.preventDefault(); } } @@ -159,7 +217,13 @@ export class MatChipInput implements MatChipTextControl, OnChanges { /** Focuses the input. */ focus(options?: FocusOptions): void { - this._inputElement.focus(options); + this.inputElement.focus(options); + } + + /** Clears the input */ + clear(): void { + this.inputElement.value = ''; + this._focusLastChipOnBackspace = true; } /** Checks whether a keycode is one of the configured separators. */ diff --git a/src/material/chips/chip-list.spec.ts b/src/material/chips/chip-list.spec.ts index 47b6eb53cf1c..6b0b95df2cbc 100644 --- a/src/material/chips/chip-list.spec.ts +++ b/src/material/chips/chip-list.spec.ts @@ -2,6 +2,7 @@ import {animate, style, transition, trigger} from '@angular/animations'; import {FocusKeyManager} from '@angular/cdk/a11y'; import {Direction, Directionality} from '@angular/cdk/bidi'; import { + A, BACKSPACE, DELETE, END, @@ -1036,7 +1037,6 @@ describe('MatChipList', () => { .toBeFalsy(`Expected chip with the old value not to be selected.`); }); - it('should clear the selection when the control is reset', () => { const array = fixture.componentInstance.chips.toArray(); @@ -1096,7 +1096,6 @@ describe('MatChipList', () => { .toEqual(false, `Expected control to stay pristine after programmatic change.`); }); - it('should set an asterisk after the placeholder if the control is required', () => { let requiredMarker = fixture.debugElement.query(By.css('.mat-form-field-required-marker'))!; expect(requiredMarker) @@ -1151,45 +1150,63 @@ describe('MatChipList', () => { }); describe('keyboard behavior', () => { + let nativeInput: HTMLInputElement; + + const expectNoItemFocused = () => expect(manager.activeItemIndex).toBe(-1); + const expectLastItemFocused = () => expect(manager.activeItemIndex).toEqual(chips.length - 1); + beforeEach(() => { chipListDebugElement = fixture.debugElement.query(By.directive(MatChipList))!; chipListInstance = chipListDebugElement.componentInstance; chips = chipListInstance.chips; manager = fixture.componentInstance.chipList._keyManager; + nativeInput = fixture.nativeElement.querySelector('input'); + nativeInput.focus(); + expectNoItemFocused(); }); describe('when the input has focus', () => { - it('should not focus the last chip when press DELETE', () => { - let nativeInput = fixture.nativeElement.querySelector('input'); - - // Focus the input - nativeInput.focus(); - expect(manager.activeItemIndex).toBe(-1); - - // Press the DELETE key + it('should not focus the last chip when pressing DELETE', () => { dispatchKeyboardEvent(nativeInput, 'keydown', DELETE); - fixture.detectChanges(); - - // It doesn't focus the last chip - expect(manager.activeItemIndex).toEqual(-1); + expectNoItemFocused(); }); - it('should focus the last chip when press BACKSPACE', () => { - let nativeInput = fixture.nativeElement.querySelector('input'); + it('should focus the last chip when pressing BACKSPACE when input is empty', () => { + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastItemFocused(); + }); - // Focus the input - nativeInput.focus(); - expect(manager.activeItemIndex).toBe(-1); + it('should not focus the last chip when pressing BACKSPACE after changing input, ' + + 'until BACKSPACE is released and pressed again', () => { + // Change the input + dispatchKeyboardEvent(nativeInput, 'keydown', A); - // Press the BACKSPACE key + // It shouldn't focus until backspace is released and pressed again dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); - fixture.detectChanges(); + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectNoItemFocused(); - // It focuses the last chip - expect(manager.activeItemIndex).toEqual(chips.length - 1); + // Still not focused + dispatchKeyboardEvent(nativeInput, 'keyup', BACKSPACE); + expectNoItemFocused(); + + // Only now should it focus the last element + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastItemFocused(); }); + it('should focus last chip after pressing BACKSPACE after creating a chip', () => { + // Create a chip + typeInElement(nativeInput, '123'); + dispatchKeyboardEvent(nativeInput, 'keydown', ENTER); + + expectNoItemFocused(); + + dispatchKeyboardEvent(nativeInput, 'keydown', BACKSPACE); + expectLastItemFocused(); + }); }); }); }); @@ -1490,11 +1507,13 @@ class MultiSelectionChipList { {{ food.viewValue }} + /> + (matChipInputTokenEnd)="add($event)" + /> ` }) @@ -1516,21 +1535,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.chipInput!.clear(); } remove(food: any): void { diff --git a/src/material/chips/chip-list.ts b/src/material/chips/chip-list.ts index d2caf2f0afd5..ed88cf512121 100644 --- a/src/material/chips/chip-list.ts +++ b/src/material/chips/chip-list.ts @@ -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, @@ -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 { @@ -68,7 +66,6 @@ export class MatChipListChange { public value: any) { } } - /** * A material design chips component (named ChipList for its similarity to the List component). */ @@ -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; @@ -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. */ @@ -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()); diff --git a/tools/public_api_guard/material/chips.d.ts b/tools/public_api_guard/material/chips.d.ts index 3a996eb3e0f6..c266a2b6e4c7 100644 --- a/tools/public_api_guard/material/chips.d.ts +++ b/tools/public_api_guard/material/chips.d.ts @@ -70,11 +70,10 @@ 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; - protected _inputElement: HTMLInputElement; get addOnBlur(): boolean; set addOnBlur(value: boolean); chipEnd: EventEmitter; @@ -84,6 +83,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges { get empty(): boolean; focused: boolean; id: string; + readonly inputElement: HTMLInputElement; placeholder: string; separatorKeyCodes: readonly number[] | ReadonlySet; constructor(_elementRef: ElementRef, _defaultOptions: MatChipsDefaultOptions); @@ -91,9 +91,13 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges { _emitChipEnd(event?: KeyboardEvent): void; _focus(): void; _keydown(event?: KeyboardEvent): void; + _keyup(event: KeyboardEvent): void; _onInput(): void; + clear(): void; focus(options?: FocusOptions): void; + ngAfterContentInit(): void; ngOnChanges(): void; + ngOnDestroy(): void; static ngAcceptInputType_addOnBlur: BooleanInput; static ngAcceptInputType_disabled: BooleanInput; static ɵdir: i0.ɵɵDirectiveDefWithMeta; @@ -101,6 +105,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges { } export interface MatChipInputEvent { + chipInput?: MatChipInput; input: HTMLInputElement; value: string; }