import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, TemplateRef, ViewContainerRef, Optional, Injector, NgZone } from '@angular/core'; import { PopoverConfig } from './popover.config'; import { ComponentLoader, ComponentLoaderFactory } from '@farris/ui-modal/component-loader'; import { PopoverContainerComponent } from './popover-container.component'; import { PositioningService } from '@farris/ui-modal/positioning'; /** * A lightweight, extensible directive for fancy popover creation. */ @Directive({ selector: '[farrisPopover]', exportAs: 'bs-popover' }) export class PopoverDirective implements OnInit, OnDestroy { /** * Content to be displayed as popover. */ /* tslint:disable-next-line: no-any */ @Input() popover: string | TemplateRef; /** * Context to be used if popover is a template. */ /* tslint:disable-next-line: no-any */ @Input() popoverContext: any; /** * Title of a popover. */ @Input() popoverTitle: string; /** * Placement of a popover. Accepts: "top", "bottom", "left", "right" */ @Input() placement: 'top' | 'bottom' | 'left' | 'right' | 'auto'; /** * Close popover on outside click */ @Input() outsideClick = true; /** * Specifies events that should trigger. Supports a space separated list of * event names. */ @Input() triggers: string; /** * A selector specifying the element the popover should be appended to. */ @Input() container = 'body'; /** * Css class for popover container */ @Input() containerClass = ''; // 按条件激活 @Input() popActive = true; // 显示动作 @Input() showAction = 'show'; /** * Returns whether or not the popover is currently being shown */ @Input() get isOpen(): boolean { return this._popover.isShown; } set isOpen(value: boolean) { if (value) { this[this.showAction](); } else { this.hide(); } } @Input() isFixed=false; /** * Emits an event when the popover is shown */ /* tslint:disable-next-line: no-any */ @Output() onShown: EventEmitter; /** * Emits an event when the popover is hidden */ /* tslint:disable-next-line: no-any */ @Output() onHidden: EventEmitter; private _popover: ComponentLoader; private _isInited = false; private ngZone; // 标记是否进入提示框内 private containerInOutState = false; // 标记 private setTimeoutFlag; constructor( _config: PopoverConfig, private _elementRef: ElementRef, private _renderer: Renderer2, _viewContainerRef: ViewContainerRef, cis: ComponentLoaderFactory, private _positionService: PositioningService, @Optional() private inject: Injector ) { if (this.inject) { this.ngZone = this.inject.get(NgZone, null); } this._popover = cis .createLoader(_elementRef, _viewContainerRef, _renderer) .provide({ provide: PopoverConfig, useValue: _config }); Object.assign(this, _config); this.onShown = this._popover.onShown; this.onHidden = this._popover.onHidden; // fix: no focus on button on Mac OS #1795 if (typeof window !== 'undefined') { _elementRef.nativeElement.addEventListener('click', function () { try { _elementRef.nativeElement.focus(); } catch (err) { return; } }); } } /** * Opens an element’s popover. This is considered a “manual” triggering of * the popover. */ show(): void { if (!this.popActive || this._popover.isShown || !this.popover) { return; } this._popover .attach(PopoverContainerComponent) .to(this.container) .position({ attachment: this.placement }) .show({ showType: "popover", content: this.popover, context: this.popoverContext, placement: this.placement, title: this.popoverTitle, containerClass: this.containerClass }); // this.isOpen = true; if (this.triggers == 'hover' && this._popover.instance && this._popover.instance.getMouseState) { this._popover.instance.getMouseState().subscribe((state) => { this.containerInOutState = state; if (!state) { this._popover.hide(); } }); } } show2(): void { if (!this.popActive || this._popover.isShown || !this.popover) { return; } const targetRef = this._popover .attach(PopoverContainerComponent) .to(this.container) .show({ content: this.popover, context: this.popoverContext, placement: this.placement, title: this.popoverTitle, containerClass: this.containerClass }); //this.isOpen = true; const { height: hostHeight, width: hostWidth, top, left } = this._elementRef.nativeElement.getBoundingClientRect(); const { height: targetHeight, width: targetWidth } = targetRef.location.nativeElement.getBoundingClientRect(); let targetTop: string, targetLeft: string; switch (this.placement) { case 'bottom': targetTop = `${top - targetHeight - 8}px`; targetLeft = `${left + hostWidth / 2 - targetWidth / 2}px`; this._renderer.addClass(targetRef.location.nativeElement, `arrow-${'left'}`) break; case 'left': targetTop = `${top - targetHeight - 8}px`; targetLeft = `${left + hostWidth / 2 - targetWidth / 2}px`; this._renderer.addClass(targetRef.location.nativeElement, `arrow-${'left'}`) break; case 'right': targetTop = `${top - targetHeight - 8}px`; targetLeft = `${left + hostWidth / 2 - targetWidth / 2}px`; this._renderer.addClass(targetRef.location.nativeElement, `arrow-${'left'}`) break; default: targetTop = `${top - targetHeight - 8}px`; targetLeft = `${left + hostWidth / 2 - targetWidth / 2}px`; this._renderer.addClass(targetRef.location.nativeElement, `arrow-${'left'}`) break; } const postion = { top: targetTop, left: targetLeft }; this.setStyles(targetRef.location.nativeElement, postion); } setStyles(element, postion) { const paramsArr = Object.keys(postion); paramsArr && paramsArr.forEach(param => { this._renderer.setStyle(element, param, postion[param]); }); } /** * Closes an element’s popover. This is considered a “manual” triggering of * the popover. */ hide(): void { if (this.triggers == 'hover' && this.ngZone) { if (this.isOpen) { this.ngZone.runOutsideAngular(() => { setTimeout(() => { if (!this.containerInOutState) { this._popover.hide(); } }, 300); }); } return; } if (this.isOpen) { this._popover.hide(); //this.isOpen = false; } } /** * Toggles an element’s popover. This is considered a “manual” triggering of * the popover. */ toggle(): void { if (this.isOpen) { return this.hide(); } this.show(); } ngOnInit(): void { // fix: seems there are an issue with `routerLinkActive` // which result in duplicated call ngOnInit without call to ngOnDestroy // read more: https://github.com/valor-software/ngx-bootstrap/issues/1885 if (this._isInited) { return; } this._isInited = true; if(this.isFixed){ this._positionService.setOptions({ modifiers: { preventOverflow: { enabled: false } } }); } if (this.triggers == 'hover') { this._popover.listen({ triggers: this.triggers, outsideClick: this.outsideClick, show: () => this[this.showAction](), hide: () => this.hide() }); } else { this._popover.listen({ triggers: this.triggers, outsideClick: this.outsideClick, show: () => this[this.showAction]() }); } } ngOnDestroy(): void { this._popover.dispose(); } }