refactor(slider): refactor slider component completed

This commit is contained in:
Ryan
2020-09-01 19:05:50 +08:00
committed by jeremywu
parent e41142f49b
commit c659d45d86
11 changed files with 1084 additions and 0 deletions

View File

@@ -28,6 +28,7 @@ import ElCollapse from '@element-plus/collapse'
import ElPopper from '@element-plus/popper'
import ElTabs from '@element-plus/tabs'
import ElTooltip from '@element-plus/tooltip'
import ElSlider from '@element-plus/slider'
export {
ElAlert,
@@ -58,6 +59,7 @@ export {
ElCollapse,
ElTabs,
ElTooltip,
ElSlider,
}
export default function install(app: App): void {
@@ -90,4 +92,5 @@ export default function install(app: App): void {
ElPopper(app)
ElTabs(app)
ElTooltip(app)
ElSlider(app)
}

6
packages/slider/index.ts Normal file
View File

@@ -0,0 +1,6 @@
import { App } from 'vue'
import Slider from './src/index.vue'
export default (app: App): void => {
app.component(Slider.name, Slider)
}

View File

@@ -0,0 +1,12 @@
{
"name": "@element-plus/slider",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.5"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
}
}

110
packages/slider/src/Slider.d.ts vendored Normal file
View File

@@ -0,0 +1,110 @@
import { ComputedRef, Ref } from 'vue'
export interface ISliderProps {
modelValue: number | number[]
min: number
max: number
step: number
value: number | number[]
showInput: boolean
showInputControls: boolean
inputSize: string
showStops: boolean
showTooltip: boolean
formatTooltip: (val: number) => number | string
disabled: boolean
range: boolean
vertical: boolean
height: string
debounce: number
label: string
tooltipClass: string
marks: Record<number, any>
}
export interface ISliderInitData {
firstValue: Nullable<number>
secondValue: Nullable<number>
oldValue: Nullable<number>
dragging: boolean
sliderSize: number
}
export interface ISliderProvider {
disabled: ComputedRef<boolean>
min: ComputedRef<number>
max: ComputedRef<number>
step: ComputedRef<number>
showTooltip: ComputedRef<boolean>
precision: ComputedRef<number>
sliderSize: ComputedRef<number>
formatTooltip: ComputedRef<(value: number) => number | string>
emitChange: () => void
resetSize: () => void
updateDragging: (val: boolean) => void
}
export type Mark = {
point: number
position: number
mark: {[s: string]: any;}
}
export type Marks = ComputedRef<Mark[]>
export type Stops = {
stops: ComputedRef<number[]>
getStopStyle: (position: number) => CSSStyleDeclaration
}
export type Slide = {
slider: Ref<HTMLElement>
firstButton: Ref<ISliderButton>
secondButton: Ref<ISliderButton>
sliderDisabled: ComputedRef<boolean>
minValue: ComputedRef<number>
maxValue: ComputedRef<number>
runwayStyle: ComputedRef<CSSStyleDeclaration>
barStyle: ComputedRef<CSSStyleDeclaration>
resetSize: () => void
setPosition: (percent: number) => void
emitChange: () => void
onSliderClick: (event: MouseEvent) => void
}
export type ButtonRefs = {
[s in 'firstButton' | 'secondButton']: Ref<Nullable<ISliderButton>>
}
export interface ISliderButtonProps {
modelValue: number
vertical: boolean
tooltipClass: string
}
export interface ISliderButtonInitData {
hovering: boolean
dragging: boolean
isClick: boolean
startX: number
currentX: number
startY: number
currentY: number
startPosition: number
newPosition: number
oldValue: number
}
export interface ISliderButton {
tooltip: Ref<Nullable<HTMLElement>>
tooltipVisible: Ref<boolean>
showTooltip: Ref<boolean>
wrapperStyle: ComputedRef<CSSStyleDeclaration>
formatValue: ComputedRef<number | string>
handleMouseEnter: () => void
handleMouseLeave: () => void
onButtonDown: (event: MouseEvent | TouchEvent) => void
onLeftKeyDown: () => void
onRightKeyDown: () => void
setPosition: (newPosition: number) => void
}

View File

@@ -0,0 +1,119 @@
<template>
<div
ref="button"
class="el-slider__button-wrapper"
:class="{ 'hover': hovering, 'dragging': dragging }"
:style="wrapperStyle"
tabindex="0"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@mousedown="onButtonDown"
@touchstart="onButtonDown"
@focus="handleMouseEnter"
@blur="handleMouseLeave"
@keydown.left="onLeftKeyDown"
@keydown.right="onRightKeyDown"
@keydown.down.prevent="onLeftKeyDown"
@keydown.up.prevent="onRightKeyDown"
>
<el-popper
v-if="showTooltip"
ref="tooltip"
placement="top"
:popper-class="tooltipClass"
manual-mode
:value="tooltipVisible"
>
<template #default>{{ formatValue }}</template>
<template #trigger>
<div class="el-slider__button el-tooltip" :class="{ 'hover': hovering, 'dragging': dragging }"></div>
</template>
</el-popper>
<div v-else class="el-slider__button el-tooltip" :class="{ 'hover': hovering, 'dragging': dragging }"></div>
</div>
</template>
<script lang="ts">
import { Popper as ElPopper } from '@element-plus/popper'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { defineComponent, reactive, toRefs } from 'vue'
import { useSliderButton } from './useSliderButton'
export default defineComponent({
name: 'ElSliderButton',
components: {
ElPopper,
},
props: {
modelValue: {
type: Number,
default: 0,
},
vertical: {
type: Boolean,
default: false,
},
tooltipClass: {
type: String,
default: '',
},
},
emits: [UPDATE_MODEL_EVENT],
setup(props, { emit }) {
const initData = reactive({
hovering: false,
dragging: false,
isClick: false,
startX: 0,
currentX: 0,
startY: 0,
currentY: 0,
startPosition: 0,
newPosition: 0,
oldValue: props.modelValue,
})
const {
tooltip,
tooltipVisible,
showTooltip,
wrapperStyle,
formatValue,
handleMouseEnter,
handleMouseLeave,
onButtonDown,
onLeftKeyDown,
onRightKeyDown,
setPosition,
} = useSliderButton(props, initData, emit)
const {
hovering,
dragging,
} = toRefs(initData)
return {
tooltip,
tooltipVisible,
showTooltip,
wrapperStyle,
formatValue,
handleMouseEnter,
handleMouseLeave,
onButtonDown,
onLeftKeyDown,
onRightKeyDown,
setPosition,
hovering,
dragging,
}
},
})
</script>

View File

@@ -0,0 +1,420 @@
<template>
<div
ref="sliderWrapper"
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-if="showInput && !range"-->
<!-- ref="input"-->
<!-- v-model="firstValue"-->
<!-- class="el-slider__input"-->
<!-- :step="step"-->
<!-- :disabled="sliderDisabled"-->
<!-- :controls="showInputControls"-->
<!-- :min="min"-->
<!-- :max="max"-->
<!-- :debounce="debounce"-->
<!-- :size="inputSize"-->
<!-- @change="emitChange"-->
<!-- />-->
<div
ref="slider"
class="el-slider__runway"
:class="{ 'show-input': showInput, 'disabled': sliderDisabled }"
:style="runwayStyle"
@click="onSliderClick"
>
<div
class="el-slider__bar"
:style="barStyle"
>
</div>
<slider-button
ref="firstButton"
v-model="firstValue"
:vertical="vertical"
:tooltip-class="tooltipClass"
/>
<slider-button
v-if="range"
ref="secondButton"
v-model="secondValue"
:vertical="vertical"
:tooltip-class="tooltipClass"
/>
<div v-if="showStops">
<div
v-for="(item, key) in stops"
:key="key"
class="el-slider__stop"
:style="getStopStyle(item)"
></div>
</div>
<template v-if="markList.length > 0">
<div>
<div
v-for="(item, key) in markList"
:key="key"
:style="getStopStyle(item.position)"
class="el-slider__stop el-slider__marks-stop"
>
</div>
</div>
<div class="el-slider__marks">
<slider-marker
v-for="(item, key) in markList"
:key="key"
:mark="item.mark"
:style="getStopStyle(item.position)"
/>
</div>
</template>
</div>
</div>
</template>
<script lang="ts">
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { off, on } from '@element-plus/utils/dom'
import throwError from '@element-plus/utils/error'
import {
computed,
defineComponent,
nextTick,
onBeforeUnmount,
onMounted,
PropType,
provide,
reactive,
ref,
Ref,
toRefs,
watch,
} from 'vue'
// TODO: waiting for ElInputNumber component refactored
// import ElInputNumber from '@element-plus/input-number'
import SliderButton from './button.vue'
import SliderMarker from './marker.vue'
import { useMarks } from './useMarks'
import { useSlide } from './useSlide'
import { useStops } from './useStops'
export default defineComponent({
name: 'ElSlider',
components: {
// ElInputNumber,
SliderButton,
SliderMarker,
},
props: {
modelValue: {
type: [Number, Array] as PropType<number | number[]>,
default: 0,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
step: {
type: Number,
default: 1,
},
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: {
type: Function as PropType<(val: number) => number | string>,
default: undefined,
},
disabled: {
type: Boolean,
default: false,
},
range: {
type: Boolean,
default: false,
},
vertical: {
type: Boolean,
default: false,
},
height: {
type: String,
default: '',
},
debounce: {
type: Number,
default: 300,
},
label: {
type: String,
default: undefined,
},
tooltipClass: {
type: String,
default: undefined,
},
marks: {
type: Object,
default: value => value,
},
},
emits: [UPDATE_MODEL_EVENT, 'change'],
setup(props, { emit }) {
const initData = reactive({
firstValue: null,
secondValue: null,
oldValue: null,
dragging: false,
sliderSize: 1,
})
const {
slider,
firstButton,
secondButton,
sliderDisabled,
minValue,
maxValue,
runwayStyle,
barStyle,
resetSize,
emitChange,
onSliderClick,
} = useSlide(props, initData, emit)
const {
stops,
getStopStyle,
} = useStops(props, initData, minValue, maxValue)
const markList = useMarks(props)
useWatch(props, initData, minValue, maxValue, emit)
const precision = computed(() => {
let precisions = [props.min, props.max, props.step].map(item => {
let decimal = ('' + item).split('.')[1]
return decimal ? decimal.length : 0
})
return Math.max.apply(null, precisions)
})
const { sliderWrapper } = useLifecycle(props, initData, resetSize)
const {
firstValue,
secondValue,
oldValue,
dragging,
sliderSize,
} = toRefs(initData)
const updateDragging = (val: boolean) => {
initData.dragging = val
}
provide('SliderProvider', {
disabled: sliderDisabled,
min: computed(() => props.min),
max: computed(() => props.max),
step: computed(() => props.step),
showTooltip: computed(() => props.showTooltip),
precision: precision,
sliderSize: computed(() => sliderSize.value),
formatTooltip: computed(() => props.formatTooltip),
emitChange: emitChange,
resetSize: resetSize,
updateDragging: updateDragging,
})
return {
firstValue,
secondValue,
oldValue,
dragging,
sliderSize,
slider,
firstButton,
secondButton,
sliderDisabled,
runwayStyle,
barStyle,
emitChange,
onSliderClick,
getStopStyle,
stops,
markList,
sliderWrapper,
}
},
})
const useWatch = (props, initData, minValue, maxValue, emit) => {
const _emit = (val: number | number[]) => {
emit(UPDATE_MODEL_EVENT, val)
}
const valueChanged = () => {
if (props.range) {
return ![minValue.value, maxValue.value]
.every((item, index) => item === initData.oldValue[index])
} else {
return props.modelValue !== initData.oldValue
}
}
const setValues = () => {
if (props.min > props.max) {
throwError('Slider', 'min should not be greater than max.')
return
}
const val = props.modelValue
if (props.range && Array.isArray(val)) {
if (val[1] < props.min) {
_emit([props.min, props.min])
} else if (val[0] > props.max) {
_emit([props.max, props.max])
} else if (val[0] < props.min) {
_emit([props.min, val[1]])
} else if (val[1] > props.max) {
_emit([val[0], props.max])
} else {
initData.firstValue = val[0]
initData.secondValue = val[1]
if (valueChanged()) {
_emit([minValue.value, maxValue.value])
initData.oldValue = val.slice()
}
}
} else if (!props.range && typeof val === 'number' && !isNaN(val)) {
if (val < props.min) {
_emit(props.min)
} else if (val > props.max) {
_emit(props.max)
} else {
initData.firstValue = val
if (valueChanged()) {
_emit(val)
initData.oldValue = val
}
}
}
}
watch(() => initData.dragging, val => {
if (!val) {
setValues()
}
})
watch(() => initData.firstValue, val => {
if (props.range) {
_emit([minValue.value, maxValue.value])
} else {
_emit(val)
}
})
watch(() => initData.secondValue, () => {
if (props.range) {
_emit([minValue.value, maxValue.value])
}
})
watch(() => props.modelValue, (val, oldVal) => {
if (initData.dragging
|| Array.isArray(val)
&& Array.isArray(oldVal)
&& val.every((item, index) => item === oldVal[index])) {
return
}
setValues()
})
watch(() => [props.min, props.max], () => {
setValues()
})
}
const useLifecycle = (props, initData, resetSize) => {
const sliderWrapper: Ref<Nullable<HTMLElement>> = ref(null)
onMounted(async () => {
let valuetext
if (props.range) {
if (Array.isArray(props.modelValue)) {
initData.firstValue = Math.max(props.min, props.modelValue[0])
initData.secondValue = Math.min(props.max, props.modelValue[1])
} else {
initData.firstValue = props.min
initData.secondValue = props.max
}
initData.oldValue = [initData.firstValue, initData.secondValue]
valuetext = `${ initData.firstValue }-${ initData.secondValue }`
} else {
if (typeof props.modelValue !== 'number' || isNaN(props.modelValue)) {
initData.firstValue = props.min
} else {
initData.firstValue = Math.min(props.max, Math.max(props.min, props.modelValue))
}
initData.oldValue = initData.firstValue
valuetext = initData.firstValue
}
sliderWrapper.value.setAttribute('aria-valuetext', valuetext)
// label screen reader
sliderWrapper.value.setAttribute('aria-label', props.label ? props.label : `slider between ${ props.min } and ${ props.max }`)
on(window, 'resize', resetSize)
await nextTick()
resetSize()
})
onBeforeUnmount(() => {
off(window, 'resize', resetSize)
})
return {
sliderWrapper,
}
}
</script>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { computed, defineComponent, h, PropType } from 'vue'
export default defineComponent({
name: 'ElMarker',
props: {
mark: {
type: [String, Object] as PropType<string | Record<string, unknown>>,
default: () => undefined,
},
},
setup(props) {
const label = computed(() => {
return typeof props.mark === 'string' ? props.mark : props.mark.label
})
return {
label,
}
},
render() {
return h('div', {
class: 'el-slider__marks-text',
style: this.mark?.style,
}, this.label)
},
})
</script>

View File

@@ -0,0 +1,21 @@
import { computed } from 'vue'
import { ISliderProps, Mark } from './Slider'
export const useMarks = (props: ISliderProps) => {
return computed(() => {
if (!props.marks) {
return []
}
const marksKeys = Object.keys(props.marks)
return marksKeys.map(parseFloat)
.sort((a, b) => a - b)
.filter(point => point <= props.max && point >= props.min)
.map((point): Mark => ({
point,
position: (point - props.min) * 100 / (props.max - props.min),
mark: props.marks[point],
}))
})
}

View File

@@ -0,0 +1,110 @@
import { computed, inject, nextTick, ref } from 'vue'
import { ButtonRefs, ISliderInitData, ISliderProps } from './Slider'
export const useSlide = (props: ISliderProps, initData: ISliderInitData, emit) => {
const elForm: {disabled?: boolean;} = inject('elForm', {})
const slider = ref<Nullable<HTMLElement>>(null)
const firstButton = ref(null)
const secondButton = ref(null)
const buttonRefs: ButtonRefs = {
firstButton,
secondButton,
}
const sliderDisabled = computed(() => {
return props.disabled || (elForm?.disabled || false)
})
const minValue = computed(() => {
return Math.min(initData.firstValue, initData.secondValue)
})
const maxValue = computed(() => {
return Math.max(initData.firstValue, initData.secondValue)
})
const barSize = computed(() => {
return props.range
? `${ 100 * (maxValue.value - minValue.value) / (props.max - props.min) }%`
: `${ 100 * (initData.firstValue - props.min) / (props.max - props.min) }%`
})
const barStart = computed(() => {
return props.range
? `${ 100 * (minValue.value - props.min) / (props.max - props.min) }%`
: '0%'
})
const runwayStyle = computed(() => {
return (props.vertical ? { height: props.height } : {}) as CSSStyleDeclaration
})
const barStyle = computed(() => {
return (props.vertical
? {
height: barSize.value,
bottom: barStart.value,
} : {
width: barSize.value,
left: barStart.value,
}) as CSSStyleDeclaration
})
const resetSize = () => {
if (slider.value) {
initData.sliderSize = slider.value[`client${ props.vertical ? 'Height' : 'Width' }`]
}
}
const setPosition = (percent: number) => {
const targetValue = props.min + percent * (props.max - props.min) / 100
if (!props.range) {
firstButton.value.setPosition(percent)
return
}
let buttonRefName: string
if (Math.abs(minValue.value - targetValue) < Math.abs(maxValue.value - targetValue)) {
buttonRefName = initData.firstValue < initData.secondValue ? 'firstButton' : 'secondButton'
} else {
buttonRefName = initData.firstValue > initData.secondValue ? 'firstButton' : 'secondButton'
}
buttonRefs[buttonRefName].value.setPosition(percent)
}
const emitChange = async () => {
await nextTick()
emit('change', props.range ? [minValue.value, maxValue.value] : props.modelValue)
}
const onSliderClick = (event: MouseEvent) => {
if (sliderDisabled.value || initData.dragging) return
resetSize()
if (props.vertical) {
const sliderOffsetBottom = slider.value.getBoundingClientRect().bottom
setPosition((sliderOffsetBottom - event.clientY) / initData.sliderSize * 100)
} else {
const sliderOffsetLeft = slider.value.getBoundingClientRect().left
setPosition((event.clientX - sliderOffsetLeft) / initData.sliderSize * 100)
}
emitChange()
}
return {
slider,
firstButton,
secondButton,
sliderDisabled,
minValue,
maxValue,
runwayStyle,
barStyle,
resetSize,
setPosition,
emitChange,
onSliderClick,
}
}

View File

@@ -0,0 +1,221 @@
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { off, on } from '@element-plus/utils/dom'
import { computed, ComputedRef, inject, nextTick, ref, watch } from 'vue'
import { ISliderButtonInitData, ISliderButtonProps, ISliderProvider } from './Slider'
const useTooltip = (props: ISliderButtonProps, formatTooltip: ComputedRef<(value: number) => number | string>, showTooltip: ComputedRef<boolean>) => {
const tooltip = ref(null)
const tooltipVisible = ref(false)
const enableFormat = computed(() => {
return formatTooltip.value instanceof Function
})
const formatValue = computed(() => {
return enableFormat.value && formatTooltip.value(props.modelValue) || props.modelValue
})
const displayTooltip = () => {
showTooltip.value && (tooltipVisible.value = true)
}
const hideTooltip = () => {
showTooltip.value && (tooltipVisible.value = false)
}
return {
tooltip,
tooltipVisible,
formatValue,
displayTooltip,
hideTooltip,
}
}
export const useSliderButton = (props: ISliderButtonProps, initData: ISliderButtonInitData, emit) => {
const {
disabled,
min,
max,
step,
showTooltip,
precision,
sliderSize,
formatTooltip,
emitChange,
resetSize,
updateDragging,
} = inject<ISliderProvider>('SliderProvider')
const {
tooltip,
tooltipVisible,
formatValue,
displayTooltip,
hideTooltip,
} = useTooltip(props, formatTooltip, showTooltip)
const currentPosition = computed(() => {
return `${ (props.modelValue - min.value) / (max.value - min.value) * 100 }%`
})
const wrapperStyle = computed(() => {
return (props.vertical ? { bottom: currentPosition.value } : { left: currentPosition.value }) as CSSStyleDeclaration
})
const handleMouseEnter = () => {
initData.hovering = true
displayTooltip()
}
const handleMouseLeave = () => {
initData.hovering = false
hideTooltip()
}
const onButtonDown = (event: MouseEvent | TouchEvent) => {
if (disabled.value) return
event.preventDefault()
onDragStart(event)
on(window, 'mousemove', onDragging)
on(window, 'touchmove', onDragging)
on(window, 'mouseup', onDragEnd)
on(window, 'touchend', onDragEnd)
on(window, 'contextmenu', onDragEnd)
}
const onLeftKeyDown = () => {
if (disabled.value) return
initData.newPosition = parseFloat(currentPosition.value) - step.value / (max.value - min.value) * 100
setPosition(initData.newPosition)
emitChange()
}
const onRightKeyDown = () => {
if (disabled.value) return
initData.newPosition = parseFloat(currentPosition.value) + step.value / (max.value - min.value) * 100
setPosition(initData.newPosition)
emitChange()
}
const getClientXY = (event: MouseEvent | TouchEvent) => {
let clientX: number
let clientY: number
if (event.type === 'touchstart') {
clientY = (event as TouchEvent).touches[0].clientY
clientX = (event as TouchEvent).touches[0].clientX
} else {
clientY = (event as MouseEvent).clientY
clientX = (event as MouseEvent).clientX
}
return {
clientX,
clientY,
}
}
const onDragStart = (event: MouseEvent | TouchEvent) => {
initData.dragging = true
initData.isClick = true
const {
clientX,
clientY,
} = getClientXY(event)
if (props.vertical) {
initData.startY = clientY
} else {
initData.startX = clientX
}
initData.startPosition = parseFloat(currentPosition.value)
initData.newPosition = initData.startPosition
}
const onDragging = (event: MouseEvent | TouchEvent) => {
if (initData.dragging) {
initData.isClick = false
displayTooltip()
resetSize()
let diff: number
const {
clientX,
clientY,
} = getClientXY(event)
if (props.vertical) {
initData.currentY = clientY
diff = (initData.startY - initData.currentY) / sliderSize.value * 100
} else {
initData.currentX = clientX
diff = (initData.currentX - initData.startX) / sliderSize.value * 100
}
initData.newPosition = initData.startPosition + diff
setPosition(initData.newPosition)
}
}
const onDragEnd = () => {
if (initData.dragging) {
/*
* 防止在 mouseup 后立即触发 click导致滑块有几率产生一小段位移
* 不使用 preventDefault 是因为 mouseup 和 click 没有注册在同一个 DOM 上
*/
setTimeout(() => {
initData.dragging = false
hideTooltip()
if (!initData.isClick) {
setPosition(initData.newPosition)
emitChange()
}
}, 0)
off(window, 'mousemove', onDragging)
off(window, 'touchmove', onDragging)
off(window, 'mouseup', onDragEnd)
off(window, 'touchend', onDragEnd)
off(window, 'contextmenu', onDragEnd)
}
}
const setPosition = async (newPosition: number) => {
if (newPosition === null || isNaN(newPosition)) return
if (newPosition < 0) {
newPosition = 0
} else if (newPosition > 100) {
newPosition = 100
}
const lengthPerStep = 100 / ((max.value - min.value) / step.value)
const steps = Math.round(newPosition / lengthPerStep)
let value = steps * lengthPerStep * (max.value - min.value) * 0.01 + min.value
value = parseFloat(value.toFixed(precision.value))
emit(UPDATE_MODEL_EVENT, value)
if (!initData.dragging && props.modelValue !== initData.oldValue) {
initData.oldValue = props.modelValue
}
await nextTick()
initData.dragging && displayTooltip()
tooltip.value && tooltip.value.popperInstance.update()
}
watch(() => initData.dragging, val => {
updateDragging(val)
})
return {
tooltip,
tooltipVisible,
showTooltip,
wrapperStyle,
formatValue,
handleMouseEnter,
handleMouseLeave,
onButtonDown,
onLeftKeyDown,
onRightKeyDown,
setPosition,
}
}

View File

@@ -0,0 +1,34 @@
import { computed, ComputedRef } from 'vue'
import { ISliderInitData, ISliderProps, Stops } from './Slider'
export const useStops = (props: ISliderProps, initData: ISliderInitData, minValue: ComputedRef<number>, maxValue: ComputedRef<number>): Stops => {
const stops = computed(() => {
if (!props.showStops || props.min > props.max) return []
if (props.step === 0) {
process.env.NODE_ENV !== 'production' && console.warn('[Element Warn][Slider]step should not be 0.')
return []
}
const stopCount = (props.max - props.min) / props.step
const stepWidth = 100 * props.step / (props.max - props.min)
const result = Array.from<number>({ length: stopCount - 1 }).map((_, index) => ((index + 1) * stepWidth))
if (props.range) {
return result.filter(step => {
return step < 100 * (minValue.value - props.min) / (props.max - props.min)
|| step > 100 * (maxValue.value - props.min) / (props.max - props.min)
})
} else {
return result.filter(step => step > 100 * (initData.firstValue - props.min) / (props.max - props.min))
}
})
const getStopStyle = (position: number) => {
return (props.vertical ? { 'bottom': position + '%' } : { 'left': position + '%' }) as CSSStyleDeclaration
}
return {
stops,
getStopStyle,
}
}