UNPKG

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