@nethserver/ns8-ui-lib 0.1.6 → 0.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nethserver/ns8-ui-lib",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Vue.js library for NethServer 8 UI",
5
5
  "keywords": [
6
6
  "nethserver",
@@ -0,0 +1,400 @@
1
+ <template>
2
+ <div :class="`ns-slider cv-slider ${carbonPrefix}--form-item`">
3
+ <div class="wrapper" :data-invalid="isInvalid">
4
+ <label
5
+ v-show="label"
6
+ :for="uid"
7
+ :class="[
8
+ `${carbonPrefix}--label`,
9
+ { [`${carbonPrefix}--label--disabled`]: disabled },
10
+ ]"
11
+ :id="labelId"
12
+ >
13
+ {{ label }}
14
+ </label>
15
+ <!-- unlimited checkbox -->
16
+ <NsCheckbox
17
+ v-if="showUnlimited"
18
+ :label="unlimitedLabel"
19
+ v-model="internalUnlimited"
20
+ :disabled="disabled"
21
+ value="checkUnlimited"
22
+ class="is-unlimited"
23
+ />
24
+ <div :class="`${carbonPrefix}--slider-container`">
25
+ <span
26
+ v-if="internalMinLabel"
27
+ :class="`${carbonPrefix}--slider__range-label`"
28
+ >{{ internalMinLabel }}</span
29
+ >
30
+ <div
31
+ :class="[
32
+ `${carbonPrefix}--slider`,
33
+ {
34
+ [`${carbonPrefix}--slider--disabled`]:
35
+ disabled || internalUnlimited,
36
+ },
37
+ ]"
38
+ data-slider
39
+ data-slider-input-box="#slider-input-box"
40
+ >
41
+ <div
42
+ :class="`${carbonPrefix}--slider__track`"
43
+ @click="onTrackClick"
44
+ ref="track"
45
+ ></div>
46
+ <div
47
+ :class="`${carbonPrefix}--slider__filled-track`"
48
+ :style="`width: ${percentage};`"
49
+ ></div>
50
+ <div
51
+ :class="[
52
+ `${carbonPrefix}--slider__thumb`,
53
+ {
54
+ [`${carbonPrefix}--slider__thumb--clicked`]: animateClick,
55
+ },
56
+ ]"
57
+ tabindex="0"
58
+ :aria-labelledby="labelId"
59
+ :style="`left: ${percentage};`"
60
+ ref="thumb"
61
+ @keydown.up.right.prevent="onUp"
62
+ @keydown.down.left.prevent="onDown"
63
+ @mousedown="onStartDrag"
64
+ ></div>
65
+ <input
66
+ :id="uid"
67
+ :class="`${carbonPrefix}--slider__input`"
68
+ type="range"
69
+ :step="step"
70
+ :min="min"
71
+ :max="max"
72
+ ref="range"
73
+ />
74
+ </div>
75
+ <span
76
+ v-if="internalMaxLabel"
77
+ :class="`${carbonPrefix}--slider__range-label`"
78
+ >{{ internalMaxLabel }}</span
79
+ >
80
+ <input
81
+ type="number"
82
+ :class="[
83
+ `${carbonPrefix}--text-input ${carbonPrefix}--slider-text-input`,
84
+ {
85
+ [`${carbonPrefix}--text-input--light`]: isLight,
86
+ [`${carbonPrefix}--text-input--invalid`]: isInvalid,
87
+ [`${carbonPrefix}--text-input--warning`]: isWarn,
88
+ },
89
+ ,
90
+ 'range-input',
91
+ ]"
92
+ :placeholder="min"
93
+ v-model="internalValue"
94
+ @change="onChange"
95
+ ref="inputBox"
96
+ @keydown.up.prevent="onUp"
97
+ @keydown.down.prevent="onDown"
98
+ :disabled="disabled || internalUnlimited"
99
+ />
100
+ <span
101
+ :class="[
102
+ `unit-label`,
103
+ {
104
+ [`${carbonPrefix}--label--disabled`]:
105
+ disabled || internalUnlimited,
106
+ },
107
+ ]"
108
+ >{{ unitLabel }}</span
109
+ >
110
+ </div>
111
+ </div>
112
+ <div :class="`${carbonPrefix}--form-requirement`" v-if="isInvalid">
113
+ <slot name="invalid-message">{{ invalidMessage }}</slot>
114
+ </div>
115
+ </div>
116
+ </template>
117
+
118
+ <script>
119
+ import {
120
+ uidMixin,
121
+ themeMixin,
122
+ carbonPrefixMixin,
123
+ methodsMixin,
124
+ } from "@carbon/vue/src/mixins";
125
+
126
+ export default {
127
+ name: "NsSlider",
128
+ mixins: [
129
+ uidMixin,
130
+ themeMixin,
131
+ carbonPrefixMixin,
132
+ methodsMixin({ thumb: ["blur", "focus"] }),
133
+ ],
134
+ props: {
135
+ disabled: Boolean,
136
+ label: String,
137
+ // NOTE: It is not safe to rely on Numbers for non-integer steps
138
+ min: { type: String, default: "0" },
139
+ max: { type: String, default: "100" },
140
+ step: { type: String, default: "1" },
141
+ stepMultiplier: {
142
+ type: String,
143
+ default: "4",
144
+ validator(val) {
145
+ if (val.length) {
146
+ let intMultiplier = parseInt(val);
147
+ if (isNaN(intMultiplier) || intMultiplier < 1) {
148
+ console.warn("ns-slider: multiplier must be >= 1");
149
+ return false;
150
+ }
151
+ }
152
+ return true;
153
+ },
154
+ },
155
+ value: String,
156
+ minLabel: String,
157
+ maxLabel: String,
158
+ showUnlimited: { type: Boolean, default: false },
159
+ isUnlimited: { type: Boolean, default: false },
160
+ unlimitedLabel: { type: String, default: "Unlimited" },
161
+ invalidMessage: { type: String, default: "" },
162
+ unitLabel: { type: String, default: "" },
163
+ },
164
+ model: {
165
+ prop: "value",
166
+ event: "modelEvent",
167
+ },
168
+ data() {
169
+ return {
170
+ internalValue: "",
171
+ animateClick: false,
172
+ isDragging: false,
173
+ dragStartX: 0,
174
+ dragStartValue: 0,
175
+ percentage: "0%",
176
+ internalUnlimited: false,
177
+ isInvalid: false,
178
+ };
179
+ },
180
+ computed: {
181
+ labelId() {
182
+ return `${this.uid}-label`;
183
+ },
184
+ internalMinLabel() {
185
+ return this.minLabel !== undefined ? this.minLabel : this.getMin();
186
+ },
187
+ internalMaxLabel() {
188
+ return this.maxLabel !== undefined ? this.maxLabel : this.getMax();
189
+ },
190
+ internalMultiplier() {
191
+ let intMultiplier = parseInt(this.stepMultiplier);
192
+ // default to 4 fro multiplier
193
+ return isNaN(intMultiplier) ? 4 : Math.max(intMultiplier, 1);
194
+ },
195
+ },
196
+ created() {
197
+ this.internalUnlimited = this.isUnlimited;
198
+ },
199
+ mounted() {
200
+ this.$refs.range.value = this.value;
201
+ this.internalValue = this.$refs.range.value;
202
+ this.percentage = `${
203
+ ((this.internalValue - this.getMin()) * 100) /
204
+ (this.getMax() - this.getMin())
205
+ }%`;
206
+ this.checkSlots();
207
+ },
208
+ updated() {
209
+ this.checkSlots();
210
+ },
211
+ watch: {
212
+ value(val) {
213
+ this.setValue(val);
214
+ },
215
+ min() {
216
+ this.$nextTick(() => {
217
+ this.setValue(this.internalValue);
218
+ });
219
+ },
220
+ max() {
221
+ this.$nextTick(() => {
222
+ this.setValue(this.internalValue);
223
+ });
224
+ },
225
+ step() {
226
+ this.$nextTick(() => {
227
+ this.setValue(this.internalValue);
228
+ });
229
+ },
230
+ isUnlimited() {
231
+ this.internalUnlimited = this.isUnlimited;
232
+ },
233
+ internalUnlimited() {
234
+ if (this.internalUnlimited) {
235
+ this.$emit("unlimited", true);
236
+ } else {
237
+ this.$emit("unlimited", false);
238
+ }
239
+ },
240
+ },
241
+ methods: {
242
+ // NOTE: It is not safe to rely on Numbers for non-integer steps
243
+ getMin() {
244
+ if (this.$refs.range) {
245
+ const val = parseFloat(this.$refs.range.min);
246
+ return isNaN(val) ? 0 : val;
247
+ }
248
+ return 0;
249
+ },
250
+ getMax() {
251
+ if (this.$refs.range) {
252
+ const val = parseFloat(this.$refs.range.max);
253
+ return isNaN(val) ? 100 : val;
254
+ }
255
+ return 100;
256
+ },
257
+ getValue() {
258
+ if (this.$refs.range) {
259
+ const val = parseFloat(this.$refs.range.value);
260
+ return isNaN(val) ? 0 : val;
261
+ }
262
+ return (this.getMax() + this.getMin()) / 2;
263
+ },
264
+ getStep() {
265
+ if (this.$refs.range) {
266
+ const val = parseFloat(this.$refs.range.step);
267
+ return isNaN(val) ? 0 : val;
268
+ }
269
+ return 1;
270
+ },
271
+ setValue(newValue) {
272
+ if (this.disabled) return;
273
+
274
+ this.$refs.range.value = newValue;
275
+ this.internalValue = this.$refs.range.value;
276
+
277
+ this.percentage = `${
278
+ ((this.internalValue - this.getMin()) * 100) /
279
+ (this.getMax() - this.getMin())
280
+ }%`;
281
+
282
+ this.$emit("modelEvent", this.$refs.range.value);
283
+ this.$emit("change", this.$refs.range.value);
284
+ },
285
+ onChange() {
286
+ let newValue = this.internalValue.length
287
+ ? parseFloat(this.internalValue)
288
+ : this.getMin();
289
+ this.setValue(newValue);
290
+ },
291
+ onStartDrag(ev) {
292
+ document.body.addEventListener("mousemove", this.onDrag);
293
+ document.body.addEventListener("mouseup", this.onStopDrag);
294
+
295
+ this.dragStartX = ev.clientX;
296
+ this.dragStartValue = this.getValue();
297
+ this.isDragging = true;
298
+ },
299
+ onDrag(ev) {
300
+ if (this.isDragging) {
301
+ // percentage change
302
+ let newValue =
303
+ (ev.clientX - this.dragStartX) / this.$refs.track.offsetWidth;
304
+ // uncapped new value
305
+ newValue =
306
+ this.dragStartValue + (this.getMax() - this.getMin()) * newValue;
307
+
308
+ this.setValue(newValue, ev);
309
+ }
310
+ },
311
+ onStopDrag() {
312
+ this.isDragging = false;
313
+ document.body.removeEventListener("mousemove", this.onDrag);
314
+ document.body.removeEventListener("mouseup", this.onStopDrag);
315
+ },
316
+ onTrackClick(ev) {
317
+ const afterAnimate = (ev) => {
318
+ if (ev.propertyName === "left") {
319
+ this.animateClick = false;
320
+ this.$refs.thumb.removeEventListener("transitionend", afterAnimate);
321
+ }
322
+ };
323
+
324
+ let newValue = ev.offsetX / this.$refs.track.offsetWidth;
325
+ newValue = (this.getMax() - this.getMin()) * newValue + this.getMin();
326
+ this.$refs.thumb.addEventListener("transitionend", afterAnimate);
327
+ this.animateClick = true;
328
+
329
+ this.setValue(newValue, ev);
330
+ },
331
+ onUp(ev) {
332
+ let curValue =
333
+ ev.target.type === "number"
334
+ ? parseFloat(ev.target.value)
335
+ : this.getValue();
336
+ let newValue =
337
+ curValue +
338
+ (ev.shiftKey
339
+ ? this.internalMultiplier * this.getStep()
340
+ : this.getStep());
341
+ this.setValue(newValue, ev);
342
+ },
343
+ onDown(ev) {
344
+ let curValue =
345
+ ev.target.type === "number"
346
+ ? parseFloat(ev.target.value)
347
+ : this.getValue();
348
+ let newValue =
349
+ curValue -
350
+ (ev.shiftKey
351
+ ? this.internalMultiplier * this.getStep()
352
+ : this.getStep());
353
+ this.setValue(newValue, ev);
354
+ },
355
+ checkSlots() {
356
+ // NOTE: this.$slots is not reactive so needs to be managed on updated
357
+ this.isInvalid = !!(
358
+ this.$slots["invalid-message"] ||
359
+ (this.invalidMessage && this.invalidMessage.length)
360
+ );
361
+ },
362
+ },
363
+ };
364
+ </script>
365
+
366
+ <style scoped lang="scss">
367
+ .is-unlimited {
368
+ margin-top: 0.5rem !important;
369
+ margin-bottom: 0.5rem !important;
370
+ }
371
+
372
+ .range-input {
373
+ margin-top: 0.5rem !important;
374
+ }
375
+
376
+ .unit-label {
377
+ margin-top: 0.5rem !important;
378
+ margin-left: 1rem;
379
+ }
380
+ </style>
381
+
382
+ <style lang="scss">
383
+ // global styles
384
+
385
+ .ns-slider .bx--slider-container {
386
+ flex-wrap: wrap;
387
+ }
388
+
389
+ .ns-slider .bx--slider-text-input {
390
+ width: 5rem;
391
+ }
392
+
393
+ .ns-slider .wrapper[data-invalid] ~ .bx--form-requirement {
394
+ display: block;
395
+ overflow: visible;
396
+ max-height: 12.5rem;
397
+ font-weight: 400;
398
+ color: #da1e28;
399
+ }
400
+ </style>