Commit 450cf81d authored by 杨奕's avatar 杨奕 Committed by baiyaaaaa

Slider: add range support (#2751)

parent 7f6d698f
......@@ -7,7 +7,8 @@
value3: 42,
value4: 0,
value5: 0,
value6: 0
value6: 0,
value7: [4, 8]
};
}
}
......@@ -119,6 +120,35 @@ Set value via a input box.
```
:::
### Range selection
Selecting a range of values is supported.
:::demo Setting the `range` attribute activates range mode, where the binding value is an array made up of two boundary values.
```html
<template>
<div class="block">
<el-slider
v-model="value7"
range
show-stops
:max="10">
</el-slider>
</div>
</template>
<script>
export default {
data() {
return {
value7: [4, 8]
}
}
}
</script>
```
:::
## Attributes
| Attribute | Description | Type | Accepted Values | Default |
|---------- |-------------- |---------- |-------------------------------- |-------- |
......@@ -126,9 +156,10 @@ Set value via a input box.
| max | maximum value | number | — | 100 |
| disabled | whether Slider is disabled | boolean | — | false |
| step | step size | number | — | 1 |
| show-input | whether to display an input box | boolean | — | false |
| show-input | whether to display an input box, works when `range` is false | boolean | — | false |
| show-input-controls | whether to display control buttons when `show-input` is true | boolean | — | true |
| show-stops | whether to display breakpoints | boolean | — | false |
| range | whether to select a range | boolean | — | false |
## Events
| Event Name | Description | Parameters |
......
......@@ -7,7 +7,8 @@
value3: 42,
value4: 0,
value5: 0,
value6: 0
value6: 0,
value7: [4, 8]
};
}
}
......@@ -143,6 +144,35 @@
```
:::
### 范围选择
支持选择某一数值范围
:::demo 设置`range`即可开启范围选择,此时绑定值是一个数组,其元素分别为最小边界值和最大边界值
```html
<template>
<div class="block">
<el-slider
v-model="value7"
range
show-stops
:max="10">
</el-slider>
</div>
</template>
<script>
export default {
data() {
return {
value7: [4, 8]
}
}
}
</script>
```
:::
### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------------- |---------- |-------------------------------- |-------- |
......@@ -150,9 +180,10 @@
| max | 最大值 | number | — | 100 |
| disabled | 是否禁用 | boolean | — | false |
| step | 步长 | number | — | 1 |
| show-input | 是否显示输入框 | boolean | — | false |
| show-input | 是否显示输入框,仅在非范围选择时有效 | boolean | — | false |
| show-input-controls | 在显示输入框的情况下,是否显示输入框的控制按钮 | boolean | — | true|
| show-stops | 是否显示间断点 | boolean | — | false |
| range | 是否为范围选择 | boolean | — | false |
### Events
| 事件名称 | 说明 | 回调参数 |
......
<template>
<div
class="el-slider__button-wrapper"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@mousedown="onButtonDown"
:class="{ 'hover': hovering, 'dragging': dragging }"
:style="{ left: currentPosition }"
ref="button">
<el-tooltip placement="top" ref="tooltip">
<span slot="content">{{ value }}</span>
<div class="el-slider__button" :class="{ 'hover': hovering, 'dragging': dragging }"></div>
</el-tooltip>
</div>
</template>
<script>
import ElTooltip from 'element-ui/packages/tooltip';
export default {
name: 'ElSliderButton',
components: {
ElTooltip
},
props: {
value: {
type: Number,
default: 0
}
},
data() {
return {
hovering: false,
dragging: false,
startX: 0,
currentX: 0,
startPosition: 0,
newPosition: null,
oldValue: this.value
};
},
computed: {
disabled() {
return this.$parent.disabled;
},
max() {
return this.$parent.max;
},
min() {
return this.$parent.min;
},
step() {
return this.$parent.step;
},
precision() {
return this.$parent.precision;
},
currentPosition() {
return `${ (this.value - this.min) / (this.max - this.min) * 100 }%`;
}
},
watch: {
dragging(val) {
this.$parent.dragging = val;
}
},
methods: {
showTooltip() {
this.$refs.tooltip && (this.$refs.tooltip.showPopper = true);
},
hideTooltip() {
this.$refs.tooltip && (this.$refs.tooltip.showPopper = false);
},
handleMouseEnter() {
this.hovering = true;
this.showTooltip();
},
handleMouseLeave() {
this.hovering = false;
this.hideTooltip();
},
onButtonDown(event) {
if (this.disabled) return;
this.onDragStart(event);
window.addEventListener('mousemove', this.onDragging);
window.addEventListener('mouseup', this.onDragEnd);
window.addEventListener('contextmenu', this.onDragEnd);
},
onDragStart(event) {
this.dragging = true;
this.startX = event.clientX;
this.startPosition = parseInt(this.currentPosition, 10);
},
onDragging(event) {
if (this.dragging) {
this.showTooltip();
this.currentX = event.clientX;
const diff = (this.currentX - this.startX) / this.$parent.$sliderWidth * 100;
this.newPosition = this.startPosition + diff;
this.setPosition(this.newPosition);
}
},
onDragEnd() {
if (this.dragging) {
/*
* 防止在 mouseup 后立即触发 click,导致滑块有几率产生一小段位移
* 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
*/
setTimeout(() => {
this.dragging = false;
this.hideTooltip();
this.setPosition(this.newPosition);
}, 0);
window.removeEventListener('mousemove', this.onDragging);
window.removeEventListener('mouseup', this.onDragEnd);
window.removeEventListener('contextmenu', this.onDragEnd);
}
},
setPosition(newPosition) {
if (newPosition < 0) {
newPosition = 0;
} else if (newPosition > 100) {
newPosition = 100;
}
const lengthPerStep = 100 / ((this.max - this.min) / this.step);
const steps = Math.round(newPosition / lengthPerStep);
let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min;
value = parseFloat(value.toFixed(this.precision));
this.$emit('input', value);
this.$refs.tooltip && this.$refs.tooltip.updatePopper();
if (!this.dragging && this.value !== this.oldValue) {
this.oldValue = this.value;
}
}
}
};
</script>
\ No newline at end of file
This diff is collapsed.
......@@ -37,7 +37,7 @@ describe('Slider', () => {
done();
});
});
}, 100);
}, 10);
});
it('show tooltip', () => {
......@@ -55,7 +55,7 @@ describe('Slider', () => {
};
}
}, true);
const slider = vm.$children[0];
const slider = vm.$children[0].$children[0];
slider.handleMouseEnter();
expect(slider.$refs.tooltip.showPopper).to.true;
slider.handleMouseLeave();
......@@ -76,14 +76,14 @@ describe('Slider', () => {
};
}
}, true);
const slider = vm.$children[0];
const slider = vm.$children[0].$children[0];
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
setTimeout(() => {
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
expect(vm.value > 0).to.true;
done();
}, 150);
}, 10);
});
it('step', done => {
......@@ -100,14 +100,14 @@ describe('Slider', () => {
};
}
}, true);
const slider = vm.$children[0];
const slider = vm.$children[0].$children[0];
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
setTimeout(() => {
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
expect(vm.value > 0.4 && vm.value < 0.6).to.true;
done();
}, 150);
}, 10);
});
it('click', done => {
......@@ -130,8 +130,8 @@ describe('Slider', () => {
setTimeout(() => {
expect(vm.value > 0).to.true;
done();
}, 150);
}, 150);
}, 10);
}, 10);
});
it('disabled', done => {
......@@ -148,15 +148,14 @@ describe('Slider', () => {
};
}
}, true);
const slider = vm.$children[0];
const slider = vm.$children[0].$children[0];
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
setTimeout(() => {
slider.onButtonDown({ clientX: 0 });
slider.onDragging({ clientX: 100 });
slider.onDragEnd();
slider.onSliderClick({ clientX: 200 });
expect(vm.value).to.equal(0);
done();
}, 100);
}, 10);
});
it('show input', done => {
......@@ -180,17 +179,145 @@ describe('Slider', () => {
setTimeout(() => {
expect(vm.value).to.equal(40);
done();
}, 150);
}, 150);
}, 10);
}, 10);
});
it('show stops', done => {
it('show stops', () => {
vm = createTest(Slider, {
showStops: true,
step: 10
}, true);
const stops = vm.$el.querySelectorAll('.el-slider__stop');
expect(stops.length).to.equal(9);
done();
});
describe('range', () => {
it('basic ranged slider', () => {
vm = createVue({
template: `
<div>
<el-slider v-model="value" range></el-slider>
</div>
`,
data() {
return {
value: [10, 20]
};
}
}, true);
const buttons = vm.$children[0].$children;
expect(buttons.length).to.equal(2);
});
it('should not exceed min and max', done => {
vm = createVue({
template: `
<div>
<el-slider v-model="value" range :min="50">
</el-slider>
</div>
`,
data() {
return {
value: [50, 60]
};
}
}, true);
setTimeout(() => {
vm.value = [40, 60];
setTimeout(() => {
expect(vm.value).to.deep.equal([50, 60]);
vm.value = [50, 120];
setTimeout(() => {
expect(vm.value).to.deep.equal([50, 100]);
done();
}, 10);
}, 10);
}, 10);
});
it('click', done => {
vm = createVue({
template: `
<div style="width: 200px;">
<el-slider range v-model="value"></el-slider>
</div>
`,
data() {
return {
value: [0, 100]
};
}
}, true);
const slider = vm.$children[0];
setTimeout(() => {
slider.onSliderClick({ clientX: 100 });
setTimeout(() => {
expect(vm.value[0] > 0).to.true;
expect(vm.value[1]).to.equal(100);
done();
}, 10);
}, 10);
});
it('responsive to dynamic min and max', done => {
vm = createVue({
template: `
<div>
<el-slider v-model="value" range :min="min" :max="max">
</el-slider>
</div>
`,
data() {
return {
min: 0,
max: 100,
value: [50, 80]
};
}
}, true);
setTimeout(() => {
vm.min = 60;
setTimeout(() => {
expect(vm.value).to.deep.equal([60, 80]);
vm.min = 30;
vm.max = 40;
setTimeout(() => {
expect(vm.value).to.deep.equal([40, 40]);
done();
}, 10);
}, 10);
}, 10);
});
it('show stops', done => {
vm = createVue({
template: `
<div>
<el-slider
v-model="value"
range
:step="10"
show-stops></el-slider>
</div>
`,
data() {
return {
value: [30, 60]
};
}
}, true);
setTimeout(() => {
const stops = vm.$el.querySelectorAll('.el-slider__stop');
expect(stops.length).to.equal(5);
done();
}, 10);
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment