mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-03-01 13:36:55 +08:00
wip(color-picker): cache up-coming value to avoid shifting of pallete cursor
This commit is contained in:
parent
acec0c0229
commit
7617b51d16
@ -35,8 +35,9 @@ function useAdjustedTo (
|
||||
|
||||
// teleport disabled key
|
||||
useAdjustedTo.tdkey = teleportDisabled
|
||||
useAdjustedTo.propTo = [String, Object, Boolean] as PropType<
|
||||
HTMLElement | string | boolean
|
||||
>
|
||||
useAdjustedTo.propTo = {
|
||||
type: [String, Object, Boolean] as PropType<HTMLElement | string | boolean>,
|
||||
default: undefined
|
||||
}
|
||||
|
||||
export { useAdjustedTo }
|
||||
|
@ -6,7 +6,9 @@ import {
|
||||
toRgbaString,
|
||||
toHslaString
|
||||
} from 'seemly'
|
||||
import { h, defineComponent, PropType } from 'vue'
|
||||
import { h, defineComponent, PropType, Fragment } from 'vue'
|
||||
import { NInputGroup } from '../../input'
|
||||
import { NSelect } from '../../select'
|
||||
import ColorInputUnit from './ColorInputUnit'
|
||||
import type { ColorPickerMode } from './utils'
|
||||
|
||||
@ -32,6 +34,20 @@ export default defineComponent({
|
||||
},
|
||||
setup (props) {
|
||||
return {
|
||||
options: [
|
||||
{
|
||||
label: 'rgba',
|
||||
value: 'rgba'
|
||||
},
|
||||
{
|
||||
label: 'hsla',
|
||||
value: 'hsla'
|
||||
},
|
||||
{
|
||||
label: 'hsva',
|
||||
value: 'hsva'
|
||||
}
|
||||
],
|
||||
handleUnitUpdateValue (index: number, value: number) {
|
||||
let nextValueArr: any
|
||||
if (props.value === null) {
|
||||
@ -60,26 +76,29 @@ export default defineComponent({
|
||||
const { value } = this
|
||||
return (
|
||||
<div class="n-color-input">
|
||||
{this.mode}
|
||||
<select
|
||||
value={this.mode}
|
||||
onChange={(e) => {
|
||||
this.onUpdateMode((e.target as any).value)
|
||||
<NInputGroup>
|
||||
{{
|
||||
default: () => (
|
||||
<>
|
||||
<NSelect
|
||||
size="small"
|
||||
value={this.mode}
|
||||
options={this.options}
|
||||
onUpdateValue={this.onUpdateMode as (value: string) => void}
|
||||
/>
|
||||
{this.mode.split('').map((v, i) => (
|
||||
<ColorInputUnit
|
||||
label={v.toUpperCase()}
|
||||
value={value === null ? null : value[i]}
|
||||
onUpdateValue={(unitValue) => {
|
||||
this.handleUnitUpdateValue(i, unitValue)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<option value="rgba">rgba</option>
|
||||
<option value="hsla">hsla</option>
|
||||
<option value="hsva">hsva</option>
|
||||
</select>
|
||||
{this.mode.split('').map((v, i) => (
|
||||
<ColorInputUnit
|
||||
label={v.toUpperCase()}
|
||||
value={value === null ? null : value[i]}
|
||||
onUpdateValue={(unitValue) => {
|
||||
this.handleUnitUpdateValue(i, unitValue)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</NInputGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -110,14 +110,13 @@ export default defineComponent({
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<div class="n-color-input__unit">
|
||||
<NInput
|
||||
value={this.inputValue}
|
||||
onUpdateValue={this.handleInputUpdateValue}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
{this.label}
|
||||
</div>
|
||||
<NInput
|
||||
size="small"
|
||||
placeholder=""
|
||||
value={this.inputValue}
|
||||
onUpdateValue={this.handleInputUpdateValue}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -1,12 +1,4 @@
|
||||
import {
|
||||
h,
|
||||
defineComponent,
|
||||
ref,
|
||||
computed,
|
||||
PropType,
|
||||
toRef,
|
||||
ComputedRef
|
||||
} from 'vue'
|
||||
import { h, defineComponent, ref, computed, PropType, toRef } from 'vue'
|
||||
import {
|
||||
hsv2rgb,
|
||||
rgb2hsv,
|
||||
@ -16,13 +8,15 @@ import {
|
||||
hsl2hsv,
|
||||
hsv2hsl,
|
||||
rgb2hsl,
|
||||
hsl2rgb,
|
||||
toRgbaString,
|
||||
toHsvaString,
|
||||
toHslaString,
|
||||
toRgbString,
|
||||
HSVA,
|
||||
RGBA,
|
||||
HSLA
|
||||
HSLA,
|
||||
toHslString
|
||||
} from 'seemly'
|
||||
import HueSlider from './HueSlider'
|
||||
import Pallete from './Pallete'
|
||||
@ -104,7 +98,7 @@ export default defineComponent({
|
||||
return [...hsv2rgb(h, s, v), a]
|
||||
case 'hsla':
|
||||
;[h, s, l, a] = hsla(mergedValue)
|
||||
return [...hsl2hsv(h, s, l), a]
|
||||
return [...hsl2rgb(h, s, l), a]
|
||||
}
|
||||
})
|
||||
|
||||
@ -137,50 +131,80 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const uncontrolledHueRef = ref<number>(0)
|
||||
const displayedHueRef: ComputedRef<number> = computed(() => {
|
||||
if (valueModeRef.value === 'rgba') {
|
||||
const hash = getRgbString(rgbaRef.value)
|
||||
const shouldFollowMouseRef = computed(() => {
|
||||
const { value: valueMode } = valueModeRef
|
||||
console.log('cachedRgbStringRef.value', cachedRgbStringRef.value)
|
||||
console.log('cachedHslStringRef.value', cachedHslStringRef.value)
|
||||
if (valueMode === null) return true
|
||||
if (valueMode === 'rgba') {
|
||||
const { value } = rgbaRef
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (hash) {
|
||||
if (hash === cachedRgbStringRef.value) {
|
||||
return cachedHueRef.value
|
||||
}
|
||||
if (value && cachedRgbStringRef.value === toRgbString(value)) {
|
||||
return true
|
||||
}
|
||||
} else if (valueMode === 'hsla') {
|
||||
const { value } = hslaRef
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (value && cachedHslStringRef.value === toHslString(value)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (hsvaRef.value) return hsvaRef.value[0]
|
||||
return false
|
||||
})
|
||||
const displayedHueRef = computed(() => {
|
||||
if (shouldFollowMouseRef.value) {
|
||||
return cachedHueRef.value
|
||||
}
|
||||
const { value } = hsvaRef
|
||||
if (value) return value[0]
|
||||
return uncontrolledHueRef.value
|
||||
})
|
||||
|
||||
function getRgbString (rgba: null): null
|
||||
function getRgbString (rgba: RGBA): string
|
||||
function getRgbString (rgba: RGBA | null): null | string
|
||||
function getRgbString (rgba: RGBA | null): null | string {
|
||||
if (!rgba) return null
|
||||
return toRgbString(rgba)
|
||||
}
|
||||
const cachedRgbStringRef = ref(
|
||||
rgbaRef.value ? toRgbString(rgbaRef.value) : null
|
||||
)
|
||||
const cachedHueRef = ref(displayedHueRef.value)
|
||||
// If you move in pallete, which means s, v in hsv is updated
|
||||
// In hsv mode, everthing is fine. Howerer in hsl & rgb mode, the controlled
|
||||
// value's change outside the component may cause cursor shifting!
|
||||
//
|
||||
// Before mousemove, everything is ok.
|
||||
// During mousemove, I think the position should follows cursor if the
|
||||
// controlled value outside is really the expected value.
|
||||
//
|
||||
// props.value rgba => hsv(old cursor position) => new hsv => new rgba (cache)
|
||||
// props.value new rgba => hsv(new cursor postion)
|
||||
// If new rgba is the same as the cached new rgba, the cursor should follow
|
||||
// mouse but not the new hsv value
|
||||
// Also the hue slider will be influenced by rgba value.
|
||||
// For hsl mode, keep the same way too.
|
||||
const { value: initRgba } = rgbaRef
|
||||
const { value: initHsla } = hslaRef
|
||||
const { value: initHsva } = hsvaRef
|
||||
const cachedRgbStringRef = ref(initRgba ? toRgbString(initRgba) : null)
|
||||
const cachedHslStringRef = ref(initHsla ? toHslString(initHsla) : null)
|
||||
const cachedHueRef = ref(initHsva?.[0] || uncontrolledHueRef.value)
|
||||
|
||||
function handleUpdateSv (s: number, v: number): void {
|
||||
const { value: hsvaArr } = hsvaRef
|
||||
const hue = displayedHueRef.value
|
||||
const alpha = hsvaArr ? hsvaArr[3] : 1
|
||||
let nextRgba: RGBA
|
||||
let nextHsla: HSLA
|
||||
let nextRgbaString: string
|
||||
let nextHslaString: string
|
||||
switch (displayedModeRef.value) {
|
||||
case 'hsva':
|
||||
doUpdateValue(toHsvaString([hue, s, v, alpha]))
|
||||
break
|
||||
case 'hsla':
|
||||
doUpdateValue(toHslaString([...hsv2hsl(hue, s, v), alpha]))
|
||||
nextHsla = [...hsv2hsl(hue, s, v), alpha]
|
||||
nextHslaString = toHslaString(nextHsla)
|
||||
cachedHslStringRef.value = toHslString(nextHsla)
|
||||
cachedHueRef.value = displayedHueRef.value
|
||||
doUpdateValue(nextHslaString)
|
||||
break
|
||||
case 'rgba':
|
||||
nextRgba = [...hsv2rgb(hue, s, v), alpha]
|
||||
nextRgbaString = toRgbaString([...hsv2rgb(hue, s, v), alpha])
|
||||
cachedRgbStringRef.value = toRgbString(nextRgba)
|
||||
cachedHueRef.value = hue
|
||||
cachedHueRef.value = displayedHueRef.value
|
||||
doUpdateValue(nextRgbaString)
|
||||
break
|
||||
}
|
||||
@ -201,7 +225,7 @@ export default defineComponent({
|
||||
doUpdateValue(toRgbaString([...hsv2rgb(hue, s, v), a]))
|
||||
break
|
||||
case 'hsla':
|
||||
doUpdateValue(toRgbaString([...hsv2hsl(hue, s, v), a]))
|
||||
doUpdateValue(toHslaString([...hsv2hsl(hue, s, v), a]))
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -220,6 +244,8 @@ export default defineComponent({
|
||||
return {
|
||||
mergedValue: mergedValueRef, // debug
|
||||
hsva: hsvaRef,
|
||||
rgba: rgbaRef,
|
||||
shouldFollowMouse: shouldFollowMouseRef,
|
||||
displayedHue: displayedHueRef,
|
||||
displayedMode: displayedModeRef,
|
||||
mergedValueArr: mergedValueArrRef,
|
||||
@ -231,20 +257,32 @@ export default defineComponent({
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
class="n-color-picker-panel"
|
||||
style={{
|
||||
width: '180px'
|
||||
}}
|
||||
>
|
||||
<div>value: {this.mergedValue}</div>
|
||||
<Pallete
|
||||
hsva={this.hsva}
|
||||
rgba={this.rgba}
|
||||
shouldFollowMouse={this.shouldFollowMouse}
|
||||
displayedHue={this.displayedHue}
|
||||
onUpdateSV={this.handleUpdateSv}
|
||||
/>
|
||||
<HueSlider hue={this.displayedHue} onUpdateHue={this.handleUpdateHue} />
|
||||
<ColorInput
|
||||
mode={this.displayedMode}
|
||||
onUpdateMode={this.handleUpdateDisplayedMode}
|
||||
value={this.mergedValueArr}
|
||||
onUpdateValue={this.handleInputUpdateValue}
|
||||
/>
|
||||
<div class="n-color-picker-control">
|
||||
<HueSlider
|
||||
hue={this.displayedHue}
|
||||
onUpdateHue={this.handleUpdateHue}
|
||||
/>
|
||||
<ColorInput
|
||||
mode={this.displayedMode}
|
||||
onUpdateMode={this.handleUpdateDisplayedMode}
|
||||
value={this.mergedValueArr}
|
||||
onUpdateValue={this.handleInputUpdateValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export default defineComponent({
|
||||
if (!railEl) return
|
||||
const { width, left } = railEl.getBoundingClientRect()
|
||||
const newHue = Math.floor(((e.clientX - left) / width) * 360)
|
||||
props.onUpdateHue(newHue > 360 ? 360 : newHue < 0 ? 0 : newHue)
|
||||
props.onUpdateHue(newHue >= 360 ? 359 : newHue < 0 ? 0 : newHue)
|
||||
}
|
||||
function handleMouseUp (): void {
|
||||
off('mousemove', document, handleMouseMove)
|
||||
@ -46,10 +46,17 @@ export default defineComponent({
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<div class="n-hue-slider">
|
||||
<div
|
||||
class="n-hue-slider"
|
||||
style={{
|
||||
marginBottom: '8px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref="railRef"
|
||||
style={{
|
||||
boxShadow: 'inset 0 0 2px 0 rgba(0, 0, 0, .24)',
|
||||
boxSizing: 'border-box',
|
||||
backgroundImage: GRADIENT,
|
||||
height: HANDLE_SIZE,
|
||||
borderRadius: RADIUS,
|
||||
@ -72,10 +79,10 @@ export default defineComponent({
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: `calc((${this.hue}%) / 18 * 5 - ${RADIUS})`,
|
||||
boxShadow: 'rgb(0 0 0 / 20%) 0px 0px 0px 1px',
|
||||
left: `calc((${this.hue}%) / 359 * 100 - ${RADIUS})`,
|
||||
boxSizing: 'border-box',
|
||||
border: '2px solid black',
|
||||
border: '2px solid white',
|
||||
backgroundColor: `hsl(${this.hue}, 100%, 50%)`,
|
||||
borderRadius: RADIUS,
|
||||
width: HANDLE_SIZE,
|
||||
|
@ -13,11 +13,16 @@ export default defineComponent({
|
||||
type: (Array as unknown) as PropType<HSVA | null>,
|
||||
default: null
|
||||
},
|
||||
rgba: {
|
||||
type: (Array as unknown) as PropType<HSVA | null>,
|
||||
default: null
|
||||
},
|
||||
// 0 - 360
|
||||
displayedHue: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
shouldFollowMouse: Boolean,
|
||||
onUpdateSV: {
|
||||
type: Function as PropType<(s: number, v: number) => void>,
|
||||
required: true
|
||||
@ -25,6 +30,12 @@ export default defineComponent({
|
||||
},
|
||||
setup (props) {
|
||||
const palleteRef = ref<HTMLElement | null>(null)
|
||||
const cachedLeftRef = ref('0')
|
||||
const cachedBottomRef = ref('0')
|
||||
function derivePosition (newS: number, newV: number): void {
|
||||
cachedLeftRef.value = `calc(${newS}% - ${RADIUS})`
|
||||
cachedBottomRef.value = `calc(${newV}% - ${RADIUS})`
|
||||
}
|
||||
function handleMouseDown (e: MouseEvent): void {
|
||||
if (!palleteRef.value) return
|
||||
on('mousemove', document, handleMouseMove)
|
||||
@ -37,11 +48,10 @@ export default defineComponent({
|
||||
const { width, height, left, bottom } = palleteEl.getBoundingClientRect()
|
||||
const newV = (bottom - e.clientY) / height
|
||||
const newS = (e.clientX - left) / width
|
||||
|
||||
props.onUpdateSV(
|
||||
100 * (newS > 100 ? 1 : newS < 0 ? 0 : newS),
|
||||
100 * (newV > 100 ? 1 : newV < 0 ? 0 : newV)
|
||||
)
|
||||
const normalizedNewS = 100 * (newS > 1 ? 1 : newS < 0 ? 0 : newS)
|
||||
const normalizedNewV = 100 * (newV > 1 ? 1 : newV < 0 ? 0 : newV)
|
||||
derivePosition(normalizedNewS, normalizedNewV)
|
||||
props.onUpdateSV(normalizedNewS, normalizedNewV)
|
||||
}
|
||||
function handleMouseUp (): void {
|
||||
off('mousemove', document, handleMouseMove)
|
||||
@ -50,10 +60,27 @@ export default defineComponent({
|
||||
return {
|
||||
palleteRef,
|
||||
handleColor: computed(() => {
|
||||
// const [r, g, b] = hsv2rgb(props.hue, props.s, props.v)
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
// return `rgb(${r}, ${g}, ${b})`
|
||||
return 'transparent'
|
||||
const { rgba } = props
|
||||
if (!rgba) return ''
|
||||
return `rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`
|
||||
}),
|
||||
position: computed(() => {
|
||||
if (props.shouldFollowMouse) {
|
||||
return {
|
||||
left: cachedLeftRef.value,
|
||||
bottom: cachedBottomRef.value
|
||||
}
|
||||
}
|
||||
if (!props.hsva) {
|
||||
return {
|
||||
left: '0',
|
||||
bottom: '0'
|
||||
}
|
||||
}
|
||||
return {
|
||||
left: `calc(${props.hsva[1]}% - ${RADIUS})`,
|
||||
bottom: `calc(${props.hsva[2]}% - ${RADIUS})`
|
||||
}
|
||||
}),
|
||||
handleMouseDown
|
||||
}
|
||||
@ -61,7 +88,7 @@ export default defineComponent({
|
||||
render () {
|
||||
return (
|
||||
<div
|
||||
style="height: 300px; position: relative;"
|
||||
style="height: 180px; position: relative; margin-bottom: 8px;"
|
||||
onMousedown={this.handleMouseDown}
|
||||
ref="palleteRef"
|
||||
>
|
||||
@ -85,7 +112,8 @@ export default defineComponent({
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, rgba(0, 0, 0, 0%), rgba(0, 0, 0, 100%))'
|
||||
'linear-gradient(180deg, rgba(0, 0, 0, 0%), rgba(0, 0, 0, 100%))',
|
||||
boxShadow: 'inset 0 0 2px 0 rgba(0, 0, 0, .24)'
|
||||
}}
|
||||
/>
|
||||
{this.hsva && (
|
||||
@ -99,10 +127,9 @@ export default defineComponent({
|
||||
boxSizing: 'border-box',
|
||||
border: '2px solid white',
|
||||
position: 'absolute',
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
left: `calc(${this.hsva[1]}% - ${RADIUS})`,
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
bottom: `calc(${this.hsva[2]}% - ${RADIUS})`
|
||||
boxShadow: 'rgb(0 0 0 / 20%) 0px 0px 0px 1px',
|
||||
left: this.position.left,
|
||||
bottom: this.position.bottom
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { c, cB, cE } from '../../../_utils/cssr'
|
||||
|
||||
export default c([
|
||||
cB('color-picker-panel', `
|
||||
padding: 8px;
|
||||
`),
|
||||
cB('color-input', `
|
||||
display: flex;
|
||||
`, [
|
||||
|
@ -40,8 +40,10 @@ export interface SelectIgnoredOption {
|
||||
|
||||
export type ValueAtom = string | number
|
||||
export type Value = ValueAtom | string[] | number[] | ValueAtom[]
|
||||
export type OnUpdateValue = <
|
||||
T extends ValueAtom &
|
||||
export type OnUpdateValue = (
|
||||
value: string &
|
||||
number &
|
||||
ValueAtom &
|
||||
string[] &
|
||||
number[] &
|
||||
ValueAtom[] &
|
||||
@ -49,11 +51,9 @@ export type OnUpdateValue = <
|
||||
(string[] | null) &
|
||||
(number[] | null) &
|
||||
(ValueAtom[] | null)
|
||||
>(
|
||||
value: T
|
||||
) => void
|
||||
export type OnUpdateValueImpl = <
|
||||
T extends
|
||||
export type OnUpdateValueImpl = (
|
||||
value:
|
||||
| ValueAtom
|
||||
| string[]
|
||||
| number[]
|
||||
@ -62,8 +62,6 @@ export type OnUpdateValueImpl = <
|
||||
| (string[] | null)
|
||||
| (number[] | null)
|
||||
| (ValueAtom[] | null)
|
||||
>(
|
||||
value: T
|
||||
) => void
|
||||
export type SelectTreeMate = TreeMate<
|
||||
SelectBaseOption,
|
||||
|
Loading…
Reference in New Issue
Block a user