From e28a458a23a7e220621c92ad4c8c4b186d44df83 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 24 Feb 2022 09:49:59 +0100 Subject: [PATCH] fix(material/autocomplete): don't block default arrow keys when using modifiers Currently we hijack all up/down arrow key events, however this interferes with keyboard shortcuts such as shift + up arrow for marking all of the text. These changes stop intercepting the arrow keys, if they're used with a modifier. These changes also fix an issue where all the mocked out key events had the `metaKey` set to `true` on some browsers. --- .../mdc-autocomplete/autocomplete.spec.ts | 14 ++++++++++++++ src/material/autocomplete/autocomplete-trigger.ts | 7 ++++--- src/material/autocomplete/autocomplete.spec.ts | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts index 18b4617393cb..3f111ab66994 100644 --- a/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts +++ b/src/material-experimental/mdc-autocomplete/autocomplete.spec.ts @@ -1470,6 +1470,20 @@ describe('MDC-based MatAutocomplete', () => { expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); })); + + it('should not prevent the default action when a modifier key is pressed', () => { + ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'].forEach(name => { + const event = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(event, name, {get: () => true}); + + fixture.componentInstance.trigger._handleKeydown(event); + fixture.detectChanges(); + + expect(event.defaultPrevented) + .withContext(`Expected autocompete not to block ${name} key`) + .toBe(false); + }); + }); }); describe('option groups', () => { diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index e5c4cc9bf150..059665bdaea7 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -390,16 +390,17 @@ export abstract class _MatAutocompleteTriggerBase _handleKeydown(event: KeyboardEvent): void { const keyCode = event.keyCode; + const hasModifier = hasModifierKey(event); // Prevent the default action on all escape key presses. This is here primarily to bring IE // in line with other browsers. By default, pressing escape on IE will cause it to revert // the input value to the one that it had on focus, however it won't dispatch any events // which means that the model value will be out of sync with the view. - if (keyCode === ESCAPE && !hasModifierKey(event)) { + if (keyCode === ESCAPE && !hasModifier) { event.preventDefault(); } - if (this.activeOption && keyCode === ENTER && this.panelOpen && !hasModifierKey(event)) { + if (this.activeOption && keyCode === ENTER && this.panelOpen && !hasModifier) { this.activeOption._selectViaInteraction(); this._resetActiveItem(); event.preventDefault(); @@ -407,7 +408,7 @@ export abstract class _MatAutocompleteTriggerBase const prevActiveItem = this.autocomplete._keyManager.activeItem; const isArrowKey = keyCode === UP_ARROW || keyCode === DOWN_ARROW; - if (this.panelOpen || keyCode === TAB) { + if (keyCode === TAB || (isArrowKey && !hasModifier && this.panelOpen)) { this.autocomplete._keyManager.onKeydown(event); } else if (isArrowKey && this._canOpen()) { this.openPanel(); diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index 6a772f8cedbd..ef3b1f562948 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -1453,6 +1453,20 @@ describe('MatAutocomplete', () => { expect(!!trigger.activeOption).withContext('Expected no active options.').toBe(false); })); + + it('should not prevent the default action when a modifier key is pressed', () => { + ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'].forEach(name => { + const event = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(event, name, {get: () => true}); + + fixture.componentInstance.trigger._handleKeydown(event); + fixture.detectChanges(); + + expect(event.defaultPrevented) + .withContext(`Expected autocompete not to block ${name} key`) + .toBe(false); + }); + }); }); describe('option groups', () => {