From 89d5d43ce51b2aa360094f3f1a9c935ea619e673 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 9 Mar 2017 16:22:40 -0800 Subject: [PATCH 1/8] don't allow going past min/max date --- src/demo-app/datepicker/datepicker-demo.html | 3 ++- src/lib/core/datetime/simple-date.ts | 18 ++++++++++++++ src/lib/datepicker/calendar.ts | 20 +++++++++++++-- src/lib/datepicker/datepicker.html | 3 +++ src/lib/datepicker/datepicker.ts | 26 ++++++++++++++++++-- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index c975b470e636..ec8334cf4f5d 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -20,6 +20,7 @@

Work in progress, not ready for use.

- + +

diff --git a/src/lib/core/datetime/simple-date.ts b/src/lib/core/datetime/simple-date.ts index abb904fbab7a..98d69b03c9f3 100644 --- a/src/lib/core/datetime/simple-date.ts +++ b/src/lib/core/datetime/simple-date.ts @@ -51,6 +51,7 @@ export class SimpleDate { /** * Adds an amount of time (in days, months, and years) to the date. * @param amount The amount of time to add. + * @returns A new SimpleDate with the given amount of time added. */ add(amount: {days?: number, months?: number, years?: number}): SimpleDate { return new SimpleDate( @@ -69,6 +70,23 @@ export class SimpleDate { return this.year - other.year || this.month - other.month || this.date - other.date; } + /** + * Clamps the date between the given min and max dates. + * @param min The minimum date + * @param max The maximum date + * @returns A new SimpleDate equal to this one clamped between the given min and max dates. + */ + clamp(min: SimpleDate, max: SimpleDate) { + let clampedDate: SimpleDate = this; + if (min && this.compare(min) < 0) { + clampedDate = min; + } + if (max && this.compare(max) > 0) { + clampedDate = max; + } + return new SimpleDate(clampedDate.year, clampedDate.month, clampedDate.date); + } + /** Converts the SimpleDate to a native JS Date object. */ toNativeDate(): Date { return new Date(this.year, this.month, this.date); diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index cca226f0de45..ef856edf3fd2 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -27,7 +27,7 @@ import {CalendarLocale} from '../core/datetime/calendar-locale'; export class MdCalendar implements AfterContentInit { /** A date representing the period (month or year) to start the calendar in. */ @Input() - get startAt() {return this._startAt; } + get startAt() { return this._startAt; } set startAt(value: any) { this._startAt = this._locale.parseDate(value); } private _startAt: SimpleDate; @@ -40,6 +40,21 @@ export class MdCalendar implements AfterContentInit { set selected(value: any) { this._selected = this._locale.parseDate(value); } private _selected: SimpleDate; + /** The minimum selectable date. */ + @Input() + get minDate(): SimpleDate { return this._minDate; }; + set minDate(date: SimpleDate) { this._minDate = this._locale.parseDate(date); } + private _minDate: SimpleDate; + + /** The maximum selectable date. */ + @Input() + get maxDate(): SimpleDate { return this._maxDate; }; + set maxDate(date: SimpleDate) { this._maxDate = this._locale.parseDate(date); } + private _maxDate: SimpleDate; + + /** A function used to filter which dates are selectable. */ + @Input() dateFilter: (date: SimpleDate) => boolean; + /** Emits when the currently selected date changes. */ @Output() selectedChange = new EventEmitter(); @@ -50,7 +65,8 @@ export class MdCalendar implements AfterContentInit { */ get _currentPeriod() { return this._normalizedCurrentPeriod; } set _currentPeriod(value: SimpleDate) { - this._normalizedCurrentPeriod = new SimpleDate(value.year, value.month, 1); + let clampedValue = value.clamp(this.minDate, this.maxDate); + this._normalizedCurrentPeriod = new SimpleDate(clampedValue.year, clampedValue.month, 1); } private _normalizedCurrentPeriod: SimpleDate; diff --git a/src/lib/datepicker/datepicker.html b/src/lib/datepicker/datepicker.html index bf20f86edd3d..369f064a0ee9 100644 --- a/src/lib/datepicker/datepicker.html +++ b/src/lib/datepicker/datepicker.html @@ -4,6 +4,9 @@ [class.mat-datepicker-touch]="touchUi" [class.mat-datepicker-non-touch]="!touchUi" [startAt]="startAt" + [minDate]="minDate" + [maxDate]="maxDate" + [dateFilter]="dateFilter" [(selected)]="_selected"> diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index a2f8fb9a94d8..fee217176ba5 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, - Component, EventEmitter, + Component, + EventEmitter, Input, OnDestroy, - Optional, Output, + Optional, + Output, TemplateRef, ViewChild, ViewContainerRef, @@ -64,12 +66,32 @@ export class MdDatepicker implements OnDestroy { @Input() touchUi = false; + /** The minimum selectable date. */ + @Input() + get minDate(): SimpleDate { return this._minDate; }; + set minDate(date: SimpleDate) { this._minDate = this._locale.parseDate(date); } + private _minDate: SimpleDate; + + /** The maximum selectable date. */ + @Input() + get maxDate(): SimpleDate { return this._maxDate; }; + set maxDate(date: SimpleDate) { this._maxDate = this._locale.parseDate(date); } + private _maxDate: SimpleDate; + + /** A function used to filter which dates are selectable. */ + @Input() + dateFilter: (date: SimpleDate) => boolean; + + /** Emits new selected date when selected date changes. */ @Output() selectedChanged = new EventEmitter(); + /** Whether the calendar is open. */ opened = false; + /** The id for the datepicker calendar. */ id = `md-datepicker-${datepickerUid++}`; + /** The currently selected date. */ get _selected(): SimpleDate { return this._datepickerInput ? this._datepickerInput.value : null; } From 3da23825ebd0aa39ed3b5ac6f0839eeab418822c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 9 Mar 2017 16:27:32 -0800 Subject: [PATCH 2/8] add back missing mdSuffix --- src/demo-app/datepicker/datepicker-demo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index ec8334cf4f5d..5aba9dde25e0 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -12,7 +12,7 @@

Work in progress, not ready for use.

- +

From 9078cb1bbb95e43445e43f427261c688bf43742f Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 9 Mar 2017 17:06:30 -0800 Subject: [PATCH 3/8] added date filtering logic --- src/lib/datepicker/calendar-table.ts | 2 +- src/lib/datepicker/calendar.html | 2 ++ src/lib/datepicker/calendar.ts | 8 +++++++- src/lib/datepicker/month-view.ts | 6 +++++- src/lib/datepicker/year-view.ts | 19 ++++++++++++++++++- 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/lib/datepicker/calendar-table.ts b/src/lib/datepicker/calendar-table.ts index 70999010cbd5..b6cd755fc2c1 100644 --- a/src/lib/datepicker/calendar-table.ts +++ b/src/lib/datepicker/calendar-table.ts @@ -13,7 +13,7 @@ import { * @docs-private */ export class MdCalendarCell { - constructor(public value: number, public displayValue: string) {} + constructor(public value: number, public displayValue: string, public enabled: boolean) {} } diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index 1c4921e792fb..39f16c66f998 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -32,6 +32,7 @@ *ngIf="_monthView" [date]="_currentPeriod" [selected]="selected" + [dateFilter]="_dateFilterForViews" (selectedChange)="_dateSelected($event)"> @@ -39,6 +40,7 @@ *ngIf="!_monthView" [date]="_currentPeriod" [selected]="selected" + [dateFilter]="_dateFilterForViews" (selectedChange)="_monthSelected($event)"> diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index ef856edf3fd2..31f5f803e3ef 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -53,11 +53,17 @@ export class MdCalendar implements AfterContentInit { private _maxDate: SimpleDate; /** A function used to filter which dates are selectable. */ - @Input() dateFilter: (date: SimpleDate) => boolean; + @Input() dateFilter = (date: SimpleDate) => true; /** Emits when the currently selected date changes. */ @Output() selectedChange = new EventEmitter(); + /** Date filter for the month and year views. */ + _dateFilterForViews = (date: SimpleDate) => { + return !!date && date.compare(this.minDate) >= 0 && date.compare(this.maxDate) && + this.dateFilter(date); + }; + /** * A date representing the current period shown in the calendar. The current period is always * normalized to the 1st of a month, this prevents date overflow issues (e.g. adding a month to diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 8fb387b9e358..0dfd3214b3b9 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -47,6 +47,9 @@ export class MdMonthView implements AfterContentInit { } private _selected: SimpleDate; + /** A function used to filter which dates are selectable. */ + @Input() dateFilter = (date: SimpleDate) => true; + /** Emits when a new date is selected. */ @Output() selectedChange = new EventEmitter(); @@ -104,8 +107,9 @@ export class MdMonthView implements AfterContentInit { this._weeks.push([]); cell = 0; } + let enabled = this.dateFilter(new SimpleDate(this.date.year, this.date.month, i + 1)); this._weeks[this._weeks.length - 1] - .push(new MdCalendarCell(i + 1, this._locale.dates[i + 1])); + .push(new MdCalendarCell(i + 1, this._locale.dates[i + 1], enabled)); } } diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index e49ea0b6b4b8..cc6b7c961565 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -42,6 +42,9 @@ export class MdYearView implements AfterContentInit { } private _selected: SimpleDate; + /** A function used to filter which dates are selectable. */ + @Input() dateFilter = (date: SimpleDate) => true; + /** Emits when a new month is selected. */ @Output() selectedChange = new EventEmitter(); @@ -95,6 +98,20 @@ export class MdYearView implements AfterContentInit { /** Creates an MdCalendarCell for the given month. */ private _createCellForMonth(month: number) { - return new MdCalendarCell(month, this._locale.shortMonths[month].toLocaleUpperCase()); + return new MdCalendarCell( + month, this._locale.shortMonths[month].toLocaleUpperCase(), this._isMonthEnabled(month)); + } + + /** Whether the given month is enabled. */ + private _isMonthEnabled(month: number) { + let enabled = false; + for (let date = new SimpleDate(this.date.year, month, 1); date.month === month; + date = date.add({days: 1})) { + enabled = enabled || this.dateFilter(date); + if (enabled) { + break; + } + } + return enabled; } } From 83fba5fbd53492eb5118f01ae8913361b2507357 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 10 Mar 2017 09:33:57 -0800 Subject: [PATCH 4/8] added working date filter --- src/demo-app/datepicker/datepicker-demo.html | 3 ++- src/demo-app/datepicker/datepicker-demo.ts | 2 ++ src/lib/datepicker/_datepicker-theme.scss | 19 ++++++++++++++++--- src/lib/datepicker/calendar-table.html | 3 ++- src/lib/datepicker/calendar-table.ts | 9 ++++++--- src/lib/datepicker/calendar.ts | 8 +++++--- src/lib/datepicker/month-view.ts | 7 ++++--- src/lib/datepicker/year-view.html | 3 ++- src/lib/datepicker/year-view.ts | 19 ++++++++++--------- 9 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html index 5aba9dde25e0..faee6d1cb911 100644 --- a/src/demo-app/datepicker/datepicker-demo.html +++ b/src/demo-app/datepicker/datepicker-demo.html @@ -20,7 +20,8 @@

Work in progress, not ready for use.

- +

diff --git a/src/demo-app/datepicker/datepicker-demo.ts b/src/demo-app/datepicker/datepicker-demo.ts index f0927ec1203c..5c681a100b8f 100644 --- a/src/demo-app/datepicker/datepicker-demo.ts +++ b/src/demo-app/datepicker/datepicker-demo.ts @@ -11,4 +11,6 @@ import {SimpleDate} from '@angular/material'; export class DatepickerDemo { date: SimpleDate; touch = false; + dateFilter = (date: SimpleDate) => !this._blacklistedMonths.has(date.month) && date.date % 2 == 0; + private _blacklistedMonths = new Set([2, 3]); } diff --git a/src/lib/datepicker/_datepicker-theme.scss b/src/lib/datepicker/_datepicker-theme.scss index 5cf64b335323..725cca5529a6 100644 --- a/src/lib/datepicker/_datepicker-theme.scss +++ b/src/lib/datepicker/_datepicker-theme.scss @@ -3,11 +3,12 @@ @mixin mat-datepicker-theme($theme) { - $mat-datepicker-selected-today-box-shadow-width: 1px; $primary: map-get($theme, primary); $foreground: map-get($theme, foreground); $background: map-get($theme, background); + $mat-datepicker-selected-today-box-shadow-width: 1px; + .mat-calendar { background-color: mat-color($background, card); } @@ -36,10 +37,14 @@ .mat-calendar-table-cell-content { color: mat-color($foreground, text); border-color: transparent; + + .mat-calendar-table-disabled > &:not(.mat-calendar-table-selected) { + color: mat-color($foreground, disabled-text); + } } - .mat-calendar-table-cell:hover { - .mat-calendar-table-cell-content:not(.mat-calendar-table-selected) { + :not(.mat-calendar-table-disabled):hover { + & > .mat-calendar-table-cell-content:not(.mat-calendar-table-selected) { background-color: mat-color($background, hover); } } @@ -47,6 +52,10 @@ .mat-calendar-table-selected { background-color: mat-color($primary); color: mat-color($primary, default-contrast); + + .mat-calendar-table-disabled > & { + background-color: fade-out(mat-color($primary), .6); + } } .mat-calendar-table-today { @@ -54,6 +63,10 @@ // Note: though it's not text, the border is a hint about the fact that this is today's date, // so we use the hint color. border-color: mat-color($foreground, hint-text); + + .mat-calendar-table-disabled > & { + border-color: fade-out(mat-color($foreground, hint-text), .2); + } } &.mat-calendar-table-selected { diff --git a/src/lib/datepicker/calendar-table.html b/src/lib/datepicker/calendar-table.html index 6df7a677b4e4..37b29b265d79 100644 --- a/src/lib/datepicker/calendar-table.html +++ b/src/lib/datepicker/calendar-table.html @@ -13,7 +13,8 @@ + [class.mat-calendar-table-disabled]="!item.enabled" + (click)="_cellClicked(item)">
diff --git a/src/lib/datepicker/calendar-table.ts b/src/lib/datepicker/calendar-table.ts index b6cd755fc2c1..aabe9c76fe70 100644 --- a/src/lib/datepicker/calendar-table.ts +++ b/src/lib/datepicker/calendar-table.ts @@ -48,14 +48,17 @@ export class MdCalendarTable { /** The number of columns in the table. */ @Input() numCols = 7; + /** Whether to allow selection of disabled cells. */ + @Input() allowDisabledSelection = false; + /** Emits when a new value is selected. */ @Output() selectedValueChange = new EventEmitter(); - _cellClicked(value: number) { - if (this.selectedValue && this.selectedValue === value) { + _cellClicked(cell: MdCalendarCell) { + if (!this.allowDisabledSelection && !cell.enabled) { return; } - this.selectedValueChange.emit(value); + this.selectedValueChange.emit(cell.value); } /** The number of blank cells to put at the beginning for the first row. */ diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 31f5f803e3ef..4cf496d49ba2 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -53,15 +53,17 @@ export class MdCalendar implements AfterContentInit { private _maxDate: SimpleDate; /** A function used to filter which dates are selectable. */ - @Input() dateFilter = (date: SimpleDate) => true; + @Input() dateFilter: (date: SimpleDate) => boolean; /** Emits when the currently selected date changes. */ @Output() selectedChange = new EventEmitter(); /** Date filter for the month and year views. */ _dateFilterForViews = (date: SimpleDate) => { - return !!date && date.compare(this.minDate) >= 0 && date.compare(this.maxDate) && - this.dateFilter(date); + return !!date && + (!this.dateFilter || this.dateFilter(date)) && + (!this.minDate || date.compare(this.minDate) >= 0) && + (!this.maxDate || date.compare(this.maxDate) <= 0); }; /** diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 0dfd3214b3b9..ced1014d9e00 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -48,7 +48,7 @@ export class MdMonthView implements AfterContentInit { private _selected: SimpleDate; /** A function used to filter which dates are selectable. */ - @Input() dateFilter = (date: SimpleDate) => true; + @Input() dateFilter: (date: SimpleDate) => boolean; /** Emits when a new date is selected. */ @Output() selectedChange = new EventEmitter(); @@ -79,7 +79,7 @@ export class MdMonthView implements AfterContentInit { /** Handles when a new date is selected. */ _dateSelected(date: number) { - if (this.selected && this.selected.date == date) { + if (this._selectedDate == date) { return; } this.selectedChange.emit(new SimpleDate(this.date.year, this.date.month, date)); @@ -107,7 +107,8 @@ export class MdMonthView implements AfterContentInit { this._weeks.push([]); cell = 0; } - let enabled = this.dateFilter(new SimpleDate(this.date.year, this.date.month, i + 1)); + let enabled = !this.dateFilter || + this.dateFilter(new SimpleDate(this.date.year, this.date.month, i + 1)); this._weeks[this._weeks.length - 1] .push(new MdCalendarCell(i + 1, this._locale.dates[i + 1], enabled)); } diff --git a/src/lib/datepicker/year-view.html b/src/lib/datepicker/year-view.html index c2aa74d8e08c..2e70db4cf0a3 100644 --- a/src/lib/datepicker/year-view.html +++ b/src/lib/datepicker/year-view.html @@ -1,4 +1,5 @@ - true; + @Input() dateFilter: (date: SimpleDate) => boolean; /** Emits when a new month is selected. */ @Output() selectedChange = new EventEmitter(); @@ -63,11 +63,7 @@ export class MdYearView implements AfterContentInit { */ _selectedMonth: number; - constructor(private _locale: CalendarLocale) { - // First row of months only contains 5 elements so we can fit the year label on the same row. - this._months = [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11]].map(row => row.map( - month => this._createCellForMonth(month))); - } + constructor(private _locale: CalendarLocale) {} ngAfterContentInit() { this._init(); @@ -75,9 +71,6 @@ export class MdYearView implements AfterContentInit { /** Handles when a new month is selected. */ _monthSelected(month: number) { - if (this.selected && this.selected.month == month) { - return; - } this.selectedChange.emit(new SimpleDate(this.date.year, month, 1)); } @@ -86,6 +79,10 @@ export class MdYearView implements AfterContentInit { this._selectedMonth = this._getMonthInCurrentYear(this.selected); this._todayMonth = this._getMonthInCurrentYear(SimpleDate.today()); this._yearLabel = this._locale.getCalendarYearHeaderLabel(this._date); + + // First row of months only contains 5 elements so we can fit the year label on the same row. + this._months = [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11]].map(row => row.map( + month => this._createCellForMonth(month))); } /** @@ -104,6 +101,10 @@ export class MdYearView implements AfterContentInit { /** Whether the given month is enabled. */ private _isMonthEnabled(month: number) { + if (!this.dateFilter) { + return true; + } + let enabled = false; for (let date = new SimpleDate(this.date.year, month, 1); date.month === month; date = date.add({days: 1})) { From 40fbfce388a342bcdf958278541bfacbcc009619 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Fri, 10 Mar 2017 09:57:02 -0800 Subject: [PATCH 5/8] disabled style for prev/next buttons --- src/lib/datepicker/calendar.html | 6 ++++-- src/lib/datepicker/calendar.scss | 5 +++++ src/lib/datepicker/calendar.ts | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index 39f16c66f998..e570acda369f 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -6,14 +6,16 @@
- -