Skip to content

Commit

Permalink
refactor: color picker
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskang committed Mar 8, 2021
1 parent 5494ed2 commit e0750f1
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 127 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@
},
"devDependencies": {
"@types/jest": "^26.0.20",
"@types/node": "^14.14.31",
"@types/node": "^14.14.32",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"@vitejs/plugin-vue": "^1.1.5",
"@vue/compiler-sfc": "^3.0.7",
"@vue/test-utils": "^2.0.0-rc.2",
"@vue/test-utils": "^2.0.0-rc.3",
"@vuedx/typescript-plugin-vue": "^0.6.3",
"chalk": "^4.1.0",
"conventional-changelog-cli": "^2.1.1",
Expand All @@ -61,7 +61,7 @@
"release-it": "^14.4.1",
"rimraf": "^3.0.2",
"tsrv": "^0.11.1",
"typescript": "^4.2.2",
"typescript": "^4.2.3",
"vue": "^3.0.7"
},
"engines": {
Expand Down
3 changes: 2 additions & 1 deletion packages/elenext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
"prepublishOnly": "yarn run build"
},
"dependencies": {
"@ctrl/tinycolor": "^3.4.0",
"@elenext/icons": "^0.13.0",
"@elenext/shared": "^0.13.0",
"@popperjs/core": "^2.9.0",
"vptypes": "^0.1.3"
"vptypes": "^0.1.5"
},
"peerDependencies": {
"vue": "^3.0.5"
Expand Down
25 changes: 16 additions & 9 deletions packages/elenext/src/components/ColorPicker/EColorPicker.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<div class="el-color-picker">
<div class="el-color-picker__main">
<color-panel :color="color" />
<hue-slider :color="color" />
<color-panel :color="hsvColor" @change="onChange" />
<hue-slider :color="hsvColor" @change="onChange" />
</div>
<alpha-slider :color="color" />
<alpha-slider :color="hsvColor" @change="onChange" />
</div>
</template>
<script lang="ts">
Expand All @@ -13,7 +13,9 @@ import vptypes from 'vptypes'
import ColorPanel from './components/ColorPanel.vue'
import HueSlider from './components/HueSlider.vue'
import AlphaSlider from './components/AlphaSlider.vue'
import { parseColor } from '../../utils/Color'
// import { parseColor } from '../../utils/Color'
import { TinyColor } from '@ctrl/tinycolor'
import { HSVAColor } from './core'

const EColorPicker = defineComponent({
name: 'EColorPicker',
Expand All @@ -23,17 +25,22 @@ const EColorPicker = defineComponent({
AlphaSlider,
},
props: {
modelValue: vptypes.hexColor(),
modelValue: vptypes.string(),
},
emits: ['update:modelValue'],
setup(props, { attrs, slots, emit }) {
const color = computed(() => {
const l = parseColor(props.modelValue)
console.log(l)
const hsvColor = computed<HSVAColor>(() => {
const l = new TinyColor(props.modelValue).toHsv()
// console.log(l)

return l
})
const onChange = (color: HSVAColor) => {
emit('update:modelValue', new TinyColor(color).toHexString())
}
return {
color,
hsvColor,
onChange,
}
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ wrapperClass: md-colorpicker
```vue demo
<template>
<EColorPicker v-model="color" />
{{ color }}
</template>
<script>
export default {
data() {
return {
color: '#ff0000',
color: '#5d0404',
}
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,42 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { computed, defineComponent, watchEffect } from 'vue'
import vptypes from 'vptypes'
import useDraggable from '../../../hooks/useDraggable'
import { Hsva } from '../../../utils/Color'
import useDraggable from '@/hooks/useDraggable'
import { HSVAColor } from '../core'

const ColorPanel = defineComponent({
name: 'ColorPanel',
components: {},
props: {
color: vptypes.object<Hsva>().isRequired,
color: vptypes.object<HSVAColor>().isRequired,
},
setup(props) {
const [targetRef, handleRef, { delta }] = useDraggable({
emits: ['change'],
setup(props, { emit }) {
const [targetRef, handleRef, { delta, limits }] = useDraggable({
viewport: true,
onInit({ width, height }) {
return {
x: props.color.s * width,
y: (1 - props.color.v) * height,
}
},
})
const background = computed(() => {
return 'hsl(' + props.color.h + ', 100%, 50%)'
})
watchEffect(() => {
if (delta.value && limits.value) {
const color: HSVAColor = {
h: props.color.h,
s: delta.value.x / limits.value.maxX,
v: 1 - delta.value.y / limits.value.maxY,
a: props.color.a,
}
emit('change', color)
}
})
return {
targetRef,
handleRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,38 @@
</div>
</template>
<script lang="ts">
import { computed, defineComponent, watchEffect } from 'vue'
import { defineComponent, watchEffect } from 'vue'
import vptypes from 'vptypes'
import useDraggable from '../../../hooks/useDraggable'
import { Hsva } from '../../../utils/Color'
import { HSVAColor } from '../core'

const HueSlider = defineComponent({
name: 'HueSlider',
props: {
color: vptypes.object<Hsva>().isRequired,
color: vptypes.object<HSVAColor>().isRequired,
},
emits: ['change'],
setup(props, { attrs, slots, emit }) {
const [targetRef, handleRef, { delta, limits }] = useDraggable({
viewport: true,
})
const asa = computed(() => {
const height = limits.value?.maxY || delta.value.y
const top = delta.value.y / height
return top * 360
onInit({ height }) {
return {
x: 0,
y: (props.color.h / 360) * height,
}
},
})
watchEffect(() => {
emit('change', asa.value)
if (delta.value && limits.value) {
const color: HSVAColor = {
h: (delta.value.y / limits.value.maxY) * 360,
s: props.color.s,
v: props.color.v,
a: props.color.a,
}
console.log(color)
emit('change', color)
}
})
return {
targetRef,
Expand Down
7 changes: 7 additions & 0 deletions packages/elenext/src/components/ColorPicker/core/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export type HSVAColor = {
h: number
s: number
v: number
a: number
}

export default {}
76 changes: 44 additions & 32 deletions packages/elenext/src/hooks/useDraggable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { Ref, ref, watchEffect } from 'vue'

export type Point = {
x: number
y: number
}

export type DraggableOptions = {
viewport?: boolean
onInit?: (limit: { width: number; height: number }) => Point | null
}

export type RectLimit = {
minX: number
maxX: number
minY: number
maxY: number
}

function calcDelta({ x, y, limits }) {
if (!limits) {
return { x, y }
Expand All @@ -13,48 +30,29 @@ function calcDelta({ x, y, limits }) {
}
}

export default function useDraggable(config: {
viewport?: boolean
}): [
export default function useDraggable(
options: DraggableOptions
): [
Ref<HTMLElement | undefined>,
Ref<HTMLElement | undefined>,
{
dragging: Ref<boolean>
delta: Ref<{ x: number; y: number }>
limits: Ref<
| {
minX: number
maxX: number
minY: number
maxY: number
}
| undefined
>
limits: Ref<RectLimit | undefined>
}
] {
const targetRef = ref<HTMLElement>()
const handleRef = ref<HTMLElement>()

const dragging = ref(false)
const prev = ref({ x: 0, y: 0 })
const delta = ref({ x: 0, y: 0 })
const initial = ref({ x: 0, y: 0 })
const limits: Ref<
| {
minX: number
maxX: number
minY: number
maxY: number
}
| undefined
> = ref()
const startDragging = event => {
const prev = ref<Point>({ x: 0, y: 0 })
const delta = ref<Point>({ x: 0, y: 0 })
const limits: Ref<RectLimit | undefined> = ref()

const startDragging = (event: TouchEvent) => {
event.preventDefault()
dragging.value = true
const source = (event.touches && event.touches[0]) || event
const { clientX, clientY } = source
initial.value = { x: clientX, y: clientY }
if (config.viewport) {
if (options.viewport) {
const { width, height } = targetRef.value!.getBoundingClientRect()
limits.value = {
minX: 0,
Expand All @@ -64,7 +62,7 @@ export default function useDraggable(config: {
}
}
}
const reposition = event => {
const reposition = (event: TouchEvent) => {
if (dragging.value) {
const source = (event.changedTouches && event.changedTouches[0]) || (event.touches && event.touches[0]) || event
const { left, top } = targetRef.value!.getBoundingClientRect()
Expand All @@ -85,13 +83,27 @@ export default function useDraggable(config: {
return prev.value
}

const stopDragging = event => {
const stopDragging = (event: TouchEvent) => {
event.preventDefault()
dragging.value = false
const newDelta = reposition(event)
prev.value = newDelta
}

watchEffect(() => {
if (targetRef.value) {
const { width, height } = targetRef.value.getBoundingClientRect()
limits.value = {
minX: 0,
maxX: width,
minY: 0,
maxY: height,
}
const initPoint = options.onInit?.({ width, height })
if (initPoint) {
delta.value = initPoint
}
}
})
watchEffect(() => {
if (handleRef.value) {
const handle = handleRef.value
Expand Down
31 changes: 31 additions & 0 deletions packages/elenext/src/hooks/useEventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { watchEffect, Ref, onBeforeUnmount } from 'vue'

function useEventListener(
target: Ref<HTMLElement | Window | null>,
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
): () => void {
let prevEle: HTMLElement | Window | null = null
const destroyWatcher = watchEffect(
() => {
target.value?.addEventListener(type, listener, options)
if (prevEle) {
prevEle.removeEventListener(type, listener)
}
prevEle = target?.value
},
{ flush: 'post' }
)
const removeListener = (isDestroyWatcher = true) => {
target.value?.removeEventListener(type, listener)
if (isDestroyWatcher) {
destroyWatcher()
}
}
onBeforeUnmount(() => {
removeListener(true)
})
return removeListener
}
export default useEventListener
23 changes: 23 additions & 0 deletions packages/elenext/src/hooks/useSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ref, Ref } from 'vue'
import useEventListener from './useEventListener'

type Size = { width: Ref<number>; height: Ref<number> }

const useSize = (target: Ref<HTMLElement | Window | null>): Size => {
const width = ref(0)
const height = ref(0)

useEventListener(
target,
'resize',
() => {
width.value = window.innerWidth
height.value = window.innerHeight
},
{ passive: true }
)

return { width, height }
}

export default useSize
Loading

0 comments on commit e0750f1

Please sign in to comment.