/*! * OverlayScrollbars * https://github.com/KingSora/OverlayScrollbars * * Version: 1.13.3 * * Copyright KingSora | Rene Haas. * https://github.com/KingSora * * Released under the MIT license. * Date: 20.07.2022 */ (function (global, factory) { if (typeof define === "function" && define.amd) define(function () { return factory(global, global.document, undefined); }); else if (typeof module === "object" && typeof module.exports === "object") module.exports = factory(global, global.document, undefined); else factory(global, global.document, undefined); })( typeof window !== "undefined" ? window : this, function (window, document, undefined) { "use strict"; var PLUGINNAME = "OverlayScrollbars"; var TYPES = { o: "object", f: "function", a: "array", s: "string", b: "boolean", n: "number", u: "undefined", z: "null", //d : 'date', //e : 'error', //r : 'regexp', //y : 'symbol' }; var LEXICON = { c: "class", s: "style", i: "id", l: "length", p: "prototype", ti: "tabindex", oH: "offsetHeight", cH: "clientHeight", sH: "scrollHeight", oW: "offsetWidth", cW: "clientWidth", sW: "scrollWidth", hOP: "hasOwnProperty", bCR: "getBoundingClientRect", }; var VENDORS = (function () { //https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix var jsCache = {}; var cssCache = {}; var cssPrefixes = ["-webkit-", "-moz-", "-o-", "-ms-"]; var jsPrefixes = ["WebKit", "Moz", "O", "MS"]; function firstLetterToUpper(str) { return str.charAt(0).toUpperCase() + str.slice(1); } return { _cssPrefixes: cssPrefixes, _jsPrefixes: jsPrefixes, _cssProperty: function (name) { var result = cssCache[name]; if (cssCache[LEXICON.hOP](name)) return result; var uppercasedName = firstLetterToUpper(name); var elmStyle = document.createElement("div")[LEXICON.s]; var resultPossibilities; var i = 0; var v; var currVendorWithoutDashes; for (; i < cssPrefixes.length; i++) { currVendorWithoutDashes = cssPrefixes[i].replace(/-/g, ""); resultPossibilities = [ name, //transition cssPrefixes[i] + name, //-webkit-transition currVendorWithoutDashes + uppercasedName, //webkitTransition firstLetterToUpper(currVendorWithoutDashes) + uppercasedName, //WebkitTransition ]; for (v = 0; v < resultPossibilities[LEXICON.l]; v++) { if (elmStyle[resultPossibilities[v]] !== undefined) { result = resultPossibilities[v]; break; } } } cssCache[name] = result; return result; }, _cssPropertyValue: function (property, values, suffix) { var name = property + " " + values; var result = cssCache[name]; if (cssCache[LEXICON.hOP](name)) return result; var dummyStyle = document.createElement("div")[LEXICON.s]; var possbleValues = values.split(" "); var preparedSuffix = suffix || ""; var i = 0; var v = -1; var prop; for (; i < possbleValues[LEXICON.l]; i++) { for (; v < VENDORS._cssPrefixes[LEXICON.l]; v++) { prop = v < 0 ? possbleValues[i] : VENDORS._cssPrefixes[v] + possbleValues[i]; dummyStyle.cssText = property + ":" + prop + preparedSuffix; if (dummyStyle[LEXICON.l]) { result = prop; break; } } } cssCache[name] = result; return result; }, _jsAPI: function (name, isInterface, fallback) { var i = 0; var result = jsCache[name]; if (!jsCache[LEXICON.hOP](name)) { result = window[name]; for (; i < jsPrefixes[LEXICON.l]; i++) result = result || window[ (isInterface ? jsPrefixes[i] : jsPrefixes[i].toLowerCase()) + firstLetterToUpper(name) ]; jsCache[name] = result; } return result || fallback; }, }; })(); var COMPATIBILITY = (function () { function windowSize(x) { return x ? window.innerWidth || document.documentElement[LEXICON.cW] || document.body[LEXICON.cW] : window.innerHeight || document.documentElement[LEXICON.cH] || document.body[LEXICON.cH]; } function bind(func, thisObj) { if (typeof func != TYPES.f) { throw "Can't bind function!"; // closest thing possible to the ECMAScript 5 // internal IsCallable function //throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var proto = LEXICON.p; var aArgs = Array[proto].slice.call(arguments, 2); var fNOP = function () {}; var fBound = function () { return func.apply( this instanceof fNOP ? this : thisObj, aArgs.concat(Array[proto].slice.call(arguments)) ); }; if (func[proto]) fNOP[proto] = func[proto]; // Function.prototype doesn't have a prototype property fBound[proto] = new fNOP(); return fBound; } return { /** * Gets the current window width. * @returns {Number|number} The current window width in pixel. */ wW: bind(windowSize, 0, true), /** * Gets the current window height. * @returns {Number|number} The current window height in pixel. */ wH: bind(windowSize, 0), /** * Gets the MutationObserver Object or undefined if not supported. * @returns {MutationObserver|*|undefined} The MutationsObserver Object or undefined. */ mO: bind(VENDORS._jsAPI, 0, "MutationObserver", true), /** * Gets the ResizeObserver Object or undefined if not supported. * @returns {MutationObserver|*|undefined} The ResizeObserver Object or undefined. */ rO: bind(VENDORS._jsAPI, 0, "ResizeObserver", true), /** * Gets the RequestAnimationFrame method or it's corresponding polyfill. * @returns {*|Function} The RequestAnimationFrame method or it's corresponding polyfill. */ rAF: bind( VENDORS._jsAPI, 0, "requestAnimationFrame", false, function (func) { return window.setTimeout(func, 1000 / 60); } ), /** * Gets the CancelAnimationFrame method or it's corresponding polyfill. * @returns {*|Function} The CancelAnimationFrame method or it's corresponding polyfill. */ cAF: bind( VENDORS._jsAPI, 0, "cancelAnimationFrame", false, function (id) { return window.clearTimeout(id); } ), /** * Gets the current time. * @returns {number} The current time. */ now: function () { return (Date.now && Date.now()) || new Date().getTime(); }, /** * Stops the propagation of the given event. * @param event The event of which the propagation shall be stoped. */ stpP: function (event) { if (event.stopPropagation) event.stopPropagation(); else event.cancelBubble = true; }, /** * Prevents the default action of the given event. * @param event The event of which the default action shall be prevented. */ prvD: function (event) { if (event.preventDefault && event.cancelable) event.preventDefault(); else event.returnValue = false; }, /** * Gets the pageX and pageY values of the given mouse event. * @param event The mouse event of which the pageX and pageX shall be got. * @returns {{x: number, y: number}} x = pageX value, y = pageY value. */ page: function (event) { event = event.originalEvent || event; var strPage = "page"; var strClient = "client"; var strX = "X"; var strY = "Y"; var target = event.target || event.srcElement || document; var eventDoc = target.ownerDocument || document; var doc = eventDoc.documentElement; var body = eventDoc.body; //if touch event return return pageX/Y of it if (event.touches !== undefined) { var touch = event.touches[0]; return { x: touch[strPage + strX], y: touch[strPage + strY], }; } // Calculate pageX/Y if not native supported if ( !event[strPage + strX] && event[strClient + strX] && event[strClient + strX] != null ) { return { x: event[strClient + strX] + ((doc && doc.scrollLeft) || (body && body.scrollLeft) || 0) - ((doc && doc.clientLeft) || (body && body.clientLeft) || 0), y: event[strClient + strY] + ((doc && doc.scrollTop) || (body && body.scrollTop) || 0) - ((doc && doc.clientTop) || (body && body.clientTop) || 0), }; } return { x: event[strPage + strX], y: event[strPage + strY], }; }, /** * Gets the clicked mouse button of the given mouse event. * @param event The mouse event of which the clicked button shal be got. * @returns {number} The number of the clicked mouse button. (0 : none | 1 : leftButton | 2 : middleButton | 3 : rightButton) */ mBtn: function (event) { var button = event.button; if (!event.which && button !== undefined) return button & 1 ? 1 : button & 2 ? 3 : button & 4 ? 2 : 0; else return event.which; }, /** * Checks whether a item is in the given array and returns its index. * @param item The item of which the position in the array shall be determined. * @param arr The array. * @returns {number} The zero based index of the item or -1 if the item isn't in the array. */ inA: function (item, arr) { for (var i = 0; i < arr[LEXICON.l]; i++) //Sometiems in IE a "SCRIPT70" Permission denied error occurs if HTML elements in a iFrame are compared try { if (arr[i] === item) return i; } catch (e) {} return -1; }, /** * Returns true if the given value is a array. * @param arr The potential array. * @returns {boolean} True if the given value is a array, false otherwise. */ isA: function (arr) { var def = Array.isArray; return def ? def(arr) : this.type(arr) == TYPES.a; }, /** * Determine the internal JavaScript [[Class]] of the given object. * @param obj The object of which the type shall be determined. * @returns {string} The type of the given object. */ type: function (obj) { if (obj === undefined) return obj + ""; if (obj === null) return obj + ""; return Object[LEXICON.p].toString .call(obj) .replace(/^\[object (.+)\]$/, "$1") .toLowerCase(); }, bind: bind, /** * Gets the vendor-prefixed CSS property by the given name. * For example the given name is "transform" and you're using a old Firefox browser then the returned value would be "-moz-transform". * If the browser doesn't need a vendor-prefix, then the returned string is the given name. * If the browser doesn't support the given property name at all (not even with a vendor-prefix) the returned value is null. * @param propName The unprefixed CSS property name. * @returns {string|null} The vendor-prefixed CSS property or null if the browser doesn't support the given CSS property. cssProp: function(propName) { return VENDORS._cssProperty(propName); } */ }; })(); var MATH = Math; var JQUERY = window.jQuery; var EASING = (function () { var _easingsMath = { p: MATH.PI, c: MATH.cos, s: MATH.sin, w: MATH.pow, t: MATH.sqrt, n: MATH.asin, a: MATH.abs, o: 1.70158, }; /* x : current percent (0 - 1), t : current time (duration * percent), b : start value (from), c : end value (to), d : duration easingName : function(x, t, b, c, d) { return easedValue; } */ return { swing: function (x, t, b, c, d) { return 0.5 - _easingsMath.c(x * _easingsMath.p) / 2; }, linear: function (x, t, b, c, d) { return x; }, easeInQuad: function (x, t, b, c, d) { return c * (t /= d) * t + b; }, easeOutQuad: function (x, t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, easeInOutQuad: function (x, t, b, c, d) { return (t /= d / 2) < 1 ? (c / 2) * t * t + b : (-c / 2) * (--t * (t - 2) - 1) + b; }, easeInCubic: function (x, t, b, c, d) { return c * (t /= d) * t * t + b; }, easeOutCubic: function (x, t, b, c, d) { return c * ((t = t / d - 1) * t * t + 1) + b; }, easeInOutCubic: function (x, t, b, c, d) { return (t /= d / 2) < 1 ? (c / 2) * t * t * t + b : (c / 2) * ((t -= 2) * t * t + 2) + b; }, easeInQuart: function (x, t, b, c, d) { return c * (t /= d) * t * t * t + b; }, easeOutQuart: function (x, t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeInOutQuart: function (x, t, b, c, d) { return (t /= d / 2) < 1 ? (c / 2) * t * t * t * t + b : (-c / 2) * ((t -= 2) * t * t * t - 2) + b; }, easeInQuint: function (x, t, b, c, d) { return c * (t /= d) * t * t * t * t + b; }, easeOutQuint: function (x, t, b, c, d) { return c * ((t = t / d - 1) * t * t * t * t + 1) + b; }, easeInOutQuint: function (x, t, b, c, d) { return (t /= d / 2) < 1 ? (c / 2) * t * t * t * t * t + b : (c / 2) * ((t -= 2) * t * t * t * t + 2) + b; }, easeInSine: function (x, t, b, c, d) { return -c * _easingsMath.c((t / d) * (_easingsMath.p / 2)) + c + b; }, easeOutSine: function (x, t, b, c, d) { return c * _easingsMath.s((t / d) * (_easingsMath.p / 2)) + b; }, easeInOutSine: function (x, t, b, c, d) { return (-c / 2) * (_easingsMath.c((_easingsMath.p * t) / d) - 1) + b; }, easeInExpo: function (x, t, b, c, d) { return t == 0 ? b : c * _easingsMath.w(2, 10 * (t / d - 1)) + b; }, easeOutExpo: function (x, t, b, c, d) { return t == d ? b + c : c * (-_easingsMath.w(2, (-10 * t) / d) + 1) + b; }, easeInOutExpo: function (x, t, b, c, d) { if (t == 0) return b; if (t == d) return b + c; if ((t /= d / 2) < 1) return (c / 2) * _easingsMath.w(2, 10 * (t - 1)) + b; return (c / 2) * (-_easingsMath.w(2, -10 * --t) + 2) + b; }, easeInCirc: function (x, t, b, c, d) { return -c * (_easingsMath.t(1 - (t /= d) * t) - 1) + b; }, easeOutCirc: function (x, t, b, c, d) { return c * _easingsMath.t(1 - (t = t / d - 1) * t) + b; }, easeInOutCirc: function (x, t, b, c, d) { return (t /= d / 2) < 1 ? (-c / 2) * (_easingsMath.t(1 - t * t) - 1) + b : (c / 2) * (_easingsMath.t(1 - (t -= 2) * t) + 1) + b; }, easeInElastic: function (x, t, b, c, d) { var s = _easingsMath.o; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * 0.3; if (a < _easingsMath.a(c)) { a = c; s = p / 4; } else s = (p / (2 * _easingsMath.p)) * _easingsMath.n(c / a); return ( -( a * _easingsMath.w(2, 10 * (t -= 1)) * _easingsMath.s(((t * d - s) * (2 * _easingsMath.p)) / p) ) + b ); }, easeOutElastic: function (x, t, b, c, d) { var s = _easingsMath.o; var p = 0; var a = c; if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * 0.3; if (a < _easingsMath.a(c)) { a = c; s = p / 4; } else s = (p / (2 * _easingsMath.p)) * _easingsMath.n(c / a); return ( a * _easingsMath.w(2, -10 * t) * _easingsMath.s(((t * d - s) * (2 * _easingsMath.p)) / p) + c + b ); }, easeInOutElastic: function (x, t, b, c, d) { var s = _easingsMath.o; var p = 0; var a = c; if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (0.3 * 1.5); if (a < _easingsMath.a(c)) { a = c; s = p / 4; } else s = (p / (2 * _easingsMath.p)) * _easingsMath.n(c / a); if (t < 1) return ( -0.5 * (a * _easingsMath.w(2, 10 * (t -= 1)) * _easingsMath.s(((t * d - s) * (2 * _easingsMath.p)) / p)) + b ); return ( a * _easingsMath.w(2, -10 * (t -= 1)) * _easingsMath.s(((t * d - s) * (2 * _easingsMath.p)) / p) * 0.5 + c + b ); }, easeInBack: function (x, t, b, c, d, s) { s = s || _easingsMath.o; return c * (t /= d) * t * ((s + 1) * t - s) + b; }, easeOutBack: function (x, t, b, c, d, s) { s = s || _easingsMath.o; return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, easeInOutBack: function (x, t, b, c, d, s) { s = s || _easingsMath.o; return (t /= d / 2) < 1 ? (c / 2) * (t * t * (((s *= 1.525) + 1) * t - s)) + b : (c / 2) * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b; }, easeInBounce: function (x, t, b, c, d) { return c - this.easeOutBounce(x, d - t, 0, c, d) + b; }, easeOutBounce: function (x, t, b, c, d) { var o = 7.5625; if ((t /= d) < 1 / 2.75) { return c * (o * t * t) + b; } else if (t < 2 / 2.75) { return c * (o * (t -= 1.5 / 2.75) * t + 0.75) + b; } else if (t < 2.5 / 2.75) { return c * (o * (t -= 2.25 / 2.75) * t + 0.9375) + b; } else { return c * (o * (t -= 2.625 / 2.75) * t + 0.984375) + b; } }, easeInOutBounce: function (x, t, b, c, d) { return t < d / 2 ? this.easeInBounce(x, t * 2, 0, c, d) * 0.5 + b : this.easeOutBounce(x, t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; }, }; /* * * TERMS OF USE - EASING EQUATIONS * * Open source under the BSD License. * * Copyright © 2001 Robert Penner * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ })(); var FRAMEWORK = (function () { var _rnothtmlwhite = /[^\x20\t\r\n\f]+/g; var _strSpace = " "; var _strEmpty = ""; var _strScrollLeft = "scrollLeft"; var _strScrollTop = "scrollTop"; var _animations = []; var _type = COMPATIBILITY.type; var _cssNumber = { animationIterationCount: true, columnCount: true, fillOpacity: true, flexGrow: true, flexShrink: true, fontWeight: true, lineHeight: true, opacity: true, order: true, orphans: true, widows: true, zIndex: true, zoom: true, }; function extend() { var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {}, i = 1, length = arguments[LEXICON.l], deep = false; // Handle a deep copy situation if (_type(target) == TYPES.b) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if (_type(target) != TYPES.o && !_type(target) == TYPES.f) { target = {}; } // extend jQuery itself if only one argument is passed if (length === i) { target = FakejQuery; --i; } for (; i < length; i++) { // Only deal with non-null/undefined values if ((options = arguments[i]) != null) { // Extend the base object for (name in options) { src = target[name]; copy = options[name]; // Prevent never-ending loop if (target === copy) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && (isPlainObject(copy) || (copyIsArray = COMPATIBILITY.isA(copy))) ) { if (copyIsArray) { copyIsArray = false; clone = src && COMPATIBILITY.isA(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[name] = extend(deep, clone, copy); // Don't bring in undefined values } else if (copy !== undefined) { target[name] = copy; } } } } // Return the modified object return target; } function inArray(item, arr, fromIndex) { for (var i = fromIndex || 0; i < arr[LEXICON.l]; i++) if (arr[i] === item) return i; return -1; } function isFunction(obj) { return _type(obj) == TYPES.f; } function isEmptyObject(obj) { for (var name in obj) return false; return true; } function isPlainObject(obj) { if (!obj || _type(obj) != TYPES.o) return false; var key; var proto = LEXICON.p; var hasOwnProperty = Object[proto].hasOwnProperty; var hasOwnConstructor = hasOwnProperty.call(obj, "constructor"); var hasIsPrototypeOf = obj.constructor && obj.constructor[proto] && hasOwnProperty.call(obj.constructor[proto], "isPrototypeOf"); if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { return false; } for (key in obj) { /**/ } return _type(key) == TYPES.u || hasOwnProperty.call(obj, key); } function each(obj, callback) { var i = 0; if (isArrayLike(obj)) { for (; i < obj[LEXICON.l]; i++) { if (callback.call(obj[i], i, obj[i]) === false) break; } } else { for (i in obj) { if (callback.call(obj[i], i, obj[i]) === false) break; } } return obj; } function isArrayLike(obj) { var length = !!obj && [LEXICON.l] in obj && obj[LEXICON.l]; var t = _type(obj); return isFunction(t) ? false : t == TYPES.a || length === 0 || (_type(length) == TYPES.n && length > 0 && length - 1 in obj); } function stripAndCollapse(value) { var tokens = value.match(_rnothtmlwhite) || []; return tokens.join(_strSpace); } function matches(elem, selector) { var nodeList = (elem.parentNode || document).querySelectorAll(selector) || []; var i = nodeList[LEXICON.l]; while (i--) if (nodeList[i] == elem) return true; return false; } function insertAdjacentElement(el, strategy, child) { if (COMPATIBILITY.isA(child)) { for (var i = 0; i < child[LEXICON.l]; i++) insertAdjacentElement(el, strategy, child[i]); } else if (_type(child) == TYPES.s) el.insertAdjacentHTML(strategy, child); else el.insertAdjacentElement(strategy, child.nodeType ? child : child[0]); } function setCSSVal(el, prop, val) { try { if (el[LEXICON.s][prop] !== undefined) el[LEXICON.s][prop] = parseCSSVal(prop, val); } catch (e) {} } function parseCSSVal(prop, val) { if (!_cssNumber[prop.toLowerCase()] && _type(val) == TYPES.n) val += "px"; return val; } function startNextAnimationInQ(animObj, removeFromQ) { var index; var nextAnim; if (removeFromQ !== false) animObj.q.splice(0, 1); if (animObj.q[LEXICON.l] > 0) { nextAnim = animObj.q[0]; animate( animObj.el, nextAnim.props, nextAnim.duration, nextAnim.easing, nextAnim.complete, true ); } else { index = inArray(animObj, _animations); if (index > -1) _animations.splice(index, 1); } } function setAnimationValue(el, prop, value) { if (prop === _strScrollLeft || prop === _strScrollTop) el[prop] = value; else setCSSVal(el, prop, value); } function animate(el, props, options, easing, complete, guaranteedNext) { var hasOptions = isPlainObject(options); var from = {}; var to = {}; var i = 0; var key; var animObj; var start; var progress; var step; var specialEasing; var duration; if (hasOptions) { easing = options.easing; start = options.start; progress = options.progress; step = options.step; specialEasing = options.specialEasing; complete = options.complete; duration = options.duration; } else duration = options; specialEasing = specialEasing || {}; duration = duration || 400; easing = easing || "swing"; guaranteedNext = guaranteedNext || false; for (; i < _animations[LEXICON.l]; i++) { if (_animations[i].el === el) { animObj = _animations[i]; break; } } if (!animObj) { animObj = { el: el, q: [], }; _animations.push(animObj); } for (key in props) { if (key === _strScrollLeft || key === _strScrollTop) from[key] = el[key]; else from[key] = FakejQuery(el).css(key); } for (key in from) { if (from[key] !== props[key] && props[key] !== undefined) to[key] = props[key]; } if (!isEmptyObject(to)) { var timeNow; var end; var percent; var fromVal; var toVal; var easedVal; var timeStart; var frame; var elapsed; var qPos = guaranteedNext ? 0 : inArray(qObj, animObj.q); var qObj = { props: to, duration: hasOptions ? options : duration, easing: easing, complete: complete, }; if (qPos === -1) { qPos = animObj.q[LEXICON.l]; animObj.q.push(qObj); } if (qPos === 0) { if (duration > 0) { timeStart = COMPATIBILITY.now(); frame = function () { timeNow = COMPATIBILITY.now(); elapsed = timeNow - timeStart; end = qObj.stop || elapsed >= duration; percent = 1 - (MATH.max(0, timeStart + duration - timeNow) / duration || 0); for (key in to) { fromVal = parseFloat(from[key]); toVal = parseFloat(to[key]); easedVal = (toVal - fromVal) * EASING[specialEasing[key] || easing]( percent, percent * duration, 0, 1, duration ) + fromVal; setAnimationValue(el, key, easedVal); if (isFunction(step)) { step(easedVal, { elem: el, prop: key, start: fromVal, now: easedVal, end: toVal, pos: percent, options: { easing: easing, speacialEasing: specialEasing, duration: duration, complete: complete, step: step, }, startTime: timeStart, }); } } if (isFunction(progress)) progress({}, percent, MATH.max(0, duration - elapsed)); if (end) { startNextAnimationInQ(animObj); if (isFunction(complete)) complete(); } else qObj.frame = COMPATIBILITY.rAF()(frame); }; qObj.frame = COMPATIBILITY.rAF()(frame); } else { for (key in to) setAnimationValue(el, key, to[key]); startNextAnimationInQ(animObj); } } } else if (guaranteedNext) startNextAnimationInQ(animObj); } function stop(el, clearQ, jumpToEnd) { var animObj; var qObj; var key; var i = 0; for (; i < _animations[LEXICON.l]; i++) { animObj = _animations[i]; if (animObj.el === el) { if (animObj.q[LEXICON.l] > 0) { qObj = animObj.q[0]; qObj.stop = true; COMPATIBILITY.cAF()(qObj.frame); animObj.q.splice(0, 1); if (jumpToEnd) for (key in qObj.props) setAnimationValue(el, key, qObj.props[key]); if (clearQ) animObj.q = []; else startNextAnimationInQ(animObj, false); } break; } } } function elementIsVisible(el) { return !!( el[LEXICON.oW] || el[LEXICON.oH] || el.getClientRects()[LEXICON.l] ); } function FakejQuery(selector) { if (arguments[LEXICON.l] === 0) return this; var base = new FakejQuery(); var elements = selector; var i = 0; var elms; var el; if (_type(selector) == TYPES.s) { elements = []; if (selector.charAt(0) === "<") { el = document.createElement("div"); el.innerHTML = selector; elms = el.children; } else { elms = document.querySelectorAll(selector); } for (; i < elms[LEXICON.l]; i++) elements.push(elms[i]); } if (elements) { if ( _type(elements) != TYPES.s && (!isArrayLike(elements) || elements === window || elements === elements.self) ) elements = [elements]; for (i = 0; i < elements[LEXICON.l]; i++) base[i] = elements[i]; base[LEXICON.l] = elements[LEXICON.l]; } return base; } FakejQuery[LEXICON.p] = { //EVENTS: on: function (eventName, handler) { eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [ _strEmpty, ]; var eventNameLength = eventName[LEXICON.l]; var i = 0; var el; return this.each(function () { el = this; try { if (el.addEventListener) { for (; i < eventNameLength; i++) el.addEventListener(eventName[i], handler); } else if (el.detachEvent) { for (; i < eventNameLength; i++) el.attachEvent("on" + eventName[i], handler); } } catch (e) {} }); }, off: function (eventName, handler) { eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [ _strEmpty, ]; var eventNameLength = eventName[LEXICON.l]; var i = 0; var el; return this.each(function () { el = this; try { if (el.removeEventListener) { for (; i < eventNameLength; i++) el.removeEventListener(eventName[i], handler); } else if (el.detachEvent) { for (; i < eventNameLength; i++) el.detachEvent("on" + eventName[i], handler); } } catch (e) {} }); }, one: function (eventName, handler) { eventName = (eventName || _strEmpty).match(_rnothtmlwhite) || [ _strEmpty, ]; return this.each(function () { var el = FakejQuery(this); FakejQuery.each(eventName, function (i, oneEventName) { var oneHandler = function (e) { handler.call(this, e); el.off(oneEventName, oneHandler); }; el.on(oneEventName, oneHandler); }); }); }, trigger: function (eventName) { var el; var event; return this.each(function () { el = this; if (document.createEvent) { event = document.createEvent("HTMLEvents"); event.initEvent(eventName, true, false); el.dispatchEvent(event); } else { el.fireEvent("on" + eventName); } }); }, //DOM NODE INSERTING / REMOVING: append: function (child) { return this.each(function () { insertAdjacentElement(this, "beforeend", child); }); }, prepend: function (child) { return this.each(function () { insertAdjacentElement(this, "afterbegin", child); }); }, before: function (child) { return this.each(function () { insertAdjacentElement(this, "beforebegin", child); }); }, after: function (child) { return this.each(function () { insertAdjacentElement(this, "afterend", child); }); }, remove: function () { return this.each(function () { var el = this; var parentNode = el.parentNode; if (parentNode != null) parentNode.removeChild(el); }); }, unwrap: function () { var parents = []; var i; var el; var parent; this.each(function () { parent = this.parentNode; if (inArray(parent, parents) === -1) parents.push(parent); }); for (i = 0; i < parents[LEXICON.l]; i++) { el = parents[i]; parent = el.parentNode; while (el.firstChild) parent.insertBefore(el.firstChild, el); parent.removeChild(el); } return this; }, wrapAll: function (wrapperHTML) { var i; var nodes = this; var wrapper = FakejQuery(wrapperHTML)[0]; var deepest = wrapper; var parent = nodes[0].parentNode; var previousSibling = nodes[0].previousSibling; while (deepest.childNodes[LEXICON.l] > 0) deepest = deepest.childNodes[0]; for ( i = 0; nodes[LEXICON.l] - i; deepest.firstChild === nodes[0] && i++ ) deepest.appendChild(nodes[i]); var nextSibling = previousSibling ? previousSibling.nextSibling : parent.firstChild; parent.insertBefore(wrapper, nextSibling); return this; }, wrapInner: function (wrapperHTML) { return this.each(function () { var el = FakejQuery(this); var contents = el.contents(); if (contents[LEXICON.l]) contents.wrapAll(wrapperHTML); else el.append(wrapperHTML); }); }, wrap: function (wrapperHTML) { return this.each(function () { FakejQuery(this).wrapAll(wrapperHTML); }); }, //DOM NODE MANIPULATION / INFORMATION: css: function (styles, val) { var el; var key; var cptStyle; var getCptStyle = window.getComputedStyle; if (_type(styles) == TYPES.s) { if (val === undefined) { el = this[0]; cptStyle = getCptStyle ? getCptStyle(el, null) : el.currentStyle[styles]; //https://bugzilla.mozilla.org/show_bug.cgi?id=548397 can be null sometimes if iframe with display: none (firefox only!) return getCptStyle ? cptStyle != null ? cptStyle.getPropertyValue(styles) : el[LEXICON.s][styles] : cptStyle; } else { return this.each(function () { setCSSVal(this, styles, val); }); } } else { return this.each(function () { for (key in styles) setCSSVal(this, key, styles[key]); }); } }, hasClass: function (className) { var elem, i = 0; var classNamePrepared = _strSpace + className + _strSpace; var classList; while ((elem = this[i++])) { classList = elem.classList; if (classList && classList.contains(className)) return true; else if ( elem.nodeType === 1 && ( _strSpace + stripAndCollapse(elem.className + _strEmpty) + _strSpace ).indexOf(classNamePrepared) > -1 ) return true; } return false; }, addClass: function (className) { var classes; var elem; var cur; var curValue; var clazz; var finalValue; var supportClassList; var elmClassList; var i = 0; var v = 0; if (className) { classes = className.match(_rnothtmlwhite) || []; while ((elem = this[i++])) { elmClassList = elem.classList; if (supportClassList === undefined) supportClassList = elmClassList !== undefined; if (supportClassList) { while ((clazz = classes[v++])) elmClassList.add(clazz); } else { curValue = elem.className + _strEmpty; cur = elem.nodeType === 1 && _strSpace + stripAndCollapse(curValue) + _strSpace; if (cur) { while ((clazz = classes[v++])) if (cur.indexOf(_strSpace + clazz + _strSpace) < 0) cur += clazz + _strSpace; finalValue = stripAndCollapse(cur); if (curValue !== finalValue) elem.className = finalValue; } } } } return this; }, removeClass: function (className) { var classes; var elem; var cur; var curValue; var clazz; var finalValue; var supportClassList; var elmClassList; var i = 0; var v = 0; if (className) { classes = className.match(_rnothtmlwhite) || []; while ((elem = this[i++])) { elmClassList = elem.classList; if (supportClassList === undefined) supportClassList = elmClassList !== undefined; if (supportClassList) { while ((clazz = classes[v++])) elmClassList.remove(clazz); } else { curValue = elem.className + _strEmpty; cur = elem.nodeType === 1 && _strSpace + stripAndCollapse(curValue) + _strSpace; if (cur) { while ((clazz = classes[v++])) while (cur.indexOf(_strSpace + clazz + _strSpace) > -1) cur = cur.replace( _strSpace + clazz + _strSpace, _strSpace ); finalValue = stripAndCollapse(cur); if (curValue !== finalValue) elem.className = finalValue; } } } } return this; }, hide: function () { return this.each(function () { this[LEXICON.s].display = "none"; }); }, show: function () { return this.each(function () { this[LEXICON.s].display = "block"; }); }, attr: function (attrName, value) { var i = 0; var el; while ((el = this[i++])) { if (value === undefined) return el.getAttribute(attrName); el.setAttribute(attrName, value); } return this; }, removeAttr: function (attrName) { return this.each(function () { this.removeAttribute(attrName); }); }, offset: function () { var el = this[0]; var rect = el[LEXICON.bCR](); var scrollLeft = window.pageXOffset || document.documentElement[_strScrollLeft]; var scrollTop = window.pageYOffset || document.documentElement[_strScrollTop]; return { top: rect.top + scrollTop, left: rect.left + scrollLeft, }; }, position: function () { var el = this[0]; return { top: el.offsetTop, left: el.offsetLeft, }; }, scrollLeft: function (value) { var i = 0; var el; while ((el = this[i++])) { if (value === undefined) return el[_strScrollLeft]; el[_strScrollLeft] = value; } return this; }, scrollTop: function (value) { var i = 0; var el; while ((el = this[i++])) { if (value === undefined) return el[_strScrollTop]; el[_strScrollTop] = value; } return this; }, val: function (value) { var el = this[0]; if (!value) return el.value; el.value = value; return this; }, //DOM TRAVERSAL / FILTERING: first: function () { return this.eq(0); }, last: function () { return this.eq(-1); }, eq: function (index) { return FakejQuery(this[index >= 0 ? index : this[LEXICON.l] + index]); }, find: function (selector) { var children = []; var i; this.each(function () { var el = this; var ch = el.querySelectorAll(selector); for (i = 0; i < ch[LEXICON.l]; i++) children.push(ch[i]); }); return FakejQuery(children); }, children: function (selector) { var children = []; var el; var ch; var i; this.each(function () { ch = this.children; for (i = 0; i < ch[LEXICON.l]; i++) { el = ch[i]; if (selector) { if ( (el.matches && el.matches(selector)) || matches(el, selector) ) children.push(el); } else children.push(el); } }); return FakejQuery(children); }, parent: function (selector) { var parents = []; var parent; this.each(function () { parent = this.parentNode; if (selector ? FakejQuery(parent).is(selector) : true) parents.push(parent); }); return FakejQuery(parents); }, is: function (selector) { var el; var i; for (i = 0; i < this[LEXICON.l]; i++) { el = this[i]; if (selector === ":visible") return elementIsVisible(el); if (selector === ":hidden") return !elementIsVisible(el); if ((el.matches && el.matches(selector)) || matches(el, selector)) return true; } return false; }, contents: function () { var contents = []; var childs; var i; this.each(function () { childs = this.childNodes; for (i = 0; i < childs[LEXICON.l]; i++) contents.push(childs[i]); }); return FakejQuery(contents); }, each: function (callback) { return each(this, callback); }, //ANIMATION: animate: function (props, duration, easing, complete) { return this.each(function () { animate(this, props, duration, easing, complete); }); }, stop: function (clearQ, jump) { return this.each(function () { stop(this, clearQ, jump); }); }, }; extend(FakejQuery, { extend: extend, inArray: inArray, isEmptyObject: isEmptyObject, isPlainObject: isPlainObject, each: each, }); return FakejQuery; })(); var INSTANCES = (function () { var _targets = []; var _instancePropertyString = "__overlayScrollbars__"; /** * Register, unregister or get a certain (or all) instances. * Register: Pass the target and the instance. * Unregister: Pass the target and null. * Get Instance: Pass the target from which the instance shall be got. * Get Targets: Pass no arguments. * @param target The target to which the instance shall be registered / from which the instance shall be unregistered / the instance shall be got * @param instance The instance. * @returns {*|void} Returns the instance from the given target. */ return function (target, instance) { var argLen = arguments[LEXICON.l]; if (argLen < 1) { //return all targets return _targets; } else { if (instance) { //register instance target[_instancePropertyString] = instance; _targets.push(target); } else { var index = COMPATIBILITY.inA(target, _targets); if (index > -1) { if (argLen > 1) { //unregister instance delete target[_instancePropertyString]; _targets.splice(index, 1); } else { //get instance from target return _targets[index][_instancePropertyString]; } } } } }; })(); var PLUGIN = (function () { var _plugin; var _pluginsGlobals; var _pluginsAutoUpdateLoop; var _pluginsExtensions = []; var _pluginsOptions = (function () { var type = COMPATIBILITY.type; var possibleTemplateTypes = [ TYPES.b, //boolean TYPES.n, //number TYPES.s, //string TYPES.a, //array TYPES.o, //object TYPES.f, //function TYPES.z, //null ]; var restrictedStringsSplit = " "; var restrictedStringsPossibilitiesSplit = ":"; var classNameAllowedValues = [TYPES.z, TYPES.s]; var numberAllowedValues = TYPES.n; var booleanNullAllowedValues = [TYPES.z, TYPES.b]; var booleanTrueTemplate = [true, TYPES.b]; var booleanFalseTemplate = [false, TYPES.b]; var callbackTemplate = [null, [TYPES.z, TYPES.f]]; var updateOnLoadTemplate = [["img"], [TYPES.s, TYPES.a, TYPES.z]]; var inheritedAttrsTemplate = [ ["style", "class"], [TYPES.s, TYPES.a, TYPES.z], ]; var resizeAllowedValues = "n:none b:both h:horizontal v:vertical"; var overflowBehaviorAllowedValues = "v-h:visible-hidden v-s:visible-scroll s:scroll h:hidden"; var scrollbarsVisibilityAllowedValues = "v:visible h:hidden a:auto"; var scrollbarsAutoHideAllowedValues = "n:never s:scroll l:leave m:move"; var optionsDefaultsAndTemplate = { className: ["os-theme-dark", classNameAllowedValues], //null || string resize: ["none", resizeAllowedValues], //none || both || horizontal || vertical || n || b || h || v sizeAutoCapable: booleanTrueTemplate, //true || false clipAlways: booleanTrueTemplate, //true || false normalizeRTL: booleanTrueTemplate, //true || false paddingAbsolute: booleanFalseTemplate, //true || false autoUpdate: [null, booleanNullAllowedValues], //true || false || null autoUpdateInterval: [33, numberAllowedValues], //number updateOnLoad: updateOnLoadTemplate, //string || array || null nativeScrollbarsOverlaid: { showNativeScrollbars: booleanFalseTemplate, //true || false initialize: booleanTrueTemplate, //true || false }, overflowBehavior: { x: ["scroll", overflowBehaviorAllowedValues], //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s y: ["scroll", overflowBehaviorAllowedValues], //visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s }, scrollbars: { visibility: ["auto", scrollbarsVisibilityAllowedValues], //visible || hidden || auto || v || h || a autoHide: ["never", scrollbarsAutoHideAllowedValues], //never || scroll || leave || move || n || s || l || m autoHideDelay: [800, numberAllowedValues], //number dragScrolling: booleanTrueTemplate, //true || false clickScrolling: booleanFalseTemplate, //true || false touchSupport: booleanTrueTemplate, //true || false snapHandle: booleanFalseTemplate, //true || false }, textarea: { dynWidth: booleanFalseTemplate, //true || false dynHeight: booleanFalseTemplate, //true || false inheritedAttrs: inheritedAttrsTemplate, //string || array || null }, callbacks: { onInitialized: callbackTemplate, //null || function onInitializationWithdrawn: callbackTemplate, //null || function onDestroyed: callbackTemplate, //null || function onScrollStart: callbackTemplate, //null || function onScroll: callbackTemplate, //null || function onScrollStop: callbackTemplate, //null || function onOverflowChanged: callbackTemplate, //null || function onOverflowAmountChanged: callbackTemplate, //null || function onDirectionChanged: callbackTemplate, //null || function onContentSizeChanged: callbackTemplate, //null || function onHostSizeChanged: callbackTemplate, //null || function onUpdated: callbackTemplate, //null || function }, }; var convert = function (template) { var recursive = function (obj) { var key; var val; var valType; for (key in obj) { if (!obj[LEXICON.hOP](key)) continue; val = obj[key]; valType = type(val); if (valType == TYPES.a) obj[key] = val[template ? 1 : 0]; else if (valType == TYPES.o) obj[key] = recursive(val); } return obj; }; return recursive( FRAMEWORK.extend(true, {}, optionsDefaultsAndTemplate) ); }; return { _defaults: convert(), _template: convert(true), /** * Validates the passed object by the passed template. * @param obj The object which shall be validated. * @param template The template which defines the allowed values and types. * @param writeErrors True if errors shall be logged to the console. * @param diffObj If a object is passed then only valid differences to this object will be returned. * @returns {{}} A object which contains two objects called "default" and "prepared" which contains only the valid properties of the passed original object and discards not different values compared to the passed diffObj. */ _validate: function (obj, template, writeErrors, diffObj) { var validatedOptions = {}; var validatedOptionsPrepared = {}; var objectCopy = FRAMEWORK.extend(true, {}, obj); var inArray = FRAMEWORK.inArray; var isEmptyObj = FRAMEWORK.isEmptyObject; var checkObjectProps = function ( data, template, diffData, validatedOptions, validatedOptionsPrepared, prevPropName ) { for (var prop in template) { if (template[LEXICON.hOP](prop) && data[LEXICON.hOP](prop)) { var isValid = false; var isDiff = false; var templateValue = template[prop]; var templateValueType = type(templateValue); var templateIsComplex = templateValueType == TYPES.o; var templateTypes = !COMPATIBILITY.isA(templateValue) ? [templateValue] : templateValue; var dataDiffValue = diffData[prop]; var dataValue = data[prop]; var dataValueType = type(dataValue); var propPrefix = prevPropName ? prevPropName + "." : ""; var error = 'The option "' + propPrefix + prop + "\" wasn't set, because"; var errorPossibleTypes = []; var errorRestrictedStrings = []; var restrictedStringValuesSplit; var restrictedStringValuesPossibilitiesSplit; var isRestrictedValue; var mainPossibility; var currType; var i; var v; var j; dataDiffValue = dataDiffValue === undefined ? {} : dataDiffValue; //if the template has a object as value, it means that the options are complex (verschachtelt) if (templateIsComplex && dataValueType == TYPES.o) { validatedOptions[prop] = {}; validatedOptionsPrepared[prop] = {}; checkObjectProps( dataValue, templateValue, dataDiffValue, validatedOptions[prop], validatedOptionsPrepared[prop], propPrefix + prop ); FRAMEWORK.each( [data, validatedOptions, validatedOptionsPrepared], function (index, value) { if (isEmptyObj(value[prop])) { delete value[prop]; } } ); } else if (!templateIsComplex) { for (i = 0; i < templateTypes[LEXICON.l]; i++) { currType = templateTypes[i]; templateValueType = type(currType); //if currtype is string and starts with restrictedStringPrefix and end with restrictedStringSuffix isRestrictedValue = templateValueType == TYPES.s && inArray(currType, possibleTemplateTypes) === -1; if (isRestrictedValue) { errorPossibleTypes.push(TYPES.s); //split it into a array which contains all possible values for example: ["y:yes", "n:no", "m:maybe"] restrictedStringValuesSplit = currType.split( restrictedStringsSplit ); errorRestrictedStrings = errorRestrictedStrings.concat( restrictedStringValuesSplit ); for ( v = 0; v < restrictedStringValuesSplit[LEXICON.l]; v++ ) { //split the possible values into their possibiliteis for example: ["y", "yes"] -> the first is always the mainPossibility restrictedStringValuesPossibilitiesSplit = restrictedStringValuesSplit[v].split( restrictedStringsPossibilitiesSplit ); mainPossibility = restrictedStringValuesPossibilitiesSplit[0]; for ( j = 0; j < restrictedStringValuesPossibilitiesSplit[LEXICON.l]; j++ ) { //if any possibility matches with the dataValue, its valid if ( dataValue === restrictedStringValuesPossibilitiesSplit[j] ) { isValid = true; break; } } if (isValid) break; } } else { errorPossibleTypes.push(currType); if (dataValueType === currType) { isValid = true; break; } } } if (isValid) { isDiff = dataValue !== dataDiffValue; if (isDiff) validatedOptions[prop] = dataValue; if ( isRestrictedValue ? inArray( dataDiffValue, restrictedStringValuesPossibilitiesSplit ) < 0 : isDiff ) validatedOptionsPrepared[prop] = isRestrictedValue ? mainPossibility : dataValue; } else if (writeErrors) { console.warn( error + " it doesn't accept the type [ " + dataValueType.toUpperCase() + ' ] with the value of "' + dataValue + '".\r\n' + "Accepted types are: [ " + errorPossibleTypes.join(", ").toUpperCase() + " ]." + (errorRestrictedStrings[length] > 0 ? "\r\nValid strings are: [ " + errorRestrictedStrings .join(", ") .split(restrictedStringsPossibilitiesSplit) .join(", ") + " ]." : "") ); } delete data[prop]; } } } }; checkObjectProps( objectCopy, template, diffObj || {}, validatedOptions, validatedOptionsPrepared ); //add values which aren't specified in the template to the finished validated object to prevent them from being discarded /* if(keepForeignProps) { FRAMEWORK.extend(true, validatedOptions, objectCopy); FRAMEWORK.extend(true, validatedOptionsPrepared, objectCopy); } */ if (!isEmptyObj(objectCopy) && writeErrors) console.warn( "The following options are discarded due to invalidity:\r\n" + window.JSON.stringify(objectCopy, null, 2) ); return { _default: validatedOptions, _prepared: validatedOptionsPrepared, }; }, }; })(); /** * Initializes the object which contains global information about the plugin and each instance of it. */ function initOverlayScrollbarsStatics() { if (!_pluginsGlobals) _pluginsGlobals = new OverlayScrollbarsGlobals( _pluginsOptions._defaults ); if (!_pluginsAutoUpdateLoop) _pluginsAutoUpdateLoop = new OverlayScrollbarsAutoUpdateLoop( _pluginsGlobals ); } /** * The global object for the OverlayScrollbars objects. It contains resources which every OverlayScrollbars object needs. This object is initialized only once: if the first OverlayScrollbars object gets initialized. * @param defaultOptions * @constructor */ function OverlayScrollbarsGlobals(defaultOptions) { var _base = this; var strOverflow = "overflow"; var strHidden = "hidden"; var strScroll = "scroll"; var bodyElement = FRAMEWORK("body"); var scrollbarDummyElement = FRAMEWORK( '
' ); var scrollbarDummyElement0 = scrollbarDummyElement[0]; var dummyContainerChild = FRAMEWORK( scrollbarDummyElement.children("div").eq(0) ); bodyElement.append(scrollbarDummyElement); scrollbarDummyElement.hide().show(); //fix IE8 bug (incorrect measuring) var nativeScrollbarSize = calcNativeScrollbarSize( scrollbarDummyElement0 ); var nativeScrollbarIsOverlaid = { x: nativeScrollbarSize.x === 0, y: nativeScrollbarSize.y === 0, }; var msie = (function () { var ua = window.navigator.userAgent; var strIndexOf = "indexOf"; var strSubString = "substring"; var msie = ua[strIndexOf]("MSIE "); var trident = ua[strIndexOf]("Trident/"); var edge = ua[strIndexOf]("Edge/"); var rv = ua[strIndexOf]("rv:"); var result; var parseIntFunc = parseInt; // IE 10 or older => return version number if (msie > 0) result = parseIntFunc( ua[strSubString](msie + 5, ua[strIndexOf](".", msie)), 10 ); // IE 11 => return version number else if (trident > 0) result = parseIntFunc( ua[strSubString](rv + 3, ua[strIndexOf](".", rv)), 10 ); // Edge (IE 12+) => return version number else if (edge > 0) result = parseIntFunc( ua[strSubString](edge + 5, ua[strIndexOf](".", edge)), 10 ); // other browser return result; })(); FRAMEWORK.extend(_base, { defaultOptions: defaultOptions, msie: msie, autoUpdateLoop: false, autoUpdateRecommended: !COMPATIBILITY.mO(), nativeScrollbarSize: nativeScrollbarSize, nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid, nativeScrollbarStyling: (function () { var result = false; scrollbarDummyElement.addClass( "os-viewport-native-scrollbars-invisible" ); try { result = (scrollbarDummyElement.css("scrollbar-width") === "none" && (msie > 9 || !msie)) || window .getComputedStyle( scrollbarDummyElement0, "::-webkit-scrollbar" ) .getPropertyValue("display") === "none"; } catch (ex) {} //fix opera bug: scrollbar styles will only appear if overflow value is scroll or auto during the activation of the style. //and set overflow to scroll //scrollbarDummyElement.css(strOverflow, strHidden).hide().css(strOverflow, strScroll).show(); //return (scrollbarDummyElement0[LEXICON.oH] - scrollbarDummyElement0[LEXICON.cH]) === 0 && (scrollbarDummyElement0[LEXICON.oW] - scrollbarDummyElement0[LEXICON.cW]) === 0; return result; })(), overlayScrollbarDummySize: { x: 30, y: 30 }, cssCalc: VENDORS._cssPropertyValue("width", "calc", "(1px)") || null, restrictedMeasuring: (function () { //https://bugzilla.mozilla.org/show_bug.cgi?id=1439305 //since 1.11.0 always false -> fixed via CSS (hopefully) scrollbarDummyElement.css(strOverflow, strHidden); var scrollSize = { w: scrollbarDummyElement0[LEXICON.sW], h: scrollbarDummyElement0[LEXICON.sH], }; scrollbarDummyElement.css(strOverflow, "visible"); var scrollSize2 = { w: scrollbarDummyElement0[LEXICON.sW], h: scrollbarDummyElement0[LEXICON.sH], }; return ( scrollSize.w - scrollSize2.w !== 0 || scrollSize.h - scrollSize2.h !== 0 ); })(), rtlScrollBehavior: (function () { scrollbarDummyElement .css({ "overflow-y": strHidden, "overflow-x": strScroll, direction: "rtl", }) .scrollLeft(0); var dummyContainerOffset = scrollbarDummyElement.offset(); var dummyContainerChildOffset = dummyContainerChild.offset(); //https://github.com/KingSora/OverlayScrollbars/issues/187 scrollbarDummyElement.scrollLeft(-999); var dummyContainerChildOffsetAfterScroll = dummyContainerChild.offset(); return { //origin direction = determines if the zero scroll position is on the left or right side //'i' means 'invert' (i === true means that the axis must be inverted to be correct) //true = on the left side //false = on the right side i: dummyContainerOffset.left === dummyContainerChildOffset.left, //negative = determines if the maximum scroll is positive or negative //'n' means 'negate' (n === true means that the axis must be negated to be correct) //true = negative //false = positive n: dummyContainerChildOffset.left !== dummyContainerChildOffsetAfterScroll.left, }; })(), supportTransform: !!VENDORS._cssProperty("transform"), supportTransition: !!VENDORS._cssProperty("transition"), supportPassiveEvents: (function () { var supportsPassive = false; try { window.addEventListener( "test", null, Object.defineProperty({}, "passive", { get: function () { supportsPassive = true; }, }) ); } catch (e) {} return supportsPassive; })(), supportResizeObserver: !!COMPATIBILITY.rO(), supportMutationObserver: !!COMPATIBILITY.mO(), }); scrollbarDummyElement.removeAttr(LEXICON.s).remove(); //Catch zoom event: (function () { if (nativeScrollbarIsOverlaid.x && nativeScrollbarIsOverlaid.y) return; var abs = MATH.abs; var windowWidth = COMPATIBILITY.wW(); var windowHeight = COMPATIBILITY.wH(); var windowDpr = getWindowDPR(); var onResize = function () { if (INSTANCES().length > 0) { var newW = COMPATIBILITY.wW(); var newH = COMPATIBILITY.wH(); var deltaW = newW - windowWidth; var deltaH = newH - windowHeight; if (deltaW === 0 && deltaH === 0) return; var deltaWRatio = MATH.round(newW / (windowWidth / 100.0)); var deltaHRatio = MATH.round(newH / (windowHeight / 100.0)); var absDeltaW = abs(deltaW); var absDeltaH = abs(deltaH); var absDeltaWRatio = abs(deltaWRatio); var absDeltaHRatio = abs(deltaHRatio); var newDPR = getWindowDPR(); var deltaIsBigger = absDeltaW > 2 && absDeltaH > 2; var difference = !differenceIsBiggerThanOne( absDeltaWRatio, absDeltaHRatio ); var dprChanged = newDPR !== windowDpr && windowDpr > 0; var isZoom = deltaIsBigger && difference && dprChanged; var oldScrollbarSize = _base.nativeScrollbarSize; var newScrollbarSize; if (isZoom) { bodyElement.append(scrollbarDummyElement); newScrollbarSize = _base.nativeScrollbarSize = calcNativeScrollbarSize(scrollbarDummyElement[0]); scrollbarDummyElement.remove(); if ( oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y ) { FRAMEWORK.each(INSTANCES(), function () { if (INSTANCES(this)) INSTANCES(this).update("zoom"); }); } } windowWidth = newW; windowHeight = newH; windowDpr = newDPR; } }; function differenceIsBiggerThanOne(valOne, valTwo) { var absValOne = abs(valOne); var absValTwo = abs(valTwo); return !( absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo ); } function getWindowDPR() { var dDPI = window.screen.deviceXDPI || 0; var sDPI = window.screen.logicalXDPI || 1; return window.devicePixelRatio || dDPI / sDPI; } FRAMEWORK(window).on("resize", onResize); })(); function calcNativeScrollbarSize(measureElement) { return { x: measureElement[LEXICON.oH] - measureElement[LEXICON.cH], y: measureElement[LEXICON.oW] - measureElement[LEXICON.cW], }; } } /** * The object which manages the auto update loop for all OverlayScrollbars objects. This object is initialized only once: if the first OverlayScrollbars object gets initialized. * @constructor */ function OverlayScrollbarsAutoUpdateLoop(globals) { var _base = this; var _inArray = FRAMEWORK.inArray; var _getNow = COMPATIBILITY.now; var _strAutoUpdate = "autoUpdate"; var _strAutoUpdateInterval = _strAutoUpdate + "Interval"; var _strLength = LEXICON.l; var _loopingInstances = []; var _loopingInstancesIntervalCache = []; var _loopIsActive = false; var _loopIntervalDefault = 33; var _loopInterval = _loopIntervalDefault; var _loopTimeOld = _getNow(); var _loopID; /** * The auto update loop which will run every 50 milliseconds or less if the update interval of a instance is lower than 50 milliseconds. */ var loop = function () { if (_loopingInstances[_strLength] > 0 && _loopIsActive) { _loopID = COMPATIBILITY.rAF()(function () { loop(); }); var timeNew = _getNow(); var timeDelta = timeNew - _loopTimeOld; var lowestInterval; var instance; var instanceOptions; var instanceAutoUpdateAllowed; var instanceAutoUpdateInterval; var now; if (timeDelta > _loopInterval) { _loopTimeOld = timeNew - (timeDelta % _loopInterval); lowestInterval = _loopIntervalDefault; for (var i = 0; i < _loopingInstances[_strLength]; i++) { instance = _loopingInstances[i]; if (instance !== undefined) { instanceOptions = instance.options(); instanceAutoUpdateAllowed = instanceOptions[_strAutoUpdate]; instanceAutoUpdateInterval = MATH.max( 1, instanceOptions[_strAutoUpdateInterval] ); now = _getNow(); if ( (instanceAutoUpdateAllowed === true || instanceAutoUpdateAllowed === null) && now - _loopingInstancesIntervalCache[i] > instanceAutoUpdateInterval ) { instance.update("auto"); _loopingInstancesIntervalCache[i] = new Date( (now += instanceAutoUpdateInterval) ); } lowestInterval = MATH.max( 1, MATH.min(lowestInterval, instanceAutoUpdateInterval) ); } } _loopInterval = lowestInterval; } } else { _loopInterval = _loopIntervalDefault; } }; /** * Add OverlayScrollbars instance to the auto update loop. Only successful if the instance isn't already added. * @param instance The instance which shall be updated in a loop automatically. */ _base.add = function (instance) { if (_inArray(instance, _loopingInstances) === -1) { _loopingInstances.push(instance); _loopingInstancesIntervalCache.push(_getNow()); if (_loopingInstances[_strLength] > 0 && !_loopIsActive) { _loopIsActive = true; globals.autoUpdateLoop = _loopIsActive; loop(); } } }; /** * Remove OverlayScrollbars instance from the auto update loop. Only successful if the instance was added before. * @param instance The instance which shall be updated in a loop automatically. */ _base.remove = function (instance) { var index = _inArray(instance, _loopingInstances); if (index > -1) { //remove from loopingInstances list _loopingInstancesIntervalCache.splice(index, 1); _loopingInstances.splice(index, 1); //correct update loop behavior if (_loopingInstances[_strLength] === 0 && _loopIsActive) { _loopIsActive = false; globals.autoUpdateLoop = _loopIsActive; if (_loopID !== undefined) { COMPATIBILITY.cAF()(_loopID); _loopID = -1; } } } }; } /** * A object which manages the scrollbars visibility of the target element. * @param pluginTargetElement The element from which the scrollbars shall be hidden. * @param options The custom options. * @param extensions The custom extensions. * @param globals * @param autoUpdateLoop * @returns {*} * @constructor */ function OverlayScrollbarsInstance( pluginTargetElement, options, extensions, globals, autoUpdateLoop ) { //shortcuts var type = COMPATIBILITY.type; var inArray = FRAMEWORK.inArray; var each = FRAMEWORK.each; //make correct instanceof var _base = new _plugin(); var _frameworkProto = FRAMEWORK[LEXICON.p]; //if passed element is no HTML element: skip and return if (!isHTMLElement(pluginTargetElement)) return; //if passed element is already initialized: set passed options if there are any and return its instance if (INSTANCES(pluginTargetElement)) { var inst = INSTANCES(pluginTargetElement); inst.options(options); return inst; } //globals: var _nativeScrollbarIsOverlaid; var _overlayScrollbarDummySize; var _rtlScrollBehavior; var _autoUpdateRecommended; var _msieVersion; var _nativeScrollbarStyling; var _cssCalc; var _nativeScrollbarSize; var _supportTransition; var _supportTransform; var _supportPassiveEvents; var _supportResizeObserver; var _supportMutationObserver; var _restrictedMeasuring; //general readonly: var _initialized; var _destroyed; var _isTextarea; var _isBody; var _documentMixed; var _domExists; //general: var _isBorderBox; var _sizeAutoObserverAdded; var _paddingX; var _paddingY; var _borderX; var _borderY; var _marginX; var _marginY; var _isRTL; var _sleeping; var _contentBorderSize = {}; var _scrollHorizontalInfo = {}; var _scrollVerticalInfo = {}; var _viewportSize = {}; var _nativeScrollbarMinSize = {}; //naming: var _strMinusHidden = "-hidden"; var _strMarginMinus = "margin-"; var _strPaddingMinus = "padding-"; var _strBorderMinus = "border-"; var _strTop = "top"; var _strRight = "right"; var _strBottom = "bottom"; var _strLeft = "left"; var _strMinMinus = "min-"; var _strMaxMinus = "max-"; var _strWidth = "width"; var _strHeight = "height"; var _strFloat = "float"; var _strEmpty = ""; var _strAuto = "auto"; var _strSync = "sync"; var _strScroll = "scroll"; var _strHundredPercent = "100%"; var _strX = "x"; var _strY = "y"; var _strDot = "."; var _strSpace = " "; var _strScrollbar = "scrollbar"; var _strMinusHorizontal = "-horizontal"; var _strMinusVertical = "-vertical"; var _strScrollLeft = _strScroll + "Left"; var _strScrollTop = _strScroll + "Top"; var _strMouseTouchDownEvent = "mousedown touchstart"; var _strMouseTouchUpEvent = "mouseup touchend touchcancel"; var _strMouseTouchMoveEvent = "mousemove touchmove"; var _strMouseEnter = "mouseenter"; var _strMouseLeave = "mouseleave"; var _strKeyDownEvent = "keydown"; var _strKeyUpEvent = "keyup"; var _strSelectStartEvent = "selectstart"; var _strTransitionEndEvent = "transitionend webkitTransitionEnd oTransitionEnd"; var _strResizeObserverProperty = "__overlayScrollbarsRO__"; //class names: var _cassNamesPrefix = "os-"; var _classNameHTMLElement = _cassNamesPrefix + "html"; var _classNameHostElement = _cassNamesPrefix + "host"; var _classNameHostElementForeign = _classNameHostElement + "-foreign"; var _classNameHostTextareaElement = _classNameHostElement + "-textarea"; var _classNameHostScrollbarHorizontalHidden = _classNameHostElement + "-" + _strScrollbar + _strMinusHorizontal + _strMinusHidden; var _classNameHostScrollbarVerticalHidden = _classNameHostElement + "-" + _strScrollbar + _strMinusVertical + _strMinusHidden; var _classNameHostTransition = _classNameHostElement + "-transition"; var _classNameHostRTL = _classNameHostElement + "-rtl"; var _classNameHostResizeDisabled = _classNameHostElement + "-resize-disabled"; var _classNameHostScrolling = _classNameHostElement + "-scrolling"; var _classNameHostOverflow = _classNameHostElement + "-overflow"; var _classNameHostOverflow = _classNameHostElement + "-overflow"; var _classNameHostOverflowX = _classNameHostOverflow + "-x"; var _classNameHostOverflowY = _classNameHostOverflow + "-y"; var _classNameTextareaElement = _cassNamesPrefix + "textarea"; var _classNameTextareaCoverElement = _classNameTextareaElement + "-cover"; var _classNamePaddingElement = _cassNamesPrefix + "padding"; var _classNameViewportElement = _cassNamesPrefix + "viewport"; var _classNameViewportNativeScrollbarsInvisible = _classNameViewportElement + "-native-scrollbars-invisible"; var _classNameViewportNativeScrollbarsOverlaid = _classNameViewportElement + "-native-scrollbars-overlaid"; var _classNameContentElement = _cassNamesPrefix + "content"; var _classNameContentArrangeElement = _cassNamesPrefix + "content-arrange"; var _classNameContentGlueElement = _cassNamesPrefix + "content-glue"; var _classNameSizeAutoObserverElement = _cassNamesPrefix + "size-auto-observer"; var _classNameResizeObserverElement = _cassNamesPrefix + "resize-observer"; var _classNameResizeObserverItemElement = _cassNamesPrefix + "resize-observer-item"; var _classNameResizeObserverItemFinalElement = _classNameResizeObserverItemElement + "-final"; var _classNameTextInherit = _cassNamesPrefix + "text-inherit"; var _classNameScrollbar = _cassNamesPrefix + _strScrollbar; var _classNameScrollbarTrack = _classNameScrollbar + "-track"; var _classNameScrollbarTrackOff = _classNameScrollbarTrack + "-off"; var _classNameScrollbarHandle = _classNameScrollbar + "-handle"; var _classNameScrollbarHandleOff = _classNameScrollbarHandle + "-off"; var _classNameScrollbarUnusable = _classNameScrollbar + "-unusable"; var _classNameScrollbarAutoHidden = _classNameScrollbar + "-" + _strAuto + _strMinusHidden; var _classNameScrollbarCorner = _classNameScrollbar + "-corner"; var _classNameScrollbarCornerResize = _classNameScrollbarCorner + "-resize"; var _classNameScrollbarCornerResizeB = _classNameScrollbarCornerResize + "-both"; var _classNameScrollbarCornerResizeH = _classNameScrollbarCornerResize + _strMinusHorizontal; var _classNameScrollbarCornerResizeV = _classNameScrollbarCornerResize + _strMinusVertical; var _classNameScrollbarHorizontal = _classNameScrollbar + _strMinusHorizontal; var _classNameScrollbarVertical = _classNameScrollbar + _strMinusVertical; var _classNameDragging = _cassNamesPrefix + "dragging"; var _classNameThemeNone = _cassNamesPrefix + "theme-none"; var _classNamesDynamicDestroy = [ _classNameViewportNativeScrollbarsInvisible, _classNameViewportNativeScrollbarsOverlaid, _classNameScrollbarTrackOff, _classNameScrollbarHandleOff, _classNameScrollbarUnusable, _classNameScrollbarAutoHidden, _classNameScrollbarCornerResize, _classNameScrollbarCornerResizeB, _classNameScrollbarCornerResizeH, _classNameScrollbarCornerResizeV, _classNameDragging, ].join(_strSpace); //callbacks: var _callbacksInitQeueue = []; //attrs viewport shall inherit from target var _viewportAttrsFromTarget = [LEXICON.ti]; //options: var _defaultOptions; var _currentOptions; var _currentPreparedOptions; //extensions: var _extensions = {}; var _extensionsPrivateMethods = "added removed on contract"; //update var _lastUpdateTime; var _swallowedUpdateHints = {}; var _swallowedUpdateTimeout; var _swallowUpdateLag = 42; var _updateOnLoadEventName = "load"; var _updateOnLoadElms = []; //DOM elements: var _windowElement; var _documentElement; var _htmlElement; var _bodyElement; var _targetElement; //the target element of this OverlayScrollbars object var _hostElement; //the host element of this OverlayScrollbars object -> may be the same as targetElement var _sizeAutoObserverElement; //observes size auto changes var _sizeObserverElement; //observes size and padding changes var _paddingElement; //manages the padding var _viewportElement; //is the viewport of our scrollbar model var _contentElement; //the element which holds the content var _contentArrangeElement; //is needed for correct sizing of the content element (only if native scrollbars are overlays) var _contentGlueElement; //has always the size of the content element var _textareaCoverElement; //only applied if target is a textarea element. Used for correct size calculation and for prevention of uncontrolled scrolling var _scrollbarCornerElement; var _scrollbarHorizontalElement; var _scrollbarHorizontalTrackElement; var _scrollbarHorizontalHandleElement; var _scrollbarVerticalElement; var _scrollbarVerticalTrackElement; var _scrollbarVerticalHandleElement; var _windowElementNative; var _documentElementNative; var _targetElementNative; var _hostElementNative; var _sizeAutoObserverElementNative; var _sizeObserverElementNative; var _paddingElementNative; var _viewportElementNative; var _contentElementNative; //Cache: var _hostSizeCache; var _contentScrollSizeCache; var _arrangeContentSizeCache; var _hasOverflowCache; var _hideOverflowCache; var _widthAutoCache; var _heightAutoCache; var _cssBoxSizingCache; var _cssPaddingCache; var _cssBorderCache; var _cssMarginCache; var _cssDirectionCache; var _cssDirectionDetectedCache; var _paddingAbsoluteCache; var _clipAlwaysCache; var _contentGlueSizeCache; var _overflowBehaviorCache; var _overflowAmountCache; var _ignoreOverlayScrollbarHidingCache; var _autoUpdateCache; var _sizeAutoCapableCache; var _contentElementScrollSizeChangeDetectedCache; var _hostElementSizeChangeDetectedCache; var _scrollbarsVisibilityCache; var _scrollbarsAutoHideCache; var _scrollbarsClickScrollingCache; var _scrollbarsDragScrollingCache; var _resizeCache; var _normalizeRTLCache; var _classNameCache; var _oldClassName; var _textareaAutoWrappingCache; var _textareaInfoCache; var _textareaSizeCache; var _textareaDynHeightCache; var _textareaDynWidthCache; var _bodyMinSizeCache; var _updateAutoCache = {}; //MutationObserver: var _mutationObserverHost; var _mutationObserverContent; var _mutationObserverHostCallback; var _mutationObserverContentCallback; var _mutationObserversConnected; var _mutationObserverAttrsTextarea = ["wrap", "cols", "rows"]; var _mutationObserverAttrsHost = [ LEXICON.i, LEXICON.c, LEXICON.s, "open", ].concat(_viewportAttrsFromTarget); //events: var _destroyEvents = []; //textarea: var _textareaHasFocus; //scrollbars: var _scrollbarsAutoHideTimeoutId; var _scrollbarsAutoHideMoveTimeoutId; var _scrollbarsAutoHideDelay; var _scrollbarsAutoHideNever; var _scrollbarsAutoHideScroll; var _scrollbarsAutoHideMove; var _scrollbarsAutoHideLeave; var _scrollbarsHandleHovered; var _scrollbarsHandlesDefineScrollPos; //resize var _resizeNone; var _resizeBoth; var _resizeHorizontal; var _resizeVertical; //==== Event Listener ====// /** * Adds or removes a event listener from the given element. * @param element The element to which the event listener shall be applied or removed. * @param eventNames The name(s) of the events. * @param listener The method which shall be called. * @param remove True if the handler shall be removed, false or undefined if the handler shall be added. * @param passiveOrOptions The options for the event. */ function setupResponsiveEventListener( element, eventNames, listener, remove, passiveOrOptions ) { var collected = COMPATIBILITY.isA(eventNames) && COMPATIBILITY.isA(listener); var method = remove ? "removeEventListener" : "addEventListener"; var onOff = remove ? "off" : "on"; var events = collected ? false : eventNames.split(_strSpace); var i = 0; var passiveOrOptionsIsObj = FRAMEWORK.isPlainObject(passiveOrOptions); var passive = (_supportPassiveEvents && (passiveOrOptionsIsObj ? passiveOrOptions._passive : passiveOrOptions)) || false; var capture = passiveOrOptionsIsObj && (passiveOrOptions._capture || false); var nativeParam = _supportPassiveEvents ? { passive: passive, capture: capture, } : capture; if (collected) { for (; i < eventNames[LEXICON.l]; i++) setupResponsiveEventListener( element, eventNames[i], listener[i], remove, passiveOrOptions ); } else { for (; i < events[LEXICON.l]; i++) { if (_supportPassiveEvents) { element[0][method](events[i], listener, nativeParam); } else { element[onOff](events[i], listener); } } } } function addDestroyEventListener( element, eventNames, listener, passive ) { setupResponsiveEventListener( element, eventNames, listener, false, passive ); _destroyEvents.push( COMPATIBILITY.bind( setupResponsiveEventListener, 0, element, eventNames, listener, true, passive ) ); } //==== Resize Observer ====// /** * Adds or removes a resize observer from the given element. * @param targetElement The element to which the resize observer shall be added or removed. * @param onElementResizedCallback The callback which is fired every time the resize observer registers a size change or false / undefined if the resizeObserver shall be removed. */ function setupResizeObserver(targetElement, onElementResizedCallback) { if (targetElement) { var resizeObserver = COMPATIBILITY.rO(); var strAnimationStartEvent = "animationstart mozAnimationStart webkitAnimationStart MSAnimationStart"; var strChildNodes = "childNodes"; var constScroll = 3333333; var callback = function () { targetElement[_strScrollTop](constScroll)[_strScrollLeft]( _isRTL ? _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll : constScroll ); onElementResizedCallback(); }; //add resize observer: if (onElementResizedCallback) { if (_supportResizeObserver) { var element = targetElement .addClass("observed") .append(generateDiv(_classNameResizeObserverElement)) .contents()[0]; var observer = (element[_strResizeObserverProperty] = new resizeObserver(callback)); observer.observe(element); } else { if (_msieVersion > 9 || !_autoUpdateRecommended) { targetElement.prepend( generateDiv( _classNameResizeObserverElement, generateDiv( { c: _classNameResizeObserverItemElement, dir: "ltr" }, generateDiv( _classNameResizeObserverItemElement, generateDiv(_classNameResizeObserverItemFinalElement) ) + generateDiv( _classNameResizeObserverItemElement, generateDiv({ c: _classNameResizeObserverItemFinalElement, style: "width: 200%; height: 200%", }) ) ) ) ); var observerElement = targetElement[0][strChildNodes][0][strChildNodes][0]; var shrinkElement = FRAMEWORK( observerElement[strChildNodes][1] ); var expandElement = FRAMEWORK( observerElement[strChildNodes][0] ); var expandElementChild = FRAMEWORK( expandElement[0][strChildNodes][0] ); var widthCache = observerElement[LEXICON.oW]; var heightCache = observerElement[LEXICON.oH]; var isDirty; var rAFId; var currWidth; var currHeight; var factor = 2; var nativeScrollbarSize = globals.nativeScrollbarSize; //care don't make changes to this object!!! var reset = function () { /* var sizeResetWidth = observerElement[LEXICON.oW] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; var sizeResetHeight = observerElement[LEXICON.oH] + nativeScrollbarSize.x * factor + nativeScrollbarSize.y * factor + _overlayScrollbarDummySize.x + _overlayScrollbarDummySize.y; var expandChildCSS = {}; expandChildCSS[_strWidth] = sizeResetWidth; expandChildCSS[_strHeight] = sizeResetHeight; expandElementChild.css(expandChildCSS); expandElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); shrinkElement[_strScrollLeft](sizeResetWidth)[_strScrollTop](sizeResetHeight); */ expandElement[_strScrollLeft](constScroll)[_strScrollTop]( constScroll ); shrinkElement[_strScrollLeft](constScroll)[_strScrollTop]( constScroll ); }; var onResized = function () { rAFId = 0; if (!isDirty) return; widthCache = currWidth; heightCache = currHeight; callback(); }; var onScroll = function (event) { currWidth = observerElement[LEXICON.oW]; currHeight = observerElement[LEXICON.oH]; isDirty = currWidth != widthCache || currHeight != heightCache; if (event && isDirty && !rAFId) { COMPATIBILITY.cAF()(rAFId); rAFId = COMPATIBILITY.rAF()(onResized); } else if (!event) onResized(); reset(); if (event) { COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); } return false; }; var expandChildCSS = {}; var observerElementCSS = {}; setTopRightBottomLeft(observerElementCSS, _strEmpty, [ -((nativeScrollbarSize.y + 1) * factor), nativeScrollbarSize.x * -factor, nativeScrollbarSize.y * -factor, -((nativeScrollbarSize.x + 1) * factor), ]); FRAMEWORK(observerElement).css(observerElementCSS); expandElement.on(_strScroll, onScroll); shrinkElement.on(_strScroll, onScroll); targetElement.on(strAnimationStartEvent, function () { onScroll(false); }); //lets assume that the divs will never be that large and a constant value is enough expandChildCSS[_strWidth] = constScroll; expandChildCSS[_strHeight] = constScroll; expandElementChild.css(expandChildCSS); reset(); } else { var attachEvent = _documentElementNative.attachEvent; var isIE = _msieVersion !== undefined; if (attachEvent) { targetElement.prepend( generateDiv(_classNameResizeObserverElement) ); findFirst( targetElement, _strDot + _classNameResizeObserverElement )[0].attachEvent("onresize", callback); } else { var obj = _documentElementNative.createElement(TYPES.o); obj.setAttribute(LEXICON.ti, "-1"); obj.setAttribute( LEXICON.c, _classNameResizeObserverElement ); obj.onload = function () { var wnd = this.contentDocument.defaultView; wnd.addEventListener("resize", callback); wnd.document.documentElement.style.display = "none"; }; obj.type = "text/html"; if (isIE) targetElement.prepend(obj); obj.data = "about:blank"; if (!isIE) targetElement.prepend(obj); targetElement.on(strAnimationStartEvent, callback); } } } if (targetElement[0] === _sizeObserverElementNative) { var directionChanged = function () { var dir = _hostElement.css("direction"); var css = {}; var scrollLeftValue = 0; var result = false; if (dir !== _cssDirectionDetectedCache) { if (dir === "ltr") { css[_strLeft] = 0; css[_strRight] = _strAuto; scrollLeftValue = constScroll; } else { css[_strLeft] = _strAuto; css[_strRight] = 0; scrollLeftValue = _rtlScrollBehavior.n ? -constScroll : _rtlScrollBehavior.i ? 0 : constScroll; } //execution order is important for IE!!! _sizeObserverElement.children().eq(0).css(css); _sizeObserverElement[_strScrollLeft](scrollLeftValue)[ _strScrollTop ](constScroll); _cssDirectionDetectedCache = dir; result = true; } return result; }; directionChanged(); addDestroyEventListener( targetElement, _strScroll, function (event) { if (directionChanged()) update(); COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); return false; } ); } } //remove resize observer: else { if (_supportResizeObserver) { var element = targetElement.contents()[0]; var resizeObserverObj = element[_strResizeObserverProperty]; if (resizeObserverObj) { resizeObserverObj.disconnect(); delete element[_strResizeObserverProperty]; } } else { remove( targetElement .children(_strDot + _classNameResizeObserverElement) .eq(0) ); } } } } /** * Freezes or unfreezes the given resize observer. * @param targetElement The element to which the target resize observer is applied. * @param freeze True if the resize observer shall be frozen, false otherwise. function freezeResizeObserver(targetElement, freeze) { if (targetElement !== undefined) { if(freeze) { if (_supportResizeObserver) { var element = targetElement.contents()[0]; element[_strResizeObserverProperty].unobserve(element); } else { targetElement = targetElement.children(_strDot + _classNameResizeObserverElement).eq(0); var w = targetElement.css(_strWidth); var h = targetElement.css(_strHeight); var css = {}; css[_strWidth] = w; css[_strHeight] = h; targetElement.css(css); } } else { if (_supportResizeObserver) { var element = targetElement.contents()[0]; element[_strResizeObserverProperty].observe(element); } else { var css = { }; css[_strHeight] = _strEmpty; css[_strWidth] = _strEmpty; targetElement.children(_strDot + _classNameResizeObserverElement).eq(0).css(css); } } } } */ //==== Mutation Observers ====// /** * Creates MutationObservers for the host and content Element if they are supported. */ function createMutationObservers() { if (_supportMutationObserver) { var mutationObserverContentLag = 11; var mutationObserver = COMPATIBILITY.mO(); var contentLastUpdate = COMPATIBILITY.now(); var mutationTarget; var mutationAttrName; var mutationIsClass; var oldMutationVal; var newClassVal; var hostClassNameRegex; var contentTimeout; var now; var sizeAuto; var action; _mutationObserverHostCallback = function (mutations) { var doUpdate = false; var doUpdateForce = false; var mutation; var mutatedAttrs = []; if (_initialized && !_sleeping) { each(mutations, function () { mutation = this; mutationTarget = mutation.target; mutationAttrName = mutation.attributeName; mutationIsClass = mutationAttrName === LEXICON.c; oldMutationVal = mutation.oldValue; newClassVal = mutationTarget.className; if (_domExists && mutationIsClass && !doUpdateForce) { // if old class value contains _classNameHostElementForeign and new class value doesn't if ( oldMutationVal.indexOf(_classNameHostElementForeign) > -1 && newClassVal.indexOf(_classNameHostElementForeign) < 0 ) { hostClassNameRegex = createHostClassNameRegExp(true); _hostElementNative.className = newClassVal .split(_strSpace) .concat( oldMutationVal .split(_strSpace) .filter(function (name) { return name.match(hostClassNameRegex); }) ) .join(_strSpace); doUpdate = doUpdateForce = true; } } if (!doUpdate) { doUpdate = mutationIsClass ? hostClassNamesChanged(oldMutationVal, newClassVal) : mutationAttrName === LEXICON.s ? oldMutationVal !== mutationTarget[LEXICON.s].cssText : true; } mutatedAttrs.push(mutationAttrName); }); updateViewportAttrsFromTarget(mutatedAttrs); if (doUpdate) _base.update(doUpdateForce || _strAuto); } return doUpdate; }; _mutationObserverContentCallback = function (mutations) { var doUpdate = false; var mutation; if (_initialized && !_sleeping) { each(mutations, function () { mutation = this; doUpdate = isUnknownMutation(mutation); return !doUpdate; }); if (doUpdate) { now = COMPATIBILITY.now(); sizeAuto = _heightAutoCache || _widthAutoCache; action = function () { if (!_destroyed) { contentLastUpdate = now; //if cols, rows or wrap attr was changed if (_isTextarea) textareaUpdate(); if (sizeAuto) update(); else _base.update(_strAuto); } }; clearTimeout(contentTimeout); if ( mutationObserverContentLag <= 0 || now - contentLastUpdate > mutationObserverContentLag || !sizeAuto ) action(); else contentTimeout = setTimeout( action, mutationObserverContentLag ); } } return doUpdate; }; _mutationObserverHost = new mutationObserver( _mutationObserverHostCallback ); _mutationObserverContent = new mutationObserver( _mutationObserverContentCallback ); } } /** * Connects the MutationObservers if they are supported. */ function connectMutationObservers() { if (_supportMutationObserver && !_mutationObserversConnected) { _mutationObserverHost.observe(_hostElementNative, { attributes: true, attributeOldValue: true, attributeFilter: _mutationObserverAttrsHost, }); _mutationObserverContent.observe( _isTextarea ? _targetElementNative : _contentElementNative, { attributes: true, attributeOldValue: true, subtree: !_isTextarea, childList: !_isTextarea, characterData: !_isTextarea, attributeFilter: _isTextarea ? _mutationObserverAttrsTextarea : _mutationObserverAttrsHost, } ); _mutationObserversConnected = true; } } /** * Disconnects the MutationObservers if they are supported. */ function disconnectMutationObservers() { if (_supportMutationObserver && _mutationObserversConnected) { _mutationObserverHost.disconnect(); _mutationObserverContent.disconnect(); _mutationObserversConnected = false; } } //==== Events of elements ====// /** * This method gets called every time the host element gets resized. IMPORTANT: Padding changes are detected too!! * It refreshes the hostResizedEventArgs and the hostSizeResizeCache. * If there are any size changes, the update method gets called. */ function hostOnResized() { if (!_sleeping) { var changed; var hostSize = { w: _sizeObserverElementNative[LEXICON.sW], h: _sizeObserverElementNative[LEXICON.sH], }; changed = checkCache(hostSize, _hostElementSizeChangeDetectedCache); _hostElementSizeChangeDetectedCache = hostSize; if (changed) update({ _hostSizeChanged: true }); } } /** * The mouse enter event of the host element. This event is only needed for the autoHide feature. */ function hostOnMouseEnter() { if (_scrollbarsAutoHideLeave) refreshScrollbarsAutoHide(true); } /** * The mouse leave event of the host element. This event is only needed for the autoHide feature. */ function hostOnMouseLeave() { if ( _scrollbarsAutoHideLeave && !_bodyElement.hasClass(_classNameDragging) ) refreshScrollbarsAutoHide(false); } /** * The mouse move event of the host element. This event is only needed for the autoHide "move" feature. */ function hostOnMouseMove() { if (_scrollbarsAutoHideMove) { refreshScrollbarsAutoHide(true); clearTimeout(_scrollbarsAutoHideMoveTimeoutId); _scrollbarsAutoHideMoveTimeoutId = setTimeout(function () { if (_scrollbarsAutoHideMove && !_destroyed) refreshScrollbarsAutoHide(false); }, 100); } } /** * Prevents text from deselection if attached to the document element on the mousedown event of a DOM element. * @param event The select start event. */ function documentOnSelectStart(event) { COMPATIBILITY.prvD(event); return false; } /** * A callback which will be called after a element has loaded. */ function updateOnLoadCallback(event) { if (!_destroyed) { var target = event.target; var elm = FRAMEWORK(event.target); var index = FRAMEWORK.inArray(target, _updateOnLoadElms); if (index > -1) { _updateOnLoadElms.splice(index, 1); } eachUpdateOnLoad(function (i, updateOnLoadSelector) { if (elm.is(updateOnLoadSelector)) { update({ _contentSizeChanged: true }); } }); } } /** * Adds or removes mouse & touch events of the host element. (for handling auto-hiding of the scrollbars) * @param destroy Indicates whether the events shall be added or removed. */ function setupHostMouseTouchEvents(destroy) { if (!destroy) setupHostMouseTouchEvents(true); setupResponsiveEventListener( _hostElement, _strMouseTouchMoveEvent.split(_strSpace)[0], hostOnMouseMove, !_scrollbarsAutoHideMove || destroy, true ); setupResponsiveEventListener( _hostElement, [_strMouseEnter, _strMouseLeave], [hostOnMouseEnter, hostOnMouseLeave], !_scrollbarsAutoHideLeave || destroy, true ); //if the plugin is initialized and the mouse is over the host element, make the scrollbars visible if (!_initialized && !destroy) _hostElement.one("mouseover", hostOnMouseEnter); } //==== Update Detection ====// /** * Measures the min width and min height of the body element and refreshes the related cache. * @returns {boolean} True if the min width or min height has changed, false otherwise. */ function bodyMinSizeChanged() { var bodyMinSize = {}; if (_isBody && _contentArrangeElement) { bodyMinSize.w = parseToZeroOrNumber( _contentArrangeElement.css(_strMinMinus + _strWidth) ); bodyMinSize.h = parseToZeroOrNumber( _contentArrangeElement.css(_strMinMinus + _strHeight) ); bodyMinSize.c = checkCache(bodyMinSize, _bodyMinSizeCache); bodyMinSize.f = true; //flag for "measured at least once" } _bodyMinSizeCache = bodyMinSize; return !!bodyMinSize.c; } /** * Returns true if the class names really changed (new class without plugin host prefix) * @param oldClassNames The old ClassName string or array. * @param newClassNames The new ClassName string or array. * @returns {boolean} True if the class names has really changed, false otherwise. */ function hostClassNamesChanged(oldClassNames, newClassNames) { var currClasses = typeof newClassNames == TYPES.s ? newClassNames.split(_strSpace) : []; var oldClasses = typeof oldClassNames == TYPES.s ? oldClassNames.split(_strSpace) : []; var diff = getArrayDifferences(oldClasses, currClasses); // remove none theme from diff list to prevent update var idx = inArray(_classNameThemeNone, diff); var i; var regex; if (idx > -1) diff.splice(idx, 1); if (diff[LEXICON.l] > 0) { regex = createHostClassNameRegExp(true, true); for (i = 0; i < diff.length; i++) { if (!diff[i].match(regex)) { return true; } } } return false; } /** * Returns true if the given mutation is not from a from the plugin generated element. If the target element is a textarea the mutation is always unknown. * @param mutation The mutation which shall be checked. * @returns {boolean} True if the mutation is from a unknown element, false otherwise. */ function isUnknownMutation(mutation) { var attributeName = mutation.attributeName; var mutationTarget = mutation.target; var mutationType = mutation.type; var strClosest = "closest"; if (mutationTarget === _contentElementNative) return attributeName === null; if ( mutationType === "attributes" && (attributeName === LEXICON.c || attributeName === LEXICON.s) && !_isTextarea ) { //ignore className changes by the plugin if ( attributeName === LEXICON.c && FRAMEWORK(mutationTarget).hasClass(_classNameHostElement) ) return hostClassNamesChanged( mutation.oldValue, mutationTarget.className ); //only do it of browser support it natively if (typeof mutationTarget[strClosest] != TYPES.f) return true; if ( mutationTarget[strClosest]( _strDot + _classNameResizeObserverElement ) !== null || mutationTarget[strClosest](_strDot + _classNameScrollbar) !== null || mutationTarget[strClosest]( _strDot + _classNameScrollbarCorner ) !== null ) return false; } return true; } /** * Returns true if the content size was changed since the last time this method was called. * @returns {boolean} True if the content size was changed, false otherwise. */ function updateAutoContentSizeChanged() { if (_sleeping) return false; var contentMeasureElement = getContentMeasureElement(); var textareaValueLength = _isTextarea && _widthAutoCache && !_textareaAutoWrappingCache ? _targetElement.val().length : 0; var setCSS = !_mutationObserversConnected && _widthAutoCache && !_isTextarea; var css = {}; var float; var bodyMinSizeC; var changed; var contentElementScrollSize; if (setCSS) { float = _contentElement.css(_strFloat); css[_strFloat] = _isRTL ? _strRight : _strLeft; css[_strWidth] = _strAuto; _contentElement.css(css); } contentElementScrollSize = { w: contentMeasureElement[LEXICON.sW] + textareaValueLength, h: contentMeasureElement[LEXICON.sH] + textareaValueLength, }; if (setCSS) { css[_strFloat] = float; css[_strWidth] = _strHundredPercent; _contentElement.css(css); } bodyMinSizeC = bodyMinSizeChanged(); changed = checkCache( contentElementScrollSize, _contentElementScrollSizeChangeDetectedCache ); _contentElementScrollSizeChangeDetectedCache = contentElementScrollSize; return changed || bodyMinSizeC; } /** * Returns true when a attribute which the MutationObserver would observe has changed. * @returns {boolean} True if one of the attributes which a MutationObserver would observe has changed, false or undefined otherwise. */ function meaningfulAttrsChanged() { if (_sleeping || _mutationObserversConnected) return; var elem; var curr; var cache; var changedAttrs = []; var checks = [ { _elem: _hostElement, _attrs: _mutationObserverAttrsHost.concat(":visible"), }, { _elem: _isTextarea ? _targetElement : undefined, _attrs: _mutationObserverAttrsTextarea, }, ]; each(checks, function (index, check) { elem = check._elem; if (elem) { each(check._attrs, function (index, attr) { curr = attr.charAt(0) === ":" ? elem.is(attr) : elem.attr(attr); cache = _updateAutoCache[attr]; if (checkCache(curr, cache)) { changedAttrs.push(attr); } _updateAutoCache[attr] = curr; }); } }); updateViewportAttrsFromTarget(changedAttrs); return changedAttrs[LEXICON.l] > 0; } /** * Checks is a CSS Property of a child element is affecting the scroll size of the content. * @param propertyName The CSS property name. * @returns {boolean} True if the property is affecting the content scroll size, false otherwise. */ function isSizeAffectingCSSProperty(propertyName) { if (!_initialized) return true; var flexGrow = "flex-grow"; var flexShrink = "flex-shrink"; var flexBasis = "flex-basis"; var affectingPropsX = [ _strWidth, _strMinMinus + _strWidth, _strMaxMinus + _strWidth, _strMarginMinus + _strLeft, _strMarginMinus + _strRight, _strLeft, _strRight, "font-weight", "word-spacing", flexGrow, flexShrink, flexBasis, ]; var affectingPropsXContentBox = [ _strPaddingMinus + _strLeft, _strPaddingMinus + _strRight, _strBorderMinus + _strLeft + _strWidth, _strBorderMinus + _strRight + _strWidth, ]; var affectingPropsY = [ _strHeight, _strMinMinus + _strHeight, _strMaxMinus + _strHeight, _strMarginMinus + _strTop, _strMarginMinus + _strBottom, _strTop, _strBottom, "line-height", flexGrow, flexShrink, flexBasis, ]; var affectingPropsYContentBox = [ _strPaddingMinus + _strTop, _strPaddingMinus + _strBottom, _strBorderMinus + _strTop + _strWidth, _strBorderMinus + _strBottom + _strWidth, ]; var _strS = "s"; var _strVS = "v-s"; var checkX = _overflowBehaviorCache.x === _strS || _overflowBehaviorCache.x === _strVS; var checkY = _overflowBehaviorCache.y === _strS || _overflowBehaviorCache.y === _strVS; var sizeIsAffected = false; var checkPropertyName = function (arr, name) { for (var i = 0; i < arr[LEXICON.l]; i++) { if (arr[i] === name) return true; } return false; }; if (checkY) { sizeIsAffected = checkPropertyName(affectingPropsY, propertyName); if (!sizeIsAffected && !_isBorderBox) sizeIsAffected = checkPropertyName( affectingPropsYContentBox, propertyName ); } if (checkX && !sizeIsAffected) { sizeIsAffected = checkPropertyName(affectingPropsX, propertyName); if (!sizeIsAffected && !_isBorderBox) sizeIsAffected = checkPropertyName( affectingPropsXContentBox, propertyName ); } return sizeIsAffected; } //==== Update ====// /** * Sets the attribute values of the viewport element to the values from the target element. * The value of a attribute is only set if the attribute is whitelisted. * @attrs attrs The array of attributes which shall be set or undefined if all whitelisted shall be set. */ function updateViewportAttrsFromTarget(attrs) { attrs = attrs || _viewportAttrsFromTarget; each(attrs, function (index, attr) { if (COMPATIBILITY.inA(attr, _viewportAttrsFromTarget) > -1) { var targetAttr = _targetElement.attr(attr); if (type(targetAttr) == TYPES.s) { _viewportElement.attr(attr, targetAttr); } else { _viewportElement.removeAttr(attr); } } }); } /** * Updates the variables and size of the textarea element, and manages the scroll on new line or new character. */ function textareaUpdate() { if (!_sleeping) { var wrapAttrOff = !_textareaAutoWrappingCache; var minWidth = _viewportSize.w; var minHeight = _viewportSize.h; var css = {}; var doMeasure = _widthAutoCache || wrapAttrOff; var origWidth; var width; var origHeight; var height; //reset min size css[_strMinMinus + _strWidth] = _strEmpty; css[_strMinMinus + _strHeight] = _strEmpty; //set width auto css[_strWidth] = _strAuto; _targetElement.css(css); //measure width origWidth = _targetElementNative[LEXICON.oW]; width = doMeasure ? MATH.max(origWidth, _targetElementNative[LEXICON.sW] - 1) : 1; /*width += (_widthAutoCache ? _marginX + (!_isBorderBox ? wrapAttrOff ? 0 : _paddingX + _borderX : 0) : 0);*/ //set measured width css[_strWidth] = _widthAutoCache ? _strAuto /*width*/ : _strHundredPercent; css[_strMinMinus + _strWidth] = _strHundredPercent; //set height auto css[_strHeight] = _strAuto; _targetElement.css(css); //measure height origHeight = _targetElementNative[LEXICON.oH]; height = MATH.max(origHeight, _targetElementNative[LEXICON.sH] - 1); //append correct size values css[_strWidth] = width; css[_strHeight] = height; _textareaCoverElement.css(css); //apply min width / min height to prevent textarea collapsing css[_strMinMinus + _strWidth] = minWidth /*+ (!_isBorderBox && _widthAutoCache ? _paddingX + _borderX : 0)*/; css[_strMinMinus + _strHeight] = minHeight /*+ (!_isBorderBox && _heightAutoCache ? _paddingY + _borderY : 0)*/; _targetElement.css(css); return { _originalWidth: origWidth, _originalHeight: origHeight, _dynamicWidth: width, _dynamicHeight: height, }; } } /** * Updates the plugin and DOM to the current options. * This method should only be called if a update is 100% required. * @param updateHints A objects which contains hints for this update: * { * _hostSizeChanged : boolean, * _contentSizeChanged : boolean, * _force : boolean, == preventSwallowing * _changedOptions : { }, == preventSwallowing && preventSleep * } */ function update(updateHints) { clearTimeout(_swallowedUpdateTimeout); updateHints = updateHints || {}; _swallowedUpdateHints._hostSizeChanged |= updateHints._hostSizeChanged; _swallowedUpdateHints._contentSizeChanged |= updateHints._contentSizeChanged; _swallowedUpdateHints._force |= updateHints._force; var now = COMPATIBILITY.now(); var hostSizeChanged = !!_swallowedUpdateHints._hostSizeChanged; var contentSizeChanged = !!_swallowedUpdateHints._contentSizeChanged; var force = !!_swallowedUpdateHints._force; var changedOptions = updateHints._changedOptions; var swallow = _swallowUpdateLag > 0 && _initialized && !_destroyed && !force && !changedOptions && now - _lastUpdateTime < _swallowUpdateLag && !_heightAutoCache && !_widthAutoCache; var displayIsHidden; if (swallow) _swallowedUpdateTimeout = setTimeout(update, _swallowUpdateLag); //abort update due to: //destroyed //swallowing //sleeping //host is hidden or has false display if ( _destroyed || swallow || (_sleeping && !changedOptions) || (_initialized && !force && (displayIsHidden = _hostElement.is(":hidden"))) || _hostElement.css("display") === "inline" ) return; _lastUpdateTime = now; _swallowedUpdateHints = {}; //if scrollbar styling is possible and native scrollbars aren't overlaid the scrollbar styling will be applied which hides the native scrollbars completely. if ( _nativeScrollbarStyling && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y) ) { //native scrollbars are hidden, so change the values to zero _nativeScrollbarSize.x = 0; _nativeScrollbarSize.y = 0; } else { //refresh native scrollbar size (in case of zoom) _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); } // Scrollbar padding is needed for firefox, because firefox hides scrollbar automatically if the size of the div is too small. // The calculation: [scrollbar size +3 *3] // (+3 because of possible decoration e.g. borders, margins etc., but only if native scrollbar is NOT a overlaid scrollbar) // (*3 because (1)increase / (2)decrease -button and (3)resize handle) _nativeScrollbarMinSize = { x: (_nativeScrollbarSize.x + (_nativeScrollbarIsOverlaid.x ? 0 : 3)) * 3, y: (_nativeScrollbarSize.y + (_nativeScrollbarIsOverlaid.y ? 0 : 3)) * 3, }; changedOptions = changedOptions || {}; //freezeResizeObserver(_sizeObserverElement, true); //freezeResizeObserver(_sizeAutoObserverElement, true); var checkCacheAutoForce = function () { return checkCache.apply( this, [].slice.call(arguments).concat([force]) ); }; //save current scroll offset var currScroll = { x: _viewportElement[_strScrollLeft](), y: _viewportElement[_strScrollTop](), }; var currentPreparedOptionsScrollbars = _currentPreparedOptions.scrollbars; var currentPreparedOptionsTextarea = _currentPreparedOptions.textarea; //scrollbars visibility: var scrollbarsVisibility = currentPreparedOptionsScrollbars.visibility; var scrollbarsVisibilityChanged = checkCacheAutoForce( scrollbarsVisibility, _scrollbarsVisibilityCache ); //scrollbars autoHide: var scrollbarsAutoHide = currentPreparedOptionsScrollbars.autoHide; var scrollbarsAutoHideChanged = checkCacheAutoForce( scrollbarsAutoHide, _scrollbarsAutoHideCache ); //scrollbars click scrolling var scrollbarsClickScrolling = currentPreparedOptionsScrollbars.clickScrolling; var scrollbarsClickScrollingChanged = checkCacheAutoForce( scrollbarsClickScrolling, _scrollbarsClickScrollingCache ); //scrollbars drag scrolling var scrollbarsDragScrolling = currentPreparedOptionsScrollbars.dragScrolling; var scrollbarsDragScrollingChanged = checkCacheAutoForce( scrollbarsDragScrolling, _scrollbarsDragScrollingCache ); //className var className = _currentPreparedOptions.className; var classNameChanged = checkCacheAutoForce( className, _classNameCache ); //resize var resize = _currentPreparedOptions.resize; var resizeChanged = checkCacheAutoForce(resize, _resizeCache) && !_isBody; //body can't be resized since the window itself acts as resize possibility. //paddingAbsolute var paddingAbsolute = _currentPreparedOptions.paddingAbsolute; var paddingAbsoluteChanged = checkCacheAutoForce( paddingAbsolute, _paddingAbsoluteCache ); //clipAlways var clipAlways = _currentPreparedOptions.clipAlways; var clipAlwaysChanged = checkCacheAutoForce( clipAlways, _clipAlwaysCache ); //sizeAutoCapable var sizeAutoCapable = _currentPreparedOptions.sizeAutoCapable && !_isBody; //body can never be size auto, because it shall be always as big as the viewport. var sizeAutoCapableChanged = checkCacheAutoForce( sizeAutoCapable, _sizeAutoCapableCache ); //showNativeScrollbars var ignoreOverlayScrollbarHiding = _currentPreparedOptions.nativeScrollbarsOverlaid .showNativeScrollbars; var ignoreOverlayScrollbarHidingChanged = checkCacheAutoForce( ignoreOverlayScrollbarHiding, _ignoreOverlayScrollbarHidingCache ); //autoUpdate var autoUpdate = _currentPreparedOptions.autoUpdate; var autoUpdateChanged = checkCacheAutoForce( autoUpdate, _autoUpdateCache ); //overflowBehavior var overflowBehavior = _currentPreparedOptions.overflowBehavior; var overflowBehaviorChanged = checkCacheAutoForce( overflowBehavior, _overflowBehaviorCache, force ); //dynWidth: var textareaDynWidth = currentPreparedOptionsTextarea.dynWidth; var textareaDynWidthChanged = checkCacheAutoForce( _textareaDynWidthCache, textareaDynWidth ); //dynHeight: var textareaDynHeight = currentPreparedOptionsTextarea.dynHeight; var textareaDynHeightChanged = checkCacheAutoForce( _textareaDynHeightCache, textareaDynHeight ); //scrollbars visibility _scrollbarsAutoHideNever = scrollbarsAutoHide === "n"; _scrollbarsAutoHideScroll = scrollbarsAutoHide === "s"; _scrollbarsAutoHideMove = scrollbarsAutoHide === "m"; _scrollbarsAutoHideLeave = scrollbarsAutoHide === "l"; //scrollbars autoHideDelay _scrollbarsAutoHideDelay = currentPreparedOptionsScrollbars.autoHideDelay; //old className _oldClassName = _classNameCache; //resize _resizeNone = resize === "n"; _resizeBoth = resize === "b"; _resizeHorizontal = resize === "h"; _resizeVertical = resize === "v"; //normalizeRTL _normalizeRTLCache = _currentPreparedOptions.normalizeRTL; //ignore overlay scrollbar hiding ignoreOverlayScrollbarHiding = ignoreOverlayScrollbarHiding && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y; //refresh options cache _scrollbarsVisibilityCache = scrollbarsVisibility; _scrollbarsAutoHideCache = scrollbarsAutoHide; _scrollbarsClickScrollingCache = scrollbarsClickScrolling; _scrollbarsDragScrollingCache = scrollbarsDragScrolling; _classNameCache = className; _resizeCache = resize; _paddingAbsoluteCache = paddingAbsolute; _clipAlwaysCache = clipAlways; _sizeAutoCapableCache = sizeAutoCapable; _ignoreOverlayScrollbarHidingCache = ignoreOverlayScrollbarHiding; _autoUpdateCache = autoUpdate; _overflowBehaviorCache = extendDeep({}, overflowBehavior); _textareaDynWidthCache = textareaDynWidth; _textareaDynHeightCache = textareaDynHeight; _hasOverflowCache = _hasOverflowCache || { x: false, y: false }; //set correct class name to the host element if (classNameChanged) { removeClass( _hostElement, _oldClassName + _strSpace + _classNameThemeNone ); addClass( _hostElement, className !== undefined && className !== null && className.length > 0 ? className : _classNameThemeNone ); } //set correct auto Update if (autoUpdateChanged) { if ( autoUpdate === true || (autoUpdate === null && _autoUpdateRecommended) ) { disconnectMutationObservers(); autoUpdateLoop.add(_base); } else { autoUpdateLoop.remove(_base); connectMutationObservers(); } } //activate or deactivate size auto capability if (sizeAutoCapableChanged) { if (sizeAutoCapable) { if (_contentGlueElement) { _contentGlueElement.show(); } else { _contentGlueElement = FRAMEWORK( generateDiv(_classNameContentGlueElement) ); _paddingElement.before(_contentGlueElement); } if (_sizeAutoObserverAdded) { _sizeAutoObserverElement.show(); } else { _sizeAutoObserverElement = FRAMEWORK( generateDiv(_classNameSizeAutoObserverElement) ); _sizeAutoObserverElementNative = _sizeAutoObserverElement[0]; _contentGlueElement.before(_sizeAutoObserverElement); var oldSize = { w: -1, h: -1 }; setupResizeObserver(_sizeAutoObserverElement, function () { var newSize = { w: _sizeAutoObserverElementNative[LEXICON.oW], h: _sizeAutoObserverElementNative[LEXICON.oH], }; if (checkCache(newSize, oldSize)) { if ( (_initialized && _heightAutoCache && newSize.h > 0) || (_widthAutoCache && newSize.w > 0) ) { update(); } else if ( (_initialized && !_heightAutoCache && newSize.h === 0) || (!_widthAutoCache && newSize.w === 0) ) { update(); } } oldSize = newSize; }); _sizeAutoObserverAdded = true; //fix heightAuto detector bug if height is fixed but contentHeight is 0. //the probability this bug will ever happen is very very low, thats why its ok if we use calc which isn't supported in IE8. if (_cssCalc !== null) _sizeAutoObserverElement.css( _strHeight, _cssCalc + "(100% + 1px)" ); } } else { if (_sizeAutoObserverAdded) _sizeAutoObserverElement.hide(); if (_contentGlueElement) _contentGlueElement.hide(); } } //if force, update all resizeObservers too if (force) { _sizeObserverElement.find("*").trigger(_strScroll); if (_sizeAutoObserverAdded) _sizeAutoObserverElement.find("*").trigger(_strScroll); } //display hidden: displayIsHidden = displayIsHidden === undefined ? _hostElement.is(":hidden") : displayIsHidden; //textarea AutoWrapping: var textareaAutoWrapping = _isTextarea ? _targetElement.attr("wrap") !== "off" : false; var textareaAutoWrappingChanged = checkCacheAutoForce( textareaAutoWrapping, _textareaAutoWrappingCache ); //detect direction: var cssDirection = _hostElement.css("direction"); var cssDirectionChanged = checkCacheAutoForce( cssDirection, _cssDirectionCache ); //detect box-sizing: var boxSizing = _hostElement.css("box-sizing"); var boxSizingChanged = checkCacheAutoForce( boxSizing, _cssBoxSizingCache ); //detect padding: var padding = getTopRightBottomLeftHost(_strPaddingMinus); //width + height auto detecting var: var sizeAutoObserverElementBCRect; //exception occurs in IE8 sometimes (unknown exception) try { sizeAutoObserverElementBCRect = _sizeAutoObserverAdded ? _sizeAutoObserverElementNative[LEXICON.bCR]() : null; } catch (ex) { return; } _isRTL = cssDirection === "rtl"; _isBorderBox = boxSizing === "border-box"; var isRTLLeft = _isRTL ? _strLeft : _strRight; var isRTLRight = _isRTL ? _strRight : _strLeft; //detect width auto: var widthAutoResizeDetection = false; var widthAutoObserverDetection = _sizeAutoObserverAdded && _hostElement.css(_strFloat) !== "none" /*|| _isTextarea */ ? MATH.round( sizeAutoObserverElementBCRect.right - sizeAutoObserverElementBCRect.left ) === 0 && (!paddingAbsolute ? _hostElementNative[LEXICON.cW] - _paddingX > 0 : true) : false; if (sizeAutoCapable && !widthAutoObserverDetection) { var tmpCurrHostWidth = _hostElementNative[LEXICON.oW]; var tmpCurrContentGlueWidth = _contentGlueElement.css(_strWidth); _contentGlueElement.css(_strWidth, _strAuto); var tmpNewHostWidth = _hostElementNative[LEXICON.oW]; _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; if (!widthAutoResizeDetection) { _contentGlueElement.css(_strWidth, tmpCurrHostWidth + 1); tmpNewHostWidth = _hostElementNative[LEXICON.oW]; _contentGlueElement.css(_strWidth, tmpCurrContentGlueWidth); widthAutoResizeDetection = tmpCurrHostWidth !== tmpNewHostWidth; } } var widthAuto = (widthAutoObserverDetection || widthAutoResizeDetection) && sizeAutoCapable && !displayIsHidden; var widthAutoChanged = checkCacheAutoForce( widthAuto, _widthAutoCache ); var wasWidthAuto = !widthAuto && _widthAutoCache; //detect height auto: var heightAuto = _sizeAutoObserverAdded && sizeAutoCapable && !displayIsHidden ? MATH.round( sizeAutoObserverElementBCRect.bottom - sizeAutoObserverElementBCRect.top ) === 0 /* && (!paddingAbsolute && (_msieVersion > 9 || !_msieVersion) ? true : true) */ : false; var heightAutoChanged = checkCacheAutoForce( heightAuto, _heightAutoCache ); var wasHeightAuto = !heightAuto && _heightAutoCache; //detect border: //we need the border only if border box and auto size var updateBorderX = (widthAuto && _isBorderBox) || !_isBorderBox; var updateBorderY = (heightAuto && _isBorderBox) || !_isBorderBox; var border = getTopRightBottomLeftHost( _strBorderMinus, "-" + _strWidth, !updateBorderX, !updateBorderY ); //detect margin: var margin = getTopRightBottomLeftHost(_strMarginMinus); //vars to apply correct css var contentElementCSS = {}; var contentGlueElementCSS = {}; //funcs var getHostSize = function () { //has to be clientSize because offsetSize respect borders return { w: _hostElementNative[LEXICON.cW], h: _hostElementNative[LEXICON.cH], }; }; var getViewportSize = function () { //viewport size is padding container because it never has padding, margin and a border //determine zoom rounding error -> sometimes scrollWidth/Height is smaller than clientWidth/Height //if this happens add the difference to the viewportSize to compensate the rounding error return { w: _paddingElementNative[LEXICON.oW] + MATH.max( 0, _contentElementNative[LEXICON.cW] - _contentElementNative[LEXICON.sW] ), h: _paddingElementNative[LEXICON.oH] + MATH.max( 0, _contentElementNative[LEXICON.cH] - _contentElementNative[LEXICON.sH] ), }; }; //set info for padding var paddingAbsoluteX = (_paddingX = padding.l + padding.r); var paddingAbsoluteY = (_paddingY = padding.t + padding.b); paddingAbsoluteX *= paddingAbsolute ? 1 : 0; paddingAbsoluteY *= paddingAbsolute ? 1 : 0; padding.c = checkCacheAutoForce(padding, _cssPaddingCache); //set info for border _borderX = border.l + border.r; _borderY = border.t + border.b; border.c = checkCacheAutoForce(border, _cssBorderCache); //set info for margin _marginX = margin.l + margin.r; _marginY = margin.t + margin.b; margin.c = checkCacheAutoForce(margin, _cssMarginCache); //refresh cache _textareaAutoWrappingCache = textareaAutoWrapping; _cssDirectionCache = cssDirection; _cssBoxSizingCache = boxSizing; _widthAutoCache = widthAuto; _heightAutoCache = heightAuto; _cssPaddingCache = padding; _cssBorderCache = border; _cssMarginCache = margin; //IEFix direction changed if (cssDirectionChanged && _sizeAutoObserverAdded) _sizeAutoObserverElement.css(_strFloat, isRTLRight); //apply padding: if ( padding.c || cssDirectionChanged || paddingAbsoluteChanged || widthAutoChanged || heightAutoChanged || boxSizingChanged || sizeAutoCapableChanged ) { var paddingElementCSS = {}; var textareaCSS = {}; var paddingValues = [padding.t, padding.r, padding.b, padding.l]; setTopRightBottomLeft(contentGlueElementCSS, _strMarginMinus, [ -padding.t, -padding.r, -padding.b, -padding.l, ]); if (paddingAbsolute) { setTopRightBottomLeft( paddingElementCSS, _strEmpty, paddingValues ); setTopRightBottomLeft( _isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus ); } else { setTopRightBottomLeft(paddingElementCSS, _strEmpty); setTopRightBottomLeft( _isTextarea ? textareaCSS : contentElementCSS, _strPaddingMinus, paddingValues ); } _paddingElement.css(paddingElementCSS); _targetElement.css(textareaCSS); } //viewport size is padding container because it never has padding, margin and a border. _viewportSize = getViewportSize(); //update Textarea var textareaSize = _isTextarea ? textareaUpdate() : false; var textareaSizeChanged = _isTextarea && checkCacheAutoForce(textareaSize, _textareaSizeCache); var textareaDynOrigSize = _isTextarea && textareaSize ? { w: textareaDynWidth ? textareaSize._dynamicWidth : textareaSize._originalWidth, h: textareaDynHeight ? textareaSize._dynamicHeight : textareaSize._originalHeight, } : {}; _textareaSizeCache = textareaSize; //fix height auto / width auto in cooperation with current padding & boxSizing behavior: if ( heightAuto && (heightAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c) ) { contentElementCSS[_strHeight] = _strAuto; } else if (heightAutoChanged || paddingAbsoluteChanged) { contentElementCSS[_strHeight] = _strHundredPercent; } if ( widthAuto && (widthAutoChanged || paddingAbsoluteChanged || boxSizingChanged || padding.c || border.c || cssDirectionChanged) ) { contentElementCSS[_strWidth] = _strAuto; contentGlueElementCSS[_strMaxMinus + _strWidth] = _strHundredPercent; //IE Fix } else if (widthAutoChanged || paddingAbsoluteChanged) { contentElementCSS[_strWidth] = _strHundredPercent; contentElementCSS[_strFloat] = _strEmpty; contentGlueElementCSS[_strMaxMinus + _strWidth] = _strEmpty; //IE Fix } if (widthAuto) { //textareaDynOrigSize.w || _strAuto :: doesnt works because applied margin will shift width contentGlueElementCSS[_strWidth] = _strAuto; contentElementCSS[_strWidth] = VENDORS._cssPropertyValue(_strWidth, "max-content intrinsic") || _strAuto; contentElementCSS[_strFloat] = isRTLRight; } else { contentGlueElementCSS[_strWidth] = _strEmpty; } if (heightAuto) { //textareaDynOrigSize.h || _contentElementNative[LEXICON.cH] :: use for anti scroll jumping contentGlueElementCSS[_strHeight] = textareaDynOrigSize.h || _contentElementNative[LEXICON.cH]; } else { contentGlueElementCSS[_strHeight] = _strEmpty; } if (sizeAutoCapable) _contentGlueElement.css(contentGlueElementCSS); _contentElement.css(contentElementCSS); //CHECKPOINT HERE ~ contentElementCSS = {}; contentGlueElementCSS = {}; //if [content(host) client / scroll size, or target element direction, or content(host) max-sizes] changed, or force is true if ( hostSizeChanged || contentSizeChanged || textareaSizeChanged || cssDirectionChanged || boxSizingChanged || paddingAbsoluteChanged || widthAutoChanged || widthAuto || heightAutoChanged || heightAuto || ignoreOverlayScrollbarHidingChanged || overflowBehaviorChanged || clipAlwaysChanged || resizeChanged || scrollbarsVisibilityChanged || scrollbarsAutoHideChanged || scrollbarsDragScrollingChanged || scrollbarsClickScrollingChanged || textareaDynWidthChanged || textareaDynHeightChanged || textareaAutoWrappingChanged ) { var strOverflow = "overflow"; var strOverflowX = strOverflow + "-x"; var strOverflowY = strOverflow + "-y"; var strHidden = "hidden"; var strVisible = "visible"; //Reset the viewport (very important for natively overlaid scrollbars and zoom change //don't change the overflow prop as it is very expensive and affects performance !A LOT! if (!_nativeScrollbarStyling) { var viewportElementResetCSS = {}; var resetXTmp = _hasOverflowCache.y && _hideOverflowCache.ys && !ignoreOverlayScrollbarHiding ? _nativeScrollbarIsOverlaid.y ? _viewportElement.css(isRTLLeft) : -_nativeScrollbarSize.y : 0; var resetBottomTmp = _hasOverflowCache.x && _hideOverflowCache.xs && !ignoreOverlayScrollbarHiding ? _nativeScrollbarIsOverlaid.x ? _viewportElement.css(_strBottom) : -_nativeScrollbarSize.x : 0; setTopRightBottomLeft(viewportElementResetCSS, _strEmpty); _viewportElement.css(viewportElementResetCSS); } //measure several sizes: var contentMeasureElement = getContentMeasureElement(); //in Firefox content element has to have overflow hidden, else element margins aren't calculated properly, this element prevents this bug, but only if scrollbars aren't overlaid var contentSize = { //use clientSize because natively overlaidScrollbars add borders w: textareaDynOrigSize.w || contentMeasureElement[LEXICON.cW], h: textareaDynOrigSize.h || contentMeasureElement[LEXICON.cH], }; var scrollSize = { w: contentMeasureElement[LEXICON.sW], h: contentMeasureElement[LEXICON.sH], }; //apply the correct viewport style and measure viewport size if (!_nativeScrollbarStyling) { viewportElementResetCSS[_strBottom] = wasHeightAuto ? _strEmpty : resetBottomTmp; viewportElementResetCSS[isRTLLeft] = wasWidthAuto ? _strEmpty : resetXTmp; _viewportElement.css(viewportElementResetCSS); } _viewportSize = getViewportSize(); //measure and correct several sizes var hostSize = getHostSize(); var hostAbsoluteRectSize = { w: hostSize.w - _marginX - _borderX - (_isBorderBox ? 0 : _paddingX), h: hostSize.h - _marginY - _borderY - (_isBorderBox ? 0 : _paddingY), }; var contentGlueSize = { //client/scrollSize + AbsolutePadding -> because padding is only applied to the paddingElement if its absolute, so you have to add it manually //hostSize is clientSize -> so padding should be added manually, right? FALSE! Because content glue is inside hostElement, so we don't have to worry about padding w: MATH.max( (widthAuto ? contentSize.w : scrollSize.w) + paddingAbsoluteX, hostAbsoluteRectSize.w ), h: MATH.max( (heightAuto ? contentSize.h : scrollSize.h) + paddingAbsoluteY, hostAbsoluteRectSize.h ), }; contentGlueSize.c = checkCacheAutoForce( contentGlueSize, _contentGlueSizeCache ); _contentGlueSizeCache = contentGlueSize; //apply correct contentGlue size if (sizeAutoCapable) { //size contentGlue correctly to make sure the element has correct size if the sizing switches to auto if (contentGlueSize.c || heightAuto || widthAuto) { contentGlueElementCSS[_strWidth] = contentGlueSize.w; contentGlueElementCSS[_strHeight] = contentGlueSize.h; //textarea-sizes are already calculated correctly at this point if (!_isTextarea) { contentSize = { //use clientSize because natively overlaidScrollbars add borders w: contentMeasureElement[LEXICON.cW], h: contentMeasureElement[LEXICON.cH], }; } } var textareaCoverCSS = {}; var setContentGlueElementCSSfunction = function (horizontal) { var scrollbarVars = getScrollbarVars(horizontal); var wh = scrollbarVars._w_h; var strWH = scrollbarVars._width_height; var autoSize = horizontal ? widthAuto : heightAuto; var borderSize = horizontal ? _borderX : _borderY; var paddingSize = horizontal ? _paddingX : _paddingY; var marginSize = horizontal ? _marginX : _marginY; var viewportSize = _viewportSize[wh] - borderSize - marginSize - (_isBorderBox ? 0 : paddingSize); //make contentGlue size -1 if element is not auto sized, to make sure that a resize event happens when the element shrinks if (!autoSize || (!autoSize && border.c)) contentGlueElementCSS[strWH] = hostAbsoluteRectSize[wh] - 1; //if size is auto and host is smaller than size as min size, make content glue size -1 to make sure size changes will be detected (this is only needed if padding is 0) if ( autoSize && contentSize[wh] < viewportSize && (horizontal && _isTextarea ? !textareaAutoWrapping : true) ) { if (_isTextarea) textareaCoverCSS[strWH] = parseToZeroOrNumber(_textareaCoverElement.css(strWH)) - 1; contentGlueElementCSS[strWH] -= 1; } //make sure content glue size is at least 1 if (contentSize[wh] > 0) contentGlueElementCSS[strWH] = MATH.max( 1, contentGlueElementCSS[strWH] ); }; setContentGlueElementCSSfunction(true); setContentGlueElementCSSfunction(false); if (_isTextarea) _textareaCoverElement.css(textareaCoverCSS); _contentGlueElement.css(contentGlueElementCSS); } if (widthAuto) contentElementCSS[_strWidth] = _strHundredPercent; if (widthAuto && !_isBorderBox && !_mutationObserversConnected) contentElementCSS[_strFloat] = "none"; //apply and reset content style _contentElement.css(contentElementCSS); contentElementCSS = {}; //measure again, but this time all correct sizes: var contentScrollSize = { w: contentMeasureElement[LEXICON.sW], h: contentMeasureElement[LEXICON.sH], }; contentScrollSize.c = contentSizeChanged = checkCacheAutoForce( contentScrollSize, _contentScrollSizeCache ); _contentScrollSizeCache = contentScrollSize; //refresh viewport size after correct measuring _viewportSize = getViewportSize(); hostSize = getHostSize(); hostSizeChanged = checkCacheAutoForce(hostSize, _hostSizeCache); _hostSizeCache = hostSize; var hideOverflowForceTextarea = _isTextarea && (_viewportSize.w === 0 || _viewportSize.h === 0); var previousOverflowAmount = _overflowAmountCache; var overflowBehaviorIsVS = {}; var overflowBehaviorIsVH = {}; var overflowBehaviorIsS = {}; var overflowAmount = {}; var hasOverflow = {}; var hideOverflow = {}; var canScroll = {}; var viewportRect = _paddingElementNative[LEXICON.bCR](); var setOverflowVariables = function (horizontal) { var scrollbarVars = getScrollbarVars(horizontal); var scrollbarVarsInverted = getScrollbarVars(!horizontal); var xyI = scrollbarVarsInverted._x_y; var xy = scrollbarVars._x_y; var wh = scrollbarVars._w_h; var widthHeight = scrollbarVars._width_height; var scrollMax = _strScroll + scrollbarVars._Left_Top + "Max"; var fractionalOverflowAmount = viewportRect[widthHeight] ? MATH.abs(viewportRect[widthHeight] - _viewportSize[wh]) : 0; var checkFractionalOverflowAmount = previousOverflowAmount && previousOverflowAmount[xy] > 0 && _viewportElementNative[scrollMax] === 0; overflowBehaviorIsVS[xy] = overflowBehavior[xy] === "v-s"; overflowBehaviorIsVH[xy] = overflowBehavior[xy] === "v-h"; overflowBehaviorIsS[xy] = overflowBehavior[xy] === "s"; overflowAmount[xy] = MATH.max( 0, MATH.round((contentScrollSize[wh] - _viewportSize[wh]) * 100) / 100 ); overflowAmount[xy] *= hideOverflowForceTextarea || (checkFractionalOverflowAmount && fractionalOverflowAmount > 0 && fractionalOverflowAmount < 1) ? 0 : 1; hasOverflow[xy] = overflowAmount[xy] > 0; //hideOverflow: //x || y : true === overflow is hidden by "overflow: scroll" OR "overflow: hidden" //xs || ys : true === overflow is hidden by "overflow: scroll" hideOverflow[xy] = overflowBehaviorIsVS[xy] || overflowBehaviorIsVH[xy] ? hasOverflow[xyI] && !overflowBehaviorIsVS[xyI] && !overflowBehaviorIsVH[xyI] : hasOverflow[xy]; hideOverflow[xy + "s"] = hideOverflow[xy] ? overflowBehaviorIsS[xy] || overflowBehaviorIsVS[xy] : false; canScroll[xy] = hasOverflow[xy] && hideOverflow[xy + "s"]; }; setOverflowVariables(true); setOverflowVariables(false); overflowAmount.c = checkCacheAutoForce( overflowAmount, _overflowAmountCache ); _overflowAmountCache = overflowAmount; hasOverflow.c = checkCacheAutoForce(hasOverflow, _hasOverflowCache); _hasOverflowCache = hasOverflow; hideOverflow.c = checkCacheAutoForce( hideOverflow, _hideOverflowCache ); _hideOverflowCache = hideOverflow; //if native scrollbar is overlay at x OR y axis, prepare DOM if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) { var borderDesign = "px solid transparent"; var contentArrangeElementCSS = {}; var arrangeContent = {}; var arrangeChanged = force; var setContentElementCSS; if (hasOverflow.x || hasOverflow.y) { arrangeContent.w = _nativeScrollbarIsOverlaid.y && hasOverflow.y ? contentScrollSize.w + _overlayScrollbarDummySize.y : _strEmpty; arrangeContent.h = _nativeScrollbarIsOverlaid.x && hasOverflow.x ? contentScrollSize.h + _overlayScrollbarDummySize.x : _strEmpty; arrangeChanged = checkCacheAutoForce( arrangeContent, _arrangeContentSizeCache ); _arrangeContentSizeCache = arrangeContent; } if ( hasOverflow.c || hideOverflow.c || contentScrollSize.c || cssDirectionChanged || widthAutoChanged || heightAutoChanged || widthAuto || heightAuto || ignoreOverlayScrollbarHidingChanged ) { contentElementCSS[_strMarginMinus + isRTLRight] = contentElementCSS[_strBorderMinus + isRTLRight] = _strEmpty; setContentElementCSS = function (horizontal) { var scrollbarVars = getScrollbarVars(horizontal); var scrollbarVarsInverted = getScrollbarVars(!horizontal); var xy = scrollbarVars._x_y; var strDirection = horizontal ? _strBottom : isRTLLeft; var invertedAutoSize = horizontal ? heightAuto : widthAuto; if ( _nativeScrollbarIsOverlaid[xy] && hasOverflow[xy] && hideOverflow[xy + "s"] ) { contentElementCSS[_strMarginMinus + strDirection] = invertedAutoSize ? ignoreOverlayScrollbarHiding ? _strEmpty : _overlayScrollbarDummySize[xy] : _strEmpty; contentElementCSS[_strBorderMinus + strDirection] = (horizontal ? !invertedAutoSize : true) && !ignoreOverlayScrollbarHiding ? _overlayScrollbarDummySize[xy] + borderDesign : _strEmpty; } else { arrangeContent[scrollbarVarsInverted._w_h] = contentElementCSS[_strMarginMinus + strDirection] = contentElementCSS[_strBorderMinus + strDirection] = _strEmpty; arrangeChanged = true; } }; if (_nativeScrollbarStyling) { addRemoveClass( _viewportElement, _classNameViewportNativeScrollbarsInvisible, !ignoreOverlayScrollbarHiding ); } else { setContentElementCSS(true); setContentElementCSS(false); } } if (ignoreOverlayScrollbarHiding) { arrangeContent.w = arrangeContent.h = _strEmpty; arrangeChanged = true; } if (arrangeChanged && !_nativeScrollbarStyling) { contentArrangeElementCSS[_strWidth] = hideOverflow.y ? arrangeContent.w : _strEmpty; contentArrangeElementCSS[_strHeight] = hideOverflow.x ? arrangeContent.h : _strEmpty; if (!_contentArrangeElement) { _contentArrangeElement = FRAMEWORK( generateDiv(_classNameContentArrangeElement) ); _viewportElement.prepend(_contentArrangeElement); } _contentArrangeElement.css(contentArrangeElementCSS); } _contentElement.css(contentElementCSS); } var viewportElementCSS = {}; var paddingElementCSS = {}; var setViewportCSS; if ( hostSizeChanged || hasOverflow.c || hideOverflow.c || contentScrollSize.c || overflowBehaviorChanged || boxSizingChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged || clipAlwaysChanged || heightAutoChanged ) { viewportElementCSS[isRTLRight] = _strEmpty; setViewportCSS = function (horizontal) { var scrollbarVars = getScrollbarVars(horizontal); var scrollbarVarsInverted = getScrollbarVars(!horizontal); var xy = scrollbarVars._x_y; var XY = scrollbarVars._X_Y; var strDirection = horizontal ? _strBottom : isRTLLeft; var reset = function () { viewportElementCSS[strDirection] = _strEmpty; _contentBorderSize[scrollbarVarsInverted._w_h] = 0; }; if (hasOverflow[xy] && hideOverflow[xy + "s"]) { viewportElementCSS[strOverflow + XY] = _strScroll; if (ignoreOverlayScrollbarHiding || _nativeScrollbarStyling) { reset(); } else { viewportElementCSS[strDirection] = -(_nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[xy] : _nativeScrollbarSize[xy]); _contentBorderSize[scrollbarVarsInverted._w_h] = _nativeScrollbarIsOverlaid[xy] ? _overlayScrollbarDummySize[scrollbarVarsInverted._x_y] : 0; } } else { viewportElementCSS[strOverflow + XY] = _strEmpty; reset(); } }; setViewportCSS(true); setViewportCSS(false); // if the scroll container is too small and if there is any overflow with no overlay scrollbar (and scrollbar styling isn't possible), // make viewport element greater in size (Firefox hide Scrollbars fix) // because firefox starts hiding scrollbars on too small elements // with this behavior the overflow calculation may be incorrect or the scrollbars would appear suddenly // https://bugzilla.mozilla.org/show_bug.cgi?id=292284 if ( !_nativeScrollbarStyling && (_viewportSize.h < _nativeScrollbarMinSize.x || _viewportSize.w < _nativeScrollbarMinSize.y) && ((hasOverflow.x && hideOverflow.x && !_nativeScrollbarIsOverlaid.x) || (hasOverflow.y && hideOverflow.y && !_nativeScrollbarIsOverlaid.y)) ) { viewportElementCSS[_strPaddingMinus + _strTop] = _nativeScrollbarMinSize.x; viewportElementCSS[_strMarginMinus + _strTop] = -_nativeScrollbarMinSize.x; viewportElementCSS[_strPaddingMinus + isRTLRight] = _nativeScrollbarMinSize.y; viewportElementCSS[_strMarginMinus + isRTLRight] = -_nativeScrollbarMinSize.y; } else { viewportElementCSS[_strPaddingMinus + _strTop] = viewportElementCSS[_strMarginMinus + _strTop] = viewportElementCSS[_strPaddingMinus + isRTLRight] = viewportElementCSS[_strMarginMinus + isRTLRight] = _strEmpty; } viewportElementCSS[_strPaddingMinus + isRTLLeft] = viewportElementCSS[_strMarginMinus + isRTLLeft] = _strEmpty; //if there is any overflow (x OR y axis) and this overflow shall be hidden, make overflow hidden, else overflow visible if ( (hasOverflow.x && hideOverflow.x) || (hasOverflow.y && hideOverflow.y) || hideOverflowForceTextarea ) { //only hide if is Textarea if (_isTextarea && hideOverflowForceTextarea) { paddingElementCSS[strOverflowX] = paddingElementCSS[ strOverflowY ] = strHidden; } } else { if ( !clipAlways || overflowBehaviorIsVH.x || overflowBehaviorIsVS.x || overflowBehaviorIsVH.y || overflowBehaviorIsVS.y ) { //only un-hide if Textarea if (_isTextarea) { paddingElementCSS[strOverflowX] = paddingElementCSS[ strOverflowY ] = _strEmpty; } viewportElementCSS[strOverflowX] = viewportElementCSS[ strOverflowY ] = strVisible; } } _paddingElement.css(paddingElementCSS); _viewportElement.css(viewportElementCSS); viewportElementCSS = {}; //force soft redraw in webkit because without the scrollbars will may appear because DOM wont be redrawn under special conditions if ( (hasOverflow.c || boxSizingChanged || widthAutoChanged || heightAutoChanged) && !(_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y) ) { var elementStyle = _contentElementNative[LEXICON.s]; var dump; elementStyle.webkitTransform = "scale(1)"; elementStyle.display = "run-in"; dump = _contentElementNative[LEXICON.oH]; elementStyle.display = _strEmpty; //|| dump; //use dump to prevent it from deletion if minify elementStyle.webkitTransform = _strEmpty; } /* //force hard redraw in webkit if native overlaid scrollbars shall appear if (ignoreOverlayScrollbarHidingChanged && ignoreOverlayScrollbarHiding) { _hostElement.hide(); var dump = _hostElementNative[LEXICON.oH]; _hostElement.show(); } */ } //change to direction RTL and width auto Bugfix in Webkit //without this fix, the DOM still thinks the scrollbar is LTR and thus the content is shifted to the left contentElementCSS = {}; if (cssDirectionChanged || widthAutoChanged || heightAutoChanged) { if (_isRTL && widthAuto) { var floatTmp = _contentElement.css(_strFloat); var posLeftWithoutFloat = MATH.round( _contentElement .css(_strFloat, _strEmpty) .css(_strLeft, _strEmpty) .position().left ); _contentElement.css(_strFloat, floatTmp); var posLeftWithFloat = MATH.round( _contentElement.position().left ); if (posLeftWithoutFloat !== posLeftWithFloat) contentElementCSS[_strLeft] = posLeftWithoutFloat; } else { contentElementCSS[_strLeft] = _strEmpty; } } _contentElement.css(contentElementCSS); //handle scroll position if (_isTextarea && contentSizeChanged) { var textareaInfo = getTextareaInfo(); if (textareaInfo) { var textareaRowsChanged = _textareaInfoCache === undefined ? true : textareaInfo._rows !== _textareaInfoCache._rows; var cursorRow = textareaInfo._cursorRow; var cursorCol = textareaInfo._cursorColumn; var widestRow = textareaInfo._widestRow; var lastRow = textareaInfo._rows; var lastCol = textareaInfo._columns; var cursorPos = textareaInfo._cursorPosition; var cursorMax = textareaInfo._cursorMax; var cursorIsLastPosition = cursorPos >= cursorMax && _textareaHasFocus; var textareaScrollAmount = { x: !textareaAutoWrapping && cursorCol === lastCol && cursorRow === widestRow ? _overflowAmountCache.x : -1, y: ( textareaAutoWrapping ? cursorIsLastPosition || (textareaRowsChanged && (previousOverflowAmount ? currScroll.y === previousOverflowAmount.y : false)) : (cursorIsLastPosition || textareaRowsChanged) && cursorRow === lastRow ) ? _overflowAmountCache.y : -1, }; currScroll.x = textareaScrollAmount.x > -1 ? _isRTL && _normalizeRTLCache && _rtlScrollBehavior.i ? 0 : textareaScrollAmount.x : currScroll.x; //if inverted, scroll to 0 -> normalized this means to max scroll offset. currScroll.y = textareaScrollAmount.y > -1 ? textareaScrollAmount.y : currScroll.y; } _textareaInfoCache = textareaInfo; } if ( _isRTL && _rtlScrollBehavior.i && _nativeScrollbarIsOverlaid.y && hasOverflow.x && _normalizeRTLCache ) currScroll.x += _contentBorderSize.w || 0; if (widthAuto) _hostElement[_strScrollLeft](0); if (heightAuto) _hostElement[_strScrollTop](0); _viewportElement[_strScrollLeft](currScroll.x)[_strScrollTop]( currScroll.y ); //scrollbars management: var scrollbarsVisibilityVisible = scrollbarsVisibility === "v"; var scrollbarsVisibilityHidden = scrollbarsVisibility === "h"; var scrollbarsVisibilityAuto = scrollbarsVisibility === "a"; var refreshScrollbarsVisibility = function (showX, showY) { showY = showY === undefined ? showX : showY; refreshScrollbarAppearance(true, showX, canScroll.x); refreshScrollbarAppearance(false, showY, canScroll.y); }; //manage class name which indicates scrollable overflow addRemoveClass( _hostElement, _classNameHostOverflow, hideOverflow.x || hideOverflow.y ); addRemoveClass( _hostElement, _classNameHostOverflowX, hideOverflow.x ); addRemoveClass( _hostElement, _classNameHostOverflowY, hideOverflow.y ); //add or remove rtl class name for styling purposes except when its body, then the scrollbar stays if (cssDirectionChanged && !_isBody) { addRemoveClass(_hostElement, _classNameHostRTL, _isRTL); } //manage the resize feature (CSS3 resize "polyfill" for this plugin) if (_isBody) addClass(_hostElement, _classNameHostResizeDisabled); if (resizeChanged) { addRemoveClass( _hostElement, _classNameHostResizeDisabled, _resizeNone ); addRemoveClass( _scrollbarCornerElement, _classNameScrollbarCornerResize, !_resizeNone ); addRemoveClass( _scrollbarCornerElement, _classNameScrollbarCornerResizeB, _resizeBoth ); addRemoveClass( _scrollbarCornerElement, _classNameScrollbarCornerResizeH, _resizeHorizontal ); addRemoveClass( _scrollbarCornerElement, _classNameScrollbarCornerResizeV, _resizeVertical ); } //manage the scrollbars general visibility + the scrollbar interactivity (unusable class name) if ( scrollbarsVisibilityChanged || overflowBehaviorChanged || hideOverflow.c || hasOverflow.c || ignoreOverlayScrollbarHidingChanged ) { if (ignoreOverlayScrollbarHiding) { if (ignoreOverlayScrollbarHidingChanged) { removeClass(_hostElement, _classNameHostScrolling); if (ignoreOverlayScrollbarHiding) { refreshScrollbarsVisibility(false); } } } else if (scrollbarsVisibilityAuto) { refreshScrollbarsVisibility(canScroll.x, canScroll.y); } else if (scrollbarsVisibilityVisible) { refreshScrollbarsVisibility(true); } else if (scrollbarsVisibilityHidden) { refreshScrollbarsVisibility(false); } } //manage the scrollbars auto hide feature (auto hide them after specific actions) if ( scrollbarsAutoHideChanged || ignoreOverlayScrollbarHidingChanged ) { setupHostMouseTouchEvents( !_scrollbarsAutoHideLeave && !_scrollbarsAutoHideMove ); refreshScrollbarsAutoHide( _scrollbarsAutoHideNever, !_scrollbarsAutoHideNever ); } //manage scrollbars handle length & offset - don't remove! if ( hostSizeChanged || overflowAmount.c || heightAutoChanged || widthAutoChanged || resizeChanged || boxSizingChanged || paddingAbsoluteChanged || ignoreOverlayScrollbarHidingChanged || cssDirectionChanged ) { refreshScrollbarHandleLength(true); refreshScrollbarHandleOffset(true); refreshScrollbarHandleLength(false); refreshScrollbarHandleOffset(false); } //manage interactivity if (scrollbarsClickScrollingChanged) refreshScrollbarsInteractive(true, scrollbarsClickScrolling); if (scrollbarsDragScrollingChanged) refreshScrollbarsInteractive(false, scrollbarsDragScrolling); //callbacks: dispatchCallback( "onDirectionChanged", { isRTL: _isRTL, dir: cssDirection, }, cssDirectionChanged ); dispatchCallback( "onHostSizeChanged", { width: _hostSizeCache.w, height: _hostSizeCache.h, }, hostSizeChanged ); dispatchCallback( "onContentSizeChanged", { width: _contentScrollSizeCache.w, height: _contentScrollSizeCache.h, }, contentSizeChanged ); dispatchCallback( "onOverflowChanged", { x: hasOverflow.x, y: hasOverflow.y, xScrollable: hideOverflow.xs, yScrollable: hideOverflow.ys, clipped: hideOverflow.x || hideOverflow.y, }, hasOverflow.c || hideOverflow.c ); dispatchCallback( "onOverflowAmountChanged", { x: overflowAmount.x, y: overflowAmount.y, }, overflowAmount.c ); } //fix body min size if ( _isBody && _bodyMinSizeCache && (_hasOverflowCache.c || _bodyMinSizeCache.c) ) { //its possible that no min size was measured until now, because the content arrange element was just added now, in this case, measure now the min size. if (!_bodyMinSizeCache.f) bodyMinSizeChanged(); if (_nativeScrollbarIsOverlaid.y && _hasOverflowCache.x) _contentElement.css( _strMinMinus + _strWidth, _bodyMinSizeCache.w + _overlayScrollbarDummySize.y ); if (_nativeScrollbarIsOverlaid.x && _hasOverflowCache.y) _contentElement.css( _strMinMinus + _strHeight, _bodyMinSizeCache.h + _overlayScrollbarDummySize.x ); _bodyMinSizeCache.c = false; } if (_initialized && changedOptions.updateOnLoad) { updateElementsOnLoad(); } //freezeResizeObserver(_sizeObserverElement, false); //freezeResizeObserver(_sizeAutoObserverElement, false); dispatchCallback("onUpdated", { forced: force }); } /** * Updates the found elements of which the load event shall be handled. */ function updateElementsOnLoad() { if (!_isTextarea) { eachUpdateOnLoad(function (i, updateOnLoadSelector) { _contentElement.find(updateOnLoadSelector).each(function (i, el) { // if element doesn't have a updateOnLoadCallback applied if (COMPATIBILITY.inA(el, _updateOnLoadElms) < 0) { _updateOnLoadElms.push(el); FRAMEWORK(el) .off(_updateOnLoadEventName, updateOnLoadCallback) .on(_updateOnLoadEventName, updateOnLoadCallback); } }); }); } } //==== Options ====// /** * Sets new options but doesn't call the update method. * @param newOptions The object which contains the new options. * @returns {*} A object which contains the changed options. */ function setOptions(newOptions) { var validatedOpts = _pluginsOptions._validate( newOptions, _pluginsOptions._template, true, _currentOptions ); _currentOptions = extendDeep( {}, _currentOptions, validatedOpts._default ); _currentPreparedOptions = extendDeep( {}, _currentPreparedOptions, validatedOpts._prepared ); return validatedOpts._prepared; } //==== Structure ====// /** * Builds or destroys the wrapper and helper DOM elements. * @param destroy Indicates whether the DOM shall be build or destroyed. */ /** * Builds or destroys the wrapper and helper DOM elements. * @param destroy Indicates whether the DOM shall be build or destroyed. */ function setupStructureDOM(destroy) { var strParent = "parent"; var classNameResizeObserverHost = "os-resize-observer-host"; var classNameTextareaElementFull = _classNameTextareaElement + _strSpace + _classNameTextInherit; var textareaClass = _isTextarea ? _strSpace + _classNameTextInherit : _strEmpty; var adoptAttrs = _currentPreparedOptions.textarea.inheritedAttrs; var adoptAttrsMap = {}; var applyAdoptedAttrs = function () { var applyAdoptedAttrsElm = destroy ? _targetElement : _hostElement; each(adoptAttrsMap, function (key, value) { if (type(value) == TYPES.s) { if (key == LEXICON.c) applyAdoptedAttrsElm.addClass(value); else applyAdoptedAttrsElm.attr(key, value); } }); }; var hostElementClassNames = [ _classNameHostElement, _classNameHostElementForeign, _classNameHostTextareaElement, _classNameHostResizeDisabled, _classNameHostRTL, _classNameHostScrollbarHorizontalHidden, _classNameHostScrollbarVerticalHidden, _classNameHostTransition, _classNameHostScrolling, _classNameHostOverflow, _classNameHostOverflowX, _classNameHostOverflowY, _classNameThemeNone, _classNameTextareaElement, _classNameTextInherit, _classNameCache, ].join(_strSpace); var hostElementCSS = {}; //get host element as first element, because that's the most upper element and required for the other elements _hostElement = _hostElement || (_isTextarea ? _domExists ? _targetElement[strParent]() [strParent]() [strParent]() [strParent]() : FRAMEWORK(generateDiv(_classNameHostTextareaElement)) : _targetElement); _contentElement = _contentElement || selectOrGenerateDivByClass( _classNameContentElement + textareaClass ); _viewportElement = _viewportElement || selectOrGenerateDivByClass( _classNameViewportElement + textareaClass ); _paddingElement = _paddingElement || selectOrGenerateDivByClass( _classNamePaddingElement + textareaClass ); _sizeObserverElement = _sizeObserverElement || selectOrGenerateDivByClass(classNameResizeObserverHost); _textareaCoverElement = _textareaCoverElement || (_isTextarea ? selectOrGenerateDivByClass(_classNameTextareaCoverElement) : undefined); //add this class to workaround class changing issues with UI frameworks especially Vue if (_domExists) addClass(_hostElement, _classNameHostElementForeign); //on destroy, remove all generated class names from the host element before collecting the adopted attributes //to prevent adopting generated class names if (destroy) removeClass(_hostElement, hostElementClassNames); //collect all adopted attributes adoptAttrs = type(adoptAttrs) == TYPES.s ? adoptAttrs.split(_strSpace) : adoptAttrs; if (COMPATIBILITY.isA(adoptAttrs) && _isTextarea) { each(adoptAttrs, function (i, v) { if (type(v) == TYPES.s) { adoptAttrsMap[v] = destroy ? _hostElement.attr(v) : _targetElement.attr(v); } }); } if (!destroy) { if (_isTextarea) { if (!_currentPreparedOptions.sizeAutoCapable) { hostElementCSS[_strWidth] = _targetElement.css(_strWidth); hostElementCSS[_strHeight] = _targetElement.css(_strHeight); } if (!_domExists) _targetElement .addClass(_classNameTextInherit) .wrap(_hostElement); //jQuery clones elements in wrap functions, so we have to select them again _hostElement = _targetElement[strParent]().css(hostElementCSS); } if (!_domExists) { //add the correct class to the target element addClass( _targetElement, _isTextarea ? classNameTextareaElementFull : _classNameHostElement ); //wrap the content into the generated elements to create the required DOM _hostElement .wrapInner(_contentElement) .wrapInner(_viewportElement) .wrapInner(_paddingElement) .prepend(_sizeObserverElement); //jQuery clones elements in wrap functions, so we have to select them again _contentElement = findFirst( _hostElement, _strDot + _classNameContentElement ); _viewportElement = findFirst( _hostElement, _strDot + _classNameViewportElement ); _paddingElement = findFirst( _hostElement, _strDot + _classNamePaddingElement ); if (_isTextarea) { _contentElement.prepend(_textareaCoverElement); applyAdoptedAttrs(); } } if (_nativeScrollbarStyling) addClass( _viewportElement, _classNameViewportNativeScrollbarsInvisible ); if (_nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y) addClass( _viewportElement, _classNameViewportNativeScrollbarsOverlaid ); if (_isBody) addClass(_htmlElement, _classNameHTMLElement); _sizeObserverElementNative = _sizeObserverElement[0]; _hostElementNative = _hostElement[0]; _paddingElementNative = _paddingElement[0]; _viewportElementNative = _viewportElement[0]; _contentElementNative = _contentElement[0]; updateViewportAttrsFromTarget(); } else { if (_domExists && _initialized) { //clear size observer _sizeObserverElement.children().remove(); //remove the style property and classes from already generated elements each( [ _paddingElement, _viewportElement, _contentElement, _textareaCoverElement, ], function (i, elm) { if (elm) { removeClass( elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy ); } } ); //add classes to the host element which was removed previously to match the expected DOM addClass( _hostElement, _isTextarea ? _classNameHostTextareaElement : _classNameHostElement ); } else { //remove size observer remove(_sizeObserverElement); //unwrap the content to restore DOM _contentElement.contents().unwrap().unwrap().unwrap(); if (_isTextarea) { _targetElement.unwrap(); remove(_hostElement); remove(_textareaCoverElement); applyAdoptedAttrs(); } } if (_isTextarea) _targetElement.removeAttr(LEXICON.s); if (_isBody) removeClass(_htmlElement, _classNameHTMLElement); } } /** * Adds or removes all wrapper elements interactivity events. * @param destroy Indicates whether the Events shall be added or removed. */ function setupStructureEvents() { var textareaKeyDownRestrictedKeyCodes = [ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 123, //F1 to F12 33, 34, //page up, page down 37, 38, 39, 40, //left, up, right, down arrows 16, 17, 18, 19, 20, 144, //Shift, Ctrl, Alt, Pause, CapsLock, NumLock ]; var textareaKeyDownKeyCodesList = []; var textareaUpdateIntervalID; var scrollStopTimeoutId; var scrollStopDelay = 175; var strFocus = "focus"; function updateTextarea(doClearInterval) { textareaUpdate(); _base.update(_strAuto); if (doClearInterval && _autoUpdateRecommended) clearInterval(textareaUpdateIntervalID); } function textareaOnScroll(event) { _targetElement[_strScrollLeft]( _rtlScrollBehavior.i && _normalizeRTLCache ? 9999999 : 0 ); _targetElement[_strScrollTop](0); COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); return false; } function textareaOnDrop(event) { setTimeout(function () { if (!_destroyed) updateTextarea(); }, 50); } function textareaOnFocus() { _textareaHasFocus = true; addClass(_hostElement, strFocus); } function textareaOnFocusout() { _textareaHasFocus = false; textareaKeyDownKeyCodesList = []; removeClass(_hostElement, strFocus); updateTextarea(true); } function textareaOnKeyDown(event) { var keyCode = event.keyCode; if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { if (!textareaKeyDownKeyCodesList[LEXICON.l]) { updateTextarea(); textareaUpdateIntervalID = setInterval( updateTextarea, 1000 / 60 ); } if (inArray(keyCode, textareaKeyDownKeyCodesList) < 0) textareaKeyDownKeyCodesList.push(keyCode); } } function textareaOnKeyUp(event) { var keyCode = event.keyCode; var index = inArray(keyCode, textareaKeyDownKeyCodesList); if (inArray(keyCode, textareaKeyDownRestrictedKeyCodes) < 0) { if (index > -1) textareaKeyDownKeyCodesList.splice(index, 1); if (!textareaKeyDownKeyCodesList[LEXICON.l]) updateTextarea(true); } } function contentOnTransitionEnd(event) { if (_autoUpdateCache === true) return; event = event.originalEvent || event; if (isSizeAffectingCSSProperty(event.propertyName)) _base.update(_strAuto); } function viewportOnScroll(event) { if (!_sleeping) { if (scrollStopTimeoutId !== undefined) clearTimeout(scrollStopTimeoutId); else { if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) refreshScrollbarsAutoHide(true); if (!nativeOverlayScrollbarsAreActive()) addClass(_hostElement, _classNameHostScrolling); dispatchCallback("onScrollStart", event); } //if a scrollbars handle gets dragged, the mousemove event is responsible for refreshing the handle offset //because if CSS scroll-snap is used, the handle offset gets only refreshed on every snap point //this looks laggy & clunky, it looks much better if the offset refreshes with the mousemove if (!_scrollbarsHandlesDefineScrollPos) { refreshScrollbarHandleOffset(true); refreshScrollbarHandleOffset(false); } dispatchCallback("onScroll", event); scrollStopTimeoutId = setTimeout(function () { if (!_destroyed) { //OnScrollStop: clearTimeout(scrollStopTimeoutId); scrollStopTimeoutId = undefined; if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) refreshScrollbarsAutoHide(false); if (!nativeOverlayScrollbarsAreActive()) removeClass(_hostElement, _classNameHostScrolling); dispatchCallback("onScrollStop", event); } }, scrollStopDelay); } } if (_isTextarea) { if (_msieVersion > 9 || !_autoUpdateRecommended) { addDestroyEventListener(_targetElement, "input", updateTextarea); } else { addDestroyEventListener( _targetElement, [_strKeyDownEvent, _strKeyUpEvent], [textareaOnKeyDown, textareaOnKeyUp] ); } addDestroyEventListener( _targetElement, [_strScroll, "drop", strFocus, strFocus + "out"], [ textareaOnScroll, textareaOnDrop, textareaOnFocus, textareaOnFocusout, ] ); } else { addDestroyEventListener( _contentElement, _strTransitionEndEvent, contentOnTransitionEnd ); } addDestroyEventListener( _viewportElement, _strScroll, viewportOnScroll, true ); } //==== Scrollbars ====// /** * Builds or destroys all scrollbar DOM elements (scrollbar, track, handle) * @param destroy Indicates whether the DOM shall be build or destroyed. */ function setupScrollbarsDOM(destroy) { var selectOrGenerateScrollbarDOM = function (isHorizontal) { var scrollbarClassName = isHorizontal ? _classNameScrollbarHorizontal : _classNameScrollbarVertical; var scrollbar = selectOrGenerateDivByClass( _classNameScrollbar + _strSpace + scrollbarClassName, true ); var track = selectOrGenerateDivByClass( _classNameScrollbarTrack, scrollbar ); var handle = selectOrGenerateDivByClass( _classNameScrollbarHandle, scrollbar ); if (!_domExists && !destroy) { scrollbar.append(track); track.append(handle); } return { _scrollbar: scrollbar, _track: track, _handle: handle, }; }; function resetScrollbarDOM(isHorizontal) { var scrollbarVars = getScrollbarVars(isHorizontal); var scrollbar = scrollbarVars._scrollbar; var track = scrollbarVars._track; var handle = scrollbarVars._handle; if (_domExists && _initialized) { each([scrollbar, track, handle], function (i, elm) { removeClass( elm.removeAttr(LEXICON.s), _classNamesDynamicDestroy ); }); } else { remove( scrollbar || selectOrGenerateScrollbarDOM(isHorizontal)._scrollbar ); } } var horizontalElements; var verticalElements; if (!destroy) { horizontalElements = selectOrGenerateScrollbarDOM(true); verticalElements = selectOrGenerateScrollbarDOM(); _scrollbarHorizontalElement = horizontalElements._scrollbar; _scrollbarHorizontalTrackElement = horizontalElements._track; _scrollbarHorizontalHandleElement = horizontalElements._handle; _scrollbarVerticalElement = verticalElements._scrollbar; _scrollbarVerticalTrackElement = verticalElements._track; _scrollbarVerticalHandleElement = verticalElements._handle; if (!_domExists) { _paddingElement.after(_scrollbarVerticalElement); _paddingElement.after(_scrollbarHorizontalElement); } } else { resetScrollbarDOM(true); resetScrollbarDOM(); } } /** * Initializes all scrollbar interactivity events. (track and handle dragging, clicking, scrolling) * @param isHorizontal True if the target scrollbar is the horizontal scrollbar, false if the target scrollbar is the vertical scrollbar. */ function setupScrollbarEvents(isHorizontal) { var scrollbarVars = getScrollbarVars(isHorizontal); var scrollbarVarsInfo = scrollbarVars._info; var insideIFrame = _windowElementNative.top !== _windowElementNative; var xy = scrollbarVars._x_y; var XY = scrollbarVars._X_Y; var scroll = _strScroll + scrollbarVars._Left_Top; var strActive = "active"; var strSnapHandle = "snapHandle"; var strClickEvent = "click"; var scrollDurationFactor = 1; var increaseDecreaseScrollAmountKeyCodes = [16, 17]; //shift, ctrl var trackTimeout; var mouseDownScroll; var mouseDownOffset; var mouseDownInvertedScale; function getPointerPosition(event) { return _msieVersion && insideIFrame ? event["screen" + XY] : COMPATIBILITY.page(event)[xy]; //use screen coordinates in EDGE & IE because the page values are incorrect in frames. } function getPreparedScrollbarsOption(name) { return _currentPreparedOptions.scrollbars[name]; } function increaseTrackScrollAmount() { scrollDurationFactor = 0.5; } function decreaseTrackScrollAmount() { scrollDurationFactor = 1; } function stopClickEventPropagation(event) { COMPATIBILITY.stpP(event); } function documentKeyDown(event) { if ( inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1 ) increaseTrackScrollAmount(); } function documentKeyUp(event) { if ( inArray(event.keyCode, increaseDecreaseScrollAmountKeyCodes) > -1 ) decreaseTrackScrollAmount(); } function onMouseTouchDownContinue(event) { var originalEvent = event.originalEvent || event; var isTouchEvent = originalEvent.touches !== undefined; return _sleeping || _destroyed || nativeOverlayScrollbarsAreActive() || !_scrollbarsDragScrollingCache || (isTouchEvent && !getPreparedScrollbarsOption("touchSupport")) ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; } function documentDragMove(event) { if (onMouseTouchDownContinue(event)) { var trackLength = scrollbarVarsInfo._trackLength; var handleLength = scrollbarVarsInfo._handleLength; var scrollRange = scrollbarVarsInfo._maxScroll; var scrollRaw = (getPointerPosition(event) - mouseDownOffset) * mouseDownInvertedScale; var scrollDeltaPercent = scrollRaw / (trackLength - handleLength); var scrollDelta = scrollRange * scrollDeltaPercent; scrollDelta = isFinite(scrollDelta) ? scrollDelta : 0; if (_isRTL && isHorizontal && !_rtlScrollBehavior.i) scrollDelta *= -1; _viewportElement[scroll]( MATH.round(mouseDownScroll + scrollDelta) ); if (_scrollbarsHandlesDefineScrollPos) refreshScrollbarHandleOffset( isHorizontal, mouseDownScroll + scrollDelta ); if (!_supportPassiveEvents) COMPATIBILITY.prvD(event); } else documentMouseTouchUp(event); } function documentMouseTouchUp(event) { event = event || event.originalEvent; setupResponsiveEventListener( _documentElement, [ _strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent, ], [ documentDragMove, documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart, ], true ); COMPATIBILITY.rAF()(function () { setupResponsiveEventListener( _documentElement, strClickEvent, stopClickEventPropagation, true, { _capture: true } ); }); if (_scrollbarsHandlesDefineScrollPos) refreshScrollbarHandleOffset(isHorizontal, true); _scrollbarsHandlesDefineScrollPos = false; removeClass(_bodyElement, _classNameDragging); removeClass(scrollbarVars._handle, strActive); removeClass(scrollbarVars._track, strActive); removeClass(scrollbarVars._scrollbar, strActive); mouseDownScroll = undefined; mouseDownOffset = undefined; mouseDownInvertedScale = 1; decreaseTrackScrollAmount(); if (trackTimeout !== undefined) { _base.scrollStop(); clearTimeout(trackTimeout); trackTimeout = undefined; } if (event) { var rect = _hostElementNative[LEXICON.bCR](); var mouseInsideHost = event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom; //if mouse is outside host element if (!mouseInsideHost) hostOnMouseLeave(); if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) refreshScrollbarsAutoHide(false); } } function onHandleMouseTouchDown(event) { if (onMouseTouchDownContinue(event)) onHandleMouseTouchDownAction(event); } function onHandleMouseTouchDownAction(event) { mouseDownScroll = _viewportElement[scroll](); mouseDownScroll = isNaN(mouseDownScroll) ? 0 : mouseDownScroll; if ((_isRTL && isHorizontal && !_rtlScrollBehavior.n) || !_isRTL) mouseDownScroll = mouseDownScroll < 0 ? 0 : mouseDownScroll; mouseDownInvertedScale = getHostElementInvertedScale()[xy]; mouseDownOffset = getPointerPosition(event); _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); addClass(_bodyElement, _classNameDragging); addClass(scrollbarVars._handle, strActive); addClass(scrollbarVars._scrollbar, strActive); setupResponsiveEventListener( _documentElement, [ _strMouseTouchMoveEvent, _strMouseTouchUpEvent, _strSelectStartEvent, ], [documentDragMove, documentMouseTouchUp, documentOnSelectStart] ); COMPATIBILITY.rAF()(function () { setupResponsiveEventListener( _documentElement, strClickEvent, stopClickEventPropagation, false, { _capture: true } ); }); if (_msieVersion || !_documentMixed) COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); } function onTrackMouseTouchDown(event) { if (onMouseTouchDownContinue(event)) { var handleToViewportRatio = scrollbarVars._info._handleLength / Math.round( MATH.min( 1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h] ) * scrollbarVars._info._trackLength ); var scrollDistance = MATH.round( _viewportSize[scrollbarVars._w_h] * handleToViewportRatio ); var scrollBaseDuration = 270 * handleToViewportRatio; var scrollFirstIterationDelay = 400 * handleToViewportRatio; var trackOffset = scrollbarVars._track.offset()[scrollbarVars._left_top]; var ctrlKey = event.ctrlKey; var instantScroll = event.shiftKey; var instantScrollTransition = instantScroll && ctrlKey; var isFirstIteration = true; var easing = "linear"; var decreaseScroll; var finishedCondition; var scrollActionFinsished = function (transition) { if (_scrollbarsHandlesDefineScrollPos) refreshScrollbarHandleOffset(isHorizontal, transition); }; var scrollActionInstantFinished = function () { scrollActionFinsished(); onHandleMouseTouchDownAction(event); }; var scrollAction = function () { if (!_destroyed) { var mouseOffset = (mouseDownOffset - trackOffset) * mouseDownInvertedScale; var handleOffset = scrollbarVarsInfo._handleOffset; var trackLength = scrollbarVarsInfo._trackLength; var handleLength = scrollbarVarsInfo._handleLength; var scrollRange = scrollbarVarsInfo._maxScroll; var currScroll = scrollbarVarsInfo._currentScroll; var scrollDuration = scrollBaseDuration * scrollDurationFactor; var timeoutDelay = isFirstIteration ? MATH.max(scrollFirstIterationDelay, scrollDuration) : scrollDuration; var instantScrollPosition = scrollRange * ((mouseOffset - handleLength / 2) / (trackLength - handleLength)); // 100% * positionPercent var rtlIsNormal = _isRTL && isHorizontal && ((!_rtlScrollBehavior.i && !_rtlScrollBehavior.n) || _normalizeRTLCache); var decreaseScrollCondition = rtlIsNormal ? handleOffset < mouseOffset : handleOffset > mouseOffset; var scrollObj = {}; var animationObj = { easing: easing, step: function (now) { if (_scrollbarsHandlesDefineScrollPos) { _viewportElement[scroll](now); //https://github.com/jquery/jquery/issues/4340 refreshScrollbarHandleOffset(isHorizontal, now); } }, }; instantScrollPosition = isFinite(instantScrollPosition) ? instantScrollPosition : 0; instantScrollPosition = _isRTL && isHorizontal && !_rtlScrollBehavior.i ? scrollRange - instantScrollPosition : instantScrollPosition; //_base.scrollStop(); if (instantScroll) { _viewportElement[scroll](instantScrollPosition); //scroll instantly to new position if (instantScrollTransition) { //get the scroll position after instant scroll (in case CSS Snap Points are used) to get the correct snapped scroll position //and the animation stops at the correct point instantScrollPosition = _viewportElement[scroll](); //scroll back to the position before instant scrolling so animation can be performed _viewportElement[scroll](currScroll); instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.i ? scrollRange - instantScrollPosition : instantScrollPosition; instantScrollPosition = rtlIsNormal && _rtlScrollBehavior.n ? -instantScrollPosition : instantScrollPosition; scrollObj[xy] = instantScrollPosition; _base.scroll( scrollObj, extendDeep(animationObj, { duration: 130, complete: scrollActionInstantFinished, }) ); } else scrollActionInstantFinished(); } else { decreaseScroll = isFirstIteration ? decreaseScrollCondition : decreaseScroll; finishedCondition = rtlIsNormal ? decreaseScroll ? handleOffset + handleLength >= mouseOffset : handleOffset <= mouseOffset : decreaseScroll ? handleOffset <= mouseOffset : handleOffset + handleLength >= mouseOffset; if (finishedCondition) { clearTimeout(trackTimeout); _base.scrollStop(); trackTimeout = undefined; scrollActionFinsished(true); } else { trackTimeout = setTimeout(scrollAction, timeoutDelay); scrollObj[xy] = (decreaseScroll ? "-=" : "+=") + scrollDistance; _base.scroll( scrollObj, extendDeep(animationObj, { duration: scrollDuration, }) ); } isFirstIteration = false; } } }; if (ctrlKey) increaseTrackScrollAmount(); mouseDownInvertedScale = getHostElementInvertedScale()[xy]; mouseDownOffset = COMPATIBILITY.page(event)[xy]; _scrollbarsHandlesDefineScrollPos = !getPreparedScrollbarsOption(strSnapHandle); addClass(_bodyElement, _classNameDragging); addClass(scrollbarVars._track, strActive); addClass(scrollbarVars._scrollbar, strActive); setupResponsiveEventListener( _documentElement, [ _strMouseTouchUpEvent, _strKeyDownEvent, _strKeyUpEvent, _strSelectStartEvent, ], [ documentMouseTouchUp, documentKeyDown, documentKeyUp, documentOnSelectStart, ] ); scrollAction(); COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); } } function onTrackMouseTouchEnter(event) { //make sure both scrollbars will stay visible if one scrollbar is hovered if autoHide is "scroll" or "move". _scrollbarsHandleHovered = true; if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) refreshScrollbarsAutoHide(true); } function onTrackMouseTouchLeave(event) { _scrollbarsHandleHovered = false; if (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove) refreshScrollbarsAutoHide(false); } function onScrollbarMouseTouchDown(event) { COMPATIBILITY.stpP(event); } addDestroyEventListener( scrollbarVars._handle, _strMouseTouchDownEvent, onHandleMouseTouchDown ); addDestroyEventListener( scrollbarVars._track, [_strMouseTouchDownEvent, _strMouseEnter, _strMouseLeave], [ onTrackMouseTouchDown, onTrackMouseTouchEnter, onTrackMouseTouchLeave, ] ); addDestroyEventListener( scrollbarVars._scrollbar, _strMouseTouchDownEvent, onScrollbarMouseTouchDown ); if (_supportTransition) { addDestroyEventListener( scrollbarVars._scrollbar, _strTransitionEndEvent, function (event) { if (event.target !== scrollbarVars._scrollbar[0]) return; refreshScrollbarHandleLength(isHorizontal); refreshScrollbarHandleOffset(isHorizontal); } ); } } /** * Shows or hides the given scrollbar and applied a class name which indicates if the scrollbar is scrollable or not. * @param isHorizontal True if the horizontal scrollbar is the target, false if the vertical scrollbar is the target. * @param shallBeVisible True if the scrollbar shall be shown, false if hidden. * @param canScroll True if the scrollbar is scrollable, false otherwise. */ function refreshScrollbarAppearance( isHorizontal, shallBeVisible, canScroll ) { var scrollbarHiddenClassName = isHorizontal ? _classNameHostScrollbarHorizontalHidden : _classNameHostScrollbarVerticalHidden; var scrollbarElement = isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement; addRemoveClass( _hostElement, scrollbarHiddenClassName, !shallBeVisible ); addRemoveClass( scrollbarElement, _classNameScrollbarUnusable, !canScroll ); } /** * Autoshows / autohides both scrollbars with. * @param shallBeVisible True if the scrollbars shall be autoshown (only the case if they are hidden by a autohide), false if the shall be auto hidden. * @param delayfree True if the scrollbars shall be hidden without a delay, false or undefined otherwise. */ function refreshScrollbarsAutoHide(shallBeVisible, delayfree) { clearTimeout(_scrollbarsAutoHideTimeoutId); if (shallBeVisible) { //if(_hasOverflowCache.x && _hideOverflowCache.xs) removeClass( _scrollbarHorizontalElement, _classNameScrollbarAutoHidden ); //if(_hasOverflowCache.y && _hideOverflowCache.ys) removeClass( _scrollbarVerticalElement, _classNameScrollbarAutoHidden ); } else { var anyActive; var strActive = "active"; var hide = function () { if (!_scrollbarsHandleHovered && !_destroyed) { anyActive = _scrollbarHorizontalHandleElement.hasClass(strActive) || _scrollbarVerticalHandleElement.hasClass(strActive); if ( !anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave) ) addClass( _scrollbarHorizontalElement, _classNameScrollbarAutoHidden ); if ( !anyActive && (_scrollbarsAutoHideScroll || _scrollbarsAutoHideMove || _scrollbarsAutoHideLeave) ) addClass( _scrollbarVerticalElement, _classNameScrollbarAutoHidden ); } }; if (_scrollbarsAutoHideDelay > 0 && delayfree !== true) _scrollbarsAutoHideTimeoutId = setTimeout( hide, _scrollbarsAutoHideDelay ); else hide(); } } /** * Refreshes the handle length of the given scrollbar. * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. */ function refreshScrollbarHandleLength(isHorizontal) { var handleCSS = {}; var scrollbarVars = getScrollbarVars(isHorizontal); var scrollbarVarsInfo = scrollbarVars._info; var digit = 1000000; //get and apply intended handle length var handleRatio = MATH.min( 1, _viewportSize[scrollbarVars._w_h] / _contentScrollSizeCache[scrollbarVars._w_h] ); handleCSS[scrollbarVars._width_height] = MATH.floor(handleRatio * 100 * digit) / digit + "%"; //the last * digit / digit is for flooring to the 4th digit if (!nativeOverlayScrollbarsAreActive()) scrollbarVars._handle.css(handleCSS); //measure the handle length to respect min & max length scrollbarVarsInfo._handleLength = scrollbarVars._handle[0]["offset" + scrollbarVars._Width_Height]; scrollbarVarsInfo._handleLengthRatio = handleRatio; } /** * Refreshes the handle offset of the given scrollbar. * @param isHorizontal True if the horizontal scrollbar handle shall be refreshed, false if the vertical one shall be refreshed. * @param scrollOrTransition The scroll position of the given scrollbar axis to which the handle shall be moved or a boolean which indicates whether a transition shall be applied. If undefined or boolean if the current scroll-offset is taken. (if isHorizontal ? scrollLeft : scrollTop) */ function refreshScrollbarHandleOffset( isHorizontal, scrollOrTransition ) { var transition = type(scrollOrTransition) == TYPES.b; var transitionDuration = 250; var isRTLisHorizontal = _isRTL && isHorizontal; var scrollbarVars = getScrollbarVars(isHorizontal); var scrollbarVarsInfo = scrollbarVars._info; var strTranslateBrace = "translate("; var strTransform = VENDORS._cssProperty("transform"); var strTransition = VENDORS._cssProperty("transition"); var nativeScroll = isHorizontal ? _viewportElement[_strScrollLeft]() : _viewportElement[_strScrollTop](); var currentScroll = scrollOrTransition === undefined || transition ? nativeScroll : scrollOrTransition; //measure the handle length to respect min & max length var handleLength = scrollbarVarsInfo._handleLength; var trackLength = scrollbarVars._track[0]["offset" + scrollbarVars._Width_Height]; var handleTrackDiff = trackLength - handleLength; var handleCSS = {}; var transformOffset; var translateValue; //DONT use the variable '_contentScrollSizeCache[scrollbarVars._w_h]' instead of '_viewportElement[0]['scroll' + scrollbarVars._Width_Height]' // because its a bit behind during the small delay when content size updates //(delay = mutationObserverContentLag, if its 0 then this var could be used) var maxScroll = (_viewportElementNative[_strScroll + scrollbarVars._Width_Height] - _viewportElementNative["client" + scrollbarVars._Width_Height]) * (_rtlScrollBehavior.n && isRTLisHorizontal ? -1 : 1); //* -1 if rtl scroll max is negative var getScrollRatio = function (base) { return isNaN(base / maxScroll) ? 0 : MATH.max(0, MATH.min(1, base / maxScroll)); }; var getHandleOffset = function (scrollRatio) { var offset = handleTrackDiff * scrollRatio; offset = isNaN(offset) ? 0 : offset; offset = isRTLisHorizontal && !_rtlScrollBehavior.i ? trackLength - handleLength - offset : offset; offset = MATH.max(0, offset); return offset; }; var scrollRatio = getScrollRatio(nativeScroll); var unsnappedScrollRatio = getScrollRatio(currentScroll); var handleOffset = getHandleOffset(unsnappedScrollRatio); var snappedHandleOffset = getHandleOffset(scrollRatio); scrollbarVarsInfo._maxScroll = maxScroll; scrollbarVarsInfo._currentScroll = nativeScroll; scrollbarVarsInfo._currentScrollRatio = scrollRatio; if (_supportTransform) { transformOffset = isRTLisHorizontal ? -(trackLength - handleLength - handleOffset) : handleOffset; //in px //transformOffset = (transformOffset / trackLength * 100) * (trackLength / handleLength); //in % translateValue = isHorizontal ? strTranslateBrace + transformOffset + "px, 0)" : strTranslateBrace + "0, " + transformOffset + "px)"; handleCSS[strTransform] = translateValue; //apply or clear up transition if (_supportTransition) handleCSS[strTransition] = transition && MATH.abs(handleOffset - scrollbarVarsInfo._handleOffset) > 1 ? getCSSTransitionString(scrollbarVars._handle) + ", " + (strTransform + _strSpace + transitionDuration + "ms") : _strEmpty; } else handleCSS[scrollbarVars._left_top] = handleOffset; //only apply css if offset has changed and overflow exists. if (!nativeOverlayScrollbarsAreActive()) { scrollbarVars._handle.css(handleCSS); //clear up transition if (_supportTransform && _supportTransition && transition) { scrollbarVars._handle.one(_strTransitionEndEvent, function () { if (!_destroyed) scrollbarVars._handle.css(strTransition, _strEmpty); }); } } scrollbarVarsInfo._handleOffset = handleOffset; scrollbarVarsInfo._snappedHandleOffset = snappedHandleOffset; scrollbarVarsInfo._trackLength = trackLength; } /** * Refreshes the interactivity of the given scrollbar element. * @param isTrack True if the track element is the target, false if the handle element is the target. * @param value True for interactivity false for no interactivity. */ function refreshScrollbarsInteractive(isTrack, value) { var action = value ? "removeClass" : "addClass"; var element1 = isTrack ? _scrollbarHorizontalTrackElement : _scrollbarHorizontalHandleElement; var element2 = isTrack ? _scrollbarVerticalTrackElement : _scrollbarVerticalHandleElement; var className = isTrack ? _classNameScrollbarTrackOff : _classNameScrollbarHandleOff; element1[action](className); element2[action](className); } /** * Returns a object which is used for fast access for specific variables. * @param isHorizontal True if the horizontal scrollbar vars shall be accessed, false if the vertical scrollbar vars shall be accessed. * @returns {{wh: string, WH: string, lt: string, _wh: string, _lt: string, t: *, h: *, c: {}, s: *}} */ function getScrollbarVars(isHorizontal) { return { _width_height: isHorizontal ? _strWidth : _strHeight, _Width_Height: isHorizontal ? "Width" : "Height", _left_top: isHorizontal ? _strLeft : _strTop, _Left_Top: isHorizontal ? "Left" : "Top", _x_y: isHorizontal ? _strX : _strY, _X_Y: isHorizontal ? "X" : "Y", _w_h: isHorizontal ? "w" : "h", _l_t: isHorizontal ? "l" : "t", _track: isHorizontal ? _scrollbarHorizontalTrackElement : _scrollbarVerticalTrackElement, _handle: isHorizontal ? _scrollbarHorizontalHandleElement : _scrollbarVerticalHandleElement, _scrollbar: isHorizontal ? _scrollbarHorizontalElement : _scrollbarVerticalElement, _info: isHorizontal ? _scrollHorizontalInfo : _scrollVerticalInfo, }; } //==== Scrollbar Corner ====// /** * Builds or destroys the scrollbar corner DOM element. * @param destroy Indicates whether the DOM shall be build or destroyed. */ function setupScrollbarCornerDOM(destroy) { _scrollbarCornerElement = _scrollbarCornerElement || selectOrGenerateDivByClass(_classNameScrollbarCorner, true); if (!destroy) { if (!_domExists) { _hostElement.append(_scrollbarCornerElement); } } else { if (_domExists && _initialized) { removeClass( _scrollbarCornerElement.removeAttr(LEXICON.s), _classNamesDynamicDestroy ); } else { remove(_scrollbarCornerElement); } } } /** * Initializes all scrollbar corner interactivity events. */ function setupScrollbarCornerEvents() { var insideIFrame = _windowElementNative.top !== _windowElementNative; var mouseDownPosition = {}; var mouseDownSize = {}; var mouseDownInvertedScale = {}; var reconnectMutationObserver; function documentDragMove(event) { if (onMouseTouchDownContinue(event)) { var pageOffset = getCoordinates(event); var hostElementCSS = {}; if (_resizeHorizontal || _resizeBoth) hostElementCSS[_strWidth] = mouseDownSize.w + (pageOffset.x - mouseDownPosition.x) * mouseDownInvertedScale.x; if (_resizeVertical || _resizeBoth) hostElementCSS[_strHeight] = mouseDownSize.h + (pageOffset.y - mouseDownPosition.y) * mouseDownInvertedScale.y; _hostElement.css(hostElementCSS); COMPATIBILITY.stpP(event); } else { documentMouseTouchUp(event); } } function documentMouseTouchUp(event) { var eventIsTrusted = event !== undefined; setupResponsiveEventListener( _documentElement, [ _strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent, ], [documentOnSelectStart, documentDragMove, documentMouseTouchUp], true ); removeClass(_bodyElement, _classNameDragging); if (_scrollbarCornerElement.releaseCapture) _scrollbarCornerElement.releaseCapture(); if (eventIsTrusted) { if (reconnectMutationObserver) connectMutationObservers(); _base.update(_strAuto); } reconnectMutationObserver = false; } function onMouseTouchDownContinue(event) { var originalEvent = event.originalEvent || event; var isTouchEvent = originalEvent.touches !== undefined; return _sleeping || _destroyed ? false : COMPATIBILITY.mBtn(event) === 1 || isTouchEvent; } function getCoordinates(event) { return _msieVersion && insideIFrame ? { x: event.screenX, y: event.screenY } : COMPATIBILITY.page(event); } addDestroyEventListener( _scrollbarCornerElement, _strMouseTouchDownEvent, function (event) { if (onMouseTouchDownContinue(event) && !_resizeNone) { if (_mutationObserversConnected) { reconnectMutationObserver = true; disconnectMutationObservers(); } mouseDownPosition = getCoordinates(event); mouseDownSize.w = _hostElementNative[LEXICON.oW] - (!_isBorderBox ? _paddingX : 0); mouseDownSize.h = _hostElementNative[LEXICON.oH] - (!_isBorderBox ? _paddingY : 0); mouseDownInvertedScale = getHostElementInvertedScale(); setupResponsiveEventListener( _documentElement, [ _strSelectStartEvent, _strMouseTouchMoveEvent, _strMouseTouchUpEvent, ], [ documentOnSelectStart, documentDragMove, documentMouseTouchUp, ] ); addClass(_bodyElement, _classNameDragging); if (_scrollbarCornerElement.setCapture) _scrollbarCornerElement.setCapture(); COMPATIBILITY.prvD(event); COMPATIBILITY.stpP(event); } } ); } //==== Utils ====// /** * Calls the callback with the given name. The Context of this callback is always _base (this). * @param name The name of the target which shall be called. * @param args The args with which the callback shall be called. * @param dependent Boolean which decides whether the callback shall be fired, undefined is like a "true" value. */ function dispatchCallback(name, args, dependent) { if (dependent === false) return; if (_initialized) { var callback = _currentPreparedOptions.callbacks[name]; var extensionOnName = name; var ext; if (extensionOnName.substr(0, 2) === "on") extensionOnName = extensionOnName.substr(2, 1).toLowerCase() + extensionOnName.substr(3); if (type(callback) == TYPES.f) callback.call(_base, args); each(_extensions, function () { ext = this; if (type(ext.on) == TYPES.f) ext.on(extensionOnName, args); }); } else if (!_destroyed) _callbacksInitQeueue.push({ n: name, a: args }); } /** * Sets the "top, right, bottom, left" properties, with a given prefix, of the given css object. * @param targetCSSObject The css object to which the values shall be applied. * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) * @param values A array of values which shall be applied to the "top, right, bottom, left" -properties. The array order is [top, right, bottom, left]. * If this argument is undefined the value '' (empty string) will be applied to all properties. */ function setTopRightBottomLeft(targetCSSObject, prefix, values) { prefix = prefix || _strEmpty; values = values || [_strEmpty, _strEmpty, _strEmpty, _strEmpty]; targetCSSObject[prefix + _strTop] = values[0]; targetCSSObject[prefix + _strRight] = values[1]; targetCSSObject[prefix + _strBottom] = values[2]; targetCSSObject[prefix + _strLeft] = values[3]; } /** * Gets the "top, right, bottom, left" CSS properties of the CSS property with the given prefix from the host element. * @param prefix The prefix of the "top, right, bottom, left" css properties. (example: 'padding-' is a valid prefix) * @param suffix The suffix of the "top, right, bottom, left" css properties. (example: 'border-' is a valid prefix with '-width' is a valid suffix) * @param zeroX True if the x axis shall be 0. * @param zeroY True if the y axis shall be 0. * @returns {{}} The object which contains the numbers of the read CSS properties. */ function getTopRightBottomLeftHost(prefix, suffix, zeroX, zeroY) { suffix = suffix || _strEmpty; prefix = prefix || _strEmpty; return { t: zeroY ? 0 : parseToZeroOrNumber( _hostElement.css(prefix + _strTop + suffix) ), r: zeroX ? 0 : parseToZeroOrNumber( _hostElement.css(prefix + _strRight + suffix) ), b: zeroY ? 0 : parseToZeroOrNumber( _hostElement.css(prefix + _strBottom + suffix) ), l: zeroX ? 0 : parseToZeroOrNumber( _hostElement.css(prefix + _strLeft + suffix) ), }; } /** * Returns the computed CSS transition string from the given element. * @param element The element from which the transition string shall be returned. * @returns {string} The CSS transition string from the given element. */ function getCSSTransitionString(element) { var transitionStr = VENDORS._cssProperty("transition"); var assembledValue = element.css(transitionStr); if (assembledValue) return assembledValue; var regExpString = "\\s*(" + "([^,(]+(\\(.+?\\))?)+" + ")[\\s,]*"; var regExpMain = new RegExp(regExpString); var regExpValidate = new RegExp("^(" + regExpString + ")+$"); var properties = "property duration timing-function delay".split(" "); var result = []; var strResult; var valueArray; var i = 0; var j; var splitCssStyleByComma = function (str) { strResult = []; if (!str.match(regExpValidate)) return str; while (str.match(regExpMain)) { strResult.push(RegExp.$1); str = str.replace(regExpMain, _strEmpty); } return strResult; }; for (; i < properties[LEXICON.l]; i++) { valueArray = splitCssStyleByComma( element.css(transitionStr + "-" + properties[i]) ); for (j = 0; j < valueArray[LEXICON.l]; j++) result[j] = (result[j] ? result[j] + _strSpace : _strEmpty) + valueArray[j]; } return result.join(", "); } /** * Generates a Regular Expression which matches with a string which starts with 'os-host'. * @param {boolean} withCurrClassNameOption The Regular Expression also matches if the string is the current ClassName option (multiple values splitted by space possible). * @param {boolean} withOldClassNameOption The Regular Expression also matches if the string is the old ClassName option (multiple values splitted by space possible). */ function createHostClassNameRegExp( withCurrClassNameOption, withOldClassNameOption ) { var i; var split; var appendix; var appendClasses = function (classes, condition) { appendix = ""; if (condition && typeof classes == TYPES.s) { split = classes.split(_strSpace); for (i = 0; i < split[LEXICON.l]; i++) appendix += "|" + split[i] + "$"; // split[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') for escaping regex characters } return appendix; }; return new RegExp( "(^" + _classNameHostElement + "([-_].+|)$)" + appendClasses(_classNameCache, withCurrClassNameOption) + appendClasses(_oldClassName, withOldClassNameOption), "g" ); } /** * Calculates the host-elements inverted scale. (invertedScale = 1 / scale) * @returns {{x: number, y: number}} The scale of the host-element. */ function getHostElementInvertedScale() { var rect = _paddingElementNative[LEXICON.bCR](); return { x: _supportTransform ? 1 / (MATH.round(rect.width) / _paddingElementNative[LEXICON.oW]) || 1 : 1, y: _supportTransform ? 1 / (MATH.round(rect.height) / _paddingElementNative[LEXICON.oH]) || 1 : 1, }; } /** * Checks whether the given object is a HTMLElement. * @param o The object which shall be checked. * @returns {boolean} True the given object is a HTMLElement, false otherwise. */ function isHTMLElement(o) { var strOwnerDocument = "ownerDocument"; var strHTMLElement = "HTMLElement"; var wnd = o && o[strOwnerDocument] ? o[strOwnerDocument].parentWindow || window : window; return typeof wnd[strHTMLElement] == TYPES.o ? o instanceof wnd[strHTMLElement] //DOM2 : o && typeof o == TYPES.o && o !== null && o.nodeType === 1 && typeof o.nodeName == TYPES.s; } /** * Compares 2 arrays and returns the differences between them as a array. * @param a1 The first array which shall be compared. * @param a2 The second array which shall be compared. * @returns {Array} The differences between the two arrays. */ function getArrayDifferences(a1, a2) { var a = []; var diff = []; var i; var k; for (i = 0; i < a1.length; i++) a[a1[i]] = true; for (i = 0; i < a2.length; i++) { if (a[a2[i]]) delete a[a2[i]]; else a[a2[i]] = true; } for (k in a) diff.push(k); return diff; } /** * Returns Zero or the number to which the value can be parsed. * @param value The value which shall be parsed. * @param toFloat Indicates whether the number shall be parsed to a float. */ function parseToZeroOrNumber(value, toFloat) { var num = toFloat ? parseFloat(value) : parseInt(value, 10); return isNaN(num) ? 0 : num; } /** * Gets several information of the textarea and returns them as a object or undefined if the browser doesn't support it. * @returns {{cursorRow: Number, cursorCol, rows: Number, cols: number, wRow: number, pos: number, max : number}} or undefined if not supported. */ function getTextareaInfo() { //read needed values var textareaCursorPosition = _targetElementNative.selectionStart; if (textareaCursorPosition === undefined) return; var textareaValue = _targetElement.val(); var textareaLength = textareaValue[LEXICON.l]; var textareaRowSplit = textareaValue.split("\n"); var textareaLastRow = textareaRowSplit[LEXICON.l]; var textareaCurrentCursorRowSplit = textareaValue .substr(0, textareaCursorPosition) .split("\n"); var widestRow = 0; var textareaLastCol = 0; var cursorRow = textareaCurrentCursorRowSplit[LEXICON.l]; var cursorCol = textareaCurrentCursorRowSplit[ textareaCurrentCursorRowSplit[LEXICON.l] - 1 ][LEXICON.l]; var rowCols; var i; //get widest Row and the last column of the textarea for (i = 0; i < textareaRowSplit[LEXICON.l]; i++) { rowCols = textareaRowSplit[i][LEXICON.l]; if (rowCols > textareaLastCol) { widestRow = i + 1; textareaLastCol = rowCols; } } return { _cursorRow: cursorRow, //cursorRow _cursorColumn: cursorCol, //cursorCol _rows: textareaLastRow, //rows _columns: textareaLastCol, //cols _widestRow: widestRow, //wRow _cursorPosition: textareaCursorPosition, //pos _cursorMax: textareaLength, //max }; } /** * Determines whether native overlay scrollbars are active. * @returns {boolean} True if native overlay scrollbars are active, false otherwise. */ function nativeOverlayScrollbarsAreActive() { return ( _ignoreOverlayScrollbarHidingCache && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y ); } /** * Gets the element which is used to measure the content size. * @returns {*} TextareaCover if target element is textarea else the ContentElement. */ function getContentMeasureElement() { return _isTextarea ? _textareaCoverElement[0] : _contentElementNative; } /** * Generates a string which represents a HTML div with the given classes or attributes. * @param classesOrAttrs The class of the div as string or a object which represents the attributes of the div. (The class attribute can also be written as "className".) * @param content The content of the div as string. * @returns {string} The concated string which represents a HTML div and its content. */ function generateDiv(classesOrAttrs, content) { return ( "
" + (content || _strEmpty) + "
" ); } /** * Selects or generates a div with the given class attribute. * @param className The class names (divided by spaces) of the div which shall be selected or generated. * @param selectParentOrOnlyChildren The parent element from which of the element shall be selected. (if undefined or boolean its hostElement) * If its a boolean it decides whether only the children of the host element shall be selected. * @returns {*} The generated or selected element. */ function selectOrGenerateDivByClass( className, selectParentOrOnlyChildren ) { var onlyChildren = type(selectParentOrOnlyChildren) == TYPES.b; var selectParent = onlyChildren ? _hostElement : selectParentOrOnlyChildren || _hostElement; return _domExists && !selectParent[LEXICON.l] ? null : _domExists ? selectParent[onlyChildren ? "children" : "find"]( _strDot + className.replace(/\s/g, _strDot) ).eq(0) : FRAMEWORK(generateDiv(className)); } /** * Gets the value of the given property from the given object. * @param obj The object from which the property value shall be got. * @param path The property of which the value shall be got. * @returns {*} Returns the value of the searched property or undefined of the property wasn't found. */ function getObjectPropVal(obj, path) { var splits = path.split(_strDot); var i = 0; var val; for (; i < splits.length; i++) { if (!obj[LEXICON.hOP](splits[i])) return; val = obj[splits[i]]; if (i < splits.length && type(val) == TYPES.o) obj = val; } return val; } /** * Sets the value of the given property from the given object. * @param obj The object from which the property value shall be set. * @param path The property of which the value shall be set. * @param val The value of the property which shall be set. */ function setObjectPropVal(obj, path, val) { var splits = path.split(_strDot); var splitsLength = splits.length; var i = 0; var extendObj = {}; var extendObjRoot = extendObj; for (; i < splitsLength; i++) extendObj = extendObj[splits[i]] = i + 1 < splitsLength ? {} : val; FRAMEWORK.extend(obj, extendObjRoot, true); } /** * Runs a action for each selector inside the updateOnLoad option. * @param {Function} action The action for each updateOnLoad selector, the arguments the function takes is the index and the value (the selector). */ function eachUpdateOnLoad(action) { var updateOnLoad = _currentPreparedOptions.updateOnLoad; updateOnLoad = type(updateOnLoad) == TYPES.s ? updateOnLoad.split(_strSpace) : updateOnLoad; if (COMPATIBILITY.isA(updateOnLoad) && !_destroyed) { each(updateOnLoad, action); } } //==== Utils Cache ====// /** * Compares two values or objects and returns true if they aren't equal. * @param current The first value or object which shall be compared. * @param cache The second value or object which shall be compared. * @param force If true the returned value is always true. * @returns {boolean} True if both values or objects aren't equal or force is true, false otherwise. */ function checkCache(current, cache, force) { if (force) return force; if (type(current) == TYPES.o && type(cache) == TYPES.o) { for (var prop in current) { if (prop !== "c") { if (current[LEXICON.hOP](prop) && cache[LEXICON.hOP](prop)) { if (checkCache(current[prop], cache[prop])) return true; } else { return true; } } } } else { return current !== cache; } return false; } //==== Shortcuts ====// /** * jQuery extend method shortcut with a appended "true" as first argument. */ function extendDeep() { return FRAMEWORK.extend.apply( this, [true].concat([].slice.call(arguments)) ); } /** * jQuery addClass method shortcut. */ function addClass(el, classes) { return _frameworkProto.addClass.call(el, classes); } /** * jQuery removeClass method shortcut. */ function removeClass(el, classes) { return _frameworkProto.removeClass.call(el, classes); } /** * Adds or removes the given classes dependent on the boolean value. True for add, false for remove. */ function addRemoveClass(el, classes, doAdd) { return doAdd ? addClass(el, classes) : removeClass(el, classes); } /** * jQuery remove method shortcut. */ function remove(el) { return _frameworkProto.remove.call(el); } /** * Finds the first child element with the given selector of the given element. * @param el The root element from which the selector shall be valid. * @param selector The selector of the searched element. * @returns {*} The first element which is a child of the given element and matches the givens selector. */ function findFirst(el, selector) { return _frameworkProto.find.call(el, selector).eq(0); } //==== API ====// /** * Puts the instance to sleep. It wont respond to any changes in the DOM and won't update. Scrollbar Interactivity is also disabled as well as the resize handle. * This behavior can be reset by calling the update method. */ _base.sleep = function () { _sleeping = true; }; /** * Updates the plugin and DOM to the current options. * This method should only be called if a update is 100% required. * @param force True if every property shall be updated and the cache shall be ignored. * !INTERNAL USAGE! : force can be a string "auto", "sync" or "zoom" too * if "auto" then before a real update the content size and host element attributes gets checked, and if they changed only then the update method will be called. * if "sync" then the async update process (MutationObserver or UpdateLoop) gets synchronized and a corresponding update takes place if one was needed due to pending changes. * if "zoom" then a update takes place where it's assumed that content and host size changed * @returns {boolean|undefined} * If force is "sync" then a boolean is returned which indicates whether a update was needed due to pending changes. * If force is "auto" then a boolean is returned whether a update was needed due to attribute or size changes. * undefined otherwise. */ _base.update = function (force) { if (_destroyed) return; var attrsChanged; var contentSizeC; var isString = type(force) == TYPES.s; var doUpdateAuto; var mutHost; var mutContent; if (isString) { if (force === _strAuto) { attrsChanged = meaningfulAttrsChanged(); contentSizeC = updateAutoContentSizeChanged(); doUpdateAuto = attrsChanged || contentSizeC; if (doUpdateAuto) { update({ _contentSizeChanged: contentSizeC, _changedOptions: _initialized ? undefined : _currentPreparedOptions, }); } } else if (force === _strSync) { if (_mutationObserversConnected) { mutHost = _mutationObserverHostCallback( _mutationObserverHost.takeRecords() ); mutContent = _mutationObserverContentCallback( _mutationObserverContent.takeRecords() ); } else { mutHost = _base.update(_strAuto); } } else if (force === "zoom") { update({ _hostSizeChanged: true, _contentSizeChanged: true, }); } } else { force = _sleeping || force; _sleeping = false; if (!_base.update(_strSync) || force) update({ _force: force }); } updateElementsOnLoad(); return doUpdateAuto || mutHost || mutContent; }; /** Gets or sets the current options. The update method will be called automatically if new options were set. * @param newOptions If new options are given, then the new options will be set, if new options aren't given (undefined or a not a plain object) then the current options will be returned. * @param value If new options is a property path string, then this value will be used to set the option to which the property path string leads. * @returns {*} */ _base.options = function (newOptions, value) { var option = {}; var changedOps; //return current options if newOptions are undefined or empty if ( FRAMEWORK.isEmptyObject(newOptions) || !FRAMEWORK.isPlainObject(newOptions) ) { if (type(newOptions) == TYPES.s) { if (arguments.length > 1) { setObjectPropVal(option, newOptions, value); changedOps = setOptions(option); } else return getObjectPropVal(_currentOptions, newOptions); } else return _currentOptions; } else { changedOps = setOptions(newOptions); } if (!FRAMEWORK.isEmptyObject(changedOps)) { update({ _changedOptions: changedOps }); } }; /** * Restore the DOM, disconnects all observers, remove all resize observers and put the instance to sleep. */ _base.destroy = function () { if (_destroyed) return; //remove this instance from auto update loop autoUpdateLoop.remove(_base); //disconnect all mutation observers disconnectMutationObservers(); //remove all resize observers setupResizeObserver(_sizeObserverElement); setupResizeObserver(_sizeAutoObserverElement); //remove all extensions for (var extName in _extensions) _base.removeExt(extName); //remove all 'destroy' events while (_destroyEvents[LEXICON.l] > 0) _destroyEvents.pop()(); //remove all events from host element setupHostMouseTouchEvents(true); //remove all helper / detection elements if (_contentGlueElement) remove(_contentGlueElement); if (_contentArrangeElement) remove(_contentArrangeElement); if (_sizeAutoObserverAdded) remove(_sizeAutoObserverElement); //remove all generated DOM setupScrollbarsDOM(true); setupScrollbarCornerDOM(true); setupStructureDOM(true); //remove all generated image load events for (var i = 0; i < _updateOnLoadElms[LEXICON.l]; i++) FRAMEWORK(_updateOnLoadElms[i]).off( _updateOnLoadEventName, updateOnLoadCallback ); _updateOnLoadElms = undefined; _destroyed = true; _sleeping = true; //remove this instance from the instances list INSTANCES(pluginTargetElement, 0); dispatchCallback("onDestroyed"); //remove all properties and methods //for (var property in _base) // delete _base[property]; //_base = undefined; }; /** * Scrolls to a given position or element. * @param coordinates * 1. Can be "coordinates" which looks like: * { x : ?, y : ? } OR Object with x and y properties * { left : ?, top : ? } OR Object with left and top properties * { l : ?, t : ? } OR Object with l and t properties * [ ?, ? ] OR Array where the first two element are the coordinates (first is x, second is y) * ? A single value which stays for both axis * A value can be a number, a string or a calculation. * * Operators: * [NONE] The current scroll will be overwritten by the value. * '+=' The value will be added to the current scroll offset * '-=' The value will be subtracted from the current scroll offset * '*=' The current scroll wil be multiplicated by the value. * '/=' The current scroll wil be divided by the value. * * Units: * [NONE] The value is the final scroll amount. final = (value * 1) * 'px' Same as none * '%' The value is dependent on the current scroll value. final = ((currentScrollValue / 100) * value) * 'vw' The value is multiplicated by the viewport width. final = (value * viewportWidth) * 'vh' The value is multiplicated by the viewport height. final = (value * viewportHeight) * * example final values: * 200, '200px', '50%', '1vw', '1vh', '+=200', '/=1vw', '*=2px', '-=5vh', '+=33%', '+= 50% - 2px', '-= 1vw - 50%' * * 2. Can be a HTML or jQuery element: * The final scroll offset is the offset (without margin) of the given HTML / jQuery element. * * 3. Can be a object with a HTML or jQuery element with additional settings: * { * el : [HTMLElement, jQuery element], MUST be specified, else this object isn't valid. * scroll : [string, array, object], Default value is 'always'. * block : [string, array, object], Default value is 'begin'. * margin : [number, boolean, array, object] Default value is false. * } * * Possible scroll settings are: * 'always' Scrolls always. * 'ifneeded' Scrolls only if the element isnt fully in view. * 'never' Scrolls never. * * Possible block settings are: * 'begin' Both axis shall be docked to the "begin" edge. - The element will be docked to the top and left edge of the viewport. * 'end' Both axis shall be docked to the "end" edge. - The element will be docked to the bottom and right edge of the viewport. (If direction is RTL to the bottom and left edge.) * 'center' Both axis shall be docked to "center". - The element will be centered in the viewport. * 'nearest' The element will be docked to the nearest edge(s). * * Possible margin settings are: -- The actual margin of the element wont be affect, this option affects only the final scroll offset. * [BOOLEAN] If true the css margin of the element will be used, if false no margin will be used. * [NUMBER] The margin will be used for all edges. * * @param duration The duration of the scroll animation, OR a jQuery animation configuration object. * @param easing The animation easing. * @param complete The animation complete callback. * @returns {{ * position: {x: number, y: number}, * ratio: {x: number, y: number}, * max: {x: number, y: number}, * handleOffset: {x: number, y: number}, * handleLength: {x: number, y: number}, * handleLengthRatio: {x: number, y: number}, t * rackLength: {x: number, y: number}, * isRTL: boolean, * isRTLNormalized: boolean * }} */ _base.scroll = function (coordinates, duration, easing, complete) { if (arguments.length === 0 || coordinates === undefined) { var infoX = _scrollHorizontalInfo; var infoY = _scrollVerticalInfo; var normalizeInvert = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.i; var normalizeNegate = _normalizeRTLCache && _isRTL && _rtlScrollBehavior.n; var scrollX = infoX._currentScroll; var scrollXRatio = infoX._currentScrollRatio; var maxScrollX = infoX._maxScroll; scrollXRatio = normalizeInvert ? 1 - scrollXRatio : scrollXRatio; scrollX = normalizeInvert ? maxScrollX - scrollX : scrollX; scrollX *= normalizeNegate ? -1 : 1; maxScrollX *= normalizeNegate ? -1 : 1; return { position: { x: scrollX, y: infoY._currentScroll, }, ratio: { x: scrollXRatio, y: infoY._currentScrollRatio, }, max: { x: maxScrollX, y: infoY._maxScroll, }, handleOffset: { x: infoX._handleOffset, y: infoY._handleOffset, }, handleLength: { x: infoX._handleLength, y: infoY._handleLength, }, handleLengthRatio: { x: infoX._handleLengthRatio, y: infoY._handleLengthRatio, }, trackLength: { x: infoX._trackLength, y: infoY._trackLength, }, snappedHandleOffset: { x: infoX._snappedHandleOffset, y: infoY._snappedHandleOffset, }, isRTL: _isRTL, isRTLNormalized: _normalizeRTLCache, }; } _base.update(_strSync); var normalizeRTL = _normalizeRTLCache; var coordinatesXAxisProps = [_strX, _strLeft, "l"]; var coordinatesYAxisProps = [_strY, _strTop, "t"]; var coordinatesOperators = ["+=", "-=", "*=", "/="]; var durationIsObject = type(duration) == TYPES.o; var completeCallback = durationIsObject ? duration.complete : complete; var i; var finalScroll = {}; var specialEasing = {}; var doScrollLeft; var doScrollTop; var animationOptions; var strEnd = "end"; var strBegin = "begin"; var strCenter = "center"; var strNearest = "nearest"; var strAlways = "always"; var strNever = "never"; var strIfNeeded = "ifneeded"; var strLength = LEXICON.l; var settingsAxis; var settingsScroll; var settingsBlock; var settingsMargin; var finalElement; var elementObjSettingsAxisValues = [_strX, _strY, "xy", "yx"]; var elementObjSettingsBlockValues = [ strBegin, strEnd, strCenter, strNearest, ]; var elementObjSettingsScrollValues = [ strAlways, strNever, strIfNeeded, ]; var coordinatesIsElementObj = coordinates[LEXICON.hOP]("el"); var possibleElement = coordinatesIsElementObj ? coordinates.el : coordinates; var possibleElementIsJQuery = possibleElement instanceof FRAMEWORK || JQUERY ? possibleElement instanceof JQUERY : false; var possibleElementIsHTMLElement = possibleElementIsJQuery ? false : isHTMLElement(possibleElement); var updateScrollbarInfos = function () { if (doScrollLeft) refreshScrollbarHandleOffset(true); if (doScrollTop) refreshScrollbarHandleOffset(false); }; var proxyCompleteCallback = type(completeCallback) != TYPES.f ? undefined : function () { updateScrollbarInfos(); completeCallback(); }; function checkSettingsStringValue(currValue, allowedValues) { for (i = 0; i < allowedValues[strLength]; i++) { if (currValue === allowedValues[i]) return true; } return false; } function getRawScroll(isX, coordinates) { var coordinateProps = isX ? coordinatesXAxisProps : coordinatesYAxisProps; coordinates = type(coordinates) == TYPES.s || type(coordinates) == TYPES.n ? [coordinates, coordinates] : coordinates; if (COMPATIBILITY.isA(coordinates)) return isX ? coordinates[0] : coordinates[1]; else if (type(coordinates) == TYPES.o) { //decides RTL normalization "hack" with .n //normalizeRTL = type(coordinates.n) == TYPES.b ? coordinates.n : normalizeRTL; for (i = 0; i < coordinateProps[strLength]; i++) if (coordinateProps[i] in coordinates) return coordinates[coordinateProps[i]]; } } function getFinalScroll(isX, rawScroll) { var isString = type(rawScroll) == TYPES.s; var operator; var amount; var scrollInfo = isX ? _scrollHorizontalInfo : _scrollVerticalInfo; var currScroll = scrollInfo._currentScroll; var maxScroll = scrollInfo._maxScroll; var mult = " * "; var finalValue; var isRTLisX = _isRTL && isX; var normalizeShortcuts = isRTLisX && _rtlScrollBehavior.n && !normalizeRTL; var strReplace = "replace"; var evalFunc = eval; var possibleOperator; if (isString) { //check operator if (rawScroll[strLength] > 2) { possibleOperator = rawScroll.substr(0, 2); if (inArray(possibleOperator, coordinatesOperators) > -1) operator = possibleOperator; } //calculate units and shortcuts rawScroll = operator ? rawScroll.substr(2) : rawScroll; rawScroll = rawScroll[strReplace](/min/g, 0) //'min' = 0% [strReplace](//g, (normalizeShortcuts ? "-" : _strEmpty) + _strHundredPercent ) //'>' = 100% [strReplace](/px/g, _strEmpty) [strReplace]( /%/g, mult + (maxScroll * (isRTLisX && _rtlScrollBehavior.n ? -1 : 1)) / 100.0 ) [strReplace](/vw/g, mult + _viewportSize.w) [strReplace](/vh/g, mult + _viewportSize.h); amount = parseToZeroOrNumber( isNaN(rawScroll) ? parseToZeroOrNumber(evalFunc(rawScroll), true).toFixed() : rawScroll ); } else { amount = rawScroll; } if ( amount !== undefined && !isNaN(amount) && type(amount) == TYPES.n ) { var normalizeIsRTLisX = normalizeRTL && isRTLisX; var operatorCurrScroll = currScroll * (normalizeIsRTLisX && _rtlScrollBehavior.n ? -1 : 1); var invert = normalizeIsRTLisX && _rtlScrollBehavior.i; var negate = normalizeIsRTLisX && _rtlScrollBehavior.n; operatorCurrScroll = invert ? maxScroll - operatorCurrScroll : operatorCurrScroll; switch (operator) { case "+=": finalValue = operatorCurrScroll + amount; break; case "-=": finalValue = operatorCurrScroll - amount; break; case "*=": finalValue = operatorCurrScroll * amount; break; case "/=": finalValue = operatorCurrScroll / amount; break; default: finalValue = amount; break; } finalValue = invert ? maxScroll - finalValue : finalValue; finalValue *= negate ? -1 : 1; finalValue = isRTLisX && _rtlScrollBehavior.n ? MATH.min(0, MATH.max(maxScroll, finalValue)) : MATH.max(0, MATH.min(maxScroll, finalValue)); } return finalValue === currScroll ? undefined : finalValue; } function getPerAxisValue( value, valueInternalType, defaultValue, allowedValues ) { var resultDefault = [defaultValue, defaultValue]; var valueType = type(value); var valueArrLength; var valueArrItem; //value can be [ string, or array of two strings ] if (valueType == valueInternalType) { value = [value, value]; } else if (valueType == TYPES.a) { valueArrLength = value[strLength]; if (valueArrLength > 2 || valueArrLength < 1) value = resultDefault; else { if (valueArrLength === 1) value[1] = defaultValue; for (i = 0; i < valueArrLength; i++) { valueArrItem = value[i]; if ( type(valueArrItem) != valueInternalType || !checkSettingsStringValue(valueArrItem, allowedValues) ) { value = resultDefault; break; } } } } else if (valueType == TYPES.o) value = [ value[_strX] || defaultValue, value[_strY] || defaultValue, ]; else value = resultDefault; return { x: value[0], y: value[1] }; } function generateMargin(marginTopRightBottomLeftArray) { var result = []; var currValue; var currValueType; var valueDirections = [_strTop, _strRight, _strBottom, _strLeft]; for (i = 0; i < marginTopRightBottomLeftArray[strLength]; i++) { if (i === valueDirections[strLength]) break; currValue = marginTopRightBottomLeftArray[i]; currValueType = type(currValue); if (currValueType == TYPES.b) result.push( currValue ? parseToZeroOrNumber( finalElement.css(_strMarginMinus + valueDirections[i]) ) : 0 ); else result.push(currValueType == TYPES.n ? currValue : 0); } return result; } if (possibleElementIsJQuery || possibleElementIsHTMLElement) { //get settings var margin = coordinatesIsElementObj ? coordinates.margin : 0; var axis = coordinatesIsElementObj ? coordinates.axis : 0; var scroll = coordinatesIsElementObj ? coordinates.scroll : 0; var block = coordinatesIsElementObj ? coordinates.block : 0; var marginDefault = [0, 0, 0, 0]; var marginType = type(margin); var marginLength; finalElement = possibleElementIsJQuery ? possibleElement : FRAMEWORK(possibleElement); if (finalElement[strLength] > 0) { //margin can be [ boolean, number, array of 2, array of 4, object ] if (marginType == TYPES.n || marginType == TYPES.b) margin = generateMargin([margin, margin, margin, margin]); else if (marginType == TYPES.a) { marginLength = margin[strLength]; if (marginLength === 2) margin = generateMargin([ margin[0], margin[1], margin[0], margin[1], ]); else if (marginLength >= 4) margin = generateMargin(margin); else margin = marginDefault; } else if (marginType == TYPES.o) margin = generateMargin([ margin[_strTop], margin[_strRight], margin[_strBottom], margin[_strLeft], ]); else margin = marginDefault; //block = type(block) === TYPES.b ? block ? [ strNearest, strBegin ] : [ strNearest, strEnd ] : block; settingsAxis = checkSettingsStringValue( axis, elementObjSettingsAxisValues ) ? axis : "xy"; settingsScroll = getPerAxisValue( scroll, TYPES.s, strAlways, elementObjSettingsScrollValues ); settingsBlock = getPerAxisValue( block, TYPES.s, strBegin, elementObjSettingsBlockValues ); settingsMargin = margin; var viewportScroll = { l: _scrollHorizontalInfo._currentScroll, t: _scrollVerticalInfo._currentScroll, }; // use padding element instead of viewport element because padding element has never padding, margin or position applied. var viewportOffset = _paddingElement.offset(); //get coordinates var elementOffset = finalElement.offset(); var doNotScroll = { x: settingsScroll.x == strNever || settingsAxis == _strY, y: settingsScroll.y == strNever || settingsAxis == _strX, }; elementOffset[_strTop] -= settingsMargin[0]; elementOffset[_strLeft] -= settingsMargin[3]; var elementScrollCoordinates = { x: MATH.round( elementOffset[_strLeft] - viewportOffset[_strLeft] + viewportScroll.l ), y: MATH.round( elementOffset[_strTop] - viewportOffset[_strTop] + viewportScroll.t ), }; if (_isRTL) { if (!_rtlScrollBehavior.n && !_rtlScrollBehavior.i) elementScrollCoordinates.x = MATH.round( viewportOffset[_strLeft] - elementOffset[_strLeft] + viewportScroll.l ); if (_rtlScrollBehavior.n && normalizeRTL) elementScrollCoordinates.x *= -1; if (_rtlScrollBehavior.i && normalizeRTL) elementScrollCoordinates.x = MATH.round( viewportOffset[_strLeft] - elementOffset[_strLeft] + (_scrollHorizontalInfo._maxScroll - viewportScroll.l) ); } //measuring is required if ( settingsBlock.x != strBegin || settingsBlock.y != strBegin || settingsScroll.x == strIfNeeded || settingsScroll.y == strIfNeeded || _isRTL ) { var measuringElm = finalElement[0]; var rawElementSize = _supportTransform ? measuringElm[LEXICON.bCR]() : { width: measuringElm[LEXICON.oW], height: measuringElm[LEXICON.oH], }; var elementSize = { w: rawElementSize[_strWidth] + settingsMargin[3] + settingsMargin[1], h: rawElementSize[_strHeight] + settingsMargin[0] + settingsMargin[2], }; var finalizeBlock = function (isX) { var vars = getScrollbarVars(isX); var wh = vars._w_h; var lt = vars._left_top; var xy = vars._x_y; var blockIsEnd = settingsBlock[xy] == (isX ? (_isRTL ? strBegin : strEnd) : strEnd); var blockIsCenter = settingsBlock[xy] == strCenter; var blockIsNearest = settingsBlock[xy] == strNearest; var scrollNever = settingsScroll[xy] == strNever; var scrollIfNeeded = settingsScroll[xy] == strIfNeeded; var vpSize = _viewportSize[wh]; var vpOffset = viewportOffset[lt]; var elSize = elementSize[wh]; var elOffset = elementOffset[lt]; var divide = blockIsCenter ? 2 : 1; var elementCenterOffset = elOffset + elSize / 2; var viewportCenterOffset = vpOffset + vpSize / 2; var isInView = elSize <= vpSize && elOffset >= vpOffset && elOffset + elSize <= vpOffset + vpSize; if (scrollNever) doNotScroll[xy] = true; else if (!doNotScroll[xy]) { if (blockIsNearest || scrollIfNeeded) { doNotScroll[xy] = scrollIfNeeded ? isInView : false; blockIsEnd = elSize < vpSize ? elementCenterOffset > viewportCenterOffset : elementCenterOffset < viewportCenterOffset; } elementScrollCoordinates[xy] -= blockIsEnd || blockIsCenter ? (vpSize / divide - elSize / divide) * (isX && _isRTL && normalizeRTL ? -1 : 1) : 0; } }; finalizeBlock(true); finalizeBlock(false); } if (doNotScroll.y) delete elementScrollCoordinates.y; if (doNotScroll.x) delete elementScrollCoordinates.x; coordinates = elementScrollCoordinates; } } finalScroll[_strScrollLeft] = getFinalScroll( true, getRawScroll(true, coordinates) ); finalScroll[_strScrollTop] = getFinalScroll( false, getRawScroll(false, coordinates) ); doScrollLeft = finalScroll[_strScrollLeft] !== undefined; doScrollTop = finalScroll[_strScrollTop] !== undefined; if ( (doScrollLeft || doScrollTop) && (duration > 0 || durationIsObject) ) { if (durationIsObject) { duration.complete = proxyCompleteCallback; _viewportElement.animate(finalScroll, duration); } else { animationOptions = { duration: duration, complete: proxyCompleteCallback, }; if ( COMPATIBILITY.isA(easing) || FRAMEWORK.isPlainObject(easing) ) { specialEasing[_strScrollLeft] = easing[0] || easing.x; specialEasing[_strScrollTop] = easing[1] || easing.y; animationOptions.specialEasing = specialEasing; } else { animationOptions.easing = easing; } _viewportElement.animate(finalScroll, animationOptions); } } else { if (doScrollLeft) _viewportElement[_strScrollLeft](finalScroll[_strScrollLeft]); if (doScrollTop) _viewportElement[_strScrollTop](finalScroll[_strScrollTop]); updateScrollbarInfos(); } }; /** * Stops all scroll animations. * @returns {*} The current OverlayScrollbars instance (for chaining). */ _base.scrollStop = function (param1, param2, param3) { _viewportElement.stop(param1, param2, param3); return _base; }; /** * Returns all relevant elements. * @param elementName The name of the element which shall be returned. * @returns {{target: *, host: *, padding: *, viewport: *, content: *, scrollbarHorizontal: {scrollbar: *, track: *, handle: *}, scrollbarVertical: {scrollbar: *, track: *, handle: *}, scrollbarCorner: *} | *} */ _base.getElements = function (elementName) { var obj = { target: _targetElementNative, host: _hostElementNative, padding: _paddingElementNative, viewport: _viewportElementNative, content: _contentElementNative, scrollbarHorizontal: { scrollbar: _scrollbarHorizontalElement[0], track: _scrollbarHorizontalTrackElement[0], handle: _scrollbarHorizontalHandleElement[0], }, scrollbarVertical: { scrollbar: _scrollbarVerticalElement[0], track: _scrollbarVerticalTrackElement[0], handle: _scrollbarVerticalHandleElement[0], }, scrollbarCorner: _scrollbarCornerElement[0], }; return type(elementName) == TYPES.s ? getObjectPropVal(obj, elementName) : obj; }; /** * Returns a object which describes the current state of this instance. * @param stateProperty A specific property from the state object which shall be returned. * @returns {{widthAuto, heightAuto, overflowAmount, hideOverflow, hasOverflow, contentScrollSize, viewportSize, hostSize, autoUpdate} | *} */ _base.getState = function (stateProperty) { function prepare(obj) { if (!FRAMEWORK.isPlainObject(obj)) return obj; var extended = extendDeep({}, obj); var changePropertyName = function (from, to) { if (extended[LEXICON.hOP](from)) { extended[to] = extended[from]; delete extended[from]; } }; changePropertyName("w", _strWidth); //change w to width changePropertyName("h", _strHeight); //change h to height delete extended.c; //delete c (the 'changed' prop) return extended; } var obj = { destroyed: !!prepare(_destroyed), sleeping: !!prepare(_sleeping), autoUpdate: prepare(!_mutationObserversConnected), widthAuto: prepare(_widthAutoCache), heightAuto: prepare(_heightAutoCache), padding: prepare(_cssPaddingCache), overflowAmount: prepare(_overflowAmountCache), hideOverflow: prepare(_hideOverflowCache), hasOverflow: prepare(_hasOverflowCache), contentScrollSize: prepare(_contentScrollSizeCache), viewportSize: prepare(_viewportSize), hostSize: prepare(_hostSizeCache), documentMixed: prepare(_documentMixed), }; return type(stateProperty) == TYPES.s ? getObjectPropVal(obj, stateProperty) : obj; }; /** * Gets all or specific extension instance. * @param extName The name of the extension from which the instance shall be got. * @returns {{}} The instance of the extension with the given name or undefined if the instance couldn't be found. */ _base.ext = function (extName) { var result; var privateMethods = _extensionsPrivateMethods.split(" "); var i = 0; if (type(extName) == TYPES.s) { if (_extensions[LEXICON.hOP](extName)) { result = extendDeep({}, _extensions[extName]); for (; i < privateMethods.length; i++) delete result[privateMethods[i]]; } } else { result = {}; for (i in _extensions) result[i] = extendDeep({}, _base.ext(i)); } return result; }; /** * Adds a extension to this instance. * @param extName The name of the extension which shall be added. * @param extensionOptions The extension options which shall be used. * @returns {{}} The instance of the added extension or undefined if the extension couldn't be added properly. */ _base.addExt = function (extName, extensionOptions) { var registeredExtensionObj = _plugin.extension(extName); var instance; var instanceAdded; var instanceContract; var contractResult; var contractFulfilled = true; if (registeredExtensionObj) { if (!_extensions[LEXICON.hOP](extName)) { instance = registeredExtensionObj.extensionFactory.call( _base, extendDeep({}, registeredExtensionObj.defaultOptions), FRAMEWORK, COMPATIBILITY ); if (instance) { instanceContract = instance.contract; if (type(instanceContract) == TYPES.f) { contractResult = instanceContract(window); contractFulfilled = type(contractResult) == TYPES.b ? contractResult : contractFulfilled; } if (contractFulfilled) { _extensions[extName] = instance; instanceAdded = instance.added; if (type(instanceAdded) == TYPES.f) instanceAdded(extensionOptions); return _base.ext(extName); } } } else return _base.ext(extName); } else console.warn( 'A extension with the name "' + extName + "\" isn't registered." ); }; /** * Removes a extension from this instance. * @param extName The name of the extension which shall be removed. * @returns {boolean} True if the extension was removed, false otherwise e.g. if the extension wasn't added before. */ _base.removeExt = function (extName) { var instance = _extensions[extName]; var instanceRemoved; if (instance) { delete _extensions[extName]; instanceRemoved = instance.removed; if (type(instanceRemoved) == TYPES.f) instanceRemoved(); return true; } return false; }; /** * Constructs the plugin. * @param targetElement The element to which the plugin shall be applied. * @param options The initial options of the plugin. * @param extensions The extension(s) which shall be added right after the initialization. * @returns {boolean} True if the plugin was successfully initialized, false otherwise. */ function construct(targetElement, options, extensions) { _defaultOptions = globals.defaultOptions; _nativeScrollbarStyling = globals.nativeScrollbarStyling; _nativeScrollbarSize = extendDeep({}, globals.nativeScrollbarSize); _nativeScrollbarIsOverlaid = extendDeep( {}, globals.nativeScrollbarIsOverlaid ); _overlayScrollbarDummySize = extendDeep( {}, globals.overlayScrollbarDummySize ); _rtlScrollBehavior = extendDeep({}, globals.rtlScrollBehavior); //parse & set options but don't update setOptions(extendDeep({}, _defaultOptions, options)); _cssCalc = globals.cssCalc; _msieVersion = globals.msie; _autoUpdateRecommended = globals.autoUpdateRecommended; _supportTransition = globals.supportTransition; _supportTransform = globals.supportTransform; _supportPassiveEvents = globals.supportPassiveEvents; _supportResizeObserver = globals.supportResizeObserver; _supportMutationObserver = globals.supportMutationObserver; _restrictedMeasuring = globals.restrictedMeasuring; _documentElement = FRAMEWORK(targetElement.ownerDocument); _documentElementNative = _documentElement[0]; _windowElement = FRAMEWORK( _documentElementNative.defaultView || _documentElementNative.parentWindow ); _windowElementNative = _windowElement[0]; _htmlElement = findFirst(_documentElement, "html"); _bodyElement = findFirst(_htmlElement, "body"); _targetElement = FRAMEWORK(targetElement); _targetElementNative = _targetElement[0]; _isTextarea = _targetElement.is("textarea"); _isBody = _targetElement.is("body"); _documentMixed = _documentElementNative !== document; /* On a div Element The if checks only whether: * - the targetElement has the class "os-host" * - the targetElement has a a child with the class "os-padding" * * If that's the case, its assumed the DOM has already the following structure: * (The ".os-host" element is the targetElement) * *
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* * ===================================================================================== * * On a Textarea Element The if checks only whether: * - the targetElement has the class "os-textarea" * - the targetElement is inside a element with the class "os-content" * * If that's the case, its assumed the DOM has already the following structure: * (The ".os-textarea" (textarea) element is the targetElement) * *
*
*
*
*
*
* *
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/ _domExists = _isTextarea ? _targetElement.hasClass(_classNameTextareaElement) && _targetElement.parent().hasClass(_classNameContentElement) : _targetElement.hasClass(_classNameHostElement) && _targetElement.children(_strDot + _classNamePaddingElement)[ LEXICON.l ]; var initBodyScroll; var bodyMouseTouchDownListener; //check if the plugin hasn't to be initialized if ( _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y && !_currentPreparedOptions.nativeScrollbarsOverlaid.initialize ) { _initialized = true; // workaround so the onInitializationWithdrawn callback below is fired dispatchCallback("onInitializationWithdrawn"); if (_domExists) { setupStructureDOM(true); setupScrollbarsDOM(true); setupScrollbarCornerDOM(true); } _initialized = false; _destroyed = true; _sleeping = true; return _base; } if (_isBody) { initBodyScroll = {}; initBodyScroll.l = MATH.max( _targetElement[_strScrollLeft](), _htmlElement[_strScrollLeft](), _windowElement[_strScrollLeft]() ); initBodyScroll.t = MATH.max( _targetElement[_strScrollTop](), _htmlElement[_strScrollTop](), _windowElement[_strScrollTop]() ); bodyMouseTouchDownListener = function () { _viewportElement.removeAttr(LEXICON.ti); setupResponsiveEventListener( _viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, true, true ); }; } //build OverlayScrollbars DOM setupStructureDOM(); setupScrollbarsDOM(); setupScrollbarCornerDOM(); //create OverlayScrollbars events setupStructureEvents(); setupScrollbarEvents(true); setupScrollbarEvents(false); setupScrollbarCornerEvents(); //create mutation observers createMutationObservers(); //build resize observer for the host element setupResizeObserver(_sizeObserverElement, hostOnResized); if (_isBody) { //apply the body scroll to handle it right in the update method _viewportElement[_strScrollLeft](initBodyScroll.l)[_strScrollTop]( initBodyScroll.t ); //set the focus on the viewport element so you dont have to click on the page to use keyboard keys (up / down / space) for scrolling if ( document.activeElement == targetElement && _viewportElementNative.focus ) { //set a tabindex to make the viewportElement focusable _viewportElement.attr(LEXICON.ti, "-1"); _viewportElementNative.focus(); /* the tabindex has to be removed due to; * If you set the tabindex attribute on an
, then its child content cannot be scrolled with the arrow keys unless you set tabindex on the content, too * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex */ setupResponsiveEventListener( _viewportElement, _strMouseTouchDownEvent, bodyMouseTouchDownListener, false, true ); } } //update for the first time & initialize cache _base.update(_strAuto); //the plugin is initialized now! _initialized = true; dispatchCallback("onInitialized"); //call all callbacks which would fire before the initialized was complete each(_callbacksInitQeueue, function (index, value) { dispatchCallback(value.n, value.a); }); _callbacksInitQeueue = []; //add extensions if (type(extensions) == TYPES.s) extensions = [extensions]; if (COMPATIBILITY.isA(extensions)) each(extensions, function (index, value) { _base.addExt(value); }); else if (FRAMEWORK.isPlainObject(extensions)) each(extensions, function (key, value) { _base.addExt(key, value); }); //add the transition class for transitions AFTER the first update & AFTER the applied extensions (for preventing unwanted transitions) setTimeout(function () { if (_supportTransition && !_destroyed) addClass(_hostElement, _classNameHostTransition); }, 333); return _base; } if ( _plugin.valid(construct(pluginTargetElement, options, extensions)) ) { INSTANCES(pluginTargetElement, _base); } return _base; } /** * Initializes a new OverlayScrollbarsInstance object or changes options if already initialized or returns the current instance. * @param pluginTargetElements The elements to which the Plugin shall be initialized. * @param options The custom options with which the plugin shall be initialized. * @param extensions The extension(s) which shall be added right after initialization. * @returns {*} */ _plugin = window[PLUGINNAME] = function ( pluginTargetElements, options, extensions ) { if (arguments[LEXICON.l] === 0) return this; var arr = []; var optsIsPlainObj = FRAMEWORK.isPlainObject(options); var inst; var result; //pluginTargetElements is null or undefined if (!pluginTargetElements) return optsIsPlainObj || !options ? result : arr; /* pluginTargetElements will be converted to: 1. A jQueryElement Array 2. A HTMLElement Array 3. A Array with a single HTML Element so pluginTargetElements is always a array. */ pluginTargetElements = pluginTargetElements[LEXICON.l] != undefined ? pluginTargetElements : [pluginTargetElements[0] || pluginTargetElements]; initOverlayScrollbarsStatics(); if (pluginTargetElements[LEXICON.l] > 0) { if (optsIsPlainObj) { FRAMEWORK.each(pluginTargetElements, function (i, v) { inst = v; if (inst !== undefined) arr.push( OverlayScrollbarsInstance( inst, options, extensions, _pluginsGlobals, _pluginsAutoUpdateLoop ) ); }); } else { FRAMEWORK.each(pluginTargetElements, function (i, v) { inst = INSTANCES(v); if ( (options === "!" && _plugin.valid(inst)) || (COMPATIBILITY.type(options) == TYPES.f && options(v, inst)) ) arr.push(inst); else if (options === undefined) arr.push(inst); }); } result = arr[LEXICON.l] === 1 ? arr[0] : arr; } return result; }; /** * Returns a object which contains global information about the plugin and each instance of it. * The returned object is just a copy, that means that changes to the returned object won't have any effect to the original object. */ _plugin.globals = function () { initOverlayScrollbarsStatics(); var globals = FRAMEWORK.extend(true, {}, _pluginsGlobals); delete globals["msie"]; return globals; }; /** * Gets or Sets the default options for each new plugin initialization. * @param newDefaultOptions The object with which the default options shall be extended. */ _plugin.defaultOptions = function (newDefaultOptions) { initOverlayScrollbarsStatics(); var currDefaultOptions = _pluginsGlobals.defaultOptions; if (newDefaultOptions === undefined) return FRAMEWORK.extend(true, {}, currDefaultOptions); //set the new default options _pluginsGlobals.defaultOptions = FRAMEWORK.extend( true, {}, currDefaultOptions, _pluginsOptions._validate( newDefaultOptions, _pluginsOptions._template, true, currDefaultOptions )._default ); }; /** * Checks whether the passed instance is a non-destroyed OverlayScrollbars instance. * @param osInstance The potential OverlayScrollbars instance which shall be checked. * @returns {boolean} True if the passed value is a non-destroyed OverlayScrollbars instance, false otherwise. */ _plugin.valid = function (osInstance) { return ( osInstance instanceof _plugin && !osInstance.getState().destroyed ); }; /** * Registers, Unregisters or returns a extension. * Register: Pass the name and the extension. (defaultOptions is optional) * Unregister: Pass the name and anything except a function as extension parameter. * Get extension: Pass the name of the extension which shall be got. * Get all extensions: Pass no arguments. * @param extensionName The name of the extension which shall be registered, unregistered or returned. * @param extension A function which generates the instance of the extension or anything other to remove a already registered extension. * @param defaultOptions The default options which shall be used for the registered extension. */ _plugin.extension = function (extensionName, extension, defaultOptions) { var extNameTypeString = COMPATIBILITY.type(extensionName) == TYPES.s; var argLen = arguments[LEXICON.l]; var i = 0; if (argLen < 1 || !extNameTypeString) { //return a copy of all extension objects return FRAMEWORK.extend( true, { length: _pluginsExtensions[LEXICON.l] }, _pluginsExtensions ); } else if (extNameTypeString) { if (COMPATIBILITY.type(extension) == TYPES.f) { //register extension _pluginsExtensions.push({ name: extensionName, extensionFactory: extension, defaultOptions: defaultOptions, }); } else { for (; i < _pluginsExtensions[LEXICON.l]; i++) { if (_pluginsExtensions[i].name === extensionName) { if (argLen > 1) _pluginsExtensions.splice(i, 1); //remove extension else return FRAMEWORK.extend(true, {}, _pluginsExtensions[i]); //return extension with the given name } } } } }; return _plugin; })(); if (JQUERY && JQUERY.fn) { /** * The jQuery initialization interface. * @param options The initial options for the construction of the plugin. To initialize the plugin, this option has to be a object! If it isn't a object, the instance(s) are returned and the plugin wont be initialized. * @param extensions The extension(s) which shall be added right after initialization. * @returns {*} After initialization it returns the jQuery element array, else it returns the instance(s) of the elements which are selected. */ JQUERY.fn.overlayScrollbars = function (options, extensions) { var _elements = this; if (JQUERY.isPlainObject(options)) { JQUERY.each(_elements, function () { PLUGIN(this, options, extensions); }); return _elements; } else return PLUGIN(_elements, options); }; } return PLUGIN; } );