Skip to content

Commit

Permalink
fix(material/datepicker): mark date input as touched when calendar is…
Browse files Browse the repository at this point in the history
… closed (#21646)

Currently we mark the date input model as touched when it is blurred or the user types
something in, however opening and closing the calendar is also an indicator that the user
has interacted with it.

These changes add some logic to mark the inputs as touched when the calendar is closed as well.

Fixes #21643.

(cherry picked from commit b559786)
  • Loading branch information
crisbeto authored and andrewseguin committed Jan 25, 2021
1 parent 958ecb2 commit 75367e7
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 3 deletions.
31 changes: 31 additions & 0 deletions src/material/datepicker/date-range-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,36 @@ describe('MatDateRangeInput', () => {
expect(endModel.dirty).toBe(true);
}));

it('should mark both inputs as touched when the range picker is closed', fakeAsync(() => {
const fixture = createComponent(RangePickerNgModel);
fixture.detectChanges();
flush();
const {startModel, endModel, rangePicker} = fixture.componentInstance;

expect(startModel.dirty).toBe(false);
expect(startModel.touched).toBe(false);
expect(endModel.dirty).toBe(false);
expect(endModel.touched).toBe(false);

rangePicker.open();
fixture.detectChanges();
flush();

expect(startModel.dirty).toBe(false);
expect(startModel.touched).toBe(false);
expect(endModel.dirty).toBe(false);
expect(endModel.touched).toBe(false);

rangePicker.close();
fixture.detectChanges();
flush();

expect(startModel.dirty).toBe(false);
expect(startModel.touched).toBe(true);
expect(endModel.dirty).toBe(false);
expect(endModel.touched).toBe(true);
}));

it('should move focus to the start input when pressing backspace on an empty end input', () => {
const fixture = createComponent(StandardRangePicker);
fixture.detectChanges();
Expand Down Expand Up @@ -928,6 +958,7 @@ class RangePickerNgModel {
@ViewChild(MatEndDate, {read: NgModel}) endModel: NgModel;
@ViewChild(MatStartDate, {read: ElementRef}) startInput: ElementRef<HTMLInputElement>;
@ViewChild(MatEndDate, {read: ElementRef}) endInput: ElementRef<HTMLInputElement>;
@ViewChild(MatDateRangePicker) rangePicker: MatDateRangePicker<Date>;
start: Date | null = null;
end: Date | null = null;
}
Expand Down
10 changes: 9 additions & 1 deletion src/material/datepicker/date-range-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import {MatFormFieldControl, MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field';
import {ThemePalette, DateAdapter} from '@angular/material/core';
import {NgControl, ControlContainer} from '@angular/forms';
import {Subject, merge} from 'rxjs';
import {Subject, merge, Subscription} from 'rxjs';
import {coerceBooleanProperty, BooleanInput} from '@angular/cdk/coercion';
import {
MatStartDate,
Expand Down Expand Up @@ -68,6 +68,8 @@ let nextUniqueId = 0;
export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
MatDatepickerControl<D>, MatDateRangeInputParent<D>, MatDateRangePickerInput<D>,
AfterContentInit, OnChanges, OnDestroy {
private _closedSubscription = Subscription.EMPTY;

/** Current value of the range input. */
get value() {
return this._model ? this._model.selection : null;
Expand Down Expand Up @@ -105,6 +107,11 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
if (rangePicker) {
this._model = rangePicker.registerInput(this);
this._rangePicker = rangePicker;
this._closedSubscription.unsubscribe();
this._closedSubscription = rangePicker.closedStream.subscribe(() => {
this._startInput?._onTouched();
this._endInput?._onTouched();
});
this._registerModel(this._model!);
}
}
Expand Down Expand Up @@ -291,6 +298,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
}

ngOnDestroy() {
this._closedSubscription.unsubscribe();
this.stateChanges.complete();
}

Expand Down
12 changes: 11 additions & 1 deletion src/material/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
forwardRef,
Inject,
Input,
OnDestroy,
Optional,
} from '@angular/core';
import {
Expand All @@ -28,6 +29,7 @@ import {
} from '@angular/material/core';
import {MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field';
import {MAT_INPUT_VALUE_ACCESSOR} from '@angular/material/input';
import {Subscription} from 'rxjs';
import {MatDatepickerInputBase, DateFilterFn} from './datepicker-input-base';
import {MatDatepickerControl, MatDatepickerPanel} from './datepicker-base';
import {DateSelectionModelChange} from './date-selection-model';
Expand Down Expand Up @@ -72,12 +74,15 @@ export const MAT_DATEPICKER_VALIDATORS: any = {
exportAs: 'matDatepickerInput',
})
export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D>
implements MatDatepickerControl<D | null> {
implements MatDatepickerControl<D | null>, OnDestroy {
private _closedSubscription = Subscription.EMPTY;

/** The datepicker that this input is associated with. */
@Input()
set matDatepicker(datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>) {
if (datepicker) {
this._datepicker = datepicker;
this._closedSubscription = datepicker.closedStream.subscribe(() => this._onTouched());
this._registerModel(datepicker.registerInput(this));
}
}
Expand Down Expand Up @@ -152,6 +157,11 @@ export class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D>
return this.value;
}

ngOnDestroy() {
super.ngOnDestroy();
this._closedSubscription.unsubscribe();
}

/** Opens the associated datepicker. */
protected _openPopup(): void {
if (this._datepicker) {
Expand Down
20 changes: 20 additions & 0 deletions src/material/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,26 @@ describe('MatDatepicker', () => {
expect(inputEl.classList).toContain('ng-touched');
});

it('should mark input as touched when the datepicker is closed', fakeAsync(() => {
let inputEl = fixture.debugElement.query(By.css('input'))!.nativeElement;

expect(inputEl.classList).toContain('ng-untouched');

fixture.componentInstance.datepicker.open();
fixture.detectChanges();
flush();
fixture.detectChanges();

expect(inputEl.classList).toContain('ng-untouched');

fixture.componentInstance.datepicker.close();
fixture.detectChanges();
flush();
fixture.detectChanges();

expect(inputEl.classList).toContain('ng-touched');
}));

it('should reformat the input value on blur', () => {
if (SUPPORTS_INTL) {
// Skip this test if the internationalization API is not supported in the current
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/datepicker.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export declare class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>
static ɵfac: i0.ɵɵFactoryDef<MatDatepickerContent<any, any>, [null, null, null, null, { optional: true; }, null]>;
}

export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D> implements MatDatepickerControl<D | null> {
export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D | null, D> implements MatDatepickerControl<D | null>, OnDestroy {
_datepicker: MatDatepickerPanel<MatDatepickerControl<D>, D | null, D>;
protected _validator: ValidatorFn | null;
get dateFilter(): DateFilterFn<D | null>;
Expand All @@ -257,6 +257,7 @@ export declare class MatDatepickerInput<D> extends MatDatepickerInputBase<D | nu
getConnectedOverlayOrigin(): ElementRef;
getStartValue(): D | null;
getThemePalette(): ThemePalette;
ngOnDestroy(): void;
static ngAcceptInputType_value: any;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatDatepickerInput<any>, "input[matDatepicker]", ["matDatepickerInput"], { "matDatepicker": "matDatepicker"; "min": "min"; "max": "max"; "dateFilter": "matDatepickerFilter"; }, {}, never>;
static ɵfac: i0.ɵɵFactoryDef<MatDatepickerInput<any>, [null, { optional: true; }, { optional: true; }, { optional: true; }]>;
Expand Down

0 comments on commit 75367e7

Please sign in to comment.