UNPKG

reanimated-color-picker

Version:
402 lines (392 loc) 13.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true, }); exports.Panel3 = Panel3; 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 _PanelCore = require('../PanelCore'); var _styles = require('../../../styles'); var _Thumb = _interopRequireDefault(require('../../Thumb/Thumb')); var _utils = require('../../../utils'); var _Panel3Context = require('./Panel3Context'); 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 [Panel3](https://alabsi91.github.io/reanimated-color-picker/api/panels/panel3/) */ function Panel3({ renderCenterLine = false, centerChannel = 'saturation', gestures = [], style = {}, rotate = 0, children, ...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 boundedThumb = props.boundedThumb ?? ctx.boundedThumb; const renderThumb = props.renderThumb ?? ctx.renderThumb; const thumbStyle = props.thumbStyle ?? ctx.thumbStyle ?? {}; const thumbScaleAnimationValue = props.thumbScaleUpValue ?? ctx.thumbScaleAnimationValue; const thumbScaleAnimationDuration = props.thumbScaleUpDuration ?? ctx.thumbScaleAnimationDuration; const thumbInnerStyle = props.thumbInnerStyle ?? ctx.thumbInnerStyle ?? {}; const adaptSpectrum = props.adaptSpectrum ?? ctx.adaptSpectrum; const isGestureActive = (0, _reactNativeReanimated.useSharedValue)(false); const width = (0, _reactNativeReanimated.useSharedValue)(0); const handleScale = (0, _reactNativeReanimated.useSharedValue)(1); const lastHslSaturationValue = (0, _reactNativeReanimated.useSharedValue)(0); const borderRadius = 2000; // We need to keep track of the HSL saturation value because, when the luminance is 0 or 100, // converting to/from HSV causes the previous saturation value to be lost. const hsl = (0, _reactNativeReanimated.useDerivedValue)(() => { const hsvColor = { h: hueValue.value, s: saturationValue.value, v: brightnessValue.value, }; const { h, s, l } = _index.default.runOnUI().HSL(hsvColor).object(false); if (l === 100 || l === 0) { return { h, s: lastHslSaturationValue.value, l, }; } lastHslSaturationValue.value = s; return { h, s, l, }; }, [hueValue, saturationValue, brightnessValue]); const centerChannelValue = (0, _reactNativeReanimated.useDerivedValue)(() => { if (centerChannel === 'brightness') { return brightnessValue.value; } if (centerChannel === 'hsl-saturation') { return hsl.value.s; } return saturationValue.value; }, [brightnessValue, saturationValue, hsl]); const thumbAnimatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { const center = width.value / 2 - (boundedThumb ? thumbSize / 2 : 0); const rotatedHue = (hueValue.value - rotate) % 360; const distance = (centerChannelValue.value / 100) * (width.value / 2 - (boundedThumb ? thumbSize / 2 : 0)); const angle = (rotatedHue * Math.PI) / 180; const posY = width.value - (Math.sin(angle) * distance + center) - (boundedThumb ? thumbSize : thumbSize / 2); const posX = width.value - (Math.cos(angle) * distance + center) - (boundedThumb ? thumbSize : thumbSize / 2); return { transform: [ { translateX: posX, }, { translateY: posY, }, { scale: handleScale.value, }, { rotate: rotatedHue + 90 + 'deg', }, ], }; }, [width, centerChannelValue, hueValue, handleScale, boundedThumb, thumbSize, rotate]); const spectrumStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { if (!adaptSpectrum) { return {}; } if (centerChannel === 'brightness') { return { backgroundColor: `rgba(255, 255, 255, ${1 - saturationValue.value / 100})`, }; } if (centerChannel === 'hsl-saturation') { if (hsl.value.l < 50) { return { backgroundColor: `rgba(0, 0, 0, ${1 - hsl.value.l / 50})`, }; } return { backgroundColor: `rgba(255, 255, 255, ${(hsl.value.l - 50) / 50})`, }; } return { backgroundColor: `rgba(0, 0, 0, ${1 - brightnessValue.value / 100})`, }; }, [saturationValue, brightnessValue, hsl, adaptSpectrum, centerChannel]); const centerLineStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { if (!renderCenterLine) { return {}; } const lineThickness = 1; const center = width.value / 2 - (boundedThumb ? thumbSize / 2 : 0); const rotatedHue = (hueValue.value - rotate) % 360; const distance = (centerChannelValue.value / 100) * center; const angle = (rotatedHue + 180) % 360; // reversed angle return { top: (width.value - lineThickness) / 2, left: (width.value - distance) / 2, height: lineThickness, width: distance, transform: [ { rotate: angle + 'deg', }, { translateX: distance / 2, }, ], }; }, [width, hueValue, centerChannelValue, renderCenterLine, boundedThumb, thumbSize, rotate]); 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; } isGestureActive.value = true; handleScale.value = (0, _reactNativeReanimated.withTiming)(thumbScaleAnimationValue, { duration: thumbScaleAnimationDuration, }); }; const onUpdate = (newXValue, newYValue) => { 'worklet'; if (hueValue.value === newXValue && centerChannelValue.value === newYValue) { return; } hueValue.value = newXValue; if (centerChannel === 'hsl-saturation') { // To prevent locking this slider when the luminance is 0 or 100, // this should not affect the resulting color, as the value will be rounded. const l = hsl.value.l === 0 ? 0.01 : hsl.value.l === 100 ? 99.99 : hsl.value.l; const { s, v } = _index.default .runOnUI() .HSV({ h: hsl.value.h, s: newYValue, l, }) .object(false); saturationValue.value = s; brightnessValue.value = v; } // Vertical channel is brightness else if (centerChannel === 'brightness') { brightnessValue.value = newYValue; } // Vertical channel is saturation else { saturationValue.value = newYValue; } onGestureChange(); }; const onGestureUpdate = ({ x, y }) => { 'worklet'; if (!isGestureActive.value) return; const center = (width.value - (boundedThumb ? thumbSize : 0)) / 2; const dx = center - x + (boundedThumb ? thumbSize / 2 : 0); const dy = center - y + (boundedThumb ? thumbSize / 2 : 0); const radius = (0, _utils.clamp)(Math.sqrt(dx * dx + dy * dy), center); // distance from center const theta = Math.atan2(dy, dx) * (180 / Math.PI); // [0 - 180] range const angle = theta < 0 ? 360 + theta : theta; // [0 - 360] range const radiusPercent = radius / center; const newHueValue = (angle + rotate) % 360; const newChannelValue = radiusPercent * 100; onUpdate(newHueValue, newChannelValue); }; const onEnd = () => { 'worklet'; isGestureActive.value = false; handleScale.value = (0, _reactNativeReanimated.withTiming)(1, { duration: thumbScaleAnimationDuration, }); onGestureEnd(); }; const getAdaptiveColor = hsva => { 'worklet'; if (adaptSpectrum) { return hsva; } switch (centerChannel) { case 'saturation': return { h: hsva.h, s: hsva.s, v: 100, }; case 'brightness': return { h: hsva.h, s: 100, v: hsva.v, }; case 'hsl-saturation': { const { h, s } = hsl.value; return { h, s, l: 50, }; } default: return hsva; } }; return /*#__PURE__*/ _react.default.createElement( _Panel3Context.Panel3ContextProvider, { value: { width, adaptSpectrum, centerChannel, centerChannelValue, thumbShape, thumbColor, thumbStyle, thumbInnerStyle, renderThumb, boundedThumb, renderCenterLine, thumbSize, rotate, }, }, /*#__PURE__*/ _react.default.createElement( _PanelCore.PanelCore, { style: [ _styles.styles.panelContainer, style, { position: 'relative', aspectRatio: 1, borderWidth: 0, padding: 0, borderRadius, }, ], label: props.accessibilityLabel ?? `Hue and ${centerChannel} wheel slider`, hint: props.accessibilityHint ?? `Double tap to switch between hue and ${centerChannel}`, labelX: 'Hue', currentXValue: hueValue, maxXValue: 360, labelY: centerChannel, currentYValue: centerChannelValue, width: width, gestures: gestures, onGestureUpdate: onGestureUpdate, onBegin: onBegin, onUpdate: onUpdate, onEnd: onEnd, }, /*#__PURE__*/ _react.default.createElement( _reactNative.ImageBackground, { source: require('../../../assets/circularHue.png'), style: _styles.styles.panelImage, imageStyle: { transform: [ { rotate: -rotate + 'deg', }, ], }, resizeMode: 'stretch', 'aria-hidden': true, }, /*#__PURE__*/ _react.default.createElement( _utils.ConditionalRendering, { if: adaptSpectrum && centerChannel === 'brightness', }, /*#__PURE__*/ _react.default.createElement(_reactNativeReanimated.default.View, { style: [ { borderRadius, }, spectrumStyle, _reactNative.StyleSheet.absoluteFill, ], }), ), /*#__PURE__*/ _react.default.createElement(_reactNative.Image, { source: require('../../../assets/blackRadial.png'), style: _styles.styles.panelImage, tintColor: centerChannel === 'saturation' ? '#fff' : centerChannel === 'hsl-saturation' ? '#888' : undefined, resizeMode: 'stretch', }), /*#__PURE__*/ _react.default.createElement( _utils.ConditionalRendering, { if: adaptSpectrum && (centerChannel === 'saturation' || centerChannel === 'hsl-saturation'), }, /*#__PURE__*/ _react.default.createElement(_reactNativeReanimated.default.View, { style: [ { borderRadius, }, spectrumStyle, _reactNative.StyleSheet.absoluteFill, ], }), ), ), /*#__PURE__*/ _react.default.createElement( _utils.ConditionalRendering, { if: renderCenterLine, }, /*#__PURE__*/ _react.default.createElement(_reactNativeReanimated.default.View, { style: [_styles.styles.panel3Line, centerLineStyle], 'aria-hidden': true, }), ), children, /*#__PURE__*/ _react.default.createElement(_Thumb.default, { thumbShape: thumbShape, thumbSize: thumbSize, thumbColor: thumbColor, renderThumb: renderThumb, innerStyle: thumbInnerStyle, thumbAnimatedStyle: thumbAnimatedStyle, style: thumbStyle, getAdaptiveColor: getAdaptiveColor, }), ), ); }