Skip to content

Commit e85d108

Browse files
andrewseguinmmalerba
authored andcommitted
feat(tooltip): add input for delaying show and hide (#2101)
* feat(tooltip): add input for delaying show and hide * change input for delays to number * remove fakeasyc from tests that dont need show tick * fix comment * rename new inputs to camelCase
1 parent ca2046b commit e85d108

File tree

4 files changed

+92
-26
lines changed

4 files changed

+92
-26
lines changed

src/demo-app/tooltip/tooltip-demo.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ <h1>Tooltip Demo</h1>
66
md-raised-button
77
color="primary"
88
[md-tooltip]="message"
9-
[tooltip-position]="position">
9+
[tooltip-position]="position"
10+
[tooltipShowDelay]="showDelay"
11+
[tooltipHideDelay]="hideDelay">
1012
Mouse over to see the tooltip
1113
</button>
1214
</p>
@@ -27,11 +29,21 @@ <h1>Tooltip Demo</h1>
2729
<md-input-container><input md-input type="text" [(ngModel)]="message"></md-input-container>
2830
</p>
2931

32+
<p>
33+
<strong>Show Delay (ms): </strong>
34+
<md-input type="number" [(ngModel)]="showDelay"></md-input>
35+
</p>
36+
37+
<p>
38+
<strong>Hide Delay (ms): </strong>
39+
<md-input type="number" [(ngModel)]="hideDelay"></md-input>
40+
</p>
41+
3042
<strong>Mouse over to</strong>
3143
<button md-raised-button color="primary" (mouseenter)="tooltip.show()">
3244
Show tooltip
3345
</button>
34-
<button md-raised-button color="primary" (mouseenter)="tooltip.hide(0)">
46+
<button md-raised-button color="primary" (mouseenter)="tooltip.hide()">
3547
Hide tooltip
3648
</button>
3749
<button md-raised-button color="primary" (mouseenter)="tooltip.toggle()">

src/demo-app/tooltip/tooltip-demo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ import {TooltipPosition} from '@angular/material';
1111
export class TooltipDemo {
1212
position: TooltipPosition = 'below';
1313
message: string = 'Here is the tooltip';
14+
showDelay = 0;
15+
hideDelay = 0;
1416
}

src/lib/tooltip/tooltip.spec.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ describe('MdTooltip', () => {
5454
expect(tooltipDirective._tooltipInstance).toBeUndefined();
5555

5656
tooltipDirective.show();
57+
tick(0); // Tick for the show delay (default is 0)
5758
expect(tooltipDirective._isTooltipVisible()).toBe(true);
5859

5960
fixture.detectChanges();
@@ -74,7 +75,37 @@ describe('MdTooltip', () => {
7475
expect(tooltipDirective._tooltipInstance).toBeNull();
7576
}));
7677

77-
it('should not show tooltip if message is not present or empty', fakeAsync(() => {
78+
it('should show with delay', fakeAsync(() => {
79+
expect(tooltipDirective._tooltipInstance).toBeUndefined();
80+
81+
const tooltipDelay = 1000;
82+
tooltipDirective.show(tooltipDelay);
83+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
84+
85+
fixture.detectChanges();
86+
expect(overlayContainerElement.textContent).toContain('');
87+
88+
tick(tooltipDelay);
89+
expect(tooltipDirective._isTooltipVisible()).toBe(true);
90+
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
91+
}));
92+
93+
it('should not show if hide is called before delay finishes', fakeAsync(() => {
94+
expect(tooltipDirective._tooltipInstance).toBeUndefined();
95+
96+
const tooltipDelay = 1000;
97+
tooltipDirective.show(tooltipDelay);
98+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
99+
100+
fixture.detectChanges();
101+
expect(overlayContainerElement.textContent).toContain('');
102+
103+
tooltipDirective.hide();
104+
tick(tooltipDelay);
105+
expect(tooltipDirective._isTooltipVisible()).toBe(false);
106+
}));
107+
108+
it('should not show tooltip if message is not present or empty', () => {
78109
expect(tooltipDirective._tooltipInstance).toBeUndefined();
79110

80111
tooltipDirective.message = undefined;
@@ -96,10 +127,11 @@ describe('MdTooltip', () => {
96127
fixture.detectChanges();
97128
tooltipDirective.show();
98129
expect(tooltipDirective._tooltipInstance).toBeUndefined();
99-
}));
130+
});
100131

101132
it('should not follow through with hide if show is called after', fakeAsync(() => {
102133
tooltipDirective.show();
134+
tick(0); // Tick for the show delay (default is 0)
103135
expect(tooltipDirective._isTooltipVisible()).toBe(true);
104136

105137
// After hide called, a timeout delay is created that will to hide the tooltip.
@@ -133,10 +165,11 @@ describe('MdTooltip', () => {
133165
expect(tooltipDirective._overlayRef).toBeNull();
134166
});
135167

136-
it('should be able to modify the tooltip message', () => {
168+
it('should be able to modify the tooltip message', fakeAsync(() => {
137169
expect(tooltipDirective._tooltipInstance).toBeUndefined();
138170

139171
tooltipDirective.show();
172+
tick(0); // Tick for the show delay (default is 0)
140173
expect(tooltipDirective._tooltipInstance._visibility).toBe('visible');
141174

142175
fixture.detectChanges();
@@ -147,16 +180,17 @@ describe('MdTooltip', () => {
147180

148181
fixture.detectChanges();
149182
expect(overlayContainerElement.textContent).toContain(newMessage);
150-
});
183+
}));
151184

152-
it('should be removed after parent destroyed', () => {
185+
it('should be removed after parent destroyed', fakeAsync(() => {
153186
tooltipDirective.show();
187+
tick(0); // Tick for the show delay (default is 0)
154188
expect(tooltipDirective._isTooltipVisible()).toBe(true);
155189

156190
fixture.destroy();
157191
expect(overlayContainerElement.childNodes.length).toBe(0);
158192
expect(overlayContainerElement.textContent).toBe('');
159-
});
193+
}));
160194

161195
it('should not try to dispose the tooltip when destroyed and done hiding', fakeAsync(() => {
162196
tooltipDirective.show();

src/lib/tooltip/tooltip.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export class MdTooltip {
7474
}
7575
}
7676

77+
/** The default delay in ms before showing the tooltip after show is called */
78+
@Input('tooltipShowDelay') showDelay = 0;
79+
80+
/** The default delay in ms before hiding the tooltip after hide is called */
81+
@Input('tooltipHideDelay') hideDelay = 0;
82+
7783
/** The message to be displayed in the tooltip */
7884
private _message: string;
7985
@Input('md-tooltip') get message() {
@@ -97,22 +103,20 @@ export class MdTooltip {
97103
}
98104
}
99105

100-
/** Shows the tooltip */
101-
show(): void {
102-
if (!this._message || !this._message.trim()) {
103-
return;
104-
}
106+
/** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */
107+
show(delay: number = this.showDelay): void {
108+
if (!this._message || !this._message.trim()) { return; }
105109

106110
if (!this._tooltipInstance) {
107111
this._createTooltip();
108112
}
109113

110114
this._setTooltipMessage(this._message);
111-
this._tooltipInstance.show(this._position);
115+
this._tooltipInstance.show(this._position, delay);
112116
}
113117

114-
/** Hides the tooltip after the provided delay in ms, defaulting to 0ms. */
115-
hide(delay = 0): void {
118+
/** Hides the tooltip after the delay in ms, defaults to tooltip-delay-hide or 0ms if no input */
119+
hide(delay: number = this.hideDelay): void {
116120
if (this._tooltipInstance) {
117121
this._tooltipInstance.hide(delay);
118122
}
@@ -222,7 +226,7 @@ export class MdTooltip {
222226
}
223227
}
224228

225-
export type TooltipVisibility = 'visible' | 'hidden';
229+
export type TooltipVisibility = 'initial' | 'visible' | 'hidden';
226230

227231
@Component({
228232
moduleId: module.id,
@@ -232,6 +236,7 @@ export type TooltipVisibility = 'visible' | 'hidden';
232236
animations: [
233237
trigger('state', [
234238
state('void', style({transform: 'scale(0)'})),
239+
state('initial', style({transform: 'scale(0)'})),
235240
state('visible', style({transform: 'scale(1)'})),
236241
state('hidden', style({transform: 'scale(0)'})),
237242
transition('* => visible', animate('150ms cubic-bezier(0.0, 0.0, 0.2, 1)')),
@@ -246,11 +251,14 @@ export class TooltipComponent {
246251
/** Message to display in the tooltip */
247252
message: string;
248253

254+
/** The timeout ID of any current timer set to show the tooltip */
255+
_showTimeoutId: number;
256+
249257
/** The timeout ID of any current timer set to hide the tooltip */
250258
_hideTimeoutId: number;
251259

252260
/** Property watched by the animation framework to show or hide the tooltip */
253-
_visibility: TooltipVisibility;
261+
_visibility: TooltipVisibility = 'initial';
254262

255263
/** Whether interactions on the page should close the tooltip */
256264
_closeOnInteraction: boolean = false;
@@ -264,23 +272,33 @@ export class TooltipComponent {
264272
constructor(@Optional() private _dir: Dir) {}
265273

266274
/** Shows the tooltip with an animation originating from the provided origin */
267-
show(position: TooltipPosition): void {
268-
this._closeOnInteraction = false;
269-
this._visibility = 'visible';
270-
this._setTransformOrigin(position);
271-
275+
show(position: TooltipPosition, delay: number): void {
272276
// Cancel the delayed hide if it is scheduled
273277
if (this._hideTimeoutId) {
274278
clearTimeout(this._hideTimeoutId);
275279
}
276280

277-
// If this was set to true immediately, then the body click would trigger interaction and
278-
// close the tooltip right after it was displayed.
279-
setTimeout(() => { this._closeOnInteraction = true; }, 0);
281+
// Body interactions should cancel the tooltip if there is a delay in showing.
282+
this._closeOnInteraction = true;
283+
284+
this._setTransformOrigin(position);
285+
this._showTimeoutId = setTimeout(() => {
286+
this._visibility = 'visible';
287+
288+
// If this was set to true immediately, then a body click that triggers show() would
289+
// trigger interaction and close the tooltip right after it was displayed.
290+
this._closeOnInteraction = false;
291+
setTimeout(() => { this._closeOnInteraction = true; }, 0);
292+
}, delay);
280293
}
281294

282295
/** Begins the animation to hide the tooltip after the provided delay in ms */
283296
hide(delay: number): void {
297+
// Cancel the delayed show if it is scheduled
298+
if (this._showTimeoutId) {
299+
clearTimeout(this._showTimeoutId);
300+
}
301+
284302
this._hideTimeoutId = setTimeout(() => {
285303
this._visibility = 'hidden';
286304
this._closeOnInteraction = false;

0 commit comments

Comments
 (0)