6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { Directive , ElementRef , Optional , Self , InjectionToken , Inject } from '@angular/core' ;
9
+ import {
10
+ Directive ,
11
+ ElementRef ,
12
+ Optional ,
13
+ InjectionToken ,
14
+ Inject ,
15
+ OnInit ,
16
+ Injector ,
17
+ InjectFlags ,
18
+ DoCheck ,
19
+ } from '@angular/core' ;
10
20
import {
11
21
NG_VALUE_ACCESSOR ,
12
22
NG_VALIDATORS ,
13
- ControlValueAccessor ,
14
- Validator ,
15
- AbstractControl ,
16
- ValidationErrors ,
17
23
NgForm ,
18
24
FormGroupDirective ,
19
25
NgControl ,
26
+ ValidatorFn ,
27
+ Validators ,
20
28
} from '@angular/forms' ;
21
29
import {
22
30
CanUpdateErrorState ,
23
- CanDisable ,
24
- ErrorStateMatcher ,
25
- CanDisableCtor ,
26
31
CanUpdateErrorStateCtor ,
27
32
mixinErrorState ,
28
- mixinDisabled ,
33
+ MAT_DATE_FORMATS ,
34
+ DateAdapter ,
35
+ MatDateFormats ,
36
+ ErrorStateMatcher ,
29
37
} from '@angular/material/core' ;
30
38
import { BooleanInput } from '@angular/cdk/coercion' ;
39
+ import { MatDatepickerInputBase } from './datepicker-input-base' ;
31
40
32
- /** Parent component that should be wrapped around `MatStartDate` and `MatEndDate`. */
41
+ /** Parent component that should be wrapped around `MatStartDate` and `MatEndDate`. */
33
42
export interface MatDateRangeInputParent {
34
43
id : string ;
35
44
_ariaDescribedBy : string | null ;
36
45
_ariaLabelledBy : string | null ;
37
46
_handleChildValueChange : ( ) => void ;
47
+ _openDatepicker : ( ) => void ;
38
48
}
39
49
40
50
/**
@@ -44,72 +54,56 @@ export interface MatDateRangeInputParent {
44
54
export const MAT_DATE_RANGE_INPUT_PARENT =
45
55
new InjectionToken < MatDateRangeInputParent > ( 'MAT_DATE_RANGE_INPUT_PARENT' ) ;
46
56
47
- // Boilerplate for applying mixins to MatDateRangeInput.
48
- /** @docs -private */
49
- class MatDateRangeInputPartMixinBase {
50
- constructor ( public _defaultErrorStateMatcher : ErrorStateMatcher ,
51
- public _parentForm : NgForm ,
52
- public _parentFormGroup : FormGroupDirective ,
53
- /** @docs -private */
54
- public ngControl : NgControl ) { }
55
- }
56
- const _MatDateRangeInputMixinBase : CanDisableCtor &
57
- CanUpdateErrorStateCtor & typeof MatDateRangeInputPartMixinBase =
58
- mixinErrorState ( mixinDisabled ( MatDateRangeInputPartMixinBase ) ) ;
59
-
60
57
/**
61
58
* Base class for the individual inputs that can be projected inside a `mat-date-range-input`.
62
59
*/
63
60
@Directive ( )
64
- abstract class MatDateRangeInputPartBase < D > extends _MatDateRangeInputMixinBase implements
65
- ControlValueAccessor , Validator , CanUpdateErrorState , CanDisable , CanUpdateErrorState {
66
-
67
- private _onTouched = ( ) => { } ;
68
-
69
- constructor (
70
- protected _elementRef : ElementRef < HTMLInputElement > ,
71
- @Inject ( MAT_DATE_RANGE_INPUT_PARENT ) public _rangeInput : MatDateRangeInputParent ,
72
- defaultErrorStateMatcher : ErrorStateMatcher ,
73
- @Optional ( ) parentForm : NgForm ,
74
- @Optional ( ) parentFormGroup : FormGroupDirective ,
75
- @Optional ( ) @Self ( ) ngControl : NgControl ) {
76
- super ( defaultErrorStateMatcher , parentForm , parentFormGroup , ngControl ) ;
77
- }
61
+ class MatDateRangeInputPartBase < D > extends MatDatepickerInputBase < D > implements OnInit , DoCheck {
62
+ protected _validator : ValidatorFn | null ;
78
63
79
64
/** @docs -private */
80
- writeValue ( _value : D | null ) : void {
81
- // TODO(crisbeto): implement
82
- }
65
+ ngControl : NgControl ;
83
66
84
67
/** @docs -private */
85
- registerOnChange ( _fn : ( ) => void ) : void {
86
- // TODO(crisbeto): implement
87
- }
68
+ updateErrorState : ( ) => void ;
88
69
89
- /** @docs -private */
90
- registerOnTouched ( fn : ( ) => void ) : void {
91
- this . _onTouched = fn ;
92
- }
93
-
94
- /** @docs -private */
95
- setDisabledState ( isDisabled : boolean ) : void {
96
- this . disabled = isDisabled ;
70
+ constructor (
71
+ @Inject ( MAT_DATE_RANGE_INPUT_PARENT ) public _rangeInput : MatDateRangeInputParent ,
72
+ elementRef : ElementRef < HTMLInputElement > ,
73
+ public _defaultErrorStateMatcher : ErrorStateMatcher ,
74
+ private _injector : Injector ,
75
+ @Optional ( ) public _parentForm : NgForm ,
76
+ @Optional ( ) public _parentFormGroup : FormGroupDirective ,
77
+ @Optional ( ) dateAdapter : DateAdapter < D > ,
78
+ @Optional ( ) @Inject ( MAT_DATE_FORMATS ) dateFormats : MatDateFormats ) {
79
+ super ( elementRef , dateAdapter , dateFormats ) ;
97
80
}
98
81
99
- /** @docs -private */
100
- validate ( _control : AbstractControl ) : ValidationErrors | null {
101
- // TODO(crisbeto): implement
102
- return null ;
82
+ ngOnInit ( ) {
83
+ // We need the date input to provide itself as a `ControlValueAccessor` and a `Validator`, while
84
+ // injecting its `NgControl` so that the error state is handled correctly. This introduces a
85
+ // circular dependency, because both `ControlValueAccessor` and `Validator` depend on the input
86
+ // itself. Usually we can work around it for the CVA, but there's no API to do it for the
87
+ // validator. We work around it here by injecting the `NgControl` in `ngOnInit`, after
88
+ // everything has been resolved.
89
+ const ngControl = this . _injector . get ( NgControl , null , InjectFlags . Self ) ;
90
+
91
+ if ( ngControl ) {
92
+ this . ngControl = ngControl ;
93
+ }
103
94
}
104
95
105
- /** @docs -private */
106
- registerOnValidatorChange ( _fn : ( ) => void ) : void {
107
- // TODO(crisbeto): implement
96
+ ngDoCheck ( ) {
97
+ if ( this . ngControl ) {
98
+ // We need to re-evaluate this on every change detection cycle, because there are some
99
+ // error triggers that we can't subscribe to (e.g. parent form submissions). This means
100
+ // that whatever logic is in here has to be super lean or we risk destroying the performance.
101
+ this . updateErrorState ( ) ;
102
+ }
108
103
}
109
104
110
105
/** Gets whether the input is empty. */
111
106
isEmpty ( ) : boolean {
112
- // TODO(crisbeto): should look at the CVA value.
113
107
return this . _elementRef . nativeElement . value . length === 0 ;
114
108
}
115
109
@@ -118,34 +112,55 @@ abstract class MatDateRangeInputPartBase<D> extends _MatDateRangeInputMixinBase
118
112
this . _elementRef . nativeElement . focus ( ) ;
119
113
}
120
114
121
- /** Handles blur events on the input. */
122
- _handleBlur ( ) : void {
123
- this . _onTouched ( ) ;
115
+ /** Handles `input` events on the input element. */
116
+ _onInput ( value : string ) {
117
+ super . _onInput ( value ) ;
118
+ this . _rangeInput . _handleChildValueChange ( ) ;
124
119
}
125
120
126
- static ngAcceptInputType_disabled : BooleanInput ;
121
+ /** Opens the datepicker associated with the input. */
122
+ protected _openPopup ( ) : void {
123
+ this . _rangeInput . _openDatepicker ( ) ;
124
+ }
125
+
126
+ protected _assignModelValue ( _model : D | null ) : void {
127
+ // TODO(crisbeto): implement
128
+ }
127
129
}
128
130
131
+ const _MatDateRangeInputBase :
132
+ CanUpdateErrorStateCtor & typeof MatDateRangeInputPartBase =
133
+ mixinErrorState ( MatDateRangeInputPartBase ) ;
129
134
130
135
/** Input for entering the start date in a `mat-date-range-input`. */
131
136
@Directive ( {
132
137
selector : 'input[matStartDate]' ,
133
- inputs : [ 'disabled' ] ,
134
138
host : {
135
- '[id]' : '_rangeInput.id' ,
139
+ 'class' : 'mat-date-range-input-inner' ,
140
+ '[disabled]' : 'disabled' ,
141
+ '(input)' : '_onInput($event.target.value)' ,
142
+ '(change)' : '_onChange()' ,
143
+ '(keydown)' : '_onKeydown($event)' ,
136
144
'[attr.aria-labelledby]' : '_rangeInput._ariaLabelledBy' ,
137
145
'[attr.aria-describedby]' : '_rangeInput._ariaDescribedBy' ,
138
- 'class ' : 'mat-date-range-input-inner ' ,
146
+ '(blur) ' : '_onBlur() ' ,
139
147
'type' : 'text' ,
140
- '(blur)' : '_handleBlur()' ,
141
- '(input)' : '_rangeInput._handleChildValueChange()'
148
+
149
+ // TODO(crisbeto): to be added once the datepicker is implemented
150
+ // '[attr.aria-haspopup]': '_datepicker ? "dialog" : null',
151
+ // '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
152
+ // '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',
153
+ // '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',
142
154
} ,
143
155
providers : [
144
156
{ provide : NG_VALUE_ACCESSOR , useExisting : MatStartDate , multi : true } ,
145
157
{ provide : NG_VALIDATORS , useExisting : MatStartDate , multi : true }
146
158
]
147
159
} )
148
- export class MatStartDate < D > extends MatDateRangeInputPartBase < D > {
160
+ export class MatStartDate < D > extends _MatDateRangeInputBase < D > implements CanUpdateErrorState {
161
+ // TODO(crisbeto): start-range-specific validators should go here.
162
+ protected _validator = Validators . compose ( [ this . _parseValidator ] ) ;
163
+
149
164
/** Gets the value that should be used when mirroring the input's size. */
150
165
getMirrorValue ( ) : string {
151
166
const element = this . _elementRef . nativeElement ;
@@ -160,19 +175,31 @@ export class MatStartDate<D> extends MatDateRangeInputPartBase<D> {
160
175
/** Input for entering the end date in a `mat-date-range-input`. */
161
176
@Directive ( {
162
177
selector : 'input[matEndDate]' ,
163
- inputs : [ 'disabled' ] ,
164
178
host : {
165
179
'class' : 'mat-date-range-input-inner' ,
180
+ '[disabled]' : 'disabled' ,
181
+ '(input)' : '_onInput($event.target.value)' ,
182
+ '(change)' : '_onChange()' ,
183
+ '(keydown)' : '_onKeydown($event)' ,
166
184
'[attr.aria-labelledby]' : '_rangeInput._ariaLabelledBy' ,
167
185
'[attr.aria-describedby]' : '_rangeInput._ariaDescribedBy' ,
168
- '(blur)' : '_handleBlur ' ,
186
+ '(blur)' : '_onBlur() ' ,
169
187
'type' : 'text' ,
188
+
189
+ // TODO(crisbeto): to be added once the datepicker is implemented
190
+ // '[attr.aria-haspopup]': '_datepicker ? "dialog" : null',
191
+ // '[attr.aria-owns]': '(_datepicker?.opened && _datepicker.id) || null',
192
+ // '[attr.min]': 'min ? _dateAdapter.toIso8601(min) : null',
193
+ // '[attr.max]': 'max ? _dateAdapter.toIso8601(max) : null',
170
194
} ,
171
195
providers : [
172
196
{ provide : NG_VALUE_ACCESSOR , useExisting : MatEndDate , multi : true } ,
173
197
{ provide : NG_VALIDATORS , useExisting : MatEndDate , multi : true }
174
198
]
175
199
} )
176
- export class MatEndDate < D > extends MatDateRangeInputPartBase < D > {
200
+ export class MatEndDate < D > extends _MatDateRangeInputBase < D > implements CanUpdateErrorState {
201
+ // TODO(crisbeto): end-range-specific validators should go here.
202
+ protected _validator = Validators . compose ( [ this . _parseValidator ] ) ;
203
+
177
204
static ngAcceptInputType_disabled : BooleanInput ;
178
205
}
0 commit comments