PageSourceSearch

https://xn----utbjjici.xn--p1ai/local/templates/main_catalog/plugins/noUiSlider-14.6.3/nouislider.js

js xn----utbjjici.xn--p1ai collected 2026-09-28 11:31:48 UTC 99,355 bytes, 2,693 lines download raw bytes

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

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.