@@ -10,6 +10,7 @@ import {
10
10
Directive ,
11
11
ElementRef ,
12
12
Optional ,
13
+ inject ,
13
14
InjectionToken ,
14
15
Inject ,
15
16
OnInit ,
@@ -36,9 +37,12 @@ import {
36
37
MatDateFormats ,
37
38
ErrorStateMatcher ,
38
39
} from '@angular/material/core' ;
39
- import { BACKSPACE } from '@angular/cdk/keycodes' ;
40
+ import { Directionality } from '@angular/cdk/bidi' ;
41
+ import { BACKSPACE , LEFT_ARROW , RIGHT_ARROW } from '@angular/cdk/keycodes' ;
40
42
import { MatDatepickerInputBase , DateFilterFn } from './datepicker-input-base' ;
41
43
import { DateRange , DateSelectionModelChange } from './date-selection-model' ;
44
+ import { Subject } from 'rxjs' ;
45
+ import { filter , take } from 'rxjs/operators' ;
42
46
43
47
/** Parent component that should be wrapped around `MatStartDate` and `MatEndDate`. */
44
48
export interface MatDateRangeInputParent < D > {
@@ -86,17 +90,20 @@ abstract class MatDateRangeInputPartBase<D>
86
90
protected abstract override _assignValueToModel ( value : D | null ) : void ;
87
91
protected abstract override _getValueFromModel ( modelValue : DateRange < D > ) : D | null ;
88
92
93
+ protected readonly _dir = inject ( Directionality , InjectFlags . Optional ) ;
94
+ private readonly _onKeyupSubject = new Subject < KeyboardEvent > ( ) ;
95
+
89
96
constructor (
90
97
@Inject ( MAT_DATE_RANGE_INPUT_PARENT ) public _rangeInput : MatDateRangeInputParent < D > ,
91
- elementRef : ElementRef < HTMLInputElement > ,
98
+ public override _elementRef : ElementRef < HTMLInputElement > ,
92
99
public _defaultErrorStateMatcher : ErrorStateMatcher ,
93
100
private _injector : Injector ,
94
101
@Optional ( ) public _parentForm : NgForm ,
95
102
@Optional ( ) public _parentFormGroup : FormGroupDirective ,
96
103
@Optional ( ) dateAdapter : DateAdapter < D > ,
97
104
@Optional ( ) @Inject ( MAT_DATE_FORMATS ) dateFormats : MatDateFormats ,
98
105
) {
99
- super ( elementRef , dateAdapter , dateFormats ) ;
106
+ super ( _elementRef , dateAdapter , dateFormats ) ;
100
107
}
101
108
102
109
ngOnInit ( ) {
@@ -181,6 +188,26 @@ abstract class MatDateRangeInputPartBase<D>
181
188
) as MatDateRangeInputPartBase < D > | undefined ;
182
189
opposite ?. _validatorOnChange ( ) ;
183
190
}
191
+
192
+ protected _onKeyup ( event : KeyboardEvent ) {
193
+ this . _onKeyupSubject . next ( event ) ;
194
+ }
195
+
196
+ protected _moveFocusOnKeyup (
197
+ target : MatDateRangeInputPartBase < D > ,
198
+ downEvent : KeyboardEvent ,
199
+ cursorPosition : number ,
200
+ ) {
201
+ this . _onKeyupSubject
202
+ . pipe (
203
+ filter ( upEvent => upEvent . keyCode === downEvent . keyCode ) ,
204
+ take ( 1 ) ,
205
+ )
206
+ . subscribe ( ( ) => {
207
+ target . _elementRef . nativeElement . setSelectionRange ( cursorPosition , cursorPosition ) ;
208
+ target . focus ( ) ;
209
+ } ) ;
210
+ }
184
211
}
185
212
186
213
const _MatDateRangeInputBase = mixinErrorState ( MatDateRangeInputPartBase ) ;
@@ -194,6 +221,7 @@ const _MatDateRangeInputBase = mixinErrorState(MatDateRangeInputPartBase);
194
221
'(input)' : '_onInput($event.target.value)' ,
195
222
'(change)' : '_onChange()' ,
196
223
'(keydown)' : '_onKeydown($event)' ,
224
+ '(keyup)' : '_onKeyup($event)' ,
197
225
'[attr.id]' : '_rangeInput.id' ,
198
226
'[attr.aria-haspopup]' : '_rangeInput.rangePicker ? "dialog" : null' ,
199
227
'[attr.aria-owns]' : '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null' ,
@@ -284,6 +312,24 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
284
312
const value = element . value ;
285
313
return value . length > 0 ? value : element . placeholder ;
286
314
}
315
+
316
+ override _onKeydown ( event : KeyboardEvent ) {
317
+ const endInput = this . _rangeInput . _endInput ;
318
+ const element = this . _elementRef . nativeElement ;
319
+ const isLtr = this . _dir ?. value !== 'rtl' ;
320
+
321
+ // If the user hits RIGHT (LTR) when at the end of the input (and no
322
+ // selection), move the cursor to the start of the end input.
323
+ if (
324
+ ( ( event . keyCode === RIGHT_ARROW && isLtr ) || ( event . keyCode === LEFT_ARROW && ! isLtr ) ) &&
325
+ element . selectionStart === element . value . length &&
326
+ element . selectionEnd === element . value . length
327
+ ) {
328
+ this . _moveFocusOnKeyup ( endInput , event , 0 ) ;
329
+ }
330
+
331
+ super . _onKeydown ( event ) ;
332
+ }
287
333
}
288
334
289
335
/** Input for entering the end date in a `mat-date-range-input`. */
@@ -295,6 +341,7 @@ export class MatStartDate<D> extends _MatDateRangeInputBase<D> implements CanUpd
295
341
'(input)' : '_onInput($event.target.value)' ,
296
342
'(change)' : '_onChange()' ,
297
343
'(keydown)' : '_onKeydown($event)' ,
344
+ '(keyup)' : '_onKeyup($event)' ,
298
345
'[attr.aria-haspopup]' : '_rangeInput.rangePicker ? "dialog" : null' ,
299
346
'[attr.aria-owns]' : '(_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null' ,
300
347
'[attr.min]' : '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null' ,
@@ -370,9 +417,23 @@ export class MatEndDate<D> extends _MatDateRangeInputBase<D> implements CanUpdat
370
417
}
371
418
372
419
override _onKeydown ( event : KeyboardEvent ) {
420
+ const startInput = this . _rangeInput . _startInput ;
421
+ const element = this . _elementRef . nativeElement ;
422
+ const isLtr = this . _dir ?. value !== 'rtl' ;
423
+
373
424
// If the user is pressing backspace on an empty end input, move focus back to the start.
374
- if ( event . keyCode === BACKSPACE && ! this . _elementRef . nativeElement . value ) {
375
- this . _rangeInput . _startInput . focus ( ) ;
425
+ if ( event . keyCode === BACKSPACE && ! element . value ) {
426
+ startInput . focus ( ) ;
427
+ }
428
+
429
+ // If the user hits LEFT (LTR) when at the start of the input (and no
430
+ // selection), move the cursor to the end of the start input.
431
+ if (
432
+ ( ( event . keyCode === LEFT_ARROW && isLtr ) || ( event . keyCode === RIGHT_ARROW && ! isLtr ) ) &&
433
+ element . selectionStart === 0 &&
434
+ element . selectionEnd === 0
435
+ ) {
436
+ this . _moveFocusOnKeyup ( startInput , event , startInput . _elementRef . nativeElement . value . length ) ;
376
437
}
377
438
378
439
super . _onKeydown ( event ) ;
0 commit comments