UNPKG

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