1 | const isNumeric = require('is-numeric')
|
2 | const css = require('dom-css')
|
3 | const isMobile = require('is-mobile')()
|
4 | const format = require('param-case')
|
5 | const clamp = require('mumath/clamp')
|
6 | const EventEmitter = require('events').EventEmitter
|
7 | const inherits = require('inherits');
|
8 | const precision = require('mumath/precision');
|
9 |
|
10 | module.exports = Range
|
11 |
|
12 | inherits(Range, EventEmitter);
|
13 |
|
14 | function Range (opts) {
|
15 | if (!(this instanceof Range)) return new Range(opts);
|
16 |
|
17 | this.update(opts);
|
18 | }
|
19 |
|
20 | Range.prototype.update = function (opts) {
|
21 | var self = this
|
22 | var scaleValue, scaleValueInverse, logmin, logmax, logsign, input, handle, panel;
|
23 |
|
24 | if (!!opts.step && !!opts.steps) {
|
25 | throw new Error('Cannot specify both step and steps. Got step = ' + opts.step + ', steps = ', opts.steps)
|
26 | }
|
27 |
|
28 | opts.container.innerHTML = '';
|
29 |
|
30 | if (opts.step) {
|
31 | var prec = precision(opts.step) || 1;
|
32 | }
|
33 | else {
|
34 | var prec = precision( (opts.max - opts.min) / opts.steps ) || 1;
|
35 | }
|
36 |
|
37 |
|
38 | if (opts.scale === 'log' || opts.log) {
|
39 | scaleValue = function (x) {
|
40 | return logsign * Math.exp(Math.log(logmin) + (Math.log(logmax) - Math.log(logmin)) * x / 100)
|
41 | }
|
42 | scaleValueInverse = function (y) {
|
43 | return (Math.log(y * logsign) - Math.log(logmin)) * 100 / (Math.log(logmax) - Math.log(logmin))
|
44 | }
|
45 | } else {
|
46 | scaleValue = scaleValueInverse = function (x) { return x }
|
47 | }
|
48 |
|
49 | if (!Array.isArray(opts.value)) {
|
50 | opts.value = []
|
51 | }
|
52 | if (opts.scale === 'log' || opts.log) {
|
53 |
|
54 | opts.max = (isNumeric(opts.max)) ? opts.max : 100
|
55 | opts.min = (isNumeric(opts.min)) ? opts.min : 0.1
|
56 |
|
57 |
|
58 | if (opts.min * opts.max <= 0) {
|
59 | throw new Error('Log range min/max must have the same sign and not equal zero. Got min = ' + opts.min + ', max = ' + opts.max)
|
60 | } else {
|
61 |
|
62 | logmin = opts.min
|
63 | logmax = opts.max
|
64 | logsign = opts.min > 0 ? 1 : -1
|
65 |
|
66 |
|
67 | logmin = Math.abs(logmin)
|
68 | logmax = Math.abs(logmax)
|
69 |
|
70 |
|
71 | opts.min = 0
|
72 | opts.max = 100
|
73 |
|
74 |
|
75 | if (isNumeric(opts.step)) {
|
76 | throw new Error('Log may only use steps (integer number of steps), not a step value. Got step =' + opts.step)
|
77 | }
|
78 |
|
79 | opts.step = 1
|
80 | }
|
81 |
|
82 | opts.value = [
|
83 | scaleValueInverse(isNumeric(opts.value[0]) ? opts.value[0] : scaleValue(opts.min + (opts.max - opts.min) * 0.25)),
|
84 | scaleValueInverse(isNumeric(opts.value[1]) ? opts.value[1] : scaleValue(opts.min + (opts.max - opts.min) * 0.75))
|
85 | ]
|
86 |
|
87 | if (scaleValue(opts.value[0]) * scaleValue(opts.max) <= 0 || scaleValue(opts.value[1]) * scaleValue(opts.max) <= 0) {
|
88 | throw new Error('Log range initial value must have the same sign as min/max and must not equal zero. Got initial value = [' + scaleValue(opts.value[0]) + ', ' + scaleValue(opts.value[1]) + ']')
|
89 | }
|
90 | } else {
|
91 |
|
92 | opts.max = (isNumeric(opts.max)) ? opts.max : 100
|
93 | opts.min = (isNumeric(opts.min)) ? opts.min : 0
|
94 | opts.step = (isNumeric(opts.step)) ? opts.step : (opts.max - opts.min) / 100
|
95 |
|
96 | opts.value = [
|
97 | isNumeric(opts.value[0]) ? opts.value[0] : (opts.min + opts.max) * 0.25,
|
98 | isNumeric(opts.value[1]) ? opts.value[1] : (opts.min + opts.max) * 0.75
|
99 | ]
|
100 | }
|
101 |
|
102 |
|
103 | if (isNumeric(opts.steps)) {
|
104 | opts.step = isNumeric(opts.steps) ? (opts.max - opts.min) / opts.steps : opts.step
|
105 | }
|
106 |
|
107 |
|
108 | opts.value[0] = opts.min + opts.step * Math.round((opts.value[0] - opts.min) / opts.step)
|
109 | opts.value[1] = opts.min + opts.step * Math.round((opts.value[1] - opts.min) / opts.step)
|
110 |
|
111 |
|
112 |
|
113 | var lValue = require('./value')({
|
114 | container: opts.container,
|
115 | value: scaleValue(opts.value[0]).toFixed(prec),
|
116 | type: 'text',
|
117 | left: true,
|
118 | disabled: opts.disabled,
|
119 | id: opts.id,
|
120 | className: 'settings-panel-interval-value settings-panel-interval-value--left',
|
121 | input: v => {
|
122 |
|
123 | }
|
124 | })
|
125 |
|
126 | panel = opts.container.parentNode;
|
127 |
|
128 | input = opts.container.appendChild(document.createElement('span'))
|
129 | input.id = 'settings-panel-interval'
|
130 | input.className = 'settings-panel-interval'
|
131 |
|
132 | handle = document.createElement('span')
|
133 | handle.className = 'settings-panel-interval-handle'
|
134 | handle.value = 50;
|
135 | handle.min = 0;
|
136 | handle.max = 50;
|
137 | input.appendChild(handle)
|
138 |
|
139 | var value = opts.value
|
140 |
|
141 |
|
142 | var rValue = require('./value')({
|
143 | container: opts.container,
|
144 | disabled: opts.disabled,
|
145 | value: scaleValue(opts.value[1]).toFixed(prec),
|
146 | type: 'text',
|
147 | className: 'settings-panel-interval-value settings-panel-interval-value--right',
|
148 | input: v => {
|
149 |
|
150 | }
|
151 | })
|
152 |
|
153 | function setHandleCSS () {
|
154 | let left = ((value[0] - opts.min) / (opts.max - opts.min) * 100);
|
155 | let right = (100 - (value[1] - opts.min) / (opts.max - opts.min) * 100);
|
156 | css(handle, {
|
157 | left: left + '%',
|
158 | width: (100 - left - right) + '%'
|
159 | });
|
160 | opts.container.style.setProperty('--low', left + '%');
|
161 | opts.container.style.setProperty('--high', 100 - right + '%');
|
162 | lValue.style.setProperty('--value', left + '%');
|
163 | rValue.style.setProperty('--value', 100 - right + '%');
|
164 | }
|
165 |
|
166 |
|
167 | setHandleCSS()
|
168 |
|
169 | var activeIndex = -1
|
170 |
|
171 | function mouseX (ev) {
|
172 |
|
173 | return (ev.touches && ev.touches[0] || ev).pageX - input.getBoundingClientRect().left
|
174 | }
|
175 |
|
176 | function setActiveValue (fraction) {
|
177 | if (activeIndex === -1) {
|
178 | return
|
179 | }
|
180 |
|
181 |
|
182 | var lofrac = (value[0] - opts.min) / (opts.max - opts.min)
|
183 | var hifrac = (value[1] - opts.min) / (opts.max - opts.min)
|
184 |
|
185 |
|
186 | if (activeIndex === 0) {
|
187 | fraction = Math.min(hifrac, fraction)
|
188 | } else {
|
189 | fraction = Math.max(lofrac, fraction)
|
190 | }
|
191 |
|
192 |
|
193 | var newValue = opts.min + Math.round((opts.max - opts.min) * fraction / opts.step) * opts.step
|
194 |
|
195 |
|
196 | value[activeIndex] = newValue
|
197 |
|
198 |
|
199 | setHandleCSS()
|
200 | input.oninput()
|
201 | }
|
202 |
|
203 | var mousemoveListener = function (ev) {
|
204 | if (ev.target === input || ev.target === handle) ev.preventDefault()
|
205 |
|
206 | var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1)
|
207 |
|
208 | setActiveValue(fraction)
|
209 | }
|
210 |
|
211 | var mouseupListener = function (ev) {
|
212 | panel.classList.remove('settings-panel-interval-dragging')
|
213 |
|
214 | document.removeEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener)
|
215 | document.removeEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener)
|
216 |
|
217 | activeIndex = -1
|
218 | }
|
219 |
|
220 | input.addEventListener(isMobile ? 'touchstart' : 'mousedown', function (ev) {
|
221 |
|
222 | panel.classList.add('settings-panel-interval-dragging')
|
223 |
|
224 |
|
225 | var fraction = clamp(mouseX(ev) / input.offsetWidth, 0, 1)
|
226 |
|
227 |
|
228 | var lofrac = (value[0] - opts.min) / (opts.max - opts.min)
|
229 | var hifrac = (value[1] - opts.min) / (opts.max - opts.min)
|
230 |
|
231 |
|
232 |
|
233 | lofrac -= Math.abs(opts.max - opts.min) * 1e-15
|
234 | hifrac += Math.abs(opts.max - opts.min) * 1e-15
|
235 |
|
236 |
|
237 | var lodiff = Math.abs(lofrac - fraction)
|
238 | var hidiff = Math.abs(hifrac - fraction)
|
239 |
|
240 | activeIndex = lodiff < hidiff ? 0 : 1
|
241 |
|
242 |
|
243 |
|
244 | document.addEventListener(isMobile ? 'touchmove' : 'mousemove', mousemoveListener)
|
245 | document.addEventListener(isMobile ? 'touchend' : 'mouseup', mouseupListener)
|
246 | })
|
247 |
|
248 | setTimeout(() => {
|
249 | var scaledLValue = scaleValue(value[0])
|
250 | var scaledRValue = scaleValue(value[1])
|
251 | lValue.value = scaledLValue.toFixed(prec)
|
252 | rValue.value = scaledRValue.toFixed(prec)
|
253 | this.emit('init', [scaledLValue, scaledRValue])
|
254 | })
|
255 |
|
256 | input.oninput = () => {
|
257 | var scaledLValue = scaleValue(value[0])
|
258 | var scaledRValue = scaleValue(value[1])
|
259 | lValue.value = scaledLValue.toFixed(prec)
|
260 | rValue.value = scaledRValue.toFixed(prec)
|
261 | this.emit('input', [scaledLValue, scaledRValue])
|
262 | }
|
263 |
|
264 | return this;
|
265 | } |
\ | No newline at end of file |