Skip to content

Commit 54f36a3

Browse files
authored
fix(material/datepicker): don't handle escape key presses with modifier (#20713)
Aligns the datepicker's escape key handling with the rest of the components where they aren't being handled if the user is pressing a modifier key.
1 parent 407398f commit 54f36a3

File tree

4 files changed

+62
-4
lines changed

4 files changed

+62
-4
lines changed

src/material/datepicker/datepicker-base.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Directionality} from '@angular/cdk/bidi';
1010
import {BooleanInput, coerceBooleanProperty, coerceStringArray} from '@angular/cdk/coercion';
11-
import {ESCAPE, UP_ARROW} from '@angular/cdk/keycodes';
11+
import {ESCAPE, hasModifierKey, UP_ARROW} from '@angular/cdk/keycodes';
1212
import {
1313
Overlay,
1414
OverlayConfig,
@@ -607,8 +607,8 @@ export abstract class MatDatepickerBase<C extends MatDatepickerControl<D>, S,
607607
this._popupRef.detachments(),
608608
this._popupRef.keydownEvents().pipe(filter(event => {
609609
// Closing on alt + up is only valid when there's an input associated with the datepicker.
610-
return event.keyCode === ESCAPE ||
611-
(this._datepickerInput && event.altKey && event.keyCode === UP_ARROW);
610+
return (event.keyCode === ESCAPE && !hasModifierKey(event)) || (this._datepickerInput &&
611+
hasModifierKey(event, 'altKey') && event.keyCode === UP_ARROW);
612612
}))
613613
).subscribe(event => {
614614
if (event) {

src/material/datepicker/datepicker.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,23 @@ describe('MatDatepicker', () => {
223223
expect(event.defaultPrevented).toBe(true);
224224
}));
225225

226+
it('should not close the popup when pressing ESCAPE with a modifier key', fakeAsync(() => {
227+
testComponent.datepicker.open();
228+
fixture.detectChanges();
229+
230+
expect(testComponent.datepicker.opened).toBe(true, 'Expected datepicker to be open.');
231+
232+
const event = dispatchKeyboardEvent(document.body, 'keydown', ESCAPE, undefined, {
233+
alt: true
234+
});
235+
fixture.detectChanges();
236+
flush();
237+
238+
expect(testComponent.datepicker.opened).toBe(true, 'Expected datepicker to stay open.');
239+
expect(event.defaultPrevented).toBe(false);
240+
}));
241+
242+
226243
it('should set the proper role on the popup', fakeAsync(() => {
227244
testComponent.datepicker.open();
228245
fixture.detectChanges();

src/material/datepicker/month-view.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,46 @@ describe('MatMonthView', () => {
333333
expect(testComponent.selected).toBeFalsy();
334334
});
335335

336+
it('should not cancel the current range selection when pressing escape with a modifier key',
337+
() => {
338+
const cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
339+
testComponent.selected = new DateRange(new Date(2017, JAN, 10), null);
340+
fixture.detectChanges();
341+
dispatchMouseEvent(cellEls[15], 'mouseenter');
342+
fixture.detectChanges();
343+
344+
const rangeStarts =
345+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length;
346+
const rangeMids =
347+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length;
348+
const rangeEnds =
349+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length;
350+
351+
// Note that here we only care that _some_ kind of range is rendered. There are
352+
// plenty of tests in the calendar body which assert that everything is correct.
353+
expect(rangeStarts).toBeGreaterThan(0);
354+
expect(rangeMids).toBeGreaterThan(0);
355+
expect(rangeEnds).toBeGreaterThan(0);
356+
357+
const event = createKeyboardEvent('keydown', ESCAPE, 'Escape', {alt: true});
358+
spyOn(event, 'stopPropagation');
359+
dispatchEvent(calendarBodyEl, event);
360+
fixture.detectChanges();
361+
362+
expect(
363+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-start').length
364+
).toBe(rangeStarts);
365+
expect(
366+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-in-preview').length
367+
).toBe(rangeMids);
368+
expect(
369+
monthViewNativeElement.querySelectorAll('.mat-calendar-body-preview-end').length
370+
).toBe(rangeEnds);
371+
expect(event.stopPropagation).not.toHaveBeenCalled();
372+
expect(event.defaultPrevented).toBe(false);
373+
expect(testComponent.selected).toBeTruthy();
374+
});
375+
336376
it('should not clear the range when pressing escape while there is no preview', () => {
337377
const getRangeElements = () => monthViewNativeElement.querySelectorAll([
338378
'.mat-calendar-body-range-start',

src/material/datepicker/month-view.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
UP_ARROW,
1919
SPACE,
2020
ESCAPE,
21+
hasModifierKey,
2122
} from '@angular/cdk/keycodes';
2223
import {
2324
AfterContentInit,
@@ -290,7 +291,7 @@ export class MatMonthView<D> implements AfterContentInit, OnChanges, OnDestroy {
290291
return;
291292
case ESCAPE:
292293
// Abort the current range selection if the user presses escape mid-selection.
293-
if (this._previewEnd != null) {
294+
if (this._previewEnd != null && !hasModifierKey(event)) {
294295
this._previewStart = this._previewEnd = null;
295296
this.selectedChange.emit(null);
296297
this._userSelection.emit({value: null, event});

0 commit comments

Comments
 (0)