reanimated-color-picker
Version:
A Pure JavaScript Color Picker for React Native
290 lines (279 loc) • 9.78 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true,
});
exports.LuminanceCircular = LuminanceCircular;
var _react = _interopRequireDefault(require('react'));
var _reactNative = require('react-native');
var _reactNativeReanimated = _interopRequireWildcard(require('react-native-reanimated'));
var _index = _interopRequireDefault(require('../../../colorKit/index'));
var _AppContext = _interopRequireDefault(require('../../../AppContext'));
var _CircularSliderCore = require('../CircularSliderCore');
var _styles = require('../../../styles');
var _Thumb = _interopRequireDefault(require('../../Thumb/Thumb'));
function _interopRequireWildcard(e, t) {
if ('function' == typeof WeakMap)
var r = new WeakMap(),
n = new WeakMap();
return (_interopRequireWildcard = function (e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = { __proto__: null, default: e };
if (null === e || ('object' != typeof e && 'function' != typeof e)) return f;
if ((o = t ? n : r)) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (const t in e)
'default' !== t &&
{}.hasOwnProperty.call(e, t) &&
((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set)
? o(f, t, i)
: (f[t] = e[t]));
return f;
})(e, t);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
/** @see [LuminanceCircular](https://alabsi91.github.io/reanimated-color-picker/api/sliders/hsl/luminance-circular-slider/) */
function LuminanceCircular({ children, gestures = [], style = {}, containerStyle = {}, rotate = 0, ...props }) {
const { hueValue, saturationValue, brightnessValue, onGestureChange, onGestureEnd, ...ctx } = (0, _AppContext.default)();
const thumbShape = props.thumbShape ?? ctx.thumbShape;
const thumbSize = props.thumbSize ?? ctx.thumbSize;
const thumbColor = props.thumbColor ?? ctx.thumbColor;
const renderThumb = props.renderThumb ?? ctx.renderThumb;
const thumbStyle = props.thumbStyle ?? ctx.thumbStyle ?? {};
const sliderThickness = props.sliderThickness ?? ctx.sliderThickness;
const thumbScaleAnimationValue = props.thumbScaleAnimationValue ?? ctx.thumbScaleAnimationValue;
const thumbScaleAnimationDuration = props.thumbScaleAnimationDuration ?? ctx.thumbScaleAnimationDuration;
const adaptSpectrum = props.adaptSpectrum ?? ctx.adaptSpectrum;
const thumbInnerStyle = props.thumbInnerStyle ?? ctx.thumbInnerStyle ?? {};
const isGestureActive = (0, _reactNativeReanimated.useSharedValue)(false);
const width = (0, _reactNativeReanimated.useSharedValue)(0);
const borderRadius = (0, _reactNativeReanimated.useSharedValue)(0);
const borderRadiusStyle = (0, _reactNativeReanimated.useAnimatedStyle)(
() => ({
borderRadius: borderRadius.value,
}),
[borderRadius],
);
const handleScale = (0, _reactNativeReanimated.useSharedValue)(1);
const thumbSide = (0, _reactNativeReanimated.useSharedValue)(0); // to determine in which side of the circle the thumb is
// HSL saturation is mathematically undefined (collapses to 0) when luminance is 0 or 100,
// because those represent pure black and white regardless of saturation.
// This ref holds the last valid saturation so we can restore it when luminance moves away from the boundary.
const hslSaturationValue = (0, _reactNativeReanimated.useSharedValue)(0);
const hslLuminanceValue = (0, _reactNativeReanimated.useSharedValue)(0);
const hsl = (0, _reactNativeReanimated.useDerivedValue)(() => {
const currentHsvColor = {
h: hueValue.value,
s: saturationValue.value,
v: brightnessValue.value,
};
const { h, s, l } = _index.default.runOnUI().HSL(currentHsvColor).object(false);
hslLuminanceValue.value = l;
// At l=0 (black) or l=100 (white), the conversion loses saturation information.
// Substitute the last known saturation so it's restored when luminance changes.
if (l === 100 || l === 0) {
return {
h,
s: hslSaturationValue.value,
l,
};
}
hslSaturationValue.value = s;
return {
h,
s,
l,
};
}, [hueValue, saturationValue, brightnessValue, hslLuminanceValue]);
const thumbAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
const center = width.value / 2;
const distance = (width.value - sliderThickness) / 2;
const angle = (hsl.value.l / 100) * 180 + thumbSide.value * 180;
const mirroredAngle = ((thumbSide.value === 1 ? 180 - angle : angle) - rotate) % 360;
const posY = width.value - (Math.sin((mirroredAngle * Math.PI) / 180) * distance + center) - thumbSize / 2;
const posX = width.value - (Math.cos((mirroredAngle * Math.PI) / 180) * distance + center) - thumbSize / 2;
return {
transform: [
{
translateX: posX,
},
{
translateY: posY,
},
{
scale: handleScale.value,
},
{
rotate: mirroredAngle + 90 + 'deg',
},
],
};
}, [width, hsl, handleScale, thumbSide, rotate]);
const activeColorStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
return {
backgroundColor: `hsl(${hsl.value.h}, ${adaptSpectrum ? hsl.value.s : 100}%, ${50}%)`,
borderRadius: width.value / 2,
};
}, [hsl, width, adaptSpectrum]);
const clipViewStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
return {
position: 'absolute',
width: width.value - sliderThickness * 2,
height: width.value - sliderThickness * 2,
borderRadius: width.value / 2,
};
}, [width, sliderThickness]);
const onBegin = ({ x, y }) => {
'worklet';
const radius = width.value / 2;
const dx = radius - x;
const dy = radius - y;
const pressDistance = Math.sqrt(dx * dx + dy * dy);
// Check if the press is outside the circle
if (pressDistance > radius) {
isGestureActive.value = false;
return;
}
// check if the press inside the circle (not on the actual slider)
const innerR = width.value / 2 - sliderThickness;
if (pressDistance < innerR) {
isGestureActive.value = false;
return;
}
isGestureActive.value = true;
handleScale.value = (0, _reactNativeReanimated.withTiming)(thumbScaleAnimationValue, {
duration: thumbScaleAnimationDuration,
});
};
const onUpdate = newValue => {
'worklet';
if (newValue === hsl.value.l) {
return;
}
const { s, v } = _index.default
.runOnUI()
.HSV({
h: hsl.value.h,
s: hsl.value.s,
l: newValue,
})
.object(false);
saturationValue.value = s;
brightnessValue.value = v;
onGestureChange();
};
const onGestureUpdate = ({ x, y }) => {
'worklet';
if (!isGestureActive.value) return;
const center = width.value / 2;
const dx = center - x;
const dy = center - y;
const theta = (Math.atan2(dy, dx) + rotate * (Math.PI / 180)) * (180 / Math.PI); // [0 - 180] range
const angle = theta < 0 ? 360 + theta : theta; // [0 - 360] range
const mirroredAngle = angle <= 180 ? angle : 360 - angle;
const newLuminanceValue = (mirroredAngle / 180) * 100;
thumbSide.value = angle <= 180 ? 0 : 1;
onUpdate(newLuminanceValue);
};
const onEnd = () => {
'worklet';
isGestureActive.value = false;
handleScale.value = (0, _reactNativeReanimated.withTiming)(1, {
duration: thumbScaleAnimationDuration,
});
onGestureEnd();
};
const getAdaptiveColor = () => {
'worklet';
const { h, l } = hsl.value;
return {
h,
s: 100,
l,
};
};
return /*#__PURE__*/ _react.default.createElement(
_CircularSliderCore.CircularSliderCore,
{
style: [
_styles.styles.panelContainer,
{
justifyContent: 'center',
alignItems: 'center',
},
style,
{
position: 'relative',
aspectRatio: 1,
borderWidth: 0,
padding: 0,
},
borderRadiusStyle,
],
label: props.accessibilityLabel ?? 'Luminance circular slider',
hint: props.accessibilityHint,
currentValue: hslLuminanceValue,
width: width,
borderRadius: borderRadius,
gestures: gestures,
onGestureUpdate: onGestureUpdate,
onBegin: onBegin,
onUpdate: onUpdate,
onEnd: onEnd,
},
/*#__PURE__*/ _react.default.createElement(
_reactNativeReanimated.default.View,
{
style: [
_styles.styles.panelImage,
activeColorStyle,
{
transform: [
{
rotate: -rotate + 'deg',
},
],
},
],
'aria-hidden': true,
},
/*#__PURE__*/ _react.default.createElement(_reactNative.Image, {
source: require('../../../assets/angular-luminance.png'),
style: {
width: '100%',
height: '100%',
flex: 1,
},
resizeMode: 'stretch',
}),
),
/*#__PURE__*/ _react.default.createElement(
_reactNativeReanimated.default.View,
{
style: [
clipViewStyle,
{
backgroundColor: '#fff',
},
containerStyle,
],
},
children,
),
/*#__PURE__*/ _react.default.createElement(_Thumb.default, {
thumbShape: thumbShape,
thumbSize: thumbSize,
thumbColor: thumbColor,
renderThumb: renderThumb,
innerStyle: thumbInnerStyle,
thumbAnimatedStyle: thumbAnimatedStyle,
style: thumbStyle,
getAdaptiveColor: getAdaptiveColor,
}),
);
}