Skip to content

Commit d590847

Browse files
authored
feat(material/datepicker): add support for cancel/apply buttons (#21487)
Adds support for projecting in "Cancel" and "Apply" buttons in `mat-datepicker` and `mat-date-range-picker`. Doing so will change the behavior of the datepicker so that the user has to explicitly accept a value after they've clicked on it. The consumption is as follows: ``` <mat-datepicker> <mat-datepicker-actions> <button mat-button matDatepickerCancel>Cancel</button> <button mat-raised-button color="primary" matDatepickerApply>Apply</button> </mat-datepicker-actions> </mat-datepicker> ```
1 parent 9f5cfbd commit d590847

20 files changed

+669
-22
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.example-form-field {
2+
margin-right: 20px;
3+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<mat-form-field appearance="fill" class="example-form-field">
2+
<mat-label>Choose a date</mat-label>
3+
<input matInput [matDatepicker]="datepicker">
4+
<mat-datepicker-toggle matSuffix [for]="datepicker"></mat-datepicker-toggle>
5+
<!-- #docregion datepicker-actions -->
6+
<mat-datepicker #datepicker>
7+
<mat-datepicker-actions>
8+
<button mat-button matDatepickerCancel>Cancel</button>
9+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
10+
</mat-datepicker-actions>
11+
</mat-datepicker>
12+
<!-- #enddocregion datepicker-actions -->
13+
</mat-form-field>
14+
15+
<mat-form-field appearance="fill" class="example-form-field">
16+
<mat-label>Enter a date range</mat-label>
17+
<mat-date-range-input [rangePicker]="rangePicker">
18+
<input matStartDate placeholder="Start date">
19+
<input matEndDate placeholder="End date">
20+
</mat-date-range-input>
21+
<mat-datepicker-toggle matSuffix [for]="rangePicker"></mat-datepicker-toggle>
22+
<!-- #docregion date-range-picker-actions -->
23+
<mat-date-range-picker #rangePicker>
24+
<mat-date-range-picker-actions>
25+
<button mat-button matDateRangePickerCancel>Cancel</button>
26+
<button mat-raised-button color="primary" matDateRangePickerApply>Apply</button>
27+
</mat-date-range-picker-actions>
28+
</mat-date-range-picker>
29+
<!-- #enddocregion date-range-picker-actions -->
30+
</mat-form-field>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {Component} from '@angular/core';
2+
3+
/** @title Datepicker action buttons */
4+
@Component({
5+
selector: 'datepicker-actions-example',
6+
templateUrl: 'datepicker-actions-example.html',
7+
styleUrls: ['datepicker-actions-example.css']
8+
})
9+
export class DatepickerActionsExample {}

src/components-examples/material/datepicker/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
DateRangePickerSelectionStrategyExample
4343
} from './date-range-picker-selection-strategy/date-range-picker-selection-strategy-example';
4444
import {DatepickerHarnessExample} from './datepicker-harness/datepicker-harness-example';
45+
import {DatepickerActionsExample} from './datepicker-actions/datepicker-actions-example';
4546

4647
export {
4748
DatepickerApiExample,
@@ -66,6 +67,7 @@ export {
6667
DateRangePickerFormsExample,
6768
DateRangePickerComparisonExample,
6869
DateRangePickerSelectionStrategyExample,
70+
DatepickerActionsExample,
6971
ExampleHeader,
7072
};
7173

@@ -92,6 +94,7 @@ const EXAMPLES = [
9294
DateRangePickerFormsExample,
9395
DateRangePickerComparisonExample,
9496
DateRangePickerSelectionStrategyExample,
97+
DatepickerActionsExample,
9598
ExampleHeader,
9699
];
97100

src/dev-app/datepicker/datepicker-demo.html

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ <h2>Options</h2>
55
<mat-checkbox [(ngModel)]="yearView">Start in year view</mat-checkbox>
66
<mat-checkbox [(ngModel)]="datepickerDisabled">Disable datepicker</mat-checkbox>
77
<mat-checkbox [(ngModel)]="inputDisabled">Disable input</mat-checkbox>
8+
<mat-checkbox [(ngModel)]="showActions">Show action buttons</mat-checkbox>
89
<mat-form-field>
910
<mat-select [(ngModel)]="color" placeholder="Color">
1011
<mat-option value="primary">Primary</mat-option>
@@ -19,14 +20,24 @@ <h2>Options</h2>
1920
<input matInput [matDatepicker]="minDatePicker" [(ngModel)]="minDate"
2021
[disabled]="inputDisabled" [max]="maxDate">
2122
<mat-datepicker-toggle matSuffix [for]="minDatePicker"></mat-datepicker-toggle>
22-
<mat-datepicker #minDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></mat-datepicker>
23+
<mat-datepicker #minDatePicker [touchUi]="touch" [disabled]="datepickerDisabled">
24+
<mat-datepicker-actions *ngIf="showActions">
25+
<button mat-button matDatepickerCancel>Cancel</button>
26+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
27+
</mat-datepicker-actions>
28+
</mat-datepicker>
2329
</mat-form-field>
2430
<mat-form-field color="accent">
2531
<mat-label>Max date</mat-label>
2632
<input matInput [matDatepicker]="maxDatePicker" [(ngModel)]="maxDate"
2733
[disabled]="inputDisabled" [min]="minDate">
2834
<mat-datepicker-toggle matSuffix [for]="maxDatePicker"></mat-datepicker-toggle>
29-
<mat-datepicker #maxDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></mat-datepicker>
35+
<mat-datepicker #maxDatePicker [touchUi]="touch" [disabled]="datepickerDisabled">
36+
<mat-datepicker-actions *ngIf="showActions">
37+
<button mat-button matDatepickerCancel>Cancel</button>
38+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
39+
</mat-datepicker-actions>
40+
</mat-datepicker>
3041
</mat-form-field>
3142
</p>
3243
<p>
@@ -35,7 +46,12 @@ <h2>Options</h2>
3546
<input matInput [matDatepicker]="startAtPicker" [(ngModel)]="startAt"
3647
[disabled]="inputDisabled">
3748
<mat-datepicker-toggle matSuffix [for]="startAtPicker"></mat-datepicker-toggle>
38-
<mat-datepicker #startAtPicker [touchUi]="touch" [disabled]="datepickerDisabled"></mat-datepicker>
49+
<mat-datepicker #startAtPicker [touchUi]="touch" [disabled]="datepickerDisabled">
50+
<mat-datepicker-actions *ngIf="showActions">
51+
<button mat-button matDatepickerCancel>Cancel</button>
52+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
53+
</mat-datepicker-actions>
54+
</mat-datepicker>
3955
</mat-form-field>
4056
</p>
4157

@@ -62,6 +78,10 @@ <h2>Result</h2>
6278
[startAt]="startAt"
6379
[startView]="yearView ? 'year' : 'month'"
6480
[color]="color">
81+
<mat-datepicker-actions *ngIf="showActions">
82+
<button mat-button matDatepickerCancel>Cancel</button>
83+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
84+
</mat-datepicker-actions>
6585
</mat-datepicker>
6686
<mat-error *ngIf="resultPickerModel.hasError('matDatepickerParse')">
6787
"{{resultPickerModel.getError('matDatepickerParse').text}}" is not a valid date!
@@ -90,6 +110,10 @@ <h2>Result</h2>
90110
[disabled]="datepickerDisabled"
91111
[startAt]="startAt"
92112
[startView]="yearView ? 'year' : 'month'">
113+
<mat-datepicker-actions *ngIf="showActions">
114+
<button mat-button matDatepickerCancel>Cancel</button>
115+
<button mat-raised-button color="primary" matDatepickerApply>Apply</button>
116+
</mat-datepicker-actions>
93117
</mat-datepicker>
94118
</p>
95119

@@ -191,7 +215,12 @@ <h2>Range picker</h2>
191215
<mat-date-range-picker
192216
[touchUi]="touch"
193217
[disabled]="datepickerDisabled"
194-
#range1Picker></mat-date-range-picker>
218+
#range1Picker>
219+
<mat-date-range-picker-actions *ngIf="showActions">
220+
<button mat-button matDateRangePickerCancel>Cancel</button>
221+
<button mat-raised-button color="primary" matDateRangePickerApply>Apply</button>
222+
</mat-date-range-picker-actions>
223+
</mat-date-range-picker>
195224
</mat-form-field>
196225
<div>{{range1.value | json}}</div>
197226
</div>
@@ -216,7 +245,12 @@ <h2>Range picker</h2>
216245
[touchUi]="touch"
217246
[disabled]="datepickerDisabled"
218247
panelClass="demo-custom-range"
219-
#range2Picker></mat-date-range-picker>
248+
#range2Picker>
249+
<mat-date-range-picker-actions *ngIf="showActions">
250+
<button mat-button matDateRangePickerCancel>Cancel</button>
251+
<button mat-raised-button color="primary" matDateRangePickerApply>Apply</button>
252+
</mat-date-range-picker-actions>
253+
</mat-date-range-picker>
220254
</mat-form-field>
221255
<div>{{range2.value | json}}</div>
222256
</div>
@@ -240,7 +274,12 @@ <h2>Range picker</h2>
240274
<mat-date-range-picker
241275
[touchUi]="touch"
242276
[disabled]="datepickerDisabled"
243-
#range3Picker></mat-date-range-picker>
277+
#range3Picker>
278+
<mat-date-range-picker-actions *ngIf="showActions">
279+
<button mat-button matDateRangePickerCancel>Cancel</button>
280+
<button mat-raised-button color="primary" matDateRangePickerApply>Apply</button>
281+
</mat-date-range-picker-actions>
282+
</mat-date-range-picker>
244283
</mat-form-field>
245284
<div>{{range3.value | json}}</div>
246285
</div>
@@ -255,6 +294,11 @@ <h2>Range picker with custom selection strategy</h2>
255294
<input matEndDate placeholder="End date"/>
256295
</mat-date-range-input>
257296
<mat-datepicker-toggle [for]="range4Picker" matSuffix></mat-datepicker-toggle>
258-
<mat-date-range-picker customRangeStrategy #range4Picker></mat-date-range-picker>
297+
<mat-date-range-picker customRangeStrategy #range4Picker>
298+
<mat-date-range-picker-actions *ngIf="showActions">
299+
<button mat-button matDateRangePickerCancel>Cancel</button>
300+
<button mat-raised-button color="primary" matDateRangePickerApply>Apply</button>
301+
</mat-date-range-picker-actions>
302+
</mat-date-range-picker>
259303
</mat-form-field>
260304
</div>

src/dev-app/datepicker/datepicker-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class DatepickerDemo {
5252
lastDateInput: Date | null;
5353
lastDateChange: Date | null;
5454
color: ThemePalette;
55+
showActions = false;
5556

5657
dateCtrl = new FormControl();
5758
range1 = new FormGroup({start: new FormControl(), end: new FormControl()});

src/material/datepicker/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ ng_module(
1818
),
1919
assets = [
2020
":datepicker-content.css",
21+
":datepicker-actions.css",
2122
":datepicker-toggle.css",
2223
":date-range-input.css",
2324
":calendar-body.css",
@@ -79,6 +80,11 @@ sass_binary(
7980
deps = ["//src/material/core:core_scss_lib"],
8081
)
8182

83+
sass_binary(
84+
name = "datepicker_actions_scss",
85+
src = "datepicker-actions.scss",
86+
)
87+
8288
ng_test_library(
8389
name = "unit_test_sources",
8490
srcs = glob(
@@ -98,6 +104,7 @@ ng_test_library(
98104
"//src/material/form-field",
99105
"//src/material/input",
100106
"//src/material/testing",
107+
"@npm//@angular/common",
101108
"@npm//@angular/forms",
102109
"@npm//@angular/platform-browser",
103110
"@npm//@angular/platform-browser-dynamic",

src/material/datepicker/date-range-picker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface MatDateRangePickerInput<D> extends MatDatepickerControl<D> {
3333
providers: [
3434
MAT_RANGE_DATE_SELECTION_MODEL_PROVIDER,
3535
MAT_CALENDAR_RANGE_STRATEGY_PROVIDER,
36+
{provide: MatDatepickerBase, useExisting: MatDateRangePicker},
3637
]
3738
})
3839
export class MatDateRangePicker<D> extends MatDatepickerBase<MatDateRangePickerInput<D>,

src/material/datepicker/date-selection-model.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ export abstract class MatDateSelectionModel<S, D = ExtractDateTypeFromSelection<
8383

8484
/** Checks whether the current selection is complete. */
8585
abstract isComplete(): boolean;
86+
87+
/**
88+
* Clones the selection model.
89+
* @deprecated To be turned into an abstract method.
90+
* @breaking-change 12.0.0
91+
*/
92+
clone(): MatDateSelectionModel<S, D> {
93+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
94+
throw Error('Not implemented');
95+
}
96+
97+
return null!;
98+
}
8699
}
87100

88101
/** A selection model that contains a single date. */
@@ -112,6 +125,13 @@ export class MatSingleDateSelectionModel<D> extends MatDateSelectionModel<D | nu
112125
isComplete() {
113126
return this.selection != null;
114127
}
128+
129+
/** Clones the selection model. */
130+
clone() {
131+
const clone = new MatSingleDateSelectionModel<D>(this._adapter);
132+
clone.updateSelection(this.selection, this);
133+
return clone;
134+
}
115135
}
116136

117137
/** A selection model that contains a date range. */
@@ -168,6 +188,13 @@ export class MatRangeDateSelectionModel<D> extends MatDateSelectionModel<DateRan
168188
isComplete(): boolean {
169189
return this.selection.start != null && this.selection.end != null;
170190
}
191+
192+
/** Clones the selection model. */
193+
clone() {
194+
const clone = new MatRangeDateSelectionModel<D>(this._adapter);
195+
clone.updateSelection(this.selection, this);
196+
return clone;
197+
}
171198
}
172199

173200
/** @docs-private */
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.mat-datepicker-actions {
2+
$spacing: 8px;
3+
display: flex;
4+
justify-content: flex-end;
5+
align-items: center;
6+
padding: 0 $spacing $spacing $spacing;
7+
8+
.mat-button-base + .mat-button-base {
9+
margin-left: $spacing;
10+
11+
[dir='rtl'] & {
12+
margin-left: 0;
13+
margin-right: $spacing;
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)