@fto-consult/expo-ui
Version:
Bibliothèque de composants UI Expo,react-native
739 lines (715 loc) • 25.7 kB
JavaScript
import React, { Component } from "$react";
import {normalize,RGB_MAX,HUE_MAX,SV_MAX,hexToRgb} from "$theme/colors";
import {extendObj,defaultStr,isNonNullString,defaultObj,isObj} from "$cutils";
import View from "$ecomponents/View";
import {
Animated,
Image,
PanResponder,
StyleSheet,
Pressable,
} from 'react-native';
import Elevations from '$ecomponents/Surface/Elevations';
import srcWheel from './assets/color-wheel.png';
import srcSlider from './assets/black-gradient.png';
import srcSliderRotated from './assets/black-gradient-rotated.png';
import theme,{Colors} from "$theme";
import TextField from "$ecomponents/TextField";
const PALETTE = [
'#000000',
'#888888',
'#ed1c24',
'#d11cd5',
'#1633e6',
'#00aeef',
'#00c85d',
'#57ff0a',
'#ffde17',
'#f26522',
]
// expands hex to full 6 chars (#fff -> #ffffff) if necessary
const expandColor = color => typeof color == 'string' && color.length === 4
? `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}`
: color;
export default class ColorPickerComponent extends Component {
// testData = {}
// testView = {forceUpdate(){}}
color = {h:0,s:0,v:100}
slideX = new Animated.Value(0)
slideY = new Animated.Value(0)
panX = new Animated.Value(30)
panY = new Animated.Value(30)
sliderLength = 0
wheelSize = 0
sliderMeasure = {}
wheelMeasure = {}
wheelWidth = 0
static defaultProps = {
row: false, // use row or vertical layout
noSnap: false, // enables snapping on the center of wheel and edges of wheel and slider
thumbSize: 50, // wheel color thumb size
sliderSize: 20, // slider and slider color thumb size
gapSize: 16, // size of gap between slider and wheel
discrete: false, // use swatchs of shades instead of slider
discreteLength: 10, // number of swatchs of shades
sliderHidden: false, // if true the slider is hidden
swatches: true, // show color swatches
swatchesLast: true, // if false swatches are shown before wheel
swatchesOnly: false, // show swatch only and hide wheel and slider
swatchesHitSlop: undefined, // defines how far the touch event can start away from the swatch
color: '#ffffff', // color of the color picker
palette: PALETTE, // palette colors of swatches
shadeWheelThumb: true, // if true the wheel thumb color is shaded
shadeSliderThumb: false, // if true the slider thumb color is shaded
autoResetSlider: false, // if true the slider thumb is reset to 0 value when wheel thumb is moved
onInteractionStart: () => {}, // callback function triggered when user begins dragging slider/wheel
onColorChange: () => {}, // callback function providing current color while user is actively dragging slider/wheel
onColorChangeComplete: () => {}, // callback function providing final color when user stops dragging slider/wheel
}
wheelPanResponder = PanResponder.create({
onStartShouldSetPanResponderCapture: (event, gestureState) => {
const {nativeEvent} = event
if (this.outOfWheel(nativeEvent)) return
this.wheelMovement(event, gestureState)
this.updateHueSaturation({nativeEvent})
return true
},
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (event, gestureState) => {
const { locationX, locationY } = event.nativeEvent
const { moveX, moveY, x0, y0 } = gestureState
const x = x0 - locationX, y = y0 - locationY
this.wheelMeasure.x = x
this.wheelMeasure.y = y
this.props.onInteractionStart();
return true
},
onPanResponderMove: (event, gestureState) => {
if(event && event.nativeEvent && typeof event.nativeEvent.preventDefault == 'function') event.nativeEvent.preventDefault()
if(event && event.nativeEvent && typeof event.nativeEvent.stopPropagation == 'function') event.nativeEvent.stopPropagation()
if (this.outOfWheel(event.nativeEvent) || this.outOfBox(this.wheelMeasure, gestureState)) return;
this.wheelMovement(event, gestureState)
},
onMoveShouldSetPanResponder: () => true,
onPanResponderRelease: (event, gestureState) => {
const {nativeEvent} = event
const {radius} = this.polar(nativeEvent)
const {hsv} = this.state
const {h,s,v} = hsv
if (!this.props.noSnap && radius <= 0.10 && radius >= 0) this.animate('#ffffff', 'hs', false, true)
if (!this.props.noSnap && radius >= 0.95 && radius <= 1) this.animate(this.state.currentColor, 'hs', true)
if (this.props.onColorChangeComplete) this.props.onColorChangeComplete(hsvToHex(hsv))
this.setState({currentColor:this.state.currentColor}, x=>this.renderDiscs())
},
})
sliderPanResponder = PanResponder.create({
onStartShouldSetPanResponderCapture: (event, gestureState) => {
const {nativeEvent} = event
if (this.outOfSlider(nativeEvent)) return
this.sliderMovement(event, gestureState)
this.updateValue({nativeEvent})
return true
},
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: (event, gestureState) => {
const { locationX, locationY } = event.nativeEvent
const { moveX, moveY, x0, y0 } = gestureState
const x = x0 - locationX, y = y0 - locationY
this.sliderMeasure.x = x
this.sliderMeasure.y = y
this.props.onInteractionStart();
return true
},
onPanResponderMove: (event, gestureState) => {
if(event && event.nativeEvent && typeof event.nativeEvent.preventDefault == 'function') event.nativeEvent.preventDefault()
if(event && event.nativeEvent && typeof event.nativeEvent.stopPropagation == 'function') event.nativeEvent.stopPropagation()
if (this.outOfSlider(event.nativeEvent) || this.outOfBox(this.sliderMeasure, gestureState)) return;
this.sliderMovement(event, gestureState)
},
onMoveShouldSetPanResponder: () => true,
onPanResponderRelease: (event, gestureState) => {
const {nativeEvent} = event
const {hsv} = this.state
const {h,s,v} = hsv
const ratio = this.ratio(nativeEvent)
if (!this.props.noSnap && ratio <= 0.05 && ratio >= 0) this.animate(this.state.currentColor, 'v', false)
if (!this.props.noSnap && ratio >= 0.95 && ratio <= 1) this.animate(this.state.currentColor, 'v', true)
if (this.props.onColorChangeComplete) this.props.onColorChangeComplete(hsvToHex(hsv))
},
})
constructor (props) {
super(props)
extendObj(this.state,{
wheelOpacity: 0,
visible : typeof this.props.visible ==='boolean'? this.props.visible : false,
sliderOpacity: 0,
hueSaturation: hsvToHex(this.color.h,this.color.s,100),
currentColor: props.color,
hsv: {h:0,s:0,v:100},
});
this.wheelMovement = new Animated.event(
[
{
nativeEvent: {
locationX: this.panX,
locationY: this.panY,
}
},
null,
],
{
useNativeDriver: false,
listener: this.updateHueSaturation
}
)
this.sliderMovement = new Animated.event(
[
{
nativeEvent: {
locationX: this.slideX,
locationY: this.slideY,
}
},
null,
],
{
useNativeDriver: false,
listener: this.updateValue
}
)
this.swatchAnim = props.palette.map((c,i) => (new Animated.Value(0)))
this.discAnim = (`1`).repeat(props.discreteLength).split('').map((c,i) => (new Animated.Value(0)))
this.renderSwatches()
this.renderDiscs()
}
onSwatchPress = (c,i) => {
this.swatchAnim[i].stopAnimation()
Animated.timing(this.swatchAnim[i], {
toValue: 1,
useNativeDriver: false,
duration: 500,
}).start(x=>{
this.swatchAnim[i].setValue(0)
})
this.animate(c)
}
onDiscPress = (c,i) => {
this.discAnim[i].stopAnimation()
Animated.timing(this.discAnim[i], {
toValue: 1,
useNativeDriver: false,
duration: 500,
}).start(x=>{
this.discAnim[i].setValue(0)
})
const val = i>=9?100:11*i
this.updateValue({nativeEvent:null}, val)
this.animate({h:this.color.h,s:this.color.s,v:val}, 'v')
}
onSquareLayout = (e) => {
let {x, y, width, height} = e.nativeEvent.layout
this.wheelWidth = Math.min(width, height)
this.tryForceUpdate()
}
onWheelLayout = (e) => {
/*
* const {x, y, width, height} = nativeEvent.layout
* onlayout values are different than measureInWindow
* x and y are the distances to its previous element
* but in measureInWindow they are relative to the window
*/
this.wheel.measureInWindow((x, y, width, height) => {
this.wheelMeasure = {x, y, width, height}
this.wheelSize = width
// this.panX.setOffset(-width/2)
// this.panY.setOffset(-width/2)
this.update(this.state.currentColor)
this.setState({wheelOpacity:1})
})
}
onSliderLayout = (e) => {
if(!this.slider || !this.slider.measureInWindow) return;
this.slider.measureInWindow((x, y, width, height) => {
this.sliderMeasure = {x, y, width, height}
this.sliderLength = this.props.row ? height-width : width-height
// this.slideX.setOffset(-width/2)
// this.slideY.setOffset(-width/2)
this.update(this.state.currentColor)
this.setState({sliderOpacity:1})
})
}
outOfBox (measure, gestureState) {
const { x, y, width, height } = measure
const { moveX, moveY, x0, y0 } = gestureState
// console.log(`${moveX} , ${moveY} / ${x} , ${y} / ${locationX} , ${locationY}`);
return !(moveX >= x && moveX <= x+width && moveY >= y && moveY <= y+height)
}
outOfWheel (nativeEvent) {
const {radius} = this.polar(nativeEvent)
return radius > 1
}
outOfSlider (nativeEvent) {
const row = this.props.row
const loc = row ? nativeEvent.locationY : nativeEvent.locationX
const {width,height} = this.sliderMeasure
return (loc > (row ? height-width : width-height))
}
val (v) {
const d = this.props.discrete, r = 11*Math.round(v/11)
return d ? (r>=99?100:r) : v
}
ratio (nativeEvent) {
const row = this.props.row
const loc = row ? nativeEvent.locationY : nativeEvent.locationX
const {width,height} = this.sliderMeasure
return 1 - (loc / (row ? height-width : width-height))
}
polar (nativeEvent) {
const lx = nativeEvent.locationX, ly = nativeEvent.locationY
const [x, y] = [lx - this.wheelSize/2, ly - this.wheelSize/2]
return {
deg: Math.atan2(y, x) * (-180 / Math.PI),
radius: Math.sqrt(y * y + x * x) / (this.wheelSize / 2),
}
}
cartesian (deg, radius) {
const r = radius * this.wheelSize / 2 // was normalized
const rad = Math.PI * deg / 180
const x = r * Math.cos(rad)
const y = r * Math.sin(rad)
return {
left: this.wheelSize / 2 + x,
top: this.wheelSize / 2 - y,
}
}
updateHueSaturation = ({nativeEvent}) => {
const {deg, radius} = this.polar(nativeEvent), h = deg, s = 100 * radius, v = this.color.v
// if(radius > 1 ) return
const hsv = {h,s,v}// v: 100} // causes bug
if(this.props.autoResetSlider === true) {
this.slideX.setValue(0)
this.slideY.setValue(0)
hsv.v = 100
}
const currentColor = hsvToHex(hsv)
this.color = hsv
this.setState({hsv, currentColor, hueSaturation: hsvToHex(this.color.h,this.color.s,100)})
this.props.onColorChange(hsvToHex(hsv))
// this.testData.deg = deg
// this.testData.radius = radius
// this.testData.pan = JSON.stringify({x:this.panX,y:this.panY})
// this.testData.pan = JSON.stringify(this.state.pan.getTranslateTransform())
// this.testView.forceUpdate()
}
updateValue = ({nativeEvent}, val) => {
const {h,s} = this.color, v = (typeof val == 'number') ? val : 100 * this.ratio(nativeEvent)
const hsv = {h,s,v}
const currentColor = hsvToHex(hsv)
this.color = hsv
this.setState({hsv, currentColor, hueSaturation: hsvToHex(this.color.h,this.color.s,100)})
this.props.onColorChange(hsvToHex(hsv))
}
update = (color, who, max, force) => {
const isHex = /^#(([0-9a-f]{2}){3}|([0-9a-f]){3})$/i
if (!isHex.test(color)) color = '#ffffff'
color = expandColor(color);
const specific = (typeof who == 'string'), who_hs = (who=='hs'), who_v = (who=='v')
let {h, s, v} = (typeof color == 'string') ? hexToHsv(color) : color, stt = {}
h = (who_hs||!specific) ? h : this.color.h
s = (who_hs && max) ? 100 : (who_hs && max===false) ? 0 : (who_hs||!specific) ? s : this.color.s
v = (who_v && max) ? 100 : (who_v && max===false) ? 0 : (who_v||!specific) ? v : this.color.v
const range = (100 - v) / 100 * this.sliderLength
const {left, top} = this.cartesian(h, s / 100)
const hsv = {h,s,v}
if(!specific||force) {
this.color = hsv
stt.hueSaturation = hsvToHex(this.color.h,this.color.s,100)
// this.setState({hueSaturation: hsvToHex(this.color.h,this.color.s,100)})
}
stt.currentColor = hsvToHex(hsv)
this.setState(stt, x=>{ this.tryForceUpdate(); this.renderDiscs(); })
// this.setState({currentColor:hsvToHex(hsv)}, x=>this.tryForceUpdate())
this.props.onColorChange(hsvToHex(hsv))
if (this.props.onColorChangeComplete) this.props.onColorChangeComplete(hsvToHex(hsv))
if(who_hs||!specific) {
this.panY.setValue(top)// - this.props.thumbSize / 2)
this.panX.setValue(left)// - this.props.thumbSize / 2)
}
if(who_v||!specific) {
this.slideX.setValue(range)
this.slideY.setValue(range)
}
}
animate = (color, who, max, force) => {
color = expandColor(color);
const specific = (typeof who == 'string'), who_hs = (who=='hs'), who_v = (who=='v')
let {h, s, v} = (typeof color == 'string') ? hexToHsv(color) : color, stt = {}
h = (who_hs||!specific) ? h : this.color.h
s = (who_hs && max) ? 100 : (who_hs && max===false) ? 0 : (who_hs||!specific) ? s : this.color.s
v = (who_v && max) ? 100 : (who_v && max===false) ? 0 : (who_v||!specific) ? v : this.color.v
const range = (100 - v) / 100 * this.sliderLength
const {left, top} = this.cartesian(h, s / 100)
const hsv = {h,s,v}
// console.log(hsv);
if(!specific||force) {
this.color = hsv
stt.hueSaturation = hsvToHex(this.color.h,this.color.s,100)
// this.setState({hueSaturation: hsvToHex(this.color.h,this.color.s,100)})
}
stt.currentColor = hsvToHex(hsv)
this.setState(stt, x=>{ this.tryForceUpdate(); this.renderDiscs(); })
// this.setState({currentColor:hsvToHex(hsv)}, x=>this.tryForceUpdate())
this.props.onColorChange(hsvToHex(hsv))
if (this.props.onColorChangeComplete) this.props.onColorChangeComplete(hsvToHex(hsv))
let anims = []
if(who_hs||!specific) anims.push(//{//
Animated.spring(this.panX, { toValue: left, useNativeDriver: false, friction: 90 }),//.start()//
Animated.spring(this.panY, { toValue: top, useNativeDriver: false, friction: 90 }),//.start()//
)//}//
if(who_v||!specific) anims.push(//{//
Animated.spring(this.slideX, { toValue: range, useNativeDriver: false, friction: 90 }),//.start()//
Animated.spring(this.slideY, { toValue: range, useNativeDriver: false, friction: 90 }),//.start()//
)//}//
Animated.parallel(anims).start()
}
// componentWillReceiveProps(nextProps) {
// const { color } = nextProps
// if(color !== this.props.color) this.animate(color)
// }
componentDidUpdate(prevProps) {
super.componentDidUpdate();
const { color } = this.props
if(color !== prevProps.color) this.animate(color)
}
revert() {
if(this._isMounted()) this.animate(this.props.color)
}
tryForceUpdate () {
if(this._isMounted()) this.forceUpdate()
}
renderSwatches () {
this.swatches = this.props.palette.map((c,i) => (
<View testID={"RN_ColorPicker_SWATCHES"} style={[styles.swatch,theme.styles.cursorPointer,{backgroundColor:c}]} key={'S'+i} hitSlop={this.props.swatchesHitSlop}>
<Pressable onPress={x=>this.onSwatchPress(c,i)} hitSlop={this.props.swatchesHitSlop}>
<Animated.View style={[styles.swatchTouch,{backgroundColor:c,transform:[{scale:this.swatchAnim[i].interpolate({inputRange:[0,0.5,1],outputRange:[0.666,1,0.666]})}]}]} />
</Pressable>
</View>
))
}
renderDiscs () {
this.disc = (`1`).repeat(this.props.discreteLength).split('').map((c,i) => (
<View testID={"RN_ColorPicker_DISC"} style={[styles.swatch,{backgroundColor:this.state.hueSaturation}]} key={'D'+i} hitSlop={this.props.swatchesHitSlop}>
<Pressable style={[theme.styles.cursorPointer]} onPress={x=>this.onDiscPress(c,i)} hitSlop={this.props.swatchesHitSlop}>
<Animated.View style={[styles.swatchTouch,{backgroundColor:this.state.hueSaturation,transform:[{scale:this.discAnim[i].interpolate({inputRange:[0,0.5,1],outputRange:[0.666,1,0.666]})}]}]}>
<View style={[styles.wheelImg,{backgroundColor:'#000',opacity:1-(i>=9?1:(i*11/100))}]}></View>
</Animated.View>
</Pressable>
</View>
)).reverse()
this.tryForceUpdate()
}
render () {
const {
style,
thumbSize,
sliderSize,
gapSize,
swatchesLast,
swatchesOnly,
sliderHidden,
discrete,
disabled,
readOnly,
row,
testID : customTestId,
} = this.props
const swatches = !!(this.props.swatches || swatchesOnly)
const hsv = hsvToHex(this.color), hex = hsvToHex(this.color.h,this.color.s,100)
const wheelPanHandlers = this.wheelPanResponder && this.wheelPanResponder.panHandlers || {}
const sliderPanHandlers = this.sliderPanResponder && this.sliderPanResponder.panHandlers || {}
const opacity = this.state.wheelOpacity// * this.state.sliderOpacity
const margin = swatchesOnly ? 0 : gapSize
const wheelThumbStyle = {
width: thumbSize,
height: thumbSize,
borderRadius: thumbSize / 2,
backgroundColor: this.props.shadeWheelThumb === true ? hsv: hex,
transform: [{translateX:-thumbSize/2},{translateY:-thumbSize/2}],
left: this.panX,
top: this.panY,
opacity,
////
// transform: [{translateX:this.panX},{translateY:this.panY}],
// left: -this.props.thumbSize/2,
// top: -this.props.thumbSize/2,
// zIndex: 2,
}
const sliderThumbStyle = {
left: row?0:this.slideX,
top: row?this.slideY:0,
// transform: [row?{translateX:8}:{translateY:8}],
backgroundColor: this.props.shadeSliderThumb === true ? hsv: hex,
borderRadius: sliderSize/2,
height: sliderSize,
width: sliderSize,
opacity,
}
const sliderStyle = {
width:row?sliderSize:'100%',
height:row?'100%':sliderSize,
marginLeft:row?gapSize:0,
marginTop:row?0:gapSize,
borderRadius:sliderSize/2,
}
const swatchStyle = {
flexDirection:row?'column':'row',
width:row?20:'100%',
height:row?'100%':20,
marginLeft:row?margin:0,
marginTop:row?0:margin,
}
const swatchFirstStyle = {
marginTop:0,
marginLeft:0,
marginRight:row?margin:0,
marginBottom:row?0:margin,
}
// console.log('RENDER >>',row,thumbSize,sliderSize)
const testID = defaultStr(customTestId,"RN_ColorPickerContainer");
const hasHex = Colors.isValid(hex);
const contrastColor = hasHex ? Colors.getContrast(hex) : undefined;
const restProps = hasHex ? {
color : contrastColor,
backgroundColor : hex,
style : [{
color : contrastColor,
backgroundColor : hex,
}]
} : {};
return (
<View testID={testID}>
<View testID={`${testID}_RowContainer`} style={[styles.root,row?{flexDirection:'row'}:{},style]}>
{ swatches && !swatchesLast && <View testID={`${testID}_SwatchestLast`} style={[styles.swatches,swatchStyle,swatchFirstStyle]} key={'SW'}>{ this.swatches }</View> }
{ !swatchesOnly && <View testID={`${testID}_SwatchesOnly`} style={[styles.wheel]} key={'$1'} onLayout={this.onSquareLayout}>
{ this.wheelWidth>0 && <View style={[{padding:thumbSize/2,width:this.wheelWidth,height:this.wheelWidth}]}>
<View style={[styles.wheelWrap,theme.styles.cursorPointer]} testID={`${testID}_WheelWrap`}>
<Image testID={testID+"_WheelImg"} style={styles.wheelImg} source={srcWheel} />
<Animated.View testID={testID+"WheelThumb"} style={[styles.wheelThumb,wheelThumbStyle,Elevations[4],{pointerEvents:'none'}]} />
<View testID={testID+"_WheelWrapLayout"} style={[styles.cover]} onLayout={this.onWheelLayout} {...wheelPanHandlers} ref={r => { this.wheel = r }}></View>
</View>
</View> }
</View> }
{ !swatchesOnly && !sliderHidden && (discrete ? <View testID={`${testID}_SwatchesDiscrete`} style={[styles.swatches,swatchStyle]} key={'$2'}>{ this.disc }</View> : <View style={[styles.slider,sliderStyle]} key={'$2'}>
<View testID={testID+"_WheelGrad"} sstyle={[styles.grad,{backgroundColor:hex}]}>
<Image style={styles.sliderImg} source={row?srcSliderRotated:srcSlider} resizeMode="stretch" />
</View>
<Animated.View style={[styles.sliderThumb,sliderThumbStyle,Elevations[4],{pointerEvents:'none'}]} />
<View style={[styles.cover]} onLayout={this.onSliderLayout} {...sliderPanHandlers} ref={r => { this.slider = r }}></View>
</View>) }
{ swatches && swatchesLast && <View testID={`${testID}_SwatchesList`} style={[styles.swatches,swatchStyle]} key={'SW'}>{ this.swatches }</View> }
<TextField
testID={`${testID}_ColorPickerInput`}
enableCopy
label = ''
readOnly = {readOnly}
disabled = {disabled}
defaultValue = {hex}
{...restProps}
style = {[styles.textInput,restProps.style]}
onChangeText = {(value)=>{
if(Colors.isHex(value)){
this.update(value);
}
}}
/>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
root: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'visible',
// aspectRatio: 1,
// backgroundColor: '#ffcccc',
},
wheel: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
overflow: 'visible',
width: '100%',
minWidth: 200,
minHeight: 200,
// aspectRatio: 1,
// backgroundColor: '#ffccff',
},
wheelWrap: {
width: '100%',
height: '100%',
// backgroundColor: '#ffffcc',
},
wheelImg: {
width: '100%',
height: '100%',
// backgroundColor: '#ffffcc',
},
wheelThumb: {
position: 'absolute',
backgroundColor: '#EEEEEE',
borderWidth: 3,
borderColor: '#EEEEEE',
elevation: 4,
shadowColor: 'rgb(46, 48, 58)',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 0.8,
shadowRadius: 2,
},
cover: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
// backgroundColor: '#ccccff88',
},
slider: {
width: '100%',
// height: 32,
marginTop: 16,
// overflow: 'hidden',
flexDirection: 'column-reverse',
// elevation: 4,
// backgroundColor: '#ccccff',
},
sliderImg: {
width: '100%',
height: '100%',
},
sliderThumb: {
position: 'absolute',
top: 0,
left: 0,
borderWidth: 2,
borderColor: '#EEEEEE',
elevation: 4,
// backgroundColor: '#f00',
},
grad: {
borderRadius: 100,
overflow: "hidden",
height: '100%',
},
swatches: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 16,
// padding: 16,
},
swatch: {
width: 20,
height: 20,
borderRadius: 10,
// borderWidth: 1,
borderColor: '#8884',
alignItems: 'center',
justifyContent: 'center',
overflow: 'visible',
},
swatchTouch: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: '#f004',
overflow: 'hidden',
},
})
const rgbToHsv = (r, g, b) => {
if (typeof r === 'object') {
const args = r
r = args.r; g = args.g; b = args.b;
}
// It converts [0,255] format, to [0,1]
r = (r === RGB_MAX) ? 1 : (r % RGB_MAX / parseFloat(RGB_MAX))
g = (g === RGB_MAX) ? 1 : (g % RGB_MAX / parseFloat(RGB_MAX))
b = (b === RGB_MAX) ? 1 : (b % RGB_MAX / parseFloat(RGB_MAX))
let max = Math.max(r, g, b)
let min = Math.min(r, g, b)
let h, s, v = max
let d = max - min
s = max === 0 ? 0 : d / max
if (max === min) {
h = 0 // achromatic
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return {
h: Math.round(h * HUE_MAX),
s: Math.round(s * SV_MAX),
v: Math.round(v * SV_MAX)
}
}
const hsvToRgb = (h, s, v) => {
if (typeof h === 'object') {
const args = h
h = args.h; s = args.s; v = args.v;
}
h = normalize(h)
h = (h === HUE_MAX) ? 1 : (h % HUE_MAX / parseFloat(HUE_MAX) * 6)
s = (s === SV_MAX) ? 1 : (s % SV_MAX / parseFloat(SV_MAX))
v = (v === SV_MAX) ? 1 : (v % SV_MAX / parseFloat(SV_MAX))
let i = Math.floor(h)
let f = h - i
let p = v * (1 - s)
let q = v * (1 - f * s)
let t = v * (1 - (1 - f) * s)
let mod = i % 6
let r = [v, q, p, p, t, v][mod]
let g = [t, v, v, q, p, p][mod]
let b = [p, p, t, v, v, q][mod]
return {
r: Math.floor(r * RGB_MAX),
g: Math.floor(g * RGB_MAX),
b: Math.floor(b * RGB_MAX),
}
}
export const hsvToHex = (h, s, v) => {
const rgb = hsvToRgb(h, s, v)
return rgb ? rgbToHex(rgb.r, rgb.g, rgb.b) : null;
}
export const hexToHsv = (hex) => {
const rgb = hexToRgb(hex)
return rgb ? rgbToHsv(rgb.r, rgb.g, rgb.b) : null;
}
const rgbToHex = (r, g, b) => {
if (typeof r === 'object') {
const args = r
r = args.r; g = args.g; b = args.b;
}
r = Math.round(r).toString(16)
g = Math.round(g).toString(16)
b = Math.round(b).toString(16)
r = r.length === 1 ? '0' + r : r
g = g.length === 1 ? '0' + g : g
b = b.length === 1 ? '0' + b : b
return '#' + r + g + b
}