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