materialize-sass 0.97.8 → 0.98.0

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