diff --git a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip-target.directive.ts b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip-target.directive.ts index c55e158f229..394a4f2f557 100644 --- a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip-target.directive.ts +++ b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip-target.directive.ts @@ -27,6 +27,8 @@ export interface ITooltipHideEventArgs extends IBaseEventArgs { cancel: boolean; } +const HOVER_SHOW_TRIGGERS = new Set(['mouseenter', 'mouseover', 'pointerenter', 'pointerover']); + /** * **Ignite UI for Angular Tooltip Target** - * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip) @@ -564,8 +566,14 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen this._pendingShowTrigger = triggerEvent?.type ?? null; this.target.timeoutId = setTimeout(() => { - // Call open() of IgxTooltipDirective + const isHoverTrigger = this._pendingShowTrigger && HOVER_SHOW_TRIGGERS.has(this._pendingShowTrigger); this._pendingShowTrigger = null; + this.target.timeoutId = null; + + if (isHoverTrigger && !this.nativeElement.matches(':hover')) { + return; + } + this.target.open(this._mergedOverlaySettings); }, withDelay ? this.showDelay : 0); } diff --git a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts index 318b4e0f1e6..aaa8d5536bf 100644 --- a/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts +++ b/projects/igniteui-angular/directives/src/directives/tooltip/tooltip.directive.spec.ts @@ -15,6 +15,7 @@ const SHOW_DELAY = 200; const HIDE_DELAY = 300; const AUTO_HIDE_DELAY = 180; const TOOLTIP_ARROW_SELECTOR = '[data-arrow="true"]'; +const hoveredElements = new WeakSet(); describe('IgxTooltip', () => { let fix: ComponentFixture; @@ -23,6 +24,11 @@ describe('IgxTooltip', () => { let button: DebugElement; beforeEach(waitForAsync(() => { + const matches = Element.prototype.matches; + spyOn(Element.prototype, 'matches').and.callFake(function(this: Element, selectors: string): boolean { + return selectors === ':hover' ? hoveredElements.has(this) : matches.call(this, selectors); + } as typeof Element.prototype.matches); + TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, @@ -532,6 +538,22 @@ describe('IgxTooltip', () => { tick(300); verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, false); })); + + for (const trigger of ['mouseenter', 'mouseover', 'pointerenter', 'pointerover']) { + it(`should not open after the delay when the target is no longer hovered using ${trigger}`, fakeAsync(() => { + tooltipTarget.showDelay = 500; + tooltipTarget.showTriggers = trigger; + tooltipTarget.hideTriggers = 'click'; + fix.detectChanges(); + + hoverElement(button, trigger); + tick(300); + unhoverElement(button); + tick(200); + + verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, false); + })); + } }); }); @@ -1169,9 +1191,15 @@ interface ElementRefLike { nativeElement: HTMLElement } -const hoverElement = (element: ElementRefLike) => element.nativeElement.dispatchEvent(new MouseEvent('pointerenter')); +const hoverElement = (element: ElementRefLike, event = 'pointerenter') => { + hoveredElements.add(element.nativeElement); + element.nativeElement.dispatchEvent(new MouseEvent(event)); +}; -const unhoverElement = (element: ElementRefLike) => element.nativeElement.dispatchEvent(new MouseEvent('pointerleave')); +const unhoverElement = (element: ElementRefLike) => { + hoveredElements.delete(element.nativeElement); + element.nativeElement.dispatchEvent(new MouseEvent('pointerleave')); +}; const simulateTriggerEvent = (element: ElementRefLike, event: string) => element.nativeElement.dispatchEvent(new Event(event, { bubbles: true })); @@ -1188,7 +1216,7 @@ const alignmentTolerance = 2; export const verifyTooltipPosition = ( tooltipNativeElement: HTMLElement, actualTarget: { nativeElement: HTMLElement }, - shouldAlign:boolean = true, + shouldAlign: boolean = true, placement: Placement = Placement.Bottom, offset: number = 6 ) => { diff --git a/projects/igniteui-angular/grids/grid/src/grid-validation.spec.ts b/projects/igniteui-angular/grids/grid/src/grid-validation.spec.ts index 2e6ab931322..5e5c274860e 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-validation.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-validation.spec.ts @@ -225,6 +225,7 @@ describe('IgxGrid - Validation #grid', () => { expect(cell.errorTooltip.first.collapsed).toBeTrue(); const element = fixture.debugElement.query(By.directive(IgxTooltipTargetDirective)).nativeElement; + spyOn(element, 'matches').and.returnValue(true); element.dispatchEvent(new MouseEvent('pointerenter')); flush(); fixture.detectChanges();