diff options
Diffstat (limited to 'catalog-ui/src/app/ng2/components/tooltip')
5 files changed, 324 insertions, 0 deletions
diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html new file mode 100644 index 0000000000..1fbf45e39f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html @@ -0,0 +1,12 @@ +<div class="tooltip {{ placement }}" + [style.top]="top + 'px'" + [style.left]="left + 'px'" + [class.in]="isIn" + [class.fade]="isFade" + role="tooltip"> + <div class="tooltip-arrow"></div> + <div class="tooltip-inner"> + <ng-content></ng-content> + {{ content }} + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less new file mode 100644 index 0000000000..1ff496f840 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less @@ -0,0 +1,11 @@ +.tooltip-inner { + word-wrap: break-word; + max-width: 300px; +} + +.tooltip.bottom .tooltip-arrow { + border-bottom-color: #000 !important; +} + + + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts new file mode 100644 index 0000000000..6e3e8065bb --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts @@ -0,0 +1,195 @@ +import {Component, AfterViewInit, Input, ElementRef, ChangeDetectorRef} from "@angular/core"; + +@Component +({ + selector: "tooltip-content", + templateUrl: "./tooltip-content.component.html", + styleUrls: ["./tooltip-content.component.less"] +}) + +export class TooltipContentComponent implements AfterViewInit { + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input() hostElement: HTMLElement; + @Input() content: string; + @Input() placement: "top"|"bottom"|"left"|"right" = "bottom"; + @Input() animation: boolean = true; + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + top: number = -100000; + left: number = -100000; + isIn: boolean = false; + isFade: boolean = false; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private element: ElementRef, + private cdr: ChangeDetectorRef) { + } + + // ------------------------------------------------------------------------- + // Lifecycle callbacks + // ------------------------------------------------------------------------- + + ngAfterViewInit(): void { + this.show(); + this.cdr.detectChanges(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if(!this.hostElement) { + return; + } + + const position = this.positionElement(this.hostElement, this.element.nativeElement.children[0], this.placement); + this.top = position.top; + this.left = position.left; + this.isIn = true; + if (this.animation) { + this.isFade = true; + } + } + + hide(): void { + this.top = -100000; + this.left = -100000; + this.isIn = true; + if(this.animation) { + this.isFade = false; + } + } + + // ------------------------------------------------------------------------- + // Private Methods + // ------------------------------------------------------------------------- + + private positionElement(hostElem: HTMLElement, targetElem: HTMLElement, positionStr: string, appendToBody: boolean = false): {top: number, left: number} { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElemPosition = appendToBody ? this.offset(hostElem) : this.position(hostElem); + let targetElemWidth = targetElem.offsetWidth; + let targetElemHeight = targetElem.offsetHeight; + let shiftWidth: any = { + center(): number { + return hostElemPosition.left + hostElemPosition.width / 2 - targetElemWidth / 2; + }, + left(): number { + return hostElemPosition.left; + }, + right(): number { + return hostElemPosition.left + hostElemPosition.width; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElemPosition.top + hostElemPosition.height / 2 - targetElemHeight / 2; + }, + top: function (): number { + return hostElemPosition.top; + }, + bottom: function (): number { + return hostElemPosition.top + hostElemPosition.height; + } + } + + let targetElemPosition: {top: number, left: number}; + + switch (pos0) { + case "right": + targetElemPosition = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + + case "left": + targetElemPosition = { + top: shiftHeight[pos1](), + left: hostElemPosition.left - targetElemWidth + }; + break; + + case "bottom": + targetElemPosition = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + + default: + targetElemPosition = { + top: hostElemPosition.top - targetElemHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElemPosition; + } + + + private position(nativeElem: HTMLElement): {width: number, height: number, top: number, left: number} { + let offsetParentCBR = {top: 0, left: 0}; + const elemBCR = this.offset(nativeElem); + const offsetParentElem = this.parentOffsetElem(nativeElem); + if(offsetParentElem !== window.document) { + offsetParentCBR = this.offset(offsetParentElem); + offsetParentCBR.top += offsetParentElem.clientTop - offsetParentElem.scrollTop; + offsetParentCBR.left += offsetParentElem.clientLeft - offsetParentElem.scrollTop; + } + + const boundingClientRect = nativeElem.getBoundingClientRect(); + + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: elemBCR.top - offsetParentCBR.top, + left: elemBCR.left - offsetParentCBR.left + }; + } + + private offset(nativeElem:any): {width: number, height: number, top: number, left: number} { + const boundingClientRect = nativeElem.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + private getStyle(nativeElem: HTMLElement, cssProperty: string): string { + if(window.getComputedStyle) { + return (window.getComputedStyle(nativeElem) as any)[cssProperty]; + } + + return (nativeElem.style as any)[cssProperty]; + } + + private isStaticPositioned(nativeElem: HTMLElement): boolean { + return (this.getStyle(nativeElem, "position") || "static") === "static"; + } + + private parentOffsetElem(nativeElem: HTMLElement): any { + let offsetParent: any = nativeElem.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || window.document; + } +} diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts new file mode 100644 index 0000000000..e98b69003e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts @@ -0,0 +1,81 @@ +import { + Directive, ComponentRef, ViewContainerRef, ComponentFactoryResolver, Input, HostListener +} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; + +@Directive ({ + selector: "[tooltip]" +}) +export class TooltipComponent { + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + private tooltip: ComponentRef<TooltipContentComponent>; + private visible: boolean; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private viewContainerRef: ViewContainerRef, + private resolver: ComponentFactoryResolver) { + } + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input("tooltip") content: string|TooltipContentComponent; + @Input() tooltipDisabled: boolean; + @Input() tooltipAnimation: boolean = true; + @Input() tooltipPlacement: "top"|"bottom"|"left"|"right" = "bottom"; + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + @HostListener("mouseenter") + show(): void { + if(this.tooltipDisabled || this.visible || this.content === "") { + return; + } + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(TooltipContentComponent); + if (!this.visible) { + return; + } + + this.tooltip = this.viewContainerRef.createComponent(factory); + this.tooltip.instance.hostElement = this.viewContainerRef.element.nativeElement; + this.tooltip.instance.content = this.content as string; + this.tooltip.instance.placement = this.tooltipPlacement; + this.tooltip.instance.animation = this.tooltipAnimation; + } else { + const tooltip = this.content as TooltipContentComponent; + tooltip.hostElement = this.viewContainerRef.element.nativeElement; + tooltip.placement = this.tooltipPlacement; + tooltip.animation = this.tooltipAnimation; + tooltip.show(); + } + } + + @HostListener("mouseleave") + hide(): void { + if (!this.visible) { + return; + } + + this.visible = false; + if (this.tooltip) { + this.tooltip.destroy(); + } + if (this.content instanceof TooltipContentComponent) { + (this.content as TooltipContentComponent).hide(); + } + } +} + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts new file mode 100644 index 0000000000..69976da6af --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts @@ -0,0 +1,25 @@ +import {NgModule} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; +import {TooltipComponent} from "./tooltip.component"; +import {CommonModule} from "@angular/common"; + +@NgModule({ + declarations: [ + TooltipComponent, + TooltipContentComponent, + ], + imports: [ + CommonModule + ], + exports: [ + TooltipComponent, + TooltipContentComponent, + ], + entryComponents: [ + TooltipContentComponent + ], + providers: [] +}) +export class TooltipModule { + +} |