mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(datepicker): Add Custom Header to DatePicker (#9639)
* feature (add custom header to mat-calendar)
TODO: the custom header should be passed from datepicker
* refactor (define custom header component)
* refactor (remove useless text)
* refactor (pass custom header component type as input)
* refactor (add header as component portal)
* refactor (custom header as component portal)
- TODO: solve problem with directive cdkPortalOutlet in calendar.html
* refactor (switch for custom header component)
* refactor (define entry components)
* fix (correct entry point)
* refactor (custom header component): add test function to custom header component
* refactor (some code formatting)
* refactor (set indentation to two spaces)
* refactor (formatting)
* refactor (custom header for mat-calendar): renaming and code improvements
* refactor (calendarHeaderComponent) rename input of MatDatepicker
* refactor (calendarHeaderComponent): make a demo for custom header
* refactor (calendar header): make calendar header portal variable public
* refactor (calendar header): formatting
* tests (MatCalendar): fix tests
* tests (calendar): fix import issue
* tests (MatCalendar): remove unused import
* tests (MatCalendar): import MatDatepickerModule without redeclaration
* tests (MatCalendar): fix imports
* refactor (MatCalendar): default header comes with empty template
* refactor (MatCalendar): remove useless newline
* feature (MatCalendarHeader): move header in own template
* refactor (MatCalendar): some code reformatting
* fix (MatCalendar): add missin change detection for MatCalendarHeader
* refactor (MatCalendar): fix indentation
* fix (MatCalendar): clean stream up after component destruction
* fix (MatCalendar): fix change detection for MatCalendarHeader
* refactor (MatCalendarHeader): add dateadapter to constructor
* refactor (MatCalendarHeader): move methods to calendar header (ongoing)
* refactor (MatCalendarHeader): move label getter methods to MatCalendarHeader
* refactor (MatCalendarHeader): move MatCalendarHeader component in a separate file
* tests (MatCalendarHeader): move header related tests from MatCalendar (ongoing)
* refactor (MatCalendar): linting
* refactor (MatCalendarHeader): move MatCalendarHeader component back to calendar.ts (to avoid circular deps)
* refactor (MatCalendarHeader): move header related methods
* feature (MatCalendarHeader): add sample custom header controls to demo page
* refactor (MatCalendarHeader): fix some styling issues
* refactor (MatCalendarHeader): fix indentation
* refactor (MatCalendarHeader): make calendar private for custom header
* refactor (MatCalendarHeader): use type parameter for header (generics)
* refactor (MatCalendarHeader): make template file for custom header on demo page
* refactor (MatCalendarHeader): separate section own demo page
* refactor (MatCalendarHeader): style custom header
* refactor (MatCalendarHeader): fix styling issue
* refactor (package-lock.json): revert changes to version on master branch
* refactor (package-lock.json): revert to commit 6f63fd283e
* fix (MatCalendarHeader): correct forward reference to MatCalendar
* Add `stateChanges` to `MatCalendar`
* fix nits
This commit is contained in:
committed by
mmalerba
parent
f7b5d34449
commit
4e1bb265e3
Generated
+1
-1
@@ -8136,7 +8136,7 @@
|
||||
"github": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/github/-/github-12.1.0.tgz",
|
||||
"integrity": "sha512-HhWjhd/OATC4Hjj7xfGjGRtwWzo/fzTc55EkvsRatI9G6Vp47mVcdBIt1lQ56A9Qit/yVQRX1+M9jbWlcJvgug==",
|
||||
"integrity": "sha1-8qLcvUQReBVZQiV0kaS8CL9mHdc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dotenv": "4.0.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="custom-header">
|
||||
<button mat-icon-button (click)="previousClicked('year')"><<</button>
|
||||
<button mat-icon-button (click)="previousClicked('month')"><</button>
|
||||
<span class="custom-header-label">{{periodLabel}}</span>
|
||||
<button mat-icon-button (click)="nextClicked('month')">></button>
|
||||
<button mat-icon-button (click)="nextClicked('year')">>></button>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
.custom-header {
|
||||
padding: 1em 1.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-header-label {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -144,3 +144,14 @@
|
||||
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
|
||||
<h2>Datepicker with custom header</h2>
|
||||
<p>
|
||||
<mat-form-field>
|
||||
<mat-label>Custom calendar header</mat-label>
|
||||
<input matInput [matDatepicker]="customCalendarHeaderPicker"
|
||||
[disabled]="inputDisabled">
|
||||
<mat-datepicker-toggle matSuffix [for]="customCalendarHeaderPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #customCalendarHeaderPicker [touchUi]="touch" [disabled]="datepickerDisabled" [calendarHeaderComponent]="customHeader"></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, Host} from '@angular/core';
|
||||
import {FormControl} from '@angular/forms';
|
||||
import {MatDatepickerInputEvent} from '@angular/material/datepicker';
|
||||
import {DateAdapter} from '@angular/material/core';
|
||||
import {MatCalendar} from '@angular/material';
|
||||
import {ThemePalette} from '@angular/material/core';
|
||||
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'datepicker-demo',
|
||||
@@ -40,4 +41,38 @@ export class DatepickerDemo {
|
||||
|
||||
onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value;
|
||||
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value;
|
||||
|
||||
// pass custom header component type as input
|
||||
customHeader = CustomHeader;
|
||||
}
|
||||
|
||||
// Custom header component for datepicker
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'custom-header',
|
||||
templateUrl: 'custom-header.html',
|
||||
styleUrls: ['custom-header.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CustomHeader<D> {
|
||||
constructor(@Host() private _calendar: MatCalendar<D>,
|
||||
private _dateAdapter: DateAdapter<D>) {}
|
||||
|
||||
get periodLabel() {
|
||||
const year = this._dateAdapter.getYearName(this._calendar.activeDate);
|
||||
const month = (this._dateAdapter.getMonth(this._calendar.activeDate) + 1);
|
||||
return `${month}/${year}`;
|
||||
}
|
||||
|
||||
previousClicked(mode: 'month' | 'year') {
|
||||
this._calendar.activeDate = mode == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, -1) :
|
||||
this._dateAdapter.addCalendarYears(this._calendar.activeDate, -1);
|
||||
}
|
||||
|
||||
nextClicked(mode: 'month' | 'year') {
|
||||
this._calendar.activeDate = mode == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._calendar.activeDate, 1) :
|
||||
this._dateAdapter.addCalendarYears(this._calendar.activeDate, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {ButtonDemo} from '../button/button-demo';
|
||||
import {CardDemo} from '../card/card-demo';
|
||||
import {CheckboxDemo, MatCheckboxDemoNestedChecklist} from '../checkbox/checkbox-demo';
|
||||
import {ChipsDemo} from '../chips/chips-demo';
|
||||
import {DatepickerDemo} from '../datepicker/datepicker-demo';
|
||||
import {CustomHeader, DatepickerDemo} from '../datepicker/datepicker-demo';
|
||||
import {DemoMaterialModule} from '../demo-material-module';
|
||||
import {ContentElementDialog, DialogDemo, IFrameDialog, JazzDialog} from '../dialog/dialog-demo';
|
||||
import {DrawerDemo} from '../drawer/drawer-demo';
|
||||
@@ -88,6 +88,7 @@ import {ConnectedOverlayDemo, DemoOverlay} from '../connected-overlay/connected-
|
||||
ChipsDemo,
|
||||
ContentElementDialog,
|
||||
DatepickerDemo,
|
||||
CustomHeader,
|
||||
DemoApp,
|
||||
DialogDemo,
|
||||
DrawerDemo,
|
||||
@@ -148,6 +149,7 @@ import {ConnectedOverlayDemo, DemoOverlay} from '../connected-overlay/connected-
|
||||
ScienceJoke,
|
||||
SpagettiPanel,
|
||||
ExampleBottomSheet,
|
||||
CustomHeader,
|
||||
DemoOverlay,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="mat-calendar-header">
|
||||
<div class="mat-calendar-controls">
|
||||
<button mat-button class="mat-calendar-period-button"
|
||||
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel">
|
||||
{{periodButtonText}}
|
||||
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="calendar.currentView != 'month'"></div>
|
||||
</button>
|
||||
|
||||
<div class="mat-calendar-spacer"></div>
|
||||
|
||||
<button mat-icon-button class="mat-calendar-previous-button"
|
||||
[disabled]="!previousEnabled()" (click)="previousClicked()"
|
||||
[attr.aria-label]="prevButtonLabel">
|
||||
</button>
|
||||
|
||||
<button mat-icon-button class="mat-calendar-next-button"
|
||||
[disabled]="!nextEnabled()" (click)="nextClicked()"
|
||||
[attr.aria-label]="nextButtonLabel">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,170 @@
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {MatDatepickerModule} from './datepicker-module';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
import {DEC, FEB, JAN, MatNativeDateModule} from '@angular/material/core';
|
||||
import {Component} from '@angular/core';
|
||||
import {MatCalendar} from './calendar';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {yearsPerPage} from './multi-year-view';
|
||||
|
||||
describe('MatCalendarHeader', () => {
|
||||
let dir: { value: Direction };
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatNativeDateModule,
|
||||
MatDatepickerModule,
|
||||
],
|
||||
declarations: [
|
||||
// Test components.
|
||||
StandardCalendar,
|
||||
],
|
||||
providers: [
|
||||
MatDatepickerIntl,
|
||||
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
|
||||
],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
describe('standard calendar', () => {
|
||||
let fixture: ComponentFixture<StandardCalendar>;
|
||||
let testComponent: StandardCalendar;
|
||||
let calendarElement: HTMLElement;
|
||||
let periodButton: HTMLElement;
|
||||
let prevButton: HTMLElement;
|
||||
let nextButton: HTMLElement;
|
||||
let calendarInstance: MatCalendar<Date>;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StandardCalendar);
|
||||
fixture.detectChanges();
|
||||
|
||||
let calendarDebugElement = fixture.debugElement.query(By.directive(MatCalendar));
|
||||
calendarElement = calendarDebugElement.nativeElement;
|
||||
periodButton = calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
|
||||
prevButton = calendarElement.querySelector('.mat-calendar-previous-button') as HTMLElement;
|
||||
nextButton = calendarElement.querySelector('.mat-calendar-next-button') as HTMLElement;
|
||||
|
||||
calendarInstance = calendarDebugElement.componentInstance;
|
||||
testComponent = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should be in month view with specified month active', () => {
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should toggle view when period clicked', () => {
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
});
|
||||
|
||||
it('should go to next and previous month', () => {
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 28));
|
||||
});
|
||||
|
||||
it('should go to previous and next year', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to previous and next multi-year range', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back to month view after selecting year and month', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
let yearCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(yearCells[0] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
|
||||
let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(monthCells[monthCells.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
expect(testComponent.selected).toBeFalsy('no date should be selected yet');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-calendar
|
||||
[startAt]="startDate"
|
||||
[(selected)]="selected"
|
||||
(yearSelected)="selectedYear=$event"
|
||||
(monthSelected)="selectedMonth=$event">
|
||||
</mat-calendar>`
|
||||
})
|
||||
class StandardCalendar {
|
||||
selected: Date;
|
||||
selectedYear: Date;
|
||||
selectedMonth: Date;
|
||||
startDate = new Date(2017, JAN, 31);
|
||||
}
|
||||
@@ -1,29 +1,10 @@
|
||||
<div class="mat-calendar-header">
|
||||
<div class="mat-calendar-controls">
|
||||
<button mat-button class="mat-calendar-period-button"
|
||||
(click)="_currentPeriodClicked()" [attr.aria-label]="_periodButtonLabel">
|
||||
{{_periodButtonText}}
|
||||
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="_currentView != 'month'"></div>
|
||||
</button>
|
||||
|
||||
<div class="mat-calendar-spacer"></div>
|
||||
<ng-template [cdkPortalOutlet]="_calendarHeaderPortal"></ng-template>
|
||||
|
||||
<button mat-icon-button class="mat-calendar-previous-button"
|
||||
[disabled]="!_previousEnabled()" (click)="_previousClicked()"
|
||||
[attr.aria-label]="_prevButtonLabel">
|
||||
</button>
|
||||
|
||||
<button mat-icon-button class="mat-calendar-next-button"
|
||||
[disabled]="!_nextEnabled()" (click)="_nextClicked()"
|
||||
[attr.aria-label]="_nextButtonLabel">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-calendar-content" [ngSwitch]="_currentView" cdkMonitorSubtreeFocus tabindex="-1">
|
||||
<div class="mat-calendar-content" [ngSwitch]="currentView" cdkMonitorSubtreeFocus tabindex="-1">
|
||||
<mat-month-view
|
||||
*ngSwitchCase="'month'"
|
||||
[(activeDate)]="_activeDate"
|
||||
[(activeDate)]="activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
@@ -34,7 +15,7 @@
|
||||
|
||||
<mat-year-view
|
||||
*ngSwitchCase="'year'"
|
||||
[activeDate]="_activeDate"
|
||||
[activeDate]="activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
@@ -45,7 +26,7 @@
|
||||
|
||||
<mat-multi-year-view
|
||||
*ngSwitchCase="'multi-year'"
|
||||
[activeDate]="_activeDate"
|
||||
[activeDate]="activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
|
||||
@@ -8,14 +8,9 @@ import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing';
|
||||
import {DEC, FEB, JAN, MatNativeDateModule, NOV} from '@angular/material/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {MatButtonModule} from '../button/index';
|
||||
import {MatCalendar} from './calendar';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
|
||||
import {MatDatepickerModule} from './datepicker-module';
|
||||
|
||||
describe('MatCalendar', () => {
|
||||
let dir: {value: Direction};
|
||||
@@ -23,16 +18,10 @@ describe('MatCalendar', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatNativeDateModule,
|
||||
MatDatepickerModule,
|
||||
],
|
||||
declarations: [
|
||||
MatCalendar,
|
||||
MatCalendarBody,
|
||||
MatMonthView,
|
||||
MatYearView,
|
||||
MatMultiYearView,
|
||||
|
||||
// Test components.
|
||||
StandardCalendar,
|
||||
CalendarWithMinMax,
|
||||
@@ -52,8 +41,6 @@ describe('MatCalendar', () => {
|
||||
let testComponent: StandardCalendar;
|
||||
let calendarElement: HTMLElement;
|
||||
let periodButton: HTMLElement;
|
||||
let prevButton: HTMLElement;
|
||||
let nextButton: HTMLElement;
|
||||
let calendarInstance: MatCalendar<Date>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -63,108 +50,14 @@ describe('MatCalendar', () => {
|
||||
let calendarDebugElement = fixture.debugElement.query(By.directive(MatCalendar));
|
||||
calendarElement = calendarDebugElement.nativeElement;
|
||||
periodButton = calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
|
||||
prevButton = calendarElement.querySelector('.mat-calendar-previous-button') as HTMLElement;
|
||||
nextButton = calendarElement.querySelector('.mat-calendar-next-button') as HTMLElement;
|
||||
|
||||
calendarInstance = calendarDebugElement.componentInstance;
|
||||
testComponent = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should be in month view with specified month active', () => {
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should toggle view when period clicked', () => {
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
});
|
||||
|
||||
it('should go to next and previous month', () => {
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 28));
|
||||
});
|
||||
|
||||
it('should go to previous and next year', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to previous and next multi-year range', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back to month view after selecting year and month', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
let yearCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(yearCells[0] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
|
||||
let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(monthCells[monthCells.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
expect(testComponent.selected).toBeFalsy('no date should be selected yet');
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should select date in month view', () => {
|
||||
@@ -172,7 +65,7 @@ describe('MatCalendar', () => {
|
||||
(monthCells[monthCells.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
@@ -180,14 +73,14 @@ describe('MatCalendar', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
|
||||
@@ -199,8 +92,8 @@ describe('MatCalendar', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
|
||||
@@ -237,7 +130,7 @@ describe('MatCalendar', () => {
|
||||
});
|
||||
|
||||
it('should initially set start date active', () => {
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should make the calendar body focusable', () => {
|
||||
@@ -249,12 +142,12 @@ describe('MatCalendar', () => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
|
||||
(calendarBodyEl.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
});
|
||||
|
||||
it('should return to month view on enter', () => {
|
||||
@@ -266,8 +159,8 @@ describe('MatCalendar', () => {
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -277,7 +170,7 @@ describe('MatCalendar', () => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance.currentView).toBe('multi-year');
|
||||
});
|
||||
|
||||
it('should go to year view on enter', () => {
|
||||
@@ -289,8 +182,8 @@ describe('MatCalendar', () => {
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -319,14 +212,14 @@ describe('MatCalendar', () => {
|
||||
testComponent.startAt = new Date(2000, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
});
|
||||
|
||||
it('should clamp startAt value above max date', () => {
|
||||
testComponent.startAt = new Date(2020, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
});
|
||||
|
||||
it('should not go back past min date', () => {
|
||||
@@ -337,18 +230,18 @@ describe('MatCalendar', () => {
|
||||
calendarElement.querySelector('.mat-calendar-previous-button') as HTMLButtonElement;
|
||||
|
||||
expect(prevButton.disabled).toBe(false, 'previous button should not be disabled');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, FEB, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, FEB, 1));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(prevButton.disabled).toBe(true, 'previous button should be disabled');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
});
|
||||
|
||||
it('should not go forward past max date', () => {
|
||||
@@ -359,18 +252,18 @@ describe('MatCalendar', () => {
|
||||
calendarElement.querySelector('.mat-calendar-next-button') as HTMLButtonElement;
|
||||
|
||||
expect(nextButton.disabled).toBe(false, 'next button should not be disabled');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, DEC, 1));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(nextButton.disabled).toBe(true, 'next button should be disabled');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
});
|
||||
|
||||
it('should re-render the month view when the minDate changes', () => {
|
||||
@@ -501,8 +394,8 @@ describe('MatCalendar', () => {
|
||||
});
|
||||
|
||||
it('should not allow selection of disabled date in month view', () => {
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
@@ -519,16 +412,16 @@ describe('MatCalendar', () => {
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, NOV, 1);
|
||||
calendarInstance.activeDate = new Date(2017, NOV, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance.currentView).toBe('year');
|
||||
|
||||
tableBodyEl = calendarElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance.currentView).toBe('month');
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
+157
-95
@@ -6,12 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ComponentPortal, ComponentType, Portal} from '@angular/cdk/portal';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Host,
|
||||
Inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
@@ -23,6 +26,9 @@ import {
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {takeUntil} from 'rxjs/operators/takeUntil';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
@@ -30,6 +36,127 @@ import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
|
||||
/** Default header for MatCalendar */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-calendar-header',
|
||||
templateUrl: 'calendar-header.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MatCalendarHeader<D> implements OnDestroy {
|
||||
/** Subject that emits when the component has been destroyed. */
|
||||
private _destroyed = new Subject<void>();
|
||||
|
||||
constructor(private _intl: MatDatepickerIntl,
|
||||
@Host() @Inject(forwardRef(() => MatCalendar)) public calendar: MatCalendar<D>,
|
||||
@Optional() private _dateAdapter: DateAdapter<D>,
|
||||
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
|
||||
changeDetectorRef: ChangeDetectorRef) {
|
||||
this.calendar.stateChanges.pipe(takeUntil(this._destroyed))
|
||||
.subscribe(() => changeDetectorRef.markForCheck());
|
||||
}
|
||||
|
||||
/** The label for the current calendar view. */
|
||||
get periodButtonText(): string {
|
||||
if (this.calendar.currentView == 'month') {
|
||||
return this._dateAdapter
|
||||
.format(this.calendar.activeDate, this._dateFormats.display.monthYearLabel)
|
||||
.toLocaleUpperCase();
|
||||
}
|
||||
if (this.calendar.currentView == 'year') {
|
||||
return this._dateAdapter.getYearName(this.calendar.activeDate);
|
||||
}
|
||||
const activeYear = this._dateAdapter.getYear(this.calendar.activeDate);
|
||||
const firstYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear - activeYear % 24, 0, 1));
|
||||
const lastYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear + yearsPerPage - 1 - activeYear % 24, 0, 1));
|
||||
return `${firstYearInView} \u2013 ${lastYearInView}`;
|
||||
}
|
||||
|
||||
get periodButtonLabel(): string {
|
||||
return this.calendar.currentView == 'month' ?
|
||||
this._intl.switchToMultiYearViewLabel : this._intl.switchToMonthViewLabel;
|
||||
}
|
||||
|
||||
/** The label for the the previous button. */
|
||||
get prevButtonLabel(): string {
|
||||
return {
|
||||
'month': this._intl.prevMonthLabel,
|
||||
'year': this._intl.prevYearLabel,
|
||||
'multi-year': this._intl.prevMultiYearLabel
|
||||
}[this.calendar.currentView];
|
||||
}
|
||||
|
||||
/** The label for the the next button. */
|
||||
get nextButtonLabel(): string {
|
||||
return {
|
||||
'month': this._intl.nextMonthLabel,
|
||||
'year': this._intl.nextYearLabel,
|
||||
'multi-year': this._intl.nextMultiYearLabel
|
||||
}[this.calendar.currentView];
|
||||
}
|
||||
|
||||
/** Handles user clicks on the period label. */
|
||||
currentPeriodClicked(): void {
|
||||
this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month';
|
||||
}
|
||||
|
||||
/** Handles user clicks on the previous button. */
|
||||
previousClicked(): void {
|
||||
this.calendar.activeDate = this.calendar.currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this.calendar.activeDate, -1) :
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this.calendar.activeDate, this.calendar.currentView == 'year' ? -1 : -yearsPerPage
|
||||
);
|
||||
}
|
||||
|
||||
/** Handles user clicks on the next button. */
|
||||
nextClicked(): void {
|
||||
this.calendar.activeDate = this.calendar.currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this.calendar.activeDate, 1) :
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this.calendar.activeDate,
|
||||
this.calendar.currentView == 'year' ? 1 : yearsPerPage
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether the previous period button is enabled. */
|
||||
previousEnabled(): boolean {
|
||||
if (!this.calendar.minDate) {
|
||||
return true;
|
||||
}
|
||||
return !this.calendar.minDate ||
|
||||
!this._isSameView(this.calendar.activeDate, this.calendar.minDate);
|
||||
}
|
||||
|
||||
/** Whether the next period button is enabled. */
|
||||
nextEnabled(): boolean {
|
||||
return !this.calendar.maxDate ||
|
||||
!this._isSameView(this.calendar.activeDate, this.calendar.maxDate);
|
||||
}
|
||||
|
||||
/** Whether the two dates represent the same view in the current view mode (month or year). */
|
||||
private _isSameView(date1: D, date2: D): boolean {
|
||||
if (this.calendar.currentView == 'month') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&
|
||||
this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2);
|
||||
}
|
||||
if (this.calendar.currentView == 'year') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
|
||||
}
|
||||
// Otherwise we are in 'multi-year' view.
|
||||
return Math.floor(this._dateAdapter.getYear(date1) / yearsPerPage) ==
|
||||
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._destroyed.next();
|
||||
this._destroyed.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A calendar that is used as part of the datepicker.
|
||||
@@ -48,6 +175,12 @@ import {MatYearView} from './year-view';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
/** An input indicating the type of the header component, if set. */
|
||||
@Input() headerComponent: ComponentType<any>;
|
||||
|
||||
/** A portal containing the header component type for this calendar. */
|
||||
_calendarHeaderPortal: Portal<any>;
|
||||
|
||||
private _intlChanges: Subscription;
|
||||
|
||||
/** A date representing the period (month or year) to start the calendar in. */
|
||||
@@ -119,56 +252,26 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
* The current active date. This determines which time period is shown and which date is
|
||||
* highlighted when using keyboard navigation.
|
||||
*/
|
||||
get _activeDate(): D { return this._clampedActiveDate; }
|
||||
set _activeDate(value: D) {
|
||||
get activeDate(): D { return this._clampedActiveDate; }
|
||||
set activeDate(value: D) {
|
||||
this._clampedActiveDate = this._dateAdapter.clampDate(value, this.minDate, this.maxDate);
|
||||
this._stateChanges.next();
|
||||
}
|
||||
private _clampedActiveDate: D;
|
||||
|
||||
/** Whether the calendar is in month view. */
|
||||
_currentView: 'month' | 'year' | 'multi-year';
|
||||
currentView: 'month' | 'year' | 'multi-year';
|
||||
|
||||
/** The label for the current calendar view. */
|
||||
get _periodButtonText(): string {
|
||||
if (this._currentView == 'month') {
|
||||
return this._dateAdapter.format(this._activeDate, this._dateFormats.display.monthYearLabel)
|
||||
.toLocaleUpperCase();
|
||||
}
|
||||
if (this._currentView == 'year') {
|
||||
return this._dateAdapter.getYearName(this._activeDate);
|
||||
}
|
||||
const activeYear = this._dateAdapter.getYear(this._activeDate);
|
||||
const firstYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear - activeYear % 24, 0, 1));
|
||||
const lastYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear + yearsPerPage - 1 - activeYear % 24, 0, 1));
|
||||
return `${firstYearInView} \u2013 ${lastYearInView}`;
|
||||
/**
|
||||
* An observable that emits whenever there is a state change that the header may need to respond
|
||||
* to.
|
||||
*/
|
||||
get stateChanges(): Observable<void> {
|
||||
return this._stateChanges.asObservable();
|
||||
}
|
||||
private _stateChanges = new Subject<void>();
|
||||
|
||||
get _periodButtonLabel(): string {
|
||||
return this._currentView == 'month' ?
|
||||
this._intl.switchToMultiYearViewLabel : this._intl.switchToMonthViewLabel;
|
||||
}
|
||||
|
||||
/** The label for the the previous button. */
|
||||
get _prevButtonLabel(): string {
|
||||
return {
|
||||
'month': this._intl.prevMonthLabel,
|
||||
'year': this._intl.prevYearLabel,
|
||||
'multi-year': this._intl.prevMultiYearLabel
|
||||
}[this._currentView];
|
||||
}
|
||||
|
||||
/** The label for the the next button. */
|
||||
get _nextButtonLabel(): string {
|
||||
return {
|
||||
'month': this._intl.nextMonthLabel,
|
||||
'year': this._intl.nextYearLabel,
|
||||
'multi-year': this._intl.nextMultiYearLabel
|
||||
}[this._currentView];
|
||||
}
|
||||
|
||||
constructor(private _intl: MatDatepickerIntl,
|
||||
constructor(_intl: MatDatepickerIntl,
|
||||
@Optional() private _dateAdapter: DateAdapter<D>,
|
||||
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
|
||||
changeDetectorRef: ChangeDetectorRef) {
|
||||
@@ -181,12 +284,17 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
throw createMissingDateImplError('MAT_DATE_FORMATS');
|
||||
}
|
||||
|
||||
this._intlChanges = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
|
||||
this._intlChanges = _intl.changes.subscribe(() => {
|
||||
changeDetectorRef.markForCheck();
|
||||
this._stateChanges.next();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._activeDate = this.startAt || this._dateAdapter.today();
|
||||
this._currentView = this.startView;
|
||||
this._calendarHeaderPortal = new ComponentPortal(this.headerComponent || MatCalendarHeader);
|
||||
|
||||
this.activeDate = this.startAt || this._dateAdapter.today();
|
||||
this.currentView = this.startView;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -203,6 +311,8 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
view._init();
|
||||
}
|
||||
}
|
||||
|
||||
this._stateChanges.next();
|
||||
}
|
||||
|
||||
/** Handles date selection in the month view. */
|
||||
@@ -228,56 +338,8 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
|
||||
/** Handles year/month selection in the multi-year/year views. */
|
||||
_goToDateInView(date: D, view: 'month' | 'year' | 'multi-year'): void {
|
||||
this._activeDate = date;
|
||||
this._currentView = view;
|
||||
}
|
||||
|
||||
/** Handles user clicks on the period label. */
|
||||
_currentPeriodClicked(): void {
|
||||
this._currentView = this._currentView == 'month' ? 'multi-year' : 'month';
|
||||
}
|
||||
|
||||
/** Handles user clicks on the previous button. */
|
||||
_previousClicked(): void {
|
||||
this._activeDate = this._currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, -1) :
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, this._currentView == 'year' ? -1 : -yearsPerPage);
|
||||
}
|
||||
|
||||
/** Handles user clicks on the next button. */
|
||||
_nextClicked(): void {
|
||||
this._activeDate = this._currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, 1) :
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, this._currentView == 'year' ? 1 : yearsPerPage);
|
||||
}
|
||||
|
||||
/** Whether the previous period button is enabled. */
|
||||
_previousEnabled(): boolean {
|
||||
if (!this.minDate) {
|
||||
return true;
|
||||
}
|
||||
return !this.minDate || !this._isSameView(this._activeDate, this.minDate);
|
||||
}
|
||||
|
||||
/** Whether the next period button is enabled. */
|
||||
_nextEnabled(): boolean {
|
||||
return !this.maxDate || !this._isSameView(this._activeDate, this.maxDate);
|
||||
}
|
||||
|
||||
/** Whether the two dates represent the same view in the current view mode (month or year). */
|
||||
private _isSameView(date1: D, date2: D): boolean {
|
||||
if (this._currentView == 'month') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&
|
||||
this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2);
|
||||
}
|
||||
if (this._currentView == 'year') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
|
||||
}
|
||||
// Otherwise we are in 'multi-year' view.
|
||||
return Math.floor(this._dateAdapter.getYear(date1) / yearsPerPage) ==
|
||||
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage);
|
||||
this.activeDate = date;
|
||||
this.currentView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[minDate]="datepicker._minDate"
|
||||
[maxDate]="datepicker._maxDate"
|
||||
[dateFilter]="datepicker._dateFilter"
|
||||
[headerComponent]="datepicker.calendarHeaderComponent"
|
||||
[selected]="datepicker._selected"
|
||||
[@fadeInCalendar]="'enter'"
|
||||
(selectedChange)="datepicker._select($event)"
|
||||
|
||||
@@ -13,6 +13,7 @@ import {NgModule} from '@angular/core';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatDialogModule} from '@angular/material/dialog';
|
||||
import {MatCalendar} from './calendar';
|
||||
import {MatCalendarHeader} from './calendar';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatDatepicker, MatDatepickerContent} from './datepicker';
|
||||
import {MatDatepickerInput} from './datepicker-input';
|
||||
@@ -21,15 +22,17 @@ import {MatDatepickerToggle, MatDatepickerToggleIcon} from './datepicker-toggle'
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
import {PortalModule} from '@angular/cdk/portal';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
A11yModule,
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
OverlayModule,
|
||||
A11yModule,
|
||||
PortalModule,
|
||||
],
|
||||
exports: [
|
||||
MatCalendar,
|
||||
@@ -54,12 +57,14 @@ import {MatYearView} from './year-view';
|
||||
MatMonthView,
|
||||
MatYearView,
|
||||
MatMultiYearView,
|
||||
MatCalendarHeader
|
||||
],
|
||||
providers: [
|
||||
MatDatepickerIntl,
|
||||
],
|
||||
entryComponents: [
|
||||
MatDatepickerContent,
|
||||
MatCalendarHeader,
|
||||
]
|
||||
})
|
||||
export class MatDatepickerModule {}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
PositionStrategy,
|
||||
ScrollStrategy,
|
||||
} from '@angular/cdk/overlay';
|
||||
import {ComponentPortal} from '@angular/cdk/portal';
|
||||
import {ComponentPortal, ComponentType} from '@angular/cdk/portal';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {
|
||||
AfterContentInit,
|
||||
@@ -176,6 +176,9 @@ export class MatDatepickerContent<D> extends _MatDatepickerContentMixinBase
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class MatDatepicker<D> implements OnDestroy, CanColor {
|
||||
/** An input indicating the type of the custom header component for the calendar, if set. */
|
||||
@Input() calendarHeaderComponent: ComponentType<any>;
|
||||
|
||||
/** The date to open the calendar to initially. */
|
||||
@Input()
|
||||
get startAt(): D | null {
|
||||
|
||||
Reference in New Issue
Block a user