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