• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 15275490875

27 May 2025 12:39PM UTC coverage: 91.672% (+0.003%) from 91.669%
15275490875

Pull #15833

github

web-flow
fix(tooltip): make touch events passive - 19.2.x (#15813)

* fix(tooltip): make touch events passive

* fix(tooltip): add check for tooltipTarget

* test(tooltip): should not call hideTooltip multiple times on document:touchstart

* test(tooltip): add flush for document touchstart test

* fix(tooltip): should not emit hide event multiple times

* fix(tooltip): set tooltipTarget in showTooltip method

* fix(tooltip): handle document touch event in tooltipTarget

* fix(tooltip): handle documentTouch in tooltip

* chore(tooltip): remove touchstart removeEventListener

* fix(tooltip): remove document listener on destroy

* test(tooltip): A multi target touch test

---------

Co-authored-by: Galina Edinakova <gedinakova@infragistics.com>
Co-authored-by: Pablo <pmoleri@infragistics.com>
Pull Request #15833: Mass merging 19.2.x to master

13413 of 15683 branches covered (85.53%)

81 of 87 new or added lines in 9 files covered. (93.1%)

2 existing lines in 2 files now uncovered.

26979 of 29430 relevant lines covered (91.67%)

34422.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.64
/projects/igniteui-angular/src/lib/directives/tooltip/tooltip-target.directive.ts
1
import { useAnimation } from '@angular/animations';
2
import { Directive, OnInit, OnDestroy, Output, ElementRef, Optional, ViewContainerRef, HostListener, Input, EventEmitter, booleanAttribute } from '@angular/core';
3
import { Subject } from 'rxjs';
4
import { takeUntil } from 'rxjs/operators';
5
import { IgxNavigationService } from '../../core/navigation';
6
import { IBaseEventArgs } from '../../core/utils';
7
import { AutoPositionStrategy, HorizontalAlignment, PositionSettings } from '../../services/public_api';
8
import { IgxToggleActionDirective } from '../toggle/toggle.directive';
9
import { IgxTooltipComponent } from './tooltip.component';
10
import { IgxTooltipDirective } from './tooltip.directive';
11
import { fadeOut, scaleInCenter } from 'igniteui-angular/animations';
12

13
export interface ITooltipShowEventArgs extends IBaseEventArgs {
14
    target: IgxTooltipTargetDirective;
15
    tooltip: IgxTooltipDirective;
16
    cancel: boolean;
17
}
18
export interface ITooltipHideEventArgs extends IBaseEventArgs {
19
    target: IgxTooltipTargetDirective;
20
    tooltip: IgxTooltipDirective;
21
    cancel: boolean;
22
}
23

24
/**
25
 * **Ignite UI for Angular Tooltip Target** -
26
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/tooltip)
27
 *
28
 * The Ignite UI for Angular Tooltip Target directive is used to mark an HTML element in the markup as one that has a tooltip.
29
 * The tooltip target is used in combination with the Ignite UI for Angular Tooltip by assigning the exported tooltip reference to the
30
 * target's selector property.
31
 *
32
 * Example:
33
 * ```html
34
 * <button type="button" igxButton [igxTooltipTarget]="tooltipRef">Hover me</button>
35
 * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
36
 * ```
37
 */
38
@Directive({
39
    exportAs: 'tooltipTarget',
40
    selector: '[igxTooltipTarget]',
41
    standalone: true
42
})
43
export class IgxTooltipTargetDirective extends IgxToggleActionDirective implements OnInit, OnDestroy {
3✔
44
    /**
45
     * Gets/sets the amount of milliseconds that should pass before showing the tooltip.
46
     *
47
     * ```typescript
48
     * // get
49
     * let tooltipShowDelay = this.tooltipTarget.showDelay;
50
     * ```
51
     *
52
     * ```html
53
     * <!--set-->
54
     * <button type="button" igxButton [igxTooltipTarget]="tooltipRef" [showDelay]="1500">Hover me</button>
55
     * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
56
     * ```
57
     */
58
    @Input()
59
    public showDelay = 500;
853✔
60

61
    /**
62
     * Gets/sets the amount of milliseconds that should pass before hiding the tooltip.
63
     *
64
     * ```typescript
65
     * // get
66
     * let tooltipHideDelay = this.tooltipTarget.hideDelay;
67
     * ```
68
     *
69
     * ```html
70
     * <!--set-->
71
     * <button type="button" igxButton [igxTooltipTarget]="tooltipRef" [hideDelay]="1500">Hover me</button>
72
     * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
73
     * ```
74
     */
75
    @Input()
76
    public hideDelay = 500;
853✔
77

78
    /**
79
     * Specifies if the tooltip should not show when hovering its target with the mouse. (defaults to false)
80
     * While setting this property to 'true' will disable the user interactions that shows/hides the tooltip,
81
     * the developer will still be able to show/hide the tooltip through the API.
82
     *
83
     * ```typescript
84
     * // get
85
     * let tooltipDisabledValue = this.tooltipTarget.tooltipDisabled;
86
     * ```
87
     *
88
     * ```html
89
     * <!--set-->
90
     * <button type="button" igxButton [igxTooltipTarget]="tooltipRef" [tooltipDisabled]="true">Hover me</button>
91
     * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
92
     * ```
93
     */
94
    @Input({ transform: booleanAttribute })
95
    public tooltipDisabled = false;
853✔
96

97
    /**
98
     * @hidden
99
     */
100
    @Input('igxTooltipTarget')
101
    public override set target(target: any) {
102
        if (target instanceof IgxTooltipDirective) {
854✔
103
            this._target = target;
851✔
104
        }
105
    }
106

107
    /**
108
     * @hidden
109
     */
110
    public override get target(): any {
111
        if (typeof this._target === 'string') {
4,417!
112
            return this._navigationService.get(this._target);
×
113
        }
114
        return this._target;
4,417✔
115
    }
116

117
    /**
118
    * @hidden
119
    */
120
    @Input()
121
    public set tooltip(content: any) {
122
        if (!this.target && (typeof content === 'string' || content instanceof String)) {
32!
123
            const tooltipComponent = this._viewContainerRef.createComponent(IgxTooltipComponent);
2✔
124
            tooltipComponent.instance.content = content as string;
2✔
125

126
            this._target = tooltipComponent.instance.tooltip;
2✔
127
        }
128
    }
129

130
    /**
131
     * Gets the respective native element of the directive.
132
     *
133
     * ```typescript
134
     * let tooltipTargetElement = this.tooltipTarget.nativeElement;
135
     * ```
136
     */
137
    public get nativeElement() {
138
        return this._element.nativeElement;
1,721✔
139
    }
140

141
    /**
142
     * Indicates if the tooltip that is is associated with this target is currently hidden.
143
     *
144
     * ```typescript
145
     * let tooltipHiddenValue = this.tooltipTarget.tooltipHidden;
146
     * ```
147
     */
148
    public get tooltipHidden(): boolean {
149
        return !this.target || this.target.collapsed;
57✔
150
    }
151

152
    /**
153
     * Emits an event when the tooltip that is associated with this target starts showing.
154
     * This event is fired before the start of the countdown to showing the tooltip.
155
     *
156
     * ```typescript
157
     * tooltipShowing(args: ITooltipShowEventArgs) {
158
     *    alert("Tooltip started showing!");
159
     * }
160
     * ```
161
     *
162
     * ```html
163
     * <button type="button" igxButton [igxTooltipTarget]="tooltipRef" (tooltipShow)='tooltipShowing($event)'>Hover me</button>
164
     * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
165
     * ```
166
     */
167
    @Output()
168
    public tooltipShow = new EventEmitter<ITooltipShowEventArgs>();
853✔
169

170
    /**
171
     * Emits an event when the tooltip that is associated with this target starts hiding.
172
     * This event is fired before the start of the countdown to hiding the tooltip.
173
     *
174
     * ```typescript
175
     * tooltipHiding(args: ITooltipHideEventArgs) {
176
     *    alert("Tooltip started hiding!");
177
     * }
178
     * ```
179
     *
180
     * ```html
181
     * <button type="button" igxButton [igxTooltipTarget]="tooltipRef" (tooltipHide)='tooltipHiding($event)'>Hover me</button>
182
     * <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
183
     * ```
184
     */
185
    @Output()
186
    public tooltipHide = new EventEmitter<ITooltipHideEventArgs>();
853✔
187

188
    private destroy$ = new Subject<void>();
853✔
189

190
    constructor(private _element: ElementRef,
853✔
191
        @Optional() private _navigationService: IgxNavigationService, private _viewContainerRef: ViewContainerRef) {
853✔
192
        super(_element, _navigationService);
853✔
193
    }
194

195
    /**
196
     * @hidden
197
     */
198
    @HostListener('click')
199
    public override onClick() {
200
        if (!this.target.collapsed) {
118✔
201
            this.target.forceClose(this.mergedOverlaySettings);
4✔
202
        }
203
    }
204

205
    /**
206
     * @hidden
207
     */
208
    @HostListener('mouseenter')
209
    public onMouseEnter() {
210
        if (this.tooltipDisabled) {
27✔
211
            return;
1✔
212
        }
213

214
        this.checkOutletAndOutsideClick();
26✔
215
        const shouldReturn = this.preMouseEnterCheck();
26✔
216
        if (shouldReturn) {
26!
217
            return;
×
218
        }
219

220
        this.target.tooltipTarget = this;
26✔
221

222
        const showingArgs = { target: this, tooltip: this.target, cancel: false };
26✔
223
        this.tooltipShow.emit(showingArgs);
26✔
224

225
        if (showingArgs.cancel) {
26✔
226
            return;
1✔
227
        }
228

229
        this.target.toBeShown = true;
25✔
230
        this.target.timeoutId = setTimeout(() => {
25✔
231
            this.target.open(this.mergedOverlaySettings); // Call open() of IgxTooltipDirective
24✔
232
            this.target.toBeShown = false;
24✔
233
        }, this.showDelay);
234
    }
235

236
    /**
237
     * @hidden
238
     */
239
    @HostListener('mouseleave')
240
    public onMouseLeave() {
241
        if (this.tooltipDisabled) {
14!
242
            return;
×
243
        }
244

245
        this.checkOutletAndOutsideClick();
14✔
246
        const shouldReturn = this.preMouseLeaveCheck();
14✔
247
        if (shouldReturn || this.target.collapsed) {
14✔
248
            return;
2✔
249
        }
250

251
        this.target.toBeHidden = true;
12✔
252
        this.target.timeoutId = setTimeout(() => {
12✔
253
            this.target.close(); // Call close() of IgxTooltipDirective
11✔
254
            this.target.toBeHidden = false;
11✔
255
        }, this.hideDelay);
256

257

258
    }
259

260
    /**
261
     * @hidden
262
     */
263
    public onTouchStart() {
264
        if (this.tooltipDisabled) {
8✔
265
            return;
1✔
266
        }
267

268
        this.showTooltip();
7✔
269
    }
270

271
    /**
272
     * @hidden
273
     */
274
    public onDocumentTouchStart(event) {
275
        if (this.tooltipDisabled) {
5!
UNCOV
276
            return;
×
277
        }
278

279
        if (this.nativeElement !== event.target &&
5✔
280
            !this.nativeElement.contains(event.target)
281
        ) {
282
            this.hideTooltip();
4✔
283
        }
284
    }
285

286
    /**
287
     * @hidden
288
     */
289
    public override ngOnInit() {
290
        super.ngOnInit();
853✔
291

292
        const positionSettings: PositionSettings = {
853✔
293
            horizontalDirection: HorizontalAlignment.Center,
294
            horizontalStartPoint: HorizontalAlignment.Center,
295
            openAnimation: useAnimation(scaleInCenter, { params: { duration: '150ms' } }),
296
            closeAnimation: useAnimation(fadeOut, { params: { duration: '75ms' } })
297
        };
298

299
        this._overlayDefaults.positionStrategy = new AutoPositionStrategy(positionSettings);
853✔
300
        this._overlayDefaults.closeOnOutsideClick = false;
853✔
301
        this._overlayDefaults.closeOnEscape = true;
853✔
302

303
        this.target.closing.pipe(takeUntil(this.destroy$)).subscribe((event) => {
853✔
304
            if (this.target.tooltipTarget !== this) {
29✔
305
                return;
6✔
306
            }
307

308
            const hidingArgs = { target: this, tooltip: this.target, cancel: false };
23✔
309
            this.tooltipHide.emit(hidingArgs);
23✔
310

311
            if (hidingArgs.cancel) {
23✔
312
                event.cancel = true;
2✔
313
            }
314
        });
315

316
        this.nativeElement.addEventListener('touchstart', this.onTouchStart = this.onTouchStart.bind(this), { passive: true });
853✔
317
    }
318

319
    /**
320
     * @hidden
321
     */
322
    public ngOnDestroy() {
323
        this.hideTooltip();
853✔
324
        this.nativeElement.removeEventListener('touchstart', this.onTouchStart);
853✔
325
        this.destroy$.next();
853✔
326
        this.destroy$.complete();
853✔
327
    }
328

329
    /**
330
     * Shows the tooltip by respecting the 'showDelay' property.
331
     *
332
     * ```typescript
333
     * this.tooltipTarget.showTooltip();
334
     * ```
335
     */
336
    public showTooltip() {
337
        clearTimeout(this.target.timeoutId);
18✔
338

339
        if (!this.target.collapsed) {
18✔
340
            //  if close animation has started finish it, or close the tooltip with no animation
341
            this.target.forceClose(this.mergedOverlaySettings);
2✔
342
            this.target.toBeHidden = false;
2✔
343
        }
344
        this.target.tooltipTarget = this;
18✔
345

346
        const showingArgs = { target: this, tooltip: this.target, cancel: false };
18✔
347
        this.tooltipShow.emit(showingArgs);
18✔
348

349
        if (showingArgs.cancel) {
18✔
350
            return;
1✔
351
        }
352

353
        this.target.toBeShown = true;
17✔
354
        this.target.timeoutId = setTimeout(() => {
17✔
355
            this.target.open(this.mergedOverlaySettings); // Call open() of IgxTooltipDirective
17✔
356
            this.target.toBeShown = false;
17✔
357
        }, this.showDelay);
358
    }
359

360
    /**
361
     * Hides the tooltip by respecting the 'hideDelay' property.
362
     *
363
     * ```typescript
364
     * this.tooltipTarget.hideTooltip();
365
     * ```
366
     */
367
    public hideTooltip() {
368
        if (this.target.collapsed && this.target.toBeShown) {
862!
369
            clearTimeout(this.target.timeoutId);
×
370
        }
371

372
        if (this.target.collapsed || this.target.toBeHidden) {
862✔
373
            return;
814✔
374
        }
375

376
        this.target.toBeHidden = true;
48✔
377
        this.target.timeoutId = setTimeout(() => {
48✔
378
            this.target.close(); // Call close() of IgxTooltipDirective
48✔
379
            this.target.toBeHidden = false;
48✔
380
        }, this.hideDelay);
381
    }
382

383
    private checkOutletAndOutsideClick() {
384
        if (this.outlet) {
40✔
385
            this._overlayDefaults.outlet = this.outlet;
1✔
386
        }
387
    }
388

389
    private get mergedOverlaySettings() {
390
        return Object.assign({}, this._overlayDefaults, this.overlaySettings);
49✔
391
    }
392

393
    // Return true if the execution in onMouseEnter should be terminated after this method
394
    private preMouseEnterCheck() {
395
        // If tooltip is about to be opened
396
        if (this.target.toBeShown) {
26!
397
            clearTimeout(this.target.timeoutId);
×
398
            this.target.toBeShown = false;
×
399
        }
400

401
        // If Tooltip is opened or about to be hidden
402
        if (!this.target.collapsed || this.target.toBeHidden) {
26✔
403
            clearTimeout(this.target.timeoutId);
2✔
404

405
            //  if close animation has started finish it, or close the tooltip with no animation
406
            this.target.forceClose(this.mergedOverlaySettings);
2✔
407
            this.target.toBeHidden = false;
2✔
408
        }
409

410
        return false;
26✔
411
    }
412

413
    // Return true if the execution in onMouseLeave should be terminated after this method
414
    private preMouseLeaveCheck(): boolean {
415
        clearTimeout(this.target.timeoutId);
14✔
416

417
        // If tooltip is about to be opened
418
        if (this.target.toBeShown) {
14✔
419
            this.target.toBeShown = false;
1✔
420
            this.target.toBeHidden = false;
1✔
421
            return true;
1✔
422
        }
423

424
        return false;
13✔
425
    }
426
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc