PageSourceSearch

https://asavto-exeed.ru/local/templates/exeed/js/libs/nouislider.js

js asavto-exeed.ru collected 2026-09-24 12:07:08 UTC 86,858 bytes, 2,306 lines download raw bytes

vendor: 5,696 bytes, lines 1-173
1(function(factory) {
2    if (typeof define === "function" && define.amd) {
3        // AMD. Register as an anonymous module.
4        define([], factory);
5    } else if (typeof exports === "object") {
6        // Node/CommonJS
7        module.exports = factory();
8    } else {
9        // Browser globals
10        window.noUiSlider = factory();
11    }
12})(function() {
13    "use strict";
14
15    var VERSION = "12.1.0";
16
17    function isValidFormatter(entry) {
18        return typeof entry === "object" && typeof entry.to === "function" && typeof entry.from === "function";
19    }
20
21    function removeElement(el) {
22        el.parentElement.removeChild(el);
23    }
24
25    function isSet(value) {
26        return value !== null && value !== undefined;
27    }
28
29    // Bindable version
30    function preventDefault(e) {
31        e.preventDefault();
32    }
33
34    // Removes duplicates from an array.
35    function unique(array) {
36        return array.filter(function(a) {
37            return !this[a] ? (this[a] = true) : false;
38        }, {});
39    }
40
41    // Round a value to the closest 'to'.
42    function closest(value, to) {
43        return Math.round(value / to) * to;
44    }
45
46    // Current position of an element relative to the document.
47    function offset(elem, orientation) {
48        var rect = elem.getBoundingClientRect();
49        var doc = elem.ownerDocument;
50        var docElem = doc.documentElement;
51        var pageOffset = getPageOffset(doc);
52
53        // getBoundingClientRect contains left scroll in Chrome on Android.
54        // I haven't found a feature detection that proves this. Worst case
55        // scenario on mis-match: the 'tap' feature on horizontal sliders breaks.
56        if (/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)) {
57            pageOffset.x = 0;
58        }
59
60        return orientation
61            ? rect.top + pageOffset.y - docElem.clientTop
62            : rect.left + pageOffset.x - docElem.clientLeft;
63    }
64
65    // Checks whether a value is numerical.
66    function isNumeric(a) {
67        return typeof a === "number" && !isNaN(a) && isFinite(a);
68    }
69
70    // Sets a class and removes it after [duration] ms.
71    function addClassFor(element, className, duration) {
72        if (duration > 0) {
73            addClass(element, className);
74            setTimeout(function() {
75                removeClass(element, className);
76            }, duration);
77        }
78    }
79
80    // Limits a value to 0 - 100
81    function limit(a) {
82        return Math.max(Math.min(a, 100), 0);
83    }
84
85    // Wraps a variable as an array, if it isn't one yet.
86    // Note that an input array is returned by reference!
87    function asArray(a) {
88        return Array.isArray(a) ? a : [a];
89    }
90
91    // Counts decimals
92    function countDecimals(numStr) {
93        numStr = String(numStr);
94        var pieces = numStr.split(".");
95        return pieces.length > 1 ? pieces[1].length : 0;
96    }
97
98    // http://youmightnotneedjquery.com/#add_class
99    function addClass(el, className) {
100        if (el.classList) {
101            el.classList.add(className);
102        } else {
103            el.className += " " + className;
104        }
105    }
106
107    // http://youmightnotneedjquery.com/#remove_class
108    function removeClass(el, className) {
109        if (el.classList) {
110            el.classList.remove(className);
111        } else {
112            el.className = el.className.replace(
113                new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi"),
114                " "
115            );
116        }
117    }
118
119    // https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/
120    function hasClass(el, className) {
121        return el.classList
122            ? el.classList.contains(className)
123            : new RegExp("\\b" + className + "\\b").test(el.className);
124    }
125
126    // https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
127    function getPageOffset(doc) {
128        var supportPageOffset = window.pageXOffset !== undefined;
129        var isCSS1Compat = (doc.compatMode || "") === "CSS1Compat";
130        var x = supportPageOffset
131            ? window.pageXOffset
132            : isCSS1Compat
133                ? doc.documentElement.scrollLeft
134                : doc.body.scrollLeft;
135        var y = supportPageOffset
136            ? window.pageYOffset
137            : isCSS1Compat
138                ? doc.documentElement.scrollTop
139                : doc.body.scrollTop;
140
141        return {
142            x: x,
143            y: y
144        };
145    }
146
147    // we provide a function to compute constants instead
148    // of accessing window.* as soon as the module needs it
149    // so that we do not compute anything if not needed
150    function getActions() {
151        // Determine the events to bind. IE11 implements pointerEvents without
152        // a prefix, which breaks compatibility with the IE10 implementation.
153        return window.navigator.pointerEnabled
154            ? {
155                  start: "pointerdown",
156                  move: "pointermove",
157                  end: "pointerup"
158              }
159            : window.navigator.msPointerEnabled
160                ? {
161                      start: "MSPointerDown",
162                      move: "MSPointerMove",
163                      end: "MSPointerUp"
164                  }
165                : {
166                      start: "mousedown touchstart",
167                      move: "mousemove touchmove",
168                      end: "mouseup touchend"
169                  };
170    }
171
172    // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
173    // Issue #785
174    function getSupportsPassive() {
175        var supportsPassive = false;
176
177        /* eslint-disable */
178        try {
179            var opts = Object.defineProperty({}, "passive", {
180                get: function() {
181                    supportsPassive = true;
182                }
183            });
184
185            window.addEventListener("test", null, opts);
186        } catch (e) {}
187        /* eslint-enable */
188
189        return supportsPassive;
190    }
191
192    function getSupportsTouchActionNone() {
193        return window.CSS && CSS.supports && CSS.supports("touch-action", "none");
194    }
195
196    // Value calculation
197
198    // Determine the size of a sub-range in relation to a full range.
199    function subRangeRatio(pa, pb) {
200        return 100 / (pb - pa);
201    }
202
203    // (percentage) How many percent is this value of this range?
204    function fromPercentage(range, value) {
205        return (value * 100) / (range[1] - range[0]);
206    }
207
208    // (percentage) Where is this value on this range?
209    function toPercentage(range, value) {
210        return fromPercentage(range, range[0] < 0 ? value + Math.abs(range[0]) : value - range[0]);
211    }
212
213    // (value) How much is this percentage on this range?
214    function isPercentage(range, value) {
215        return (value * (range[1] - range[0])) / 100 + range[0];
216    }
217
218    // Range conversion
219
220    function getJ(value, arr) {
221        var j = 1;
222
223        while (value >= arr[j]) {
224            j += 1;
225        }
226
227        return j;
228    }
229
230    // (percentage) Input a value, find where, on a scale of 0-100, it applies.
231    function toStepping(xVal, xPct, value) {
232        if (value >= xVal.slice(-1)[0]) {
233            return 100;
234        }
235
236        var j = getJ(value, xVal);
237        var va = xVal[j - 1];
238        var vb = xVal[j];
239        var pa = xPct[j - 1];
240        var pb = xPct[j];
241
242        return pa + toPercentage([va, vb], value) / subRangeRatio(pa, pb);
243    }
244
245    // (value) Input a percentage, find where it is on the specified range.
246    function fromStepping(xVal, xPct, value) {
247        // There is no range group that fits 100
248        if (value >= 100) {
249            return xVal.slice(-1)[0];
250        }
251
252        var j = getJ(value, xPct);
253        var va = xVal[j - 1];
254        var vb = xVal[j];
255        var pa = xPct[j - 1];
256        var pb = xPct[j];
257
258        return isPercentage([va, vb], (value - pa) * subRangeRatio(pa, pb));
259    }
260
261    // (percentage) Get the step that applies at a certain value.
262    function getStep(xPct, xSteps, snap, value) {
263        if (value === 100) {
264            return value;
265        }
266
267        var j = getJ(value, xPct);
268        var a = xPct[j - 1];
269        var b = xPct[j];
270
271        // If 'snap' is set, steps are used as fixed points on the slider.
272        if (snap) {
273            // Find the closest position, a or b.
274            if (value - a > (b - a) / 2) {
275                return b;
276            }
277
278            return a;
279        }
280
281        if (!xSteps[j - 1]) {
282            return value;
283        }
284
285        return xPct[j - 1] + closest(value - xPct[j - 1], xSteps[j - 1]);
286    }
287
288    // Entry parsing
289
290    function handleEntryPoint(index, value, that) {
291        var percentage;
292
293        // Wrap numerical input in an array.
294        if (typeof value === "number") {
295            value = [value];
296        }
297
298        // Reject any invalid input, by testing whether value is an array.
299        if (!Array.isArray(value)) {
300            throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value.");
301        }
302
303        // Covert min/max syntax to 0 and 100.
304        if (index === "min") {
305            percentage = 0;
306        } else if (index === "max") {
307            percentage = 100;
308        } else {
309            percentage = parseFloat(index);
310        }
311
312        // Check for correct input.
313        if (!isNumeric(percentage) || !isNumeric(value[0])) {
314            throw new Error("noUiSlider (" + VERSION + "): 'range' value isn't numeric.");
315        }
316
317        // Store values.
318        that.xPct.push(percentage);
319        that.xVal.push(value[0]);
320
321        // NaN will evaluate to false too, but to keep
322        // logging clear, set step explicitly. Make sure
323        // not to override the 'step' setting with false.
324        if (!percentage) {
325            if (!isNaN(value[1])) {
326                that.xSteps[0] = value[1];
327            }
328        } else {
329            that.xSteps.push(isNaN(value[1]) ? false : value[1]);
330        }
331
332        that.xHighestCompleteStep.push(0);
333    }
334
335    function handleStepPoint(i, n, that) {
336        // Ignore 'false' stepping.
337        if (!n) {
338            return true;
339        }
340
341        // Factor to range ratio
342        that.xSteps[i] =
343            fromPercentage([that.xVal[i], that.xVal[i + 1]], n) / subRangeRatio(that.xPct[i], that.xPct[i + 1]);
344
345        var totalSteps = (that.xVal[i + 1] - that.xVal[i]) / that.xNumSteps[i];
346        var highestStep = Math.ceil(Number(totalSteps.toFixed(3)) - 1);
347        var step = that.xVal[i] + that.xNumSteps[i] * highestStep;
348
349        that.xHighestCompleteStep[i] = step;
350    }
351
352    // Interface
353
354    function Spectrum(entry, snap, singleStep) {
355        this.xPct = [];
356        this.xVal = [];
357        this.xSteps = [singleStep || false];
358        this.xNumSteps = [false];
359        this.xHighestCompleteStep = [];
360
361        this.snap = snap;
362
363        var index;
364        var ordered = []; // [0, 'min'], [1, '50%'], [2, 'max']
365
366        // Map the object keys to an array.
367        for (index in entry) {
368            if (entry.hasOwnProperty(index)) {
369                ordered.push([entry[index], index]);
370            }
371        }
372
373        // Sort all entries by value (numeric sort).
374        if (ordered.length && typeof ordered[0][0] === "object") {
375            ordered.sort(function(a, b) {
376                return a[0][0] - b[0][0];
377            });
378        } else {
379            ordered.sort(function(a, b) {
380                return a[0] - b[0];
381            });
382        }
383
384        // Convert all entries to subranges.
385        for (index = 0; index < ordered.length; index++) {
386            handleEntryPoint(ordered[index][1], ordered[index][0], this);
387        }
388
389        // Store the actual step values.
390        // xSteps is sorted in the same order as xPct and xVal.
391        this.xNumSteps = this.xSteps.slice(0);
392
393        // Convert all numeric steps to the percentage of the subrange they represent.
394        for (index = 0; index < this.xNumSteps.length; index++) {
395            handleStepPoint(index, this.xNumSteps[index], this);
396        }
397    }
398
399    Spectrum.prototype.getMargin = function(value) {
400        var step = this.xNumSteps[0];
401
402        if (step && (value / step) % 1 !== 0) {
403            throw new Error("noUiSlider (" + VERSION + "): 'limit', 'margin' and 'padding' must be divisible by step.");
404        }
405
406        return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false;
407    };
408
409    Spectrum.prototype.toStepping = function(value) {
410        value = toStepping(this.xVal, this.xPct, value);
411
412        return value;
413    };
414
415    Spectrum.prototype.fromStepping = function(value) {
416        return fromStepping(this.xVal, this.xPct, value);
417    };
418
419    Spectrum.prototype.getStep = function(value) {
420        value = getStep(this.xPct, this.xSteps, this.snap, value);
421
422        return value;
423    };
424
425    Spectrum.prototype.getNearbySteps = function(value) {
426        var j = getJ(value, this.xPct);
427
428        return {
429            stepBefore: {
430                startValue: this.xVal[j - 2],
431                step: this.xNumSteps[j - 2],
432                highestStep: this.xHighestCompleteStep[j - 2]
433            },
434            thisStep: {
435                startValue: this.xVal[j - 1],
436                step: this.xNumSteps[j - 1],
437                highestStep: this.xHighestCompleteStep[j - 1]
438            },
439            stepAfter: {
440                startValue: this.xVal[j],
441                step: this.xNumSteps[j],
442                highestStep: this.xHighestCompleteStep[j]
443            }
444        };
445    };
446
447    Spectrum.prototype.countStepDecimals = function() {
448        var stepDecimals = this.xNumSteps.map(countDecimals);
449        return Math.max.apply(null, stepDecimals);
450    };
451
452    // Outside testing
453    Spectrum.prototype.convert = function(value) {
454        return this.getStep(this.toStepping(value));
455    };
456
457    /*	Every input option is tested and parsed. This'll prevent
458        endless validation in internal methods. These tests are
459        structured with an item for every option available. An
460        option can be marked as required by setting the 'r' flag.
461        The testing function is provided with three arguments:
462            - The provided value for the option;
463            - A reference to the options object;
464            - The name for the option;
465
466        The testing function returns false when an error is detected,
467        or true when everything is OK. It can also modify the option
468        object, to make sure all values can be correctly looped elsewhere. */
469
470    var defaultFormatter = {
471        to: function(value) {
472            return value !== undefined && value.toFixed(2);
473        },
474        from: Number
475    };
476
477    function validateFormat(entry) {
478        // Any object with a to and from method is supported.
479        if (isValidFormatter(entry)) {
480            return true;
481        }
482
483        throw new Error("noUiSlider (" + VERSION + "): 'format' requires 'to' and 'from' methods.");
484    }
485
486    function testStep(parsed, entry) {
487        if (!isNumeric(entry)) {
488            throw new Error("noUiSlider (" + VERSION + "): 'step' is not numeric.");
489        }
490
491        // The step option can still be used to set stepping
492        // for linear sliders. Overwritten if set in 'range'.
493        parsed.singleStep = entry;
494    }
495
496    function testRange(parsed, entry) {
497        // Filter incorrect input.
498        if (typeof entry !== "object" || Array.isArray(entry)) {
499            throw new Error("noUiSlider (" + VERSION + "): 'range' is not an object.");
500        }
501
502        // Catch missing start or end.
503        if (entry.min === undefined || entry.max === undefined) {
504            throw new Error("noUiSlider (" + VERSION + "): Missing 'min' or 'max' in 'range'.");
505        }
506
507        // Catch equal start or end.
508        if (entry.min === entry.max) {
509            throw new Error("noUiSlider (" + VERSION + "): 'range' 'min' and 'max' cannot be equal.");
510        }
511
512        parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.singleStep);
513    }
514
515    function testStart(parsed, entry) {
516        entry = asArray(entry);
517
518        // Validate input. Values aren't tested, as the public .val method
519        // will always provide a valid location.
520        if (!Array.isArray(entry) || !entry.length) {
521            throw new Error("noUiSlider (" + VERSION + "): 'start' option is incorrect.");
522        }
523
524        // Store the number of handles.
525        parsed.handles = entry.length;
526
527        // When the slider is initialized, the .val method will
528        // be called with the start options.
529        parsed.start = entry;
530    }
531
532    function testSnap(parsed, entry) {
533        // Enforce 100% stepping within subranges.
534        parsed.snap = entry;
535
536        if (typeof entry !== "boolean") {
537            throw new Error("noUiSlider (" + VERSION + "): 'snap' option must be a boolean.");
538        }
539    }
540
541    function testAnimate(parsed, entry) {
542        // Enforce 100% stepping within subranges.
543        parsed.animate = entry;
544
545        if (typeof entry !== "boolean") {
546            throw new Error("noUiSlider (" + VERSION + "): 'animate' option must be a boolean.");
547        }
548    }
549
550    function testAnimationDuration(parsed, entry) {
551        parsed.animationDuration = entry;
552
553        if (typeof entry !== "number") {
554            throw new Error("noUiSlider (" + VERSION + "): 'animationDuration' option must be a number.");
555        }
556    }
557
558    function testConnect(parsed, entry) {
559        var connect = [false];
560        var i;
561
562        // Map legacy options
563        if (entry === "lower") {
564            entry = [true, false];
565        } else if (entry === "upper") {
566            entry = [false, true];
567        }
568
569        // Handle boolean options
570        if (entry === true || entry === false) {
571            for (i = 1; i < parsed.handles; i++) {
572                connect.push(entry);
573            }
574
575            connect.push(false);
576        }
577
578        // Reject invalid input
579        else if (!Array.isArray(entry) || !entry.length || entry.length !== parsed.handles + 1) {
580            throw new Error("noUiSlider (" + VERSION + "): 'connect' option doesn't match handle count.");
581        } else {
582            connect = entry;
583        }
584
585        parsed.connect = connect;
586    }
587
588    function testOrientation(parsed, entry) {
589        // Set orientation to an a numerical value for easy
590        // array selection.
591        switch (entry) {
592            case "horizontal":
593                parsed.ort = 0;
594                break;
595            case "vertical":
596                parsed.ort = 1;
597                break;
598            default:
599                throw new Error("noUiSlider (" + VERSION + "): 'orientation' option is invalid.");
600        }
601    }
602
603    function testMargin(parsed, entry) {
604        if (!isNumeric(entry)) {
605            throw new Error("noUiSlider (" + VERSION + "): 'margin' option must be numeric.");
606        }
607
608        // Issue #582
609        if (entry === 0) {
610            return;
611        }
612
613        parsed.margin = parsed.spectrum.getMargin(entry);
614
615        if (!parsed.margin) {
616            throw new Error("noUiSlider (" + VERSION + "): 'margin' option is only supported on linear sliders.");
617        }
618    }
619
620    function testLimit(parsed, entry) {
621        if (!isNumeric(entry)) {
622            throw new Error("noUiSlider (" + VERSION + "): 'limit' option must be numeric.");
623        }
624
625        parsed.limit = parsed.spectrum.getMargin(entry);
626
627        if (!parsed.limit || parsed.handles < 2) {
628            throw new Error(
629                "noUiSlider (" +
630                    VERSION +
631                    "): 'limit' option is only supported on linear sliders with 2 or more handles."
632            );
633        }
634    }
635
636    function testPadding(parsed, entry) {
637        if (!isNumeric(entry) && !Array.isArray(entry)) {
638            throw new Error(
639                "noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."
640            );
641        }
642
643        if (Array.isArray(entry) && !(entry.length === 2 || isNumeric(entry[0]) || isNumeric(entry[1]))) {
644            throw new Error(
645                "noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."
646            );
647        }
648
649        if (entry === 0) {
650            return;
651        }
652
653        if (!Array.isArray(entry)) {
654            entry = [entry, entry];
655        }
656
657        // 'getMargin' returns false for invalid values.
658        parsed.padding = [parsed.spectrum.getMargin(entry[0]), parsed.spectrum.getMargin(entry[1])];
659
660        if (parsed.padding[0] === false || parsed.padding[1] === false) {
661            throw new Error("noUiSlider (" + VERSION + "): 'padding' option is only supported on linear sliders.");
662        }
663
664        if (parsed.padding[0] < 0 || parsed.padding[1] < 0) {
665            throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s).");
666        }
667
668        if (parsed.padding[0] + parsed.padding[1] >= 100) {
669            throw new Error("noUiSlider (" + VERSION + "): 'padding' option must not exceed 100% of the range.");
670        }
671    }
672
673    function testDirection(parsed, entry) {
674        // Set direction as a numerical value for easy parsing.
675        // Invert connection for RTL sliders, so that the proper
676        // handles get the connect/background classes.
677        switch (entry) {
678            case "ltr":
679                parsed.dir = 0;
680                break;
681            case "rtl":
682                parsed.dir = 1;
683                break;
684            default:
685                throw new Error("noUiSlider (" + VERSION + "): 'direction' option was not recognized.");
686        }
687    }
688
689    function testBehaviour(parsed, entry) {
690        // Make sure the input is a string.
691        if (typeof entry !== "string") {
692            throw new Error("noUiSlider (" + VERSION + "): 'behaviour' must be a string containing options.");
693        }
694
695        // Check if the string contains any keywords.
696        // None are required.
697        var tap = entry.indexOf("tap") >= 0;
698        var drag = entry.indexOf("drag") >= 0;
699        var fixed = entry.indexOf("fixed") >= 0;
700        var snap = entry.indexOf("snap") >= 0;
701        var hover = entry.indexOf("hover") >= 0;
702        var unconstrained = entry.indexOf("unconstrained") >= 0;
703
704        if (fixed) {
705            if (parsed.handles !== 2) {
706                throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles");
707            }
708
709            // Use margin to enforce fixed state
710            testMargin(parsed, parsed.start[1] - parsed.start[0]);
711        }
712
713        if (unconstrained && (parsed.margin || parsed.limit)) {
714            throw new Error(
715                "noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit"
716            );
717        }
718
719        parsed.events = {
720            tap: tap || snap,
721            drag: drag,
722            fixed: fixed,
723            snap: snap,
724            hover: hover,
725            unconstrained: unconstrained
726        };
727    }
728
729    function testTooltips(parsed, entry) {
730        if (entry === false) {
731            return;
732        }
733
734        if (entry === true) {
735            parsed.tooltips = [];
736
737            for (var i = 0; i < parsed.handles; i++) {
738                parsed.tooltips.push(true);
739            }
740        } else {
741            parsed.tooltips = asArray(entry);
742
743            if (parsed.tooltips.length !== parsed.handles) {
744                throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles.");
745            }
746
747            parsed.tooltips.forEach(function(formatter) {
748                if (
749                    typeof formatter !== "boolean" &&
750                    (typeof formatter !== "object" || typeof formatter.to !== "function")
751                ) {
752                    throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'.");
753                }
754            });
755        }
756    }
757
758    function testAriaFormat(parsed, entry) {
759        parsed.ariaFormat = entry;
760        validateFormat(entry);
761    }
762
763    function testFormat(parsed, entry) {
764        parsed.format = entry;
765        validateFormat(entry);
766    }
767
768    function testKeyboardSupport(parsed, entry) {
769        parsed.keyboardSupport = entry;
770
771        if (typeof entry !== "boolean") {
772            throw new Error("noUiSlider (" + VERSION + "): 'keyboardSupport' option must be a boolean.");
773        }
774    }
775
776    function testDocumentElement(parsed, entry) {
777        // This is an advanced option. Passed values are used without validation.
778        parsed.documentElement = entry;
779    }
780
781    function testCssPrefix(parsed, entry) {
782        if (typeof entry !== "string" && entry !== false) {
783            throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`.");
784        }
785
786        parsed.cssPrefix = entry;
787    }
788
789    function testCssClasses(parsed, entry) {
790        if (typeof entry !== "object") {
791            throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object.");
792        }
793
794        if (typeof parsed.cssPrefix === "string") {
795            parsed.cssClasses = {};
796
797            for (var key in entry) {
798                if (!entry.hasOwnProperty(key)) {
799                    continue;
800                }
801
802                parsed.cssClasses[key] = parsed.cssPrefix + entry[key];
803            }
804        } else {
805            parsed.cssClasses = entry;
806        }
807    }
808
809    // Test all developer settings and parse to assumption-safe values.
810    function testOptions(options) {
811        // To prove a fix for #537, freeze options here.
812        // If the object is modified, an error will be thrown.
813        // Object.freeze(options);
814
815        var parsed = {
816            margin: 0,
817            limit: 0,
818            padding: 0,
819            animate: true,
820            animationDuration: 300,
821            ariaFormat: defaultFormatter,
822            format: defaultFormatter
823        };
824
825        // Tests are executed in the order they are presented here.
826        var tests = {
827            step: { r: false, t: testStep },
828            start: { r: true, t: testStart },
829            connect: { r: true, t: testConnect },
830            direction: { r: true, t: testDirection },
831            snap: { r: false, t: testSnap },
832            animate: { r: false, t: testAnimate },
833            animationDuration: { r: false, t: testAnimationDuration },
834            range: { r: true, t: testRange },
835            orientation: { r: false, t: testOrientation },
836            margin: { r: false, t: testMargin },
837            limit: { r: false, t: testLimit },
838            padding: { r: false, t: testPadding },
839            behaviour: { r: true, t: testBehaviour },
840            ariaFormat: { r: false, t: testAriaFormat },
841            format: { r: false, t: testFormat },
842            tooltips: { r: false, t: testTooltips },
843            keyboardSupport: { r: true, t: testKeyboardSupport },
844            documentElement: { r: false, t: testDocumentElement },
845            cssPrefix: { r: true, t: testCssPrefix },
846            cssClasses: { r: true, t: testCssClasses }
847        };
848
849        var defaults = {
850            connect: false,
851            direction: "ltr",
852            behaviour: "tap",
853            orientation: "horizontal",
854            keyboardSupport: true,
855            cssPrefix: "noUi-",
856            cssClasses: {
857                target: "target",
858                base: "base",
859                origin: "origin",
860                handle: "handle",
861                handleLower: "handle-lower",
862                handleUpper: "handle-upper",
863                horizontal: "horizontal",
864                vertical: "vertical",
865                background: "background",
866                connect: "connect",
867                connects: "connects",
868                ltr: "ltr",
869                rtl: "rtl",
870                draggable: "draggable",
871                drag: "state-drag",
872                tap: "state-tap",
873                active: "active",
874                tooltip: "tooltip",
875                pips: "pips",
876                pipsHorizontal: "pips-horizontal",
877                pipsVertical: "pips-vertical",
878                marker: "marker",
879                markerHorizontal: "marker-horizontal",
880                markerVertical: "marker-vertical",
881                markerNormal: "marker-normal",
882                markerLarge: "marker-large",
883                markerSub: "marker-sub",
884                value: "value",
885                valueHorizontal: "value-horizontal",
886                valueVertical: "value-vertical",
887                valueNormal: "value-normal",
888                valueLarge: "value-large",
889                valueSub: "value-sub"
890            }
891        };
892
893        // AriaFormat defaults to regular format, if any.
894        if (options.format && !options.ariaFormat) {
895            options.ariaFormat = options.format;
896        }
897
898        // Run all options through a testing mechanism to ensure correct
899        // input. It should be noted that options might get modified to
900        // be handled properly. E.g. wrapping integers in arrays.
901        Object.keys(tests).forEach(function(name) {
902            // If the option isn't set, but it is required, throw an error.
903            if (!isSet(options[name]) && defaults[name] === undefined) {
904                if (tests[name].r) {
905                    throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required.");
906                }
907
908                return true;
909            }
910
911            tests[name].t(parsed, !isSet(options[name]) ? defaults[name] : options[name]);
912        });
913
914        // Forward pips options
915        parsed.pips = options.pips;
916
917        // All recent browsers accept unprefixed transform.
918        // We need -ms- for IE9 and -webkit- for older Android;
919        // Assume use of -webkit- if unprefixed and -ms- are not supported.
920        // https://caniuse.com/#feat=transforms2d
921        var d = document.createElement("div");
922        var msPrefix = d.style.msTransform !== undefined;
923        var noPrefix = d.style.transform !== undefined;
924
925        parsed.transformRule = noPrefix ? "transform" : msPrefix ? "msTransform" : "webkitTransform";
926
927        // Pips don't move, so we can place them using left/top.
928        var styles = [["left", "top"], ["right", "bottom"]];
929
930        parsed.style = styles[parsed.dir][parsed.ort];
931
932        return parsed;
933    }
934
935    function scope(target, options, originalOptions) {
936        var actions = getActions();
937        var supportsTouchActionNone = getSupportsTouchActionNone();
938        var supportsPassive = supportsTouchActionNone && getSupportsPassive();
939
940        // All variables local to 'scope' are prefixed with 'scope_'
941        var scope_Target = target;
942        var scope_Locations = [];
943        var scope_Base;
944        var scope_Handles;
945        var scope_HandleNumbers = [];
946        var scope_ActiveHandlesCount = 0;
947        var scope_Connects;
948        var scope_Spectrum = options.spectrum;
949        var scope_Values = [];
950        var scope_Events = {};
951        var scope_Self;
952        var scope_Pips;
953        var scope_Document = target.ownerDocument;
954        var scope_DocumentElement = options.documentElement || scope_Document.documentElement;
955        var scope_Body = scope_Document.body;
956
957        // Pips constants
958        var PIPS_NONE = -1;
959        var PIPS_NO_VALUE = 0;
960        var PIPS_LARGE_VALUE = 1;
961        var PIPS_SMALL_VALUE = 2;
962
963        // For horizontal sliders in standard ltr documents,
964        // make .noUi-origin overflow to the left so the document doesn't scroll.
965        var scope_DirOffset = scope_Document.dir === "rtl" || options.ort === 1 ? 0 : 100;
966
967        // Creates a node, adds it to target, returns the new node.
968        function addNodeTo(addTarget, className) {
969            var div = scope_Document.createElement("div");
970
971            if (className) {
972                addClass(div, className);
973            }
974
975            addTarget.appendChild(div);
976
977            return div;
978        }
979
980        // Append a origin to the base
981        function addOrigin(base, handleNumber) {
982            var origin = addNodeTo(base, options.cssClasses.origin);
983            var handle = addNodeTo(origin, options.cssClasses.handle);
984
985            handle.setAttribute("data-handle", handleNumber);
986
987            if (options.keyboardSupport) {
988                // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
989                // 0 = focusable and reachable
990                handle.setAttribute("tabindex", "0");
991            }
992
993            handle.setAttribute("role", "slider");
994            handle.setAttribute("aria-orientation", options.ort ? "vertical" : "horizontal");
995
996            if (handleNumber === 0) {
997                addClass(handle, options.cssClasses.handleLower);
998            } else if (handleNumber === options.handles - 1) {
999                addClass(handle, options.cssClasses.handleUpper);
1000            }
1001
1002            return origin;
1003        }
1004
1005        // Insert nodes for connect elements
1006        function addConnect(base, add) {
1007            if (!add) {
1008                return false;
1009            }
1010
1011            return addNodeTo(base, options.cssClasses.connect);
1012        }
1013
1014        // Add handles to the slider base.
1015        function addElements(connectOptions, base) {
1016            var connectBase = addNodeTo(base, options.cssClasses.connects);
1017
1018            scope_Handles = [];
1019            scope_Connects = [];
1020
1021            scope_Connects.push(addConnect(connectBase, connectOptions[0]));
1022
1023            // [::::O====O====O====]
1024            // connectOptions = [0, 1, 1, 1]
1025
1026            for (var i = 0; i < options.handles; i++) {
1027                // Keep a list of all added handles.
1028                scope_Handles.push(addOrigin(base, i));
1029                scope_HandleNumbers[i] = i;
1030                scope_Connects.push(addConnect(connectBase, connectOptions[i + 1]));
1031            }
1032        }
1033
1034        // Initialize a single slider.
1035        function addSlider(addTarget) {
1036            // Apply classes and data to the target.
1037            addClass(addTarget, options.cssClasses.target);
1038
1039            if (options.dir === 0) {
1040                addClass(addTarget, options.cssClasses.ltr);
1041            } else {
1042                addClass(addTarget, options.cssClasses.rtl);
1043            }
1044
1045            if (options.ort === 0) {
1046                addClass(addTarget, options.cssClasses.horizontal);
1047            } else {
1048                addClass(addTarget, options.cssClasses.vertical);
1049            }
1050
1051            return addNodeTo(addTarget, options.cssClasses.base);
1052        }
1053
1054        function addTooltip(handle, handleNumber) {
1055            if (!options.tooltips[handleNumber]) {
1056                return false;
1057            }
1058
1059            return addNodeTo(handle.firstChild, options.cssClasses.tooltip);
1060        }
1061
1062        // The tooltips option is a shorthand for using the 'update' event.
1063        function tooltips() {
1064            // Tooltips are added with options.tooltips in original order.
1065            var tips = scope_Handles.map(addTooltip);
1066
1067            bindEvent("update", function(values, handleNumber, unencoded) {
1068                if (!tips[handleNumber]) {
1069                    return;
1070                }
1071
1072                var formattedValue = values[handleNumber];
1073
1074                if (options.tooltips[handleNumber] !== true) {
1075                    formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]);
1076                }
1077
1078                tips[handleNumber].innerHTML = formattedValue;
1079            });
1080        }
1081
1082        function aria() {
1083            bindEvent("update", function(values, handleNumber, unencoded, tap, positions) {
1084                // Update Aria Values for all handles, as a change in one changes min and max values for the next.
1085                scope_HandleNumbers.forEach(function(index) {
1086                    var handle = scope_Handles[index];
1087
1088                    var min = checkHandlePosition(scope_Locations, index, 0, true, true, true);
1089                    var max = checkHandlePosition(scope_Locations, index, 100, true, true, true);
1090
1091                    var now = positions[index];
1092
1093                    // Formatted value for display
1094                    var text = options.ariaFormat.to(unencoded[index]);
1095
1096                    // Map to slider range values
1097                    min = scope_Spectrum.fromStepping(min).toFixed(1);
1098                    max = scope_Spectrum.fromStepping(max).toFixed(1);
1099                    now = scope_Spectrum.fromStepping(now).toFixed(1);
1100
1101                    handle.children[0].setAttribute("aria-valuemin", min);
1102                    handle.children[0].setAttribute("aria-valuemax", max);
1103                    handle.children[0].setAttribute("aria-valuenow", now);
1104                    handle.children[0].setAttribute("aria-valuetext", text);
1105                });
1106            });
1107        }
1108
1109        function getGroup(mode, values, stepped) {
1110            // Use the range.
1111            if (mode === "range" || mode === "steps") {
1112                return scope_Spectrum.xVal;
1113            }
1114
1115            if (mode === "count") {
1116                if (values < 2) {
1117                    throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'.");
1118                }
1119
1120                // Divide 0 - 100 in 'count' parts.
1121                var interval = values - 1;
1122                var spread = 100 / interval;
1123
1124                values = [];
1125
1126                // List these parts and have them handled as 'positions'.
1127                while (interval--) {
1128                    values[interval] = interval * spread;
1129                }
1130
1131                values.push(100);
1132
1133                mode = "positions";
1134            }
1135
1136            if (mode === "positions") {
1137                // Map all percentages to on-range values.
1138                return values.map(function(value) {
1139                    return scope_Spectrum.fromStepping(stepped ? scope_Spectrum.getStep(value) : value);
1140                });
1141            }
1142
1143            if (mode === "values") {
1144                // If the value must be stepped, it needs to be converted to a percentage first.
1145                if (stepped) {
1146                    return values.map(function(value) {
1147                        // Convert to percentage, apply step, return to value.
1148                        return scope_Spectrum.fromStepping(scope_Spectrum.getStep(scope_Spectrum.toStepping(value)));
1149                    });
1150                }
1151
1152                // Otherwise, we can simply use the values.
1153                return values;
1154            }
1155        }
1156
1157        function generateSpread(density, mode, group) {
1158            function safeIncrement(value, increment) {
1159                // Avoid floating point variance by dropping the smallest decimal places.
1160                return (value + increment).toFixed(7) / 1;
1161            }
1162
1163            var indexes = {};
1164            var firstInRange = scope_Spectrum.xVal[0];
1165            var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length - 1];
1166            var ignoreFirst = false;
1167            var ignoreLast = false;
1168            var prevPct = 0;
1169
1170            // Create a copy of the group, sort it and filter away all duplicates.
1171            group = unique(
1172                group.slice().sort(function(a, b) {
1173                    return a - b;
1174                })
1175            );
1176
1177            // Make sure the range starts with the first element.
1178            if (group[0] !== firstInRange) {
1179                group.unshift(firstInRange);
1180                ignoreFirst = true;
1181            }
1182
1183            // Likewise for the last one.
1184            if (group[group.length - 1] !== lastInRange) {
1185                group.push(lastInRange);
1186                ignoreLast = true;
1187            }
1188
1189            group.forEach(function(current, index) {
1190                // Get the current step and the lower + upper positions.
1191                var step;
1192                var i;
1193                var q;
1194                var low = current;
1195                var high = group[index + 1];
1196                var newPct;
1197                var pctDifference;
1198                var pctPos;
1199                var type;
1200                var steps;
1201                var realSteps;
1202                var stepSize;
1203                var isSteps = mode === "steps";
1204
1205                // When using 'steps' mode, use the provided steps.
1206                // Otherwise, we'll step on to the next subrange.
1207                if (isSteps) {
1208                    step = scope_Spectrum.xNumSteps[index];
1209                }
1210
1211                // Default to a 'full' step.
1212                if (!step) {
1213                    step = high - low;
1214                }
1215
1216                // Low can be 0, so test for false. If high is undefined,
1217                // we are at the last subrange. Index 0 is already handled.
1218                if (low === false || high === undefined) {
1219                    return;
1220                }
1221
1222                // Make sure step isn't 0, which would cause an infinite loop (#654)
1223                step = Math.max(step, 0.0000001);
1224
1225                // Find all steps in the subrange.
1226                for (i = low; i <= high; i = safeIncrement(i, step)) {
1227                    // Get the percentage value for the current step,
1228                    // calculate the size for the subrange.
1229                    newPct = scope_Spectrum.toStepping(i);
1230                    pctDifference = newPct - prevPct;
1231
1232                    steps = pctDifference / density;
1233                    realSteps = Math.round(steps);
1234
1235                    // This ratio represents the amount of percentage-space a point indicates.
1236                    // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-divided.
1237                    // Round the percentage offset to an even number, then divide by two
1238                    // to spread the offset on both sides of the range.
1239                    stepSize = pctDifference / realSteps;
1240
1241                    // Divide all points evenly, adding the correct number to this subrange.
1242                    // Run up to <= so that 100% gets a point, event if ignoreLast is set.
1243                    for (q = 1; q <= realSteps; q += 1) {
1244                        // The ratio between the rounded value and the actual size might be ~1% off.
1245                        // Correct the percentage offset by the number of points
1246                        // per subrange. density = 1 will result in 100 points on the
1247                        // full range, 2 for 50, 4 for 25, etc.
1248                        pctPos = prevPct + q * stepSize;
1249                        indexes[pctPos.toFixed(5)] = [scope_Spectrum.fromStepping(pctPos), 0];
1250                    }
1251
1252                    // Determine the point type.
1253                    type = group.indexOf(i) > -1 ? PIPS_LARGE_VALUE : isSteps ? PIPS_SMALL_VALUE : PIPS_NO_VALUE;
1254
1255                    // Enforce the 'ignoreFirst' option by overwriting the type for 0.
1256                    if (!index && ignoreFirst) {
1257                        type = 0;
1258                    }
1259
1260                    if (!(i === high && ignoreLast)) {
1261                        // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
1262                        indexes[newPct.toFixed(5)] = [i, type];
1263                    }
1264
1265                    // Update the percentage count.
1266                    prevPct = newPct;
1267                }
1268            });
1269
1270            return indexes;
1271        }
1272
1273        function addMarking(spread, filterFunc, formatter) {
1274            var element = scope_Document.createElement("div");
1275
1276            var valueSizeClasses = [];
1277            valueSizeClasses[PIPS_NO_VALUE] = options.cssClasses.valueNormal;
1278            valueSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.valueLarge;
1279            valueSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.valueSub;
1280
1281            var markerSizeClasses = [];
1282            markerSizeClasses[PIPS_NO_VALUE] = options.cssClasses.markerNormal;
1283            markerSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.markerLarge;
1284            markerSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.markerSub;
1285
1286            var valueOrientationClasses = [options.cssClasses.valueHorizontal, options.cssClasses.valueVertical];
1287            var markerOrientationClasses = [options.cssClasses.markerHorizontal, options.cssClasses.markerVertical];
1288
1289            addClass(element, options.cssClasses.pips);
1290            addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical);
1291
1292            function getClasses(type, source) {
1293                var a = source === options.cssClasses.value;
1294                var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses;
1295                var sizeClasses = a ? valueSizeClasses : markerSizeClasses;
1296
1297                return source + " " + orientationClasses[options.ort] + " " + sizeClasses[type];
1298            }
1299
1300            function addSpread(offset, value, type) {
1301                // Apply the filter function, if it is set.
1302                type = filterFunc ? filterFunc(value, type) : type;
1303
1304                if (type === PIPS_NONE) {
1305                    return;
1306                }
1307
1308                // Add a marker for every point
1309                var node = addNodeTo(element, false);
1310                node.className = getClasses(type, options.cssClasses.marker);
1311                node.style[options.style] = offset + "%";
1312
1313                // Values are only appended for points marked '1' or '2'.
1314                if (type > PIPS_NO_VALUE) {
1315                    node = addNodeTo(element, false);
1316                    node.className = getClasses(type, options.cssClasses.value);
1317                    node.setAttribute("data-value", value);
1318                    node.style[options.style] = offset + "%";
1319                    node.innerHTML = formatter.to(value);
1320                }
1321            }
1322
1323            // Append all points.
1324            Object.keys(spread).forEach(function(offset) {
1325                addSpread(offset, spread[offset][0], spread[offset][1]);
1326            });
1327
1328            return element;
1329        }
1330
1331        function removePips() {
1332            if (scope_Pips) {
1333                removeElement(scope_Pips);
1334                scope_Pips = null;
1335            }
1336        }
1337
1338        function pips(grid) {
1339            // Fix #669
1340            removePips();
1341
1342            var mode = grid.mode;
1343            var density = grid.density || 1;
1344            var filter = grid.filter || false;
1345            var values = grid.values || false;
1346            var stepped = grid.stepped || false;
1347            var group = getGroup(mode, values, stepped);
1348            var spread = generateSpread(density, mode, group);
1349            var format = grid.format || {
1350                to: Math.round
1351            };
1352
1353            scope_Pips = scope_Target.appendChild(addMarking(spread, filter, format));
1354
1355            return scope_Pips;
1356        }
1357
1358        // Shorthand for base dimensions.
1359        function baseSize() {
1360            var rect = scope_Base.getBoundingClientRect();
1361            var alt = "offset" + ["Width", "Height"][options.ort];
1362            return options.ort === 0 ? rect.width || scope_Base[alt] : rect.height || scope_Base[alt];
1363        }
1364
1365        // Handler for attaching events trough a proxy.
1366        function attachEvent(events, element, callback, data) {
1367            // This function can be used to 'filter' events to the slider.
1368            // element is a node, not a nodeList
1369
1370            var method = function(e) {
1371                e = fixEvent(e, data.pageOffset, data.target || element);
1372
1373                // fixEvent returns false if this event has a different target
1374                // when handling (multi-) touch events;
1375                if (!e) {
1376                    return false;
1377                }
1378
1379                // doNotReject is passed by all end events to make sure released touches
1380                // are not rejected, leaving the slider "stuck" to the cursor;
1381                if (scope_Target.hasAttribute("disabled") && !data.doNotReject) {
1382                    return false;
1383                }
1384
1385                // Stop if an active 'tap' transition is taking place.
vendor: 5,004 bytes, lines 1386-1507
1386                if (hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject) {
1387                    return false;
1388                }
1389
1390                // Ignore right or middle clicks on start #454
1391                if (events === actions.start && e.buttons !== undefined && e.buttons > 1) {
1392                    return false;
1393                }
1394
1395                // Ignore right or middle clicks on start #454
1396                if (data.hover && e.buttons) {
1397                    return false;
1398                }
1399
1400                // 'supportsPassive' is only true if a browser also supports touch-action: none in CSS.
1401                // iOS safari does not, so it doesn't get to benefit from passive scrolling. iOS does support
1402                // touch-action: manipulation, but that allows panning, which breaks
1403                // sliders after zooming/on non-responsive pages.
1404                // See: https://bugs.webkit.org/show_bug.cgi?id=133112
1405                if (!supportsPassive) {
1406                    e.preventDefault();
1407                }
1408
1409                e.calcPoint = e.points[options.ort];
1410
1411                // Call the event handler with the event [ and additional data ].
1412                callback(e, data);
1413            };
1414
1415            var methods = [];
1416
1417            // Bind a closure on the target for every event type.
1418            events.split(" ").forEach(function(eventName) {
1419                element.addEventListener(eventName, method, supportsPassive ? { passive: true } : false);
1420                methods.push([eventName, method]);
1421            });
1422
1423            return methods;
1424        }
1425
1426        // Provide a clean event with standardized offset values.
1427        function fixEvent(e, pageOffset, eventTarget) {
1428            // Filter the event to register the type, which can be
1429            // touch, mouse or pointer. Offset changes need to be
1430            // made on an event specific basis.
1431            var touch = e.type.indexOf("touch") === 0;
1432            var mouse = e.type.indexOf("mouse") === 0;
1433            var pointer = e.type.indexOf("pointer") === 0;
1434
1435            var x;
1436            var y;
1437
1438            // IE10 implemented pointer events with a prefix;
1439            if (e.type.indexOf("MSPointer") === 0) {
1440                pointer = true;
1441            }
1442
1443            // The only thing one handle should be concerned about is the touches that originated on top of it.
1444            if (touch) {
1445                // Returns true if a touch originated on the target.
1446                var isTouchOnTarget = function(checkTouch) {
1447                    return checkTouch.target === eventTarget || eventTarget.contains(checkTouch.target);
1448                };
1449
1450                // In the case of touchstart events, we need to make sure there is still no more than one
1451                // touch on the target so we look amongst all touches.
1452                if (e.type === "touchstart") {
1453                    var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget);
1454
1455                    // Do not support more than one touch per handle.
1456                    if (targetTouches.length > 1) {
1457                        return false;
1458                    }
1459
1460                    x = targetTouches[0].pageX;
1461                    y = targetTouches[0].pageY;
1462                } else {
1463                    // In the other cases, find on changedTouches is enough.
1464                    var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget);
1465
1466                    // Cancel if the target touch has not moved.
1467                    if (!targetTouch) {
1468                        return false;
1469                    }
1470
1471                    x = targetTouch.pageX;
1472                    y = targetTouch.pageY;
1473                }
1474            }
1475
1476            pageOffset = pageOffset || getPageOffset(scope_Document);
1477
1478            if (mouse || pointer) {
1479                x = e.clientX + pageOffset.x;
1480                y = e.clientY + pageOffset.y;
1481            }
1482
1483            e.pageOffset = pageOffset;
1484            e.points = [x, y];
1485            e.cursor = mouse || pointer; // Fix #435
1486
1487            return e;
1488        }
1489
1490        // Translate a coordinate in the document to a percentage on the slider
1491        function calcPointToPercentage(calcPoint) {
1492            var location = calcPoint - offset(scope_Base, options.ort);
1493            var proposal = (location * 100) / baseSize();
1494
1495            // Clamp proposal between 0% and 100%
1496            // Out-of-bound coordinates may occur when .noUi-base pseudo-elements
1497            // are used (e.g. contained handles feature)
1498            proposal = limit(proposal);
1499
1500            return options.dir ? 100 - proposal : proposal;
1501        }
1502
1503        // Find handle closest to a certain percentage on the slider
1504        function getClosestHandle(proposal) {
1505            var closest = 100;
1506            var handleNumber = false;
1507
1508            scope_Handles.forEach(function(handle, index) {
1509                // Disabled handles are ignored
1510                if (handle.hasAttribute("disabled")) {
1511                    return;
1512                }
1513
1514                var pos = Math.abs(scope_Locations[index] - proposal);
1515
1516                if (pos < closest || (pos === 100 && closest === 100)) {
1517                    handleNumber = index;
1518                    closest = pos;
1519                }
1520            });
1521
1522            return handleNumber;
1523        }
1524
1525        // Fire 'end' when a mouse or pen leaves the document.
1526        function documentLeave(event, data) {
1527            if (event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null) {
1528                eventEnd(event, data);
1529            }
1530        }
1531
1532        // Handle movement on document for handle and range drag.
1533        function eventMove(event, data) {
1534            // Fix #498
1535            // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
1536            // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
1537            // IE9 has .buttons and .which zero on mousemove.
1538            // Firefox breaks the spec MDN defines.
1539            if (navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0) {
1540                return eventEnd(event, data);
1541            }
1542
1543            // Check if we are moving up or down
1544            var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint);
1545
1546            // Convert the movement into a percentage of the slider width/height
1547            var proposal = (movement * 100) / data.baseSize;
1548
1549            moveHandles(movement > 0, proposal, data.locations, data.handleNumbers);
1550        }
1551
1552        // Unbind move events on document, call callbacks.
1553        function eventEnd(event, data) {
1554            // The handle is no longer active, so remove the class.
1555            if (data.handle) {
1556                removeClass(data.handle, options.cssClasses.active);
1557                scope_ActiveHandlesCount -= 1;
1558            }
1559
1560            // Unbind the move and end events, which are added on 'start'.
1561            data.listeners.forEach(function(c) {
1562                scope_DocumentElement.removeEventListener(c[0], c[1]);
1563            });
1564
1565            if (scope_ActiveHandlesCount === 0) {
1566                // Remove dragging class.
1567                removeClass(scope_Target, options.cssClasses.drag);
1568                setZindex();
1569
1570                // Remove cursor styles and text-selection events bound to the body.
1571                if (event.cursor) {
1572                    scope_Body.style.cursor = "";
1573                    scope_Body.removeEventListener("selectstart", preventDefault);
1574                }
1575            }
1576
1577            data.handleNumbers.forEach(function(handleNumber) {
1578                fireEvent("change", handleNumber);
1579                fireEvent("set", handleNumber);
1580                fireEvent("end", handleNumber);
1581            });
1582        }
1583
1584        // Bind move events on document.
1585        function eventStart(event, data) {
1586            var handle;
1587            if (data.handleNumbers.length === 1) {
1588                var handleOrigin = scope_Handles[data.handleNumbers[0]];
1589
1590                // Ignore 'disabled' handles
1591                if (handleOrigin.hasAttribute("disabled")) {
1592                    return false;
1593                }
1594
1595                handle = handleOrigin.children[0];
1596                scope_ActiveHandlesCount += 1;
1597
1598                // Mark the handle as 'active' so it can be styled.
1599                addClass(handle, options.cssClasses.active);
1600            }
1601
1602            // A drag should never propagate up to the 'tap' event.
1603            event.stopPropagation();
1604
1605            // Record the event listeners.
1606            var listeners = [];
1607
1608            // Attach the move and end events.
1609            var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, {
1610                // The event target has changed so we need to propagate the original one so that we keep
1611                // relying on it to extract target touches.
1612                target: event.target,
1613                handle: handle,
1614                listeners: listeners,
1615                startCalcPoint: event.calcPoint,
1616                baseSize: baseSize(),
1617                pageOffset: event.pageOffset,
1618                handleNumbers: data.handleNumbers,
1619                buttonsProperty: event.buttons,
1620                locations: scope_Locations.slice()
1621            });
1622
1623            var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, {
1624                target: event.target,
1625                handle: handle,
1626                listeners: listeners,
1627                doNotReject: true,
1628                handleNumbers: data.handleNumbers
1629            });
1630
1631            var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, {
1632                target: event.target,
1633                handle: handle,
1634                listeners: listeners,
1635                doNotReject: true,
1636                handleNumbers: data.handleNumbers
1637            });
1638
1639            // We want to make sure we pushed the listeners in the listener list rather than creating
1640            // a new one as it has already been passed to the event handlers.
1641            listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent));
1642
1643            // Text selection isn't an issue on touch devices,
1644            // so adding cursor styles can be skipped.
1645            if (event.cursor) {
1646                // Prevent the 'I' cursor and extend the range-drag cursor.
1647                scope_Body.style.cursor = getComputedStyle(event.target).cursor;
1648
1649                // Mark the target with a dragging state.
1650                if (scope_Handles.length > 1) {
1651                    addClass(scope_Target, options.cssClasses.drag);
1652                }
1653
1654                // Prevent text selection when dragging the handles.
1655                // In noUiSlider <= 9.2.0, this was handled by calling preventDefault on mouse/touch start/move,
1656                // which is scroll blocking. The selectstart event is supported by FireFox starting from version 52,
1657                // meaning the only holdout is iOS Safari. This doesn't matter: text selection isn't triggered there.
1658                // The 'cursor' flag is false.
1659                // See: http://caniuse.com/#search=selectstart
1660                scope_Body.addEventListener("selectstart", preventDefault, false);
1661            }
1662
1663            data.handleNumbers.forEach(function(handleNumber) {
1664                fireEvent("start", handleNumber);
1665            });
1666        }
1667
1668        // Move closest handle to tapped location.
1669        function eventTap(event) {
1670            // The tap event shouldn't propagate up
1671            event.stopPropagation();
1672
1673            var proposal = calcPointToPercentage(event.calcPoint);
1674            var handleNumber = getClosestHandle(proposal);
1675
1676            // Tackle the case that all handles are 'disabled'.
1677            if (handleNumber === false) {
1678                return false;
1679            }
1680
1681            // Flag the slider as it is now in a transitional state.
1682            // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
1683            if (!options.events.snap) {
1684                addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
1685            }
1686
1687            setHandle(handleNumber, proposal, true, true);
1688
1689            setZindex();
1690
1691            fireEvent("slide", handleNumber, true);
1692            fireEvent("update", handleNumber, true);
1693            fireEvent("change", handleNumber, true);
1694            fireEvent("set", handleNumber, true);
1695
1696            if (options.events.snap) {
1697                eventStart(event, { handleNumbers: [handleNumber] });
1698            }
1699        }
1700
1701        // Fires a 'hover' event for a hovered mouse/pen position.
1702        function eventHover(event) {
1703            var proposal = calcPointToPercentage(event.calcPoint);
1704
1705            var to = scope_Spectrum.getStep(proposal);
1706            var value = scope_Spectrum.fromStepping(to);
1707
1708            Object.keys(scope_Events).forEach(function(targetEvent) {
1709                if ("hover" === targetEvent.split(".")[0]) {
1710                    scope_Events[targetEvent].forEach(function(callback) {
1711                        callback.call(scope_Self, value);
1712                    });
1713                }
1714            });
1715        }
1716
1717        // Attach events to several slider parts.
1718        function bindSliderEvents(behaviour) {
1719            // Attach the standard drag event to the handles.
1720            if (!behaviour.fixed) {
1721                scope_Handles.forEach(function(handle, index) {
1722                    // These events are only bound to the visual handle
1723                    // element, not the 'real' origin element.
1724                    attachEvent(actions.start, handle.children[0], eventStart, {
1725                        handleNumbers: [index]
1726                    });
1727                });
1728            }
1729
1730            // Attach the tap event to the slider base.
1731            if (behaviour.tap) {
1732                attachEvent(actions.start, scope_Base, eventTap, {});
1733            }
1734
1735            // Fire hover events
1736            if (behaviour.hover) {
1737                attachEvent(actions.move, scope_Base, eventHover, {
1738                    hover: true
1739                });
1740            }
1741
1742            // Make the range draggable.
1743            if (behaviour.drag) {
1744                scope_Connects.forEach(function(connect, index) {
1745                    if (connect === false || index === 0 || index === scope_Connects.length - 1) {
1746                        return;
1747                    }
1748
1749                    var handleBefore = scope_Handles[index - 1];
1750                    var handleAfter = scope_Handles[index];
1751                    var eventHolders = [connect];
1752
1753                    addClass(connect, options.cssClasses.draggable);
1754
1755                    // When the range is fixed, the entire range can
1756                    // be dragged by the handles. The handle in the first
1757                    // origin will propagate the start event upward,
1758                    // but it needs to be bound manually on the other.
1759                    if (behaviour.fixed) {
1760                        eventHolders.push(handleBefore.children[0]);
1761                        eventHolders.push(handleAfter.children[0]);
1762                    }
1763
1764                    eventHolders.forEach(function(eventHolder) {
1765                        attachEvent(actions.start, eventHolder, eventStart, {
1766                            handles: [handleBefore, handleAfter],
1767                            handleNumbers: [index - 1, index]
1768                        });
1769                    });
1770                });
1771            }
1772        }
1773
1774        // Attach an event to this slider, possibly including a namespace
1775        function bindEvent(namespacedEvent, callback) {
1776            scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
1777            scope_Events[namespacedEvent].push(callback);
1778
1779            // If the event bound is 'update,' fire it immediately for all handles.
1780            if (namespacedEvent.split(".")[0] === "update") {
1781                scope_Handles.forEach(function(a, index) {
1782                    fireEvent("update", index);
1783                });
1784            }
1785        }
1786
1787        // Undo attachment of event
1788        function removeEvent(namespacedEvent) {
1789            var event = namespacedEvent && namespacedEvent.split(".")[0];
1790            var namespace = event && namespacedEvent.substring(event.length);
1791
1792            Object.keys(scope_Events).forEach(function(bind) {
1793                var tEvent = bind.split(".")[0];
1794                var tNamespace = bind.substring(tEvent.length);
1795
1796                if ((!event || event === tEvent) && (!namespace || namespace === tNamespace)) {
1797                    delete scope_Events[bind];
1798                }
1799            });
1800        }
1801
1802        // External event handling
1803        function fireEvent(eventName, handleNumber, tap) {
1804            Object.keys(scope_Events).forEach(function(targetEvent) {
1805                var eventType = targetEvent.split(".")[0];
1806
1807                if (eventName === eventType) {
1808                    scope_Events[targetEvent].forEach(function(callback) {
1809                        callback.call(
1810                            // Use the slider public API as the scope ('this')
1811                            scope_Self,
1812                            // Return values as array, so arg_1[arg_2] is always valid.
1813                            scope_Values.map(options.format.to),
1814                            // Handle index, 0 or 1
1815                            handleNumber,
1816                            // Un-formatted slider values
1817                            scope_Values.slice(),
1818                            // Event is fired by tap, true or false
1819                            tap || false,
1820                            // Left offset of the handle, in relation to the slider
1821                            scope_Locations.slice()
1822                        );
1823                    });
1824                }
1825            });
1826        }
1827
1828        function toPct(pct) {
1829            return pct + "%";
1830        }
1831
1832        // Split out the handle positioning logic so the Move event can use it, too
1833        function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) {
1834            // For sliders with multiple handles, limit movement to the other handle.
1835            // Apply the margin option by adding it to the handle positions.
1836            if (scope_Handles.length > 1 && !options.events.unconstrained) {
1837                if (lookBackward && handleNumber > 0) {
1838                    to = Math.max(to, reference[handleNumber - 1] + options.margin);
1839                }
1840
1841                if (lookForward && handleNumber < scope_Handles.length - 1) {
1842                    to = Math.min(to, reference[handleNumber + 1] - options.margin);
1843                }
1844            }
1845
1846            // The limit option has the opposite effect, limiting handles to a
1847            // maximum distance from another. Limit must be > 0, as otherwise
1848            // handles would be unmovable.
1849            if (scope_Handles.length > 1 && options.limit) {
1850                if (lookBackward && handleNumber > 0) {
1851                    to = Math.min(to, reference[handleNumber - 1] + options.limit);
1852                }
1853
1854                if (lookForward && handleNumber < scope_Handles.length - 1) {
1855                    to = Math.max(to, reference[handleNumber + 1] - options.limit);
1856                }
1857            }
1858
1859            // The padding option keeps the handles a certain distance from the
1860            // edges of the slider. Padding must be > 0.
1861            if (options.padding) {
1862                if (handleNumber === 0) {
1863                    to = Math.max(to, options.padding[0]);
1864                }
1865
1866                if (handleNumber === scope_Handles.length - 1) {
1867                    to = Math.min(to, 100 - options.padding[1]);
1868                }
1869            }
1870
1871            to = scope_Spectrum.getStep(to);
1872
1873            // Limit percentage to the 0 - 100 range
1874            to = limit(to);
1875
1876            // Return false if handle can't move
1877            if (to === reference[handleNumber] && !getValue) {
1878                return false;
1879            }
1880
1881            return to;
1882        }
1883
1884        // Uses slider orientation to create CSS rules. a = base value;
1885        function inRuleOrder(v, a) {
1886            var o = options.ort;
1887            return (o ? a : v) + ", " + (o ? v : a);
1888        }
1889
1890        // Moves handle(s) by a percentage
1891        // (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...])
1892        function moveHandles(upward, proposal, locations, handleNumbers) {
1893            var proposals = locations.slice();
1894
1895            var b = [!upward, upward];
1896            var f = [upward, !upward];
1897
1898            // Copy handleNumbers so we don't change the dataset
1899            handleNumbers = handleNumbers.slice();
1900
1901            // Check to see which handle is 'leading'.
1902            // If that one can't move the second can't either.
1903            if (upward) {
1904                handleNumbers.reverse();
1905            }
1906
1907            // Step 1: get the maximum percentage that any of the handles can move
1908            if (handleNumbers.length > 1) {
1909                handleNumbers.forEach(function(handleNumber, o) {
1910                    var to = checkHandlePosition(
1911                        proposals,
1912                        handleNumber,
1913                        proposals[handleNumber] + proposal,
1914                        b[o],
1915                        f[o],
1916                        false
1917                    );
1918
1919                    // Stop if one of the handles can't move.
1920                    if (to === false) {
1921                        proposal = 0;
1922                    } else {
1923                        proposal = to - proposals[handleNumber];
1924                        proposals[handleNumber] = to;
1925                    }
1926                });
1927            }
1928
1929            // If using one handle, check backward AND forward
1930            else {
1931                b = f = [true];
1932            }
1933
1934            var state = false;
1935
1936            // Step 2: Try to set the handles with the found percentage
1937            handleNumbers.forEach(function(handleNumber, o) {
1938                state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state;
1939            });
1940
1941            // Step 3: If a handle moved, fire events
1942            if (state) {
1943                handleNumbers.forEach(function(handleNumber) {
1944                    fireEvent("update", handleNumber);
1945                    fireEvent("slide", handleNumber);
1946                });
1947            }
1948        }
1949
1950        // Takes a base value and an offset. This offset is used for the connect bar size.
1951        // In the initial design for this feature, the origin element was 1% wide.
1952        // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature
1953        // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223
1954        function transformDirection(a, b) {
1955            return options.dir ? 100 - a - b : a;
1956        }
1957
1958        // Updates scope_Locations and scope_Values, updates visual state
1959        function updateHandlePosition(handleNumber, to) {
1960            // Update locations.
1961            scope_Locations[handleNumber] = to;
1962
1963            // Convert the value to the slider stepping/range.
1964            scope_Values[handleNumber] = scope_Spectrum.fromStepping(to);
1965
1966            var rule = "translate(" + inRuleOrder(toPct(transformDirection(to, 0) - scope_DirOffset), "0") + ")";
1967            scope_Handles[handleNumber].style[options.transformRule] = rule;
1968
1969            updateConnect(handleNumber);
1970            updateConnect(handleNumber + 1);
1971        }
1972
1973        // Handles before the slider middle are stacked later = higher,
1974        // Handles after the middle later is lower
1975        // [[7] [8] .......... | .......... [5] [4]
1976        function setZindex() {
1977            scope_HandleNumbers.forEach(function(handleNumber) {
1978                var dir = scope_Locations[handleNumber] > 50 ? -1 : 1;
1979                var zIndex = 3 + (scope_Handles.length + dir * handleNumber);
1980                scope_Handles[handleNumber].style.zIndex = zIndex;
1981            });
1982        }
1983
1984        // Test suggested values and apply margin, step.
1985        function setHandle(handleNumber, to, lookBackward, lookForward) {
1986            to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false);
1987
1988            if (to === false) {
1989                return false;
1990            }
1991
1992            updateHandlePosition(handleNumber, to);
1993
1994            return true;
1995        }
1996
1997        // Updates style attribute for connect nodes
1998        function updateConnect(index) {
1999            // Skip connects set to false
2000            if (!scope_Connects[index]) {
2001                return;
2002            }
2003
2004            var l = 0;
2005            var h = 100;
2006
2007            if (index !== 0) {
2008                l = scope_Locations[index - 1];
2009            }
2010
2011            if (index !== scope_Connects.length - 1) {
2012                h = scope_Locations[index];
2013            }
2014
2015            // We use two rules:
2016            // 'translate' to change the left/top offset;
2017            // 'scale' to change the width of the element;
2018            // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base)
2019            var connectWidth = h - l;
2020            var translateRule = "translate(" + inRuleOrder(toPct(transformDirection(l, connectWidth)), "0") + ")";
2021            var scaleRule = "scale(" + inRuleOrder(connectWidth / 100, "1") + ")";
2022
2023            scope_Connects[index].style[options.transformRule] = translateRule + " " + scaleRule;
2024        }
2025
2026        // Parses value passed to .set method. Returns current value if not parse-able.
2027        function resolveToValue(to, handleNumber) {
2028            // Setting with null indicates an 'ignore'.
2029            // Inputting 'false' is invalid.
2030            if (to === null || to === false || to === undefined) {
2031                return scope_Locations[handleNumber];
2032            }
2033
2034            // If a formatted number was passed, attempt to decode it.
2035            if (typeof to === "number") {
2036                to = String(to);
2037            }
2038
2039            to = options.format.from(to);
2040            to = scope_Spectrum.toStepping(to);
2041
2042            // If parsing the number failed, use the current value.
2043            if (to === false || isNaN(to)) {
2044                return scope_Locations[handleNumber];
2045            }
2046
2047            return to;
2048        }
2049
2050        // Set the slider value.
2051        function valueSet(input, fireSetEvent) {
2052            var values = asArray(input);
2053            var isInit = scope_Locations[0] === undefined;
2054
2055            // Event fires by default
2056            fireSetEvent = fireSetEvent === undefined ? true : !!fireSetEvent;
2057
2058            // Animation is optional.
2059            // Make sure the initial values were set before using animated placement.
2060            if (options.animate && !isInit) {
2061                addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
2062            }
2063
2064            // First pass, without lookAhead but with lookBackward. Values are set from left to right.
2065            scope_HandleNumbers.forEach(function(handleNumber) {
2066                setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false);
2067            });
2068
2069            // Second pass. Now that all base values are set, apply constraints
2070            scope_HandleNumbers.forEach(function(handleNumber) {
2071                setHandle(handleNumber, scope_Locations[handleNumber], true, true);
2072            });
2073
2074            setZindex();
2075
2076            scope_HandleNumbers.forEach(function(handleNumber) {
2077                fireEvent("update", handleNumber);
2078
2079                // Fire the event only for handles that received a new value, as per #579
2080                if (values[handleNumber] !== null && fireSetEvent) {
2081                    fireEvent("set", handleNumber);
2082                }
2083            });
2084        }
2085
2086        // Reset slider to initial values
2087        function valueReset(fireSetEvent) {
2088            valueSet(options.start, fireSetEvent);
2089        }
2090
2091        // Set value for a single handle
2092        function valueSetHandle(handleNumber, value, fireSetEvent) {
2093            var values = [];
2094
2095            // Ensure numeric input
2096            handleNumber = Number(handleNumber);
2097
2098            if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) {
2099                throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber);
2100            }
2101
2102            for (var i = 0; i < scope_HandleNumbers.length; i++) {
2103                values[i] = null;
2104            }
2105
2106            values[handleNumber] = value;
2107
2108            valueSet(values, fireSetEvent);
2109        }
2110
2111        // Get the slider value.
2112        function valueGet() {
2113            var values = scope_Values.map(options.format.to);
2114
2115            // If only one handle is used, return a single value.
2116            if (values.length === 1) {
2117                return values[0];
2118            }
2119
2120            return values;
2121        }
2122
2123        // Removes classes from the root and empties it.
2124        function destroy() {
2125            for (var key in options.cssClasses) {
2126                if (!options.cssClasses.hasOwnProperty(key)) {
2127                    continue;
2128                }
2129                removeClass(scope_Target, options.cssClasses[key]);
2130            }
2131
2132            while (scope_Target.firstChild) {
2133                scope_Target.removeChild(scope_Target.firstChild);
2134            }
2135
2136            delete scope_Target.noUiSlider;
2137        }
2138
2139        // Get the current step size for the slider.
2140        function getCurrentStep() {
2141            // Check all locations, map them to their stepping point.
2142            // Get the step point, then find it in the input list.
2143            return scope_Locations.map(function(location, index) {
2144                var nearbySteps = scope_Spectrum.getNearbySteps(location);
2145                var value = scope_Values[index];
2146                var increment = nearbySteps.thisStep.step;
2147                var decrement = null;
2148
2149                // If the next value in this step moves into the next step,
2150                // the increment is the start of the next step - the current value
2151                if (increment !== false) {
2152                    if (value + increment > nearbySteps.stepAfter.startValue) {
2153                        increment = nearbySteps.stepAfter.startValue - value;
2154                    }
2155                }
2156
2157                // If the value is beyond the starting point
2158                if (value > nearbySteps.thisStep.startValue) {
2159                    decrement = nearbySteps.thisStep.step;
2160                }
2160 else if (nearbySteps.stepBefore.step === false) {
2161                    decrement = false;
2162                }
2163
2164                // If a handle is at the start of a step, it always steps back into the previous step first
2165                else {
2166                    decrement = value - nearbySteps.stepBefore.highestStep;
2167                }
2168
2169                // Now, if at the slider edges, there is not in/decrement
2170                if (location === 100) {
2171                    increment = null;
2172                } else if (location === 0) {
2173                    decrement = null;
2174                }
2175
2176                // As per #391, the comparison for the decrement step can have some rounding issues.
2177                var stepDecimals = scope_Spectrum.countStepDecimals();
2178
2179                // Round per #391
2180                if (increment !== null && increment !== false) {
2181                    increment = Number(increment.toFixed(stepDecimals));
2182                }
2183
2184                if (decrement !== null && decrement !== false) {
2185                    decrement = Number(decrement.toFixed(stepDecimals));
2186                }
2187
2188                return [decrement, increment];
2189            });
2190        }
2191
2192        // Updateable: margin, limit, padding, step, range, animate, snap
2193        function updateOptions(optionsToUpdate, fireSetEvent) {
2194            // Spectrum is created using the range, snap, direction and step options.
2195            // 'snap' and 'step' can be updated.
2196            // If 'snap' and 'step' are not passed, they should remain unchanged.
2197            var v = valueGet();
2198
2199            var updateAble = ["margin", "limit", "padding", "range", "animate", "snap", "step", "format"];
2200
2201            // Only change options that we're actually passed to update.
2202            updateAble.forEach(function(name) {
2203                if (optionsToUpdate[name] !== undefined) {
2204                    originalOptions[name] = optionsToUpdate[name];
2205                }
2206            });
2207
2208            var newOptions = testOptions(originalOptions);
2209
2210            // Load new options into the slider state
2211            updateAble.forEach(function(name) {
2212                if (optionsToUpdate[name] !== undefined) {
2213                    options[name] = newOptions[name];
2214                }
2215            });
2216
2217            scope_Spectrum = newOptions.spectrum;
2218
2219            // Limit, margin and padding depend on the spectrum but are stored outside of it. (#677)
2220            options.margin = newOptions.margin;
2221            options.limit = newOptions.limit;
2222            options.padding = newOptions.padding;
2223
2224            // Update pips, removes existing.
2225            if (options.pips) {
2226                pips(options.pips);
2227            }
2228
2229            // Invalidate the current positioning so valueSet forces an update.
2230            scope_Locations = [];
2231            valueSet(optionsToUpdate.start || v, fireSetEvent);
2232        }
2233
2234        // Create the base element, initialize HTML and set classes.
2235        // Add handles and connect elements.
2236        scope_Base = addSlider(scope_Target);
2237        addElements(options.connect, scope_Base);
2238
2239        // Attach user events.
2240        bindSliderEvents(options.events);
2241
2242        // Use the public value method to set the start values.
2243        valueSet(options.start);
2244
2245        // noinspection JSUnusedGlobalSymbols
2246        scope_Self = {
2247            destroy: destroy,
2248            steps: getCurrentStep,
2249            on: bindEvent,
2250            off: removeEvent,
2251            get: valueGet,
2252            set: valueSet,
2253            setHandle: valueSetHandle,
2254            reset: valueReset,
2255            // Exposed for unit testing, don't use this in your application.
2256            __moveHandles: function(a, b, c) {
2257                moveHandles(a, b, scope_Locations, c);
2258            },
2259            options: originalOptions, // Issue #600, #678
2260            updateOptions: updateOptions,
2261            target: scope_Target, // Issue #597
2262            removePips: removePips,
2263            pips: pips // Issue #594
2264        };
2265
2266        if (options.pips) {
2267            pips(options.pips);
2268        }
2269
2270        if (options.tooltips) {
2271            tooltips();
2272        }
2273
2274        aria();
2275
2276        return scope_Self;
2277    }
2278
2279    // Run the standard initializer
2280    function initialize(target, originalOptions) {
2281        if (!target || !target.nodeName) {
2282            throw new Error("noUiSlider (" + VERSION + "): create requires a single element, got: " + target);
2283        }
2284
2285        // Throw an error if the slider was already initialized.
2286        if (target.noUiSlider) {
2287            throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized.");
2288        }
2289
2290        // Test the options and create the slider environment;
2291        var options = testOptions(originalOptions, target);
2292        var api = scope(target, options, originalOptions);
2293
2294        target.noUiSlider = api;
2295
2296        return api;
2297    }
2298
2299    // Use an object instead of a function for future expandability;
2300    return {
2301        // Exposed for unit testing, don't use this in your application.
2302        __spectrum: Spectrum,
2303        version: VERSION,
2304        create: initialize
2305    };
2306});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.