@umbraco-ui/uui-range-slider 1.2.0-rc.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { UUIHorizontalPulseKeyframes } from '@umbraco-ui/uui-base/lib/animations';
2
2
  import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
3
3
  import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
4
- import { LitElement, svg, html, css } from 'lit';
4
+ import { LitElement, html, svg, nothing, css } from 'lit';
5
5
  import { property, state, query } from 'lit/decorators.js';
6
6
  import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
7
7
 
@@ -21,115 +21,100 @@ var __decorateClass = (decorators, target, key, kind) => {
21
21
  __defProp(target, key, result);
22
22
  return result;
23
23
  };
24
+ var __accessCheck = (obj, member, msg) => {
25
+ if (!member.has(obj))
26
+ throw TypeError("Cannot " + msg);
27
+ };
28
+ var __privateAdd = (obj, member, value) => {
29
+ if (member.has(obj))
30
+ throw TypeError("Cannot add the same private member more than once");
31
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
32
+ };
33
+ var __privateMethod = (obj, member, method) => {
34
+ __accessCheck(obj, member, "access private method");
35
+ return method;
36
+ };
37
+ var _setValue, setValue_fn;
24
38
  const TRACK_PADDING = 12;
25
39
  const STEP_MIN_WIDTH = 24;
26
40
  let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
27
41
  constructor() {
28
42
  super();
43
+ __privateAdd(this, _setValue);
29
44
  this.disabled = false;
30
45
  this.step = 1;
31
46
  this.hideStepValues = false;
32
- this._min = 0;
33
- this._max = 100;
47
+ this.min = 0;
48
+ this.max = 100;
34
49
  this._valueLow = 0;
35
50
  this._valueHigh = 100;
36
- this._minGap = 1;
37
51
  this._trackWidth = 0;
38
- this._handle = {
39
- low: false,
40
- high: false,
41
- both: false,
42
- startPosition: 0,
43
- lowStart: 0,
44
- highStart: 0
45
- };
46
- this._onTouchStart = (e) => {
47
- e.preventDefault();
48
- if (!this.disabled) {
49
- const target = e.composedPath()[0];
50
- if (target == this._inputLow) {
51
- this._handle.low = true;
52
- } else if (target == this._inputHigh) {
53
- this._handle.high = true;
54
- } else {
55
- const thumb = this._getHandles(e.touches[0].pageX);
56
- if ((thumb == null ? void 0 : thumb.type) == "low" && this._handle.low == true) {
57
- this.valueLow = thumb.value;
58
- } else if ((thumb == null ? void 0 : thumb.type) == "high" && this._handle.high == true) {
59
- this.valueHigh = thumb.value;
60
- }
61
- }
62
- window.addEventListener("touchend", this._onTouchEnd);
63
- window.addEventListener("touchmove", this._onTouchMove);
64
- }
65
- };
66
- this._onTouchMove = (e) => {
67
- if (this._innerSliderTrack) {
68
- const offsetX = e.touches[0].pageX - this._innerSliderTrack.getBoundingClientRect().left;
69
- if (this._handle.both == true) {
70
- this._updateBothValues(offsetX);
71
- } else if (this._handle.low == true) {
72
- this.valueLow = this._getValue(offsetX);
73
- } else if (this._handle.high == true) {
74
- this.valueHigh = this._getValue(offsetX);
75
- }
76
- }
77
- };
78
- this._onTouchEnd = () => {
79
- this.stopMoving();
80
- };
52
+ this._currentThumbFocus = "low";
53
+ this._startPos = 0;
54
+ this._startLow = 0;
55
+ this._startHigh = 0;
81
56
  this._onMouseDown = (e) => {
82
- if (!this.disabled) {
83
- const target = e.composedPath()[0];
84
- if (target == this._inputLow) {
85
- this._handle.low = true;
86
- } else if (target == this._inputHigh) {
87
- this._handle.high = true;
88
- } else {
89
- const thumb = this._getHandles(e.pageX);
90
- if ((thumb == null ? void 0 : thumb.type) == "low" && this._handle.low == true) {
91
- this.valueLow = thumb.value;
92
- } else if ((thumb == null ? void 0 : thumb.type) == "high" && this._handle.high == true) {
93
- this.valueHigh = thumb.value;
94
- }
95
- }
96
- window.addEventListener("mouseup", this._onMouseUp);
97
- window.addEventListener("mousemove", this._onMouseMove);
98
- }
57
+ e.preventDefault();
58
+ if (this.disabled)
59
+ return;
60
+ window.addEventListener("mouseup", this._onMouseUp);
61
+ window.addEventListener("mousemove", this._onMouseMove);
62
+ const target = e.composedPath()[0];
63
+ const pageX = e.pageX;
64
+ target == this._bothThumbsTarget ? this._grabbingBoth = true : this._grabbingBoth = false;
65
+ if (this._grabbingBoth) {
66
+ this._saveStartPoint(pageX, this.valueLow, this.valueHigh);
67
+ return;
68
+ }
69
+ this._moveThumb(pageX);
99
70
  };
100
71
  this._onMouseMove = (e) => {
101
- if (this._handle.both == true) {
102
- e.preventDefault();
103
- this._updateBothValues(e.offsetX);
104
- } else if (this._handle.low == true) {
105
- const newVal = this._getValue(e.offsetX);
106
- if (newVal != this.valueLow) {
107
- this.valueLow = newVal;
108
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
109
- }
110
- } else if (this._handle.high == true) {
111
- const newVal = this._getValue(e.offsetX);
112
- if (newVal != this.valueHigh) {
113
- this.valueHigh = newVal;
114
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
115
- }
116
- }
72
+ e.preventDefault();
73
+ const pageX = e.pageX;
74
+ const val = this._getValue(pageX);
75
+ if (!this._grabbingBoth)
76
+ this._setValueBasedOnCurrentThumb(
77
+ this._validateValueBasedOnCurrentThumb(val)
78
+ );
79
+ else
80
+ this._moveBoth(pageX);
81
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
117
82
  };
118
83
  this._onMouseUp = () => {
119
- this.stopMoving();
84
+ this._stop();
120
85
  window.removeEventListener("mouseup", this._onMouseUp);
121
86
  window.removeEventListener("mousemove", this._onMouseMove);
122
87
  };
123
- this.stopMoving = () => {
124
- this._handle.both = false;
125
- this._handle.high = false;
126
- this._handle.low = false;
127
- this._handle.startPosition = 0;
128
- this.pristine = false;
129
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.CHANGE));
88
+ this._onTouchStart = (e) => {
89
+ e.preventDefault();
90
+ if (this.disabled)
91
+ return;
92
+ window.addEventListener("touchend", this._onTouchEnd);
93
+ window.addEventListener("touchmove", this._onTouchMove);
94
+ const target = e.composedPath()[0];
95
+ const pageX = e.touches[0].pageX;
96
+ target == this._bothThumbsTarget ? this._grabbingBoth = true : this._grabbingBoth = false;
97
+ if (this._grabbingBoth) {
98
+ this._saveStartPoint(pageX, this.valueLow, this.valueHigh);
99
+ return;
100
+ }
101
+ this._moveThumb(pageX);
130
102
  };
131
- this._onInputMouseDown = (e) => {
132
- e.stopPropagation();
103
+ this._onTouchMove = (e) => {
104
+ const pageX = e.touches[0].pageX;
105
+ const val = this._getValue(pageX);
106
+ if (!this._grabbingBoth)
107
+ this._setValueBasedOnCurrentThumb(
108
+ this._validateValueBasedOnCurrentThumb(val)
109
+ );
110
+ else
111
+ this._moveBoth(pageX);
112
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
113
+ };
114
+ this._onTouchEnd = () => {
115
+ this._stop();
116
+ window.removeEventListener("touchend", this._onTouchEnd);
117
+ window.removeEventListener("touchmove", this._onTouchMove);
133
118
  };
134
119
  this.addEventListener("keypress", this._onKeypress);
135
120
  this.addEventListener("mousedown", this._onMouseDown);
@@ -142,7 +127,7 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
142
127
  this.addValidator(
143
128
  "stepMismatch",
144
129
  () => `Maxmimum gap needs to be higher than minimum gap`,
145
- () => !!this.maxGap && this.maxGap <= this.minGap
130
+ () => !!this.maxGap && !!this.minGap && this.maxGap <= this.minGap
146
131
  );
147
132
  this.addValidator(
148
133
  "rangeUnderflow",
@@ -155,36 +140,6 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
155
140
  () => this.valueHigh > this.max
156
141
  );
157
142
  }
158
- set min(newVal) {
159
- const old = this._min;
160
- if (newVal < this.max) {
161
- this._min = newVal;
162
- this.requestUpdate("min", old);
163
- if (this.valueLow < newVal) {
164
- const move = newVal - old;
165
- this.valueHigh = this.valueHigh + move;
166
- this.valueLow = this.valueLow + move;
167
- }
168
- }
169
- }
170
- get min() {
171
- return this._min;
172
- }
173
- set max(newVal) {
174
- const old = this._max;
175
- if (newVal > this.min) {
176
- this._max = newVal;
177
- this.requestUpdate("max", old);
178
- if (this.valueHigh > newVal) {
179
- const move = newVal - old;
180
- this.valueLow = this.valueLow + move;
181
- this.valueHigh = this.valueHigh + move;
182
- }
183
- }
184
- }
185
- get max() {
186
- return this._max;
187
- }
188
143
  get value() {
189
144
  return this._value;
190
145
  }
@@ -192,98 +147,72 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
192
147
  if (newVal instanceof String) {
193
148
  super.value = newVal;
194
149
  const values = newVal.split(",");
195
- this._valueLow = parseInt(values[0]);
196
- this._valueHigh = parseInt(values[1]);
150
+ this.valueLow = parseInt(values[0]);
151
+ this.valueHigh = parseInt(values[1]);
197
152
  }
198
153
  }
199
- set valueLow(newVal) {
200
- const old = this._valueLow;
201
- if (newVal <= this.valueHigh - this.minGap && newVal >= this.min && (!this.maxGap || this.maxGap >= this.valueHigh - newVal)) {
202
- this._valueLow = newVal;
203
- super.value = `${newVal},${this.valueHigh}`;
204
- this.requestUpdate("valueLow", old);
205
- } else if (newVal < this.min) {
154
+ set valueLow(newLow) {
155
+ const old = this._valueHigh;
156
+ if (newLow <= this.min) {
206
157
  this._valueLow = this.min;
207
- super.value = `${newVal},${this.min}`;
158
+ super.value = `${this.min},${this.valueHigh}`;
159
+ this.requestUpdate("valueLow", old);
160
+ return;
161
+ }
162
+ if (newLow >= this.valueHigh - this.step) {
163
+ this._valueLow = this.valueHigh - this.step;
164
+ super.value = `${this.valueHigh - this.step},${this.valueHigh}`;
208
165
  this.requestUpdate("valueLow", old);
166
+ return;
209
167
  }
168
+ this._valueLow = newLow;
169
+ super.value = `${newLow},${this.valueHigh}`;
170
+ this.requestUpdate("valueLow", old);
210
171
  }
211
172
  get valueLow() {
212
173
  return this._valueLow;
213
174
  }
214
- set valueHigh(newVal) {
175
+ set valueHigh(newHigh) {
215
176
  const old = this._valueHigh;
216
- if (newVal >= this.valueLow + this.minGap && newVal <= this.max && (!this.maxGap || this.maxGap >= newVal - this.valueLow)) {
217
- this._valueHigh = newVal;
218
- super.value = `${this.valueLow},${newVal}`;
219
- this.requestUpdate("valueHigh", old);
220
- } else if (newVal > this.max) {
221
- this.valueHigh = this.max;
177
+ if (newHigh >= this.max) {
178
+ this._valueHigh = this.max;
222
179
  super.value = `${this.valueLow},${this.max}`;
223
180
  this.requestUpdate("valueHigh", old);
181
+ return;
182
+ }
183
+ if (newHigh <= this.valueLow + this.step) {
184
+ this._valueHigh = this.valueLow + this.step;
185
+ super.value = `${this.valueLow},${this.valueLow + this.step}`;
186
+ this.requestUpdate("valueHigh", old);
187
+ return;
224
188
  }
189
+ this._valueHigh = newHigh;
190
+ super.value = `${this.valueLow},${newHigh}`;
191
+ this.requestUpdate("valueHigh", old);
225
192
  }
226
193
  get valueHigh() {
227
194
  return this._valueHigh;
228
195
  }
229
- set minGap(newVal) {
230
- const old = this._minGap;
231
- if (newVal > this.step && newVal > 0) {
232
- this._minGap = newVal;
233
- } else {
234
- this._minGap = this.step;
235
- }
236
- this.requestUpdate("minGap", old);
237
- }
238
- get minGap() {
239
- return this._minGap;
240
- }
241
- set maxGap(newVal) {
242
- const old = this._maxGap;
243
- if (newVal && newVal > this.minGap && newVal > this.step) {
244
- this._maxGap = newVal;
245
- } else {
246
- this._maxGap = void 0;
247
- }
248
- this.requestUpdate("maxGap", old);
249
- }
250
- get maxGap() {
251
- return this._maxGap;
196
+ getFormElement() {
197
+ return this._currentInputFocus ? this._currentInputFocus : this._inputLow;
252
198
  }
253
199
  focus() {
254
- this._inputLow.focus();
255
- }
256
- getFormElement() {
257
- return this._inputLow;
200
+ this._currentInputFocus ? this._currentInputFocus.focus() : this._inputLow.focus();
258
201
  }
259
- _onMinInput(e) {
260
- e.stopPropagation();
261
- const low = parseInt(this._inputLow.value);
262
- const high = parseInt(this._inputHigh.value) - this.minGap;
263
- if (low >= high && (!this.maxGap || this.maxGap >= high - low)) {
264
- this._inputLow.value = String(high);
265
- this.valueLow = high;
266
- } else {
267
- this.valueLow = parseInt(this._inputLow.value);
202
+ _onKeypress(e) {
203
+ if (e.key == "Enter") {
204
+ this.submit();
268
205
  }
269
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
270
206
  }
271
- _onMaxInput(e) {
272
- e.stopPropagation();
273
- const high = parseInt(this._inputHigh.value);
274
- const low = parseInt(this._inputLow.value) + this.minGap;
275
- if (high <= low && (!this.maxGap || this.maxGap >= high - low)) {
276
- this._inputHigh.value = String(low);
277
- this.valueHigh = low;
278
- } else {
279
- this.valueHigh = parseInt(this._inputHigh.value);
280
- }
281
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
207
+ _sliderLowThumbPosition() {
208
+ const ratio = (this.valueLow - this.min) / (this.max - this.min);
209
+ const valueLowPercent = `${Math.floor(ratio * 1e5) / 1e3}%`;
210
+ return valueLowPercent;
282
211
  }
283
- _onChange(e) {
284
- e.stopPropagation();
285
- this.pristine = false;
286
- this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.CHANGE));
212
+ _sliderHighThumbPosition() {
213
+ const ratio = (this.valueHigh - this.min) / (this.max - this.min);
214
+ const valueHighPercent = `${Math.floor(ratio * 1e5) / 1e3}%`;
215
+ return valueHighPercent;
287
216
  }
288
217
  _fillColor() {
289
218
  const percentStart = (this.valueLow - this.min) / (this.max - this.min) * 100;
@@ -291,71 +220,190 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
291
220
  this._innerColor.style.left = `${percentStart}%`;
292
221
  this._innerColor.style.right = `${100 - percentEnd}%`;
293
222
  }
294
- _onKeypress(e) {
295
- if (e.key == "Enter") {
296
- this.submit();
297
- }
223
+ _moveThumb(pageX) {
224
+ const value = this._getValue(pageX);
225
+ if (value >= this.valueHigh)
226
+ this._setThumb(this._thumbHigh);
227
+ if (value <= this.valueLow)
228
+ this._setThumb(this._thumbLow);
229
+ this._setValueBasedOnCurrentThumb(
230
+ this._validateValueBasedOnCurrentThumb(value)
231
+ );
298
232
  }
299
- _getValue(offsetX) {
300
- const p = offsetX / (this._trackWidth + TRACK_PADDING * 2);
301
- const trackDiff = this.max - this.min;
302
- const positionValue = p * trackDiff + this.min;
303
- const value = Math.round(positionValue / this.step) * this.step;
304
- return value;
233
+ _stop() {
234
+ this._grabbingBoth = false;
235
+ this.pristine = false;
236
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.CHANGE));
305
237
  }
306
- _getHandles(pageX) {
307
- const mouseXPosition = pageX - this._innerSliderTrack.getBoundingClientRect().left;
238
+ _setThumb(target) {
239
+ this._currentThumbFocus = target === this._thumbLow ? "low" : "high";
240
+ this._currentThumbFocus === "low" ? this._currentInputFocus = this._inputLow : this._currentInputFocus = this._inputHigh;
241
+ this.focus();
242
+ }
243
+ _setValueBasedOnCurrentThumb(val) {
244
+ this._currentThumbFocus === "low" ? this.valueLow = val : this.valueHigh = val;
245
+ }
246
+ _getValue(pageX) {
247
+ const mouseXPosition = pageX - this._innerTrack.getBoundingClientRect().left;
308
248
  const clickPercent = mouseXPosition / (this._trackWidth - TRACK_PADDING * 2);
309
249
  const clickedValue = clickPercent * (this.max - this.min) + this.min;
310
250
  const newValue = Math.round(clickedValue / this.step) * this.step;
311
- if (clickedValue < this.valueLow) {
312
- this._handle.low = true;
313
- return { type: "low", value: newValue };
314
- } else if (clickedValue > this.valueHigh) {
315
- this._handle.high = true;
316
- return { type: "high", value: newValue };
317
- } else if (clickedValue > this.valueLow && clickedValue < this.valueHigh) {
318
- this._handle.both = true;
319
- this._handle.lowStart = this.valueLow;
320
- this._handle.highStart = this.valueHigh;
321
- this._handle.startPosition = mouseXPosition;
322
- return { type: "both", value: newValue };
251
+ return newValue;
252
+ }
253
+ _validateLowByMinGap(value) {
254
+ if (!this.minGap || this.minGap <= this.step)
255
+ return value;
256
+ return value + this.minGap >= this.valueHigh ? this.valueHigh - this.minGap : value;
257
+ }
258
+ _validateLowByMaxGap(value) {
259
+ if (!this.maxGap)
260
+ return value;
261
+ return this.valueHigh - value >= this.maxGap ? this.valueHigh - this.maxGap : value;
262
+ }
263
+ _validateHighByMinGap(value) {
264
+ if (!this.minGap || this.minGap <= this.step)
265
+ return value;
266
+ return value <= this.valueLow + this.minGap ? this.valueLow + this.minGap : value;
267
+ }
268
+ _validateHighByMaxGap(value) {
269
+ if (!this.maxGap)
270
+ return value;
271
+ return value >= this.valueLow + this.maxGap ? this.valueLow + this.maxGap : value;
272
+ }
273
+ _validateValueBasedOnCurrentThumb(newValue) {
274
+ if (this._currentThumbFocus == "low") {
275
+ let newLow;
276
+ newLow = newValue < this.valueHigh - this.step ? newValue : this.valueHigh - this.step;
277
+ newLow = newLow >= this.min ? newLow : this.min;
278
+ newLow = this.minGap ? this._validateLowByMinGap(newLow) : newLow;
279
+ newLow = this.maxGap ? this._validateLowByMaxGap(newLow) : newLow;
280
+ return newLow;
323
281
  }
324
- return;
325
- }
326
- _updateBothValues(mousePosition) {
327
- const drag = mousePosition - this._handle.startPosition;
282
+ let newHigh;
283
+ newHigh = newValue > this.valueLow + this.step ? newValue : this.valueLow + this.step;
284
+ newHigh = newHigh <= this.max ? newHigh : this.max;
285
+ newHigh = this.minGap ? this._validateHighByMinGap(newHigh) : newHigh;
286
+ newHigh = this.maxGap ? this._validateHighByMaxGap(newHigh) : newHigh;
287
+ return newHigh;
288
+ }
289
+ _saveStartPoint(pageX, lowVal, highVal) {
290
+ this._startPos = pageX;
291
+ this._startLow = lowVal;
292
+ this._startHigh = highVal;
293
+ }
294
+ _moveBoth(pageX) {
295
+ const drag = pageX - this._startPos;
328
296
  const trackDiff = this.max - this.min;
329
297
  const dragPercent = drag / (this._trackWidth + TRACK_PADDING * 2);
330
298
  const dragValue = Math.round(dragPercent * trackDiff / this.step) * this.step;
331
- const newLow = this._handle.lowStart + dragValue;
332
- const newHigh = this._handle.highStart + dragValue;
333
- if (this.valueLow !== newLow && newLow >= this.min && newHigh <= this.max && dragValue != 0) {
334
- this.valueLow = newLow;
335
- this.valueHigh = newHigh;
299
+ const newValueLow = this._startLow + dragValue;
300
+ const newValueHigh = this._startHigh + dragValue;
301
+ const value = this._getValidatedValues(newValueLow, newValueHigh);
302
+ if (newValueLow === value.low && newValueHigh === value.high) {
303
+ this.valueLow = newValueLow;
304
+ this.valueHigh = newValueHigh;
336
305
  this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
337
306
  }
338
307
  }
339
- updated(changedProperties) {
340
- super.updated(changedProperties);
341
- this._trackWidth = this._sliderTrack.offsetWidth;
342
- this._fillColor();
308
+ _getValidatedValues(low, high) {
309
+ const validatedLow = low > this.min ? low : this.min;
310
+ const validatedHigh = high < this.max ? high : this.max;
311
+ return { low: validatedLow, high: validatedHigh };
312
+ }
313
+ _onChange(e) {
314
+ e.stopPropagation();
315
+ this.pristine = false;
316
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.CHANGE));
317
+ }
318
+ _onLowInput(e) {
319
+ e.stopPropagation();
320
+ let value = parseInt(this._inputLow.value);
321
+ value = this._validateLowByMinGap(value);
322
+ value = this._validateLowByMaxGap(value);
323
+ this._inputLow.value = String(value);
324
+ this.valueLow = value;
325
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
326
+ }
327
+ _onHighInput(e) {
328
+ e.stopPropagation();
329
+ let value = parseInt(this._inputHigh.value);
330
+ value = this._validateHighByMinGap(value);
331
+ value = this._validateHighByMaxGap(value);
332
+ this._inputHigh.value = String(value);
333
+ this.valueHigh = value;
334
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
343
335
  }
344
336
  connectedCallback() {
345
337
  super.connectedCallback();
338
+ __privateMethod(this, _setValue, setValue_fn).call(this);
346
339
  window.addEventListener("resize", () => {
347
- this._trackWidth = this._sliderTrack.offsetWidth;
340
+ this._trackWidth = this._outerTrack.offsetWidth;
348
341
  });
349
342
  }
350
- _sliderLowThumbPosition() {
351
- const ratio = (parseFloat(this.valueLow || "0") - this.min) / (this.max - this.min);
352
- const valueLowPercent = `${Math.floor(ratio * 1e5) / 1e3}%`;
353
- return valueLowPercent;
343
+ updated(changedProperties) {
344
+ super.updated(changedProperties);
345
+ this._trackWidth = this._outerTrack.offsetWidth;
346
+ this._fillColor();
354
347
  }
355
- _sliderHighThumbPosition() {
356
- const ratio = (parseFloat(this.valueHigh || "0") - this.min) / (this.max - this.min);
357
- const valueHighPercent = `${Math.floor(ratio * 1e5) / 1e3}%`;
358
- return valueHighPercent;
348
+ render() {
349
+ return html`
350
+ <div id="range-slider">
351
+ ${this.renderNativeInputs()}
352
+ <div id="inner-track">
353
+ <span class="color"><span class="color-target"></span></span>
354
+ ${this.renderStepsOutput()} ${this.renderThumbs()}
355
+ </div>
356
+ <div id="step-values">${this.renderStepValues()}</div>
357
+ </div>
358
+ `;
359
+ }
360
+ renderNativeInputs() {
361
+ return html`<input
362
+ id="low-input"
363
+ class="slider"
364
+ type="range"
365
+ .value="${String(this.valueLow)}"
366
+ min="${this.min}"
367
+ max="${this.max}"
368
+ step="${this.step}"
369
+ aria-label="${this.label} low value"
370
+ ?disabled="${this.disabled}"
371
+ @change="${this._onChange}"
372
+ @input="${this._onLowInput}" />
373
+ <input
374
+ id="high-input"
375
+ class="slider"
376
+ type="range"
377
+ .value="${String(this.valueHigh)}"
378
+ min="${this.min}"
379
+ max="${this.max}"
380
+ step="${this.step}"
381
+ aria-label="${this.label} high value"
382
+ ?disabled="${this.disabled}"
383
+ @change="${this._onChange}"
384
+ @input="${this._onHighInput}" />`;
385
+ }
386
+ renderThumbs() {
387
+ return html`<div
388
+ id="low-thumb"
389
+ class="thumb"
390
+ style="left: ${this._sliderLowThumbPosition()}">
391
+ <div class="value value-min">${this.valueLow}</div>
392
+ </div>
393
+ <div
394
+ id="high-thumb"
395
+ class="thumb"
396
+ style="left: ${this._sliderHighThumbPosition()}">
397
+ <div class="value value-max">${this.valueHigh}</div>
398
+ </div>`;
399
+ }
400
+ renderStepsOutput() {
401
+ return html`<div class="svg-wrapper">
402
+ <svg height="100%" width="100%">
403
+ <rect x="9" y="9" height="3" rx="2" />
404
+ ${this.renderSteps()}
405
+ </svg>
406
+ </div>`;
359
407
  }
360
408
  renderSteps() {
361
409
  const stepAmount = (this.max - this.min) / this.step;
@@ -381,10 +429,12 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
381
429
  return svg``;
382
430
  }
383
431
  }
384
- renderStepValues(hide) {
432
+ renderStepValues() {
433
+ if (this.hideStepValues)
434
+ return nothing;
385
435
  const stepAmount = (this.max - this.min) / this.step;
386
436
  const stepWidth = (this._trackWidth - TRACK_PADDING * 2) / stepAmount;
387
- if (stepWidth >= STEP_MIN_WIDTH && stepAmount <= 20 && !hide) {
437
+ if (stepWidth >= STEP_MIN_WIDTH && stepAmount <= 20) {
388
438
  let i = 0;
389
439
  const stepValues = [];
390
440
  for (i; i <= stepAmount; i++) {
@@ -397,70 +447,18 @@ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
397
447
  return html``;
398
448
  }
399
449
  }
400
- render() {
401
- return html`
402
- <div id="wrapper">
403
- <div class="slider-track">
404
- <div class="svg-wrapper">
405
- <svg height="100%" width="100%">
406
- <rect x="9" y="9" height="3" rx="2" />
407
- ${this.renderSteps()}
408
- </svg>
409
- </div>
410
- <div id="thumb-wrapper">
411
- <input
412
- type="range"
413
- id="min-slider"
414
- min="${this.min}"
415
- max="${this.max}"
416
- step="${this.step}"
417
- ?disabled="${this.disabled}"
418
- .value="${String(this.valueLow)}"
419
- aria-label="${this.label} low value"
420
- @mousedown="${this._onInputMouseDown}"
421
- @input="${this._onMinInput}"
422
- @change="${this._onChange}" />
423
- <div
424
- class="thumb thumb-min"
425
- style="left: ${this._sliderLowThumbPosition()}">
426
- <div class="value value-min">${this.valueLow}</div>
427
- </div>
428
- <input
429
- type="range"
430
- id="max-slider"
431
- min="${this.min}"
432
- max="${this.max}"
433
- step="${this.step}"
434
- ?disabled="${this.disabled}"
435
- .value="${String(this.valueHigh)}"
436
- aria-label="${this.label} high value"
437
- @mousedown="${this._onInputMouseDown}"
438
- @input="${this._onMaxInput}"
439
- @change="${this._onChange}" />
440
- <div
441
- class="thumb thumb-max"
442
- style="left: ${this._sliderHighThumbPosition()}">
443
- <div class="value value-max">${this.valueHigh}</div>
444
- </div>
445
- <div class="inner-track"><span class="color"></span></div>
446
- </div>
447
- </div>
448
- <div id="step-values">
449
- ${this.renderStepValues(this.hideStepValues)}
450
- </div>
451
- </div>
452
- `;
453
- }
450
+ };
451
+ _setValue = new WeakSet();
452
+ setValue_fn = function(val) {
453
+ this._value = val ? val : `${this.valueLow},${this.valueHigh}`;
454
454
  };
455
455
  UUIRangeSliderElement.styles = [
456
456
  UUIHorizontalPulseKeyframes,
457
457
  css`
458
458
  :host {
459
- position: relative;
459
+ display: block;
460
+ min-height: 50px;
460
461
  width: 100%;
461
- min-height: 30px;
462
- padding: 0;
463
- margin: 0;
464
462
  place-items: center;
465
463
  -webkit-user-select: none; /* Safari */
466
464
  -moz-user-select: none; /* Firefox */
@@ -473,154 +471,125 @@ UUIRangeSliderElement.styles = [
473
471
  cursor: default;
474
472
  }
475
473
 
476
- #wrapper {
477
- position: relative;
478
- border-radius: 20px;
479
- min-height: 40px;
480
- }
481
-
482
- #wrapper:focus-visible {
483
- outline: none;
484
- }
485
-
486
- #wrapper:focus-visible .slider-track {
487
- outline: calc(2px * var(--uui-show-focus-outline, 1)) solid
488
- var(--uui-color-focus,#3879ff);
489
- }
474
+ /** NATIVE INPUT STYLING */
490
475
 
491
- .slider-track {
492
- left: 0;
493
- right: 0;
494
- height: 0;
495
- position: absolute;
496
- height: 18px;
497
- border-radius: 10px;
498
- }
499
-
500
- .inner-track {
476
+ input {
477
+ -webkit-appearance: none;
478
+ -moz-appearance: none;
479
+ appearance: none;
501
480
  position: absolute;
502
- border-radius: 10px;
503
- top: 50%;
504
- transform: translateY(-50%);
481
+ top: 0;
482
+ background-color: transparent;
483
+ pointer-events: none;
505
484
  left: 0;
506
485
  right: 0;
507
- height: 3px;
508
- margin: -23px 0;
509
- z-index: -1;
510
- background-color: #a1a1a1;
486
+ border-radius: 20px;
511
487
  }
512
488
 
513
- .inner-track:focus {
514
- background-color: var(--uui-color-border-standalone,#c2c2c2);
489
+ input::-webkit-slider-thumb {
490
+ pointer-events: all;
491
+ position: relative;
492
+ z-index: 1;
493
+ outline: 0;
515
494
  }
516
495
 
517
- .inner-track .color {
518
- height: 3px;
519
- position: absolute;
520
- transition: left 120ms ease, right 120ms ease;
496
+ input::-moz-range-thumb {
497
+ pointer-events: all;
498
+ position: relative;
499
+ z-index: 10;
500
+ -moz-appearance: none;
501
+ background: linear-gradient(to bottom, #ededed 0%, #dedede 100%);
502
+ width: 11px;
521
503
  }
522
504
 
523
- input[type='range']:not([disabled]) ~ .inner-track .color {
524
- background-color: var(--uui-color-selected,#3544b1);
505
+ input::-moz-range-track {
506
+ position: relative;
507
+ z-index: -1;
508
+ background-color: rgba(0, 0, 0, 0.15);
509
+ border: 0;
525
510
  }
526
511
 
527
- input[type='range']:disabled ~ .inner-track .color {
528
- background-color: #555;
512
+ input:last-of-type::-moz-range-track {
513
+ -moz-appearance: none;
514
+ background: none transparent;
515
+ border: 0;
529
516
  }
530
517
 
531
- #thumb-wrapper {
532
- position: relative;
533
- margin: 0 ${TRACK_PADDING}px;
534
- }
518
+ /** TRACK */
535
519
 
536
- .thumb {
537
- width: 17px;
538
- height: 17px;
520
+ #inner-track .color-target {
539
521
  position: absolute;
540
- top: -31px;
541
- bottom: 0;
522
+ z-index: 2;
542
523
  left: 0;
543
- margin-left: -8px;
544
- margin-right: -8px;
545
- border-radius: 50%;
546
- box-sizing: border-box;
547
- background-color: var(--uui-color-surface,#fff);
548
- border: 2px solid var(--uui-color-selected,#3544b1);
549
- transition: 120ms left ease;
550
- z-index: 10;
524
+ right: 0;
525
+ height: 25px;
526
+ transform: translateY(-50%);
551
527
  }
552
528
 
553
- .thumb:after {
554
- content: '';
529
+ #inner-track .color {
530
+ height: 3px;
555
531
  position: absolute;
556
- top: 2px;
557
- left: 2px;
558
- height: 9px;
559
- width: 9px;
560
- border-radius: 50%;
561
- background-color: var(--uui-color-selected,#3544b1);
532
+ transition: background-color 320ms ease-out;
562
533
  }
563
534
 
564
- :host([disabled]) .thumb {
565
- background-color: var(--uui-color-disabled,#f3f3f5);
566
- border-color: var(--uui-palette-mine-grey,#3e3e3e);
567
- }
568
- :host([disabled]) .thumb:after {
569
- background-color: var(--uui-palette-mine-grey,#3e3e3e);
535
+ #range-slider #inner-track .color:has(.color-target:hover),
536
+ #range-slider #inner-track .color:has(.color-target:active) {
537
+ background-color: var(--uui-color-focus,#3879ff);
570
538
  }
571
539
 
572
- .thumb .value {
573
- position: absolute;
574
- box-sizing: border-box;
575
- font-weight: 700;
576
- bottom: 15px;
577
- left: 50%;
578
- width: 40px;
579
- margin-left: -20px;
580
- text-align: center;
581
- opacity: 1;
582
- transition: 120ms opacity;
583
- color: var(--uui-color-selected,#3544b1);
584
- visibility: hidden;
585
- opacity: 0;
586
- }
587
- :host([disabled]) .thumb .value {
588
- color: var(--uui-palette-mine-grey,#3e3e3e);
540
+ :host(:not([disabled])) #range-slider .color {
541
+ background-color: var(--uui-color-selected,#3544b1);
589
542
  }
590
543
 
591
- #wrapper:active .thumb .value,
592
- #wrapper:focus .thumb .value,
593
- #wrapper:hover .thumb .value {
594
- visibility: visible;
595
- opacity: 1;
544
+ :host([disabled]) #range-slider .color {
545
+ background-color: #555;
596
546
  }
597
547
 
598
- .svg-wrapper svg {
599
- margin-top: -6px;
600
- height: 30px;
548
+ #range-slider {
549
+ transform: translateY(50%);
550
+ position: relative;
551
+ height: 18px;
552
+ display: flex;
553
+ flex-direction: column;
601
554
  width: 100%;
602
555
  }
603
556
 
604
- #wrapper:hover .track-step,
605
- #wrapper:active .track-step {
606
- fill: #a1a1a1;
557
+ #inner-track {
558
+ border-radius: 10px;
559
+ position: absolute;
560
+ height: 3px;
561
+ background-color: var(--uui-color-border-standalone,#c2c2c2);
562
+ left: ${TRACK_PADDING}px; /* Match TRACK_MARGIN */
563
+ right: ${TRACK_PADDING}px; /* Match TRACK_MARGIN */
607
564
  }
608
565
 
566
+ #range-slider:hover #inner-track,
567
+ #range-slider:active #inner-track {
568
+ background-color: #a1a1a1;
569
+ }
570
+
571
+ /** STEP VALUES */
572
+
609
573
  .track-step {
610
574
  fill: var(--uui-color-border,#d8d7d9);
611
575
  }
612
576
 
613
- #wrapper .track-step.filled {
577
+ :host .track-step.filled {
614
578
  fill: var(--uui-color-selected,#3544b1) !important;
615
579
  }
616
580
 
617
- #wrapper .track-step.filled-disabled {
581
+ :host .track-step.filled-disabled {
618
582
  fill: var(--uui-palette-mine-grey,#3e3e3e) !important;
619
583
  }
620
584
 
585
+ #range-slider:hover .track-step,
586
+ #range-slider:active .track-step {
587
+ fill: #a1a1a1;
588
+ }
589
+
621
590
  #step-values {
622
591
  margin: 0 ${TRACK_PADDING}px; /* Match TRACK_MARGIN */
623
- padding-top: 24px;
592
+ padding-top: ${TRACK_PADDING + 3}px;
624
593
  display: flex;
625
594
  align-items: flex-end;
626
595
  box-sizing: border-box;
@@ -644,35 +613,123 @@ UUIRangeSliderElement.styles = [
644
613
  flex-grow: 0;
645
614
  }
646
615
 
647
- #input-wrapper {
648
- position: relative;
649
- margin: -45px ${TRACK_PADDING / 2}px 45px;
616
+ .svg-wrapper {
617
+ margin: 0 ${-1 * TRACK_PADDING}px;
618
+ height: 18px;
619
+ transform: translateY(-75%);
650
620
  }
651
621
 
652
- input {
653
- -webkit-appearance: none;
654
- -moz-appearance: none;
655
- appearance: none;
622
+ .svg-wrapper svg {
623
+ margin-top: ${TRACK_PADDING / 2}px;
624
+ }
625
+
626
+ /** FOCUS */
627
+
628
+ input[type='range'] {
656
629
  position: absolute;
657
- top: 0;
658
- background-color: transparent;
659
- pointer-events: none;
660
630
  left: 0;
661
631
  right: 0;
662
- margin: -30px -8px;
663
- z-index: 12;
664
- border-radius: 20px;
632
+ top: -50%;
665
633
  }
666
634
 
667
635
  input[type='range']:focus-visible {
668
636
  outline: none;
669
637
  }
670
638
 
639
+ #low-input:focus-visible ~ #inner-track #low-thumb,
640
+ #high-input:focus-visible ~ #inner-track #high-thumb,
641
+ #low-input:focus ~ #inner-track #low.thumb,
642
+ #high-input:focus ~ #inner-track #high-thumb,
643
+ #low-input:active ~ #inner-track #low.thumb,
644
+ #high-input:active ~ #inner-track #high-thumb {
645
+ outline: calc(2px * var(--uui-show-focus-outline, 1)) solid
646
+ var(--uui-color-focus,#3879ff);
647
+ }
648
+
671
649
  input[type='range']:focus + .thumb {
672
650
  outline: calc(2px * var(--uui-show-focus-outline, 1)) solid
673
651
  var(--uui-color-focus,#3879ff);
674
652
  }
675
653
 
654
+ #range-slider #inner-track .color:has(.color-target:hover) ~ #low-thumb,
655
+ #range-slider #inner-track .color:has(.color-target:active) ~ #low-thumb,
656
+ #range-slider #inner-track .color:has(.color-target:hover) ~ #high-thumb,
657
+ #range-slider
658
+ #inner-track
659
+ .color:has(.color-target:active)
660
+ ~ #high-thumb {
661
+ outline: calc(2px * var(--uui-show-focus-outline, 1)) solid
662
+ var(--uui-color-focus,#3879ff);
663
+ }
664
+
665
+ /** THUMBS */
666
+
667
+ .thumb {
668
+ z-index: 3;
669
+ transform: translateY(-50%);
670
+ position: absolute;
671
+ top: 2px;
672
+ bottom: 0px;
673
+ left: 0px;
674
+ height: 17px;
675
+ width: 17px;
676
+ margin-left: -8px;
677
+ margin-right: -8px;
678
+ border-radius: 50%;
679
+ box-sizing: border-box;
680
+ background-color: var(--uui-color-surface, #fff);
681
+ border: 2px solid var(--uui-color-selected, #3544b1);
682
+ transition: left 120ms ease 0s;
683
+ }
684
+
685
+ .thumb:after {
686
+ content: '';
687
+ position: absolute;
688
+ top: 2px;
689
+ left: 2px;
690
+ height: 9px;
691
+ width: 9px;
692
+ border-radius: 50%;
693
+ background-color: var(--uui-color-selected,#3544b1);
694
+ }
695
+
696
+ :host([disabled]) .thumb {
697
+ background-color: var(--uui-color-disabled,#f3f3f5);
698
+ border-color: var(--uui-palette-mine-grey,#3e3e3e);
699
+ }
700
+ :host([disabled]) .thumb:after {
701
+ background-color: var(--uui-palette-mine-grey,#3e3e3e);
702
+ }
703
+
704
+ .thumb .value {
705
+ position: absolute;
706
+ box-sizing: border-box;
707
+ font-weight: 700;
708
+ bottom: 15px;
709
+ left: 50%;
710
+ width: 40px;
711
+ margin-left: -20px;
712
+ text-align: center;
713
+ opacity: 1;
714
+ transition: 120ms opacity;
715
+ color: var(--uui-color-selected,#3544b1);
716
+ visibility: hidden;
717
+ opacity: 0;
718
+ }
719
+
720
+ :host([disabled]) .thumb .value {
721
+ color: var(--uui-palette-mine-grey,#3e3e3e);
722
+ }
723
+
724
+ #range-slider:active .thumb .value,
725
+ #range-slider:focus .thumb .value,
726
+ #range-slider:hover .thumb .value {
727
+ visibility: visible;
728
+ opacity: 1;
729
+ }
730
+
731
+ /** NATIVE THUMB STYLING */
732
+
676
733
  input[type='range']::-webkit-slider-thumb {
677
734
  -webkit-appearance: none;
678
735
  appearance: none;
@@ -733,10 +790,16 @@ __decorateClass([
733
790
  ], UUIRangeSliderElement.prototype, "hideStepValues", 2);
734
791
  __decorateClass([
735
792
  property({ type: Number })
736
- ], UUIRangeSliderElement.prototype, "min", 1);
793
+ ], UUIRangeSliderElement.prototype, "min", 2);
737
794
  __decorateClass([
738
795
  property({ type: Number })
739
- ], UUIRangeSliderElement.prototype, "max", 1);
796
+ ], UUIRangeSliderElement.prototype, "max", 2);
797
+ __decorateClass([
798
+ property({ type: Number, attribute: "min-gap" })
799
+ ], UUIRangeSliderElement.prototype, "minGap", 2);
800
+ __decorateClass([
801
+ property({ type: Number, attribute: "max-gap" })
802
+ ], UUIRangeSliderElement.prototype, "maxGap", 2);
740
803
  __decorateClass([
741
804
  property({ type: String })
742
805
  ], UUIRangeSliderElement.prototype, "value", 1);
@@ -747,32 +810,50 @@ __decorateClass([
747
810
  property({ type: Number, attribute: "value-high" })
748
811
  ], UUIRangeSliderElement.prototype, "valueHigh", 1);
749
812
  __decorateClass([
750
- property({ type: Number, attribute: "min-gap" })
751
- ], UUIRangeSliderElement.prototype, "minGap", 1);
813
+ state()
814
+ ], UUIRangeSliderElement.prototype, "_trackWidth", 2);
752
815
  __decorateClass([
753
- property({ type: Number, attribute: "max-gap" })
754
- ], UUIRangeSliderElement.prototype, "maxGap", 1);
816
+ state()
817
+ ], UUIRangeSliderElement.prototype, "_currentInputFocus", 2);
755
818
  __decorateClass([
756
- property({ type: Number })
757
- ], UUIRangeSliderElement.prototype, "_trackWidth", 2);
819
+ state()
820
+ ], UUIRangeSliderElement.prototype, "_currentThumbFocus", 2);
821
+ __decorateClass([
822
+ state()
823
+ ], UUIRangeSliderElement.prototype, "_grabbingBoth", 2);
824
+ __decorateClass([
825
+ state()
826
+ ], UUIRangeSliderElement.prototype, "_startPos", 2);
758
827
  __decorateClass([
759
828
  state()
760
- ], UUIRangeSliderElement.prototype, "_handle", 2);
829
+ ], UUIRangeSliderElement.prototype, "_startLow", 2);
761
830
  __decorateClass([
762
- query("#min-slider")
831
+ state()
832
+ ], UUIRangeSliderElement.prototype, "_startHigh", 2);
833
+ __decorateClass([
834
+ query("#low-input")
763
835
  ], UUIRangeSliderElement.prototype, "_inputLow", 2);
764
836
  __decorateClass([
765
- query("#max-slider")
837
+ query("#high-input")
766
838
  ], UUIRangeSliderElement.prototype, "_inputHigh", 2);
767
839
  __decorateClass([
768
- query(".slider-track")
769
- ], UUIRangeSliderElement.prototype, "_sliderTrack", 2);
840
+ query("#range-slider")
841
+ ], UUIRangeSliderElement.prototype, "_outerTrack", 2);
842
+ __decorateClass([
843
+ query("#inner-track")
844
+ ], UUIRangeSliderElement.prototype, "_innerTrack", 2);
770
845
  __decorateClass([
771
- query(".inner-track")
772
- ], UUIRangeSliderElement.prototype, "_innerSliderTrack", 2);
846
+ query("#low-thumb")
847
+ ], UUIRangeSliderElement.prototype, "_thumbLow", 2);
848
+ __decorateClass([
849
+ query("#high-thumb")
850
+ ], UUIRangeSliderElement.prototype, "_thumbHigh", 2);
773
851
  __decorateClass([
774
852
  query(".color")
775
853
  ], UUIRangeSliderElement.prototype, "_innerColor", 2);
854
+ __decorateClass([
855
+ query(".color-target")
856
+ ], UUIRangeSliderElement.prototype, "_bothThumbsTarget", 2);
776
857
  UUIRangeSliderElement = __decorateClass([
777
858
  defineElement("uui-range-slider")
778
859
  ], UUIRangeSliderElement);