import { Component, Directive, Input, Output, EventEmitter, ChangeDetectionStrategy, OnInit, OnDestroy, Injector, Renderer2, ComponentRef, ElementRef, TemplateRef, ViewContainerRef, ComponentFactoryResolver, NgZone } from '@angular/core'; import {listenToTriggers} from '../util/triggers'; import {positionElements, Placement, PlacementArray} from '../util/positioning'; import {PopupService} from '../util/popup'; import {NgbTooltipConfig} from './tooltip-config'; let nextId = 0; @Component({ selector: 'ngb-tooltip-window', changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class]': '"tooltip show bs-tooltip-" + placement.split("-")[0]+" bs-tooltip-" + placement', 'role': 'tooltip', '[id]': 'id' }, template: `
`, styles: [` :host.bs-tooltip-top .arrow, :host.bs-tooltip-bottom .arrow { left: calc(50% - 0.4rem); } :host.bs-tooltip-top-left .arrow, :host.bs-tooltip-bottom-left .arrow { left: 1em; } :host.bs-tooltip-top-right .arrow, :host.bs-tooltip-bottom-right .arrow { left: auto; right: 0.8rem; } :host.bs-tooltip-left .arrow, :host.bs-tooltip-right .arrow { top: calc(50% - 0.4rem); } :host.bs-tooltip-left-top .arrow, :host.bs-tooltip-right-top .arrow { top: 0.4rem; } :host.bs-tooltip-left-bottom .arrow, :host.bs-tooltip-right-bottom .arrow { top: auto; bottom: 0.4rem; } `] }) export class NgbTooltipWindow { @Input() placement: Placement = 'top'; @Input() id: string; constructor(private _element: ElementRef, private _renderer: Renderer2) {} applyPlacement(_placement: Placement) { // remove the current placement classes this._renderer.removeClass(this._element.nativeElement, 'bs-tooltip-' + this.placement.toString().split('-')[0]); this._renderer.removeClass(this._element.nativeElement, 'bs-tooltip-' + this.placement.toString()); // set the new placement classes this.placement = _placement; // apply the new placement this._renderer.addClass(this._element.nativeElement, 'bs-tooltip-' + this.placement.toString().split('-')[0]); this._renderer.addClass(this._element.nativeElement, 'bs-tooltip-' + this.placement.toString()); } } /** * A lightweight, extensible directive for fancy tooltip creation. */ @Directive({selector: '[ngbTooltip]', exportAs: 'ngbTooltip'}) export class NgbTooltip implements OnInit, OnDestroy { /** * Placement of a popover accepts: * "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right", * "left", "left-top", "left-bottom", "right", "right-top", "right-bottom" * and array of above values. */ @Input() placement: PlacementArray; /** * 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; /** * A flag indicating if a given tooltip is disabled and should not be displayed. * * @since 1.1.0 */ @Input() disableTooltip: boolean; /** * Emits an event when the tooltip is shown */ @Output() shown = new EventEmitter(); /** * Emits an event when the tooltip is hidden */ @Output() hidden = new EventEmitter(); private _ngbTooltip: string | TemplateRef; private _ngbTooltipWindowId = `ngb-tooltip-${nextId++}`; private _popupService: PopupService; private _windowRef: ComponentRef; private _unregisterListenersFn; private _zoneSubscription: any; constructor( private _elementRef: ElementRef, private _renderer: Renderer2, injector: Injector, componentFactoryResolver: ComponentFactoryResolver, viewContainerRef: ViewContainerRef, config: NgbTooltipConfig, ngZone: NgZone) { this.placement = config.placement; this.triggers = config.triggers; this.container = config.container; this.disableTooltip = config.disableTooltip; this._popupService = new PopupService( NgbTooltipWindow, injector, viewContainerRef, _renderer, componentFactoryResolver); this._zoneSubscription = ngZone.onStable.subscribe(() => { if (this._windowRef) { this._windowRef.instance.applyPlacement( positionElements( this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement, this.container === 'body')); } }); } /** * Content to be displayed as tooltip. If falsy, the tooltip won't open. */ @Input() set ngbTooltip(value: string | TemplateRef) { this._ngbTooltip = value; if (!value && this._windowRef) { this.close(); } } get ngbTooltip() { return this._ngbTooltip; } /** * Opens an element’s tooltip. This is considered a “manual” triggering of the tooltip. * The context is an optional value to be injected into the tooltip template when it is created. */ open(context?: any) { if (!this._windowRef && this._ngbTooltip && !this.disableTooltip) { this._windowRef = this._popupService.open(this._ngbTooltip, context); this._windowRef.instance.id = this._ngbTooltipWindowId; this._renderer.setAttribute(this._elementRef.nativeElement, 'aria-describedby', this._ngbTooltipWindowId); if (this.container === 'body') { window.document.querySelector(this.container).appendChild(this._windowRef.location.nativeElement); } this._windowRef.instance.placement = Array.isArray(this.placement) ? this.placement[0] : this.placement; // apply styling to set basic css-classes on target element, before going for positioning this._windowRef.changeDetectorRef.detectChanges(); this._windowRef.changeDetectorRef.markForCheck(); // position tooltip along the element this._windowRef.instance.applyPlacement( positionElements( this._elementRef.nativeElement, this._windowRef.location.nativeElement, this.placement, this.container === 'body')); this.shown.emit(); } } /** * Closes an element’s tooltip. This is considered a “manual” triggering of the tooltip. */ close(): void { if (this._windowRef != null) { this._renderer.removeAttribute(this._elementRef.nativeElement, 'aria-describedby'); this._popupService.close(); this._windowRef = null; this.hidden.emit(); } } /** * Toggles an element’s tooltip. This is considered a “manual” triggering of the tooltip. */ toggle(): void { if (this._windowRef) { this.close(); } else { this.open(); } } /** * Returns whether or not the tooltip is currently being shown */ isOpen(): boolean { return this._windowRef != null; } ngOnInit() { this._unregisterListenersFn = listenToTriggers( this._renderer, this._elementRef.nativeElement, this.triggers, this.open.bind(this), this.close.bind(this), this.toggle.bind(this)); } ngOnDestroy() { this.close(); // This check is needed as it might happen that ngOnDestroy is called before ngOnInit // under certain conditions, see: https://github.com/ng-bootstrap/ng-bootstrap/issues/2199 if (this._unregisterListenersFn) { this._unregisterListenersFn(); } this._zoneSubscription.unsubscribe(); } }