├── .gitignore ├── README.md ├── gulpfile.js ├── gulpfile.ts ├── package.json ├── resources └── tooltip-example.png ├── sample └── sample1-simple-usage │ ├── index.html │ └── main.ts ├── src ├── Tooltip.ts ├── TooltipContent.ts └── index.ts ├── tsconfig-aot.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | typings/ 2 | build/ 3 | node_modules 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This repository is for demonstration purposes of how it can be implemented in Angular and is not maintaned. Please fork and maintain your own version of this repository. 2 | 3 | # ngx-tooltip 4 | 5 | Simple tooltip control for your angular2 applications using bootstrap3. Does not depend of jquery. 6 | If you want to use it without bootstrap - simply create proper css classes. Please star a project if you liked it, or create an issue if you have problems with it. 7 | 8 | ![angular 2 tooltip](https://raw.githubusercontent.com/pleerock/ngx-tooltip/master/resources/tooltip-example.png) 9 | 10 | ## Installation 11 | 12 | 1. Install npm module: 13 | 14 | `npm install ngx-tooltip --save` 15 | 16 | 2. If you are using system.js you may want to add this into `map` and `package` config: 17 | 18 | ```json 19 | { 20 | "map": { 21 | "ngx-tooltip": "node_modules/ngx-tooltip" 22 | }, 23 | "packages": { 24 | "ngx-tooltip": { "main": "index.js", "defaultExtension": "js" } 25 | } 26 | } 27 | ``` 28 | 29 | ## Usage 30 | 31 | Example of simple usage: 32 | 33 | ```html 34 | 38 | element on which this tooltip is applied. 39 | 40 | ``` 41 | 42 | Example of usage with dynamic html content: 43 | 44 | ```html 45 | 46 | Very Dynamic Reusable 47 | Tooltip With Html support. 48 | 49 | 50 | 51 | ``` 52 | 53 | * ``: 54 | * `tooltip="string"` The message to be shown in the tooltip. 55 | * `[tooltipDisabled]="true|false"` Indicates if tooltip should be disabled. If tooltip is disabled then it will not be shown. Default is **false** 56 | * `[tooltipAnimation]="true|false"` Indicates if all tooltip should be shown with animation or not. Default is **true**. 57 | * `tooltipPlacement="top|bottom|left|right"` Indicates where the tooltip should be placed. Default is **"bottom"**. 58 | * ``: 59 | * `[animation]="true|false"` Indicates if all tooltip should be shown with animation or not. Default is **true**. 60 | * `placement="top|bottom|left|right"` Indicates where the tooltip should be placed. Default is **"bottom"**. 61 | 62 | ## Sample 63 | 64 | ```typescript 65 | import {Component} from "@angular/core"; 66 | import {TooltipModule} from "ngx-tooltip"; 67 | 68 | @Component({ 69 | selector: "app", 70 | template: ` 71 |
72 | 73 | 74 |

75 | It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. 76 | The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. 77 | Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. 78 | Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like) 79 |

80 | 81 | 82 |
83 | 84 | Very Dynamic Reusable 85 | Tooltip With Html support. 86 | 87 | 88 | 89 |
90 | 91 |
92 | ` 93 | }) 94 | export class App { 95 | 96 | } 97 | 98 | @NgModule({ 99 | imports: [ 100 | // ... 101 | TooltipModule 102 | ], 103 | declarations: [ 104 | App 105 | ], 106 | bootstrap: [ 107 | App 108 | ] 109 | }) 110 | export class AppModule { 111 | 112 | } 113 | ``` 114 | 115 | Take a look on samples in [./sample](https://github.com/pleerock/ngx-tooltip/tree/master/sample) for more examples of 116 | usages. 117 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | eval(require("typescript").transpile(require("fs").readFileSync("./gulpfile.ts").toString())); -------------------------------------------------------------------------------- /gulpfile.ts: -------------------------------------------------------------------------------- 1 | import {Gulpclass, Task, SequenceTask, MergedTask} from "gulpclass"; 2 | 3 | const gulp = require("gulp"); 4 | const del = require("del"); 5 | const shell = require("gulp-shell"); 6 | const replace = require("gulp-replace"); 7 | const mocha = require("gulp-mocha"); 8 | const chai = require("chai"); 9 | const tslint = require("gulp-tslint"); 10 | const stylish = require("tslint-stylish"); 11 | const ts = require("gulp-typescript"); 12 | const rename = require("gulp-rename"); 13 | const file = require("gulp-file"); 14 | const uglify = require("gulp-uglify"); 15 | 16 | const packageName = require("./package.json").name; 17 | 18 | @Gulpclass() 19 | export class Gulpfile { 20 | 21 | // ------------------------------------------------------------------------- 22 | // General tasks 23 | // ------------------------------------------------------------------------- 24 | 25 | /** 26 | * Cleans build folder. 27 | */ 28 | @Task() 29 | clean(cb: Function) { 30 | return del(["./build/**"], cb); 31 | } 32 | 33 | /** 34 | * Runs typescript files compilation. 35 | */ 36 | @Task() 37 | compile() { 38 | return gulp.src("package.json", { read: false }) 39 | .pipe(shell([ 40 | "\"node_modules/.bin/ngc\" -p tsconfig-aot.json" 41 | ])); 42 | } 43 | 44 | // ------------------------------------------------------------------------- 45 | // Packaging and Publishing tasks 46 | // ------------------------------------------------------------------------- 47 | 48 | /** 49 | * Compiles and compiles bundles. 50 | */ 51 | @MergedTask() 52 | compileBundles() { 53 | const amdTsProject = ts.createProject("tsconfig.json", { 54 | module: "amd", 55 | outFile: packageName + ".amd.js", 56 | typescript: require("typescript") 57 | }); 58 | const systemTsProject = ts.createProject("tsconfig.json", { 59 | module: "system", 60 | outFile: packageName + ".system.js", 61 | typescript: require("typescript") 62 | }); 63 | const amdPureTsProject = ts.createProject("tsconfig.json", { 64 | module: "amd", 65 | outFile: packageName + ".pure.amd.js", 66 | noEmitHelpers: true, 67 | noImplicitUseStrict: true, 68 | typescript: require("typescript") 69 | }); 70 | const systemPureTsProject = ts.createProject("tsconfig.json", { 71 | module: "system", 72 | outFile: packageName + ".pure.system.js", 73 | noEmitHelpers: true, 74 | noImplicitUseStrict: true, 75 | typescript: require("typescript") 76 | }); 77 | 78 | return [ 79 | gulp.src("build/bundle/**/*.ts") 80 | .pipe(amdTsProject()).js 81 | .pipe(gulp.dest("build/package")), 82 | 83 | gulp.src("build/bundle/**/*.ts") 84 | .pipe(systemTsProject()).js 85 | .pipe(gulp.dest("build/package")), 86 | 87 | gulp.src("build/bundle/**/*.ts") 88 | .pipe(amdPureTsProject()).js 89 | .pipe(gulp.dest("build/package")), 90 | 91 | gulp.src("build/bundle/**/*.ts") 92 | .pipe(systemPureTsProject()).js 93 | .pipe(gulp.dest("build/package")) 94 | ]; 95 | } 96 | 97 | /** 98 | * Copies all source files into destination folder in a correct structure to build bundles. 99 | */ 100 | @Task() 101 | bundleCopySources() { 102 | return gulp.src(["./src/**/*.ts"]) 103 | .pipe(gulp.dest("./build/bundle/" + packageName)); 104 | } 105 | 106 | /** 107 | * Creates special main file for bundle build. 108 | */ 109 | @Task() 110 | bundleCopyMainFile() { 111 | return gulp.src("./package.json", { read: false }) 112 | .pipe(file(packageName + ".ts", `export * from "./${packageName}/index";`)) 113 | .pipe(gulp.dest("./build/bundle")); 114 | } 115 | 116 | /** 117 | * Uglifys bundles. 118 | */ 119 | @MergedTask() 120 | uglify() { 121 | return [ 122 | gulp.src(`./build/package/${packageName}.pure.amd.js`) 123 | .pipe(uglify()) 124 | .pipe(rename(`${packageName}.pure.amd.min.js`)) 125 | .pipe(gulp.dest("./build/package")), 126 | 127 | gulp.src(`./build/package/${packageName}.pure.system.js`) 128 | .pipe(uglify()) 129 | .pipe(rename(`${packageName}.pure.system.min.js`)) 130 | .pipe(gulp.dest("./build/package")), 131 | 132 | gulp.src(`./build/package/${packageName}.amd.js`) 133 | .pipe(uglify()) 134 | .pipe(rename(`${packageName}.amd.min.js`)) 135 | .pipe(gulp.dest("./build/package")), 136 | 137 | gulp.src(`./build/package/${packageName}.system.js`) 138 | .pipe(uglify()) 139 | .pipe(rename(`${packageName}.system.min.js`)) 140 | .pipe(gulp.dest("./build/package")), 141 | ]; 142 | } 143 | 144 | /** 145 | * Publishes a package to npm from ./build/package directory. 146 | */ 147 | @Task() 148 | npmPublish() { 149 | return gulp.src("package.json", { read: false }) 150 | .pipe(shell([ 151 | "cd ./build/package && npm publish" 152 | ])); 153 | } 154 | 155 | /** 156 | * Change the "private" state of the packaged package.json file to public. 157 | */ 158 | @Task() 159 | packagePreparePackageFile() { 160 | return gulp.src("./package.json") 161 | .pipe(replace("\"private\": true,", "\"private\": false,")) 162 | .pipe(gulp.dest("./build/package")); 163 | } 164 | 165 | /** 166 | * This task will replace all typescript code blocks in the README (since npm does not support typescript syntax 167 | * highlighting) and copy this README file into the package folder. 168 | */ 169 | @Task() 170 | packageReadmeFile() { 171 | return gulp.src("./README.md") 172 | .pipe(replace(/```typescript([\s\S]*?)```/g, "```javascript$1```")) 173 | .pipe(gulp.dest("./build/package")); 174 | } 175 | 176 | /** 177 | * Creates a package that can be published to npm. 178 | */ 179 | @SequenceTask() 180 | package() { 181 | return [ 182 | "clean", 183 | ["bundleCopySources", "bundleCopyMainFile"], 184 | ["compile", "compileBundles"], 185 | ["uglify"], 186 | ["packagePreparePackageFile", "packageReadmeFile"] 187 | ]; 188 | } 189 | 190 | /** 191 | * Creates a package and publishes it to npm. 192 | */ 193 | @SequenceTask() 194 | publish() { 195 | return ["package", "npmPublish"]; 196 | } 197 | 198 | // ------------------------------------------------------------------------- 199 | // Run tests tasks 200 | // ------------------------------------------------------------------------- 201 | 202 | /** 203 | * Runs ts linting to validate source code. 204 | */ 205 | @Task() 206 | tslint() { 207 | return gulp.src(["./src/**/*.ts", "./test/**/*.ts", "./sample/**/*.ts"]) 208 | .pipe(tslint()) 209 | .pipe(tslint.report(stylish, { 210 | emitError: true, 211 | sort: true, 212 | bell: true 213 | })); 214 | } 215 | 216 | /** 217 | * Runs unit-tests. 218 | */ 219 | @Task() 220 | unit() { 221 | chai.should(); 222 | chai.use(require("sinon-chai")); 223 | return gulp.src("./build/es5/test/unit/**/*.js") 224 | .pipe(mocha()); 225 | } 226 | 227 | /** 228 | * Compiles the code and runs tests. 229 | */ 230 | @SequenceTask() 231 | tests() { 232 | return ["clean", "compile", "tslint", "unit"]; 233 | } 234 | 235 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-tooltip", 3 | "version": "0.0.9", 4 | "description": "Simple tooltip control for your angular2 applications using bootstrap3.", 5 | "license": "MIT", 6 | "readmeFilename": "README.md", 7 | "private": true, 8 | "author": { 9 | "name": "Umed Khudoiberdiev", 10 | "email": "pleerock.me@gmail.com" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/pleerock/ngx-tooltip.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/pleerock/ngx-tooltip/issues" 18 | }, 19 | "tags": [ 20 | "tooltip", 21 | "angular2", 22 | "typescript", 23 | "angular2-tooltip" 24 | ], 25 | "peerDependencies": { 26 | "@angular/core": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "@angular/common": "^2.4.3", 30 | "@angular/compiler": "^2.4.3", 31 | "@angular/compiler-cli": "^2.4.3", 32 | "@angular/core": "^2.4.3", 33 | "@angular/forms": "^2.4.3", 34 | "@angular/platform-browser": "^2.4.3", 35 | "@angular/platform-browser-dynamic": "^2.4.3", 36 | 37 | "@types/node": "^7.0.0", 38 | "bootstrap": "^3.3.7", 39 | "chai": "^3.4.1", 40 | "del": "^2.2.2", 41 | "es6-shim": "^0.35.2", 42 | "gulp": "^3.9.0", 43 | "gulp-file": "^0.3.0", 44 | "gulp-mocha": "^3.0.1", 45 | "gulp-rename": "^1.2.2", 46 | "gulp-replace": "^0.5.4", 47 | "gulp-shell": "^0.5.1", 48 | "gulp-tslint": "^7.0.1", 49 | "gulp-typescript": "^3.1.4", 50 | "gulp-uglify": "^2.0.0", 51 | "gulpclass": "^0.1.1", 52 | "mocha": "^3.2.0", 53 | "reflect-metadata": "0.1.9", 54 | "rxjs": "^5.0.3", 55 | "sinon": "^1.17.7", 56 | "sinon-chai": "^2.8.0", 57 | "systemjs": "0.19.42", 58 | "tslint": "^4.3.1", 59 | "tslint-stylish": "^2.1.0-beta", 60 | "typescript": "^2.1.5", 61 | "zone.js": "^0.7.6" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /resources/tooltip-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pleerock/ngx-tooltip/1f93800d737d6bfb5cfe4d0e0a9ee16627e56ba6/resources/tooltip-example.png -------------------------------------------------------------------------------- /sample/sample1-simple-usage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ngx-tooltip 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | Loading... 67 | 68 | -------------------------------------------------------------------------------- /sample/sample1-simple-usage/main.ts: -------------------------------------------------------------------------------- 1 | import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; 2 | import {Component, NgModule} from "@angular/core"; 3 | import {BrowserModule} from "@angular/platform-browser"; 4 | import {TooltipModule} from "../../src/index"; 5 | 6 | @Component({ 7 | selector: "app", 8 | template: ` 9 |
10 | 11 | 12 |

13 | It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. 14 | The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. 15 | Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. 16 | Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like) 17 |

18 | 19 | 20 |
21 | 22 | Very Dynamic Reusable 23 | Tooltip With Html support. 24 | 25 | 26 | 27 |
28 | 29 |
30 | ` 31 | }) 32 | export class Sample1App { 33 | 34 | } 35 | 36 | @NgModule({ 37 | imports: [ 38 | BrowserModule, 39 | TooltipModule 40 | ], 41 | declarations: [ 42 | Sample1App 43 | ], 44 | bootstrap: [ 45 | Sample1App 46 | ] 47 | }) 48 | export class Sample1Module { 49 | 50 | } 51 | 52 | platformBrowserDynamic().bootstrapModule(Sample1Module); -------------------------------------------------------------------------------- /src/Tooltip.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, HostListener, ComponentRef, ViewContainerRef, Input, ComponentFactoryResolver, 3 | ComponentFactory 4 | } from "@angular/core"; 5 | import {TooltipContent} from "./TooltipContent"; 6 | 7 | @Directive({ 8 | selector: "[tooltip]" 9 | }) 10 | export class Tooltip { 11 | 12 | // ------------------------------------------------------------------------- 13 | // Properties 14 | // ------------------------------------------------------------------------- 15 | 16 | private tooltip: ComponentRef; 17 | private visible: boolean; 18 | 19 | // ------------------------------------------------------------------------- 20 | // Constructor 21 | // ------------------------------------------------------------------------- 22 | 23 | constructor(private viewContainerRef: ViewContainerRef, 24 | private resolver: ComponentFactoryResolver) { 25 | } 26 | 27 | // ------------------------------------------------------------------------- 28 | // Inputs / Outputs 29 | // ------------------------------------------------------------------------- 30 | 31 | @Input("tooltip") 32 | content: string|TooltipContent; 33 | 34 | @Input() 35 | tooltipDisabled: boolean; 36 | 37 | @Input() 38 | tooltipAnimation: boolean = true; 39 | 40 | @Input() 41 | tooltipPlacement: "top"|"bottom"|"left"|"right" = "bottom"; 42 | 43 | // ------------------------------------------------------------------------- 44 | // Public Methods 45 | // ------------------------------------------------------------------------- 46 | 47 | @HostListener("focusin") 48 | @HostListener("mouseenter") 49 | show(): void { 50 | if (this.tooltipDisabled || this.visible) 51 | return; 52 | 53 | this.visible = true; 54 | if (typeof this.content === "string") { 55 | const factory = this.resolver.resolveComponentFactory(TooltipContent); 56 | if (!this.visible) 57 | return; 58 | 59 | this.tooltip = this.viewContainerRef.createComponent(factory); 60 | this.tooltip.instance.hostElement = this.viewContainerRef.element.nativeElement; 61 | this.tooltip.instance.content = this.content as string; 62 | this.tooltip.instance.placement = this.tooltipPlacement; 63 | this.tooltip.instance.animation = this.tooltipAnimation; 64 | } else { 65 | const tooltip = this.content as TooltipContent; 66 | tooltip.hostElement = this.viewContainerRef.element.nativeElement; 67 | tooltip.placement = this.tooltipPlacement; 68 | tooltip.animation = this.tooltipAnimation; 69 | tooltip.show(); 70 | } 71 | } 72 | 73 | @HostListener("focusout") 74 | @HostListener("mouseleave") 75 | hide(): void { 76 | if (!this.visible) 77 | return; 78 | 79 | this.visible = false; 80 | if (this.tooltip) 81 | this.tooltip.destroy(); 82 | 83 | if (this.content instanceof TooltipContent) 84 | (this.content as TooltipContent).hide(); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/TooltipContent.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, AfterViewInit, ElementRef, ChangeDetectorRef} from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "tooltip-content", 5 | template: ` 6 | 18 | ` 19 | }) 20 | export class TooltipContent implements AfterViewInit { 21 | 22 | // ------------------------------------------------------------------------- 23 | // Inputs / Outputs 24 | // ------------------------------------------------------------------------- 25 | 26 | @Input() 27 | hostElement: HTMLElement; 28 | 29 | @Input() 30 | content: string; 31 | 32 | @Input() 33 | placement: "top"|"bottom"|"left"|"right" = "bottom"; 34 | 35 | @Input() 36 | animation: boolean = true; 37 | 38 | // ------------------------------------------------------------------------- 39 | // Properties 40 | // ------------------------------------------------------------------------- 41 | 42 | top: number = -100000; 43 | left: number = -100000; 44 | isIn: boolean = false; 45 | isFade: boolean = false; 46 | 47 | // ------------------------------------------------------------------------- 48 | // Constructor 49 | // ------------------------------------------------------------------------- 50 | 51 | constructor(private element: ElementRef, 52 | private cdr: ChangeDetectorRef) { 53 | } 54 | 55 | // ------------------------------------------------------------------------- 56 | // Lifecycle callbacks 57 | // ------------------------------------------------------------------------- 58 | 59 | ngAfterViewInit(): void { 60 | this.show(); 61 | this.cdr.detectChanges(); 62 | } 63 | 64 | // ------------------------------------------------------------------------- 65 | // Public Methods 66 | // ------------------------------------------------------------------------- 67 | 68 | show(): void { 69 | if (!this.hostElement) 70 | return; 71 | 72 | const p = this.positionElements(this.hostElement, this.element.nativeElement.children[0], this.placement); 73 | this.top = p.top; 74 | this.left = p.left; 75 | this.isIn = true; 76 | if (this.animation) 77 | this.isFade = true; 78 | } 79 | 80 | hide(): void { 81 | this.top = -100000; 82 | this.left = -100000; 83 | this.isIn = true; 84 | if (this.animation) 85 | this.isFade = false; 86 | } 87 | 88 | // ------------------------------------------------------------------------- 89 | // Private Methods 90 | // ------------------------------------------------------------------------- 91 | 92 | private positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { 93 | let positionStrParts = positionStr.split("-"); 94 | let pos0 = positionStrParts[0]; 95 | let pos1 = positionStrParts[1] || "center"; 96 | let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); 97 | let targetElWidth = targetEl.offsetWidth; 98 | let targetElHeight = targetEl.offsetHeight; 99 | let shiftWidth: any = { 100 | center: function (): number { 101 | return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; 102 | }, 103 | left: function (): number { 104 | return hostElPos.left; 105 | }, 106 | right: function (): number { 107 | return hostElPos.left + hostElPos.width; 108 | } 109 | }; 110 | 111 | let shiftHeight: any = { 112 | center: function (): number { 113 | return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; 114 | }, 115 | top: function (): number { 116 | return hostElPos.top; 117 | }, 118 | bottom: function (): number { 119 | return hostElPos.top + hostElPos.height; 120 | } 121 | }; 122 | 123 | let targetElPos: { top: number, left: number }; 124 | switch (pos0) { 125 | case "right": 126 | targetElPos = { 127 | top: shiftHeight[pos1](), 128 | left: shiftWidth[pos0]() 129 | }; 130 | break; 131 | 132 | case "left": 133 | targetElPos = { 134 | top: shiftHeight[pos1](), 135 | left: hostElPos.left - targetElWidth 136 | }; 137 | break; 138 | 139 | case "bottom": 140 | targetElPos = { 141 | top: shiftHeight[pos0](), 142 | left: shiftWidth[pos1]() 143 | }; 144 | break; 145 | 146 | default: 147 | targetElPos = { 148 | top: hostElPos.top - targetElHeight, 149 | left: shiftWidth[pos1]() 150 | }; 151 | break; 152 | } 153 | 154 | return targetElPos; 155 | } 156 | 157 | private position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } { 158 | let offsetParentBCR = { top: 0, left: 0 }; 159 | const elBCR = this.offset(nativeEl); 160 | const offsetParentEl = this.parentOffsetEl(nativeEl); 161 | if (offsetParentEl !== window.document) { 162 | offsetParentBCR = this.offset(offsetParentEl); 163 | offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; 164 | offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; 165 | } 166 | 167 | const boundingClientRect = nativeEl.getBoundingClientRect(); 168 | return { 169 | width: boundingClientRect.width || nativeEl.offsetWidth, 170 | height: boundingClientRect.height || nativeEl.offsetHeight, 171 | top: elBCR.top - offsetParentBCR.top, 172 | left: elBCR.left - offsetParentBCR.left 173 | }; 174 | } 175 | 176 | private offset(nativeEl:any): { width: number, height: number, top: number, left: number } { 177 | const boundingClientRect = nativeEl.getBoundingClientRect(); 178 | return { 179 | width: boundingClientRect.width || nativeEl.offsetWidth, 180 | height: boundingClientRect.height || nativeEl.offsetHeight, 181 | top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), 182 | left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) 183 | }; 184 | } 185 | 186 | private getStyle(nativeEl: HTMLElement, cssProp: string): string { 187 | if ((nativeEl as any).currentStyle) // IE 188 | return (nativeEl as any).currentStyle[cssProp]; 189 | 190 | if (window.getComputedStyle) 191 | return (window.getComputedStyle(nativeEl) as any)[cssProp]; 192 | 193 | // finally try and get inline style 194 | return (nativeEl.style as any)[cssProp]; 195 | } 196 | 197 | private isStaticPositioned(nativeEl: HTMLElement): boolean { 198 | return (this.getStyle(nativeEl, "position") || "static" ) === "static"; 199 | } 200 | 201 | private parentOffsetEl(nativeEl: HTMLElement): any { 202 | let offsetParent: any = nativeEl.offsetParent || window.document; 203 | while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { 204 | offsetParent = offsetParent.offsetParent; 205 | } 206 | return offsetParent || window.document; 207 | } 208 | 209 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from "@angular/core"; 2 | import {CommonModule} from "@angular/common"; 3 | import {Tooltip} from "./Tooltip"; 4 | import {TooltipContent} from "./TooltipContent"; 5 | 6 | export * from "./Tooltip"; 7 | export * from "./TooltipContent"; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule 12 | ], 13 | declarations: [ 14 | Tooltip, 15 | TooltipContent, 16 | ], 17 | exports: [ 18 | Tooltip, 19 | TooltipContent, 20 | ], 21 | entryComponents: [ 22 | TooltipContent 23 | ] 24 | }) 25 | export class TooltipModule { 26 | 27 | } -------------------------------------------------------------------------------- /tsconfig-aot.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/package", 4 | "target": "es5", 5 | "lib": ["es6", "dom"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "sourceMap": true, 11 | "noEmitHelpers": false, 12 | "noImplicitAny": true, 13 | "declaration": true, 14 | "skipLibCheck": true, 15 | "stripInternal": true 16 | }, 17 | "exclude": [ 18 | "build", 19 | "node_modules" 20 | ], 21 | "files": [ 22 | "./src/index" 23 | ], 24 | "angularCompilerOptions": { 25 | "genDir": "build/factories" 26 | } 27 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.3", 3 | "compilerOptions": { 4 | "outDir": "build/es5", 5 | "target": "es5", 6 | "lib": ["es6", "dom"], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "sourceMap": true, 12 | "noImplicitAny": true, 13 | "declaration": true 14 | }, 15 | "exclude": [ 16 | "build", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-internal-module": true, 15 | "no-trailing-whitespace": true, 16 | "no-var-keyword": true, 17 | "one-line": [ 18 | true, 19 | "check-open-brace", 20 | "check-whitespace" 21 | ], 22 | "quotemark": [ 23 | true, 24 | "double" 25 | ], 26 | "semicolon": true, 27 | "triple-equals": [ 28 | true, 29 | "allow-null-check" 30 | ], 31 | "typedef-whitespace": [ 32 | true, 33 | { 34 | "call-signature": "nospace", 35 | "index-signature": "nospace", 36 | "parameter": "nospace", 37 | "property-declaration": "nospace", 38 | "variable-declaration": "nospace" 39 | } 40 | ], 41 | "variable-name": [ 42 | true, 43 | "ban-keywords" 44 | ], 45 | "whitespace": [ 46 | true, 47 | "check-branch", 48 | "check-decl", 49 | "check-operator", 50 | "check-separator", 51 | "check-type" 52 | ] 53 | } 54 | } --------------------------------------------------------------------------------