PageSourceSearch

https://mees.co.jp/js/nouislider.js

js mees.co.jp collected 2026-09-25 13:29:37 UTC 61,412 bytes, 2,256 lines download raw bytes

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

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.