element-plus
Version:
A Component Library for Vue 3
243 lines (216 loc) • 6.28 kB
text/typescript
import { computed, inject, nextTick, ref, watch } from 'vue'
import debounce from 'lodash/debounce'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import { off, on } from '@element-plus/utils/dom'
import type { ComputedRef, CSSProperties } from 'vue'
import type {
ISliderButtonInitData,
ISliderButtonProps,
ISliderProvider,
} from './slider.type'
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 = debounce(() => {
showTooltip.value && (tooltipVisible.value = true)
}, 50)
const hideTooltip = debounce(() => {
showTooltip.value && (tooltipVisible.value = false)
}, 50)
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 CSSProperties
})
const handleMouseEnter = () => {
initData.hovering = true
displayTooltip()
}
const handleMouseLeave = () => {
initData.hovering = false
if (!initData.dragging) {
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.startsWith('touch')) {
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
if (!initData.hovering) {
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.updatePopper()
}
watch(
() => initData.dragging,
(val) => {
updateDragging(val)
}
)
return {
tooltip,
tooltipVisible,
showTooltip,
wrapperStyle,
formatValue,
handleMouseEnter,
handleMouseLeave,
onButtonDown,
onLeftKeyDown,
onRightKeyDown,
setPosition,
}
}