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