|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; |
10 |
| -import {ENTER, SPACE} from '@angular/cdk/keycodes'; |
11 |
| -import {Directive, ElementRef, Inject, Input} from '@angular/core'; |
12 |
| -import {HasTabIndex, mixinTabIndex} from '@angular/material-experimental/mdc-core'; |
13 |
| -import {MAT_CHIP} from './tokens'; |
| 9 | +import { |
| 10 | + AfterViewInit, |
| 11 | + ChangeDetectorRef, |
| 12 | + Directive, |
| 13 | + ElementRef, |
| 14 | + Inject, |
| 15 | + Input, |
| 16 | + OnChanges, |
| 17 | + OnDestroy, |
| 18 | + SimpleChanges, |
| 19 | +} from '@angular/core'; |
| 20 | +import {DOCUMENT} from '@angular/common'; |
| 21 | +import { |
| 22 | + MDCChipActionAdapter, |
| 23 | + MDCChipActionFoundation, |
| 24 | + MDCChipActionType, |
| 25 | + MDCChipPrimaryActionFoundation, |
| 26 | +} from '@material/chips'; |
| 27 | +import {emitCustomEvent} from './emit-event'; |
| 28 | +import { |
| 29 | + CanDisable, |
| 30 | + HasTabIndex, |
| 31 | + mixinDisabled, |
| 32 | + mixinTabIndex, |
| 33 | +} from '@angular/material-experimental/mdc-core'; |
14 | 34 |
|
15 |
| -abstract class _MatChipActionBase { |
16 |
| - abstract disabled: boolean; |
17 |
| -} |
18 |
| - |
19 |
| -const _MatChipActionMixinBase = mixinTabIndex(_MatChipActionBase, -1); |
| 35 | +const _MatChipActionMixinBase = mixinTabIndex(mixinDisabled(class {}), -1); |
20 | 36 |
|
21 | 37 | /**
|
22 |
| - * Section within a chip. |
| 38 | + * Interactive element within a chip. |
23 | 39 | * @docs-private
|
24 | 40 | */
|
25 | 41 | @Directive({
|
26 | 42 | selector: '[matChipAction]',
|
27 | 43 | inputs: ['disabled', 'tabIndex'],
|
28 | 44 | host: {
|
29 | 45 | 'class': 'mdc-evolution-chip__action mat-mdc-chip-action',
|
30 |
| - '[class.mdc-evolution-chip__action--primary]': '_isPrimary', |
| 46 | + '[class.mdc-evolution-chip__action--primary]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`, |
31 | 47 | // Note that while our actions are interactive, we have to add the `--presentational` class,
|
32 | 48 | // in order to avoid some super-specific `:hover` styles from MDC.
|
33 |
| - '[class.mdc-evolution-chip__action--presentational]': '_isPrimary', |
34 |
| - '[class.mdc-evolution-chip__action--trailing]': '!_isPrimary', |
| 49 | + '[class.mdc-evolution-chip__action--presentational]': `_getFoundation().actionType() === ${MDCChipActionType.PRIMARY}`, |
| 50 | + '[class.mdc-evolution-chip__action--trailing]': `_getFoundation().actionType() === ${MDCChipActionType.TRAILING}`, |
35 | 51 | '[attr.tabindex]': '(disabled || !isInteractive) ? null : tabIndex',
|
36 | 52 | '[attr.disabled]': "disabled ? '' : null",
|
37 | 53 | '[attr.aria-disabled]': 'disabled',
|
38 | 54 | '(click)': '_handleClick($event)',
|
39 | 55 | '(keydown)': '_handleKeydown($event)',
|
40 | 56 | },
|
41 | 57 | })
|
42 |
| -export class MatChipAction extends _MatChipActionMixinBase implements HasTabIndex { |
| 58 | +export class MatChipAction |
| 59 | + extends _MatChipActionMixinBase |
| 60 | + implements AfterViewInit, OnDestroy, CanDisable, HasTabIndex, OnChanges |
| 61 | +{ |
| 62 | + private _document: Document; |
| 63 | + private _foundation: MDCChipActionFoundation; |
| 64 | + private _adapter: MDCChipActionAdapter = { |
| 65 | + focus: () => this.focus(), |
| 66 | + getAttribute: (name: string) => this._elementRef.nativeElement.getAttribute(name), |
| 67 | + setAttribute: (name: string, value: string) => { |
| 68 | + // MDC tries to update the tabindex directly in the DOM when navigating using the keyboard |
| 69 | + // which overrides our own handling. If we detect such a case, assign it to the same property |
| 70 | + // as the Angular binding in order to maintain consistency. |
| 71 | + if (name === 'tabindex') { |
| 72 | + this._updateTabindex(parseInt(value)); |
| 73 | + } else { |
| 74 | + this._elementRef.nativeElement.setAttribute(name, value); |
| 75 | + } |
| 76 | + }, |
| 77 | + removeAttribute: (name: string) => { |
| 78 | + if (name !== 'tabindex') { |
| 79 | + this._elementRef.nativeElement.removeAttribute(name); |
| 80 | + } |
| 81 | + }, |
| 82 | + getElementID: () => this._elementRef.nativeElement.id, |
| 83 | + emitEvent: <T>(eventName: string, data: T) => { |
| 84 | + emitCustomEvent<T>(this._elementRef.nativeElement, this._document, eventName, data, true); |
| 85 | + }, |
| 86 | + }; |
| 87 | + |
43 | 88 | /** Whether the action is interactive. */
|
44 | 89 | @Input() isInteractive = true;
|
45 | 90 |
|
46 |
| - /** Whether this is the primary action in the chip. */ |
47 |
| - _isPrimary = true; |
| 91 | + _handleClick(event: MouseEvent) { |
| 92 | + // Usually these events can't happen while the chip is disabled since the browser won't |
| 93 | + // allow them which is what MDC seems to rely on, however the event can be faked in tests. |
| 94 | + if (!this.disabled && this.isInteractive) { |
| 95 | + this._foundation.handleClick(); |
| 96 | + event.preventDefault(); |
| 97 | + } |
| 98 | + } |
48 | 99 |
|
49 |
| - /** Whether the action is disabled. */ |
50 |
| - @Input() |
51 |
| - get disabled(): boolean { |
52 |
| - return this._disabled || this._parentChip.disabled; |
| 100 | + _handleKeydown(event: KeyboardEvent) { |
| 101 | + // Usually these events can't happen while the chip is disabled since the browser won't |
| 102 | + // allow them which is what MDC seems to rely on, however the event can be faked in tests. |
| 103 | + if (!this.disabled && this.isInteractive) { |
| 104 | + this._foundation.handleKeydown(event); |
| 105 | + } |
53 | 106 | }
|
54 |
| - set disabled(value: BooleanInput) { |
55 |
| - this._disabled = coerceBooleanProperty(value); |
| 107 | + |
| 108 | + protected _createFoundation(adapter: MDCChipActionAdapter): MDCChipActionFoundation { |
| 109 | + return new MDCChipPrimaryActionFoundation(adapter); |
56 | 110 | }
|
57 |
| - private _disabled = false; |
58 | 111 |
|
59 | 112 | constructor(
|
60 |
| - public _elementRef: ElementRef<HTMLElement>, |
61 |
| - @Inject(MAT_CHIP) |
62 |
| - protected _parentChip: { |
63 |
| - _handlePrimaryActionInteraction(): void; |
64 |
| - remove(): void; |
65 |
| - disabled: boolean; |
66 |
| - }, |
| 113 | + public _elementRef: ElementRef, |
| 114 | + @Inject(DOCUMENT) _document: any, |
| 115 | + private _changeDetectorRef: ChangeDetectorRef, |
67 | 116 | ) {
|
68 | 117 | super();
|
| 118 | + this._foundation = this._createFoundation(this._adapter); |
69 | 119 |
|
70 | 120 | if (_elementRef.nativeElement.nodeName === 'BUTTON') {
|
71 | 121 | _elementRef.nativeElement.setAttribute('type', 'button');
|
72 | 122 | }
|
73 | 123 | }
|
74 | 124 |
|
| 125 | + ngAfterViewInit() { |
| 126 | + this._foundation.init(); |
| 127 | + this._foundation.setDisabled(this.disabled); |
| 128 | + } |
| 129 | + |
| 130 | + ngOnChanges(changes: SimpleChanges) { |
| 131 | + if (changes['disabled']) { |
| 132 | + this._foundation.setDisabled(this.disabled); |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + ngOnDestroy() { |
| 137 | + this._foundation.destroy(); |
| 138 | + } |
| 139 | + |
75 | 140 | focus() {
|
76 | 141 | this._elementRef.nativeElement.focus();
|
77 | 142 | }
|
78 | 143 |
|
79 |
| - _handleClick(event: MouseEvent) { |
80 |
| - if (!this.disabled && this.isInteractive && this._isPrimary) { |
81 |
| - event.preventDefault(); |
82 |
| - this._parentChip._handlePrimaryActionInteraction(); |
83 |
| - } |
| 144 | + _getFoundation() { |
| 145 | + return this._foundation; |
84 | 146 | }
|
85 | 147 |
|
86 |
| - _handleKeydown(event: KeyboardEvent) { |
87 |
| - if ( |
88 |
| - (event.keyCode === ENTER || event.keyCode === SPACE) && |
89 |
| - !this.disabled && |
90 |
| - this.isInteractive && |
91 |
| - this._isPrimary |
92 |
| - ) { |
93 |
| - event.preventDefault(); |
94 |
| - this._parentChip._handlePrimaryActionInteraction(); |
95 |
| - } |
| 148 | + _updateTabindex(value: number) { |
| 149 | + this.tabIndex = value; |
| 150 | + this._changeDetectorRef.markForCheck(); |
96 | 151 | }
|
97 | 152 | }
|
0 commit comments