├── .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 |
--------------------------------------------------------------------------------
/.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 | 
7 | 
8 |
9 | 
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 |
--------------------------------------------------------------------------------