PageSourceSearch

https://eservices.ggmc.gov.gy/lib/nouislider/distribute/nouislider.js

js ggmc.gov.gy collected 2026-09-25 05:37:54 UTC 55,553 bytes, 2,052 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.