Skip to content

Commit

Permalink
feat(datepicker): add animation to calendar popup
Browse files Browse the repository at this point in the history
Adds an animation when opening and closing the datepicker's calendar.
  • Loading branch information
crisbeto committed Nov 19, 2017
1 parent 0f954a0 commit 8493b5d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/lib/datepicker/datepicker-content.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[maxDate]="datepicker._maxDate"
[dateFilter]="datepicker._dateFilter"
[selected]="datepicker._selected"
[@fadeInCalendar]="'enter'"
(selectedChange)="datepicker._select($event)"
(_userSelection)="datepicker.close()">
</mat-calendar>
53 changes: 50 additions & 3 deletions src/lib/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
dispatchMouseEvent,
} from '@angular/cdk/testing';
import {Component, ViewChild} from '@angular/core';
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {async, ComponentFixture, inject, TestBed, fakeAsync, flush} from '@angular/core/testing';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {
DEC,
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('MatDatepicker', () => {
});
});

it('should close the popup when pressing ESCAPE', () => {
it('should close the popup when pressing ESCAPE', fakeAsync(() => {
testComponent.datepicker.open();
fixture.detectChanges();

Expand All @@ -168,14 +168,15 @@ describe('MatDatepicker', () => {

dispatchEvent(content, keyboardEvent);
fixture.detectChanges();
flush();

content = document.querySelector('.cdk-overlay-pane mat-datepicker-content')!;

expect(content).toBeFalsy('Expected datepicker to be closed.');
expect(stopPropagationSpy).toHaveBeenCalled();
expect(keyboardEvent.defaultPrevented)
.toBe(true, 'Expected default ESCAPE action to be prevented.');
});
}));

it('close should close dialog', () => {
testComponent.touch = true;
Expand Down Expand Up @@ -1090,6 +1091,52 @@ describe('MatDatepicker', () => {
});
});
});

describe('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 set the correct transform origin when opening upwards', fakeAsync(() => {
fixture.componentInstance.datepicker.open();
fixture.detectChanges();
flush();
fixture.detectChanges();

const content =
document.querySelector('.cdk-overlay-pane mat-datepicker-content')! as HTMLElement;

expect(content.style.transformOrigin).toContain('top');
}));

it('should set the correct transform origin when opening downwards', fakeAsync(() => {
const input = fixture.debugElement.nativeElement.querySelector('input');

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.style.transformOrigin).toContain('bottom');
}));

});
});


Expand Down
61 changes: 58 additions & 3 deletions src/lib/datepicker/datepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
PositionStrategy,
RepositionScrollStrategy,
ScrollStrategy,
ConnectedPositionStrategy,
} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {first} from 'rxjs/operators/first';
Expand All @@ -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';
Expand All @@ -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. */
Expand Down Expand Up @@ -81,23 +85,74 @@ 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',
// Note: binding to `transform-origin` here doesn't work on IE and Edge for some reason.
'[style.transformOrigin]': '_transformOrigin',
'(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 {
datepicker: MatDatepicker<D>;
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>;

/** Origin of the opening animation. */
_transformOrigin: string;

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 newOrigin = `${change.connectionPair.overlayY} center`;

if (newOrigin !== this._transformOrigin) {
this._transformOrigin = newOrigin;
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.
Expand Down Expand Up @@ -211,7 +266,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;
Expand Down

0 comments on commit 8493b5d

Please sign in to comment.