Skip to content

Commit aad5727

Browse files
committed
feat(tooltip): Add custom close button and disable arrow functionality
1 parent f72d834 commit aad5727

File tree

4 files changed

+274
-10
lines changed

4 files changed

+274
-10
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Component, Output, EventEmitter } from '@angular/core';
2+
import { IgxIconComponent} from 'igniteui-angular';
3+
4+
@Component({
5+
selector: 'igx-tooltip-close-button',
6+
template: `
7+
<div class="close-button">
8+
<igx-icon
9+
name="close"
10+
ariaLabel="Close tooltip"
11+
(click)="handleClick()">
12+
</igx-icon>
13+
</div>
14+
`,
15+
imports: [IgxIconComponent],
16+
})
17+
export class TooltipCloseButtonComponent {
18+
@Output() public clicked = new EventEmitter<void>();
19+
20+
public handleClick(): void {
21+
this.clicked.emit();
22+
}
23+
}

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

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useAnimation } from '@angular/animations';
2-
import { Directive, OnInit, OnDestroy, Output, ElementRef, Optional, ViewContainerRef, HostListener, Input, EventEmitter, booleanAttribute } from '@angular/core';
2+
import { Directive, OnInit, OnDestroy, Output, ElementRef, Optional, ViewContainerRef, HostListener, Input, EventEmitter, booleanAttribute, TemplateRef } from '@angular/core';
33
import { Subject } from 'rxjs';
44
import { takeUntil } from 'rxjs/operators';
55
import { IgxNavigationService } from '../../core/navigation';
@@ -75,6 +75,96 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
7575
@Input()
7676
public hideDelay = 500;
7777

78+
/**
79+
* Controls whether the arrow element of the tooltip is rendered.
80+
* Set to true to hide the arrow. Default value is `false`.
81+
*
82+
* ```typescript
83+
* // get
84+
* let isArrowDisabled = this.tooltip.disableArrow;
85+
* ```
86+
*
87+
* ```typescript
88+
* // set
89+
* this.tooltip.disableArrow = false;
90+
* ```
91+
*
92+
* ```html
93+
* <!--set-->
94+
* <igx-icon igxTooltipTarget [disableArrow]="true" [tooltip]="'Infragistics Inc. HQ'">info</igx-icon>
95+
* ```
96+
*/
97+
@Input()
98+
public set disableArrow(value: boolean) {
99+
this.target._disableArrow = value;
100+
}
101+
102+
public get disableArrow(): boolean {
103+
return this.target._disableArrow;
104+
}
105+
106+
/**
107+
* Specifies if the tooltip remains visible until the user closes it via the close button or Esc key.
108+
*
109+
* ```typescript
110+
* // get
111+
* let isSticky = this.tooltip.sticky;
112+
* ```
113+
*
114+
* ```typescript
115+
* // set
116+
* this.tooltip.sticky = true;
117+
* ```
118+
*
119+
* ```html
120+
* <!--set-->
121+
* <igx-icon igxTooltipTarget [sticky]="true" [tooltip]="'Infragistics Inc. HQ'">info</igx-icon>
122+
* ```
123+
*/
124+
@Input()
125+
public set sticky (value: boolean) {
126+
this.target._sticky = value;
127+
};
128+
129+
public get sticky (): boolean {
130+
return this.target._sticky;
131+
}
132+
133+
/**
134+
* Allows full control over the appearance of the close button inside the tooltip.
135+
*
136+
* ```typescript
137+
* // get
138+
* let customCloseTemplate = this.tooltip.customCloseTemplate;
139+
* ```
140+
*
141+
* ```typescript
142+
* // set
143+
* this.tooltip.customCloseTemplate = TemplateRef<any>;
144+
* ```
145+
*
146+
* ```html
147+
* <!--set-->
148+
* <igx-icon igxTooltipTarget [closeButtonTemplate]="customClose" [tooltip]="'Infragistics Inc. HQ'">info</igx-icon>
149+
* <ng-template #customClose>
150+
* <button class="my-close-btn">Close Me</button>
151+
* </ng-template>
152+
* ```
153+
*/
154+
@Input('closeButtonTemplate')
155+
public set customCloseTemplate(value: TemplateRef<any>) {
156+
this.target._customCloseTemplate = value;
157+
if (value) {
158+
this.target.renderCustomCloseTemplate();
159+
} else {
160+
this.target.appendDefaultCloseIcon();
161+
}
162+
}
163+
164+
public get customCloseTemplate(): TemplateRef<any> | undefined {
165+
return this.target._customCloseTemplate;
166+
}
167+
78168
/**
79169
* Specifies if the tooltip should not show when hovering its target with the mouse. (defaults to false)
80170
* While setting this property to 'true' will disable the user interactions that shows/hides the tooltip,
@@ -236,6 +326,10 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen
236326
*/
237327
@HostListener('mouseleave')
238328
public onMouseLeave() {
329+
if (this.sticky) {
330+
return;
331+
}
332+
239333
if (this.tooltipDisabled) {
240334
return;
241335
}

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

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import {
2-
Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject
2+
Directive, ElementRef, Input, ChangeDetectorRef, Optional, HostBinding, Inject,
3+
TemplateRef,
4+
ViewContainerRef, OnInit, OnDestroy, AfterViewInit,
35
} from '@angular/core';
46
import { IgxOverlayService } from '../../services/overlay/overlay';
5-
import { OverlaySettings } from '../../services/public_api';
7+
import { HorizontalAlignment, OverlaySettings, VerticalAlignment } from '../../services/public_api';
68
import { IgxNavigationService } from '../../core/navigation';
79
import { IgxToggleDirective } from '../toggle/toggle.directive';
10+
import { takeUntil } from 'rxjs';
11+
import { TooltipCloseButtonComponent } from './tooltip-close-button.component';
812

913
let NEXT_ID = 0;
1014
/**
@@ -26,7 +30,13 @@ let NEXT_ID = 0;
2630
selector: '[igxTooltip]',
2731
standalone: true
2832
})
29-
export class IgxTooltipDirective extends IgxToggleDirective {
33+
export class IgxTooltipDirective extends IgxToggleDirective implements OnInit, OnDestroy, AfterViewInit {
34+
private _arrowEl: HTMLElement;
35+
private _customCloseTemplate: TemplateRef<any>;
36+
private _disableArrow = false;
37+
private _sticky = false;
38+
private _offset = 6;
39+
3040
/**
3141
* @hidden
3242
*/
@@ -107,11 +117,36 @@ export class IgxTooltipDirective extends IgxToggleDirective {
107117
elementRef: ElementRef,
108118
cdr: ChangeDetectorRef,
109119
@Inject(IgxOverlayService) overlayService: IgxOverlayService,
110-
@Optional() navigationService: IgxNavigationService) {
120+
@Optional() navigationService: IgxNavigationService,
121+
private viewContainerRef: ViewContainerRef) {
111122
// D.P. constructor duplication due to es6 compilation, might be obsolete in the future
112123
super(elementRef, cdr, overlayService, navigationService);
113124
}
114125

126+
public override ngOnInit() {
127+
super.ngOnInit();
128+
this.createArrowElement();
129+
130+
this.opened.pipe(takeUntil(this.destroy$)).subscribe(() => {
131+
this._arrowEl.style.display = this._disableArrow ? 'none' : 'block';
132+
this.positionArrow(this.overlayService.getOverlayById(this._overlayId).settings);
133+
});
134+
}
135+
136+
137+
public ngAfterViewInit() {
138+
if(this._sticky && !this._customCloseTemplate){
139+
this.appendDefaultCloseIcon();
140+
}
141+
}
142+
143+
144+
public override ngOnDestroy() {
145+
super.ngOnDestroy();
146+
this.removeArrow();
147+
this.removeCloseButton();
148+
}
149+
115150
/**
116151
* If there is open animation in progress this method will finish is.
117152
* If there is no open animation in progress this method will open the toggle with no animation.
@@ -154,4 +189,108 @@ export class IgxTooltipDirective extends IgxToggleDirective {
154189
overlaySettings.positionStrategy.settings.closeAnimation = animation;
155190
}
156191
}
192+
193+
private setOffsetTooltip(settings: OverlaySettings) {
194+
const pos = settings.positionStrategy.settings;
195+
if (!pos) return;
196+
197+
if (pos.verticalDirection === VerticalAlignment.Top) {
198+
this.setOffset(0, -this._offset);
199+
} else if (pos.verticalDirection === VerticalAlignment.Bottom) {
200+
this.setOffset(0, this._offset);
201+
} else if (pos.horizontalDirection === HorizontalAlignment.Left) {
202+
this.setOffset(this._offset, 0);
203+
} else if (pos.horizontalDirection === HorizontalAlignment.Right) {
204+
this.setOffset(-this._offset, 0);
205+
}
206+
}
207+
208+
//TO DO:
209+
// Fix arrow flicker on animation played
210+
public positionArrow(settings?: OverlaySettings) {
211+
const pos = settings?.positionStrategy?.settings;
212+
if (!pos || !this._arrowEl) return;
213+
214+
const style = this._arrowEl.style;
215+
const offset = 4;
216+
217+
// Reset all directions and transforms
218+
style.top = '';
219+
style.bottom = '';
220+
style.left = '';
221+
style.right = '';
222+
style.transform = '';
223+
224+
const set = (s: Partial<CSSStyleDeclaration>) => Object.assign(style, s);
225+
226+
if (pos.verticalDirection === VerticalAlignment.Top) {
227+
set({
228+
bottom: `-${offset}px`,
229+
left: '50%',
230+
transform: 'translateX(-50%)'
231+
});
232+
} else if (pos.verticalDirection === VerticalAlignment.Bottom) {
233+
set({
234+
top: `-${offset}px`,
235+
left: '50%',
236+
transform: 'translateX(-50%)',
237+
});
238+
} else if (pos.horizontalDirection === HorizontalAlignment.Left) {
239+
set({
240+
right: `-${offset}px`,
241+
top: '50%',
242+
transform: 'translateY(-50%)'
243+
});
244+
} else if (pos.horizontalDirection === HorizontalAlignment.Right) {
245+
set({
246+
left: `-${offset}px`,
247+
top: '50%',
248+
transform: 'translateY(-50%)'
249+
});
250+
}
251+
}
252+
253+
private createArrowElement() {
254+
this._arrowEl = document.createElement('div');
255+
this._arrowEl.classList.add('igx-tooltip--arrow');
256+
this._arrowEl.style.position = 'absolute';
257+
this._arrowEl.style.width = '8px';
258+
this._arrowEl.style.height = '8px';
259+
this._arrowEl.style.transform = 'rotate(45deg)';
260+
this._arrowEl.style.background = 'inherit';
261+
this.element.appendChild(this._arrowEl);
262+
}
263+
264+
protected renderCustomCloseTemplate(): void {
265+
this.removeCloseButton();
266+
267+
const view = this.viewContainerRef.createEmbeddedView(this._customCloseTemplate);
268+
view.detectChanges();
269+
270+
for (const node of view.rootNodes) {
271+
if (node instanceof HTMLElement) {
272+
node.classList.add('close-button');
273+
this.element.appendChild(node);
274+
}
275+
}
276+
}
277+
278+
protected appendDefaultCloseIcon(): void {
279+
this.removeCloseButton();
280+
const buttonRef = this.viewContainerRef.createComponent(TooltipCloseButtonComponent);
281+
buttonRef.instance.clicked.pipe(takeUntil(this.destroy$)).subscribe(() => this.close());
282+
this.element.appendChild(buttonRef.location.nativeElement);
283+
}
284+
285+
private removeCloseButton(){
286+
const closeButton = this.element.querySelector('.close-button');
287+
if (closeButton) {
288+
closeButton.remove();
289+
}
290+
}
291+
292+
private removeArrow() {
293+
this._arrowEl.remove();
294+
this._arrowEl = null;
295+
}
157296
}

src/app/tooltip/tooltip.sample.html

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
<div class="column-container">
22
<article class="column">
33
<h4 class="sample-title">Simple tooltip</h4>
4-
<igx-avatar #target="tooltipTarget" [igxTooltipTarget]="simple" [tooltip]="'Infragistics Inc. HQ'" [overlaySettings]="settings"
4+
<igx-avatar #target="tooltipTarget" [igxTooltipTarget]="simple" [tooltip]="'Infragistics Inc. HQ'"
5+
[sticky]="true"
6+
[overlaySettings]="settings"
57
src="assets/images/avatar/10.jpg" size="medium"
68
(onTooltipShow)="showing()"
79
(onTooltipHide)="hiding()"
810
aria-describedby="myid">
911
</igx-avatar>
1012

13+
<ng-template #customClose>
14+
<button class="my-close-btn" (click)="simple.close()">Close Me</button>
15+
</ng-template>
16+
1117
<div class="custom-width" #simple="tooltip" igxTooltip id="myid">
1218
Her name is Toola Tipa
13-
</div>
19+
</div>
20+
1421
</article>
1522

1623
<article class="column">
1724
<h4 class="sample-title">Tooltip input</h4>
1825
<div class="sample-tooltip-input">
19-
<igx-avatar class="bottomMargin" src="assets/images/avatar/10.jpg" size="medium" igxTooltipTarget [tooltip]="'Her name is Toola Tipa'">
26+
27+
<igx-avatar class="bottomMargin" src="assets/images/avatar/10.jpg" size="medium" igxTooltipTarget [tooltip]="'Her name is Toola Tipa'" [disableArrow]="true">
2028
</igx-avatar>
2129

2230
<igx-icon igxTooltipTarget [tooltip]="'Infragistics Inc. HQ'">
2331
info
2432
</igx-icon>
25-
</div>
33+
</div>
2634
</article>
2735

2836
<article class="column">
@@ -103,5 +111,5 @@ <h4 class="sample-title">Grid with tooltips</h4>
103111
</igx-column>
104112
</igx-grid>
105113
</div>
106-
</article>
114+
</article>
107115
</div>

0 commit comments

Comments
 (0)