From 45211aea588e3f401c3e9f76b920181960576253 Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Mon, 30 Mar 2026 12:56:27 +0300 Subject: [PATCH] fix(combo/simple-combo): prevent Escape from closing parent container on clear --- .../combo/src/combo/combo.component.spec.ts | 34 +++++++++++++++++++ .../combo/src/combo/combo.component.ts | 1 + .../simple-combo.component.spec.ts | 32 +++++++++++++++++ .../simple-combo/simple-combo.component.ts | 1 + 4 files changed, 68 insertions(+) diff --git a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts index b8cdd320ea4..5aef5e69f8e 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts @@ -1942,6 +1942,40 @@ describe('igxCombo', () => { expect(document.activeElement).toEqual(combo.comboInput.nativeElement); expect(combo.selection.length).toEqual(0); })); + it('should stop Escape keydown event propagation when the dropdown is open', fakeAsync(() => { + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }); + spyOn(escapeEvent, 'stopPropagation'); + + combo.comboInput.nativeElement.focus(); + fixture.detectChanges(); + + combo.toggle(); + fixture.detectChanges(); + expect(combo.collapsed).toBeFalsy(); + + combo.onEscape(escapeEvent); + tick(); + fixture.detectChanges(); + + expect(escapeEvent.stopPropagation).toHaveBeenCalled(); + })); + it('should stop Escape key propagation when the combo is collapsed and has a selection', fakeAsync(() => { + combo.comboInput.nativeElement.focus(); + fixture.detectChanges(); + + combo.select([combo.data[0][combo.valueKey]]); + expect(combo.selection.length).toEqual(1); + fixture.detectChanges(); + + const keyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + const stopPropSpy = spyOn(keyEvent, 'stopPropagation'); + + combo.onEscape(keyEvent); + tick(); + fixture.detectChanges(); + + expect(stopPropSpy).toHaveBeenCalledTimes(1); + })); it('should close the combo and preserve the focus when Escape key is pressed', fakeAsync(() => { combo.comboInput.nativeElement.focus(); fixture.detectChanges(); diff --git a/projects/igniteui-angular/combo/src/combo/combo.component.ts b/projects/igniteui-angular/combo/src/combo/combo.component.ts index d2a09546a61..359ace7083c 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.ts @@ -199,6 +199,7 @@ export class IgxComboComponent extends IgxComboBaseDirective implements AfterVie @HostListener('keydown.Escape', ['$event']) public onEscape(event: Event) { + event.stopPropagation(); if (this.collapsed) { this.deselectAllItems(true, event); } diff --git a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts index 2470de89631..2935001cf78 100644 --- a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts @@ -1220,6 +1220,38 @@ describe('IgxSimpleCombo', () => { expect(combo.selection).not.toBeDefined(); })); + it('should stop Escape keydown event propagation when the dropdown is open', fakeAsync(() => { + const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }); + spyOn(escapeEvent, 'stopPropagation'); + + combo.open(); + fixture.detectChanges(); + expect(combo.collapsed).toBeFalsy(); + + combo.handleKeyDown(escapeEvent); + tick(); + fixture.detectChanges(); + + expect(escapeEvent.stopPropagation).toHaveBeenCalled(); + })); + + it('should stop Escape key propagation when the combo is collapsed and has a selection', fakeAsync(() => { + combo.comboInput.nativeElement.focus(); + fixture.detectChanges(); + + combo.select(combo.data[2][combo.valueKey]); + fixture.detectChanges(); + expect(combo.selection).toBeDefined(); + + const keyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + const stopPropSpy = spyOn(keyEvent, 'stopPropagation'); + + combo.handleKeyDown(keyEvent); + fixture.detectChanges(); + + expect(stopPropSpy).toHaveBeenCalledTimes(1); + })); + it('should clear the selection on tab/blur if the search text does not match any value', () => { // allowCustomValues does not matter combo.select(combo.data[2][combo.valueKey]); diff --git a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.ts b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.ts index e787f9f6c7d..69ba54f2e50 100644 --- a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.ts +++ b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.ts @@ -372,6 +372,7 @@ export class IgxSimpleComboComponent extends IgxComboBaseDirective implements Co } } if (event.key === this.platformUtil.KEYMAP.ESCAPE) { + event.stopPropagation(); if (this.collapsed) { const oldSelection = this.selection; this.clearSelection(true);