pghero_fork 2.7.3

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