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