diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index d223f8e7e39..23afbbea5c5 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -1218,6 +1218,9 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement /** @hidden @internal */ public handleClosed() { this.closed.emit({ owner: this }); + if(this.comboInput.nativeElement !== document.activeElement){ + this.validateComboState(); + } } /** @hidden @internal */ @@ -1257,14 +1260,15 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement public onBlur() { if (this.collapsed) { this._onTouchedCallback(); - if (this.ngControl && this.ngControl.invalid) { - this.valid = IgxInputState.INVALID; - } else { - this.valid = IgxInputState.INITIAL; - } + this.validateComboState(); } } + /** @hidden @internal */ + public onFocus(): void { + this._onTouchedCallback(); + } + /** @hidden @internal */ public setActiveDescendant(): void { this.activeDescendant = this.dropdown.focusedItem?.id || ''; @@ -1289,6 +1293,14 @@ export abstract class IgxComboBaseDirective extends DisplayDensityBase implement this.manageRequiredAsterisk(); }; + private validateComboState() { + if (this.ngControl && this.ngControl.invalid) { + this.valid = IgxInputState.INVALID; + } else { + this.valid = IgxInputState.INITIAL; + } + } + private get isTouchedOrDirty(): boolean { return (this.ngControl.control.touched || this.ngControl.control.dirty); } diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index a1cf5445d94..ea5834c7b4a 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -13,7 +13,8 @@ role="combobox" aria-haspopup="listbox" [attr.aria-expanded]="!dropdown.collapsed" [attr.aria-controls]="dropdown.listId" [attr.aria-labelledby]="ariaLabelledBy || label?.id || placeholder" - (blur)="onBlur()" /> + (blur)="onBlur()" + (focus)="onFocus()" /> diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index 65ceb5fc5dc..34fcd5529d3 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -3416,6 +3416,27 @@ describe('igxCombo', () => { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); })); + it('should mark as touched and invalid when combo is focused, dropdown appears, and user clicks away without selection', fakeAsync(() => { + const ngModel = fixture.debugElement.query(By.directive(NgModel)).injector.get(NgModel); + expect(combo.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + expect(ngModel.touched).toBeFalse(); + + combo.open(); + input.triggerEventHandler('focus', {}); + fixture.detectChanges(); + expect(ngModel.touched).toBeTrue(); + const documentClickEvent = new MouseEvent('click', { bubbles: true }); + document.body.dispatchEvent(documentClickEvent); + fixture.detectChanges(); + tick(); + document.body.focus(); + fixture.detectChanges(); + tick(); + expect(combo.valid).toEqual(IgxInputState.INVALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID); + expect(ngModel.touched).toBeTrue(); + })); }); }); describe('Display density', () => { @@ -3627,7 +3648,7 @@ class IgxComboFormComponent { @Component({ template: `
-