Skip to content

Commit

Permalink
feat(datepicker): min & max dates + dateFilter (angular#3556)
Browse files Browse the repository at this point in the history
* don't allow going past min/max date

* add back missing mdSuffix

* added date filtering logic

* added working date filter

* disabled style for prev/next buttons

* add unit tests

* fix lint

* address comments
  • Loading branch information
mmalerba committed Mar 28, 2017
1 parent b0f931d commit 8f1ba76
Show file tree
Hide file tree
Showing 19 changed files with 439 additions and 29 deletions.
6 changes: 4 additions & 2 deletions src/demo-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ <h1>Work in progress, not ready for use.</h1>
<p>
<md-input-container>
<input mdInput [mdDatepicker]="dp2" [(ngModel)]="date">
<button [mdDatepickerToggle]="dp2"></button>
<button mdSuffix [mdDatepickerToggle]="dp2"></button>
<md-datepicker #dp2 [touchUi]="touch"></md-datepicker>
</md-input-container>
</p>
<p>
<button [mdDatepickerToggle]="dp3"></button>
<md-input-container>
<input mdInput [mdDatepicker]="dp3" [(ngModel)]="date">
<md-datepicker #dp3 [touchUi]="touch"></md-datepicker>
<md-datepicker #dp3 [touchUi]="touch" startAt="1/1/17" minDate="1/1/16" maxDate="1/1/18"
[dateFilter]="dateFilter">
</md-datepicker>
</md-input-container>
</p>
2 changes: 2 additions & 0 deletions src/demo-app/datepicker/datepicker-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
19 changes: 19 additions & 0 deletions src/lib/core/datetime/simple-date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,23 @@ describe('SimpleDate', () => {
expect(new SimpleDate(2017, 0, 1).add({years: 1, months: 1, days: 1}))
.toEqual(new SimpleDate(2018, 1, 2));
});

it('clamps date at lower bound', () => {
let date = new SimpleDate(2017, 0, 1);
let lower = new SimpleDate(2018, 1, 2);
let upper = new SimpleDate(2019, 2, 3);
expect(date.clamp(lower, upper)).toEqual(lower);
});

it('clamps date at upper bound', () => {
let date = new SimpleDate(2020, 0, 1);
let lower = new SimpleDate(2018, 1, 2);
let upper = new SimpleDate(2019, 2, 3);
expect(date.clamp(lower, upper)).toEqual(upper);
});

it('clamp treats null as unbounded', () => {
let date = new SimpleDate(2017, 0, 1);
expect(date.clamp(null, null)).toEqual(date);
});
});
18 changes: 18 additions & 0 deletions src/lib/core/datetime/simple-date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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): 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);
Expand Down
22 changes: 19 additions & 3 deletions src/lib/datepicker/_datepicker-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@


@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-datepicker-selected-fade-amount: 0.6;
$mat-datepicker-today-fade-amount: 0.2;

.mat-calendar {
background-color: mat-color($background, card);
}
Expand Down Expand Up @@ -36,24 +39,37 @@
.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);
}
}

.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), $mat-datepicker-selected-fade-amount);
}
}

.mat-calendar-table-today {
&:not(.mat-calendar-table-selected) {
// 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), $mat-datepicker-today-fade-amount);
}
}

&.mat-calendar-table-selected {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/datepicker/calendar-table.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
</td>
<td *ngFor="let item of row"
class="mat-calendar-table-cell"
(click)="_cellClicked(item.value)">
[class.mat-calendar-table-disabled]="!item.enabled"
(click)="_cellClicked(item)">
<div class="mat-calendar-table-cell-content"
[class.mat-calendar-table-selected]="selectedValue === item.value"
[class.mat-calendar-table-today]="todayValue === item.value">
Expand Down
53 changes: 52 additions & 1 deletion src/lib/datepicker/calendar-table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {MdCalendarCell, MdCalendarTable} from './calendar-table';
import {By} from '@angular/platform-browser';
import {SimpleDate} from '../core/datetime/simple-date';


describe('MdCalendarTable', () => {
Expand All @@ -12,6 +13,7 @@ describe('MdCalendarTable', () => {

// Test components.
StandardCalendarTable,
CalendarTableWithDisabledCells,
],
});

Expand Down Expand Up @@ -85,6 +87,38 @@ describe('MdCalendarTable', () => {
.toContain('mat-calendar-table-selected', 'today should be selected');
});
});

describe('calendar table with disabled cells', () => {
let fixture: ComponentFixture<CalendarTableWithDisabledCells>;
let testComponent: CalendarTableWithDisabledCells;
let calendarTableNativeElement: Element;
let cellEls: NodeListOf<Element>;

beforeEach(() => {
fixture = TestBed.createComponent(CalendarTableWithDisabledCells);
fixture.detectChanges();

let calendarTableDebugElement = fixture.debugElement.query(By.directive(MdCalendarTable));
calendarTableNativeElement = calendarTableDebugElement.nativeElement;
testComponent = fixture.componentInstance;
cellEls = calendarTableNativeElement.querySelectorAll('.mat-calendar-table-cell');
});

it('should only allow selection of disabled cells when allowDisabledSelection is true', () => {
(cellEls[0] as HTMLElement).click();
fixture.detectChanges();

expect(testComponent.selected).toBeFalsy();

testComponent.allowDisabledSelection = true;
fixture.detectChanges();

(cellEls[0] as HTMLElement).click();
fixture.detectChanges();

expect(testComponent.selected).toBe(1);
});
});
});


Expand Down Expand Up @@ -112,6 +146,23 @@ class StandardCalendarTable {
}


@Component({
template: `<md-calendar-table [rows]="rows"
[allowDisabledSelection]="allowDisabledSelection"
(selectedValueChange)="selected = $event">
</md-calendar-table>`
})
class CalendarTableWithDisabledCells {
rows = [[1, 2, 3, 4]].map(r => r.map(d => {
let cell = createCell(d);
cell.enabled = d % 2 == 0;
return cell;
}));
allowDisabledSelection = false;
selected: SimpleDate;
}


function createCell(value: number) {
return new MdCalendarCell(value, `${value}`);
return new MdCalendarCell(value, `${value}`, true);
}
11 changes: 7 additions & 4 deletions src/lib/datepicker/calendar-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
}


Expand Down Expand Up @@ -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<number>();

_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. */
Expand Down
8 changes: 6 additions & 2 deletions src/lib/datepicker/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="!_monthView"></div>
</button>
<div class="mat-calendar-spacer"></div>
<button class="mat-calendar-button mat-calendar-previous-button" (click)="_previousClicked()">
<button class="mat-calendar-button mat-calendar-previous-button"
[class.mat-calendar-disabled]="!_previousEnabled()" (click)="_previousClicked()">
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"
fill="currentColor">
<path d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"/>
<path d="M0-.5h24v24H0z" fill="none"/>
</svg>
</button>
<button class="mat-calendar-button mat-calendar-next-button" (click)="_nextClicked()">
<button class="mat-calendar-button mat-calendar-next-button"
[class.mat-calendar-disabled]="!_nextEnabled()" (click)="_nextClicked()">
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"
fill="currentColor">
<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>
Expand All @@ -32,13 +34,15 @@
*ngIf="_monthView"
[date]="_currentPeriod"
[selected]="selected"
[dateFilter]="_dateFilterForViews"
(selectedChange)="_dateSelected($event)">
</md-month-view>

<md-year-view
*ngIf="!_monthView"
[date]="_currentPeriod"
[selected]="selected"
[dateFilter]="_dateFilterForViews"
(selectedChange)="_monthSelected($event)">
</md-year-view>
</div>
5 changes: 5 additions & 0 deletions src/lib/datepicker/calendar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $mat-calendar-controls-start-padding: calc(100% / 14 - 6px);
$mat-calendar-controls-end-padding: calc(100% / 14 - 12px);
$mat-calendar-period-font-size: 14px;
$mat-calendar-arrow-size: 5px !default;
$mat-calendar-arrow-disabled-opacity: 0.5 !default;
$mat-calendar-weekday-table-font-size: 11px !default;


Expand Down Expand Up @@ -41,6 +42,10 @@ $mat-calendar-weekday-table-font-size: 11px !default;
margin: 0;
border: none;
outline: none;

&.mat-calendar-disabled {
opacity: $mat-calendar-arrow-disabled-opacity;
}
}

.mat-calendar-period-button {
Expand Down
Loading

0 comments on commit 8f1ba76

Please sign in to comment.