|
6 | 6 | * found in the LICENSE file at https://angular.io/license |
7 | 7 | */ |
8 | 8 |
|
9 | | -import {Component} from '@angular/core'; |
10 | | -import {MDCChipFoundation} from '@material/chips'; |
| 9 | +import {AfterContentInit, AfterViewInit, Component, ChangeDetectorRef, ContentChild, Directive, ElementRef, EventEmitter, Input, OnDestroy, Output} from '@angular/core'; |
| 10 | +import {MDCChipAdapter, MDCChipFoundation} from '@material/chips'; |
| 11 | +import {Subject} from 'rxjs'; |
| 12 | +import {takeUntil} from 'rxjs/operators'; |
| 13 | + |
| 14 | +let uid = 0; |
| 15 | + |
| 16 | +/** |
| 17 | + * Dummy directive to add CSS class to chip leading icon. |
| 18 | + * @docs-private |
| 19 | + */ |
| 20 | +@Directive({ |
| 21 | + selector: 'mat-chip-avatar, [matChipAvatar]', |
| 22 | + host: { |
| 23 | + 'class': 'mat-chip-avatar mdc-chip__icon mdc-chip__icon--leading', |
| 24 | + } |
| 25 | +}) |
| 26 | +export class MatChipAvatar { |
| 27 | + _classes: {[key: string]: boolean} = {}; |
| 28 | + |
| 29 | + /** Sets whether the given CSS class should be applied to the leading icon. */ |
| 30 | + setClass(cssClass: string, active: boolean) { |
| 31 | + const element = this._elementRef.nativeElement; |
| 32 | + active ? element.addClass(cssClass) : element.removeClass(cssClass); |
| 33 | + this._changeDetectorRef.markForCheck(); |
| 34 | + } |
| 35 | + |
| 36 | + constructor(private _changeDetectorRef: ChangeDetectorRef, |
| 37 | + private _elementRef: ElementRef) {} |
| 38 | +} |
| 39 | + |
| 40 | +/** |
| 41 | + * Dummy directive to add CSS class to chip trailing icon. |
| 42 | + * @docs-private |
| 43 | + */ |
| 44 | +@Directive({ |
| 45 | + selector: 'mat-chip-trailing-icon, [matChipTrailingIcon]', |
| 46 | + host: { |
| 47 | + 'class': 'mat-chip-trailing-icon mdc-chip__icon mdc-chip__icon--trailing', |
| 48 | + 'tabindex': '0', |
| 49 | + 'role': 'button', |
| 50 | + '(click)': 'interaction.emit($event)', |
| 51 | + '(keydown)': 'interaction.emit($event)', |
| 52 | + } |
| 53 | +}) |
| 54 | +export class MatChipTrailingIcon { |
| 55 | + @Output() interaction = new EventEmitter<MouseEvent | KeyboardEvent>(); |
| 56 | +} |
11 | 57 |
|
12 | 58 | @Component({ |
13 | 59 | selector: 'mat-chip-cell', |
14 | 60 | templateUrl: 'chip-cell.html', |
15 | 61 | styleUrls: ['chips.css'], |
16 | 62 | host: { |
17 | 63 | 'class': 'mat-mdc-chip-cell', |
| 64 | + '[id]': 'id', |
18 | 65 | '(click)': '_chipFoundation.handleInteraction($event)', |
19 | | - '(keydown)': '_chipFoundation.handleInteraction($event)' |
| 66 | + '(keydown)': '_chipFoundation.handleInteraction($event)', |
| 67 | + '(transitionend)': '_chipFoundation.handleTransitionEnd($event)' |
20 | 68 | }, |
21 | 69 | }) |
22 | | -export class MatChipCell { |
| 70 | +export class MatChipCell implements AfterContentInit, AfterViewInit, OnDestroy { |
| 71 | + private _uniqueId = `mat-mdc-chip-${uid++}`; |
| 72 | + |
| 73 | + /** A unique id for the chip. If none is supplied, it will be auto-generated. */ |
| 74 | + @Input() id: string = this._uniqueId; |
| 75 | + |
| 76 | + /** |
| 77 | + * EventEmitters used to notify the parent chip-set of an event on the chip. |
| 78 | + */ |
| 79 | + @Output() removal = new EventEmitter<string>(); |
| 80 | + @Output() trailingIconInteraction = new EventEmitter<string>(); |
| 81 | + @Output() interaction = new EventEmitter<string>(); |
| 82 | + @Output() selection = new EventEmitter<{id: string, selected: boolean}>(); |
| 83 | + |
| 84 | + /** Whether the chip is selected. */ |
| 85 | + get selected(): boolean { |
| 86 | + return this._chipFoundation.isSelected(); |
| 87 | + } |
| 88 | + |
| 89 | + /** Sets selected state on the chip. */ |
| 90 | + set selected(selected: boolean) { |
| 91 | + this._chipFoundation.setSelected(selected); |
| 92 | + } |
| 93 | + |
| 94 | + /** Whether a trailing icon click should trigger exit/removal of the chip. */ |
| 95 | + get shouldRemoveOnTrailingIconClick(): boolean { |
| 96 | + return this._chipFoundation.getShouldRemoveOnTrailingIconClick(); |
| 97 | + } |
| 98 | + |
| 99 | + /** Sets whether a trailing icon click should trigger exit/removal of the chip. */ |
| 100 | + set shouldRemoveOnTrailingIconClick(shouldRemove: boolean) { |
| 101 | + this._chipFoundation.setShouldRemoveOnTrailingIconClick(shouldRemove); |
| 102 | + } |
| 103 | + |
| 104 | + /** The MDC foundation containing business logic for MDC chip. */ |
23 | 105 | _chipFoundation: MDCChipFoundation; |
24 | 106 |
|
25 | | - constructor() { |
26 | | - this._chipFoundation = new MDCChipFoundation(); |
| 107 | + /** |
| 108 | + * Map from class to whether the class is enabled. |
| 109 | + * Enabled classes are set on the chip element. |
| 110 | + */ |
| 111 | + _classes: {[key: string]: boolean} = {}; |
| 112 | + |
| 113 | + /** Subject that emits when the component has been destroyed. */ |
| 114 | + private _destroyed = new Subject<void>(); |
| 115 | + |
| 116 | + /** The chip's leading icon. */ |
| 117 | + @ContentChild(MatChipAvatar, {static: false}) leadingIcon: MatChipAvatar; |
| 118 | + |
| 119 | + /** The chip's trailing icon. */ |
| 120 | + @ContentChild(MatChipTrailingIcon, {static: false}) trailingIcon: MatChipTrailingIcon; |
| 121 | + |
| 122 | + /** |
| 123 | + * Implementation of the MDC chip adapter interface. |
| 124 | + * These methods are called by the chip foundation. |
| 125 | + */ |
| 126 | + private _chipAdapter: MDCChipAdapter = { |
| 127 | + addClass: (className) => this._setClass(className, true), |
| 128 | + removeClass: (className) => this._setClass(className, false), |
| 129 | + hasClass: (className) => className in this._classes, |
| 130 | + addClassToLeadingIcon: (className) => this.leadingIcon.setClass(className, true), |
| 131 | + removeClassFromLeadingIcon: (className) => this.leadingIcon.setClass(className, false), |
| 132 | + eventTargetHasClass: (target: EventTarget | null, className: string) => { |
| 133 | + return target ? (target as Element).classList.contains(className) : false; |
| 134 | + }, |
| 135 | + notifyInteraction: () => this.interaction.emit(this.id), |
| 136 | + notifySelection: () => this.selection.emit({id: this.id, selected: this.selected}), |
| 137 | + notifyTrailingIconInteraction: () => this.trailingIconInteraction.emit(this.id), |
| 138 | + notifyRemoval: () => this.removal.emit(this.id), |
| 139 | + getComputedStyleValue: (propertyName) => { |
| 140 | + return window.getComputedStyle(this._elementRef.nativeElement).getPropertyValue(propertyName); |
| 141 | + }, |
| 142 | + setStyleProperty: (propertyName: string, value: string) => { |
| 143 | + this._elementRef.nativeElement.style.setProperty(propertyName, value); |
| 144 | + }, |
| 145 | + hasLeadingIcon: () => {return !!this.leadingIcon}, |
| 146 | + getRootBoundingClientRect: () => this._elementRef.nativeElement.getBoundingClientRect(), |
| 147 | + getCheckmarkBoundingClientRect: () => {return null}, |
| 148 | + }; |
| 149 | + |
| 150 | + constructor( |
| 151 | + private _changeDetectorRef: ChangeDetectorRef, |
| 152 | + private _elementRef: ElementRef) { |
| 153 | + this._chipFoundation = new MDCChipFoundation(this._chipAdapter); |
| 154 | + } |
| 155 | + |
| 156 | + ngAfterContentInit() { |
| 157 | + if (this.trailingIcon) { |
| 158 | + this.trailingIcon.interaction |
| 159 | + .pipe(takeUntil(this._destroyed)) |
| 160 | + .subscribe((event) => this._chipFoundation.handleTrailingIconInteraction(event)); |
| 161 | + } |
27 | 162 | } |
28 | 163 |
|
29 | 164 | ngAfterViewInit() { |
30 | 165 | this._chipFoundation.init(); |
31 | 166 | } |
32 | 167 |
|
33 | 168 | ngOnDestroy() { |
| 169 | + this._destroyed.next(); |
| 170 | + this._destroyed.complete(); |
34 | 171 | this._chipFoundation.destroy(); |
35 | 172 | } |
| 173 | + |
| 174 | + /** |
| 175 | + * Begins the exit animation which leads to removal of the chip. |
| 176 | + * If shouldRemoveOnTrailingIconClick is set to false, you must manually call |
| 177 | + * this method to remove the chip. |
| 178 | + */ |
| 179 | + beginExit() { |
| 180 | + this._chipFoundation.beginExit(); |
| 181 | + } |
| 182 | + |
| 183 | + /** Sets whether the given CSS class should be applied to the chip. */ |
| 184 | + private _setClass(cssClass: string, active: boolean) { |
| 185 | + this._classes[cssClass] = active; |
| 186 | + this._changeDetectorRef.markForCheck(); |
| 187 | + } |
36 | 188 | } |
| 189 | + |
0 commit comments