Skip to content

Commit

Permalink
feat(slider): support vertical mode
Browse files Browse the repository at this point in the history
  • Loading branch information
dyggod authored Apr 26, 2023
1 parent 78fa0b1 commit 43acba6
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -577,18 +577,18 @@ exports[`test form with select 4`] = `
exports[`test form with slider 1`] = `
"<div class="var-form">
<div class="var-slider">
<div class="var-slider__block var-slider--disabled">
<div class="var-slider__track">
<div class="var-slider__track-background"></div>
<div class="var-slider__track-fill" style="width: 5%; left: 0%;"></div>
</div>
<div class="var-slider__thumb" style="left: 5%;">
<div class="var-slider__thumb-block"></div>
<div class="var-slider__thumb-ripple">
<div class="var-slider__horizontal">
<div class="var-slider__horizontal__block var-slider--disabled">
<div class="var-slider__horizontal__track">
<div class="var-slider__horizontal__track-background" style="width: 100%;"></div>
<div class="var-slider__horizontal__track-fill" style="top: 0px; width: 5%; left: 0%;"></div>
</div>
<div class="var-slider__horizontal__thumb" style="left: 5%;">
<div class="var-slider__horizontal__thumb-block"></div>
<div class="var-slider__horizontal__thumb-ripple">
<div class="var-hover-overlay"></div>
</div>
<div class="var-slider__thumb-label"><span>5</span></div>
<div class="var-slider__horizontal__thumb-label"><span>5</span></div>
</div>
</div>
<transition-stub name="var-form-details" appear="false" persisted="false" css="true" class="var-slider__form" var-slider-cover="">
Expand All @@ -600,18 +600,18 @@ exports[`test form with slider 1`] = `
exports[`test form with slider 2`] = `
"<div class="var-form">
<div class="var-slider">
<div class="var-slider__block var-slider--error">
<div class="var-slider__track">
<div class="var-slider__track-background"></div>
<div class="var-slider__track-fill" style="width: 5%; left: 0%;"></div>
<div class="var-slider__horizontal">
<div class="var-slider__horizontal__block var-slider--error">
<div class="var-slider__horizontal__track">
<div class="var-slider__horizontal__track-background" style="width: 100%;"></div>
<div class="var-slider__horizontal__track-fill" style="top: 0px; width: 5%; left: 0%;"></div>
</div>
<div class="var-slider__thumb" style="left: 5%;">
<div class="var-slider__thumb-block"></div>
<div class="var-slider__thumb-ripple">
<div class="var-slider__horizontal__thumb" style="left: 5%;">
<div class="var-slider__horizontal__thumb-block"></div>
<div class="var-slider__horizontal__thumb-ripple">
<div class="var-hover-overlay"></div>
</div>
<div class="var-slider__thumb-label"><span>5</span></div>
<div class="var-slider__horizontal__thumb-label"><span>5</span></div>
</div>
</div>
<transition-stub name="var-form-details" appear="false" persisted="false" css="true" class="var-slider__form" var-slider-cover="">
Expand All @@ -634,18 +634,18 @@ exports[`test form with slider 2`] = `
exports[`test form with slider 3`] = `
"<div class="var-form">
<div class="var-slider">
<div class="var-slider__block">
<div class="var-slider__track">
<div class="var-slider__track-background"></div>
<div class="var-slider__track-fill" style="width: 0%; left: 0%;"></div>
<div class="var-slider__horizontal">
<div class="var-slider__horizontal__block">
<div class="var-slider__horizontal__track">
<div class="var-slider__horizontal__track-background" style="width: 100%;"></div>
<div class="var-slider__horizontal__track-fill" style="top: 0px; width: 0%; left: 0%;"></div>
</div>
<div class="var-slider__thumb" style="left: 0%;">
<div class="var-slider__thumb-block"></div>
<div class="var-slider__thumb-ripple">
<div class="var-slider__horizontal__thumb" style="left: 0%;">
<div class="var-slider__horizontal__thumb-block"></div>
<div class="var-slider__horizontal__thumb-ripple">
<div class="var-hover-overlay"></div>
</div>
<div class="var-slider__thumb-label"><span>0</span></div>
<div class="var-slider__horizontal__thumb-label"><span>0</span></div>
</div>
</div>
<transition-stub name="var-form-details" appear="false" persisted="false" css="true" class="var-slider__form" var-slider-cover="">
Expand Down
2 changes: 1 addition & 1 deletion packages/varlet-ui/src/form/__tests__/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ test('test form with slider', async () => {

expect(wrapper.html()).toMatchSnapshot()

const el = wrapper.find('.var-slider__thumb-label')
const el = wrapper.find('.var-slider__horizontal__thumb-label')
await trigger(el, 'touchstart', 0, 0)
await trigger(el, 'touchmove', 20, 0)
await trigger(el, 'touchend', 40, 0)
Expand Down
124 changes: 86 additions & 38 deletions packages/varlet-ui/src/slider/Slider.vue
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
<template>
<div :class="n()">
<div :class="n(direction)">
<div
:class="classes(n('block'), [isDisabled, n('--disabled')], [errorMessage, n('--error')])"
:class="classes(n(`${direction}__block`), [isDisabled, n('--disabled')], [errorMessage, n('--error')])"
ref="sliderEl"
@click="click"
>
<div :class="n('track')">
<div :class="n(`${direction}__track`)">
<div
:class="n('track-background')"
:class="n(`${direction}__track-background`)"
:style="{
background: trackColor,
height: multiplySizeUnit(trackHeight),
height: vertical ? '100%' : multiplySizeUnit(trackHeight),
width: vertical ? multiplySizeUnit(trackHeight) : '100%',
}"
></div>
<div :class="n('track-fill')" :style="getFillStyle"></div>
<div :class="n(`${direction}__track-fill`)" :style="getFillStyle"></div>
</div>
<div
v-for="item in thumbList"
:class="n('thumb')"
:class="n(`${direction}__thumb`)"
:key="item.enumValue"
:style="{
left: `${item.value}%`,
zIndex: thumbsProps[item.enumValue].active ? 1 : undefined,
}"
:style="syncThumbStyle(item)"
@touchstart.stop="start($event, item.enumValue)"
@touchmove.stop="move($event, item.enumValue)"
@touchend="end(item.enumValue)"
@touchcancel="end(item.enumValue)"
>
<slot name="button" :current-value="item.text">
<div
:class="n('thumb-block')"
:class="n(`${direction}__thumb-block`)"
:style="{
background: thumbColor,
}"
v-hover:desktop="(value) => hover(value, item)"
v-hover:desktop="(value: boolean) => hover(value, item)"
></div>
<div
:class="classes(n('thumb-ripple'), [thumbsProps[item.enumValue].active, n('thumb-ripple--active')])"
:class="
classes(n(`${direction}__thumb-ripple`), [
thumbsProps[item.enumValue].active,
n(`${direction}__thumb-ripple--active`),
])
"
:style="{
background: thumbsProps[item.enumValue].active ? thumbColor : undefined,
}"
>
<var-hover-overlay :hovering="item.hovering" />
</div>
<div
:class="classes(n('thumb-label'), [showLabel(item.enumValue), n('thumb-label--active')])"
:class="
classes(n(`${direction}__thumb-label`), [
showLabel(item.enumValue),
n(`${direction}__thumb-label--active`),
])
"
:style="{
background: labelColor,
color: labelTextColor,
Expand Down Expand Up @@ -80,7 +88,7 @@ import { useValidation, createNamespace, call } from '../utils/components'
import { useForm } from '../form/provide'
import VarHoverOverlay, { useHoverOverlay } from '../hover-overlay'
import Hover from '../hover'
import { getLeft, multiplySizeUnit } from '../utils/elements'
import { getLeft, getClientTop, multiplySizeUnit } from '../utils/elements'
import { warn } from '../utils/logger'
import { isArray, isNumber, toNumber } from '@varlet/shared'
import { props, Thumbs, type ThumbProps, type ThumbsProps, type ThumbsListProps } from './props'
Expand All @@ -107,15 +115,15 @@ export default defineComponent({
const getThumbProps = (): ThumbProps => ({
startPosition: 0,
currentLeft: 0,
currentOffset: 0,
active: false,
percentValue: 0,
})
const validateWithTrigger = () => nextTick(() => vt(['onChange'], 'onChange', props.rules, props.modelValue))
const sliderEl: Ref<HTMLDivElement | null> = ref(null)
const maxWidth = ref(0)
const maxDistance = ref(0)
const isScroll: Ref<boolean> = ref(false)
const thumbsProps: UnwrapRef<ThumbsProps> = reactive({
Expand All @@ -124,7 +132,7 @@ export default defineComponent({
})
const scope: ComputedRef<number> = computed(() => toNumber(props.max) - toNumber(props.min))
const unitWidth: ComputedRef<number> = computed(() => (maxWidth.value / scope.value) * toNumber(props.step))
const unitWidth: ComputedRef<number> = computed(() => (maxDistance.value / scope.value) * toNumber(props.step))
const thumbList: ComputedRef<Array<ThumbsListProps>> = computed(() => {
const { modelValue, range } = props
let list: ThumbsListProps[] = []
Expand Down Expand Up @@ -164,24 +172,59 @@ export default defineComponent({
const getFillStyle: ComputedRef<Record<string, string | undefined>> = computed(() => {
const { activeColor, range, modelValue } = props
const left = range && isArray(modelValue) ? getValue(Math.min(modelValue[0], modelValue[1])) : 0
const gap = range && isArray(modelValue) ? getValue(Math.min(modelValue[0], modelValue[1])) : 0
const width =
const fillLength =
range && isArray(modelValue)
? getValue(Math.max(modelValue[0], modelValue[1])) - left
? getValue(Math.max(modelValue[0], modelValue[1])) - gap
: getValue(modelValue as number)
return {
width: `${width}%`,
left: `${left}%`,
background: activeColor,
}
return vertical.value
? {
left: '0px',
height: `${fillLength}%`,
bottom: `${gap}%`,
background: activeColor,
}
: {
top: '0px',
width: `${fillLength}%`,
left: `${gap}%`,
background: activeColor,
}
})
const isDisabled: ComputedRef<boolean | undefined> = computed(() => props.disabled || form?.disabled.value)
const isReadonly: ComputedRef<boolean | undefined> = computed(() => props.readonly || form?.readonly.value)
const vertical: ComputedRef<boolean> = computed(() => props.direction === 'vertical')
const getOffset = (e: MouseEvent) => {
const { direction } = props
const currentTarget = e.currentTarget as HTMLElement
if (!currentTarget) return 0
if (direction === 'horizontal') {
return e.clientX - getLeft(currentTarget)
}
// 用元素实际的高度减去鼠标与元素顶部的距离差
return maxDistance.value - (e.clientY - getClientTop(currentTarget as HTMLElement))
}
const syncThumbStyle = (thumb: ThumbsListProps) => {
return vertical.value
? {
bottom: `${thumb.value}%`,
zIndex: thumbsProps[thumb.enumValue].active ? 1 : undefined,
}
: {
left: `${thumb.value}%`,
zIndex: thumbsProps[thumb.enumValue].active ? 1 : undefined,
}
}
const showLabel = (type: keyof ThumbsProps): boolean => {
if (props.labelVisible === 'always') return true
if (props.labelVisible === 'never') return false
Expand Down Expand Up @@ -247,22 +290,25 @@ export default defineComponent({
}
const start = (event: TouchEvent, type: keyof ThumbsProps) => {
if (!maxWidth.value) maxWidth.value = (sliderEl.value as HTMLDivElement).offsetWidth
if (!maxDistance.value) maxDistance.value = (sliderEl.value as HTMLDivElement).offsetWidth
if (!isDisabled.value) {
thumbsProps[type].active = true
}
if (isDisabled.value || isReadonly.value) return
call(props.onStart)
isScroll.value = true
thumbsProps[type].startPosition = event.touches[0].clientX
thumbsProps[type].startPosition = event.touches[0][vertical.value ? 'clientY' : 'clientX']
}
const move = (event: TouchEvent, type: keyof ThumbsProps) => {
if (isDisabled.value || isReadonly.value || !isScroll.value) return
let moveDistance = event.touches[0].clientX - thumbsProps[type].startPosition + thumbsProps[type].currentLeft
let moveDistance =
(vertical.value
? thumbsProps[type].startPosition - event.touches[0].clientY
: event.touches[0].clientX - thumbsProps[type].startPosition) + thumbsProps[type].currentOffset
if (moveDistance <= 0) moveDistance = 0
else if (moveDistance >= maxWidth.value) moveDistance = maxWidth.value
else if (moveDistance >= maxDistance.value) moveDistance = maxDistance.value
setPercent(moveDistance, type)
}
Expand All @@ -275,7 +321,7 @@ export default defineComponent({
if (isDisabled.value || isReadonly.value) return
let rangeValue: Array<number> = []
thumbsProps[type].currentLeft = thumbsProps[type].percentValue * unitWidth.value
thumbsProps[type].currentOffset = thumbsProps[type].percentValue * unitWidth.value
const curValue = thumbsProps[type].percentValue * toNumber(step) + toNumber(min)
Expand All @@ -290,7 +336,7 @@ export default defineComponent({
const click = (event: MouseEvent) => {
if (isDisabled.value || isReadonly.value) return
if ((event.target as HTMLDivElement).closest(`.${n('thumb')}`)) return
const offset = event.clientX - getLeft(event.currentTarget as HTMLDivElement)
const offset = getOffset(event)
const type = getType(offset)
setPercent(offset, type)
end(type)
Expand Down Expand Up @@ -338,12 +384,12 @@ export default defineComponent({
if (props.range && isArray(modelValue)) {
thumbsProps[Thumbs.First].percentValue = getPercent(modelValue[0])
thumbsProps[Thumbs.First].currentLeft = thumbsProps[Thumbs.First].percentValue * unitWidth.value
thumbsProps[Thumbs.First].currentOffset = thumbsProps[Thumbs.First].percentValue * unitWidth.value
thumbsProps[Thumbs.Second].percentValue = getPercent(modelValue[1])
thumbsProps[Thumbs.Second].currentLeft = thumbsProps[Thumbs.Second].percentValue * unitWidth.value
thumbsProps[Thumbs.Second].currentOffset = thumbsProps[Thumbs.Second].percentValue * unitWidth.value
} else if (isNumber(modelValue)) {
thumbsProps[Thumbs.First].currentLeft = getPercent(modelValue) * unitWidth.value
thumbsProps[Thumbs.First].currentOffset = getPercent(modelValue) * unitWidth.value
}
}
Expand All @@ -366,12 +412,12 @@ export default defineComponent({
setProps(modelValue as number | number[], toNumber(step))
})
watch(maxWidth, () => setProps())
watch(maxDistance, () => setProps())
useMounted(() => {
if (!stepValidator() || !valueValidator()) return
maxWidth.value = (sliderEl.value as HTMLDivElement).offsetWidth
maxDistance.value = (sliderEl.value as HTMLDivElement)[vertical.value ? 'offsetHeight' : 'offsetWidth']
})
return {
Expand All @@ -381,6 +427,8 @@ export default defineComponent({
sliderEl,
getFillStyle,
isDisabled,
vertical,
syncThumbStyle,
errorMessage,
thumbsProps,
thumbList,
Expand Down
Loading

0 comments on commit 43acba6

Please sign in to comment.