PageSourceSearch

https://www.despellesetdesbouillons.org/wordpress/wp-content/plugi…/js/tooltipster.bundle.js?ver=1.1.9

js despellesetdesbouillons.org collected 2026-09-25 23:06:16 UTC 118,873 bytes, 4,249 lines download raw bytes

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

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.