Skip to content

Commit e3f83b3

Browse files
authored
fix(tooltip): make touch events passive - 18.2.x (#15815)
* fix(tooltip): make touch events passive * test(tooltip): fix document touchstart test * fix(tooltip): should not emit hide event multiple times * fix(tooltip): handle document touch in tooltip
1 parent 610da48 commit e3f83b3

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-6
lines changed

projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
217217
return;
218218
}
219219

220+
this.target.tooltipTarget = this;
221+
220222
const showingArgs = { target: this, tooltip: this.target, cancel: false };
221223
this.tooltipShow.emit(showingArgs);
222224

@@ -258,7 +260,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
258260
/**
259261
* @hidden
260262
*/
261-
@HostListener('touchstart')
262263
public onTouchStart() {
263264
if (this.tooltipDisabled) {
264265
return;
@@ -270,7 +271,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
270271
/**
271272
* @hidden
272273
*/
273-
@HostListener('document:touchstart', ['$event'])
274274
public onDocumentTouchStart(event) {
275275
if (this.tooltipDisabled) {
276276
return;
@@ -301,20 +301,27 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
301301
this._overlayDefaults.closeOnEscape = true;
302302

303303
this.target.closing.pipe(takeUntil(this.destroy$)).subscribe((event) => {
304+
if (this.target.tooltipTarget !== this) {
305+
return;
306+
}
307+
304308
const hidingArgs = { target: this, tooltip: this.target, cancel: false };
305309
this.tooltipHide.emit(hidingArgs);
306310

307311
if (hidingArgs.cancel) {
308312
event.cancel = true;
309313
}
310314
});
315+
316+
this.nativeElement.addEventListener('touchstart', this.onTouchStart = this.onTouchStart.bind(this), { passive: true });
311317
}
312318

313319
/**
314320
* @hidden
315321
*/
316322
public ngOnDestroy() {
317323
this.hideTooltip();
324+
this.nativeElement.removeEventListener('touchstart', this.onTouchStart);
318325
this.destroy$.next();
319326
this.destroy$.complete();
320327
}
@@ -335,6 +342,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
335342
this.target.toBeHidden = false;
336343
}
337344

345+
this.target.tooltipTarget = this;
346+
338347
const showingArgs = { target: this, tooltip: this.target, cancel: false };
339348
this.tooltipShow.emit(showingArgs);
340349

projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ describe('IgxTooltip', () => {
276276
flush();
277277

278278
verifyTooltipVisibility(tooltipNativeElement, tooltipTarget, true);
279-
279+
280280
fix.componentInstance.showButton = false;
281281
fix.detectChanges();
282282
flush();
@@ -567,6 +567,47 @@ describe('IgxTooltip', () => {
567567
// Tooltip is NOT visible and positioned relative to buttonOne
568568
verifyTooltipPosition(tooltipNativeElement, buttonOne, false);
569569
}));
570+
571+
it('Should not call `hideTooltip` multiple times on document:touchstart', fakeAsync(() => {
572+
spyOn(targetOne, 'hideTooltip').and.callThrough();
573+
spyOn(targetTwo, 'hideTooltip').and.callThrough();
574+
575+
touchElement(buttonOne);
576+
tick(500);
577+
578+
const dummyDiv = fix.debugElement.query(By.css('.dummyDiv'));
579+
touchElement(dummyDiv);
580+
flush();
581+
582+
expect(targetOne.hideTooltip).toHaveBeenCalledTimes(1);
583+
expect(targetTwo.hideTooltip).not.toHaveBeenCalled();
584+
}));
585+
586+
it('should not emit tooltipHide event multiple times', fakeAsync(() => {
587+
spyOn(targetOne.tooltipHide, 'emit');
588+
spyOn(targetTwo.tooltipHide, 'emit');
589+
590+
hoverElement(buttonOne);
591+
flush();
592+
593+
const tooltipHideArgsTargetOne = { target: targetOne, tooltip: fix.componentInstance.tooltip, cancel: false };
594+
const tooltipHideArgsTargetTwo = { target: targetTwo, tooltip: fix.componentInstance.tooltip, cancel: false };
595+
596+
unhoverElement(buttonOne);
597+
tick(500);
598+
expect(targetOne.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetOne);
599+
expect(targetTwo.tooltipHide.emit).not.toHaveBeenCalled();
600+
flush();
601+
602+
hoverElement(buttonTwo);
603+
flush();
604+
605+
unhoverElement(buttonTwo);
606+
tick(500);
607+
expect(targetOne.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetOne);
608+
expect(targetTwo.tooltipHide.emit).toHaveBeenCalledOnceWith(tooltipHideArgsTargetTwo);
609+
flush();
610+
}))
570611
});
571612

572613
describe('Tooltip integration', () => {

projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
2-
Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject
2+
Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject, OnDestroy
33
} from '@angular/core';
44
import { IgxOverlayService } from '../../services/overlay/overlay';
55
import { OverlaySettings } from '../../services/public_api';
66
import { IgxNavigationService } from '../../core/navigation';
77
import { IgxToggleDirective } from '../toggle/toggle.directive';
8+
import { Subject, takeUntil } from 'rxjs';
9+
import { IgxTooltipTargetDirective } from './tooltip-target.directive';
810

911
let NEXT_ID = 0;
1012
/**
@@ -26,7 +28,7 @@ let NEXT_ID = 0;
2628
selector: '[igxTooltip]',
2729
standalone: true
2830
})
29-
export class IgxTooltipDirective extends IgxToggleDirective {
31+
export class IgxTooltipDirective extends IgxToggleDirective implements OnDestroy {
3032
/**
3133
* @hidden
3234
*/
@@ -102,6 +104,13 @@ export class IgxTooltipDirective extends IgxToggleDirective {
102104
*/
103105
public toBeShown = false;
104106

107+
/**
108+
* @hidden
109+
*/
110+
public tooltipTarget: IgxTooltipTargetDirective;
111+
112+
private _destroy$ = new Subject<boolean>();
113+
105114
/** @hidden */
106115
constructor(
107116
elementRef: ElementRef,
@@ -110,6 +119,23 @@ export class IgxTooltipDirective extends IgxToggleDirective {
110119
@Optional() navigationService: IgxNavigationService) {
111120
// D.P. constructor duplication due to es6 compilation, might be obsolete in the future
112121
super(elementRef, cdr, overlayService, navigationService);
122+
123+
this.onDocumentTouchStart = this.onDocumentTouchStart.bind(this);
124+
this.overlayService.opening.pipe(takeUntil(this._destroy$)).subscribe(() => {
125+
document.addEventListener('touchstart', this.onDocumentTouchStart, { passive: true });
126+
});
127+
this.overlayService.closed.pipe(takeUntil(this._destroy$)).subscribe(() => {
128+
document.removeEventListener('touchstart', this.onDocumentTouchStart);
129+
});
130+
}
131+
132+
/** @hidden */
133+
public override ngOnDestroy() {
134+
super.ngOnDestroy();
135+
136+
document.removeEventListener('touchstart', this.onDocumentTouchStart);
137+
this._destroy$.next(true);
138+
this._destroy$.complete();
113139
}
114140

115141
/**
@@ -154,4 +180,8 @@ export class IgxTooltipDirective extends IgxToggleDirective {
154180
overlaySettings.positionStrategy.settings.closeAnimation = animation;
155181
}
156182
}
183+
184+
private onDocumentTouchStart(event) {
185+
this.tooltipTarget?.onDocumentTouchStart(event);
186+
}
157187
}

projects/igniteui-angular/src/lib/test-utils/tooltip-components.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { IgxToggleActionDirective, IgxToggleDirective } from '../directives/togg
66
@Component({
77
template: `
88
<div class="dummyDiv">dummy div for touch tests</div>
9-
9+
1010
@if (showButton) {
1111
<button [igxTooltipTarget]="tooltipRef" [tooltip]="'Infragistics Inc. HQ'"
1212
(tooltipShow)="showing($event)" (tooltipHide)="hiding($event)"
@@ -43,6 +43,8 @@ export class IgxTooltipSingleTargetComponent {
4343

4444
@Component({
4545
template: `
46+
<div class="dummyDiv">dummy div for touch tests</div>
47+
4648
<button class="buttonOne" #targetOne="tooltipTarget" [igxTooltipTarget]="tooltipRef" style="margin: 100px">
4749
Target One
4850
</button>

0 commit comments

Comments
 (0)