@umbraco-ui/uui-range-slider 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 uui-app
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # uui-range-slider
2
+
3
+ ![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-range-slider?logoColor=%231B264F)
4
+
5
+ Umbraco style range-slider component.
6
+
7
+ ## Installation
8
+
9
+ ### ES imports
10
+
11
+ ```zsh
12
+ npm i @umbraco-ui/uui-range-slider
13
+ ```
14
+
15
+ Import the registration of `<uui-range-slider>` via:
16
+
17
+ ```javascript
18
+ import '@umbraco-ui/uui-range-slider';
19
+ ```
20
+
21
+ When looking to leverage the `UUIRangeSliderElement` base class as a type and/or for extension purposes, do so via:
22
+
23
+ ```javascript
24
+ import { UUIRangeSliderElement } from '@umbraco-ui/uui-range-slider';
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```html
30
+ <uui-range-slider></uui-range-slider>
31
+ ```
@@ -0,0 +1,250 @@
1
+ {
2
+ "version": "experimental",
3
+ "tags": [
4
+ {
5
+ "name": "uui-range-slider",
6
+ "path": "./lib/uui-range-slider.element.ts",
7
+ "attributes": [
8
+ {
9
+ "name": "disabled",
10
+ "description": "Disables the input.",
11
+ "type": "boolean",
12
+ "default": "\"false\""
13
+ },
14
+ {
15
+ "name": "label",
16
+ "description": "Label to be used for aria-label and eventually as visual label. Adds \" low value\" and \" high value\" endings for the two values.",
17
+ "type": "string"
18
+ },
19
+ {
20
+ "name": "step",
21
+ "description": "This reflects the behavior of a native input step attribute.",
22
+ "type": "number",
23
+ "default": "\"1\""
24
+ },
25
+ {
26
+ "name": "hide-step-values",
27
+ "description": "Hides the numbers representing the value of each steps. Dots will still be visible",
28
+ "type": "boolean",
29
+ "default": "\"false\""
30
+ },
31
+ {
32
+ "name": "min",
33
+ "description": "Sets the minimum allowed value.",
34
+ "type": "number",
35
+ "default": "\"0\""
36
+ },
37
+ {
38
+ "name": "max",
39
+ "description": "Sets the maximum allowed value.",
40
+ "type": "number",
41
+ "default": "\"100\""
42
+ },
43
+ {
44
+ "name": "value-low",
45
+ "description": "The lower picked value.",
46
+ "type": "number",
47
+ "default": "\"0\""
48
+ },
49
+ {
50
+ "name": "value-high",
51
+ "description": "The higher picked value.",
52
+ "type": "number",
53
+ "default": "\"100\""
54
+ },
55
+ {
56
+ "name": "min-gap",
57
+ "description": "Minimum value gap between the the two picked values. Cannot be lower than the step value and cannot be higher than the maximum gap",
58
+ "type": "number",
59
+ "default": "\"1\""
60
+ },
61
+ {
62
+ "name": "max-gap",
63
+ "description": "Maximum value gap between the the two picked values. Cannot be lower than the minimum gap.",
64
+ "type": "number",
65
+ "default": "\"undefined\""
66
+ },
67
+ {
68
+ "name": "name",
69
+ "description": "This is a name property of the component.",
70
+ "type": "string",
71
+ "default": "\"''\""
72
+ },
73
+ {
74
+ "name": "value",
75
+ "description": "This is a value property of the uui-range-slider. Split the two values with comma, forexample 10,50 sets the values to 10 and 50.",
76
+ "type": "string",
77
+ "default": "\"0,100\""
78
+ },
79
+ {
80
+ "name": "pristine",
81
+ "description": "Determines wether the form control has been touched or interacted with, this determines wether the validation-status of this form control should be made visible.",
82
+ "type": "boolean",
83
+ "default": "\"false\""
84
+ },
85
+ {
86
+ "name": "required",
87
+ "description": "Apply validation rule for requiring a value of this form control.",
88
+ "type": "boolean",
89
+ "default": "\"false\""
90
+ },
91
+ {
92
+ "name": "required-message",
93
+ "description": "Required validation message.",
94
+ "type": "string",
95
+ "default": "\"This field is required\""
96
+ },
97
+ {
98
+ "name": "error",
99
+ "description": "Apply custom error on this input.",
100
+ "type": "boolean",
101
+ "default": "false"
102
+ },
103
+ {
104
+ "name": "error-message",
105
+ "description": "Custom error message.",
106
+ "type": "string",
107
+ "default": "\"This field is invalid\""
108
+ }
109
+ ],
110
+ "properties": [
111
+ {
112
+ "name": "styles",
113
+ "type": "CSSResult[]",
114
+ "default": "[\"UUIHorizontalPulseKeyframes\",null]"
115
+ },
116
+ {
117
+ "name": "disabled",
118
+ "attribute": "disabled",
119
+ "description": "Disables the input.",
120
+ "type": "boolean",
121
+ "default": "\"false\""
122
+ },
123
+ {
124
+ "name": "label",
125
+ "attribute": "label",
126
+ "description": "Label to be used for aria-label and eventually as visual label. Adds \" low value\" and \" high value\" endings for the two values.",
127
+ "type": "string"
128
+ },
129
+ {
130
+ "name": "step",
131
+ "attribute": "step",
132
+ "description": "This reflects the behavior of a native input step attribute.",
133
+ "type": "number",
134
+ "default": "\"1\""
135
+ },
136
+ {
137
+ "name": "hideStepValues",
138
+ "attribute": "hide-step-values",
139
+ "description": "Hides the numbers representing the value of each steps. Dots will still be visible",
140
+ "type": "boolean",
141
+ "default": "\"false\""
142
+ },
143
+ {
144
+ "name": "min",
145
+ "attribute": "min",
146
+ "description": "Sets the minimum allowed value.",
147
+ "type": "number",
148
+ "default": "\"0\""
149
+ },
150
+ {
151
+ "name": "max",
152
+ "attribute": "max",
153
+ "description": "Sets the maximum allowed value.",
154
+ "type": "number",
155
+ "default": "\"100\""
156
+ },
157
+ {
158
+ "name": "valueLow",
159
+ "attribute": "value-low",
160
+ "description": "The lower picked value.",
161
+ "type": "number",
162
+ "default": "\"0\""
163
+ },
164
+ {
165
+ "name": "valueHigh",
166
+ "attribute": "value-high",
167
+ "description": "The higher picked value.",
168
+ "type": "number",
169
+ "default": "\"100\""
170
+ },
171
+ {
172
+ "name": "minGap",
173
+ "attribute": "min-gap",
174
+ "description": "Minimum value gap between the the two picked values. Cannot be lower than the step value and cannot be higher than the maximum gap",
175
+ "type": "number",
176
+ "default": "\"1\""
177
+ },
178
+ {
179
+ "name": "maxGap",
180
+ "attribute": "max-gap",
181
+ "description": "Maximum value gap between the the two picked values. Cannot be lower than the minimum gap.",
182
+ "type": "number",
183
+ "default": "\"undefined\""
184
+ },
185
+ {
186
+ "name": "formAssociated",
187
+ "description": "This is a static class field indicating that the element is can be used inside a native form and participate in its events.\nIt may require a polyfill, check support here https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals.\nRead more about form controls here https://web.dev/more-capable-form-controls/",
188
+ "type": "boolean",
189
+ "default": "true"
190
+ },
191
+ {
192
+ "name": "name",
193
+ "attribute": "name",
194
+ "description": "This is a name property of the component.",
195
+ "type": "string",
196
+ "default": "\"''\""
197
+ },
198
+ {
199
+ "name": "value",
200
+ "attribute": "value",
201
+ "description": "This is a value property of the uui-range-slider. Split the two values with comma, forexample 10,50 sets the values to 10 and 50.",
202
+ "type": "string",
203
+ "default": "\"0,100\""
204
+ },
205
+ {
206
+ "name": "pristine",
207
+ "attribute": "pristine",
208
+ "description": "Determines wether the form control has been touched or interacted with, this determines wether the validation-status of this form control should be made visible.",
209
+ "type": "boolean",
210
+ "default": "\"false\""
211
+ },
212
+ {
213
+ "name": "required",
214
+ "attribute": "required",
215
+ "description": "Apply validation rule for requiring a value of this form control.",
216
+ "type": "boolean",
217
+ "default": "\"false\""
218
+ },
219
+ {
220
+ "name": "requiredMessage",
221
+ "attribute": "required-message",
222
+ "description": "Required validation message.",
223
+ "type": "string",
224
+ "default": "\"This field is required\""
225
+ },
226
+ {
227
+ "name": "error",
228
+ "attribute": "error",
229
+ "description": "Apply custom error on this input.",
230
+ "type": "boolean",
231
+ "default": "false"
232
+ },
233
+ {
234
+ "name": "errorMessage",
235
+ "attribute": "error-message",
236
+ "description": "Custom error message.",
237
+ "type": "string",
238
+ "default": "\"This field is invalid\""
239
+ },
240
+ {
241
+ "name": "validity",
242
+ "type": "ValidityState"
243
+ },
244
+ {
245
+ "name": "validationMessage"
246
+ }
247
+ ]
248
+ }
249
+ ]
250
+ }
@@ -0,0 +1,6 @@
1
+ import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
2
+ import { UUIRangeSliderElement } from './uui-range-slider.element';
3
+ export declare class UUIRangeSliderEvent extends UUIEvent<{}, UUIRangeSliderElement> {
4
+ static readonly INPUT = "input";
5
+ static readonly CHANGE = "change";
6
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './uui-range-slider.element';
package/lib/index.js ADDED
@@ -0,0 +1,780 @@
1
+ import { UUIHorizontalPulseKeyframes } from '@umbraco-ui/uui-base/lib/animations';
2
+ import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
3
+ import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
4
+ import { LitElement, svg, html, css } from 'lit';
5
+ import { property, state, query } from 'lit/decorators.js';
6
+ import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
7
+
8
+ class UUIRangeSliderEvent extends UUIEvent {
9
+ }
10
+ UUIRangeSliderEvent.INPUT = "input";
11
+ UUIRangeSliderEvent.CHANGE = "change";
12
+
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
+ var __decorateClass = (decorators, target, key, kind) => {
16
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
17
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
18
+ if (decorator = decorators[i])
19
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
20
+ if (kind && result)
21
+ __defProp(target, key, result);
22
+ return result;
23
+ };
24
+ const TRACK_PADDING = 12;
25
+ const STEP_MIN_WIDTH = 24;
26
+ let UUIRangeSliderElement = class extends FormControlMixin(LitElement) {
27
+ constructor() {
28
+ super();
29
+ this.disabled = false;
30
+ this.step = 1;
31
+ this.hideStepValues = false;
32
+ this._min = 0;
33
+ this._max = 100;
34
+ this._valueLow = 0;
35
+ this._valueHigh = 100;
36
+ this._minGap = 1;
37
+ 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
+ };
81
+ 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
+ }
99
+ };
100
+ 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
+ }
117
+ };
118
+ this._onMouseUp = () => {
119
+ this.stopMoving();
120
+ window.removeEventListener("mouseup", this._onMouseUp);
121
+ window.removeEventListener("mousemove", this._onMouseMove);
122
+ };
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));
130
+ };
131
+ this._onInputMouseDown = (e) => {
132
+ e.stopPropagation();
133
+ };
134
+ this.addEventListener("keypress", this._onKeypress);
135
+ this.addEventListener("mousedown", this._onMouseDown);
136
+ this.addEventListener("touchstart", this._onTouchStart);
137
+ this.addValidator(
138
+ "stepMismatch",
139
+ () => `Step property needs to be higher than 0`,
140
+ () => this.step <= 0
141
+ );
142
+ this.addValidator(
143
+ "stepMismatch",
144
+ () => `Maxmimum gap needs to be higher than minimum gap`,
145
+ () => !!this.maxGap && this.maxGap <= this.minGap
146
+ );
147
+ this.addValidator(
148
+ "rangeUnderflow",
149
+ () => `The lower end value (${this.valueLow}) cannot be below the the minimum value setting (${this.min})`,
150
+ () => this.valueLow < this.min
151
+ );
152
+ this.addValidator(
153
+ "rangeOverflow",
154
+ () => `The higher end value (${this.valueHigh}) cannot be above the the maximum value setting (${this.max})`,
155
+ () => this.valueHigh > this.max
156
+ );
157
+ }
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
+ get value() {
189
+ return this._value;
190
+ }
191
+ set value(newVal) {
192
+ if (newVal instanceof String) {
193
+ super.value = newVal;
194
+ const values = newVal.split(",");
195
+ this._valueLow = parseInt(values[0]);
196
+ this._valueHigh = parseInt(values[1]);
197
+ }
198
+ }
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) {
206
+ this._valueLow = this.min;
207
+ super.value = `${newVal},${this.min}`;
208
+ this.requestUpdate("valueLow", old);
209
+ }
210
+ }
211
+ get valueLow() {
212
+ return this._valueLow;
213
+ }
214
+ set valueHigh(newVal) {
215
+ 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;
222
+ super.value = `${this.valueLow},${this.max}`;
223
+ this.requestUpdate("valueHigh", old);
224
+ }
225
+ }
226
+ get valueHigh() {
227
+ return this._valueHigh;
228
+ }
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;
252
+ }
253
+ focus() {
254
+ this._inputLow.focus();
255
+ }
256
+ getFormElement() {
257
+ return this._inputLow;
258
+ }
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);
268
+ }
269
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
270
+ }
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));
282
+ }
283
+ _onChange(e) {
284
+ e.stopPropagation();
285
+ this.pristine = false;
286
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.CHANGE));
287
+ }
288
+ _fillColor() {
289
+ const percentStart = (this.valueLow - this.min) / (this.max - this.min) * 100;
290
+ const percentEnd = (this.valueHigh - this.min) / (this.max - this.min) * 100;
291
+ this._innerColor.style.left = `${percentStart}%`;
292
+ this._innerColor.style.right = `${100 - percentEnd}%`;
293
+ }
294
+ _onKeypress(e) {
295
+ if (e.key == "Enter") {
296
+ this.submit();
297
+ }
298
+ }
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;
305
+ }
306
+ _getHandles(pageX) {
307
+ const mouseXPosition = pageX - this._innerSliderTrack.getBoundingClientRect().left;
308
+ const clickPercent = mouseXPosition / (this._trackWidth - TRACK_PADDING * 2);
309
+ const clickedValue = clickPercent * (this.max - this.min) + this.min;
310
+ 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 };
323
+ }
324
+ return;
325
+ }
326
+ _updateBothValues(mousePosition) {
327
+ const drag = mousePosition - this._handle.startPosition;
328
+ const trackDiff = this.max - this.min;
329
+ const dragPercent = drag / (this._trackWidth + TRACK_PADDING * 2);
330
+ 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;
336
+ this.dispatchEvent(new UUIRangeSliderEvent(UUIRangeSliderEvent.INPUT));
337
+ }
338
+ }
339
+ updated(changedProperties) {
340
+ super.updated(changedProperties);
341
+ this._trackWidth = this._sliderTrack.offsetWidth;
342
+ this._fillColor();
343
+ }
344
+ connectedCallback() {
345
+ super.connectedCallback();
346
+ window.addEventListener("resize", () => {
347
+ this._trackWidth = this._sliderTrack.offsetWidth;
348
+ });
349
+ }
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;
354
+ }
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;
359
+ }
360
+ renderSteps() {
361
+ const stepAmount = (this.max - this.min) / this.step;
362
+ const stepWidth = (this._trackWidth - TRACK_PADDING * 2) / stepAmount;
363
+ const trackValue = this._trackWidth / (this.max - this.min);
364
+ if (stepWidth >= STEP_MIN_WIDTH) {
365
+ let i = 0;
366
+ const steps = [];
367
+ for (i; i <= stepAmount; i++) {
368
+ steps.push(i * stepWidth);
369
+ }
370
+ const colorClass = this.disabled == false ? `filled` : `filled-disabled`;
371
+ return svg`
372
+ ${steps.map((position) => {
373
+ const x = position + TRACK_PADDING;
374
+ if (x / trackValue > this.valueLow - this.min && x / trackValue < this.valueHigh - this.min) {
375
+ return svg`<circle class="track-step ${colorClass}" cx="${x}" cy="50%" r="4.5" />`;
376
+ } else {
377
+ return svg`<circle class="track-step" cx="${x}" cy="50%" r="4.5" />`;
378
+ }
379
+ })}`;
380
+ } else {
381
+ return svg``;
382
+ }
383
+ }
384
+ renderStepValues(hide) {
385
+ const stepAmount = (this.max - this.min) / this.step;
386
+ const stepWidth = (this._trackWidth - TRACK_PADDING * 2) / stepAmount;
387
+ if (stepWidth >= STEP_MIN_WIDTH && stepAmount <= 20 && !hide) {
388
+ let i = 0;
389
+ const stepValues = [];
390
+ for (i; i <= stepAmount; i++) {
391
+ stepValues.push(i * this.step + this.min);
392
+ }
393
+ return html` ${stepValues.map((stepValue) => {
394
+ return html`<span><span>${stepValue}</span></span>`;
395
+ })}`;
396
+ } else {
397
+ return html``;
398
+ }
399
+ }
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
+ }
454
+ };
455
+ UUIRangeSliderElement.styles = [
456
+ UUIHorizontalPulseKeyframes,
457
+ css`
458
+ :host {
459
+ position: relative;
460
+ width: 100%;
461
+ min-height: 30px;
462
+ padding: 0;
463
+ margin: 0;
464
+ place-items: center;
465
+ -webkit-user-select: none; /* Safari */
466
+ -moz-user-select: none; /* Firefox */
467
+ -ms-user-select: none; /* IE10+/Edge */
468
+ user-select: none;
469
+ cursor: pointer;
470
+ }
471
+
472
+ :host([disabled]) {
473
+ cursor: default;
474
+ }
475
+
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
+ }
490
+
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 {
501
+ position: absolute;
502
+ border-radius: 10px;
503
+ top: 50%;
504
+ transform: translateY(-50%);
505
+ left: 0;
506
+ right: 0;
507
+ height: 3px;
508
+ margin: -23px 0;
509
+ z-index: -1;
510
+ background-color: #a1a1a1;
511
+ }
512
+
513
+ .inner-track:focus {
514
+ background-color: var(--uui-color-border-standalone,#c2c2c2);
515
+ }
516
+
517
+ .inner-track .color {
518
+ height: 3px;
519
+ position: absolute;
520
+ transition: left 120ms ease, right 120ms ease;
521
+ }
522
+
523
+ input[type='range']:not([disabled]) ~ .inner-track .color {
524
+ background-color: var(--uui-color-selected,#3544b1);
525
+ }
526
+
527
+ input[type='range']:disabled ~ .inner-track .color {
528
+ background-color: #555;
529
+ }
530
+
531
+ #thumb-wrapper {
532
+ position: relative;
533
+ margin: 0 ${TRACK_PADDING}px;
534
+ }
535
+
536
+ .thumb {
537
+ width: 17px;
538
+ height: 17px;
539
+ position: absolute;
540
+ top: -31px;
541
+ bottom: 0;
542
+ 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;
551
+ }
552
+
553
+ .thumb:after {
554
+ content: '';
555
+ 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);
562
+ }
563
+
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);
570
+ }
571
+
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);
589
+ }
590
+
591
+ #wrapper:active .thumb .value,
592
+ #wrapper:focus .thumb .value,
593
+ #wrapper:hover .thumb .value {
594
+ visibility: visible;
595
+ opacity: 1;
596
+ }
597
+
598
+ .svg-wrapper svg {
599
+ margin-top: -6px;
600
+ height: 30px;
601
+ width: 100%;
602
+ }
603
+
604
+ #wrapper:hover .track-step,
605
+ #wrapper:active .track-step {
606
+ fill: #a1a1a1;
607
+ }
608
+
609
+ .track-step {
610
+ fill: var(--uui-color-border,#d8d7d9);
611
+ }
612
+
613
+ #wrapper .track-step.filled {
614
+ fill: var(--uui-color-selected,#3544b1) !important;
615
+ }
616
+
617
+ #wrapper .track-step.filled-disabled {
618
+ fill: var(--uui-palette-mine-grey,#3e3e3e) !important;
619
+ }
620
+
621
+ #step-values {
622
+ margin: 0 ${TRACK_PADDING}px; /* Match TRACK_MARGIN */
623
+ padding-top: 24px;
624
+ display: flex;
625
+ align-items: flex-end;
626
+ box-sizing: border-box;
627
+ }
628
+
629
+ #step-values > span {
630
+ flex-basis: 0;
631
+ flex-grow: 1;
632
+ color: var(--uui-color-disabled-contrast,#c4c4c4);
633
+ }
634
+
635
+ #step-values > span > span {
636
+ transform: translateX(-50%);
637
+ display: inline-block;
638
+ text-align: center;
639
+ font-size: var(--uui-type-small-size,12px);
640
+ }
641
+
642
+ #step-values > span:last-child {
643
+ width: 0;
644
+ flex-grow: 0;
645
+ }
646
+
647
+ #input-wrapper {
648
+ position: relative;
649
+ margin: -45px ${TRACK_PADDING / 2}px 45px;
650
+ }
651
+
652
+ input {
653
+ -webkit-appearance: none;
654
+ -moz-appearance: none;
655
+ appearance: none;
656
+ position: absolute;
657
+ top: 0;
658
+ background-color: transparent;
659
+ pointer-events: none;
660
+ left: 0;
661
+ right: 0;
662
+ margin: -30px -8px;
663
+ z-index: 12;
664
+ border-radius: 20px;
665
+ }
666
+
667
+ input[type='range']:focus-visible {
668
+ outline: none;
669
+ }
670
+
671
+ input[type='range']:focus + .thumb {
672
+ outline: calc(2px * var(--uui-show-focus-outline, 1)) solid
673
+ var(--uui-color-focus,#3879ff);
674
+ }
675
+
676
+ input[type='range']::-webkit-slider-thumb {
677
+ -webkit-appearance: none;
678
+ appearance: none;
679
+ width: 17px;
680
+ height: 17px;
681
+ background-color: transparent;
682
+ display: block;
683
+ border-radius: 100%;
684
+ pointer-events: auto;
685
+ cursor: pointer;
686
+ }
687
+ input[type='range']:disabled::-webkit-slider-thumb {
688
+ cursor: default;
689
+ }
690
+
691
+ input[type='range']::-moz-range-thumb {
692
+ -moz-appearance: none;
693
+ appearance: none;
694
+ width: 17px;
695
+ height: 17px;
696
+ background-color: transparent;
697
+ display: block;
698
+ border-radius: 100%;
699
+ pointer-events: auto;
700
+ cursor: pointer;
701
+ }
702
+ input[type='range']:disabled::-moz-range-thumb {
703
+ cursor: default;
704
+ }
705
+
706
+ input[type='range']::-ms-thumb {
707
+ appearance: none;
708
+ width: 17px;
709
+ height: 17px;
710
+ background-color: transparent;
711
+ display: block;
712
+ border-radius: 100%;
713
+ pointer-events: auto;
714
+ cursor: pointer;
715
+ }
716
+ input[type='range']:disabled::-ms-thumb {
717
+ cursor: default;
718
+ }
719
+ `
720
+ ];
721
+ UUIRangeSliderElement.formAssociated = true;
722
+ __decorateClass([
723
+ property({ type: Boolean, reflect: true })
724
+ ], UUIRangeSliderElement.prototype, "disabled", 2);
725
+ __decorateClass([
726
+ property({ type: String })
727
+ ], UUIRangeSliderElement.prototype, "label", 2);
728
+ __decorateClass([
729
+ property({ type: Number })
730
+ ], UUIRangeSliderElement.prototype, "step", 2);
731
+ __decorateClass([
732
+ property({ type: Boolean, attribute: "hide-step-values" })
733
+ ], UUIRangeSliderElement.prototype, "hideStepValues", 2);
734
+ __decorateClass([
735
+ property({ type: Number })
736
+ ], UUIRangeSliderElement.prototype, "min", 1);
737
+ __decorateClass([
738
+ property({ type: Number })
739
+ ], UUIRangeSliderElement.prototype, "max", 1);
740
+ __decorateClass([
741
+ property({ type: String })
742
+ ], UUIRangeSliderElement.prototype, "value", 1);
743
+ __decorateClass([
744
+ property({ type: Number, attribute: "value-low" })
745
+ ], UUIRangeSliderElement.prototype, "valueLow", 1);
746
+ __decorateClass([
747
+ property({ type: Number, attribute: "value-high" })
748
+ ], UUIRangeSliderElement.prototype, "valueHigh", 1);
749
+ __decorateClass([
750
+ property({ type: Number, attribute: "min-gap" })
751
+ ], UUIRangeSliderElement.prototype, "minGap", 1);
752
+ __decorateClass([
753
+ property({ type: Number, attribute: "max-gap" })
754
+ ], UUIRangeSliderElement.prototype, "maxGap", 1);
755
+ __decorateClass([
756
+ property({ type: Number })
757
+ ], UUIRangeSliderElement.prototype, "_trackWidth", 2);
758
+ __decorateClass([
759
+ state()
760
+ ], UUIRangeSliderElement.prototype, "_handle", 2);
761
+ __decorateClass([
762
+ query("#min-slider")
763
+ ], UUIRangeSliderElement.prototype, "_inputLow", 2);
764
+ __decorateClass([
765
+ query("#max-slider")
766
+ ], UUIRangeSliderElement.prototype, "_inputHigh", 2);
767
+ __decorateClass([
768
+ query(".slider-track")
769
+ ], UUIRangeSliderElement.prototype, "_sliderTrack", 2);
770
+ __decorateClass([
771
+ query(".inner-track")
772
+ ], UUIRangeSliderElement.prototype, "_innerSliderTrack", 2);
773
+ __decorateClass([
774
+ query(".color")
775
+ ], UUIRangeSliderElement.prototype, "_innerColor", 2);
776
+ UUIRangeSliderElement = __decorateClass([
777
+ defineElement("uui-range-slider")
778
+ ], UUIRangeSliderElement);
779
+
780
+ export { UUIRangeSliderElement };
@@ -0,0 +1,138 @@
1
+ import { LitElement } from 'lit';
2
+ declare const UUIRangeSliderElement_base: (new (...args: any[]) => import("@umbraco-ui/uui-base/lib/mixins").FormControlMixinInterface) & typeof LitElement;
3
+ /**
4
+ * @element uui-range-slider
5
+ * @description - Range slider with two handles. Use uui-slider for a single handle.
6
+ */
7
+ export declare class UUIRangeSliderElement extends UUIRangeSliderElement_base {
8
+ static styles: import("lit").CSSResult[];
9
+ static readonly formAssociated = true;
10
+ /**
11
+ * Disables the input.
12
+ * @type {boolean}
13
+ * @attr
14
+ * @default false
15
+ */
16
+ disabled: boolean;
17
+ /**
18
+ * Label to be used for aria-label and eventually as visual label. Adds " low value" and " high value" endings for the two values.
19
+ * @type {string}
20
+ * @attr
21
+ */
22
+ label: String;
23
+ /**
24
+ * This reflects the behavior of a native input step attribute.
25
+ * @type {number}
26
+ * @attr
27
+ * @default 1
28
+ */
29
+ step: number;
30
+ /**
31
+ * Hides the numbers representing the value of each steps. Dots will still be visible
32
+ * @type {boolean}
33
+ * @attr 'hide-step-values'
34
+ * @default false
35
+ */
36
+ hideStepValues: boolean;
37
+ private _min;
38
+ private _max;
39
+ private _valueLow;
40
+ private _valueHigh;
41
+ private _minGap;
42
+ private _maxGap?;
43
+ /**
44
+ * Sets the minimum allowed value.
45
+ * @type {number}
46
+ * @attr min
47
+ * @default 0
48
+ */
49
+ set min(newVal: number);
50
+ get min(): number;
51
+ /**
52
+ * Sets the maximum allowed value.
53
+ * @type {number}
54
+ * @attr max
55
+ * @default 100
56
+ */
57
+ set max(newVal: number);
58
+ get max(): number;
59
+ /**
60
+ * This is a value property of the uui-range-slider. Split the two values with comma, forexample 10,50 sets the values to 10 and 50.
61
+ * @type {string}
62
+ * @attr
63
+ * @default 0,100
64
+ */
65
+ get value(): FormDataEntryValue | FormData;
66
+ set value(newVal: FormDataEntryValue | FormData);
67
+ /**
68
+ * The lower picked value.
69
+ * @type {number}
70
+ * @attr value-low
71
+ * @default 0
72
+ */
73
+ set valueLow(newVal: number);
74
+ get valueLow(): number;
75
+ /**
76
+ * The higher picked value.
77
+ * @type {number}
78
+ * @attr value-high
79
+ * @default 100
80
+ */
81
+ set valueHigh(newVal: number);
82
+ get valueHigh(): number;
83
+ /**
84
+ * Minimum value gap between the the two picked values. Cannot be lower than the step value and cannot be higher than the maximum gap
85
+ * @type {number}
86
+ * @attr min-gap
87
+ * @default 1
88
+ */
89
+ set minGap(newVal: number);
90
+ get minGap(): number;
91
+ /**
92
+ * Maximum value gap between the the two picked values. Cannot be lower than the minimum gap.
93
+ * @type {number}
94
+ * @attr max-gap
95
+ * @default undefined
96
+ */
97
+ set maxGap(newVal: number | undefined);
98
+ get maxGap(): number | undefined;
99
+ private _trackWidth;
100
+ private _handle;
101
+ private _inputLow;
102
+ private _inputHigh;
103
+ private _sliderTrack;
104
+ private _innerSliderTrack;
105
+ private _innerColor;
106
+ focus(): void;
107
+ protected getFormElement(): HTMLElement;
108
+ private _onMinInput;
109
+ private _onMaxInput;
110
+ private _onChange;
111
+ private _fillColor;
112
+ private _onKeypress;
113
+ private _onTouchStart;
114
+ private _onTouchMove;
115
+ private _onTouchEnd;
116
+ private _onMouseDown;
117
+ private _onMouseMove;
118
+ private _onMouseUp;
119
+ private stopMoving;
120
+ private _getValue;
121
+ private _getHandles;
122
+ private _updateBothValues;
123
+ constructor();
124
+ updated(changedProperties: Map<string | number | symbol, unknown>): void;
125
+ connectedCallback(): void;
126
+ private _sliderLowThumbPosition;
127
+ private _sliderHighThumbPosition;
128
+ renderSteps(): import("lit-html").TemplateResult<2>;
129
+ renderStepValues(hide: boolean): import("lit-html").TemplateResult<1>;
130
+ private _onInputMouseDown;
131
+ render(): import("lit-html").TemplateResult<1>;
132
+ }
133
+ declare global {
134
+ interface HTMLElementTagNameMap {
135
+ 'uui-range-slider': UUIRangeSliderElement;
136
+ }
137
+ }
138
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@umbraco-ui/uui-range-slider",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "keywords": [
6
+ "Umbraco",
7
+ "Custom elements",
8
+ "Web components",
9
+ "UI",
10
+ "Lit",
11
+ "Range Slider"
12
+ ],
13
+ "description": "Umbraco UI range-slider component",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/umbraco/Umbraco.UI.git",
17
+ "directory": "packages/uui-range-slider"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/umbraco/Umbraco.UI/issues"
21
+ },
22
+ "main": "./lib/index.js",
23
+ "module": "./lib/index.js",
24
+ "types": "./lib/index.d.ts",
25
+ "type": "module",
26
+ "customElements": "custom-elements.json",
27
+ "files": [
28
+ "lib/**/*.d.ts",
29
+ "lib/**/*.js",
30
+ "custom-elements.json"
31
+ ],
32
+ "dependencies": {
33
+ "@umbraco-ui/uui-base": "1.1.0"
34
+ },
35
+ "scripts": {
36
+ "build": "npm run analyze && tsc --build --force && rollup -c rollup.config.js",
37
+ "clean": "tsc --build --clean && rimraf dist lib/*.js lib/**/*.js custom-elements.json",
38
+ "analyze": "web-component-analyzer **/*.element.ts --outFile custom-elements.json"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "homepage": "https://uui.umbraco.com/?path=/story/uui-range-slider",
44
+ "gitHead": "5159a81e353c893c30073cdbf2fdd94306477d6e"
45
+ }