PageSourceSearch

https://sadei.es/v_portal/javascript/tooltipster.js

js sadei.es collected 2026-10-02 11:22:04 UTC 119,772 bytes, 4,273 lines download raw bytes

vendor: 2,730 bytes, lines 1-92
1/**
2 * tooltipster http://iamceege.github.io/tooltipster/
3 * A rockin' custom tooltip jQuery plugin
4 * Developed by Caleb Jacob and Louis Ameline
5 * MIT license
6 */
7(function (root, factory) {
8  if (typeof define === 'function' && define.amd) {
9    // AMD. Register as an anonymous module unless amdModuleId is set
10    define(["jquery"], function (a0) {
11      return (factory(a0));
12    });
13  } else if (typeof exports === 'object') {
14    // Node. Does not work with strict CommonJS, but
15    // only CommonJS-like environments that support module.exports,
16    // like Node.
17    module.exports = factory(require("jquery"));
18  } else {
19    factory(jQuery);
20  }
21}(this, function ($) {
22
23// This file will be UMDified by a build task.
24
25var defaults = {
26		animation: 'fade',
27		animationDuration: 350,
28		content: null,
29		contentAsHTML: false,
30		contentCloning: false,
31		debug: true,
32		delay: 300,
33		delayTouch: [300, 500],
34		functionInit: null,
35		functionBefore: null,
36		functionReady: null,
37		functionAfter: null,
38		functionFormat: null,
39		IEmin: 6,
40		interactive: false,
41		multiple: false,
42		// will default to document.body, or must be an element positioned at (0, 0)
43		// in the document, typically like the very top views of an app.
44		parent: null,
45		plugins: ['sideTip'],
46		repositionOnScroll: false,
47		restoration: 'none',
48		selfDestruction: true,
49		theme: [],
50		timer: 0,
51		trackerInterval: 500,
52		trackOrigin: false,
53		trackTooltip: false,
54		trigger: 'hover',
55		triggerClose: {
56			click: false,
57			mouseleave: false,
58			originClick: false,
59			scroll: false,
60			tap: false,
61			touchleave: false
62		},
63		triggerOpen: {
64			click: false,
65			mouseenter: false,
66			tap: false,
67			touchstart: false
68		},
69		updateAnimation: 'rotate',
70		zIndex: 9999999
71	},
72	// we'll avoid using the 'window' global as a good practice but npm's
73	// jquery@<2.1.0 package actually requires a 'window' global, so not sure
74	// it's useful at all
75	win = (typeof window != 'undefined') ? window : null,
76	// env will be proxied by the core for plugins to have access its properties
77	env = {
78		// detect if this device can trigger touch events. Better have a false
79		// positive (unused listeners, that's ok) than a false negative.
80		// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
81		// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
82		hasTouchCapability: !!(
83			win
84			&&	(	'ontouchstart' in win
85				||	(win.DocumentTouch && win.document instanceof win.DocumentTouch)
86				||	win.navigator.maxTouchPoints
87			)
88		),
89		hasTransitions: transitionSupport(),
90		IE: false,
91		// don't set manually, it will be updated by a build task after the manifest
92		semVer: '4.2.5
vendor: 79,354 bytes, lines 92-3111
92',
93		window: win
94	},
95	core = function() {
96		
97		// core variables
98		
99		// the core emitters
100		this.__$emitterPrivate = $({});
101		this.__$emitterPublic = $({});
102		this.__instancesLatestArr = [];
103		// collects plugin constructors
104		this.__plugins = {};
105		// proxy env variables for plugins who might use them
106		this._env = env;
107	};
108
109// core methods
110core.prototype = {
111	
112	/**
113	 * A function to proxy the public methods of an object onto another
114	 *
115	 * @param {object} constructor The constructor to bridge
116	 * @param {object} obj The object that will get new methods (an instance or the core)
117	 * @param {string} pluginName A plugin name for the console log message
118	 * @return {core}
119	 * @private
120	 */
121	__bridge: function(constructor, obj, pluginName) {
122		
123		// if it's not already bridged
124		if (!obj[pluginName]) {
125			
126			var fn = function() {};
127			fn.prototype = constructor;
128			
129			var pluginInstance = new fn();
130			
131			// the _init method has to exist in instance constructors but might be missing
132			// in core constructors
133			if (pluginInstance.__init) {
134				pluginInstance.__init(obj);
135			}
136			
137			$.each(constructor, function(methodName, fn) {
138				
139				// don't proxy "private" methods, only "protected" and public ones
140				if (methodName.indexOf('__') != 0) {
141					
142					// if the method does not exist yet
143					if (!obj[methodName]) {
144						
145						obj[methodName] = function() {
146							return pluginInstance[methodName].apply(pluginInstance, Array.prototype.slice.apply(arguments));
147						};
148						
149						// remember to which plugin this method corresponds (several plugins may
150						// have methods of the same name, we need to be sure)
151						obj[methodName].bridged = pluginInstance;
152					}
153					else if (defaults.debug) {
154						
155						console.log('The '+ methodName +' method of the '+ pluginName
156							+' plugin conflicts with another plugin or native methods');
157					}
158				}
159			});
160			
161			obj[pluginName] = pluginInstance;
162		}
163		
164		return this;
165	},
166	
167	/**
168	 * For mockup in Node env if need be, for testing purposes
169	 *
170	 * @return {core}
171	 * @private
172	 */
173	__setWindow: function(window) {
174		env.window = window;
175		return this;
176	},
177	
178	/**
179	 * Returns a ruler, a tool to help measure the size of a tooltip under
180	 * various settings. Meant for plugins
181	 * 
182	 * @see Ruler
183	 * @return {object} A Ruler instance
184	 * @protected
185	 */
186	_getRuler: function($tooltip) {
187		return new Ruler($tooltip);
188	},
189	
190	/**
191	 * For internal use by plugins, if needed
192	 *
193	 * @return {core}
194	 * @protected
195	 */
196	_off: function() {
197		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
198		return this;
199	},
200	
201	/**
202	 * For internal use by plugins, if needed
203	 *
204	 * @return {core}
205	 * @protected
206	 */
207	_on: function() {
208		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
209		return this;
210	},
211	
212	/**
213	 * For internal use by plugins, if needed
214	 *
215	 * @return {core}
216	 * @protected
217	 */
218	_one: function() {
219		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
220		return this;
221	},
222	
223	/**
224	 * Returns (getter) or adds (setter) a plugin
225	 *
226	 * @param {string|object} plugin Provide a string (in the full form
227	 * "namespace.name") to use as as getter, an object to use as a setter
228	 * @return {object|core}
229	 * @protected
230	 */
231	_plugin: function(plugin) {
232		
233		var self = this;
234		
235		// getter
236		if (typeof plugin == 'string') {
237			
238			var pluginName = plugin,
239				p = null;
240			
241			// if the namespace is provided, it's easy to search
242			if (pluginName.indexOf('.') > 0) {
243				p = self.__plugins[pluginName];
244			}
245			// otherwise, return the first name that matches
246			else {
247				$.each(self.__plugins, function(i, plugin) {
248					
249					if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
250						p = plugin;
251						return false;
252					}
253				});
254			}
255			
256			return p;
257		}
258		// setter
259		else {
260			
261			// force namespaces
262			if (plugin.name.indexOf('.') < 0) {
263				throw new Error('Plugins must be namespaced');
264			}
265			
266			self.__plugins[plugin.name] = plugin;
267			
268			// if the plugin has core features
269			if (plugin.core) {
270				
271				// bridge non-private methods onto the core to allow new core methods
272				self.__bridge(plugin.core, self, plugin.name);
273			}
274			
275			return this;
276		}
277	},
278	
279	/**
280	 * Trigger events on the core emitters
281	 * 
282	 * @returns {core}
283	 * @protected
284	 */
285	_trigger: function() {
286		
287		var args = Array.prototype.slice.apply(arguments);
288		
289		if (typeof args[0] == 'string') {
290			args[0] = { type: args[0] };
291		}
292		
293		// note: the order of emitters matters
294		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
295		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
296		
297		return this;
298	},
299	
300	/**
301	 * Returns instances of all tooltips in the page or an a given element
302	 *
303	 * @param {string|HTML object collection} selector optional Use this
304	 * parameter to restrict the set of objects that will be inspected
305	 * for the retrieval of instances. By default, all instances in the
306	 * page are returned.
307	 * @return {array} An array of instance objects
308	 * @public
309	 */
310	instances: function(selector) {
311		
312		var instances = [],
313			sel = selector || '.tooltipstered';
314		
315		$(sel).each(function() {
316			
317			var $this = $(this),
318				ns = $this.data('tooltipster-ns');
319			
320			if (ns) {
321				
322				$.each(ns, function(i, namespace) {
323					instances.push($this.data(namespace));
324				});
325			}
326		});
327		
328		return instances;
329	},
330	
331	/**
332	 * Returns the Tooltipster objects generated by the last initializing call
333	 *
334	 * @return {array} An array of instance objects
335	 * @public
336	 */
337	instancesLatest: function() {
338		return this.__instancesLatestArr;
339	},
340	
341	/**
342	 * For public use only, not to be used by plugins (use ::_off() instead)
343	 *
344	 * @return {core}
345	 * @public
346	 */
347	off: function() {
348		this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
349		return this;
350	},
351	
352	/**
353	 * For public use only, not to be used by plugins (use ::_on() instead)
354	 *
355	 * @return {core}
356	 * @public
357	 */
358	on: function() {
359		this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
360		return this;
361	},
362	
363	/**
364	 * For public use only, not to be used by plugins (use ::_one() instead)
365	 * 
366	 * @return {core}
367	 * @public
368	 */
369	one: function() {
370		this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
371		return this;
372	},
373	
374	/**
375	 * Returns all HTML elements which have one or more tooltips
376	 *
377	 * @param {string} selector optional Use this to restrict the results
378	 * to the descendants of an element
379	 * @return {array} An array of HTML elements
380	 * @public
381	 */
382	origins: function(selector) {
383		
384		var sel = selector ?
385			selector +' ' :
386			'';
387		
388		return $(sel +'.tooltipstered').toArray();
389	},
390	
391	/**
392	 * Change default options for all future instances
393	 *
394	 * @param {object} d The options that should be made defaults
395	 * @return {core}
396	 * @public
397	 */
398	setDefaults: function(d) {
399		$.extend(defaults, d);
400		return this;
401	},
402	
403	/**
404	 * For users to trigger their handlers on the public emitter
405	 * 
406	 * @returns {core}
407	 * @public
408	 */
409	triggerHandler: function() {
410		this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
411		return this;
412	}
413};
414
415// $.tooltipster will be used to call core methods
416$.tooltipster = new core();
417
418// the Tooltipster instance class (mind the capital T)
419$.Tooltipster = function(element, options) {
420	
421	// list of instance variables
422	
423	// stack of custom callbacks provided as parameters to API methods
424	this.__callbacks = {
425		close: [],
426		open: []
427	};
428	// the schedule time of DOM removal
429	this.__closingTime;
430	// this will be the user content shown in the tooltip. A capital "C" is used
431	// because there is also a method called content()
432	this.__Content;
433	// for the size tracker
434	this.__contentBcr;
435	// to disable the tooltip after destruction
436	this.__destroyed = false;
437	// we can't emit directly on the instance because if a method with the same
438	// name as the event exists, it will be called by jQuery. Se we use a plain
439	// object as emitter. This emitter is for internal use by plugins,
440	// if needed.
441	this.__$emitterPrivate = $({});
442	// this emitter is for the user to listen to events without risking to mess
443	// with our internal listeners
444	this.__$emitterPublic = $({});
445	this.__enabled = true;
446	// the reference to the gc interval
447	this.__garbageCollector;
448	// various position and size data recomputed before each repositioning
449	this.__Geometry;
450	// the tooltip position, saved after each repositioning by a plugin
451	this.__lastPosition;
452	// a unique namespace per instance
453	this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
454	this.__options;
455	// will be used to support origins in scrollable areas
456	this.__$originParents;
457	this.__pointerIsOverOrigin = false;
458	// to remove themes if needed
459	this.__previousThemes = [];
460	// the state can be either: appearing, stable, disappearing, closed
461	this.__state = 'closed';
462	// timeout references
463	this.__timeouts = {
464		close: [],
465		open: null
466	};
467	// store touch events to be able to detect emulated mouse events
468	this.__touchEvents = [];
469	// the reference to the tracker interval
470	this.__tracker = null;
471	// the element to which this tooltip is associated
472	this._$origin;
473	// this will be the tooltip element (jQuery wrapped HTML element).
474	// It's the job of a plugin to create it and append it to the DOM
475	this._$tooltip;
476	
477	// launch
478	this.__init(element, options);
479};
480
481$.Tooltipster.prototype = {
482	
483	/**
484	 * @param origin
485	 * @param options
486	 * @private
487	 */
488	__init: function(origin, options) {
489		
490		var self = this;
491		
492		self._$origin = $(origin);
493		self.__options = $.extend(true, {}, defaults, options);
494		
495		// some options may need to be reformatted
496		self.__optionsFormat();
497		
498		// don't run on old IE if asked no to
499		if (	!env.IE
500			||	env.IE >= self.__options.IEmin
501		) {
502			
503			// note: the content is null (empty) by default and can stay that
504			// way if the plugin remains initialized but not fed any content. The
505			// tooltip will just not appear.
506			
507			// let's save the initial value of the title attribute for later
508			// restoration if need be.
509			var initialTitle = null;
510			
511			// it will already have been saved in case of multiple tooltips
512			if (self._$origin.data('tooltipster-initialTitle') === undefined) {
513				
514				initialTitle = self._$origin.attr('title');
515				
516				// we do not want initialTitle to be "undefined" because
517				// of how jQuery's .data() method works
518				if (initialTitle === undefined) initialTitle = null;
519				
520				self._$origin.data('tooltipster-initialTitle', initialTitle);
521			}
522			
523			// If content is provided in the options, it has precedence over the
524			// title attribute.
525			// Note: an empty string is considered content, only 'null' represents
526			// the absence of content.
527			// Also, an existing title="" attribute will result in an empty string
528			// content
529			if (self.__options.content !== null) {
530				self.__contentSet(self.__options.content);
531			}
532			else {
533				
534				var selector = self._$origin.attr('data-tooltip-content'),
535					$el;
536				
537				if (selector){
538					$el = $(selector);
539				}
540				
541				if ($el && $el[0]) {
542					self.__contentSet($el.first());
543				}
544				else {
545					self.__contentSet(initialTitle);
546				}
547			}
548			
549			self._$origin
550				// strip the title off of the element to prevent the default tooltips
551				// from popping up
552				.removeAttr('title')
553				// to be able to find all instances on the page later (upon window
554				// events in particular)
555				.addClass('tooltipstered');
556			
557			// set listeners on the origin
558			self.__prepareOrigin();
559			
560			// set the garbage collector
561			self.__prepareGC();
562			
563			// init plugins
564			$.each(self.__options.plugins, function(i, pluginName) {
565				self._plug(pluginName);
566			});
567			
568			// to detect swiping
569			if (env.hasTouchCapability) {
570				$(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
571					self._touchRecordEvent(event);
572				});
573			}
574			
575			self
576				// prepare the tooltip when it gets created. This event must
577				// be fired by a plugin
578				._on('created', function() {
579					self.__prepareTooltip();
580				})
581				// save position information when it's sent by a plugin
582				._on('repositioned', function(e) {
583					self.__lastPosition = e.position;
584				});
585		}
586		else {
587			self.__options.disabled = true;
588		}
589	},
590	
591	/**
592	 * Insert the content into the appropriate HTML element of the tooltip
593	 * 
594	 * @returns {self}
595	 * @private
596	 */
597	__contentInsert: function() {
598		
599		var self = this,
600			$el = self._$tooltip.find('.tooltipster-content'),
601			formattedContent = self.__Content,
602			format = function(content) {
603				formattedContent = content;
604			};
605		
606		self._trigger({
607			type: 'format',
608			content: self.__Content,
609			format: format
610		});
611		
612		if (self.__options.functionFormat) {
613			
614			formattedContent = self.__options.functionFormat.call(
615				self,
616				self,
617				{ origin: self._$origin[0] },
618				self.__Content
619			);
620		}
621		
622		if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
623			$el.text(formattedContent);
624		}
625		else {
626			$el
627				.empty()
628				.append(formattedContent);
629		}
630		
631		return self;
632	},
633	
634	/**
635	 * Save the content, cloning it beforehand if need be
636	 * 
637	 * @param content
638	 * @returns {self}
639	 * @private
640	 */
641	__contentSet: function(content) {
642		
643		// clone if asked. Cloning the object makes sure that each instance has its
644		// own version of the content (in case a same object were provided for several
645		// instances)
646		// reminder: typeof null === object
647		if (content instanceof $ && this.__options.contentCloning) {
648			content = content.clone(true);
649		}
650		
651		this.__Content = content;
652		
653		this._trigger({
654			type: 'updated',
655			content: content
656		});
657		
658		return this;
659	},
660	
661	/**
662	 * Error message about a method call made after destruction
663	 * 
664	 * @private
665	 */
666	__destroyError: function() {
667		throw new Error('This tooltip has been destroyed and cannot execute your method call.');
668	},
669	
670	/**
671	 * Gather all information about dimensions and available space,
672	 * called before every repositioning
673	 * 
674	 * @private
675	 * @returns {object}
676	 */
677	__geometry: function() {
678		
679		var	self = this,
680			$target = self._$origin,
681			originIsArea = self._$origin.is('area');
682		
683		// if this._$origin is a map area, the target we'll need
684		// the dimensions of is actually the image using the map,
685		// not the area itself
686		if (originIsArea) {
687			
688			var mapName = self._$origin.parent().attr('name');
689			
690			$target = $('img[usemap="#'+ mapName +'"]');
691		}
692		
693		var bcr = $target[0].getBoundingClientRect(),
694			$document = $(env.window.document),
695			$window = $(env.window),
696			$parent = $target,
697			// some useful properties of important elements
698			geo = {
699				// available space for the tooltip, see down below
700				available: {
701					document: null,
702					window: null
703				},
704				document: {
705					size: {
706						height: $document.height(),
707						width: $document.width()
708					}
709				},
710				window: {
711					scroll: {
712						// the second ones are for IE compatibility
713						left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
714						top: env.window.scrollY || env.window.document.documentElement.scrollTop
715					},
716					size: {
717						height: $window.height(),
718						width: $window.width()
719					}
720				},
721				origin: {
722					// the origin has a fixed lineage if itself or one of its
723					// ancestors has a fixed position
724					fixedLineage: false,
725					// relative to the document
726					offset: {},
727					size: {
728						height: bcr.bottom - bcr.top,
729						width: bcr.right - bcr.left
730					},
731					usemapImage: originIsArea ? $target[0] : null,
732					// relative to the window
733					windowOffset: {
734						bottom: bcr.bottom,
735						left: bcr.left,
736						right: bcr.right,
737						top: bcr.top
738					}
739				}
740			},
741			geoFixed = false;
742		
743		// if the element is a map area, some properties may need
744		// to be recalculated
745		if (originIsArea) {
746			
747			var shape = self._$origin.attr('shape'),
748				coords = self._$origin.attr('coords');
749			
750			if (coords) {
751				
752				coords = coords.split(',');
753				
754				$.map(coords, function(val, i) {
755					coords[i] = parseInt(val);
756				});
757			}
758			
759			// if the image itself is the area, nothing more to do
760			if (shape != 'default') {
761				
762				switch(shape) {
763					
764					case 'circle':
765						
766						var circleCenterLeft = coords[0],
767							circleCenterTop = coords[1],
768							circleRadius = coords[2],
769							areaTopOffset = circleCenterTop - circleRadius,
770							areaLeftOffset = circleCenterLeft - circleRadius;
771						
772						geo.origin.size.height = circleRadius * 2;
773						geo.origin.size.width = geo.origin.size.height;
774						
775						geo.origin.windowOffset.left += areaLeftOffset;
776						geo.origin.windowOffset.top += areaTopOffset;
777						
778						break;
779					
780					case 'rect':
781						
782						var areaLeft = coords[0],
783							areaTop = coords[1],
784							areaRight = coords[2],
785							areaBottom = coords[3];
786						
787						geo.origin.size.height = areaBottom - areaTop;
788						geo.origin.size.width = areaRight - areaLeft;
789						
790						geo.origin.windowOffset.left += areaLeft;
791						geo.origin.windowOffset.top += areaTop;
792						
793						break;
794					
795					case 'poly':
796						
797						var areaSmallestX = 0,
798							areaSmallestY = 0,
799							areaGreatestX = 0,
800							areaGreatestY = 0,
801							arrayAlternate = 'even';
802						
803						for (var i = 0; i < coords.length; i++) {
804							
805							var areaNumber = coords[i];
806							
807							if (arrayAlternate == 'even') {
808								
809								if (areaNumber > areaGreatestX) {
810									
811									areaGreatestX = areaNumber;
812									
813									if (i === 0) {
814										areaSmallestX = areaGreatestX;
815									}
816								}
817								
818								if (areaNumber < areaSmallestX) {
819									areaSmallestX = areaNumber;
820								}
821								
822								arrayAlternate = 'odd';
823							}
824							else {
825								if (areaNumber > areaGreatestY) {
826									
827									areaGreatestY = areaNumber;
828									
829									if (i == 1) {
830										areaSmallestY = areaGreatestY;
831									}
832								}
833								
834								if (areaNumber < areaSmallestY) {
835									areaSmallestY = areaNumber;
836								}
837								
838								arrayAlternate = 'even';
839							}
840						}
841						
842						geo.origin.size.height = areaGreatestY - areaSmallestY;
843						geo.origin.size.width = areaGreatestX - areaSmallestX;
844						
845						geo.origin.windowOffset.left += areaSmallestX;
846						geo.origin.windowOffset.top += areaSmallestY;
847						
848						break;
849				}
850			}
851		}
852		
853		// user callback through an event
854		var edit = function(r) {
855			geo.origin.size.height = r.height,
856				geo.origin.windowOffset.left = r.left,
857				geo.origin.windowOffset.top = r.top,
858				geo.origin.size.width = r.width
859		};
860		
861		self._trigger({
862			type: 'geometry',
863			edit: edit,
864			geometry: {
865				height: geo.origin.size.height,
866				left: geo.origin.windowOffset.left,
867				top: geo.origin.windowOffset.top,
868				width: geo.origin.size.width
869			}
870		});
871		
872		// calculate the remaining properties with what we got
873		
874		geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
875		geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
876		
877		geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
878		geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
879		geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
880		geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
881		
882		// the space that is available to display the tooltip relatively to the document
883		geo.available.document = {
884			bottom: {
885				height: geo.document.size.height - geo.origin.offset.bottom,
886				width: geo.document.size.width
887			},
888			left: {
889				height: geo.document.size.height,
890				width: geo.origin.offset.left
891			},
892			right: {
893				height: geo.document.size.height,
894				width: geo.document.size.width - geo.origin.offset.right
895			},
896			top: {
897				height: geo.origin.offset.top,
898				width: geo.document.size.width
899			}
900		};
901		
902		// the space that is available to display the tooltip relatively to the viewport
903		// (the resulting values may be negative if the origin overflows the viewport)
904		geo.available.window = {
905			bottom: {
906				// the inner max is here to make sure the available height is no bigger
907				// than the viewport height (when the origin is off screen at the top).
908				// The outer max just makes sure that the height is not negative (when
909				// the origin overflows at the bottom).
910				height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
911				width: geo.window.size.width
912			},
913			left: {
914				height: geo.window.size.height,
915				width: Math.max(geo.origin.windowOffset.left, 0)
916			},
917			right: {
918				height: geo.window.size.height,
919				width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
920			},
921			top: {
922				height: Math.max(geo.origin.windowOffset.top, 0),
923				width: geo.window.size.width
924			}
925		};
926		
927		while ($parent[0].tagName.toLowerCase() != 'html') {
928			
929			if ($parent.css('position') == 'fixed') {
930				geo.origin.fixedLineage = true;
931				break;
932			}
933			
934			$parent = $parent.parent();
935		}
936		
937		return geo;
938	},
939	
940	/**
941	 * Some options may need to be formated before being used
942	 * 
943	 * @returns {self}
944	 * @private
945	 */
946	__optionsFormat: function() {
947		
948		if (typeof this.__options.animationDuration == 'number') {
949			this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
950		}
951		
952		if (typeof this.__options.delay == 'number') {
953			this.__options.delay = [this.__options.delay, this.__options.delay];
954		}
955		
956		if (typeof this.__options.delayTouch == 'number') {
957			this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
958		}
959		
960		if (typeof this.__options.theme == 'string') {
961			this.__options.theme = [this.__options.theme];
962		}
963		
964		// determine the future parent
965		if (this.__options.parent === null) {
966			this.__options.parent = $(env.window.document.body);
967		}
968		else if (typeof this.__options.parent == 'string') {
969			this.__options.parent = $(this.__options.parent);
970		}
971		
972		if (this.__options.trigger == 'hover') {
973			
974			this.__options.triggerOpen = {
975				mouseenter: true,
976				touchstart: true
977			};
978			
979			this.__options.triggerClose = {
980				mouseleave: true,
981				originClick: true,
982				touchleave: true
983			};
984		}
985		else if (this.__options.trigger == 'click') {
986			
987			this.__options.triggerOpen = {
988				click: true,
989				tap: true
990			};
991			
992			this.__options.triggerClose = {
993				click: true,
994				tap: true
995			};
996		}
997		
998		// for the plugins
999		this._trigger('options');
1000		
1001		return this;
1002	},
1003	
1004	/**
1005	 * Schedules or cancels the garbage collector task
1006	 *
1007	 * @returns {self}
1008	 * @private
1009	 */
1010	__prepareGC: function() {
1011		
1012		var self = this;
1013		
1014		// in case the selfDestruction option has been changed by a method call
1015		if (self.__options.selfDestruction) {
1016			
1017			// the GC task
1018			self.__garbageCollector = setInterval(function() {
1019				
1020				var now = new Date().getTime();
1021				
1022				// forget the old events
1023				self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
1024					// 1 minute
1025					return now - event.time > 60000;
1026				});
1027				
1028				// auto-destruct if the origin is gone
1029				if (!bodyContains(self._$origin)) {
1030					
1031					self.close(function(){
1032						self.destroy();
1033					});
1034				}
1035			}, 20000);
1036		}
1037		else {
1038			clearInterval(self.__garbageCollector);
1039		}
1040		
1041		return self;
1042	},
1043	
1044	/**
1045	 * Sets listeners on the origin if the open triggers require them.
1046	 * Unlike the listeners set at opening time, these ones
1047	 * remain even when the tooltip is closed. It has been made a
1048	 * separate method so it can be called when the triggers are
1049	 * changed in the options. Closing is handled in _open()
1050	 * because of the bindings that may be needed on the tooltip
1051	 * itself
1052	 *
1053	 * @returns {self}
1054	 * @private
1055	 */
1056	__prepareOrigin: function() {
1057		
1058		var self = this;
1059		
1060		// in case we're resetting the triggers
1061		self._$origin.off('.'+ self.__namespace +'-triggerOpen');
1062		
1063		// if the device is touch capable, even if only mouse triggers
1064		// are asked, we need to listen to touch events to know if the mouse
1065		// events are actually emulated (so we can ignore them)
1066		if (env.hasTouchCapability) {
1067			
1068			self._$origin.on(
1069				'touchstart.'+ self.__namespace +'-triggerOpen ' +
1070				'touchend.'+ self.__namespace +'-triggerOpen ' +
1071				'touchcancel.'+ self.__namespace +'-triggerOpen',
1072				function(event){
1073					self._touchRecordEvent(event);
1074				}
1075			);
1076		}
1077		
1078		// mouse click and touch tap work the same way
1079		if (	self.__options.triggerOpen.click
1080			||	(self.__options.triggerOpen.tap && env.hasTouchCapability)
1081		) {
1082			
1083			var eventNames = '';
1084			if (self.__options.triggerOpen.click) {
1085				eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
1086			}
1087			if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
1088				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
1089			}
1090			
1091			self._$origin.on(eventNames, function(event) {
1092				if (self._touchIsMeaningfulEvent(event)) {
1093					self._open(event);
1094				}
1095			});
1096		}
1097		
1098		// mouseenter and touch start work the same way
1099		if (	self.__options.triggerOpen.mouseenter
1100			||	(self.__options.triggerOpen.touchstart && env.hasTouchCapability)
1101		) {
1102			
1103			var eventNames = '';
1104			if (self.__options.triggerOpen.mouseenter) {
1105				eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
1106			}
1107			if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
1108				eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
1109			}
1110			
1111			self._$origin.on(eventNames, function(event) {
1112				if (	self._touchIsTouchEvent(event)
1113					||	!self._touchIsEmulatedEvent(event)
1114				) {
1115					self.__pointerIsOverOrigin = true;
1116					self._openShortly(event);
1117				}
1118			});
1119		}
1120		
1121		// info for the mouseleave/touchleave close triggers when they use a delay
1122		if (	self.__options.triggerClose.mouseleave
1123			||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
1124		) {
1125			
1126			var eventNames = '';
1127			if (self.__options.triggerClose.mouseleave) {
1128				eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
1129			}
1130			if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1131				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
1132			}
1133			
1134			self._$origin.on(eventNames, function(event) {
1135				
1136				if (self._touchIsMeaningfulEvent(event)) {
1137					self.__pointerIsOverOrigin = false;
1138				}
1139			});
1140		}
1141		
1142		return self;
1143	},
1144	
1145	/**
1146	 * Do the things that need to be done only once after the tooltip
1147	 * HTML element it has been created. It has been made a separate
1148	 * method so it can be called when options are changed. Remember
1149	 * that the tooltip may actually exist in the DOM before it is
1150	 * opened, and present after it has been closed: it's the display
1151	 * plugin that takes care of handling it.
1152	 * 
1153	 * @returns {self}
1154	 * @private
1155	 */
1156	__prepareTooltip: function() {
1157		
1158		var self = this,
1159			p = self.__options.interactive ? 'auto' : '';
1160		
1161		// this will be useful to know quickly if the tooltip is in
1162		// the DOM or not 
1163		self._$tooltip
1164			.attr('id', self.__namespace)
1165			.css({
1166				// pointer events
1167				'pointer-events': p,
1168				zIndex: self.__options.zIndex
1169			});
1170		
1171		// themes
1172		// remove the old ones and add the new ones
1173		$.each(self.__previousThemes, function(i, theme) {
1174			self._$tooltip.removeClass(theme);
1175		});
1176		$.each(self.__options.theme, function(i, theme) {
1177			self._$tooltip.addClass(theme);
1178		});
1179		
1180		self.__previousThemes = $.merge([], self.__options.theme);
1181		
1182		return self;
1183	},
1184	
1185	/**
1186	 * Handles the scroll on any of the parents of the origin (when the
1187	 * tooltip is open)
1188	 *
1189	 * @param {object} event
1190	 * @returns {self}
1191	 * @private
1192	 */
1193	__scrollHandler: function(event) {
1194		
1195		var self = this;
1196		
1197		if (self.__options.triggerClose.scroll) {
1198			self._close(event);
1199		}
1200		else {
1201			
1202			// if the origin or tooltip have been removed: do nothing, the tracker will
1203			// take care of it later
1204			if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
1205				
1206				var geo = null;
1207				
1208				// if the scroll happened on the window
1209				if (event.target === env.window.document) {
1210					
1211					// if the origin has a fixed lineage, window scroll will have no
1212					// effect on its position nor on the position of the tooltip
1213					if (!self.__Geometry.origin.fixedLineage) {
1214						
1215						// we don't need to do anything unless repositionOnScroll is true
1216						// because the tooltip will already have moved with the window
1217						// (and of course with the origin)
1218						if (self.__options.repositionOnScroll) {
1219							self.reposition(event);
1220						}
1221					}
1222				}
1223				// if the scroll happened on another parent of the tooltip, it means
1224				// that it's in a scrollable area and now needs to have its position
1225				// adjusted or recomputed, depending ont the repositionOnScroll
1226				// option. Also, if the origin is partly hidden due to a parent that
1227				// hides its overflow, we'll just hide (not close) the tooltip.
1228				else {
1229					
1230					geo = self.__geometry();
1231					
1232					var overflows = false;
1233					
1234					// a fixed position origin is not affected by the overflow hiding
1235					// of a parent
1236					if (self._$origin.css('position') != 'fixed') {
1237						
1238						self.__$originParents.each(function(i, el) {
1239							
1240							var $el = $(el),
1241								overflowX = $el.css('overflow-x'),
1242								overflowY = $el.css('overflow-y');
1243							
1244							if (overflowX != 'visible' || overflowY != 'visible') {
1245								
1246								var bcr = el.getBoundingClientRect();
1247								
1248								if (overflowX != 'visible') {
1249									
1250									if (	geo.origin.windowOffset.left < bcr.left
1251										||	geo.origin.windowOffset.right > bcr.right
1252									) {
1253										overflows = true;
1254										return false;
1255									}
1256								}
1257								
1258								if (overflowY != 'visible') {
1259									
1260									if (	geo.origin.windowOffset.top < bcr.top
1261										||	geo.origin.windowOffset.bottom > bcr.bottom
1262									) {
1263										overflows = true;
1264										return false;
1265									}
1266								}
1267							}
1268							
1269							// no need to go further if fixed, for the same reason as above
1270							if ($el.css('position') == 'fixed') {
1271								return false;
1272							}
1273						});
1274					}
1275					
1276					if (overflows) {
1277						self._$tooltip.css('visibility', 'hidden');
1278					}
1279					else {
1280						
1281						self._$tooltip.css('visibility', 'visible');
1282						
1283						// reposition
1284						if (self.__options.repositionOnScroll) {
1285							self.reposition(event);
1286						}
1287						// or just adjust offset
1288						else {
1289							
1290							// we have to use offset and not windowOffset because this way,
1291							// only the scroll distance of the scrollable areas are taken into
1292							// account (the scrolltop value of the main window must be
1293							// ignored since the tooltip already moves with it)
1294							var offsetLeft = geo.origin.offset.left - self.__Geometry.origin.offset.left,
1295								offsetTop = geo.origin.offset.top - self.__Geometry.origin.offset.top;
1296							
1297							// add the offset to the position initially computed by the display plugin
1298							self._$tooltip.css({
1299								left: self.__lastPosition.coord.left + offsetLeft,
1300								top: self.__lastPosition.coord.top + offsetTop
1301							});
1302						}
1303					}
1304				}
1305				
1306				self._trigger({
1307					type: 'scroll',
1308					event: event,
1309					geo: geo
1310				});
1311			}
1312		}
1313		
1314		return self;
1315	},
1316	
1317	/**
1318	 * Changes the state of the tooltip
1319	 *
1320	 * @param {string} state
1321	 * @returns {self}
1322	 * @private
1323	 */
1324	__stateSet: function(state) {
1325		
1326		this.__state = state;
1327		
1328		this._trigger({
1329			type: 'state',
1330			state: state
1331		});
1332		
1333		return this;
1334	},
1335	
1336	/**
1337	 * Clear appearance timeouts
1338	 *
1339	 * @returns {self}
1340	 * @private
1341	 */
1342	__timeoutsClear: function() {
1343		
1344		// there is only one possible open timeout: the delayed opening
1345		// when the mouseenter/touchstart open triggers are used
1346		clearTimeout(this.__timeouts.open);
1347		this.__timeouts.open = null;
1348		
1349		// ... but several close timeouts: the delayed closing when the
1350		// mouseleave close trigger is used and the timer option
1351		$.each(this.__timeouts.close, function(i, timeout) {
1352			clearTimeout(timeout);
1353		});
1354		this.__timeouts.close = [];
1355		
1356		return this;
1357	},
1358	
1359	/**
1360	 * Start the tracker that will make checks at regular intervals
1361	 * 
1362	 * @returns {self}
1363	 * @private
1364	 */
1365	__trackerStart: function() {
1366		
1367		var self = this,
1368			$content = self._$tooltip.find('.tooltipster-content');
1369		
1370		// get the initial content size
1371		if (self.__options.trackTooltip) {
1372			self.__contentBcr = $content[0].getBoundingClientRect();
1373		}
1374		
1375		self.__tracker = setInterval(function() {
1376			
1377			// if the origin or tooltip elements have been removed.
1378			// Note: we could destroy the instance now if the origin has
1379			// been removed but we'll leave that task to our garbage collector
1380			if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
1381				self._close();
1382			}
1383			// if everything is alright
1384			else {
1385				
1386				// compare the former and current positions of the origin to reposition
1387				// the tooltip if need be
1388				if (self.__options.trackOrigin) {
1389					
1390					var g = self.__geometry(),
1391						identical = false;
1392					
1393					// compare size first (a change requires repositioning too)
1394					if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
1395						
1396						// for elements that have a fixed lineage (see __geometry()), we track the
1397						// top and left properties (relative to window)
1398						if (self.__Geometry.origin.fixedLineage) {
1399							if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
1400								identical = true;
1401							}
1402						}
1403						// otherwise, track total offset (relative to document)
1404						else {
1405							if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
1406								identical = true;
1407							}
1408						}
1409					}
1410					
1411					if (!identical) {
1412						
1413						// close the tooltip when using the mouseleave close trigger
1414						// (see https://github.com/iamceege/tooltipster/pull/253)
1415						if (self.__options.triggerClose.mouseleave) {
1416							self._close();
1417						}
1418						else {
1419							self.reposition();
1420						}
1421					}
1422				}
1423				
1424				if (self.__options.trackTooltip) {
1425					
1426					var currentBcr = $content[0].getBoundingClientRect();
1427					
1428					if (	currentBcr.height !== self.__contentBcr.height
1429						||	currentBcr.width !== self.__contentBcr.width
1430					) {
1431						self.reposition();
1432						self.__contentBcr = currentBcr;
1433					}
1434				}
1435			}
1436		}, self.__options.trackerInterval);
1437		
1438		return self;
1439	},
1440	
1441	/**
1442	 * Closes the tooltip (after the closing delay)
1443	 * 
1444	 * @param event
1445	 * @param callback
1446	 * @param force Set to true to override a potential refusal of the user's function
1447	 * @returns {self}
1448	 * @protected
1449	 */
1450	_close: function(event, callback, force) {
1451		
1452		var self = this,
1453			ok = true;
1454		
1455		self._trigger({
1456			type: 'close',
1457			event: event,
1458			stop: function() {
1459				ok = false;
1460			}
1461		});
1462		
1463		// a destroying tooltip (force == true) may not refuse to close
1464		if (ok || force) {
1465			
1466			// save the method custom callback and cancel any open method custom callbacks
1467			if (callback) self.__callbacks.close.push(callback);
1468			self.__callbacks.open = [];
1469			
1470			// clear open/close timeouts
1471			self.__timeoutsClear();
1472			
1473			var finishCallbacks = function() {
1474				
1475				// trigger any close method custom callbacks and reset them
1476				$.each(self.__callbacks.close, function(i,c) {
1477					c.call(self, self, {
1478						event: event,
1479						origin: self._$origin[0]
1480					});
1481				});
1482				
1483				self.__callbacks.close = [];
1484			};
1485			
1486			if (self.__state != 'closed') {
1487				
1488				var necessary = true,
1489					d = new Date(),
1490					now = d.getTime(),
1491					newClosingTime = now + self.__options.animationDuration[1];
1492				
1493				// the tooltip may already already be disappearing, but if a new
1494				// call to close() is made after the animationDuration was changed
1495				// to 0 (for example), we ought to actually close it sooner than
1496				// previously scheduled. In that case it should be noted that the
1497				// browser will not adapt the animation duration to the new
1498				// animationDuration that was set after the start of the closing
1499				// animation.
1500				// Note: the same thing could be considered at opening, but is not
1501				// really useful since the tooltip is actually opened immediately
1502				// upon a call to _open(). Since it would not make the opening
1503				// animation finish sooner, its sole impact would be to trigger the
1504				// state event and the open callbacks sooner than the actual end of
1505				// the opening animation, which is not great.
1506				if (self.__state == 'disappearing') {
1507					
1508					if (	newClosingTime > self.__closingTime
1509						// in case closing is actually overdue because the script
1510						// execution was suspended. See #679
1511						&&	self.__options.animationDuration[1] > 0
1512					) {
1513						necessary = false;
1514					}
1515				}
1516				
1517				if (necessary) {
1518					
1519					self.__closingTime = newClosingTime;
1520					
1521					if (self.__state != 'disappearing') {
1522						self.__stateSet('disappearing');
1523					}
1524					
1525					var finish = function() {
1526						
1527						// stop the tracker
1528						clearInterval(self.__tracker);
1529						
1530						// a "beforeClose" option has been asked several times but would
1531						// probably useless since the content element is still accessible
1532						// via ::content(), and because people can always use listeners
1533						// inside their content to track what's going on. For the sake of
1534						// simplicity, this has been denied. Bur for the rare people who
1535						// really need the option (for old browsers or for the case where
1536						// detaching the content is actually destructive, for file or
1537						// password inputs for example), this event will do the work.
1538						self._trigger({
1539							type: 'closing',
1540							event: event
1541						});
1542						
1543						// unbind listeners which are no longer needed
1544						
1545						self._$tooltip
1546							.off('.'+ self.__namespace +'-triggerClose')
1547							.removeClass('tooltipster-dying');
1548						
1549						// orientationchange, scroll and resize listeners
1550						$(env.window).off('.'+ self.__namespace +'-triggerClose');
1551						
1552						// scroll listeners
1553						self.__$originParents.each(function(i, el) {
1554							$(el).off('scroll.'+ self.__namespace +'-triggerClose');
1555						});
1556						// clear the array to prevent memory leaks
1557						self.__$originParents = null;
1558						
1559						$(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
1560						
1561						self._$origin.off('.'+ self.__namespace +'-triggerClose');
1562						
1563						self._off('dismissable');
1564						
1565						// a plugin that would like to remove the tooltip from the
1566						// DOM when closed should bind on this
1567						self.__stateSet('closed');
1568						
1569						// trigger event
1570						self._trigger({
1571							type: 'after',
1572							event: event
1573						});
1574						
1575						// call our constructor custom callback function
1576						if (self.__options.functionAfter) {
1577							self.__options.functionAfter.call(self, self, {
1578								event: event,
1579								origin: self._$origin[0]
1580							});
1581						}
1582						
1583						// call our method custom callbacks functions
1584						finishCallbacks();
1585					};
1586					
1587					if (env.hasTransitions) {
1588						
1589						self._$tooltip.css({
1590							'-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
1591							'-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
1592							'-o-animation-duration': self.__options.animationDuration[1] + 'ms',
1593							'-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
1594							'animation-duration': self.__options.animationDuration[1] + 'ms',
1595							'transition-duration': self.__options.animationDuration[1] + 'ms'
1596						});
1597						
1598						self._$tooltip
1599							// clear both potential open and close tasks
1600							.clearQueue()
1601							.removeClass('tooltipster-show')
1602							// for transitions only
1603							.addClass('tooltipster-dying');
1604						
1605						if (self.__options.animationDuration[1] > 0) {
1606							self._$tooltip.delay(self.__options.animationDuration[1]);
1607						}
1608						
1609						self._$tooltip.queue(finish);
1610					}
1611					else {
1612						
1613						self._$tooltip
1614							.stop()
1615							.fadeOut(self.__options.animationDuration[1], finish);
1616					}
1617				}
1618			}
1619			// if the tooltip is already closed, we still need to trigger
1620			// the method custom callbacks
1621			else {
1622				finishCallbacks();
1623			}
1624		}
1625		
1626		return self;
1627	},
1628	
1629	/**
1630	 * For internal use by plugins, if needed
1631	 * 
1632	 * @returns {self}
1633	 * @protected
1634	 */
1635	_off: function() {
1636		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1637		return this;
1638	},
1639	
1640	/**
1641	 * For internal use by plugins, if needed
1642	 *
1643	 * @returns {self}
1644	 * @protected
1645	 */
1646	_on: function() {
1647		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1648		return this;
1649	},
1650	
1651	/**
1652	 * For internal use by plugins, if needed
1653	 *
1654	 * @returns {self}
1655	 * @protected
1656	 */
1657	_one: function() {
1658		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1659		return this;
1660	},
1661	
1662	/**
1663	 * Opens the tooltip right away.
1664	 *
1665	 * @param event
1666	 * @param callback Will be called when the opening animation is over
1667	 * @returns {self}
1668	 * @protected
1669	 */
1670	_open: function(event, callback) {
1671		
1672		var self = this;
1673		
1674		// if the destruction process has not begun and if this was not
1675		// triggered by an unwanted emulated click event
1676		if (!self.__destroying) {
1677			
1678			// check that the origin is still in the DOM
1679			if (	bodyContains(self._$origin)
1680				// if the tooltip is enabled
1681				&&	self.__enabled
1682			) {
1683				
1684				var ok = true;
1685				
1686				// if the tooltip is not open yet, we need to call functionBefore.
1687				// otherwise we can jst go on
1688				if (self.__state == 'closed') {
1689					
1690					// trigger an event. The event.stop function allows the callback
1691					// to prevent the opening of the tooltip
1692					self._trigger({
1693						type: 'before',
1694						event: event,
1695						stop: function() {
1696							ok = false;
1697						}
1698					});
1699					
1700					if (ok && self.__options.functionBefore) {
1701						
1702						// call our custom function before continuing
1703						ok = self.__options.functionBefore.call(self, self, {
1704							event: event,
1705							origin: self._$origin[0]
1706						});
1707					}
1708				}
1709				
1710				if (ok !== false) {
1711					
1712					// if there is some content
1713					if (self.__Content !== null) {
1714						
1715						// save the method callback and cancel close method callbacks
1716						if (callback) {
1717							self.__callbacks.open.push(callback);
1718						}
1719						self.__callbacks.close = [];
1720						
1721						// get rid of any appearance timeouts
1722						self.__timeoutsClear();
1723						
1724						var extraTime,
1725							finish = function() {
1726								
1727								if (self.__state != 'stable') {
1728									self.__stateSet('stable');
1729								}
1730								
1731								// trigger any open method custom callbacks and reset them
1732								$.each(self.__callbacks.open, function(i,c) {
1733									c.call(self, self, {
1734										origin: self._$origin[0],
1735										tooltip: self._$tooltip[0]
1736									});
1737								});
1738								
1739								self.__callbacks.open = [];
1740							};
1741						
1742						// if the tooltip is already open
1743						if (self.__state !== 'closed') {
1744							
1745							// the timer (if any) will start (or restart) right now
1746							extraTime = 0;
1747							
1748							// if it was disappearing, cancel that
1749							if (self.__state === 'disappearing') {
1750								
1751								self.__stateSet('appearing');
1752								
1753								if (env.hasTransitions) {
1754									
1755									self._$tooltip
1756										.clearQueue()
1757										.removeClass('tooltipster-dying')
1758										.addClass('tooltipster-show');
1759									
1760									if (self.__options.animationDuration[0] > 0) {
1761										self._$tooltip.delay(self.__options.animationDuration[0]);
1762									}
1763									
1764									self._$tooltip.queue(finish);
1765								}
1766								else {
1767									// in case the tooltip was currently fading out, bring it back
1768									// to life
1769									self._$tooltip
1770										.stop()
1771										.fadeIn(finish);
1772								}
1773							}
1774							// if the tooltip is already open, we still need to trigger the method
1775							// custom callback
1776							else if (self.__state == 'stable') {
1777								finish();
1778							}
1779						}
1780						// if the tooltip isn't already open, open it
1781						else {
1782							
1783							// a plugin must bind on this and store the tooltip in this._$tooltip
1784							self.__stateSet('appearing');
1785							
1786							// the timer (if any) will start when the tooltip has fully appeared
1787							// after its transition
1788							extraTime = self.__options.animationDuration[0];
1789							
1790							// insert the content inside the tooltip
1791							self.__contentInsert();
1792							
1793							// reposition the tooltip and attach to the DOM
1794							self.reposition(event, true);
1795							
1796							// animate in the tooltip. If the display plugin wants no css
1797							// animations, it may override the animation option with a
1798							// dummy value that will produce no effect
1799							if (env.hasTransitions) {
1800								
1801								// note: there seems to be an issue with start animations which
1802								// are randomly not played on fast devices in both Chrome and FF,
1803								// couldn't find a way to solve it yet. It seems that applying
1804								// the classes before appending to the DOM helps a little, but
1805								// it messes up some CSS transitions. The issue almost never
1806								// happens when delay[0]==0 though
1807								self._$tooltip
1808									.addClass('tooltipster-'+ self.__options.animation)
1809									.addClass('tooltipster-initial')
1810									.css({
1811										'-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
1812										'-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
1813										'-o-animation-duration': self.__options.animationDuration[0] + 'ms',
1814										'-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
1815										'animation-duration': self.__options.animationDuration[0] + 'ms',
1816										'transition-duration': self.__options.animationDuration[0] + 'ms'
1817									});
1818								
1819								setTimeout(
1820									function() {
1821										
1822										// a quick hover may have already triggered a mouseleave
1823										if (self.__state != 'closed') {
1824											
1825											self._$tooltip
1826												.addClass('tooltipster-show')
1827												.removeClass('tooltipster-initial');
1828											
1829											if (self.__options.animationDuration[0] > 0) {
1830												self._$tooltip.delay(self.__options.animationDuration[0]);
1831											}
1832											
1833											self._$tooltip.queue(finish);
1834										}
1835									},
1836									0
1837								);
1838							}
1839							else {
1840								
1841								// old browsers will have to live with this
1842								self._$tooltip
1843									.css('display', 'none')
1844									.fadeIn(self.__options.animationDuration[0], finish);
1845							}
1846							
1847							// checks if the origin is removed while the tooltip is open
1848							self.__trackerStart();
1849							
1850							// NOTE: the listeners below have a '-triggerClose' namespace
1851							// because we'll remove them when the tooltip closes (unlike
1852							// the '-triggerOpen' listeners). So some of them are actually
1853							// not about close triggers, rather about positioning.
1854							
1855							$(env.window)
1856								// reposition on resize
1857								.on('resize.'+ self.__namespace +'-triggerClose', function(e) {
1858									
1859									var $ae = $(document.activeElement);
1860									
1861									// reposition only if the resize event was not triggered upon the opening
1862									// of a virtual keyboard due to an input field being focused within the tooltip
1863									// (otherwise the repositioning would lose the focus)
1864									if (	(!$ae.is('input') && !$ae.is('textarea'))
1865										||	!$.contains(self._$tooltip[0], $ae[0])
1866									) {
1867										self.reposition(e);
1868									}
1869								})
1870								// same as below for parents
1871								.on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1872									self.__scrollHandler(e);
1873								});
1874							
1875							self.__$originParents = self._$origin.parents();
1876							
1877							// scrolling may require the tooltip to be moved or even
1878							// repositioned in some cases
1879							self.__$originParents.each(function(i, parent) {
1880								
1881								$(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1882									self.__scrollHandler(e);
1883								});
1884							});
1885							
1886							if (	self.__options.triggerClose.mouseleave
1887								||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
1888							) {
1889								
1890								// we use an event to allow users/plugins to control when the mouseleave/touchleave
1891								// close triggers will come to action. It allows to have more triggering elements
1892								// than just the origin and the tooltip for example, or to cancel/delay the closing,
1893								// or to make the tooltip interactive even if it wasn't when it was open, etc.
1894								self._on('dismissable', function(event) {
1895									
1896									if (event.dismissable) {
1897										
1898										if (event.delay) {
1899											
1900											timeout = setTimeout(function() {
1901												// event.event may be undefined
1902												self._close(event.event);
1903											}, event.delay);
1904											
1905											self.__timeouts.close.push(timeout);
1906										}
1907										else {
1908											self._close(event);
1909										}
1910									}
1911									else {
1912										clearTimeout(timeout);
1913									}
1914								});
1915								
1916								// now set the listeners that will trigger 'dismissable' events
1917								var $elements = self._$origin,
1918									eventNamesIn = '',
1919									eventNamesOut = '',
1920									timeout = null;
1921								
1922								// if we have to allow interaction, bind on the tooltip too
1923								if (self.__options.interactive) {
1924									$elements = $elements.add(self._$tooltip);
1925								}
1926								
1927								if (self.__options.triggerClose.mouseleave) {
1928									eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
1929									eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
1930								}
1931								if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1932									eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
1933									eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
1934								}
1935								
1936								$elements
1937									// close after some time spent outside of the elements
1938									.on(eventNamesOut, function(event) {
1939										
1940										// it's ok if the touch gesture ended up to be a swipe,
1941										// it's still a "touch leave" situation
1942										if (	self._touchIsTouchEvent(event)
1943											||	!self._touchIsEmulatedEvent(event)
1944										) {
1945											
1946											var delay = (event.type == 'mouseleave') ?
1947												self.__options.delay :
1948												self.__options.delayTouch;
1949											
1950											self._trigger({
1951												delay: delay[1],
1952												dismissable: true,
1953												event: event,
1954												type: 'dismissable'
1955											});
1956										}
1957									})
1958									// suspend the mouseleave timeout when the pointer comes back
1959									// over the elements
1960									.on(eventNamesIn, function(event) {
1961										
1962										// it's also ok if the touch event is a swipe gesture
1963										if (	self._touchIsTouchEvent(event)
1964											||	!self._touchIsEmulatedEvent(event)
1965										) {
1966											self._trigger({
1967												dismissable: false,
1968												event: event,
1969												type: 'dismissable'
1970											});
1971										}
1972									});
1973							}
1974							
1975							// close the tooltip when the origin gets a mouse click (common behavior of
1976							// native tooltips)
1977							if (self.__options.triggerClose.originClick) {
1978								
1979								self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
1980									
1981									// we could actually let a tap trigger this but this feature just
1982									// does not make sense on touch devices
1983									if (	!self._touchIsTouchEvent(event)
1984										&&	!self._touchIsEmulatedEvent(event)
1985									) {
1986										self._close(event);
1987									}
1988								});
1989							}
1990							
1991							// set the same bindings for click and touch on the body to close the tooltip
1992							if (	self.__options.triggerClose.click
1993								||	(self.__options.triggerClose.tap && env.hasTouchCapability)
1994							) {
1995								
1996								// don't set right away since the click/tap event which triggered this method
1997								// (if it was a click/tap) is going to bubble up to the body, we don't want it
1998								// to close the tooltip immediately after it opened
1999								setTimeout(function() {
2000									
2001									if (self.__state != 'closed') {
2002										
2003										var eventNames = '',
2004											$body = $(env.window.document.body);
2005										
2006										if (self.__options.triggerClose.click) {
2007											eventNames += 'click.'+ self.__namespace +'-triggerClose ';
2008										}
2009										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2010											eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
2011										}
2012										
2013										$body.on(eventNames, function(event) {
2014											
2015											if (self._touchIsMeaningfulEvent(event)) {
2016												
2017												self._touchRecordEvent(event);
2018												
2019												if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
2020													self._close(event);
2021												}
2022											}
2023										});
2024										
2025										// needed to detect and ignore swiping
2026										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2027											
2028											$body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
2029												self._touchRecordEvent(event);
2030											});
2031										}
2032									}
2033								}, 0);
2034							}
2035							
2036							self._trigger('ready');
2037							
2038							// call our custom callback
2039							if (self.__options.functionReady) {
2040								self.__options.functionReady.call(self, self, {
2041									origin: self._$origin[0],
2042									tooltip: self._$tooltip[0]
2043								});
2044							}
2045						}
2046						
2047						// if we have a timer set, let the countdown begin
2048						if (self.__options.timer > 0) {
2049							
2050							var timeout = setTimeout(function() {
2051								self._close();
2052							}, self.__options.timer + extraTime);
2053							
2054							self.__timeouts.close.push(timeout);
2055						}
2056					}
2057				}
2058			}
2059		}
2060		
2061		return self;
2062	},
2063	
2064	/**
2065	 * When using the mouseenter/touchstart open triggers, this function will
2066	 * schedule the opening of the tooltip after the delay, if there is one
2067	 *
2068	 * @param event
2069	 * @returns {self}
2070	 * @protected
2071 	 */
2072	_openShortly: function(event) {
2073		
2074		var self = this,
2075			ok = true;
2076		
2077		if (self.__state != 'stable' && self.__state != 'appearing') {
2078			
2079			// if a timeout is not already running
2080			if (!self.__timeouts.open) {
2081				
2082				self._trigger({
2083					type: 'start',
2084					event: event,
2085					stop: function() {
2086						ok = false;
2087					}
2088				});
2089				
2090				if (ok) {
2091					
2092					var delay = (event.type.indexOf('touch') == 0) ?
2093						self.__options.delayTouch :
2094						self.__options.delay;
2095					
2096					if (delay[0]) {
2097						
2098						self.__timeouts.open = setTimeout(function() {
2099							
2100							self.__timeouts.open = null;
2101							
2102							// open only if the pointer (mouse or touch) is still over the origin.
2103							// The check on the "meaningful event" can only be made here, after some
2104							// time has passed (to know if the touch was a swipe or not)
2105							if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
2106								
2107								// signal that we go on
2108								self._trigger('startend');
2109								
2110								self._open(event);
2111							}
2112							else {
2113								// signal that we cancel
2114								self._trigger('startcancel');
2115							}
2116						}, delay[0]);
2117					}
2118					else {
2119						// signal that we go on
2120						self._trigger('startend');
2121						
2122						self._open(event);
2123					}
2124				}
2125			}
2126		}
2127		
2128		return self;
2129	},
2130	
2131	/**
2132	 * Meant for plugins to get their options
2133	 * 
2134	 * @param {string} pluginName The name of the plugin that asks for its options
2135	 * @param {object} defaultOptions The default options of the plugin
2136	 * @returns {object} The options
2137	 * @protected
2138	 */
2139	_optionsExtract: function(pluginName, defaultOptions) {
2140		
2141		var self = this,
2142			options = $.extend(true, {}, defaultOptions);
2143		
2144		// if the plugin options were isolated in a property named after the
2145		// plugin, use them (prevents conflicts with other plugins)
2146		var pluginOptions = self.__options[pluginName];
2147		
2148		// if not, try to get them as regular options
2149		if (!pluginOptions){
2150			
2151			pluginOptions = {};
2152			
2153			$.each(defaultOptions, function(optionName, value) {
2154				
2155				var o = self.__options[optionName];
2156				
2157				if (o !== undefined) {
2158					pluginOptions[optionName] = o;
2159				}
2160			});
2161		}
2162		
2163		// let's merge the default options and the ones that were provided. We'd want
2164		// to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
2165		// extend on two levels, that will be enough if options are not more than 1
2166		// level deep
2167		$.each(options, function(optionName, value) {
2168			
2169			if (pluginOptions[optionName] !== undefined) {
2170				
2171				if ((		typeof value == 'object'
2172						&&	!(value instanceof Array)
2173						&&	value != null
2174					)
2175					&&
2176					(		typeof pluginOptions[optionName] == 'object'
2177						&&	!(pluginOptions[optionName] instanceof Array)
2178						&&	pluginOptions[optionName] != null
2179					)
2180				) {
2181					$.extend(options[optionName], pluginOptions[optionName]);
2182				}
2183				else {
2184					options[optionName] = pluginOptions[optionName];
2185				}
2186			}
2187		});
2188		
2189		return options;
2190	},
2191	
2192	/**
2193	 * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
2194	 * on existing instances
2195	 * 
2196	 * @param {object} pluginName
2197	 * @returns {self}
2198	 * @protected
2199	 */
2200	_plug: function(pluginName) {
2201		
2202		var plugin = $.tooltipster._plugin(pluginName);
2203		
2204		if (plugin) {
2205			
2206			// if there is a constructor for instances
2207			if (plugin.instance) {
2208				
2209				// proxy non-private methods on the instance to allow new instance methods
2210				$.tooltipster.__bridge(plugin.instance, this, plugin.name);
2211			}
2212		}
2213		else {
2214			throw new Error('The "'+ pluginName +'" plugin is not defined');
2215		}
2216		
2217		return this;
2218	},
2219	
2220	/**
2221	 * This will return true if the event is a mouse event which was
2222	 * emulated by the browser after a touch event. This allows us to
2223	 * really dissociate mouse and touch triggers.
2224	 * 
2225	 * There is a margin of error if a real mouse event is fired right
2226	 * after (within the delay shown below) a touch event on the same
2227	 * element, but hopefully it should not happen often.
2228	 * 
2229	 * @returns {boolean}
2230	 * @protected
2231	 */
2232	_touchIsEmulatedEvent: function(event) {
2233		
2234		var isEmulated = false,
2235			now = new Date().getTime();
2236		
2237		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2238			
2239			var e = this.__touchEvents[i];
2240			
2241			// delay, in milliseconds. It's supposed to be 300ms in
2242			// most browsers (350ms on iOS) to allow a double tap but
2243			// can be less (check out FastClick for more info)
2244			if (now - e.time < 500) {
2245				
2246				if (e.target === event.target) {
2247					isEmulated = true;
2248				}
2249			}
2250			else {
2251				break;
2252			}
2253		}
2254		
2255		return isEmulated;
2256	},
2257	
2258	/**
2259	 * Returns false if the event was an emulated mouse event or
2260	 * a touch event involved in a swipe gesture.
2261	 * 
2262	 * @param {object} event
2263	 * @returns {boolean}
2264	 * @protected
2265	 */
2266	_touchIsMeaningfulEvent: function(event) {
2267		return (
2268				(this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
2269			||	(!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
2270		);
2271	},
2272	
2273	/**
2274	 * Checks if an event is a touch event
2275	 * 
2276	 * @param {object} event
2277	 * @returns {boolean}
2278	 * @protected
2279	 */
2280	_touchIsTouchEvent: function(event){
2281		return event.type.indexOf('touch') == 0;
2282	},
2283	
2284	/**
2285	 * Store touch events for a while to detect swiping and emulated mouse events
2286	 * 
2287	 * @param {object} event
2288	 * @returns {self}
2289	 * @protected
2290	 */
2291	_touchRecordEvent: function(event) {
2292		
2293		if (this._touchIsTouchEvent(event)) {
2294			event.time = new Date().getTime();
2295			this.__touchEvents.push(event);
2296		}
2297		
2298		return this;
2299	},
2300	
2301	/**
2302	 * Returns true if a swipe happened after the last touchstart event fired on
2303	 * event.target.
2304	 * 
2305	 * We need to differentiate a swipe from a tap before we let the event open
2306	 * or close the tooltip. A swipe is when a touchmove (scroll) event happens
2307	 * on the body between the touchstart and the touchend events of an element.
2308	 * 
2309	 * @param {object} target The HTML element that may have triggered the swipe
2310	 * @returns {boolean}
2311	 * @protected
2312	 */
2313	_touchSwiped: function(target) {
2314		
2315		var swiped = false;
2316		
2317		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2318			
2319			var e = this.__touchEvents[i];
2320			
2321			if (e.type == 'touchmove') {
2322				swiped = true;
2323				break;
2324			}
2325			else if (
2326				e.type == 'touchstart'
2327				&&	target === e.target
2328			) {
2329				break;
2330			}
2331		}
2332		
2333		return swiped;
2334	},
2335	
2336	/**
2337	 * Triggers an event on the instance emitters
2338	 * 
2339	 * @returns {self}
2340	 * @protected
2341	 */
2342	_trigger: function() {
2343		
2344		var args = Array.prototype.slice.apply(arguments);
2345		
2346		if (typeof args[0] == 'string') {
2347			args[0] = { type: args[0] };
2348		}
2349		
2350		// add properties to the event
2351		args[0].instance = this;
2352		args[0].origin = this._$origin ? this._$origin[0] : null;
2353		args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
2354		
2355		// note: the order of emitters matters
2356		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
2357		$.tooltipster._trigger.apply($.tooltipster, args);
2358		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
2359		
2360		return this;
2361	},
2362	
2363	/**
2364	 * Deactivate a plugin on this instance
2365	 * 
2366	 * @returns {self}
2367	 * @protected
2368	 */
2369	_unplug: function(pluginName) {
2370		
2371		var self = this;
2372		
2373		// if the plugin has been activated on this instance
2374		if (self[pluginName]) {
2375			
2376			var plugin = $.tooltipster._plugin(pluginName);
2377			
2378			// if there is a constructor for instances
2379			if (plugin.instance) {
2380				
2381				// unbridge
2382				$.each(plugin.instance, function(methodName, fn) {
2383					
2384					// if the method exists (privates methods do not) and comes indeed from
2385					// this plugin (may be missing or come from a conflicting plugin).
2386					if (	self[methodName]
2387						&&	self[methodName].bridged === self[pluginName]
2388					) {
2389						delete self[methodName];
2390					}
2391				});
2392			}
2393			
2394			// destroy the plugin
2395			if (self[pluginName].__destroy) {
2396				self[pluginName].__destroy();
2397			}
2398			
2399			// remove the reference to the plugin instance
2400			delete self[pluginName];
2401		}
2402		
2403		return self;
2404	},
2405	
2406	/**
2407	 * @see self::_close
2408	 * @returns {self}
2409	 * @public
2410	 */
2411	close: function(callback) {
2412		
2413		if (!this.__destroyed) {
2414			this._close(null, callback);
2415		}
2416		else {
2417			this.__destroyError();
2418		}
2419		
2420		return this;
2421	},
2422	
2423	/**
2424	 * Sets or gets the content of the tooltip
2425	 * 
2426	 * @returns {mixed|self}
2427	 * @public
2428	 */
2429	content: function(content) {
2430		
2431		var self = this;
2432		
2433		// getter method
2434		if (content === undefined) {
2435			return self.__Content;
2436		}
2437		// setter method
2438		else {
2439			
2440			if (!self.__destroyed) {
2441				
2442				// change the content
2443				self.__contentSet(content);
2444				
2445				if (self.__Content !== null) {
2446					
2447					// update the tooltip if it is open
2448					if (self.__state !== 'closed') {
2449						
2450						// reset the content in the tooltip
2451						self.__contentInsert();
2452						
2453						// reposition and resize the tooltip
2454						self.reposition();
2455						
2456						// if we want to play a little animation showing the content changed
2457						if (self.__options.updateAnimation) {
2458							
2459							if (env.hasTransitions) {
2460								
2461								// keep the reference in the local scope
2462								var animation = self.__options.updateAnimation;
2463								
2464								self._$tooltip.addClass('tooltipster-update-'+ animation);
2465								
2466								// remove the class after a while. The actual duration of the
2467								// update animation may be shorter, it's set in the CSS rules
2468								setTimeout(function() {
2469									
2470									if (self.__state != 'closed') {
2471										
2472										self._$tooltip.removeClass('tooltipster-update-'+ animation);
2473									}
2474								}, 1000);
2475							}
2476							else {
2477								self._$tooltip.fadeTo(200, 0.5, function() {
2478									if (self.__state != 'closed') {
2479										self._$tooltip.fadeTo(200, 1);
2480									}
2481								});
2482							}
2483						}
2484					}
2485				}
2486				else {
2487					self._close();
2488				}
2489			}
2490			else {
2491				self.__destroyError();
2492			}
2493			
2494			return self;
2495		}
2496	},
2497	
2498	/**
2499	 * Destroys the tooltip
2500	 * 
2501	 * @returns {self}
2502	 * @public
2503	 */
2504	destroy: function() {
2505		
2506		var self = this;
2507		
2508		if (!self.__destroyed) {
2509			
2510			if(self.__state != 'closed'){
2511				
2512				// no closing delay
2513				self.option('animationDuration', 0)
2514					// force closing
2515					._close(null, null, true);
2516			}
2517			else {
2518				// there might be an open timeout still running
2519				self.__timeoutsClear();
2520			}
2521			
2522			// send event
2523			self._trigger('destroy');
2524			
2525			self.__destroyed = true;
2526			
2527			self._$origin
2528				.removeData(self.__namespace)
2529				// remove the open trigger listeners
2530				.off('.'+ self.__namespace +'-triggerOpen');
2531			
2532			// remove the touch listener
2533			$(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
2534			
2535			var ns = self._$origin.data('tooltipster-ns');
2536			
2537			// if the origin has been removed from DOM, its data may
2538			// well have been destroyed in the process and there would
2539			// be nothing to clean up or restore
2540			if (ns) {
2541				
2542				// if there are no more tooltips on this element
2543				if (ns.length === 1) {
2544					
2545					// optional restoration of a title attribute
2546					var title = null;
2547					if (self.__options.restoration == 'previous') {
2548						title = self._$origin.data('tooltipster-initialTitle');
2549					}
2550					else if (self.__options.restoration == 'current') {
2551						
2552						// old school technique to stringify when outerHTML is not supported
2553						title = (typeof self.__Content == 'string') ?
2554							self.__Content :
2555							$('<div></div>').append(self.__Content).html();
2556					}
2557					
2558					if (title) {
2559						self._$origin.attr('title', title);
2560					}
2561					
2562					// final cleaning
2563					
2564					self._$origin.removeClass('tooltipstered');
2565					
2566					self._$origin
2567						.removeData('tooltipster-ns')
2568						.removeData('tooltipster-initialTitle');
2569				}
2570				else {
2571					// remove the instance namespace from the list of namespaces of
2572					// tooltips present on the element
2573					ns = $.grep(ns, function(el, i) {
2574						return el !== self.__namespace;
2575					});
2576					self._$origin.data('tooltipster-ns', ns);
2577				}
2578			}
2579			
2580			// last event
2581			self._trigger('destroyed');
2582			
2583			// unbind private and public event listeners
2584			self._off();
2585			self.off();
2586			
2587			// remove external references, just in case
2588			self.__Content = null;
2589			self.__$emitterPrivate = null;
2590			self.__$emitterPublic = null;
2591			self.__options.parent = null;
2592			self._$origin = null;
2593			self._$tooltip = null;
2594			
2595			// make sure the object is no longer referenced in there to prevent
2596			// memory leaks
2597			$.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
2598				return self !== el;
2599			});
2600			
2601			clearInterval(self.__garbageCollector);
2602		}
2603		else {
2604			self.__destroyError();
2605		}
2606		
2607		// we return the scope rather than true so that the call to
2608		// .tooltipster('destroy') actually returns the matched elements
2609		// and applies to all of them
2610		return self;
2611	},
2612	
2613	/**
2614	 * Disables the tooltip
2615	 * 
2616	 * @returns {self}
2617	 * @public
2618	 */
2619	disable: function() {
2620		
2621		if (!this.__destroyed) {
2622			
2623			// close first, in case the tooltip would not disappear on
2624			// its own (no close trigger)
2625			this._close();
2626			this.__enabled = false;
2627			
2628			return this;
2629		}
2630		else {
2631			this.__destroyError();
2632		}
2633		
2634		return this;
2635	},
2636	
2637	/**
2638	 * Returns the HTML element of the origin
2639	 *
2640	 * @returns {self}
2641	 * @public
2642	 */
2643	elementOrigin: function() {
2644		
2645		if (!this.__destroyed) {
2646			return this._$origin[0];
2647		}
2648		else {
2649			this.__destroyError();
2650		}
2651	},
2652	
2653	/**
2654	 * Returns the HTML element of the tooltip
2655	 *
2656	 * @returns {self}
2657	 * @public
2658	 */
2659	elementTooltip: function() {
2660		return this._$tooltip ? this._$tooltip[0] : null;
2661	},
2662	
2663	/**
2664	 * Enables the tooltip
2665	 * 
2666	 * @returns {self}
2667	 * @public
2668	 */
2669	enable: function() {
2670		this.__enabled = true;
2671		return this;
2672	},
2673	
2674	/**
2675	 * Alias, deprecated in 4.0.0
2676	 * 
2677	 * @param {function} callback
2678	 * @returns {self}
2679	 * @public
2680	 */
2681	hide: function(callback) {
2682		return this.close(callback);
2683	},
2684	
2685	/**
2686	 * Returns the instance
2687	 * 
2688	 * @returns {self}
2689	 * @public
2690	 */
2691	instance: function() {
2692		return this;
2693	},
2694	
2695	/**
2696	 * For public use only, not to be used by plugins (use ::_off() instead)
2697	 * 
2698	 * @returns {self}
2699	 * @public
2700	 */
2701	off: function() {
2702		
2703		if (!this.__destroyed) {
2704			this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2705		}
2706		
2707		return this;
2708	},
2709	
2710	/**
2711	 * For public use only, not to be used by plugins (use ::_on() instead)
2712	 *
2713	 * @returns {self}
2714	 * @public
2715	 */
2716	on: function() {
2717		
2718		if (!this.__destroyed) {
2719			this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2720		}
2721		else {
2722			this.__destroyError();
2723		}
2724		
2725		return this;
2726	},
2727	
2728	/**
2729	 * For public use only, not to be used by plugins
2730	 *
2731	 * @returns {self}
2732	 * @public
2733	 */
2734	one: function() {
2735		
2736		if (!this.__destroyed) {
2737			this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2738		}
2739		else {
2740			this.__destroyError();
2741		}
2742		
2743		return this;
2744	},
2745	
2746	/**
2747	 * @see self::_open
2748	 * @returns {self}
2749	 * @public
2750	 */
2751	open: function(callback) {
2752		
2753		if (!this.__destroyed) {
2754			this._open(null, callback);
2755		}
2756		else {
2757			this.__destroyError();
2758		}
2759		
2760		return this;
2761	},
2762	
2763	/**
2764	 * Get or set options. For internal use and advanced users only.
2765	 * 
2766	 * @param {string} o Option name
2767	 * @param {mixed} val optional A new value for the option
2768	 * @return {mixed|self} If val is omitted, the value of the option
2769	 * is returned, otherwise the instance itself is returned
2770	 * @public
2771	 */ 
2772	option: function(o, val) {
2773		
2774		// getter
2775		if (val === undefined) {
2776			return this.__options[o];
2777		}
2778		// setter
2779		else {
2780			
2781			if (!this.__destroyed) {
2782				
2783				// change value
2784				this.__options[o] = val;
2785				
2786				// format
2787				this.__optionsFormat();
2788				
2789				// re-prepare the triggers if needed
2790				if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
2791					this.__prepareOrigin();
2792				}
2793				
2794				if (o === 'selfDestruction') {
2795					this.__prepareGC();
2796				}
2797			}
2798			else {
2799				this.__destroyError();
2800			}
2801			
2802			return this;
2803		}
2804	},
2805	
2806	/**
2807	 * This method is in charge of setting the position and size properties of the tooltip.
2808	 * All the hard work is delegated to the display plugin.
2809	 * Note: The tooltip may be detached from the DOM at the moment the method is called 
2810	 * but must be attached by the end of the method call.
2811	 * 
2812	 * @param {object} event For internal use only. Defined if an event such as
2813	 * window resizing triggered the repositioning
2814	 * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
2815	 * know that the tooltip not being in the DOM is not an issue (typically when the
2816	 * tooltip element has just been created but has not been added to the DOM yet).
2817	 * @returns {self}
2818	 * @public
2819	 */
2820	reposition: function(event, tooltipIsDetached) {
2821		
2822		var self = this;
2823		
2824		if (!self.__destroyed) {
2825			
2826			// if the tooltip is still open and the origin is still in the DOM
2827			if (self.__state != 'closed' && bodyContains(self._$origin)) {
2828				
2829				// if the tooltip has not been removed from DOM manually (or if it
2830				// has been detached on purpose)
2831				if (tooltipIsDetached || bodyContains(self._$tooltip)) {
2832					
2833					if (!tooltipIsDetached) {
2834						// detach in case the tooltip overflows the window and adds
2835						// scrollbars to it, so __geometry can be accurate
2836						self._$tooltip.detach();
2837					}
2838					
2839					// refresh the geometry object before passing it as a helper
2840					self.__Geometry = self.__geometry();
2841					
2842					// let a plugin fo the rest
2843					self._trigger({
2844						type: 'reposition',
2845						event: event,
2846						helper: {
2847							geo: self.__Geometry
2848						}
2849					});
2850				}
2851			}
2852		}
2853		else {
2854			self.__destroyError();
2855		}
2856		
2857		return self;
2858	},
2859	
2860	/**
2861	 * Alias, deprecated in 4.0.0
2862	 *
2863	 * @param callback
2864	 * @returns {self}
2865	 * @public
2866	 */
2867	show: function(callback) {
2868		return this.open(callback);
2869	},
2870	
2871	/**
2872	 * Returns some properties about the instance
2873	 * 
2874	 * @returns {object}
2875	 * @public
2876	 */
2877	status: function() {
2878		
2879		return {
2880			destroyed: this.__destroyed,
2881			enabled: this.__enabled,
2882			open: this.__state !== 'closed',
2883			state: this.__state
2884		};
2885	},
2886	
2887	/**
2888	 * For public use only, not to be used by plugins
2889	 *
2890	 * @returns {self}
2891	 * @public
2892	 */
2893	triggerHandler: function() {
2894		
2895		if (!this.__destroyed) {
2896			this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2897		}
2898		else {
2899			this.__destroyError();
2900		}
2901		
2902		return this;
2903	}
2904};
2905
2906$.fn.tooltipster = function() {
2907	
2908	// for using in closures
2909	var args = Array.prototype.slice.apply(arguments),
2910		// common mistake: an HTML element can't be in several tooltips at the same time
2911		contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
2912	
2913	// this happens with $(sel).tooltipster(...) when $(sel) does not match anything
2914	if (this.length === 0) {
2915		
2916		// still chainable
2917		return this;
2918	}
2919	// this happens when calling $(sel).tooltipster('methodName or options')
2920	// where $(sel) matches one or more elements
2921	else {
2922		
2923		// method calls
2924		if (typeof args[0] === 'string') {
2925			
2926			var v = '#*$~&';
2927			
2928			this.each(function() {
2929				
2930				// retrieve the namepaces of the tooltip(s) that exist on that element.
2931				// We will interact with the first tooltip only.
2932				var ns = $(this).data('tooltipster-ns'),
2933					// self represents the instance of the first tooltipster plugin
2934					// associated to the current HTML object of the loop
2935					self = ns ? $(this).data(ns[0]) : null;
2936				
2937				// if the current element holds a tooltipster instance
2938				if (self) {
2939					
2940					if (typeof self[args[0]] === 'function') {
2941						
2942						if (	this.length > 1
2943							&&	args[0] == 'content'
2944							&&	(	args[1] instanceof $
2945								|| (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
2946							)
2947							&&	!self.__options.contentCloning
2948							&&	self.__options.debug
2949						) {
2950							console.log(contentCloningWarning);
2951						}
2952						
2953						// note : args[1] and args[2] may not be defined
2954						var resp = self[args[0]](args[1], args[2]);
2955					}
2956					else {
2957						throw new Error('Unknown method "'+ args[0] +'"');
2958					}
2959					
2960					// if the function returned anything other than the instance
2961					// itself (which implies chaining, except for the `instance` method)
2962					if (resp !== self || args[0] === 'instance') {
2963						
2964						v = resp;
2965						
2966						// return false to stop .each iteration on the first element
2967						// matched by the selector
2968						return false;
2969					}
2970				}
2971				else {
2972					throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
2973				}
2974			});
2975			
2976			return (v !== '#*$~&') ? v : this;
2977		}
2978		// first argument is undefined or an object: the tooltip is initializing
2979		else {
2980			
2981			// reset the array of last initialized objects
2982			$.tooltipster.__instancesLatestArr = [];
2983			
2984			// is there a defined value for the multiple option in the options object ?
2985			var	multipleIsSet = args[0] && args[0].multiple !== undefined,
2986				// if the multiple option is set to true, or if it's not defined but
2987				// set to true in the defaults
2988				multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
2989				// same for content
2990				contentIsSet = args[0] && args[0].content !== undefined,
2991				content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
2992				// same for contentCloning
2993				contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
2994				contentCloning =
2995						(contentCloningIsSet && args[0].contentCloning)
2996					||	(!contentCloningIsSet && defaults.contentCloning),
2997				// same for debug
2998				debugIsSet = args[0] && args[0].debug !== undefined,
2999				debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
3000			
3001			if (	this.length > 1
3002				&&	(	content instanceof $
3003					|| (typeof content == 'object' && content != null && content.tagName)
3004				)
3005				&&	!contentCloning
3006				&&	debug
3007			) {
3008				console.log(contentCloningWarning);
3009			}
3010			
3011			// create a tooltipster instance for each element if it doesn't
3012			// already have one or if the multiple option is set, and attach the
3013			// object to it
3014			this.each(function() {
3015				
3016				var go = false,
3017					$this = $(this),
3018					ns = $this.data('tooltipster-ns'),
3019					obj = null;
3020				
3021				if (!ns) {
3022					go = true;
3023				}
3024				else if (multiple) {
3025					go = true;
3026				}
3027				else if (debug) {
3028					console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
3029					console.log(this);
3030				}
3031				
3032				if (go) {
3033					obj = new $.Tooltipster(this, args[0]);
3034					
3035					// save the reference of the new instance
3036					if (!ns) ns = [];
3037					ns.push(obj.__namespace);
3038					$this.data('tooltipster-ns', ns);
3039					
3040					// save the instance itself
3041					$this.data(obj.__namespace, obj);
3042					
3043					// call our constructor custom function.
3044					// we do this here and not in ::init() because we wanted
3045					// the object to be saved in $this.data before triggering
3046					// it
3047					if (obj.__options.functionInit) {
3048						obj.__options.functionInit.call(obj, obj, {
3049							origin: this
3050						});
3051					}
3052					
3053					// and now the event, for the plugins and core emitter
3054					obj._trigger('init');
3055				}
3056				
3057				$.tooltipster.__instancesLatestArr.push(obj);
3058			});
3059			
3060			return this;
3061		}
3062	}
3063};
3064
3065// Utilities
3066
3067/**
3068 * A class to check if a tooltip can fit in given dimensions
3069 * 
3070 * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
3071 */
3072function Ruler($tooltip) {
3073	
3074	// list of instance variables
3075	
3076	this.$container;
3077	this.constraints = null;
3078	this.__$tooltip;
3079	
3080	this.__init($tooltip);
3081}
3082
3083Ruler.prototype = {
3084	
3085	/**
3086	 * Move the tooltip into an invisible div that does not allow overflow to make
3087	 * size tests. Note: the tooltip may or may not be attached to the DOM at the
3088	 * moment this method is called, it does not matter.
3089	 * 
3090	 * @param {object} $tooltip The object to test. May be just a clone of the
3091	 * actual tooltip.
3092	 * @private
3093	 */
3094	__init: function($tooltip) {
3095		
3096		this.__$tooltip = $tooltip;
3097		
3098		this.__$tooltip
3099			.css({
3100				// for some reason we have to specify top and left 0
3101				left: 0,
3102				// any overflow will be ignored while measuring
3103				overflow: 'hidden',
3104				// positions at (0,0) without the div using 100% of the available width
3105				position: 'absolute',
3106				top: 0
3107			})
3108			// overflow must be auto during the test. We re-set this in case
3109			// it were modified by the user
3110			.find('.tooltipster-content')
3111				.css('overflow', '
3111visible
vendor: 7,092 bytes, lines 3111-3343
3111');
3112		
3113		this.$container = $('<div class="tooltipster-ruler"></div>')
3114			.append(this.__$tooltip)
3115			.appendTo(env.window.document.body);
3116	},
3117	
3118	/**
3119	 * Force the browser to redraw (re-render) the tooltip immediately. This is required
3120	 * when you changed some CSS properties and need to make something with it
3121	 * immediately, without waiting for the browser to redraw at the end of instructions.
3122	 *
3123	 * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
3124	 * @private
3125	 */
3126	__forceRedraw: function() {
3127		
3128		// note: this would work but for Webkit only
3129		//this.__$tooltip.close();
3130		//this.__$tooltip[0].offsetHeight;
3131		//this.__$tooltip.open();
3132		
3133		// works in FF too
3134		var $p = this.__$tooltip.parent();
3135		this.__$tooltip.detach();
3136		this.__$tooltip.appendTo($p);
3137	},
3138	
3139	/**
3140	 * Set maximum dimensions for the tooltip. A call to ::measure afterwards
3141	 * will tell us if the content overflows or if it's ok
3142	 *
3143	 * @param {int} width
3144	 * @param {int} height
3145	 * @return {Ruler}
3146	 * @public
3147	 */
3148	constrain: function(width, height) {
3149		
3150		this.constraints = {
3151			width: width,
3152			height: height
3153		};
3154		
3155		this.__$tooltip.css({
3156			// we disable display:flex, otherwise the content would overflow without
3157			// creating horizontal scrolling (which we need to detect).
3158			display: 'block',
3159			// reset any previous height
3160			height: '',
3161			// we'll check if horizontal scrolling occurs
3162			overflow: 'auto',
3163			// we'll set the width and see what height is generated and if there
3164			// is horizontal overflow
3165			width: width
3166		});
3167		
3168		return this;
3169	},
3170	
3171	/**
3172	 * Reset the tooltip content overflow and remove the test container
3173	 * 
3174	 * @returns {Ruler}
3175	 * @public
3176	 */
3177	destroy: function() {
3178		
3179		// in case the element was not a clone
3180		this.__$tooltip
3181			.detach()
3182			.find('.tooltipster-content')
3183				.css({
3184					// reset to CSS value
3185					display: '',
3186					overflow: ''
3187				});
3188		
3189		this.$container.remove();
3190	},
3191	
3192	/**
3193	 * Removes any constraints
3194	 * 
3195	 * @returns {Ruler}
3196	 * @public
3197	 */
3198	free: function() {
3199		
3200		this.constraints = null;
3201		
3202		// reset to natural size
3203		this.__$tooltip.css({
3204			display: '',
3205			height: '',
3206			overflow: 'visible',
3207			width: ''
3208		});
3209		
3210		return this;
3211	},
3212	
3213	/**
3214	 * Returns the size of the tooltip. When constraints are applied, also returns
3215	 * whether the tooltip fits in the provided dimensions.
3216	 * The idea is to see if the new height is small enough and if the content does
3217	 * not overflow horizontally.
3218	 *
3219	 * @param {int} width
3220	 * @param {int} height
3221	 * @returns {object} An object with a bool `fits` property and a `size` property
3222	 * @public
3223	 */
3224	measure: function() {
3225		
3226		this.__forceRedraw();
3227		
3228		var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
3229			result = { size: {
3230				// bcr.width/height are not defined in IE8- but in this
3231				// case, bcr.right/bottom will have the same value
3232				// except in iOS 8+ where tooltipBcr.bottom/right are wrong
3233				// after scrolling for reasons yet to be determined.
3234				// tooltipBcr.top/left might not be 0, see issue #514
3235				height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
3236				width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
3237			}};
3238		
3239		if (this.constraints) {
3240			
3241			// note: we used to use offsetWidth instead of boundingRectClient but
3242			// it returned rounded values, causing issues with sub-pixel layouts.
3243			
3244			// note2: noticed that the bcrWidth of text content of a div was once
3245			// greater than the bcrWidth of its container by 1px, causing the final
3246			// tooltip box to be too small for its content. However, evaluating
3247			// their widths one against the other (below) surprisingly returned
3248			// equality. Happened only once in Chrome 48, was not able to reproduce
3249			// => just having fun with float position values...
3250			
3251			var $content = this.__$tooltip.find('.tooltipster-content'),
3252				height = this.__$tooltip.outerHeight(),
3253				contentBcr = $content[0].getBoundingClientRect(),
3254				fits = {
3255					height: height <= this.constraints.height,
3256					width: (
3257						// this condition accounts for min-width property that
3258						// may apply
3259						tooltipBcr.width <= this.constraints.width
3260							// the -1 is here because scrollWidth actually returns
3261							// a rounded value, and may be greater than bcr.width if
3262							// it was rounded up. This may cause an issue for contents
3263							// which actually really overflow  by 1px or so, but that
3264							// should be rare. Not sure how to solve this efficiently.
3265							// See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
3266						&&	contentBcr.width >= $content[0].scrollWidth - 1
3267					)
3268				};
3269			
3270			result.fits = fits.height && fits.width;
3271		}
3272		
3273		// old versions of IE get the width wrong for some reason and it causes
3274		// the text to be broken to a new line, so we round it up. If the width
3275		// is the width of the screen though, we can assume it is accurate.
3276		if (	env.IE
3277			&&	env.IE <= 11
3278			&&	result.size.width !== env.window.document.documentElement.clientWidth
3279		) {
3280			result.size.width = Math.ceil(result.size.width) + 1;
3281		}
3282		
3283		return result;
3284	}
3285};
3286
3287// quick & dirty compare function, not bijective nor multidimensional
3288function areEqual(a,b) {
3289	var same = true;
3290	$.each(a, function(i, _) {
3291		if (b[i] === undefined || a[i] !== b[i]) {
3292			same = false;
3293			return false;
3294		}
3295	});
3296	return same;
3297}
3298
3299/**
3300 * A fast function to check if an element is still in the DOM. It
3301 * tries to use an id as ids are indexed by the browser, or falls
3302 * back to jQuery's `contains` method. May fail if two elements
3303 * have the same id, but so be it
3304 *
3305 * @param {object} $obj A jQuery-wrapped HTML element
3306 * @return {boolean}
3307 */
3308function bodyContains($obj) {
3309	var id = $obj.attr('id'),
3310		el = id ? env.window.document.getElementById(id) : null;
3311	// must also check that the element with the id is the one we want
3312	return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
3313}
3314
3315// detect IE versions for dirty fixes
3316var uA = navigator.userAgent.toLowerCase();
3317if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
3318else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
3319else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
3320
3321// detecting support for CSS transitions
3322function transitionSupport() {
3323	
3324	// env.window is not defined yet when this is called
3325	if (!win) return false;
3326	
3327	var b = win.document.body || win.document.documentElement,
3328		s = b.style,
3329		p = 'transition',
3330		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
3331	
3332	if (typeof s[p] == 'string') { return true; }
3333	
3334	p = p.charAt(0).toUpperCase() + p.substr(1);
3335	for (var i=0; i<v.length; i++) {
3336		if (typeof s[v[i] + p] == 'string') { return true; }
3337	}
3338	return false;
3339}
3340
3341// we'll return jQuery for plugins not to have to declare it as a dependency,
3342// but it's done by a build task since it should be included only once at the
3343// end when we concatenate the main file with a plugin
vendor: 5,217 bytes, lines 3343-3529
3343
3344// sideTip is Tooltipster's default plugin.
3345// This file will be UMDified by a build task.
3346
3347var pluginName = 'tooltipster.sideTip';
3348
3349$.tooltipster._plugin({
3350	name: pluginName,
3351	instance: {
3352		/**
3353		 * Defaults are provided as a function for an easy override by inheritance
3354		 *
3355		 * @return {object} An object with the defaults options
3356		 * @private
3357		 */
3358		__defaults: function() {
3359			
3360			return {
3361				// if the tooltip should display an arrow that points to the origin
3362				arrow: true,
3363				// the distance in pixels between the tooltip and the origin
3364				distance: 6,
3365				// allows to easily change the position of the tooltip
3366				functionPosition: null,
3367				maxWidth: null,
3368				// used to accomodate the arrow of tooltip if there is one.
3369				// First to make sure that the arrow target is not too close
3370				// to the edge of the tooltip, so the arrow does not overflow
3371				// the tooltip. Secondly when we reposition the tooltip to
3372				// make sure that it's positioned in such a way that the arrow is
3373				// still pointing at the target (and not a few pixels beyond it).
3374				// It should be equal to or greater than half the width of
3375				// the arrow (by width we mean the size of the side which touches
3376				// the side of the tooltip).
3377				minIntersection: 16,
3378				minWidth: 0,
3379				// deprecated in 4.0.0. Listed for _optionsExtract to pick it up
3380				position: null,
3381				side: 'top',
3382				// set to false to position the tooltip relatively to the document rather
3383				// than the window when we open it
3384				viewportAware: true
3385			};
3386		},
3387		
3388		/**
3389		 * Run once: at instantiation of the plugin
3390		 *
3391		 * @param {object} instance The tooltipster object that instantiated this plugin
3392		 * @private
3393		 */
3394		__init: function(instance) {
3395			
3396			var self = this;
3397			
3398			// list of instance variables
3399			
3400			self.__instance = instance;
3401			self.__namespace = 'tooltipster-sideTip-'+ Math.round(Math.random()*1000000);
3402			self.__previousState = 'closed';
3403			self.__options;
3404			
3405			// initial formatting
3406			self.__optionsFormat();
3407			
3408			self.__instance._on('state.'+ self.__namespace, function(event) {
3409				
3410				if (event.state == 'closed') {
3411					self.__close();
3412				}
3413				else if (event.state == 'appearing' && self.__previousState == 'closed') {
3414					self.__create();
3415				}
3416				
3417				self.__previousState = event.state;
3418			});
3419			
3420			// reformat every time the options are changed
3421			self.__instance._on('options.'+ self.__namespace, function() {
3422				self.__optionsFormat();
3423			});
3424			
3425			self.__instance._on('reposition.'+ self.__namespace, function(e) {
3426				self.__reposition(e.event, e.helper);
3427			});
3428		},
3429		
3430		/**
3431		 * Called when the tooltip has closed
3432		 * 
3433		 * @private
3434		 */
3435		__close: function() {
3436			
3437			// detach our content object first, so the next jQuery's remove()
3438			// call does not unbind its event handlers
3439			if (this.__instance.content() instanceof $) {
3440				this.__instance.content().detach();
3441			}
3442			
3443			// remove the tooltip from the DOM
3444			this.__instance._$tooltip.remove();
3445			this.__instance._$tooltip = null;
3446		},
3447		
3448		/**
3449		 * Creates the HTML element of the tooltip.
3450		 * 
3451		 * @private
3452		 */
3453		__create: function() {
3454			
3455			// note: we wrap with a .tooltipster-box div to be able to set a margin on it
3456			// (.tooltipster-base must not have one)
3457			var $html = $(
3458				'<div class="tooltipster-base tooltipster-sidetip">' +
3459					'<div class="tooltipster-box">' +
3460						'<div class="tooltipster-content"></div>' +
3461					'</div>' +
3462					'<div class="tooltipster-arrow">' +
3463						'<div class="tooltipster-arrow-uncropped">' +
3464							'<div class="tooltipster-arrow-border"></div>' +
3465							'<div class="tooltipster-arrow-background"></div>' +
3466						'</div>' +
3467					'</div>' +
3468				'</div>'
3469			);
3470			
3471			// hide arrow if asked
3472			if (!this.__options.arrow) {
3473				$html
3474					.find('.tooltipster-box')
3475						.css('margin', 0)
3476						.end()
3477					.find('.tooltipster-arrow')
3478						.hide();
3479			}
3480			
3481			// apply min/max width if asked
3482			if (this.__options.minWidth) {
3483				$html.css('min-width', this.__options.minWidth + 'px');
3484			}
3485			if (this.__options.maxWidth) {
3486				$html.css('max-width', this.__options.maxWidth + 'px');
3487			}
3488			
3489			this.__instance._$tooltip = $html;
3490			
3491			// tell the instance that the tooltip element has been created
3492			this.__instance._trigger('created');
3493		},
3494		
3495		/**
3496		 * Used when the plugin is to be unplugged
3497		 *
3498		 * @private
3499		 */
3500		__destroy: function() {
3501			this.__instance._off('.'+ self.__namespace);
3502		},
3503		
3504		/**
3505		 * (Re)compute this.__options from the options declared to the instance
3506		 *
3507		 * @private
3508		 */
3509		__optionsFormat: function() {
3510			
3511			var self = this;
3512			
3513			// get the options
3514			self.__options = self.__instance._optionsExtract(pluginName, self.__defaults());
3515			
3516			// for backward compatibility, deprecated in v4.0.0
3517			if (self.__options.position) {
3518				self.__options.side = self.__options.position;
3519			}
3520			
3521			// options formatting
3522			
3523			// format distance as a four-cell array if it ain't one yet and then make
3524			// it an object with top/bottom/left/right properties
3525			if (typeof self.__options.distance != 'object') {
3526				self.__options.distance = [self.__options.distance];
3527			}
3528			if (self.__options.distance.length < 4) {
3529				
3529
3530				if (self.__options.distance[1] === undefined) self.__options.distance[1] = self.__options.distance[0];
3531				if (self.__options.distance[2] === undefined) self.__options.distance[2] = self.__options.distance[0];
3532				if (self.__options.distance[3] === undefined) self.__options.distance[3] = self.__options.distance[1];
3533				
3534				self.__options.distance = {
3535					top: self.__options.distance[0],
3536					right: self.__options.distance[1],
3537					bottom: self.__options.distance[2],
3538					left: self.__options.distance[3]
3539				};
3540			}
vendor: 24,845 bytes, lines 3540-4273
3540
3541			
3542			// let's transform:
3543			// 'top' into ['top', 'bottom', 'right', 'left']
3544			// 'right' into ['right', 'left', 'top', 'bottom']
3545			// 'bottom' into ['bottom', 'top', 'right', 'left']
3546			// 'left' into ['left', 'right', 'top', 'bottom']
3547			if (typeof self.__options.side == 'string') {
3548				
3549				var opposites = {
3550					'top': 'bottom',
3551					'right': 'left',
3552					'bottom': 'top',
3553					'left': 'right'
3554				};
3555				
3556				self.__options.side = [self.__options.side, opposites[self.__options.side]];
3557				
3558				if (self.__options.side[0] == 'left' || self.__options.side[0] == 'right') {
3559					self.__options.side.push('top', 'bottom');
3560				}
3561				else {
3562					self.__options.side.push('right', 'left');
3563				}
3564			}
3565			
3566			// misc
3567			// disable the arrow in IE6 unless the arrow option was explicitly set to true
3568			if (	$.tooltipster._env.IE === 6
3569				&&	self.__options.arrow !== true
3570			) {
3571				self.__options.arrow = false;
3572			}
3573		},
3574		
3575		/**
3576		 * This method must compute and set the positioning properties of the
3577		 * tooltip (left, top, width, height, etc.). It must also make sure the
3578		 * tooltip is eventually appended to its parent (since the element may be
3579		 * detached from the DOM at the moment the method is called).
3580		 *
3581		 * We'll evaluate positioning scenarios to find which side can contain the
3582		 * tooltip in the best way. We'll consider things relatively to the window
3583		 * (unless the user asks not to), then to the document (if need be, or if the
3584		 * user explicitly requires the tests to run on the document). For each
3585		 * scenario, measures are taken, allowing us to know how well the tooltip
3586		 * is going to fit. After that, a sorting function will let us know what
3587		 * the best scenario is (we also allow the user to choose his favorite
3588		 * scenario by using an event).
3589		 * 
3590		 * @param {object} helper An object that contains variables that plugin
3591		 * creators may find useful (see below)
3592		 * @param {object} helper.geo An object with many layout properties
3593		 * about objects of interest (window, document, origin). This should help
3594		 * plugin users compute the optimal position of the tooltip
3595		 * @private
3596		 */
3597		__reposition: function(event, helper) {
3598			
3599			var self = this,
3600				finalResult,
3601				// to know where to put the tooltip, we need to know on which point
3602				// of the x or y axis we should center it. That coordinate is the target
3603				targets = self.__targetFind(helper),
3604				testResults = [];
3605			
3606			// make sure the tooltip is detached while we make tests on a clone
3607			self.__instance._$tooltip.detach();
3608			
3609			// we could actually provide the original element to the Ruler and
3610			// not a clone, but it just feels right to keep it out of the
3611			// machinery.
3612			var $clone = self.__instance._$tooltip.clone(),
3613				// start position tests session
3614				ruler = $.tooltipster._getRuler($clone),
3615				satisfied = false,
3616				animation = self.__instance.option('animation');
3617			
3618			// an animation class could contain properties that distort the size
3619			if (animation) {
3620				$clone.removeClass('tooltipster-'+ animation);
3621			}
3622			
3623			// start evaluating scenarios
3624			$.each(['window', 'document'], function(i, container) {
3625				
3626				var takeTest = null;
3627				
3628				// let the user decide to keep on testing or not
3629				self.__instance._trigger({
3630					container: container,
3631					helper: helper,
3632					satisfied: satisfied,
3633					takeTest: function(bool) {
3634						takeTest = bool;
3635					},
3636					results: testResults,
3637					type: 'positionTest'
3638				});
3639				
3640				if (	takeTest == true
3641					||	(	takeTest != false
3642						&&	satisfied == false
3643							// skip the window scenarios if asked. If they are reintegrated by
3644							// the callback of the positionTest event, they will have to be
3645							// excluded using the callback of positionTested
3646						&&	(container != 'window' || self.__options.viewportAware)
3647					)
3648				) {
3649					
3650					// for each allowed side
3651					for (var i=0; i < self.__options.side.length; i++) {
3652						
3653						var distance = {
3654								horizontal: 0,
3655								vertical: 0
3656							},
3657							side = self.__options.side[i];
3658						
3659						if (side == 'top' || side == 'bottom') {
3660							distance.vertical = self.__options.distance[side];
3661						}
3662						else {
3663							distance.horizontal = self.__options.distance[side];
3664						}
3665						
3666						// this may have an effect on the size of the tooltip if there are css
3667						// rules for the arrow or something else
3668						self.__sideChange($clone, side);
3669						
3670						$.each(['natural', 'constrained'], function(i, mode) {
3671							
3672							takeTest = null;
3673							
3674							// emit an event on the instance
3675							self.__instance._trigger({
3676								container: container,
3677								event: event,
3678								helper: helper,
3679								mode: mode,
3680								results: testResults,
3681								satisfied: satisfied,
3682								side: side,
3683								takeTest: function(bool) {
3684									takeTest = bool;
3685								},
3686								type: 'positionTest'
3687							});
3688							
3689							if (	takeTest == true
3690								||	(	takeTest != false
3691									&&	satisfied == false
3692								)
3693							) {
3694								
3695								var testResult = {
3696									container: container,
3697									// we let the distance as an object here, it can make things a little easier
3698									// during the user's calculations at positionTest/positionTested
3699									distance: distance,
3700									// whether the tooltip can fit in the size of the viewport (does not mean
3701									// that we'll be able to make it initially entirely visible, see 'whole')
3702									fits: null,
3703									mode: mode,
3704									outerSize: null,
3705									side: side,
3706									size: null,
3707									target: targets[side],
3708									// check if the origin has enough surface on screen for the tooltip to
3709									// aim at it without overflowing the viewport (this is due to the thickness
3710									// of the arrow represented by the minIntersection length).
3711									// If not, the tooltip will have to be partly or entirely off screen in
3712									// order to stay docked to the origin. This value will stay null when the
3713									// container is the document, as it is not relevant
3714									whole: null
3715								};
3716								
3717								// get the size of the tooltip with or without size constraints
3718								var rulerConfigured = (mode == 'natural') ?
3719										ruler.free() :
3720										ruler.constrain(
3721											helper.geo.available[container][side].width - distance.horizontal,
3722											helper.geo.available[container][side].height - distance.vertical
3723										),
3724									rulerResults = rulerConfigured.measure();
3725								
3726								testResult.size = rulerResults.size;
3727								testResult.outerSize = {
3728									height: rulerResults.size.height + distance.vertical,
3729									width: rulerResults.size.width + distance.horizontal
3730								};
3731								
3732								if (mode == 'natural') {
3733									
3734									if(		helper.geo.available[container][side].width >= testResult.outerSize.width
3735										&&	helper.geo.available[container][side].height >= testResult.outerSize.height
3736									) {
3737										testResult.fits = true;
3738									}
3739									else {
3740										testResult.fits = false;
3741									}
3742								}
3743								else {
3744									testResult.fits = rulerResults.fits;
3745								}
3746								
3747								if (container == 'window') {
3748									
3749									if (!testResult.fits) {
3750										testResult.whole = false;
3751									}
3752									else {
3753										if (side == 'top' || side == 'bottom') {
3754											
3755											testResult.whole = (
3756													helper.geo.origin.windowOffset.right >= self.__options.minIntersection
3757												&&	helper.geo.window.size.width - helper.geo.origin.windowOffset.left >= self.__options.minIntersection
3758											);
3759										}
3760										else {
3761											testResult.whole = (
3762													helper.geo.origin.windowOffset.bottom >= self.__options.minIntersection
3763												&&	helper.geo.window.size.height - helper.geo.origin.windowOffset.top >= self.__options.minIntersection
3764											);
3765										}
3766									}
3767								}
3768								
3769								testResults.push(testResult);
3770								
3771								// we don't need to compute more positions if we have one fully on screen
3772								if (testResult.whole) {
3773									satisfied = true;
3774								}
3775								else {
3776									// don't run the constrained test unless the natural width was greater
3777									// than the available width, otherwise it's pointless as we know it
3778									// wouldn't fit either
3779									if (	testResult.mode == 'natural'
3780										&&	(	testResult.fits
3781											||	testResult.size.width <= helper.geo.available[container][side].width
3782										)
3783									) {
3784										return false;
3785									}
3786								}
3787							}
3788						});
3789					}
3790				}
3791			});
3792			
3793			// the user may eliminate the unwanted scenarios from testResults, but he's
3794			// not supposed to alter them at this point. functionPosition and the
3795			// position event serve that purpose.
3796			self.__instance._trigger({
3797				edit: function(r) {
3798					testResults = r;
3799				},
3800				event: event,
3801				helper: helper,
3802				results: testResults,
3803				type: 'positionTested'
3804			});
3805			
3806			/**
3807			 * Sort the scenarios to find the favorite one.
3808			 * 
3809			 * The favorite scenario is when we can fully display the tooltip on screen,
3810			 * even if it means that the middle of the tooltip is no longer centered on
3811			 * the middle of the origin (when the origin is near the edge of the screen
3812			 * or even partly off screen). We want the tooltip on the preferred side,
3813			 * even if it means that we have to use a constrained size rather than a
3814			 * natural one (as long as it fits). When the origin is off screen at the top
3815			 * the tooltip will be positioned at the bottom (if allowed), if the origin
3816			 * is off screen on the right, it will be positioned on the left, etc.
3817			 * If there are no scenarios where the tooltip can fit on screen, or if the
3818			 * user does not want the tooltip to fit on screen (viewportAware == false),
3819			 * we fall back to the scenarios relative to the document.
3820			 * 
3821			 * When the tooltip is bigger than the viewport in either dimension, we stop
3822			 * looking at the window scenarios and consider the document scenarios only,
3823			 * with the same logic to find on which side it would fit best.
3824			 * 
3825			 * If the tooltip cannot fit the document on any side, we force it at the
3826			 * bottom, so at least the user can scroll to see it.
3827 			 */
3828			testResults.sort(function(a, b) {
3829				
3830				// best if it's whole (the tooltip fits and adapts to the viewport)
3831				if (a.whole && !b.whole) {
3832					return -1;
3833				}
3834				else if (!a.whole && b.whole) {
3835					return 1;
3836				}
3837				else if (a.whole && b.whole) {
3838					
3839					var ai = self.__options.side.indexOf(a.side),
3840						bi = self.__options.side.indexOf(b.side);
3841					
3842					// use the user's sides fallback array
3843					if (ai < bi) {
3844						return -1;
3845					}
3846					else if (ai > bi) {
3847						return 1;
3848					}
3849					else {
3850						// will be used if the user forced the tests to continue
3851						return a.mode == 'natural' ? -1 : 1;
3852					}
3853				}
3854				else {
3855					
3856					// better if it fits
3857					if (a.fits && !b.fits) {
3858						return -1;
3859					}
3860					else if (!a.fits && b.fits) {
3861						return 1;
3862					}
3863					else if (a.fits && b.fits) {
3864						
3865						var ai = self.__options.side.indexOf(a.side),
3866							bi = self.__options.side.indexOf(b.side);
3867						
3868						// use the user's sides fallback array
3869						if (ai < bi) {
3870							return -1;
3871						}
3872						else if (ai > bi) {
3873							return 1;
3874						}
3875						else {
3876							// will be used if the user forced the tests to continue
3877							return a.mode == 'natural' ? -1 : 1;
3878						}
3879					}
3880					else {
3881						
3882						// if everything failed, this will give a preference to the case where
3883						// the tooltip overflows the document at the bottom
3884						if (	a.container == 'document'
3885							&&	a.side == 'bottom'
3886							&&	a.mode == 'natural'
3887						) {
3888							return -1;
3889						}
3890						else {
3891							return 1;
3892						}
3893					}
3894				}
3895			});
3896			
3897			finalResult = testResults[0];
3898			
3899			
3900			// now let's find the coordinates of the tooltip relatively to the window
3901			finalResult.coord = {};
3902			
3903			switch (finalResult.side) {
3904				
3905				case 'left':
3906				case 'right':
3907					finalResult.coord.top = Math.floor(finalResult.target - finalResult.size.height / 2);
3908					break;
3909				
3910				case 'bottom':
3911				case 'top':
3912					finalResult.coord.left = Math.floor(finalResult.target - finalResult.size.width / 2);
3913					break;
3914			}
3915			
3916			switch (finalResult.side) {
3917				
3918				case 'left':
3919					finalResult.coord.left = helper.geo.origin.windowOffset.left - finalResult.outerSize.width;
3920					break;
3921				
3922				case 'right':
3923					finalResult.coord.left = helper.geo.origin.windowOffset.right + finalResult.distance.horizontal;
3924					break;
3925				
3926				case 'top':
3927					finalResult.coord.top = helper.geo.origin.windowOffset.top - finalResult.outerSize.height;
3928					break;
3929				
3930				case 'bottom':
3931					finalResult.coord.top = helper.geo.origin.windowOffset.bottom + finalResult.distance.vertical;
3932					break;
3933			}
3934			
3935			// if the tooltip can potentially be contained within the viewport dimensions
3936			// and that we are asked to make it fit on screen
3937			if (finalResult.container == 'window') {
3938				
3939				// if the tooltip overflows the viewport, we'll move it accordingly (then it will
3940				// not be centered on the middle of the origin anymore). We only move horizontally
3941				// for top and bottom tooltips and vice versa.
3942				if (finalResult.side == 'top' || finalResult.side == 'bottom') {
3943					
3944					// if there is an overflow on the left
3945					if (finalResult.coord.left < 0) {
3946						
3947						// prevent the overflow unless the origin itself gets off screen (minus the
3948						// margin needed to keep the arrow pointing at the target)
3949						if (helper.geo.origin.windowOffset.right - this.__options.minIntersection >= 0) {
3950							finalResult.coord.left = 0;
3951						}
3952						else {
3953							finalResult.coord.left = helper.geo.origin.windowOffset.right - this.__options.minIntersection - 1;
3954						}
3955					}
3956					// or an overflow on the right
3957					else if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
3958						
3959						if (helper.geo.origin.windowOffset.left + this.__options.minIntersection <= helper.geo.window.size.width) {
3960							finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
3961						}
3962						else {
3963							finalResult.coord.left = helper.geo.origin.windowOffset.left + this.__options.minIntersection + 1 - finalResult.size.width;
3964						}
3965					}
3966				}
3967				else {
3968					
3969					// overflow at the top
3970					if (finalResult.coord.top < 0) {
3971						
3972						if (helper.geo.origin.windowOffset.bottom - this.__options.minIntersection >= 0) {
3973							finalResult.coord.top = 0;
3974						}
3975						else {
3976							finalResult.coord.top = helper.geo.origin.windowOffset.bottom - this.__options.minIntersection - 1;
3977						}
3978					}
3979					// or at the bottom
3980					else if (finalResult.coord.top > helper.geo.window.size.height - finalResult.size.height) {
3981						
3982						if (helper.geo.origin.windowOffset.top + this.__options.minIntersection <= helper.geo.window.size.height) {
3983							finalResult.coord.top = helper.geo.window.size.height - finalResult.size.height;
3984						}
3985						else {
3986							finalResult.coord.top = helper.geo.origin.windowOffset.top + this.__options.minIntersection + 1 - finalResult.size.height;
3987						}
3988					}
3989				}
3990			}
3991			else {
3992				
3993				// there might be overflow here too but it's easier to handle. If there has
3994				// to be an overflow, we'll make sure it's on the right side of the screen
3995				// (because the browser will extend the document size if there is an overflow
3996				// on the right, but not on the left). The sort function above has already
3997				// made sure that a bottom document overflow is preferred to a top overflow,
3998				// so we don't have to care about it.
3999				
4000				// if there is an overflow on the right
4001				if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
4002					
4003					// this may actually create on overflow on the left but we'll fix it in a sec
4004					finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
4005				}
4006				
4007				// if there is an overflow on the left
4008				if (finalResult.coord.left < 0) {
4009					
4010					// don't care if it overflows the right after that, we made our best
4011					finalResult.coord.left = 0;
4012				}
4013			}
4014			
4015			
4016			// submit the positioning proposal to the user function which may choose to change
4017			// the side, size and/or the coordinates
4018			
4019			// first, set the rules that corresponds to the proposed side: it may change
4020			// the size of the tooltip, and the custom functionPosition may want to detect the
4021			// size of something before making a decision. So let's make things easier for the
4022			// implementor
4023			self.__sideChange($clone, finalResult.side);
4024			
4025			// add some variables to the helper
4026			helper.tooltipClone = $clone[0];
4027			helper.tooltipParent = self.__instance.option('parent').parent[0];
4028			// move informative values to the helper
4029			helper.mode = finalResult.mode;
4030			helper.whole = finalResult.whole;
4031			// add some variables to the helper for the functionPosition callback (these
4032			// will also be added to the event fired by self.__instance._trigger but that's
4033			// ok, we're just being consistent)
4034			helper.origin = self.__instance._$origin[0];
4035			helper.tooltip = self.__instance._$tooltip[0];
4036			
4037			// leave only the actionable values in there for functionPosition
4038			delete finalResult.container;
4039			delete finalResult.fits;
4040			delete finalResult.mode;
4041			delete finalResult.outerSize;
4042			delete finalResult.whole;
4043			
4044			// keep only the distance on the relevant side, for clarity
4045			finalResult.distance = finalResult.distance.horizontal || finalResult.distance.vertical;
4046			
4047			// beginners may not be comfortable with the concept of editing the object
4048			//  passed by reference, so we provide an edit function and pass a clone
4049			var finalResultClone = $.extend(true, {}, finalResult);
4050			
4051			// emit an event on the instance
4052			self.__instance._trigger({
4053				edit: function(result) {
4054					finalResult = result;
4055				},
4056				event: event,
4057				helper: helper,
4058				position: finalResultClone,
4059				type: 'position'
4060			});
4061			
4062			if (self.__options.functionPosition) {
4063				
4064				var result = self.__options.functionPosition.call(self, self.__instance, helper, finalResultClone);
4065				
4066				if (result) finalResult = result;
4067			}
4068			
4069			// end the positioning tests session (the user might have had a
4070			// use for it during the position event, now it's over)
4071			ruler.destroy();
4072			
4073			// compute the position of the target relatively to the tooltip root
4074			// element so we can place the arrow and make the needed adjustments
4075			var arrowCoord,
4076				maxVal;
4077			
4078			if (finalResult.side == 'top' || finalResult.side == 'bottom') {
4079				
4080				arrowCoord = {
4081					prop: 'left',
4082					val: finalResult.target - finalResult.coord.left
4083				};
4084				maxVal = finalResult.size.width - this.__options.minIntersection;
4085			}
4086			else {
4087				
4088				arrowCoord = {
4089					prop: 'top',
4090					val: finalResult.target - finalResult.coord.top
4091				};
4092				maxVal = finalResult.size.height - this.__options.minIntersection;
4093			}
4094			
4095			// cannot lie beyond the boundaries of the tooltip, minus the
4096			// arrow margin
4097			if (arrowCoord.val < this.__options.minIntersection) {
4098				arrowCoord.val = this.__options.minIntersection;
4099			}
4100			else if (arrowCoord.val > maxVal) {
4101				arrowCoord.val = maxVal;
4102			}
4103			
4104			var originParentOffset;
4105			
4106			// let's convert the window-relative coordinates into coordinates relative to the
4107			// future positioned parent that the tooltip will be appended to
4108			if (helper.geo.origin.fixedLineage) {
4109				
4110				// same as windowOffset when the position is fixed
4111				originParentOffset = helper.geo.origin.windowOffset;
4112			}
4113			else {
4114				
4115				// this assumes that the parent of the tooltip is located at
4116				// (0, 0) in the document, typically like when the parent is
4117				// <body>.
4118				// If we ever allow other types of parent, .tooltipster-ruler
4119				// will have to be appended to the parent to inherit css style
4120				// values that affect the display of the text and such.
4121				originParentOffset = {
4122					left: helper.geo.origin.windowOffset.left + helper.geo.window.scroll.left,
4123					top: helper.geo.origin.windowOffset.top + helper.geo.window.scroll.top
4124				};
4125			}
4126			
4127			finalResult.coord = {
4128				left: originParentOffset.left + (finalResult.coord.left - helper.geo.origin.windowOffset.left),
4129				top: originParentOffset.top + (finalResult.coord.top - helper.geo.origin.windowOffset.top)
4130			};
4131			
4132			// set position values on the original tooltip element
4133			
4134			self.__sideChange(self.__instance._$tooltip, finalResult.side);
4135			
4136			if (helper.geo.origin.fixedLineage) {
4137				self.__instance._$tooltip
4138					.css('position', 'fixed');
4139			}
4140			else {
4141				// CSS default
4142				self.__instance._$tooltip
4143					.css('position', '');
4144			}
4145			
4146			self.__instance._$tooltip
4147				.css({
4148					left: finalResult.coord.left,
4149					top: finalResult.coord.top,
4150					// we need to set a size even if the tooltip is in its natural size
4151					// because when the tooltip is positioned beyond the width of the body
4152					// (which is by default the width of the window; it will happen when
4153					// you scroll the window horizontally to get to the origin), its text
4154					// content will otherwise break lines at each word to keep up with the
4155					// body overflow strategy.
4156					height: finalResult.size.height,
4157					width: finalResult.size.width
4158				})
4159				.find('.tooltipster-arrow')
4160					.css({
4161						'left': '',
4162						'top': ''
4163					})
4164					.css(arrowCoord.prop, arrowCoord.val);
4165			
4166			// append the tooltip HTML element to its parent
4167			self.__instance._$tooltip.appendTo(self.__instance.option('parent'));
4168			
4169			self.__instance._trigger({
4170				type: 'repositioned',
4171				event: event,
4172				position: finalResult
4173			});
4174		},
4175		
4176		/**
4177		 * Make whatever modifications are needed when the side is changed. This has
4178		 * been made an independant method for easy inheritance in custom plugins based
4179		 * on this default plugin.
4180		 *
4181		 * @param {object} $obj
4182		 * @param {string} side
4183		 * @private
4184		 */
4185		__sideChange: function($obj, side) {
4186			
4187			$obj
4188				.removeClass('tooltipster-bottom')
4189				.removeClass('tooltipster-left')
4190				.removeClass('tooltipster-right')
4191				.removeClass('tooltipster-top')
4192				.addClass('tooltipster-'+ side);
4193		},
4194		
4195		/**
4196		 * Returns the target that the tooltip should aim at for a given side.
4197		 * The calculated value is a distance from the edge of the window
4198		 * (left edge for top/bottom sides, top edge for left/right side). The
4199		 * tooltip will be centered on that position and the arrow will be
4200		 * positioned there (as much as possible).
4201		 *
4202		 * @param {object} helper
4203		 * @return {integer}
4204		 * @private
4205		 */
4206		__targetFind: function(helper) {
4207			
4208			var target = {},
4209				rects = this.__instance._$origin[0].getClientRects();
4210			
4211			// these lines fix a Chrome bug (issue #491)
4212			if (rects.length > 1) {
4213				var opacity = this.__instance._$origin.css('opacity');
4214				if(opacity == 1) {
4215					this.__instance._$origin.css('opacity', 0.99);
4216					rects = this.__instance._$origin[0].getClientRects();
4217					this.__instance._$origin.css('opacity', 1);
4218				}
4219			}
4220			
4221			// by default, the target will be the middle of the origin
4222			if (rects.length < 2) {
4223				
4224				target.top = Math.floor(helper.geo.origin.windowOffset.left + (helper.geo.origin.size.width / 2));
4225				target.bottom = target.top;
4226				
4227				target.left = Math.floor(helper.geo.origin.windowOffset.top + (helper.geo.origin.size.height / 2));
4228				target.right = target.left;
4229			}
4230			// if multiple client rects exist, the element may be text split
4231			// up into multiple lines and the middle of the origin may not be
4232			// best option anymore. We need to choose the best target client rect
4233			else {
4234				
4235				// top: the first
4236				var targetRect = rects[0];
4237				target.top = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4238		
4239				// right: the middle line, rounded down in case there is an even
4240				// number of lines (looks more centered => check out the
4241				// demo with 4 split lines)
4242				if (rects.length > 2) {
4243					targetRect = rects[Math.ceil(rects.length / 2) - 1];
4244				}
4245				else {
4246					targetRect = rects[0];
4247				}
4248				target.right = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4249		
4250				// bottom: the last
4251				targetRect = rects[rects.length - 1];
4252				target.bottom = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4253		
4254				// left: the middle line, rounded up
4255				if (rects.length > 2) {
4256					targetRect = rects[Math.ceil((rects.length + 1) / 2) - 1];
4257				}
4258				else {
4259					targetRect = rects[rects.length - 1];
4260				}
4261				
4262				target.left = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4263			}
4264			
4265			return target;
4266		}
4267	}
4268});
4269
4270/* a build task will add "return $;" here */
4271return $;
4272
4273}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.