PageSourceSearch

https://i-sosna.ru/bitrix/templates/adaptive/js/nouislider/nouislider.js?178544501683577

js i-sosna.ru collected 2026-10-04 16:08:01 UTC 83,577 bytes, 2,278 lines download raw bytes

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 = "%%REPLACE_THIS_WITH_VERSION%%";
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
703        if (fixed) {
704            if (parsed.handles !== 2) {
705                throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles");
706            }
707
708            // Use margin to enforce fixed state
709            testMargin(parsed, parsed.start[1] - parsed.start[0]);
710        }
711
712        parsed.events = {
713            tap: tap || snap,
714            drag: drag,
715            fixed: fixed,
716            snap: snap,
717            hover: hover
718        };
719    }
720
721    function testTooltips(parsed, entry) {
722        if (entry === false) {
723            return;
724        }
725
726        if (entry === true) {
727            parsed.tooltips = [];
728
729            for (var i = 0; i < parsed.handles; i++) {
730                parsed.tooltips.push(true);
731            }
732        } else {
733            parsed.tooltips = asArray(entry);
734
735            if (parsed.tooltips.length !== parsed.handles) {
736                throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles.");
737            }
738
739            parsed.tooltips.forEach(function(formatter) {
740                if (
741                    typeof formatter !== "boolean" &&
742                    (typeof formatter !== "object" || typeof formatter.to !== "function")
743                ) {
744                    throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'.");
745                }
746            });
747        }
748    }
749
750    function testAriaFormat(parsed, entry) {
751        parsed.ariaFormat = entry;
752        validateFormat(entry);
753    }
754
755    function testFormat(parsed, entry) {
756        parsed.format = entry;
757        validateFormat(entry);
758    }
759
760    function testKeyboardSupport(parsed, entry) {
761        parsed.keyboardSupport = entry;
762
763        if (typeof entry !== "boolean") {
764            throw new Error("noUiSlider (" + VERSION + "): 'keyboardSupport' option must be a boolean.");
765        }
766    }
767
768    function testDocumentElement(parsed, entry) {
769        // This is an advanced option. Passed values are used without validation.
770        parsed.documentElement = entry;
771    }
772
773    function testCssPrefix(parsed, entry) {
774        if (typeof entry !== "string" && entry !== false) {
775            throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`.");
776        }
777
778        parsed.cssPrefix = entry;
779    }
780
781    function testCssClasses(parsed, entry) {
782        if (typeof entry !== "object") {
783            throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object.");
784        }
785
786        if (typeof parsed.cssPrefix === "string") {
787            parsed.cssClasses = {};
788
789            for (var key in entry) {
790                if (!entry.hasOwnProperty(key)) {
791                    continue;
792                }
793
794                parsed.cssClasses[key] = parsed.cssPrefix + entry[key];
795            }
796        } else {
797            parsed.cssClasses = entry;
798        }
799    }
800
801    // Test all developer settings and parse to assumption-safe values.
802    function testOptions(options) {
803        // To prove a fix for #537, freeze options here.
804        // If the object is modified, an error will be thrown.
805        // Object.freeze(options);
806
807        var parsed = {
808            margin: 0,
809            limit: 0,
810            padding: 0,
811            animate: true,
812            animationDuration: 300,
813            ariaFormat: defaultFormatter,
814            format: defaultFormatter
815        };
816
817        // Tests are executed in the order they are presented here.
818        var tests = {
819            step: { r: false, t: testStep },
820            start: { r: true, t: testStart },
821            connect: { r: true, t: testConnect },
822            direction: { r: true, t: testDirection },
823            snap: { r: false, t: testSnap },
824            animate: { r: false, t: testAnimate },
825            animationDuration: { r: false, t: testAnimationDuration },
826            range: { r: true, t: testRange },
827            orientation: { r: false, t: testOrientation },
828            margin: { r: false, t: testMargin },
829            limit: { r: false, t: testLimit },
830            padding: { r: false, t: testPadding },
831            behaviour: { r: true, t: testBehaviour },
832            ariaFormat: { r: false, t: testAriaFormat },
833            format: { r: false, t: testFormat },
834            tooltips: { r: false, t: testTooltips },
835            keyboardSupport: { r: true, t: testKeyboardSupport },
836            documentElement: { r: false, t: testDocumentElement },
837            cssPrefix: { r: true, t: testCssPrefix },
838            cssClasses: { r: true, t: testCssClasses }
839        };
840
841        var defaults = {
842            connect: false,
843            direction: "ltr",
844            behaviour: "tap",
845            orientation: "horizontal",
846            keyboardSupport: true,
847            cssPrefix: "noUi-",
848            cssClasses: {
849                target: "target",
850                base: "base",
851                origin: "origin",
852                handle: "handle",
853                handleLower: "handle-lower",
854                handleUpper: "handle-upper",
855                horizontal: "horizontal",
856                vertical: "vertical",
857                background: "background",
858                connect: "connect",
859                connects: "connects",
860                ltr: "ltr",
861                rtl: "rtl",
862                draggable: "draggable",
863                drag: "state-drag",
864                tap: "state-tap",
865                active: "active",
866                tooltip: "tooltip",
867                pips: "pips",
868                pipsHorizontal: "pips-horizontal",
869                pipsVertical: "pips-vertical",
870                marker: "marker",
871                markerHorizontal: "marker-horizontal",
872                markerVertical: "marker-vertical",
873                markerNormal: "marker-normal",
874                markerLarge: "marker-large",
875                markerSub: "marker-sub",
876                value: "value",
877                valueHorizontal: "value-horizontal",
878                valueVertical: "value-vertical",
879                valueNormal: "value-normal",
880                valueLarge: "value-large",
881                valueSub: "value-sub"
882            }
883        };
884
885        // AriaFormat defaults to regular format, if any.
886        if (options.format && !options.ariaFormat) {
887            options.ariaFormat = options.format;
888        }
889
890        // Run all options through a testing mechanism to ensure correct
891        // input. It should be noted that options might get modified to
892        // be handled properly. E.g. wrapping integers in arrays.
893        Object.keys(tests).forEach(function(name) {
894            // If the option isn't set, but it is required, throw an error.
895            if (!isSet(options[name]) && defaults[name] === undefined) {
896                if (tests[name].r) {
897                    throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required.");
898                }
899
900                return true;
901            }
902
903            tests[name].t(parsed, !isSet(options[name]) ? defaults[name] : options[name]);
904        });
905
906        // Forward pips options
907        parsed.pips = options.pips;
908
909        // All recent browsers accept unprefixed transform.
910        // We need -ms- for IE9 and -webkit- for older Android;
911        // Assume use of -webkit- if unprefixed and -ms- are not supported.
912        // https://caniuse.com/#feat=transforms2d
913        var d = document.createElement("div");
914        var msPrefix = d.style.msTransform !== undefined;
915        var noPrefix = d.style.transform !== undefined;
916
917        parsed.transformRule = noPrefix ? "transform" : msPrefix ? "msTransform" : "webkitTransform";
918
919        // Pips don't move, so we can place them using left/top.
920        var styles = [["left", "top"], ["right", "bottom"]];
921
922        parsed.style = styles[parsed.dir][parsed.ort];
923
924        return parsed;
925    }
926
927    function scope(target, options, originalOptions) {
928        var actions = getActions();
929        var supportsTouchActionNone = getSupportsTouchActionNone();
930        var supportsPassive = supportsTouchActionNone && getSupportsPassive();
931
932        // All variables local to 'scope' are prefixed with 'scope_'
933        var scope_Target = target;
934        var scope_Locations = [];
935        var scope_Base;
936        var scope_Handles;
937        var scope_HandleNumbers = [];
938        var scope_ActiveHandlesCount = 0;
939        var scope_Connects;
940        var scope_Spectrum = options.spectrum;
941        var scope_Values = [];
942        var scope_Events = {};
943        var scope_Self;
944        var scope_Pips;
945        var scope_Document = target.ownerDocument;
946        var scope_DocumentElement = options.documentElement || scope_Document.documentElement;
947        var scope_Body = scope_Document.body;
948
949        // Pips constants
950        var PIPS_NONE = -1;
951        var PIPS_NO_VALUE = 0;
952        var PIPS_LARGE_VALUE = 1;
953        var PIPS_SMALL_VALUE = 2;
954
955        // For horizontal sliders in standard ltr documents,
956        // make .noUi-origin overflow to the left so the document doesn't scroll.
957        var scope_DirOffset = scope_Document.dir === "rtl" || options.ort === 1 ? 0 : 100;
958
959        // Creates a node, adds it to target, returns the new node.
960        function addNodeTo(addTarget, className) {
961            var div = scope_Document.createElement("div");
962
963            if (className) {
964                addClass(div, className);
965            }
966
967            addTarget.appendChild(div);
968
969            return div;
970        }
971
972        // Append a origin to the base
973        function addOrigin(base, handleNumber) {
974            var origin = addNodeTo(base, options.cssClasses.origin);
975            var handle = addNodeTo(origin, options.cssClasses.handle);
976
977            handle.setAttribute("data-handle", handleNumber);
978
979            if (options.keyboardSupport) {
980                // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
981                // 0 = focusable and reachable
982                handle.setAttribute("tabindex", "0");
983            }
984
985            handle.setAttribute("role", "slider");
986            handle.setAttribute("aria-orientation", options.ort ? "vertical" : "horizontal");
987
988            if (handleNumber === 0) {
989                addClass(handle, options.cssClasses.handleLower);
990            } else if (handleNumber === options.handles - 1) {
991                addClass(handle, options.cssClasses.handleUpper);
992            }
993
994            return origin;
995        }
996
997        // Insert nodes for connect elements
998        function addConnect(base, add) {
999            if (!add) {
1000                return false;
1001            }
1002
1003            return addNodeTo(base, options.cssClasses.connect);
1004        }
1005
1006        // Add handles to the slider base.
1007        function addElements(connectOptions, base) {
1008            var connectBase = addNodeTo(base, options.cssClasses.connects);
1009
1010            scope_Handles = [];
1011            scope_Connects = [];
1012
1013            scope_Connects.push(addConnect(connectBase, connectOptions[0]));
1014
1015            // [::::O====O====O====]
1016            // connectOptions = [0, 1, 1, 1]
1017
1018            for (var i = 0; i < options.handles; i++) {
1019                // Keep a list of all added handles.
1020                scope_Handles.push(addOrigin(base, i));
1021                scope_HandleNumbers[i] = i;
1022                scope_Connects.push(addConnect(connectBase, connectOptions[i + 1]));
1023            }
1024        }
1025
1026        // Initialize a single slider.
1027        function addSlider(addTarget) {
1028            // Apply classes and data to the target.
1029            addClass(addTarget, options.cssClasses.target);
1030
1031            if (options.dir === 0) {
1032                addClass(addTarget, options.cssClasses.ltr);
1033            } else {
1034                addClass(addTarget, options.cssClasses.rtl);
1035            }
1036
1037            if (options.ort === 0) {
1038                addClass(addTarget, options.cssClasses.horizontal);
1039            } else {
1040                addClass(addTarget, options.cssClasses.vertical);
1041            }
1042
1043            return addNodeTo(addTarget, options.cssClasses.base);
1044        }
1045
1046        function addTooltip(handle, handleNumber) {
1047            if (!options.tooltips[handleNumber]) {
1048                return false;
1049            }
1050
1051            return addNodeTo(handle.firstChild, options.cssClasses.tooltip);
1052        }
1053
1054        // The tooltips option is a shorthand for using the 'update' event.
1055        function tooltips() {
1056            // Tooltips are added with options.tooltips in original order.
1057            var tips = scope_Handles.map(addTooltip);
1058
1059            bindEvent("update", function(values, handleNumber, unencoded) {
1060                if (!tips[handleNumber]) {
1061                    return;
1062                }
1063
1064                var formattedValue = values[handleNumber];
1065
1066                if (options.tooltips[handleNumber] !== true) {
1067                    formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]);
1068                }
1069
1070                tips[handleNumber].innerHTML = formattedValue;
1071            });
1072        }
1073
1074        function aria() {
1075            bindEvent("update", function(values, handleNumber, unencoded, tap, positions) {
1076                // Update Aria Values for all handles, as a change in one changes min and max values for the next.
1077                scope_HandleNumbers.forEach(function(index) {
1078                    var handle = scope_Handles[index];
1079
1080                    var min = checkHandlePosition(scope_Locations, index, 0, true, true, true);
1081                    var max = checkHandlePosition(scope_Locations, index, 100, true, true, true);
1082
1083                    var now = positions[index];
1084
1085                    // Formatted value for display
1086                    var text = options.ariaFormat.to(unencoded[index]);
1087
1088                    // Map to slider range values
1089                    min = scope_Spectrum.fromStepping(min).toFixed(1);
1090                    max = scope_Spectrum.fromStepping(max).toFixed(1);
1091                    now = scope_Spectrum.fromStepping(now).toFixed(1);
1092
1093                    handle.children[0].setAttribute("aria-valuemin", min);
1094                    handle.children[0].setAttribute("aria-valuemax", max);
1095                    handle.children[0].setAttribute("aria-valuenow", now);
1096                    handle.children[0].setAttribute("aria-valuetext", text);
1097                });
1098            });
1099        }
1100
1101        function getGroup(mode, values, stepped) {
1102            // Use the range.
1103            if (mode === "range" || mode === "steps") {
1104                return scope_Spectrum.xVal;
1105            }
1106
1107            if (mode === "count") {
1108                if (values < 2) {
1109                    throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'.");
1110                }
1111
1112                // Divide 0 - 100 in 'count' parts.
1113                var interval = values - 1;
1114                var spread = 100 / interval;
1115
1116                values = [];
1117
1118                // List these parts and have them handled as 'positions'.
1119                while (interval--) {
1120                    values[interval] = interval * spread;
1121                }
1122
1123                values.push(100);
1124
1125                mode = "positions";
1126            }
1127
1128            if (mode === "positions") {
1129                // Map all percentages to on-range values.
1130                return values.map(function(value) {
1131                    return scope_Spectrum.fromStepping(stepped ? scope_Spectrum.getStep(value) : value);
1132                });
1133            }
1134
1135            if (mode === "values") {
1136                // If the value must be stepped, it needs to be converted to a percentage first.
1137                if (stepped) {
1138                    return values.map(function(value) {
1139                        // Convert to percentage, apply step, return to value.
1140                        return scope_Spectrum.fromStepping(scope_Spectrum.getStep(scope_Spectrum.toStepping(value)));
1141                    });
1142                }
1143
1144                // Otherwise, we can simply use the values.
1145                return values;
1146            }
1147        }
1148
1149        function generateSpread(density, mode, group) {
1150            function safeIncrement(value, increment) {
1151                // Avoid floating point variance by dropping the smallest decimal places.
1152                return (value + increment).toFixed(7) / 1;
1153            }
1154
1155            var indexes = {};
1156            var firstInRange = scope_Spectrum.xVal[0];
1157            var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length - 1];
1158            var ignoreFirst = false;
1159            var ignoreLast = false;
1160            var prevPct = 0;
1161
1162            // Create a copy of the group, sort it and filter away all duplicates.
1163            group = unique(
1164                group.slice().sort(function(a, b) {
1165                    return a - b;
1166                })
1167            );
1168
1169            // Make sure the range starts with the first element.
1170            if (group[0] !== firstInRange) {
1171                group.unshift(firstInRange);
1172                ignoreFirst = true;
1173            }
1174
1175            // Likewise for the last one.
1176            if (group[group.length - 1] !== lastInRange) {
1177                group.push(lastInRange);
1178                ignoreLast = true;
1179            }
1180
1181            group.forEach(function(current, index) {
1182                // Get the current step and the lower + upper positions.
1183                var step;
1184                var i;
1185                var q;
1186                var low = current;
1187                var high = group[index + 1];
1188                var newPct;
1189                var pctDifference;
1190                var pctPos;
1191                var type;
1192                var steps;
1193                var realSteps;
1194                var stepSize;
1195                var isSteps = mode === "steps";
1196
1197                // When using 'steps' mode, use the provided steps.
1198                // Otherwise, we'll step on to the next subrange.
1199                if (isSteps) {
1200                    step = scope_Spectrum.xNumSteps[index];
1201                }
1202
1203                // Default to a 'full' step.
1204                if (!step) {
1205                    step = high - low;
1206                }
1207
1208                // Low can be 0, so test for false. If high is undefined,
1209                // we are at the last subrange. Index 0 is already handled.
1210                if (low === false || high === undefined) {
1211                    return;
1212                }
1213
1214                // Make sure step isn't 0, which would cause an infinite loop (#654)
1215                step = Math.max(step, 0.0000001);
1216
1217                // Find all steps in the subrange.
1218                for (i = low; i <= high; i = safeIncrement(i, step)) {
1219                    // Get the percentage value for the current step,
1220                    // calculate the size for the subrange.
1221                    newPct = scope_Spectrum.toStepping(i);
1222                    pctDifference = newPct - prevPct;
1223
1224                    steps = pctDifference / density;
1225                    realSteps = Math.round(steps);
1226
1227                    // This ratio represents the amount of percentage-space a point indicates.
1228                    // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-divided.
1229                    // Round the percentage offset to an even number, then divide by two
1230                    // to spread the offset on both sides of the range.
1231                    stepSize = pctDifference / realSteps;
1232
1233                    // Divide all points evenly, adding the correct number to this subrange.
1234                    // Run up to <= so that 100% gets a point, event if ignoreLast is set.
1235                    for (q = 1; q <= realSteps; q += 1) {
1236                        // The ratio between the rounded value and the actual size might be ~1% off.
1237                        // Correct the percentage offset by the number of points
1238                        // per subrange. density = 1 will result in 100 points on the
1239                        // full range, 2 for 50, 4 for 25, etc.
1240                        pctPos = prevPct + q * stepSize;
1241                        indexes[pctPos.toFixed(5)] = [scope_Spectrum.fromStepping(pctPos), 0];
1242                    }
1243
1244                    // Determine the point type.
1245                    type = group.indexOf(i) > -1 ? PIPS_LARGE_VALUE : isSteps ? PIPS_SMALL_VALUE : PIPS_NO_VALUE;
1246
1247                    // Enforce the 'ignoreFirst' option by overwriting the type for 0.
1248                    if (!index && ignoreFirst) {
1249                        type = 0;
1250                    }
1251
1252                    if (!(i === high && ignoreLast)) {
1253                        // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
1254                        indexes[newPct.toFixed(5)] = [i, type];
1255                    }
1256
1257                    // Update the percentage count.
1258                    prevPct = newPct;
1259                }
1260            });
1261
1262            return indexes;
1263        }
1264
1265        function addMarking(spread, filterFunc, formatter) {
1266            var element = scope_Document.createElement("div");
1267
1268            var valueSizeClasses = [];
1269            valueSizeClasses[PIPS_NO_VALUE] = options.cssClasses.valueNormal;
1270            valueSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.valueLarge;
1271            valueSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.valueSub;
1272
1273            var markerSizeClasses = [];
1274            markerSizeClasses[PIPS_NO_VALUE] = options.cssClasses.markerNormal;
1275            markerSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.markerLarge;
1276            markerSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.markerSub;
1277
1278            var valueOrientationClasses = [options.cssClasses.valueHorizontal, options.cssClasses.valueVertical];
1279            var markerOrientationClasses = [options.cssClasses.markerHorizontal, options.cssClasses.markerVertical];
1280
1281            addClass(element, options.cssClasses.pips);
1282            addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical);
1283
1284            function getClasses(type, source) {
1285                var a = source === options.cssClasses.value;
1286                var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses;
1287                var sizeClasses = a ? valueSizeClasses : markerSizeClasses;
1288
1289                return source + " " + orientationClasses[options.ort] + " " + sizeClasses[type];
1290            }
1291
1292            function addSpread(offset, value, type) {
1293                // Apply the filter function, if it is set.
1294                type = filterFunc ? filterFunc(value, type) : type;
1295
1296                if (type === PIPS_NONE) {
1297                    return;
1298                }
1299
1300                // Add a marker for every point
1301                var node = addNodeTo(element, false);
1302                node.className = getClasses(type, options.cssClasses.marker);
1303                node.style[options.style] = offset + "%";
1304
1305                // Values are only appended for points marked '1' or '2'.
1306                if (type > PIPS_NO_VALUE) {
1307                    node = addNodeTo(element, false);
1308                    node.className = getClasses(type, options.cssClasses.value);
1309                    node.setAttribute("data-value", value);
1310                    node.style[options.style] = offset + "%";
1311                    node.innerHTML = formatter.to(value);
1312                }
1313            }
1314
1315            // Append all points.
1316            Object.keys(spread).forEach(function(offset) {
1317                addSpread(offset, spread[offset][0], spread[offset][1]);
1318            });
1319
1320            return element;
1321        }
1322
1323        function removePips() {
1324            if (scope_Pips) {
1325                removeElement(scope_Pips);
1326                scope_Pips = null;
1327            }
1328        }
1329
1330        function pips(grid) {
1331            // Fix #669
1332            removePips();
1333
1334            var mode = grid.mode;
1335            var density = grid.density || 1;
1336            var filter = grid.filter || false;
1337            var values = grid.values || false;
1338            var stepped = grid.stepped || false;
1339            var group = getGroup(mode, values, stepped);
1340            var spread = generateSpread(density, mode, group);
1341            var format = grid.format || {
1342                to: Math.round
1343            };
1344
1345            scope_Pips = scope_Target.appendChild(addMarking(spread, filter, format));
1346
1347            return scope_Pips;
1348        }
1349
1350        // Shorthand for base dimensions.
1351        function baseSize() {
1352            var rect = scope_Base.getBoundingClientRect();
1353            var alt = "offset" + ["Width", "Height"][options.ort];
1354            return options.ort === 0 ? rect.width || scope_Base[alt] : rect.height || scope_Base[alt];
1355        }
1356
1357        // Handler for attaching events trough a proxy.
1358        function attachEvent(events, element, callback, data) {
1359            // This function can be used to 'filter' events to the slider.
1360            // element is a node, not a nodeList
1361
1362            var method = function(e) {
1363                e = fixEvent(e, data.pageOffset, data.target || element);
1364
1365                // fixEvent returns false if this event has a different target
1366                // when handling (multi-) touch events;
1367                if (!e) {
1368                    return false;
1369                }
1370
1371                // doNotReject is passed by all end events to make sure released touches
1372                // are not rejected, leaving the slider "stuck" to the cursor;
1373                if (scope_Target.hasAttribute("disabled") && !data.doNotReject) {
1374                    return false;
1375                }
1376
1377                // Stop if an active 'tap' transition is taking place.
1378                if (hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject) {
1379                    return false;
1380                }
1381
1382                // Ignore right or middle clicks on start #454
1383                if (events === actions.start && e.buttons !== undefined && e.buttons > 1) {
1384                    return false;
1385                }
1386
1387                // Ignore right or middle clicks on start #454
1388                if (data.hover && e.buttons) {
1389                    return false;
1390                }
1391
1392                // 'supportsPassive' is only true if a browser also supports touch-action: none in CSS.
1393                // iOS safari does not, so it doesn't get to benefit from passive scrolling. iOS does support
1394                // touch-action: manipulation, but that allows panning, which breaks
1395                // sliders after zooming/on non-responsive pages.
1396                // See: https://bugs.webkit.org/show_bug.cgi?id=133112
1397                if (!supportsPassive) {
1398                    e.preventDefault();
1399                }
1400
1401                e.calcPoint = e.points[options.ort];
1402
1403                // Call the event handler with the event [ and additional data ].
1404                callback(e, data);
1405            };
1406
1407            var methods = [];
1408
1409            // Bind a closure on the target for every event type.
1410            events.split(" ").forEach(function(eventName) {
1411                element.addEventListener(eventName, method, supportsPassive ? { passive: true } : false);
1412                methods.push([eventName, method]);
1413            });
1414
1415            return methods;
1416        }
1417
1418        // Provide a clean event with standardized offset values.
1419        function fixEvent(e, pageOffset, eventTarget) {
1420            // Filter the event to register the type, which can be
1421            // touch, mouse or pointer. Offset changes need to be
1422            // made on an event specific basis.
1423            var touch = e.type.indexOf("touch") === 0;
1424            var mouse = e.type.indexOf("mouse") === 0;
1425            var pointer = e.type.indexOf("pointer") === 0;
1426
1427            var x;
1428            var y;
1429
1430            // IE10 implemented pointer events with a prefix;
1431            if (e.type.indexOf("MSPointer") === 0) {
1432                pointer = true;
1433            }
1434
1435            // In the event that multitouch is activated, the only thing one handle should be concerned
1436            // about is the touches that originated on top of it.
1437            if (touch) {
1438                // Returns true if a touch originated on the target.
1439                var isTouchOnTarget = function(checkTouch) {
1440                    return checkTouch.target === eventTarget || eventTarget.contains(checkTouch.target);
1441                };
1442
1443                // In the case of touchstart events, we need to make sure there is still no more than one
1444                // touch on the target so we look amongst all touches.
1445                if (e.type === "touchstart") {
1446                    var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget);
1447
1448                    // Do not support more than one touch per handle.
1449                    if (targetTouches.length > 1) {
1450                        return false;
1451                    }
1452
1453                    x = targetTouches[0].pageX;
1454                    y = targetTouches[0].pageY;
1455                } else {
1456                    // In the other cases, find on changedTouches is enough.
1457                    var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget);
1458
1459                    // Cancel if the target touch has not moved.
1460                    if (!targetTouch) {
1461                        return false;
1462                    }
1463
1464                    x = targetTouch.pageX;
1465                    y = targetTouch.pageY;
1466                }
1467            }
1468
1469            pageOffset = pageOffset || getPageOffset(scope_Document);
1470
1471            if (mouse || pointer) {
1472                x = e.clientX + pageOffset.x;
1473                y = e.clientY + pageOffset.y;
1474            }
1475
1476            e.pageOffset = pageOffset;
1477            e.points = [x, y];
1478            e.cursor = mouse || pointer; // Fix #435
1479
1480            return e;
1481        }
1482
1483        // Translate a coordinate in the document to a percentage on the slider
1484        function calcPointToPercentage(calcPoint) {
1485            var location = calcPoint - offset(scope_Base, options.ort);
1486            var proposal = (location * 100) / baseSize();
1487
1488            // Clamp proposal between 0% and 100%
1489            // Out-of-bound coordinates may occur when .noUi-base pseudo-elements
1490            // are used (e.g. contained handles feature)
1491            proposal = limit(proposal);
1492
1493            return options.dir ? 100 - proposal : proposal;
1494        }
1495
1496        // Find handle closest to a certain percentage on the slider
1497        function getClosestHandle(proposal) {
1498            var closest = 100;
1499            var handleNumber = false;
1500
1501            scope_Handles.forEach(function(handle, index) {
1502                // Disabled handles are ignored
1503                if (handle.hasAttribute("disabled")) {
1504                    return;
1505                }
1506
1507                var pos = Math.abs(scope_Locations[index] - proposal);
1508
1509                if (pos < closest || (pos === 100 && closest === 100)) {
1510                    handleNumber = index;
1511                    closest = pos;
1512                }
1513            });
1514
1515            return handleNumber;
1516        }
1517
1518        // Fire 'end' when a mouse or pen leaves the document.
1519        function documentLeave(event, data) {
1520            if (event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null) {
1521                eventEnd(event, data);
1522            }
1523        }
1524
1525        // Handle movement on document for handle and range drag.
1526        function eventMove(event, data) {
1527            // Fix #498
1528            // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
1529            // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
1530            // IE9 has .buttons and .which zero on mousemove.
1531            // Firefox breaks the spec MDN defines.
1532            if (navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0) {
1533                return eventEnd(event, data);
1534            }
1535
1536            // Check if we are moving up or down
1537            var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint);
1538
1539            // Convert the movement into a percentage of the slider width/height
1540            var proposal = (movement * 100) / data.baseSize;
1541
1542            moveHandles(movement > 0, proposal, data.locations, data.handleNumbers);
1543        }
1544
1545        // Unbind move events on document, call callbacks.
1546        function eventEnd(event, data) {
1547            // The handle is no longer active, so remove the class.
1548            if (data.handle) {
1549                removeClass(data.handle, options.cssClasses.active);
1550                scope_ActiveHandlesCount -= 1;
1551            }
1552
1553            // Unbind the move and end events, which are added on 'start'.
1554            data.listeners.forEach(function(c) {
1555                scope_DocumentElement.removeEventListener(c[0], c[1]);
1556            });
1557
1558            if (scope_ActiveHandlesCount === 0) {
1559                // Remove dragging class.
1560                removeClass(scope_Target, options.cssClasses.drag);
1561                setZindex();
1562
1563                // Remove cursor styles and text-selection events bound to the body.
1564                if (event.cursor) {
1565                    scope_Body.style.cursor = "";
1566                    scope_Body.removeEventListener("selectstart", preventDefault);
1567                }
1568            }
1569
1570            data.handleNumbers.forEach(function(handleNumber) {
1571                fireEvent("change", handleNumber);
1572                fireEvent("set", handleNumber);
1573                fireEvent("end", handleNumber);
1574            });
1575        }
1576
1577        // Bind move events on document.
1578        function eventStart(event, data) {
1579            var handle;
1580            if (data.handleNumbers.length === 1) {
1581                var handleOrigin = scope_Handles[data.handleNumbers[0]];
1582
1583                // Ignore 'disabled' handles
1584                if (handleOrigin.hasAttribute("disabled")) {
1585                    return false;
1586                }
1587
1588                handle = handleOrigin.children[0];
1589                scope_ActiveHandlesCount += 1;
1590
1591                // Mark the handle as 'active' so it can be styled.
1592                addClass(handle, options.cssClasses.active);
1593            }
1594
1595            // A drag should never propagate up to the 'tap' event.
1596            event.stopPropagation();
1597
1598            // Record the event listeners.
1599            var listeners = [];
1600
1601            // Attach the move and end events.
1602            var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, {
1603                // The event target has changed so we need to propagate the original one so that we keep
1604                // relying on it to extract target touches.
1605                target: event.target,
1606                handle: handle,
1607                listeners: listeners,
1608                startCalcPoint: event.calcPoint,
1609                baseSize: baseSize(),
1610                pageOffset: event.pageOffset,
1611                handleNumbers: data.handleNumbers,
1612                buttonsProperty: event.buttons,
1613                locations: scope_Locations.slice()
1614            });
1615
1616            var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, {
1617                target: event.target,
1618                handle: handle,
1619                listeners: listeners,
1620                doNotReject: true,
1621                handleNumbers: data.handleNumbers
1622            });
1623
1624            var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, {
1625                target: event.target,
1626                handle: handle,
1627                listeners: listeners,
1628                doNotReject: true,
1629                handleNumbers: data.handleNumbers
1630            });
1631
1632            // We want to make sure we pushed the listeners in the listener list rather than creating
1633            // a new one as it has already been passed to the event handlers.
1634            listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent));
1635
1636            // Text selection isn't an issue on touch devices,
1637            // so adding cursor styles can be skipped.
1638            if (event.cursor) {
1639                // Prevent the 'I' cursor and extend the range-drag cursor.
1640                scope_Body.style.cursor = getComputedStyle(event.target).cursor;
1641
1642                // Mark the target with a dragging state.
1643                if (scope_Handles.length > 1) {
1644                    addClass(scope_Target, options.cssClasses.drag);
1645                }
1646
1647                // Prevent text selection when dragging the handles.
1648                // In noUiSlider <= 9.2.0, this was handled by calling preventDefault on mouse/touch start/move,
1649                // which is scroll blocking. The selectstart event is supported by FireFox starting from version 52,
1650                // meaning the only holdout is iOS Safari. This doesn't matter: text selection isn't triggered there.
1651                // The 'cursor' flag is false.
1652                // See: http://caniuse.com/#search=selectstart
1653                scope_Body.addEventListener("selectstart", preventDefault, false);
1654            }
1655
1656            data.handleNumbers.forEach(function(handleNumber) {
1657                fireEvent("start", handleNumber);
1658            });
1659        }
1660
1661        // Move closest handle to tapped location.
1662        function eventTap(event) {
1663            // The tap event shouldn't propagate up
1664            event.stopPropagation();
1665
1666            var proposal = calcPointToPercentage(event.calcPoint);
1667            var handleNumber = getClosestHandle(proposal);
1668
1669            // Tackle the case that all handles are 'disabled'.
1670            if (handleNumber === false) {
1671                return false;
1672            }
1673
1674            // Flag the slider as it is now in a transitional state.
1675            // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
1676            if (!options.events.snap) {
1677                addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
1678            }
1679
1680            setHandle(handleNumber, proposal, true, true);
1681
1682            setZindex();
1683
1684            fireEvent("slide", handleNumber, true);
1685            fireEvent("update", handleNumber, true);
1686            fireEvent("change", handleNumber, true);
1687            fireEvent("set", handleNumber, true);
1688
1689            if (options.events.snap) {
1690                eventStart(event, { handleNumbers: [handleNumber] });
1691            }
1692        }
1693
1694        // Fires a 'hover' event for a hovered mouse/pen position.
1695        function eventHover(event) {
1696            var proposal = calcPointToPercentage(event.calcPoint);
1697
1698            var to = scope_Spectrum.getStep(proposal);
1699            var value = scope_Spectrum.fromStepping(to);
1700
1701            Object.keys(scope_Events).forEach(function(targetEvent) {
1702                if ("hover" === targetEvent.split(".")[0]) {
1703                    scope_Events[targetEvent].forEach(function(callback) {
1704                        callback.call(scope_Self, value);
1705                    });
1706                }
1707            });
1708        }
1709
1710        // Attach events to several slider parts.
1711        function bindSliderEvents(behaviour) {
1712            // Attach the standard drag event to the handles.
1713            if (!behaviour.fixed) {
1714                scope_Handles.forEach(function(handle, index) {
1715                    // These events are only bound to the visual handle
1716                    // element, not the 'real' origin element.
1717                    attachEvent(actions.start, handle.children[0], eventStart, {
1718                        handleNumbers: [index]
1719                    });
1720                });
1721            }
1722
1723            // Attach the tap event to the slider base.
1724            if (behaviour.tap) {
1725                attachEvent(actions.start, scope_Base, eventTap, {});
1726            }
1727
1728            // Fire hover events
1729            if (behaviour.hover) {
1730                attachEvent(actions.move, scope_Base, eventHover, {
1731                    hover: true
1732                });
1733            }
1734
1735            // Make the range draggable.
1736            if (behaviour.drag) {
1737                scope_Connects.forEach(function(connect, index) {
1738                    if (connect === false || index === 0 || index === scope_Connects.length - 1) {
1739                        return;
1740                    }
1741
1742                    var handleBefore = scope_Handles[index - 1];
1743                    var handleAfter = scope_Handles[index];
1744                    var eventHolders = [connect];
1745
1746                    addClass(connect, options.cssClasses.draggable);
1747
1748                    // When the range is fixed, the entire range can
1749                    // be dragged by the handles. The handle in the first
1750                    // origin will propagate the start event upward,
1751                    // but it needs to be bound manually on the other.
1752                    if (behaviour.fixed) {
1753                        eventHolders.push(handleBefore.children[0]);
1754                        eventHolders.push(handleAfter.children[0]);
1755                    }
1756
1757                    eventHolders.forEach(function(eventHolder) {
1758                        attachEvent(actions.start, eventHolder, eventStart, {
1759                            handles: [handleBefore, handleAfter],
1760                            handleNumbers: [index - 1, index]
1761                        });
1762                    });
1763                });
1764            }
1765        }
1766
1767        // Attach an event to this slider, possibly including a namespace
1768        function bindEvent(namespacedEvent, callback) {
1769            scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
1770            scope_Events[namespacedEvent].push(callback);
1771
1772            // If the event bound is 'update,' fire it immediately for all handles.
1773            if (namespacedEvent.split(".")[0] === "update") {
1774                scope_Handles.forEach(function(a, index) {
1775                    fireEvent("update", index);
1776                });
1777            }
1778        }
1779
1780        // Undo attachment of event
1781        function removeEvent(namespacedEvent) {
1782            var event = namespacedEvent && namespacedEvent.split(".")[0];
1783            var namespace = event && namespacedEvent.substring(event.length);
1784
1785            Object.keys(scope_Events).forEach(function(bind) {
1786                var tEvent = bind.split(".")[0];
1787                var tNamespace = bind.substring(tEvent.length);
1788
1789                if ((!event || event === tEvent) && (!namespace || namespace === tNamespace)) {
1790                    delete scope_Events[bind];
1791                }
1792            });
1793        }
1794
1795        // External event handling
1796        function fireEvent(eventName, handleNumber, tap) {
1797            Object.keys(scope_Events).forEach(function(targetEvent) {
1798                var eventType = targetEvent.split(".")[0];
1799
1800                if (eventName === eventType) {
1801                    scope_Events[targetEvent].forEach(function(callback) {
1802                        callback.call(
1803                            // Use the slider public API as the scope ('this')
1804                            scope_Self,
1805                            // Return values as array, so arg_1[arg_2] is always valid.
1806                            scope_Values.map(options.format.to),
1807                            // Handle index, 0 or 1
1808                            handleNumber,
1809                            // Un-formatted slider values
1810                            scope_Values.slice(),
1811                            // Event is fired by tap, true or false
1812                            tap || false,
1813                            // Left offset of the handle, in relation to the slider
1814                            scope_Locations.slice()
1815                        );
1816                    });
1817                }
1818            });
1819        }
1820
1821        function toPct(pct) {
1822            return pct + "%";
1823        }
1824
1825        // Split out the handle positioning logic so the Move event can use it, too
1826        function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) {
1827            // For sliders with multiple handles, limit movement to the other handle.
1828            // Apply the margin option by adding it to the handle positions.
1829            if (scope_Handles.length > 1) {
1830                if (lookBackward && handleNumber > 0) {
1831                    to = Math.max(to, reference[handleNumber - 1] + options.margin);
1832                }
1833
1834                if (lookForward && handleNumber < scope_Handles.length - 1) {
1835                    to = Math.min(to, reference[handleNumber + 1] - options.margin);
1836                }
1837            }
1838
1839            // The limit option has the opposite effect, limiting handles to a
1840            // maximum distance from another. Limit must be > 0, as otherwise
1841            // handles would be unmovable.
1842            if (scope_Handles.length > 1 && options.limit) {
1843                if (lookBackward && handleNumber > 0) {
1844                    to = Math.min(to, reference[handleNumber - 1] + options.limit);
1845                }
1846
1847                if (lookForward && handleNumber < scope_Handles.length - 1) {
1848                    to = Math.max(to, reference[handleNumber + 1] - options.limit);
1849                }
1850            }
1851
1852            // The padding option keeps the handles a certain distance from the
1853            // edges of the slider. Padding must be > 0.
1854            if (options.padding) {
1855                if (handleNumber === 0) {
1856                    to = Math.max(to, options.padding[0]);
1857                }
1858
1859                if (handleNumber === scope_Handles.length - 1) {
1860                    to = Math.min(to, 100 - options.padding[1]);
1861                }
1862            }
1863
1864            to = scope_Spectrum.getStep(to);
1865
1866            // Limit percentage to the 0 - 100 range
1867            to = limit(to);
1868
1869            // Return false if handle can't move
1870            if (to === reference[handleNumber] && !getValue) {
1871                return false;
1872            }
1873
1874            return to;
1875        }
1876
1877        // Uses slider orientation to create CSS rules. a = base value;
1878        function inRuleOrder(v, a) {
1879            var o = options.ort;
1880            return (o ? a : v) + ", " + (o ? v : a);
1881        }
1882
1883        // Moves handle(s) by a percentage
1884        // (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...])
1885        function moveHandles(upward, proposal, locations, handleNumbers) {
1886            var proposals = locations.slice();
1887
1888            var b = [!upward, upward];
1889            var f = [upward, !upward];
1890
1891            // Copy handleNumbers so we don't change the dataset
1892            handleNumbers = handleNumbers.slice();
1893
1894            // Check to see which handle is 'leading'.
1895            // If that one can't move the second can't either.
1896            if (upward) {
1897                handleNumbers.reverse();
1898            }
1899
1900            // Step 1: get the maximum percentage that any of the handles can move
1901            if (handleNumbers.length > 1) {
1902                handleNumbers.forEach(function(handleNumber, o) {
1903                    var to = checkHandlePosition(
1904                        proposals,
1905                        handleNumber,
1906                        proposals[handleNumber] + proposal,
1907                        b[o],
1908                        f[o],
1909                        false
1910                    );
1911
1912                    // Stop if one of the handles can't move.
1913                    if (to === false) {
1914                        proposal = 0;
1915                    } else {
1916                        proposal = to - proposals[handleNumber];
1917                        proposals[handleNumber] = to;
1918                    }
1919                });
1920            }
1921
1922            // If using one handle, check backward AND forward
1923            else {
1924                b = f = [true];
1925            }
1926
1927            var state = false;
1928
1929            // Step 2: Try to set the handles with the found percentage
1930            handleNumbers.forEach(function(handleNumber, o) {
1931                state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state;
1932            });
1933
1934            // Step 3: If a handle moved, fire events
1935            if (state) {
1936                handleNumbers.forEach(function(handleNumber) {
1937                    fireEvent("update", handleNumber);
1938                    fireEvent("slide", handleNumber);
1939                });
1940            }
1941        }
1942
1943        // Takes a base value and an offset. This offset is used for the connect bar size.
1944        // In the initial design for this feature, the origin element was 1% wide.
1945        // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature
1946        // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223
1947        function transformDirection(a, b) {
1948            return options.dir ? 100 - a - b : a;
1949        }
1950
1951        // Updates scope_Locations and scope_Values, updates visual state
1952        function updateHandlePosition(handleNumber, to) {
1953            // Update locations.
1954            scope_Locations[handleNumber] = to;
1955
1956            // Convert the value to the slider stepping/range.
1957            scope_Values[handleNumber] = scope_Spectrum.fromStepping(to);
1958
1959            var rule = "translate(" + inRuleOrder(toPct(transformDirection(to, 0) - scope_DirOffset), "0") + ")";
1960            scope_Handles[handleNumber].style[options.transformRule] = rule;
1961
1962            updateConnect(handleNumber);
1963            updateConnect(handleNumber + 1);
1964        }
1965
1966        // Handles before the slider middle are stacked later = higher,
1967        // Handles after the middle later is lower
1968        // [[7] [8] .......... | .......... [5] [4]
1969        function setZindex() {
1970            scope_HandleNumbers.forEach(function(handleNumber) {
1971                var dir = scope_Locations[handleNumber] > 50 ? -1 : 1;
1972                var zIndex = 3 + (scope_Handles.length + dir * handleNumber);
1973                scope_Handles[handleNumber].style.zIndex = zIndex;
1974            });
1975        }
1976
1977        // Test suggested values and apply margin, step.
1978        function setHandle(handleNumber, to, lookBackward, lookForward) {
1979            to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false);
1980
1981            if (to === false) {
1982                return false;
1983            }
1984
1985            updateHandlePosition(handleNumber, to);
1986
1987            return true;
1988        }
1989
1990        // Updates style attribute for connect nodes
1991        function updateConnect(index) {
1992            // Skip connects set to false
1993            if (!scope_Connects[index]) {
1994                return;
1995            }
1996
1997            var l = 0;
1998            var h = 100;
1999
2000            if (index !== 0) {
2001                l = scope_Locations[index - 1];
2002            }
2003
2004            if (index !== scope_Connects.length - 1) {
2005                h = scope_Locations[index];
2006            }
2007
2008            // We use two rules:
2009            // 'translate' to change the left/top offset;
2010            // 'scale' to change the width of the element;
2011            // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base)
2012            var connectWidth = h - l;
2013            var translateRule = "translate(" + inRuleOrder(toPct(transformDirection(l, connectWidth)), "0") + ")";
2014            var scaleRule = "scale(" + inRuleOrder(connectWidth / 100, "1") + ")";
2015
2016            scope_Connects[index].style[options.transformRule] = translateRule + " " + scaleRule;
2017        }
2018
2019        // Parses value passed to .set method. Returns current value if not parse-able.
2020        function resolveToValue(to, handleNumber) {
2021            // Setting with null indicates an 'ignore'.
2022            // Inputting 'false' is invalid.
2023            if (to === null || to === false || to === undefined) {
2024                return scope_Locations[handleNumber];
2025            }
2026
2027            // If a formatted number was passed, attempt to decode it.
2028            if (typeof to === "number") {
2029                to = String(to);
2030            }
2031
2032            to = options.format.from(to);
2033            to = scope_Spectrum.toStepping(to);
2034
2035            // If parsing the number failed, use the current value.
2036            if (to === false || isNaN(to)) {
2037                return scope_Locations[handleNumber];
2038            }
2039
2040            return to;
2041        }
2042
2043        // Set the slider value.
2044        function valueSet(input, fireSetEvent) {
2045            var values = asArray(input);
2046            var isInit = scope_Locations[0] === undefined;
2047
2048            // Event fires by default
2049            fireSetEvent = fireSetEvent === undefined ? true : !!fireSetEvent;
2050
2051            // Animation is optional.
2052            // Make sure the initial values were set before using animated placement.
2053            if (options.animate && !isInit) {
2054                addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
2055            }
2056
2057            // First pass, without lookAhead but with lookBackward. Values are set from left to right.
2058            scope_HandleNumbers.forEach(function(handleNumber) {
2059                setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false);
2060            });
2061
2062            // Second pass. Now that all base values are set, apply constraints
2063            scope_HandleNumbers.forEach(function(handleNumber) {
2064                setHandle(handleNumber, scope_Locations[handleNumber], true, true);
2065            });
2066
2067            setZindex();
2068
2069            scope_HandleNumbers.forEach(function(handleNumber) {
2070                fireEvent("update", handleNumber);
2071
2072                // Fire the event only for handles that received a new value, as per #579
2073                if (values[handleNumber] !== null && fireSetEvent) {
2074                    fireEvent("set", handleNumber);
2075                }
2076            });
2077        }
2078
2079        // Reset slider to initial values
2080        function valueReset(fireSetEvent) {
2081            valueSet(options.start, fireSetEvent);
2082        }
2083
2084        // Get the slider value.
2085        function valueGet() {
2086            var values = scope_Values.map(options.format.to);
2087
2088            // If only one handle is used, return a single value.
2089            if (values.length === 1) {
2090                return values[0];
2091            }
2092
2093            return values;
2094        }
2095
2096        // Removes classes from the root and empties it.
2097        function destroy() {
2098            for (var key in options.cssClasses) {
2099                if (!options.cssClasses.hasOwnProperty(key)) {
2100                    continue;
2101                }
2102                removeClass(scope_Target, options.cssClasses[key]);
2103            }
2104
2105            while (scope_Target.firstChild) {
2106                scope_Target.removeChild(scope_Target.firstChild);
2107            }
2108
2109            delete scope_Target.noUiSlider;
2110        }
2111
2112        // Get the current step size for the slider.
2113        function getCurrentStep() {
2114            // Check all locations, map them to their stepping point.
2115            // Get the step point, then find it in the input list.
2116            return scope_Locations.map(function(location, index) {
2117                var nearbySteps = scope_Spectrum.getNearbySteps(location);
2118                var value = scope_Values[index];
2119                var increment = nearbySteps.thisStep.step;
2120                var decrement = null;
2121
2122                // If the next value in this step moves into the next step,
2123                // the increment is the start of the next step - the current value
2124                if (increment !== false) {
2125                    if (value + increment > nearbySteps.stepAfter.startValue) {
2126                        increment = nearbySteps.stepAfter.startValue - value;
2127                    }
2128                }
2129
2130                // If the value is beyond the starting point
2131                if (value > nearbySteps.thisStep.startValue) {
2132                    decrement = nearbySteps.thisStep.step;
2133                } else if (nearbySteps.stepBefore.step === false) {
2134                    decrement = false;
2135                }
2136
2137                // If a handle is at the start of a step, it always steps back into the previous step first
2138                else {
2139                    decrement = value - nearbySteps.stepBefore.highestStep;
2140                }
2141
2142                // Now, if at the slider edges, there is not in/decrement
2143                if (location === 100) {
2144                    increment = null;
2145                } else if (location === 0) {
2146                    decrement = null;
2147                }
2148
2149                // As per #391, the comparison for the decrement step can have some rounding issues.
2150                var stepDecimals = scope_Spectrum.countStepDecimals();
2151
2152                // Round per #391
2153                if (increment !== null && increment !== false) {
2154                    increment = Number(increment.toFixed(stepDecimals));
2155                }
2156
2157                if (decrement !== null && decrement !== false) {
2158                    decrement = Number(decrement.toFixed(stepDecimals));
2159                }
2160
2161                return [decrement, increment];
2162            });
2163        }
2164
2165        // Updateable: margin, limit, padding, step, range, animate, snap
2166        function updateOptions(optionsToUpdate, fireSetEvent) {
2167            // Spectrum is created using the range, snap, direction and step options.
2168            // 'snap' and 'step' can be updated.
2169            // If 'snap' and 'step' are not passed, they should remain unchanged.
2170            var v = valueGet();
2171
2172            var updateAble = ["margin", "limit", "padding", "range", "animate", "snap", "step", "format"];
2173
2174            // Only change options that we're actually passed to update.
2175            updateAble.forEach(function(name) {
2176                if (optionsToUpdate[name] !== undefined) {
2177                    originalOptions[name] = optionsToUpdate[name];
2178                }
2179            });
2180
2181            var newOptions = testOptions(originalOptions);
2182
2183            // Load new options into the slider state
2184            updateAble.forEach(function(name) {
2185                if (optionsToUpdate[name] !== undefined) {
2186                    options[name] = newOptions[name];
2187                }
2188            });
2189
2190            scope_Spectrum = newOptions.spectrum;
2191
2192            // Limit, margin and padding depend on the spectrum but are stored outside of it. (#677)
2193            options.margin = newOptions.margin;
2194            options.limit = newOptions.limit;
2195            options.padding = newOptions.padding;
2196
2197            // Update pips, removes existing.
2198            if (options.pips) {
2199                pips(options.pips);
2200            }
2201
2202            // Invalidate the current positioning so valueSet forces an update.
2203            scope_Locations = [];
2204            valueSet(optionsToUpdate.start || v, fireSetEvent);
2205        }
2206
2207        // Create the base element, initialize HTML and set classes.
2208        // Add handles and connect elements.
2209        scope_Base = addSlider(scope_Target);
2210        addElements(options.connect, scope_Base);
2211
2212        // Attach user events.
2213        bindSliderEvents(options.events);
2214
2215        // Use the public value method to set the start values.
2216        valueSet(options.start);
2217
2218        // noinspection JSUnusedGlobalSymbols
2219        scope_Self = {
2220            destroy: destroy,
2221            steps: getCurrentStep,
2222            on: bindEvent,
2223            off: removeEvent,
2224            get: valueGet,
2225            set: valueSet,
2226            reset: valueReset,
2227            // Exposed for unit testing, don't use this in your application.
2228            __moveHandles: function(a, b, c) {
2229                moveHandles(a, b, scope_Locations, c);
2230            },
2231            options: originalOptions, // Issue #600, #678
2232            updateOptions: updateOptions,
2233            target: scope_Target, // Issue #597
2234            removePips: removePips,
2235            pips: pips // Issue #594
2236        };
2237
2238        if (options.pips) {
2239            pips(options.pips);
2240        }
2241
2242        if (options.tooltips) {
2243            tooltips();
2244        }
2245
2246        aria();
2247
2248        return scope_Self;
2249    }
2250
2251    // Run the standard initializer
2252    function initialize(target, originalOptions) {
2253        if (!target || !target.nodeName) {
2254            throw new Error("noUiSlider (" + VERSION + "): create requires a single element, got: " + target);
2255        }
2256
2257        // Throw an error if the slider was already initialized.
2258        if (target.noUiSlider) {
2259            throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized.");
2260        }
2261
2262        // Test the options and create the slider environment;
2263        var options = testOptions(originalOptions, target);
2264        var api = scope(target, options, originalOptions);
2265
2266        target.noUiSlider = api;
2267
2268        return api;
2269    }
2270
2271    // Use an object instead of a function for future expandability;
2272    return {
2273        // Exposed for unit testing, don't use this in your application.
2274        __spectrum: Spectrum,
2275        version: VERSION,
2276        create: initialize
2277    };
2278});

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.