├── .npmignore ├── template.html ├── .gitignore ├── Makefile ├── tip.css ├── component.json ├── package.json ├── test ├── auto.html └── index.html ├── Readme.md ├── History.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /components 3 | /build 4 | 5 | test/*.js 6 | test/*.css 7 | 8 | # root level development temp files 9 | /?.html 10 | /?.js 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: tip.css index.js template.html components 3 | @component build --dev 4 | 5 | components: component.json 6 | @component install --dev 7 | 8 | clean: 9 | rm -fr build components 10 | 11 | test: build 12 | @open test/index.html 13 | 14 | .PHONY: clean test 15 | -------------------------------------------------------------------------------- /tip.css: -------------------------------------------------------------------------------- 1 | .tip { 2 | position: absolute; 3 | padding: 5px; 4 | z-index: 1000; 5 | /* default offset for edge-cases: https://github.com/component/tip/pull/12 */ 6 | top: 0; 7 | left: 0; 8 | } 9 | 10 | /* effects */ 11 | 12 | .tip.fade { 13 | transition: opacity 100ms; 14 | -moz-transition: opacity 100ms; 15 | -webkit-transition: opacity 100ms; 16 | } 17 | 18 | .tip-hide { 19 | opacity: 0; 20 | } 21 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tip", 3 | "description": "Tip component", 4 | "version": "2.4.1", 5 | "keywords": [ 6 | "browser", 7 | "component", 8 | "tooltip", 9 | "tip", 10 | "ui" 11 | ], 12 | "dependencies": { 13 | "component/bind": "*", 14 | "component/emitter": "1.1.3", 15 | "component/query": "0.0.3", 16 | "component/events": "1.0.9", 17 | "component/domify": "1.3.0", 18 | "component/classes": "1.2.1", 19 | "component/css": "0.0.6", 20 | "webmodules/bounding-client-rect": "1.0.5" 21 | }, 22 | "development": { 23 | "component/aurora-tip": "*", 24 | "component/event": "*" 25 | }, 26 | "scripts": [ 27 | "index.js" 28 | ], 29 | "styles": [ 30 | "tip.css" 31 | ], 32 | "templates": [ 33 | "template.html" 34 | ], 35 | "demo": [ 36 | "http://component.github.io/tip/" 37 | ], 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-tip", 3 | "description": "Tip component", 4 | "version": "2.4.1", 5 | "keywords": [ 6 | "browser", 7 | "component", 8 | "tooltip", 9 | "tip", 10 | "ui" 11 | ], 12 | "dependencies": { 13 | "bounding-client-rect": "1.0.5", 14 | "component-bind": "*", 15 | "component-classes": "1.2.1", 16 | "component-css": "0.0.6", 17 | "component-emitter": "1.1.3", 18 | "component-events": "1.0.9", 19 | "component-query": "0.0.3", 20 | "domify": "1.3.0", 21 | "stringify": "^3.1.0" 22 | }, 23 | "browserify": { 24 | "transform": [ 25 | "stringify" 26 | ] 27 | }, 28 | "browser": { 29 | "bind": "component-bind", 30 | "classes": "component-classes", 31 | "css": "component-css", 32 | "emitter": "component-emitter", 33 | "events": "component-events", 34 | "query": "component-query" 35 | }, 36 | "style": "tip.css", 37 | "component": { 38 | "styles": [ 39 | "tip.css" 40 | ], 41 | "templates": [ 42 | "template.html" 43 | ], 44 | "scripts": { 45 | "tip/index.js": "index.js" 46 | } 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/component/tip.git" 51 | }, 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /test/auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tip 5 | 6 | 7 | 32 | 33 | 34 | Tip 35 | 36 | 39 | 47 | 48 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Tip 2 | 3 | Tip component. Inspired by [tipsy](https://github.com/jaz303/tipsy) without the weird jQuery 4 | API. 5 | 6 | ![js tip component](http://f.cl.ly/items/2H1D232Y0g1T3g1G0l3s/Screen%20Shot%202012-08-02%20at%202.31.50%20PM.png) 7 | ![js tip with markup](http://f.cl.ly/items/2h1F2B1P1C3M0g0a0M0n/Screen%20Shot%202012-08-02%20at%203.34.06%20PM.png) 8 | 9 | ![js maru](http://f.cl.ly/items/1I2V2o0q3M2p1E2H183w/Screen%20Shot%202012-08-02%20at%206.48.28%20PM.png) 10 | 11 | Live demo is [here](http://component.github.io/tip/). 12 | 13 | ## Installation 14 | 15 | ``` bash 16 | $ npm install component-tip 17 | ``` 18 | 19 | ## Features 20 | 21 | - events for composition 22 | - "auto" positioning on window resize / scroll 23 | - fluent API 24 | 25 | ## Events 26 | 27 | - `show` the tip is shown 28 | - `hide` the tip is hidden 29 | 30 | ## API 31 | 32 | ### Tip(el, string) 33 | 34 | Equivalent to `Tip(el, { value: string })`. 35 | 36 | ### Tip(el, [options]) 37 | 38 | Attach a `Tip` to an element, and display the `title` 39 | attribute's contents on hover. Optionally apply a hide `delay` 40 | in milliseconds. 41 | 42 | ```js 43 | var tip = require('tip'); 44 | tip('a[title]', { delay: 300 }); 45 | ``` 46 | 47 | ### new Tip(content) 48 | 49 | Create a new tip with `content` being 50 | either a string, html, element, etc. 51 | 52 | ```js 53 | var Tip = require('tip'); 54 | var tip = new Tip('Hello!'); 55 | tip.show('#mylink'); 56 | ``` 57 | 58 | ### Tip#position(type, [options]) 59 | 60 | - `top` 61 | - `top right` 62 | - `top left` 63 | - `bottom` 64 | - `bottom right` 65 | - `bottom left` 66 | - `right` 67 | - `left` 68 | 69 | Options: 70 | 71 | - `auto` set to __false__ to disable auto-positioning 72 | 73 | ### Tip#show(el) 74 | 75 | Show the tip attached to `el`, where `el` 76 | may be a selector or element. 77 | 78 | ### Tip#show(x, y) 79 | 80 | Show the tip at the absolute position `(x, y)`. 81 | 82 | ### Tip#hide([ms]) 83 | 84 | Hide the tip immediately or wait `ms`. 85 | 86 | ### Tip#attach(el) 87 | 88 | Attach the tip to the given `el`, showing on `mouseover` and hiding on `mouseout`. 89 | 90 | ### Tip#effect(name) 91 | 92 | Use effect `name`. Default with `Tip.effect = 'fade'` for example. 93 | 94 | ### Themes 95 | 96 | - [Aurora](https://github.com/component/aurora-tip) 97 | - [Nightrider](https://github.com/jb55/nightrider-tip) 98 | 99 | ## License 100 | 101 | MIT 102 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tip 5 | 6 | 7 | 49 | 50 | 51 | 52 |
53 | 54 |
55 | 75 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.4.1 / 2015-02-26 3 | ================== 4 | 5 | * update "bounding-client-rect" to v1.0.5 6 | 7 | 2.4.0 / 2015-01-28 8 | ================== 9 | 10 | * index: add support for `pad` option 11 | 12 | 2.3.4 / 2015-01-20 13 | ================== 14 | 15 | * index: fix "bottom right" (bi-directional) position case in `suggested()` 16 | 17 | 2.3.3 / 2015-01-20 18 | ================== 19 | 20 | * package: update "bounding-client-rect" to v1.0.4 21 | 22 | 2.3.2 / 2015-01-14 23 | ================== 24 | 25 | * use an elimination strategy for the `suggested()` function (#50, @TooTallNate) 26 | 27 | 2.3.1 / 2014-12-17 28 | ================== 29 | 30 | * update "bounding-client-rect" to v1.0.2 31 | 32 | 2.3.0 / 2014-12-16 33 | ================== 34 | 35 | * use "bounding-client-rect" module (#49, @TooTallNate) 36 | * index: rearrange require calls 37 | 38 | 2.2.0 / 2014-12-11 39 | ================== 40 | 41 | * update "document-offset" to v1.0.3 42 | * accept elements and html string for content (#45, @bmcmahen) 43 | * add "hiding" event 44 | 45 | 2.1.2 / 2014-08-01 46 | ================== 47 | 48 | * component, package: update "events" to v1.0.9 49 | 50 | 2.1.1 / 2014-07-14 51 | ================== 52 | 53 | * package: use "document-offset" module 54 | * component: update deps 55 | * package: update deps 56 | 57 | 2.1.0 / 2014-06-21 58 | ================== 59 | 60 | * index: use dimensions() to get the `target` dims 61 | * index: add `dimensions()` calculation function 62 | * index: fix @api JSDoc for remove() 63 | * test: remove redundant `` element 64 | * package: update "timoxley-offset" to v1.0.1 65 | 66 | 2.0.0 / 2014-05-27 67 | ================== 68 | 69 | * gitignore: ignore root level development temp files 70 | * component, package: update "css" and "events" 71 | * package: initial browserify support 72 | * package: make dependencies be equivalent to component.json 73 | * package: update name to "component-tip" 74 | * Readme: update Installation section 75 | * component: add "browser" keyword 76 | 77 | 1.0.3 / 2014-04-08 78 | ================== 79 | 80 | * improve binding of 'scroll' and 'resize' handlers 81 | 82 | 1.0.2 / 2014-04-08 83 | ================== 84 | 85 | * fix Tip position for elements with borders 86 | * add live demo links 87 | * remove jquery from examples 88 | * remove component-convert step 89 | * update pinned dependencies 90 | 91 | 1.0.1 / 2014-04-07 92 | ================== 93 | 94 | * update `component/events` 95 | * instantiate Tip with options 96 | * pin `component/css@0.0.4` 97 | * add attach to API documentation 98 | * fix IE8 compatibility 99 | 100 | 1.0.0 / 2014-01-17 101 | ================== 102 | 103 | * change direction keywords 104 | 105 | 0.3.1 / 2014-01-02 106 | ================== 107 | 108 | * emit `reposition` event 109 | * fix setting `undefined` css classname 110 | * update emitter 111 | 112 | 0.3.0 / 2013-10-24 113 | ================== 114 | 115 | * undo 'fix' for cancelOnHide 116 | * removed jquery dep 117 | 118 | 0.2.1 / 2013-05-27 119 | ================== 120 | 121 | * pin deps 122 | * change default position back to above the element 123 | * remove silly inherit dep 124 | * fix for offset bug on 100% pages 125 | 126 | 0.2.0 / 2013-03-25 127 | ================== 128 | 129 | * add .position() .auto option to completely disable auto positioning 130 | 131 | 0.1.5 / 2013-03-05 132 | ================== 133 | 134 | * add explicit Tip#message() call 135 | 136 | 0.1.4 / 2013-02-28 137 | ================== 138 | 139 | * rename .content() to .message() for inheritance bullshit 140 | 141 | 0.1.3 / 2013-02-28 142 | ================== 143 | 144 | * fix backwards positioning 145 | 146 | 0.1.2 / 2013-02-21 147 | ================== 148 | 149 | * add inherit dependency 150 | 151 | 0.1.1 / 2012-12-18 152 | ================== 153 | 154 | * fix .position(), replace class immediately 155 | 156 | 0.1.0 / 2012-12-03 157 | ================== 158 | 159 | * add absolute positioning support via .show(x, y) 160 | * add Tip#cancelHideOnHover() 161 | 162 | 0.0.5 / 2012-08-31 163 | ================== 164 | 165 | * fix hiding of tip when hover back over the target 166 | 167 | 0.0.4 / 2012-08-22 168 | ================== 169 | 170 | * fix unnecessary applying of content on .show() [guille] 171 | 172 | 0.0.3 / 2012-08-22 173 | ================== 174 | 175 | * add `Tip#attach(el, [delay])` 176 | * add `.value` option 177 | * change `Tip#cancelHideOnHover()` to be public 178 | * fix npm template.js usage 179 | 180 | 0.0.2 / 2012-08-22 181 | ================== 182 | 183 | * add `Tip#cancelHideOnHover()` 184 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var css = require('css'); 6 | var bind = require('bind'); 7 | var query = require('query'); 8 | var domify = require('domify'); 9 | var events = require('events'); 10 | var Emitter = require('emitter'); 11 | var classes = require('classes'); 12 | var getBoundingClientRect = require('bounding-client-rect'); 13 | 14 | var html = domify(require('./template.html')); 15 | 16 | /** 17 | * Expose `Tip`. 18 | */ 19 | 20 | module.exports = Tip; 21 | 22 | /** 23 | * Apply the average use-case of simply 24 | * showing a tool-tip on `el` hover. 25 | * 26 | * Options: 27 | * 28 | * - `delay` hide delay in milliseconds [0] 29 | * - `value` defaulting to the element's title attribute 30 | * 31 | * @param {Mixed} elem 32 | * @param {Object|String} options or value 33 | * @api public 34 | */ 35 | 36 | function tip(elem, options) { 37 | if ('string' == typeof options) options = { value : options }; 38 | var els = ('string' == typeof elem) ? query.all(elem) : [elem]; 39 | for(var i = 0, el; el = els[i]; i++) { 40 | var val = options.value || el.getAttribute('title'); 41 | var tip = new Tip(val, options); 42 | el.setAttribute('title', ''); 43 | tip.cancelHideOnHover(); 44 | tip.attach(el); 45 | } 46 | } 47 | 48 | /** 49 | * Initialize a `Tip` with the given `content`. 50 | * 51 | * @param {Mixed} content 52 | * @api public 53 | */ 54 | 55 | function Tip(content, options) { 56 | options = options || {}; 57 | if (!(this instanceof Tip)) return tip(content, options); 58 | Emitter.call(this); 59 | this.classname = ''; 60 | this.delay = options.delay || 300; 61 | this.pad = null == options.pad ? 15 : options.pad; 62 | this.el = html.cloneNode(true); 63 | this.events = events(this.el, this); 64 | this.classes = classes(this.el); 65 | this.inner = query('.tip-inner', this.el); 66 | this.message(content); 67 | this.position('top'); 68 | if (Tip.effect) this.effect(Tip.effect); 69 | } 70 | 71 | /** 72 | * Mixin emitter. 73 | */ 74 | 75 | Emitter(Tip.prototype); 76 | 77 | /** 78 | * Set tip `content`. 79 | * 80 | * @param {String|Element} content 81 | * @return {Tip} self 82 | * @api public 83 | */ 84 | 85 | Tip.prototype.message = function(content){ 86 | if ('string' == typeof content) content = domify(content); 87 | this.inner.appendChild(content); 88 | return this; 89 | }; 90 | 91 | /** 92 | * Attach to the given `el` with optional hide `delay`. 93 | * 94 | * @param {Element} el 95 | * @param {Number} delay 96 | * @return {Tip} 97 | * @api public 98 | */ 99 | 100 | Tip.prototype.attach = function(el){ 101 | this.target = el; 102 | this.handleEvents = events(el, this); 103 | this.handleEvents.bind('mouseover'); 104 | this.handleEvents.bind('mouseout'); 105 | return this; 106 | }; 107 | 108 | /** 109 | * On mouse over 110 | * 111 | * @param {Event} e 112 | * @return {Tip} 113 | * @api private 114 | */ 115 | 116 | Tip.prototype.onmouseover = function() { 117 | this.show(this.target); 118 | this.cancelHide(); 119 | }; 120 | 121 | /** 122 | * On mouse out 123 | * 124 | * @param {Event} e 125 | * @return {Tip} 126 | * @api private 127 | */ 128 | 129 | Tip.prototype.onmouseout = function() { 130 | this.hide(this.delay); 131 | }; 132 | 133 | /** 134 | * Cancel hide on hover, hide with the given `delay`. 135 | * 136 | * @param {Number} delay 137 | * @return {Tip} 138 | * @api public 139 | */ 140 | 141 | Tip.prototype.cancelHideOnHover = function(){ 142 | this.events.bind('mouseover', 'cancelHide'); 143 | this.events.bind('mouseout', 'hide'); 144 | return this; 145 | }; 146 | 147 | /** 148 | * Set the effect to `type`. 149 | * 150 | * @param {String} type 151 | * @return {Tip} 152 | * @api public 153 | */ 154 | 155 | Tip.prototype.effect = function(type){ 156 | this._effect = type; 157 | this.classes.add(type); 158 | return this; 159 | }; 160 | 161 | /** 162 | * Set position: 163 | * 164 | * - `top` 165 | * - `top left` 166 | * - `top right` 167 | * - `bottom` 168 | * - `bottom left` 169 | * - `bottom right` 170 | * - `left` 171 | * - `right` 172 | * 173 | * @param {String} pos 174 | * @param {Object} options 175 | * @return {Tip} 176 | * @api public 177 | */ 178 | 179 | Tip.prototype.position = function(pos, options){ 180 | options = options || {}; 181 | this._position = pos; 182 | this._auto = false != options.auto; 183 | this.replaceClass(pos); 184 | this.emit('reposition'); 185 | return this; 186 | }; 187 | 188 | /** 189 | * Show the tip attached to `el`. 190 | * 191 | * Emits "show" (el) event. 192 | * 193 | * @param {String|Element|Number} el or x 194 | * @param {Number} [y] 195 | * @return {Tip} 196 | * @api public 197 | */ 198 | 199 | Tip.prototype.show = function(el){ 200 | if ('string' == typeof el) el = query(el); 201 | 202 | // show it 203 | document.body.appendChild(this.el); 204 | this.classes.add('tip-' + this._position.replace(/\s+/g, '-')); 205 | this.classes.remove('tip-hide'); 206 | 207 | // x,y 208 | if ('number' == typeof el) { 209 | var x = arguments[0]; 210 | var y = arguments[1]; 211 | this.emit('show'); 212 | css(this.el, { 213 | top: y, 214 | left: x 215 | }); 216 | return this; 217 | } 218 | 219 | // el 220 | this.target = el; 221 | this.reposition(); 222 | this.emit('show', this.target); 223 | 224 | if (!this.winEvents) { 225 | this.winEvents = events(window, this); 226 | this.winEvents.bind('resize', 'reposition'); 227 | this.winEvents.bind('scroll', 'reposition'); 228 | } 229 | 230 | return this; 231 | }; 232 | 233 | /** 234 | * Reposition the tip if necessary. 235 | * 236 | * @api private 237 | */ 238 | 239 | Tip.prototype.reposition = function(){ 240 | var pos = this._position; 241 | var off = this.offset(pos); 242 | var newpos = this._auto && this.suggested(pos, off); 243 | if (newpos && newpos !== pos) { 244 | pos = newpos; 245 | off = this.offset(pos); 246 | } 247 | this.replaceClass(pos); 248 | this.emit('reposition'); 249 | css(this.el, off); 250 | }; 251 | 252 | /** 253 | * Compute the "suggested" position favouring `pos`. 254 | * 255 | * Returns `pos` if no suggestion can be determined. 256 | * 257 | * @param {String} pos 258 | * @param {Object} offset 259 | * @return {String} 260 | * @api private 261 | */ 262 | 263 | Tip.prototype.suggested = function(pos, off){ 264 | var el = this.el; 265 | 266 | var ew = el.clientWidth; 267 | var eh = el.clientHeight; 268 | var top = window.scrollY; 269 | var left = window.scrollX; 270 | var w = window.innerWidth; 271 | var h = window.innerHeight; 272 | 273 | var good = { 274 | top: true, 275 | bottom: true, 276 | left: true, 277 | right: true 278 | }; 279 | 280 | // too low 281 | if (off.top + eh > top + h) good.bottom = false; 282 | 283 | // too high 284 | if (off.top < top) good.top = false; 285 | 286 | // too far to the right 287 | if (off.left + ew > left + w) good.right = false; 288 | 289 | // too far to the left 290 | if (off.left < left) good.left = false; 291 | 292 | var i; 293 | var positions = pos.split(/\s+/); 294 | 295 | // attempt to give the preferred position first, consider "bottom right" 296 | for (i = 0; i < positions.length; i++) { 297 | if (!good[positions[i]]) break; 298 | if (i === positions.length - 1) { 299 | // last one! 300 | return pos; 301 | } 302 | } 303 | 304 | // attempt to get close to preferred position, i.e. "bottom" or "right" 305 | for (i = 0; i < positions.length; i++) { 306 | if (good[positions[i]]) return positions[i]; 307 | } 308 | 309 | if (good[pos]) return pos; 310 | if (good.top) return 'top'; 311 | if (good.bottom) return 'bottom'; 312 | if (good.left) return 'left'; 313 | if (good.right) return 'right'; 314 | }; 315 | 316 | /** 317 | * Replace position class `name`. 318 | * 319 | * @param {String} name 320 | * @api private 321 | */ 322 | 323 | Tip.prototype.replaceClass = function(name){ 324 | name = name.split(' ').join('-'); 325 | var classname = this.classname + ' tip tip-' + name; 326 | if (this._effect) classname += ' ' + this._effect; 327 | this.el.setAttribute('class', classname); 328 | }; 329 | 330 | /** 331 | * Compute the offset for `.target` 332 | * based on the given `pos`. 333 | * 334 | * @param {String} pos 335 | * @return {Object} 336 | * @api private 337 | */ 338 | 339 | Tip.prototype.offset = function(pos){ 340 | var pad = this.pad; 341 | 342 | var tipRect = getBoundingClientRect(this.el); 343 | if (!tipRect) throw new Error('could not get bounding client rect of Tip element'); 344 | var ew = tipRect.width; 345 | var eh = tipRect.height; 346 | 347 | var targetRect = getBoundingClientRect(this.target); 348 | if (!targetRect) throw new Error('could not get bounding client rect of `target`'); 349 | var tw = targetRect.width; 350 | var th = targetRect.height; 351 | 352 | var to = offset(targetRect, document); 353 | if (!to) throw new Error('could not determine page offset of `target`'); 354 | 355 | switch (pos) { 356 | case 'top': 357 | return { 358 | top: to.top - eh, 359 | left: to.left + tw / 2 - ew / 2 360 | } 361 | case 'bottom': 362 | return { 363 | top: to.top + th, 364 | left: to.left + tw / 2 - ew / 2 365 | } 366 | case 'right': 367 | return { 368 | top: to.top + th / 2 - eh / 2, 369 | left: to.left + tw 370 | } 371 | case 'left': 372 | return { 373 | top: to.top + th / 2 - eh / 2, 374 | left: to.left - ew 375 | } 376 | case 'top left': 377 | return { 378 | top: to.top - eh, 379 | left: to.left + tw / 2 - ew + pad 380 | } 381 | case 'top right': 382 | return { 383 | top: to.top - eh, 384 | left: to.left + tw / 2 - pad 385 | } 386 | case 'bottom left': 387 | return { 388 | top: to.top + th, 389 | left: to.left + tw / 2 - ew + pad 390 | } 391 | case 'bottom right': 392 | return { 393 | top: to.top + th, 394 | left: to.left + tw / 2 - pad 395 | } 396 | default: 397 | throw new Error('invalid position "' + pos + '"'); 398 | } 399 | }; 400 | 401 | /** 402 | * Cancel the `.hide()` timeout. 403 | * 404 | * @api private 405 | */ 406 | 407 | Tip.prototype.cancelHide = function(){ 408 | clearTimeout(this._hide); 409 | }; 410 | 411 | /** 412 | * Hide the tip with optional `ms` delay. 413 | * 414 | * Emits "hide" event. 415 | * 416 | * @param {Number} ms 417 | * @return {Tip} 418 | * @api public 419 | */ 420 | 421 | Tip.prototype.hide = function(ms){ 422 | var self = this; 423 | 424 | this.emit('hiding'); 425 | 426 | // duration 427 | if (ms) { 428 | this._hide = setTimeout(bind(this, this.hide), ms); 429 | return this; 430 | } 431 | 432 | // hide 433 | this.classes.add('tip-hide'); 434 | if (this._effect) { 435 | setTimeout(bind(this, this.remove), 300); 436 | } else { 437 | self.remove(); 438 | } 439 | 440 | return this; 441 | }; 442 | 443 | /** 444 | * Hide the tip without potential animation. 445 | * 446 | * @return {Tip} 447 | * @api public 448 | */ 449 | 450 | Tip.prototype.remove = function(){ 451 | if (this.winEvents) { 452 | this.winEvents.unbind(); 453 | this.winEvents = null; 454 | } 455 | this.emit('hide'); 456 | 457 | var parent = this.el.parentNode; 458 | if (parent) parent.removeChild(this.el); 459 | return this; 460 | }; 461 | 462 | /** 463 | * Extracted from `timoxley/offset`, but directly using a 464 | * TextRectangle instead of getting another version. 465 | * 466 | * @param {TextRectangle} box - result from a `getBoundingClientRect()` call 467 | * @param {Document} doc - Document instance to use 468 | * @return {Object} an object with `top` and `left` Number properties 469 | * @api private 470 | */ 471 | 472 | function offset (box, doc) { 473 | var body = doc.body || doc.getElementsByTagName('body')[0]; 474 | var docEl = doc.documentElement || body.parentNode; 475 | var clientTop = docEl.clientTop || body.clientTop || 0; 476 | var clientLeft = docEl.clientLeft || body.clientLeft || 0; 477 | var scrollTop = window.pageYOffset || docEl.scrollTop; 478 | var scrollLeft = window.pageXOffset || docEl.scrollLeft; 479 | 480 | return { 481 | top: box.top + scrollTop - clientTop, 482 | left: box.left + scrollLeft - clientLeft 483 | }; 484 | } 485 | --------------------------------------------------------------------------------