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