PageSourceSearch

https://www.bellingcat.com/app/plugins/before-after-image-slider-l…/nouislider-14.6.2/nouislider.js?ver=14.6.2

js bellingcat.com collected 2026-09-24 06:16:33 UTC 98,567 bytes, 2,676 lines download raw bytes

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

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.