diff --git a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts index a26400879b6..698e2e4810f 100644 --- a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts @@ -217,6 +217,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen return; } + this.target.tooltipTarget = this; + const showingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipShow.emit(showingArgs); @@ -258,7 +260,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen /** * @hidden */ - @HostListener('touchstart') public onTouchStart() { if (this.tooltipDisabled) { return; @@ -270,7 +271,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen /** * @hidden */ - @HostListener('document:touchstart', ['$event']) public onDocumentTouchStart(event) { if (this.tooltipDisabled) { return; @@ -301,6 +301,10 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen this._overlayDefaults.closeOnEscape = true; this.target.closing.pipe(takeUntil(this.destroy$)).subscribe((event) => { + if (this.target.tooltipTarget !== this) { + return; + } + const hidingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipHide.emit(hidingArgs); @@ -308,6 +312,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen event.cancel = true; } }); + + this.nativeElement.addEventListener('touchstart', this.onTouchStart = this.onTouchStart.bind(this), { passive: true }); } /** @@ -315,6 +321,7 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen */ public ngOnDestroy() { this.hideTooltip(); + this.nativeElement.removeEventListener('touchstart', this.onTouchStart); this.destroy$.next(); this.destroy$.complete(); } @@ -335,6 +342,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen this.target.toBeHidden = false; } + this.target.tooltipTarget = this; + const showingArgs = { target: this, tooltip: this.target, cancel: false }; this.tooltipShow.emit(showingArgs); diff --git a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.spec.ts index 8ff5e020576..5b01be0a82b 100644 --- a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.spec.ts @@ -276,7 +276,7 @@ describe('IgxTooltip', () => { flush(); verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, true); - + fix.componentInstance.showButton = false; fix.detectChanges(); flush(); @@ -567,6 +567,47 @@ describe('IgxTooltip', () => { // Tooltip is NOT visible and positioned relative to buttonOne verifyTooltipPosition(tooltipNativeElement, buttonOne, false); })); + + it('Should not call `hideTooltip` multiple times on document:touchstart', fakeAsync(() => { + spyOn(targetOne, 'hideTooltip').and.callThrough(); + spyOn(targetTwo, 'hideTooltip').and.callThrough(); + + touchElement(buttonOne); + tick(500); + + const dummyDiv = fix.debugElement.query(By.css('.dummyDiv')); + touchElement(dummyDiv); + flush(); + + expect(targetOne.hideTooltip).toHaveBeenCalledTimes(1); + expect(targetTwo.hideTooltip).not.toHaveBeenCalled(); + })); + + it('should not emit tooltipHide event multiple times', fakeAsync(() => { + spyOn(targetOne.tooltipHide, 'emit'); + spyOn(targetTwo.tooltipHide, 'emit'); + + hoverElement(buttonOne); + flush(); + + const tooltipHideArgsTargetOne = { target: targetOne, tooltip: fix.componentInstance.tooltip, cancel: false }; + const tooltipHideArgsTargetTwo = { target: targetTwo, tooltip: fix.componentInstance.tooltip, cancel: false }; + + unhoverElement(buttonOne); + tick(500); + expect(targetOne.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetOne); + expect(targetTwo.tooltipHide.emit).not.toHaveBeenCalled(); + flush(); + + hoverElement(buttonTwo); + flush(); + + unhoverElement(buttonTwo); + tick(500); + expect(targetOne.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetOne); + expect(targetTwo.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetTwo); + flush(); + })) }); describe('Tooltip integration', () => { diff --git a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts index 7cff12228af..661606e70f5 100644 --- a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts @@ -1,10 +1,12 @@ import { - Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject + Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject, OnDestroy } from '@angular/core'; import { IgxOverlayService } from '../../services/overlay/overlay'; import { OverlaySettings } from '../../services/public_api'; import { IgxNavigationService } from '../../core/navigation'; import { IgxToggleDirective } from '../toggle/toggle.directive'; +import { Subject, takeUntil } from 'rxjs'; +import { IgxTooltipTargetDirective } from './tooltip-target.directive'; let NEXT_ID = 0; /** @@ -26,7 +28,7 @@ let NEXT_ID = 0; selector: '[igxTooltip]', standalone: true }) -export class IgxTooltipDirective extends IgxToggleDirective { +export class IgxTooltipDirective extends IgxToggleDirective implements OnDestroy { /** * @hidden */ @@ -102,6 +104,13 @@ export class IgxTooltipDirective extends IgxToggleDirective { */ public toBeShown = false; + /** + * @hidden + */ + public tooltipTarget: IgxTooltipTargetDirective; + + private _destroy$ = new Subject(); + /** @hidden */ constructor( elementRef: ElementRef, @@ -110,6 +119,23 @@ export class IgxTooltipDirective extends IgxToggleDirective { @Optional() navigationService: IgxNavigationService) { // D.P. constructor duplication due to es6 compilation, might be obsolete in the future super(elementRef, cdr, overlayService, navigationService); + + this.onDocumentTouchStart = this.onDocumentTouchStart.bind(this); + this.overlayService.opening.pipe(takeUntil(this._destroy$)).subscribe(() => { + document.addEventListener('touchstart', this.onDocumentTouchStart, { passive: true }); + }); + this.overlayService.closed.pipe(takeUntil(this._destroy$)).subscribe(() => { + document.removeEventListener('touchstart', this.onDocumentTouchStart); + }); + } + + /** @hidden */ + public override ngOnDestroy() { + super.ngOnDestroy(); + + document.removeEventListener('touchstart', this.onDocumentTouchStart); + this._destroy$.next(true); + this._destroy$.complete(); } /** @@ -154,4 +180,8 @@ export class IgxTooltipDirective extends IgxToggleDirective { overlaySettings.positionStrategy.settings.closeAnimation = animation; } } + + private onDocumentTouchStart(event) { + this.tooltipTarget?.onDocumentTouchStart(event); + } } diff --git a/projects/igniteui-angular/src/lib/test-utils/tooltip-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tooltip-components.spec.ts index b4d646276ec..f47c687697f 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tooltip-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tooltip-components.spec.ts @@ -6,7 +6,7 @@ import { IgxToggleActionDirective, IgxToggleDirective } from '../directives/togg @Component({ template: `
dummy div for touch tests
- + @if (showButton) {