@vtaits/react-color-picker
Version:
React Color Picker
677 lines (667 loc) • 16.8 kB
JavaScript
// 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