You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6661 lines
354 KiB
6661 lines
354 KiB
/*! |
|
* OverlayScrollbars |
|
* https://github.com/KingSora/OverlayScrollbars |
|
* |
|
* Version: 1.13.0 |
|
* |
|
* Copyright KingSora | Rene Haas. |
|
* https://github.com/KingSora |
|
* |
|
* Released under the MIT license. |
|
* Date: 02.08.2020 |
|
*/ |
|
|
|
(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 * .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 * .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 * (.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 -.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) * .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 + .75) + b; |
|
} else if (t < (2.5 / 2.75)) { |
|
return c * (o * (t -= (2.25 / 2.75)) * t + .9375) + b; |
|
} else { |
|
return c * (o * (t -= (2.625 / 2.75)) * t + .984375) + b; |
|
} |
|
}, |
|
easeInOutBounce: function (x, t, b, c, d) { |
|
return (t < d / 2) ? this.easeInBounce(x, t * 2, 0, c, d) * .5 + b : this.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .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('<div id="os-dummy-scrollbar-size"><div></div></div>'); |
|
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) { |
|
var elm = FRAMEWORK(event.target); |
|
|
|
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 '<div ' + (classesOrAttrs ? type(classesOrAttrs) == TYPES.s ? |
|
'class="' + classesOrAttrs + '"' : |
|
(function () { |
|
var key; |
|
var attrs = _strEmpty; |
|
if (FRAMEWORK.isPlainObject(classesOrAttrs)) { |
|
for (key in classesOrAttrs) |
|
attrs += (key === 'c' ? 'class' : key) + '="' + classesOrAttrs[key] + '" '; |
|
} |
|
return attrs; |
|
})() : |
|
_strEmpty) + |
|
'>' + |
|
(content || _strEmpty) + |
|
'</div>'; |
|
} |
|
|
|
/** |
|
* 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, 0) //'<' = 0% |
|
[strReplace](/max/g, (normalizeShortcuts ? '-' : _strEmpty) + _strHundredPercent) //'max' = 100% |
|
[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) |
|
* |
|
* <div class="os-host"> |
|
* <div class="os-resize-observer-host"></div> |
|
* <div class="os-padding"> |
|
* <div class="os-viewport"> |
|
* <div class="os-content"></div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar os-scrollbar-horizontal "> |
|
* <div class="os-scrollbar-track"> |
|
* <div class="os-scrollbar-handle"></div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar os-scrollbar-vertical"> |
|
* <div class="os-scrollbar-track"> |
|
* <div class="os-scrollbar-handle"></div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar-corner"></div> |
|
* </div> |
|
* |
|
* ===================================================================================== |
|
* |
|
* 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) |
|
* |
|
* <div class="os-host-textarea"> |
|
* <div class="os-resize-observer-host"></div> |
|
* <div class="os-padding os-text-inherit"> |
|
* <div class="os-viewport os-text-inherit"> |
|
* <div class="os-content os-text-inherit"> |
|
* <div class="os-textarea-cover"></div> |
|
* <textarea class="os-textarea os-text-inherit"></textarea> |
|
* </div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar os-scrollbar-horizontal "> |
|
* <div class="os-scrollbar-track"> |
|
* <div class="os-scrollbar-handle"></div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar os-scrollbar-vertical"> |
|
* <div class="os-scrollbar-track"> |
|
* <div class="os-scrollbar-handle"></div> |
|
* </div> |
|
* </div> |
|
* <div class="os-scrollbar-corner"></div> |
|
* </div> |
|
*/ |
|
_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) { |
|
dispatchCallback('onInitializationWithdrawn'); |
|
if (_domExists) { |
|
setupStructureDOM(true); |
|
setupScrollbarsDOM(true); |
|
setupScrollbarCornerDOM(true); |
|
} |
|
|
|
_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 <div>, 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; |
|
} |
|
)); |