UNPKG

@storybook/addon-ondevice-controls

Version:

Display storybook controls on your device.

360 lines (359 loc) 15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TriangleColorPicker = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const react_native_1 = require("react-native"); const tinycolor2_1 = __importDefault(require("tinycolor2")); const utils_1 = require("./utils"); function makeRotationKey(props, angle) { const { rotationHackFactor = 100 } = props; if (rotationHackFactor < 1) { return undefined; } const key = Math.floor(angle * rotationHackFactor); return `r${key}`; } class TriangleColorPicker extends react_1.PureComponent { _layout; _pageX; _pageY; _changingHColor; _isRTL; _pickerResponder; pickerContainer = (0, react_1.createRef)(); constructor(props) { super(props); const state = { color: { h: 0, s: 1, v: 1 }, pickerSize: null, }; if (props.oldColor) { state.color = (0, tinycolor2_1.default)(props.oldColor).toHsv(); } if (props.defaultColor) { state.color = (0, tinycolor2_1.default)(props.defaultColor).toHsv(); } this.state = state; this._layout = { width: 0, height: 0, x: 0, y: 0 }; this._pageX = 0; this._pageY = 0; this._isRTL = react_native_1.I18nManager.isRTL; this._pickerResponder = (0, utils_1.createPanResponder)({ onStart: ({ x, y }) => { const { s, v } = this._computeColorFromTriangle({ x, y }); this._changingHColor = s > 1 || s < 0 || v > 1 || v < 0; this._handleColorChange({ x, y }); }, onMove: this._handleColorChange, }); } getColor() { return (0, tinycolor2_1.default)(this._getColor()).toHexString(); } _handleColorChange = ({ x, y }) => { if (this._changingHColor) { this._handleHColorChange({ x, y }); } else { this._handleSVColorChange({ x, y }); } }; _getColor() { const passedColor = typeof this.props.color === 'string' ? (0, tinycolor2_1.default)(this.props.color).toHsv() : this.props.color; return passedColor || this.state.color; } _onColorSelected = () => { const { onColorSelected } = this.props; const color = (0, tinycolor2_1.default)(this._getColor()).toHexString(); if (onColorSelected) { onColorSelected(color); } }; _onOldColorSelected = () => { const { oldColor, onOldColorSelected } = this.props; const color = (0, tinycolor2_1.default)(oldColor); this.setState({ color: color.toHsv() }); if (onOldColorSelected) { onOldColorSelected(color.toHexString()); } }; _onSValueChange = (s) => { const { h, v } = this._getColor(); this._onColorChange({ h, s, v }); }; _onVValueChange = (v) => { const { h, s } = this._getColor(); this._onColorChange({ h, s, v }); }; _onColorChange = (color) => { this.setState({ color }); if (this.props.onColorChange) { this.props.onColorChange(color); } }; _onLayout = (l) => { this._layout = l.nativeEvent.layout; const { width, height } = this._layout; const pickerSize = Math.min(width, height); if (this.state.pickerSize !== pickerSize) { this.setState({ pickerSize }); } react_native_1.InteractionManager.runAfterInteractions(() => { if (this.pickerContainer.current) { this.pickerContainer.current.measure((_x, _y, _width, _height, pageX, pageY) => { this._pageX = pageX; this._pageY = pageY; }); } }); }; _computeHValue(x, y) { const mx = this.state.pickerSize / 2; const my = this.state.pickerSize / 2; const dx = x - mx; const dy = y - my; const rad = Math.atan2(dx, dy) + Math.PI + Math.PI / 2; return ((rad * 180) / Math.PI) % 360; } _hValueToRad(deg) { const rad = (deg * Math.PI) / 180; return rad - Math.PI - Math.PI / 2; } _handleHColorChange = ({ x, y }) => { const { s, v } = this._getColor(); const marginLeft = (this._layout.width - this.state.pickerSize) / 2; const marginTop = (this._layout.height - this.state.pickerSize) / 2; const relativeX = x - this._pageX - marginLeft; const relativeY = y - this._pageY - marginTop; const h = this._computeHValue(relativeX, relativeY); this._onColorChange({ h, s, v }); }; _handleSVColorChange = ({ x, y }) => { const { h, s: rawS, v: rawV } = this._computeColorFromTriangle({ x, y }); const s = Math.min(Math.max(0, rawS), 1); const v = Math.min(Math.max(0, rawV), 1); this._onColorChange({ h, s, v }); }; _normalizeTriangleTouch(s, v, sRatio) { const CORNER_ZONE_SIZE = 0.12; const NORMAL_MARGIN = 0.1; const CORNER_MARGIN = 0.05; let margin = NORMAL_MARGIN; const posNS = v > 0 ? 1 - (1 - s) * sRatio : 1 - s * sRatio; const negNS = v > 0 ? s * sRatio : (1 - s) * sRatio; const ns = s > 1 ? posNS : negNS; const rightCorner = s > 1 - CORNER_ZONE_SIZE && v > 1 - CORNER_ZONE_SIZE; const leftCorner = ns < 0 + CORNER_ZONE_SIZE && v > 1 - CORNER_ZONE_SIZE; const topCorner = ns < 0 + CORNER_ZONE_SIZE && v < 0 + CORNER_ZONE_SIZE; if (rightCorner) { return { s, v }; } if (leftCorner || topCorner) { margin = CORNER_MARGIN; } let s1 = s; let v1 = v; s1 = s1 < 0 && ns > 0 - margin ? 0 : s1; s1 = s1 > 1 && ns < 1 + margin ? 1 : s1; v1 = v1 < 0 && v1 > 0 - margin ? 0 : v1; v1 = v1 > 1 && v1 < 1 + margin ? 1 : v1; return { s: s1, v: v1 }; } _computeColorFromTriangle({ x, y }) { const { pickerSize } = this.state; const { triangleHeight, triangleWidth } = getPickerProperties(pickerSize); const left = pickerSize / 2 - triangleWidth / 2; const top = pickerSize / 2 - (2 * triangleHeight) / 3; const marginLeft = (this._layout.width - this.state.pickerSize) / 2; const marginTop = (this._layout.height - this.state.pickerSize) / 2; const relativeX = x - this._pageX - marginLeft - left; const relativeY = y - this._pageY - marginTop - top; const { h } = this._getColor(); const deg = (h - 330 + 360) % 360; const rad = (deg * Math.PI) / 180; const center = { x: triangleWidth / 2, y: (2 * triangleHeight) / 3, }; const rotated = (0, utils_1.rotatePoint)({ x: relativeX, y: relativeY }, rad, center); const line = (triangleWidth * rotated.y) / triangleHeight; const margin = triangleWidth / 2 - ((triangleWidth / 2) * rotated.y) / triangleHeight; const s = (rotated.x - margin) / line; const v = rotated.y / triangleHeight; const normalized = this._normalizeTriangleTouch(s, v, line / triangleHeight); return { h, s: normalized.s, v: normalized.v }; } render() { const { pickerSize } = this.state; const { oldColor, style } = this.props; const color = this._getColor(); const { h } = color; const angle = this._hValueToRad(h); const selectedColor = (0, tinycolor2_1.default)(color).toHexString(); const indicatorColor = (0, tinycolor2_1.default)({ h, s: 1, v: 1 }).toHexString(); const computed = makeComputedStyles({ pickerSize: pickerSize, // selectedColor, selectedColorHsv: color, indicatorColor, // oldColor, angle, isRTL: this._isRTL, }); const rotationHack = makeRotationKey(this.props, angle); return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: style, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { onLayout: this._onLayout, ref: this.pickerContainer, style: styles.pickerContainer, children: !pickerSize ? null : ((0, jsx_runtime_1.jsxs)(react_native_1.View, { children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.triangleContainer, computed.triangleContainer], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.triangleUnderlayingColor, computed.triangleUnderlayingColor] }), (0, jsx_runtime_1.jsx)(react_native_1.Image, { style: [/* styles.triangleImage, */ computed.triangleImage], source: require('./resources/hsv_triangle_mask.png') })] }, rotationHack), (0, jsx_runtime_1.jsxs)(react_native_1.View, { ...this._pickerResponder.panHandlers, style: [/* styles.picker, */ computed.picker], collapsable: false, children: [(0, jsx_runtime_1.jsx)(react_native_1.Image, { source: require('./resources/color-circle.png'), resizeMode: "contain", style: [styles.pickerImage] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.pickerIndicator, computed.pickerIndicator], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.pickerIndicatorTick, computed.pickerIndicatorTick] }) }, rotationHack), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.svIndicator, computed.svIndicator] })] })] })) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.colorPreviews, computed.colorPreviews], children: [oldColor && ((0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.colorPreview, { backgroundColor: oldColor }], onPress: this._onOldColorSelected, activeOpacity: 0.7 })), (0, jsx_runtime_1.jsx)(react_native_1.TouchableOpacity, { style: [styles.colorPreview, { backgroundColor: selectedColor }], onPress: this._onColorSelected, activeOpacity: 0.7 })] })] })); } } exports.TriangleColorPicker = TriangleColorPicker; function getPickerProperties(pickerSize) { const indicatorPickerRatio = 42 / 510; // computed from picker image const originalIndicatorSize = indicatorPickerRatio * pickerSize; const indicatorSize = originalIndicatorSize; const pickerPadding = originalIndicatorSize / 3; const triangleSize = pickerSize - 6 * pickerPadding; const triangleRadius = triangleSize / 2; const triangleHeight = (triangleRadius * 3) / 2; const triangleWidth = 2 * triangleRadius * Math.sqrt(3 / 4); return { triangleSize, triangleRadius, triangleHeight, triangleWidth, indicatorPickerRatio, indicatorSize, pickerPadding, }; } const makeComputedStyles = ({ indicatorColor, angle, pickerSize, selectedColorHsv, isRTL, }) => { const { triangleSize, triangleHeight, triangleWidth, indicatorSize, pickerPadding } = getPickerProperties(pickerSize); const indicatorRadius = pickerSize / 2 - indicatorSize / 2 - pickerPadding; const mx = pickerSize / 2; const my = pickerSize / 2; const dx = Math.cos(angle) * indicatorRadius; const dy = Math.sin(angle) * indicatorRadius; const triangleTop = pickerPadding * 3; const triangleLeft = pickerPadding * 3; const triangleAngle = -angle + Math.PI / 3; const markerColor = 'rgba(0,0,0,0.8)'; const { s, v, h } = selectedColorHsv; const svIndicatorSize = 18; const svY = v * triangleHeight; const margin = triangleWidth / 2 - v * (triangleWidth / 2); const svX = s * (triangleWidth - 2 * margin) + margin; const svIndicatorMarginLeft = (pickerSize - triangleWidth) / 2; const svIndicatorMarginTop = (pickerSize - (4 * triangleHeight) / 3) / 2; const deg = (h - 330 + 360) % 360; // starting angle is 330 due to comfortable calculation const rad = (deg * Math.PI) / 180; const center = { x: pickerSize / 2, y: pickerSize / 2 }; const notRotatedPoint = { x: svIndicatorMarginTop + svY, y: svIndicatorMarginLeft + svX, }; const svIndicatorPoint = (0, utils_1.rotatePoint)(notRotatedPoint, rad, center); return { picker: { padding: pickerPadding, width: pickerSize, height: pickerSize, }, pickerIndicator: { top: mx + dx - indicatorSize / 2, [isRTL ? 'right' : 'left']: my + dy - indicatorSize / 2, width: indicatorSize, height: indicatorSize, transform: [ { rotate: `${-angle}rad`, }, ], }, pickerIndicatorTick: { height: indicatorSize / 2, backgroundColor: markerColor, }, svIndicator: { top: svIndicatorPoint.x - svIndicatorSize / 2, [isRTL ? 'right' : 'left']: svIndicatorPoint.y - svIndicatorSize / 2, width: svIndicatorSize, height: svIndicatorSize, borderRadius: svIndicatorSize / 2, borderColor: markerColor, }, triangleContainer: { width: triangleSize, height: triangleSize, transform: [ { rotate: `${triangleAngle}rad`, }, ], top: triangleTop, left: triangleLeft, }, triangleImage: { width: triangleWidth, height: triangleHeight, }, triangleUnderlayingColor: { left: (triangleSize - triangleWidth) / 2, borderLeftWidth: triangleWidth / 2, borderRightWidth: triangleWidth / 2, borderBottomWidth: triangleHeight, borderBottomColor: indicatorColor, }, colorPreviews: { height: pickerSize * 0.1, }, }; }; const styles = react_native_1.StyleSheet.create({ pickerContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', }, pickerImage: { flex: 1, width: null, height: null, }, pickerIndicator: { position: 'absolute', alignItems: 'center', justifyContent: 'center', }, triangleContainer: { position: 'absolute', alignItems: 'center', }, triangleUnderlayingColor: { position: 'absolute', top: 0, width: 0, height: 0, backgroundColor: 'transparent', borderStyle: 'solid', borderLeftColor: 'transparent', borderRightColor: 'transparent', }, pickerAlignment: { alignItems: 'center', }, svIndicator: { position: 'absolute', borderWidth: 4, }, pickerIndicatorTick: { width: 5, }, colorPreviews: { flexDirection: 'row', }, colorPreview: { flex: 1, }, });