Skip to content

Commit d2ec07b

Browse files
authored
Revert "refactor(material-experimental/mdc-chips): remove usage of MDC adapter (#24961)" (#24989)
This reverts commit 50992f3.
1 parent 939d250 commit d2ec07b

23 files changed

+924
-507
lines changed

src/dev-app/mdc-chips/mdc-chips-demo.html

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,8 @@ <h4>Input is last child of chip grid</h4>
147147
<mat-form-field class="demo-has-chip-list">
148148
<mat-label>New Contributor...</mat-label>
149149
<mat-chip-grid #chipGrid1 [(ngModel)]="selectedPeople" required [disabled]="disableInputs">
150-
<!-- Disable the third chip to test focus management with a disabled chip. -->
151-
<mat-chip-row *ngFor="let person of people; let index = index"
150+
<mat-chip-row *ngFor="let person of people"
152151
[editable]="editable"
153-
[disabled]="index === 2"
154152
(removed)="remove(person)"
155153
(edited)="edit(person, $event)">
156154
{{person.name}}

src/material-experimental/mdc-chips/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ ng_module(
2929
"@npm//@angular/common",
3030
"@npm//@angular/core",
3131
"@npm//@angular/forms",
32+
"@npm//@material/chips",
3233
],
3334
)
3435

@@ -90,6 +91,7 @@ ng_test_library(
9091
"@npm//@angular/common",
9192
"@npm//@angular/forms",
9293
"@npm//@angular/platform-browser",
94+
"@npm//@material/chips",
9395
"@npm//rxjs",
9496
],
9597
)

src/material-experimental/mdc-chips/chip-action.ts

Lines changed: 101 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,92 +6,147 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

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';
1434

15-
abstract class _MatChipActionBase {
16-
abstract disabled: boolean;
17-
}
18-
19-
const _MatChipActionMixinBase = mixinTabIndex(_MatChipActionBase, -1);
35+
const _MatChipActionMixinBase = mixinTabIndex(mixinDisabled(class {}), -1);
2036

2137
/**
22-
* Section within a chip.
38+
* Interactive element within a chip.
2339
* @docs-private
2440
*/
2541
@Directive({
2642
selector: '[matChipAction]',
2743
inputs: ['disabled', 'tabIndex'],
2844
host: {
2945
'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}`,
3147
// Note that while our actions are interactive, we have to add the `--presentational` class,
3248
// 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}`,
3551
'[attr.tabindex]': '(disabled || !isInteractive) ? null : tabIndex',
3652
'[attr.disabled]': "disabled ? '' : null",
3753
'[attr.aria-disabled]': 'disabled',
3854
'(click)': '_handleClick($event)',
3955
'(keydown)': '_handleKeydown($event)',
4056
},
4157
})
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+
4388
/** Whether the action is interactive. */
4489
@Input() isInteractive = true;
4590

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+
}
4899

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+
}
53106
}
54-
set disabled(value: BooleanInput) {
55-
this._disabled = coerceBooleanProperty(value);
107+
108+
protected _createFoundation(adapter: MDCChipActionAdapter): MDCChipActionFoundation {
109+
return new MDCChipPrimaryActionFoundation(adapter);
56110
}
57-
private _disabled = false;
58111

59112
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,
67116
) {
68117
super();
118+
this._foundation = this._createFoundation(this._adapter);
69119

70120
if (_elementRef.nativeElement.nodeName === 'BUTTON') {
71121
_elementRef.nativeElement.setAttribute('type', 'button');
72122
}
73123
}
74124

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+
75140
focus() {
76141
this._elementRef.nativeElement.focus();
77142
}
78143

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;
84146
}
85147

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();
96151
}
97152
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {InjectionToken} from '@angular/core';
10+
11+
/** Default options, for the chips module, that can be overridden. */
12+
export interface MatChipsDefaultOptions {
13+
/** The list of key codes that will trigger a chipEnd event. */
14+
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
15+
}
16+
17+
/** Injection token to be used to override the default options for the chips module. */
18+
export const MAT_CHIPS_DEFAULT_OPTIONS = new InjectionToken<MatChipsDefaultOptions>(
19+
'mat-chips-default-options',
20+
);

src/material-experimental/mdc-chips/chip-grid.spec.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
dispatchFakeEvent,
1717
dispatchKeyboardEvent,
1818
MockNgZone,
19-
patchElementFocus,
2019
typeInElement,
2120
} from '@angular/cdk/testing/private';
2221
import {
@@ -35,6 +34,7 @@ import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'
3534
import {MatInputModule} from '@angular/material-experimental/mdc-input';
3635
import {By} from '@angular/platform-browser';
3736
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
37+
import {MDCChipAnimation} from '@material/chips';
3838
import {MatChipEvent, MatChipGrid, MatChipInputEvent, MatChipRow, MatChipsModule} from './index';
3939

4040
describe('MDC-based MatChipGrid', () => {
@@ -199,7 +199,6 @@ describe('MDC-based MatChipGrid', () => {
199199
// Destroy the middle item
200200
testComponent.chips.splice(2, 1);
201201
fixture.detectChanges();
202-
flush();
203202

204203
// Should not have focus
205204
expect(chipGridNativeElement.contains(document.activeElement)).toBe(false);
@@ -209,7 +208,6 @@ describe('MDC-based MatChipGrid', () => {
209208
testComponent.chips = [0];
210209

211210
spyOn(chipGridInstance, 'focus');
212-
patchElementFocus(chips.last.primaryAction!._elementRef.nativeElement);
213211
chips.last.focus();
214212

215213
testComponent.chips.pop();
@@ -218,22 +216,27 @@ describe('MDC-based MatChipGrid', () => {
218216
expect(chipGridInstance.focus).toHaveBeenCalled();
219217
});
220218

221-
it('should move focus to the last chip when the focused chip was deleted inside a component with animations', fakeAsync(() => {
222-
fixture.destroy();
223-
TestBed.resetTestingModule();
219+
it(
220+
'should move focus to the last chip when the focused chip was deleted inside a ' +
221+
'component with animations',
222+
fakeAsync(() => {
223+
fixture.destroy();
224+
TestBed.resetTestingModule();
224225

225-
fixture = createComponent(StandardChipGridWithAnimations, BrowserAnimationsModule);
226+
fixture = createComponent(StandardChipGridWithAnimations, BrowserAnimationsModule);
226227

227-
patchElementFocus(chips.last.primaryAction!._elementRef.nativeElement);
228-
chips.last.focus();
229-
fixture.detectChanges();
228+
chips.last.focus();
229+
fixture.detectChanges();
230230

231-
dispatchKeyboardEvent(chips.last._elementRef.nativeElement, 'keydown', BACKSPACE);
232-
fixture.detectChanges();
233-
tick(500);
231+
expect(document.activeElement).toBe(primaryActions[primaryActions.length - 1]);
234232

235-
expect(document.activeElement).toBe(primaryActions[primaryActions.length - 2]);
236-
}));
233+
dispatchKeyboardEvent(chips.last._elementRef.nativeElement, 'keydown', BACKSPACE);
234+
fixture.detectChanges();
235+
tick(500);
236+
237+
expect(document.activeElement).toBe(primaryActions[primaryActions.length - 2]);
238+
}),
239+
);
237240
});
238241

239242
it('should have a focus indicator', () => {
@@ -391,7 +394,6 @@ describe('MDC-based MatChipGrid', () => {
391394
expect(document.activeElement).toBe(primaryActions[1]);
392395

393396
directionality.value = 'rtl';
394-
directionality.change.next('rtl');
395397
fixture.detectChanges();
396398

397399
dispatchKeyboardEvent(primaryActions[1], 'keydown', RIGHT_ARROW);
@@ -560,7 +562,14 @@ describe('MDC-based MatChipGrid', () => {
560562
// associated chip remove element.
561563
trailingActions[2].click();
562564
fixture.detectChanges();
565+
(chip as any)._handleAnimationend({
566+
animationName: MDCChipAnimation.EXIT,
567+
target: chip._elementRef.nativeElement,
568+
});
563569
flush();
570+
(chip as any)._handleTransitionend({target: chip._elementRef.nativeElement});
571+
flush();
572+
fixture.detectChanges();
564573

565574
expect(document.activeElement).toBe(primaryActions[3]);
566575
}));
@@ -580,6 +589,7 @@ describe('MDC-based MatChipGrid', () => {
580589
.map(chip => chip.nativeElement);
581590

582591
nativeChipGrid = fixture.debugElement.query(By.css('mat-chip-grid'))!.nativeElement;
592+
583593
nativeInput = fixture.nativeElement.querySelector('input');
584594
});
585595

@@ -720,21 +730,18 @@ describe('MDC-based MatChipGrid', () => {
720730

721731
it('should blur the form field when the active chip is blurred', fakeAsync(() => {
722732
const formField: HTMLElement = fixture.nativeElement.querySelector('.mat-mdc-form-field');
723-
const firstAction = nativeChips[0].querySelector('.mat-mdc-chip-action') as HTMLElement;
724733

725-
patchElementFocus(firstAction);
726-
firstAction.focus();
734+
dispatchFakeEvent(nativeChips[0], 'focusin');
727735
fixture.detectChanges();
728736

729737
expect(formField.classList).toContain('mat-focused');
730738

731-
firstAction.blur();
739+
dispatchFakeEvent(nativeChips[0], 'focusout');
732740
fixture.detectChanges();
741+
tick();
733742
fixture.detectChanges();
734743
zone.simulateZoneExit();
735744
fixture.detectChanges();
736-
flush();
737-
738745
expect(formField.classList).not.toContain('mat-focused');
739746
}));
740747

0 commit comments

Comments
 (0)