PageSourceSearch

https://partnersstevy.sk/themes/partnersgroup/assets/js/jquery.powertip-1.3.0/jquery.powertip.js

js partnersstevy.sk collected 2026-09-27 15:11:40 UTC 42,057 bytes, 1,355 lines download raw bytes

vendor: 42,057 bytes, lines 1-1355
1/*!
2 PowerTip v1.3.0 (2017-01-15)
3 https://stevenbenner.github.io/jquery-powertip/
4 Copyright (c) 2017 Steven Benner (http://stevenbenner.com/).
5 Released under MIT license.
6 https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
7*/
8(function(root, factory) {
9	// support loading the plugin via common patterns
10	if (typeof define === 'function' && define.amd) {
11		// load the plugin as an amd module
12		define([ 'jquery' ], factory);
13	} else if (typeof module === 'object' && module.exports) {
14		// load the plugin as a commonjs module
15		module.exports = factory(require('jquery'));
16	} else {
17		// load the plugin as a global
18		factory(root.jQuery);
19	}
20}(this, function($) {
21
22	// useful private variables
23	var $document = $(document),
24		$window = $(window),
25		$body = $('body');
26
27	// constants
28	var DATA_DISPLAYCONTROLLER = 'displayController',
29		DATA_HASACTIVEHOVER = 'hasActiveHover',
30		DATA_FORCEDOPEN = 'forcedOpen',
31		DATA_HASMOUSEMOVE = 'hasMouseMove',
32		DATA_MOUSEONTOTIP = 'mouseOnToPopup',
33		DATA_ORIGINALTITLE = 'originalTitle',
34		DATA_POWERTIP = 'powertip',
35		DATA_POWERTIPJQ = 'powertipjq',
36		DATA_POWERTIPTARGET = 'powertiptarget',
37		EVENT_NAMESPACE = '.powertip',
38		RAD2DEG = 180 / Math.PI;
39
40	/**
41	 * Session data
42	 * Private properties global to all powerTip instances
43	 */
44	var session = {
45		elements: null,
46		tooltips: null,
47		isTipOpen: false,
48		isFixedTipOpen: false,
49		isClosing: false,
50		tipOpenImminent: false,
51		activeHover: null,
52		currentX: 0,
53		currentY: 0,
54		previousX: 0,
55		previousY: 0,
56		desyncTimeout: null,
57		closeDelayTimeout: null,
58		mouseTrackingActive: false,
59		delayInProgress: false,
60		windowWidth: 0,
61		windowHeight: 0,
62		scrollTop: 0,
63		scrollLeft: 0
64	};
65
66	/**
67	 * Collision enumeration
68	 * @enum {number}
69	 */
70	var Collision = {
71		none: 0,
72		top: 1,
73		bottom: 2,
74		left: 4,
75		right: 8
76	};
77
78	/**
79	 * Display hover tooltips on the matched elements.
80	 * @param {(Object|string)=} opts The options object to use for the plugin, or
81	 *     the name of a method to invoke on the first matched element.
82	 * @param {*=} [arg] Argument for an invoked method (optional).
83	 * @return {jQuery} jQuery object for the matched selectors.
84	 */
85	$.fn.powerTip = function(opts, arg) {
86		var targetElements = this,
87			options,
88			tipController;
89
90		// don't do any work if there were no matched elements
91		if (!targetElements.length) {
92			return targetElements;
93		}
94
95		// handle api method calls on the plugin, e.g. powerTip('hide')
96		if ($.type(opts) === 'string' && $.powerTip[opts]) {
97			return $.powerTip[opts].call(targetElements, targetElements, arg);
98		}
99
100		// extend options and instantiate TooltipController
101		options = $.extend({}, $.fn.powerTip.defaults, opts);
102		tipController = new TooltipController(options);
103
104		// hook mouse and viewport dimension tracking
105		initTracking();
106
107		// setup the elements
108		targetElements.each(function elementSetup() {
109			var $this = $(this),
110				dataPowertip = $this.data(DATA_POWERTIP),
111				dataElem = $this.data(DATA_POWERTIPJQ),
112				dataTarget = $this.data(DATA_POWERTIPTARGET),
113				title;
114
115			// handle repeated powerTip calls on the same element by destroying the
116			// original instance hooked to it and replacing it with this call
117			if ($this.data(DATA_DISPLAYCONTROLLER)) {
118				$.powerTip.destroy($this);
119			}
120
121			// attempt to use title attribute text if there is no data-powertip,
122			// data-powertipjq or data-powertiptarget. If we do use the title
123			// attribute, delete the attribute so the browser will not show it
124			title = $this.attr('title');
125			if (!dataPowertip && !dataTarget && !dataElem && title) {
126				$this.data(DATA_POWERTIP, title);
127				$this.data(DATA_ORIGINALTITLE, title);
128				$this.removeAttr('title');
129			}
130
131			// create hover controllers for each element
132			$this.data(
133				DATA_DISPLAYCONTROLLER,
134				new DisplayController($this, options, tipController)
135			);
136		});
137
138		// attach events to matched elements if the manual option is not enabled
139		if (!options.manual) {
140			// attach open events
141			$.each(options.openEvents, function(idx, evt) {
142				if ($.inArray(evt, options.closeEvents) > -1) {
143					// event is in both openEvents and closeEvents, so toggle it
144					targetElements.on(evt + EVENT_NAMESPACE, function elementToggle(event) {
145						$.powerTip.toggle(this, event);
146					});
147				} else {
148					targetElements.on(evt + EVENT_NAMESPACE, function elementOpen(event) {
149						$.powerTip.show(this, event);
150					});
151				}
152			});
153
154			// attach close events
155			$.each(options.closeEvents, function(idx, evt) {
156				if ($.inArray(evt, options.openEvents) < 0) {
157					targetElements.on(evt + EVENT_NAMESPACE, function elementClose(event) {
158						// set immediate to true for any event without mouse info
159						$.powerTip.hide(this, !isMouseEvent(event));
160					});
161				}
162			});
163
164			// attach escape key close event
165			targetElements.on('keydown' + EVENT_NAMESPACE, function elementKeyDown(event) {
166				// always close tooltip when the escape key is pressed
167				if (event.keyCode === 27) {
168					$.powerTip.hide(this, true);
169				}
170			});
171		}
172
173		// remember elements that the plugin is attached to
174		session.elements = session.elements ? session.elements.add(targetElements) : targetElements;
175
176		return targetElements;
177	};
178
179	/**
180	 * Default options for the powerTip plugin.
181	 */
182	$.fn.powerTip.defaults = {
183		fadeInTime: 200,
184		fadeOutTime: 100,
185		followMouse: false,
186		popupId: 'powerTip',
187		popupClass: null,
188		intentSensitivity: 7,
189		intentPollInterval: 100,
190		closeDelay: 100,
191		placement: 'n',
192		smartPlacement: false,
193		offset: 10,
194		mouseOnToPopup: false,
195		manual: false,
196		openEvents: [ 'mouseenter', 'focus' ],
197		closeEvents: [ 'mouseleave', 'blur' ]
198	};
199
200	/**
201	 * Default smart placement priority lists.
202	 * The first item in the array is the highest priority, the last is the lowest.
203	 * The last item is also the default, which will be used if all previous options
204	 * do not fit.
205	 */
206	$.fn.powerTip.smartPlacementLists = {
207		n: [ 'n', 'ne', 'nw', 's' ],
208		e: [ 'e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e' ],
209		s: [ 's', 'se', 'sw', 'n' ],
210		w: [ 'w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w' ],
211		nw: [ 'nw', 'w', 'sw', 'n', 's', 'se', 'nw' ],
212		ne: [ 'ne', 'e', 'se', 'n', 's', 'sw', 'ne' ],
213		sw: [ 'sw', 'w', 'nw', 's', 'n', 'ne', 'sw' ],
214		se: [ 'se', 'e', 'ne', 's', 'n', 'nw', 'se' ],
215		'nw-alt': [ 'nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e' ],
216		'ne-alt': [ 'ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w' ],
217		'sw-alt': [ 'sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e' ],
218		'se-alt': [ 'se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w' ]
219	};
220
221	/**
222	 * Public API
223	 */
224	$.powerTip = {
225		/**
226		 * Attempts to show the tooltip for the specified element.
227		 * @param {jQuery|Element} element The element to open the tooltip for.
228		 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
229		 *     tracking (optional).
230		 * @return {jQuery|Element} The original jQuery object or DOM Element.
231		 */
232		show: function apiShowTip(element, event) {
233			// if we were given a mouse event then run the hover intent testing,
234			// otherwise, simply show the tooltip asap
235			if (isMouseEvent(event)) {
236				trackMouse(event);
237				session.previousX = event.pageX;
238				session.previousY = event.pageY;
239				$(element).data(DATA_DISPLAYCONTROLLER).show();
240			} else {
241				$(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
242			}
243			return element;
244		},
245
246		/**
247		 * Repositions the tooltip on the element.
248		 * @param {jQuery|Element} element The element the tooltip is shown for.
249		 * @return {jQuery|Element} The original jQuery object or DOM Element.
250		 */
251		reposition: function apiResetPosition(element) {
252			$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
253			return element;
254		},
255
256		/**
257		 * Attempts to close any open tooltips.
258		 * @param {(jQuery|Element)=} element The element with the tooltip that
259		 *     should be closed (optional).
260		 * @param {boolean=} immediate Disable close delay (optional).
261		 * @return {jQuery|Element|undefined} The original jQuery object or DOM
262		 *     Element, if one was specified.
263		 */
264		hide: function apiCloseTip(element, immediate) {
265			var displayController;
266
267			// set immediate to true when no element is specified
268			immediate = element ? immediate : true;
269
270			// find the relevant display controller
271			if (element) {
272				displayController = $(element).first().data(DATA_DISPLAYCONTROLLER);
273			} else if (session.activeHover) {
274				displayController = session.activeHover.data(DATA_DISPLAYCONTROLLER);
275			}
276
277			// if found, hide the tip
278			if (displayController) {
279				displayController.hide(immediate);
280			}
281
282			return element;
283		},
284
285		/**
286		 * Toggles the tooltip for the specified element. This will open a closed
287		 * tooltip, or close an open tooltip.
288		 * @param {jQuery|Element} element The element with the tooltip that
289		 *     should be toggled.
290		 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
291		 *     tracking (optional).
292		 * @return {jQuery|Element} The original jQuery object or DOM Element.
293		 */
294		toggle: function apiToggle(element, event) {
295			if (session.activeHover && session.activeHover.is(element)) {
296				// tooltip for element is active, so close it
297				$.powerTip.hide(element, !isMouseEvent(event));
298			} else {
299				// tooltip for element is not active, so open it
300				$.powerTip.show(element, event);
301			}
302			return element;
303		},
304
305		/**
306		 * Destroy and roll back any powerTip() instance on the specified elements.
307		 * If no elements are specified then all elements that the plugin is
308		 * currently attached to will be rolled back.
309		 * @param {(jQuery|Element)=} element The element with the powerTip instance.
310		 * @return {jQuery|Element|undefined} The original jQuery object or DOM
311		 *     Element, if one was specified.
312		 */
313		destroy: function apiDestroy(element) {
314			var $element = element ? $(element) : session.elements;
315
316			// if the plugin is not hooked to any elements then there is no point
317			// trying to destroy anything, or dealing with the possible errors
318			if (!session.elements || session.elements.length === 0) {
319				return element;
320			}
321
322			// unhook events and destroy plugin changes to each element
323			$element.off(EVENT_NAMESPACE).each(function destroy() {
324				var $this = $(this),
325					dataAttributes = [
326						DATA_ORIGINALTITLE,
327						DATA_DISPLAYCONTROLLER,
328						DATA_HASACTIVEHOVER,
329						DATA_FORCEDOPEN
330					];
331
332				// revert title attribute
333				if ($this.data(DATA_ORIGINALTITLE)) {
334					$this.attr('title', $this.data(DATA_ORIGINALTITLE));
335					dataAttributes.push(DATA_POWERTIP);
336				}
337
338				// remove data attributes
339				$this.removeData(dataAttributes);
340			});
341
342			// remove destroyed element from active elements collection
343			session.elements = session.elements.not($element);
344
345			// if there are no active elements left then we will unhook all of the
346			// events that we've bound code to and remove the tooltip elements
347			if (session.elements.length === 0) {
348				$window.off(EVENT_NAMESPACE);
349				$document.off(EVENT_NAMESPACE);
350				session.mouseTrackingActive = false;
351				session.tooltips.remove();
352				session.tooltips = null;
353			}
354
355			return element;
356		}
357	};
358
359	// API aliasing
360	$.powerTip.showTip = $.powerTip.show;
361	$.powerTip.closeTip = $.powerTip.hide;
362
363	/**
364	 * Creates a new CSSCoordinates object.
365	 * @private
366	 * @constructor
367	 */
368	function CSSCoordinates() {
369		var me = this;
370
371		// initialize object properties
372		me.top = 'auto';
373		me.left = 'auto';
374		me.right = 'auto';
375		me.bottom = 'auto';
376
377		/**
378		 * Set a property to a value.
379		 * @private
380		 * @param {string} property The name of the property.
381		 * @param {number} value The value of the property.
382		 */
383		me.set = function(property, value) {
384			if ($.isNumeric(value)) {
385				me[property] = Math.round(value);
386			}
387		};
388	}
389
390	/**
391	 * Creates a new tooltip display controller.
392	 * @private
393	 * @constructor
394	 * @param {jQuery} element The element that this controller will handle.
395	 * @param {Object} options Options object containing settings.
396	 * @param {TooltipController} tipController The TooltipController object for
397	 *     this instance.
398	 */
399	function DisplayController(element, options, tipController) {
400		var hoverTimer = null,
401			myCloseDelay = null;
402
403		/**
404		 * Begins the process of showing a tooltip.
405		 * @private
406		 * @param {boolean=} immediate Skip intent testing (optional).
407		 * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
408		 *     open (optional).
409		 */
410		function openTooltip(immediate, forceOpen) {
411			cancelTimer();
412			if (!element.data(DATA_HASACTIVEHOVER)) {
413				if (!immediate) {
414					session.tipOpenImminent = true;
415					hoverTimer = setTimeout(
416						function intentDelay() {
417							hoverTimer = null;
418							checkForIntent();
419						},
420						options.intentPollInterval
421					);
422				} else {
423					if (forceOpen) {
424						element.data(DATA_FORCEDOPEN, true);
425					}
426					closeAnyDelayed();
427					tipController.showTip(element);
428				}
429			} else {
430				// cursor left and returned to this element, cancel close
431				cancelClose();
432			}
433		}
434
435		/**
436		 * Begins the process of closing a tooltip.
437		 * @private
438		 * @param {boolean=} disableDelay Disable close delay (optional).
439		 */
440		function closeTooltip(disableDelay) {
441			// if this instance already has a close delay in progress then halt it
442			if (myCloseDelay) {
443				myCloseDelay = session.closeDelayTimeout = clearTimeout(myCloseDelay);
444				session.delayInProgress = false;
445			}
446			cancelTimer();
447			session.tipOpenImminent = false;
448			if (element.data(DATA_HASACTIVEHOVER)) {
449				element.data(DATA_FORCEDOPEN, false);
450				if (!disableDelay) {
451					session.delayInProgress = true;
452					session.closeDelayTimeout = setTimeout(
453						function closeDelay() {
454							session.closeDelayTimeout = null;
455							tipController.hideTip(element);
456							session.delayInProgress = false;
457							myCloseDelay = null;
458						},
459						options.closeDelay
460					);
461					// save internal reference close delay id so we can check if the
462					// active close delay belongs to this instance
463					myCloseDelay = session.closeDelayTimeout;
464				} else {
465					tipController.hideTip(element);
466				}
467			}
468		}
469
470		/**
471		 * Checks mouse position to make sure that the user intended to hover on the
472		 * specified element before showing the tooltip.
473		 * @private
474		 */
475		function checkForIntent() {
476			// calculate mouse position difference
477			var xDifference = Math.abs(session.previousX - session.currentX),
478				yDifference = Math.abs(session.previousY - session.currentY),
479				totalDifference = xDifference + yDifference;
480
481			// check if difference has passed the sensitivity threshold
482			if (totalDifference < options.intentSensitivity) {
483				cancelClose();
484				closeAnyDelayed();
485				tipController.showTip(element);
486			} else {
487				// try again
488				session.previousX = session.currentX;
489				session.previousY = session.currentY;
490				openTooltip();
491			}
492		}
493
494		/**
495		 * Cancels active hover timer.
496		 * @private
497		 * @param {boolean=} stopClose Cancel any active close delay timer.
498		 */
499		function cancelTimer(stopClose) {
500			hoverTimer = clearTimeout(hoverTimer);
501			// cancel the current close delay if the active close delay is for this
502			// element or the stopClose argument is true
503			if (session.closeDelayTimeout && myCloseDelay === session.closeDelayTimeout || stopClose) {
504				cancelClose();
505			}
506		}
507
508		/**
509		 * Cancels any active close delay timer.
510		 * @private
511		 */
512		function cancelClose() {
513			session.closeDelayTimeout = clearTimeout(session.closeDelayTimeout);
514			session.delayInProgress = false;
515		}
516
517		/**
518		 * Asks any tooltips waiting on their close delay to close now.
519		 * @private
520		 */
521		function closeAnyDelayed() {
522			// if another element is waiting for its close delay then we should ask
523			// it to close immediately so we can proceed without unexpected timeout
524			// code being run during this tooltip's lifecycle
525			if (session.delayInProgress && session.activeHover && !session.activeHover.is(element)) {
526				session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
527			}
528		}
529
530		/**
531		 * Repositions the tooltip on this element.
532		 * @private
533		 */
534		function repositionTooltip() {
535			tipController.resetPosition(element);
536		}
537
538		// expose the methods
539		this.show = openTooltip;
540		this.hide = closeTooltip;
541		this.cancel = cancelTimer;
542		this.resetPosition = repositionTooltip;
543	}
544
545	/**
546	 * Creates a new Placement Calculator.
547	 * @private
548	 * @constructor
549	 */
550	function PlacementCalculator() {
551		/**
552		 * Compute the CSS position to display a tooltip at the specified placement
553		 * relative to the specified element.
554		 * @private
555		 * @param {jQuery} element The element that the tooltip should target.
556		 * @param {string} placement The placement for the tooltip.
557		 * @param {number} tipWidth Width of the tooltip element in pixels.
558		 * @param {number} tipHeight Height of the tooltip element in pixels.
559		 * @param {number} offset Distance to offset tooltips in pixels.
560		 * @return {CSSCoordinates} A CSSCoordinates object with the position.
561		 */
562		function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
563			var placementBase = placement.split('-')[0], // ignore 'alt' for corners
564				coords = new CSSCoordinates(),
565				position;
566
567			if (isSvgElement(element)) {
568				position = getSvgPlacement(element, placementBase);
569			} else {
570				position = getHtmlPlacement(element, placementBase);
571			}
572
573			// calculate the appropriate x and y position in the document
574			switch (placement) {
575				case 'n':
576					coords.set('left', position.left - (tipWidth / 2));
577					coords.set('bottom', session.windowHeight - position.top + offset);
578					break;
579				case 'e':
580					coords.set('left', position.left + offset);
581					coords.set('top', position.top - (tipHeight / 2));
582					break;
583				case 's':
584					coords.set('left', position.left - (tipWidth / 2));
585					coords.set('top', position.top + offset);
586					break;
587				case 'w':
588					coords.set('top', position.top - (tipHeight / 2));
589					coords.set('right', session.windowWidth - position.left + offset);
590					break;
591				case 'nw':
592					coords.set('bottom', session.windowHeight - position.top + offset);
593					coords.set('right', session.windowWidth - position.left - 20);
594					break;
595				case 'nw-alt':
596					coords.set('left', position.left);
597					coords.set('bottom', session.windowHeight - position.top + offset);
598					break;
599				case 'ne':
600					coords.set('left', position.left - 20);
601					coords.set('bottom', session.windowHeight - position.top + offset);
602					break;
603				case 'ne-alt':
604					coords.set('bottom', session.windowHeight - position.top + offset);
605					coords.set('right', session.windowWidth - position.left);
606					break;
607				case 'sw':
608					coords.set('top', position.top + offset);
609					coords.set('right', session.windowWidth - position.left - 20);
610					break;
611				case 'sw-alt':
612					coords.set('left', position.left);
613					coords.set('top', position.top + offset);
614					break;
615				case 'se':
616					coords.set('left', position.left - 20);
617					coords.set('top', position.top + offset);
618					break;
619				case 'se-alt':
620					coords.set('top', position.top + offset);
621					coords.set('right', session.windowWidth - position.left);
622					break;
623			}
624
625			return coords;
626		}
627
628		/**
629		 * Finds the tooltip attachment point in the document for a HTML DOM element
630		 * for the specified placement.
631		 * @private
632		 * @param {jQuery} element The element that the tooltip should target.
633		 * @param {string} placement The placement for the tooltip.
634		 * @return {Object} An object with the top,left position values.
635		 */
636		function getHtmlPlacement(element, placement) {
637			var objectOffset = element.offset(),
638				objectWidth = element.outerWidth(),
639				objectHeight = element.outerHeight(),
640				left,
641				top;
642
643			// calculate the appropriate x and y position in the document
644			switch (placement) {
645				case 'n':
646					left = objectOffset.left + objectWidth / 2;
647					top = objectOffset.top;
648					break;
649				case 'e':
650					left = objectOffset.left + objectWidth;
651					top = objectOffset.top + objectHeight / 2;
652					break;
653				case 's':
654					left = objectOffset.left + objectWidth / 2;
655					top = objectOffset.top + objectHeight;
656					break;
657				case 'w':
658					left = objectOffset.left;
659					top = objectOffset.top + objectHeight / 2;
660					break;
661				case 'nw':
662					left = objectOffset.left;
663					top = objectOffset.top;
664					break;
665				case 'ne':
666					left = objectOffset.left + objectWidth;
667					top = objectOffset.top;
668					break;
669				case 'sw':
670					left = objectOffset.left;
671					top = objectOffset.top + objectHeight;
672					break;
673				case 'se':
674					left = objectOffset.left + objectWidth;
675					top = objectOffset.top + objectHeight;
676					break;
677			}
678
679			return {
680				top: top,
681				left: left
682			};
683		}
684
685		/**
686		 * Finds the tooltip attachment point in the document for a SVG element for
687		 * the specified placement.
688		 * @private
689		 * @param {jQuery} element The element that the tooltip should target.
690		 * @param {string} placement The placement for the tooltip.
691		 * @return {Object} An object with the top,left position values.
692		 */
693		function getSvgPlacement(element, placement) {
694			var svgElement = element.closest('svg')[0],
695				domElement = element[0],
696				point = svgElement.createSVGPoint(),
697				boundingBox = domElement.getBBox(),
698				matrix = domElement.getScreenCTM(),
699				halfWidth = boundingBox.width / 2,
700				halfHeight = boundingBox.height / 2,
701				placements = [],
702				placementKeys = [ 'nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w' ],
703				coords,
704				rotation,
705				steps,
706				x;
707
708			function pushPlacement() {
709				placements.push(point.matrixTransform(matrix));
710			}
711
712			// get bounding box corners and midpoints
713			point.x = boundingBox.x;
714			point.y = boundingBox.y;
715			pushPlacement();
716			point.x += halfWidth;
717			pushPlacement();
718			point.x += halfWidth;
719			pushPlacement();
720			point.y += halfHeight;
721			pushPlacement();
722			point.y += halfHeight;
723			pushPlacement();
724			point.x -= halfWidth;
725			pushPlacement();
726			point.x -= halfWidth;
727			pushPlacement();
728			point.y -= halfHeight;
729			pushPlacement();
730
731			// determine rotation
732			if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
733				rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
734				steps = Math.ceil(((rotation % 360) - 22.5) / 45);
735				if (steps < 1) {
736					steps += 8;
737				}
738				while (steps--) {
739					placementKeys.push(placementKeys.shift());
740				}
741			}
742
743			// find placement
744			for (x = 0; x < placements.length; x++) {
745				if (placementKeys[x] === placement) {
746					coords = placements[x];
747					break;
748				}
749			}
750
751			return {
752				top: coords.y + session.scrollTop,
753				left: coords.x + session.scrollLeft
754			};
755		}
756
757		// expose methods
758		this.compute = computePlacementCoords;
759	}
760
761	/**
762	 * Creates a new tooltip controller.
763	 * @private
764	 * @constructor
765	 * @param {Object} options Options object containing settings.
766	 */
767	function TooltipController(options) {
768		var placementCalculator = new PlacementCalculator(),
769			tipElement = $('#' + options.popupId);
770
771		// build and append tooltip div if it does not already exist
772		if (tipElement.length === 0) {
773			tipElement = $('<div/>', { id: options.popupId });
774			// grab body element if it was not populated when the script loaded
775			// note: this hack exists solely for jsfiddle support
776			if ($body.length === 0) {
777				$body = $('body');
778			}
779			$body.append(tipElement);
780			// remember the tooltip elements that the plugin has created
781			session.tooltips = session.tooltips ? session.tooltips.add(tipElement) : tipElement;
782		}
783
784		// hook mousemove for cursor follow tooltips
785		if (options.followMouse) {
786			// only one positionTipOnCursor hook per tooltip element, please
787			if (!tipElement.data(DATA_HASMOUSEMOVE)) {
788				$document.on('mousemove' + EVENT_NAMESPACE, positionTipOnCursor);
789				$window.on('scroll' + EVENT_NAMESPACE, positionTipOnCursor);
790				tipElement.data(DATA_HASMOUSEMOVE, true);
791			}
792		}
793
794		/**
795		 * Gives the specified element the active-hover state and queues up the
796		 * showTip function.
797		 * @private
798		 * @param {jQuery} element The element that the tooltip should target.
799		 */
800		function beginShowTip(element) {
801			element.data(DATA_HASACTIVEHOVER, true);
802			// show tooltip, asap
803			tipElement.queue(function queueTipInit(next) {
804				showTip(element);
805				next();
806			});
807		}
808
809		/**
810		 * Shows the tooltip, as soon as possible.
811		 * @private
812		 * @param {jQuery} element The element that the tooltip should target.
813		 */
814		function showTip(element) {
815			var tipContent;
816
817			// it is possible, especially with keyboard navigation, to move on to
818			// another element with a tooltip during the queue to get to this point
819			// in the code. if that happens then we need to not proceed or we may
820			// have the fadeout callback for the last tooltip execute immediately
821			// after this code runs, causing bugs.
822			if (!element.data(DATA_HASACTIVEHOVER)) {
823				return;
824			}
825
826			// if the tooltip is open and we got asked to open another one then the
827			// old one is still in its fadeOut cycle, so wait and try again
828			if (session.isTipOpen) {
829				if (!session.isClosing) {
830					hideTip(session.activeHover);
831				}
832				tipElement.delay(100).queue(function queueTipAgain(next) {
833					showTip(element);
834					next();
835				});
836				return;
837			}
838
839			// trigger powerTipPreRender event
840			element.trigger('powerTipPreRender');
841
842			// set tooltip content
843			tipContent = getTooltipContent(element);
844			if (tipContent) {
845				tipElement.empty().append(tipContent);
846			} else {
847				// we have no content to display, give up
848				return;
849			}
850
851			// trigger powerTipRender event
852			element.trigger('powerTipRender');
853
854			session.activeHover = element;
855			session.isTipOpen = true;
856
857			tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
858
859			// set tooltip position
860			if (!options.followMouse) {
861				positionTipOnElement(element);
862				session.isFixedTipOpen = true;
863			} else {
864				positionTipOnCursor();
865			}
866
867			// add custom class to tooltip element
868			tipElement.addClass(options.popupClass);
869
870			// close tooltip when clicking anywhere on the page, with the exception
871			// of the tooltip's trigger element and any elements that are within a
872			// tooltip that has 'mouseOnToPopup' option enabled
873			if (!element.data(DATA_FORCEDOPEN)) {
874				$document.on('click' + EVENT_NAMESPACE, function documentClick(event) {
875					var target = event.target;
876					if (target !== element[0]) {
877						if (options.mouseOnToPopup) {
878							if (target !== tipElement[0] && !$.contains(tipElement[0], target)) {
879								$.powerTip.hide();
880							}
881						} else {
882							$.powerTip.hide();
883						}
884					}
885				});
886			}
887
888			// if we want to be able to mouse on to the tooltip then we need to
889			// attach hover events to the tooltip that will cancel a close request
890			// on mouseenter and start a new close request on mouseleave
891			// only hook these listeners if we're not in manual mode
892			if (options.mouseOnToPopup && !options.manual) {
893				tipElement.on('mouseenter' + EVENT_NAMESPACE, function tipMouseEnter() {
894					// check activeHover in case the mouse cursor entered the
895					// tooltip during the fadeOut and close cycle
896					if (session.activeHover) {
897						session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
898					}
899				});
900				tipElement.on('mouseleave' + EVENT_NAMESPACE, function tipMouseLeave() {
901					// check activeHover in case the mouse cursor left the tooltip
902					// during the fadeOut and close cycle
903					if (session.activeHover) {
904						session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
905					}
906				});
907			}
908
909			// fadein
910			tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
911				// start desync polling
912				if (!session.desyncTimeout) {
913					session.desyncTimeout = setInterval(closeDesyncedTip, 500);
914				}
915
916				// trigger powerTipOpen event
917				element.trigger('powerTipOpen');
918			});
919		}
920
921		/**
922		 * Hides the tooltip.
923		 * @private
924		 * @param {jQuery} element The element that the tooltip should target.
925		 */
926		function hideTip(element) {
927			// reset session
928			session.isClosing = true;
929			session.isTipOpen = false;
930
931			// stop desync polling
932			session.desyncTimeout = clearInterval(session.desyncTimeout);
933
934			// reset element state
935			element.data(DATA_HASACTIVEHOVER, false);
936			element.data(DATA_FORCEDOPEN, false);
937
938			// remove document click handler
939			$document.off('click' + EVENT_NAMESPACE);
940
941			// unbind the mouseOnToPopup events if they were set
942			tipElement.off(EVENT_NAMESPACE);
943
944			// fade out
945			tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
946				var coords = new CSSCoordinates();
947
948				// reset session and tooltip element
949				session.activeHover = null;
950				session.isClosing = false;
951				session.isFixedTipOpen = false;
952				tipElement.removeClass();
953
954				// support mouse-follow and fixed position tips at the same time by
955				// moving the tooltip to the last cursor location after it is hidden
956				coords.set('top', session.currentY + options.offset);
957				coords.set('left', session.currentX + options.offset);
958				tipElement.css(coords);
959
960				// trigger powerTipClose event
961				element.trigger('powerTipClose');
962			});
963		}
964
965		/**
966		 * Moves the tooltip to the users mouse cursor.
967		 * @private
968		 */
969		function positionTipOnCursor() {
970			// to support having fixed tooltips on the same page as cursor tooltips,
971			// where both instances are referencing the same tooltip element, we
972			// need to keep track of the mouse position constantly, but we should
973			// only set the tip location if a fixed tip is not currently open, a tip
974			// open is imminent or active, and the tooltip element in question does
975			// have a mouse-follow using it.
976			if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
977				// grab measurements
978				var tipWidth = tipElement.outerWidth(),
979					tipHeight = tipElement.outerHeight(),
980					coords = new CSSCoordinates(),
981					collisions,
982					collisionCount;
983
984				// grab collisions
985				coords.set('top', session.currentY + options.offset);
986				coords.set('left', session.currentX + options.offset);
987				collisions = getViewportCollisions(
988					coords,
989					tipWidth,
990					tipHeight
991				);
992
993				// handle tooltip view port collisions
994				if (collisions !== Collision.none) {
995					collisionCount = countFlags(collisions);
996					if (collisionCount === 1) {
997						// if there is only one collision (bottom or right) then
998						// simply constrain the tooltip to the view port
999						if (collisions === Collision.right) {
1000							coords.set('left', session.windowWidth - tipWidth);
1001						} else if (collisions === Collision.bottom) {
1002							coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
1003						}
1004					} else {
1005						// if the tooltip has more than one collision then it is
1006						// trapped in the corner and should be flipped to get it out
1007						// of the users way
1008						coords.set('left', session.currentX - tipWidth - options.offset);
1009						coords.set('top', session.currentY - tipHeight - options.offset);
1010					}
1011				}
1012
1013				// position the tooltip
1014				tipElement.css(coords);
1015			}
1016		}
1017
1018		/**
1019		 * Sets the tooltip to the correct position relative to the specified target
1020		 * element. Based on options settings.
1021		 * @private
1022		 * @param {jQuery} element The element that the tooltip should target.
1023		 */
1024		function positionTipOnElement(element) {
1025			var priorityList,
1026				finalPlacement;
1027
1028			if (options.smartPlacement) {
1029				priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
1030
1031				// iterate over the priority list and use the first placement option
1032				// that does not collide with the view port. if they all collide
1033				// then the last placement in the list will be used.
1034				$.each(priorityList, function(idx, pos) {
1035					// place tooltip and find collisions
1036					var collisions = getViewportCollisions(
1037						placeTooltip(element, pos),
1038						tipElement.outerWidth(),
1039						tipElement.outerHeight()
1040					);
1041
1042					// update the final placement variable
1043					finalPlacement = pos;
1044
1045					// break if there were no collisions
1046					if (collisions === Collision.none) {
1047						return false;
1048					}
1049				});
1050			} else {
1051				// if we're not going to use the smart placement feature then just
1052				// compute the coordinates and do it
1053				placeTooltip(element, options.placement);
1054				finalPlacement = options.placement;
1055			}
1056
1057			// add placement as class for CSS arrows
1058			tipElement.removeClass('w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt');
1059			tipElement.addClass(finalPlacement);
1060		}
1061
1062		/**
1063		 * Sets the tooltip position to the appropriate values to show the tip at
1064		 * the specified placement. This function will iterate and test the tooltip
1065		 * to support elastic tooltips.
1066		 * @private
1067		 * @param {jQuery} element The element that the tooltip should target.
1068		 * @param {string} placement The placement for the tooltip.
1069		 * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
1070		 *     right position values.
1071		 */
1072		function placeTooltip(element, placement) {
1073			var iterationCount = 0,
1074				tipWidth,
1075				tipHeight,
1076				coords = new CSSCoordinates();
1077
1078			// set the tip to 0,0 to get the full expanded width
1079			coords.set('top', 0);
1080			coords.set('left', 0);
1081			tipElement.css(coords);
1082
1083			// to support elastic tooltips we need to check for a change in the
1084			// rendered dimensions after the tooltip has been positioned
1085			do {
1086				// grab the current tip dimensions
1087				tipWidth = tipElement.outerWidth();
1088				tipHeight = tipElement.outerHeight();
1089
1090				// get placement coordinates
1091				coords = placementCalculator.compute(
1092					element,
1093					placement,
1094					tipWidth,
1095					tipHeight,
1096					options.offset
1097				);
1098
1099				// place the tooltip
1100				tipElement.css(coords);
1101			} while (
1102				// sanity check: limit to 5 iterations, and...
1103				++iterationCount <= 5 &&
1104				// try again if the dimensions changed after placement
1105				(tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
1106			);
1107
1108			return coords;
1109		}
1110
1111		/**
1112		 * Checks for a tooltip desync and closes the tooltip if one occurs.
1113		 * @private
1114		 */
1115		function closeDesyncedTip() {
1116			var isDesynced = false;
1117			// It is possible for the mouse cursor to leave an element without
1118			// firing the mouseleave or blur event. This most commonly happens when
1119			// the element is disabled under mouse cursor. If this happens it will
1120			// result in a desynced tooltip because the tooltip was never asked to
1121			// close. So we should periodically check for a desync situation and
1122			// close the tip if such a situation arises.
1123			if (session.isTipOpen && !session.isClosing && !session.delayInProgress && ($.inArray('mouseleave', options.closeEvents) > -1 || $.inArray('mouseout', options.closeEvents) > -1 || $.inArray('blur', options.closeEvents) > -1 || $.inArray('focusout', options.closeEvents) > -1)) {
1124				// user moused onto another tip or active hover is disabled
1125				if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
1126					isDesynced = true;
1127				} else {
1128					// hanging tip - have to test if mouse position is not over the
1129					// active hover and not over a tooltip set to let the user
1130					// interact with it.
1131					// for keyboard navigation: this only counts if the element does
1132					// not have focus.
1133					// for tooltips opened via the api: we need to check if it has
1134					// the forcedOpen flag.
1135					if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
1136						if (tipElement.data(DATA_MOUSEONTOTIP)) {
1137							if (!isMouseOver(tipElement)) {
1138								isDesynced = true;
1139							}
1140						} else {
1141							isDesynced = true;
1142						}
1143					}
1144				}
1145
1146				if (isDesynced) {
1147					// close the desynced tip
1148					hideTip(session.activeHover);
1149				}
1150			}
1151		}
1152
1153		// expose methods
1154		this.showTip = beginShowTip;
1155		this.hideTip = hideTip;
1156		this.resetPosition = positionTipOnElement;
1157	}
1158
1159	/**
1160	 * Determine whether a jQuery object is an SVG element
1161	 * @private
1162	 * @param {jQuery} element The element to check
1163	 * @return {boolean} Whether this is an SVG element
1164	 */
1165	function isSvgElement(element) {
1166		return Boolean(window.SVGElement && element[0] instanceof SVGElement);
1167	}
1168
1169	/**
1170	 * Determines if the specified jQuery.Event object has mouse data.
1171	 * @private
1172	 * @param {jQuery.Event=} event The jQuery.Event object to test.
1173	 * @return {boolean} True if there is mouse data, otherwise false.
1174	 */
1175	function isMouseEvent(event) {
1176		return Boolean(event && typeof event.pageX === 'number');
1177	}
1178
1179	/**
1180	 * Initializes the viewport dimension cache and hooks up the mouse position
1181	 * tracking and viewport dimension tracking events.
1182	 * Prevents attaching the events more than once.
1183	 * @private
1184	 */
1185	function initTracking() {
1186		if (!session.mouseTrackingActive) {
1187			session.mouseTrackingActive = true;
1188
1189			// grab the current viewport dimensions on load
1190			getViewportDimensions();
1191			$(getViewportDimensions);
1192
1193			// hook mouse move tracking
1194			$document.on('mousemove' + EVENT_NAMESPACE, trackMouse);
1195
1196			// hook viewport dimensions tracking
1197			$window.on('resize' + EVENT_NAMESPACE, trackResize);
1198			$window.on('scroll' + EVENT_NAMESPACE, trackScroll);
1199		}
1200	}
1201
1202	/**
1203	 * Updates the viewport dimensions cache.
1204	 * @private
1205	 */
1206	function getViewportDimensions() {
1207		session.scrollLeft = $window.scrollLeft();
1208		session.scrollTop = $window.scrollTop();
1209		session.windowWidth = $window.width();
1210		session.windowHeight = $window.height();
1211	}
1212
1213	/**
1214	 * Updates the window size info in the viewport dimensions cache.
1215	 * @private
1216	 */
1217	function trackResize() {
1218		session.windowWidth = $window.width();
1219		session.windowHeight = $window.height();
1220	}
1221
1222	/**
1223	 * Updates the scroll offset info in the viewport dimensions cache.
1224	 * @private
1225	 */
1226	function trackScroll() {
1227		var x = $window.scrollLeft(),
1228			y = $window.scrollTop();
1229		if (x !== session.scrollLeft) {
1230			session.currentX += x - session.scrollLeft;
1231			session.scrollLeft = x;
1232		}
1233		if (y !== session.scrollTop) {
1234			session.currentY += y - session.scrollTop;
1235			session.scrollTop = y;
1236		}
1237	}
1238
1239	/**
1240	 * Saves the current mouse coordinates to the session object.
1241	 * @private
1242	 * @param {jQuery.Event} event The mousemove event for the document.
1243	 */
1244	function trackMouse(event) {
1245		session.currentX = event.pageX;
1246		session.currentY = event.pageY;
1247	}
1248
1249	/**
1250	 * Tests if the mouse is currently over the specified element.
1251	 * @private
1252	 * @param {jQuery} element The element to check for hover.
1253	 * @return {boolean}
1254	 */
1255	function isMouseOver(element) {
1256		// use getBoundingClientRect() because jQuery's width() and height()
1257		// methods do not work with SVG elements
1258		// compute width/height because those properties do not exist on the object
1259		// returned by getBoundingClientRect() in older versions of IE
1260		var elementPosition = element.offset(),
1261			elementBox = element[0].getBoundingClientRect(),
1262			elementWidth = elementBox.right - elementBox.left,
1263			elementHeight = elementBox.bottom - elementBox.top;
1264
1265		return session.currentX >= elementPosition.left &&
1266			session.currentX <= elementPosition.left + elementWidth &&
1267			session.currentY >= elementPosition.top &&
1268			session.currentY <= elementPosition.top + elementHeight;
1269	}
1270
1271	/**
1272	 * Fetches the tooltip content from the specified element's data attributes.
1273	 * @private
1274	 * @param {jQuery} element The element to get the tooltip content for.
1275	 * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
1276	 *     undefined if there was no tooltip content for the element.
1277	 */
1278	function getTooltipContent(element) {
1279		var tipText = element.data(DATA_POWERTIP),
1280			tipObject = element.data(DATA_POWERTIPJQ),
1281			tipTarget = element.data(DATA_POWERTIPTARGET),
1282			targetElement,
1283			content;
1284
1285		if (tipText) {
1286			if ($.isFunction(tipText)) {
1287				tipText = tipText.call(element[0]);
1288			}
1289			content = tipText;
1290		} else if (tipObject) {
1291			if ($.isFunction(tipObject)) {
1292				tipObject = tipObject.call(element[0]);
1293			}
1294			if (tipObject.length > 0) {
1295				content = tipObject.clone(true, true);
1296			}
1297		} else if (tipTarget) {
1298			targetElement = $('#' + tipTarget);
1299			if (targetElement.length > 0) {
1300				content = targetElement.html();
1301			}
1302		}
1303
1304		return content;
1305	}
1306
1307	/**
1308	 * Finds any viewport collisions that an element (the tooltip) would have if it
1309	 * were absolutely positioned at the specified coordinates.
1310	 * @private
1311	 * @param {CSSCoordinates} coords Coordinates for the element.
1312	 * @param {number} elementWidth Width of the element in pixels.
1313	 * @param {number} elementHeight Height of the element in pixels.
1314	 * @return {number} Value with the collision flags.
1315	 */
1316	function getViewportCollisions(coords, elementWidth, elementHeight) {
1317		var viewportTop = session.scrollTop,
1318			viewportLeft =  session.scrollLeft,
1319			viewportBottom = viewportTop + session.windowHeight,
1320			viewportRight = viewportLeft + session.windowWidth,
1321			collisions = Collision.none;
1322
1323		if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
1324			collisions |= Collision.top;
1325		}
1326		if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
1327			collisions |= Collision.bottom;
1328		}
1329		if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
1330			collisions |= Collision.left;
1331		}
1332		if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
1333			collisions |= Collision.right;
1334		}
1335
1336		return collisions;
1337	}
1338
1339	/**
1340	 * Counts the number of bits set on a flags value.
1341	 * @param {number} value The flags value.
1342	 * @return {number} The number of bits that have been set.
1343	 */
1344	function countFlags(value) {
1345		var count = 0;
1346		while (value) {
1347			value &= value - 1;
1348			count++;
1349		}
1350		return count;
1351	}
1352
1353// return api for commonjs and amd environments
1354	return $.powerTip;
1355}));

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.