UNPKG

11.5 kBPlain TextView Raw
1<template>
2 <div
3 class="el-slider"
4 :class="{ 'is-vertical': vertical, 'el-slider--with-input': showInput }"
5 role="slider"
6 :aria-valuemin="min"
7 :aria-valuemax="max"
8 :aria-orientation="vertical ? 'vertical': 'horizontal'"
9 :aria-disabled="sliderDisabled"
10 >
11 <el-input-number
12 v-model="firstValue"
13 v-if="showInput && !range"
14 class="el-slider__input"
15 ref="input"
16 @change="emitChange"
17 :step="step"
18 :disabled="sliderDisabled"
19 :controls="showInputControls"
20 :min="min"
21 :max="max"
22 :debounce="debounce"
23 :size="inputSize">
24 </el-input-number>
25 <div
26 class="el-slider__runway"
27 :class="{ 'show-input': showInput, 'disabled': sliderDisabled }"
28 :style="runwayStyle"
29 @click="onSliderClick"
30 ref="slider">
31 <div
32 class="el-slider__bar"
33 :style="barStyle">
34 </div>
35 <slider-button
36 :vertical="vertical"
37 v-model="firstValue"
38 :tooltip-class="tooltipClass"
39 ref="button1">
40 </slider-button>
41 <slider-button
42 :vertical="vertical"
43 v-model="secondValue"
44 :tooltip-class="tooltipClass"
45 ref="button2"
46 v-if="range">
47 </slider-button>
48 <div
49 class="el-slider__stop"
50 v-for="(item, key) in stops"
51 :key="key"
52 :style="getStopStyle(item)"
53 v-if="showStops">
54 </div>
55 <template v-if="markList.length > 0">
56 <div>
57 <div
58 v-for="(item, key) in markList"
59 :style="getStopStyle(item.position)"
60 class="el-slider__stop el-slider__marks-stop"
61 :key="key">
62 </div>
63 </div>
64 <div class="el-slider__marks">
65 <slider-marker
66 :mark="item.mark" v-for="(item, key) in markList"
67 :key="key"
68 :style="getStopStyle(item.position)">
69 </slider-marker>
70 </div>
71 </template>
72 </div>
73 </div>
74</template>
75
76<script type="text/babel">
77 import ElInputNumber from 'element-ui/packages/input-number';
78 import SliderButton from './button.vue';
79 import SliderMarker from './marker';
80 import Emitter from 'element-ui/src/mixins/emitter';
81
82 export default {
83 name: 'ElSlider',
84
85 mixins: [Emitter],
86
87 inject: {
88 elForm: {
89 default: ''
90 }
91 },
92
93 props: {
94 min: {
95 type: Number,
96 default: 0
97 },
98 max: {
99 type: Number,
100 default: 100
101 },
102 step: {
103 type: Number,
104 default: 1
105 },
106 value: {
107 type: [Number, Array],
108 default: 0
109 },
110 showInput: {
111 type: Boolean,
112 default: false
113 },
114 showInputControls: {
115 type: Boolean,
116 default: true
117 },
118 inputSize: {
119 type: String,
120 default: 'small'
121 },
122 showStops: {
123 type: Boolean,
124 default: false
125 },
126 showTooltip: {
127 type: Boolean,
128 default: true
129 },
130 formatTooltip: Function,
131 disabled: {
132 type: Boolean,
133 default: false
134 },
135 range: {
136 type: Boolean,
137 default: false
138 },
139 vertical: {
140 type: Boolean,
141 default: false
142 },
143 height: {
144 type: String
145 },
146 debounce: {
147 type: Number,
148 default: 300
149 },
150 label: {
151 type: String
152 },
153 tooltipClass: String,
154 marks: Object
155 },
156
157 components: {
158 ElInputNumber,
159 SliderButton,
160 SliderMarker
161 },
162
163 data() {
164 return {
165 firstValue: null,
166 secondValue: null,
167 oldValue: null,
168 dragging: false,
169 sliderSize: 1
170 };
171 },
172
173 watch: {
174 value(val, oldVal) {
175 if (this.dragging ||
176 Array.isArray(val) &&
177 Array.isArray(oldVal) &&
178 val.every((item, index) => item === oldVal[index])) {
179 return;
180 }
181 this.setValues();
182 },
183
184 dragging(val) {
185 if (!val) {
186 this.setValues();
187 }
188 },
189
190 firstValue(val) {
191 if (this.range) {
192 this.$emit('input', [this.minValue, this.maxValue]);
193 } else {
194 this.$emit('input', val);
195 }
196 },
197
198 secondValue() {
199 if (this.range) {
200 this.$emit('input', [this.minValue, this.maxValue]);
201 }
202 },
203
204 min() {
205 this.setValues();
206 },
207
208 max() {
209 this.setValues();
210 }
211 },
212
213 methods: {
214 valueChanged() {
215 if (this.range) {
216 return ![this.minValue, this.maxValue]
217 .every((item, index) => item === this.oldValue[index]);
218 } else {
219 return this.value !== this.oldValue;
220 }
221 },
222 setValues() {
223 if (this.min > this.max) {
224 console.error('[Element Error][Slider]min should not be greater than max.');
225 return;
226 }
227 const val = this.value;
228 if (this.range && Array.isArray(val)) {
229 if (val[1] < this.min) {
230 this.$emit('input', [this.min, this.min]);
231 } else if (val[0] > this.max) {
232 this.$emit('input', [this.max, this.max]);
233 } else if (val[0] < this.min) {
234 this.$emit('input', [this.min, val[1]]);
235 } else if (val[1] > this.max) {
236 this.$emit('input', [val[0], this.max]);
237 } else {
238 this.firstValue = val[0];
239 this.secondValue = val[1];
240 if (this.valueChanged()) {
241 this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue]);
242 this.oldValue = val.slice();
243 }
244 }
245 } else if (!this.range && typeof val === 'number' && !isNaN(val)) {
246 if (val < this.min) {
247 this.$emit('input', this.min);
248 } else if (val > this.max) {
249 this.$emit('input', this.max);
250 } else {
251 this.firstValue = val;
252 if (this.valueChanged()) {
253 this.dispatch('ElFormItem', 'el.form.change', val);
254 this.oldValue = val;
255 }
256 }
257 }
258 },
259
260 setPosition(percent) {
261 const targetValue = this.min + percent * (this.max - this.min) / 100;
262 if (!this.range) {
263 this.$refs.button1.setPosition(percent);
264 return;
265 }
266 let button;
267 if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
268 button = this.firstValue < this.secondValue ? 'button1' : 'button2';
269 } else {
270 button = this.firstValue > this.secondValue ? 'button1' : 'button2';
271 }
272 this.$refs[button].setPosition(percent);
273 },
274
275 onSliderClick(event) {
276 if (this.sliderDisabled || this.dragging) return;
277 this.resetSize();
278 if (this.vertical) {
279 const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom;
280 this.setPosition((sliderOffsetBottom - event.clientY) / this.sliderSize * 100);
281 } else {
282 const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
283 this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize * 100);
284 }
285 this.emitChange();
286 },
287
288 resetSize() {
289 if (this.$refs.slider) {
290 this.sliderSize = this.$refs.slider[`client${ this.vertical ? 'Height' : 'Width' }`];
291 }
292 },
293
294 emitChange() {
295 this.$nextTick(() => {
296 this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value);
297 });
298 },
299
300 getStopStyle(position) {
301 return this.vertical ? { 'bottom': position + '%' } : { 'left': position + '%' };
302 }
303 },
304
305 computed: {
306 stops() {
307 if (!this.showStops || this.min > this.max) return [];
308 if (this.step === 0) {
309 process.env.NODE_ENV !== 'production' &&
310 console.warn('[Element Warn][Slider]step should not be 0.');
311 return [];
312 }
313 const stopCount = (this.max - this.min) / this.step;
314 const stepWidth = 100 * this.step / (this.max - this.min);
315 const result = [];
316 for (let i = 1; i < stopCount; i++) {
317 result.push(i * stepWidth);
318 }
319 if (this.range) {
320 return result.filter(step => {
321 return step < 100 * (this.minValue - this.min) / (this.max - this.min) ||
322 step > 100 * (this.maxValue - this.min) / (this.max - this.min);
323 });
324 } else {
325 return result.filter(step => step > 100 * (this.firstValue - this.min) / (this.max - this.min));
326 }
327 },
328
329 markList() {
330 if (!this.marks) {
331 return [];
332 }
333
334 const marksKeys = Object.keys(this.marks);
335 return marksKeys.map(parseFloat)
336 .sort((a, b) => a - b)
337 .filter(point => point <= this.max && point >= this.min)
338 .map(point => ({
339 point,
340 position: (point - this.min) * 100 / (this.max - this.min),
341 mark: this.marks[point]
342 }));
343 },
344
345 minValue() {
346 return Math.min(this.firstValue, this.secondValue);
347 },
348
349 maxValue() {
350 return Math.max(this.firstValue, this.secondValue);
351 },
352
353 barSize() {
354 return this.range
355 ? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
356 : `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
357 },
358
359 barStart() {
360 return this.range
361 ? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
362 : '0%';
363 },
364
365 precision() {
366 let precisions = [this.min, this.max, this.step].map(item => {
367 let decimal = ('' + item).split('.')[1];
368 return decimal ? decimal.length : 0;
369 });
370 return Math.max.apply(null, precisions);
371 },
372
373 runwayStyle() {
374 return this.vertical ? { height: this.height } : {};
375 },
376
377 barStyle() {
378 return this.vertical
379 ? {
380 height: this.barSize,
381 bottom: this.barStart
382 } : {
383 width: this.barSize,
384 left: this.barStart
385 };
386 },
387
388 sliderDisabled() {
389 return this.disabled || (this.elForm || {}).disabled;
390 }
391 },
392
393 mounted() {
394 let valuetext;
395 if (this.range) {
396 if (Array.isArray(this.value)) {
397 this.firstValue = Math.max(this.min, this.value[0]);
398 this.secondValue = Math.min(this.max, this.value[1]);
399 } else {
400 this.firstValue = this.min;
401 this.secondValue = this.max;
402 }
403 this.oldValue = [this.firstValue, this.secondValue];
404 valuetext = `${this.firstValue}-${this.secondValue}`;
405 } else {
406 if (typeof this.value !== 'number' || isNaN(this.value)) {
407 this.firstValue = this.min;
408 } else {
409 this.firstValue = Math.min(this.max, Math.max(this.min, this.value));
410 }
411 this.oldValue = this.firstValue;
412 valuetext = this.firstValue;
413 }
414 this.$el.setAttribute('aria-valuetext', valuetext);
415
416 // label screen reader
417 this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`);
418
419 this.resetSize();
420 window.addEventListener('resize', this.resetSize);
421 },
422
423 beforeDestroy() {
424 window.removeEventListener('resize', this.resetSize);
425 }
426 };
427</script>