UNPKG

react-bezier-curve-editor

Version:
627 lines (616 loc) 22.9 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // node_modules/classnames/index.js var require_classnames = __commonJS({ "node_modules/classnames/index.js"(exports, module) { (function() { "use strict"; var hasOwn = {}.hasOwnProperty; function classNames() { var classes = []; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; var argType = typeof arg; if (argType === "string" || argType === "number") { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { var inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === "object") { if (arg.toString === Object.prototype.toString) { for (var key in arg) { if (hasOwn.call(arg, key) && arg[key]) { classes.push(key); } } } else { classes.push(arg.toString()); } } } return classes.join(" "); } if (typeof module !== "undefined" && module.exports) { classNames.default = classNames; module.exports = classNames; } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) { define("classnames", [], function() { return classNames; }); } else { window.classNames = classNames; } })(); } }); // src/utils/bezierCurveParamsFromSizeAndValue.ts function bezierCurveParamsFromSizeAndValue(size, value) { return { startCoordinate: getStartPointCoordinate(size, value), endCoordinate: getEndPointCoordinate(size, value), startBezierHandle: getStartHandleCoordinate(size, value), endBezierHandle: getEndHandleCoordinate(size, value) }; } function getStartPointCoordinate(size, value) { return [size * value[0], size * (1 - value[1])]; } function getStartHandleCoordinate(size, value) { return [size * value[2], size * (1 - value[3])]; } function getEndPointCoordinate(size, value) { return [size * value[6], size * (1 - value[7])]; } function getEndHandleCoordinate(size, value) { return [size * value[4], size * (1 - value[5])]; } // src/components/BezierCurveEditor/BezierCurveEditor.tsx var import_classnames2 = __toESM(require_classnames(), 1); import React5 from "react"; // src/components/BezierCurveEditor/BezierCurveEditor.module.css var BezierCurveEditor_default = { root: "BezierCurveEditor_root", wrap: "BezierCurveEditor_wrap", bg: "BezierCurveEditor_bg", plane: "BezierCurveEditor_plane", innerArea: "BezierCurveEditor_innerArea", row: "BezierCurveEditor_row", curve: "BezierCurveEditor_curve", handleLine: "BezierCurveEditor_handleLine", curveLine: "BezierCurveEditor_curveLine", preview: "BezierCurveEditor_preview", "preview-loop": "BezierCurveEditor_preview-loop", handle: "BezierCurveEditor_handle", active: "BezierCurveEditor_active", fixed: "BezierCurveEditor_fixed", start: "BezierCurveEditor_start", end: "BezierCurveEditor_end" }; // src/components/BezierCurveEditor/BezierCurveEditorPlane.tsx import React from "react"; function BezierCurveEditorPlane({ size, outerAreaSize, strokeWidth, innerAreaColor, rowColor }) { return /* @__PURE__ */ React.createElement( "div", { className: BezierCurveEditor_default.plane, style: { top: `${outerAreaSize + strokeWidth}px`, left: `${strokeWidth}px`, width: `${size - strokeWidth}px`, height: `${size}px` } }, /* @__PURE__ */ React.createElement("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ React.createElement("rect", { width: "100", height: "101", className: BezierCurveEditor_default.innerArea, fill: innerAreaColor }), /* @__PURE__ */ React.createElement("g", { className: BezierCurveEditor_default.row, fill: rowColor }, /* @__PURE__ */ React.createElement("rect", { width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "10", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "20", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "30", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "40", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "50", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "60", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "70", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "80", width: "100", height: "5" }), /* @__PURE__ */ React.createElement("rect", { y: "90", width: "100", height: "5" }))) ); } // src/components/BezierCurveEditor/BezierCurveEditorCurve.tsx import React2 from "react"; function BezierCurveEditorCurve({ value, size, strokeWidth, outerAreaSize, handleLineColor, curveLineColor, previewState }) { const { startCoordinate, endCoordinate, startBezierHandle, endBezierHandle } = bezierCurveParamsFromSizeAndValue( size, value ); const svgWidth = size + strokeWidth * 2; const svgHeight = size + strokeWidth * 2 + outerAreaSize * 2; return /* @__PURE__ */ React2.createElement( "svg", { className: BezierCurveEditor_default.curve, fill: "none", width: svgWidth, height: svgHeight, viewBox: `0 0 ${svgWidth} ${svgHeight}` }, /* @__PURE__ */ React2.createElement("g", { transform: `translate(${strokeWidth}, ${outerAreaSize + strokeWidth})` }, /* @__PURE__ */ React2.createElement( "line", { className: BezierCurveEditor_default.handleLine, stroke: handleLineColor, strokeWidth: "1", strokeLinecap: "round", x1: startCoordinate[0], y1: startCoordinate[1], x2: startBezierHandle[0], y2: startBezierHandle[1] } ), /* @__PURE__ */ React2.createElement( "line", { className: BezierCurveEditor_default.handleLine, stroke: handleLineColor, strokeWidth: "1", strokeLinecap: "round", x1: endCoordinate[0], y1: endCoordinate[1], x2: endBezierHandle[0], y2: endBezierHandle[1] } ), /* @__PURE__ */ React2.createElement( "path", { className: BezierCurveEditor_default.curveLine, stroke: curveLineColor, strokeWidth, strokeLinecap: "round", d: `M${startCoordinate} C${startBezierHandle} ${endBezierHandle} ${endCoordinate}` } ), previewState !== "hidden" && /* @__PURE__ */ React2.createElement( "circle", { className: BezierCurveEditor_default.preview, r: 8, cx: 0, cy: 0, strokeWidth, style: { offsetPath: `path('M${startCoordinate} C${startBezierHandle} ${endBezierHandle} ${endCoordinate}')`, animationTimingFunction: `cubic-bezier(${value})`, animationPlayState: previewState } } )) ); } // src/components/BezierCurveEditor/BezierCurveEditorEndPoints.tsx var import_classnames = __toESM(require_classnames(), 1); import React3 from "react"; function BezierCurveEditorEndPoints(_a) { var _b = _a, { size, value, outerAreaSize, strokeWidth, handleLineColor, fixedHandleColor, activeClassName } = _b, props = __objRest(_b, [ "size", "value", "outerAreaSize", "strokeWidth", "handleLineColor", "fixedHandleColor", "activeClassName" ]); const { startCoordinate, endCoordinate } = bezierCurveParamsFromSizeAndValue(size, value); if (props.isEditable !== true) { return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement( "span", { className: (0, import_classnames.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.fixed), style: { top: `${startCoordinate[1] + outerAreaSize + strokeWidth}px`, left: `${startCoordinate[0] + strokeWidth}px`, borderColor: handleLineColor, backgroundColor: fixedHandleColor } } ), /* @__PURE__ */ React3.createElement( "span", { className: (0, import_classnames.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.fixed), style: { top: `${endCoordinate[1] + outerAreaSize + strokeWidth}px`, left: `${endCoordinate[0] + strokeWidth}px`, borderColor: handleLineColor, backgroundColor: fixedHandleColor } } )); } const { movingEndPoint, movingStartPoint, handleEndPointPointerLeaveOrUp, handleEndPointPointerMove, handleEndPointStartMoving, handleStartPointPointerLeaveOrUp, handleStartPointPointerMove, handleStartPointStartMoving } = props; return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement( "button", { type: "button", className: (0, import_classnames.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.fixed, { [BezierCurveEditor_default.active]: movingStartPoint, [activeClassName]: !!activeClassName && movingStartPoint }), style: { top: `${startCoordinate[1] + outerAreaSize + strokeWidth}px`, left: `${startCoordinate[0] + strokeWidth}px`, borderColor: handleLineColor, backgroundColor: fixedHandleColor }, onPointerDown: handleStartPointStartMoving, onPointerMove: handleStartPointPointerMove, onPointerUp: handleStartPointPointerLeaveOrUp } ), /* @__PURE__ */ React3.createElement( "button", { type: "button", className: (0, import_classnames.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.fixed, { [BezierCurveEditor_default.active]: movingEndPoint, [activeClassName]: !!activeClassName && movingEndPoint }), style: { top: `${endCoordinate[1] + outerAreaSize + strokeWidth}px`, left: `${endCoordinate[0] + strokeWidth}px`, borderColor: handleLineColor, backgroundColor: fixedHandleColor }, onPointerDown: handleEndPointStartMoving, onPointerMove: handleEndPointPointerMove, onPointerUp: handleEndPointPointerLeaveOrUp } )); } // src/components/BezierCurveEditor/hooks.ts import React4 from "react"; function useHandleState(moveHandle2, handleMoveStart) { const handlePositionRef = React4.useRef({ x: 0, y: 0 }); const [movingHandle, setMovingHandle] = React4.useState(false); const handlePointerStartMoving = React4.useCallback( (event) => { if (movingHandle) return; event.preventDefault(); setMovingHandle(true); handleMoveStart(); const startX = event.clientX; const startY = event.clientY; handlePositionRef.current = { x: startX, y: startY }; event.currentTarget.setPointerCapture(event.pointerId); }, [movingHandle, handleMoveStart] ); const handlePointerMove = React4.useCallback( (event) => { if (movingHandle) { const x = event.clientX; const y = event.clientY; moveHandle2(handlePositionRef.current, { x, y }); } }, [moveHandle2, movingHandle] ); const handlePointerLeaveOrUp = React4.useCallback( (event) => { event.currentTarget.releasePointerCapture(event.pointerId); setMovingHandle(false); }, [setMovingHandle] ); return [movingHandle, { handlePointerLeaveOrUp, handlePointerMove, handlePointerStartMoving }]; } // src/components/BezierCurveEditor/BezierCurveEditor.tsx var defaultProps = { size: 200, outerAreaSize: 50, strokeWidth: 2, value: [0.4, 0, 1, 0.6] // easeIn }; function BezierCurveEditor(_a) { var _b = _a, { size = defaultProps.size, strokeWidth = defaultProps.strokeWidth, outerAreaSize = defaultProps.outerAreaSize, value: valueProp = defaultProps.value, innerAreaColor, outerAreaColor, rowColor, handleLineColor, curveLineColor, fixedHandleColor, startHandleColor, endHandleColor, enablePreview, className, startHandleClassName, endHandleClassName, fixedPointActiveClassName, startHandleActiveClassName, endHandleActiveClassName } = _b, props = __objRest(_b, [ "size", "strokeWidth", "outerAreaSize", "value", "innerAreaColor", "outerAreaColor", "rowColor", "handleLineColor", "curveLineColor", "fixedHandleColor", "startHandleColor", "endHandleColor", "enablePreview", "className", "startHandleClassName", "endHandleClassName", "fixedPointActiveClassName", "startHandleActiveClassName", "endHandleActiveClassName" ]); const initialValueRef = React5.useRef( valueProp.length === 4 ? [0, 0, ...valueProp, 1, 1] : valueProp ); const value = React5.useMemo(() => { return valueProp.length === 4 ? [0, 0, ...valueProp, 1, 1] : valueProp; }, [valueProp]); const updateValueRef = React5.useCallback(() => { initialValueRef.current = [...value]; }, [value]); const moveStartHandle = React5.useCallback( (start, movement) => { const nextValue = moveHandle(initialValueRef.current, size, [2, 3], start, movement); const clampedValue = clampValue(outerAreaSize, size, nextValue); if (props.onChange && props.allowNodeEditing) props.onChange(clampedValue); if (props.onChange && props.allowNodeEditing !== true) props.onChange(clampedValue.slice(2, 6)); }, [size, outerAreaSize, props.onChange, props.allowNodeEditing] ); const [ movingStartHandle, { handlePointerLeaveOrUp: handleStartHandlePointerLeaveOrUp, handlePointerMove: handleStartHandlePointerMove, handlePointerStartMoving: handleStartHandleStartMoving } ] = useHandleState(moveStartHandle, updateValueRef); const moveEndHandle = React5.useCallback( (start, movement) => { const nextValue = moveHandle(initialValueRef.current, size, [4, 5], start, movement); const clampedValue = clampValue(outerAreaSize, size, nextValue); if (props.onChange && props.allowNodeEditing) props.onChange(clampedValue); if (props.onChange && props.allowNodeEditing !== true) props.onChange(clampedValue.slice(2, 6)); }, [size, outerAreaSize, props.onChange, props.allowNodeEditing] ); const [ movingEndHandle, { handlePointerLeaveOrUp: handleEndHandlePointerLeaveOrUp, handlePointerMove: handleEndHandlePointerMove, handlePointerStartMoving: handleEndHandleStartMoving } ] = useHandleState(moveEndHandle, updateValueRef); const moveStartPoint = React5.useCallback( (start, movement) => { const nextValue = moveHandle(initialValueRef.current, size, [0, 1], start, movement); const clampedValue = clampValue(outerAreaSize, size, nextValue); if (props.onChange && props.allowNodeEditing) props.onChange(clampedValue); if (props.onChange && props.allowNodeEditing !== true) props.onChange(clampedValue.slice(2, 6)); }, [size, outerAreaSize, props.onChange, props.allowNodeEditing] ); const [ movingStartPoint, { handlePointerLeaveOrUp: handleStartPointPointerLeaveOrUp, handlePointerMove: handleStartPointPointerMove, handlePointerStartMoving: handleStartPointStartMoving } ] = useHandleState(moveStartPoint, updateValueRef); const moveEndPoint = React5.useCallback( (start, movement) => { const nextValue = moveHandle(initialValueRef.current, size, [6, 7], start, movement); const clampedValue = clampValue(outerAreaSize, size, nextValue); if (props.onChange && props.allowNodeEditing) props.onChange(clampedValue); if (props.onChange && props.allowNodeEditing !== true) props.onChange(clampedValue.slice(2, 6)); }, [size, outerAreaSize, props.onChange, props.allowNodeEditing] ); const [ movingEndPoint, { handlePointerLeaveOrUp: handleEndPointPointerLeaveOrUp, handlePointerMove: handleEndPointPointerMove, handlePointerStartMoving: handleEndPointStartMoving } ] = useHandleState(moveEndPoint, updateValueRef); const { startBezierHandle, endBezierHandle } = bezierCurveParamsFromSizeAndValue(size, value); return /* @__PURE__ */ React5.createElement("div", { className: (0, import_classnames2.default)(BezierCurveEditor_default.root, className) }, /* @__PURE__ */ React5.createElement("div", { className: BezierCurveEditor_default.wrap }, /* @__PURE__ */ React5.createElement( "div", { role: "presentation", className: BezierCurveEditor_default.bg, style: { left: `${strokeWidth}px`, width: `${size - strokeWidth}px`, backgroundColor: outerAreaColor } } ), /* @__PURE__ */ React5.createElement( BezierCurveEditorPlane, { size, outerAreaSize, strokeWidth, innerAreaColor, rowColor } ), /* @__PURE__ */ React5.createElement( BezierCurveEditorCurve, { value, size, strokeWidth, outerAreaSize, handleLineColor, curveLineColor, previewState: !enablePreview ? "hidden" : movingStartHandle || movingEndHandle ? "paused" : "running" } ), /* @__PURE__ */ React5.createElement( BezierCurveEditorEndPoints, { size, value, outerAreaSize, strokeWidth, handleLineColor, fixedHandleColor, isEditable: props.allowNodeEditing, activeClassName: fixedPointActiveClassName, movingEndPoint, movingStartPoint, handleStartPointStartMoving, handleStartPointPointerMove, handleStartPointPointerLeaveOrUp, handleEndPointStartMoving, handleEndPointPointerMove, handleEndPointPointerLeaveOrUp } ), /* @__PURE__ */ React5.createElement( "button", { type: "button", className: (0, import_classnames2.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.start, startHandleClassName, { [BezierCurveEditor_default.active]: movingStartHandle, [startHandleActiveClassName]: !!startHandleActiveClassName && movingStartHandle }), style: { top: `${startBezierHandle[1] + outerAreaSize + strokeWidth}px`, left: `${startBezierHandle[0] + strokeWidth}px`, color: startHandleColor, backgroundColor: startHandleColor }, onPointerDown: handleStartHandleStartMoving, onPointerMove: handleStartHandlePointerMove, onPointerUp: handleStartHandlePointerLeaveOrUp } ), /* @__PURE__ */ React5.createElement( "button", { type: "button", className: (0, import_classnames2.default)(BezierCurveEditor_default.handle, BezierCurveEditor_default.end, endHandleClassName, { [BezierCurveEditor_default.active]: movingEndHandle, [endHandleActiveClassName]: !!endHandleActiveClassName && movingEndHandle }), style: { top: `${endBezierHandle[1] + outerAreaSize + strokeWidth}px`, left: `${endBezierHandle[0] + strokeWidth}px`, color: endHandleColor, backgroundColor: endHandleColor }, onPointerDown: handleEndHandleStartMoving, onPointerMove: handleEndHandlePointerMove, onPointerUp: handleEndHandlePointerLeaveOrUp } ))); } function clampValue(outerAreaSize, size, value) { const nextValue = [...value]; const allowedOuterValue = outerAreaSize / size; nextValue[0] = Math.max(0, Math.min(1, nextValue[0])); nextValue[1] = Math.max(0, Math.min(1, nextValue[1])); nextValue[2] = Math.max(0, Math.min(1, nextValue[2])); nextValue[4] = Math.max(0, Math.min(1, nextValue[4])); nextValue[6] = Math.max(0, Math.min(1, nextValue[6])); nextValue[7] = Math.max(0, Math.min(1, nextValue[7])); nextValue[3] = Math.max(-allowedOuterValue, Math.min(1 + allowedOuterValue, nextValue[3])); nextValue[5] = Math.max(-allowedOuterValue, Math.min(1 + allowedOuterValue, nextValue[5])); return nextValue; } function moveHandle(value, size, targetIndices, start, { x, y }) { const nextValue = [...value]; const [xIndex, yIndex] = targetIndices; const relXMoved = (x - start.x) / size; const relYMoved = (y - start.y) / size; nextValue[xIndex] = nextValue[xIndex] + relXMoved; nextValue[yIndex] = nextValue[yIndex] - relYMoved; return nextValue; } export { BezierCurveEditor, bezierCurveParamsFromSizeAndValue }; /*! Bundled license information: classnames/index.js: (*! Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames *) */