/*! nouislider - 13.0.0 - 2/6/2019 */
(function(factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === "object") {
// Node/CommonJS
module.exports = factory();
} else {
// Browser globals
window.noUiSlider = factory();
}
})(function() {
"use strict";
var VERSION = "13.0.0";
function isValidFormatter(entry) {
return typeof entry === "object" && typeof entry.to === "function" && typeof entry.from === "function";
}
function removeElement(el) {
el.parentElement.removeChild(el);
}
function isSet(value) {
return value !== null && value !== undefined;
}
// Bindable version
function preventDefault(e) {
e.preventDefault();
}
// Removes duplicates from an array.
function unique(array) {
return array.filter(function(a) {
return !this[a] ? (this[a] = true) : false;
}, {});
}
// Round a value to the closest 'to'.
function closest(value, to) {
return Math.round(value / to) * to;
}
// Current position of an element relative to the document.
function offset(elem, orientation) {
var rect = elem.getBoundingClientRect();
var doc = elem.ownerDocument;
var docElem = doc.documentElement;
var pageOffset = getPageOffset(doc);
// getBoundingClientRect contains left scroll in Chrome on Android.
// I haven't found a feature detection that proves this. Worst case
// scenario on mis-match: the 'tap' feature on horizontal sliders breaks.
if (/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)) {
pageOffset.x = 0;
}
return orientation
? rect.top + pageOffset.y - docElem.clientTop
: rect.left + pageOffset.x - docElem.clientLeft;
}
// Checks whether a value is numerical.
function isNumeric(a) {
return typeof a === "number" && !isNaN(a) && isFinite(a);
}
// Sets a class and removes it after [duration] ms.
function addClassFor(element, className, duration) {
if (duration > 0) {
addClass(element, className);
setTimeout(function() {
removeClass(element, className);
}, duration);
}
}
// Limits a value to 0 - 100
function limit(a) {
return Math.max(Math.min(a, 100), 0);
}
// Wraps a variable as an array, if it isn't one yet.
// Note that an input array is returned by reference!
function asArray(a) {
return Array.isArray(a) ? a : [a];
}
// Counts decimals
function countDecimals(numStr) {
numStr = String(numStr);
var pieces = numStr.split(".");
return pieces.length > 1 ? pieces[1].length : 0;
}
// http://youmightnotneedjquery.com/#add_class
function addClass(el, className) {
if (el.classList) {
el.classList.add(className);
} else {
el.className += " " + className;
}
}
// http://youmightnotneedjquery.com/#remove_class
function removeClass(el, className) {
if (el.classList) {
el.classList.remove(className);
} else {
el.className = el.className.replace(
new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi"),
" "
);
}
}
// https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/
function hasClass(el, className) {
return el.classList
? el.classList.contains(className)
: new RegExp("\\b" + className + "\\b").test(el.className);
}
// https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
function getPageOffset(doc) {
var supportPageOffset = window.pageXOffset !== undefined;
var isCSS1Compat = (doc.compatMode || "") === "CSS1Compat";
var x = supportPageOffset
? window.pageXOffset
: isCSS1Compat
? doc.documentElement.scrollLeft
: doc.body.scrollLeft;
var y = supportPageOffset
? window.pageYOffset
: isCSS1Compat
? doc.documentElement.scrollTop
: doc.body.scrollTop;
return {
x: x,
y: y
};
}
// we provide a function to compute constants instead
// of accessing window.* as soon as the module needs it
// so that we do not compute anything if not needed
function getActions() {
// Determine the events to bind. IE11 implements pointerEvents without
// a prefix, which breaks compatibility with the IE10 implementation.
return window.navigator.pointerEnabled
? {
start: "pointerdown",
move: "pointermove",
end: "pointerup"
}
: window.navigator.msPointerEnabled
? {
start: "MSPointerDown",
move: "MSPointerMove",
end: "MSPointerUp"
}
: {
start: "mousedown touchstart",
move: "mousemove touchmove",
end: "mouseup touchend"
};
}
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
// Issue #785
function getSupportsPassive() {
var supportsPassive = false;
/* eslint-disable */
try {
var opts = Object.defineProperty({}, "passive", {
get: function() {
supportsPassive = true;
}
});
window.addEventListener("test", null, opts);
} catch (e) {}
/* eslint-enable */
return supportsPassive;
}
function getSupportsTouchActionNone() {
return window.CSS && CSS.supports && CSS.supports("touch-action", "none");
}
// Value calculation
// Determine the size of a sub-range in relation to a full range.
function subRangeRatio(pa, pb) {
return 100 / (pb - pa);
}
// (percentage) How many percent is this value of this range?
function fromPercentage(range, value) {
return (value * 100) / (range[1] - range[0]);
}
// (percentage) Where is this value on this range?
function toPercentage(range, value) {
return fromPercentage(range, range[0] < 0 ? value + Math.abs(range[0]) : value - range[0]);
}
// (value) How much is this percentage on this range?
function isPercentage(range, value) {
return (value * (range[1] - range[0])) / 100 + range[0];
}
// Range conversion
function getJ(value, arr) {
var j = 1;
while (value >= arr[j]) {
j += 1;
}
return j;
}
// (percentage) Input a value, find where, on a scale of 0-100, it applies.
function toStepping(xVal, xPct, value) {
if (value >= xVal.slice(-1)[0]) {
return 100;
}
var j = getJ(value, xVal);
var va = xVal[j - 1];
var vb = xVal[j];
var pa = xPct[j - 1];
var pb = xPct[j];
return pa + toPercentage([va, vb], value) / subRangeRatio(pa, pb);
}
// (value) Input a percentage, find where it is on the specified range.
function fromStepping(xVal, xPct, value) {
// There is no range group that fits 100
if (value >= 100) {
return xVal.slice(-1)[0];
}
var j = getJ(value, xPct);
var va = xVal[j - 1];
var vb = xVal[j];
var pa = xPct[j - 1];
var pb = xPct[j];
return isPercentage([va, vb], (value - pa) * subRangeRatio(pa, pb));
}
// (percentage) Get the step that applies at a certain value.
function getStep(xPct, xSteps, snap, value) {
if (value === 100) {
return value;
}
var j = getJ(value, xPct);
var a = xPct[j - 1];
var b = xPct[j];
// If 'snap' is set, steps are used as fixed points on the slider.
if (snap) {
// Find the closest position, a or b.
if (value - a > (b - a) / 2) {
return b;
}
return a;
}
if (!xSteps[j - 1]) {
return value;
}
return xPct[j - 1] + closest(value - xPct[j - 1], xSteps[j - 1]);
}
// Entry parsing
function handleEntryPoint(index, value, that) {
var percentage;
// Wrap numerical input in an array.
if (typeof value === "number") {
value = [value];
}
// Reject any invalid input, by testing whether value is an array.
if (!Array.isArray(value)) {
throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value.");
}
// Covert min/max syntax to 0 and 100.
if (index === "min") {
percentage = 0;
} else if (index === "max") {
percentage = 100;
} else {
percentage = parseFloat(index);
}
// Check for correct input.
if (!isNumeric(percentage) || !isNumeric(value[0])) {
throw new Error("noUiSlider (" + VERSION + "): 'range' value isn't numeric.");
}
// Store values.
that.xPct.push(percentage);
that.xVal.push(value[0]);
// NaN will evaluate to false too, but to keep
// logging clear, set step explicitly. Make sure
// not to override the 'step' setting with false.
if (!percentage) {
if (!isNaN(value[1])) {
that.xSteps[0] = value[1];
}
} else {
that.xSteps.push(isNaN(value[1]) ? false : value[1]);
}
that.xHighestCompleteStep.push(0);
}
function handleStepPoint(i, n, that) {
// Ignore 'false' stepping.
if (!n) {
return true;
}
// Factor to range ratio
that.xSteps[i] =
fromPercentage([that.xVal[i], that.xVal[i + 1]], n) / subRangeRatio(that.xPct[i], that.xPct[i + 1]);
var totalSteps = (that.xVal[i + 1] - that.xVal[i]) / that.xNumSteps[i];
var highestStep = Math.ceil(Number(totalSteps.toFixed(3)) - 1);
var step = that.xVal[i] + that.xNumSteps[i] * highestStep;
that.xHighestCompleteStep[i] = step;
}
// Interface
function Spectrum(entry, snap, singleStep) {
this.xPct = [];
this.xVal = [];
this.xSteps = [singleStep || false];
this.xNumSteps = [false];
this.xHighestCompleteStep = [];
this.snap = snap;
var index;
var ordered = []; // [0, 'min'], [1, '50%'], [2, 'max']
// Map the object keys to an array.
for (index in entry) {
if (entry.hasOwnProperty(index)) {
ordered.push([entry[index], index]);
}
}
// Sort all entries by value (numeric sort).
if (ordered.length && typeof ordered[0][0] === "object") {
ordered.sort(function(a, b) {
return a[0][0] - b[0][0];
});
} else {
ordered.sort(function(a, b) {
return a[0] - b[0];
});
}
// Convert all entries to subranges.
for (index = 0; index < ordered.length; index++) {
handleEntryPoint(ordered[index][1], ordered[index][0], this);
}
// Store the actual step values.
// xSteps is sorted in the same order as xPct and xVal.
this.xNumSteps = this.xSteps.slice(0);
// Convert all numeric steps to the percentage of the subrange they represent.
for (index = 0; index < this.xNumSteps.length; index++) {
handleStepPoint(index, this.xNumSteps[index], this);
}
}
Spectrum.prototype.getMargin = function(value) {
var step = this.xNumSteps[0];
if (step && (value / step) % 1 !== 0) {
throw new Error("noUiSlider (" + VERSION + "): 'limit', 'margin' and 'padding' must be divisible by step.");
}
return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false;
};
Spectrum.prototype.toStepping = function(value) {
value = toStepping(this.xVal, this.xPct, value);
return value;
};
Spectrum.prototype.fromStepping = function(value) {
return fromStepping(this.xVal, this.xPct, value);
};
Spectrum.prototype.getStep = function(value) {
value = getStep(this.xPct, this.xSteps, this.snap, value);
return value;
};
Spectrum.prototype.getDefaultStep = function(value, isDown, size) {
var j = getJ(value, this.xPct);
// When at the top or stepping down, look at the previous sub-range
if (value === 100 || (isDown && value === this.xPct[j - 1])) {
j = Math.max(j - 1, 1);
}
return (this.xVal[j] - this.xVal[j - 1]) / size;
};
Spectrum.prototype.getNearbySteps = function(value) {
var j = getJ(value, this.xPct);
return {
stepBefore: {
startValue: this.xVal[j - 2],
step: this.xNumSteps[j - 2],
highestStep: this.xHighestCompleteStep[j - 2]
},
thisStep: {
startValue: this.xVal[j - 1],
step: this.xNumSteps[j - 1],
highestStep: this.xHighestCompleteStep[j - 1]
},
stepAfter: {
startValue: this.xVal[j],
step: this.xNumSteps[j],
highestStep: this.xHighestCompleteStep[j]
}
};
};
Spectrum.prototype.countStepDecimals = function() {
var stepDecimals = this.xNumSteps.map(countDecimals);
return Math.max.apply(null, stepDecimals);
};
// Outside testing
Spectrum.prototype.convert = function(value) {
return this.getStep(this.toStepping(value));
};
/* Every input option is tested and parsed. This'll prevent
endless validation in internal methods. These tests are
structured with an item for every option available. An
option can be marked as required by setting the 'r' flag.
The testing function is provided with three arguments:
- The provided value for the option;
- A reference to the options object;
- The name for the option;
The testing function returns false when an error is detected,
or true when everything is OK. It can also modify the option
object, to make sure all values can be correctly looped elsewhere. */
var defaultFormatter = {
to: function(value) {
return value !== undefined && value.toFixed(2);
},
from: Number
};
function validateFormat(entry) {
// Any object with a to and from method is supported.
if (isValidFormatter(entry)) {
return true;
}
throw new Error("noUiSlider (" + VERSION + "): 'format' requires 'to' and 'from' methods.");
}
function testStep(parsed, entry) {
if (!isNumeric(entry)) {
throw new Error("noUiSlider (" + VERSION + "): 'step' is not numeric.");
}
// The step option can still be used to set stepping
// for linear sliders. Overwritten if set in 'range'.
parsed.singleStep = entry;
}
function testRange(parsed, entry) {
// Filter incorrect input.
if (typeof entry !== "object" || Array.isArray(entry)) {
throw new Error("noUiSlider (" + VERSION + "): 'range' is not an object.");
}
// Catch missing start or end.
if (entry.min === undefined || entry.max === undefined) {
throw new Error("noUiSlider (" + VERSION + "): Missing 'min' or 'max' in 'range'.");
}
// Catch equal start or end.
if (entry.min === entry.max) {
throw new Error("noUiSlider (" + VERSION + "): 'range' 'min' and 'max' cannot be equal.");
}
parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.singleStep);
}
function testStart(parsed, entry) {
entry = asArray(entry);
// Validate input. Values aren't tested, as the public .val method
// will always provide a valid location.
if (!Array.isArray(entry) || !entry.length) {
throw new Error("noUiSlider (" + VERSION + "): 'start' option is incorrect.");
}
// Store the number of handles.
parsed.handles = entry.length;
// When the slider is initialized, the .val method will
// be called with the start options.
parsed.start = entry;
}
function testSnap(parsed, entry) {
// Enforce 100% stepping within subranges.
parsed.snap = entry;
if (typeof entry !== "boolean") {
throw new Error("noUiSlider (" + VERSION + "): 'snap' option must be a boolean.");
}
}
function testAnimate(parsed, entry) {
// Enforce 100% stepping within subranges.
parsed.animate = entry;
if (typeof entry !== "boolean") {
throw new Error("noUiSlider (" + VERSION + "): 'animate' option must be a boolean.");
}
}
function testAnimationDuration(parsed, entry) {
parsed.animationDuration = entry;
if (typeof entry !== "number") {
throw new Error("noUiSlider (" + VERSION + "): 'animationDuration' option must be a number.");
}
}
function testConnect(parsed, entry) {
var connect = [false];
var i;
// Map legacy options
if (entry === "lower") {
entry = [true, false];
} else if (entry === "upper") {
entry = [false, true];
}
// Handle boolean options
if (entry === true || entry === false) {
for (i = 1; i < parsed.handles; i++) {
connect.push(entry);
}
connect.push(false);
}
// Reject invalid input
else if (!Array.isArray(entry) || !entry.length || entry.length !== parsed.handles + 1) {
throw new Error("noUiSlider (" + VERSION + "): 'connect' option doesn't match handle count.");
} else {
connect = entry;
}
parsed.connect = connect;
}
function testOrientation(parsed, entry) {
// Set orientation to an a numerical value for easy
// array selection.
switch (entry) {
case "horizontal":
parsed.ort = 0;
break;
case "vertical":
parsed.ort = 1;
break;
default:
throw new Error("noUiSlider (" + VERSION + "): 'orientation' option is invalid.");
}
}
function testMargin(parsed, entry) {
if (!isNumeric(entry)) {
throw new Error("noUiSlider (" + VERSION + "): 'margin' option must be numeric.");
}
// Issue #582
if (entry === 0) {
return;
}
parsed.margin = parsed.spectrum.getMargin(entry);
if (!parsed.margin) {
throw new Error("noUiSlider (" + VERSION + "): 'margin' option is only supported on linear sliders.");
}
}
function testLimit(parsed, entry) {
if (!isNumeric(entry)) {
throw new Error("noUiSlider (" + VERSION + "): 'limit' option must be numeric.");
}
parsed.limit = parsed.spectrum.getMargin(entry);
if (!parsed.limit || parsed.handles < 2) {
throw new Error(
"noUiSlider (" +
VERSION +
"): 'limit' option is only supported on linear sliders with 2 or more handles."
);
}
}
function testPadding(parsed, entry) {
if (!isNumeric(entry) && !Array.isArray(entry)) {
throw new Error(
"noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."
);
}
if (Array.isArray(entry) && !(entry.length === 2 || isNumeric(entry[0]) || isNumeric(entry[1]))) {
throw new Error(
"noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."
);
}
if (entry === 0) {
return;
}
if (!Array.isArray(entry)) {
entry = [entry, entry];
}
// 'getMargin' returns false for invalid values.
parsed.padding = [parsed.spectrum.getMargin(entry[0]), parsed.spectrum.getMargin(entry[1])];
if (parsed.padding[0] === false || parsed.padding[1] === false) {
throw new Error("noUiSlider (" + VERSION + "): 'padding' option is only supported on linear sliders.");
}
if (parsed.padding[0] < 0 || parsed.padding[1] < 0) {
throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s).");
}
if (parsed.padding[0] + parsed.padding[1] >= 100) {
throw new Error("noUiSlider (" + VERSION + "): 'padding' option must not exceed 100% of the range.");
}
}
function testDirection(parsed, entry) {
// Set direction as a numerical value for easy parsing.
// Invert connection for RTL sliders, so that the proper
// handles get the connect/background classes.
switch (entry) {
case "ltr":
parsed.dir = 0;
break;
case "rtl":
parsed.dir = 1;
break;
default:
throw new Error("noUiSlider (" + VERSION + "): 'direction' option was not recognized.");
}
}
function testBehaviour(parsed, entry) {
// Make sure the input is a string.
if (typeof entry !== "string") {
throw new Error("noUiSlider (" + VERSION + "): 'behaviour' must be a string containing options.");
}
// Check if the string contains any keywords.
// None are required.
var tap = entry.indexOf("tap") >= 0;
var drag = entry.indexOf("drag") >= 0;
var fixed = entry.indexOf("fixed") >= 0;
var snap = entry.indexOf("snap") >= 0;
var hover = entry.indexOf("hover") >= 0;
var unconstrained = entry.indexOf("unconstrained") >= 0;
if (fixed) {
if (parsed.handles !== 2) {
throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles");
}
// Use margin to enforce fixed state
testMargin(parsed, parsed.start[1] - parsed.start[0]);
}
if (unconstrained && (parsed.margin || parsed.limit)) {
throw new Error(
"noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit"
);
}
parsed.events = {
tap: tap || snap,
drag: drag,
fixed: fixed,
snap: snap,
hover: hover,
unconstrained: unconstrained
};
}
function testTooltips(parsed, entry) {
if (entry === false) {
return;
}
if (entry === true) {
parsed.tooltips = [];
for (var i = 0; i < parsed.handles; i++) {
parsed.tooltips.push(true);
}
} else {
parsed.tooltips = asArray(entry);
if (parsed.tooltips.length !== parsed.handles) {
throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles.");
}
parsed.tooltips.forEach(function(formatter) {
if (
typeof formatter !== "boolean" &&
(typeof formatter !== "object" || typeof formatter.to !== "function")
) {
throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'.");
}
});
}
}
function testAriaFormat(parsed, entry) {
parsed.ariaFormat = entry;
validateFormat(entry);
}
function testFormat(parsed, entry) {
parsed.format = entry;
validateFormat(entry);
}
function testKeyboardSupport(parsed, entry) {
parsed.keyboardSupport = entry;
if (typeof entry !== "boolean") {
throw new Error("noUiSlider (" + VERSION + "): 'keyboardSupport' option must be a boolean.");
}
}
function testDocumentElement(parsed, entry) {
// This is an advanced option. Passed values are used without validation.
parsed.documentElement = entry;
}
function testCssPrefix(parsed, entry) {
if (typeof entry !== "string" && entry !== false) {
throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`.");
}
parsed.cssPrefix = entry;
}
function testCssClasses(parsed, entry) {
if (typeof entry !== "object") {
throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object.");
}
if (typeof parsed.cssPrefix === "string") {
parsed.cssClasses = {};
for (var key in entry) {
if (!entry.hasOwnProperty(key)) {
continue;
}
parsed.cssClasses[key] = parsed.cssPrefix + entry[key];
}
} else {
parsed.cssClasses = entry;
}
}
// Test all developer settings and parse to assumption-safe values.
function testOptions(options) {
// To prove a fix for #537, freeze options here.
// If the object is modified, an error will be thrown.
// Object.freeze(options);
var parsed = {
margin: 0,
limit: 0,
padding: 0,
animate: true,
animationDuration: 300,
ariaFormat: defaultFormatter,
format: defaultFormatter
};
// Tests are executed in the order they are presented here.
var tests = {
step: { r: false, t: testStep },
start: { r: true, t: testStart },
connect: { r: true, t: testConnect },
direction: { r: true, t: testDirection },
snap: { r: false, t: testSnap },
animate: { r: false, t: testAnimate },
animationDuration: { r: false, t: testAnimationDuration },
range: { r: true, t: testRange },
orientation: { r: false, t: testOrientation },
margin: { r: false, t: testMargin },
limit: { r: false, t: testLimit },
padding: { r: false, t: testPadding },
behaviour: { r: true, t: testBehaviour },
ariaFormat: { r: false, t: testAriaFormat },
format: { r: false, t: testFormat },
tooltips: { r: false, t: testTooltips },
keyboardSupport: { r: true, t: testKeyboardSupport },
documentElement: { r: false, t: testDocumentElement },
cssPrefix: { r: true, t: testCssPrefix },
cssClasses: { r: true, t: testCssClasses }
};
var defaults = {
connect: false,
direction: "ltr",
behaviour: "tap",
orientation: "horizontal",
keyboardSupport: true,
cssPrefix: "noUi-",
cssClasses: {
target: "target",
base: "base",
origin: "origin",
handle: "handle",
handleLower: "handle-lower",
handleUpper: "handle-upper",
touchArea: "touch-area",
horizontal: "horizontal",
vertical: "vertical",
background: "background",
connect: "connect",
connects: "connects",
ltr: "ltr",
rtl: "rtl",
draggable: "draggable",
drag: "state-drag",
tap: "state-tap",
active: "active",
tooltip: "tooltip",
pips: "pips",
pipsHorizontal: "pips-horizontal",
pipsVertical: "pips-vertical",
marker: "marker",
markerHorizontal: "marker-horizontal",
markerVertical: "marker-vertical",
markerNormal: "marker-normal",
markerLarge: "marker-large",
markerSub: "marker-sub",
value: "value",
valueHorizontal: "value-horizontal",
valueVertical: "value-vertical",
valueNormal: "value-normal",
valueLarge: "value-large",
valueSub: "value-sub"
}
};
// AriaFormat defaults to regular format, if any.
if (options.format && !options.ariaFormat) {
options.ariaFormat = options.format;
}
// Run all options through a testing mechanism to ensure correct
// input. It should be noted that options might get modified to
// be handled properly. E.g. wrapping integers in arrays.
Object.keys(tests).forEach(function(name) {
// If the option isn't set, but it is required, throw an error.
if (!isSet(options[name]) && defaults[name] === undefined) {
if (tests[name].r) {
throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required.");
}
return true;
}
tests[name].t(parsed, !isSet(options[name]) ? defaults[name] : options[name]);
});
// Forward pips options
parsed.pips = options.pips;
// All recent browsers accept unprefixed transform.
// We need -ms- for IE9 and -webkit- for older Android;
// Assume use of -webkit- if unprefixed and -ms- are not supported.
// https://caniuse.com/#feat=transforms2d
var d = document.createElement("div");
var msPrefix = d.style.msTransform !== undefined;
var noPrefix = d.style.transform !== undefined;
parsed.transformRule = noPrefix ? "transform" : msPrefix ? "msTransform" : "webkitTransform";
// Pips don't move, so we can place them using left/top.
var styles = [["left", "top"], ["right", "bottom"]];
parsed.style = styles[parsed.dir][parsed.ort];
return parsed;
}
function scope(target, options, originalOptions) {
var actions = getActions();
var supportsTouchActionNone = getSupportsTouchActionNone();
var supportsPassive = supportsTouchActionNone && getSupportsPassive();
// All variables local to 'scope' are prefixed with 'scope_'
// Slider DOM Nodes
var scope_Target = target;
var scope_Base;
var scope_Handles;
var scope_Connects;
var scope_Pips;
// Override for the 'animate' option
var scope_ShouldAnimate = true;
// Slider state values
var scope_Spectrum = options.spectrum;
var scope_Values = [];
var scope_Locations = [];
var scope_HandleNumbers = [];
var scope_ActiveHandlesCount = 0;
var scope_Events = {};
// Exposed API
var scope_Self;
// Document Nodes
var scope_Document = target.ownerDocument;
var scope_DocumentElement = options.documentElement || scope_Document.documentElement;
var scope_Body = scope_Document.body;
// Pips constants
var PIPS_NONE = -1;
var PIPS_NO_VALUE = 0;
var PIPS_LARGE_VALUE = 1;
var PIPS_SMALL_VALUE = 2;
// For horizontal sliders in standard ltr documents,
// make .noUi-origin overflow to the left so the document doesn't scroll.
var scope_DirOffset = scope_Document.dir === "rtl" || options.ort === 1 ? 0 : 100;
// Creates a node, adds it to target, returns the new node.
function addNodeTo(addTarget, className) {
var div = scope_Document.createElement("div");
if (className) {
addClass(div, className);
}
addTarget.appendChild(div);
return div;
}
// Append a origin to the base
function addOrigin(base, handleNumber) {
var origin = addNodeTo(base, options.cssClasses.origin);
var handle = addNodeTo(origin, options.cssClasses.handle);
addNodeTo(handle, options.cssClasses.touchArea);
handle.setAttribute("data-handle", handleNumber);
if (options.keyboardSupport) {
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
// 0 = focusable and reachable
handle.setAttribute("tabindex", "0");
handle.addEventListener("keydown", function(event) {
return eventKeydown(event, handleNumber);
});
}
handle.setAttribute("role", "slider");
handle.setAttribute("aria-orientation", options.ort ? "vertical" : "horizontal");
if (handleNumber === 0) {
addClass(handle, options.cssClasses.handleLower);
} else if (handleNumber === options.handles - 1) {
addClass(handle, options.cssClasses.handleUpper);
}
return origin;
}
// Insert nodes for connect elements
function addConnect(base, add) {
if (!add) {
return false;
}
return addNodeTo(base, options.cssClasses.connect);
}
// Add handles to the slider base.
function addElements(connectOptions, base) {
var connectBase = addNodeTo(base, options.cssClasses.connects);
scope_Handles = [];
scope_Connects = [];
scope_Connects.push(addConnect(connectBase, connectOptions[0]));
// [::::O====O====O====]
// connectOptions = [0, 1, 1, 1]
for (var i = 0; i < options.handles; i++) {
// Keep a list of all added handles.
scope_Handles.push(addOrigin(base, i));
scope_HandleNumbers[i] = i;
scope_Connects.push(addConnect(connectBase, connectOptions[i + 1]));
}
}
// Initialize a single slider.
function addSlider(addTarget) {
// Apply classes and data to the target.
addClass(addTarget, options.cssClasses.target);
if (options.dir === 0) {
addClass(addTarget, options.cssClasses.ltr);
} else {
addClass(addTarget, options.cssClasses.rtl);
}
if (options.ort === 0) {
addClass(addTarget, options.cssClasses.horizontal);
} else {
addClass(addTarget, options.cssClasses.vertical);
}
return addNodeTo(addTarget, options.cssClasses.base);
}
function addTooltip(handle, handleNumber) {
if (!options.tooltips[handleNumber]) {
return false;
}
return addNodeTo(handle.firstChild, options.cssClasses.tooltip);
}
// Disable the slider dragging if any handle is disabled
function isHandleDisabled(handleNumber) {
var handleOrigin = scope_Handles[handleNumber];
return handleOrigin.hasAttribute("disabled");
}
// The tooltips option is a shorthand for using the 'update' event.
function tooltips() {
// Tooltips are added with options.tooltips in original order.
var tips = scope_Handles.map(addTooltip);
bindEvent("update", function(values, handleNumber, unencoded) {
if (!tips[handleNumber]) {
return;
}
var formattedValue = values[handleNumber];
if (options.tooltips[handleNumber] !== true) {
formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]);
}
tips[handleNumber].innerHTML = formattedValue;
});
}
function aria() {
bindEvent("update", function(values, handleNumber, unencoded, tap, positions) {
// Update Aria Values for all handles, as a change in one changes min and max values for the next.
scope_HandleNumbers.forEach(function(index) {
var handle = scope_Handles[index];
var min = checkHandlePosition(scope_Locations, index, 0, true, true, true);
var max = checkHandlePosition(scope_Locations, index, 100, true, true, true);
var now = positions[index];
// Formatted value for display
var text = options.ariaFormat.to(unencoded[index]);
// Map to slider range values
min = scope_Spectrum.fromStepping(min).toFixed(1);
max = scope_Spectrum.fromStepping(max).toFixed(1);
now = scope_Spectrum.fromStepping(now).toFixed(1);
handle.children[0].setAttribute("aria-valuemin", min);
handle.children[0].setAttribute("aria-valuemax", max);
handle.children[0].setAttribute("aria-valuenow", now);
handle.children[0].setAttribute("aria-valuetext", text);
});
});
}
function getGroup(mode, values, stepped) {
// Use the range.
if (mode === "range" || mode === "steps") {
return scope_Spectrum.xVal;
}
if (mode === "count") {
if (values < 2) {
throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'.");
}
// Divide 0 - 100 in 'count' parts.
var interval = values - 1;
var spread = 100 / interval;
values = [];
// List these parts and have them handled as 'positions'.
while (interval--) {
values[interval] = interval * spread;
}
values.push(100);
mode = "positions";
}
if (mode === "positions") {
// Map all percentages to on-range values.
return values.map(function(value) {
return scope_Spectrum.fromStepping(stepped ? scope_Spectrum.getStep(value) : value);
});
}
if (mode === "values") {
// If the value must be stepped, it needs to be converted to a percentage first.
if (stepped) {
return values.map(function(value) {
// Convert to percentage, apply step, return to value.
return scope_Spectrum.fromStepping(scope_Spectrum.getStep(scope_Spectrum.toStepping(value)));
});
}
// Otherwise, we can simply use the values.
return values;
}
}
function generateSpread(density, mode, group) {
function safeIncrement(value, increment) {
// Avoid floating point variance by dropping the smallest decimal places.
return (value + increment).toFixed(7) / 1;
}
var indexes = {};
var firstInRange = scope_Spectrum.xVal[0];
var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length - 1];
var ignoreFirst = false;
var ignoreLast = false;
var prevPct = 0;
// Create a copy of the group, sort it and filter away all duplicates.
group = unique(
group.slice().sort(function(a, b) {
return a - b;
})
);
// Make sure the range starts with the first element.
if (group[0] !== firstInRange) {
group.unshift(firstInRange);
ignoreFirst = true;
}
// Likewise for the last one.
if (group[group.length - 1] !== lastInRange) {
group.push(lastInRange);
ignoreLast = true;
}
group.forEach(function(current, index) {
// Get the current step and the lower + upper positions.
var step;
var i;
var q;
var low = current;
var high = group[index + 1];
var newPct;
var pctDifference;
var pctPos;
var type;
var steps;
var realSteps;
var stepSize;
var isSteps = mode === "steps";
// When using 'steps' mode, use the provided steps.
// Otherwise, we'll step on to the next subrange.
if (isSteps) {
step = scope_Spectrum.xNumSteps[index];
}
// Default to a 'full' step.
if (!step) {
step = high - low;
}
// Low can be 0, so test for false. If high is undefined,
// we are at the last subrange. Index 0 is already handled.
if (low === false || high === undefined) {
return;
}
// Make sure step isn't 0, which would cause an infinite loop (#654)
step = Math.max(step, 0.0000001);
// Find all steps in the subrange.
for (i = low; i <= high; i = safeIncrement(i, step)) {
// Get the percentage value for the current step,
// calculate the size for the subrange.
newPct = scope_Spectrum.toStepping(i);
pctDifference = newPct - prevPct;
steps = pctDifference / density;
realSteps = Math.round(steps);
// This ratio represents the amount of percentage-space a point indicates.
// For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-divided.
// Round the percentage offset to an even number, then divide by two
// to spread the offset on both sides of the range.
stepSize = pctDifference / realSteps;
// Divide all points evenly, adding the correct number to this subrange.
// Run up to <= so that 100% gets a point, event if ignoreLast is set.
for (q = 1; q <= realSteps; q += 1) {
// The ratio between the rounded value and the actual size might be ~1% off.
// Correct the percentage offset by the number of points
// per subrange. density = 1 will result in 100 points on the
// full range, 2 for 50, 4 for 25, etc.
pctPos = prevPct + q * stepSize;
indexes[pctPos.toFixed(5)] = [scope_Spectrum.fromStepping(pctPos), 0];
}
// Determine the point type.
type = group.indexOf(i) > -1 ? PIPS_LARGE_VALUE : isSteps ? PIPS_SMALL_VALUE : PIPS_NO_VALUE;
// Enforce the 'ignoreFirst' option by overwriting the type for 0.
if (!index && ignoreFirst) {
type = 0;
}
if (!(i === high && ignoreLast)) {
// Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
indexes[newPct.toFixed(5)] = [i, type];
}
// Update the percentage count.
prevPct = newPct;
}
});
return indexes;
}
function addMarking(spread, filterFunc, formatter) {
var element = scope_Document.createElement("div");
var valueSizeClasses = [];
valueSizeClasses[PIPS_NO_VALUE] = options.cssClasses.valueNormal;
valueSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.valueLarge;
valueSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.valueSub;
var markerSizeClasses = [];
markerSizeClasses[PIPS_NO_VALUE] = options.cssClasses.markerNormal;
markerSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.markerLarge;
markerSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.markerSub;
var valueOrientationClasses = [options.cssClasses.valueHorizontal, options.cssClasses.valueVertical];
var markerOrientationClasses = [options.cssClasses.markerHorizontal, options.cssClasses.markerVertical];
addClass(element, options.cssClasses.pips);
addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical);
function getClasses(type, source) {
var a = source === options.cssClasses.value;
var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses;
var sizeClasses = a ? valueSizeClasses : markerSizeClasses;
return source + " " + orientationClasses[options.ort] + " " + sizeClasses[type];
}
function addSpread(offset, value, type) {
// Apply the filter function, if it is set.
type = filterFunc ? filterFunc(value, type) : type;
if (type === PIPS_NONE) {
return;
}
// Add a marker for every point
var node = addNodeTo(element, false);
node.className = getClasses(type, options.cssClasses.marker);
node.style[options.style] = offset + "%";
// Values are only appended for points marked '1' or '2'.
if (type > PIPS_NO_VALUE) {
node = addNodeTo(element, false);
node.className = getClasses(type, options.cssClasses.value);
node.setAttribute("data-value", value);
node.style[options.style] = offset + "%";
node.innerHTML = formatter.to(value);
}
}
// Append all points.
Object.keys(spread).forEach(function(offset) {
addSpread(offset, spread[offset][0], spread[offset][1]);
});
return element;
}
function removePips() {
if (scope_Pips) {
removeElement(scope_Pips);
scope_Pips = null;
}
}
function pips(grid) {
// Fix #669
removePips();
var mode = grid.mode;
var density = grid.density || 1;
var filter = grid.filter || false;
var values = grid.values || false;
var stepped = grid.stepped || false;
var group = getGroup(mode, values, stepped);
var spread = generateSpread(density, mode, group);
var format = grid.format || {
to: Math.round
};
scope_Pips = scope_Target.appendChild(addMarking(spread, filter, format));
return scope_Pips;
}
// Shorthand for base dimensions.
function baseSize() {
var rect = scope_Base.getBoundingClientRect();
var alt = "offset" + ["Width", "Height"][options.ort];
return options.ort === 0 ? rect.width || scope_Base[alt] : rect.height || scope_Base[alt];
}
// Handler for attaching events trough a proxy.
function attachEvent(events, element, callback, data) {
// This function can be used to 'filter' events to the slider.
// element is a node, not a nodeList
var method = function(e) {
e = fixEvent(e, data.pageOffset, data.target || element);
// fixEvent returns false if this event has a different target
// when handling (multi-) touch events;
if (!e) {
return false;
}
// doNotReject is passed by all end events to make sure released touches
// are not rejected, leaving the slider "stuck" to the cursor;
if (scope_Target.hasAttribute("disabled") && !data.doNotReject) {
return false;
}
// Stop if an active 'tap' transition is taking place.
if (hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject) {
return false;
}
// Ignore right or middle clicks on start #454
if (events === actions.start && e.buttons !== undefined && e.buttons > 1) {
return false;
}
// Ignore right or middle clicks on start #454
if (data.hover && e.buttons) {
return false;
}
// 'supportsPassive' is only true if a browser also supports touch-action: none in CSS.
// iOS safari does not, so it doesn't get to benefit from passive scrolling. iOS does support
// touch-action: manipulation, but that allows panning, which breaks
// sliders after zooming/on non-responsive pages.
// See: https://bugs.webkit.org/show_bug.cgi?id=133112
if (!supportsPassive) {
e.preventDefault();
}
e.calcPoint = e.points[options.ort];
// Call the event handler with the event [ and additional data ].
callback(e, data);
};
var methods = [];
// Bind a closure on the target for every event type.
events.split(" ").forEach(function(eventName) {
element.addEventListener(eventName, method, supportsPassive ? { passive: true } : false);
methods.push([eventName, method]);
});
return methods;
}
// Provide a clean event with standardized offset values.
function fixEvent(e, pageOffset, eventTarget) {
// Filter the event to register the type, which can be
// touch, mouse or pointer. Offset changes need to be
// made on an event specific basis.
var touch = e.type.indexOf("touch") === 0;
var mouse = e.type.indexOf("mouse") === 0;
var pointer = e.type.indexOf("pointer") === 0;
var x;
var y;
// IE10 implemented pointer events with a prefix;
if (e.type.indexOf("MSPointer") === 0) {
pointer = true;
}
// The only thing one handle should be concerned about is the touches that originated on top of it.
if (touch) {
// Returns true if a touch originated on the target.
var isTouchOnTarget = function(checkTouch) {
return checkTouch.target === eventTarget || eventTarget.contains(checkTouch.target);
};
// In the case of touchstart events, we need to make sure there is still no more than one
// touch on the target so we look amongst all touches.
if (e.type === "touchstart") {
var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget);
// Do not support more than one touch per handle.
if (targetTouches.length > 1) {
return false;
}
x = targetTouches[0].pageX;
y = targetTouches[0].pageY;
} else {
// In the other cases, find on changedTouches is enough.
var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget);
// Cancel if the target touch has not moved.
if (!targetTouch) {
return false;
}
x = targetTouch.pageX;
y = targetTouch.pageY;
}
}
pageOffset = pageOffset || getPageOffset(scope_Document);
if (mouse || pointer) {
x = e.clientX + pageOffset.x;
y = e.clientY + pageOffset.y;
}
e.pageOffset = pageOffset;
e.points = [x, y];
e.cursor = mouse || pointer; // Fix #435
return e;
}
// Translate a coordinate in the document to a percentage on the slider
function calcPointToPercentage(calcPoint) {
var location = calcPoint - offset(scope_Base, options.ort);
var proposal = (location * 100) / baseSize();
// Clamp proposal between 0% and 100%
// Out-of-bound coordinates may occur when .noUi-base pseudo-elements
// are used (e.g. contained handles feature)
proposal = limit(proposal);
return options.dir ? 100 - proposal : proposal;
}
// Find handle closest to a certain percentage on the slider
function getClosestHandle(proposal) {
var closest = 100;
var handleNumber = false;
scope_Handles.forEach(function(handle, index) {
// Disabled handles are ignored
if (isHandleDisabled(index)) {
return;
}
var pos = Math.abs(scope_Locations[index] - proposal);
if (pos < closest || (pos === 100 && closest === 100)) {
handleNumber = index;
closest = pos;
}
});
return handleNumber;
}
// Fire 'end' when a mouse or pen leaves the document.
function documentLeave(event, data) {
if (event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null) {
eventEnd(event, data);
}
}
// Handle movement on document for handle and range drag.
function eventMove(event, data) {
// Fix #498
// Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
// https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
// IE9 has .buttons and .which zero on mousemove.
// Firefox breaks the spec MDN defines.
if (navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0) {
return eventEnd(event, data);
}
// Check if we are moving up or down
var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint);
// Convert the movement into a percentage of the slider width/height
var proposal = (movement * 100) / data.baseSize;
moveHandles(movement > 0, proposal, data.locations, data.handleNumbers);
}
// Unbind move events on document, call callbacks.
function eventEnd(event, data) {
// The handle is no longer active, so remove the class.
if (data.handle) {
removeClass(data.handle, options.cssClasses.active);
scope_ActiveHandlesCount -= 1;
}
// Unbind the move and end events, which are added on 'start'.
data.listeners.forEach(function(c) {
scope_DocumentElement.removeEventListener(c[0], c[1]);
});
if (scope_ActiveHandlesCount === 0) {
// Remove dragging class.
removeClass(scope_Target, options.cssClasses.drag);
setZindex();
// Remove cursor styles and text-selection events bound to the body.
if (event.cursor) {
scope_Body.style.cursor = "";
scope_Body.removeEventListener("selectstart", preventDefault);
}
}
data.handleNumbers.forEach(function(handleNumber) {
fireEvent("change", handleNumber);
fireEvent("set", handleNumber);
fireEvent("end", handleNumber);
});
}
// Bind move events on document.
function eventStart(event, data) {
// Ignore event if any handle is disabled
if (data.handleNumbers.some(isHandleDisabled)) {
return false;
}
var handle;
if (data.handleNumbers.length === 1) {
var handleOrigin = scope_Handles[data.handleNumbers[0]];
handle = handleOrigin.children[0];
scope_ActiveHandlesCount += 1;
// Mark the handle as 'active' so it can be styled.
addClass(handle, options.cssClasses.active);
}
// A drag should never propagate up to the 'tap' event.
event.stopPropagation();
// Record the event listeners.
var listeners = [];
// Attach the move and end events.
var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, {
// The event target has changed so we need to propagate the original one so that we keep
// relying on it to extract target touches.
target: event.target,
handle: handle,
listeners: listeners,
startCalcPoint: event.calcPoint,
baseSize: baseSize(),
pageOffset: event.pageOffset,
handleNumbers: data.handleNumbers,
buttonsProperty: event.buttons,
locations: scope_Locations.slice()
});
var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, {
target: event.target,
handle: handle,
listeners: listeners,
doNotReject: true,
handleNumbers: data.handleNumbers
});
var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, {
target: event.target,
handle: handle,
listeners: listeners,
doNotReject: true,
handleNumbers: data.handleNumbers
});
// We want to make sure we pushed the listeners in the listener list rather than creating
// a new one as it has already been passed to the event handlers.
listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent));
// Text selection isn't an issue on touch devices,
// so adding cursor styles can be skipped.
if (event.cursor) {
// Prevent the 'I' cursor and extend the range-drag cursor.
scope_Body.style.cursor = getComputedStyle(event.target).cursor;
// Mark the target with a dragging state.
if (scope_Handles.length > 1) {
addClass(scope_Target, options.cssClasses.drag);
}
// Prevent text selection when dragging the handles.
// In noUiSlider <= 9.2.0, this was handled by calling preventDefault on mouse/touch start/move,
// which is scroll blocking. The selectstart event is supported by FireFox starting from version 52,
// meaning the only holdout is iOS Safari. This doesn't matter: text selection isn't triggered there.
// The 'cursor' flag is false.
// See: http://caniuse.com/#search=selectstart
scope_Body.addEventListener("selectstart", preventDefault, false);
}
data.handleNumbers.forEach(function(handleNumber) {
fireEvent("start", handleNumber);
});
}
// Move closest handle to tapped location.
function eventTap(event) {
// The tap event shouldn't propagate up
event.stopPropagation();
var proposal = calcPointToPercentage(event.calcPoint);
var handleNumber = getClosestHandle(proposal);
// Tackle the case that all handles are 'disabled'.
if (handleNumber === false) {
return false;
}
// Flag the slider as it is now in a transitional state.
// Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
if (!options.events.snap) {
addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
}
setHandle(handleNumber, proposal, true, true);
setZindex();
fireEvent("slide", handleNumber, true);
fireEvent("update", handleNumber, true);
fireEvent("change", handleNumber, true);
fireEvent("set", handleNumber, true);
if (options.events.snap) {
eventStart(event, { handleNumbers: [handleNumber] });
}
}
// Fires a 'hover' event for a hovered mouse/pen position.
function eventHover(event) {
var proposal = calcPointToPercentage(event.calcPoint);
var to = scope_Spectrum.getStep(proposal);
var value = scope_Spectrum.fromStepping(to);
Object.keys(scope_Events).forEach(function(targetEvent) {
if ("hover" === targetEvent.split(".")[0]) {
scope_Events[targetEvent].forEach(function(callback) {
callback.call(scope_Self, value);
});
}
});
}
// Handles keydown on focused handles
// Don't move the document when pressing arrow keys on focused handles
function eventKeydown(event, handleNumber) {
if (isHandleDisabled(handleNumber)) {
return false;
}
var horizontalKeys = ["Left", "Right"];
var verticalKeys = ["Down", "Up"];
if (options.dir && !options.ort) {
// On an right-to-left slider, the left and right keys act inverted
horizontalKeys.reverse();
} else if (options.ort && !options.dir) {
// On a top-to-bottom slider, the up and down keys act inverted
verticalKeys.reverse();
}
// Strip "Arrow" for IE compatibility. https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
var key = event.key.replace("Arrow", "");
var isDown = key === verticalKeys[0] || key === horizontalKeys[0];
var isUp = key === verticalKeys[1] || key === horizontalKeys[1];
if (!isDown && !isUp) {
return true;
}
event.preventDefault();
var direction = isDown ? 0 : 1;
var steps = getNextStepsForHandle(handleNumber);
var step = steps[direction];
// At the edge of a slider, do nothing
if (step === null) {
return false;
}
// No step set, use the default of 10% of the sub-range
if (step === false) {
step = scope_Spectrum.getDefaultStep(scope_Locations[handleNumber], isDown, 10);
}
// Decrement for down steps
step = (isDown ? -1 : 1) * step;
scope_ShouldAnimate = false;
valueSetHandle(handleNumber, scope_Values[handleNumber] + step, true);
scope_ShouldAnimate = true;
return false;
}
// Attach events to several slider parts.
function bindSliderEvents(behaviour) {
// Attach the standard drag event to the handles.
if (!behaviour.fixed) {
scope_Handles.forEach(function(handle, index) {
// These events are only bound to the visual handle
// element, not the 'real' origin element.
attachEvent(actions.start, handle.children[0], eventStart, {
handleNumbers: [index]
});
});
}
// Attach the tap event to the slider base.
if (behaviour.tap) {
attachEvent(actions.start, scope_Base, eventTap, {});
}
// Fire hover events
if (behaviour.hover) {
attachEvent(actions.move, scope_Base, eventHover, {
hover: true
});
}
// Make the range draggable.
if (behaviour.drag) {
scope_Connects.forEach(function(connect, index) {
if (connect === false || index === 0 || index === scope_Connects.length - 1) {
return;
}
var handleBefore = scope_Handles[index - 1];
var handleAfter = scope_Handles[index];
var eventHolders = [connect];
addClass(connect, options.cssClasses.draggable);
// When the range is fixed, the entire range can
// be dragged by the handles. The handle in the first
// origin will propagate the start event upward,
// but it needs to be bound manually on the other.
if (behaviour.fixed) {
eventHolders.push(handleBefore.children[0]);
eventHolders.push(handleAfter.children[0]);
}
eventHolders.forEach(function(eventHolder) {
attachEvent(actions.start, eventHolder, eventStart, {
handles: [handleBefore, handleAfter],
handleNumbers: [index - 1, index]
});
});
});
}
}
// Attach an event to this slider, possibly including a namespace
function bindEvent(namespacedEvent, callback) {
scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
scope_Events[namespacedEvent].push(callback);
// If the event bound is 'update,' fire it immediately for all handles.
if (namespacedEvent.split(".")[0] === "update") {
scope_Handles.forEach(function(a, index) {
fireEvent("update", index);
});
}
}
// Undo attachment of event
function removeEvent(namespacedEvent) {
var event = namespacedEvent && namespacedEvent.split(".")[0];
var namespace = event && namespacedEvent.substring(event.length);
Object.keys(scope_Events).forEach(function(bind) {
var tEvent = bind.split(".")[0];
var tNamespace = bind.substring(tEvent.length);
if ((!event || event === tEvent) && (!namespace || namespace === tNamespace)) {
delete scope_Events[bind];
}
});
}
// External event handling
function fireEvent(eventName, handleNumber, tap) {
Object.keys(scope_Events).forEach(function(targetEvent) {
var eventType = targetEvent.split(".")[0];
if (eventName === eventType) {
scope_Events[targetEvent].forEach(function(callback) {
callback.call(
// Use the slider public API as the scope ('this')
scope_Self,
// Return values as array, so arg_1[arg_2] is always valid.
scope_Values.map(options.format.to),
// Handle index, 0 or 1
handleNumber,
// Un-formatted slider values
scope_Values.slice(),
// Event is fired by tap, true or false
tap || false,
// Left offset of the handle, in relation to the slider
scope_Locations.slice()
);
});
}
});
}
// Split out the handle positioning logic so the Move event can use it, too
function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) {
// For sliders with multiple handles, limit movement to the other handle.
// Apply the margin option by adding it to the handle positions.
if (scope_Handles.length > 1 && !options.events.unconstrained) {
if (lookBackward && handleNumber > 0) {
to = Math.max(to, reference[handleNumber - 1] + options.margin);
}
if (lookForward && handleNumber < scope_Handles.length - 1) {
to = Math.min(to, reference[handleNumber + 1] - options.margin);
}
}
// The limit option has the opposite effect, limiting handles to a
// maximum distance from another. Limit must be > 0, as otherwise
// handles would be unmovable.
if (scope_Handles.length > 1 && options.limit) {
if (lookBackward && handleNumber > 0) {
to = Math.min(to, reference[handleNumber - 1] + options.limit);
}
if (lookForward && handleNumber < scope_Handles.length - 1) {
to = Math.max(to, reference[handleNumber + 1] - options.limit);
}
}
// The padding option keeps the handles a certain distance from the
// edges of the slider. Padding must be > 0.
if (options.padding) {
if (handleNumber === 0) {
to = Math.max(to, options.padding[0]);
}
if (handleNumber === scope_Handles.length - 1) {
to = Math.min(to, 100 - options.padding[1]);
}
}
to = scope_Spectrum.getStep(to);
// Limit percentage to the 0 - 100 range
to = limit(to);
// Return false if handle can't move
if (to === reference[handleNumber] && !getValue) {
return false;
}
return to;
}
// Uses slider orientation to create CSS rules. a = base value;
function inRuleOrder(v, a) {
var o = options.ort;
return (o ? a : v) + ", " + (o ? v : a);
}
// Moves handle(s) by a percentage
// (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...])
function moveHandles(upward, proposal, locations, handleNumbers) {
var proposals = locations.slice();
var b = [!upward, upward];
var f = [upward, !upward];
// Copy handleNumbers so we don't change the dataset
handleNumbers = handleNumbers.slice();
// Check to see which handle is 'leading'.
// If that one can't move the second can't either.
if (upward) {
handleNumbers.reverse();
}
// Step 1: get the maximum percentage that any of the handles can move
if (handleNumbers.length > 1) {
handleNumbers.forEach(function(handleNumber, o) {
var to = checkHandlePosition(
proposals,
handleNumber,
proposals[handleNumber] + proposal,
b[o],
f[o],
false
);
// Stop if one of the handles can't move.
if (to === false) {
proposal = 0;
} else {
proposal = to - proposals[handleNumber];
proposals[handleNumber] = to;
}
});
}
// If using one handle, check backward AND forward
else {
b = f = [true];
}
var state = false;
// Step 2: Try to set the handles with the found percentage
handleNumbers.forEach(function(handleNumber, o) {
state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state;
});
// Step 3: If a handle moved, fire events
if (state) {
handleNumbers.forEach(function(handleNumber) {
fireEvent("update", handleNumber);
fireEvent("slide", handleNumber);
});
}
}
// Takes a base value and an offset. This offset is used for the connect bar size.
// In the initial design for this feature, the origin element was 1% wide.
// Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature
// in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223
function transformDirection(a, b) {
return options.dir ? 100 - a - b : a;
}
// Updates scope_Locations and scope_Values, updates visual state
function updateHandlePosition(handleNumber, to) {
// Update locations.
scope_Locations[handleNumber] = to;
// Convert the value to the slider stepping/range.
scope_Values[handleNumber] = scope_Spectrum.fromStepping(to);
var rule = "translate(" + inRuleOrder(transformDirection(to, 0) - scope_DirOffset + "%", "0") + ")";
scope_Handles[handleNumber].style[options.transformRule] = rule;
updateConnect(handleNumber);
updateConnect(handleNumber + 1);
}
// Handles before the slider middle are stacked later = higher,
// Handles after the middle later is lower
// [[7] [8] .......... | .......... [5] [4]
function setZindex() {
scope_HandleNumbers.forEach(function(handleNumber) {
var dir = scope_Locations[handleNumber] > 50 ? -1 : 1;
var zIndex = 3 + (scope_Handles.length + dir * handleNumber);
scope_Handles[handleNumber].style.zIndex = zIndex;
});
}
// Test suggested values and apply margin, step.
function setHandle(handleNumber, to, lookBackward, lookForward) {
to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false);
if (to === false) {
return false;
}
updateHandlePosition(handleNumber, to);
return true;
}
// Updates style attribute for connect nodes
function updateConnect(index) {
// Skip connects set to false
if (!scope_Connects[index]) {
return;
}
var l = 0;
var h = 100;
if (index !== 0) {
l = scope_Locations[index - 1];
}
if (index !== scope_Connects.length - 1) {
h = scope_Locations[index];
}
// We use two rules:
// 'translate' to change the left/top offset;
// 'scale' to change the width of the element;
// As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base)
var connectWidth = h - l;
var translateRule = "translate(" + inRuleOrder(transformDirection(l, connectWidth) + "%", "0") + ")";
var scaleRule = "scale(" + inRuleOrder(connectWidth / 100, "1") + ")";
scope_Connects[index].style[options.transformRule] = translateRule + " " + scaleRule;
}
// Parses value passed to .set method. Returns current value if not parse-able.
function resolveToValue(to, handleNumber) {
// Setting with null indicates an 'ignore'.
// Inputting 'false' is invalid.
if (to === null || to === false || to === undefined) {
return scope_Locations[handleNumber];
}
// If a formatted number was passed, attempt to decode it.
if (typeof to === "number") {
to = String(to);
}
to = options.format.from(to);
to = scope_Spectrum.toStepping(to);
// If parsing the number failed, use the current value.
if (to === false || isNaN(to)) {
return scope_Locations[handleNumber];
}
return to;
}
// Set the slider value.
function valueSet(input, fireSetEvent) {
var values = asArray(input);
var isInit = scope_Locations[0] === undefined;
// Event fires by default
fireSetEvent = fireSetEvent === undefined ? true : !!fireSetEvent;
// Animation is optional.
// Make sure the initial values were set before using animated placement.
if (options.animate && !isInit && scope_ShouldAnimate) {
addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
}
// First pass, without lookAhead but with lookBackward. Values are set from left to right.
scope_HandleNumbers.forEach(function(handleNumber) {
setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false);
});
// Second pass. Now that all base values are set, apply constraints
scope_HandleNumbers.forEach(function(handleNumber) {
setHandle(handleNumber, scope_Locations[handleNumber], true, true);
});
setZindex();
scope_HandleNumbers.forEach(function(handleNumber) {
fireEvent("update", handleNumber);
// Fire the event only for handles that received a new value, as per #579
if (values[handleNumber] !== null && fireSetEvent) {
fireEvent("set", handleNumber);
}
});
}
// Reset slider to initial values
function valueReset(fireSetEvent) {
valueSet(options.start, fireSetEvent);
}
// Set value for a single handle
function valueSetHandle(handleNumber, value, fireSetEvent) {
var values = [];
// Ensure numeric input
handleNumber = Number(handleNumber);
if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) {
throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber);
}
for (var i = 0; i < scope_HandleNumbers.length; i++) {
values[i] = null;
}
values[handleNumber] = value;
valueSet(values, fireSetEvent);
}
// Get the slider value.
function valueGet() {
var values = scope_Values.map(options.format.to);
// If only one handle is used, return a single value.
if (values.length === 1) {
return values[0];
}
return values;
}
// Removes classes from the root and empties it.
function destroy() {
for (var key in options.cssClasses) {
if (!options.cssClasses.hasOwnProperty(key)) {
continue;
}
removeClass(scope_Target, options.cssClasses[key]);
}
while (scope_Target.firstChild) {
scope_Target.removeChild(scope_Target.firstChild);
}
delete scope_Target.noUiSlider;
}
function getNextStepsForHandle(handleNumber) {
var location = scope_Locations[handleNumber];
var nearbySteps = scope_Spectrum.getNearbySteps(location);
var value = scope_Values[handleNumber];
var increment = nearbySteps.thisStep.step;
var decrement = null;
// If the next value in this step moves into the next step,
// the increment is the start of the next step - the current value
if (increment !== false) {
if (value + increment > nearbySteps.stepAfter.startValue) {
increment = nearbySteps.stepAfter.startValue - value;
}
}
// If the value is beyond the starting point
if (value > nearbySteps.thisStep.startValue) {
decrement = nearbySteps.thisStep.step;
} else if (nearbySteps.stepBefore.step === false) {
decrement = false;
}
// If a handle is at the start of a step, it always steps back into the previous step first
else {
decrement = value - nearbySteps.stepBefore.highestStep;
}
// Now, if at the slider edges, there is no in/decrement
if (location === 100) {
increment = null;
} else if (location === 0) {
decrement = null;
}
// As per #391, the comparison for the decrement step can have some rounding issues.
var stepDecimals = scope_Spectrum.countStepDecimals();
// Round per #391
if (increment !== null && increment !== false) {
increment = Number(increment.toFixed(stepDecimals));
}
if (decrement !== null && decrement !== false) {
decrement = Number(decrement.toFixed(stepDecimals));
}
return [decrement, increment];
}
// Get the current step size for the slider.
function getNextSteps() {
return scope_HandleNumbers.map(getNextStepsForHandle);
}
// Updateable: margin, limit, padding, step, range, animate, snap
function updateOptions(optionsToUpdate, fireSetEvent) {
// Spectrum is created using the range, snap, direction and step options.
// 'snap' and 'step' can be updated.
// If 'snap' and 'step' are not passed, they should remain unchanged.
var v = valueGet();
var updateAble = ["margin", "limit", "padding", "range", "animate", "snap", "step", "format"];
// Only change options that we're actually passed to update.
updateAble.forEach(function(name) {
if (optionsToUpdate[name] !== undefined) {
originalOptions[name] = optionsToUpdate[name];
}
});
var newOptions = testOptions(originalOptions);
// Load new options into the slider state
updateAble.forEach(function(name) {
if (optionsToUpdate[name] !== undefined) {
options[name] = newOptions[name];
}
});
scope_Spectrum = newOptions.spectrum;
// Limit, margin and padding depend on the spectrum but are stored outside of it. (#677)
options.margin = newOptions.margin;
options.limit = newOptions.limit;
options.padding = newOptions.padding;
// Update pips, removes existing.
if (options.pips) {
pips(options.pips);
}
// Invalidate the current positioning so valueSet forces an update.
scope_Locations = [];
valueSet(optionsToUpdate.start || v, fireSetEvent);
}
// Initialization steps
function setupSlider() {
// Create the base element, initialize HTML and set classes.
// Add handles and connect elements.
scope_Base = addSlider(scope_Target);
addElements(options.connect, scope_Base);
// Attach user events.
bindSliderEvents(options.events);
// Use the public value method to set the start values.
valueSet(options.start);
if (options.pips) {
pips(options.pips);
}
if (options.tooltips) {
tooltips();
}
aria();
}
setupSlider();
// noinspection JSUnusedGlobalSymbols
scope_Self = {
destroy: destroy,
steps: getNextSteps,
on: bindEvent,
off: removeEvent,
get: valueGet,
set: valueSet,
setHandle: valueSetHandle,
reset: valueReset,
// Exposed for unit testing, don't use this in your application.
__moveHandles: function(a, b, c) {
moveHandles(a, b, scope_Locations, c);
},
options: originalOptions, // Issue #600, #678
updateOptions: updateOptions,
target: scope_Target, // Issue #597
removePips: removePips,
pips: pips // Issue #594
};
return scope_Self;
}
// Run the standard initializer
function initialize(target, originalOptions) {
if (!target || !target.nodeName) {
throw new Error("noUiSlider (" + VERSION + "): create requires a single element, got: " + target);
}
// Throw an error if the slider was already initialized.
if (target.noUiSlider) {
throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized.");
}
// Test the options and create the slider environment;
var options = testOptions(originalOptions, target);
var api = scope(target, options, originalOptions);
target.noUiSlider = api;
return api;
}
// Use an object instead of a function for future expandability;
return {
// Exposed for unit testing, don't use this in your application.
__spectrum: Spectrum,
version: VERSION,
create: initialize
};
});
Многие из нас подвержены влиянию различных привычек в повседневной жизни. Однако, мало кто задумывается о том, что эти привычки могут иметь негативное влияние на нашу сексуальную активность. В этой статье мы рассмотрим, какие привычки могут мешать здоровой сексуальной жизни и как избавиться от них.
Опасные привычки, которые влияют на сексуальную активность
1. Постоянная усталость
Поездки по работе, нехватка сна и прочие факторы могут привести к постоянной усталости, которая сильно сказывается на сексуальной активности. Усталость уменьшает желание и способность к интимной близости. Для решения этой проблемы необходимо регулярно отдыхать, следить за режимом дня и заниматься спортом.
2. Эксперименты с вредными привычками
Курение, употребление алкоголя, наркотиков – все эти вредные привычки приводят к нарушению сексуальной функции организма. Никотин и алкоголь ухудшают кровоснабжение органов малого таза, что негативно сказывается на сексуальной активности. Отказ от вредных привычек может привести к улучшению сексуальной жизни.
3. Стресс и негативные эмоции
Постоянный стресс, негативные эмоции, конфликты в отношениях – все это может сильно повлиять на сексуальную жизнь. Стресс вызывает выделение гормонов, которые могут подавлять сексуальное влечение. Для борьбы со стрессом полезно заниматься медитацией, йогой, спортом или обращаться к психологу.
Как избавиться от привычек, влияющих на сексуальную активность
1. Формирование здоровых привычек
Постоянное занятие спортом, соблюдение правильного питания, регулярный сон – все это поможет улучшить общее самочувствие и личную жизнь. Формирование здоровых привычек способствует нормализации работы организма и улучшению сексуальной функции.
2. Отказ от вредных привычек
Если у вас есть вредные привычки, такие как курение или употребление алкоголя, важно понять их вред и постараться избавиться от них. Существует множество поддержки и методик по борьбе с зависимостями, в том числе обращение к специалистам.
3. Поддержка психического здоровья
Для сохранения здоровой сексуальной активности важно следить за своим эмоциональным состоянием. Регулярные консультации у психолога или психиатра могут помочь разобраться с проблемами, вызывающими стресс или негативные эмоции.
Привычки играют важную роль в нашей жизни и могут сильно влиять на сексуальную активность. Понимание вредных привычек и работа над их устранением являются ключевыми шагами к улучшению сексуальной жизни ebar82.com. Помните, что забота о собственном здоровье и психическом благополучии – залог качественных и гармоничных отношений.
For many who wear’t yet , very own any crypto, much of all of our sites give you the choice to order it indeed there after which playing with fiat money. Keen on enjoying exactly how our finest crypto and you may Bitcoin gambling enterprises evaluate to each other.
Witamy w Slottica! Nasza platforma to harmonijne połączenie innowacji, różnorodności i najwyższych standardów bezpieczeństwa, zaprojektowane z myślą o wymagających graczach z Polski. Oferujemy nie tylko szeroki wybór gier – od dynamicznych automatów, przez strategiczne gry stołowe, po ekskluzywne sesje z live dealerami – ale także kompleksowe rozwiązania dostosowane do Twoich preferencji.
Dzięki zaawansowanej technologii mobilnej, ciesz się płynnym dostępem do ulubionych rozgrywek na dowolnym urządzeniu, gdziekolwiek jesteś. Slottica Casino współpracuje wyłącznie z uznanymi dostawcami oprogramowania, gwarantując najwyższą jakość grafiki, uczciwość mechaniki i regularne aktualizacje biblioteki gier.
Priorytetem jest dla nas Twój komfort i zaufanie: wykorzystujemy szyfrowanie klasy bankowej, systemy szybkich wypłat oraz całodobowe wsparcie w języku polskim. Dodatkowo, przygotowaliśmy ekskluzywne promocje i program lojalnościowy, które wynagradzają każdą minutę spędzoną na platformie.
Dlaczego Warto Wybrać Slottica Kasyno?
1. Bogata Biblioteka Gier:
Slottica Kasyno zapewnia dostęp do tysięcy tytułów od wiodących dostawców, takich jak NetEnt, Play’n GO czy Pragmatic Play. Niezależnie od tego, czy wolisz klasyczne automaty owocowe, pełne akcji sloty wideo, czy emocjonujące gry stołowe – każdy znajdzie tutaj coś w sam raz dla siebie.
2. Szybkie i Bezpieczne Wypłaty:
Polscy gracze mogą liczyć na ekspresowe wypłaty oraz szeroki wachlarz metod płatności, m.in. Visa, Mastercard, Skrill, a nawet popularne kryptowaluty. Platforma wykorzystuje zaawansowane zabezpieczenia, dzięki czemu Twoje dane i środki finansowe pozostają w pełni chronione.
3. Atrakcyjne Bonusy:
Na start czeka na Ciebie 200% bonusu od depozytu do 140 PLN, a jeżeli chcesz wypróbować kasyno bez ryzyka – sprawdź dostępny bonus bez depozytu. Nie brakuje również regularnych promocji, turniejów z pulą nagród sięgającą 875 000 PLN oraz loterii z luksusowymi nagrodami, takimi jak smartfony czy smartwatche.
Bonusy i Promocje, Które Cię Zachwycą
Bonus Powitalny: Odbierz aż 200% do 140 PLN za pierwszy depozyt!
Bonusy Bez Depozytu: Korzystaj z najnowszych ofert, by grać bez konieczności wpłaty!
Codzienne i Tygodniowe Turnieje: Rywalizuj z innymi graczami o pule nagród dochodzące nawet do 875 000 PLN!
Loteria i Darmowe Spiny: Zgarniaj regularnie przyznawane darmowe obroty i bierz udział w loteriach z fantastycznymi nagrodami, w tym topowymi smartfonami oraz smartwatchami!
Program VIP: Dołącz do elitarnego programu VIP, by cieszyć się spersonalizowanymi bonusami i priorytetowym wsparciem.
Najpopularniejsze gry w Slottica PL
Candy Monsta – słodka przygoda, w której cukierkowe symbole mogą zamienić się w pokaźne wygrane.
Fire Joker – klasyczny slot o ognistej tematyce, uwielbiany przez polskich fanów gier hazardowych.
Book of Dead – wyrusz do mrocznego świata starożytnego Egiptu, by odkryć skarby ukryte w tajemniczych księgach.
Royal Coins, Big Bass Bonanza oraz Gates of Olympus – różnorodna oferta automatów z fantastycznymi motywami i nowoczesną oprawą graficzną.
Poza imponującą kolekcją slotów, Slottica Kasyno oferuje również klasyczne gry stołowe (Blackjack, Ruletka, Baccarat), kasyno na żywo z prawdziwymi krupierami oraz progresywne jackpoty – gdzie jedna szczęśliwa runda może odmienić Twoje życie.
Nowe gry
Świeżo dodane tytuły w kasynie;
Hity Kasyna
Gry z najwyższym zwrotem i szansą na wygraną;
Sloty Online
Najszerzej reprezentowana kategoria z bogatym wyborem tematów;
Gry stołowe
Gry karciane, jak poker, ruletka, blackjack, bakarat;
Kasyno na Żywo
Gry na żywo z krupierem, jak w prawdziwym kasynie;
Gry z Jackpotami
Automaty z progresywnymi jackpotami;
Różne Gry
Rzadkie gry, jak kości, bingo i inne.
Metody Płatności
Slottica Casino zapewnia polskim graczom szybkie, bezpieczne i wygodne formy transakcji. Niezależnie od tego, czy wolisz karty płatnicze, e-portfele czy kryptowaluty – u nas płatności przebiegają sprawnie i bezproblemowo.
Depozyty: Możesz rozpocząć grę, wpłacając już od 5 PLN (e-portfele) lub 80 PLN (karty kredytowe).
Wypłaty: Zlecenia wypłaty są zazwyczaj realizowane w ciągu 24 godzin, a my nie pobieramy żadnych ukrytych opłat.
Dostępne metody: Visa, Mastercard, Skrill, Bitcoin, ecoPayz, MiFinity i wiele innych – wybierz opcję, która najbardziej Ci odpowiada.
Gry na smartfonach w Slottica Kasyno
Doskonale rozumiemy, że polscy gracze często wolą rozgrywkę na urządzeniach mobilnych – w domu, w podróży czy podczas przerwy w pracy. Dlatego nasza strona jest w pełni responsywna i świetnie dopasowuje się do ekranów smartfonów i tabletów.
Aplikacja na Androida: Stworzyliśmy dedykowaną aplikację, która gwarantuje jeszcze szybszy dostęp do konta, płynniejszą rozgrywkę oraz specjalne promocje dostępne wyłącznie dla użytkowników aplikacji.
Bonus dla użytkowników aplikacji: Już samo pobranie aplikacji może przynieść dodatkowe premie, dzięki którym czeka Was jeszcze więcej frajdy przy ulubionych automatach i grach stołowych.
Bezpieczeństwo i Obsługa Klienta
W Slottica Kasyno dokładamy wszelkich starań, aby zapewnić maksymalny poziom ochrony danych oraz najwyższy standard obsługi użytkowników. Nasz zespół wsparcia jest dostępny 24/7, oferując pomoc w języku polskim przy wszelkich pytaniach związanych z płatnościami, rozgrywką czy kwestiami technicznymi. Dzięki zaawansowanemu szyfrowaniu SSL masz pewność, że Twoje dane osobowe i finansowe pozostają w pełni bezpieczne.
The on-line casino incentive codes get you the best offers inside the us. You need to go after their terms and conditions on the letter, even though, if you are to make sure your on line casino feel is just as enjoyable you could.
In recent times, Jumpman Betting are extremely including focussed on the position video game, a method it’ve pursued keenly to your creation of the fresh ‘Jumpman Harbors’ network. That it circle quickly turned into the home of those harbors and bingo orientated websites and after this one to complete really stands from the above 110 names.
Savaspin is a leading financial platform that offers a wide range of services to its users, including trading, investing, and lending. One of the most important steps in using Savaspin is verifying your account and completing the Know Your Customer (KYC) process. This process ensures that Savaspin complies with all relevant regulations and protects its users from fraud and other risks.
To verify your account and complete KYC with Savaspin, follow these steps:
1. Sign up for an account: The first step in using Savaspin is signing up for an account. To do this, visit the Savaspin website and click on the “Sign Up” button. Fill in your personal information, including your name, email address, and phone number. You will also need to create a password for your account.
2. Verify your email address: After signing up for an account, Savaspin will send you an email to verify your email address. Click on the link in the email to confirm your email address and activate your account.
3. Provide additional information: To complete the KYC process, you will need to provide additional information about yourself, including your address, date of birth, and government-issued identification. This information is used to verify your identity and ensure that you are who you say you are.
4. Upload your documents: To verify your identity, you will need to upload copies of your government-issued identification, such as a driver’s license or passport, as well as proof of address, such as a utility bill or bank statement. Make sure that the documents you provide are clear and legible to ensure a smooth verification process.
5. Wait for verification: Once you have uploaded your documents, Savaspin will review them to verify your identity. This process can take anywhere from a few hours to a few days, depending on the volume of requests. You will receive an email once your account has been verified.
6. Enjoy the benefits of a verified account: Once your account is verified, you will have access to all of the features and services that Savaspin offers, including trading, investing, and lending. You can also enjoy greater security and peace of mind knowing that your identity has been verified.
In conclusion, verifying your account and completing KYC with Savaspin is a straightforward process that Savaspin Bonus helps protect both you and the platform from fraud and other risks. By following the steps outlined above, you can ensure that your account is verified quickly and efficiently, allowing you to enjoy all of the benefits that Savaspin has to offer.
As well as the situation with almost every slot one to actually lived, the real fun plus the most potential awaits on the 100 percent free Spins. Gluey Bandits step 3 Really Desired takes an extremely Lifeless or Real time means thanks to their gameplay.
Начнем с того, что стресс и тревога – это вполне естественные явления, с которыми сталкивается каждый человек в той или иной степени. Однако, каким образом они могут повлиять на принятие мужчинами решения об использовании платных интимных услуг? Давайте разберемся в этом вопросе более подробно.
Стресс и тревога: их воздействие на мужчин
Стресс и тревога, оказывая негативное воздействие на психическое и физическое состояние человека, могут вызвать желание найти способы для расслабления и отвлечения. Многие мужчины в поиске такого «выхода из ситуации» обращаются к платным интимным услугам, воспринимая их как способ получить удовлетворение и эмоциональное разряжение.
Как стресс влияет на эмоциональное состояние мужчин
Стресс вызывает ряд изменений в работе гормональной системы, что может привести к нарушениям эмоционального равновесия. Мужчины, подверженные стрессу, могут испытывать повышенную раздражительность, усталость, апатию, а в некоторых случаях – даже депрессию. Поэтому платные интимные услуги могут восприниматься ими как способ найти временное облегчение и утешение.
Тревога и ее влияние на мужчин
Тревога является еще одним фактором, который может толкнуть мужчин к поиску альтернативных способов расслабления. Чувство постоянного напряжения, беспокойства и неуверенности может привести к желанию найти способ «сбросить пар», и платные интимные услуги могут казаться привлекательным вариантом для этого.
Психологический ebar64.com аспект использования платных интимных услуг
Психологи указывают на то, что у некоторых мужчин использование платных интимных услуг может связываться не только с физическим удовлетворением, но и с поиском эмоциональной близости и поддержки. В условиях стресса и тревоги человек может испытывать недостаток внимания и заботы, и интимные услуги могут быть способом заполнить это эмоциональное пустое место.
Социальные и культурные аспекты
Кроме психологических факторов, влияющих на принятие решения о использовании платных интимных услуг, существуют и социальные и культурные аспекты. Например, в определенных культурах и общественных средах такие услуги могут рассматриваться как общепринятая практика для удовлетворения собственных потребностей.
Психотерапия как альтернатива
Важно отметить, что использование платных интимных услуг в качестве способа расслабления или снятия стресса может быть временным решением проблемы. Более продуктивным и долгосрочным путем для решения психологических проблем, вызванных стрессом и тревогой, может стать обращение к профессиональному психотерапевту или другим специалистам в области психологии.
В завершение можно сказать, что стресс и тревога действительно могут оказать влияние на принятие решения мужчинами об использовании платных интимных услуг. Однако, важно помнить о негативных последствиях такого подхода и обратиться за помощью к специалистам, если стресс и тревога начинают серьезно влиять на психическое состояние. Понимание причин и механизмов своего поведения поможет каждому человеку справиться с трудностями без дополнительных негативных последствий.
This minimal RTP reflects the particular high-risk and high-reward nature of parte games. While your own chances of reaching a jackpot are slim, the potential payout draws many players in. Understanding this balance is usually key when playing lotto online—it’s about the thrill of the opportunity to succeed big, however the odds are more difficult than any other casino games.
To strike the particular right balance among entertainment and strategic gaming using RTP, ensure you pick games with advantageous RTP that minimize risks while increasing rewards.
Once they signal up and meet all requirements, you can earn a $100 casino bonus and a new 100% deposit match of up to $3, 500.
All bonuses and special offers listed on Online casino. Guide are content to the conditions and terms of the individual site offering typically the promotion.
Therefore, this means you shouldn’t anticipate to always acquire back $96. 01 in wins regarding every $100 you bet. You may struck a winning ability and make a profit or encounter the stretch of bad luck and win absolutely nothing. Across all online casino games, you’re likely in order to win money inside slots with the highest RTP. However, actually if a machine has a decrease RTP, it isn’t the only deciding aspect in what kind of money is compensated out. This is usually actually a mixture of the comparative difficulty of the casino game, volatility, residence advantage, and hit frequency will also be essential.” “[newline]Finding the RTP percent of your preferred slot games enables you to create wise decisions relating to when and how much to wager. In most jurisdictions, a casino or perhaps gaming establishment is required legally to display return-to-player proportions for their movie slots publicly.
Understanding Rtp In Gambling
However, you will find a difference inside the RTPs regarding online and land-based casino slot devices. Understanding RTP and how functions can help players choose games to choose in the casino. By selecting games together with high RTP or playing at online casinos using the greatest payouts, players can increase their likelihood of winning and lessen long-term losses. Before playing a new game, you could review the RTP percentage on the regulations page of most on the internet casinos. As mentioned, slots scoring above 94% give you a high payout and your chances of winning are increased. As a new beginner, you would choose something with a high possibility before having your fingers dirty and getting more risks mostbet bd.
To determine RTP, you divide the particular total money repaid to players, divide it by the particular total money gamers have bet, plus then multiply simply by 100 to choose that into a percent.
If a casino offers game titles with a high RTP consistently, this shows that the particular casino understands the need to offer players decent odds of winning in buy to maintain its customer base.
So in case, say, you create 100 £1 wagers on a game with an RTP of 95%, you could expect the return of around £95 in profits.
These platforms often provide a better video gaming experience with increased winning chances.
If players take the really good search, RTP will demonstrate the twist of the house edge. A machine may have a 20% advantage, and the Return-to-Player percentage typically will be 80%. In time, that will machine gets a new 20% revenue, while all players have got a fair possibility of taking back again 80% of all bets. You’ll see that you will find in no way return to gamer percentages over 100%.
Delving Into The Regarding Rtp
A game could spend out much more as compared to its RTP, or it could pay out less. It is important to remember that will gambling is a form of recreation that comes at a cost. The RTP is actually a reminder that – in the particular long run – it’s impossible to be able to beat the house. You must play to be able to your limits, accept the cost, take pleasure in those winners, and walk away any time the time is right.
It’s important to remember of which individual gaming sessions may differ widely credited to the component of chance — thus don’t take it as a guarantee.
Betting instructions often provide posts summarizing all recommended online casinos most suitable now, so end up being sure to get a look.
An RTP in gaming is short for referring to “return to player”, meaning the anticipated payout level of a player’s wagers over time.
You may end up being surprised to discover of which the figure isn’t calculated on the particular great a particular game.
We almost all see the term RTP mentioned at almost all online casinos in addition to games that people enjoy online,” “however, not everyone knows exactly what it implies. It is essential to get a full information of the word before wagering actual money upon any game at online casinos. So, theoretically, a gamer may earn 97. 3% of their money-back if they play European roulette longer enough. In some other words, for each and every $100 you wager on the game, you might be expected to acquire $97. 3 back.
How Can It Be Calculated?
As detailed in this guide, understating the RTP of games can help you make informed selections that match your expectations and rewards. To strike the right balance in between entertainment and proper gaming using RTP, ensure you pick games with advantageous RTP that reduce risks while maximizing rewards. And inside” “a market that thrives on honesty and visibility, RTP remains a new pillar for dependable gaming and informed decision-making on playing platforms. For example, if a casino contains a game together with a different RTP than advertised, that can get inside trouble mostbet app download.
With an average RTP of 95%, slot machines are the almost all popular casino game titles and it’s well worth checking out the particular RTP for any game you’re thinking of playing.
Land-based casinos can be trickier because whilst they sometimes display boards showing typically the RTP of slot machine games, only a few machines follow the same percent that is marketed.
On average, casinos can program since low as 80% to 98% RTP within their slot machines.
Sign until March twenty one and take advantage of the Shamrock Reload Bonus.
Earning money by gambling online isn’t easy, but when spinning slots, forming as many earning combinations as feasible means understanding how this figure works. Hit frequency will be a term often used to describe how often a machine will form a winning combination. For example, suppose a machine has the hit frequency of 7% — this implies that it will get a winning mixture and pay out 7% of the particular time. Roulette and similar derivatives employ slightly more intricate calculations. The probability of winning each and every spin is based on the styles of the tyre. Casinos achieve a new house advantage together with the addition regarding one or additional ‘green’ zero pockets — a single zero in European roulette and a dual zero in United states roulette.” “[newline]Divide the full quantity returned to participants by the total amount wagered.
What Does Rtp Mean?
It will help with controlling expectations and budgeting effectively, as video games with higher RTP tend to offer better long-term results. RTP stands regarding Come back to Player and is a percentage value indicating the money a new casino game is usually expected to pay back to the player within the long expression. This” “value is calculated using the total amount regarding bets placed upon the game to represent the average return a person can expect. It reflects the rewards players may expect from on-line slots or on line casino games. Rewarding in this context means both paying back or getting back to a gamer.
However, almost all players consider a great RTP value associated with 95% or higher relatively good, although values below 90% are typically regarded less favorable. Understanding and applying RTPs to other game titles such as online different roulette games is significantly easier. The RTP is the particular lifeblood of prosperous online casinos because it is their own source of revenue. The payout ratio is usually a profit perimeter that is completely different from the margin over a bookmaker’s odds. However, the bookmakers’ model illustrates well what sort of profit margin is implied. Of course, there are usually plenty of desk games that result in zero decision-making.
Rtp Versus Rng: What’s The?
And one psychological element of RTP will be the thrill of anticipation. When an individual choose games to play based on typically the RTP percentage, a sense of exhilaration accumulates as an individual anticipate certain prospective payouts. Table video games often have a number of the highest RTP prices, the reason they contribute less toward the casino gambling. Figuring out the RTP of scratch cards requires you to know the dimensions of the house edge — which is different from slots. As mentioned earlier, the two are literally the opposite of one another, in addition to combining them will get you 100%. This single number symbolizes the house profit on a Roulette table.
This offers the most correct measure while still replicating what countless Canadian casino participants experience.
Instead, it’s the long-term average determined over thousands (or even millions) regarding gameplays.
However, the return percentages regarding certain casino video games are more difficult to calculate.
Games like Roulette in addition to blackjack have noteworthy RTP differences.
In the case of a slot sport, the provider will consider the quantity” “of paylines as well as the bonuses included in typically the game.
You simply divide the general amount won (or returned) to players on a specific game by typically the total amount gambled with the same players. You may be surprised to learn that will the figure isn’t calculated on the particular history of a particular game. Instead, this is replicated by simply computer algorithms that will spin the fishing reels or play times hundreds and hundreds of times over a short period. This provides the most correct measure while still replicating what a great number of Canadian casino gamers experience.
The Rtp Lowdown – A Vital Tool For The Savvy Betmgm Player
After you obtain to know the dimensions of the basic games and they are provided up with these people, do some research again plus search for more complicated slots with good RTP. Quite besides something else, clearly displayed RTP percentages are a good indication of the casino’s trustworthiness. When we review plus feature a casino game we are always absolute to highlight the RTP in slots. This makes it simple to scan via and see which usually slots games have the best RTP before you move ahead and provide one a go.
The payout percent is most related for online slot machines as you can gamble aside your cash much more quickly with slot games than with many other traditional casino game titles.
Return-to-Player percent controls RNG within just how much it will certainly return to the ball player in the form of winning outlines.
Psychologically, choosing games based on RTP creates exhilaration and anticipation, boosting the overall gaming experience.
If the go line in craps is returning 102%, that does not mean the chop are due in order to go cold.
That is due to the fact there are dozens upon dozens associated with versions of on the internet roulette available. Review the Return in order to Player percentage on the casino’s rules page. RTP manuals potential returns, while RNG determines the particular randomness of each and every outcome.
How Are Different Casino Games Affected By Rtp?
Over time a new VLT or slot machine pays backside players for typically the RTP percentage assigned over a specific system. It can end up being advantageous to each party, players, and houses, and is determined over a long-term schedule. Let’s take the popular NetEnt slot game Starburst because a prime example to explain exactly what is casino RTP. With a positive return portion of 96. 01%, we expect typically the game to pay out $96. 01 for every $100 wagered on typical. However, it’s vital to remember that the return to gamer gambling percentage is based on millions of spins or rounds.
Review the Return in order to Player percentage upon the casino’s regulations page.
For typical slot players, RTP offers you a fairer chance of getting more value for your investment decision.
Whether in a new physical casino or even online casinos, RTP gives slot sport players a chance to get back the funds that they played.
If you’ve ever spun the reels of an online slot device or tried your hand with a digital casino game, you’ve probably find the particular term RTP.
When a person understand how RTP works, the likelihood of your current money going again to you” “by means of wins will furthermore be your benefit.
In fact, Algunas Vegas casinos need to have a number that is 75% or above.
But keep in brain that despite having the above formula, the particular simpleness of establishing the RTP likewise depends on the particular casino game sort. For instance, determining the RTP of table games, such as blackjack, craps, online poker, baccarat, and different roulette games is quite challenging. This is due to the fact the Come back to Participant depends on the bet amount and the strategy associated with players.
Common Myths Regarding The Return-to-player
Our BetMGM editors and creators are sports professionals using a wealth regarding knowledge of the sports industry at all levels. Their insurance coverage includes sports news, previews and estimations, fun facts, in addition to betting. Commissions perform not affect the editorial” “selections and the ratings we deliver to on the internet sportsbooks and online casino operators. BETANDBEAT. apresentando is actually a trusted independent gambling authority built by passionate bettors for passionate bettors. We offer informative contents in the form of free articles, news, guides, digital ebooks, programs, reviews, etc.
It is essential to get a full information of the term before wagering real cash upon any game from online casinos.
So, even if might won a $1,000,000 goldmine from a single spin, the online casino would still technically win since they could have made more money through all the some other players who lost.
And so on, most abundant in improbable case being either zero or 9 successful tickets were marketed.
Higher RTP games are generally far better for players since they give a person a better chance of winning over time.
But let’s be sincere, once we visit a good online casino, exactly what we really would like to do will be win.
You will certainly never find the game with the percentage over 100% as the home always needs to be able to have the advantage – that is how the casino makes the money. If a game title has an RTP of 96% after that this means that you will win typically $96 for every single $100 you devote. In fact, whenever it comes to the online domain name, slots that pay off under 95% are actually considered as low returns.
American Roulette
RTP is an extensive average, and this doesn’t determine exactly how much money you will win or even lose in a single session. However, it is much better to learn games together with a higher RTP, because you will forfeit less money in the long run. Keep in mind that online internet casinos have no control of the RTP of any game. Understanding the RTP associated with online slots video games can also influence your gaming strategy that help you recognize games that match your risk inclination. For instance, a person can choose in order to play slots with high RTP because the majority tend in order to be less unstable, offering small nevertheless frequent payouts.
The parameters might range from the probability regarding each symbol showing on the fishing reel, the paytable and the specific game regulations.
The highest regarding which are for your Playtech games Limitless Blackjack at 99. 54% and Mess Blackjack at 99. 47%, followed by Infinite Blackjack coming from Evolution Gaming in 99. 51%.
A Video Lottery Terminal, or VLT, is a kind of electronic gambling.
Slot games often tumble within the array of 92% to 98%, while table game titles like blackjack plus roulette may have got RTP percentages between 94% and 98%.
Games with increased RTP values are generally better with regard to players, since they provide a greater possibility of winning over typically the long term.
The procedure for calculating RTP is complex plus involves the game’s specific design variables. The parameters would range from the probability associated with each symbol showing up on the fishing reel, the paytable and the specific game guidelines. Additionally, it’s essential to remember that RTP is” “according to a theoretical model, not actual sport outcomes. What is very important to understand here is that this house, in this case, the casino, will certainly always possess a share of the revenue. The RTP is usually put in location to ensure fair play, and RNG is set upward to give just about all players an opportunity to get a winning combination. For most people, this experience is what these people sought in a casino establishment just like what you see in Las Sin city or Macau.
Betmgm On Line Casino Ontario Referral Bonus
Notably, the tolerance (difference among theoretical and real RTP) will be wider each time a limited amount of play has been built up. However, the patience will decrease because the volume of have fun goes up. With his extensive knowledge of the gambling market and college diploma in law, he knows what a person should look away for think about the particular best casino or even sportsbook. However, the end result of the first toss doesn’t impact the probability regarding heads or tails on subsequent throws.
Gambling without understanding RTP (Return-to-Player) is a recipe for failing, especially with punters who invest lots of money into a sport.
The” “computation takes into accounts all bets manufactured by players plus the corresponding payouts.
RTP, also known as slots odds, is a percentage value addressing the total amount of bets paid back to gamers over an offered amount of moment.
Anything below 90% when it arrives to online slot machine games should be prevented.
The graphic above shows a new few versions associated with BlackJack games in NetBet Casino.
Casinos are usually required to end up being transparent and supply the RTP regarding each game so that you know what in order to expect before adding your funds. According to the Gaming Commission, RTPs have to be clearly posted upon every machine. It may be displayed on a game assist screen or inside some other display screen area, but this won’t be” “difficult to get. The cool portion about blackjack is that you can use methods to enhance your chances.
Can You In Fact Win Money Upon Online Slots
The RTP rate associated with a slot sport is not simply an arbitrary amount; it is carefully determined by the combination of different factors. Understanding these kinds of factors can supply valuable insights directly into your gameplay strategy. It’s crucial to bear in mind, however, that RTP is a long-term measure. It does not guarantee any instant wins or determine the size associated with the wins. Slot games are innately games of chance, with each rewrite independent of the particular previous or next one. Seasoned plus professional casino participants need to understand the particular technical jargon regarding gambling.
As such calculating return to player (RTP), as a single deciding point for selecting an on line casino is just not as very clear cut as that may seem.
Once you know the, you may end up being prepared to get additional risks and play the game titles with a much lower RTP percentage.
Bet365 has 1 of the greatest casinos out there and things are getting better with its latest promotion.
In chop games, it truly is identified by the quantity of rolls needed to produce a new winning outcome.
Let’s take Starburst, one of the particular most popular on the internet slots ever, because an example. Starburst has eight RTP options, ranging through 90. 05% to be able to 99. 06%. Which version of Starburst you get depends on which version typically the casino decides to get and how nice they want to be in order to players.
What Will Be Rtp (return To Be Able To Player)?
Essentially, it offers you an thought of how very much you could expect to win in the long function. In simple conditions, it is a way of measuring the total wager that the player may expect to earn back over a prolonged period associated with gameplay. If the game boasts a great RTP of 95%, then — in theory, at least — a player could anticipate to get $95 back from every $100″ “wagered. A Video Lottery Terminal, or VLT, is a type of electronic gaming. Each machine is connected to a centralized computer program that monitors the particular gameplay and collects the casino’s show from the revenue.
Each gamer should weigh upwards the risks of a new casino game just before playing and stay ready to lose the bets they help to make.
The opportunity to be able to win a jackpot may be the same for every spin of which you play.
Of course, this does not really mean that a game title is “better” if it has a higher RTP.
This does not mean that online slot machines with a lower RTP should not really be played, however, amateur players should consider games having a high percentage.
For game titles with relatively insignificant strategy (like About three Card Poker), the RTP is generally only slightly fewer than 100% – (House Edge). For extremely complicated game titles, like Blackjack Switch or Super Enjoyment 21, the RTP can be a number of percentage points beneath the theoretical value. These numbers are set from the game developers and therefore are centered on mathematical formulas. We also advise checking the RTP of any sport before you start playing.
Myth #1: A High Return-to-player Guarantees Payouts
Suppose a person is playing roulette and is also betting $1 straight up on red-7. Now suppose a player wagers that similar $1, 000, 500 one wager in a time on “even” instead associated with red-7. In this specific case, because earning and losing occur about equally usually, the RTP may quickly converge to be able to 94. 74% for this player. The return to player percentage shows gamblers how generous a casino video game is.
Generally, the RTP for slot machine games in on-line casinos ranges through 92% to 98%, while table games are about 99%.
Land-based slot device RTPs are the little harder to get, and they are also generally reduced compared to slots in online gambling dens.
In today’s online gambling planet, the standard RTP is around 96%, therefore anything near or even above that quantity is ideal with regard to beginners.
There are only nine winning tickets inside the lots of 1 million tickets, thus somewhere between zero and 9 in the winning tickets is going to be sold.
Low variance implies that a machine pays out small victories more frequently, in addition to high variance implies larger prizes that will are paid for significantly less frequently. In several cases, casinos can’t give reliable RTP estimates for games because they can’t account for the talent level of each and every gamer. The cards, dice, reels and tires don’t know what assumptive RTP they may be supposed to be in addition to try and get presently there. If the go line in craps is returning 102%, that does not mean the chop are due to go cold. The actual RTP will be not an indicator of what’s” “bound to happen in the future to make the difference.
What Payout Percentages May You Expect And Exactly What Is The House Edge?
The larger the house edge, typically the more chances are within the casino’s favour. It’s simple mathematics — subtract typically the RTP from 100% and you get the house edge. Ever scrolled through an online casino site, loking for the jackpot slots, pondering over what game to perform next? You may have spotted anything called the RTP — a three-letter acronym that may appear complicated but is central to understanding your gaming experience. This blog post unpacks the concept of Return to Participant, or RTP, to be able to illuminate its importance in your gameplay and potential income. Land-based slot machine RTPs are the little harder to look for, and they are also generally lower compared to slot machine games in online casinos.
In comparison, Player W might wager five thousand Canadian dollars and only earn two thousand Canadian dollars in come back.
If you love participating in online slots or are new to typically the game, then you may have got heard of the particular term RTP in addition to wondered what this means to you as a player.
This fact becomes clearer when” “we delve into the particular intricate dynamics of casino gameplay and the strategic choices players will make.
As a result, typically the RTP has turn out to be three little characters that many slot players look regarding when searching for” “a slot machine. However, if the RTP will be too small , and consumers will switch providers – this is why the particular RTP is now even more and more nice recently. All slot games possess their own particular return-to-player percentage in most casino. On average, internet casinos can program since low as 80% to 98% RTP inside their slot machines.