UNPKG

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