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