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