From 52f91341904f30e19e6f6ecc5b69134aa6ec983d Mon Sep 17 00:00:00 2001 From: georgianastasov Date: Mon, 30 Mar 2026 13:16:35 +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 f226828ec82..072bd5413f5 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts @@ -1866,6 +1866,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 495dfaedd46..dc5e99240ca 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.ts @@ -187,6 +187,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 2c29c9a95d2..5063ad2b8bb 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 @@ -1134,6 +1134,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 aa73c06b7c7..868bf6fd09f 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 @@ -350,6 +350,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);