PageSourceSearch

https://app.schmetterling-argus.de/js/library/jquery.powertip.js

js schmetterling-argus.de collected 2026-09-25 23:59:57 UTC 34,614 bytes, 1,166 lines download raw bytes

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

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.