PageSourceSearch

https://fasad-gk.ru/wp-content/themes/wp-bootstrap-starter/plugins…/dist/js/tooltipster.main.js?ver=6.7.9

js fasad-gk.ru collected 2026-10-03 00:53:17 UTC 92,539 bytes, 3,344 lines download raw bytes

vendor: 68,153 bytes, lines 1-2424
1/**
2 * tooltipster http://iamceege.github.io/tooltipster/
3 * A rockin' custom tooltip jQuery plugin
4 * Developed by Caleb Jacob and Louis Ameline
5 * MIT license
6 */
7(function (root, factory) {
8  if (typeof define === 'function' && define.amd) {
9    // AMD. Register as an anonymous module unless amdModuleId is set
10    define(["jquery"], function (a0) {
11      return (factory(a0));
12    });
13  } else if (typeof exports === 'object') {
14    // Node. Does not work with strict CommonJS, but
15    // only CommonJS-like environments that support module.exports,
16    // like Node.
17    module.exports = factory(require("jquery"));
18  } else {
19    factory(jQuery);
20  }
21}(this, function ($) {
22
23// This file will be UMDified by a build task.
24
25var defaults = {
26		animation: 'fade',
27		animationDuration: 350,
28		content: null,
29		contentAsHTML: false,
30		contentCloning: false,
31		debug: true,
32		delay: 300,
33		delayTouch: [300, 500],
34		functionInit: null,
35		functionBefore: null,
36		functionReady: null,
37		functionAfter: null,
38		functionFormat: null,
39		IEmin: 6,
40		interactive: false,
41		multiple: false,
42		// will default to document.body, or must be an element positioned at (0, 0)
43		// in the document, typically like the very top views of an app.
44		parent: null,
45		plugins: ['sideTip'],
46		repositionOnScroll: false,
47		restoration: 'none',
48		selfDestruction: true,
49		theme: [],
50		timer: 0,
51		trackerInterval: 500,
52		trackOrigin: false,
53		trackTooltip: false,
54		trigger: 'hover',
55		triggerClose: {
56			click: false,
57			mouseleave: false,
58			originClick: false,
59			scroll: false,
60			tap: false,
61			touchleave: false
62		},
63		triggerOpen: {
64			click: false,
65			mouseenter: false,
66			tap: false,
67			touchstart: false
68		},
69		updateAnimation: 'rotate',
70		zIndex: 9999999
71	},
72	// we'll avoid using the 'window' global as a good practice but npm's
73	// jquery@<2.1.0 package actually requires a 'window' global, so not sure
74	// it's useful at all
75	win = (typeof window != 'undefined') ? window : null,
76	// env will be proxied by the core for plugins to have access its properties
77	env = {
78		// detect if this device can trigger touch events. Better have a false
79		// positive (unused listeners, that's ok) than a false negative.
80		// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
81		// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
82		hasTouchCapability: !!(
83			win
84			&&	(	'ontouchstart' in win
85				||	(win.DocumentTouch && win.document instanceof win.DocumentTouch)
86				||	win.navigator.maxTouchPoints
87			)
88		),
89		hasTransitions: transitionSupport(),
90		IE: false,
91		// don't set manually, it will be updated by a build task after the manifest
92		semVer: '4.2.6',
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 +' plugin conflicts with another plugin or native methods');
156					}
157				}
158			});
159			
160			obj[pluginName] = pluginInstance;
161		}
162		
163		return this;
164	},
165	
166	/**
167	 * For mockup in Node env if need be, for testing purposes
168	 *
169	 * @return {core}
170	 * @private
171	 */
172	__setWindow: function(window) {
173		env.window = window;
174		return this;
175	},
176	
177	/**
178	 * Returns a ruler, a tool to help measure the size of a tooltip under
179	 * various settings. Meant for plugins
180	 * 
181	 * @see Ruler
182	 * @return {object} A Ruler instance
183	 * @protected
184	 */
185	_getRuler: function($tooltip) {
186		return new Ruler($tooltip);
187	},
188	
189	/**
190	 * For internal use by plugins, if needed
191	 *
192	 * @return {core}
193	 * @protected
194	 */
195	_off: function() {
196		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
197		return this;
198	},
199	
200	/**
201	 * For internal use by plugins, if needed
202	 *
203	 * @return {core}
204	 * @protected
205	 */
206	_on: function() {
207		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
208		return this;
209	},
210	
211	/**
212	 * For internal use by plugins, if needed
213	 *
214	 * @return {core}
215	 * @protected
216	 */
217	_one: function() {
218		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
219		return this;
220	},
221	
222	/**
223	 * Returns (getter) or adds (setter) a plugin
224	 *
225	 * @param {string|object} plugin Provide a string (in the full form
226	 * "namespace.name") to use as as getter, an object to use as a setter
227	 * @return {object|core}
228	 * @protected
229	 */
230	_plugin: function(plugin) {
231		
232		var self = this;
233		
234		// getter
235		if (typeof plugin == 'string') {
236			
237			var pluginName = plugin,
238				p = null;
239			
240			// if the namespace is provided, it's easy to search
241			if (pluginName.indexOf('.') > 0) {
242				p = self.__plugins[pluginName];
243			}
244			// otherwise, return the first name that matches
245			else {
246				$.each(self.__plugins, function(i, plugin) {
247					
248					if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
249						p = plugin;
250						return false;
251					}
252				});
253			}
254			
255			return p;
256		}
257		// setter
258		else {
259			
260			// force namespaces
261			if (plugin.name.indexOf('.') < 0) {
262				throw new Error('Plugins must be namespaced');
263			}
264			
265			self.__plugins[plugin.name] = plugin;
266			
267			// if the plugin has core features
268			if (plugin.core) {
269				
270				// bridge non-private methods onto the core to allow new core methods
271				self.__bridge(plugin.core, self, plugin.name);
272			}
273			
274			return this;
275		}
276	},
277	
278	/**
279	 * Trigger events on the core emitters
280	 * 
281	 * @returns {core}
282	 * @protected
283	 */
284	_trigger: function() {
285		
286		var args = Array.prototype.slice.apply(arguments);
287		
288		if (typeof args[0] == 'string') {
289			args[0] = { type: args[0] };
290		}
291		
292		// note: the order of emitters matters
293		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
294		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
295		
296		return this;
297	},
298	
299	/**
300	 * Returns instances of all tooltips in the page or an a given element
301	 *
302	 * @param {string|HTML object collection} selector optional Use this
303	 * parameter to restrict the set of objects that will be inspected
304	 * for the retrieval of instances. By default, all instances in the
305	 * page are returned.
306	 * @return {array} An array of instance objects
307	 * @public
308	 */
309	instances: function(selector) {
310		
311		var instances = [],
312			sel = selector || '.tooltipstered';
313		
314		$(sel).each(function() {
315			
316			var $this = $(this),
317				ns = $this.data('tooltipster-ns');
318			
319			if (ns) {
320				
321				$.each(ns, function(i, namespace) {
322					instances.push($this.data(namespace));
323				});
324			}
325		});
326		
327		return instances;
328	},
329	
330	/**
331	 * Returns the Tooltipster objects generated by the last initializing call
332	 *
333	 * @return {array} An array of instance objects
334	 * @public
335	 */
336	instancesLatest: function() {
337		return this.__instancesLatestArr;
338	},
339	
340	/**
341	 * For public use only, not to be used by plugins (use ::_off() instead)
342	 *
343	 * @return {core}
344	 * @public
345	 */
346	off: function() {
347		this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
348		return this;
349	},
350	
351	/**
352	 * For public use only, not to be used by plugins (use ::_on() instead)
353	 *
354	 * @return {core}
355	 * @public
356	 */
357	on: function() {
358		this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
359		return this;
360	},
361	
362	/**
363	 * For public use only, not to be used by plugins (use ::_one() instead)
364	 * 
365	 * @return {core}
366	 * @public
367	 */
368	one: function() {
369		this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
370		return this;
371	},
372	
373	/**
374	 * Returns all HTML elements which have one or more tooltips
375	 *
376	 * @param {string} selector optional Use this to restrict the results
377	 * to the descendants of an element
378	 * @return {array} An array of HTML elements
379	 * @public
380	 */
381	origins: function(selector) {
382		
383		var sel = selector ?
384			selector +' ' :
385			'';
386		
387		return $(sel +'.tooltipstered').toArray();
388	},
389	
390	/**
391	 * Change default options for all future instances
392	 *
393	 * @param {object} d The options that should be made defaults
394	 * @return {core}
395	 * @public
396	 */
397	setDefaults: function(d) {
398		$.extend(defaults, d);
399		return this;
400	},
401	
402	/**
403	 * For users to trigger their handlers on the public emitter
404	 * 
405	 * @returns {core}
406	 * @public
407	 */
408	triggerHandler: function() {
409		this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
410		return this;
411	}
412};
413
414// $.tooltipster will be used to call core methods
415$.tooltipster = new core();
416
417// the Tooltipster instance class (mind the capital T)
418$.Tooltipster = function(element, options) {
419	
420	// list of instance variables
421	
422	// stack of custom callbacks provided as parameters to API methods
423	this.__callbacks = {
424		close: [],
425		open: []
426	};
427	// the schedule time of DOM removal
428	this.__closingTime;
429	// this will be the user content shown in the tooltip. A capital "C" is used
430	// because there is also a method called content()
431	this.__Content;
432	// for the size tracker
433	this.__contentBcr;
434	// to disable the tooltip after destruction
435	this.__destroyed = false;
436	// we can't emit directly on the instance because if a method with the same
437	// name as the event exists, it will be called by jQuery. Se we use a plain
438	// object as emitter. This emitter is for internal use by plugins,
439	// if needed.
440	this.__$emitterPrivate = $({});
441	// this emitter is for the user to listen to events without risking to mess
442	// with our internal listeners
443	this.__$emitterPublic = $({});
444	this.__enabled = true;
445	// the reference to the gc interval
446	this.__garbageCollector;
447	// various position and size data recomputed before each repositioning
448	this.__Geometry;
449	// the tooltip position, saved after each repositioning by a plugin
450	this.__lastPosition;
451	// a unique namespace per instance
452	this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
453	this.__options;
454	// will be used to support origins in scrollable areas
455	this.__$originParents;
456	this.__pointerIsOverOrigin = false;
457	// to remove themes if needed
458	this.__previousThemes = [];
459	// the state can be either: appearing, stable, disappearing, closed
460	this.__state = 'closed';
461	// timeout references
462	this.__timeouts = {
463		close: [],
464		open: null
465	};
466	// store touch events to be able to detect emulated mouse events
467	this.__touchEvents = [];
468	// the reference to the tracker interval
469	this.__tracker = null;
470	// the element to which this tooltip is associated
471	this._$origin;
472	// this will be the tooltip element (jQuery wrapped HTML element).
473	// It's the job of a plugin to create it and append it to the DOM
474	this._$tooltip;
475	
476	// launch
477	this.__init(element, options);
478};
479
480$.Tooltipster.prototype = {
481	
482	/**
483	 * @param origin
484	 * @param options
485	 * @private
486	 */
487	__init: function(origin, options) {
488		
489		var self = this;
490		
491		self._$origin = $(origin);
492		self.__options = $.extend(true, {}, defaults, options);
493		
494		// some options may need to be reformatted
495		self.__optionsFormat();
496		
497		// don't run on old IE if asked no to
498		if (	!env.IE
499			||	env.IE >= self.__options.IEmin
500		) {
501			
502			// note: the content is null (empty) by default and can stay that
503			// way if the plugin remains initialized but not fed any content. The
504			// tooltip will just not appear.
505			
506			// let's save the initial value of the title attribute for later
507			// restoration if need be.
508			var initialTitle = null;
509			
510			// it will already have been saved in case of multiple tooltips
511			if (self._$origin.data('tooltipster-initialTitle') === undefined) {
512				
513				initialTitle = self._$origin.attr('title');
514				
515				// we do not want initialTitle to be "undefined" because
516				// of how jQuery's .data() method works
517				if (initialTitle === undefined) initialTitle = null;
518				
519				self._$origin.data('tooltipster-initialTitle', initialTitle);
520			}
521			
522			// If content is provided in the options, it has precedence over the
523			// title attribute.
524			// Note: an empty string is considered content, only 'null' represents
525			// the absence of content.
526			// Also, an existing title="" attribute will result in an empty string
527			// content
528			if (self.__options.content !== null) {
529				self.__contentSet(self.__options.content);
530			}
531			else {
532				
533				var selector = self._$origin.attr('data-tooltip-content'),
534					$el;
535				
536				if (selector){
537					$el = $(selector);
538				}
539				
540				if ($el && $el[0]) {
541					self.__contentSet($el.first());
542				}
543				else {
544					self.__contentSet(initialTitle);
545				}
546			}
547			
548			self._$origin
549				// strip the title off of the element to prevent the default tooltips
550				// from popping up
551				.removeAttr('title')
552				// to be able to find all instances on the page later (upon window
553				// events in particular)
554				.addClass('tooltipstered');
555			
556			// set listeners on the origin
557			self.__prepareOrigin();
558			
559			// set the garbage collector
560			self.__prepareGC();
561			
562			// init plugins
563			$.each(self.__options.plugins, function(i, pluginName) {
564				self._plug(pluginName);
565			});
566			
567			// to detect swiping
568			if (env.hasTouchCapability) {
569				$(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
570					self._touchRecordEvent(event);
571				});
572			}
573			
574			self
575				// prepare the tooltip when it gets created. This event must
576				// be fired by a plugin
577				._on('created', function() {
578					self.__prepareTooltip();
579				})
580				// save position information when it's sent by a plugin
581				._on('repositioned', function(e) {
582					self.__lastPosition = e.position;
583				});
584		}
585		else {
586			self.__options.disabled = true;
587		}
588	},
589	
590	/**
591	 * Insert the content into the appropriate HTML element of the tooltip
592	 * 
593	 * @returns {self}
594	 * @private
595	 */
596	__contentInsert: function() {
597		
598		var self = this,
599			$el = self._$tooltip.find('.tooltipster-content'),
600			formattedContent = self.__Content,
601			format = function(content) {
602				formattedContent = content;
603			};
604		
605		self._trigger({
606			type: 'format',
607			content: self.__Content,
608			format: format
609		});
610		
611		if (self.__options.functionFormat) {
612			
613			formattedContent = self.__options.functionFormat.call(
614				self,
615				self,
616				{ origin: self._$origin[0] },
617				self.__Content
618			);
619		}
620		
621		if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
622			$el.text(formattedContent);
623		}
624		else {
625			$el
626				.empty()
627				.append(formattedContent);
628		}
629		
630		return self;
631	},
632	
633	/**
634	 * Save the content, cloning it beforehand if need be
635	 * 
636	 * @param content
637	 * @returns {self}
638	 * @private
639	 */
640	__contentSet: function(content) {
641		
642		// clone if asked. Cloning the object makes sure that each instance has its
643		// own version of the content (in case a same object were provided for several
644		// instances)
645		// reminder: typeof null === object
646		if (content instanceof $ && this.__options.contentCloning) {
647			content = content.clone(true);
648		}
649		
650		this.__Content = content;
651		
652		this._trigger({
653			type: 'updated',
654			content: content
655		});
656		
657		return this;
658	},
659	
660	/**
661	 * Error message about a method call made after destruction
662	 * 
663	 * @private
664	 */
665	__destroyError: function() {
666		throw new Error('This tooltip has been destroyed and cannot execute your method call.');
667	},
668	
669	/**
670	 * Gather all information about dimensions and available space,
671	 * called before every repositioning
672	 * 
673	 * @private
674	 * @returns {object}
675	 */
676	__geometry: function() {
677		
678		var	self = this,
679			$target = self._$origin,
680			originIsArea = self._$origin.is('area');
681		
682		// if this._$origin is a map area, the target we'll need
683		// the dimensions of is actually the image using the map,
684		// not the area itself
685		if (originIsArea) {
686			
687			var mapName = self._$origin.parent().attr('name');
688			
689			$target = $('img[usemap="#'+ mapName +'"]');
690		}
691		
692		var bcr = $target[0].getBoundingClientRect(),
693			$document = $(env.window.document),
694			$window = $(env.window),
695			$parent = $target,
696			// some useful properties of important elements
697			geo = {
698				// available space for the tooltip, see down below
699				available: {
700					document: null,
701					window: null
702				},
703				document: {
704					size: {
705						height: $document.height(),
706						width: $document.width()
707					}
708				},
709				window: {
710					scroll: {
711						// the second ones are for IE compatibility
712						left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
713						top: env.window.scrollY || env.window.document.documentElement.scrollTop
714					},
715					size: {
716						height: $window.height(),
717						width: $window.width()
718					}
719				},
720				origin: {
721					// the origin has a fixed lineage if itself or one of its
722					// ancestors has a fixed position
723					fixedLineage: false,
724					// relative to the document
725					offset: {},
726					size: {
727						height: bcr.bottom - bcr.top,
728						width: bcr.right - bcr.left
729					},
730					usemapImage: originIsArea ? $target[0] : null,
731					// relative to the window
732					windowOffset: {
733						bottom: bcr.bottom,
734						left: bcr.left,
735						right: bcr.right,
736						top: bcr.top
737					}
738				}
739			},
740			geoFixed = false;
741		
742		// if the element is a map area, some properties may need
743		// to be recalculated
744		if (originIsArea) {
745			
746			var shape = self._$origin.attr('shape'),
747				coords = self._$origin.attr('coords');
748			
749			if (coords) {
750				
751				coords = coords.split(',');
752				
753				$.map(coords, function(val, i) {
754					coords[i] = parseInt(val);
755				});
756			}
757			
758			// if the image itself is the area, nothing more to do
759			if (shape != 'default') {
760				
761				switch(shape) {
762					
763					case 'circle':
764						
765						var circleCenterLeft = coords[0],
766							circleCenterTop = coords[1],
767							circleRadius = coords[2],
768							areaTopOffset = circleCenterTop - circleRadius,
769							areaLeftOffset = circleCenterLeft - circleRadius;
770						
771						geo.origin.size.height = circleRadius * 2;
772						geo.origin.size.width = geo.origin.size.height;
773						
774						geo.origin.windowOffset.left += areaLeftOffset;
775						geo.origin.windowOffset.top += areaTopOffset;
776						
777						break;
778					
779					case 'rect':
780						
781						var areaLeft = coords[0],
782							areaTop = coords[1],
783							areaRight = coords[2],
784							areaBottom = coords[3];
785						
786						geo.origin.size.height = areaBottom - areaTop;
787						geo.origin.size.width = areaRight - areaLeft;
788						
789						geo.origin.windowOffset.left += areaLeft;
790						geo.origin.windowOffset.top += areaTop;
791						
792						break;
793					
794					case 'poly':
795						
796						var areaSmallestX = 0,
797							areaSmallestY = 0,
798							areaGreatestX = 0,
799							areaGreatestY = 0,
800							arrayAlternate = 'even';
801						
802						for (var i = 0; i < coords.length; i++) {
803							
804							var areaNumber = coords[i];
805							
806							if (arrayAlternate == 'even') {
807								
808								if (areaNumber > areaGreatestX) {
809									
810									areaGreatestX = areaNumber;
811									
812									if (i === 0) {
813										areaSmallestX = areaGreatestX;
814									}
815								}
816								
817								if (areaNumber < areaSmallestX) {
818									areaSmallestX = areaNumber;
819								}
820								
821								arrayAlternate = 'odd';
822							}
823							else {
824								if (areaNumber > areaGreatestY) {
825									
826									areaGreatestY = areaNumber;
827									
828									if (i == 1) {
829										areaSmallestY = areaGreatestY;
830									}
831								}
832								
833								if (areaNumber < areaSmallestY) {
834									areaSmallestY = areaNumber;
835								}
836								
837								arrayAlternate = 'even';
838							}
839						}
840						
841						geo.origin.size.height = areaGreatestY - areaSmallestY;
842						geo.origin.size.width = areaGreatestX - areaSmallestX;
843						
844						geo.origin.windowOffset.left += areaSmallestX;
845						geo.origin.windowOffset.top += areaSmallestY;
846						
847						break;
848				}
849			}
850		}
851		
852		// user callback through an event
853		var edit = function(r) {
854			geo.origin.size.height = r.height,
855				geo.origin.windowOffset.left = r.left,
856				geo.origin.windowOffset.top = r.top,
857				geo.origin.size.width = r.width
858		};
859		
860		self._trigger({
861			type: 'geometry',
862			edit: edit,
863			geometry: {
864				height: geo.origin.size.height,
865				left: geo.origin.windowOffset.left,
866				top: geo.origin.windowOffset.top,
867				width: geo.origin.size.width
868			}
869		});
870		
871		// calculate the remaining properties with what we got
872		
873		geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
874		geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
875		
876		geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
877		geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
878		geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
879		geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
880		
881		// the space that is available to display the tooltip relatively to the document
882		geo.available.document = {
883			bottom: {
884				height: geo.document.size.height - geo.origin.offset.bottom,
885				width: geo.document.size.width
886			},
887			left: {
888				height: geo.document.size.height,
889				width: geo.origin.offset.left
890			},
891			right: {
892				height: geo.document.size.height,
893				width: geo.document.size.width - geo.origin.offset.right
894			},
895			top: {
896				height: geo.origin.offset.top,
897				width: geo.document.size.width
898			}
899		};
900		
901		// the space that is available to display the tooltip relatively to the viewport
902		// (the resulting values may be negative if the origin overflows the viewport)
903		geo.available.window = {
904			bottom: {
905				// the inner max is here to make sure the available height is no bigger
906				// than the viewport height (when the origin is off screen at the top).
907				// The outer max just makes sure that the height is not negative (when
908				// the origin overflows at the bottom).
909				height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
910				width: geo.window.size.width
911			},
912			left: {
913				height: geo.window.size.height,
914				width: Math.max(geo.origin.windowOffset.left, 0)
915			},
916			right: {
917				height: geo.window.size.height,
918				width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
919			},
920			top: {
921				height: Math.max(geo.origin.windowOffset.top, 0),
922				width: geo.window.size.width
923			}
924		};
925		
926		while ($parent[0].tagName.toLowerCase() != 'html') {
927			
928			if ($parent.css('position') == 'fixed') {
929				geo.origin.fixedLineage = true;
930				break;
931			}
932			
933			$parent = $parent.parent();
934		}
935		
936		return geo;
937	},
938	
939	/**
940	 * Some options may need to be formated before being used
941	 * 
942	 * @returns {self}
943	 * @private
944	 */
945	__optionsFormat: function() {
946		
947		if (typeof this.__options.animationDuration == 'number') {
948			this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
949		}
950		
951		if (typeof this.__options.delay == 'number') {
952			this.__options.delay = [this.__options.delay, this.__options.delay];
953		}
954		
955		if (typeof this.__options.delayTouch == 'number') {
956			this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
957		}
958		
959		if (typeof this.__options.theme == 'string') {
960			this.__options.theme = [this.__options.theme];
961		}
962		
963		// determine the future parent
964		if (this.__options.parent === null) {
965			this.__options.parent = $(env.window.document.body);
966		}
967		else if (typeof this.__options.parent == 'string') {
968			this.__options.parent = $(this.__options.parent);
969		}
970		
971		if (this.__options.trigger == 'hover') {
972			
973			this.__options.triggerOpen = {
974				mouseenter: true,
975				touchstart: true
976			};
977			
978			this.__options.triggerClose = {
979				mouseleave: true,
980				originClick: true,
981				touchleave: true
982			};
983		}
984		else if (this.__options.trigger == 'click') {
985			
986			this.__options.triggerOpen = {
987				click: true,
988				tap: true
989			};
990			
991			this.__options.triggerClose = {
992				click: true,
993				tap: true
994			};
995		}
996		
997		// for the plugins
998		this._trigger('options');
999		
1000		return this;
1001	},
1002	
1003	/**
1004	 * Schedules or cancels the garbage collector task
1005	 *
1006	 * @returns {self}
1007	 * @private
1008	 */
1009	__prepareGC: function() {
1010		
1011		var self = this;
1012		
1013		// in case the selfDestruction option has been changed by a method call
1014		if (self.__options.selfDestruction) {
1015			
1016			// the GC task
1017			self.__garbageCollector = setInterval(function() {
1018				
1019				var now = new Date().getTime();
1020				
1021				// forget the old events
1022				self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
1023					// 1 minute
1024					return now - event.time > 60000;
1025				});
1026				
1027				// auto-destruct if the origin is gone
1028				if (!bodyContains(self._$origin)) {
1029					
1030					self.close(function(){
1031						self.destroy();
1032					});
1033				}
1034			}, 20000);
1035		}
1036		else {
1037			clearInterval(self.__garbageCollector);
1038		}
1039		
1040		return self;
1041	},
1042	
1043	/**
1044	 * Sets listeners on the origin if the open triggers require them.
1045	 * Unlike the listeners set at opening time, these ones
1046	 * remain even when the tooltip is closed. It has been made a
1047	 * separate method so it can be called when the triggers are
1048	 * changed in the options. Closing is handled in _open()
1049	 * because of the bindings that may be needed on the tooltip
1050	 * itself
1051	 *
1052	 * @returns {self}
1053	 * @private
1054	 */
1055	__prepareOrigin: function() {
1056		
1057		var self = this;
1058		
1059		// in case we're resetting the triggers
1060		self._$origin.off('.'+ self.__namespace +'-triggerOpen');
1061		
1062		// if the device is touch capable, even if only mouse triggers
1063		// are asked, we need to listen to touch events to know if the mouse
1064		// events are actually emulated (so we can ignore them)
1065		if (env.hasTouchCapability) {
1066			
1067			self._$origin.on(
1068				'touchstart.'+ self.__namespace +'-triggerOpen ' +
1069				'touchend.'+ self.__namespace +'-triggerOpen ' +
1070				'touchcancel.'+ self.__namespace +'-triggerOpen',
1071				function(event){
1072					self._touchRecordEvent(event);
1073				}
1074			);
1075		}
1076		
1077		// mouse click and touch tap work the same way
1078		if (	self.__options.triggerOpen.click
1079			||	(self.__options.triggerOpen.tap && env.hasTouchCapability)
1080		) {
1081			
1082			var eventNames = '';
1083			if (self.__options.triggerOpen.click) {
1084				eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
1085			}
1086			if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
1087				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
1088			}
1089			
1090			self._$origin.on(eventNames, function(event) {
1091				if (self._touchIsMeaningfulEvent(event)) {
1092					self._open(event);
1093				}
1094			});
1095		}
1096		
1097		// mouseenter and touch start work the same way
1098		if (	self.__options.triggerOpen.mouseenter
1099			||	(self.__options.triggerOpen.touchstart && env.hasTouchCapability)
1100		) {
1101			
1102			var eventNames = '';
1103			if (self.__options.triggerOpen.mouseenter) {
1104				eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
1105			}
1106			if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
1107				eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
1108			}
1109			
1110			self._$origin.on(eventNames, function(event) {
1111				if (	self._touchIsTouchEvent(event)
1112					||	!self._touchIsEmulatedEvent(event)
1113				) {
1114					self.__pointerIsOverOrigin = true;
1115					self._openShortly(event);
1116				}
1117			});
1118		}
1119		
1120		// info for the mouseleave/touchleave close triggers when they use a delay
1121		if (	self.__options.triggerClose.mouseleave
1122			||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
1123		) {
1124			
1125			var eventNames = '';
1126			if (self.__options.triggerClose.mouseleave) {
1127				eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
1128			}
1129			if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1130				eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
1131			}
1132			
1133			self._$origin.on(eventNames, function(event) {
1134				
1135				if (self._touchIsMeaningfulEvent(event)) {
1136					self.__pointerIsOverOrigin = false;
1137				}
1138			});
1139		}
1140		
1141		return self;
1142	},
1143	
1144	/**
1145	 * Do the things that need to be done only once after the tooltip
1146	 * HTML element it has been created. It has been made a separate
1147	 * method so it can be called when options are changed. Remember
1148	 * that the tooltip may actually exist in the DOM before it is
1149	 * opened, and present after it has been closed: it's the display
1150	 * plugin that takes care of handling it.
1151	 * 
1152	 * @returns {self}
1153	 * @private
1154	 */
1155	__prepareTooltip: function() {
1156		
1157		var self = this,
1158			p = self.__options.interactive ? 'auto' : '';
1159		
1160		// this will be useful to know quickly if the tooltip is in
1161		// the DOM or not 
1162		self._$tooltip
1163			.attr('id', self.__namespace)
1164			.css({
1165				// pointer events
1166				'pointer-events': p,
1167				zIndex: self.__options.zIndex
1168			});
1169		
1170		// themes
1171		// remove the old ones and add the new ones
1172		$.each(self.__previousThemes, function(i, theme) {
1173			self._$tooltip.removeClass(theme);
1174		});
1175		$.each(self.__options.theme, function(i, theme) {
1176			self._$tooltip.addClass(theme);
1177		});
1178		
1179		self.__previousThemes = $.merge([], self.__options.theme);
1180		
1181		return self;
1182	},
1183	
1184	/**
1185	 * Handles the scroll on any of the parents of the origin (when the
1186	 * tooltip is open)
1187	 *
1188	 * @param {object} event
1189	 * @returns {self}
1190	 * @private
1191	 */
1192	__scrollHandler: function(event) {
1193		
1194		var self = this;
1195		
1196		if (self.__options.triggerClose.scroll) {
1197			self._close(event);
1198		}
1199		else {
1200			
1201			// if the origin or tooltip have been removed: do nothing, the tracker will
1202			// take care of it later
1203			if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
1204				
1205				var geo = null;
1206				
1207				// if the scroll happened on the window
1208				if (event.target === env.window.document) {
1209					
1210					// if the origin has a fixed lineage, window scroll will have no
1211					// effect on its position nor on the position of the tooltip
1212					if (!self.__Geometry.origin.fixedLineage) {
1213						
1214						// we don't need to do anything unless repositionOnScroll is true
1215						// because the tooltip will already have moved with the window
1216						// (and of course with the origin)
1217						if (self.__options.repositionOnScroll) {
1218							self.reposition(event);
1219						}
1220					}
1221				}
1222				// if the scroll happened on another parent of the tooltip, it means
1223				// that it's in a scrollable area and now needs to have its position
1224				// adjusted or recomputed, depending ont the repositionOnScroll
1225				// option. Also, if the origin is partly hidden due to a parent that
1226				// hides its overflow, we'll just hide (not close) the tooltip.
1227				else {
1228					
1229					geo = self.__geometry();
1230					
1231					var overflows = false;
1232					
1233					// a fixed position origin is not affected by the overflow hiding
1234					// of a parent
1235					if (self._$origin.css('position') != 'fixed') {
1236						
1237						self.__$originParents.each(function(i, el) {
1238							
1239							var $el = $(el),
1240								overflowX = $el.css('overflow-x'),
1241								overflowY = $el.css('overflow-y');
1242							
1243							if (overflowX != 'visible' || overflowY != 'visible') {
1244								
1245								var bcr = el.getBoundingClientRect();
1246								
1247								if (overflowX != 'visible') {
1248									
1249									if (	geo.origin.windowOffset.left < bcr.left
1250										||	geo.origin.windowOffset.right > bcr.right
1251									) {
1252										overflows = true;
1253										return false;
1254									}
1255								}
1256								
1257								if (overflowY != 'visible') {
1258									
1259									if (	geo.origin.windowOffset.top < bcr.top
1260										||	geo.origin.windowOffset.bottom > bcr.bottom
1261									) {
1262										overflows = true;
1263										return false;
1264									}
1265								}
1266							}
1267							
1268							// no need to go further if fixed, for the same reason as above
1269							if ($el.css('position') == 'fixed') {
1270								return false;
1271							}
1272						});
1273					}
1274					
1275					if (overflows) {
1276						self._$tooltip.css('visibility', 'hidden');
1277					}
1278					else {
1279						
1280						self._$tooltip.css('visibility', 'visible');
1281						
1282						// reposition
1283						if (self.__options.repositionOnScroll) {
1284							self.reposition(event);
1285						}
1286						// or just adjust offset
1287						else {
1288							
1289							// we have to use offset and not windowOffset because this way,
1290							// only the scroll distance of the scrollable areas are taken into
1291							// account (the scrolltop value of the main window must be
1292							// ignored since the tooltip already moves with it)
1293							var offsetLeft = geo.origin.offset.left - self.__Geometry.origin.offset.left,
1294								offsetTop = geo.origin.offset.top - self.__Geometry.origin.offset.top;
1295							
1296							// add the offset to the position initially computed by the display plugin
1297							self._$tooltip.css({
1298								left: self.__lastPosition.coord.left + offsetLeft,
1299								top: self.__lastPosition.coord.top + offsetTop
1300							});
1301						}
1302					}
1303				}
1304				
1305				self._trigger({
1306					type: 'scroll',
1307					event: event,
1308					geo: geo
1309				});
1310			}
1311		}
1312		
1313		return self;
1314	},
1315	
1316	/**
1317	 * Changes the state of the tooltip
1318	 *
1319	 * @param {string} state
1320	 * @returns {self}
1321	 * @private
1322	 */
1323	__stateSet: function(state) {
1324		
1325		this.__state = state;
1326		
1327		this._trigger({
1328			type: 'state',
1329			state: state
1330		});
1331		
1332		return this;
1333	},
1334	
1335	/**
1336	 * Clear appearance timeouts
1337	 *
1338	 * @returns {self}
1339	 * @private
1340	 */
1341	__timeoutsClear: function() {
1342		
1343		// there is only one possible open timeout: the delayed opening
1344		// when the mouseenter/touchstart open triggers are used
1345		clearTimeout(this.__timeouts.open);
1346		this.__timeouts.open = null;
1347		
1348		// ... but several close timeouts: the delayed closing when the
1349		// mouseleave close trigger is used and the timer option
1350		$.each(this.__timeouts.close, function(i, timeout) {
1351			clearTimeout(timeout);
1352		});
1353		this.__timeouts.close = [];
1354		
1355		return this;
1356	},
1357	
1358	/**
1359	 * Start the tracker that will make checks at regular intervals
1360	 * 
1361	 * @returns {self}
1362	 * @private
1363	 */
1364	__trackerStart: function() {
1365		
1366		var self = this,
1367			$content = self._$tooltip.find('.tooltipster-content');
1368		
1369		// get the initial content size
1370		if (self.__options.trackTooltip) {
1371			self.__contentBcr = $content[0].getBoundingClientRect();
1372		}
1373		
1374		self.__tracker = setInterval(function() {
1375			
1376			// if the origin or tooltip elements have been removed.
1377			// Note: we could destroy the instance now if the origin has
1378			// been removed but we'll leave that task to our garbage collector
1379			if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
1380				self._close();
1381			}
1382			// if everything is alright
1383			else {
1384				
1385				// compare the former and current positions of the origin to reposition
1386				// the tooltip if need be
1387				if (self.__options.trackOrigin) {
1388					
1389					var g = self.__geometry(),
1390						identical = false;
1391					
1392					// compare size first (a change requires repositioning too)
1393					if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
1394						
1395						// for elements that have a fixed lineage (see __geometry()), we track the
1396						// top and left properties (relative to window)
1397						if (self.__Geometry.origin.fixedLineage) {
1398							if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
1399								identical = true;
1400							}
1401						}
1402						// otherwise, track total offset (relative to document)
1403						else {
1404							if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
1405								identical = true;
1406							}
1407						}
1408					}
1409					
1410					if (!identical) {
1411						
1412						// close the tooltip when using the mouseleave close trigger
1413						// (see https://github.com/iamceege/tooltipster/pull/253)
1414						if (self.__options.triggerClose.mouseleave) {
1415							self._close();
1416						}
1417						else {
1418							self.reposition();
1419						}
1420					}
1421				}
1422				
1423				if (self.__options.trackTooltip) {
1424					
1425					var currentBcr = $content[0].getBoundingClientRect();
1426					
1427					if (	currentBcr.height !== self.__contentBcr.height
1428						||	currentBcr.width !== self.__contentBcr.width
1429					) {
1430						self.reposition();
1431						self.__contentBcr = currentBcr;
1432					}
1433				}
1434			}
1435		}, self.__options.trackerInterval);
1436		
1437		return self;
1438	},
1439	
1440	/**
1441	 * Closes the tooltip (after the closing delay)
1442	 * 
1443	 * @param event
1444	 * @param callback
1445	 * @param force Set to true to override a potential refusal of the user's function
1446	 * @returns {self}
1447	 * @protected
1448	 */
1449	_close: function(event, callback, force) {
1450		
1451		var self = this,
1452			ok = true;
1453		
1454		self._trigger({
1455			type: 'close',
1456			event: event,
1457			stop: function() {
1458				ok = false;
1459			}
1460		});
1461		
1462		// a destroying tooltip (force == true) may not refuse to close
1463		if (ok || force) {
1464			
1465			// save the method custom callback and cancel any open method custom callbacks
1466			if (callback) self.__callbacks.close.push(callback);
1467			self.__callbacks.open = [];
1468			
1469			// clear open/close timeouts
1470			self.__timeoutsClear();
1471			
1472			var finishCallbacks = function() {
1473				
1474				// trigger any close method custom callbacks and reset them
1475				$.each(self.__callbacks.close, function(i,c) {
1476					c.call(self, self, {
1477						event: event,
1478						origin: self._$origin[0]
1479					});
1480				});
1481				
1482				self.__callbacks.close = [];
1483			};
1484			
1485			if (self.__state != 'closed') {
1486				
1487				var necessary = true,
1488					d = new Date(),
1489					now = d.getTime(),
1490					newClosingTime = now + self.__options.animationDuration[1];
1491				
1492				// the tooltip may already already be disappearing, but if a new
1493				// call to close() is made after the animationDuration was changed
1494				// to 0 (for example), we ought to actually close it sooner than
1495				// previously scheduled. In that case it should be noted that the
1496				// browser will not adapt the animation duration to the new
1497				// animationDuration that was set after the start of the closing
1498				// animation.
1499				// Note: the same thing could be considered at opening, but is not
1500				// really useful since the tooltip is actually opened immediately
1501				// upon a call to _open(). Since it would not make the opening
1502				// animation finish sooner, its sole impact would be to trigger the
1503				// state event and the open callbacks sooner than the actual end of
1504				// the opening animation, which is not great.
1505				if (self.__state == 'disappearing') {
1506					
1507					if (	newClosingTime > self.__closingTime
1508						// in case closing is actually overdue because the script
1509						// execution was suspended. See #679
1510						&&	self.__options.animationDuration[1] > 0
1511					) {
1512						necessary = false;
1513					}
1514				}
1515				
1516				if (necessary) {
1517					
1518					self.__closingTime = newClosingTime;
1519					
1520					if (self.__state != 'disappearing') {
1521						self.__stateSet('disappearing');
1522					}
1523					
1524					var finish = function() {
1525						
1526						// stop the tracker
1527						clearInterval(self.__tracker);
1528						
1529						// a "beforeClose" option has been asked several times but would
1530						// probably useless since the content element is still accessible
1531						// via ::content(), and because people can always use listeners
1532						// inside their content to track what's going on. For the sake of
1533						// simplicity, this has been denied. Bur for the rare people who
1534						// really need the option (for old browsers or for the case where
1535						// detaching the content is actually destructive, for file or
1536						// password inputs for example), this event will do the work.
1537						self._trigger({
1538							type: 'closing',
1539							event: event
1540						});
1541						
1542						// unbind listeners which are no longer needed
1543						
1544						self._$tooltip
1545							.off('.'+ self.__namespace +'-triggerClose')
1546							.removeClass('tooltipster-dying');
1547						
1548						// orientationchange, scroll and resize listeners
1549						$(env.window).off('.'+ self.__namespace +'-triggerClose');
1550						
1551						// scroll listeners
1552						self.__$originParents.each(function(i, el) {
1553							$(el).off('scroll.'+ self.__namespace +'-triggerClose');
1554						});
1555						// clear the array to prevent memory leaks
1556						self.__$originParents = null;
1557						
1558						$(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
1559						
1560						self._$origin.off('.'+ self.__namespace +'-triggerClose');
1561						
1562						self._off('dismissable');
1563						
1564						// a plugin that would like to remove the tooltip from the
1565						// DOM when closed should bind on this
1566						self.__stateSet('closed');
1567						
1568						// trigger event
1569						self._trigger({
1570							type: 'after',
1571							event: event
1572						});
1573						
1574						// call our constructor custom callback function
1575						if (self.__options.functionAfter) {
1576							self.__options.functionAfter.call(self, self, {
1577								event: event,
1578								origin: self._$origin[0]
1579							});
1580						}
1581						
1582						// call our method custom callbacks functions
1583						finishCallbacks();
1584					};
1585					
1586					if (env.hasTransitions) {
1587						
1588						self._$tooltip.css({
1589							'-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
1590							'-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
1591							'-o-animation-duration': self.__options.animationDuration[1] + 'ms',
1592							'-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
1593							'animation-duration': self.__options.animationDuration[1] + 'ms',
1594							'transition-duration': self.__options.animationDuration[1] + 'ms'
1595						});
1596						
1597						self._$tooltip
1598							// clear both potential open and close tasks
1599							.clearQueue()
1600							.removeClass('tooltipster-show')
1601							// for transitions only
1602							.addClass('tooltipster-dying');
1603						
1604						if (self.__options.animationDuration[1] > 0) {
1605							self._$tooltip.delay(self.__options.animationDuration[1]);
1606						}
1607						
1608						self._$tooltip.queue(finish);
1609					}
1610					else {
1611						
1612						self._$tooltip
1613							.stop()
1614							.fadeOut(self.__options.animationDuration[1], finish);
1615					}
1616				}
1617			}
1618			// if the tooltip is already closed, we still need to trigger
1619			// the method custom callbacks
1620			else {
1621				finishCallbacks();
1622			}
1623		}
1624		
1625		return self;
1626	},
1627	
1628	/**
1629	 * For internal use by plugins, if needed
1630	 * 
1631	 * @returns {self}
1632	 * @protected
1633	 */
1634	_off: function() {
1635		this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1636		return this;
1637	},
1638	
1639	/**
1640	 * For internal use by plugins, if needed
1641	 *
1642	 * @returns {self}
1643	 * @protected
1644	 */
1645	_on: function() {
1646		this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1647		return this;
1648	},
1649	
1650	/**
1651	 * For internal use by plugins, if needed
1652	 *
1653	 * @returns {self}
1654	 * @protected
1655	 */
1656	_one: function() {
1657		this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1658		return this;
1659	},
1660	
1661	/**
1662	 * Opens the tooltip right away.
1663	 *
1664	 * @param event
1665	 * @param callback Will be called when the opening animation is over
1666	 * @returns {self}
1667	 * @protected
1668	 */
1669	_open: function(event, callback) {
1670		
1671		var self = this;
1672		
1673		// if the destruction process has not begun and if this was not
1674		// triggered by an unwanted emulated click event
1675		if (!self.__destroying) {
1676			
1677			// check that the origin is still in the DOM
1678			if (	bodyContains(self._$origin)
1679				// if the tooltip is enabled
1680				&&	self.__enabled
1681			) {
1682				
1683				var ok = true;
1684				
1685				// if the tooltip is not open yet, we need to call functionBefore.
1686				// otherwise we can jst go on
1687				if (self.__state == 'closed') {
1688					
1689					// trigger an event. The event.stop function allows the callback
1690					// to prevent the opening of the tooltip
1691					self._trigger({
1692						type: 'before',
1693						event: event,
1694						stop: function() {
1695							ok = false;
1696						}
1697					});
1698					
1699					if (ok && self.__options.functionBefore) {
1700						
1701						// call our custom function before continuing
1702						ok = self.__options.functionBefore.call(self, self, {
1703							event: event,
1704							origin: self._$origin[0]
1705						});
1706					}
1707				}
1708				
1709				if (ok !== false) {
1710					
1711					// if there is some content
1712					if (self.__Content !== null) {
1713						
1714						// save the method callback and cancel close method callbacks
1715						if (callback) {
1716							self.__callbacks.open.push(callback);
1717						}
1718						self.__callbacks.close = [];
1719						
1720						// get rid of any appearance timeouts
1721						self.__timeoutsClear();
1722						
1723						var extraTime,
1724							finish = function() {
1725								
1726								if (self.__state != 'stable') {
1727									self.__stateSet('stable');
1728								}
1729								
1730								// trigger any open method custom callbacks and reset them
1731								$.each(self.__callbacks.open, function(i,c) {
1732									c.call(self, self, {
1733										origin: self._$origin[0],
1734										tooltip: self._$tooltip[0]
1735									});
1736								});
1737								
1738								self.__callbacks.open = [];
1739							};
1740						
1741						// if the tooltip is already open
1742						if (self.__state !== 'closed') {
1743							
1744							// the timer (if any) will start (or restart) right now
1745							extraTime = 0;
1746							
1747							// if it was disappearing, cancel that
1748							if (self.__state === 'disappearing') {
1749								
1750								self.__stateSet('appearing');
1751								
1752								if (env.hasTransitions) {
1753									
1754									self._$tooltip
1755										.clearQueue()
1756										.removeClass('tooltipster-dying')
1757										.addClass('tooltipster-show');
1758									
1759									if (self.__options.animationDuration[0] > 0) {
1760										self._$tooltip.delay(self.__options.animationDuration[0]);
1761									}
1762									
1763									self._$tooltip.queue(finish);
1764								}
1765								else {
1766									// in case the tooltip was currently fading out, bring it back
1767									// to life
1768									self._$tooltip
1769										.stop()
1770										.fadeIn(finish);
1771								}
1772							}
1773							// if the tooltip is already open, we still need to trigger the method
1774							// custom callback
1775							else if (self.__state == 'stable') {
1776								finish();
1777							}
1778						}
1779						// if the tooltip isn't already open, open it
1780						else {
1781							
1782							// a plugin must bind on this and store the tooltip in this._$tooltip
1783							self.__stateSet('appearing');
1784							
1785							// the timer (if any) will start when the tooltip has fully appeared
1786							// after its transition
1787							extraTime = self.__options.animationDuration[0];
1788							
1789							// insert the content inside the tooltip
1790							self.__contentInsert();
1791							
1792							// reposition the tooltip and attach to the DOM
1793							self.reposition(event, true);
1794							
1795							// animate in the tooltip. If the display plugin wants no css
1796							// animations, it may override the animation option with a
1797							// dummy value that will produce no effect
1798							if (env.hasTransitions) {
1799								
1800								// note: there seems to be an issue with start animations which
1801								// are randomly not played on fast devices in both Chrome and FF,
1802								// couldn't find a way to solve it yet. It seems that applying
1803								// the classes before appending to the DOM helps a little, but
1804								// it messes up some CSS transitions. The issue almost never
1805								// happens when delay[0]==0 though
1806								self._$tooltip
1807									.addClass('tooltipster-'+ self.__options.animation)
1808									.addClass('tooltipster-initial')
1809									.css({
1810										'-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
1811										'-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
1812										'-o-animation-duration': self.__options.animationDuration[0] + 'ms',
1813										'-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
1814										'animation-duration': self.__options.animationDuration[0] + 'ms',
1815										'transition-duration': self.__options.animationDuration[0] + 'ms'
1816									});
1817								
1818								setTimeout(
1819									function() {
1820										
1821										// a quick hover may have already triggered a mouseleave
1822										if (self.__state != 'closed') {
1823											
1824											self._$tooltip
1825												.addClass('tooltipster-show')
1826												.removeClass('tooltipster-initial');
1827											
1828											if (self.__options.animationDuration[0] > 0) {
1829												self._$tooltip.delay(self.__options.animationDuration[0]);
1830											}
1831											
1832											self._$tooltip.queue(finish);
1833										}
1834									},
1835									0
1836								);
1837							}
1838							else {
1839								
1840								// old browsers will have to live with this
1841								self._$tooltip
1842									.css('display', 'none')
1843									.fadeIn(self.__options.animationDuration[0], finish);
1844							}
1845							
1846							// checks if the origin is removed while the tooltip is open
1847							self.__trackerStart();
1848							
1849							// NOTE: the listeners below have a '-triggerClose' namespace
1850							// because we'll remove them when the tooltip closes (unlike
1851							// the '-triggerOpen' listeners). So some of them are actually
1852							// not about close triggers, rather about positioning.
1853							
1854							$(env.window)
1855								// reposition on resize
1856								.on('resize.'+ self.__namespace +'-triggerClose', function(e) {
1857									
1858									var $ae = $(document.activeElement);
1859									
1860									// reposition only if the resize event was not triggered upon the opening
1861									// of a virtual keyboard due to an input field being focused within the tooltip
1862									// (otherwise the repositioning would lose the focus)
1863									if (	(!$ae.is('input') && !$ae.is('textarea'))
1864										||	!$.contains(self._$tooltip[0], $ae[0])
1865									) {
1866										self.reposition(e);
1867									}
1868								})
1869								// same as below for parents
1870								.on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1871									self.__scrollHandler(e);
1872								});
1873							
1874							self.__$originParents = self._$origin.parents();
1875							
1876							// scrolling may require the tooltip to be moved or even
1877							// repositioned in some cases
1878							self.__$originParents.each(function(i, parent) {
1879								
1880								$(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1881									self.__scrollHandler(e);
1882								});
1883							});
1884							
1885							if (	self.__options.triggerClose.mouseleave
1886								||	(self.__options.triggerClose.touchleave && env.hasTouchCapability)
1887							) {
1888								
1889								// we use an event to allow users/plugins to control when the mouseleave/touchleave
1890								// close triggers will come to action. It allows to have more triggering elements
1891								// than just the origin and the tooltip for example, or to cancel/delay the closing,
1892								// or to make the tooltip interactive even if it wasn't when it was open, etc.
1893								self._on('dismissable', function(event) {
1894									
1895									if (event.dismissable) {
1896										
1897										if (event.delay) {
1898											
1899											timeout = setTimeout(function() {
1900												// event.event may be undefined
1901												self._close(event.event);
1902											}, event.delay);
1903											
1904											self.__timeouts.close.push(timeout);
1905										}
1906										else {
1907											self._close(event);
1908										}
1909									}
1910									else {
1911										clearTimeout(timeout);
1912									}
1913								});
1914								
1915								// now set the listeners that will trigger 'dismissable' events
1916								var $elements = self._$origin,
1917									eventNamesIn = '',
1918									eventNamesOut = '',
1919									timeout = null;
1920								
1921								// if we have to allow interaction, bind on the tooltip too
1922								if (self.__options.interactive) {
1923									$elements = $elements.add(self._$tooltip);
1924								}
1925								
1926								if (self.__options.triggerClose.mouseleave) {
1927									eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
1928									eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
1929								}
1930								if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1931									eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
1932									eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
1933								}
1934								
1935								$elements
1936									// close after some time spent outside of the elements
1937									.on(eventNamesOut, function(event) {
1938										
1939										// it's ok if the touch gesture ended up to be a swipe,
1940										// it's still a "touch leave" situation
1941										if (	self._touchIsTouchEvent(event)
1942											||	!self._touchIsEmulatedEvent(event)
1943										) {
1944											
1945											var delay = (event.type == 'mouseleave') ?
1946												self.__options.delay :
1947												self.__options.delayTouch;
1948											
1949											self._trigger({
1950												delay: delay[1],
1951												dismissable: true,
1952												event: event,
1953												type: 'dismissable'
1954											});
1955										}
1956									})
1957									// suspend the mouseleave timeout when the pointer comes back
1958									// over the elements
1959									.on(eventNamesIn, function(event) {
1960										
1961										// it's also ok if the touch event is a swipe gesture
1962										if (	self._touchIsTouchEvent(event)
1963											||	!self._touchIsEmulatedEvent(event)
1964										) {
1965											self._trigger({
1966												dismissable: false,
1967												event: event,
1968												type: 'dismissable'
1969											});
1970										}
1971									});
1972							}
1973							
1974							// close the tooltip when the origin gets a mouse click (common behavior of
1975							// native tooltips)
1976							if (self.__options.triggerClose.originClick) {
1977								
1978								self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
1979									
1980									// we could actually let a tap trigger this but this feature just
1981									// does not make sense on touch devices
1982									if (	!self._touchIsTouchEvent(event)
1983										&&	!self._touchIsEmulatedEvent(event)
1984									) {
1985										self._close(event);
1986									}
1987								});
1988							}
1989							
1990							// set the same bindings for click and touch on the body to close the tooltip
1991							if (	self.__options.triggerClose.click
1992								||	(self.__options.triggerClose.tap && env.hasTouchCapability)
1993							) {
1994								
1995								// don't set right away since the click/tap event which triggered this method
1996								// (if it was a click/tap) is going to bubble up to the body, we don't want it
1997								// to close the tooltip immediately after it opened
1998								setTimeout(function() {
1999									
2000									if (self.__state != 'closed') {
2001										
2002										var eventNames = '',
2003											$body = $(env.window.document.body);
2004										
2005										if (self.__options.triggerClose.click) {
2006											eventNames += 'click.'+ self.__namespace +'-triggerClose ';
2007										}
2008										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2009											eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
2010										}
2011										
2012										$body.on(eventNames, function(event) {
2013											
2014											if (self._touchIsMeaningfulEvent(event)) {
2015												
2016												self._touchRecordEvent(event);
2017												
2018												if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
2019													self._close(event);
2020												}
2021											}
2022										});
2023										
2024										// needed to detect and ignore swiping
2025										if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2026											
2027											$body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
2028												self._touchRecordEvent(event);
2029											});
2030										}
2031									}
2032								}, 0);
2033							}
2034							
2035							self._trigger('ready');
2036							
2037							// call our custom callback
2038							if (self.__options.functionReady) {
2039								self.__options.functionReady.call(self, self, {
2040									origin: self._$origin[0],
2041									tooltip: self._$tooltip[0]
2042								});
2043							}
2044						}
2045						
2046						// if we have a timer set, let the countdown begin
2047						if (self.__options.timer > 0) {
2048							
2049							var timeout = setTimeout(function() {
2050								self._close();
2051							}, self.__options.timer + extraTime);
2052							
2053							self.__timeouts.close.push(timeout);
2054						}
2055					}
2056				}
2057			}
2058		}
2059		
2060		return self;
2061	},
2062	
2063	/**
2064	 * When using the mouseenter/touchstart open triggers, this function will
2065	 * schedule the opening of the tooltip after the delay, if there is one
2066	 *
2067	 * @param event
2068	 * @returns {self}
2069	 * @protected
2070 	 */
2071	_openShortly: function(event) {
2072		
2073		var self = this,
2074			ok = true;
2075		
2076		if (self.__state != 'stable' && self.__state != 'appearing') {
2077			
2078			// if a timeout is not already running
2079			if (!self.__timeouts.open) {
2080				
2081				self._trigger({
2082					type: 'start',
2083					event: event,
2084					stop: function() {
2085						ok = false;
2086					}
2087				});
2088				
2089				if (ok) {
2090					
2091					var delay = (event.type.indexOf('touch') == 0) ?
2092						self.__options.delayTouch :
2093						self.__options.delay;
2094					
2095					if (delay[0]) {
2096						
2097						self.__timeouts.open = setTimeout(function() {
2098							
2099							self.__timeouts.open = null;
2100							
2101							// open only if the pointer (mouse or touch) is still over the origin.
2102							// The check on the "meaningful event" can only be made here, after some
2103							// time has passed (to know if the touch was a swipe or not)
2104							if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
2105								
2106								// signal that we go on
2107								self._trigger('startend');
2108								
2109								self._open(event);
2110							}
2111							else {
2112								// signal that we cancel
2113								self._trigger('startcancel');
2114							}
2115						}, delay[0]);
2116					}
2117					else {
2118						// signal that we go on
2119						self._trigger('startend');
2120						
2121						self._open(event);
2122					}
2123				}
2124			}
2125		}
2126		
2127		return self;
2128	},
2129	
2130	/**
2131	 * Meant for plugins to get their options
2132	 * 
2133	 * @param {string} pluginName The name of the plugin that asks for its options
2134	 * @param {object} defaultOptions The default options of the plugin
2135	 * @returns {object} The options
2136	 * @protected
2137	 */
2138	_optionsExtract: function(pluginName, defaultOptions) {
2139		
2140		var self = this,
2141			options = $.extend(true, {}, defaultOptions);
2142		
2143		// if the plugin options were isolated in a property named after the
2144		// plugin, use them (prevents conflicts with other plugins)
2145		var pluginOptions = self.__options[pluginName];
2146		
2147		// if not, try to get them as regular options
2148		if (!pluginOptions){
2149			
2150			pluginOptions = {};
2151			
2152			$.each(defaultOptions, function(optionName, value) {
2153				
2154				var o = self.__options[optionName];
2155				
2156				if (o !== undefined) {
2157					pluginOptions[optionName] = o;
2158				}
2159			});
2160		}
2161		
2162		// let's merge the default options and the ones that were provided. We'd want
2163		// to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
2164		// extend on two levels, that will be enough if options are not more than 1
2165		// level deep
2166		$.each(options, function(optionName, value) {
2167			
2168			if (pluginOptions[optionName] !== undefined) {
2169				
2170				if ((		typeof value == 'object'
2171						&&	!(value instanceof Array)
2172						&&	value != null
2173					)
2174					&&
2175					(		typeof pluginOptions[optionName] == 'object'
2176						&&	!(pluginOptions[optionName] instanceof Array)
2177						&&	pluginOptions[optionName] != null
2178					)
2179				) {
2180					$.extend(options[optionName], pluginOptions[optionName]);
2181				}
2182				else {
2183					options[optionName] = pluginOptions[optionName];
2184				}
2185			}
2186		});
2187		
2188		return options;
2189	},
2190	
2191	/**
2192	 * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
2193	 * on existing instances
2194	 * 
2195	 * @param {object} pluginName
2196	 * @returns {self}
2197	 * @protected
2198	 */
2199	_plug: function(pluginName) {
2200		
2201		var plugin = $.tooltipster._plugin(pluginName);
2202		
2203		if (plugin) {
2204			
2205			// if there is a constructor for instances
2206			if (plugin.instance) {
2207				
2208				// proxy non-private methods on the instance to allow new instance methods
2209				$.tooltipster.__bridge(plugin.instance, this, plugin.name);
2210			}
2211		}
2212		else {
2213			throw new Error('The "'+ pluginName +'" plugin is not defined');
2214		}
2215		
2216		return this;
2217	},
2218	
2219	/**
2220	 * This will return true if the event is a mouse event which was
2221	 * emulated by the browser after a touch event. This allows us to
2222	 * really dissociate mouse and touch triggers.
2223	 * 
2224	 * There is a margin of error if a real mouse event is fired right
2225	 * after (within the delay shown below) a touch event on the same
2226	 * element, but hopefully it should not happen often.
2227	 * 
2228	 * @returns {boolean}
2229	 * @protected
2230	 */
2231	_touchIsEmulatedEvent: function(event) {
2232		
2233		var isEmulated = false,
2234			now = new Date().getTime();
2235		
2236		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2237			
2238			var e = this.__touchEvents[i];
2239			
2240			// delay, in milliseconds. It's supposed to be 300ms in
2241			// most browsers (350ms on iOS) to allow a double tap but
2242			// can be less (check out FastClick for more info)
2243			if (now - e.time < 500) {
2244				
2245				if (e.target === event.target) {
2246					isEmulated = true;
2247				}
2248			}
2249			else {
2250				break;
2251			}
2252		}
2253		
2254		return isEmulated;
2255	},
2256	
2257	/**
2258	 * Returns false if the event was an emulated mouse event or
2259	 * a touch event involved in a swipe gesture.
2260	 * 
2261	 * @param {object} event
2262	 * @returns {boolean}
2263	 * @protected
2264	 */
2265	_touchIsMeaningfulEvent: function(event) {
2266		return (
2267				(this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
2268			||	(!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
2269		);
2270	},
2271	
2272	/**
2273	 * Checks if an event is a touch event
2274	 * 
2275	 * @param {object} event
2276	 * @returns {boolean}
2277	 * @protected
2278	 */
2279	_touchIsTouchEvent: function(event){
2280		return event.type.indexOf('touch') == 0;
2281	},
2282	
2283	/**
2284	 * Store touch events for a while to detect swiping and emulated mouse events
2285	 * 
2286	 * @param {object} event
2287	 * @returns {self}
2288	 * @protected
2289	 */
2290	_touchRecordEvent: function(event) {
2291		
2292		if (this._touchIsTouchEvent(event)) {
2293			event.time = new Date().getTime();
2294			this.__touchEvents.push(event);
2295		}
2296		
2297		return this;
2298	},
2299	
2300	/**
2301	 * Returns true if a swipe happened after the last touchstart event fired on
2302	 * event.target.
2303	 * 
2304	 * We need to differentiate a swipe from a tap before we let the event open
2305	 * or close the tooltip. A swipe is when a touchmove (scroll) event happens
2306	 * on the body between the touchstart and the touchend events of an element.
2307	 * 
2308	 * @param {object} target The HTML element that may have triggered the swipe
2309	 * @returns {boolean}
2310	 * @protected
2311	 */
2312	_touchSwiped: function(target) {
2313		
2314		var swiped = false;
2315		
2316		for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2317			
2318			var e = this.__touchEvents[i];
2319			
2320			if (e.type == 'touchmove') {
2321				swiped = true;
2322				break;
2323			}
2324			else if (
2325				e.type == 'touchstart'
2326				&&	target === e.target
2327			) {
2328				break;
2329			}
2330		}
2331		
2332		return swiped;
2333	},
2334	
2335	/**
2336	 * Triggers an event on the instance emitters
2337	 * 
2338	 * @returns {self}
2339	 * @protected
2340	 */
2341	_trigger: function() {
2342		
2343		var args = Array.prototype.slice.apply(arguments);
2344		
2345		if (typeof args[0] == 'string') {
2346			args[0] = { type: args[0] };
2347		}
2348		
2349		// add properties to the event
2350		args[0].instance = this;
2351		args[0].origin = this._$origin ? this._$origin[0] : null;
2352		args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
2353		
2354		// note: the order of emitters matters
2355		this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
2356		$.tooltipster._trigger.apply($.tooltipster, args);
2357		this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
2358		
2359		return this;
2360	},
2361	
2362	/**
2363	 * Deactivate a plugin on this instance
2364	 * 
2365	 * @returns {self}
2366	 * @protected
2367	 */
2368	_unplug: function(pluginName) {
2369		
2370		var self = this;
2371		
2372		// if the plugin has been activated on this instance
2373		if (self[pluginName]) {
2374			
2375			var plugin = $.tooltipster._plugin(pluginName);
2376			
2377			// if there is a constructor for instances
2378			if (plugin.instance) {
2379				
2380				// unbridge
2381				$.each(plugin.instance, function(methodName, fn) {
2382					
2383					// if the method exists (privates methods do not) and comes indeed from
2384					// this plugin (may be missing or come from a conflicting plugin).
2385					if (	self[methodName]
2386						&&	self[methodName].bridged === self[pluginName]
2387					) {
2388						delete self[methodName];
2389					}
2390				});
2391			}
2392			
2393			// destroy the plugin
2394			if (self[pluginName].__destroy) {
2395				self[pluginName].__destroy();
2396			}
2397			
2398			// remove the reference to the plugin instance
2399			delete self[pluginName];
2400		}
2401		
2402		return self;
2403	},
2404	
2405	/**
2406	 * @see self::_close
2407	 * @returns {self}
2408	 * @public
2409	 */
2410	close: function(callback) {
2411		
2412		if (!this.__destroyed) {
2413			this._close(null, callback);
2414		}
2415		else {
2416			this.__destroyError();
2417		}
2418		
2419		return this;
2420	},
2421	
2422	/**
2423	 * Sets or gets the content of the tooltip
2424	 * 
2425	 * @returns {mixed|self}
2426	 * @public
2427	 */
2428	content: function(content) {
2429		
2430		var self = this;
2431		
2432		// getter method
2433		if (content === undefined) {
2434			return self.__Content;
2435		}
2436		// setter method
2437		else {
2438			
2439			if (!self.__destroyed) {
2440				
2441				// change the content
2442				self.__contentSet(content);
2443				
2444				if (self.__Content !== null) {
2445					
2446					// update the tooltip if it is open
2447					if (self.__state !== 'closed') {
2448						
2449						// reset the content in the tooltip
2450						self.__contentInsert();
2451						
2452						// reposition and resize the tooltip
2453						self.reposition();
2454						
2455						// if we want to play a little animation showing the content changed
2456						if (self.__options.updateAnimation) {
2457							
2458							if (env.hasTransitions) {
2459								
2460								// keep the reference in the local scope
2461								var animation = self.__options.updateAnimation;
2462								
2463								self._$tooltip.addClass('tooltipster-update-'+ animation);
2464								
2465								// remove the class after a while. The actual duration of the
2466								// update animation may be shorter, it's set in the CSS rules
2467								setTimeout(function() {
2468									
2469									if (self.__state != 'closed') {
2470										
2471										self._$tooltip.removeClass('tooltipster-update-'+ animation);
2472									}
2473								}, 1000);
2474							}
2475							else {
2476								self._$tooltip.fadeTo(200, 0.5, function() {
2477									if (self.__state != 'closed') {
2478										self._$tooltip.fadeTo(200, 1);
2479									}
2480								});
vendor: 22,868 bytes, lines 2481-3344
2481							}
2482						}
2483					}
2484				}
2485				else {
2486					self._close();
2487				}
2488			}
2489			else {
2490				self.__destroyError();
2491			}
2492			
2493			return self;
2494		}
2495	},
2496	
2497	/**
2498	 * Destroys the tooltip
2499	 * 
2500	 * @returns {self}
2501	 * @public
2502	 */
2503	destroy: function() {
2504		
2505		var self = this;
2506		
2507		if (!self.__destroyed) {
2508			
2509			if(self.__state != 'closed'){
2510				
2511				// no closing delay
2512				self.option('animationDuration', 0)
2513					// force closing
2514					._close(null, null, true);
2515			}
2516			else {
2517				// there might be an open timeout still running
2518				self.__timeoutsClear();
2519			}
2520			
2521			// send event
2522			self._trigger('destroy');
2523			
2524			self.__destroyed = true;
2525			
2526			self._$origin
2527				.removeData(self.__namespace)
2528				// remove the open trigger listeners
2529				.off('.'+ self.__namespace +'-triggerOpen');
2530			
2531			// remove the touch listener
2532			$(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
2533			
2534			var ns = self._$origin.data('tooltipster-ns');
2535			
2536			// if the origin has been removed from DOM, its data may
2537			// well have been destroyed in the process and there would
2538			// be nothing to clean up or restore
2539			if (ns) {
2540				
2541				// if there are no more tooltips on this element
2542				if (ns.length === 1) {
2543					
2544					// optional restoration of a title attribute
2545					var title = null;
2546					if (self.__options.restoration == 'previous') {
2547						title = self._$origin.data('tooltipster-initialTitle');
2548					}
2549					else if (self.__options.restoration == 'current') {
2550						
2551						// old school technique to stringify when outerHTML is not supported
2552						title = (typeof self.__Content == 'string') ?
2553							self.__Content :
2554							$('<div></div>').append(self.__Content).html();
2555					}
2556					
2557					if (title) {
2558						self._$origin.attr('title', title);
2559					}
2560					
2561					// final cleaning
2562					
2563					self._$origin.removeClass('tooltipstered');
2564					
2565					self._$origin
2566						.removeData('tooltipster-ns')
2567						.removeData('tooltipster-initialTitle');
2568				}
2569				else {
2570					// remove the instance namespace from the list of namespaces of
2571					// tooltips present on the element
2572					ns = $.grep(ns, function(el, i) {
2573						return el !== self.__namespace;
2574					});
2575					self._$origin.data('tooltipster-ns', ns);
2576				}
2577			}
2578			
2579			// last event
2580			self._trigger('destroyed');
2581			
2582			// unbind private and public event listeners
2583			self._off();
2584			self.off();
2585			
2586			// remove external references, just in case
2587			self.__Content = null;
2588			self.__$emitterPrivate = null;
2589			self.__$emitterPublic = null;
2590			self.__options.parent = null;
2591			self._$origin = null;
2592			self._$tooltip = null;
2593			
2594			// make sure the object is no longer referenced in there to prevent
2595			// memory leaks
2596			$.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
2597				return self !== el;
2598			});
2599			
2600			clearInterval(self.__garbageCollector);
2601		}
2602		else {
2603			self.__destroyError();
2604		}
2605		
2606		// we return the scope rather than true so that the call to
2607		// .tooltipster('destroy') actually returns the matched elements
2608		// and applies to all of them
2609		return self;
2610	},
2611	
2612	/**
2613	 * Disables the tooltip
2614	 * 
2615	 * @returns {self}
2616	 * @public
2617	 */
2618	disable: function() {
2619		
2620		if (!this.__destroyed) {
2621			
2622			// close first, in case the tooltip would not disappear on
2623			// its own (no close trigger)
2624			this._close();
2625			this.__enabled = false;
2626			
2627			return this;
2628		}
2629		else {
2630			this.__destroyError();
2631		}
2632		
2633		return this;
2634	},
2635	
2636	/**
2637	 * Returns the HTML element of the origin
2638	 *
2639	 * @returns {self}
2640	 * @public
2641	 */
2642	elementOrigin: function() {
2643		
2644		if (!this.__destroyed) {
2645			return this._$origin[0];
2646		}
2647		else {
2648			this.__destroyError();
2649		}
2650	},
2651	
2652	/**
2653	 * Returns the HTML element of the tooltip
2654	 *
2655	 * @returns {self}
2656	 * @public
2657	 */
2658	elementTooltip: function() {
2659		return this._$tooltip ? this._$tooltip[0] : null;
2660	},
2661	
2662	/**
2663	 * Enables the tooltip
2664	 * 
2665	 * @returns {self}
2666	 * @public
2667	 */
2668	enable: function() {
2669		this.__enabled = true;
2670		return this;
2671	},
2672	
2673	/**
2674	 * Alias, deprecated in 4.0.0
2675	 * 
2676	 * @param {function} callback
2677	 * @returns {self}
2678	 * @public
2679	 */
2680	hide: function(callback) {
2681		return this.close(callback);
2682	},
2683	
2684	/**
2685	 * Returns the instance
2686	 * 
2687	 * @returns {self}
2688	 * @public
2689	 */
2690	instance: function() {
2691		return this;
2692	},
2693	
2694	/**
2695	 * For public use only, not to be used by plugins (use ::_off() instead)
2696	 * 
2697	 * @returns {self}
2698	 * @public
2699	 */
2700	off: function() {
2701		
2702		if (!this.__destroyed) {
2703			this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2704		}
2705		
2706		return this;
2707	},
2708	
2709	/**
2710	 * For public use only, not to be used by plugins (use ::_on() instead)
2711	 *
2712	 * @returns {self}
2713	 * @public
2714	 */
2715	on: function() {
2716		
2717		if (!this.__destroyed) {
2718			this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2719		}
2720		else {
2721			this.__destroyError();
2722		}
2723		
2724		return this;
2725	},
2726	
2727	/**
2728	 * For public use only, not to be used by plugins
2729	 *
2730	 * @returns {self}
2731	 * @public
2732	 */
2733	one: function() {
2734		
2735		if (!this.__destroyed) {
2736			this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2737		}
2738		else {
2739			this.__destroyError();
2740		}
2741		
2742		return this;
2743	},
2744	
2745	/**
2746	 * @see self::_open
2747	 * @returns {self}
2748	 * @public
2749	 */
2750	open: function(callback) {
2751		
2752		if (!this.__destroyed) {
2753			this._open(null, callback);
2754		}
2755		else {
2756			this.__destroyError();
2757		}
2758		
2759		return this;
2760	},
2761	
2762	/**
2763	 * Get or set options. For internal use and advanced users only.
2764	 * 
2765	 * @param {string} o Option name
2766	 * @param {mixed} val optional A new value for the option
2767	 * @return {mixed|self} If val is omitted, the value of the option
2768	 * is returned, otherwise the instance itself is returned
2769	 * @public
2770	 */ 
2771	option: function(o, val) {
2772		
2773		// getter
2774		if (val === undefined) {
2775			return this.__options[o];
2776		}
2777		// setter
2778		else {
2779			
2780			if (!this.__destroyed) {
2781				
2782				// change value
2783				this.__options[o] = val;
2784				
2785				// format
2786				this.__optionsFormat();
2787				
2788				// re-prepare the triggers if needed
2789				if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
2790					this.__prepareOrigin();
2791				}
2792				
2793				if (o === 'selfDestruction') {
2794					this.__prepareGC();
2795				}
2796			}
2797			else {
2798				this.__destroyError();
2799			}
2800			
2801			return this;
2802		}
2803	},
2804	
2805	/**
2806	 * This method is in charge of setting the position and size properties of the tooltip.
2807	 * All the hard work is delegated to the display plugin.
2808	 * Note: The tooltip may be detached from the DOM at the moment the method is called 
2809	 * but must be attached by the end of the method call.
2810	 * 
2811	 * @param {object} event For internal use only. Defined if an event such as
2812	 * window resizing triggered the repositioning
2813	 * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
2814	 * know that the tooltip not being in the DOM is not an issue (typically when the
2815	 * tooltip element has just been created but has not been added to the DOM yet).
2816	 * @returns {self}
2817	 * @public
2818	 */
2819	reposition: function(event, tooltipIsDetached) {
2820		
2821		var self = this;
2822		
2823		if (!self.__destroyed) {
2824			
2825			// if the tooltip is still open and the origin is still in the DOM
2826			if (self.__state != 'closed' && bodyContains(self._$origin)) {
2827				
2828				// if the tooltip has not been removed from DOM manually (or if it
2829				// has been detached on purpose)
2830				if (tooltipIsDetached || bodyContains(self._$tooltip)) {
2831					
2832					if (!tooltipIsDetached) {
2833						// detach in case the tooltip overflows the window and adds
2834						// scrollbars to it, so __geometry can be accurate
2835						self._$tooltip.detach();
2836					}
2837					
2838					// refresh the geometry object before passing it as a helper
2839					self.__Geometry = self.__geometry();
2840					
2841					// let a plugin fo the rest
2842					self._trigger({
2843						type: 'reposition',
2844						event: event,
2845						helper: {
2846							geo: self.__Geometry
2847						}
2848					});
2849				}
2850			}
2851		}
2852		else {
2853			self.__destroyError();
2854		}
2855		
2856		return self;
2857	},
2858	
2859	/**
2860	 * Alias, deprecated in 4.0.0
2861	 *
2862	 * @param callback
2863	 * @returns {self}
2864	 * @public
2865	 */
2866	show: function(callback) {
2867		return this.open(callback);
2868	},
2869	
2870	/**
2871	 * Returns some properties about the instance
2872	 * 
2873	 * @returns {object}
2874	 * @public
2875	 */
2876	status: function() {
2877		
2878		return {
2879			destroyed: this.__destroyed,
2880			enabled: this.__enabled,
2881			open: this.__state !== 'closed',
2882			state: this.__state
2883		};
2884	},
2885	
2886	/**
2887	 * For public use only, not to be used by plugins
2888	 *
2889	 * @returns {self}
2890	 * @public
2891	 */
2892	triggerHandler: function() {
2893		
2894		if (!this.__destroyed) {
2895			this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2896		}
2897		else {
2898			this.__destroyError();
2899		}
2900		
2901		return this;
2902	}
2903};
2904
2905$.fn.tooltipster = function() {
2906	
2907	// for using in closures
2908	var args = Array.prototype.slice.apply(arguments),
2909		// common mistake: an HTML element can't be in several tooltips at the same time
2910		contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
2911	
2912	// this happens with $(sel).tooltipster(...) when $(sel) does not match anything
2913	if (this.length === 0) {
2914		
2915		// still chainable
2916		return this;
2917	}
2918	// this happens when calling $(sel).tooltipster('methodName or options')
2919	// where $(sel) matches one or more elements
2920	else {
2921		
2922		// method calls
2923		if (typeof args[0] === 'string') {
2924			
2925			var v = '#*$~&';
2926			
2927			this.each(function() {
2928				
2929				// retrieve the namepaces of the tooltip(s) that exist on that element.
2930				// We will interact with the first tooltip only.
2931				var ns = $(this).data('tooltipster-ns'),
2932					// self represents the instance of the first tooltipster plugin
2933					// associated to the current HTML object of the loop
2934					self = ns ? $(this).data(ns[0]) : null;
2935				
2936				// if the current element holds a tooltipster instance
2937				if (self) {
2938					
2939					if (typeof self[args[0]] === 'function') {
2940						
2941						if (	this.length > 1
2942							&&	args[0] == 'content'
2943							&&	(	args[1] instanceof $
2944								|| (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
2945							)
2946							&&	!self.__options.contentCloning
2947							&&	self.__options.debug
2948						) {
2949							console.log(contentCloningWarning);
2950						}
2951						
2952						// note : args[1] and args[2] may not be defined
2953						var resp = self[args[0]](args[1], args[2]);
2954					}
2955					else {
2956						throw new Error('Unknown method "'+ args[0] +'"');
2957					}
2958					
2959					// if the function returned anything other than the instance
2960					// itself (which implies chaining, except for the `instance` method)
2961					if (resp !== self || args[0] === 'instance') {
2962						
2963						v = resp;
2964						
2965						// return false to stop .each iteration on the first element
2966						// matched by the selector
2967						return false;
2968					}
2969				}
2970				else {
2971					throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
2972				}
2973			});
2974			
2975			return (v !== '#*$~&') ? v : this;
2976		}
2977		// first argument is undefined or an object: the tooltip is initializing
2978		else {
2979			
2980			// reset the array of last initialized objects
2981			$.tooltipster.__instancesLatestArr = [];
2982			
2983			// is there a defined value for the multiple option in the options object ?
2984			var	multipleIsSet = args[0] && args[0].multiple !== undefined,
2985				// if the multiple option is set to true, or if it's not defined but
2986				// set to true in the defaults
2987				multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
2988				// same for content
2989				contentIsSet = args[0] && args[0].content !== undefined,
2990				content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
2991				// same for contentCloning
2992				contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
2993				contentCloning =
2994						(contentCloningIsSet && args[0].contentCloning)
2995					||	(!contentCloningIsSet && defaults.contentCloning),
2996				// same for debug
2997				debugIsSet = args[0] && args[0].debug !== undefined,
2998				debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
2999			
3000			if (	this.length > 1
3001				&&	(	content instanceof $
3002					|| (typeof content == 'object' && content != null && content.tagName)
3003				)
3004				&&	!contentCloning
3005				&&	debug
3006			) {
3007				console.log(contentCloningWarning);
3008			}
3009			
3010			// create a tooltipster instance for each element if it doesn't
3011			// already have one or if the multiple option is set, and attach the
3012			// object to it
3013			this.each(function() {
3014				
3015				var go = false,
3016					$this = $(this),
3017					ns = $this.data('tooltipster-ns'),
3018					obj = null;
3019				
3020				if (!ns) {
3021					go = true;
3022				}
3023				else if (multiple) {
3024					go = true;
3025				}
3026				else if (debug) {
3027					//console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
3028					//console.log(this);
3029				}
3030				
3031				if (go) {
3032					obj = new $.Tooltipster(this, args[0]);
3033					
3034					// save the reference of the new instance
3035					if (!ns) ns = [];
3036					ns.push(obj.__namespace);
3037					$this.data('tooltipster-ns', ns);
3038					
3039					// save the instance itself
3040					$this.data(obj.__namespace, obj);
3041					
3042					// call our constructor custom function.
3043					// we do this here and not in ::init() because we wanted
3044					// the object to be saved in $this.data before triggering
3045					// it
3046					if (obj.__options.functionInit) {
3047						obj.__options.functionInit.call(obj, obj, {
3048							origin: this
3049						});
3050					}
3051					
3052					// and now the event, for the plugins and core emitter
3053					obj._trigger('init');
3054				}
3055				
3056				$.tooltipster.__instancesLatestArr.push(obj);
3057			});
3058			
3059			return this;
3060		}
3061	}
3062};
3063
3064// Utilities
3065
3066/**
3067 * A class to check if a tooltip can fit in given dimensions
3068 * 
3069 * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
3070 */
3071function Ruler($tooltip) {
3072	
3073	// list of instance variables
3074	
3075	this.$container;
3076	this.constraints = null;
3077	this.__$tooltip;
3078	
3079	this.__init($tooltip);
3080}
3081
3082Ruler.prototype = {
3083	
3084	/**
3085	 * Move the tooltip into an invisible div that does not allow overflow to make
3086	 * size tests. Note: the tooltip may or may not be attached to the DOM at the
3087	 * moment this method is called, it does not matter.
3088	 * 
3089	 * @param {object} $tooltip The object to test. May be just a clone of the
3090	 * actual tooltip.
3091	 * @private
3092	 */
3093	__init: function($tooltip) {
3094		
3095		this.__$tooltip = $tooltip;
3096		
3097		this.__$tooltip
3098			.css({
3099				// for some reason we have to specify top and left 0
3100				left: 0,
3101				// any overflow will be ignored while measuring
3102				overflow: 'hidden',
3103				// positions at (0,0) without the div using 100% of the available width
3104				position: 'absolute',
3105				top: 0
3106			})
3107			// overflow must be auto during the test. We re-set this in case
3108			// it were modified by the user
3109			.find('.tooltipster-content')
3110				.css('overflow', 'auto');
3111		
3112		this.$container = $('<div class="tooltipster-ruler"></div>')
3113			.append(this.__$tooltip)
3114			.appendTo(env.window.document.body);
3115	},
3116	
3117	/**
3118	 * Force the browser to redraw (re-render) the tooltip immediately. This is required
3119	 * when you changed some CSS properties and need to make something with it
3120	 * immediately, without waiting for the browser to redraw at the end of instructions.
3121	 *
3122	 * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
3123	 * @private
3124	 */
3125	__forceRedraw: function() {
3126		
3127		// note: this would work but for Webkit only
3128		//this.__$tooltip.close();
3129		//this.__$tooltip[0].offsetHeight;
3130		//this.__$tooltip.open();
3131		
3132		// works in FF too
3133		var $p = this.__$tooltip.parent();
3134		this.__$tooltip.detach();
3135		this.__$tooltip.appendTo($p);
3136	},
3137	
3138	/**
3139	 * Set maximum dimensions for the tooltip. A call to ::measure afterwards
3140	 * will tell us if the content overflows or if it's ok
3141	 *
3142	 * @param {int} width
3143	 * @param {int} height
3144	 * @return {Ruler}
3145	 * @public
3146	 */
3147	constrain: function(width, height) {
3148		
3149		this.constraints = {
3150			width: width,
3151			height: height
3152		};
3153		
3154		this.__$tooltip.css({
3155			// we disable display:flex, otherwise the content would overflow without
3156			// creating horizontal scrolling (which we need to detect).
3157			display: 'block',
3158			// reset any previous height
3159			height: '',
3160			// we'll check if horizontal scrolling occurs
3161			overflow: 'auto',
3162			// we'll set the width and see what height is generated and if there
3163			// is horizontal overflow
3164			width: width
3165		});
3166		
3167		return this;
3168	},
3169	
3170	/**
3171	 * Reset the tooltip content overflow and remove the test container
3172	 * 
3173	 * @returns {Ruler}
3174	 * @public
3175	 */
3176	destroy: function() {
3177		
3178		// in case the element was not a clone
3179		this.__$tooltip
3180			.detach()
3181			.find('.tooltipster-content')
3182				.css({
3183					// reset to CSS value
3184					display: '',
3185					overflow: ''
3186				});
3187		
3188		this.$container.remove();
3189	},
3190	
3191	/**
3192	 * Removes any constraints
3193	 * 
3194	 * @returns {Ruler}
3195	 * @public
3196	 */
3197	free: function() {
3198		
3199		this.constraints = null;
3200		
3201		// reset to natural size
3202		this.__$tooltip.css({
3203			display: '',
3204			height: '',
3205			overflow: 'visible',
3206			width: ''
3207		});
3208		
3209		return this;
3210	},
3211	
3212	/**
3213	 * Returns the size of the tooltip. When constraints are applied, also returns
3214	 * whether the tooltip fits in the provided dimensions.
3215	 * The idea is to see if the new height is small enough and if the content does
3216	 * not overflow horizontally.
3217	 *
3218	 * @param {int} width
3219	 * @param {int} height
3220	 * @returns {object} An object with a bool `fits` property and a `size` property
3221	 * @public
3222	 */
3223	measure: function() {
3224		
3225		this.__forceRedraw();
3226		
3227		var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
3228			result = { size: {
3229				// bcr.width/height are not defined in IE8- but in this
3230				// case, bcr.right/bottom will have the same value
3231				// except in iOS 8+ where tooltipBcr.bottom/right are wrong
3232				// after scrolling for reasons yet to be determined.
3233				// tooltipBcr.top/left might not be 0, see issue #514
3234				height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
3235				width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
3236			}};
3237		
3238		if (this.constraints) {
3239			
3240			// note: we used to use offsetWidth instead of boundingRectClient but
3241			// it returned rounded values, causing issues with sub-pixel layouts.
3242			
3243			// note2: noticed that the bcrWidth of text content of a div was once
3244			// greater than the bcrWidth of its container by 1px, causing the final
3245			// tooltip box to be too small for its content. However, evaluating
3246			// their widths one against the other (below) surprisingly returned
3247			// equality. Happened only once in Chrome 48, was not able to reproduce
3248			// => just having fun with float position values...
3249			
3250			var $content = this.__$tooltip.find('.tooltipster-content'),
3251				height = this.__$tooltip.outerHeight(),
3252				contentBcr = $content[0].getBoundingClientRect(),
3253				fits = {
3254					height: height <= this.constraints.height,
3255					width: (
3256						// this condition accounts for min-width property that
3257						// may apply
3258						tooltipBcr.width <= this.constraints.width
3259							// the -1 is here because scrollWidth actually returns
3260							// a rounded value, and may be greater than bcr.width if
3261							// it was rounded up. This may cause an issue for contents
3262							// which actually really overflow  by 1px or so, but that
3263							// should be rare. Not sure how to solve this efficiently.
3264							// See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
3265						&&	contentBcr.width >= $content[0].scrollWidth - 1
3266					)
3267				};
3268			
3269			result.fits = fits.height && fits.width;
3270		}
3271		
3272		// old versions of IE get the width wrong for some reason and it causes
3273		// the text to be broken to a new line, so we round it up. If the width
3274		// is the width of the screen though, we can assume it is accurate.
3275		if (	env.IE
3276			&&	env.IE <= 11
3277			&&	result.size.width !== env.window.document.documentElement.clientWidth
3278		) {
3279			result.size.width = Math.ceil(result.size.width) + 1;
3280		}
3281		
3282		return result;
3283	}
3284};
3285
3286// quick & dirty compare function, not bijective nor multidimensional
3287function areEqual(a,b) {
3288	var same = true;
3289	$.each(a, function(i, _) {
3290		if (b[i] === undefined || a[i] !== b[i]) {
3291			same = false;
3292			return false;
3293		}
3294	});
3295	return same;
3296}
3297
3298/**
3299 * A fast function to check if an element is still in the DOM. It
3300 * tries to use an id as ids are indexed by the browser, or falls
3301 * back to jQuery's `contains` method. May fail if two elements
3302 * have the same id, but so be it
3303 *
3304 * @param {object} $obj A jQuery-wrapped HTML element
3305 * @return {boolean}
3306 */
3307function bodyContains($obj) {
3308	var id = $obj.attr('id'),
3309		el = id ? env.window.document.getElementById(id) : null;
3310	// must also check that the element with the id is the one we want
3311	return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
3312}
3313
3314// detect IE versions for dirty fixes
3315var uA = navigator.userAgent.toLowerCase();
3316if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
3317else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
3318else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
3319
3320// detecting support for CSS transitions
3321function transitionSupport() {
3322	
3323	// env.window is not defined yet when this is called
3324	if (!win) return false;
3325	
3326	var b = win.document.body || win.document.documentElement,
3327		s = b.style,
3328		p = 'transition',
3329		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
3330	
3331	if (typeof s[p] == 'string') { return true; }
3332	
3333	p = p.charAt(0).toUpperCase() + p.substr(1);
3334	for (var i=0; i<v.length; i++) {
3335		if (typeof s[v[i] + p] == 'string') { return true; }
3336	}
3337	return false;
3338}
3339
3340// we'll return jQuery for plugins not to have to declare it as a dependency,
3341// but it's done by a build task since it should be included only once at the
3342// end when we concatenate the main file with a pluginreturn $;
3343
3344}));

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.