https://xn----utbjjici.xn--p1ai/local/templates/main_catalog/plugins/noUiSlider-14.6.3/nouislider.js
vendor: 9,398 bytes, lines 1-312
1/*! nouislider - 14.6.3 - 11/19/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.3"; 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 // Namespaces of internal event listeners 637 var INTERNAL_EVENT_NS = { 638 tooltips: ".__tooltips", 639 aria: ".__aria" 640 }; 641 642 //endregion 643 644 function validateFormat(entry) { 645 // Any object with a to and from method is supported. 646 if (isValidFormatter(entry)) { 647 return true; 648 } 649 650 throw new Error("noUiSlider (" + VERSION + "): 'format' requires 'to' and 'from' methods."); 651 } 652 653 function testStep(parsed, entry) { 654 if (!isNumeric(entry)) { 655 throw new Error("noUiSlider (" + VERSION + "): 'step' is not numeric."); 656 } 657 658 // The step option can still be used to set stepping 659 // for linear sliders. Overwritten if set in 'range'. 660 parsed.singleStep = entry; 661 } 662 663 function testKeyboardPageMultiplier(parsed, entry) { 664 if (!isNumeric(entry)) { 665 throw new Error("noUiSlider (" + VERSION + "): 'keyboardPageMultiplier' is not numeric."); 666 } 667 668 parsed.keyboardPageMultiplier = entry; 669 } 670 671 function testKeyboardDefaultStep(parsed, entry) { 672 if (!isNumeric(entry)) { 673 throw new Error("noUiSlider (" + VERSION + "): 'keyboardDefaultStep' is not numeric."); 674 } 675 676 parsed.keyboardDefaultStep = entry; 677 } 678 679 function testRange(parsed, entry) { 680 // Filter incorrect input. 681 if (typeof entry !== "object" || Array.isArray(entry)) { 682 throw new Error("noUiSlider (" + VERSION + "): 'range' is not an object."); 683 } 684 685 // Catch missing start or end. 686 if (entry.min === undefined || entry.max === undefined) { 687 throw new Error("noUiSlider (" + VERSION + "): Missing 'min' or 'max' in 'range'."); 688 } 689 690 // Catch equal start or end. 691 if (entry.min === entry.max) { 692 throw new Error("noUiSlider (" + VERSION + "): 'range' 'min' and 'max' cannot be equal."); 693 } 694 695 parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.singleStep); 696 } 697 698 function testStart(parsed, entry) { 699 entry = asArray(entry); 700 701 // Validate input. Values aren't tested, as the public .val method 702 // will always provide a valid location. 703 if (!Array.isArray(entry) || !entry.length) { 704 throw new Error("noUiSlider (" + VERSION + "): 'start' option is incorrect."); 705 } 706 707 // Store the number of handles. 708 parsed.handles = entry.length; 709 710 // When the slider is initialized, the .val method will 711 // be called with the start options. 712 parsed.start = entry; 713 } 714 715 function testSnap(parsed, entry) { 716 // Enforce 100% stepping within subranges. 717 parsed.snap = entry; 718 719 if (typeof entry !== "boolean") { 720 throw new Error("noUiSlider (" + VERSION + "): 'snap' option must be a boolean."); 721 } 722 } 723 724 function testAnimate(parsed, entry) { 725 // Enforce 100% stepping within subranges. 726 parsed.animate = entry; 727 728 if (typeof entry !== "boolean") { 729 throw new Error("noUiSlider (" + VERSION + "): 'animate' option must be a boolean."); 730 } 731 } 732 733 function testAnimationDuration(parsed, entry) { 734 parsed.animationDuration = entry; 735 736 if (typeof entry !== "number") { 737 throw new Error("noUiSlider (" + VERSION + "): 'animationDuration' option must be a number."); 738 } 739 } 740 741 function testConnect(parsed, entry) { 742 var connect = [false]; 743 var i; 744 745 // Map legacy options 746 if (entry === "lower") { 747 entry = [true, false]; 748 } else if (entry === "upper") {
749 entry = [false, true]; 750 } 751 752 // Handle boolean options 753 if (entry === true || entry === false) { 754 for (i = 1; i < parsed.handles; i++) { 755 connect.push(entry); 756 } 757 758 connect.push(false); 759 } 760 761 // Reject invalid input 762 else if (!Array.isArray(entry) || !entry.length || entry.length !== parsed.handles + 1) { 763 throw new Error("noUiSlider (" + VERSION + "): 'connect' option doesn't match handle count."); 764 } else { 765 connect = entry; 766 } 767 768 parsed.connect = connect; 769 } 770 771 function testOrientation(parsed, entry) { 772 // Set orientation to an a numerical value for easy 773 // array selection. 774 switch (entry) { 775 case "horizontal": 776 parsed.ort = 0; 777 break; 778 case "vertical": 779 parsed.ort = 1; 780 break; 781 default: 782 throw new Error("noUiSlider (" + VERSION + "): 'orientation' option is invalid."); 783 } 784 } 785 786 function testMargin(parsed, entry) { 787 if (!isNumeric(entry)) { 788 throw new Error("noUiSlider (" + VERSION + "): 'margin' option must be numeric."); 789 } 790 791 // Issue #582 792 if (entry === 0) { 793 return; 794 } 795 796 parsed.margin = parsed.spectrum.getDistance(entry); 797 } 798 799 function testLimit(parsed, entry) { 800 if (!isNumeric(entry)) { 801 throw new Error("noUiSlider (" + VERSION + "): 'limit' option must be numeric."); 802 } 803 804 parsed.limit = parsed.spectrum.getDistance(entry); 805 806 if (!parsed.limit || parsed.handles < 2) { 807 throw new Error( 808 "noUiSlider (" + 809 VERSION + 810 "): 'limit' option is only supported on linear sliders with 2 or more handles." 811 ); 812 } 813 } 814 815 function testPadding(parsed, entry) { 816 var index; 817 818 if (!isNumeric(entry) && !Array.isArray(entry)) { 819 throw new Error( 820 "noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers." 821 ); 822 } 823 824 if (Array.isArray(entry) && !(entry.length === 2 || isNumeric(entry[0]) || isNumeric(entry[1]))) { 825 throw new Error( 826 "noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers." 827 ); 828 } 829 830 if (entry === 0) { 831 return; 832 } 833 834 if (!Array.isArray(entry)) { 835 entry = [entry, entry]; 836 } 837 838 // 'getDistance' returns false for invalid values. 839 parsed.padding = [parsed.spectrum.getDistance(entry[0]), parsed.spectrum.getDistance(entry[1])]; 840 841 for (index = 0; index < parsed.spectrum.xNumSteps.length - 1; index++) {
842 // last "range" can't contain step size as it is purely an endpoint. 843 if (parsed.padding[0][index] < 0 || parsed.padding[1][index] < 0) { 844 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s)."); 845 } 846 } 847 848 var totalPadding = entry[0] + entry[1]; 849 var firstValue = parsed.spectrum.xVal[0]; 850 var lastValue = parsed.spectrum.xVal[parsed.spectrum.xVal.length - 1]; 851 852 if (totalPadding / (lastValue - firstValue) > 1) { 853 throw new Error("noUiSlider (" + VERSION + "): 'padding' option must not exceed 100% of the range."); 854 } 855 } 856 857 function testDirection(parsed, entry) { 858 // Set direction as a numerical value for easy parsing. 859 // Invert connection for RTL sliders, so that the proper 860 // handles get the connect/background classes. 861 switch (entry) { 862 case "ltr": 863 parsed.dir = 0; 864 break; 865 case "rtl": 866 parsed.dir = 1; 867 break; 868 default: 869 throw new Error("noUiSlider (" + VERSION + "): 'direction' option was not recognized."); 870 } 871 } 872 873 function testBehaviour(parsed, entry) { 874 // Make sure the input is a string. 875 if (typeof entry !== "string") { 876 throw new Error("noUiSlider (" + VERSION + "): 'behaviour' must be a string containing options."); 877 } 878 879 // Check if the string contains any keywords. 880 // None are required. 881 var tap = entry.indexOf("tap") >= 0; 882 var drag = entry.indexOf("drag") >= 0; 883 var fixed = entry.indexOf("fixed") >= 0; 884 var snap = entry.indexOf("snap") >= 0; 885 var hover = entry.indexOf("hover") >= 0; 886 var unconstrained = entry.indexOf("unconstrained") >= 0; 887 888 if (fixed) { 889 if (parsed.handles !== 2) { 890 throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles"); 891 } 892 893 // Use margin to enforce fixed state 894 testMargin(parsed, parsed.start[1] - parsed.start[0]); 895 } 896 897 if (unconstrained && (parsed.margin || parsed.limit)) { 898 throw new Error( 899 "noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit" 900 ); 901 } 902 903 parsed.events = { 904 tap: tap || snap, 905 drag: drag, 906 fixed: fixed, 907 snap: snap, 908 hover: hover, 909 unconstrained: unconstrained 910 }; 911 } 912 913 function testTooltips(parsed, entry) { 914 if (entry === false) { 915 return; 916 } 917 918 if (entry === true) { 919 parsed.tooltips = []; 920 921 for (var i = 0; i < parsed.handles; i++) { 922 parsed.tooltips.push(true); 923 } 924 } else { 925 parsed.tooltips = asArray(entry); 926 927 if (parsed.tooltips.length !== parsed.handles) { 928 throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles."); 929 } 930 931 parsed.tooltips.forEach(function(formatter) { 932 if ( 933 typeof formatter !== "boolean" && 934 (typeof formatter !== "object" || typeof formatter.to !== "function") 935 ) { 936 throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'."); 937 } 938 }); 939 } 940 } 941 942 function testAriaFormat(parsed, entry) { 943 parsed.ariaFormat = entry; 944 validateFormat(entry); 945 } 946 947 function testFormat(parsed, entry) { 948 parsed.format = entry; 949 validateFormat(entry); 950 } 951 952 function testKeyboardSupport(parsed, entry) { 953 parsed.keyboardSupport = entry; 954 955 if (typeof entry !== "boolean") { 956 throw new Error("noUiSlider (" + VERSION + "): 'keyboardSupport' option must be a boolean."); 957 } 958 } 959 960 function testDocumentElement(parsed, entry) { 961 // This is an advanced option. Passed values are used without validation. 962 parsed.documentElement = entry; 963 } 964 965 function testCssPrefix(parsed, entry) { 966 if (typeof entry !== "string" && entry !== false) { 967 throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`."); 968 } 969 970 parsed.cssPrefix = entry; 971 } 972
973 function testCssClasses(parsed, entry) { 974 if (typeof entry !== "object") { 975 throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object."); 976 } 977 978 if (typeof parsed.cssPrefix === "string") { 979 parsed.cssClasses = {}; 980 981 for (var key in entry) { 982 if (!entry.hasOwnProperty(key)) { 983 continue; 984 } 985 986 parsed.cssClasses[key] = parsed.cssPrefix + entry[key]; 987 } 988 } else { 989 parsed.cssClasses = entry; 990 } 991 } 992 993 // Test all developer settings and parse to assumption-safe values. 994 function testOptions(options) { 995 // To prove a fix for #537, freeze options here. 996 // If the object is modified, an error will be thrown. 997 // Object.freeze(options); 998 999 var parsed = { 1000 margin: 0, 1001 limit: 0, 1002 padding: 0, 1003 animate: true, 1004 animationDuration: 300, 1005 ariaFormat: defaultFormatter, 1006 format: defaultFormatter 1007 }; 1008 1009 // Tests are executed in the order they are presented here. 1010 var tests = { 1011 step: { r: false, t: testStep }, 1012 keyboardPageMultiplier: { r: false, t: testKeyboardPageMultiplier }, 1013 keyboardDefaultStep: { r: false, t: testKeyboardDefaultStep }, 1014 start: { r: true, t: testStart }, 1015 connect: { r: true, t: testConnect }, 1016 direction: { r: true, t: testDirection }, 1017 snap: { r: false, t: testSnap }, 1018 animate: { r: false, t: testAnimate }, 1019 animationDuration: { r: false, t: testAnimationDuration }, 1020 range: { r: true, t: testRange }, 1021 orientation: { r: false, t: testOrientation }, 1022 margin: { r: false, t: testMargin }, 1023 limit: { r: false, t: testLimit }, 1024 padding: { r: false, t: testPadding }, 1025 behaviour: { r: true, t: testBehaviour }, 1026 ariaFormat: { r: false, t: testAriaFormat }, 1027 format: { r: false, t: testFormat }, 1028 tooltips: { r: false, t: testTooltips }, 1029 keyboardSupport: { r: true, t: testKeyboardSupport }, 1030 documentElement: { r: false, t: testDocumentElement }, 1031 cssPrefix: { r: true, t: testCssPrefix },
1032 cssClasses: { r: true, t: testCssClasses } 1033 }; 1034 1035 var defaults = { 1036 connect: false, 1037 direction: "ltr", 1038 behaviour: "tap", 1039 orientation: "horizontal", 1040 keyboardSupport: true, 1041 cssPrefix: "noUi-", 1042 cssClasses: cssClasses, 1043 keyboardPageMultiplier: 5, 1044 keyboardDefaultStep: 10 1045 }; 1046 1047 // AriaFormat defaults to regular format, if any. 1048 if (options.format && !options.ariaFormat) { 1049 options.ariaFormat = options.format; 1050 } 1051 1052 // Run all options through a testing mechanism to ensure correct 1053 // input. It should be noted that options might get modified to 1054 // be handled properly. E.g. wrapping integers in arrays. 1055 Object.keys(tests).forEach(function(name) { 1056 // If the option isn't set, but it is required, throw an error. 1057 if (!isSet(options[name]) && defaults[name] === undefined) { 1058 if (tests[name].r) { 1059 throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required."); 1060 } 1061 1062 return true; 1063 } 1064 1065 tests[name].t(parsed, !isSet(options[name]) ? defaults[name] : options[name]); 1066 }); 1067 1068 // Forward pips options 1069 parsed.pips = options.pips; 1070 1071 // All recent browsers accept unprefixed transform. 1072 // We need -ms- for IE9 and -webkit- for older Android; 1073 // Assume use of -webkit- if unprefixed and -ms- are not supported. 1074 // https://caniuse.com/#feat=transforms2d 1075 var d = document.createElement("div"); 1076 var msPrefix = d.style.msTransform !== undefined; 1077 var noPrefix = d.style.transform !== undefined; 1078 1079 parsed.transformRule = noPrefix ? "transform" : msPrefix ? "msTransform" : "webkitTransform"; 1080 1081 // Pips don't move, so we can place them using left/top. 1082 var styles = [["left", "top"], ["right", "bottom"]]; 1083 1084 parsed.style = styles[parsed.dir][parsed.ort]; 1085 1086 return parsed; 1087 } 1088 1089 //endregion 1090 1091 function scope(target, options, originalOptions) { 1092 var actions = getActions(); 1093 var supportsTouchActionNone = getSupportsTouchActionNone(); 1094 var supportsPassive = supportsTouchActionNone && getSupportsPassive(); 1095 1096 // All variables local to 'scope' are prefixed with 'scope_' 1097 1098 // Slider DOM Nodes 1099 var scope_Target = target; 1100 var scope_Base; 1101 var scope_Handles; 1102 var scope_Connects; 1103 var scope_Pips; 1104 var scope_Tooltips; 1105 1106 // Slider state values 1107 var scope_Spectrum = options.spectrum; 1108 var scope_Values = []; 1109 var scope_Locations = []; 1110 var scope_HandleNumbers = []; 1111 var scope_ActiveHandlesCount = 0; 1112 var scope_Events = {}; 1113 1114 // Exposed API 1115 var scope_Self; 1116 1117 // Document Nodes 1118 var scope_Document = target.ownerDocument; 1119 var scope_DocumentElement = options.documentElement || scope_Document.documentElement; 1120 var scope_Body = scope_Document.body; 1121 1122 // Pips constants 1123 var PIPS_NONE = -1; 1124 var PIPS_NO_VALUE = 0; 1125 var PIPS_LARGE_VALUE = 1; 1126 var PIPS_SMALL_VALUE = 2; 1127 1128 // For horizontal sliders in standard ltr documents, 1129 // make .noUi-origin overflow to the left so the document doesn't scroll. 1130 var scope_DirOffset = scope_Document.dir === "rtl" || options.ort === 1 ? 0 : 100; 1131 1132 // Creates a node, adds it to target, returns the new node. 1133 function addNodeTo(addTarget, className) { 1134 var div = scope_Document.createElement("div"); 1135 1136 if (className) { 1137 addClass(div, className); 1138 } 1139 1140 addTarget.appendChild(div); 1141 1142 return div; 1143 } 1144 1145 // Append a origin to the base 1146 function addOrigin(base, handleNumber) { 1147 var origin = addNodeTo(base, options.cssClasses.origin); 1148 var handle = addNodeTo(origin, options.cssClasses.handle); 1149
vendor: 4,991 bytes, lines 1150-1287
1150 addNodeTo(handle, options.cssClasses.touchArea); 1151 1152 handle.setAttribute("data-handle", handleNumber); 1153 1154 if (options.keyboardSupport) { 1155 // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex 1156 // 0 = focusable and reachable 1157 handle.setAttribute("tabindex", "0"); 1158 handle.addEventListener("keydown", function(event) { 1159 return eventKeydown(event, handleNumber); 1160 }); 1161 } 1162 1163 handle.setAttribute("role", "slider"); 1164 handle.setAttribute("aria-orientation", options.ort ? "vertical" : "horizontal"); 1165 1166 if (handleNumber === 0) { 1167 addClass(handle, options.cssClasses.handleLower); 1168 } else if (handleNumber === options.handles - 1) { 1169 addClass(handle, options.cssClasses.handleUpper); 1170 } 1171 1172 return origin; 1173 } 1174 1175 // Insert nodes for connect elements 1176 function addConnect(base, add) { 1177 if (!add) { 1178 return false; 1179 } 1180 1181 return addNodeTo(base, options.cssClasses.connect); 1182 } 1183 1184 // Add handles to the slider base. 1185 function addElements(connectOptions, base) { 1186 var connectBase = addNodeTo(base, options.cssClasses.connects); 1187 1188 scope_Handles = []; 1189 scope_Connects = []; 1190 1191 scope_Connects.push(addConnect(connectBase, connectOptions[0])); 1192 1193 // [::::O====O====O====] 1194 // connectOptions = [0, 1, 1, 1] 1195 1196 for (var i = 0; i < options.handles; i++) { 1197 // Keep a list of all added handles. 1198 scope_Handles.push(addOrigin(base, i)); 1199 scope_HandleNumbers[i] = i; 1200 scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); 1201 } 1202 } 1203 1204 // Initialize a single slider. 1205 function addSlider(addTarget) { 1206 // Apply classes and data to the target. 1207 addClass(addTarget, options.cssClasses.target); 1208 1209 if (options.dir === 0) { 1210 addClass(addTarget, options.cssClasses.ltr); 1211 } else { 1212 addClass(addTarget, options.cssClasses.rtl); 1213 } 1214 1215 if (options.ort === 0) { 1216 addClass(addTarget, options.cssClasses.horizontal); 1217 } else { 1218 addClass(addTarget, options.cssClasses.vertical); 1219 } 1220 1221 var textDirection = getComputedStyle(addTarget).direction; 1222 1223 if (textDirection === "rtl") { 1224 addClass(addTarget, options.cssClasses.textDirectionRtl); 1225 } else { 1226 addClass(addTarget, options.cssClasses.textDirectionLtr); 1227 } 1228 1229 return addNodeTo(addTarget, options.cssClasses.base); 1230 } 1231 1232 function addTooltip(handle, handleNumber) { 1233 if (!options.tooltips[handleNumber]) { 1234 return false; 1235 } 1236 1237 return addNodeTo(handle.firstChild, options.cssClasses.tooltip); 1238 } 1239 1240 function isSliderDisabled() { 1241 return scope_Target.hasAttribute("disabled"); 1242 } 1243 1244 // Disable the slider dragging if any handle is disabled 1245 function isHandleDisabled(handleNumber) { 1246 var handleOrigin = scope_Handles[handleNumber]; 1247 return handleOrigin.hasAttribute("disabled"); 1248 } 1249 1250 function removeTooltips() { 1251 if (scope_Tooltips) { 1252 removeEvent("update" + INTERNAL_EVENT_NS.tooltips); 1253 scope_Tooltips.forEach(function(tooltip) { 1254 if (tooltip) { 1255 removeElement(tooltip); 1256 } 1257 }); 1258 scope_Tooltips = null; 1259 } 1260 } 1261 1262 // The tooltips option is a shorthand for using the 'update' event. 1263 function tooltips() { 1264 removeTooltips(); 1265 1266 // Tooltips are added with options.tooltips in original order. 1267 scope_Tooltips = scope_Handles.map(addTooltip); 1268 1269 bindEvent("update" + INTERNAL_EVENT_NS.tooltips, function(values, handleNumber, unencoded) { 1270 if (!scope_Tooltips[handleNumber]) { 1271 return; 1272 } 1273 1274 var formattedValue = values[handleNumber]; 1275 1276 if (options.tooltips[handleNumber] !== true) { 1277 formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]); 1278 } 1279 1280 scope_Tooltips[handleNumber].innerHTML = formattedValue; 1281 }); 1282 } 1283 1284 function aria() { 1285 removeEvent("update" + INTERNAL_EVENT_NS.aria); 1286 bindEvent("update" + INTERNAL_EVENT_NS.aria, function(values, handleNumber, unencoded, tap, positions) { 1287 // Update Aria Values for all handles, as a change in one changes min and max values for the next.
1288 scope_HandleNumbers.forEach(function(index) { 1289 var handle = scope_Handles[index]; 1290 1291 var min = checkHandlePosition(scope_Locations, index, 0, true, true, true); 1292 var max = checkHandlePosition(scope_Locations, index, 100, true, true, true); 1293 1294 var now = positions[index]; 1295 1296 // Formatted value for display 1297 var text = options.ariaFormat.to(unencoded[index]); 1298 1299 // Map to slider range values 1300 min = scope_Spectrum.fromStepping(min).toFixed(1); 1301 max = scope_Spectrum.fromStepping(max).toFixed(1); 1302 now = scope_Spectrum.fromStepping(now).toFixed(1); 1303 1304 handle.children[0].setAttribute("aria-valuemin", min); 1305 handle.children[0].setAttribute("aria-valuemax", max); 1306 handle.children[0].setAttribute("aria-valuenow", now); 1307 handle.children[0].setAttribute("aria-valuetext", text); 1308 }); 1309 }); 1310 } 1311 1312 function getGroup(mode, values, stepped) { 1313 // Use the range. 1314 if (mode === "range" || mode === "steps") { 1315 return scope_Spectrum.xVal; 1316 } 1317 1318 if (mode === "count") { 1319 if (values < 2) { 1320 throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'."); 1321 } 1322 1323 // Divide 0 - 100 in 'count' parts. 1324 var interval = values - 1; 1325 var spread = 100 / interval; 1326 1327 values = []; 1328 1329 // List these parts and have them handled as 'positions'. 1330 while (interval--) { 1331 values[interval] = interval * spread; 1332 } 1333 1334 values.push(100); 1335 1336 mode = "positions"; 1337 } 1338 1339 if (mode === "positions") { 1340 // Map all percentages to on-range values. 1341 return values.map(function(value) { 1342 return scope_Spectrum.fromStepping(stepped ? scope_Spectrum.getStep(value) : value); 1343 }); 1344 } 1345 1346 if (mode === "values") { 1347 // If the value must be stepped, it needs to be converted to a percentage first. 1348 if (stepped) { 1349 return values.map(function(value) { 1350 // Convert to percentage, apply step, return to value. 1351 return scope_Spectrum.fromStepping(scope_Spectrum.getStep(scope_Spectrum.toStepping(value))); 1352 }); 1353 } 1354 1355 // Otherwise, we can simply use the values. 1356 return values; 1357 } 1358 } 1359 1360 function generateSpread(density, mode, group) { 1361 function safeIncrement(value, increment) { 1362 // Avoid floating point variance by dropping the smallest decimal places. 1363 return (value + increment).toFixed(7) / 1; 1364 } 1365 1366 var indexes = {}; 1367 var firstInRange = scope_Spectrum.xVal[0]; 1368 var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length - 1]; 1369 var ignoreFirst = false; 1370 var ignoreLast = false; 1371 var prevPct = 0; 1372 1373 // Create a copy of the group, sort it and filter away all duplicates. 1374 group = unique( 1375 group.slice().sort(function(a, b) { 1376 return a - b; 1377 }) 1378 ); 1379 1380 // Make sure the range starts with the first element. 1381 if (group[0] !== firstInRange) { 1382 group.unshift(firstInRange); 1383 ignoreFirst = true; 1384 } 1385 1386 // Likewise for the last one. 1387 if (group[group.length - 1] !== lastInRange) { 1388 group.push(lastInRange); 1389 ignoreLast = true; 1390 } 1391 1392 group.forEach(function(current, index) { 1393 // Get the current step and the lower + upper positions. 1394 var step; 1395 var i; 1396 var q; 1397 var low = current; 1398 var high = group[index + 1]; 1399 var newPct; 1400 var pctDifference; 1401 var pctPos; 1402 var type; 1403 var steps; 1404 var realSteps; 1405 var stepSize; 1406 var isSteps = mode === "steps"; 1407 1408 // When using 'steps' mode, use the provided steps. 1409 // Otherwise, we'll step on to the next subrange. 1410 if (isSteps) { 1411 step = scope_Spectrum.xNumSteps[index]; 1412 } 1413 1414 // Default to a 'full' step. 1415 if (!step) { 1416 step = high - low; 1417 } 1418 1419 // Low can be 0, so test for false. Index 0 is already handled. 1420 if (low === false) { 1421 return; 1422 } 1423 1424 // If high is undefined we are at the last subrange. Make sure it iterates once (#1088) 1425 if (high === undefined) { 1426 high = low; 1427 } 1428 1429 // Make sure step isn't 0, which would cause an infinite loop (#654) 1430 step = Math.max(step, 0.0000001); 1431 1432 // Find all steps in the subrange. 1433 for (i = low; i <= high; i = safeIncrement(i, step)) { 1434 // Get the percentage value for the current step, 1435 // calculate the size for the subrange. 1436 newPct = scope_Spectrum.toStepping(i); 1437 pctDifference = newPct - prevPct; 1438 1439 steps = pctDifference / density; 1440 realSteps = Math.round(steps); 1441 1442 // This ratio represents the amount of percentage-space a point indicates. 1443 // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-divided. 1444 // Round the percentage offset to an even number, then divide by two 1445 // to spread the offset on both sides of the range. 1446 stepSize = pctDifference / realSteps; 1447 1448 // Divide all points evenly, adding the correct number to this subrange. 1449 // Run up to <= so that 100% gets a point, event if ignoreLast is set. 1450 for (q = 1; q <= realSteps; q += 1) { 1451 // The ratio between the rounded value and the actual size might be ~1% off. 1452 // Correct the percentage offset by the number of points 1453 // per subrange. density = 1 will result in 100 points on the 1454 // full range, 2 for 50, 4 for 25, etc. 1455 pctPos = prevPct + q * stepSize; 1456 indexes[pctPos.toFixed(5)] = [scope_Spectrum.fromStepping(pctPos), 0]; 1457 } 1458 1459 // Determine the point type. 1460 type = group.indexOf(i) > -1 ? PIPS_LARGE_VALUE : isSteps ? PIPS_SMALL_VALUE : PIPS_NO_VALUE; 1461 1462 // Enforce the 'ignoreFirst' option by overwriting the type for 0. 1463 if (!index && ignoreFirst && i !== high) { 1464 type = 0; 1465 } 1466 1467 if (!(i === high && ignoreLast)) { 1468 // Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value. 1469 indexes[newPct.toFixed(5)] = [i, type]; 1470 } 1471 1472 // Update the percentage count. 1473 prevPct = newPct; 1474 } 1475 }); 1476 1477 return indexes; 1478 } 1479 1480 function addMarking(spread, filterFunc, formatter) { 1481 var element = scope_Document.createElement("div"); 1482 1483 var valueSizeClasses = [];
1484 valueSizeClasses[PIPS_NO_VALUE] = options.cssClasses.valueNormal; 1485 valueSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.valueLarge; 1486 valueSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.valueSub; 1487 1488 var markerSizeClasses = []; 1489 markerSizeClasses[PIPS_NO_VALUE] = options.cssClasses.markerNormal; 1490 markerSizeClasses[PIPS_LARGE_VALUE] = options.cssClasses.markerLarge; 1491 markerSizeClasses[PIPS_SMALL_VALUE] = options.cssClasses.markerSub; 1492 1493 var valueOrientationClasses = [options.cssClasses.valueHorizontal, options.cssClasses.valueVertical]; 1494 var markerOrientationClasses = [options.cssClasses.markerHorizontal, options.cssClasses.markerVertical]; 1495 1496 addClass(element, options.cssClasses.pips); 1497 addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical); 1498 1499 function getClasses(type, source) { 1500 var a = source === options.cssClasses.value; 1501 var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses; 1502 var sizeClasses = a ? valueSizeClasses : markerSizeClasses; 1503 1504 return source + " " + orientationClasses[options.ort] + " " + sizeClasses[type]; 1505 } 1506 1507 function addSpread(offset, value, type) { 1508 // Apply the filter function, if it is set. 1509 type = filterFunc ? filterFunc(value, type) : type; 1510 1511 if (type === PIPS_NONE) { 1512 return; 1513 } 1514 1515 // Add a marker for every point 1516 var node = addNodeTo(element, false); 1517 node.className = getClasses(type, options.cssClasses.marker); 1518 node.style[options.style] = offset + "%"; 1519 1520 // Values are only appended for points marked '1' or '2'. 1521 if (type > PIPS_NO_VALUE) { 1522 node = addNodeTo(element, false); 1523 node.className = getClasses(type, options.cssClasses.value); 1524 node.setAttribute("data-value", value); 1525 node.style[options.style] = offset + "%"; 1526 node.innerHTML = formatter.to(value); 1527 } 1528 } 1529 1530 // Append all points. 1531 Object.keys(spread).forEach(function(offset) { 1532 addSpread(offset, spread[offset][0], spread[offset][1]); 1533 }); 1534 1535 return element; 1536 } 1537 1538 function removePips() { 1539 if (scope_Pips) { 1540 removeElement(scope_Pips); 1541 scope_Pips = null; 1542 } 1543 } 1544 1545 function pips(grid) { 1546 // Fix #669 1547 removePips(); 1548 1549 var mode = grid.mode; 1550 var density = grid.density || 1; 1551 var filter = grid.filter || false; 1552 var values = grid.values || false; 1553 var stepped = grid.stepped || false; 1554 var group = getGroup(mode, values, stepped); 1555 var spread = generateSpread(density, mode, group); 1556 var format = grid.format || { 1557 to: Math.round 1558 }; 1559 1560 scope_Pips = scope_Target.appendChild(addMarking(spread, filter, format)); 1561 1562 return scope_Pips; 1563 } 1564 1565 // Shorthand for base dimensions. 1566 function baseSize() { 1567 var rect = scope_Base.getBoundingClientRect(); 1568 var alt = "offset" + ["Width", "Height"][options.ort]; 1569 return options.ort === 0 ? rect.width || scope_Base[alt] : rect.height || scope_Base[alt]; 1570 } 1571 1572 // Handler for attaching events trough a proxy. 1573 function attachEvent(events, element, callback, data) { 1574 // This function can be used to 'filter' events to the slider. 1575 // element is a node, not a nodeList 1576 1577 var method = function(e) { 1578 e = fixEvent(e, data.pageOffset, data.target || element); 1579 1580 // fixEvent returns false if this event has a different target 1581 // when handling (multi-) touch events; 1582 if (!e) { 1583 return false;
1584 } 1585 1586 // doNotReject is passed by all end events to make sure released touches 1587 // are not rejected, leaving the slider "stuck" to the cursor; 1588 if (isSliderDisabled() && !data.doNotReject) { 1589 return false; 1590 } 1591 1592 // Stop if an active 'tap' transition is taking place. 1593 if (hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject) { 1594 return false; 1595 } 1596 1597 // Ignore right or middle clicks on start #454 1598 if (events === actions.start && e.buttons !== undefined && e.buttons > 1) { 1599 return false; 1600 } 1601 1602 // Ignore right or middle clicks on start #454 1603 if (data.hover && e.buttons) { 1604 return false; 1605 } 1606 1607 // 'supportsPassive' is only true if a browser also supports touch-action: none in CSS. 1608 // iOS safari does not, so it doesn't get to benefit from passive scrolling. iOS does support 1609 // touch-action: manipulation, but that allows panning, which breaks 1610 // sliders after zooming/on non-responsive pages. 1611 // See: https://bugs.webkit.org/show_bug.cgi?id=133112 1612 if (!supportsPassive) { 1613 e.preventDefault(); 1614 } 1615 1616 e.calcPoint = e.points[options.ort]; 1617 1618 // Call the event handler with the event [ and additional data ]. 1619 callback(e, data); 1620 }; 1621 1622 var methods = []; 1623 1624 // Bind a closure on the target for every event type. 1625 events.split(" ").forEach(function(eventName) { 1626 element.addEventListener(eventName, method, supportsPassive ? { passive: true } : false); 1627 methods.push([eventName, method]); 1628 }); 1629 1630 return methods; 1631 } 1632 1633 // Provide a clean event with standardized offset values. 1634 function fixEvent(e, pageOffset, eventTarget) { 1635 // Filter the event to register the type, which can be 1636 // touch, mouse or pointer. Offset changes need to be 1637 // made on an event specific basis. 1638 var touch = e.type.indexOf("touch") === 0; 1639 var mouse = e.type.indexOf("mouse") === 0; 1640 var pointer = e.type.indexOf("pointer") === 0; 1641 1642 var x; 1643 var y; 1644 1645 // IE10 implemented pointer events with a prefix; 1646 if (e.type.indexOf("MSPointer") === 0) { 1647 pointer = true; 1648 } 1649 1650 // Erroneous events seem to be passed in occasionally on iOS/iPadOS after user finishes interacting with 1651 // the slider. They appear to be of type MouseEvent, yet they don't have usual properties set. Ignore 1652 // events that have no touches or buttons associated with them. (#1057, #1079, #1095) 1653 if (e.type === "mousedown" && !e.buttons && !e.touches) { 1654 return false; 1655 } 1656 1657 // The only thing one handle should be concerned about is the touches that originated on top of it. 1658 if (touch) { 1659 // Returns true if a touch originated on the target. 1660 var isTouchOnTarget = function(checkTouch) { 1661 return ( 1662 checkTouch.target === eventTarget || 1663 eventTarget.contains(checkTouch.target) || 1664 (checkTouch.target.shadowRoot && checkTouch.target.shadowRoot.contains(eventTarget)) 1665 ); 1666 }; 1667 1668 // In the case of touchstart events, we need to make sure there is still no more than one 1669 // touch on the target so we look amongst all touches. 1670 if (e.type === "touchstart") { 1671 var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget); 1672 1673 // Do not support more than one touch per handle. 1674 if (targetTouches.length > 1) { 1675 return false;
vendor: 21,666 bytes, lines 1676-2193
1676 } 1677 1678 x = targetTouches[0].pageX; 1679 y = targetTouches[0].pageY; 1680 } else { 1681 // In the other cases, find on changedTouches is enough. 1682 var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget); 1683 1684 // Cancel if the target touch has not moved. 1685 if (!targetTouch) { 1686 return false; 1687 } 1688 1689 x = targetTouch.pageX; 1690 y = targetTouch.pageY; 1691 } 1692 } 1693 1694 pageOffset = pageOffset || getPageOffset(scope_Document); 1695 1696 if (mouse || pointer) { 1697 x = e.clientX + pageOffset.x; 1698 y = e.clientY + pageOffset.y; 1699 } 1700 1701 e.pageOffset = pageOffset; 1702 e.points = [x, y]; 1703 e.cursor = mouse || pointer; // Fix #435 1704 1705 return e; 1706 } 1707 1708 // Translate a coordinate in the document to a percentage on the slider 1709 function calcPointToPercentage(calcPoint) { 1710 var location = calcPoint - offset(scope_Base, options.ort); 1711 var proposal = (location * 100) / baseSize(); 1712 1713 // Clamp proposal between 0% and 100% 1714 // Out-of-bound coordinates may occur when .noUi-base pseudo-elements 1715 // are used (e.g. contained handles feature) 1716 proposal = limit(proposal); 1717 1718 return options.dir ? 100 - proposal : proposal; 1719 } 1720 1721 // Find handle closest to a certain percentage on the slider 1722 function getClosestHandle(clickedPosition) { 1723 var smallestDifference = 100; 1724 var handleNumber = false; 1725 1726 scope_Handles.forEach(function(handle, index) { 1727 // Disabled handles are ignored 1728 if (isHandleDisabled(index)) { 1729 return; 1730 } 1731 1732 var handlePosition = scope_Locations[index]; 1733 var differenceWithThisHandle = Math.abs(handlePosition - clickedPosition); 1734 1735 // Initial state 1736 var clickAtEdge = differenceWithThisHandle === 100 && smallestDifference === 100; 1737 1738 // Difference with this handle is smaller than the previously checked handle 1739 var isCloser = differenceWithThisHandle < smallestDifference; 1740 var isCloserAfter = differenceWithThisHandle <= smallestDifference && clickedPosition > handlePosition; 1741 1742 if (isCloser || isCloserAfter || clickAtEdge) { 1743 handleNumber = index; 1744 smallestDifference = differenceWithThisHandle; 1745 } 1746 }); 1747 1748 return handleNumber; 1749 } 1750 1751 // Fire 'end' when a mouse or pen leaves the document. 1752 function documentLeave(event, data) { 1753 if (event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null) { 1754 eventEnd(event, data); 1755 } 1756 } 1757 1758 // Handle movement on document for handle and range drag. 1759 function eventMove(event, data) { 1760 // Fix #498 1761 // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty). 1762 // https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero 1763 // IE9 has .buttons and .which zero on mousemove. 1764 // Firefox breaks the spec MDN defines. 1765 if (navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0) { 1766 return eventEnd(event, data); 1767 } 1768 1769 // Check if we are moving up or down 1770 var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint); 1771 1772 // Convert the movement into a percentage of the slider width/height 1773 var proposal = (movement * 100) / data.baseSize; 1774 1775 moveHandles(movement > 0, proposal, data.locations, data.handleNumbers); 1776 } 1777 1778 // Unbind move events on document, call callbacks. 1779 function eventEnd(event, data) { 1780 // The handle is no longer active, so remove the class. 1781 if (data.handle) { 1782 removeClass(data.handle, options.cssClasses.active); 1783 scope_ActiveHandlesCount -= 1; 1784 } 1785 1786 // Unbind the move and end events, which are added on 'start'. 1787 data.listeners.forEach(function(c) { 1788 scope_DocumentElement.removeEventListener(c[0], c[1]); 1789 }); 1790 1791 if (scope_ActiveHandlesCount === 0) { 1792 // Remove dragging class. 1793 removeClass(scope_Target, options.cssClasses.drag); 1794 setZindex(); 1795 1796 // Remove cursor styles and text-selection events bound to the body. 1797 if (event.cursor) { 1798 scope_Body.style.cursor = ""; 1799 scope_Body.removeEventListener("selectstart", preventDefault); 1800 } 1801 } 1802 1803 data.handleNumbers.forEach(function(handleNumber) { 1804 fireEvent("change", handleNumber); 1805 fireEvent("set", handleNumber); 1806 fireEvent("end", handleNumber); 1807 }); 1808 } 1809 1810 // Bind move events on document. 1811 function eventStart(event, data) { 1812 // Ignore event if any handle is disabled 1813 if (data.handleNumbers.some(isHandleDisabled)) { 1814 return false; 1815 } 1816 1817 var handle; 1818 1819 if (data.handleNumbers.length === 1) { 1820 var handleOrigin = scope_Handles[data.handleNumbers[0]]; 1821 1822 handle = handleOrigin.children[0]; 1823 scope_ActiveHandlesCount += 1; 1824 1825 // Mark the handle as 'active' so it can be styled. 1826 addClass(handle, options.cssClasses.active); 1827 } 1828 1829 // A drag should never propagate up to the 'tap' event. 1830 event.stopPropagation(); 1831 1832 // Record the event listeners. 1833 var listeners = []; 1834 1835 // Attach the move and end events. 1836 var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, { 1837 // The event target has changed so we need to propagate the original one so that we keep 1838 // relying on it to extract target touches. 1839 target: event.target, 1840 handle: handle, 1841 listeners: listeners, 1842 startCalcPoint: event.calcPoint, 1843 baseSize: baseSize(), 1844 pageOffset: event.pageOffset, 1845 handleNumbers: data.handleNumbers, 1846 buttonsProperty: event.buttons, 1847 locations: scope_Locations.slice() 1848 }); 1849 1850 var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, { 1851 target: event.target, 1852 handle: handle, 1853 listeners: listeners, 1854 doNotReject: true, 1855 handleNumbers: data.handleNumbers 1856 }); 1857 1858 var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, { 1859 target: event.target, 1860 handle: handle, 1861 listeners: listeners, 1862 doNotReject: true, 1863 handleNumbers: data.handleNumbers 1864 }); 1865 1866 // We want to make sure we pushed the listeners in the listener list rather than creating 1867 // a new one as it has already been passed to the event handlers. 1868 listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent)); 1869 1870 // Text selection isn't an issue on touch devices, 1871 // so adding cursor styles can be skipped. 1872 if (event.cursor) { 1873 // Prevent the 'I' cursor and extend the range-drag cursor. 1874 scope_Body.style.cursor = getComputedStyle(event.target).cursor; 1875 1876 // Mark the target with a dragging state. 1877 if (scope_Handles.length > 1) { 1878 addClass(scope_Target, options.cssClasses.drag); 1879 } 1880 1881 // Prevent text selection when dragging the handles. 1882 // In noUiSlider <= 9.2.0, this was handled by calling preventDefault on mouse/touch start/move, 1883 // which is scroll blocking. The selectstart event is supported by FireFox starting from version 52, 1884 // meaning the only holdout is iOS Safari. This doesn't matter: text selection isn't triggered there. 1885 // The 'cursor' flag is false. 1886 // See: http://caniuse.com/#search=selectstart 1887 scope_Body.addEventListener("selectstart", preventDefault, false); 1888 } 1889 1890 data.handleNumbers.forEach(function(handleNumber) { 1891 fireEvent("start", handleNumber); 1892 }); 1893 } 1894 1895 // Move closest handle to tapped location. 1896 function eventTap(event) { 1897 // The tap event shouldn't propagate up 1898 event.stopPropagation(); 1899 1900 var proposal = calcPointToPercentage(event.calcPoint); 1901 var handleNumber = getClosestHandle(proposal); 1902 1903 // Tackle the case that all handles are 'disabled'. 1904 if (handleNumber === false) { 1905 return false; 1906 } 1907 1908 // Flag the slider as it is now in a transitional state. 1909 // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that. 1910 if (!options.events.snap) { 1911 addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); 1912 } 1913 1914 setHandle(handleNumber, proposal, true, true); 1915 1916 setZindex(); 1917 1918 fireEvent("slide", handleNumber, true); 1919 fireEvent("update", handleNumber, true); 1920 fireEvent("change", handleNumber, true); 1921 fireEvent("set", handleNumber, true); 1922 1923 if (options.events.snap) { 1924 eventStart(event, { handleNumbers: [handleNumber] }); 1925 } 1926 } 1927 1928 // Fires a 'hover' event for a hovered mouse/pen position. 1929 function eventHover(event) { 1930 var proposal = calcPointToPercentage(event.calcPoint); 1931 1932 var to = scope_Spectrum.getStep(proposal); 1933 var value = scope_Spectrum.fromStepping(to); 1934 1935 Object.keys(scope_Events).forEach(function(targetEvent) { 1936 if ("hover" === targetEvent.split(".")[0]) { 1937 scope_Events[targetEvent].forEach(function(callback) { 1938 callback.call(scope_Self, value); 1939 }); 1940 } 1941 }); 1942 } 1943 1944 // Handles keydown on focused handles 1945 // Don't move the document when pressing arrow keys on focused handles 1946 function eventKeydown(event, handleNumber) { 1947 if (isSliderDisabled() || isHandleDisabled(handleNumber)) { 1948 return false; 1949 } 1950 1951 var horizontalKeys = ["Left", "Right"]; 1952 var verticalKeys = ["Down", "Up"]; 1953 var largeStepKeys = ["PageDown", "PageUp"]; 1954 var edgeKeys = ["Home", "End"]; 1955 1956 if (options.dir && !options.ort) { 1957 // On an right-to-left slider, the left and right keys act inverted 1958 horizontalKeys.reverse(); 1959 } else if (options.ort && !options.dir) { 1960 // On a top-to-bottom slider, the up and down keys act inverted 1961 verticalKeys.reverse(); 1962 largeStepKeys.reverse(); 1963 } 1964 1965 // Strip "Arrow" for IE compatibility. https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key 1966 var key = event.key.replace("Arrow", ""); 1967 1968 var isLargeDown = key === largeStepKeys[0]; 1969 var isLargeUp = key === largeStepKeys[1]; 1970 var isDown = key === verticalKeys[0] || key === horizontalKeys[0] || isLargeDown; 1971 var isUp = key === verticalKeys[1] || key === horizontalKeys[1] || isLargeUp; 1972 var isMin = key === edgeKeys[0]; 1973 var isMax = key === edgeKeys[1]; 1974 1975 if (!isDown && !isUp && !isMin && !isMax) { 1976 return true; 1977 } 1978 1979 event.preventDefault(); 1980 1981 var to; 1982 1983 if (isUp || isDown) { 1984 var multiplier = options.keyboardPageMultiplier; 1985 var direction = isDown ? 0 : 1; 1986 var steps = getNextStepsForHandle(handleNumber); 1987 var step = steps[direction]; 1988 1989 // At the edge of a slider, do nothing 1990 if (step === null) { 1991 return false; 1992 } 1993 1994 // No step set, use the default of 10% of the sub-range 1995 if (step === false) { 1996 step = scope_Spectrum.getDefaultStep( 1997 scope_Locations[handleNumber], 1998 isDown, 1999 options.keyboardDefaultStep 2000 ); 2001 } 2002 2003 if (isLargeUp || isLargeDown) { 2004 step *= multiplier; 2005 } 2006 2007 // Step over zero-length ranges (#948); 2008 step = Math.max(step, 0.0000001); 2009 2010 // Decrement for down steps 2011 step = (isDown ? -1 : 1) * step; 2012 2013 to = scope_Values[handleNumber] + step; 2014 } else if (isMax) { 2015 // End key 2016 to = options.spectrum.xVal[options.spectrum.xVal.length - 1]; 2017 } else { 2018 // Home key 2019 to = options.spectrum.xVal[0]; 2020 } 2021 2022 setHandle(handleNumber, scope_Spectrum.toStepping(to), true, true); 2023 2024 fireEvent("slide", handleNumber); 2025 fireEvent("update", handleNumber); 2026 fireEvent("change", handleNumber); 2027 fireEvent("set", handleNumber); 2028 2029 return false; 2030 } 2031 2032 // Attach events to several slider parts. 2033 function bindSliderEvents(behaviour) { 2034 // Attach the standard drag event to the handles. 2035 if (!behaviour.fixed) { 2036 scope_Handles.forEach(function(handle, index) { 2037 // These events are only bound to the visual handle 2038 // element, not the 'real' origin element. 2039 attachEvent(actions.start, handle.children[0], eventStart, { 2040 handleNumbers: [index] 2041 }); 2042 }); 2043 } 2044 2045 // Attach the tap event to the slider base. 2046 if (behaviour.tap) { 2047 attachEvent(actions.start, scope_Base, eventTap, {}); 2048 } 2049 2050 // Fire hover events 2051 if (behaviour.hover) { 2052 attachEvent(actions.move, scope_Base, eventHover, { 2053 hover: true 2054 }); 2055 } 2056 2057 // Make the range draggable. 2058 if (behaviour.drag) { 2059 scope_Connects.forEach(function(connect, index) { 2060 if (connect === false || index === 0 || index === scope_Connects.length - 1) { 2061 return; 2062 } 2063 2064 var handleBefore = scope_Handles[index - 1]; 2065 var handleAfter = scope_Handles[index]; 2066 var eventHolders = [connect]; 2067 2068 addClass(connect, options.cssClasses.draggable); 2069 2070 // When the range is fixed, the entire range can 2071 // be dragged by the handles. The handle in the first 2072 // origin will propagate the start event upward, 2073 // but it needs to be bound manually on the other. 2074 if (behaviour.fixed) { 2075 eventHolders.push(handleBefore.children[0]); 2076 eventHolders.push(handleAfter.children[0]); 2077 } 2078 2079 eventHolders.forEach(function(eventHolder) { 2080 attachEvent(actions.start, eventHolder, eventStart, { 2081 handles: [handleBefore, handleAfter], 2082 handleNumbers: [index - 1, index] 2083 }); 2084 }); 2085 }); 2086 } 2087 } 2088 2089 // Attach an event to this slider, possibly including a namespace 2090 function bindEvent(namespacedEvent, callback) { 2091 scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || []; 2092 scope_Events[namespacedEvent].push(callback); 2093 2094 // If the event bound is 'update,' fire it immediately for all handles. 2095 if (namespacedEvent.split(".")[0] === "update") { 2096 scope_Handles.forEach(function(a, index) { 2097 fireEvent("update", index); 2098 }); 2099 } 2100 } 2101 2102 function isInternalNamespace(namespace) { 2103 return namespace === INTERNAL_EVENT_NS.aria || namespace === INTERNAL_EVENT_NS.tooltips; 2104 } 2105 2106 // Undo attachment of event 2107 function removeEvent(namespacedEvent) { 2108 var event = namespacedEvent && namespacedEvent.split(".")[0]; 2109 var namespace = event ? namespacedEvent.substring(event.length) : namespacedEvent; 2110 2111 Object.keys(scope_Events).forEach(function(bind) { 2112 var tEvent = bind.split(".")[0]; 2113 var tNamespace = bind.substring(tEvent.length); 2114 if ((!event || event === tEvent) && (!namespace || namespace === tNamespace)) { 2115 // only delete protected internal event if intentional 2116 if (!isInternalNamespace(tNamespace) || namespace === tNamespace) { 2117 delete scope_Events[bind]; 2118 } 2119 } 2120 }); 2121 } 2122 2123 // External event handling 2124 function fireEvent(eventName, handleNumber, tap) { 2125 Object.keys(scope_Events).forEach(function(targetEvent) { 2126 var eventType = targetEvent.split(".")[0]; 2127 2128 if (eventName === eventType) { 2129 scope_Events[targetEvent].forEach(function(callback) { 2130 callback.call( 2131 // Use the slider public API as the scope ('this') 2132 scope_Self, 2133 // Return values as array, so arg_1[arg_2] is always valid. 2134 scope_Values.map(options.format.to), 2135 // Handle index, 0 or 1 2136 handleNumber, 2137 // Un-formatted slider values 2138 scope_Values.slice(), 2139 // Event is fired by tap, true or false 2140 tap || false, 2141 // Left offset of the handle, in relation to the slider 2142 scope_Locations.slice(), 2143 // add the slider public API to an accessible parameter when this is unavailable 2144 scope_Self 2145 ); 2146 }); 2147 } 2148 }); 2149 } 2150 2151 // Split out the handle positioning logic so the Move event can use it, too 2152 function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) { 2153 var distance; 2154 2155 // For sliders with multiple handles, limit movement to the other handle. 2156 // Apply the margin option by adding it to the handle positions. 2157 if (scope_Handles.length > 1 && !options.events.unconstrained) { 2158 if (lookBackward && handleNumber > 0) { 2159 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber - 1], options.margin, 0); 2160 to = Math.max(to, distance); 2161 } 2162 2163 if (lookForward && handleNumber < scope_Handles.length - 1) { 2164 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber + 1], options.margin, 1); 2165 to = Math.min(to, distance); 2166 } 2167 } 2168 2169 // The limit option has the opposite effect, limiting handles to a 2170 // maximum distance from another. Limit must be > 0, as otherwise 2171 // handles would be unmovable. 2172 if (scope_Handles.length > 1 && options.limit) { 2173 if (lookBackward && handleNumber > 0) { 2174 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber - 1], options.limit, 0); 2175 to = Math.min(to, distance); 2176 } 2177 2178 if (lookForward && handleNumber < scope_Handles.length - 1) { 2179 distance = scope_Spectrum.getAbsoluteDistance(reference[handleNumber + 1], options.limit, 1); 2180 to = Math.max(to, distance); 2181 } 2182 } 2183 2184 // The padding option keeps the handles a certain distance from the 2185 // edges of the slider. Padding must be > 0. 2186 if (options.padding) { 2187 if (handleNumber === 0) { 2188 distance = scope_Spectrum.getAbsoluteDistance(0, options.padding[0], 0); 2189 to = Math.max(to, distance); 2190 } 2191 2192 if (handleNumber === scope_Handles.length - 1) { 2193 distance = scope_Spectrum.getAbsoluteDistance(100, options.padding[1], 1
2193); 2194 to = Math.min(to, distance); 2195 } 2196 } 2197 2198 to = scope_Spectrum.getStep(to); 2199 2200 // Limit percentage to the 0 - 100 range 2201 to = limit(to); 2202 2203 // Return false if handle can't move 2204 if (to === reference[handleNumber] && !getValue) { 2205 return false; 2206 } 2207 2208 return to; 2209 } 2210 2211 // Uses slider orientation to create CSS rules. a = base value; 2212 function inRuleOrder(v, a) { 2213 var o = options.ort; 2214 return (o ? a : v) + ", " + (o ? v : a); 2215 } 2216 2217 // Moves handle(s) by a percentage 2218 // (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...]) 2219 function moveHandles(upward, proposal, locations, handleNumbers) { 2220 var proposals = locations.slice(); 2221 2222 var b = [!upward, upward]; 2223 var f = [upward, !upward]; 2224 2225 // Copy handleNumbers so we don't change the dataset 2226 handleNumbers = handleNumbers.slice(); 2227 2228 // Check to see which handle is 'leading'. 2229 // If that one can't move the second can't either. 2230 if (upward) { 2231 handleNumbers.reverse(); 2232 } 2233 2234 // Step 1: get the maximum percentage that any of the handles can move 2235 if (handleNumbers.length > 1) { 2236 handleNumbers.forEach(function(handleNumber, o) { 2237 var to = checkHandlePosition( 2238 proposals, 2239 handleNumber, 2240 proposals[handleNumber] + proposal, 2241 b[o], 2242 f[o], 2243 false 2244 ); 2245 2246 // Stop if one of the handles can't move. 2247 if (to === false) { 2248 proposal = 0; 2249 } else { 2250 proposal = to - proposals[handleNumber]; 2251 proposals[handleNumber] = to; 2252 } 2253 }); 2254 } 2255 2256 // If using one handle, check backward AND forward 2257 else { 2258 b = f = [true]; 2259 } 2260 2261 var state = false; 2262 2263 // Step 2: Try to set the handles with the found percentage 2264 handleNumbers.forEach(function(handleNumber, o) { 2265 state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state; 2266 }); 2267 2268 // Step 3: If a handle moved, fire events 2269 if (state) { 2270 handleNumbers.forEach(function(handleNumber) { 2271 fireEvent("update", handleNumber); 2272 fireEvent("slide", handleNumber); 2273 }); 2274 } 2275 } 2276 2277 // Takes a base value and an offset. This offset is used for the connect bar size. 2278 // In the initial design for this feature, the origin element was 1% wide. 2279 // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature 2280 // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223 2281 function transformDirection(a, b) { 2282 return options.dir ? 100 - a - b : a; 2283 } 2284 2285 // Updates scope_Locations and scope_Values, updates visual state 2286 function updateHandlePosition(handleNumber, to) { 2287 // Update locations. 2288 scope_Locations[handleNumber] = to; 2289 2290 // Convert the value to the slider stepping/range. 2291 scope_Values[handleNumber] = scope_Spectrum.fromStepping(to); 2292 2293 var translation = 10 * (transformDirection(to, 0) - scope_DirOffset); 2294 var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; 2295 2296 scope_Handles[handleNumber].style[options.transformRule] = translateRule; 2297 2298 updateConnect(handleNumber); 2299 updateConnect(handleNumber + 1); 2300 } 2301 2302 // Handles before the slider middle are stacked later = higher, 2303 // Handles after the middle later is lower 2304 // [[7] [8] .......... | .......... [5] [4] 2305 function setZindex() {
vendor: 13,796 bytes, lines 2306-2685
2306 scope_HandleNumbers.forEach(function(handleNumber) { 2307 var dir = scope_Locations[handleNumber] > 50 ? -1 : 1; 2308 var zIndex = 3 + (scope_Handles.length + dir * handleNumber); 2309 scope_Handles[handleNumber].style.zIndex = zIndex; 2310 }); 2311 } 2312 2313 // Test suggested values and apply margin, step. 2314 // if exactInput is true, don't run checkHandlePosition, then the handle can be placed in between steps (#436) 2315 function setHandle(handleNumber, to, lookBackward, lookForward, exactInput) { 2316 if (!exactInput) { 2317 to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward, false); 2318 } 2319 2320 if (to === false) { 2321 return false; 2322 } 2323 2324 updateHandlePosition(handleNumber, to); 2325 2326 return true; 2327 } 2328 2329 // Updates style attribute for connect nodes 2330 function updateConnect(index) { 2331 // Skip connects set to false 2332 if (!scope_Connects[index]) { 2333 return; 2334 } 2335 2336 var l = 0; 2337 var h = 100; 2338 2339 if (index !== 0) { 2340 l = scope_Locations[index - 1]; 2341 } 2342 2343 if (index !== scope_Connects.length - 1) { 2344 h = scope_Locations[index]; 2345 } 2346 2347 // We use two rules: 2348 // 'translate' to change the left/top offset; 2349 // 'scale' to change the width of the element; 2350 // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base) 2351 var connectWidth = h - l; 2352 var translateRule = "translate(" + inRuleOrder(transformDirection(l, connectWidth) + "%", "0") + ")"; 2353 var scaleRule = "scale(" + inRuleOrder(connectWidth / 100, "1") + ")"; 2354 2355 scope_Connects[index].style[options.transformRule] = translateRule + " " + scaleRule; 2356 } 2357 2358 // Parses value passed to .set method. Returns current value if not parse-able. 2359 function resolveToValue(to, handleNumber) { 2360 // Setting with null indicates an 'ignore'. 2361 // Inputting 'false' is invalid. 2362 if (to === null || to === false || to === undefined) { 2363 return scope_Locations[handleNumber]; 2364 } 2365 2366 // If a formatted number was passed, attempt to decode it. 2367 if (typeof to === "number") { 2368 to = String(to); 2369 } 2370 2371 to = options.format.from(to); 2372 to = scope_Spectrum.toStepping(to); 2373 2374 // If parsing the number failed, use the current value. 2375 if (to === false || isNaN(to)) { 2376 return scope_Locations[handleNumber]; 2377 } 2378 2379 return to; 2380 } 2381 2382 // Set the slider value. 2383 function valueSet(input, fireSetEvent, exactInput) { 2384 var values = asArray(input); 2385 var isInit = scope_Locations[0] === undefined; 2386 2387 // Event fires by default 2388 fireSetEvent = fireSetEvent === undefined ? true : !!fireSetEvent; 2389 2390 // Animation is optional. 2391 // Make sure the initial values were set before using animated placement. 2392 if (options.animate && !isInit) { 2393 addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); 2394 } 2395 2396 // First pass, without lookAhead but with lookBackward. Values are set from left to right. 2397 scope_HandleNumbers.forEach(function(handleNumber) { 2398 setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false, exactInput); 2399 }); 2400 2401 var i = scope_HandleNumbers.length === 1 ? 0 : 1; 2402 2403 // Secondary passes. Now that all base values are set, apply constraints. 2404 // Iterate all handles to ensure constraints are applied for the entire slider (Issue #1009) 2405 for (; i < scope_HandleNumbers.length; ++i) { 2406 scope_HandleNumbers.forEach(function(handleNumber) { 2407 setHandle(handleNumber, scope_Locations[handleNumber], true, true, exactInput); 2408 }); 2409 } 2410 2411 setZindex(); 2412 2413 scope_HandleNumbers.forEach(function(handleNumber) { 2414 fireEvent("update", handleNumber); 2415 2416 // Fire the event only for handles that received a new value, as per #579 2417 if (values[handleNumber] !== null && fireSetEvent) { 2418 fireEvent("set", handleNumber); 2419 } 2420 }); 2421 } 2422 2423 // Reset slider to initial values 2424 function valueReset(fireSetEvent) { 2425 valueSet(options.start, fireSetEvent); 2426 } 2427 2428 // Set value for a single handle 2429 function valueSetHandle(handleNumber, value, fireSetEvent, exactInput) { 2430 // Ensure numeric input 2431 handleNumber = Number(handleNumber); 2432 2433 if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) { 2434 throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber); 2435 } 2436 2437 // Look both backward and forward, since we don't want this handle to "push" other handles (#960); 2438 // The exactInput argument can be used to ignore slider stepping (#436) 2439 setHandle(handleNumber, resolveToValue(value, handleNumber), true, true, exactInput); 2440 2441 fireEvent("update", handleNumber); 2442 2443 if (fireSetEvent) { 2444 fireEvent("set", handleNumber); 2445 } 2446 } 2447 2448 // Get the slider value. 2449 function valueGet() { 2450 var values = scope_Values.map(options.format.to); 2451 2452 // If only one handle is used, return a single value. 2453 if (values.length === 1) { 2454 return values[0]; 2455 } 2456 2457 return values; 2458 } 2459 2460 // Removes classes from the root and empties it. 2461 function destroy() { 2462 // remove protected internal listeners 2463 removeEvent(INTERNAL_EVENT_NS.aria); 2464 removeEvent(INTERNAL_EVENT_NS.tooltips); 2465 2466 for (var key in options.cssClasses) { 2467 if (!options.cssClasses.hasOwnProperty(key)) { 2468 continue; 2469 } 2470 removeClass(scope_Target, options.cssClasses[key]); 2471 } 2472 2473 while (scope_Target.firstChild) { 2474 scope_Target.removeChild(scope_Target.firstChild); 2475 } 2476 2477 delete scope_Target.noUiSlider; 2478 } 2479 2480 function getNextStepsForHandle(handleNumber) { 2481 var location = scope_Locations[handleNumber]; 2482 var nearbySteps = scope_Spectrum.getNearbySteps(location); 2483 var value = scope_Values[handleNumber]; 2484 var increment = nearbySteps.thisStep.step; 2485 var decrement = null; 2486 2487 // If snapped, directly use defined step value 2488 if (options.snap) { 2489 return [ 2490 value - nearbySteps.stepBefore.startValue || null, 2491 nearbySteps.stepAfter.startValue - value || null 2492 ]; 2493 } 2494 2495 // If the next value in this step moves into the next step, 2496 // the increment is the start of the next step - the current value 2497 if (increment !== false) { 2498 if (value + increment > nearbySteps.stepAfter.startValue) { 2499 increment = nearbySteps.stepAfter.startValue - value; 2500 } 2501 } 2502 2503 // If the value is beyond the starting point 2504 if (value > nearbySteps.thisStep.startValue) { 2505 decrement = nearbySteps.thisStep.step; 2506 } else if (nearbySteps.stepBefore.step === false) { 2507 decrement = false; 2508 } 2509 2510 // If a handle is at the start of a step, it always steps back into the previous step first 2511 else { 2512 decrement = value - nearbySteps.stepBefore.highestStep; 2513 } 2514 2515 // Now, if at the slider edges, there is no in/decrement 2516 if (location === 100) { 2517 increment = null; 2518 } else if (location === 0) { 2519 decrement = null; 2520 } 2521 2522 // As per #391, the comparison for the decrement step can have some rounding issues. 2523 var stepDecimals = scope_Spectrum.countStepDecimals(); 2524 2525 // Round per #391 2526 if (increment !== null && increment !== false) { 2527 increment = Number(increment.toFixed(stepDecimals)); 2528 } 2529 2530 if (decrement !== null && decrement !== false) { 2531 decrement = Number(decrement.toFixed(stepDecimals)); 2532 } 2533 2534 return [decrement, increment]; 2535 } 2536 2537 // Get the current step size for the slider. 2538 function getNextSteps() { 2539 return scope_HandleNumbers.map(getNextStepsForHandle); 2540 } 2541 2542 // Updateable: margin, limit, padding, step, range, animate, snap 2543 function updateOptions(optionsToUpdate, fireSetEvent) { 2544 // Spectrum is created using the range, snap, direction and step options. 2545 // 'snap' and 'step' can be updated. 2546 // If 'snap' and 'step' are not passed, they should remain unchanged. 2547 var v = valueGet(); 2548 2549 var updateAble = [ 2550 "margin", 2551 "limit", 2552 "padding", 2553 "range", 2554 "animate", 2555 "snap", 2556 "step", 2557 "format", 2558 "pips", 2559 "tooltips" 2560 ]; 2561 2562 // Only change options that we're actually passed to update. 2563 updateAble.forEach(function(name) { 2564 // Check for undefined. null removes the value. 2565 if (optionsToUpdate[name] !== undefined) { 2566 originalOptions[name] = optionsToUpdate[name]; 2567 } 2568 }); 2569 2570 var newOptions = testOptions(originalOptions); 2571 2572 // Load new options into the slider state 2573 updateAble.forEach(function(name) { 2574 if (optionsToUpdate[name] !== undefined) { 2575 options[name] = newOptions[name]; 2576 } 2577 }); 2578 2579 scope_Spectrum = newOptions.spectrum; 2580 2581 // Limit, margin and padding depend on the spectrum but are stored outside of it. (#677) 2582 options.margin = newOptions.margin; 2583 options.limit = newOptions.limit; 2584 options.padding = newOptions.padding; 2585 2586 // Update pips, removes existing. 2587 if (options.pips) { 2588 pips(options.pips); 2589 } else { 2590 removePips(); 2591 } 2592 2593 // Update tooltips, removes existing. 2594 if (options.tooltips) { 2595 tooltips(); 2596 } else { 2597 removeTooltips(); 2598 } 2599 2600 // Invalidate the current positioning so valueSet forces an update. 2601 scope_Locations = []; 2602 valueSet(optionsToUpdate.start || v, fireSetEvent); 2603 } 2604 2605 // Initialization steps 2606 function setupSlider() { 2607 // Create the base element, initialize HTML and set classes. 2608 // Add handles and connect elements. 2609 scope_Base = addSlider(scope_Target); 2610 2611 addElements(options.connect, scope_Base); 2612 2613 // Attach user events. 2614 bindSliderEvents(options.events); 2615 2616 // Use the public value method to set the start values. 2617 valueSet(options.start); 2618 2619 if (options.pips) { 2620 pips(options.pips); 2621 } 2622 2623 if (options.tooltips) { 2624 tooltips(); 2625 } 2626 2627 aria(); 2628 } 2629 2630 setupSlider(); 2631 2632 // noinspection JSUnusedGlobalSymbols 2633 scope_Self = { 2634 destroy: destroy, 2635 steps: getNextSteps, 2636 on: bindEvent, 2637 off: removeEvent, 2638 get: valueGet, 2639 set: valueSet, 2640 setHandle: valueSetHandle, 2641 reset: valueReset, 2642 // Exposed for unit testing, don't use this in your application. 2643 __moveHandles: function(a, b, c) { 2644 moveHandles(a, b, scope_Locations, c); 2645 }, 2646 options: originalOptions, // Issue #600, #678 2647 updateOptions: updateOptions, 2648 target: scope_Target, // Issue #597 2649 removePips: removePips, 2650 removeTooltips: removeTooltips, 2651 getTooltips: function() { 2652 return scope_Tooltips; 2653 }, 2654 getOrigins: function() { 2655 return scope_Handles; 2656 }, 2657 pips: pips // Issue #594 2658 }; 2659 2660 return scope_Self; 2661 } 2662 2663 // Run the standard initializer 2664 function initialize(target, originalOptions) { 2665 if (!target || !target.nodeName) { 2666 throw new Error("noUiSlider (" + VERSION + "): create requires a single element, got: " + target); 2667 } 2668 2669 // Throw an error if the slider was already initialized. 2670 if (target.noUiSlider) { 2671 throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized."); 2672 } 2673 2674 // Test the options and create the slider environment; 2675 var options = testOptions(originalOptions, target); 2676 var api = scope(target, options, originalOptions); 2677 2678 target.noUiSlider = api; 2679 2680 return api; 2681 } 2682 2683 // Use an object instead of a function for future expandability; 2684 return { 2685 // Exposed for unit testing, don't use this in your application.
2686 __spectrum: Spectrum, 2687 version: VERSION, 2688 // A reference to the default classes, allows global changes. 2689 // Use the cssClasses option for changes to one slider. 2690 cssClasses: cssClasses, 2691 create: initialize 2692 }; 2693});
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.