element/packages/slider/src/main.vue

388 lines
10 KiB
Vue

<template>
<div
class="el-slider"
:class="{ 'is-vertical': vertical, 'el-slider--with-input': showInput }"
role="slider"
:aria-valuemin="min"
:aria-valuemax="max"
:aria-orientation="vertical ? 'vertical': 'horizontal'"
:aria-disabled="sliderDisabled"
>
<el-input-number
v-model="firstValue"
v-if="showInput && !range"
class="el-slider__input"
ref="input"
@change="$nextTick(emitChange)"
:step="step"
:disabled="sliderDisabled"
:controls="showInputControls"
:min="min"
:max="max"
:debounce="debounce"
:size="inputSize">
</el-input-number>
<div
class="el-slider__runway"
:class="{ 'show-input': showInput, 'disabled': sliderDisabled }"
:style="runwayStyle"
@click="onSliderClick"
ref="slider">
<div
class="el-slider__bar"
:style="barStyle">
</div>
<slider-button
:vertical="vertical"
v-model="firstValue"
:tooltip-class="tooltipClass"
ref="button1">
</slider-button>
<slider-button
:vertical="vertical"
v-model="secondValue"
:tooltip-class="tooltipClass"
ref="button2"
v-if="range">
</slider-button>
<div
class="el-slider__stop"
v-for="(item, key) in stops"
:key="key"
:style="vertical ? { 'bottom': item + '%' } : { 'left': item + '%' }"
v-if="showStops">
</div>
</div>
</div>
</template>
<script type="text/babel">
import ElInputNumber from 'element-ui/packages/input-number';
import SliderButton from './button.vue';
import Emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElSlider',
mixins: [Emitter],
inject: {
elForm: {
default: ''
}
},
props: {
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
value: {
type: [Number, Array],
default: 0
},
showInput: {
type: Boolean,
default: false
},
showInputControls: {
type: Boolean,
default: true
},
inputSize: {
type: String,
default: 'small'
},
showStops: {
type: Boolean,
default: false
},
showTooltip: {
type: Boolean,
default: true
},
formatTooltip: Function,
disabled: {
type: Boolean,
default: false
},
range: {
type: Boolean,
default: false
},
vertical: {
type: Boolean,
default: false
},
height: {
type: String
},
debounce: {
type: Number,
default: 300
},
label: {
type: String
},
tooltipClass: String
},
components: {
ElInputNumber,
SliderButton
},
data() {
return {
firstValue: null,
secondValue: null,
oldValue: null,
dragging: false,
sliderSize: 1
};
},
watch: {
value(val, oldVal) {
if (this.dragging ||
Array.isArray(val) &&
Array.isArray(oldVal) &&
val.every((item, index) => item === oldVal[index])) {
return;
}
this.setValues();
},
dragging(val) {
if (!val) {
this.setValues();
}
},
firstValue(val) {
if (this.range) {
this.$emit('input', [this.minValue, this.maxValue]);
} else {
this.$emit('input', val);
}
},
secondValue() {
if (this.range) {
this.$emit('input', [this.minValue, this.maxValue]);
}
},
min() {
this.setValues();
},
max() {
this.setValues();
}
},
methods: {
valueChanged() {
if (this.range) {
return ![this.minValue, this.maxValue]
.every((item, index) => item === this.oldValue[index]);
} else {
return this.value !== this.oldValue;
}
},
setValues() {
if (this.min > this.max) {
console.error('[Element Error][Slider]min should not be greater than max.');
return;
}
const val = this.value;
if (this.range && Array.isArray(val)) {
if (val[1] < this.min) {
this.$emit('input', [this.min, this.min]);
} else if (val[0] > this.max) {
this.$emit('input', [this.max, this.max]);
} else if (val[0] < this.min) {
this.$emit('input', [this.min, val[1]]);
} else if (val[1] > this.max) {
this.$emit('input', [val[0], this.max]);
} else {
this.firstValue = val[0];
this.secondValue = val[1];
if (this.valueChanged()) {
this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue]);
this.oldValue = val.slice();
}
}
} else if (!this.range && typeof val === 'number' && !isNaN(val)) {
if (val < this.min) {
this.$emit('input', this.min);
} else if (val > this.max) {
this.$emit('input', this.max);
} else {
this.firstValue = val;
if (this.valueChanged()) {
this.dispatch('ElFormItem', 'el.form.change', val);
this.oldValue = val;
}
}
}
},
setPosition(percent) {
const targetValue = this.min + percent * (this.max - this.min) / 100;
if (!this.range) {
this.$refs.button1.setPosition(percent);
return;
}
let button;
if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
button = this.firstValue < this.secondValue ? 'button1' : 'button2';
} else {
button = this.firstValue > this.secondValue ? 'button1' : 'button2';
}
this.$refs[button].setPosition(percent);
},
onSliderClick(event) {
if (this.sliderDisabled || this.dragging) return;
this.resetSize();
if (this.vertical) {
const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom;
this.setPosition((sliderOffsetBottom - event.clientY) / this.sliderSize * 100);
} else {
const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize * 100);
}
this.emitChange();
},
resetSize() {
if (this.$refs.slider) {
this.sliderSize = this.$refs.slider[`client${ this.vertical ? 'Height' : 'Width' }`];
}
},
emitChange() {
this.$nextTick(() => {
this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value);
});
}
},
computed: {
stops() {
if (!this.showStops || this.min > this.max) return [];
if (this.step === 0) {
process.env.NODE_ENV !== 'production' &&
console.warn('[Element Warn][Slider]step should not be 0.');
return [];
}
const stopCount = (this.max - this.min) / this.step;
const stepWidth = 100 * this.step / (this.max - this.min);
const result = [];
for (let i = 1; i < stopCount; i++) {
result.push(i * stepWidth);
}
if (this.range) {
return result.filter(step => {
return step < 100 * (this.minValue - this.min) / (this.max - this.min) ||
step > 100 * (this.maxValue - this.min) / (this.max - this.min);
});
} else {
return result.filter(step => step > 100 * (this.firstValue - this.min) / (this.max - this.min));
}
},
minValue() {
return Math.min(this.firstValue, this.secondValue);
},
maxValue() {
return Math.max(this.firstValue, this.secondValue);
},
barSize() {
return this.range
? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
: `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
},
barStart() {
return this.range
? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
: '0%';
},
precision() {
let precisions = [this.min, this.max, this.step].map(item => {
let decimal = ('' + item).split('.')[1];
return decimal ? decimal.length : 0;
});
return Math.max.apply(null, precisions);
},
runwayStyle() {
return this.vertical ? { height: this.height } : {};
},
barStyle() {
return this.vertical
? {
height: this.barSize,
bottom: this.barStart
} : {
width: this.barSize,
left: this.barStart
};
},
sliderDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
},
mounted() {
let valuetext;
if (this.range) {
if (Array.isArray(this.value)) {
this.firstValue = Math.max(this.min, this.value[0]);
this.secondValue = Math.min(this.max, this.value[1]);
} else {
this.firstValue = this.min;
this.secondValue = this.max;
}
this.oldValue = [this.firstValue, this.secondValue];
valuetext = `${this.firstValue}-${this.secondValue}`;
} else {
if (typeof this.value !== 'number' || isNaN(this.value)) {
this.firstValue = this.min;
} else {
this.firstValue = Math.min(this.max, Math.max(this.min, this.value));
}
this.oldValue = this.firstValue;
valuetext = this.firstValue;
}
this.$el.setAttribute('aria-valuetext', valuetext);
// label screen reader
this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`);
this.resetSize();
window.addEventListener('resize', this.resetSize);
},
beforeDestroy() {
window.removeEventListener('resize', this.resetSize);
}
};
</script>