Skip to content

Commit

Permalink
feat(tooltip): add container input, support 'body'
Browse files Browse the repository at this point in the history
Closes #621
Closes #871
  • Loading branch information
spongessuck authored and pkozlowski-opensource committed Oct 11, 2016
1 parent 84d555c commit b8230e5
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<p>
Set the <code>container</code> property to "body" to have the tooltip be appended to the body instead of the triggering element's parent. This option is useful if the element triggering the tooltip is inside an element that clips its contents (i.e. <code>overflow: hidden</code>).
</p>

<div class='row'>
<div class='card'>
<button type="button" class="btn btn-secondary"
ngbTooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
Default tooltip
</button>
<button type="button" class="btn btn-secondary"
ngbTooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." container="body">
Tooltip appended to body
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Component} from '@angular/core';

@Component({
selector: 'ngbd-tooltip-container',
templateUrl: './tooltip-container.html',
styles: ['.card { padding: 50px 0; text-align: center; overflow:hidden }']
})
export class NgbdTooltipContainer {
}
7 changes: 6 additions & 1 deletion demo/src/app/components/tooltip/demos/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import {NgbdTooltipBasic} from './basic/tooltip-basic';
import {NgbdTooltipContainer} from './container/tooltip-container';
import {NgbdTooltipTplcontent} from './tplcontent/tooltip-tplcontent';
import {NgbdTooltipTriggers} from './triggers/tooltip-triggers';
import {NgbdTooltipConfig} from './config/tooltip-config';

export const DEMO_DIRECTIVES = [NgbdTooltipBasic, NgbdTooltipTplcontent, NgbdTooltipTriggers, NgbdTooltipConfig];
export const DEMO_DIRECTIVES = [NgbdTooltipBasic, NgbdTooltipContainer, NgbdTooltipTplcontent, NgbdTooltipTriggers, NgbdTooltipConfig];

export const DEMO_SNIPPETS = {
basic: {
code: require('!!prismjs?lang=typescript!./basic/tooltip-basic'),
markup: require('!!prismjs?lang=markup!./basic/tooltip-basic.html')
},
container: {
code: require('!!prismjs?lang=typescript!./container/tooltip-container'),
markup: require('!!prismjs?lang=markup!./container/tooltip-container.html')
},
tplcontent: {
code: require('!!prismjs?lang=typescript!./tplcontent/tooltip-tplcontent'),
markup: require('!!prismjs?lang=markup!./tplcontent/tooltip-tplcontent.html')
Expand Down
4 changes: 4 additions & 0 deletions demo/src/app/components/tooltip/tooltip.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import {DEMO_SNIPPETS} from './demos';
demoTitle="Custom and manual triggers" [htmlSnippet]="snippets.triggers.markup" [tsSnippet]="snippets.triggers.code">
<ngbd-tooltip-triggers></ngbd-tooltip-triggers>
</ngbd-example-box>
<ngbd-example-box
demoTitle="Append tooltip in the body" [htmlSnippet]="snippets.container.markup" [tsSnippet]="snippets.container.code">
<ngbd-tooltip-container></ngbd-tooltip-container>
</ngbd-example-box>
<ngbd-example-box
demoTitle="Global configuration of tooltips" [htmlSnippet]="snippets.config.markup" [tsSnippet]="snippets.config.code">
<ngbd-tooltip-config></ngbd-tooltip-config>
Expand Down
1 change: 1 addition & 0 deletions src/tooltip/tooltip-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ describe('ngb-tooltip-config', () => {

expect(config.placement).toBe('top');
expect(config.triggers).toBe('hover');
expect(config.container).toBeUndefined();
});
});
1 change: 1 addition & 0 deletions src/tooltip/tooltip-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ import {Injectable} from '@angular/core';
export class NgbTooltipConfig {
placement: 'top' | 'bottom' | 'left' | 'right' = 'top';
triggers = 'hover';
container?: string;
}
25 changes: 25 additions & 0 deletions src/tooltip/tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('ngb-tooltip', () => {
expect(windowEl).toHaveCssClass(`tooltip-${defaultConfig.placement}`);
expect(windowEl.textContent.trim()).toBe('Great tip!');
expect(windowEl.getAttribute('role')).toBe('tooltip');
expect(windowEl.parentNode).toBe(fixture.nativeElement);

directive.triggerEventHandler('mouseleave', {});
fixture.detectChanges();
Expand All @@ -76,6 +77,7 @@ describe('ngb-tooltip', () => {
expect(windowEl).toHaveCssClass('tooltip-top');
expect(windowEl.textContent.trim()).toBe('Hello, World!');
expect(windowEl.getAttribute('role')).toBe('tooltip');
expect(windowEl.parentNode).toBe(fixture.nativeElement);

directive.triggerEventHandler('mouseleave', {});
fixture.detectChanges();
Expand Down Expand Up @@ -293,6 +295,25 @@ describe('ngb-tooltip', () => {
});
});

describe('container', () => {

it('should be appended to the element matching the selector passed to "container"', () => {
const selector = 'body';
const fixture = createTestComponent(`<div ngbTooltip="Great tip!" container="` + selector + `"></div>`);
const directive = fixture.debugElement.query(By.directive(NgbTooltip));

directive.triggerEventHandler('mouseenter', {});
fixture.detectChanges();
let windowEl = getWindow(fixture);
expect(windowEl).toBeNull();

const container = window.document.querySelector(selector);
windowEl = container.querySelector('ngb-tooltip-window');
expect(windowEl.parentNode).toBe(container);
});

});

describe('visibility', () => {
it('should emit events when showing and hiding popover', () => {
const fixture = createTestComponent(
Expand Down Expand Up @@ -374,6 +395,7 @@ describe('ngb-tooltip', () => {
config = c;
config.placement = 'bottom';
config.triggers = 'click';
config.container = 'body';
}));

it('should initialize inputs with provided config', () => {
Expand All @@ -383,13 +405,15 @@ describe('ngb-tooltip', () => {

expect(tooltip.placement).toBe(config.placement);
expect(tooltip.triggers).toBe(config.triggers);
expect(tooltip.container).toBe(config.container);
});
});

describe('Custom config as provider', () => {
let config = new NgbTooltipConfig();
config.placement = 'bottom';
config.triggers = 'click';
config.container = 'body';

beforeEach(() => {
TestBed.configureTestingModule(
Expand All @@ -402,6 +426,7 @@ describe('ngb-tooltip', () => {

expect(tooltip.placement).toBe(config.placement);
expect(tooltip.triggers).toBe(config.triggers);
expect(tooltip.container).toBe(config.container);
});
});
});
Expand Down
15 changes: 14 additions & 1 deletion src/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export class NgbTooltip implements OnInit, OnDestroy {
* Specifies events that should trigger. Supports a space separated list of event names.
*/
@Input() triggers: string;
/**
* A selector specifying the element the tooltip should be appended to.
* Currently only supports "body".
*/
@Input() container: string;
/**
* Emits an event when the tooltip is shown
*/
Expand All @@ -69,12 +74,20 @@ export class NgbTooltip implements OnInit, OnDestroy {
ngZone: NgZone) {
this.placement = config.placement;
this.triggers = config.triggers;
this.container = config.container;
this._popupService = new PopupService<NgbTooltipWindow>(
NgbTooltipWindow, injector, viewContainerRef, _renderer, componentFactoryResolver);

this._zoneSubscription = ngZone.onStable.subscribe(() => {
if (this._windowRef) {
positionElements(this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement);
positionElements(
this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement,
this.container === 'body');

if (this.container === 'body') {
let windowEl = this._windowRef.location.nativeElement;
window.document.querySelector(this.container).appendChild(windowEl);
}
}
});
}
Expand Down

0 comments on commit b8230e5

Please sign in to comment.