mirror of https://github.com/ElemeFE/element
				
				
				
			Slider: add range support (#2751)
							parent
							
								
									7f6d698f72
								
							
						
					
					
						commit
						450cf81ded
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 事件名称      | 说明    | 回调参数      |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
<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>
 | 
			
		||||
| 
						 | 
				
			
			@ -2,9 +2,8 @@
 | 
			
		|||
  <div class="el-slider">
 | 
			
		||||
    <el-input-number
 | 
			
		||||
      v-model="inputValue"
 | 
			
		||||
      v-if="showInput"
 | 
			
		||||
      v-if="showInput && !range"
 | 
			
		||||
      class="el-slider__input"
 | 
			
		||||
      @keyup.native="onInputChange"
 | 
			
		||||
      ref="input"
 | 
			
		||||
      :step="step"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
| 
						 | 
				
			
			@ -15,29 +14,37 @@
 | 
			
		|||
    </el-input-number>
 | 
			
		||||
    <div class="el-slider__runway"
 | 
			
		||||
      :class="{ 'show-input': showInput, 'disabled': disabled }"
 | 
			
		||||
      @click="onSliderClick" ref="slider">
 | 
			
		||||
      <div class="el-slider__bar" :style="{ width: currentPosition }"></div>
 | 
			
		||||
      @click="onSliderClick"
 | 
			
		||||
      ref="slider">
 | 
			
		||||
      <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>
 | 
			
		||||
        class="el-slider__bar"
 | 
			
		||||
        :style="{
 | 
			
		||||
          width: barWidth,
 | 
			
		||||
          left: barLeft
 | 
			
		||||
        }">
 | 
			
		||||
      </div>
 | 
			
		||||
      <slider-button
 | 
			
		||||
        v-model="firstValue"
 | 
			
		||||
        ref="button1">
 | 
			
		||||
      </slider-button>
 | 
			
		||||
      <slider-button
 | 
			
		||||
        v-model="secondValue"
 | 
			
		||||
        ref="button2"
 | 
			
		||||
        v-if="range">
 | 
			
		||||
      </slider-button>
 | 
			
		||||
      <div
 | 
			
		||||
        class="el-slider__stop"
 | 
			
		||||
        v-for="item in stops"
 | 
			
		||||
        :style="{ 'left': item + '%' }"
 | 
			
		||||
        v-if="showStops">
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="el-slider__stop" v-for="item in stops" :style="{ 'left': item + '%' }" v-if="showStops"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script type="text/babel">
 | 
			
		||||
  import ElInputNumber from 'element-ui/packages/input-number';
 | 
			
		||||
  import ElTooltip from 'element-ui/packages/tooltip';
 | 
			
		||||
  import SliderButton from './button.vue';
 | 
			
		||||
  import { getStyle } from 'element-ui/src/utils/dom';
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
| 
						 | 
				
			
			@ -56,12 +63,8 @@
 | 
			
		|||
        type: Number,
 | 
			
		||||
        default: 1
 | 
			
		||||
      },
 | 
			
		||||
      defaultValue: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        default: 0
 | 
			
		||||
      },
 | 
			
		||||
      value: {
 | 
			
		||||
        type: Number,
 | 
			
		||||
        type: [Number, Array],
 | 
			
		||||
        default: 0
 | 
			
		||||
      },
 | 
			
		||||
      showInput: {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,142 +82,136 @@
 | 
			
		|||
      disabled: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: false
 | 
			
		||||
      },
 | 
			
		||||
      range: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: false
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    components: {
 | 
			
		||||
      ElInputNumber,
 | 
			
		||||
      ElTooltip
 | 
			
		||||
      SliderButton
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data() {
 | 
			
		||||
      return {
 | 
			
		||||
        firstValue: null,
 | 
			
		||||
        secondValue: null,
 | 
			
		||||
        oldValue: null,
 | 
			
		||||
        precision: 0,
 | 
			
		||||
        inputValue: null,
 | 
			
		||||
        timeout: null,
 | 
			
		||||
        hovering: false,
 | 
			
		||||
        dragging: false,
 | 
			
		||||
        startX: 0,
 | 
			
		||||
        currentX: 0,
 | 
			
		||||
        startPos: 0,
 | 
			
		||||
        newPos: null,
 | 
			
		||||
        oldValue: this.value,
 | 
			
		||||
        currentPosition: (this.value - this.min) / (this.max - this.min) * 100 + '%'
 | 
			
		||||
        dragging: false
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    watch: {
 | 
			
		||||
      inputValue(val) {
 | 
			
		||||
        this.$emit('input', Number(val));
 | 
			
		||||
        this.firstValue = val;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      value(val) {
 | 
			
		||||
        this.$nextTick(() => {
 | 
			
		||||
          this.updatePopper();
 | 
			
		||||
        });
 | 
			
		||||
        if (typeof val !== 'number' || isNaN(val) || val < this.min) {
 | 
			
		||||
          this.$emit('input', this.min);
 | 
			
		||||
      value(val, oldVal) {
 | 
			
		||||
        if (this.dragging ||
 | 
			
		||||
          Array.isArray(val) &&
 | 
			
		||||
          Array.isArray(oldVal) &&
 | 
			
		||||
          val.every((item, index) => item === oldVal[index])) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (val > this.max) {
 | 
			
		||||
          this.$emit('input', this.max);
 | 
			
		||||
          return;
 | 
			
		||||
        this.setValues();
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      dragging(val) {
 | 
			
		||||
        if (!val) {
 | 
			
		||||
          this.setValues();
 | 
			
		||||
        }
 | 
			
		||||
        this.inputValue = val;
 | 
			
		||||
        this.setPosition((val - this.min) * 100 / (this.max - this.min));
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      firstValue(val) {
 | 
			
		||||
        if (this.range) {
 | 
			
		||||
          this.$emit('input', [this.minValue, this.maxValue]);
 | 
			
		||||
        } else {
 | 
			
		||||
          this.inputValue = val;
 | 
			
		||||
          this.$emit('input', val);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      secondValue() {
 | 
			
		||||
        if (this.range) {
 | 
			
		||||
          this.$emit('input', [this.minValue, this.maxValue]);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      min() {
 | 
			
		||||
        this.setValues();
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      max() {
 | 
			
		||||
        this.setValues();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
      handleMouseEnter() {
 | 
			
		||||
        this.hovering = true;
 | 
			
		||||
        this.$refs.tooltip.showPopper = true;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      handleMouseLeave() {
 | 
			
		||||
        this.hovering = false;
 | 
			
		||||
        this.$refs.tooltip.showPopper = false;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      updatePopper() {
 | 
			
		||||
        this.$refs.tooltip.updatePopper();
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      setPosition(newPos) {
 | 
			
		||||
        if (newPos < 0) {
 | 
			
		||||
          newPos = 0;
 | 
			
		||||
        } else if (newPos > 100) {
 | 
			
		||||
          newPos = 100;
 | 
			
		||||
      valueChanged() {
 | 
			
		||||
        if (this.range) {
 | 
			
		||||
          return ![this.minValue, this.maxValue]
 | 
			
		||||
            .every((item, index) => item === this.oldValue[index]);
 | 
			
		||||
        } else {
 | 
			
		||||
          return this.value !== this.oldValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const lengthPerStep = 100 / ((this.max - this.min) / this.step);
 | 
			
		||||
        const steps = Math.round(newPos / lengthPerStep);
 | 
			
		||||
        let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min;
 | 
			
		||||
        value = parseFloat(value.toFixed(this.precision));
 | 
			
		||||
        this.$emit('input', value);
 | 
			
		||||
        this.currentPosition = (this.value - this.min) / (this.max - this.min) * 100 + '%';
 | 
			
		||||
        if (!this.dragging) {
 | 
			
		||||
          if (this.value !== this.oldValue) {
 | 
			
		||||
            this.$emit('change', this.value);
 | 
			
		||||
            this.oldValue = this.value;
 | 
			
		||||
      },
 | 
			
		||||
      setValues() {
 | 
			
		||||
        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.$emit('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.$emit('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.disabled || this.dragging) return;
 | 
			
		||||
        const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
 | 
			
		||||
        this.setPosition((event.clientX - sliderOffsetLeft) / this.$sliderWidth * 100);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onInputChange() {
 | 
			
		||||
        if (this.value === '') {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.value)) {
 | 
			
		||||
          this.setPosition((this.value - this.min) * 100 / (this.max - this.min));
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onDragStart(event) {
 | 
			
		||||
        this.dragging = true;
 | 
			
		||||
        this.startX = event.clientX;
 | 
			
		||||
        this.startPos = parseInt(this.currentPosition, 10);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onDragging(event) {
 | 
			
		||||
        if (this.dragging) {
 | 
			
		||||
          this.$refs.tooltip.showPopper = true;
 | 
			
		||||
          this.currentX = event.clientX;
 | 
			
		||||
          const diff = (this.currentX - this.startX) / this.$sliderWidth * 100;
 | 
			
		||||
          this.newPos = this.startPos + diff;
 | 
			
		||||
          this.setPosition(this.newPos);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onDragEnd() {
 | 
			
		||||
        if (this.dragging) {
 | 
			
		||||
          /*
 | 
			
		||||
           * 防止在 mouseup 后立即触发 click,导致滑块有几率产生一小段位移
 | 
			
		||||
           * 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
 | 
			
		||||
           */
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            this.dragging = false;
 | 
			
		||||
            this.$refs.tooltip.showPopper = false;
 | 
			
		||||
            this.setPosition(this.newPos);
 | 
			
		||||
          }, 0);
 | 
			
		||||
          window.removeEventListener('mousemove', this.onDragging);
 | 
			
		||||
          window.removeEventListener('mouseup', this.onDragEnd);
 | 
			
		||||
          window.removeEventListener('contextmenu', this.onDragEnd);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onButtonDown(event) {
 | 
			
		||||
        if (this.disabled) return;
 | 
			
		||||
        this.onDragStart(event);
 | 
			
		||||
        window.addEventListener('mousemove', this.onDragging);
 | 
			
		||||
        window.addEventListener('mouseup', this.onDragEnd);
 | 
			
		||||
        window.addEventListener('contextmenu', this.onDragEnd);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -224,31 +221,67 @@
 | 
			
		|||
      },
 | 
			
		||||
 | 
			
		||||
      stops() {
 | 
			
		||||
        const stopCount = (this.max - this.value) / this.step;
 | 
			
		||||
        const currentLeft = parseFloat(this.currentPosition);
 | 
			
		||||
        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(currentLeft + i * stepWidth);
 | 
			
		||||
          result.push(i * stepWidth);
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
        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);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      barWidth() {
 | 
			
		||||
        return this.range
 | 
			
		||||
          ? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
 | 
			
		||||
          : `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      barLeft() {
 | 
			
		||||
        return this.range
 | 
			
		||||
          ? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
 | 
			
		||||
          : '0%';
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    created() {
 | 
			
		||||
      if (typeof this.value !== 'number' ||
 | 
			
		||||
        isNaN(this.value) ||
 | 
			
		||||
        this.value < this.min) {
 | 
			
		||||
        this.$emit('input', this.min);
 | 
			
		||||
      } else if (this.value > this.max) {
 | 
			
		||||
        this.$emit('input', this.max);
 | 
			
		||||
    mounted() {
 | 
			
		||||
      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];
 | 
			
		||||
      } 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;
 | 
			
		||||
      }
 | 
			
		||||
      let precisions = [this.min, this.max, this.step].map(item => {
 | 
			
		||||
        let decimal = ('' + item).split('.')[1];
 | 
			
		||||
        return decimal ? decimal.length : 0;
 | 
			
		||||
      });
 | 
			
		||||
      this.precision = Math.max.apply(null, precisions);
 | 
			
		||||
      this.inputValue = this.inputValue || this.value;
 | 
			
		||||
      this.inputValue = this.inputValue || this.firstValue;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue