PageSourceSearch

https://riaz.ch/wp-content/plugins/elementor-pro/assets/lib/smartmenus/jquery.smartmenus.js?ver=1.2.1

js riaz.ch collected 2026-10-01 13:01:08 UTC 45,823 bytes, 1,225 lines download raw bytes

1/*!
2 * SmartMenus jQuery Plugin - v1.2.1 - November 3, 2022
3 * http://www.smartmenus.org/
4 *
5 * Copyright Vasil Dinkov, Vadikom Web Ltd.
6 * http://vadikom.com
7 *
8 * Licensed MIT
9 */
10
11(function(factory) {
12	if (typeof define === 'function' && define.amd) {
13		// AMD
14		define(['jquery'], factory);
15	} else if (typeof module === 'object' && typeof module.exports === 'object') {
16		// CommonJS
17		module.exports = factory(require('jquery'));
18	} else {
19		// Global jQuery
20		factory(jQuery);
21	}
22} (function($) {
23
24	var menuTrees = [],
25		mouse = false, // optimize for touch by default - we will detect for mouse input
26		touchEvents = 'ontouchstart' in window, // we use this just to choose between toucn and pointer events, not for touch screen detection
27		mouseDetectionEnabled = false,
28		requestAnimationFrame = window.requestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); },
29		cancelAnimationFrame = window.cancelAnimationFrame || function(id) { clearTimeout(id); },
30		canAnimate = !!$.fn.animate;
31
32	// Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.)
33	function initMouseDetection(disable) {
34		var eNS = '.smartmenus_mouse';
35		if (!mouseDetectionEnabled && !disable) {
36			// if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present
37			// in practice, this seems like impossible to trick unintentianally with a real mouse and a pretty safe detection on touch devices (even with older browsers that do not support touch events)
38			var firstTime = true,
39				lastMove = null,
40				events = {
41					'mousemove': function(e) {
42						var thisMove = { x: e.pageX, y: e.pageY, timeStamp: new Date().getTime() };
43						if (lastMove) {
44							var deltaX = Math.abs(lastMove.x - thisMove.x),
45								deltaY = Math.abs(lastMove.y - thisMove.y);
46							if ((deltaX > 0 || deltaY > 0) && deltaX <= 4 && deltaY <= 4 && thisMove.timeStamp - lastMove.timeStamp <= 300) {
47								mouse = true;
48								// if this is the first check after page load, check if we are not over some item by chance and call the mouseenter handler if yes
49								if (firstTime) {
50									var $a = $(e.target).closest('a');
51									if ($a.is('a')) {
52										$.each(menuTrees, function() {
53											if ($.contains(this.$root[0], $a[0])) {
54												this.itemEnter({ currentTarget: $a[0] });
55												return false;
56											}
57										});
58									}
59									firstTime = false;
60								}
61							}
62						}
63						lastMove = thisMove;
64					}
65				};
66			events[touchEvents ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut'] = function(e) {
67				if (isTouchEvent(e.originalEvent)) {
68					mouse = false;
69				}
70			};
71			$(document).on(getEventsNS(events, eNS));
72			mouseDetectionEnabled = true;
73		} else if (mouseDetectionEnabled && disable) {
74			$(document).off(eNS);
75			mouseDetectionEnabled = false;
76		}
77	}
78
79	function isTouchEvent(e) {
80		return !/^(4|mouse)$/.test(e.pointerType);
81	}
82
83	// returns a jQuery on() ready object
84	function getEventsNS(events, eNS) {
85		if (!eNS) {
86			eNS = '';
87		}
88		var eventsNS = {};
89		for (var i in events) {
90			eventsNS[i.split(' ').join(eNS + ' ') + eNS] = events[i];
91		}
92		return eventsNS;
93	}
94
95	$.SmartMenus = function(elm, options) {
96		this.$root = $(elm);
97		this.opts = options;
98		this.rootId = ''; // internal
99		this.accessIdPrefix = '';
100		this.$subArrow = null;
101		this.activatedItems = []; // stores last activated A's for each level
102		this.visibleSubMenus = []; // stores visible sub menus UL's (might be in no particular order)
103		this.showTimeout = 0;
104		this.hideTimeout = 0;
105		this.scrollTimeout = 0;
106		this.clickActivated = false;
107		this.focusActivated = false;
108		this.zIndexInc = 0;
109		this.idInc = 0;
110		this.$firstLink = null; // we'll use these for some tests
111		this.$firstSub = null; // at runtime so we'll cache them
112		this.disabled = false;
113		this.$disableOverlay = null;
114		this.$touchScrollingSub = null;
115		this.cssTransforms3d = 'perspective' in elm.style || 'webkitPerspective' in elm.style;
116		this.wasCollapsible = false;
117		this.init();
118	};
119
120	$.extend($.SmartMenus, {
121		hideAll: function() {
122			$.each(menuTrees, function() {
123				this.menuHideAll();
124			});
125		},
126		destroy: function() {
127			while (menuTrees.length) {
128				menuTrees[0].destroy();
129			}
130			initMouseDetection(true);
131		},
132		prototype: {
133			init: function(refresh) {
134				var self = this;
135
136				if (!refresh) {
137					menuTrees.push(this);
138
139					this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, '');
140					this.accessIdPrefix = 'sm-' + this.rootId + '-';
141
142					if (this.$root.hasClass('sm-rtl')) {
143						this.opts.rightToLeftSubMenus = true;
144					}
145
146					// init root (main menu)
147					var eNS = '.smartmenus';
148					this.$root
149						.data('smartmenus', this)
150						.attr('data-smartmenus-id', this.rootId)
151						.dataSM('level', 1)
152						.on(getEventsNS({
153							'mouseover focusin': $.proxy(this.rootOver, this),
154							'mouseout focusout': $.proxy(this.rootOut, this),
155							'keydown': $.proxy(this.rootKeyDown, this)
156						}, eNS))
157						.on(getEventsNS({
158							'mouseenter': $.proxy(this.itemEnter, this),
159							'mouseleave': $.proxy(this.itemLeave, this),
160							'mousedown': $.proxy(this.itemDown, this),
161							'focus': $.proxy(this.itemFocus, this),
162							'blur': $.proxy(this.itemBlur, this),
163							'click': $.proxy(this.itemClick, this)
164						}, eNS), 'a');
165
166					// hide menus on tap or click outside the root UL
167					eNS += this.rootId;
168					if (this.opts.hideOnClick) {
169						$(document).on(getEventsNS({
170							'touchstart': $.proxy(this.docTouchStart, this),
171							'touchmove': $.proxy(this.docTouchMove, this),
172							'touchend': $.proxy(this.docTouchEnd, this),
173							// for Opera Mobile < 11.5, webOS browser, etc. we'll check click too
174							'click': $.proxy(this.docClick, this)
175						}, eNS));
176					}
177					// hide sub menus on resize
178					$(window).on(getEventsNS({ 'resize orientationchange': $.proxy(this.winResize, this) }, eNS));
179
180					if (this.opts.subIndicators) {
181						this.$subArrow = $('<span/>').addClass('sub-arrow');
182						if (this.opts.subIndicatorsText) {
183							this.$subArrow.html(this.opts.subIndicatorsText);
184						}
185					}
186
187					// make sure mouse detection is enabled
188					initMouseDetection();
189				}
190
191				// init sub menus
192				this.$firstSub = this.$root.find('ul').each(function() { self.menuInit($(this)); }).eq(0);
193
194				this.$firstLink = this.$root.find('a').eq(0);
195
196				// find current item
197				if (this.opts.markCurrentItem) {
198					var reDefaultDoc = /(index|default)\.[^#\?\/]*/i,
199						reHash = /#.*/,
200						locHref = window.location.href.replace(reDefaultDoc, ''),
201						locHrefNoHash = locHref.replace(reHash, '');
202					this.$root.find('a:not(.mega-menu a)').each(function() {
203						var href = this.href.replace(reDefaultDoc, ''),
204							$this = $(this);
205						if (href == locHref || href == locHrefNoHash) {
206							$this.addClass('current');
207							if (self.opts.markCurrentTree) {
208								$this.parentsUntil('[data-smartmenus-id]', 'ul').each(function() {
209									$(this).dataSM('parent-a').addClass('current');
210								});
211							}
212						}
213					});
214				}
215
216				// save initial state
217				this.wasCollapsible = this.isCollapsible();
218			},
219			destroy: function(refresh) {
220				if (!refresh) {
221					var eNS = '.smartmenus';
222					this.$root
223						.removeData('smartmenus')
224						.removeAttr('data-smartmenus-id')
225						.removeDataSM('level')
226						.off(eNS);
227					eNS += this.rootId;
228					$(document).off(eNS);
229					$(window).off(eNS);
230					if (this.opts.subIndicators) {
231						this.$subArrow = null;
232					}
233				}
234				this.menuHideAll();
235				var self = this;
236				this.$root.find('ul').each(function() {
237					var $this = $(this);
238					if ($this.dataSM('scroll-arrows')) {
239						$this.dataSM('scroll-arrows').remove();
240					}
241					if ($this.dataSM('shown-before')) {
242						if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) {
243							$this.css({ width: '', minWidth: '', maxWidth: '' }).removeClass('sm-nowrap');
244						}
245						if ($this.dataSM('scroll-arrows')) {
246							$this.dataSM('scroll-arrows').remove();
247						}
248						$this.css({ zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: '' });
249					}
250					if (($this.attr('id') || '').indexOf(self.accessIdPrefix) == 0) {
251						$this.removeAttr('id');
252					}
253				})
254					.removeDataSM('in-mega')
255					.removeDataSM('shown-before')
256					.removeDataSM('scroll-arrows')
257					.removeDataSM('parent-a')
258					.removeDataSM('level')
259					.removeDataSM('beforefirstshowfired')
260					.removeAttr('role')
261					.removeAttr('aria-hidden')
262					.removeAttr('aria-labelledby')
263					.removeAttr('aria-expanded');
264				this.$root.find('a.has-submenu').each(function() {
265					var $this = $(this);
266					if ($this.attr('id').indexOf(self.accessIdPrefix) == 0) {
267						$this.removeAttr('id');
268					}
269				})
270					.removeClass('has-submenu')
271					.removeDataSM('sub')
272					.removeAttr('aria-haspopup')
273					.removeAttr('aria-controls')
274					.removeAttr('aria-expanded')
275					.closest('li').removeDataSM('sub');
276				if (this.opts.subIndicators) {
277					this.$root.find('span.sub-arrow').remove();
278				}
279				if (this.opts.markCurrentItem) {
280					this.$root.find('a.current').removeClass('current');
281				}
282				if (!refresh) {
283					this.$root = null;
284					this.$firstLink = null;
285					this.$firstSub = null;
286					if (this.$disableOverlay) {
287						this.$disableOverlay.remove();
288						this.$disableOverlay = null;
289					}
290					menuTrees.splice($.inArray(this, menuTrees), 1);
291				}
292			},
293			disable: function(noOverlay) {
294				if (!this.disabled) {
295					this.menuHideAll();
296					// display overlay over the menu to prevent interaction
297					if (!noOverlay && !this.opts.isPopup && this.$root.is(':visible')) {
298						var pos = this.$root.offset();
299						this.$disableOverlay = $('<div class="sm-jquery-disable-overlay"/>').css({
300							position: 'absolute',
301							top: pos.top,
302							left: pos.left,
303							width: this.$root.outerWidth(),
304							height: this.$root.outerHeight(),
305							zIndex: this.getStartZIndex(true),
306							opacity: 0
307						}).appendTo(document.body);
308					}
309					this.disabled = true;
310				}
311			},
312			docClick: function(e) {
313				if (this.$touchScrollingSub) {
314					this.$touchScrollingSub = null;
315					return;
316				}
317				// hide on any click outside the menu or on a menu link
318				if (this.visibleSubMenus.length && !$.contains(this.$root[0], e.target) || $(e.target).closest('a').length) {
319					this.menuHideAll();
320				}
321			},
322			docTouchEnd: function(e) {
323				if (!this.lastTouch) {
324					return;
325				}
326				if (this.visibleSubMenus.length && (this.lastTouch.x2 === undefined || this.lastTouch.x1 == this.lastTouch.x2) && (this.lastTouch.y2 === undefined || this.lastTouch.y1 == this.lastTouch.y2) && (!this.lastTouch.target || !$.contains(this.$root[0], this.lastTouch.target))) {
327					if (this.hideTimeout) {
328						clearTimeout(this.hideTimeout);
329						this.hideTimeout = 0;
330					}
331					// hide with a delay to prevent triggering accidental unwanted click on some page element
332					var self = this;
333					this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, 350);
334				}
335				this.lastTouch = null;
336			},
337			docTouchMove: function(e) {
338				if (!this.lastTouch) {
339					return;
340				}
341				var touchPoint = e.originalEvent.touches[0];
342				this.lastTouch.x2 = touchPoint.pageX;
343				this.lastTouch.y2 = touchPoint.pageY;
344			},
345			docTouchStart: function(e) {
346				var touchPoint = e.originalEvent.touches[0];
347				this.lastTouch = { x1: touchPoint.pageX, y1: touchPoint.pageY, target: touchPoint.target };
348			},
349			enable: function() {
350				if (this.disabled) {
351					if (this.$disableOverlay) {
352						this.$disableOverlay.remove();
353						this.$disableOverlay = null;
354					}
355					this.disabled = false;
356				}
357			},
358			getClosestMenu: function(elm) {
359				var $closestMenu = $(elm).closest('ul');
360				while ($closestMenu.dataSM('in-mega')) {
361					$closestMenu = $closestMenu.parent().closest('ul');
362				}
363				return $closestMenu[0] || null;
364			},
365			getHeight: function($elm) {
366				return this.getOffset($elm, true);
367			},
368			// returns precise width/height float values
369			getOffset: function($elm, height) {
370				var old;
371				if ($elm.css('display') == 'none') {
372					old = { position: $elm[0].style.position, visibility: $elm[0].style.visibility };
373					$elm.css({ position: 'absolute', visibility: 'hidden' }).show();
374				}
375				var box = $elm[0].getBoundingClientRect && $elm[0].getBoundingClientRect(),
376					val = box && (height ? box.height || box.bottom - box.top : box.width || box.right - box.left);
377				if (!val && val !== 0) {
378					val = height ? $elm[0].offsetHeight : $elm[0].offsetWidth;
379				}
380				if (old) {
381					$elm.hide().css(old);
382				}
383				return val;
384			},
385			getStartZIndex: function(root) {
386				var zIndex = parseInt(this[root ? '$root' : '$firstSub'].css('z-index'));
387				if (!root && isNaN(zIndex)) {
388					zIndex = parseInt(this.$root.css('z-index'));
389				}
390				return !isNaN(zIndex) ? zIndex : 1;
391			},
392			getTouchPoint: function(e) {
393				return e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0] || e;
394			},
395			getViewport: function(height) {
396				var name = height ? 'Height' : 'Width',
397					val = document.documentElement['client' + name],
398					val2 = window['inner' + name];
399				if (val2) {
400					val = Math.min(val, val2);
401				}
402				return val;
403			},
404			getViewportHeight: function() {
405				return this.getViewport(true);
406			},
407			getViewportWidth: function() {
408				return this.getViewport();
409			},
410			getWidth: function($elm) {
411				return this.getOffset($elm);
412			},
413			handleEvents: function() {
414				return !this.disabled && this.isCSSOn();
415			},
416			handleItemEvents: function($a) {
417				return this.handleEvents() && !this.isLinkInMegaMenu($a);
418			},
419			isCollapsible: function() {
420				return this.$firstSub.css('position') == 'static';
421			},
422			isCSSOn: function() {
423				return this.$firstLink.css('display') != 'inline';
424			},
425			isFixed: function() {
426				var isFixed = this.$root.css('position') == 'fixed';
427				if (!isFixed) {
428					this.$root.parentsUntil('body').each(function() {
429						if ($(this).css('position') == 'fixed') {
430							isFixed = true;
431							return false;
432						}
433					});
434				}
435				return isFixed;
436			},
437			isLinkInMegaMenu: function($a) {
438				return $(this.getClosestMenu($a[0])).hasClass('mega-menu');
439			},
440			isTouchMode: function() {
441				return !mouse || this.opts.noMouseOver || this.isCollapsible();
442			},
443			itemActivate: function($a, hideDeeperSubs) {
444				var $ul = $a.closest('ul'),
445					level = $ul.dataSM('level');
446				// if for some reason the parent item is not activated (e.g. this is an API call to activate the item), activate all parent items first
447				if (level > 1 && (!this.activatedItems[level - 2] || this.activatedItems[level - 2][0] != $ul.dataSM('parent-a')[0])) {
448					var self = this;
449					$($ul.parentsUntil('[data-smartmenus-id]', 'ul').get().reverse()).add($ul).each(function() {
450						self.itemActivate($(this).dataSM('parent-a'));
451					});
452				}
453				// hide any visible deeper level sub menus
454				if (!this.isCollapsible() || hideDeeperSubs) {
455					this.menuHideSubMenus(!this.activatedItems[level - 1] || this.activatedItems[level - 1][0] != $a[0] ? level - 1 : level);
456				}
457				// save new active item for this level
458				this.activatedItems[level - 1] = $a;
459				if (this.$root.triggerHandler('activate.smapi', $a[0]) === false) {
460					return;
461				}
462				// show the sub menu if this item has one
463				var $sub = $a.dataSM('sub');
464				if ($sub && (this.isTouchMode() || (!this.opts.showOnClick || this.clickActivated))) {
465					this.menuShow($sub);
466				}
467			},
468			itemBlur: function(e) {
469				var $a = $(e.currentTarget);
470				if (!this.handleItemEvents($a)) {
471					return;
472				}
473				this.$root.triggerHandler('blur.smapi', $a[0]);
474			},
475			itemClick: function(e) {
476				var $a = $(e.currentTarget);
477				if (!this.handleItemEvents($a)) {
478					return;
479				}
480				if (this.$touchScrollingSub && this.$touchScrollingSub[0] == $a.closest('ul')[0]) {
481					this.$touchScrollingSub = null;
482					e.stopPropagation();
483					return false;
484				}
485				if (this.$root.triggerHandler('click.smapi', $a[0]) === false) {
486					return false;
487				}
488				var $sub = $a.dataSM('sub'),
489					firstLevelSub = $sub ? $sub.dataSM('level') == 2 : false;
490				if ($sub) {
491					var subArrowClicked = $(e.target).is('.sub-arrow'),
492						collapsible = this.isCollapsible(),
493						behaviorToggle = /toggle$/.test(this.opts.collapsibleBehavior),
494						behaviorLink = /link$/.test(this.opts.collapsibleBehavior),
495						behaviorAccordion = /^accordion/.test(this.opts.collapsibleBehavior);
496					// if the sub is hidden, try to show it
497					if (!$sub.is(':visible')) {
498						if (!behaviorLink || !collapsible || subArrowClicked) {
499							if (!collapsible && this.opts.showOnClick && firstLevelSub) {
500								this.clickActivated = true;
501							}
502							// try to activate the item and show the sub
503							this.itemActivate($a, behaviorAccordion);
504							// if "itemActivate" showed the sub, prevent the click so that the link is not loaded
505							// if it couldn't show it, then the sub menus are disabled with an !important declaration (e.g. via mobile styles) so let the link get loaded
506							if ($sub.is(':visible')) {
507								this.focusActivated = true;
508								return false;
509							}
510						}
511						// if the sub is visible and showOnClick: true, hide the sub
512					} else if (!collapsible && this.opts.showOnClick && firstLevelSub) {
513						this.menuHide($sub);
514						this.clickActivated = false;
515						this.focusActivated = false;
516						return false;
517						// if the sub is visible and we are in collapsible mode
518					} else if (collapsible && (behaviorToggle || subArrowClicked)) {
519						this.itemActivate($a, behaviorAccordion);
520						this.menuHide($sub);
521						return false;
522					}
523				}
524				if (!collapsible && this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) {
525					return false;
526				}
527			},
528			itemDown: function(e) {
529				var $a = $(e.currentTarget);
530				if (!this.handleItemEvents($a)) {
531					return;
532				}
533				$a.dataSM('mousedown', true);
534			},
535			itemEnter: function(e) {
536				var $a = $(e.currentTarget);
537				if (!this.handleItemEvents($a)) {
538					return;
539				}
540				if (!this.isTouchMode()) {
541					if (this.showTimeout) {
542						clearTimeout(this.showTimeout);
543						this.showTimeout = 0;
544					}
545					var self = this;
546					this.showTimeout = setTimeout(function() { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout);
547				}
548				this.$root.triggerHandler('mouseenter.smapi', $a[0]);
549			},
550			itemFocus: function(e) {
551				var $a = $(e.currentTarget);
552				if (!this.handleItemEvents($a)) {
553					return;
554				}
555				// fix (the mousedown check): in some browsers a tap/click produces consecutive focus + click events so we don't need to activate the item on focus
556				if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) {
557					this.itemActivate($a, true);
558				}
559				this.$root.triggerHandler('focus.smapi', $a[0]);
560			},
561			itemLeave: function(e) {
562				var $a = $(e.currentTarget);
563				if (!this.handleItemEvents($a)) {
564					return;
565				}
566				if (!this.isTouchMode()) {
567					$a[0].blur();
568					if (this.showTimeout) {
569						clearTimeout(this.showTimeout);
570						this.showTimeout = 0;
571					}
572				}
573				$a.removeDataSM('mousedown');
574				this.$root.triggerHandler('mouseleave.smapi', $a[0]);
575			},
576			menuHide: function($sub) {
577				if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) {
578					return;
579				}
580				if (canAnimate) {
581					$sub.stop(true, true);
582				}
583				if ($sub.css('display') != 'none') {
584					var complete = function() {
585						// unset z-index
586						$sub.css('z-index', '');
587					};
588					// if sub is collapsible (mobile view)
589					if (this.isCollapsible()) {
590						if (canAnimate && this.opts.collapsibleHideFunction) {
591							this.opts.collapsibleHideFunction.call(this, $sub, complete);
592						} else {
593							$sub.hide(this.opts.collapsibleHideDuration, complete);
594						}
595					} else {
596						if (canAnimate && this.opts.hideFunction) {
597							this.opts.hideFunction.call(this, $sub, complete);
598						} else {
599							$sub.hide(this.opts.hideDuration, complete);
600						}
601					}
602					// deactivate scrolling if it is activated for this sub
603					if ($sub.dataSM('scroll')) {
604						this.menuScrollStop($sub);
605						$sub.css({ 'touch-action': '', '-ms-touch-action': '', '-webkit-transform': '', transform: '' })
606							.off('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide();
607					}
608					// unhighlight parent item + accessibility
609					$sub.dataSM('parent-a').removeClass('highlighted').attr('aria-expanded', 'false');
610					$sub.attr({
611						'aria-expanded': 'false',
612						'aria-hidden': 'true'
613					});
614					var level = $sub.dataSM('level');
615					this.activatedItems.splice(level - 1, 1);
616					this.visibleSubMenus.splice($.inArray($sub, this.visibleSubMenus), 1);
617					this.$root.triggerHandler('hide.smapi', $sub[0]);
618				}
619			},
620			menuHideAll: function() {
621				if (this.showTimeout) {
622					clearTimeout(this.showTimeout);
623					this.showTimeout = 0;
624				}
625				// hide all subs
626				// if it's a popup, this.visibleSubMenus[0] is the root UL
627				var level = this.opts.isPopup ? 1 : 0;
628				for (var i = this.visibleSubMenus.length - 1; i >= level; i--) {
629					this.menuHide(this.visibleSubMenus[i]);
630				}
631				// hide root if it's popup
632				if (this.opts.isPopup) {
633					if (canAnimate) {
634						this.$root.stop(true, true);
635					}
636					if (this.$root.is(':visible')) {
637						if (canAnimate && this.opts.hideFunction) {
638							this.opts.hideFunction.call(this, this.$root);
639						} else {
640							this.$root.hide(this.opts.hideDuration);
641						}
642					}
643				}
644				this.activatedItems = [];
645				this.visibleSubMenus = [];
646				this.clickActivated = false;
647				this.focusActivated = false;
648				// reset z-index increment
649				this.zIndexInc = 0;
650				this.$root.triggerHandler('hideAll.smapi');
651			},
652			menuHideSubMenus: function(level) {
653				for (var i = this.activatedItems.length - 1; i >= level; i--) {
654					var $sub = this.activatedItems[i].dataSM('sub');
655					if ($sub) {
656						this.menuHide($sub);
657					}
658				}
659			},
660			menuInit: function($ul) {
661				if (!$ul.dataSM('in-mega')) {
662					// mark UL's in mega drop downs (if any) so we can neglect them
663					if ($ul.hasClass('mega-menu')) {
664						$ul.find('ul').dataSM('in-mega', true);
665					}
666					// get level (much faster than, for example, using parentsUntil)
667					var level = 2,
668						par = $ul[0];
669					while ((par = par.parentNode.parentNode) != this.$root[0]) {
670						level++;
671					}
672					// cache stuff for quick access
673					var $a = $ul.prevAll('a').eq(-1);
674					// if the link is nested (e.g. in a heading)
675					if (!$a.length) {
676						$a = $ul.prevAll().find('a').eq(-1);
677					}
678					$a.addClass('has-submenu').dataSM('sub', $ul);
679					$ul.dataSM('parent-a', $a)
680						.dataSM('level', level)
681						.parent().dataSM('sub', $ul);
682					// accessibility
683					var aId = $a.attr('id') || this.accessIdPrefix + (++this.idInc),
684						ulId = $ul.attr('id') || this.accessIdPrefix + (++this.idInc);
685					$a.attr({
686						id: aId,
687						'aria-haspopup': 'true',
688						'aria-controls': ulId,
689						'aria-expanded': 'false'
690					});
691					$ul.attr({
692						id: ulId,
693						'role': 'group',
694						'aria-hidden': 'true',
695						'aria-labelledby': aId,
696						'aria-expanded': 'false'
697					});
698					// add sub indicator to parent item
699					if (this.opts.subIndicators) {
700						$a[this.opts.subIndicatorsPos](this.$subArrow.clone());
701					}
702				}
703			},
704			menuPosition: function($sub) {
705				var $a = $sub.dataSM('parent-a'),
706					$li = $a.closest('li'),
707					$ul = $li.parent(),
708					level = $sub.dataSM('level'),
709					subW = this.getWidth($sub),
710					subH = this.getHeight($sub),
711					itemOffset = $a.offset(),
712					itemX = itemOffset.left,
713					itemY = itemOffset.top,
714					itemW = this.getWidth($a),
715					itemH = this.getHeight($a),
716					$win = $(window),
717					winX = $win.scrollLeft(),
718					winY = $win.scrollTop(),
719					winW = this.getViewportWidth(),
720					winH = this.getViewportHeight(),
721					horizontalParent = $ul.parent().is('[data-sm-horizontal-sub]') || level == 2 && !$ul.hasClass('sm-vertical'),
722					rightToLeft = this.opts.rightToLeftSubMenus && !$li.is('[data-sm-reverse]') || !this.opts.rightToLeftSubMenus && $li.is('[data-sm-reverse]'),
723					subOffsetX = level == 2 ? this.opts.mainMenuSubOffsetX : this.opts.subMenusSubOffsetX,
724					subOffsetY = level == 2 ? this.opts.mainMenuSubOffsetY : this.opts.subMenusSubOffsetY,
725					x, y;
726				if (horizontalParent) {
727					x = rightToLeft ? itemW - subW - subOffsetX : subOffsetX;
728					y = this.opts.bottomToTopSubMenus ? -subH - subOffsetY : itemH + subOffsetY;
729				} else {
730					x = rightToLeft ? subOffsetX - subW : itemW - subOffsetX;
731					y = this.opts.bottomToTopSubMenus ? itemH - subOffsetY - subH : subOffsetY;
732				}
733				if (this.opts.keepInViewport) {
734					var absX = itemX + x,
735						absY = itemY + y;
736					if (rightToLeft && absX < winX) {
737						x = horizontalParent ? winX - absX + x : itemW - subOffsetX;
738					} else if (!rightToLeft && absX + subW > winX + winW) {
739						x = horizontalParent ? winX + winW - subW - absX + x : subOffsetX - subW;
740					}
741					if (!horizontalParent) {
742						if (subH < winH && absY + subH > winY + winH) {
743							y += winY + winH - subH - absY;
744						} else if (subH >= winH || absY < winY) {
745							y += winY - absY;
746						}
747					}
748					// do we need scrolling?
749					// 0.49 used for better precision when dealing with float values
750					if (horizontalParent && (absY + subH > winY + winH + 0.49 || absY < winY) || !horizontalParent && subH > winH + 0.49) {
751						var self = this;
752						if (!$sub.dataSM('scroll-arrows')) {
753							$sub.dataSM('scroll-arrows', $([$('<span class="scroll-up"><span class="scroll-up-arrow"></span></span>')[0], $('<span class="scroll-down"><span class="scroll-down-arrow"></span></span>')[0]])
754								.on({
755									mouseenter: function() {
756										$sub.dataSM('scroll').up = $(this).hasClass('scroll-up');
757										self.menuScroll($sub);
758									},
759									mouseleave: function(e) {
760										self.menuScrollStop($sub);
761										self.menuScrollOut($sub, e);
762									},
763									'mousewheel DOMMouseScroll': function(e) { e.preventDefault(); }
764								})
765								.insertAfter($sub)
766							);
767						}
768						// bind scroll events and save scroll data for this sub
769						var eNS = '.smartmenus_scroll';
770						$sub.dataSM('scroll', {
771							y: this.cssTransforms3d ? 0 : y - itemH,
772							step: 1,
773							// cache stuff for faster recalcs later
774							itemH: itemH,
775							subH: subH,
776							arrowDownH: this.getHeight($sub.dataSM('scroll-arrows').eq(1))
777						})
778							.on(getEventsNS({
779								'mouseover': function(e) { self.menuScrollOver($sub, e); },
780								'mouseout': function(e) { self.menuScrollOut($sub, e); },
781								'mousewheel DOMMouseScroll': function(e) { self.menuScrollMousewheel($sub, e); }
782							}, eNS))
783							.dataSM('scroll-arrows').css({ top: 'auto', left: '0', marginLeft: x + (parseInt($sub.css('border-left-width')) || 0), width: subW - (parseInt($sub.css('border-left-width')) || 0) - (parseInt($sub.css('border-right-width')) || 0), zIndex: $sub.css('z-index') })
784							.eq(horizontalParent && this.opts.bottomToTopSubMenus ? 0 : 1).show();
785						// when a menu tree is fixed positioned we allow scrolling via touch too
786						// since there is no other way to access such long sub menus if no mouse is present
787						if (this.isFixed()) {
788							var events = {};
789							events[touchEvents ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp'] = function(e) {
790								self.menuScrollTouch($sub, e);
791							};
792							$sub.css({ 'touch-action': 'none', '-ms-touch-action': 'none' }).on(getEventsNS(events, eNS));
793						}
794					}
795				}
796				$sub.css({ top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH });
797			},
798			menuScroll: function($sub, once, step) {
799				var data = $sub.dataSM('scroll'),
800					$arrows = $sub.dataSM('scroll-arrows'),
801					end = data.up ? data.upEnd : data.downEnd,
802					diff;
803				if (!once && data.momentum) {
804					data.momentum *= 0.92;
805					diff = data.momentum;
806					if (diff < 0.5) {
807						this.menuScrollStop($sub);
808						return;
809					}
810				} else {
811					diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step));
812				}
813				// hide any visible deeper level sub menus
814				var level = $sub.dataSM('level');
815				if (this.activatedItems[level - 1] && this.activatedItems[level - 1].dataSM('sub') && this.activatedItems[level - 1].dataSM('sub').is(':visible')) {
816					this.menuHideSubMenus(level - 1);
817				}
818				data.y = data.up && end <= data.y || !data.up && end >= data.y ? data.y : (Math.abs(end - data.y) > diff ? data.y + (data.up ? diff : -diff) : end);
819				$sub.css(this.cssTransforms3d ? { '-webkit-transform': 'translate3d(0, ' + data.y + 'px, 0)', transform: 'translate3d(0, ' + data.y + 'px, 0)' } : { marginTop: data.y });
820				// show opposite arrow if appropriate
821				if (mouse && (data.up && data.y > data.downEnd || !data.up && data.y < data.upEnd)) {
822					$arrows.eq(data.up ? 1 : 0).show();
823				}
824				// if we've reached the end
825				if (data.y == end) {
826					if (mouse) {
827						$arrows.eq(data.up ? 0 : 1).hide();
828					}
829					this.menuScrollStop($sub);
830				} else if (!once) {
831					if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) {
832						data.step += 0.2;
833					}
834					var self = this;
835					this.scrollTimeout = requestAnimationFrame(function() { self.menuScroll($sub); });
836				}
837			},
838			menuScrollMousewheel: function($sub, e) {
839				if (this.getClosestMenu(e.target) == $sub[0]) {
840					e = e.originalEvent;
841					var up = (e.wheelDelta || -e.detail) > 0;
842					if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) {
843						$sub.dataSM('scroll').up = up;
844						this.menuScroll($sub, true);
845					}
846				}
847				e.preventDefault();
848			},
849			menuScrollOut: function($sub, e) {
850				if (mouse) {
851					if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) {
852						$sub.dataSM('scroll-arrows').css('visibility', 'hidden');
853					}
854				}
855			},
856			menuScrollOver: function($sub, e) {
857				if (mouse) {
858					if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) {
859						this.menuScrollRefreshData($sub);
860						var data = $sub.dataSM('scroll'),
861							upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH;
862						$sub.dataSM('scroll-arrows').eq(0).css('margin-top', upEnd).end()
863							.eq(1).css('margin-top', upEnd + this.getViewportHeight() - data.arrowDownH).end()
864							.css('visibility', 'visible');
865					}
866				}
867			},
868			menuScrollRefreshData: function($sub) {
869				var data = $sub.dataSM('scroll'),
870					upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH;
871				if (this.cssTransforms3d) {
872					upEnd = -(parseFloat($sub.css('margin-top')) - upEnd);
873				}
874				$.extend(data, {
875					upEnd: upEnd,
876					downEnd: upEnd + this.getViewportHeight() - data.subH
877				});
878			},
879			menuScrollStop: function($sub) {
880				if (this.scrollTimeout) {
881					cancelAnimationFrame(this.scrollTimeout);
882					this.scrollTimeout = 0;
883					$sub.dataSM('scroll').step = 1;
884					return true;
885				}
886			},
887			menuScrollTouch: function($sub, e) {
888				e = e.originalEvent;
889				if (isTouchEvent(e)) {
890					var touchPoint = this.getTouchPoint(e);
891					// neglect event if we touched a visible deeper level sub menu
892					if (this.getClosestMenu(touchPoint.target) == $sub[0]) {
893						var data = $sub.dataSM('scroll');
894						if (/(start|down)$/i.test(e.type)) {
895							if (this.menuScrollStop($sub)) {
896								// if we were scrolling, just stop and don't activate any link on the first touch
897								e.preventDefault();
898								this.$touchScrollingSub = $sub;
899							} else {
900								this.$touchScrollingSub = null;
901							}
902							// update scroll data since the user might have zoomed, etc.
903							this.menuScrollRefreshData($sub);
904							// extend it with the touch properties
905							$.extend(data, {
906								touchStartY: touchPoint.pageY,
907								touchStartTime: e.timeStamp
908							});
909						} else if (/move$/i.test(e.type)) {
910							var prevY = data.touchY !== undefined ? data.touchY : data.touchStartY;
911							if (prevY !== undefined && prevY != touchPoint.pageY) {
912								this.$touchScrollingSub = $sub;
913								var up = prevY < touchPoint.pageY;
914								// changed direction? reset...
915								if (data.up !== undefined && data.up != up) {
916									$.extend(data, {
917										touchStartY: touchPoint.pageY,
918										touchStartTime: e.timeStamp
919									});
920								}
921								$.extend(data, {
922									up: up,
923									touchY: touchPoint.pageY
924								});
925								this.menuScroll($sub, true, Math.abs(touchPoint.pageY - prevY));
926							}
927							e.preventDefault();
928						} else { // touchend/pointerup
929							if (data.touchY !== undefined) {
930								if (data.momentum = Math.pow(Math.abs(touchPoint.pageY - data.touchStartY) / (e.timeStamp - data.touchStartTime), 2) * 15) {
931									this.menuScrollStop($sub);
932									this.menuScroll($sub);
933									e.preventDefault();
934								}
935								delete data.touchY;
936							}
937						}
938					}
939				}
940			},
941			menuShow: function($sub) {
942				if (!$sub.dataSM('beforefirstshowfired')) {
943					$sub.dataSM('beforefirstshowfired', true);
944					if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) {
945						return;
946					}
947				}
948				if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) {
949					return;
950				}
951				$sub.dataSM('shown-before', true);
952				if (canAnimate) {
953					$sub.stop(true, true);
954				}
955				if (!$sub.is(':visible')) {
956					// highlight parent item
957					var $a = $sub.dataSM('parent-a'),
958						collapsible = this.isCollapsible();
959					if (this.opts.keepHighlighted || collapsible) {
960						$a.addClass('highlighted');
961					}
962					if (collapsible) {
963						$sub.removeClass('sm-nowrap').css({ zIndex: '', width: 'auto', minWidth: '', maxWidth: '', top: '', left: '', marginLeft: '', marginTop: '' });
964					} else {
965						// set z-index
966						$sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.get
966StartZIndex()) + 1);
967						// min/max-width fix - no way to rely purely on CSS as all UL's are nested
968						if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) {
969							$sub.css({ width: 'auto', minWidth: '', maxWidth: '' }).addClass('sm-nowrap');
970							if (this.opts.subMenusMinWidth) {
971								$sub.css('min-width', this.opts.subMenusMinWidth);
972							}
973							if (this.opts.subMenusMaxWidth) {
974								var noMaxWidth = this.getWidth($sub);
975								$sub.css('max-width', this.opts.subMenusMaxWidth);
976								if (noMaxWidth > this.getWidth($sub)) {
977									$sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth);
978								}
979							}
980						}
981						this.menuPosition($sub);
982					}
983					var complete = function() {
984						// fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used
985						$sub.css('overflow', '');
986					};
987					// if sub is collapsible (mobile view)
988					if (collapsible) {
989						if (canAnimate && this.opts.collapsibleShowFunction) {
990							this.opts.collapsibleShowFunction.call(this, $sub, complete);
991						} else {
992							$sub.show(this.opts.collapsibleShowDuration, complete);
993						}
994					} else {
995						if (canAnimate && this.opts.showFunction) {
996							this.opts.showFunction.call(this, $sub, complete);
997						} else {
998							$sub.show(this.opts.showDuration, complete);
999						}
1000					}
1001					// accessibility
1002					$a.attr('aria-expanded', 'true');
1003					$sub.attr({
1004						'aria-expanded': 'true',
1005						'aria-hidden': 'false'
1006					});
1007					// store sub menu in visible array
1008					this.visibleSubMenus.push($sub);
1009					this.$root.triggerHandler('show.smapi', $sub[0]);
1010				}
1011			},
1012			popupHide: function(noHideTimeout) {
1013				if (this.hideTimeout) {
1014					clearTimeout(this.hideTimeout);
1015					this.hideTimeout = 0;
1016				}
1017				var self = this;
1018				this.hideTimeout = setTimeout(function() {
1019					self.menuHideAll();
1020				}, noHideTimeout ? 1 : this.opts.hideTimeout);
1021			},
1022			popupShow: function(left, top) {
1023				if (!this.opts.isPopup) {
1024					alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.');
1025					return;
1026				}
1027				if (this.hideTimeout) {
1028					clearTimeout(this.hideTimeout);
1029					this.hideTimeout = 0;
1030				}
1031				this.$root.dataSM('shown-before', true);
1032				if (canAnimate) {
1033					this.$root.stop(true, true);
1034				}
1035				if (!this.$root.is(':visible')) {
1036					this.$root.css({ left: left, top: top });
1037					// show menu
1038					var self = this,
1039						complete = function() {
1040							self.$root.css('overflow', '');
1041						};
1042					if (canAnimate && this.opts.showFunction) {
1043						this.opts.showFunction.call(this, this.$root, complete);
1044					} else {
1045						this.$root.show(this.opts.showDuration, complete);
1046					}
1047					this.visibleSubMenus[0] = this.$root;
1048				}
1049			},
1050			refresh: function() {
1051				this.destroy(true);
1052				this.init(true);
1053			},
1054			rootKeyDown: function(e) {
1055				if (!this.handleEvents()) {
1056					return;
1057				}
1058				switch (e.keyCode) {
1059					case 27: // reset on Esc
1060						var $activeTopItem = this.activatedItems[0];
1061						if ($activeTopItem) {
1062							this.menuHideAll();
1063							$activeTopItem[0].focus();
1064							var $sub = $activeTopItem.dataSM('sub');
1065							if ($sub) {
1066								this.menuHide($sub);
1067							}
1068						}
1069						break;
1070					case 32: // activate item's sub on Space
1071						var $target = $(e.target);
1072						if ($target.is('a') && this.handleItemEvents($target)) {
1073							var $sub = $target.dataSM('sub');
1074							if ($sub && !$sub.is(':visible')) {
1075								this.itemClick({ currentTarget: e.target });
1076								e.preventDefault();
1077							}
1078						}
1079						break;
1080				}
1081			},
1082			rootOut: function(e) {
1083				if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
1084					return;
1085				}
1086				if (this.hideTimeout) {
1087					clearTimeout(this.hideTimeout);
1088					this.hideTimeout = 0;
1089				}
1090				if (!this.opts.showOnClick || !this.opts.hideOnClick) {
1091					var self = this;
1092					this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, this.opts.hideTimeout);
1093				}
1094			},
1095			rootOver: function(e) {
1096				if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) {
1097					return;
1098				}
1099				if (this.hideTimeout) {
1100					clearTimeout(this.hideTimeout);
1101					this.hideTimeout = 0;
1102				}
1103			},
1104			winResize: function(e) {
1105				if (!this.handleEvents()) {
1106					// we still need to resize the disable overlay if it's visible
1107					if (this.$disableOverlay) {
1108						var pos = this.$root.offset();
1109						this.$disableOverlay.css({
1110							top: pos.top,
1111							left: pos.left,
1112							width: this.$root.outerWidth(),
1113							height: this.$root.outerHeight()
1114						});
1115					}
1116					return;
1117				}
1118				// hide sub menus on resize - on mobile do it only on orientation change
1119				if (!('onorientationchange' in window) || e.type == 'orientationchange') {
1120					var collapsible = this.isCollapsible();
1121					// if it was collapsible before resize and still is, don't do it
1122					if (!(this.wasCollapsible && collapsible)) {
1123						if (this.activatedItems.length) {
1124							this.activatedItems[this.activatedItems.length - 1][0].blur();
1125						}
1126						this.menuHideAll();
1127					}
1128					this.wasCollapsible = collapsible;
1129				}
1130			}
1131		}
1132	});
1133
1134	$.fn.dataSM = function(key, val) {
1135		if (val) {
1136			return this.data(key + '_smartmenus', val);
1137		}
1138		return this.data(key + '_smartmenus');
1139	};
1140
1141	$.fn.removeDataSM = function(key) {
1142		return this.removeData(key + '_smartmenus');
1143	};
1144
1145	$.fn.smartmenus = function(options) {
1146		if (typeof options == 'string') {
1147			var args = arguments,
1148				method = options;
1149			Array.prototype.shift.call(args);
1150			return this.each(function() {
1151				var smartmenus = $(this).data('smartmenus');
1152				if (smartmenus && smartmenus[method]) {
1153					smartmenus[method].apply(smartmenus, args);
1154				}
1155			});
1156		}
1157		return this.each(function() {
1158			// [data-sm-options] attribute on the root UL
1159			var dataOpts = $(this).data('sm-options') || null;
1160			if (dataOpts && typeof dataOpts != 'object') {
1161				dataOpts = null;
1162				alert('ERROR\n\nSmartMenus jQuery init:\nThe value of the "data-sm-options" attribute must be valid JSON.');
1163			}
1164			// do not support function options
1165			if (dataOpts) {
1166				$.each(['showFunction', 'hideFunction', 'collapsibleShowFunction', 'collapsibleHideFunction'], function() {
1167					if (this in dataOpts) {
1168						delete dataOpts[this];
1169					}
1170				});
1171			}
1172			new $.SmartMenus(this, $.extend({}, $.fn.smartmenus.defaults, options, dataOpts));
1173		});
1174	};
1175
1176	// default settings
1177	$.fn.smartmenus.defaults = {
1178		isPopup:		false,		// is this a popup menu (can be shown via the popupS
1178how/popupHide methods) or a permanent menu bar
1179		mainMenuSubOffsetX:	0,		// pixels offset from default position
1180		mainMenuSubOffsetY:	0,		// pixels offset from default position
1181		subMenusSubOffsetX:	0,		// pixels offset from default position
1182		subMenusSubOffsetY:	0,		// pixels offset from default position
1183		subMenusMinWidth:	'10em',		// min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
1184		subMenusMaxWidth:	'20em',		// max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored
1185		subIndicators: 		true,		// create sub menu indicators - creates a SPAN and inserts it in the A
1186		subIndicatorsPos: 	'append',	// position of the SPAN relative to the menu item content ('append', 'prepend')
1187		subIndicatorsText:	'',		// [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too)
1188		scrollStep: 		30,		// pixels step when scrolling long sub menus that do not fit in the viewport height
1189		scrollAccelerate:	true,		// accelerate scrolling or use a fixed step
1190		showTimeout:		250,		// timeout before showing the sub menus
1191		hideTimeout:		500,		// timeout before hiding the sub menus
1192		showDuration:		0,		// duration for show animation - set to 0 for no animation - matters only if showFunction:null
1193		showFunction:		null,		// custom function to use when showing a sub menu (the default is the jQuery 'show')
1194		// don't forget to call complete() at the end of whatever you do
1195		// e.g.: function($ul, complete) { $ul.fadeIn(250, complete); }
1196		hideDuration:		0,		// duration for hide animation - set to 0 for no animation - matters only if hideFunction:null
1197		hideFunction:		function($ul, complete) { $ul.fadeOut(200, complete); },	// custom function to use when hiding a sub menu (the default is the jQuery 'hide')
1198		// don't forget to call complete() at the end of whatever you do
1199		// e.g.: function($ul, complete) { $ul.fadeOut(250, complete); }
1200		collapsibleShowDuration:0,		// duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null
1201		collapsibleShowFunction:function($ul, complete) { $ul.slideDown(200, complete); },	// custom function to use when showing a collapsible sub menu
1202		// (i.e. when mobile styles are used to make the sub menus collapsible)
1203		collapsibleHideDuration:0,		// duration for hide animation for collapsible sub menus - matters only if collapsibleHideFunction:null
1204		collapsibleHideFunction:function($ul, complete) { $ul.slideUp(200, complete); },	// custom function to use when hiding a collapsible sub menu
1205		// (i.e. when mobile styles are used to make the sub menus collapsible)
1206		showOnClick:		false,		// show the first-level sub menus onclick instead of onmouseover (i.e. mimic desktop app menus) (matters only for mouse input)
1207		hideOnClick:		true,		// hide the sub menus on click/tap anywhere on the page
1208		noMouseOver:		false,		// disable sub menus activation onmouseover (i.e. behave like in touch mode - use just mouse clicks) (matters only for mouse input)
1209		keepInViewport:		true,		// reposition the sub menus if needed to make sure they always appear inside the viewport
1210		keepHighlighted:	true,		// keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's)
1211		markCurrentItem:	false,		// automatically add the 'current' class to the A element of the item linking to the current URL
1212		markCurrentTree:	true,		// add the 'current' class also to the A elements of all ancestor items of the current item
1213		rightToLeftSubMenus:	false,		// right to left display of the sub menus (check the CSS for the sub indicators' position)
1214		bottomToTopSubMenus:	false,		// bottom to top display of the sub menus
1215		collapsibleBehavior:	'default'	// parent items behavior in collapsible (mobile) view ('default', 'toggle', 'link', 'accordion', 'accordion-toggle', 'accordion-link')
1216		// 'default' - first tap on parent item expands sub, second tap loads its link
1217		// 'toggle' - the whole parent item acts just as a toggle button for its sub menu (expands/collapses on each tap)
1218		// 'link' - the parent item acts as a regular item (first tap loads its link), the sub menu can be expanded only via the +/- button
1219		// 'accordion' - like 'default' but on expand also resets any visible sub menus from deeper levels or other branches
1220		// 'accordion-toggle' - like 'toggle' but on expand also resets any visible sub menus from deeper levels or other branches
1221		// 'accordion-link' - like 'link' but on expand also resets any visible sub menus from deeper levels or other branches
1222	};
1223
1224	return $;
1225}));

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.