1/*!
2 * jScrollPane - v2.0.0beta12 - 2012-09-27
3 * http://jscrollpane.kelvinluck.com/
4 *
5 * Copyright (c) 2010 Kelvin Luck
6 * Dual licensed under the MIT or GPL licenses.
7 */
8
9// Script: jScrollPane - cross browser customisable scrollbars
10//
11// *Version: 2.0.0beta12, Last updated: 2012-09-27*
12//
13// Project Home - http://jscrollpane.kelvinluck.com/
14// GitHub       - http://github.com/vitch/jScrollPane
15// Source       - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.js
16// (Minified)   - http://github.com/vitch/jScrollPane/raw/master/script/jquery.jscrollpane.min.js
17//
18// About: License
19//
20// Copyright (c) 2012 Kelvin Luck
21// Dual licensed under the MIT or GPL Version 2 licenses.
22// http://jscrollpane.kelvinluck.com/MIT-LICENSE.txt
23// http://jscrollpane.kelvinluck.com/GPL-LICENSE.txt
24//
25// About: Examples
26//
27// All examples and demos are available through the jScrollPane example site at:
28// http://jscrollpane.kelvinluck.com/
29//
30// About: Support and Testing
31//
32// This plugin is tested on the browsers below and has been found to work reliably on them. If you run
33// into a problem on one of the supported browsers then please visit the support section on the jScrollPane
34// website (http://jscrollpane.kelvinluck.com/) for more information on getting support. You are also
35// welcome to fork the project on GitHub if you can contribute a fix for a given issue.
36//
37// jQuery Versions - tested in 1.4.2+ - reported to work in 1.3.x
38// Browsers Tested - Firefox 3.6.8, Safari 5, Opera 10.6, Chrome 5.0, IE 6, 7, 8
39//
40// About: Release History
41//
42// 2.0.0beta12 - (2012-09-27) fix for jQuery 1.8+
43// 2.0.0beta11 - (2012-05-14)
44// 2.0.0beta10 - (2011-04-17) cleaner required size calculation, improved keyboard support, stickToBottom/Left, other small fixes
45// 2.0.0beta9 - (2011-01-31) new API methods, bug fixes and correct keyboard support for FF/OSX
46// 2.0.0beta8 - (2011-01-29) touchscreen support, improved keyboard support
47// 2.0.0beta7 - (2011-01-23) scroll speed consistent (thanks Aivo Paas)
48// 2.0.0beta6 - (2010-12-07) scrollToElement horizontal support
49// 2.0.0beta5 - (2010-10-18) jQuery 1.4.3 support, various bug fixes
50// 2.0.0beta4 - (2010-09-17) clickOnTrack support, bug fixes
51// 2.0.0beta3 - (2010-08-27) Horizontal mousewheel, mwheelIntent, keyboard support, bug fixes
52// 2.0.0beta2 - (2010-08-21) Bug fixes
53// 2.0.0beta1 - (2010-08-17) Rewrite to follow modern best practices and enable horizontal scrolling, initially hidden
54//							 elements and dynamically sized elements.
55// 1.x - (2006-12-31 - 2010-07-31) Initial version, hosted at googlecode, deprecated
56
57(function($,window,undefined){
58
59	$.fn.jScrollPane = function(settings)
60	{
61		// JScrollPane "class" - public methods are available through $('selector').data('jsp')
62		function JScrollPane(elem, s)
63		{
64			var settings, jsp = this, pane, paneWidth, paneHeight, container, contentWidth, contentHeight,
65				percentInViewH, percentInViewV, isScrollableV, isScrollableH, verticalDrag, dragMaxY,
66				verticalDragPosition, horizontalDrag, dragMaxX, horizontalDragPosition,
67				verticalBar, verticalTrack, scrollbarWidth, verticalTrackHeight, verticalDragHeight, arrowUp, arrowDown,
68				horizontalBar, horizontalTrack, horizontalTrackWidth, horizontalDragWidth, arrowLeft, arrowRight,
69				reinitialiseInterval, originalPadding, originalPaddingTotalWidth, previousContentWidth,
70				wasAtTop = true, wasAtLeft = true, wasAtBottom = false, wasAtRight = false,
71				originalElement = elem.clone(false, false).empty(),
72				mwEvent = $.fn.mwheelIntent ? 'mwheelIntent.jsp' : 'mousewheel.jsp';
73
74			originalPadding = elem.css('paddingTop') + ' ' +
75								elem.css('paddingRight') + ' ' +
76								elem.css('paddingBottom') + ' ' +
77								elem.css('paddingLeft');
78			originalPaddingTotalWidth = (parseInt(elem.css('paddingLeft'), 10) || 0) +
79										(parseInt(elem.css('paddingRight'), 10) || 0);
80
81			function initialise(s)
82			{
83
84				var /*firstChild, lastChild, */isMaintainingPositon, lastContentX, lastContentY,
85						hasContainingSpaceChanged, originalScrollTop, originalScrollLeft,
86						maintainAtBottom = false, maintainAtRight = false;
87
88				settings = s;
89
90				if (pane === undefined) {
91					originalScrollTop = elem.scrollTop();
92					originalScrollLeft = elem.scrollLeft();
93
94					elem.css(
95						{
96							overflow: 'hidden',
97							padding: 0
98						}
99					);
100					// TODO: Deal with where width/ height is 0 as it probably means the element is hidden and we should
101					// come back to it later and check once it is unhidden...
102					paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
103					paneHeight = elem.innerHeight();
104
105					elem.width(paneWidth);
106
107					pane = $('<div class="jspPane" />').css('padding', originalPadding).append(elem.children());
108					container = $('<div class="jspContainer" />')
109						.css({
110							'width': paneWidth + 'px',
111							'height': paneHeight + 'px'
112						}
113					).append(pane).appendTo(elem);
114
115					/*
116					// Move any margins from the first and last children up to the container so they can still
117					// collapse with neighbouring elements as they would before jScrollPane
118					firstChild = pane.find(':first-child');
119					lastChild = pane.find(':last-child');
120					elem.css(
121						{
122							'margin-top': firstChild.css('margin-top'),
123							'margin-bottom': lastChild.css('margin-bottom')
124						}
125					);
126					firstChild.css('margin-top', 0);
127					lastChild.css('margin-bottom', 0);
128					*/
129				} else {
130					elem.css('width', '');
131
132					maintainAtBottom = settings.stickToBottom && isCloseToBottom();
133					maintainAtRight  = settings.stickToRight  && isCloseToRight();
134
135					hasContainingSpaceChanged = elem.innerWidth() + originalPaddingTotalWidth != paneWidth || elem.outerHeight() != paneHeight;
136
137					if (hasContainingSpaceChanged) {
138						paneWidth = elem.innerWidth() + originalPaddingTotalWidth;
139						paneHeight = elem.innerHeight();
140						container.css({
141							width: paneWidth + 'px',
142							height: paneHeight + 'px'
143						});
144					}
145
146					// If nothing changed since last check...
147					if (!hasContainingSpaceChanged && previousContentWidth == contentWidth && pane.outerHeight() == contentHeight) {
148						elem.width(paneWidth);
149						return;
150					}
151					previousContentWidth = contentWidth;
152
153					pane.css('width', '');
154					elem.width(paneWidth);
155
156					container.find('>.jspVerticalBar,>.jspHorizontalBar').remove().end();
157				}
158
159				pane.css('overflow', 'auto');
160				if (s.contentWidth) {
161					contentWidth = s.contentWidth;
162				} else {
163					contentWidth = pane[0].scrollWidth;
164				}
165				contentHeight = pane[0].scrollHeight;
166				pane.css('overflow', '');
167
168				percentInViewH = contentWidth / paneWidth;
169				percentInViewV = contentHeight / paneHeight;
170				isScrollableV = percentInViewV > 1;
171
172				isScrollableH = percentInViewH > 1;
173
174				//console.log(paneWidth, paneHeight, contentWidth, contentHeight, percentInViewH, percentInViewV, isScrollableH, isScrollableV);
175
176				if (!(isScrollableH || isScrollableV)) {
177					elem.removeClass('jspScrollable');
178					pane.css({
179						top: 0,
180						width: container.width() - originalPaddingTotalWidth
181					});
182					removeMousewheel();
183					removeFocusHandler();
184					removeKeyboardNav();
185					removeClickOnTrack();
186				} else {
187					elem.addClass('jspScrollable');
188
189					isMaintainingPositon = settings.maintainPosition && (verticalDragPosition || horizontalDragPosition);
190					if (isMaintainingPositon) {
191						lastContentX = contentPositionX();
192						lastContentY = contentPositionY();
193					}
194
195					initialiseVerticalScroll();
196					initialiseHorizontalScroll();
197					resizeScrollbars();
198
199					if (isMaintainingPositon) {
200						scrollToX(maintainAtRight  ? (contentWidth  - paneWidth ) : lastContentX, false);
201						scrollToY(maintainAtBottom ? (contentHeight - paneHeight) : lastContentY, false);
202					}
203
204					initFocusHandler();
205					initMousewheel();
206					initTouch();
207
208					if (settings.enableKeyboardNavigation) {
209						initKeyboardNav();
210					}
211					if (settings.clickOnTrack) {
212						initClickOnTrack();
213					}
214
215					observeHash();
216					if (settings.hijackInternalLinks) {
217						hijackInternalLinks();
218					}
219				}
220
221				if (settings.autoReinitialise && !reinitialiseInterval) {
222					reinitialiseInterval = setInterval(
223						function()
224						{
225							initialise(settings);
226						},
227						settings.autoReinitialiseDelay
228					);
229				} else if (!settings.autoReinitialise && reinitialiseInterval) {
230					clearInterval(reinitialiseInterval);
231				}
232
233				originalScrollTop && elem.scrollTop(0) && scrollToY(originalScrollTop, false);
234				originalScrollLeft && elem.scrollLeft(0) && scrollToX(originalScrollLeft, false);
235
236				elem.trigger('jsp-initialised', [isScrollableH || isScrollableV]);
237			}
238
239			function initialiseVerticalScroll()
240			{
241				if (isScrollableV) {
242
243					container.append(
244						$('<div class="jspVerticalBar" />').append(
245							$('<div class="jspCap jspCapTop" />'),
246							$('<div class="jspTrack" />').append(
247								$('<div class="jspDrag" />').append(
248									$('<div class="jspDragTop" />'),
249									$('<div class="jspDragBottom" />')
250								)
251							),
252							$('<div class="jspCap jspCapBottom" />')
253						)
254					);
255
256					verticalBar = container.find('>.jspVerticalBar');
257					verticalTrack = verticalBar.find('>.jspTrack');
258					verticalDrag = verticalTrack.find('>.jspDrag');
259
260					if (settings.showArrows) {
261						arrowUp = $('<a class="jspArrow jspArrowUp" />').bind(
262							'mousedown.jsp', getArrowScroll(0, -1)
263						).bind('click.jsp', nil);
264						arrowDown = $('<a class="jspArrow jspArrowDown" />').bind(
265							'mousedown.jsp', getArrowScroll(0, 1)
266						).bind('click.jsp', nil);
267						if (settings.arrowScrollOnHover) {
268							arrowUp.bind('mouseover.jsp', getArrowScroll(0, -1, arrowUp));
269							arrowDown.bind('mouseover.jsp', getArrowScroll(0, 1, arrowDown));
270						}
271
272						appendArrows(verticalTrack, settings.verticalArrowPositions, arrowUp, arrowDown);
273					}
274
275					verticalTrackHeight = paneHeight;
276					container.find('>.jspVerticalBar>.jspCap:visible,>.jspVerticalBar>.jspArrow').each(
277						function()
278						{
279							verticalTrackHeight -= $(this).outerHeight();
280						}
281					);
282
283
284					verticalDrag.hover(
285						function()
286						{
287							verticalDrag.addClass('jspHover');
288						},
289						function()
290						{
291							verticalDrag.removeClass('jspHover');
292						}
293					).bind(
294						'mousedown.jsp',
295						function(e)
296						{
297							// Stop IE from allowing text selection
298							$('html').bind('dragstart.jsp selectstart.jsp', nil);
299
300							verticalDrag.addClass('jspActive');
301
302							var startY = e.pageY - verticalDrag.position().top;
303
304							$('html').bind(
305								'mousemove.jsp',
306								function(e)
307								{
308									positionDragY(e.pageY - startY, false);
309								}
310							).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
311							return false;
312						}
313					);
314					sizeVerticalScrollbar();
315				}
316			}
317
318			function sizeVerticalScrollbar()
319			{
320				verticalTrack.height(verticalTrackHeight + 'px');
321				verticalDragPosition = 0;
322				scrollbarWidth = settings.verticalGutter + verticalTrack.outerWidth();
323
324				// Make the pane thinner to allow for the vertical scrollbar
325				pane.width(paneWidth - scrollbarWidth - originalPaddingTotalWidth);
326
327				// Add margin to the left of the pane if scrollbars are on that side (to position
328				// the scrollbar on the left or right set it's left or right property in CSS)
329				try {
330					if (verticalBar.position().left === 0) {
331						pane.css('margin-left', scrollbarWidth + 'px');
332					}
333				} catch (err) {
334				}
335			}
336
337			function initialiseHorizontalScroll()
338			{
339				if (isScrollableH) {
340
341					container.append(
342						$('<div class="jspHorizontalBar" />').append(
343							$('<div class="jspCap jspCapLeft" />'),
344							$('<div class="jspTrack" />').append(
345								$('<div class="jspDrag" />').append(
346									$('<div class="jspDragLeft" />'),
347									$('<div class="jspDragRight" />')
348								)
349							),
350							$('<div class="jspCap jspCapRight" />')
351						)
352					);
353
354					horizontalBar = container.find('>.jspHorizontalBar');
355					horizontalTrack = horizontalBar.find('>.jspTrack');
356					horizontalDrag = horizontalTrack.find('>.jspDrag');
357
358					if (settings.showArrows) {
359						arrowLeft = $('<a class="jspArrow jspArrowLeft" />').bind(
360							'mousedown.jsp', getArrowScroll(-1, 0)
361						).bind('click.jsp', nil);
362						arrowRight = $('<a class="jspArrow jspArrowRight" />').bind(
363							'mousedown.jsp', getArrowScroll(1, 0)
364						).bind('click.jsp', nil);
365						if (settings.arrowScrollOnHover) {
366							arrowLeft.bind('mouseover.jsp', getArrowScroll(-1, 0, arrowLeft));
367							arrowRight.bind('mouseover.jsp', getArrowScroll(1, 0, arrowRight));
368						}
369						appendArrows(horizontalTrack, settings.horizontalArrowPositions, arrowLeft, arrowRight);
370					}
371
372					horizontalDrag.hover(
373						function()
374						{
375							horizontalDrag.addClass('jspHover');
376						},
377						function()
378						{
379							horizontalDrag.removeClass('jspHover');
380						}
381					).bind(
382						'mousedown.jsp',
383						function(e)
384						{
385							// Stop IE from allowing text selection
386							$('html').bind('dragstart.jsp selectstart.jsp', nil);
387
388							horizontalDrag.addClass('jspActive');
389
390							var startX = e.pageX - horizontalDrag.position().left;
391
392							$('html').bind(
393								'mousemove.jsp',
394								function(e)
395								{
396									positionDragX(e.pageX - startX, false);
397								}
398							).bind('mouseup.jsp mouseleave.jsp', cancelDrag);
399							return false;
400						}
401					);
402					horizontalTrackWidth = container.innerWidth();
403					sizeHorizontalScrollbar();
404				}
405			}
406
407			function sizeHorizontalScrollbar()
408			{
409				container.find('>.jspHorizontalBar>.jspCap:visible,>.jspHorizontalBar>.jspArrow').each(
410					function()
411					{
412						horizontalTrackWidth -= $(this).outerWidth();
413					}
414				);
415
416				horizontalTrack.width(horizontalTrackWidth + 'px');
417				horizontalDragPosition = 0;
418			}
419
420			function resizeScrollbars()
421			{
422				if (isScrollableH && isScrollableV) {
423					var horizontalTrackHeight = horizontalTrack.outerHeight(),
424						verticalTrackWidth = verticalTrack.outerWidth();
425					verticalTrackHeight -= horizontalTrackHeight;
426					$(horizontalBar).find('>.jspCap:visible,>.jspArrow').each(
427						function()
428						{
429							horizontalTrackWidth += $(this).outerWidth();
430						}
431					);
432					horizontalTrackWidth -= verticalTrackWidth;
433					paneHeight -= verticalTrackWidth;
434					paneWidth -= horizontalTrackHeight;
435					horizontalTrack.parent().append(
436						$('<div class="jspCorner" />').css('width', horizontalTrackHeight + 'px')
437					);
438					sizeVerticalScrollbar();
439					sizeHorizontalScrollbar();
440				}
441				// reflow content
442				if (isScrollableH) {
443					pane.width((container.outerWidth() - originalPaddingTotalWidth) + 'px');
444				}
445				contentHeight = pane.outerHeight();
446				percentInViewV = contentHeight / paneHeight;
447
448				if (isScrollableH) {
449					horizontalDragWidth = Math.ceil(1 / percentInViewH * horizontalTrackWidth);
450					if (horizontalDragWidth > settings.horizontalDragMaxWidth) {
451						horizontalDragWidth = settings.horizontalDragMaxWidth;
452					} else if (horizontalDragWidth < settings.horizontalDragMinWidth) {
453						horizontalDragWidth = settings.horizontalDragMinWidth;
454					}
455					horizontalDrag.width(horizontalDragWidth + 'px');
456					dragMaxX = horizontalTrackWidth - horizontalDragWidth;
457					_positionDragX(horizontalDragPosition); // To update the state for the arrow buttons
458				}
459				if (isScrollableV) {
460					verticalDragHeight = Math.ceil(1 / percentInViewV * verticalTrackHeight);
461					if (verticalDragHeight > settings.verticalDragMaxHeight) {
462						verticalDragHeight = settings.verticalDragMaxHeight;
463					} else if (verticalDragHeight < settings.verticalDragMinHeight) {
464						verticalDragHeight = settings.verticalDragMinHeight;
465					}
466					verticalDrag.height(verticalDragHeight + 'px');
467					dragMaxY = verticalTrackHeight - verticalDragHeight;
468					_positionDragY(verticalDragPosition); // To update the state for the arrow buttons
469				}
470			}
471
472			function appendArrows(ele, p, a1, a2)
473			{
474				var p1 = "before", p2 = "after", aTemp;
475
476				// Sniff for mac... Is there a better way to determine whether the arrows would naturally appear
477				// at the top or the bottom of the bar?
478				if (p == "os") {
479					p = /Mac/.test(navigator.platform) ? "after" : "split";
480				}
481				if (p == p1) {
482					p2 = p;
483				} else if (p == p2) {
484					p1 = p;
485					aTemp = a1;
486					a1 = a2;
487					a2 = aTemp;
488				}
489
490				ele[p1](a1)[p2](a2);
491			}
492
493			function getArrowScroll(dirX, dirY, ele)
494			{
495				return function()
496				{
497					arrowScroll(dirX, dirY, this, ele);
498					this.blur();
499					return false;
500				};
501			}
502
503			function arrowScroll(dirX, dirY, arrow, ele)
504			{
505				arrow = $(arrow).addClass('jspActive');
506
507				var eve,
508					scrollTimeout,
509					isFirst = true,
510					doScroll = function()
511					{
512						if (dirX !== 0) {
513							jsp.scrollByX(dirX * settings.arrowButtonSpeed);
514						}
515						if (dirY !== 0) {
516							jsp.scrollByY(dirY * settings.arrowButtonSpeed);
517						}
518						scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.arrowRepeatFreq);
519						isFirst = false;
520					};
521
522				doScroll();
523
524				eve = ele ? 'mouseout.jsp' : 'mouseup.jsp';
525				ele = ele || $('html');
526				ele.bind(
527					eve,
528					function()
529					{
530						arrow.removeClass('jspActive');
531						scrollTimeout && clearTimeout(scrollTimeout);
532						scrollTimeout = null;
533						ele.unbind(eve);
534					}
535				);
536			}
537
538			function initClickOnTrack()
539			{
540				removeClickOnTrack();
541				if (isScrollableV) {
542					verticalTrack.bind(
543						'mousedown.jsp',
544						function(e)
545						{
546							if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
547								var clickedTrack = $(this),
548									offset = clickedTrack.offset(),
549									direction = e.pageY - offset.top - verticalDragPosition,
550									scrollTimeout,
551									isFirst = true,
552									doScroll = function()
553									{
554										var offset = clickedTrack.offset(),
555											pos = e.pageY - offset.top - verticalDragHeight / 2,
556											contentDragY = paneHeight * settings.scrollPagePercent,
557											dragY = dragMaxY * contentDragY / (contentHeight - paneHeight);
558										if (direction < 0) {
559											if (verticalDragPosition - dragY > pos) {
560												jsp.scrollByY(-contentDragY);
561											} else {
562												positionDragY(pos);
563											}
564										} else if (direction > 0) {
565											if (verticalDragPosition + dragY < pos) {
566												jsp.scrollByY(contentDragY);
567											} else {
568												positionDragY(pos);
569											}
570										} else {
571											cancelClick();
572											return;
573										}
574										scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
575										isFirst = false;
576									},
577									cancelClick = function()
578									{
579										scrollTimeout && clearTimeout(scrollTimeout);
580										scrollTimeout = null;
581										$(document).unbind('mouseup.jsp', cancelClick);
582									};
583								doScroll();
584								$(document).bind('mouseup.jsp', cancelClick);
585								return false;
586							}
587						}
588					);
589				}
590
591				if (isScrollableH) {
592					horizontalTrack.bind(
593						'mousedown.jsp',
594						function(e)
595						{
596							if (e.originalTarget === undefined || e.originalTarget == e.currentTarget) {
597								var clickedTrack = $(this),
598									offset = clickedTrack.offset(),
599									direction = e.pageX - offset.left - horizontalDragPosition,
600									scrollTimeout,
601									isFirst = true,
602									doScroll = function()
603									{
604										var offset = clickedTrack.offset(),
605											pos = e.pageX - offset.left - horizontalDragWidth / 2,
606											contentDragX = paneWidth * settings.scrollPagePercent,
607											dragX = dragMaxX * contentDragX / (contentWidth - paneWidth);
608										if (direction < 0) {
609											if (horizontalDragPosition - dragX > pos) {
610												jsp.scrollByX(-contentDragX);
611											} else {
612												positionDragX(pos);
613											}
614										} else if (direction > 0) {
615											if (horizontalDragPosition + dragX < pos) {
616												jsp.scrollByX(contentDragX);
617											} else {
618												positionDragX(pos);
619											}
620										} else {
621											cancelClick();
622											return;
623										}
624										scrollTimeout = setTimeout(doScroll, isFirst ? settings.initialDelay : settings.trackClickRepeatFreq);
625										isFirst = false;
626									},
627									cancelClick = function()
628									{
629										scrollTimeout && clearTimeout(scrollTimeout);
630										scrollTimeout = null;
631										$(document).unbind('mouseup.jsp', cancelClick);
632									};
633								doScroll();
634								$(document).bind('mouseup.jsp', cancelClick);
635								return false;
636							}
637						}
638					);
639				}
640			}
641
642			function removeClickOnTrack()
643			{
644				if (horizontalTrack) {
645					horizontalTrack.unbind('mousedown.jsp');
646				}
647				if (verticalTrack) {
648					verticalTrack.unbind('mousedown.jsp');
649				}
650			}
651
652			function cancelDrag()
653			{
654				$('html').unbind('dragstart.jsp selectstart.jsp mousemove.jsp mouseup.jsp mouseleave.jsp');
655
656				if (verticalDrag) {
657					verticalDrag.removeClass('jspActive');
658				}
659				if (horizontalDrag) {
660					horizontalDrag.removeClass('jspActive');
661				}
662			}
663
664			function positionDragY(destY, animate)
665			{
666				if (!isScrollableV) {
667					return;
668				}
669				if (destY < 0) {
670					destY = 0;
671				} else if (destY > dragMaxY) {
672					destY = dragMaxY;
673				}
674
675				// can't just check if(animate) because false is a valid value that could be passed in...
676				if (animate === undefined) {
677					animate = settings.animateScroll;
678				}
679				if (animate) {
680					jsp.animate(verticalDrag, 'top', destY,	_positionDragY);
681				} else {
682					verticalDrag.css('top', destY);
683					_positionDragY(destY);
684				}
685
686			}
687
688			function _positionDragY(destY)
689			{
690				if (destY === undefined) {
691					destY = verticalDrag.position().top;
692				}
693
694				container.scrollTop(0);
695				verticalDragPosition = destY;
696
697				var isAtTop = verticalDragPosition === 0,
698					isAtBottom = verticalDragPosition == dragMaxY,
699					percentScrolled = destY/ dragMaxY,
700					destTop = -percentScrolled * (contentHeight - paneHeight);
701
702				if (wasAtTop != isAtTop || wasAtBottom != isAtBottom) {
703					wasAtTop = isAtTop;
704					wasAtBottom = isAtBottom;
705					elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
706				}
707
708				updateVerticalArrows(isAtTop, isAtBottom);
709				pane.css('top', destTop);
710				elem.trigger('jsp-scroll-y', [-destTop, isAtTop, isAtBottom]).trigger('scroll');
711			}
712
713			function positionDragX(destX, animate)
714			{
715				if (!isScrollableH) {
716					return;
717				}
718				if (destX < 0) {
719					destX = 0;
720				} else if (destX > dragMaxX) {
721					destX = dragMaxX;
722				}
723
724				if (animate === undefined) {
725					animate = settings.animateScroll;
726				}
727				if (animate) {
728					jsp.animate(horizontalDrag, 'left', destX,	_positionDragX);
729				} else {
730					horizontalDrag.css('left', destX);
731					_positionDragX(destX);
732				}
733			}
734
735			function _positionDragX(destX)
736			{
737				if (destX === undefined) {
738					destX = horizontalDrag.position().left;
739				}
740
741				container.scrollTop(0);
742				horizontalDragPosition = destX;
743
744				var isAtLeft = horizontalDragPosition === 0,
745					isAtRight = horizontalDragPosition == dragMaxX,
746					percentScrolled = destX / dragMaxX,
747					destLeft = -percentScrolled * (contentWidth - paneWidth);
748
749				if (wasAtLeft != isAtLeft || wasAtRight != isAtRight) {
750					wasAtLeft = isAtLeft;
751					wasAtRight = isAtRight;
752					elem.trigger('jsp-arrow-change', [wasAtTop, wasAtBottom, wasAtLeft, wasAtRight]);
753				}
754
755				updateHorizontalArrows(isAtLeft, isAtRight);
756				pane.css('left', destLeft);
757				elem.trigger('jsp-scroll-x', [-destLeft, isAtLeft, isAtRight]).trigger('scroll');
758			}
759
760			function updateVerticalArrows(isAtTop, isAtBottom)
761			{
762				if (settings.showArrows) {
763					arrowUp[isAtTop ? 'addClass' : 'removeClass']('jspDisabled');
764					arrowDown[isAtBottom ? 'addClass' : 'removeClass']('jspDisabled');
765				}
766			}
767
768			function updateHorizontalArrows(isAtLeft, isAtRight)
769			{
770				if (settings.showArrows) {
771					arrowLeft[isAtLeft ? 'addClass' : 'removeClass']('jspDisabled');
772					arrowRight[isAtRight ? 'addClass' : 'removeClass']('jspDisabled');
773				}
774			}
775
776			function scrollToY(destY, animate)
777			{
778				var percentScrolled = destY / (contentHeight - paneHeight);
779				positionDragY(percentScrolled * dragMaxY, animate);
780			}
781
782			function scrollToX(destX, animate)
783			{
784				var percentScrolled = destX / (contentWidth - paneWidth);
785				positionDragX(percentScrolled * dragMaxX, animate);
786			}
787
788			function scrollToElement(ele, stickToTop, animate)
789			{
790				var e, eleHeight, eleWidth, eleTop = 0, eleLeft = 0, viewportTop, viewportLeft, maxVisibleEleTop, maxVisibleEleLeft, destY, destX;
791
792				// Legal hash values aren't necessarily legal jQuery selectors so we need to catch any
793				// errors from the lookup...
794				try {
795					e = $(ele);
796				} catch (err) {
797					return;
798				}
799				eleHeight = e.outerHeight();
800				eleWidth= e.outerWidth();
801
802				container.scrollTop(0);
803				container.scrollLeft(0);
804
805				// loop through parents adding the offset top of any elements that are relatively positioned between
806				// the focused element and the jspPane so we can get the true distance from the top
807				// of the focused element to the top of the scrollpane...
808				while (!e.is('.jspPane')) {
809					eleTop += e.position().top;
810					eleLeft += e.position().left;
811					e = e.offsetParent();
812					if (/^body|html$/i.test(e[0].nodeName)) {
813						// we ended up too high in the document structure. Quit!
814						return;
815					}
816				}
817
818				viewportTop = contentPositionY();
819				maxVisibleEleTop = viewportTop + paneHeight;
820				if (eleTop < viewportTop || stickToTop) { // element is above viewport
821					destY = eleTop - settings.verticalGutter;
822				} else if (eleTop + eleHeight > maxVisibleEleTop) { // element is below viewport
823					destY = eleTop - paneHeight + eleHeight + settings.verticalGutter;
824				}
825				if (destY) {
826					scrollToY(destY, animate);
827				}
828
829				viewportLeft = contentPositionX();
830	            maxVisibleEleLeft = viewportLeft + paneWidth;
831	            if (eleLeft < viewportLeft || stickToTop) { // element is to the left of viewport
832	                destX = eleLeft - settings.horizontalGutter;
833	            } else if (eleLeft + eleWidth > maxVisibleEleLeft) { // element is to the right viewport
834	                destX = eleLeft - paneWidth + eleWidth + settings.horizontalGutter;
835	            }
836	            if (destX) {
837	                scrollToX(destX, animate);
838	            }
839
840			}
841
842			function contentPositionX()
843			{
844				return -pane.position().left;
845			}
846
847			function contentPositionY()
848			{
849				return -pane.position().top;
850			}
851
852			function isCloseToBottom()
853			{
854				var scrollableHeight = contentHeight - paneHeight;
855				return (scrollableHeight > 20) && (scrollableHeight - contentPositionY() < 10);
856			}
857
858			function isCloseToRight()
859			{
860				var scrollableWidth = contentWidth - paneWidth;
861				return (scrollableWidth > 20) && (scrollableWidth - contentPositionX() < 10);
862			}
863
864			function initMousewheel()
865			{
866				container.unbind(mwEvent).bind(
867					mwEvent,
868					function (event, delta, deltaX, deltaY) {
869						var dX = horizontalDragPosition, dY = verticalDragPosition;
870						jsp.scrollBy(deltaX * settings.mouseWheelSpeed, -deltaY * settings.mouseWheelSpeed, false);
871						// return true if there was no movement so rest of screen can scroll
872						return dX == horizontalDragPosition && dY == verticalDragPosition;
873					}
874				);
875			}
876
877			function removeMousewheel()
878			{
879				container.unbind(mwEvent);
880			}
881
882			function nil()
883			{
884				return false;
885			}
886
887			function initFocusHandler()
888			{
889				pane.find(':input,a').unbind('focus.jsp').bind(
890					'focus.jsp',
891					function(e)
892					{
893						scrollToElement(e.target, false);
894					}
895				);
896			}
897
898			function removeFocusHandler()
899			{
900				pane.find(':input,a').unbind('focus.jsp');
901			}
902
903			function initKeyboardNav()
904			{
905				var keyDown, elementHasScrolled, validParents = [];
906				isScrollableH && validParents.push(horizontalBar[0]);
907				isScrollableV && validParents.push(verticalBar[0]);
908
909				// IE also focuses elements that don't have tabindex set.
910				pane.focus(
911					function()
912					{
913						elem.focus();
914					}
915				);
916
917				elem.attr('tabindex', 0)
918					.unbind('keydown.jsp keypress.jsp')
919					.bind(
920						'keydown.jsp',
921						function(e)
922						{
923							if (e.target !== this && !(validParents.length && $(e.target).closest(validParents).length)){
924								return;
925							}
926							var dX = horizontalDragPosition, dY = verticalDragPosition;
927							switch(e.keyCode) {
928								case 40: // down
929								case 38: // up
930								case 34: // page down
931								case 32: // space
932								case 33: // page up
933								case 39: // right
934								case 37: // left
935									keyDown = e.keyCode;
936									keyDownHandler();
937									break;
938								case 35: // end
939									scrollToY(contentHeight - paneHeight);
940									keyDown = null;
941									break;
942								case 36: // home
943									scrollToY(0);
944									keyDown = null;
945									break;
946							}
947
948							elementHasScrolled = e.keyCode == keyDown && dX != horizontalDragPosition || dY != verticalDragPosition;
949							return !elementHasScrolled;
950						}
951					).bind(
952						'keypress.jsp', // For FF/ OSX so that we can cancel the repeat key presses if the JSP scrolls...
953						function(e)
954						{
955							if (e.keyCode == keyDown) {
956								keyDownHandler();
957							}
958							return !elementHasScrolled;
959						}
960					);
961
962				if (settings.hideFocus) {
963					elem.css('outline', 'none');
964					if ('hideFocus' in container[0]){
965						elem.attr('hideFocus', true);
966					}
967				} else {
968					elem.css('outline', '');
969					if ('hideFocus' in container[0]){
970						elem.attr('hideFocus', false);
971					}
972				}
973
974				function keyDownHandler()
975				{
976					var dX = horizontalDragPosition, dY = verticalDragPosition;
977					switch(keyDown) {
978						case 40: // down
979							jsp.scrollByY(settings.keyboardSpeed, false);
980							break;
981						case 38: // up
982							jsp.scrollByY(-settings.keyboardSpeed, false);
983							break;
984						case 34: // page down
985						case 32: // space
986							jsp.scrollByY(paneHeight * settings.scrollPagePercent, false);
987							break;
988						case 33: // page up
989							jsp.scrollByY(-paneHeight * settings.scrollPagePercent, false);
990							break;
991						case 39: // right
992							jsp.scrollByX(settings.keyboardSpeed, false);
993							break;
994						case 37: // left
995							jsp.scrollByX(-settings.keyboardSpeed, false);
996							break;
997					}
998
999					elementHasScrolled = dX != horizontalDragPosition || dY != verticalDragPosition;
1000					return elementHasScrolled;
1001				}
1002			}
1003
1004			function removeKeyboardNav()
1005			{
1006				elem.attr('tabindex', '-1')
1007					.removeAttr('tabindex')
1008					.unbind('keydown.jsp keypress.jsp');
1009			}
1010
1011			function observeHash()
1012			{
1013				if (location.hash && location.hash.length > 1) {
1014					var e,
1015						retryInt,
1016						hash = escape(location.hash.substr(1)) // hash must be escaped to prevent XSS
1017						;
1018					try {
1019						e = $('#' + hash + ', a[name="' + hash + '"]');
1020					} catch (err) {
1021						return;
1022					}
1023
1024					if (e.length && pane.find(hash)) {
1025						// nasty workaround but it appears to take a little while before the hash has done its thing
1026						// to the rendered page so we just wait until the container's scrollTop has been messed up.
1027						if (container.scrollTop() === 0) {
1028							retryInt = setInterval(
1029								function()
1030								{
1031									if (container.scrollTop() > 0) {
1032										scrollToElement(e, true);
1033										$(document).scrollTop(container.position().top);
1034										clearInterval(retryInt);
1035									}
1036								},
1037								50
1038							);
1039						} else {
1040							scrollToElement(e, true);
1041							$(document).scrollTop(container.position().top);
1042						}
1043					}
1044				}
1045			}
1046
1047			function hijackInternalLinks()
1048			{
1049				// only register the link handler once
1050				if ($(document.body).data('jspHijack')) {
1051					return;
1052				}
1053
1054				// remember that the handler was bound
1055				$(document.body).data('jspHijack', true);
1056
1057				// use live handler to also capture newly created links
1058				$(document.body).delegate('a[href*=#]', 'click', function(event) {
1059					// does the link point to the same page?
1060					// this also takes care of cases with a <base>-Tag or Links not starting with the hash #
1061					// e.g. <a href="index.html#test"> when the current url already is index.html
1062					var href = this.href.substr(0, this.href.indexOf('#')),
1063						locationHref = location.href,
1064						hash,
1065						element,
1066						container,
1067						jsp,
1068						scrollTop,
1069						elementTop;
1070					if (location.href.indexOf('#') !== -1) {
1071						locationHref = location.href.substr(0, location.href.indexOf('#'));
1072					}
1073					if (href !== locationHref) {
1074						// the link points to another page
1075						return;
1076					}
1077
1078					// check if jScrollPane should handle this click event
1079					hash = escape(this.href.substr(this.href.indexOf('#') + 1));
1080
1081					// find the element on the page
1082					element;
1083					try {
1084						element = $('#' + hash + ', a[name="' + hash + '"]');
1085					} catch (e) {
1086						// hash is not a valid jQuery identifier
1087						return;
1088					}
1089
1090					if (!element.length) {
1091						// this link does not point to an element on this page
1092						return;
1093					}
1094
1095					container = element.closest('.jspScrollable');
1096					jsp = container.data('jsp');
1097
1098					// jsp might be another jsp instance than the one, that bound this event
1099					// remember: this event is only bound once for all instances.
1100					jsp.scrollToElement(element, true);
1101
1102					if (container[0].scrollIntoView) {
1103						// also scroll to the top of the container (if it is not visible)
1104						scrollTop = $(window).scrollTop();
1105						elementTop = element.offset().top;
1106						if (elementTop < scrollTop || elementTop > scrollTop + $(window).height()) {
1107							container[0].scrollIntoView();
1108						}
1109					}
1110
1111					// jsp handled this event, prevent the browser default (scrolling :P)
1112					event.preventDefault();
1113				});
1114			}
1115
1116			// Init touch on iPad, iPhone, iPod, Android
1117			function initTouch()
1118			{
1119				var startX,
1120					startY,
1121					touchStartX,
1122					touchStartY,
1123					moved,
1124					moving = false;
1125
1126				container.unbind('touchstart.jsp touchmove.jsp touchend.jsp click.jsp-touchclick').bind(
1127					'touchstart.jsp',
1128					function(e)
1129					{
1130						var touch = e.originalEvent.touches[0];
1131						startX = contentPositionX();
1132						startY = contentPositionY();
1133						touchStartX = touch.pageX;
1134						touchStartY = touch.pageY;
1135						moved = false;
1136						moving = true;
1137					}
1138				).bind(
1139					'touchmove.jsp',
1140					function(ev)
1141					{
1142						if(!moving) {
1143							return;
1144						}
1145
1146						var touchPos = ev.originalEvent.touches[0],
1147							dX = horizontalDragPosition, dY = verticalDragPosition;
1148
1149						jsp.scrollTo(startX + touchStartX - touchPos.pageX, startY + touchStartY - touchPos.pageY);
1150
1151						moved = moved || Math.abs(touchStartX - touchPos.pageX) > 5 || Math.abs(touchStartY - touchPos.pageY) > 5;
1152
1153						// return true if there was no movement so rest of screen can scroll
1154						return dX == horizontalDragPosition && dY == verticalDragPosition;
1155					}
1156				).bind(
1157					'touchend.jsp',
1158					function(e)
1159					{
1160						moving = false;
1161						/*if(moved) {
1162							return false;
1163						}*/
1164					}
1165				).bind(
1166					'click.jsp-touchclick',
1167					function(e)
1168					{
1169						if(moved) {
1170							moved = false;
1171							return false;
1172						}
1173					}
1174				);
1175			}
1176
1177			function destroy(){
1178				var currentY = contentPositionY(),
1179					currentX = contentPositionX();
1180				elem.removeClass('jspScrollable').unbind('.jsp');
1181				elem.replaceWith(originalElement.append(pane.children()));
1182				originalElement.scrollTop(currentY);
1183				originalElement.scrollLeft(currentX);
1184
1185				// clear reinitialize timer if active
1186				if (reinitialiseInterval) {
1187					clearInterval(reinitialiseInterval);
1188				}
1189			}
1190
1191			// Public API
1192			$.extend(
1193				jsp,
1194				{
1195					// Reinitialises the scroll pane (if it's internal dimensions have changed since the last time it
1196					// was initialised). The settings object which is passed in will override any settings from the
1197					// previous time it was initialised - if you don't pass any settings then the ones from the previous
1198					// initialisation will be used.
1199					reinitialise: function(s)
1200					{
1201						s = $.extend({}, settings, s);
1202						initialise(s);
1203					},
1204					// Scrolls the specified element (a jQuery object, DOM node or jQuery selector string) into view so
1205					// that it can be seen within the viewport. If stickToTop is true then the element will appear at
1206					// the top of the viewport, if it is false then the viewport will scroll as little as possible to
1207					// show the element. You can also specify if you want animation to occur. If you don't provide this
1208					// argument then the animateScroll value from the settings object is used instead.
1209					scrollToElement: function(ele, stickToTop, animate)
1210					{
1211						scrollToElement(ele, stickToTop, animate);
1212					},
1213					// Scrolls the pane so that the specified co-ordinates within the content are at the top left
1214					// of the viewport. animate is optional and if not passed then the value of animateScroll from
1215					// the settings object this jScrollPane was initialised with is used.
1216					scrollTo: function(destX, destY, animate)
1217					{
1218						scrollToX(destX, animate);
1219						scrollToY(destY, animate);
1220					},
1221					// Scrolls the pane so that the specified co-ordinate within the content is at the left of the
1222					// viewport. animate is optional and if not passed then the value of animateScroll from the settings
1223					// object this jScrollPane was initialised with is used.
1224					scrollToX: function(destX, animate)
1225					{
1226						scrollToX(destX, animate);
1227					},
1228					// Scrolls the pane so that the specified co-ordinate within the content is at the top of the
1229					// viewport. animate is optional and if not passed then the value of animateScroll from the settings
1230					// object this jScrollPane was initialised with is used.
1231					scrollToY: function(destY, animate)
1232					{
1233						scrollToY(destY, animate);
1234					},
1235					// Scrolls the pane to the specified percentage of its maximum horizontal scroll position. animate
1236					// is optional and if not passed then the value of animateScroll from the settings object this
1237					// jScrollPane was initialised with is used.
1238					scrollToPercentX: function(destPercentX, animate)
1239					{
1240						scrollToX(destPercentX * (contentWidth - paneWidth), animate);
1241					},
1242					// Scrolls the pane to the specified percentage of its maximum vertical scroll position. animate
1243					// is optional and if not passed then the value of animateScroll from the settings object this
1244					// jScrollPane was initialised with is used.
1245					scrollToPercentY: function(destPercentY, animate)
1246					{
1247						scrollToY(destPercentY * (contentHeight - paneHeight), animate);
1248					},
1249					// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
1250					// the value of animateScroll from the settings object this jScrollPane was initialised with is used.
1251					scrollBy: function(deltaX, deltaY, animate)
1252					{
1253						jsp.scrollByX(deltaX, animate);
1254						jsp.scrollByY(deltaY, animate);
1255					},
1256					// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
1257					// the value of animateScroll from the settings object this jScrollPane was initialised with is used.
1258					scrollByX: function(deltaX, animate)
1259					{
1260						var destX = contentPositionX() + Math[deltaX<0 ? 'floor' : 'ceil'](deltaX),
1261							percentScrolled = destX / (contentWidth - paneWidth);
1262						positionDragX(percentScrolled * dragMaxX, animate);
1263					},
1264					// Scrolls the pane by the specified amount of pixels. animate is optional and if not passed then
1265					// the value of animateScroll from the settings object this jScrollPane was initialised with is used.
1266					scrollByY: function(deltaY, animate)
1267					{
1268						var destY = contentPositionY() + Math[deltaY<0 ? 'floor' : 'ceil'](deltaY),
1269							percentScrolled = destY / (contentHeight - paneHeight);
1270						positionDragY(percentScrolled * dragMaxY, animate);
1271					},
1272					// Positions the horizontal drag at the specified x position (and updates the viewport to reflect
1273					// this). animate is optional and if not passed then the value of animateScroll from the settings
1274					// object this jScrollPane was initialised with is used.
1275					positionDragX: function(x, animate)
1276					{
1277						positionDragX(x, animate);
1278					},
1279					// Positions the vertical drag at the specified y position (and updates the viewport to reflect
1280					// this). animate is optional and if not passed then the value of animateScroll from the settings
1281					// object this jScrollPane was initialised with is used.
1282					positionDragY: function(y, animate)
1283					{
1284						positionDragY(y, animate);
1285					},
1286					// This method is called when jScrollPane is trying to animate to a new position. You can override
1287					// it if you want to provide advanced animation functionality. It is passed the following arguments:
1288					//  * ele          - the element whose position is being animated
1289					//  * prop         - the property that is being animated
1290					//  * value        - the value it's being animated to
1291					//  * stepCallback - a function that you must execute each time you update the value of the property
1292					// You can use the default implementation (below) as a starting point for your own implementation.
1293					animate: function(ele, prop, value, stepCallback)
1294					{
1295						var params = {};
1296						params[prop] = value;
1297						ele.animate(
1298							params,
1299							{
1300								'duration'	: settings.animateDuration,
1301								'easing'	: settings.animateEase,
1302								'queue'		: false,
1303								'step'		: stepCallback
1304							}
1305						);
1306					},
1307					// Returns the current x position of the viewport with regards to the content pane.
1308					getContentPositionX: function()
1309					{
1310						return contentPositionX();
1311					},
1312					// Returns the current y position of the viewport with regards to the content pane.
1313					getContentPositionY: function()
1314					{
1315						return contentPositionY();
1316					},
1317					// Returns the width of the content within the scroll pane.
1318					getContentWidth: function()
1319					{
1320						return contentWidth;
1321					},
1322					// Returns the height of the content within the scroll pane.
1323					getContentHeight: function()
1324					{
1325						return contentHeight;
1326					},
1327					// Returns the horizontal position of the viewport within the pane content.
1328					getPercentScrolledX: function()
1329					{
1330						return contentPositionX() / (contentWidth - paneWidth);
1331					},
1332					// Returns the vertical position of the viewport within the pane content.
1333					getPercentScrolledY: function()
1334					{
1335						return contentPositionY() / (contentHeight - paneHeight);
1336					},
1337					// Returns whether or not this scrollpane has a horizontal scrollbar.
1338					getIsScrollableH: function()
1339					{
1340						return isScrollableH;
1341					},
1342					// Returns whether or not this scrollpane has a vertical scrollbar.
1343					getIsScrollableV: function()
1344					{
1345						return isScrollableV;
1346					},
1347					// Gets a reference to the content pane. It is important that you use this method if you want to
1348					// edit the content of your jScrollPane as if you access the element directly then you may have some
1349					// problems (as your original element has had additional elements for the scrollbars etc added into
1350					// it).
1351					getContentPane: function()
1352					{
1353						return pane;
1354					},
1355					// Scrolls this jScrollPane down as far as it can currently scroll. If animate isn't passed then the
1356					// animateScroll value from settings is used instead.
1357					scrollToBottom: function(animate)
1358					{
1359						positionDragY(dragMaxY, animate);
1360					},
1361					// Hijacks the links on the page which link to content inside the scrollpane. If you have changed
1362					// the content of your page (e.g. via AJAX) and want to make sure any new anchor links to the
1363					// contents of your scroll pane will work then call this function.
1364					hijackInternalLinks: $.noop,
1365					// Removes the jScrollPane and returns the page to the state it was in before jScrollPane was
1366					// initialised.
1367					destroy: function()
1368					{
1369							destroy();
1370					}
1371				}
1372			);
1373
1374			initialise(s);
1375		}
1376
1377		// Pluginifying code...
1378		settings = $.extend({}, $.fn.jScrollPane.defaults, settings);
1379
1380		// Apply default speed
1381		$.each(['mouseWheelSpeed', 'arrowButtonSpeed', 'trackClickSpeed', 'keyboardSpeed'], function() {
1382			settings[this] = settings[this] || settings.speed;
1383		});
1384
1385		return this.each(
1386			function()
1387			{
1388				var elem = $(this), jspApi = elem.data('jsp');
1389				if (jspApi) {
1390					jspApi.reinitialise(settings);
1391				} else {
1392					$("script",elem).filter('[type="text/javascript"],:not([type])').remove();
1393					jspApi = new JScrollPane(elem, settings);
1394					elem.data('jsp', jspApi);
1395				}
1396			}
1397		);
1398	};
1399
1400	$.fn.jScrollPane.defaults = {
1401		showArrows					: false,
1402		maintainPosition			: true,
1403		stickToBottom				: false,
1404		stickToRight				: false,
1405		clickOnTrack				: true,
1406		autoReinitialise			: false,
1407		autoReinitialiseDelay		: 500,
1408		verticalDragMinHeight		: 0,
1409		verticalDragMaxHeight		: 99999,
1410		horizontalDragMinWidth		: 0,
1411		horizontalDragMaxWidth		: 99999,
1412		contentWidth				: undefined,
1413		animateScroll				: false,
1414		animateDuration				: 300,
1415		animateEase					: 'linear',
1416		hijackInternalLinks			: false,
1417		verticalGutter				: 4,
1418		horizontalGutter			: 4,
1419		mouseWheelSpeed				: 0,
1420		arrowButtonSpeed			: 0,
1421		arrowRepeatFreq				: 50,
1422		arrowScrollOnHover			: false,
1423		trackClickSpeed				: 0,
1424		trackClickRepeatFreq		: 70,
1425		verticalArrowPositions		: 'split',
1426		horizontalArrowPositions	: 'split',
1427		enableKeyboardNavigation	: true,
1428		hideFocus					: false,
1429		keyboardSpeed				: 0,
1430		initialDelay                : 300,        // Delay before starting repeating
1431		speed						: 30,		// Default speed when others falsey
1432		scrollPagePercent			: .8		// Percent of visible area scrolled when pageUp/Down or track area pressed
1433	};
1434
1435})(jQuery,this);
1436