refactor(components): [color-picker] alpha-slider (#10332)

* refactor(components): [color-picker] alpha-slider

* Extract logic code out of color-picker/alpha-slider

* fix: exposing methods

* chore: expose refs

---------

Co-authored-by: JeremyWuuuuu <15975785+JeremyWuuuuu@users.noreply.github.com>
This commit is contained in:
Jeremy
2023-03-01 14:32:26 +08:00
committed by GitHub
parent c88c6cdb8f
commit 105d41799e
3 changed files with 219 additions and 178 deletions

View File

@@ -1,191 +1,49 @@
<template>
<div :class="[ns.b(), ns.is('vertical', vertical)]">
<div
ref="bar"
:class="ns.e('bar')"
:style="{
background,
}"
@click="handleClick"
/>
<div
ref="thumb"
:class="ns.e('thumb')"
:style="{
left: thumbLeft + 'px',
top: thumbTop + 'px',
}"
/>
<div :class="rootKls">
<div ref="bar" :class="barKls" :style="barStyle" @click="handleClick" />
<div ref="thumb" :class="thumbKls" :style="thumbStyle" />
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { alphaSliderProps } from '../props/alpha-slider'
import {
defineComponent,
getCurrentInstance,
onMounted,
ref,
shallowRef,
watch,
} from 'vue'
import { getClientXY } from '@element-plus/utils'
import { useNamespace } from '@element-plus/hooks'
import { draggable } from '../utils/draggable'
useAlphaSlider,
useAlphaSliderDOM,
} from '../composables/use-alpha-slider'
import type { PropType } from 'vue'
import type Color from '../utils/color'
const COMPONENT_NAME = 'ElColorAlphaSlider'
export default defineComponent({
name: 'ElColorAlphaSlider',
props: {
color: {
type: Object as PropType<Color>,
required: true,
},
vertical: {
type: Boolean,
default: false,
},
},
setup(props) {
const ns = useNamespace('color-alpha-slider')
defineOptions({
name: COMPONENT_NAME,
})
const instance = getCurrentInstance()!
// ref
const thumb = shallowRef<HTMLElement>()
const bar = shallowRef<HTMLElement>()
const props = defineProps(alphaSliderProps)
// data
const thumbLeft = ref(0)
const thumbTop = ref(0)
const background = ref<string>()
const { bar, thumb, handleDrag, handleClick } = useAlphaSlider(props)
watch(
() => props.color.get('alpha'),
() => {
update()
}
)
watch(
() => props.color.value,
() => {
update()
}
)
const { rootKls, barKls, barStyle, thumbKls, thumbStyle, update } =
useAlphaSliderDOM(props, {
bar,
thumb,
handleDrag,
})
//methods
function getThumbLeft() {
if (!thumb.value) return 0
if (props.vertical) return 0
const el = instance.vnode.el
const alpha = props.color.get('alpha')
if (!el) return 0
return Math.round(
(alpha * (el.offsetWidth - thumb.value.offsetWidth / 2)) / 100
)
}
function getThumbTop() {
if (!thumb.value) return 0
const el = instance.vnode.el
if (!props.vertical) return 0
const alpha = props.color.get('alpha')
if (!el) return 0
return Math.round(
(alpha * (el.offsetHeight - thumb.value.offsetHeight / 2)) / 100
)
}
function getBackground() {
if (props.color && props.color.value) {
const { r, g, b } = props.color.toRgb()
return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`
}
return ''
}
function handleClick(event: MouseEvent | TouchEvent) {
const target = event.target
if (target !== thumb.value) {
handleDrag(event)
}
}
function handleDrag(event: MouseEvent | TouchEvent) {
if (!bar.value || !thumb.value) return
const el = instance.vnode.el as HTMLElement
const rect = el.getBoundingClientRect()
const { clientX, clientY } = getClientXY(event)
if (!props.vertical) {
let left = clientX - rect.left
left = Math.max(thumb.value.offsetWidth / 2, left)
left = Math.min(left, rect.width - thumb.value.offsetWidth / 2)
props.color.set(
'alpha',
Math.round(
((left - thumb.value.offsetWidth / 2) /
(rect.width - thumb.value.offsetWidth)) *
100
)
)
} else {
let top = clientY - rect.top
top = Math.max(thumb.value.offsetHeight / 2, top)
top = Math.min(top, rect.height - thumb.value.offsetHeight / 2)
props.color.set(
'alpha',
Math.round(
((top - thumb.value.offsetHeight / 2) /
(rect.height - thumb.value.offsetHeight)) *
100
)
)
}
}
function update() {
thumbLeft.value = getThumbLeft()
thumbTop.value = getThumbTop()
background.value = getBackground()
}
// mounded
onMounted(() => {
if (!bar.value || !thumb.value) return
const dragConfig = {
drag: (event: MouseEvent | TouchEvent) => {
handleDrag(event)
},
end: (event: MouseEvent | TouchEvent) => {
handleDrag(event)
},
}
draggable(bar.value, dragConfig)
draggable(thumb.value, dragConfig)
update()
})
return {
thumb,
bar,
thumbLeft,
thumbTop,
background,
handleClick,
update,
ns,
}
},
defineExpose({
/**
* @description update alpha slider manually
* @type {Function}
*/
update,
/**
* @description bar element ref
* @type {HTMLElement}
*/
bar,
/**
* @description thumb element ref
* @type {HTMLElement}
*/
thumb,
})
</script>

View File

@@ -0,0 +1,166 @@
import {
computed,
getCurrentInstance,
onMounted,
ref,
shallowRef,
watch,
} from 'vue'
import { addUnit, getClientXY } from '@element-plus/utils'
import { useNamespace } from '@element-plus/hooks'
import { draggable } from '../utils/draggable'
import type { AlphaSliderProps } from '../props/alpha-slider'
export const useAlphaSlider = (props: AlphaSliderProps) => {
const instance = getCurrentInstance()!
const thumb = shallowRef<HTMLElement>()
const bar = shallowRef<HTMLElement>()
function handleClick(event: MouseEvent | TouchEvent) {
const target = event.target
if (target !== thumb.value) {
handleDrag(event)
}
}
function handleDrag(event: MouseEvent | TouchEvent) {
if (!bar.value || !thumb.value) return
const el = instance.vnode.el as HTMLElement
const rect = el.getBoundingClientRect()
const { clientX, clientY } = getClientXY(event)
if (!props.vertical) {
let left = clientX - rect.left
left = Math.max(thumb.value.offsetWidth / 2, left)
left = Math.min(left, rect.width - thumb.value.offsetWidth / 2)
props.color.set(
'alpha',
Math.round(
((left - thumb.value.offsetWidth / 2) /
(rect.width - thumb.value.offsetWidth)) *
100
)
)
} else {
let top = clientY - rect.top
top = Math.max(thumb.value.offsetHeight / 2, top)
top = Math.min(top, rect.height - thumb.value.offsetHeight / 2)
props.color.set(
'alpha',
Math.round(
((top - thumb.value.offsetHeight / 2) /
(rect.height - thumb.value.offsetHeight)) *
100
)
)
}
}
return {
thumb,
bar,
handleDrag,
handleClick,
}
}
export const useAlphaSliderDOM = (
props: AlphaSliderProps,
{
bar,
thumb,
handleDrag,
}: Pick<ReturnType<typeof useAlphaSlider>, 'bar' | 'thumb' | 'handleDrag'>
) => {
const instance = getCurrentInstance()!
const ns = useNamespace('color-alpha-slider')
// refs
const thumbLeft = ref(0)
const thumbTop = ref(0)
const background = ref<string>()
function getThumbLeft() {
if (!thumb.value) return 0
if (props.vertical) return 0
const el = instance.vnode.el
const alpha = props.color.get('alpha')
if (!el) return 0
return Math.round(
(alpha * (el.offsetWidth - thumb.value.offsetWidth / 2)) / 100
)
}
function getThumbTop() {
if (!thumb.value) return 0
const el = instance.vnode.el
if (!props.vertical) return 0
const alpha = props.color.get('alpha')
if (!el) return 0
return Math.round(
(alpha * (el.offsetHeight - thumb.value.offsetHeight / 2)) / 100
)
}
function getBackground() {
if (props.color && props.color.value) {
const { r, g, b } = props.color.toRgb()
return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`
}
return ''
}
function update() {
thumbLeft.value = getThumbLeft()
thumbTop.value = getThumbTop()
background.value = getBackground()
}
onMounted(() => {
if (!bar.value || !thumb.value) return
const dragConfig = {
drag: (event: MouseEvent | TouchEvent) => {
handleDrag(event)
},
end: (event: MouseEvent | TouchEvent) => {
handleDrag(event)
},
}
draggable(bar.value, dragConfig)
draggable(thumb.value, dragConfig)
update()
})
watch(
() => props.color.get('alpha'),
() => update()
)
watch(
() => props.color.value,
() => update()
)
const rootKls = computed(() => [ns.b(), ns.is('vertical', props.vertical)])
const barKls = computed(() => ns.e('bar'))
const thumbKls = computed(() => ns.e('thumb'))
const barStyle = computed(() => ({ background: background.value }))
const thumbStyle = computed(() => ({
left: addUnit(thumbLeft.value),
top: addUnit(thumbTop.value),
}))
return { rootKls, barKls, barStyle, thumbKls, thumbStyle, update }
}

View File

@@ -0,0 +1,17 @@
import { buildProps, definePropType } from '@element-plus/utils'
import type { ExtractPropTypes } from 'vue'
import type Color from '../utils/color'
export const alphaSliderProps = buildProps({
color: {
type: definePropType<Color>(Object),
required: true,
},
vertical: {
type: Boolean,
default: false,
},
} as const)
export type AlphaSliderProps = ExtractPropTypes<typeof alphaSliderProps>