UNPKG

react-sketch-ruler

Version:

> In using react, the zoom operation used for page presentation

1 lines 83.6 kB
{"version":3,"file":"index.umd.cjs","sources":["../../../node_modules/.pnpm/react@19.2.0/node_modules/react/cjs/react-jsx-runtime.production.js","../../../node_modules/.pnpm/react@19.2.0/node_modules/react/jsx-runtime.js","../src/sketch-ruler/cornerImg64.ts","../../../node_modules/.pnpm/simple-panzoom@1.0.8/node_modules/simple-panzoom/dist/panzoom.js","../src/sketch-ruler/useLine.ts","../src/canvas-ruler/utils.ts","../src/sketch-ruler/RulerLine.tsx","../src/canvas-ruler/index.tsx","../src/sketch-ruler/RulerWrapper.tsx","../src/sketch-ruler/index.tsx"],"sourcesContent":["/**\n * @license React\n * react-jsx-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\");\nfunction jsxProd(type, config, maybeKey) {\n var key = null;\n void 0 !== maybeKey && (key = \"\" + maybeKey);\n void 0 !== config.key && (key = \"\" + config.key);\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n config = maybeKey.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n ref: void 0 !== config ? config : null,\n props: maybeKey\n };\n}\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.jsx = jsxProd;\nexports.jsxs = jsxProd;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","const eye64 = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAAXNSR0IArs4c6QAAAopJREFUOE/FlE9IVEEcx7+/N9ouds1Mu0QUSFZYdIgoUqQoKPBQHsKozpXE7jbTO/U8xLJvn6usBHWQ6hBFXupSkQeVbh0KJEPp0sH+eLGTsKs77xcj78m0ax0E8cHjzZv5zef3/c33xxA24KENYGJzoEEQbNNaN4Zh2OQ4znwYhr9c1/39vwrXVDo0NNS0tLR0GYB5D64BmAMwzMyvlFKz1es10Hw+f4mZ7wHYBeA9gNdENFepVOaEEM3M3OI4Thczn41gt6WUgQ3+C+r7/h0AWQD3mXnYqPA8L9nQ0HCemduIaFpKOWoAhUJhT6VSuQXgOjP3K6W8GLwKzeVyp4jonR0QBEErM48w8zFLyayUsjX+z+VyHhHdZebTSqkxM78CHRgYOKS1/ghgVErZY214RkQ7ADyRUj72ff8qgCtmXUrZGcf5vv8CwEUhxOF0Ov1pBRpla5dSdseBhUJhpznH6tIsZb1KqacW+BGArUaUXX63UuplHJTNZjuEEONSyhozfd/n6mQ1RkXZL2itz7mu+80EDA4ONi8vL/8AcM2UbikyR2BU9cSmmTU70YqKIAj2hWFo2uenlHK/BRg3Y2aeNO5GyU8S0ZbFxcUuz/NKEXAGQKPjOCcymcyX1dIi8DSAiWQyeaavr68cbSgCuBknYubnQoj+TCYzUywWE6VS6S2ADsdx2gxw1X3L7SNENMbMnwE8qK+vf5NKpRaMaeVyeW8ikfiaSqW+R7BuZr5BRMe11p2u607U9Gk8kc/ntzPzQwCmExYATDLzVBiGE0KIowAOADDf3QA+aK2VDaxRajto3K+rq+tl5nYAzQBamHmeiOYBTGmtR6ph/1Rqg9c73pz7dD1qN0TpHyNQRCUDJXrAAAAAAElFTkSuQmCC`\r\n\r\nconst closeEye64 = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAAAXNSR0IArs4c6QAAAjtJREFUOE/NlD9oFEEUxn9v9ghC0hpRUogIAUmniGAT/5Q2FrETPSNRJILg7RwimI0ox85eQFCEBGIUO1PYpFM0jSConQoBEZGgGPsgl+w+2eM2bC57SopAFqYZ3v7m+977ZoQt+GQLmGxPaBiGgYiMWWvXBHZUGoZhH3BERPYC+4F+4Keq/urt7b1RLpf/ZEBVHa9Wq0HWyg3QKIoGVPU8cA7wgK/pUtXPQJ8xZk+pVBpuNBqXUoUiEvi+P56fzTpo6+SbwHNg1lo7WzTITGEKXFlZeeB53tVCpa3CK8AFa+1cBgvD8LKIXAQOJkkyICJDeYVBEJS6u7s/qeoLa+1o+l9TqXNuBLhmjDlbqVQ+5ICjInIfOBPH8W9jzGCR5YmJiRNxHM+papgqzqDvgSlr7VTernPuO3An3c9bBt74vv+yrdaKyLDv+/1Sq9UGPc97nY9EVuycU2AQOA7cAm4Dr4D5TvVxHB9rKo2iaEFVp621Ln96FEUngaOqGmSxabVqsh3a2h+x1h5qQjNrae/yE4+iaCwDJkky73neTuBZe129Xk+H+BS4l7ZqLVIZ2BhzuFKpvMsDVXXWGPMxFdAedOfcKeCRqj7MYrUhpz09PfXl5eXrectFWXXODQHpOq2qd/95o/JXr6ura3J1dXU6SZIfwKKIHAD2tVYMPBGRx77vN10UXtO85fTkmZmZHUtLSzUR2QXsBhaAL6r6DXhbrVYXi1yss59GqOgub/bN3Z7v6X/tb9Zmp/q/kN8s+lJb8oEAAAAASUVORK5CYII=`\r\n\r\nexport { eye64, closeEye64 }\r\n","/*!simple-panzoom v1.0.72024年8月Sat Aug 10 2024 16:04:04 GMT+0800 (中国标准时间)制作*/\ntypeof window < \"u\" && (window.NodeList && !NodeList.prototype.forEach && (NodeList.prototype.forEach = Array.prototype.forEach), typeof window.CustomEvent != \"function\" && (window.CustomEvent = function(e, r) {\n r = r || { bubbles: !1, cancelable: !1, detail: null };\n var c = document.createEvent(\"CustomEvent\");\n return c.initCustomEvent(e, r.bubbles, r.cancelable, r.detail), c;\n}));\nfunction ct(t, e) {\n let r = t.length;\n for (; r--; )\n if (t[r].pointerId === e.pointerId)\n return r;\n return -1;\n}\nfunction tt(t, e) {\n let r;\n if (e.touches) {\n r = 0;\n for (const c of e.touches)\n c.pointerId = r++, tt(t, c);\n return;\n }\n r = ct(t, e), r > -1 && t.splice(r, 1), t.push(e);\n}\nfunction pt(t, e) {\n if (e.touches) {\n for (; t.length; )\n t.pop();\n return;\n }\n const r = ct(t, e);\n r > -1 && t.splice(r, 1);\n}\nfunction it(t) {\n t = t.slice(0);\n let e = t.pop(), r;\n for (; r = t.pop(); )\n e = {\n clientX: (r.clientX - e.clientX) / 2 + e.clientX,\n clientY: (r.clientY - e.clientY) / 2 + e.clientY\n };\n return e;\n}\nfunction J(t) {\n if (t.length < 2)\n return 0;\n const e = t[0], r = t[1];\n return Math.sqrt(\n Math.pow(Math.abs(r.clientX - e.clientX), 2) + Math.pow(Math.abs(r.clientY - e.clientY), 2)\n );\n}\nlet M = {\n down: \"mousedown\",\n move: \"mousemove\",\n up: \"mouseup mouseleave\"\n};\ntypeof window < \"u\" && (typeof window.PointerEvent == \"function\" ? M = {\n down: \"pointerdown\",\n move: \"pointermove\",\n up: \"pointerup pointerleave pointercancel\"\n} : typeof window.TouchEvent == \"function\" && (M = {\n down: \"touchstart\",\n move: \"touchmove\",\n up: \"touchend touchcancel\"\n}));\nfunction K(t, e, r, c) {\n M[t].split(\" \").forEach((h) => {\n e.addEventListener(\n h,\n r,\n c\n );\n });\n}\nfunction Q(t, e, r) {\n M[t].split(\" \").forEach((c) => {\n e.removeEventListener(c, r);\n });\n}\nconst mt = typeof document < \"u\" && !!document.documentMode;\nlet j;\nfunction gt() {\n return j || (j = document.createElement(\"div\").style);\n}\nconst at = [\"webkit\", \"moz\", \"ms\"], T = {};\nfunction et(t) {\n if (T[t])\n return T[t];\n const e = gt();\n if (t in e)\n return T[t] = t;\n const r = t[0].toUpperCase() + t.slice(1);\n let c = at.length;\n for (; c--; ) {\n const h = `${at[c]}${r}`;\n if (h in e)\n return T[t] = h;\n }\n}\nfunction W(t, e) {\n return parseFloat(e[et(t)]) || 0;\n}\nfunction I(t, e, r = window.getComputedStyle(t)) {\n const c = e === \"border\" ? \"Width\" : \"\";\n return {\n left: W(`${e}Left${c}`, r),\n right: W(`${e}Right${c}`, r),\n top: W(`${e}Top${c}`, r),\n bottom: W(`${e}Bottom${c}`, r)\n };\n}\nfunction x(t, e, r) {\n t.style[et(e)] = r;\n}\nfunction wt(t, e) {\n const r = et(\"transform\");\n x(t, \"transition\", `${r} ${e.duration}ms ${e.easing}`);\n}\nfunction yt(t, { x: e, y: r, scale: c, isSVG: h }, y) {\n if (x(t, \"transform\", `scale(${c}) translate(${e}px, ${r}px)`), h && mt) {\n const f = window.getComputedStyle(t).getPropertyValue(\"transform\");\n t.setAttribute(\"transform\", f);\n }\n}\nfunction D(t) {\n const e = t.parentNode, r = window.getComputedStyle(t), c = window.getComputedStyle(e), h = t.getBoundingClientRect(), y = e.getBoundingClientRect();\n return {\n elem: {\n style: r,\n width: h.width,\n height: h.height,\n top: h.top,\n bottom: h.bottom,\n left: h.left,\n right: h.right,\n margin: I(t, \"margin\", r),\n border: I(t, \"border\", r)\n },\n parent: {\n style: c,\n width: y.width,\n height: y.height,\n top: y.top,\n bottom: y.bottom,\n left: y.left,\n right: y.right,\n padding: I(e, \"padding\", c),\n border: I(e, \"border\", c)\n }\n };\n}\nfunction vt(t) {\n let e = t;\n for (; e && e.parentNode; ) {\n if (e.parentNode === document) return !0;\n e = e.parentNode instanceof ShadowRoot ? e.parentNode.host : e.parentNode;\n }\n return !1;\n}\nfunction bt(t) {\n return (t.getAttribute(\"class\") || \"\").trim();\n}\nfunction xt(t, e) {\n return t.nodeType === 1 && ` ${bt(t)} `.indexOf(` ${e} `) > -1;\n}\nfunction Et(t, e) {\n for (let r = t; r != null; r = r.parentNode)\n if (xt(r, e.excludeClass) || e.exclude.indexOf(r) > -1)\n return !0;\n return !1;\n}\nconst Pt = /^http:[\\w\\.\\/]+svg$/;\nfunction Mt(t) {\n return Pt.test(t.namespaceURI) && t.nodeName.toLowerCase() !== \"svg\";\n}\nfunction At(t) {\n const e = {};\n for (const r in t)\n t.hasOwnProperty(r) && (e[r] = t[r]);\n return e;\n}\nconst st = {\n animate: !1,\n canvas: !1,\n cursor: \"move\",\n disablePan: !1,\n disableZoom: !1,\n disableXAxis: !1,\n disableYAxis: !1,\n duration: 200,\n easing: \"ease-in-out\",\n exclude: [],\n excludeClass: \"panzoom-exclude\",\n handleStartEvent: (t) => {\n t.preventDefault(), t.stopPropagation();\n },\n maxScale: 4,\n minScale: 0.125,\n overflow: \"hidden\",\n panOnlyWhenZoomed: !1,\n pinchAndPan: !1,\n relative: !1,\n setTransform: yt,\n startX: 0,\n startY: 0,\n startScale: 1,\n step: 0.3,\n touchAction: \"none\"\n};\nfunction Ct(t, e) {\n if (!t)\n throw new Error(\"Panzoom requires an element as an argument\");\n if (t.nodeType !== 1)\n throw new Error(\"Panzoom requires an element with a nodeType of 1\");\n if (!vt(t))\n throw new Error(\n \"Panzoom should be called on elements that have been attached to the DOM\"\n );\n e = {\n ...st,\n ...e\n };\n const r = Mt(t), c = t.parentNode;\n c.style.overflow = e.overflow, c.style.userSelect = \"none\", c.style.touchAction = e.touchAction, (e.canvas ? c : t).style.cursor = e.cursor, t.style.userSelect = \"none\", t.style.touchAction = e.touchAction, x(\n t,\n \"transformOrigin\",\n typeof e.origin == \"string\" ? e.origin : r ? \"0 0\" : \"50% 50%\"\n );\n function h() {\n c.style.overflow = \"\", c.style.userSelect = \"\", c.style.touchAction = \"\", c.style.cursor = \"\", t.style.cursor = \"\", t.style.userSelect = \"\", t.style.touchAction = \"\", x(t, \"transformOrigin\", \"\");\n }\n function y(n = {}) {\n for (const i in n)\n n.hasOwnProperty(i) && (e[i] = n[i]);\n (n.hasOwnProperty(\"cursor\") || n.hasOwnProperty(\"canvas\")) && (c.style.cursor = t.style.cursor = \"\", (e.canvas ? c : t).style.cursor = e.cursor), n.hasOwnProperty(\"overflow\") && (c.style.overflow = n.overflow), n.hasOwnProperty(\"touchAction\") && (c.style.touchAction = n.touchAction, t.style.touchAction = n.touchAction);\n }\n let f = 0, p = 0, d = 1, A = !1;\n Y(e.startScale, { animate: !1, force: !0 }), setTimeout(() => {\n H(e.startX, e.startY, { animate: !1, force: !0 });\n });\n function C(n, i, s) {\n if (s.silent)\n return;\n const u = new CustomEvent(n, { detail: i });\n t.dispatchEvent(u);\n }\n function L(n, i, s) {\n const o = { x: f, y: p, scale: d, isSVG: r, originalEvent: s, dimsOut: {} };\n typeof i.animate == \"boolean\" && (i.animate ? wt(t, i) : x(t, \"transition\", \"none\")), i.setTransform(t, o, i);\n function l() {\n const a = D(t);\n o.dimsOut = a, C(n, o, i), C(\"panzoomchange\", o, i);\n }\n return i.animate ? setTimeout(() => {\n l();\n }, i.duration + 50) : requestAnimationFrame(() => {\n l();\n }), o;\n }\n function R(n, i, s, u) {\n const o = { ...e, ...u }, l = { x: f, y: p, opts: o };\n if (!o.force && (o.disablePan || o.panOnlyWhenZoomed && d === o.startScale))\n return l;\n if (n = parseFloat(n), i = parseFloat(i), o.disableXAxis || (l.x = (o.relative ? f : 0) + n), o.disableYAxis || (l.y = (o.relative ? p : 0) + i), o.contain) {\n const a = D(t), g = a.elem.width / d, m = a.elem.height / d, E = g * s, P = m * s, v = (E - g) / 2, b = (P - m) / 2;\n if (o.contain === \"inside\") {\n const k = (-a.elem.margin.left - a.parent.padding.left + v) / s, U = (a.parent.width - E - a.parent.padding.left - a.elem.margin.left - a.parent.border.left - a.parent.border.right + v) / s;\n l.x = Math.max(Math.min(l.x, U), k);\n const G = (-a.elem.margin.top - a.parent.padding.top + b) / s, _ = (a.parent.height - P - a.parent.padding.top - a.elem.margin.top - a.parent.border.top - a.parent.border.bottom + b) / s;\n l.y = Math.max(Math.min(l.y, _), G);\n } else if (o.contain === \"outside\") {\n const k = (-(E - a.parent.width) - a.parent.padding.left - a.parent.border.left - a.parent.border.right + v) / s, U = (v - a.parent.padding.left) / s;\n l.x = Math.max(Math.min(l.x, U), k);\n const G = (-(P - a.parent.height) - a.parent.padding.top - a.parent.border.top - a.parent.border.bottom + b) / s, _ = (b - a.parent.padding.top) / s;\n l.y = Math.max(Math.min(l.y, _), G);\n }\n }\n return o.roundPixels && (l.x = Math.round(l.x), l.y = Math.round(l.y)), l;\n }\n function X(n, i) {\n const s = { ...e, ...i }, u = { scale: d, opts: s };\n if (!s.force && s.disableZoom)\n return u;\n let o = e.minScale, l = e.maxScale;\n if (s.contain) {\n const a = D(t), g = a.elem.width / d, m = a.elem.height / d;\n if (g > 1 && m > 1) {\n const E = a.parent.width - a.parent.border.left - a.parent.border.right, P = a.parent.height - a.parent.border.top - a.parent.border.bottom, v = E / g, b = P / m;\n e.contain === \"inside\" ? l = Math.min(l, v, b) : e.contain === \"outside\" && (o = Math.max(o, v, b));\n }\n }\n return u.scale = Math.min(Math.max(n, o), l), u;\n }\n function H(n, i, s, u) {\n const o = R(n, i, d, s);\n return f !== o.x || p !== o.y ? (f = o.x, p = o.y, L(\"panzoompan\", o.opts, u)) : { x: f, y: p, scale: d, isSVG: r, originalEvent: u };\n }\n function Y(n, i, s) {\n const u = X(n, i), o = u.opts;\n if (!o.force && o.disableZoom)\n return;\n n = u.scale;\n let l = f, a = p;\n if (o.focal) {\n const m = o.focal;\n l = (m.x / n - m.x / d + f * n) / n, a = (m.y / n - m.y / d + p * n) / n;\n }\n const g = R(l, a, n, {\n relative: !1,\n force: !0\n });\n return f = g.x, p = g.y, d = n, L(\"panzoomzoom\", o, s);\n }\n function nt(n, i) {\n const s = { ...e, animate: !0, ...i };\n return Y(d * Math.exp((n ? 1 : -1) * s.step), s);\n }\n function lt(n) {\n return nt(!0, n);\n }\n function dt(n) {\n return nt(!1, n);\n }\n function B(n, i, s, u) {\n const o = D(t), l = {\n width: o.parent.width - o.parent.padding.left - o.parent.padding.right - o.parent.border.left - o.parent.border.right,\n height: o.parent.height - o.parent.padding.top - o.parent.padding.bottom - o.parent.border.top - o.parent.border.bottom\n };\n let a = i.clientX - o.parent.left - o.parent.padding.left - o.parent.border.left - o.elem.margin.left, g = i.clientY - o.parent.top - o.parent.padding.top - o.parent.border.top - o.elem.margin.top;\n r || (a -= o.elem.width / d / 2, g -= o.elem.height / d / 2);\n const m = {\n x: a / l.width * (l.width * n),\n y: g / l.height * (l.height * n)\n };\n return Y(\n n,\n { ...s, animate: !1, focal: m },\n u\n );\n }\n function ut(n, i) {\n n.preventDefault();\n const s = { ...e, ...i, animate: !1 }, o = (n.deltaY === 0 && n.deltaX ? n.deltaX : n.deltaY) < 0 ? 1 : -1, l = X(\n d * Math.exp(o * s.step / 3),\n s\n ).scale;\n return B(l, n, s, n);\n }\n function ft(n) {\n const i = { ...e, animate: !0, force: !0, ...n };\n d = X(i.startScale, i).scale;\n const s = R(i.startX, i.startY, d, i);\n return f = s.x, p = s.y, L(\"panzoomreset\", i);\n }\n let O, z, S, $, rt, N;\n const w = [];\n function V(n) {\n if (Et(n.target, e))\n return;\n tt(w, n), A = !0, e.handleStartEvent(n), O = f, z = p, C(\n \"panzoomstart\",\n { x: f, y: p, scale: d, isSVG: r, originalEvent: n },\n e\n );\n const i = it(w);\n S = i.clientX, $ = i.clientY, rt = d, N = J(w);\n }\n function Z(n) {\n if (!A || O === void 0 || z === void 0 || S === void 0 || $ === void 0)\n return;\n tt(w, n);\n const i = it(w), s = w.length > 1;\n let u = d;\n if (s) {\n N === 0 && (N = J(w));\n const o = J(w) - N;\n u = X(o * e.step / 80 + rt).scale, B(u, i, { animate: !1 }, n);\n }\n (!s || e.pinchAndPan) && H(\n O + (i.clientX - S) / u,\n z + (i.clientY - $) / u,\n {\n animate: !1\n },\n n\n );\n }\n function q(n) {\n w.length === 1 && C(\n \"panzoomend\",\n { x: f, y: p, scale: d, isSVG: r, originalEvent: n },\n e\n ), pt(w, n), A && (A = !1, O = z = S = $ = void 0);\n }\n let F = !1;\n function ot() {\n F || (F = !0, K(\"down\", e.canvas ? c : t, V), K(\"move\", document, Z, { passive: !0 }), K(\"up\", document, q, { passive: !0 }));\n }\n function ht() {\n F = !1, Q(\"down\", e.canvas ? c : t, V), Q(\"move\", document, Z), Q(\"up\", document, q);\n }\n return e.noBind || ot(), {\n bind: ot,\n destroy: ht,\n eventNames: M,\n getPan: () => ({ x: f, y: p }),\n getScale: () => d,\n getOptions: () => At(e),\n handleDown: V,\n handleMove: Z,\n handleUp: q,\n pan: H,\n reset: ft,\n resetStyle: h,\n setOptions: y,\n setStyle: (n, i) => x(t, n, i),\n zoom: Y,\n zoomIn: lt,\n zoomOut: dt,\n zoomToPoint: B,\n zoomWithWheel: ut\n };\n}\nCt.defaultOptions = st;\nexport {\n Ct as default\n};\n","import { useState, MouseEventHandler } from 'react'\r\nimport type { PaletteType, LineType } from '../index-types'\r\ninterface Props {\r\n palette: PaletteType\r\n lockLine: boolean\r\n scale: number\r\n snapThreshold: number\r\n snapsObj: LineType\r\n lines: LineType\r\n canvasHeight: number\r\n canvasWidth: number\r\n rate: number\r\n value?: number\r\n index?: number\r\n handleLine?: (props: LineType) => void\r\n}\r\n\r\nexport default function useLine(props: Props, vertical: boolean) {\r\n const [offsetLine, setOffsetLine] = useState(0)\r\n const [startValue, setStartValue] = useState(0)\r\n /* 记录hover标签 */\r\n const actionStyle = {\r\n backgroundColor: props.palette.hoverBg,\r\n color: props.palette.hoverColor,\r\n [vertical ? 'top' : 'left']: '-8px',\r\n [vertical ? 'left' : 'top']: `${offsetLine + 10}px`\r\n }\r\n\r\n const handleMouseMove: MouseEventHandler<HTMLDivElement> = (event) => {\r\n const offsetX = event.nativeEvent.offsetX\r\n const offsetY = event.nativeEvent.offsetY\r\n setOffsetLine(vertical ? offsetX : offsetY)\r\n }\r\n\r\n const handleMouseDown = (\r\n e: React.MouseEvent<HTMLDivElement | HTMLCanvasElement>,\r\n propValue?: number\r\n ) => {\r\n e.stopPropagation() // 阻止冒泡\r\n return new Promise<void>((resolve) => {\r\n if (props.lockLine) return\r\n const startPosition = vertical ? e.clientY : e.clientX\r\n const initialValue = propValue || startValue\r\n let tempStartValue = initialValue\r\n const moveHandler = (e: MouseEvent) => {\r\n const currentPosition = vertical ? e.clientY : e.clientX\r\n const delta = (currentPosition - startPosition) / props.scale\r\n let nextPos = delta + initialValue\r\n let guidePos = nextPos\r\n const snaps = vertical ? props.snapsObj.h : props.snapsObj.v\r\n const guideSnaps = [...snaps].sort(\r\n (a, b) => Math.abs(guidePos - a) - Math.abs(guidePos - b)\r\n )\r\n if (\r\n guideSnaps.length &&\r\n Math.abs(guideSnaps[0] - nextPos) < props.snapThreshold / props.scale\r\n ) {\r\n guidePos = guideSnaps[0]\r\n nextPos = guidePos\r\n }\r\n tempStartValue = Math.round(nextPos)\r\n setStartValue(Math.round(nextPos))\r\n }\r\n const mouseUpHandler = () => {\r\n document.removeEventListener('mousemove', moveHandler)\r\n handleLineRelease(tempStartValue, props.index)\r\n resolve()\r\n }\r\n\r\n document.addEventListener('mousemove', moveHandler)\r\n document.addEventListener('mouseup', mouseUpHandler, { once: true })\r\n })\r\n }\r\n\r\n const handleLineRelease = (value: number, index?: number) => {\r\n const linesArrs = vertical ? props.lines.h : props.lines.v\r\n const isOutOfRange = checkBoundary(value)\r\n if (!linesArrs) {\r\n return\r\n }\r\n if (isOutOfRange) {\r\n if (typeof index === 'number') {\r\n linesArrs.splice(index, 1)\r\n if (props.handleLine) {\r\n props.handleLine(props.lines)\r\n }\r\n } else {\r\n return // 新增越界,什么也不做\r\n }\r\n } else {\r\n if (typeof index !== 'number') {\r\n linesArrs.push(value)\r\n if (props.handleLine) {\r\n props.handleLine(props.lines)\r\n }\r\n } else {\r\n // 移动修改\r\n linesArrs[index] = value\r\n if (props.handleLine) {\r\n props.handleLine({ ...props.lines, [vertical ? 'h' : 'v']: linesArrs })\r\n }\r\n }\r\n }\r\n }\r\n\r\n const checkBoundary = (value: number) => {\r\n const maxOffset = vertical ? props.canvasHeight : props.canvasWidth\r\n return value < 0 || value > maxOffset\r\n }\r\n\r\n const labelContent = checkBoundary(startValue)\r\n ? '放开删除'\r\n : `${vertical ? 'Y' : 'X'}: ${startValue * props.rate}`\r\n\r\n return {\r\n startValue,\r\n setStartValue,\r\n actionStyle,\r\n labelContent,\r\n handleMouseMove,\r\n handleMouseDown\r\n }\r\n}\r\n","import type { FinalPaletteType } from '../index-types'\r\n// 标尺中每小格代表的宽度(根据scale的不同实时变化)\r\nconst getGridSize = (scale: number) => {\r\n if (scale <= 0.25) return 40\r\n if (scale <= 0.5) return 20\r\n if (scale <= 1) return 10\r\n if (scale <= 2) return 5\r\n if (scale <= 4) return 2\r\n return 1\r\n}\r\n\r\nexport function setLast(\r\n x: number,\r\n value: number,\r\n width: number,\r\n height: number,\r\n ctx: CanvasRenderingContext2D,\r\n isHorizontal?: boolean\r\n) {\r\n if (isHorizontal) {\r\n ctx.moveTo(x, 0)\r\n } else {\r\n ctx.moveTo(0, x)\r\n }\r\n ctx.save()\r\n if (isHorizontal) {\r\n ctx.translate(x + 5, height * 0.2)\r\n } else {\r\n ctx.translate(width * 0.1, x + 32)\r\n }\r\n if (!isHorizontal) ctx.rotate(-Math.PI / 2) // 旋转 -90 度\r\n ctx.fillText(Math.round(value).toString(), 4, 7)\r\n ctx.restore()\r\n if (isHorizontal) {\r\n ctx.lineTo(x, height)\r\n } else {\r\n ctx.lineTo(width, x)\r\n }\r\n ctx.stroke()\r\n ctx.closePath()\r\n ctx.setTransform(1, 0, 0, 1, 0, 0)\r\n}\r\n\r\nexport function drawShadowText(\r\n x: number,\r\n y: number,\r\n num: number,\r\n ctx: CanvasRenderingContext2D,\r\n palette: any,\r\n isHorizontal?: boolean\r\n) {\r\n ctx.fillStyle = palette.fontShadowColor\r\n ctx.strokeStyle = palette.longfgColor\r\n ctx.save()\r\n ctx.translate(x, y)\r\n if (!isHorizontal) ctx.rotate(-Math.PI / 2)\r\n ctx.strokeText(String(num), 0, 0)\r\n ctx.fillText(String(num), 0, 0)\r\n ctx.restore()\r\n}\r\n\r\nexport const drawCanvasRuler = (\r\n ctx: CanvasRenderingContext2D,\r\n start: number,\r\n selectStart: number,\r\n selectLength: number,\r\n options: {\r\n scale: number\r\n width: number\r\n height: number\r\n ratio: number\r\n gridRatio: number\r\n palette: FinalPaletteType\r\n canvasWidth: number\r\n canvasHeight: number\r\n showShadowText: boolean\r\n },\r\n isHorizontal?: boolean //横向为true,纵向缺省\r\n) => {\r\n const { scale, width, height, ratio, palette, gridRatio, showShadowText } = options\r\n const { bgColor, fontColor, shadowColor, longfgColor } = palette\r\n const endNum = isHorizontal ? options.canvasWidth : options.canvasHeight\r\n ctx.setTransform(1, 0, 0, 1, 0, 0) // 还原,否则scale变大后会错乱\r\n // 缩放ctx, 以简化计算\r\n ctx.scale(ratio, ratio)\r\n ctx.clearRect(0, 0, width, height)\r\n // 1. 画标尺底色\r\n ctx.fillStyle = bgColor\r\n ctx.fillRect(0, 0, width, height)\r\n\r\n const gridSize = getGridSize(scale) * gridRatio // 每小格表示的宽度\r\n const gridSize10 = gridSize * 10 // 每大格表示的宽度\r\n const gridPixel10 = gridSize10 * scale\r\n const startValue10 = Math.floor(start / gridSize10) * gridSize10\r\n const offsetX10 = ((startValue10 - start) / gridSize10) * gridPixel10 // 长间隔起点刻度距离ctx原点(start)的px距离\r\n const endValue = start + Math.ceil((isHorizontal ? width : height) / scale) // 终点刻度\r\n // 2. 画阴影\r\n if (selectLength) {\r\n const shadowX = (selectStart - start) * scale // 阴影起点坐标\r\n const shadowWidth = selectLength * scale // 阴影宽度\r\n ctx.fillStyle = shadowColor\r\n\r\n if (isHorizontal) {\r\n ctx.fillRect(shadowX, 0, shadowWidth, height)\r\n } else {\r\n ctx.fillRect(0, shadowX, width, shadowWidth)\r\n }\r\n\r\n // 画阴影文字起始\r\n if (showShadowText) {\r\n if (isHorizontal) {\r\n drawShadowText(shadowX, height * 0.4, Math.round(selectStart), ctx, palette, isHorizontal)\r\n const shadowEnd = (selectStart + selectLength - start) * scale\r\n drawShadowText(\r\n shadowEnd,\r\n height * 0.4,\r\n Math.round(selectStart + selectLength),\r\n ctx,\r\n palette,\r\n isHorizontal\r\n )\r\n } else {\r\n drawShadowText(width * 0.4, shadowX, Math.round(selectStart), ctx, palette, isHorizontal)\r\n const shadowEnd = (selectStart + selectLength - start) * scale\r\n drawShadowText(\r\n width * 0.4,\r\n shadowEnd,\r\n Math.round(selectStart + selectLength),\r\n ctx,\r\n palette,\r\n isHorizontal\r\n )\r\n }\r\n }\r\n }\r\n // 3. 画刻度和文字(因为刻度遮住了阴影)\r\n ctx.beginPath()\r\n ctx.fillStyle = fontColor\r\n ctx.strokeStyle = longfgColor\r\n\r\n // 绘制长间隔和文字\r\n for (let value = startValue10, count = 0; value < endValue; value += gridSize10, count++) {\r\n const x = offsetX10 + count * gridPixel10 + 0.5 // prevent canvas 1px line blurry\r\n if ((value - gridSize10 < endNum && value > endNum) || value == endNum) {\r\n // 如果尾数画最后一个刻度\r\n const xl = offsetX10 + count * gridPixel10 + 0.5 + (endNum - value) * scale\r\n setLast(xl, endNum, width, height, ctx, isHorizontal)\r\n return\r\n }\r\n\r\n if (value >= 0 && value <= endNum) {\r\n if (value == 0) {\r\n if (isHorizontal) {\r\n ctx.moveTo(x, 0)\r\n ctx.lineTo(x, height)\r\n } else {\r\n ctx.moveTo(0, x)\r\n ctx.lineTo(width, x)\r\n }\r\n } else {\r\n if (isHorizontal) {\r\n ctx.moveTo(x, 20)\r\n ctx.lineTo(x, height / 1.3)\r\n } else {\r\n ctx.moveTo(20, x)\r\n ctx.lineTo(width / 1.3, x)\r\n }\r\n }\r\n\r\n ctx.save()\r\n // 影响文字位置\r\n if (value == 0) {\r\n if (isHorizontal) {\r\n ctx.translate(x - 15, height * 0.2)\r\n } else {\r\n ctx.translate(width * 0.3, x - 5)\r\n }\r\n } else {\r\n if (isHorizontal) {\r\n ctx.translate(x - 12, height * 0.05)\r\n } else {\r\n ctx.translate(width * 0.05, x + 12)\r\n }\r\n }\r\n\r\n if (!isHorizontal) {\r\n ctx.rotate(-Math.PI / 2) // 旋转 -90 度\r\n }\r\n\r\n // 如果最后一个大刻度挨着最后一个刻度, 不画文字\r\n if (endNum - value > gridSize10 / 2) {\r\n if (\r\n !showShadowText ||\r\n selectLength == 0 ||\r\n (Math.abs(value - selectStart) > gridSize10 / 2 &&\r\n Math.abs(value - (selectStart + selectLength)) > gridSize10 / 2)\r\n ) {\r\n ctx.fillText(value.toString(), 4, 9)\r\n }\r\n }\r\n\r\n ctx.restore()\r\n }\r\n }\r\n ctx.stroke()\r\n ctx.closePath()\r\n}\r\n\r\nexport function debounce(func: () => void, wait: number) {\r\n let timeout: ReturnType<typeof setTimeout>\r\n return function () {\r\n clearTimeout(timeout)\r\n timeout = setTimeout(() => {\r\n func()\r\n }, wait)\r\n }\r\n}\r\n","import { useState, useEffect, useCallback, useMemo, memo } from 'react'\r\nimport useLine from './useLine'\r\nimport { debounce } from '../canvas-ruler/utils'\r\nimport type { LineProps } from '../index-types'\r\nconst LineComponent = ({\r\n scale,\r\n rate,\r\n palette,\r\n index,\r\n start,\r\n vertical,\r\n value,\r\n canvasWidth,\r\n canvasHeight,\r\n lines,\r\n isShowReferLine,\r\n snapThreshold,\r\n snapsObj,\r\n lockLine,\r\n handleLine\r\n}: LineProps) => {\r\n const [showLabel, setShowLabel] = useState(false)\r\n const [isInscale, setIsInscale] = useState(false)\r\n const { startValue, setStartValue, actionStyle, handleMouseMove, handleMouseDown, labelContent } =\r\n useLine(\r\n {\r\n palette,\r\n scale,\r\n snapsObj,\r\n lines,\r\n canvasWidth,\r\n canvasHeight,\r\n snapThreshold,\r\n lockLine,\r\n index,\r\n value,\r\n rate,\r\n handleLine\r\n },\r\n vertical\r\n )\r\n\r\n const showLine = useMemo(() => startValue >= start, [start, startValue, vertical])\r\n type PointerEvents = 'auto' | 'none'\r\n const combinedStyle = useMemo(() => {\r\n const { lineType, lockLineColor, lineColor } = palette\r\n const borderColor = lockLine ? lockLineColor : (lineColor ?? 'black')\r\n const pointerEvents: PointerEvents = lockLine || isInscale ? 'none' : 'auto'\r\n const cursor = isShowReferLine && !lockLine ? (vertical ? 'ns-resize' : 'ew-resize') : 'default'\r\n const borderProperty = vertical ? 'borderTop' : 'borderLeft'\r\n const offsetPx = (startValue - start) * scale\r\n\r\n return {\r\n [borderProperty]: `1px ${lineType} ${borderColor}`,\r\n pointerEvents: pointerEvents,\r\n cursor,\r\n [vertical ? 'top' : 'left']: `${offsetPx}px`\r\n }\r\n }, [palette, lockLine, isInscale, isShowReferLine, vertical, startValue, start, scale])\r\n\r\n const deactivateAfterDelay = useCallback(\r\n debounce(() => {\r\n setIsInscale(false)\r\n }, 1000),\r\n []\r\n )\r\n\r\n useEffect(() => {\r\n setIsInscale(true)\r\n deactivateAfterDelay()\r\n }, [scale])\r\n\r\n useEffect(() => {\r\n setStartValue(value)\r\n }, [value])\r\n const handleMouseenter = (e: React.MouseEvent) => {\r\n e.stopPropagation() // 阻止冒泡\r\n if (!lockLine) {\r\n setShowLabel(true)\r\n }\r\n }\r\n\r\n return (\r\n <div\r\n style={combinedStyle}\r\n className=\"line\"\r\n onMouseEnter={handleMouseenter}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => setShowLabel(false)}\r\n onMouseDown={handleMouseDown}\r\n hidden={!showLine}\r\n >\r\n <div className=\"action\" style={actionStyle}>\r\n {showLabel && <span className=\"value\">{labelContent}</span>}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nexport default memo(LineComponent)\r\n","import { memo, useRef, useEffect, useState, MouseEvent, useMemo } from 'react'\r\nimport { drawCanvasRuler } from './utils'\r\nimport type { CanvasProps } from '../index-types'\r\nconst CanvasRuler = ({\r\n scale,\r\n palette,\r\n vertical,\r\n start,\r\n width,\r\n height,\r\n selectStart,\r\n selectLength,\r\n canvasWidth,\r\n canvasHeight,\r\n rate,\r\n showShadowText,\r\n gridRatio,\r\n onDragStart\r\n}: CanvasProps) => {\r\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\r\n const [canvasContext, setCanvasContext] = useState<CanvasRenderingContext2D | null>(null)\r\n const rafIdRef = useRef<number | null>(null)\r\n useEffect(() => {\r\n if (canvasRef.current) {\r\n const ctx = canvasRef.current.getContext('2d')\r\n setCanvasContext(ctx)\r\n }\r\n }, [canvasRef])\r\n\r\n useEffect(() => {\r\n if (canvasRef.current && canvasContext) {\r\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1\r\n canvasRef.current.width = width * dpr\r\n canvasRef.current.height = height * dpr\r\n canvasContext.font = `11px -apple-system, \"Helvetica Neue\", \".SFNSText-Regular\", \"SF UI Text\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"WenQuanYi Zen Hei\", sans-serif`\r\n canvasContext.lineWidth = 1\r\n canvasContext.textBaseline = 'middle'\r\n }\r\n }, [canvasContext, width, height])\r\n\r\n useEffect(() => {\r\n const options = {\r\n scale: scale / rate,\r\n width,\r\n height,\r\n palette,\r\n canvasWidth: canvasWidth * rate,\r\n canvasHeight: canvasHeight * rate,\r\n ratio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,\r\n rate,\r\n gridRatio,\r\n showShadowText\r\n }\r\n\r\n if (canvasContext) {\r\n if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current)\r\n rafIdRef.current = requestAnimationFrame(() => {\r\n drawCanvasRuler(canvasContext, start * rate, selectStart, selectLength, options, !vertical)\r\n })\r\n }\r\n }, [\r\n scale,\r\n rate,\r\n width,\r\n height,\r\n palette,\r\n canvasWidth,\r\n canvasHeight,\r\n gridRatio,\r\n canvasContext,\r\n start,\r\n selectStart,\r\n selectLength,\r\n vertical\r\n ])\r\n\r\n useEffect(() => {\r\n return () => {\r\n if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current)\r\n }\r\n }, [])\r\n\r\n const rulerStyle = useMemo(() => {\r\n return {\r\n width: width + 'px',\r\n height: height + 'px',\r\n cursor: vertical ? 'ew-resize' : 'ns-resize',\r\n [vertical ? 'borderRight' : 'borderBottom']: `1px solid ${palette.borderColor || '#eeeeef'}`\r\n }\r\n }, [vertical, palette.borderColor])\r\n\r\n const handleDragStart = (e: MouseEvent<HTMLCanvasElement>) => onDragStart(e)\r\n return (\r\n <canvas ref={canvasRef} className=\"ruler\" style={rulerStyle} onMouseDown={handleDragStart} />\r\n )\r\n}\r\n\r\nexport default memo(CanvasRuler)\r\n","import { useState, useEffect, useMemo, memo } from 'react'\r\nimport RulerLine from './RulerLine'\r\nimport CanvasRuler from '../canvas-ruler/index'\r\nimport useLine from './useLine'\r\nimport type { RulerWrapperProps } from '../index-types'\r\nconst RulerComponent = ({\r\n scale,\r\n thick,\r\n canvasWidth,\r\n canvasHeight,\r\n palette,\r\n vertical,\r\n width,\r\n height,\r\n start,\r\n startOther,\r\n lines,\r\n selectStart,\r\n selectLength,\r\n isShowReferLine,\r\n rate,\r\n snapThreshold,\r\n snapsObj,\r\n gridRatio,\r\n lockLine,\r\n propStyle,\r\n showShadowText,\r\n handleLine\r\n}: RulerWrapperProps) => {\r\n const [isLockLine, setIsLockLine] = useState(lockLine)\r\n const [isdragle, setIsDragle] = useState(false)\r\n const [showLabel, setShowLabel] = useState(false)\r\n const { actionStyle, handleMouseMove, handleMouseDown, labelContent, startValue, setStartValue } =\r\n useLine(\r\n {\r\n palette,\r\n scale,\r\n snapsObj,\r\n lines,\r\n canvasWidth,\r\n canvasHeight,\r\n snapThreshold,\r\n lockLine: isLockLine,\r\n rate,\r\n handleLine\r\n },\r\n !vertical\r\n )\r\n\r\n const rwClassName = vertical ? 'v-container' : 'h-container'\r\n\r\n const cpuLines = useMemo(() => {\r\n return vertical ? lines.h : lines.v\r\n }, [vertical, lines])\r\n\r\n const indicatorStyle = useMemo(() => {\r\n const lineType = palette.lineType\r\n const positionKey = vertical ? 'left' : 'top'\r\n const gepKey = vertical ? 'top' : 'left'\r\n const boderKey = vertical ? 'borderLeft' : 'borderBottom'\r\n const offsetPx = (startValue - startOther) * scale + thick\r\n return {\r\n [positionKey]: offsetPx + 'px',\r\n [gepKey]: -thick + 'px',\r\n cursor: vertical ? 'ew-resize' : 'ns-resize',\r\n [boderKey]: `1px ${lineType} ${palette.lineColor}`\r\n }\r\n }, [startValue, startOther, vertical, palette.lineType, scale, palette.lineColor, thick])\r\n\r\n const mousedown = async (e: React.MouseEvent<HTMLCanvasElement>) => {\r\n const { offsetX, offsetY } = e.nativeEvent as MouseEvent\r\n setIsDragle(true)\r\n setIsLockLine(false)\r\n const tempStartValue = Math.round(\r\n startOther - thick / scale + (vertical ? offsetX : offsetY) / scale\r\n )\r\n setStartValue(tempStartValue)\r\n await handleMouseDown(e, tempStartValue)\r\n setIsDragle(false)\r\n }\r\n\r\n useEffect(() => {\r\n setIsLockLine(lockLine)\r\n }, [lockLine])\r\n\r\n return (\r\n <div className={rwClassName} style={propStyle}>\r\n <CanvasRuler\r\n vertical={vertical}\r\n scale={scale}\r\n width={width}\r\n height={height}\r\n start={start}\r\n showShadowText={showShadowText}\r\n canvasWidth={canvasWidth}\r\n canvasHeight={canvasHeight}\r\n selectStart={selectStart}\r\n selectLength={selectLength}\r\n palette={palette}\r\n rate={rate}\r\n onDragStart={mousedown}\r\n gridRatio={gridRatio}\r\n />\r\n {isShowReferLine && (\r\n <div className=\"lines\">\r\n {cpuLines.map((v, i) => (\r\n <RulerLine\r\n key={`${v}-${i}`}\r\n lockLine={isLockLine}\r\n index={i}\r\n value={Math.floor(v)}\r\n scale={scale}\r\n start={start}\r\n canvasWidth={canvasWidth}\r\n snapThreshold={snapThreshold}\r\n snapsObj={snapsObj}\r\n canvasHeight={canvasHeight}\r\n lines={lines}\r\n palette={palette}\r\n vertical={vertical}\r\n isShowReferLine={isShowReferLine}\r\n handleLine={handleLine}\r\n rate={rate}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n {isShowReferLine && (\r\n <div\r\n className=\"indicator\"\r\n onMouseEnter={() => setShowLabel(true)}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => setShowLabel(false)}\r\n hidden={!isdragle}\r\n style={indicatorStyle}\r\n >\r\n <div className=\"action\" style={actionStyle}>\r\n {showLabel && <span className=\"value\">{labelContent}</span>}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nexport default memo(RulerComponent)\r\n","import { eye64, closeEye64 } from './cornerImg64'\r\nimport Panzoom from 'simple-panzoom'\r\nimport type { PanzoomObject, PanzoomEventDetail } from 'simple-panzoom'\r\nimport React, { useState, useEffect, useMemo, useImperativeHandle, useRef } from 'react'\r\nimport './index.less'\r\nimport RulerWrapper from './RulerWrapper'\r\nimport type { SketchRulerProps, PaletteType, SketchRulerMethods } from '../index-types'\r\n\r\nconst usePaletteConfig = (palette: PaletteType) => {\r\n return useMemo(\r\n () => ({\r\n bgColor: '#f6f7f9',\r\n longfgColor: '#BABBBC',\r\n fontColor: '#7D8694', // ruler font color\r\n fontShadowColor: '#106ebe',\r\n shadowColor: '#e9f7fe', // ruler shadow color\r\n lineColor: '#51d6a9',\r\n lineType: 'solid',\r\n lockLineColor: '#d4d7dc',\r\n hoverBg: '#000',\r\n hoverColor: '#fff',\r\n borderColor: '#eeeeef',\r\n ...palette\r\n }),\r\n [palette]\r\n )\r\n}\r\n\r\nconst SketchRule = React.forwardRef<SketchRulerMethods, SketchRulerProps>(\r\n (\r\n {\r\n showRuler = true,\r\n scale = 1,\r\n rate = 1,\r\n thick = 16,\r\n width = 1400,\r\n height = 800,\r\n eyeIcon,\r\n closeEyeIcon,\r\n paddingRatio = 0.2,\r\n autoCenter = true,\r\n showShadowText = true,\r\n shadow = {\r\n x: 0,\r\n y: 0,\r\n width: 0,\r\n height: 0\r\n },\r\n lines = {\r\n h: [],\r\n v: []\r\n },\r\n isShowReferLine = true,\r\n canvasWidth = 1000,\r\n canvasHeight = 700,\r\n snapsObj = {\r\n h: [],\r\n v: []\r\n },\r\n palette,\r\n snapThreshold = 5,\r\n gridRatio = 1,\r\n lockLine = false,\r\n selfHandle = false,\r\n panzoomOption,\r\n children,\r\n onHandleCornerClick,\r\n updateScale,\r\n onZoomChange,\r\n handleLine\r\n }: SketchRulerProps,\r\n ref\r\n ) => {\r\n const paletteConfig = usePaletteConfig(palette || {})\r\n const [startX, setStartX] = useState(0)\r\n const [startY, setStartY] = useState(0)\r\n const [cursorClass, setCursorClass] = useState('defaultCursor')\r\n const zoomStartXRef = useRef(0)\r\n const zoomStartYRef = useRef(0)\r\n const [ownScale, setOwnScale] = useState(1)\r\n const [showReferLine, setShowReferLine] = useState(isShowReferLine)\r\n const panzoomInstance = useRef<PanzoomObject | null>(null)\r\n const canvasEditRef = useRef<HTMLDivElement | null>(null)\r\n const panElemRef = useRef<HTMLElement | null>(null)\r\n const rectWidth = useMemo(() => width - thick, [width, thick])\r\n const rectHeight = useMemo(() => height - thick, [height, thick])\r\n\r\n const commonProps = {\r\n thick,\r\n lines,\r\n snapThreshold,\r\n snapsObj,\r\n isShowReferLine: showReferLine,\r\n canvasWidth,\r\n canvasHeight,\r\n rate,\r\n palette: paletteConfig,\r\n gridRatio,\r\n showShadowText,\r\n lockLine,\r\n scale: ownScale,\r\n handleLine\r\n }\r\n\r\n const cornerStyle = useMemo(() => {\r\n return {\r\n backgroundImage: showReferLine\r\n ? `url(${eyeIcon || eye64})`\r\n : `url(${closeEyeIcon || closeEye64})`,\r\n width: `${thick}px`,\r\n height: `${thick}px`,\r\n borderRight: `1px solid ${paletteConfig.borderColor}`,\r\n borderBottom: `1px solid ${paletteConfig.borderColor}`\r\n }\r\n }, [showReferLine, eyeIcon, closeEyeIcon, paletteConfig])\r\n\r\n const rectStyle = useMemo(() => {\r\n return {\r\n background: paletteConfig.bgColor,\r\n left: thick + 'px',\r\n top: thick + 'px',\r\n width: rectWidth + 'px',\r\n height: rectHeight + 'px'\r\n }\r\n }, [rectHeight, rectWidth, paletteConfig])\r\n\r\n const handleWheel = (e: WheelEvent) => {\r\n if (e.ctrlKey || e.metaKey) {\r\n if (panzoomInstance.current) {\r\n panzoomInstance.current.zoomWithWheel(e)\r\n }\r\n }\r\n }\r\n\r\n const handleSpaceKeyDown = (e: KeyboardEvent) => {\r\n // 检查当前焦点元素\r\n const activeElement = document.activeElement\r\n const isEditableElement =\r\n activeElement instanceof HTMLInputElement ||\r\n activeElement instanceof HTMLTextAreaElement ||\r\n activeElement?.classList.contains('monaco-editor') ||\r\n activeElement?.getAttribute('contenteditable') === 'true'\r\n // 如果焦点在可编辑元素中,则不处理空格事件\r\n if (isEditableElement) {\r\n return\r\n }\r\n\r\n if (e.key === ' ') {\r\n if (panzoomInstance.current) {\r\n setCursorClass('grabCursor')\r\n panzoomInstance.current.bind()\r\n }\r\n e.preventDefault()\r\n }\r\n }\r\n\r\n const handleSpaceKeyUp = (e: KeyboardEvent) => {\r\n if (e.key === ' ') {\r\n if (panzoomInstance.current) {\r\n setCursorClass('defaultCursor')\r\n panzoomInstance.current.destroy()\r\n }\r\n }\r\n }\r\n\r\n const getPanOptions = (scale: number) => ({\r\n noBind: true,\r\n startScale: scale,\r\n // cursor: 'default',\r\n startX: zoomStartXRef.current,\r\n startY: zoomStartYRef.current,\r\n smoothScroll: true,\r\n canvas: true,\r\n ...panzoomOption\r\n })\r\n\r\n\r\n const handlePanzoomChange = (e: any) => {\r\n const detail = e.detail as PanzoomEventDetail\r\n const { scale: newScale, dimsOut } = detail\r\n if (dimsOut) {\r\n setOwnScale(newScale)\r\n if (updateScale) {\r\n updateScale(newScale)\r\n }\r\n const left = (dimsOut.parent.left - dimsOut.elem.left) / newScale\r\n const top = (dimsOut.parent.top - dimsOut.elem.top) / newScale\r\n setStartX(left)\r\n if (onZoomChange) {\r\n onZoomChange(detail)\r\n }\r\n setStartY(top)\r\n }\r\n }\r\n\r\n const initPanzoom = () => {\r\n const elem = canvasEditRef.current\r\n if (panElemRef.current) {\r\n panElemRef.current.removeEventListener('panzoomchange', handlePanzoomChange)\r\n }\r\n if (panzoomInstance.current) {\r\n panzoomInstance.current.destroy()\r\n }\r\n let tempScale = scale\r\n if (autoCenter) {\r\n tempScale = calculateTransform()\r\n setOwnScale(tempScale)\r\n if (updateScale) {\r\n updateScale(tempScale)\r\n }\r\n }\r\n const panzoom = Panzoom((elem as HTMLElement)!, getPanOptions(tempScale))\r\n panzoomInstance.current = panzoom\r\n if (elem) {\r\n panElemRef.current = elem as HTMLElement\r\n panElemRef.current.addEventListener('panzoomchange', handlePanzoomChange)\r\n }\r\n }\r\n\r\n /**\r\n * @desc: 居中算法\r\n */\r\n const calculateTransform = () => {\r\n const scaleX = (rectWidth * (1 - paddingRatio)) / canvasWidth\r\n const scaleY = (rectHeight * (1 - paddingRatio)) / canvasHeight\r\n const scale = Math.min(scaleX, scaleY)\r\n zoomStartXRef.current = rectWidth / 2 - canvasWidth / 2\r\n if (scale < 1) {\r\n zoomStartYRef.current =\r\n ((canvasHeight * scale) / 2 - canvasHeight / 2) / scale -\r\n (canvasHeight * scale - rectHeight) / scale / 2\r\n } else if (scale > 1) {\r\n zoomStartYRef.current =\r\n (canvasHeight * scale - canvasHeight) / 2 / scale +\r\n (rectHeight - canvasHeight * scale) / scale / 2\r\n } else {\r\n zoomStartYRef.current = 0\r\n }\r\n return scale\r\n }\r\n\r\n const reset = () => panzoomInstance.current?.reset()\r\n const zoomIn = () => panzoomInstance.current?.zoomIn()\r\n const zoomOut = () => panzoomInstance.current?.zoomOut()\r\n const setOtions = () => {\r\n const centerScale = calculateTransform()\r\n panzoomInstance.current?.setOptions(getPanOptions(centerScale))\r\n }\r\n\r\n const handleCornerClick = () => {\r\n setShowReferLine(!showReferLine)\r\n if (onHandleCornerClick) {\r\n onHandleCornerClick(!showReferLine)\r\n }\r\n }\r\n // 使用 useImperativeHandle 来暴露这些方法\r\n useImperativeHandle(ref, () => ({\r\n reset,\r\n zoomIn,\r\n zoomOut,\r\n initPanzoom,\r\n panzoomInstance\r\n }))\r\n\r\n useEffect(() => {\r\n setShowReferLine(isShowReferLine)\r\n }, [isShowReferLine])\r\n\r\n useEffect(() => {\r\n initPanzoom()\r\n if (!selfHandle) {\r\n document.addEventListener('wheel', handleWheel, { passive: false })\r\n document.addEventListener('keydown', handleSpaceKeyDown)\r\n document.addEventListener('keyup', handleSpaceKeyUp)\r\n }\r\n // 清理函数,用于移除监听器\r\n return () => {\r\n document.removeEventListener('wheel', handleWheel)\r\n document.removeEventListener('keydown', handleSpaceKeyDown)\r\n document.removeEventListener('keyup', handleSpaceKeyUp)\r\n if (panElemRef.current) {\r\n panElemRef.current.removeEventListener('panzoomchange', handlePanzoomChange)\r\n }\r\n if (panzoomInstance.current) {\r\n panzoomInstance.current.destroy()\r\n panzoomInstance.current = null\r\n }\r\n }\r\n }, [canvasWidth, canvasHeight, width, height, selfHandle])\r\n\r\n useEffect(() => {\r\n setOtions()\r\n }, [panzoomOption])\r\n\r\n // 处理children\r\n const [defaultSlot, btnSlot] = React.Children.toArray(children).reduce(\r\n (acc: [React.ReactNode | null, React.ReactNode | null], child: React.ReactNode) => {\r\n if (React.isValidElement(child)) {\r\n const el = child as React.ReactElement<any>\r\n if (el.props.slot === 'default' || !el.props.slot) {\r\n acc[0] = el\r\n } else if (el.props.slot === 'btn') {\r\n acc[1] = el\r\n }\r\n }\r\n return acc\r\n },\r\n [null, null]\r\n )\r\n return (\r\n <div className=\"sketch-ruler\" id=\"sketch-ruler\">\r\n {btnSlot}\r\n <div className={'canvasedit-parent ' + cursorClass} style={rectStyle}>\r\n <div ref={canvasEditRef} className={'canvasedit ' + cursorClass}>{defaultSlot}</div>\r\n </div>\r\n {showRuler && (\r\n <RulerWrapper\r\n {...commonProps}\r\n width={width!}\r\n propStyle={{ marginLeft: thick + 'px', width: rectWidth + 'px' }}\r\n height={thick!}\r\n start={startX!}\r\n startOther={startY!}\r\n selectStart={shadow.x!}\r\n selectLength={shadow.width}\r\n vertical={false}\r\n />\r\n )}\r\n {/* 竖直方向 */}\r\n {showRuler && (\r\n <RulerWrapper\r\n {...commonProps}\r\n propStyle={{ marginTop: thick + 'px', top: 0, height: rectHeight + 'px' }}\r\n width={thick!}\r\n height={height!}\r\n start={startY!}\r\n startOther={startX!}\r\n selectStart={shadow.y!}\r\n selectLength={shadow.height}\r\n vertical={true}\r\n />\r\n )}\r\n {showRuler && <a className=\"corner\" style={cornerStyle} onClick={handleCornerClick} />}\r\n </div>\r\n )\r\n }\r\n)\r\n\r\nexport default SketchRule\r\n"],"names":["REACT_ELEMENT_TYPE","REACT_FRAGMENT_TYP