Skip to content

Commit 757e60b

Browse files
mmalerbaandrewseguin
authored andcommitted
feat(material-experimental/mdc-autocomplete): add panel animation (angular#21525)
* feat(material-experimental/mdc-autocomplete): add panel animation * fixup! feat(material-experimental/mdc-autocomplete): add panel animation * fixup! feat(material-experimental/mdc-autocomplete): add panel animation * fixup! feat(material-experimental/mdc-autocomplete): add panel animation Co-authored-by: Andrew Seguin <[email protected]>
1 parent 2d09bb1 commit 757e60b

File tree

6 files changed

+64
-9
lines changed

6 files changed

+64
-9
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 {
10+
animate,
11+
AnimationTriggerMetadata,
12+
group,
13+
state,
14+
style,
15+
transition,
16+
trigger
17+
} from '@angular/animations';
18+
19+
// Animation values come from
20+
// https://github.com/material-components/material-components-web/blob/master/packages/mdc-menu-surface/_mixins.scss
21+
// TODO(mmalerba): Ideally find a way to import the values from MDC's code.
22+
export const panelAnimation: AnimationTriggerMetadata = trigger('panelAnimation', [
23+
state('void, hidden', style({
24+
opacity: 0,
25+
transform: 'scale(0.8)',
26+
})),
27+
transition(':enter, hidden => visible', [
28+
group([
29+
animate('0.03s linear', style({ opacity: 1 })),
30+
animate('0.12s cubic-bezier(0, 0, 0.2, 1)', style({ transform: 'scale(1)' })),
31+
]),
32+
]),
33+
transition(':leave, visible => hidden', [
34+
animate('0.075s linear', style({ opacity: 0 })),
35+
]),
36+
]);

src/material-experimental/mdc-autocomplete/autocomplete.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[ngClass]="_classList"
77
[attr.aria-label]="ariaLabel || null"
88
[attr.aria-labelledby]="_getPanelAriaLabelledby(formFieldId)"
9+
[@panelAnimation]="isOpen ? 'visible' : 'hidden'"
910
#panel>
1011
<ng-content></ng-content>
1112
</div>

src/material-experimental/mdc-autocomplete/autocomplete.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
max-height: 256px; // Prevents lists with a lot of option from growing too high.
1414
position: static; // MDC uses `absolute` by default which will throw off our positioning.
1515
visibility: hidden;
16+
// MDC sets the transform-origin programatically based on whether the dropdown is above or below
17+
// the input. We use our own positioning logic, so we need to set this ourselves.
18+
transform-origin: center top;
1619

1720
// Note that we include this private mixin, because the public
1821
// one adds a bunch of styles that we aren't using for the menu.
@@ -29,6 +32,9 @@
2932
.mat-mdc-autocomplete-panel-above & {
3033
border-bottom-left-radius: 0;
3134
border-bottom-right-radius: 0;
35+
// MDC sets the transform-origin programatically based on whether the dropdown is above or below
36+
// the input. We use our own positioning logic, so we need to set this ourselves.
37+
transform-origin: center bottom;
3238
}
3339

3440
// These classes are used to toggle the panel visibility depending on whether it has any options.

src/material-experimental/mdc-autocomplete/autocomplete.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe('MDC-based MatAutocomplete', () => {
180180
fixture.detectChanges();
181181
zone.simulateZoneExit();
182182
dispatchFakeEvent(document, 'click');
183+
tick();
183184

184185
expect(fixture.componentInstance.trigger.panelOpen)
185186
.toBe(false, `Expected clicking outside the panel to set its state to closed.`);
@@ -192,6 +193,7 @@ describe('MDC-based MatAutocomplete', () => {
192193
fixture.detectChanges();
193194
zone.simulateZoneExit();
194195
dispatchFakeEvent(document, 'auxclick');
196+
tick();
195197

196198
expect(fixture.componentInstance.trigger.panelOpen)
197199
.toBe(false, `Expected clicking outside the panel to set its state to closed.`);
@@ -219,6 +221,7 @@ describe('MDC-based MatAutocomplete', () => {
219221
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
220222
option.click();
221223
fixture.detectChanges();
224+
tick();
222225

223226
expect(fixture.componentInstance.trigger.panelOpen)
224227
.toBe(false, `Expected clicking an option to set the panel state to closed.`);
@@ -251,25 +254,27 @@ describe('MDC-based MatAutocomplete', () => {
251254
options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf<HTMLElement>;
252255
options[1].click();
253256
fixture.detectChanges();
257+
tick();
254258

255259
expect(fixture.componentInstance.trigger.panelOpen)
256260
.toBe(false, `Expected clicking a new option to set the panel state to closed.`);
257261
expect(overlayContainerElement.textContent)
258262
.toEqual('', `Expected clicking a new option to close the panel.`);
259263
}));
260264

261-
it('should close the panel programmatically', () => {
265+
it('should close the panel programmatically', fakeAsync(() => {
262266
fixture.componentInstance.trigger.openPanel();
263267
fixture.detectChanges();
264268

265269
fixture.componentInstance.trigger.closePanel();
266270
fixture.detectChanges();
271+
tick();
267272

268273
expect(fixture.componentInstance.trigger.panelOpen)
269274
.toBe(false, `Expected closing programmatically to set the panel state to closed.`);
270275
expect(overlayContainerElement.textContent)
271276
.toEqual('', `Expected closing programmatically to close the panel.`);
272-
});
277+
}));
273278

274279
it('should not throw when attempting to close the panel of a destroyed autocomplete', () => {
275280
const trigger = fixture.componentInstance.trigger;
@@ -1032,6 +1037,7 @@ describe('MDC-based MatAutocomplete', () => {
10321037
flush();
10331038
fixture.componentInstance.trigger._handleKeydown(ENTER_EVENT);
10341039
fixture.detectChanges();
1040+
tick();
10351041

10361042
expect(fixture.componentInstance.trigger.panelOpen)
10371043
.toBe(false, `Expected panel state to read closed after ENTER key.`);
@@ -1248,6 +1254,7 @@ describe('MDC-based MatAutocomplete', () => {
12481254

12491255
dispatchKeyboardEvent(input, 'keydown', TAB);
12501256
fixture.detectChanges();
1257+
tick();
12511258

12521259
expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel'))
12531260
.toBeFalsy('Expected panel to be removed.');

src/material-experimental/mdc-autocomplete/autocomplete.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77
*/
88

99
import {
10-
Component,
11-
ViewEncapsulation,
1210
ChangeDetectionStrategy,
11+
Component,
1312
ContentChildren,
1413
QueryList,
14+
ViewEncapsulation,
1515
} from '@angular/core';
16-
import {_MatAutocompleteBase} from '@angular/material/autocomplete';
1716
import {
18-
MAT_OPTION_PARENT_COMPONENT,
1917
MAT_OPTGROUP,
18+
MAT_OPTION_PARENT_COMPONENT,
2019
MatOptgroup,
2120
MatOption,
2221
} from '@angular/material-experimental/mdc-core';
23-
22+
import {_MatAutocompleteBase} from '@angular/material/autocomplete';
23+
import {panelAnimation} from './animations';
2424

2525
@Component({
2626
selector: 'mat-autocomplete',
@@ -35,7 +35,8 @@ import {
3535
},
3636
providers: [
3737
{provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatAutocomplete}
38-
]
38+
],
39+
animations: [panelAnimation],
3940
})
4041
export class MatAutocomplete extends _MatAutocompleteBase {
4142
@ContentChildren(MAT_OPTGROUP, {descendants: true}) optionGroups: QueryList<MatOptgroup>;

src/material/autocomplete/testing/shared.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Component} from '@angular/core';
55
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
66
import {MatAutocompleteModule} from '@angular/material/autocomplete';
77
import {MatAutocompleteHarness} from '@angular/material/autocomplete/testing';
8+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
89

910
/**
1011
* Function that can be used to run the shared autocomplete harness tests for either the non-MDC or
@@ -19,7 +20,10 @@ export function runHarnessTests(
1920

2021
beforeEach(async () => {
2122
await TestBed.configureTestingModule({
22-
imports: [autocompleteModule],
23+
imports: [
24+
NoopAnimationsModule,
25+
autocompleteModule
26+
],
2327
declarations: [AutocompleteHarnessTest],
2428
}).compileComponents();
2529

0 commit comments

Comments
 (0)