From 51ba94db1b7adef51a67407fc9db8504678a9af8 Mon Sep 17 00:00:00 2001 From: JoshSchoen Date: Wed, 31 May 2017 11:13:48 -0500 Subject: [PATCH] feat(chips): ability to disable chip removal (input) (#615) * added chipRemoval feature * updated chip removal label * added chipRemoval to code preview in demo * added chipRemoval to code preview in demo * fixed linting * fixed linting * added unit tests for chip removal, updated readme and remove chipAttrs from app demo --- .../components/chips/chips.component.html | 9 + .../components/chips/chips.component.ts | 34 +-- src/platform/core/chips/README.md | 2 + src/platform/core/chips/chips.component.html | 4 +- .../core/chips/chips.component.spec.ts | 215 +++++++++++++++++- src/platform/core/chips/chips.component.ts | 22 +- 6 files changed, 249 insertions(+), 37 deletions(-) diff --git a/src/app/components/components/chips/chips.component.html b/src/app/components/components/chips/chips.component.html index 75aef8c874..08bcb88163 100644 --- a/src/app/components/components/chips/chips.component.html +++ b/src/app/components/components/chips/chips.component.html @@ -8,6 +8,7 @@
Type and select a preset option or press enter:
+ +
+ + Chip removal + +
diff --git a/src/app/components/components/chips/chips.component.ts b/src/app/components/components/chips/chips.component.ts index 8045f3c5a3..4dc3a25f10 100644 --- a/src/app/components/components/chips/chips.component.ts +++ b/src/app/components/components/chips/chips.component.ts @@ -13,41 +13,9 @@ export class ChipsDemoComponent implements OnInit { @HostBinding('@routeAnimation') routeAnimation: boolean = true; @HostBinding('class.td-route-animation') classAnimation: boolean = true; - chipsAttrs: Object[] = [{ - description: `Enables Autocompletion with the provided list of strings.`, - name: 'items?', - type: 'string[]', - }, { - description: `Disables the chip input and removal.`, - name: 'readOnly?', - type: 'boolean', - }, { - description: `Validates input against the provided search list before adding it to the model. - If it doesnt exist, it cancels the event.`, - name: 'requireMatch?', - type: 'boolean', - }, { - description: `Placeholder for the autocomplete input.`, - name: 'placeholder?', - type: 'string', - }, { - description: `Method to be executed when string is added as chip through the autocomplete. - Sends chip value as event.`, - name: 'add?', - type: 'function', - }, { - description: `Method to be executed when string is removed as chip with the "remove" button. - Sends chip value as event.`, - name: 'remove?', - type: 'function', - }, { - description: `Disables the ability to add chips. If it doesn't exist chipAddition defaults to true.`, - name: 'chipAddition?', - type: 'boolean', - }]; - readOnly: boolean = false; chipAddition: boolean = true; + chipRemoval: boolean = true; filteringAsync: boolean = false; diff --git a/src/platform/core/chips/README.md b/src/platform/core/chips/README.md index b9e21c26ae..1820bde75b 100644 --- a/src/platform/core/chips/README.md +++ b/src/platform/core/chips/README.md @@ -16,6 +16,7 @@ Properties: | `stacked?` | `boolean` | Set stacked or horizontal chips depending on value. Defaults to false. | `placeholder?` | `string` | Placeholder for the autocomplete input. | `chipAddition` | `boolean` | Disables the ability to add chips. When setting readOnly as true, this will be overriden. Defaults to true. +| `chipRemoval` | `boolean` | Disables the ability to remove chips. If it doesn't exist chipRemoval defaults to true. readyOnly must be false for this option to work. | `debounce` | `string` | Debounce timeout between keypresses. Defaults to 200. | `add?` | `function` | Method to be executed when a chip is added. Sends chip value as event. | `remove?` | `function` | Method to be executed when a chip is removed. Sends chip value as event. @@ -48,6 +49,7 @@ Example for HTML usage: [(ngModel)]="model" [readOnly]="readOnly" [chipAddition]="chipAddition" + [chipRemoval]="chipRemoval" (add)="addEvent($event)" (remove)="removeEvent($event)" (inputChange)="inputChange($event)" diff --git a/src/platform/core/chips/chips.component.html b/src/platform/core/chips/chips.component.html index 58a0624234..2a6b3ac7d9 100644 --- a/src/platform/core/chips/chips.component.html +++ b/src/platform/core/chips/chips.component.html @@ -1,6 +1,6 @@
- @@ -13,7 +13,7 @@ [ngOutletContext]="{ chip: chip }">
- + cancel diff --git a/src/platform/core/chips/chips.component.spec.ts b/src/platform/core/chips/chips.component.spec.ts index a13c2a5b36..9a93681809 100644 --- a/src/platform/core/chips/chips.component.spec.ts +++ b/src/platform/core/chips/chips.component.spec.ts @@ -8,7 +8,7 @@ import { Component, DebugElement } from '@angular/core'; import { FormControl, FormsModule, ReactiveFormsModule, } from '@angular/forms'; -import { OverlayContainer, MdInputDirective, MdChip, BACKSPACE, ENTER, LEFT_ARROW, RIGHT_ARROW } from '@angular/material'; +import { OverlayContainer, MdInputDirective, MdChip, DELETE, BACKSPACE, ENTER, LEFT_ARROW, RIGHT_ARROW } from '@angular/material'; import { By } from '@angular/platform-browser'; import { CovalentChipsModule, TdChipsComponent } from './chips.module'; @@ -41,6 +41,7 @@ describe('Component: Chips', () => { TdChipsBasicTestComponent, TdChipsObjectsRequireMatchTestComponent, TdChipsStackedTestComponent, + TdChipRemovalTestComponent, ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -470,6 +471,197 @@ describe('Component: Chips', () => { }); + describe('chip removal usage, requires readOnly to be false: ', () => { + let fixture: ComponentFixture; + let input: DebugElement; + let chips: DebugElement; + + beforeEach(() => { + fixture = TestBed.createComponent(TdChipRemovalTestComponent); + fixture.detectChanges(); + + chips = fixture.debugElement.query(By.directive(TdChipsComponent)); + input = chips.query(By.css('input')); + }); + + it('should not focus input', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = true; + fixture.componentInstance.chipAddition = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect((chips.componentInstance)._inputChild.focused).toBeFalsy(); + done(); + }); + }); + }); + + it('should not show cancel button', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = false; + fixture.componentInstance.chipAddition = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.css('.td-chip-removal')).length).toBe(0); + done(); + }); + }); + }); + + it('should focus input, then focus first chip and remove first chip by clicking on the remove button', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect((chips.componentInstance)._inputChild.focused).toBeTruthy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(chips.componentInstance.value.length).toBe(3); + expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(3); + fixture.debugElement.queryAll(By.css('.td-chip-removal'))[0] + .triggerEventHandler('click', new Event('click')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(chips.componentInstance.value.length).toBe(2); + expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(2); + done(); + }); + }); + }); + }); + }); + + it('should focus first chip and remove chip with backspace and delete', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = true; + fixture.componentInstance.chipAddition = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement) + .toBe(document.activeElement); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.debugElement.queryAll(By.directive(MdChip))[0] + .triggerEventHandler('keydown', createFakeKeyboardEvent(BACKSPACE)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(chips.componentInstance.value.length).toBe(2); + expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(2); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(chips.componentInstance.value.length).toBe(2); + fixture.debugElement.queryAll(By.directive(MdChip))[0] + .triggerEventHandler('keydown', createFakeKeyboardEvent(DELETE)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(chips.componentInstance.value.length).toBe(1); + expect(fixture.debugElement.queryAll(By.directive(MdChip)).length).toBe(1); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('should focus around the chips going left', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = true; + fixture.componentInstance.chipAddition = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[0] + .triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[2].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[2] + .triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[1].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[1] + .triggerEventHandler('keydown', createFakeKeyboardEvent(LEFT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement) + .toBe(document.activeElement); + done(); + }); + }); + }); + }); + }); + }); + }); + + it('should focus around the chips going right', (done: DoneFn) => { + fixture.componentInstance.chipRemoval = true; + fixture.componentInstance.chipAddition = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.nativeElement.focus(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + chips.triggerEventHandler('focus', new Event('focus')); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[0] + .triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[1].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[1] + .triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[2].nativeElement) + .toBe(document.activeElement); + fixture.debugElement.queryAll(By.directive(MdChip))[2] + .triggerEventHandler('keydown', createFakeKeyboardEvent(RIGHT_ARROW)); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(fixture.debugElement.queryAll(By.directive(MdChip))[0].nativeElement) + .toBe(document.activeElement); + done(); + }); + }); + }); + }); + }); + }); + }); + + }); + // TODO // more requireMatch usage @@ -583,3 +775,24 @@ class TdChipsStackedTestComponent { ]; selectedItems: string[] = this.items.slice(0, 3); } + +@Component({ + template: ` + + `, +}) +class TdChipRemovalTestComponent { + chipRemoval: boolean = true; + chipAddition: boolean = true; + items: string[] = [ + 'steak', + 'pizza', + 'tacos', + 'sandwich', + 'chips', + 'pasta', + 'sushi', + ]; + selectedItems: string[] = this.items.slice(0, 3); +} diff --git a/src/platform/core/chips/chips.component.ts b/src/platform/core/chips/chips.component.ts index 9ff88ed0cc..272a434788 100644 --- a/src/platform/core/chips/chips.component.ts +++ b/src/platform/core/chips/chips.component.ts @@ -65,6 +65,7 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit, private _readOnly: boolean = false; private _color: 'primary' | 'accent' | 'warn' = 'primary'; private _chipAddition: boolean = true; + private _chipRemoval: boolean = true; private _focused: boolean = false; private _tabIndex: number = 0; @@ -166,6 +167,19 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit, return this.chipAddition && !this.readOnly; } + /** + * chipRemoval?: boolean + * Disables the ability to remove chips. If it doesn't exist chip remmoval defaults to true. + * When setting readOnly as true, this will be overriden to false. + */ + @Input('chipRemoval') + set chipRemoval(chipRemoval: boolean) { + this._chipRemoval = chipRemoval; + } + get chipRemoval(): boolean { + return this._chipRemoval; + } + /** * placeholder?: string * Placeholder for the autocomplete input. @@ -520,7 +534,13 @@ export class TdChipsComponent implements ControlValueAccessor, DoCheck, OnInit, case BACKSPACE: /** Check to see if not in [readOnly] state to delete a chip */ if (!this.readOnly) { - this.removeChip(index); + /** + * Checks [chipRemoval] state to delete a chips + * To enable [chipRemoval] the [readOnly] state must be true. + */ + if (this.chipRemoval) { + this.removeChip(index); + } } break; case UP_ARROW: