Skip to content

Commit f90b5de

Browse files
committed
feat(tooltip): add show/hide triggers
1 parent 0d418b7 commit f90b5de

File tree

3 files changed

+106
-84
lines changed

3 files changed

+106
-84
lines changed

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

Lines changed: 85 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {
2-
Directive, OnInit, OnDestroy, Output, ElementRef, Optional, ViewContainerRef, HostListener,
2+
Directive, OnInit, OnDestroy, Output, ElementRef, ViewContainerRef,
33
Input, EventEmitter, booleanAttribute, TemplateRef, ComponentRef, Renderer2,
44
EnvironmentInjector,
55
createComponent,
66
AfterViewInit,
7+
inject,
78
} from '@angular/core';
89
import { Subject } from 'rxjs';
910
import { takeUntil } from 'rxjs/operators';
@@ -14,7 +15,7 @@ import { IgxToggleActionDirective } from '../toggle/toggle.directive';
1415
import { IgxTooltipComponent } from './tooltip.component';
1516
import { IgxTooltipDirective } from './tooltip.directive';
1617
import { IgxTooltipCloseButtonComponent } from './tooltip-close-button.component';
17-
import { TooltipPositionSettings, TooltipPositionStrategy } from './tooltip.common';
18+
import { parseTriggers, TooltipPositionSettings, TooltipPositionStrategy } from './tooltip.common';
1819

1920
export interface ITooltipShowEventArgs extends IBaseEventArgs {
2021
target: IgxTooltipTargetDirective;
@@ -232,6 +233,46 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
232233
@Input({ transform: booleanAttribute })
233234
public tooltipDisabled = false;
234235

236+
/**
237+
* Which event triggers will show the tooltip.
238+
* Expects a comma separate string of different event triggers.
239+
* Defaults to `pointerenter`.
240+
* ```html
241+
* <igx-icon [igxTooltipTarget]="tooltipRef" [showTriggers]="'click,focus'">info</igx-icon>
242+
* <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
243+
* ```
244+
*/
245+
@Input()
246+
public get showTriggers(): string {
247+
return Array.from(this._showTriggers).join();
248+
}
249+
250+
public set showTriggers(value: string) {
251+
this._showTriggers = parseTriggers(value);
252+
this.removeEventListeners();
253+
this.addEventListeners();
254+
}
255+
256+
/**
257+
* Which event triggers will hide the tooltip.
258+
* Expects a comma separate string of different event triggers.
259+
* Defaults to `pointerleave` and `click`.
260+
* ```html
261+
* <igx-icon [igxTooltipTarget]="tooltipRef" [hideTriggers]="'keypress,blur'">info</igx-icon>
262+
* <span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
263+
* ```
264+
*/
265+
@Input()
266+
public get hideTriggers(): string {
267+
return Array.from(this._hideTriggers).join();
268+
}
269+
270+
public set hideTriggers(value: string) {
271+
this._hideTriggers = parseTriggers(value);
272+
this.removeEventListeners();
273+
this.addEventListeners();
274+
}
275+
235276
/**
236277
* @hidden
237278
*/
@@ -253,8 +294,11 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
253294
}
254295

255296
/**
256-
* @hidden
257-
*/
297+
* Specifies a plain text as tooltip content.
298+
* ```html
299+
* <igx-icon igxTooltipTarget [tooltip]="'Infragistics Inc. HQ'">info</igx-icon>
300+
* ```
301+
*/
258302
@Input()
259303
public set tooltip(content: any) {
260304
if (!this.target && (typeof content === 'string' || content instanceof String)) {
@@ -323,6 +367,12 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
323367
@Output()
324368
public tooltipHide = new EventEmitter<ITooltipHideEventArgs>();
325369

370+
private _element = inject(ElementRef);
371+
private _navigationService = inject(IgxNavigationService, { optional: true });
372+
private _viewContainerRef = inject(ViewContainerRef);
373+
private _renderer = inject(Renderer2);
374+
private _envInjector = inject(EnvironmentInjector);
375+
326376
private _destroy$ = new Subject<void>();
327377
private _autoHideDelay = 180;
328378
private _isForceClosed = false;
@@ -331,25 +381,20 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
331381
private _closeTemplate: TemplateRef<any>;
332382
private _sticky = false;
333383
private _positionSettings: PositionSettings = TooltipPositionSettings;
384+
private _showTriggers = new Set(['pointerenter']);
385+
private _hideTriggers = new Set(['pointerleave', 'click']);
334386

335-
constructor(
336-
private _element: ElementRef,
337-
@Optional() private _navigationService: IgxNavigationService,
338-
private _viewContainerRef: ViewContainerRef,
339-
private _renderer: Renderer2,
340-
private _envInjector: EnvironmentInjector
341-
) {
342-
super(_element, _navigationService);
343-
}
387+
private _abortController = new AbortController();
344388

345389
/**
346390
* @hidden
347391
*/
348-
@HostListener('click')
349392
public override onClick() {
350-
if (!this.target.collapsed) {
351-
this._hideOnInteraction();
352-
} else if (this.target.timeoutId) {
393+
if (
394+
this.target.timeoutId &&
395+
this.target.collapsed &&
396+
!this._showTriggers.has('click')
397+
) {
353398
clearTimeout(this.target.timeoutId);
354399
this.target.timeoutId = null;
355400
}
@@ -358,16 +403,14 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
358403
/**
359404
* @hidden
360405
*/
361-
@HostListener('mouseenter')
362-
public onMouseEnter() {
406+
public onShow(): void {
363407
this._checksBeforeShowing(() => this._showOnInteraction());
364408
}
365409

366410
/**
367411
* @hidden
368412
*/
369-
@HostListener('mouseleave')
370-
public onMouseLeave() {
413+
public onHide() : void {
371414
if (this.tooltipDisabled) {
372415
return;
373416
}
@@ -376,28 +419,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
376419
this._hideOnInteraction();
377420
}
378421

379-
/**
380-
* @hidden
381-
*/
382-
public onTouchStart() {
383-
this._checksBeforeShowing(() => this._showOnInteraction());
384-
}
385-
386-
/**
387-
* @hidden
388-
*/
389-
public onDocumentTouchStart(event) {
390-
if (this.tooltipDisabled || this?.target?.tooltipTarget !== this) {
391-
return;
392-
}
393-
394-
if (this.nativeElement !== event.target &&
395-
!this.nativeElement.contains(event.target)
396-
) {
397-
this._hideOnInteraction();
398-
}
399-
}
400-
401422
/**
402423
* @hidden
403424
*/
@@ -421,7 +442,8 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
421442
}
422443
});
423444

424-
this.nativeElement.addEventListener('touchstart', this.onTouchStart = this.onTouchStart.bind(this), { passive: true });
445+
this.removeEventListeners();
446+
this.addEventListeners();
425447
}
426448

427449
/**
@@ -438,7 +460,7 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
438460
*/
439461
public ngOnDestroy() {
440462
this.hideTooltip();
441-
this.nativeElement.removeEventListener('touchstart', this.onTouchStart);
463+
this.removeEventListeners();
442464
this._destroyCloseButton();
443465
this._destroy$.next();
444466
this._destroy$.complete();
@@ -470,6 +492,24 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
470492
return Object.assign({}, this._overlayDefaults, this.overlaySettings);
471493
}
472494

495+
private addEventListeners(): void {
496+
const options = { passive: true, signal: this._abortController.signal };
497+
498+
this.onShow = this.onShow.bind(this);
499+
for (const each of this._showTriggers) {
500+
this.nativeElement.addEventListener(each, this.onShow, options);
501+
}
502+
this.onHide = this.onHide.bind(this);
503+
for (const each of this._hideTriggers) {
504+
this.nativeElement.addEventListener(each, this.onHide, options);
505+
}
506+
}
507+
508+
private removeEventListeners(): void {
509+
this._abortController.abort();
510+
this._abortController = new AbortController();
511+
}
512+
473513
private _checkOutletAndOutsideClick(): void {
474514
if (this.outlet) {
475515
this._overlayDefaults.outlet = this.outlet;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { useAnimation } from '@angular/animations';
55
import { fadeOut, scaleInCenter } from 'igniteui-angular/animations';
66

77
export const TooltipRegexes = Object.freeze({
8+
/** Used for parsing the strings passed in the tooltip `show/hide-trigger` properties. */
9+
triggers: /[,\s]+/,
10+
811
/** Matches horizontal `Placement` end positions. `left-end` | `right-end` */
912
horizontalEnd: /^(left|right)-end$/,
1013

@@ -330,3 +333,9 @@ export const PositionsMap = new Map<Placement, PositionSettings>([
330333
verticalStartPoint: VerticalAlignment.Bottom,
331334
}]
332335
]);
336+
337+
export function parseTriggers(triggers: string): Set<string> {
338+
return new Set(
339+
(triggers ?? '').split(TooltipRegexes.triggers).filter((s) => s.trim())
340+
);
341+
}

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

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import {
2-
Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject,
3-
OnDestroy, inject, DOCUMENT, HostListener,
2+
Directive, Input, HostBinding,
3+
OnDestroy, inject, HostListener,
44
Renderer2,
55
AfterViewInit,
66
} from '@angular/core';
7-
import { IgxOverlayService } from '../../services/overlay/overlay';
87
import { OverlaySettings } from '../../services/overlay/utilities';
9-
import { IgxNavigationService } from '../../core/navigation';
108
import { IgxToggleDirective } from '../toggle/toggle.directive';
119
import { IgxTooltipTargetDirective } from './tooltip-target.directive';
12-
import { Subject, takeUntil } from 'rxjs';
1310
import { PlatformUtil } from '../../core/utils';
1411

1512
let NEXT_ID = 0;
@@ -118,29 +115,9 @@ export class IgxTooltipDirective extends IgxToggleDirective implements AfterView
118115

119116
private _arrowEl: HTMLElement;
120117
private _role: 'tooltip' | 'status' = 'tooltip';
121-
private _destroy$ = new Subject<boolean>();
122-
private _document = inject(DOCUMENT);
123118
private _renderer = inject(Renderer2);
124119
private _platformUtil = inject(PlatformUtil);
125120

126-
/** @hidden */
127-
constructor(
128-
elementRef: ElementRef,
129-
cdr: ChangeDetectorRef,
130-
@Inject(IgxOverlayService) overlayService: IgxOverlayService,
131-
@Optional() navigationService: IgxNavigationService) {
132-
// D.P. constructor duplication due to es6 compilation, might be obsolete in the future
133-
super(elementRef, cdr, overlayService, navigationService);
134-
135-
this.onDocumentTouchStart = this.onDocumentTouchStart.bind(this);
136-
this.opening.pipe(takeUntil(this._destroy$)).subscribe(() => {
137-
this._document.addEventListener('touchstart', this.onDocumentTouchStart, { passive: true });
138-
});
139-
this.closed.pipe(takeUntil(this._destroy$)).subscribe(() => {
140-
this._document.removeEventListener('touchstart', this.onDocumentTouchStart);
141-
});
142-
}
143-
144121
/** @hidden */
145122
public ngAfterViewInit(): void {
146123
if (this._platformUtil.isBrowser) {
@@ -152,10 +129,6 @@ export class IgxTooltipDirective extends IgxToggleDirective implements AfterView
152129
public override ngOnDestroy() {
153130
super.ngOnDestroy();
154131

155-
this._document.removeEventListener('touchstart', this.onDocumentTouchStart);
156-
this._destroy$.next(true);
157-
this._destroy$.complete();
158-
159132
if (this.arrow) {
160133
this._removeArrow();
161134
}
@@ -164,17 +137,21 @@ export class IgxTooltipDirective extends IgxToggleDirective implements AfterView
164137
/**
165138
* @hidden
166139
*/
167-
@HostListener('mouseenter')
168-
public onMouseEnter() {
169-
this.tooltipTarget?.onMouseEnter();
140+
@HostListener('pointerenter')
141+
protected onPointerEnter() {
142+
if (this.tooltipTarget) {
143+
this.tooltipTarget.onShow();
144+
}
170145
}
171146

172147
/**
173148
* @hidden
174149
*/
175-
@HostListener('mouseleave')
176-
public onMouseLeave() {
177-
this.tooltipTarget?.onMouseLeave();
150+
@HostListener('pointerleave')
151+
protected onPointerLeave() {
152+
if (this.tooltipTarget) {
153+
this.tooltipTarget.onHide();
154+
}
178155
}
179156

180157
/**
@@ -230,8 +207,4 @@ export class IgxTooltipDirective extends IgxToggleDirective implements AfterView
230207
this._arrowEl.remove();
231208
this._arrowEl = null;
232209
}
233-
234-
private onDocumentTouchStart(event) {
235-
this.tooltipTarget?.onDocumentTouchStart(event);
236-
}
237210
}

0 commit comments

Comments
 (0)