Blame jquery/jquery.powertip-1.2.0.js

Packit 1c1d7e
/*!
Packit 1c1d7e
 PowerTip - v1.2.0 - 2013-04-03
Packit 1c1d7e
 http://stevenbenner.github.com/jquery-powertip/
Packit 1c1d7e
 Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
Packit 1c1d7e
 Released under MIT license.
Packit 1c1d7e
 https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
Packit 1c1d7e
*/
Packit 1c1d7e
(function(factory) {
Packit 1c1d7e
	if (typeof define === 'function' && define.amd) {
Packit 1c1d7e
		// AMD. Register as an anonymous module.
Packit 1c1d7e
		define(['jquery'], factory);
Packit 1c1d7e
	} else {
Packit 1c1d7e
		// Browser globals
Packit 1c1d7e
		factory(jQuery);
Packit 1c1d7e
	}
Packit 1c1d7e
}(function($) {
Packit 1c1d7e
Packit 1c1d7e
	// useful private variables
Packit 1c1d7e
	var $document = $(document),
Packit 1c1d7e
		$window = $(window),
Packit 1c1d7e
		$body = $('body');
Packit 1c1d7e
Packit 1c1d7e
	// constants
Packit 1c1d7e
	var DATA_DISPLAYCONTROLLER = 'displayController',
Packit 1c1d7e
		DATA_HASACTIVEHOVER = 'hasActiveHover',
Packit 1c1d7e
		DATA_FORCEDOPEN = 'forcedOpen',
Packit 1c1d7e
		DATA_HASMOUSEMOVE = 'hasMouseMove',
Packit 1c1d7e
		DATA_MOUSEONTOTIP = 'mouseOnToPopup',
Packit 1c1d7e
		DATA_ORIGINALTITLE = 'originalTitle',
Packit 1c1d7e
		DATA_POWERTIP = 'powertip',
Packit 1c1d7e
		DATA_POWERTIPJQ = 'powertipjq',
Packit 1c1d7e
		DATA_POWERTIPTARGET = 'powertiptarget',
Packit 1c1d7e
		RAD2DEG = 180 / Math.PI;
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Session data
Packit 1c1d7e
	 * Private properties global to all powerTip instances
Packit 1c1d7e
	 */
Packit 1c1d7e
	var session = {
Packit 1c1d7e
		isTipOpen: false,
Packit 1c1d7e
		isFixedTipOpen: false,
Packit 1c1d7e
		isClosing: false,
Packit 1c1d7e
		tipOpenImminent: false,
Packit 1c1d7e
		activeHover: null,
Packit 1c1d7e
		currentX: 0,
Packit 1c1d7e
		currentY: 0,
Packit 1c1d7e
		previousX: 0,
Packit 1c1d7e
		previousY: 0,
Packit 1c1d7e
		desyncTimeout: null,
Packit 1c1d7e
		mouseTrackingActive: false,
Packit 1c1d7e
		delayInProgress: false,
Packit 1c1d7e
		windowWidth: 0,
Packit 1c1d7e
		windowHeight: 0,
Packit 1c1d7e
		scrollTop: 0,
Packit 1c1d7e
		scrollLeft: 0
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Collision enumeration
Packit 1c1d7e
	 * @enum {number}
Packit 1c1d7e
	 */
Packit 1c1d7e
	var Collision = {
Packit 1c1d7e
		none: 0,
Packit 1c1d7e
		top: 1,
Packit 1c1d7e
		bottom: 2,
Packit 1c1d7e
		left: 4,
Packit 1c1d7e
		right: 8
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Display hover tooltips on the matched elements.
Packit 1c1d7e
	 * @param {(Object|string)} opts The options object to use for the plugin, or
Packit 1c1d7e
	 *     the name of a method to invoke on the first matched element.
Packit 1c1d7e
	 * @param {*=} [arg] Argument for an invoked method (optional).
Packit 1c1d7e
	 * @return {jQuery} jQuery object for the matched selectors.
Packit 1c1d7e
	 */
Packit 1c1d7e
	$.fn.powerTip = function(opts, arg) {
Packit 1c1d7e
		// don't do any work if there were no matched elements
Packit 1c1d7e
		if (!this.length) {
Packit 1c1d7e
			return this;
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// handle api method calls on the plugin, e.g. powerTip('hide')
Packit 1c1d7e
		if ($.type(opts) === 'string' && $.powerTip[opts]) {
Packit 1c1d7e
			return $.powerTip[opts].call(this, this, arg);
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// extend options and instantiate TooltipController
Packit 1c1d7e
		var options = $.extend({}, $.fn.powerTip.defaults, opts),
Packit 1c1d7e
			tipController = new TooltipController(options);
Packit 1c1d7e
Packit 1c1d7e
		// hook mouse and viewport dimension tracking
Packit 1c1d7e
		initTracking();
Packit 1c1d7e
Packit 1c1d7e
		// setup the elements
Packit 1c1d7e
		this.each(function elementSetup() {
Packit 1c1d7e
			var $this = $(this),
Packit 1c1d7e
				dataPowertip = $this.data(DATA_POWERTIP),
Packit 1c1d7e
				dataElem = $this.data(DATA_POWERTIPJQ),
Packit 1c1d7e
				dataTarget = $this.data(DATA_POWERTIPTARGET),
Packit 1c1d7e
				title;
Packit 1c1d7e
Packit 1c1d7e
			// handle repeated powerTip calls on the same element by destroying the
Packit 1c1d7e
			// original instance hooked to it and replacing it with this call
Packit 1c1d7e
			if ($this.data(DATA_DISPLAYCONTROLLER)) {
Packit 1c1d7e
				$.powerTip.destroy($this);
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// attempt to use title attribute text if there is no data-powertip,
Packit 1c1d7e
			// data-powertipjq or data-powertiptarget. If we do use the title
Packit 1c1d7e
			// attribute, delete the attribute so the browser will not show it
Packit 1c1d7e
			title = $this.attr('title');
Packit 1c1d7e
			if (!dataPowertip && !dataTarget && !dataElem && title) {
Packit 1c1d7e
				$this.data(DATA_POWERTIP, title);
Packit 1c1d7e
				$this.data(DATA_ORIGINALTITLE, title);
Packit 1c1d7e
				$this.removeAttr('title');
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// create hover controllers for each element
Packit 1c1d7e
			$this.data(
Packit 1c1d7e
				DATA_DISPLAYCONTROLLER,
Packit 1c1d7e
				new DisplayController($this, options, tipController)
Packit 1c1d7e
			);
Packit 1c1d7e
		});
Packit 1c1d7e
Packit 1c1d7e
		// attach events to matched elements if the manual options is not enabled
Packit 1c1d7e
		if (!options.manual) {
Packit 1c1d7e
			this.on({
Packit 1c1d7e
				// mouse events
Packit 1c1d7e
				'mouseenter.powertip': function elementMouseEnter(event) {
Packit 1c1d7e
					$.powerTip.show(this, event);
Packit 1c1d7e
				},
Packit 1c1d7e
				'mouseleave.powertip': function elementMouseLeave() {
Packit 1c1d7e
					$.powerTip.hide(this);
Packit 1c1d7e
				},
Packit 1c1d7e
				// keyboard events
Packit 1c1d7e
				'focus.powertip': function elementFocus() {
Packit 1c1d7e
					$.powerTip.show(this);
Packit 1c1d7e
				},
Packit 1c1d7e
				'blur.powertip': function elementBlur() {
Packit 1c1d7e
					$.powerTip.hide(this, true);
Packit 1c1d7e
				},
Packit 1c1d7e
				'keydown.powertip': function elementKeyDown(event) {
Packit 1c1d7e
					// close tooltip when the escape key is pressed
Packit 1c1d7e
					if (event.keyCode === 27) {
Packit 1c1d7e
						$.powerTip.hide(this, true);
Packit 1c1d7e
					}
Packit 1c1d7e
				}
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		return this;
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Default options for the powerTip plugin.
Packit 1c1d7e
	 */
Packit 1c1d7e
	$.fn.powerTip.defaults = {
Packit 1c1d7e
		fadeInTime: 200,
Packit 1c1d7e
		fadeOutTime: 100,
Packit 1c1d7e
		followMouse: false,
Packit 1c1d7e
		popupId: 'powerTip',
Packit 1c1d7e
		intentSensitivity: 7,
Packit 1c1d7e
		intentPollInterval: 100,
Packit 1c1d7e
		closeDelay: 100,
Packit 1c1d7e
		placement: 'n',
Packit 1c1d7e
		smartPlacement: false,
Packit 1c1d7e
		offset: 10,
Packit 1c1d7e
		mouseOnToPopup: false,
Packit 1c1d7e
		manual: false
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Default smart placement priority lists.
Packit 1c1d7e
	 * The first item in the array is the highest priority, the last is the lowest.
Packit 1c1d7e
	 * The last item is also the default, which will be used if all previous options
Packit 1c1d7e
	 * do not fit.
Packit 1c1d7e
	 */
Packit 1c1d7e
	$.fn.powerTip.smartPlacementLists = {
Packit 1c1d7e
		n: ['n', 'ne', 'nw', 's'],
Packit 1c1d7e
		e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
Packit 1c1d7e
		s: ['s', 'se', 'sw', 'n'],
Packit 1c1d7e
		w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
Packit 1c1d7e
		nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
Packit 1c1d7e
		ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
Packit 1c1d7e
		sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
Packit 1c1d7e
		se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
Packit 1c1d7e
		'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
Packit 1c1d7e
		'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
Packit 1c1d7e
		'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
Packit 1c1d7e
		'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Public API
Packit 1c1d7e
	 */
Packit 1c1d7e
	$.powerTip = {
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Attempts to show the tooltip for the specified element.
Packit 1c1d7e
		 * @param {jQuery|Element} element The element to open the tooltip for.
Packit 1c1d7e
		 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
Packit 1c1d7e
		 *     tracking (optional).
Packit 1c1d7e
		 */
Packit 1c1d7e
		show: function apiShowTip(element, event) {
Packit 1c1d7e
			if (event) {
Packit 1c1d7e
				trackMouse(event);
Packit 1c1d7e
				session.previousX = event.pageX;
Packit 1c1d7e
				session.previousY = event.pageY;
Packit 1c1d7e
				$(element).data(DATA_DISPLAYCONTROLLER).show();
Packit 1c1d7e
			} else {
Packit 1c1d7e
				$(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
Packit 1c1d7e
			}
Packit 1c1d7e
			return element;
Packit 1c1d7e
		},
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Repositions the tooltip on the element.
Packit 1c1d7e
		 * @param {jQuery|Element} element The element the tooltip is shown for.
Packit 1c1d7e
		 */
Packit 1c1d7e
		reposition: function apiResetPosition(element) {
Packit 1c1d7e
			$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
Packit 1c1d7e
			return element;
Packit 1c1d7e
		},
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Attempts to close any open tooltips.
Packit 1c1d7e
		 * @param {(jQuery|Element)=} element The element with the tooltip that
Packit 1c1d7e
		 *     should be closed (optional).
Packit 1c1d7e
		 * @param {boolean=} immediate Disable close delay (optional).
Packit 1c1d7e
		 */
Packit 1c1d7e
		hide: function apiCloseTip(element, immediate) {
Packit 1c1d7e
			if (element) {
Packit 1c1d7e
				$(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
Packit 1c1d7e
			} else {
Packit 1c1d7e
				if (session.activeHover) {
Packit 1c1d7e
					session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
			return element;
Packit 1c1d7e
		},
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Destroy and roll back any powerTip() instance on the specified element.
Packit 1c1d7e
		 * @param {jQuery|Element} element The element with the powerTip instance.
Packit 1c1d7e
		 */
Packit 1c1d7e
		destroy: function apiDestroy(element) {
Packit 1c1d7e
			$(element).off('.powertip').each(function destroy() {
Packit 1c1d7e
				var $this = $(this),
Packit 1c1d7e
					dataAttributes = [
Packit 1c1d7e
						DATA_ORIGINALTITLE,
Packit 1c1d7e
						DATA_DISPLAYCONTROLLER,
Packit 1c1d7e
						DATA_HASACTIVEHOVER,
Packit 1c1d7e
						DATA_FORCEDOPEN
Packit 1c1d7e
					];
Packit 1c1d7e
Packit 1c1d7e
				if ($this.data(DATA_ORIGINALTITLE)) {
Packit 1c1d7e
					$this.attr('title', $this.data(DATA_ORIGINALTITLE));
Packit 1c1d7e
					dataAttributes.push(DATA_POWERTIP);
Packit 1c1d7e
				}
Packit 1c1d7e
Packit 1c1d7e
				$this.removeData(dataAttributes);
Packit 1c1d7e
			});
Packit 1c1d7e
			return element;
Packit 1c1d7e
		}
Packit 1c1d7e
	};
Packit 1c1d7e
Packit 1c1d7e
	// API aliasing
Packit 1c1d7e
	$.powerTip.showTip = $.powerTip.show;
Packit 1c1d7e
	$.powerTip.closeTip = $.powerTip.hide;
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Creates a new CSSCoordinates object.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @constructor
Packit 1c1d7e
	 */
Packit 1c1d7e
	function CSSCoordinates() {
Packit 1c1d7e
		var me = this;
Packit 1c1d7e
Packit 1c1d7e
		// initialize object properties
Packit 1c1d7e
		me.top = 'auto';
Packit 1c1d7e
		me.left = 'auto';
Packit 1c1d7e
		me.right = 'auto';
Packit 1c1d7e
		me.bottom = 'auto';
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Set a property to a value.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {string} property The name of the property.
Packit 1c1d7e
		 * @param {number} value The value of the property.
Packit 1c1d7e
		 */
Packit 1c1d7e
		me.set = function(property, value) {
Packit 1c1d7e
			if ($.isNumeric(value)) {
Packit 1c1d7e
				me[property] = Math.round(value);
Packit 1c1d7e
			}
Packit 1c1d7e
		};
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Creates a new tooltip display controller.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @constructor
Packit 1c1d7e
	 * @param {jQuery} element The element that this controller will handle.
Packit 1c1d7e
	 * @param {Object} options Options object containing settings.
Packit 1c1d7e
	 * @param {TooltipController} tipController The TooltipController object for
Packit 1c1d7e
	 *     this instance.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function DisplayController(element, options, tipController) {
Packit 1c1d7e
		var hoverTimer = null;
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Begins the process of showing a tooltip.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {boolean=} immediate Skip intent testing (optional).
Packit 1c1d7e
		 * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
Packit 1c1d7e
		 *     open (optional).
Packit 1c1d7e
		 */
Packit 1c1d7e
		function openTooltip(immediate, forceOpen) {
Packit 1c1d7e
			cancelTimer();
Packit 1c1d7e
			if (!element.data(DATA_HASACTIVEHOVER)) {
Packit 1c1d7e
				if (!immediate) {
Packit 1c1d7e
					session.tipOpenImminent = true;
Packit 1c1d7e
					hoverTimer = setTimeout(
Packit 1c1d7e
						function intentDelay() {
Packit 1c1d7e
							hoverTimer = null;
Packit 1c1d7e
							checkForIntent();
Packit 1c1d7e
						},
Packit 1c1d7e
						options.intentPollInterval
Packit 1c1d7e
					);
Packit 1c1d7e
				} else {
Packit 1c1d7e
					if (forceOpen) {
Packit 1c1d7e
						element.data(DATA_FORCEDOPEN, true);
Packit 1c1d7e
					}
Packit 1c1d7e
					tipController.showTip(element);
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Begins the process of closing a tooltip.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {boolean=} disableDelay Disable close delay (optional).
Packit 1c1d7e
		 */
Packit 1c1d7e
		function closeTooltip(disableDelay) {
Packit 1c1d7e
			cancelTimer();
Packit 1c1d7e
			session.tipOpenImminent = false;
Packit 1c1d7e
			if (element.data(DATA_HASACTIVEHOVER)) {
Packit 1c1d7e
				element.data(DATA_FORCEDOPEN, false);
Packit 1c1d7e
				if (!disableDelay) {
Packit 1c1d7e
					session.delayInProgress = true;
Packit 1c1d7e
					hoverTimer = setTimeout(
Packit 1c1d7e
						function closeDelay() {
Packit 1c1d7e
							hoverTimer = null;
Packit 1c1d7e
							tipController.hideTip(element);
Packit 1c1d7e
							session.delayInProgress = false;
Packit 1c1d7e
						},
Packit 1c1d7e
						options.closeDelay
Packit 1c1d7e
					);
Packit 1c1d7e
				} else {
Packit 1c1d7e
					tipController.hideTip(element);
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Checks mouse position to make sure that the user intended to hover on the
Packit 1c1d7e
		 * specified element before showing the tooltip.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 */
Packit 1c1d7e
		function checkForIntent() {
Packit 1c1d7e
			// calculate mouse position difference
Packit 1c1d7e
			var xDifference = Math.abs(session.previousX - session.currentX),
Packit 1c1d7e
				yDifference = Math.abs(session.previousY - session.currentY),
Packit 1c1d7e
				totalDifference = xDifference + yDifference;
Packit 1c1d7e
Packit 1c1d7e
			// check if difference has passed the sensitivity threshold
Packit 1c1d7e
			if (totalDifference < options.intentSensitivity) {
Packit 1c1d7e
				tipController.showTip(element);
Packit 1c1d7e
			} else {
Packit 1c1d7e
				// try again
Packit 1c1d7e
				session.previousX = session.currentX;
Packit 1c1d7e
				session.previousY = session.currentY;
Packit 1c1d7e
				openTooltip();
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Cancels active hover timer.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 */
Packit 1c1d7e
		function cancelTimer() {
Packit 1c1d7e
			hoverTimer = clearTimeout(hoverTimer);
Packit 1c1d7e
			session.delayInProgress = false;
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Repositions the tooltip on this element.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 */
Packit 1c1d7e
		function repositionTooltip() {
Packit 1c1d7e
			tipController.resetPosition(element);
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// expose the methods
Packit 1c1d7e
		this.show = openTooltip;
Packit 1c1d7e
		this.hide = closeTooltip;
Packit 1c1d7e
		this.cancel = cancelTimer;
Packit 1c1d7e
		this.resetPosition = repositionTooltip;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Creates a new Placement Calculator.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @constructor
Packit 1c1d7e
	 */
Packit 1c1d7e
	function PlacementCalculator() {
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Compute the CSS position to display a tooltip at the specified placement
Packit 1c1d7e
		 * relative to the specified element.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 * @param {string} placement The placement for the tooltip.
Packit 1c1d7e
		 * @param {number} tipWidth Width of the tooltip element in pixels.
Packit 1c1d7e
		 * @param {number} tipHeight Height of the tooltip element in pixels.
Packit 1c1d7e
		 * @param {number} offset Distance to offset tooltips in pixels.
Packit 1c1d7e
		 * @return {CSSCoordinates} A CSSCoordinates object with the position.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
Packit 1c1d7e
			var placementBase = placement.split('-')[0], // ignore 'alt' for corners
Packit 1c1d7e
				coords = new CSSCoordinates(),
Packit 1c1d7e
				position;
Packit 1c1d7e
Packit 1c1d7e
			if (isSvgElement(element)) {
Packit 1c1d7e
				position = getSvgPlacement(element, placementBase);
Packit 1c1d7e
			} else {
Packit 1c1d7e
				position = getHtmlPlacement(element, placementBase);
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// calculate the appropriate x and y position in the document
Packit 1c1d7e
			switch (placement) {
Packit 1c1d7e
			case 'n':
Packit 1c1d7e
				coords.set('left', position.left - (tipWidth / 2));
Packit 1c1d7e
				coords.set('bottom', session.windowHeight - position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'e':
Packit 1c1d7e
				coords.set('left', position.left + offset);
Packit 1c1d7e
				coords.set('top', position.top - (tipHeight / 2));
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 's':
Packit 1c1d7e
				coords.set('left', position.left - (tipWidth / 2));
Packit 1c1d7e
				coords.set('top', position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'w':
Packit 1c1d7e
				coords.set('top', position.top - (tipHeight / 2));
Packit 1c1d7e
				coords.set('right', session.windowWidth - position.left + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'nw':
Packit 1c1d7e
				coords.set('bottom', session.windowHeight - position.top + offset);
Packit 1c1d7e
				coords.set('right', session.windowWidth - position.left - 20);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'nw-alt':
Packit 1c1d7e
				coords.set('left', position.left);
Packit 1c1d7e
				coords.set('bottom', session.windowHeight - position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'ne':
Packit 1c1d7e
				coords.set('left', position.left - 20);
Packit 1c1d7e
				coords.set('bottom', session.windowHeight - position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'ne-alt':
Packit 1c1d7e
				coords.set('bottom', session.windowHeight - position.top + offset);
Packit 1c1d7e
				coords.set('right', session.windowWidth - position.left);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'sw':
Packit 1c1d7e
				coords.set('top', position.top + offset);
Packit 1c1d7e
				coords.set('right', session.windowWidth - position.left - 20);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'sw-alt':
Packit 1c1d7e
				coords.set('left', position.left);
Packit 1c1d7e
				coords.set('top', position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'se':
Packit 1c1d7e
				coords.set('left', position.left - 20);
Packit 1c1d7e
				coords.set('top', position.top + offset);
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'se-alt':
Packit 1c1d7e
				coords.set('top', position.top + offset);
Packit 1c1d7e
				coords.set('right', session.windowWidth - position.left);
Packit 1c1d7e
				break;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			return coords;
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Finds the tooltip attachment point in the document for a HTML DOM element
Packit 1c1d7e
		 * for the specified placement.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 * @param {string} placement The placement for the tooltip.
Packit 1c1d7e
		 * @return {Object} An object with the top,left position values.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function getHtmlPlacement(element, placement) {
Packit 1c1d7e
			var objectOffset = element.offset(),
Packit 1c1d7e
				objectWidth = element.outerWidth(),
Packit 1c1d7e
				objectHeight = element.outerHeight(),
Packit 1c1d7e
				left,
Packit 1c1d7e
				top;
Packit 1c1d7e
Packit 1c1d7e
			// calculate the appropriate x and y position in the document
Packit 1c1d7e
			switch (placement) {
Packit 1c1d7e
			case 'n':
Packit 1c1d7e
				left = objectOffset.left + objectWidth / 2;
Packit 1c1d7e
				top = objectOffset.top;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'e':
Packit 1c1d7e
				left = objectOffset.left + objectWidth;
Packit 1c1d7e
				top = objectOffset.top + objectHeight / 2;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 's':
Packit 1c1d7e
				left = objectOffset.left + objectWidth / 2;
Packit 1c1d7e
				top = objectOffset.top + objectHeight;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'w':
Packit 1c1d7e
				left = objectOffset.left;
Packit 1c1d7e
				top = objectOffset.top + objectHeight / 2;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'nw':
Packit 1c1d7e
				left = objectOffset.left;
Packit 1c1d7e
				top = objectOffset.top;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'ne':
Packit 1c1d7e
				left = objectOffset.left + objectWidth;
Packit 1c1d7e
				top = objectOffset.top;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'sw':
Packit 1c1d7e
				left = objectOffset.left;
Packit 1c1d7e
				top = objectOffset.top + objectHeight;
Packit 1c1d7e
				break;
Packit 1c1d7e
			case 'se':
Packit 1c1d7e
				left = objectOffset.left + objectWidth;
Packit 1c1d7e
				top = objectOffset.top + objectHeight;
Packit 1c1d7e
				break;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			return {
Packit 1c1d7e
				top: top,
Packit 1c1d7e
				left: left
Packit 1c1d7e
			};
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Finds the tooltip attachment point in the document for a SVG element for
Packit 1c1d7e
		 * the specified placement.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 * @param {string} placement The placement for the tooltip.
Packit 1c1d7e
		 * @return {Object} An object with the top,left position values.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function getSvgPlacement(element, placement) {
Packit 1c1d7e
			var svgElement = element.closest('svg')[0],
Packit 1c1d7e
				domElement = element[0],
Packit 1c1d7e
				point = svgElement.createSVGPoint(),
Packit 1c1d7e
				boundingBox = domElement.getBBox(),
Packit 1c1d7e
				matrix = domElement.getScreenCTM(),
Packit 1c1d7e
				halfWidth = boundingBox.width / 2,
Packit 1c1d7e
				halfHeight = boundingBox.height / 2,
Packit 1c1d7e
				placements = [],
Packit 1c1d7e
				placementKeys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'],
Packit 1c1d7e
				coords,
Packit 1c1d7e
				rotation,
Packit 1c1d7e
				steps,
Packit 1c1d7e
				x;
Packit 1c1d7e
Packit 1c1d7e
			function pushPlacement() {
Packit 1c1d7e
				placements.push(point.matrixTransform(matrix));
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// get bounding box corners and midpoints
Packit 1c1d7e
			point.x = boundingBox.x;
Packit 1c1d7e
			point.y = boundingBox.y;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.x += halfWidth;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.x += halfWidth;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.y += halfHeight;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.y += halfHeight;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.x -= halfWidth;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.x -= halfWidth;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
			point.y -= halfHeight;
Packit 1c1d7e
			pushPlacement();
Packit 1c1d7e
Packit 1c1d7e
			// determine rotation
Packit 1c1d7e
			if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
Packit 1c1d7e
				rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
Packit 1c1d7e
				steps = Math.ceil(((rotation % 360) - 22.5) / 45);
Packit 1c1d7e
				if (steps < 1) {
Packit 1c1d7e
					steps += 8;
Packit 1c1d7e
				}
Packit 1c1d7e
				while (steps--) {
Packit 1c1d7e
					placementKeys.push(placementKeys.shift());
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// find placement
Packit 1c1d7e
			for (x = 0; x < placements.length; x++) {
Packit 1c1d7e
				if (placementKeys[x] === placement) {
Packit 1c1d7e
					coords = placements[x];
Packit 1c1d7e
					break;
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			return {
Packit 1c1d7e
				top: coords.y + session.scrollTop,
Packit 1c1d7e
				left: coords.x + session.scrollLeft
Packit 1c1d7e
			};
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// expose methods
Packit 1c1d7e
		this.compute = computePlacementCoords;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Creates a new tooltip controller.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @constructor
Packit 1c1d7e
	 * @param {Object} options Options object containing settings.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function TooltipController(options) {
Packit 1c1d7e
		var placementCalculator = new PlacementCalculator(),
Packit 1c1d7e
			tipElement = $('#' + options.popupId);
Packit 1c1d7e
Packit 1c1d7e
		// build and append tooltip div if it does not already exist
Packit 1c1d7e
		if (tipElement.length === 0) {
Packit 1c1d7e
			tipElement = $('
', { id: options.popupId });
Packit 1c1d7e
			// grab body element if it was not populated when the script loaded
Packit 1c1d7e
			// note: this hack exists solely for jsfiddle support
Packit 1c1d7e
			if ($body.length === 0) {
Packit 1c1d7e
				$body = $('body');
Packit 1c1d7e
			}
Packit 1c1d7e
			$body.append(tipElement);
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// hook mousemove for cursor follow tooltips
Packit 1c1d7e
		if (options.followMouse) {
Packit 1c1d7e
			// only one positionTipOnCursor hook per tooltip element, please
Packit 1c1d7e
			if (!tipElement.data(DATA_HASMOUSEMOVE)) {
Packit 1c1d7e
				$document.on('mousemove', positionTipOnCursor);
Packit 1c1d7e
				$window.on('scroll', positionTipOnCursor);
Packit 1c1d7e
				tipElement.data(DATA_HASMOUSEMOVE, true);
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// if we want to be able to mouse onto the tooltip then we need to attach
Packit 1c1d7e
		// hover events to the tooltip that will cancel a close request on hover and
Packit 1c1d7e
		// start a new close request on mouseleave
Packit 1c1d7e
		if (options.mouseOnToPopup) {
Packit 1c1d7e
			tipElement.on({
Packit 1c1d7e
				mouseenter: function tipMouseEnter() {
Packit 1c1d7e
					// we only let the mouse stay on the tooltip if it is set to let
Packit 1c1d7e
					// users interact with it
Packit 1c1d7e
					if (tipElement.data(DATA_MOUSEONTOTIP)) {
Packit 1c1d7e
						// check activeHover in case the mouse cursor entered the
Packit 1c1d7e
						// tooltip during the fadeOut and close cycle
Packit 1c1d7e
						if (session.activeHover) {
Packit 1c1d7e
							session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
Packit 1c1d7e
						}
Packit 1c1d7e
					}
Packit 1c1d7e
				},
Packit 1c1d7e
				mouseleave: function tipMouseLeave() {
Packit 1c1d7e
					// check activeHover in case the mouse cursor entered the
Packit 1c1d7e
					// tooltip during the fadeOut and close cycle
Packit 1c1d7e
					if (session.activeHover) {
Packit 1c1d7e
						session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
Packit 1c1d7e
					}
Packit 1c1d7e
				}
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Gives the specified element the active-hover state and queues up the
Packit 1c1d7e
		 * showTip function.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function beginShowTip(element) {
Packit 1c1d7e
			element.data(DATA_HASACTIVEHOVER, true);
Packit 1c1d7e
			// show tooltip, asap
Packit 1c1d7e
			tipElement.queue(function queueTipInit(next) {
Packit 1c1d7e
				showTip(element);
Packit 1c1d7e
				next();
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Shows the tooltip, as soon as possible.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function showTip(element) {
Packit 1c1d7e
			var tipContent;
Packit 1c1d7e
Packit 1c1d7e
			// it is possible, especially with keyboard navigation, to move on to
Packit 1c1d7e
			// another element with a tooltip during the queue to get to this point
Packit 1c1d7e
			// in the code. if that happens then we need to not proceed or we may
Packit 1c1d7e
			// have the fadeout callback for the last tooltip execute immediately
Packit 1c1d7e
			// after this code runs, causing bugs.
Packit 1c1d7e
			if (!element.data(DATA_HASACTIVEHOVER)) {
Packit 1c1d7e
				return;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// if the tooltip is open and we got asked to open another one then the
Packit 1c1d7e
			// old one is still in its fadeOut cycle, so wait and try again
Packit 1c1d7e
			if (session.isTipOpen) {
Packit 1c1d7e
				if (!session.isClosing) {
Packit 1c1d7e
					hideTip(session.activeHover);
Packit 1c1d7e
				}
Packit 1c1d7e
				tipElement.delay(100).queue(function queueTipAgain(next) {
Packit 1c1d7e
					showTip(element);
Packit 1c1d7e
					next();
Packit 1c1d7e
				});
Packit 1c1d7e
				return;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// trigger powerTipPreRender event
Packit 1c1d7e
			element.trigger('powerTipPreRender');
Packit 1c1d7e
Packit 1c1d7e
			// set tooltip content
Packit 1c1d7e
			tipContent = getTooltipContent(element);
Packit 1c1d7e
			if (tipContent) {
Packit 1c1d7e
				tipElement.empty().append(tipContent);
Packit 1c1d7e
			} else {
Packit 1c1d7e
				// we have no content to display, give up
Packit 1c1d7e
				return;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// trigger powerTipRender event
Packit 1c1d7e
			element.trigger('powerTipRender');
Packit 1c1d7e
Packit 1c1d7e
			session.activeHover = element;
Packit 1c1d7e
			session.isTipOpen = true;
Packit 1c1d7e
Packit 1c1d7e
			tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
Packit 1c1d7e
Packit 1c1d7e
			// set tooltip position
Packit 1c1d7e
			if (!options.followMouse) {
Packit 1c1d7e
				positionTipOnElement(element);
Packit 1c1d7e
				session.isFixedTipOpen = true;
Packit 1c1d7e
			} else {
Packit 1c1d7e
				positionTipOnCursor();
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// fadein
Packit 1c1d7e
			tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
Packit 1c1d7e
				// start desync polling
Packit 1c1d7e
				if (!session.desyncTimeout) {
Packit 1c1d7e
					session.desyncTimeout = setInterval(closeDesyncedTip, 500);
Packit 1c1d7e
				}
Packit 1c1d7e
Packit 1c1d7e
				// trigger powerTipOpen event
Packit 1c1d7e
				element.trigger('powerTipOpen');
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Hides the tooltip.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function hideTip(element) {
Packit 1c1d7e
			// reset session
Packit 1c1d7e
			session.isClosing = true;
Packit 1c1d7e
			session.activeHover = null;
Packit 1c1d7e
			session.isTipOpen = false;
Packit 1c1d7e
Packit 1c1d7e
			// stop desync polling
Packit 1c1d7e
			session.desyncTimeout = clearInterval(session.desyncTimeout);
Packit 1c1d7e
Packit 1c1d7e
			// reset element state
Packit 1c1d7e
			element.data(DATA_HASACTIVEHOVER, false);
Packit 1c1d7e
			element.data(DATA_FORCEDOPEN, false);
Packit 1c1d7e
Packit 1c1d7e
			// fade out
Packit 1c1d7e
			tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
Packit 1c1d7e
				var coords = new CSSCoordinates();
Packit 1c1d7e
Packit 1c1d7e
				// reset session and tooltip element
Packit 1c1d7e
				session.isClosing = false;
Packit 1c1d7e
				session.isFixedTipOpen = false;
Packit 1c1d7e
				tipElement.removeClass();
Packit 1c1d7e
Packit 1c1d7e
				// support mouse-follow and fixed position tips at the same time by
Packit 1c1d7e
				// moving the tooltip to the last cursor location after it is hidden
Packit 1c1d7e
				coords.set('top', session.currentY + options.offset);
Packit 1c1d7e
				coords.set('left', session.currentX + options.offset);
Packit 1c1d7e
				tipElement.css(coords);
Packit 1c1d7e
Packit 1c1d7e
				// trigger powerTipClose event
Packit 1c1d7e
				element.trigger('powerTipClose');
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Moves the tooltip to the users mouse cursor.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 */
Packit 1c1d7e
		function positionTipOnCursor() {
Packit 1c1d7e
			// to support having fixed tooltips on the same page as cursor tooltips,
Packit 1c1d7e
			// where both instances are referencing the same tooltip element, we
Packit 1c1d7e
			// need to keep track of the mouse position constantly, but we should
Packit 1c1d7e
			// only set the tip location if a fixed tip is not currently open, a tip
Packit 1c1d7e
			// open is imminent or active, and the tooltip element in question does
Packit 1c1d7e
			// have a mouse-follow using it.
Packit 1c1d7e
			if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
Packit 1c1d7e
				// grab measurements
Packit 1c1d7e
				var tipWidth = tipElement.outerWidth(),
Packit 1c1d7e
					tipHeight = tipElement.outerHeight(),
Packit 1c1d7e
					coords = new CSSCoordinates(),
Packit 1c1d7e
					collisions,
Packit 1c1d7e
					collisionCount;
Packit 1c1d7e
Packit 1c1d7e
				// grab collisions
Packit 1c1d7e
				coords.set('top', session.currentY + options.offset);
Packit 1c1d7e
				coords.set('left', session.currentX + options.offset);
Packit 1c1d7e
				collisions = getViewportCollisions(
Packit 1c1d7e
					coords,
Packit 1c1d7e
					tipWidth,
Packit 1c1d7e
					tipHeight
Packit 1c1d7e
				);
Packit 1c1d7e
Packit 1c1d7e
				// handle tooltip view port collisions
Packit 1c1d7e
				if (collisions !== Collision.none) {
Packit 1c1d7e
					collisionCount = countFlags(collisions);
Packit 1c1d7e
					if (collisionCount === 1) {
Packit 1c1d7e
						// if there is only one collision (bottom or right) then
Packit 1c1d7e
						// simply constrain the tooltip to the view port
Packit 1c1d7e
						if (collisions === Collision.right) {
Packit 1c1d7e
							coords.set('left', session.windowWidth - tipWidth);
Packit 1c1d7e
						} else if (collisions === Collision.bottom) {
Packit 1c1d7e
							coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
Packit 1c1d7e
						}
Packit 1c1d7e
					} else {
Packit 1c1d7e
						// if the tooltip has more than one collision then it is
Packit 1c1d7e
						// trapped in the corner and should be flipped to get it out
Packit 1c1d7e
						// of the users way
Packit 1c1d7e
						coords.set('left', session.currentX - tipWidth - options.offset);
Packit 1c1d7e
						coords.set('top', session.currentY - tipHeight - options.offset);
Packit 1c1d7e
					}
Packit 1c1d7e
				}
Packit 1c1d7e
Packit 1c1d7e
				// position the tooltip
Packit 1c1d7e
				tipElement.css(coords);
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Sets the tooltip to the correct position relative to the specified target
Packit 1c1d7e
		 * element. Based on options settings.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function positionTipOnElement(element) {
Packit 1c1d7e
			var priorityList,
Packit 1c1d7e
				finalPlacement;
Packit 1c1d7e
Packit 1c1d7e
			if (options.smartPlacement) {
Packit 1c1d7e
				priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
Packit 1c1d7e
Packit 1c1d7e
				// iterate over the priority list and use the first placement option
Packit 1c1d7e
				// that does not collide with the view port. if they all collide
Packit 1c1d7e
				// then the last placement in the list will be used.
Packit 1c1d7e
				$.each(priorityList, function(idx, pos) {
Packit 1c1d7e
					// place tooltip and find collisions
Packit 1c1d7e
					var collisions = getViewportCollisions(
Packit 1c1d7e
						placeTooltip(element, pos),
Packit 1c1d7e
						tipElement.outerWidth(),
Packit 1c1d7e
						tipElement.outerHeight()
Packit 1c1d7e
					);
Packit 1c1d7e
Packit 1c1d7e
					// update the final placement variable
Packit 1c1d7e
					finalPlacement = pos;
Packit 1c1d7e
Packit 1c1d7e
					// break if there were no collisions
Packit 1c1d7e
					if (collisions === Collision.none) {
Packit 1c1d7e
						return false;
Packit 1c1d7e
					}
Packit 1c1d7e
				});
Packit 1c1d7e
			} else {
Packit 1c1d7e
				// if we're not going to use the smart placement feature then just
Packit 1c1d7e
				// compute the coordinates and do it
Packit 1c1d7e
				placeTooltip(element, options.placement);
Packit 1c1d7e
				finalPlacement = options.placement;
Packit 1c1d7e
			}
Packit 1c1d7e
Packit 1c1d7e
			// add placement as class for CSS arrows
Packit 1c1d7e
			tipElement.addClass(finalPlacement);
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Sets the tooltip position to the appropriate values to show the tip at
Packit 1c1d7e
		 * the specified placement. This function will iterate and test the tooltip
Packit 1c1d7e
		 * to support elastic tooltips.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 * @param {jQuery} element The element that the tooltip should target.
Packit 1c1d7e
		 * @param {string} placement The placement for the tooltip.
Packit 1c1d7e
		 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
Packit 1c1d7e
		 *     right position values.
Packit 1c1d7e
		 */
Packit 1c1d7e
		function placeTooltip(element, placement) {
Packit 1c1d7e
			var iterationCount = 0,
Packit 1c1d7e
				tipWidth,
Packit 1c1d7e
				tipHeight,
Packit 1c1d7e
				coords = new CSSCoordinates();
Packit 1c1d7e
Packit 1c1d7e
			// set the tip to 0,0 to get the full expanded width
Packit 1c1d7e
			coords.set('top', 0);
Packit 1c1d7e
			coords.set('left', 0);
Packit 1c1d7e
			tipElement.css(coords);
Packit 1c1d7e
Packit 1c1d7e
			// to support elastic tooltips we need to check for a change in the
Packit 1c1d7e
			// rendered dimensions after the tooltip has been positioned
Packit 1c1d7e
			do {
Packit 1c1d7e
				// grab the current tip dimensions
Packit 1c1d7e
				tipWidth = tipElement.outerWidth();
Packit 1c1d7e
				tipHeight = tipElement.outerHeight();
Packit 1c1d7e
Packit 1c1d7e
				// get placement coordinates
Packit 1c1d7e
				coords = placementCalculator.compute(
Packit 1c1d7e
					element,
Packit 1c1d7e
					placement,
Packit 1c1d7e
					tipWidth,
Packit 1c1d7e
					tipHeight,
Packit 1c1d7e
					options.offset
Packit 1c1d7e
				);
Packit 1c1d7e
Packit 1c1d7e
				// place the tooltip
Packit 1c1d7e
				tipElement.css(coords);
Packit 1c1d7e
			} while (
Packit 1c1d7e
				// sanity check: limit to 5 iterations, and...
Packit 1c1d7e
				++iterationCount <= 5 &&
Packit 1c1d7e
				// try again if the dimensions changed after placement
Packit 1c1d7e
				(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
Packit 1c1d7e
			);
Packit 1c1d7e
Packit 1c1d7e
			return coords;
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		/**
Packit 1c1d7e
		 * Checks for a tooltip desync and closes the tooltip if one occurs.
Packit 1c1d7e
		 * @private
Packit 1c1d7e
		 */
Packit 1c1d7e
		function closeDesyncedTip() {
Packit 1c1d7e
			var isDesynced = false;
Packit 1c1d7e
			// It is possible for the mouse cursor to leave an element without
Packit 1c1d7e
			// firing the mouseleave or blur event. This most commonly happens when
Packit 1c1d7e
			// the element is disabled under mouse cursor. If this happens it will
Packit 1c1d7e
			// result in a desynced tooltip because the tooltip was never asked to
Packit 1c1d7e
			// close. So we should periodically check for a desync situation and
Packit 1c1d7e
			// close the tip if such a situation arises.
Packit 1c1d7e
			if (session.isTipOpen && !session.isClosing && !session.delayInProgress) {
Packit 1c1d7e
				// user moused onto another tip or active hover is disabled
Packit 1c1d7e
				if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
Packit 1c1d7e
					isDesynced = true;
Packit 1c1d7e
				} else {
Packit 1c1d7e
					// hanging tip - have to test if mouse position is not over the
Packit 1c1d7e
					// active hover and not over a tooltip set to let the user
Packit 1c1d7e
					// interact with it.
Packit 1c1d7e
					// for keyboard navigation: this only counts if the element does
Packit 1c1d7e
					// not have focus.
Packit 1c1d7e
					// for tooltips opened via the api: we need to check if it has
Packit 1c1d7e
					// the forcedOpen flag.
Packit 1c1d7e
					if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
Packit 1c1d7e
						if (tipElement.data(DATA_MOUSEONTOTIP)) {
Packit 1c1d7e
							if (!isMouseOver(tipElement)) {
Packit 1c1d7e
								isDesynced = true;
Packit 1c1d7e
							}
Packit 1c1d7e
						} else {
Packit 1c1d7e
							isDesynced = true;
Packit 1c1d7e
						}
Packit 1c1d7e
					}
Packit 1c1d7e
				}
Packit 1c1d7e
Packit 1c1d7e
				if (isDesynced) {
Packit 1c1d7e
					// close the desynced tip
Packit 1c1d7e
					hideTip(session.activeHover);
Packit 1c1d7e
				}
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		// expose methods
Packit 1c1d7e
		this.showTip = beginShowTip;
Packit 1c1d7e
		this.hideTip = hideTip;
Packit 1c1d7e
		this.resetPosition = positionTipOnElement;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Determine whether a jQuery object is an SVG element
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @param {jQuery} element The element to check
Packit 1c1d7e
	 * @return {boolean} Whether this is an SVG element
Packit 1c1d7e
	 */
Packit 1c1d7e
	function isSvgElement(element) {
Packit 1c1d7e
		return window.SVGElement && element[0] instanceof SVGElement;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Initializes the viewport dimension cache and hooks up the mouse position
Packit 1c1d7e
	 * tracking and viewport dimension tracking events.
Packit 1c1d7e
	 * Prevents attaching the events more than once.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 */
Packit 1c1d7e
	function initTracking() {
Packit 1c1d7e
		if (!session.mouseTrackingActive) {
Packit 1c1d7e
			session.mouseTrackingActive = true;
Packit 1c1d7e
Packit 1c1d7e
			// grab the current viewport dimensions on load
Packit 1c1d7e
			$(function getViewportDimensions() {
Packit 1c1d7e
				session.scrollLeft = $window.scrollLeft();
Packit 1c1d7e
				session.scrollTop = $window.scrollTop();
Packit 1c1d7e
				session.windowWidth = $window.width();
Packit 1c1d7e
				session.windowHeight = $window.height();
Packit 1c1d7e
			});
Packit 1c1d7e
Packit 1c1d7e
			// hook mouse move tracking
Packit 1c1d7e
			$document.on('mousemove', trackMouse);
Packit 1c1d7e
Packit 1c1d7e
			// hook viewport dimensions tracking
Packit 1c1d7e
			$window.on({
Packit 1c1d7e
				resize: function trackResize() {
Packit 1c1d7e
					session.windowWidth = $window.width();
Packit 1c1d7e
					session.windowHeight = $window.height();
Packit 1c1d7e
				},
Packit 1c1d7e
				scroll: function trackScroll() {
Packit 1c1d7e
					var x = $window.scrollLeft(),
Packit 1c1d7e
						y = $window.scrollTop();
Packit 1c1d7e
					if (x !== session.scrollLeft) {
Packit 1c1d7e
						session.currentX += x - session.scrollLeft;
Packit 1c1d7e
						session.scrollLeft = x;
Packit 1c1d7e
					}
Packit 1c1d7e
					if (y !== session.scrollTop) {
Packit 1c1d7e
						session.currentY += y - session.scrollTop;
Packit 1c1d7e
						session.scrollTop = y;
Packit 1c1d7e
					}
Packit 1c1d7e
				}
Packit 1c1d7e
			});
Packit 1c1d7e
		}
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Saves the current mouse coordinates to the session object.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @param {jQuery.Event} event The mousemove event for the document.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function trackMouse(event) {
Packit 1c1d7e
		session.currentX = event.pageX;
Packit 1c1d7e
		session.currentY = event.pageY;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Tests if the mouse is currently over the specified element.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @param {jQuery} element The element to check for hover.
Packit 1c1d7e
	 * @return {boolean}
Packit 1c1d7e
	 */
Packit 1c1d7e
	function isMouseOver(element) {
Packit 1c1d7e
		// use getBoundingClientRect() because jQuery's width() and height()
Packit 1c1d7e
		// methods do not work with SVG elements
Packit 1c1d7e
		// compute width/height because those properties do not exist on the object
Packit 1c1d7e
		// returned by getBoundingClientRect() in older versions of IE
Packit 1c1d7e
		var elementPosition = element.offset(),
Packit 1c1d7e
			elementBox = element[0].getBoundingClientRect(),
Packit 1c1d7e
			elementWidth = elementBox.right - elementBox.left,
Packit 1c1d7e
			elementHeight = elementBox.bottom - elementBox.top;
Packit 1c1d7e
Packit 1c1d7e
		return session.currentX >= elementPosition.left &&
Packit 1c1d7e
			session.currentX <= elementPosition.left + elementWidth &&
Packit 1c1d7e
			session.currentY >= elementPosition.top &&
Packit 1c1d7e
			session.currentY <= elementPosition.top + elementHeight;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Fetches the tooltip content from the specified element's data attributes.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @param {jQuery} element The element to get the tooltip content for.
Packit 1c1d7e
	 * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
Packit 1c1d7e
	 *     undefined if there was no tooltip content for the element.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function getTooltipContent(element) {
Packit 1c1d7e
		var tipText = element.data(DATA_POWERTIP),
Packit 1c1d7e
			tipObject = element.data(DATA_POWERTIPJQ),
Packit 1c1d7e
			tipTarget = element.data(DATA_POWERTIPTARGET),
Packit 1c1d7e
			targetElement,
Packit 1c1d7e
			content;
Packit 1c1d7e
Packit 1c1d7e
		if (tipText) {
Packit 1c1d7e
			if ($.isFunction(tipText)) {
Packit 1c1d7e
				tipText = tipText.call(element[0]);
Packit 1c1d7e
			}
Packit 1c1d7e
			content = tipText;
Packit 1c1d7e
		} else if (tipObject) {
Packit 1c1d7e
			if ($.isFunction(tipObject)) {
Packit 1c1d7e
				tipObject = tipObject.call(element[0]);
Packit 1c1d7e
			}
Packit 1c1d7e
			if (tipObject.length > 0) {
Packit 1c1d7e
				content = tipObject.clone(true, true);
Packit 1c1d7e
			}
Packit 1c1d7e
		} else if (tipTarget) {
Packit 1c1d7e
			targetElement = $('#' + tipTarget);
Packit 1c1d7e
			if (targetElement.length > 0) {
Packit 1c1d7e
				content = targetElement.html();
Packit 1c1d7e
			}
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		return content;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Finds any viewport collisions that an element (the tooltip) would have if it
Packit 1c1d7e
	 * were absolutely positioned at the specified coordinates.
Packit 1c1d7e
	 * @private
Packit 1c1d7e
	 * @param {CSSCoordinates} coords Coordinates for the element.
Packit 1c1d7e
	 * @param {number} elementWidth Width of the element in pixels.
Packit 1c1d7e
	 * @param {number} elementHeight Height of the element in pixels.
Packit 1c1d7e
	 * @return {number} Value with the collision flags.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function getViewportCollisions(coords, elementWidth, elementHeight) {
Packit 1c1d7e
		var viewportTop = session.scrollTop,
Packit 1c1d7e
			viewportLeft =  session.scrollLeft,
Packit 1c1d7e
			viewportBottom = viewportTop + session.windowHeight,
Packit 1c1d7e
			viewportRight = viewportLeft + session.windowWidth,
Packit 1c1d7e
			collisions = Collision.none;
Packit 1c1d7e
Packit 1c1d7e
		if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
Packit 1c1d7e
			collisions |= Collision.top;
Packit 1c1d7e
		}
Packit 1c1d7e
		if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
Packit 1c1d7e
			collisions |= Collision.bottom;
Packit 1c1d7e
		}
Packit 1c1d7e
		if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
Packit 1c1d7e
			collisions |= Collision.left;
Packit 1c1d7e
		}
Packit 1c1d7e
		if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
Packit 1c1d7e
			collisions |= Collision.right;
Packit 1c1d7e
		}
Packit 1c1d7e
Packit 1c1d7e
		return collisions;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
	/**
Packit 1c1d7e
	 * Counts the number of bits set on a flags value.
Packit 1c1d7e
	 * @param {number} value The flags value.
Packit 1c1d7e
	 * @return {number} The number of bits that have been set.
Packit 1c1d7e
	 */
Packit 1c1d7e
	function countFlags(value) {
Packit 1c1d7e
		var count = 0;
Packit 1c1d7e
		while (value) {
Packit 1c1d7e
			value &= value - 1;
Packit 1c1d7e
			count++;
Packit 1c1d7e
		}
Packit 1c1d7e
		return count;
Packit 1c1d7e
	}
Packit 1c1d7e
Packit 1c1d7e
}));