feat(datepicker): add animation to calendar popup (#8542)

Adds an animation when opening and closing the datepicker's calendar.
This commit is contained in:
Kristiyan Kostadinov
2017-12-13 23:03:09 +01:00
committed by Andrew Seguin
parent 3a52624499
commit c3e267ffb6
4 changed files with 114 additions and 5 deletions
@@ -7,6 +7,7 @@
[maxDate]="datepicker._maxDate"
[dateFilter]="datepicker._dateFilter"
[selected]="datepicker._selected"
[@fadeInCalendar]="'enter'"
(selectedChange)="datepicker._select($event)"
(_userSelection)="datepicker.close()">
</mat-calendar>
@@ -28,6 +28,7 @@ $mat-datepicker-touch-max-height: 788px;
@include mat-elevation(8);
display: block;
transform-origin: top center;
.mat-calendar {
width: $mat-datepicker-non-touch-calendar-width;
@@ -35,6 +36,10 @@ $mat-datepicker-touch-max-height: 788px;
}
}
.mat-datepicker-content-above {
transform-origin: bottom center;
}
.mat-datepicker-content-touch {
@include mat-elevation(0);
+51 -2
View File
@@ -167,7 +167,7 @@ describe('MatDatepicker', () => {
expect(parseInt(getComputedStyle(popup).height as string)).toBe(0);
});
it('should close the popup when pressing ESCAPE', () => {
it('should close the popup when pressing ESCAPE', fakeAsync(() => {
testComponent.datepicker.open();
fixture.detectChanges();
@@ -179,6 +179,7 @@ describe('MatDatepicker', () => {
dispatchEvent(content, keyboardEvent);
fixture.detectChanges();
flush();
content = document.querySelector('.cdk-overlay-pane mat-datepicker-content')!;
@@ -186,7 +187,7 @@ describe('MatDatepicker', () => {
expect(stopPropagationSpy).toHaveBeenCalled();
expect(keyboardEvent.defaultPrevented)
.toBe(true, 'Expected default ESCAPE action to be prevented.');
});
}));
it('close should close dialog', async(() => {
testComponent.touch = true;
@@ -1236,6 +1237,54 @@ describe('MatDatepicker', () => {
});
}));
});
describe('popup animations', () => {
let fixture: ComponentFixture<StandardDatepicker>;
let testComponent: StandardDatepicker;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [MatDatepickerModule, MatNativeDateModule, NoopAnimationsModule],
declarations: [StandardDatepicker],
}).compileComponents();
fixture = TestBed.createComponent(StandardDatepicker);
fixture.detectChanges();
testComponent = fixture.componentInstance;
}));
it('should not set the `mat-datepicker-content-above` class when opening downwards',
fakeAsync(() => {
fixture.componentInstance.datepicker.open();
fixture.detectChanges();
flush();
fixture.detectChanges();
const content =
document.querySelector('.cdk-overlay-pane mat-datepicker-content')! as HTMLElement;
expect(content.classList).not.toContain('mat-datepicker-content-above');
}));
it('should set the `mat-datepicker-content-above` class when opening upwards', fakeAsync(() => {
const input = fixture.debugElement.nativeElement.querySelector('input');
// Push the input to the bottom of the page to force the calendar to open upwards
input.style.position = 'fixed';
input.style.bottom = '0';
fixture.componentInstance.datepicker.open();
fixture.detectChanges();
flush();
fixture.detectChanges();
const content =
document.querySelector('.cdk-overlay-pane mat-datepicker-content')! as HTMLElement;
expect(content.classList).toContain('mat-datepicker-content-above');
}));
});
});
+57 -3
View File
@@ -16,6 +16,7 @@ import {
PositionStrategy,
RepositionScrollStrategy,
ScrollStrategy,
ConnectedPositionStrategy,
} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {take} from 'rxjs/operators/take';
@@ -35,6 +36,8 @@ import {
ViewChild,
ViewContainerRef,
ViewEncapsulation,
ChangeDetectorRef,
OnInit,
} from '@angular/core';
import {DateAdapter} from '@angular/material/core';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
@@ -44,6 +47,7 @@ import {Subscription} from 'rxjs/Subscription';
import {MatCalendar} from './calendar';
import {createMissingDateImplError} from './datepicker-errors';
import {MatDatepickerInput} from './datepicker-input';
import{trigger, state, style, animate, transition} from '@angular/animations';
/** Used to generate a unique ID for each datepicker instance. */
@@ -81,23 +85,73 @@ export const MAT_DATEPICKER_SCROLL_STRATEGY_PROVIDER = {
styleUrls: ['datepicker-content.css'],
host: {
'class': 'mat-datepicker-content',
'[@tranformPanel]': '"enter"',
'[class.mat-datepicker-content-touch]': 'datepicker.touchUi',
'[class.mat-datepicker-content-above]': '_isAbove',
'(keydown)': '_handleKeydown($event)',
},
animations: [
trigger('tranformPanel', [
state('void', style({opacity: 0, transform: 'scale(1, 0)'})),
state('enter', style({opacity: 1, transform: 'scale(1, 1)'})),
transition('void => enter', animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)')),
transition('* => void', animate('100ms linear', style({opacity: 0})))
]),
trigger('fadeInCalendar', [
state('void', style({opacity: 0})),
state('enter', style({opacity: 1})),
transition('void => *', animate('400ms 100ms cubic-bezier(0.55, 0, 0.55, 0.2)'))
])
],
exportAs: 'matDatepickerContent',
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatDatepickerContent<D> implements AfterContentInit {
export class MatDatepickerContent<D> implements AfterContentInit, OnInit, OnDestroy {
/** Subscription to changes in the overlay's position. */
private _positionChange: Subscription|null;
/** Reference to the internal calendar component. */
@ViewChild(MatCalendar) _calendar: MatCalendar<D>;
/** Reference to the datepicker that created the overlay. */
datepicker: MatDatepicker<D>;
@ViewChild(MatCalendar) _calendar: MatCalendar<D>;
/** Whether the datepicker is above or below the input. */
_isAbove: boolean;
constructor(private _changeDetectorRef: ChangeDetectorRef) {}
ngOnInit() {
if (!this.datepicker._popupRef || this._positionChange) {
return;
}
const positionStrategy =
this.datepicker._popupRef.getConfig().positionStrategy! as ConnectedPositionStrategy;
this._positionChange = positionStrategy.onPositionChange.subscribe(change => {
const isAbove = change.connectionPair.overlayY === 'bottom';
if (isAbove !== this._isAbove) {
this._isAbove = isAbove;
this._changeDetectorRef.markForCheck();
}
});
}
ngAfterContentInit() {
this._calendar._focusActiveCell();
}
ngOnDestroy() {
if (this._positionChange) {
this._positionChange.unsubscribe();
this._positionChange = null;
}
}
/**
* Handles keydown event on datepicker content.
* @param event The event.
@@ -214,7 +268,7 @@ export class MatDatepicker<D> implements OnDestroy {
}
/** A reference to the overlay when the calendar is opened as a popup. */
private _popupRef: OverlayRef;
_popupRef: OverlayRef;
/** A reference to the dialog when the calendar is opened as a dialog. */
private _dialogRef: MatDialogRef<any> | null;