UNPKG

@vtaits/react-color-picker

Version:
677 lines (667 loc) 16.8 kB
// src/ColorPicker.tsx import cx2 from "classnames"; import { Children, Component as Component2 } from "react"; // src/defaultColor.ts var DEFAULT_COLOR = "red"; // src/utils/common.ts import { Component, createRef } from "react"; // src/utils/color.ts import tinycolor from "tinycolor2"; function toColor(color) { return tinycolor(color); } function fromRatio(color) { return tinycolor.fromRatio(color); } function toHsv(color) { return toColor(color).toHsv(); } // src/utils/toStringValue.ts function toStringValue(color) { if (typeof color === "string") { throw new Error("color cannot be string"); } const newColor = toColor({ ...color }); return newColor.toRgb().a === 1 ? newColor.toHexString() : newColor.toRgbString(); } // src/utils/common.ts var baseInitialState = { top: 0, left: 0, mouseDown: null }; var baseDefaultProps = { inPicker: false, onMouseDown: void 0, onDrag: void 0, onChange: void 0 }; function getEventInfo([clientX, clientY], region) { const x = clientX - region.left; const y = clientY - region.top; return { x, y, width: region.width, height: region.height }; } var BaseComponent = class extends Component { static defaultProps = baseDefaultProps; hsv = null; state = baseInitialState; rootRef = createRef(); updateColor(_) { return { h: 0, s: 0, v: 0, a: 0 }; } getColors(hsv) { const { inPicker } = this.props; const first = inPicker ? hsv : toStringValue(hsv); const args = [first]; if (!inPicker) { args.push({ ...hsv }); } return args; } onStartMove = (clientX, clientY) => { const rootNode = this.rootRef.current; if (!rootNode) { throw new Error("root ref is not provided"); } const rect = rootNode.getBoundingClientRect(); const info = getEventInfo([clientX, clientY], rect); const config = { initialPoint: info, minLeft: 0, maxLeft: rect.width }; const onMouseMove = (dragEvent) => { const dragInfo = getEventInfo( [dragEvent.clientX, dragEvent.clientY], rect ); const newHsv = this.updateColor(dragInfo); this.handleDrag( { ...config, diff: { left: dragInfo.x - info.x, top: dragInfo.y - info.y } }, newHsv ); }; const onTouchMove = (event) => { event.preventDefault(); const firstTouch = event.touches[0]; if (!firstTouch) { return; } const dragInfo = getEventInfo( [firstTouch.clientX, firstTouch.clientY], rect ); const newHsv = this.updateColor(dragInfo); this.handleDrag( { ...config, diff: { left: dragInfo.x - info.x, top: dragInfo.y - info.y } }, newHsv ); }; document.addEventListener("mousemove", onMouseMove); document.addEventListener("touchmove", onTouchMove); const onEnd = () => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("touchmove", onTouchMove); document.removeEventListener("mouseup", onEnd); document.removeEventListener("touchend", onEnd); }; document.addEventListener("mouseup", onEnd); document.addEventListener("touchend", onEnd); this.updateColor(info); this.handleMouseDown(config); }; onMouseDown = (event) => { event.preventDefault(); this.onStartMove(event.clientX, event.clientY); }; onTouchStart = (event) => { event.preventDefault(); const firstTouch = event.touches[0]; if (!firstTouch) { return; } this.onStartMove(firstTouch.clientX, firstTouch.clientY); }; handleMouseDown = (config) => { const { onMouseDown } = this.props; if (!this.hsv) { throw new Error("HSV is not setted"); } if (onMouseDown) { onMouseDown(...this.getColors(this.hsv)); } this.handleDrag(config, this.hsv); }; handleUpdate(config) { const diff = config.diff || { top: 0, left: 0 }; const { initialPoint } = config; if (initialPoint) { let left = initialPoint.x + diff.left; const top = initialPoint.y + diff.top; left = Math.max(left, config.minLeft); left = Math.min(left, config.maxLeft); this.setState({ top, left, mouseDown: { x: left, y: top, width: initialPoint.width, height: initialPoint.height } }); } } handleDrag(config, hsv) { const { onDrag } = this.props; this.handleUpdate(config); if (onDrag) { onDrag(...this.getColors(hsv)); } } handleDrop(config, hsv) { const { onChange } = this.props; this.handleUpdate(config); this.setState({ mouseDown: null }); if (onChange) { onChange(...this.getColors(hsv)); } } }; // src/utils/toColorValue.ts function toColorValue(value) { if (typeof value === "string") { return toHsv(value); } return { h: value.h, s: value.s, v: value.v, a: value.a }; } // src/utils/validate.ts function validate(point) { const { height, width } = point; if (point.x < 0) { point.x = 0; } if (point.x >= width) { point.x = width; } if (point.y < 0) { point.y = 0; } if (point.y >= height) { point.y = height; } return point; } // src/HueSpectrum.tsx import { jsx } from "react/jsx-runtime"; var POINTER_SIZE = 3; var HueSpectrum = class extends BaseComponent { static defaultProps = { ...baseDefaultProps, value: null, height: 300, width: 30, pointerSize: POINTER_SIZE, defaultColor: DEFAULT_COLOR }; state = { ...baseInitialState, h: 0 }; mounted = false; isComponentMounted() { return this.mounted; } componentDidMount() { this.mounted = true; this.updateDragPositionIf(); } updateDragPositionIf() { if (!this.props.height) { this.setState({}); } } getDragPosition() { const { height, pointerSize = POINTER_SIZE } = this.props; if (!height && !this.isComponentMounted()) { return null; } const computedHeight = height || this.rootRef.current?.getBoundingClientRect().height; if (typeof computedHeight !== "number") { throw new Error("cannot determine the height"); } const size = pointerSize; if (!this.hsv) { throw new Error("HSV is not setted"); } const pos = Math.round(this.hsv.h * computedHeight / 360); const diff = Math.round(size / 2); return pos - diff; } updateColor(point) { const newPoint = validate(point); if (!this.hsv) { throw new Error("HSV is not setted"); } this.hsv.h = newPoint.y * 360 / newPoint.height; const newHsv = { ...this.hsv }; let newH; if (this.hsv.h !== 0) { newH = this.hsv.h; } newH = this.hsv.h !== 0 ? this.hsv.h : 0; this.setState({ h: newH }); this.hsv = newHsv; return newHsv; } render() { const { style, value, defaultColor, pointerSize = POINTER_SIZE, height, width } = this.props; const { h } = this.state; this.hsv = toColorValue(value || defaultColor || DEFAULT_COLOR); if (h === 360 && !this.hsv.h) { this.hsv.h = 360; } const rootStyle = { ...style }; if (height) { rootStyle.height = height; } if (width) { rootStyle.width = width; } const dragStyle = { height: pointerSize }; const dragPos = this.getDragPosition(); if (dragPos !== null) { dragStyle.top = dragPos; dragStyle.display = "block"; } return /* @__PURE__ */ jsx( "div", { className: "react-color-picker__hue-spectrum", style: rootStyle, onMouseDown: this.onMouseDown, onTouchStart: this.onTouchStart, role: "button", ref: this.rootRef, tabIndex: 0, children: /* @__PURE__ */ jsx("div", { className: "react-color-picker__hue-drag", style: dragStyle, children: /* @__PURE__ */ jsx("div", { className: "react-color-picker__hue-inner" }) }) } ); } }; // src/SaturationSpectrum.tsx import cx from "classnames"; import { jsx as jsx2, jsxs } from "react/jsx-runtime"; var DEFAULT_POINTER_SIZE = 7; var getSaturationForPoint = (point) => point.x / point.width; var getColorValueForPoint = (point) => (point.height - point.y) / point.height; var prepareBackgroundColor = (color) => { const hsv = color; const col = fromRatio({ h: hsv.h % 360 / 360, s: 1, v: 1 }); return col.toRgbString(); }; var SaturationSpectrum = class extends BaseComponent { static defaultProps = { ...baseDefaultProps, value: null, height: 300, width: 300, pointerSize: DEFAULT_POINTER_SIZE, defaultColor: DEFAULT_COLOR, isSaturationSpectrum: true }; mounted = false; componentDidMount() { this.mounted = true; this.updateDragPositionIf(); } isComponentMounted() { return this.mounted; } updateDragPositionIf() { if (!this.props.height || !this.props.width) { this.setState({}); } } getDragPosition() { const { value, pointerSize = DEFAULT_POINTER_SIZE } = this.props; let { width, height } = this.props; const { mouseDown } = this.state; const sizeDefined = typeof width === "number" && typeof height === "number"; if (!sizeDefined && !this.isComponentMounted()) { return null; } if (!sizeDefined) { const rootNode = this.rootRef.current; if (!rootNode) { throw new Error("root ref is not provided"); } const rect = rootNode.getBoundingClientRect(); height = height || rect.height; width = width || rect.width; } if (!this.hsv) { throw new Error("HSV is not setted"); } if (typeof width !== "number" || typeof height !== "number") { throw new Error("size is not defined"); } let x = this.hsv.s * width; const y = height - this.hsv.v * height; const size = pointerSize; const diff = Math.floor(size / 2); if (value && mouseDown && !Number.isNaN(mouseDown.x)) { ({ x } = mouseDown); } return { left: x - diff, top: y - diff }; } prepareProps(thisProps) { const props = { ...thisProps }; const color = props.value || props.defaultColor || DEFAULT_COLOR; props.color = color; this.hsv = toColorValue(color); props.style = this.prepareStyle(props); props.className = cx( thisProps.className, "react-color-picker__saturation-spectrum" ); return props; } prepareStyle(props) { const style = { ...props.style }; if (props.height) { style.height = props.height; } if (props.width) { style.width = props.width; } if (!this.hsv) { throw new Error("HSV is not setted"); } style.backgroundColor = prepareBackgroundColor(this.hsv); return style; } render() { const { pointerSize = DEFAULT_POINTER_SIZE } = this.props; const props = this.prepareProps(this.props); const dragStyle = { width: pointerSize, height: pointerSize }; const dragPos = this.getDragPosition(); if (dragPos) { dragStyle.top = dragPos.top; dragStyle.left = dragPos.left; dragStyle.display = "block"; } return /* @__PURE__ */ jsxs( "div", { className: props.className, style: props.style, onMouseDown: this.onMouseDown, onTouchStart: this.onTouchStart, role: "button", ref: this.rootRef, tabIndex: 0, children: [ /* @__PURE__ */ jsx2("div", { className: "react-color-picker__saturation-white", children: /* @__PURE__ */ jsx2("div", { className: "react-color-picker__saturation-black" }) }), /* @__PURE__ */ jsx2("div", { className: "react-color-picker__saturation-drag", style: dragStyle, children: /* @__PURE__ */ jsx2("div", { className: "react-color-picker__saturation-inner" }) }) ] } ); } updateColor(point) { const newPoint = validate(point); if (!this.hsv) { throw new Error("HSV is not setted"); } this.hsv.s = getSaturationForPoint(newPoint); this.hsv.v = getColorValueForPoint(newPoint); const newHsv = { ...this.hsv }; this.hsv = newHsv; return newHsv; } }; // src/ColorPicker.tsx import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; var ColorPicker = class extends Component2 { static defaultProps = { className: "", hueStyle: {}, onDrag: () => { }, onChange: () => { }, value: void 0, defaultColor: DEFAULT_COLOR, hueHeight: void 0, hueMargin: 10, hueWidth: 30, saturationWidth: 300, saturationHeight: 300, children: null }; constructor(props) { super(props); this.state = { dragHue: null }; } handleSaturationChange = (color) => { this.handleChange(color); }; handleHueChange = (color) => { this.handleChange(color); }; handleHueDrag = (hsv) => { if (typeof hsv === "string") { throw new Error("color cannot be string"); } this.setState({ dragHue: hsv.h }); this.handleDrag(hsv); }; handleSaturationDrag = (hsv) => { this.handleDrag(hsv); }; handleHueMouseDown = (hsv) => { if (typeof hsv === "string") { throw new Error("color cannot be string"); } this.setState({ dragHue: hsv.h }); }; handleSaturationMouseDown = (hsv) => { if (typeof hsv === "string") { throw new Error("color cannot be string"); } this.setState({ dragHue: hsv.h }); }; handleDrag(color) { const { onDrag } = this.props; if (!onDrag) { return; } onDrag(toStringValue(color), color); } handleChange(color) { if (typeof color === "string") { throw new Error("color cannot be string"); } const { onChange } = this.props; if (!onChange) { return; } this.setState({ dragHue: null }); const newColor = { ...color }; const value = toStringValue(newColor); onChange(value, newColor); } render() { const { props } = this; const { className: propsClassName, hueStyle: propsHueStyle, hueHeight, hueMargin, hueWidth, defaultColor, value: propsValue, saturationHeight, saturationWidth, onChange, onDrag, ...divProps } = props; const { dragHue } = this.state; const className = cx2(propsClassName, "cp react-color-picker"); const hueStyle = { ...propsHueStyle, marginLeft: hueMargin }; const value = toColorValue(propsValue || defaultColor || DEFAULT_COLOR); const { children } = props; let hueSpectrumProps = {}; let saturationSpectrumProps = {}; if (children) { for (const child of Children.toArray(children)) { if (!child || !child.type) { continue; } const childElement = child; switch (childElement.type) { case SaturationSpectrum: saturationSpectrumProps = childElement.props; break; case HueSpectrum: hueSpectrumProps = childElement.props; break; default: break; } } } const saturationConfig = { onDrag: this.handleSaturationDrag, onChange: this.handleSaturationChange, onMouseDown: this.handleSaturationMouseDown, ...saturationSpectrumProps }; if (saturationConfig.width === void 0) { saturationConfig.width = saturationWidth; } if (saturationConfig.height === void 0) { saturationConfig.height = saturationHeight; } saturationConfig.inPicker = true; const hueConfig = { onDrag: this.handleHueDrag, onChange: this.handleHueChange, onMouseDown: this.handleHueMouseDown, style: hueStyle, ...hueSpectrumProps }; if (hueConfig.width === void 0) { hueConfig.width = hueWidth; } if (hueConfig.height === void 0) { hueConfig.height = hueHeight || saturationHeight; } hueConfig.inPicker = true; if (dragHue) { value.h = dragHue; } saturationConfig.value = { ...value }; hueConfig.value = { ...value }; return /* @__PURE__ */ jsxs2("div", { ...divProps, className, children: [ /* @__PURE__ */ jsx3(SaturationSpectrum, { ...saturationConfig }), /* @__PURE__ */ jsx3(HueSpectrum, { ...hueConfig }) ] }); } }; export { ColorPicker, HueSpectrum, SaturationSpectrum }; //# sourceMappingURL=index.js.map