f1ow 0.1.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Canvas/AnnotationsOverlay.d.ts +34 -0
- package/dist/components/shapes/TextLabel.d.ts +16 -0
- package/dist/f1ow.js +881 -419
- package/dist/f1ow.umd.cjs +880 -418
- package/dist/lib/FlowCanvasProps.d.ts +37 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/utils/camera.d.ts +1 -1
- package/dist/utils/connection.d.ts +26 -1
- package/dist/utils/dragSync.d.ts +50 -0
- package/dist/utils/labelMetrics.d.ts +49 -0
- package/package.json +107 -99
package/dist/f1ow.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
5
|
-
import React, { useCallback, useRef, useMemo, createContext, useContext, useState, useEffect, forwardRef, createElement, useImperativeHandle } from "react";
|
|
5
|
+
import React, { useCallback, useRef, useMemo, createContext, useContext, useState, useEffect, forwardRef, createElement, useImperativeHandle, useLayoutEffect } from "react";
|
|
6
6
|
import { Shape, Rect, Ellipse, Line, Group as Group$1, Path, Text, Image as Image$2, Transformer, Circle as Circle$1, Stage, Layer } from "react-konva";
|
|
7
7
|
import Konva from "konva";
|
|
8
8
|
import { create } from "zustand";
|
|
@@ -873,6 +873,58 @@ function simplifyElbowPath(points) {
|
|
|
873
873
|
result.push(points[points.length - 2], points[points.length - 1]);
|
|
874
874
|
return result;
|
|
875
875
|
}
|
|
876
|
+
const CURVE_RATIO = 0.2;
|
|
877
|
+
function computeCurveControlPoint(start, end, ratio = CURVE_RATIO) {
|
|
878
|
+
const mx = (start.x + end.x) / 2;
|
|
879
|
+
const my = (start.y + end.y) / 2;
|
|
880
|
+
const dx = end.x - start.x;
|
|
881
|
+
const dy = end.y - start.y;
|
|
882
|
+
const len2 = Math.sqrt(dx * dx + dy * dy);
|
|
883
|
+
if (len2 === 0) return { x: mx, y: my };
|
|
884
|
+
const nx = -dy / len2;
|
|
885
|
+
const ny = dx / len2;
|
|
886
|
+
const offset = len2 * ratio;
|
|
887
|
+
return { x: mx + nx * offset, y: my + ny * offset };
|
|
888
|
+
}
|
|
889
|
+
function quadBezierAt(p0, cp, p2, t) {
|
|
890
|
+
const mt = 1 - t;
|
|
891
|
+
return {
|
|
892
|
+
x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * p2.x,
|
|
893
|
+
y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * p2.y
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function quadBezierTangent(p0, cp, p2, t) {
|
|
897
|
+
const mt = 1 - t;
|
|
898
|
+
return {
|
|
899
|
+
x: 2 * mt * (cp.x - p0.x) + 2 * t * (p2.x - cp.x),
|
|
900
|
+
y: 2 * mt * (cp.y - p0.y) + 2 * t * (p2.y - cp.y)
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function curveArrowPrev(start, cp, end, atEnd) {
|
|
904
|
+
if (atEnd) {
|
|
905
|
+
const tan = quadBezierTangent(start, cp, end, 1);
|
|
906
|
+
const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
|
|
907
|
+
return { x: end.x - tan.x / len2 * 20, y: end.y - tan.y / len2 * 20 };
|
|
908
|
+
} else {
|
|
909
|
+
const tan = quadBezierTangent(start, cp, end, 0);
|
|
910
|
+
const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
|
|
911
|
+
return { x: start.x + tan.x / len2 * 20, y: start.y + tan.y / len2 * 20 };
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function curvatureFromDragPoint(start, end, dragWorld) {
|
|
915
|
+
const dx = end.x - start.x;
|
|
916
|
+
const dy = end.y - start.y;
|
|
917
|
+
const len2 = Math.sqrt(dx * dx + dy * dy);
|
|
918
|
+
if (len2 === 0) return CURVE_RATIO;
|
|
919
|
+
const nx = -dy / len2;
|
|
920
|
+
const ny = dx / len2;
|
|
921
|
+
const mx = (start.x + end.x) / 2;
|
|
922
|
+
const my = (start.y + end.y) / 2;
|
|
923
|
+
const vx = dragWorld.x - mx;
|
|
924
|
+
const vy = dragWorld.y - my;
|
|
925
|
+
const projDist = vx * nx + vy * ny;
|
|
926
|
+
return 2 * projDist / len2;
|
|
927
|
+
}
|
|
876
928
|
const CONNECTABLE_TYPES = /* @__PURE__ */ new Set(["rectangle", "ellipse", "diamond", "text", "image"]);
|
|
877
929
|
function isConnectable(el) {
|
|
878
930
|
return CONNECTABLE_TYPES.has(el.type);
|
|
@@ -1299,6 +1351,71 @@ function syncBoundElements(connectorId, connectorType, oldBinding, newBinding, e
|
|
|
1299
1351
|
}
|
|
1300
1352
|
}
|
|
1301
1353
|
}
|
|
1354
|
+
function computeConnectorLabelPosition(connector, textWidth, textHeight) {
|
|
1355
|
+
const pts = connector.points;
|
|
1356
|
+
const startPt = { x: pts[0], y: pts[1] };
|
|
1357
|
+
const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
|
|
1358
|
+
let midX;
|
|
1359
|
+
let midY;
|
|
1360
|
+
if (connector.lineType === "curved") {
|
|
1361
|
+
const curvature = connector.curvature ?? CURVE_RATIO;
|
|
1362
|
+
const cp = computeCurveControlPoint(startPt, endPt, curvature);
|
|
1363
|
+
const mid = quadBezierAt(startPt, cp, endPt, 0.5);
|
|
1364
|
+
midX = connector.x + mid.x;
|
|
1365
|
+
midY = connector.y + mid.y;
|
|
1366
|
+
} else if (connector.lineType === "elbow" && pts.length >= 4) {
|
|
1367
|
+
const segCount = pts.length / 2 - 1;
|
|
1368
|
+
let totalLen = 0;
|
|
1369
|
+
for (let i = 0; i < segCount; i++) {
|
|
1370
|
+
const dx = pts[(i + 1) * 2] - pts[i * 2];
|
|
1371
|
+
const dy = pts[(i + 1) * 2 + 1] - pts[i * 2 + 1];
|
|
1372
|
+
totalLen += Math.sqrt(dx * dx + dy * dy);
|
|
1373
|
+
}
|
|
1374
|
+
const half = totalLen / 2;
|
|
1375
|
+
let walked = 0;
|
|
1376
|
+
midX = connector.x + (startPt.x + endPt.x) / 2;
|
|
1377
|
+
midY = connector.y + (startPt.y + endPt.y) / 2;
|
|
1378
|
+
for (let i = 0; i < segCount; i++) {
|
|
1379
|
+
const ax = pts[i * 2], ay = pts[i * 2 + 1];
|
|
1380
|
+
const bx = pts[(i + 1) * 2], by = pts[(i + 1) * 2 + 1];
|
|
1381
|
+
const dx = bx - ax, dy = by - ay;
|
|
1382
|
+
const segLen = Math.sqrt(dx * dx + dy * dy);
|
|
1383
|
+
if (walked + segLen >= half && segLen > 0) {
|
|
1384
|
+
const t = (half - walked) / segLen;
|
|
1385
|
+
midX = connector.x + ax + dx * t;
|
|
1386
|
+
midY = connector.y + ay + dy * t;
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
walked += segLen;
|
|
1390
|
+
}
|
|
1391
|
+
} else {
|
|
1392
|
+
midX = connector.x + (startPt.x + endPt.x) / 2;
|
|
1393
|
+
midY = connector.y + (startPt.y + endPt.y) / 2;
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
x: midX - textWidth / 2,
|
|
1397
|
+
y: midY - textHeight / 2
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
function syncConnectorLabels(connectorIds, elMap) {
|
|
1401
|
+
const updates = [];
|
|
1402
|
+
for (const connId of connectorIds) {
|
|
1403
|
+
const conn = elMap.get(connId);
|
|
1404
|
+
if (!conn || conn.type !== "arrow" && conn.type !== "line") continue;
|
|
1405
|
+
if (!conn.boundElements) continue;
|
|
1406
|
+
const connector = conn;
|
|
1407
|
+
for (const be of conn.boundElements) {
|
|
1408
|
+
if (be.type !== "text") continue;
|
|
1409
|
+
const txt = elMap.get(be.id);
|
|
1410
|
+
if (!txt) continue;
|
|
1411
|
+
const textW = Math.max(10, txt.width || 60);
|
|
1412
|
+
const textH = txt.height || 30;
|
|
1413
|
+
const pos = computeConnectorLabelPosition(connector, textW, textH);
|
|
1414
|
+
updates.push({ id: txt.id, updates: { x: pos.x, y: pos.y } });
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return updates;
|
|
1418
|
+
}
|
|
1302
1419
|
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
1303
1420
|
let nanoid = (size = 21) => {
|
|
1304
1421
|
let id = "";
|
|
@@ -1823,7 +1940,7 @@ function batchElementUpdates(elements, updates) {
|
|
|
1823
1940
|
function toSet(arr) {
|
|
1824
1941
|
return new Set(arr);
|
|
1825
1942
|
}
|
|
1826
|
-
const ZOOM_STEPS = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5];
|
|
1943
|
+
const ZOOM_STEPS = [0.1, 0.25, 0.33, 0.5, 0.67, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5];
|
|
1827
1944
|
const DEFAULT_ANIMATION_DURATION = 280;
|
|
1828
1945
|
function zoomAtPoint({ viewport, point, targetScale }) {
|
|
1829
1946
|
const clampedScale = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, targetScale));
|
|
@@ -2568,58 +2685,6 @@ function getStrokeDash(style, strokeWidth) {
|
|
|
2568
2685
|
return [];
|
|
2569
2686
|
}
|
|
2570
2687
|
}
|
|
2571
|
-
const CURVE_RATIO = 0.2;
|
|
2572
|
-
function computeCurveControlPoint(start, end, ratio = CURVE_RATIO) {
|
|
2573
|
-
const mx = (start.x + end.x) / 2;
|
|
2574
|
-
const my = (start.y + end.y) / 2;
|
|
2575
|
-
const dx = end.x - start.x;
|
|
2576
|
-
const dy = end.y - start.y;
|
|
2577
|
-
const len2 = Math.sqrt(dx * dx + dy * dy);
|
|
2578
|
-
if (len2 === 0) return { x: mx, y: my };
|
|
2579
|
-
const nx = -dy / len2;
|
|
2580
|
-
const ny = dx / len2;
|
|
2581
|
-
const offset = len2 * ratio;
|
|
2582
|
-
return { x: mx + nx * offset, y: my + ny * offset };
|
|
2583
|
-
}
|
|
2584
|
-
function quadBezierAt(p0, cp, p2, t) {
|
|
2585
|
-
const mt = 1 - t;
|
|
2586
|
-
return {
|
|
2587
|
-
x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * p2.x,
|
|
2588
|
-
y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * p2.y
|
|
2589
|
-
};
|
|
2590
|
-
}
|
|
2591
|
-
function quadBezierTangent(p0, cp, p2, t) {
|
|
2592
|
-
const mt = 1 - t;
|
|
2593
|
-
return {
|
|
2594
|
-
x: 2 * mt * (cp.x - p0.x) + 2 * t * (p2.x - cp.x),
|
|
2595
|
-
y: 2 * mt * (cp.y - p0.y) + 2 * t * (p2.y - cp.y)
|
|
2596
|
-
};
|
|
2597
|
-
}
|
|
2598
|
-
function curveArrowPrev(start, cp, end, atEnd) {
|
|
2599
|
-
if (atEnd) {
|
|
2600
|
-
const tan = quadBezierTangent(start, cp, end, 1);
|
|
2601
|
-
const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
|
|
2602
|
-
return { x: end.x - tan.x / len2 * 20, y: end.y - tan.y / len2 * 20 };
|
|
2603
|
-
} else {
|
|
2604
|
-
const tan = quadBezierTangent(start, cp, end, 0);
|
|
2605
|
-
const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
|
|
2606
|
-
return { x: start.x + tan.x / len2 * 20, y: start.y + tan.y / len2 * 20 };
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
function curvatureFromDragPoint(start, end, dragWorld) {
|
|
2610
|
-
const dx = end.x - start.x;
|
|
2611
|
-
const dy = end.y - start.y;
|
|
2612
|
-
const len2 = Math.sqrt(dx * dx + dy * dy);
|
|
2613
|
-
if (len2 === 0) return CURVE_RATIO;
|
|
2614
|
-
const nx = -dy / len2;
|
|
2615
|
-
const ny = dx / len2;
|
|
2616
|
-
const mx = (start.x + end.x) / 2;
|
|
2617
|
-
const my = (start.y + end.y) / 2;
|
|
2618
|
-
const vx = dragWorld.x - mx;
|
|
2619
|
-
const vy = dragWorld.y - my;
|
|
2620
|
-
const projDist = vx * nx + vy * ny;
|
|
2621
|
-
return 2 * projDist / len2;
|
|
2622
|
-
}
|
|
2623
2688
|
const selectTool = {
|
|
2624
2689
|
name: "select",
|
|
2625
2690
|
onMouseDown(e, pos, ctx) {
|
|
@@ -3322,7 +3387,7 @@ function createImageElement(src, naturalWidth, naturalHeight, x, y, style = { ..
|
|
|
3322
3387
|
width: dims.width,
|
|
3323
3388
|
height: dims.height,
|
|
3324
3389
|
rotation: 0,
|
|
3325
|
-
style: { ...style, fillColor: "transparent" },
|
|
3390
|
+
style: { ...style, fillColor: "transparent", strokeColor: "transparent", strokeWidth: 0 },
|
|
3326
3391
|
isLocked: false,
|
|
3327
3392
|
isVisible: true,
|
|
3328
3393
|
boundElements: null,
|
|
@@ -5574,7 +5639,34 @@ const FreeDrawShape = ({ element, isSelected, isGrouped, onSelect, onChange, onD
|
|
|
5574
5639
|
);
|
|
5575
5640
|
};
|
|
5576
5641
|
const FreeDrawShape$1 = React.memo(FreeDrawShape);
|
|
5577
|
-
const
|
|
5642
|
+
const LABEL_PADDING_H = 8;
|
|
5643
|
+
const LABEL_PADDING_V = 4;
|
|
5644
|
+
const LABEL_CORNER = 4;
|
|
5645
|
+
const LABEL_LINE_HEIGHT = 1.18;
|
|
5646
|
+
const LABEL_MIN_WIDTH = 10;
|
|
5647
|
+
let _measureCanvas$1 = null;
|
|
5648
|
+
function getMeasureCtx() {
|
|
5649
|
+
if (!_measureCanvas$1) {
|
|
5650
|
+
_measureCanvas$1 = document.createElement("canvas");
|
|
5651
|
+
}
|
|
5652
|
+
return _measureCanvas$1.getContext("2d");
|
|
5653
|
+
}
|
|
5654
|
+
function measureLabelText(text, fontSize, fontFamily) {
|
|
5655
|
+
const ctx = getMeasureCtx();
|
|
5656
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
5657
|
+
const metrics = ctx.measureText(text || " ");
|
|
5658
|
+
return {
|
|
5659
|
+
width: Math.ceil(metrics.width),
|
|
5660
|
+
height: Math.ceil(fontSize * LABEL_LINE_HEIGHT)
|
|
5661
|
+
};
|
|
5662
|
+
}
|
|
5663
|
+
function computePillSize(textWidth, textHeight) {
|
|
5664
|
+
return {
|
|
5665
|
+
width: Math.max(LABEL_MIN_WIDTH, textWidth) + LABEL_PADDING_H * 2,
|
|
5666
|
+
height: textHeight + LABEL_PADDING_V * 2
|
|
5667
|
+
};
|
|
5668
|
+
}
|
|
5669
|
+
const LINE_HEIGHT = LABEL_LINE_HEIGHT;
|
|
5578
5670
|
const TextShape = ({
|
|
5579
5671
|
element,
|
|
5580
5672
|
isSelected,
|
|
@@ -5591,6 +5683,7 @@ const TextShape = ({
|
|
|
5591
5683
|
const { id, x, y, width, height, rotation, style, text, containerId, textAlign, verticalAlign, isLocked } = element;
|
|
5592
5684
|
const textRef = useRef(null);
|
|
5593
5685
|
const isEditingRef = useRef(false);
|
|
5686
|
+
const [isEditingState, setIsEditingState] = useState(false);
|
|
5594
5687
|
const autoEditDoneRef = useRef(false);
|
|
5595
5688
|
const isBound = !!containerId;
|
|
5596
5689
|
const isDraggable = !isBound && !isLocked && !isGrouped;
|
|
@@ -5599,36 +5692,12 @@ const TextShape = ({
|
|
|
5599
5692
|
return allElements.find((el) => el.id === containerId) ?? null;
|
|
5600
5693
|
}, [containerId, allElements]);
|
|
5601
5694
|
const boundPos = useMemo(() => {
|
|
5602
|
-
var _a, _b;
|
|
5603
5695
|
if (!container) return { x, y };
|
|
5604
|
-
if (container.type === "arrow" || container.type === "line") {
|
|
5605
|
-
const conn = container;
|
|
5606
|
-
const pts = conn.points;
|
|
5607
|
-
const startPt = { x: pts[0], y: pts[1] };
|
|
5608
|
-
const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
|
|
5609
|
-
let midX, midY;
|
|
5610
|
-
if (conn.lineType === "curved") {
|
|
5611
|
-
const cp = computeCurveControlPoint(startPt, endPt, conn.curvature ?? CURVE_RATIO);
|
|
5612
|
-
const mid = quadBezierAt(startPt, cp, endPt, 0.5);
|
|
5613
|
-
midX = conn.x + mid.x;
|
|
5614
|
-
midY = conn.y + mid.y;
|
|
5615
|
-
} else {
|
|
5616
|
-
midX = conn.x + (startPt.x + endPt.x) / 2;
|
|
5617
|
-
midY = conn.y + (startPt.y + endPt.y) / 2;
|
|
5618
|
-
}
|
|
5619
|
-
const textWidth2 = Math.max(80, width || 80);
|
|
5620
|
-
const textHeight = ((_a = textRef.current) == null ? void 0 : _a.height()) ?? height;
|
|
5621
|
-
return {
|
|
5622
|
-
x: midX - textWidth2 / 2,
|
|
5623
|
-
y: midY - textHeight / 2,
|
|
5624
|
-
width: textWidth2
|
|
5625
|
-
};
|
|
5626
|
-
}
|
|
5627
5696
|
const PADDING2 = 4;
|
|
5628
5697
|
const cw = container.width - PADDING2 * 2;
|
|
5629
5698
|
const textWidth = Math.max(20, cw);
|
|
5630
5699
|
const bx = container.x + PADDING2;
|
|
5631
|
-
const textActualHeight =
|
|
5700
|
+
const textActualHeight = height;
|
|
5632
5701
|
let by;
|
|
5633
5702
|
if (verticalAlign === "top") {
|
|
5634
5703
|
by = container.y + PADDING2;
|
|
@@ -5638,7 +5707,7 @@ const TextShape = ({
|
|
|
5638
5707
|
by = container.y + (container.height - textActualHeight) / 2;
|
|
5639
5708
|
}
|
|
5640
5709
|
return { x: bx, y: by, width: textWidth };
|
|
5641
|
-
}, [container, x, y, height, verticalAlign]);
|
|
5710
|
+
}, [container, x, y, width, height, verticalAlign]);
|
|
5642
5711
|
const syncSize = useCallback(() => {
|
|
5643
5712
|
const node = textRef.current;
|
|
5644
5713
|
if (!node || isEditingRef.current) return;
|
|
@@ -5660,8 +5729,14 @@ const TextShape = ({
|
|
|
5660
5729
|
onChange(id, updates);
|
|
5661
5730
|
}
|
|
5662
5731
|
}, [id, height, width, isBound, onChange]);
|
|
5732
|
+
const syncSizeInitRef = useRef(true);
|
|
5663
5733
|
useEffect(() => {
|
|
5664
|
-
|
|
5734
|
+
if (syncSizeInitRef.current) {
|
|
5735
|
+
syncSizeInitRef.current = false;
|
|
5736
|
+
if (text && height > 0) return;
|
|
5737
|
+
}
|
|
5738
|
+
const id2 = requestAnimationFrame(syncSize);
|
|
5739
|
+
return () => cancelAnimationFrame(id2);
|
|
5665
5740
|
}, [text, style.fontSize, style.fontFamily, syncSize]);
|
|
5666
5741
|
const openEditor = useCallback(() => {
|
|
5667
5742
|
const textNode = textRef.current;
|
|
@@ -5669,6 +5744,7 @@ const TextShape = ({
|
|
|
5669
5744
|
const stage = textNode.getStage();
|
|
5670
5745
|
if (!stage) return;
|
|
5671
5746
|
isEditingRef.current = true;
|
|
5747
|
+
setIsEditingState(true);
|
|
5672
5748
|
onEditStart == null ? void 0 : onEditStart(id);
|
|
5673
5749
|
const stageContainer = stage.container();
|
|
5674
5750
|
const absTransform = textNode.getAbsoluteTransform().copy();
|
|
@@ -5691,7 +5767,7 @@ const TextShape = ({
|
|
|
5691
5767
|
containerScreenH = container.height * stageScaleX;
|
|
5692
5768
|
}
|
|
5693
5769
|
const nodeWidth = textNode.width();
|
|
5694
|
-
const screenWidth = isBound ? containerScreenW - PADDING_SCREEN * 2 : Math.max(nodeWidth,
|
|
5770
|
+
const screenWidth = isBound ? containerScreenW - PADDING_SCREEN * 2 : Math.max(nodeWidth, 60) * stageScaleX;
|
|
5695
5771
|
const screenLeft = isBound ? containerScreenLeft + PADDING_SCREEN : absPos.x;
|
|
5696
5772
|
const originalText = text;
|
|
5697
5773
|
const textarea = document.createElement("textarea");
|
|
@@ -5765,7 +5841,8 @@ const TextShape = ({
|
|
|
5765
5841
|
}
|
|
5766
5842
|
if (!isBound) {
|
|
5767
5843
|
textarea.style.width = "auto";
|
|
5768
|
-
|
|
5844
|
+
const minW = 100 * stageScaleX;
|
|
5845
|
+
textarea.style.width = `${Math.max(textarea.scrollWidth, minW)}px`;
|
|
5769
5846
|
}
|
|
5770
5847
|
};
|
|
5771
5848
|
textarea.addEventListener("input", autoGrow);
|
|
@@ -5778,6 +5855,7 @@ const TextShape = ({
|
|
|
5778
5855
|
const finishEdit = () => {
|
|
5779
5856
|
if (!isEditingRef.current) return;
|
|
5780
5857
|
isEditingRef.current = false;
|
|
5858
|
+
setIsEditingState(false);
|
|
5781
5859
|
const newText = cancelled ? originalText : textarea.value;
|
|
5782
5860
|
const isEmpty = newText.trim() === "";
|
|
5783
5861
|
textarea.removeEventListener("input", autoGrow);
|
|
@@ -5793,9 +5871,7 @@ const TextShape = ({
|
|
|
5793
5871
|
}
|
|
5794
5872
|
onEditEnd == null ? void 0 : onEditEnd(id, isEmpty);
|
|
5795
5873
|
};
|
|
5796
|
-
const handleBlur = () =>
|
|
5797
|
-
finishEdit();
|
|
5798
|
-
};
|
|
5874
|
+
const handleBlur = () => finishEdit();
|
|
5799
5875
|
const handleKeyDown = (e) => {
|
|
5800
5876
|
e.stopPropagation();
|
|
5801
5877
|
if (e.key === "Escape") {
|
|
@@ -5846,7 +5922,7 @@ const TextShape = ({
|
|
|
5846
5922
|
wrap: isBound ? "word" : "none",
|
|
5847
5923
|
rotation: isBound ? (container == null ? void 0 : container.rotation) ?? rotation : rotation,
|
|
5848
5924
|
transformsEnabled: (isBound ? (container == null ? void 0 : container.rotation) ?? rotation : rotation) ? "all" : "position",
|
|
5849
|
-
visible: !
|
|
5925
|
+
visible: !isEditingState && !(autoEdit && !text),
|
|
5850
5926
|
opacity: text ? style.opacity : isBound ? 0 : 0.4,
|
|
5851
5927
|
draggable: isDraggable,
|
|
5852
5928
|
listening: !isBound,
|
|
@@ -5896,6 +5972,263 @@ const TextShape = ({
|
|
|
5896
5972
|
);
|
|
5897
5973
|
};
|
|
5898
5974
|
const TextShape$1 = React.memo(TextShape);
|
|
5975
|
+
const TextLabel = ({
|
|
5976
|
+
element,
|
|
5977
|
+
connector,
|
|
5978
|
+
onChange,
|
|
5979
|
+
autoEdit,
|
|
5980
|
+
onEditStart,
|
|
5981
|
+
onEditEnd
|
|
5982
|
+
}) => {
|
|
5983
|
+
const { id, x, y, width, height, style, text } = element;
|
|
5984
|
+
const labelFontSize = style.fontSize * 0.9;
|
|
5985
|
+
const textRef = useRef(null);
|
|
5986
|
+
const bgRectRef = useRef(null);
|
|
5987
|
+
const isEditingRef = useRef(false);
|
|
5988
|
+
const [isEditingState, setIsEditingState] = useState(false);
|
|
5989
|
+
const autoEditDoneRef = useRef(false);
|
|
5990
|
+
const boundPos = useMemo(() => {
|
|
5991
|
+
const textWidth = Math.max(LABEL_MIN_WIDTH, width || 60);
|
|
5992
|
+
const pos = computeConnectorLabelPosition(connector, textWidth, height);
|
|
5993
|
+
return { x: pos.x, y: pos.y, width: textWidth };
|
|
5994
|
+
}, [connector, width, height]);
|
|
5995
|
+
const effectiveX = boundPos.x;
|
|
5996
|
+
const effectiveY = boundPos.y;
|
|
5997
|
+
const syncSize = useCallback(() => {
|
|
5998
|
+
const node = textRef.current;
|
|
5999
|
+
if (!node || isEditingRef.current) return;
|
|
6000
|
+
const measuredHeight = node.height();
|
|
6001
|
+
const updates = {};
|
|
6002
|
+
let needsUpdate = false;
|
|
6003
|
+
if (Math.abs(measuredHeight - height) > 1) {
|
|
6004
|
+
updates.height = measuredHeight;
|
|
6005
|
+
needsUpdate = true;
|
|
6006
|
+
}
|
|
6007
|
+
const measuredWidth = measureLabelText(text || " ", labelFontSize, style.fontFamily).width;
|
|
6008
|
+
if (Math.abs(measuredWidth - width) > 1) {
|
|
6009
|
+
updates.width = measuredWidth;
|
|
6010
|
+
needsUpdate = true;
|
|
6011
|
+
}
|
|
6012
|
+
if (needsUpdate) {
|
|
6013
|
+
onChange(id, updates);
|
|
6014
|
+
}
|
|
6015
|
+
}, [id, text, height, width, labelFontSize, style.fontFamily, onChange]);
|
|
6016
|
+
const syncSizeInitRef = useRef(true);
|
|
6017
|
+
useEffect(() => {
|
|
6018
|
+
if (syncSizeInitRef.current) {
|
|
6019
|
+
syncSizeInitRef.current = false;
|
|
6020
|
+
if (text && height > 0) return;
|
|
6021
|
+
}
|
|
6022
|
+
const rafId = requestAnimationFrame(syncSize);
|
|
6023
|
+
return () => cancelAnimationFrame(rafId);
|
|
6024
|
+
}, [text, labelFontSize, style.fontFamily, syncSize]);
|
|
6025
|
+
const openEditor = useCallback(() => {
|
|
6026
|
+
var _a;
|
|
6027
|
+
const textNode = textRef.current;
|
|
6028
|
+
if (!textNode || isEditingRef.current) return;
|
|
6029
|
+
const stage = textNode.getStage();
|
|
6030
|
+
if (!stage) return;
|
|
6031
|
+
isEditingRef.current = true;
|
|
6032
|
+
setIsEditingState(true);
|
|
6033
|
+
onEditStart == null ? void 0 : onEditStart(id);
|
|
6034
|
+
const stageContainer = stage.container();
|
|
6035
|
+
const absTransform = textNode.getAbsoluteTransform().copy();
|
|
6036
|
+
const absPos = absTransform.point({ x: 0, y: 0 });
|
|
6037
|
+
const stageScaleX = stage.scaleX();
|
|
6038
|
+
const screenFontSize = labelFontSize * stageScaleX;
|
|
6039
|
+
const nodeWidth = textNode.width();
|
|
6040
|
+
const scaledPadH = LABEL_PADDING_H * stageScaleX;
|
|
6041
|
+
const scaledPadV = LABEL_PADDING_V * stageScaleX;
|
|
6042
|
+
const initMeasured = measureLabelText(text || " ", labelFontSize, style.fontFamily);
|
|
6043
|
+
const initPillW = Math.max(LABEL_MIN_WIDTH, initMeasured.width) * stageScaleX + scaledPadH * 2;
|
|
6044
|
+
const initPillH = initMeasured.height * stageScaleX + scaledPadV * 2;
|
|
6045
|
+
const connMidScreenX = absPos.x + nodeWidth * stageScaleX / 2;
|
|
6046
|
+
const connMidScreenY = absPos.y + textNode.height() * stageScaleX / 2;
|
|
6047
|
+
const originalText = text;
|
|
6048
|
+
const textarea = document.createElement("textarea");
|
|
6049
|
+
stageContainer.appendChild(textarea);
|
|
6050
|
+
textarea.value = text;
|
|
6051
|
+
textarea.style.position = "absolute";
|
|
6052
|
+
textarea.style.top = `${connMidScreenY - initPillH / 2}px`;
|
|
6053
|
+
textarea.style.left = `${connMidScreenX - initPillW / 2}px`;
|
|
6054
|
+
textarea.style.width = `${initPillW}px`;
|
|
6055
|
+
textarea.style.height = `${initPillH}px`;
|
|
6056
|
+
textarea.style.fontSize = `${screenFontSize}px`;
|
|
6057
|
+
textarea.style.fontFamily = style.fontFamily;
|
|
6058
|
+
textarea.style.color = style.strokeColor;
|
|
6059
|
+
textarea.style.lineHeight = `${LABEL_LINE_HEIGHT}`;
|
|
6060
|
+
textarea.style.border = "none";
|
|
6061
|
+
textarea.style.margin = "0";
|
|
6062
|
+
textarea.style.outline = "none";
|
|
6063
|
+
textarea.style.resize = "none";
|
|
6064
|
+
textarea.style.overflow = "hidden";
|
|
6065
|
+
textarea.style.zIndex = "1000";
|
|
6066
|
+
textarea.rows = 1;
|
|
6067
|
+
textarea.style.minHeight = `${Math.max(20, screenFontSize * LABEL_LINE_HEIGHT)}px`;
|
|
6068
|
+
textarea.style.boxSizing = "border-box";
|
|
6069
|
+
textarea.style.transformOrigin = "left top";
|
|
6070
|
+
textarea.style.letterSpacing = "normal";
|
|
6071
|
+
textarea.style.caretColor = style.strokeColor;
|
|
6072
|
+
textarea.style.background = "#f8f9fa";
|
|
6073
|
+
textarea.style.borderRadius = `${LABEL_CORNER * stageScaleX}px`;
|
|
6074
|
+
textarea.style.padding = `${scaledPadV}px ${scaledPadH}px`;
|
|
6075
|
+
textarea.style.textAlign = "center";
|
|
6076
|
+
textarea.style.whiteSpace = "nowrap";
|
|
6077
|
+
textarea.style.wordBreak = "normal";
|
|
6078
|
+
const autoGrow = () => {
|
|
6079
|
+
const currentText = textarea.value || " ";
|
|
6080
|
+
const measured = measureLabelText(currentText, labelFontSize, style.fontFamily);
|
|
6081
|
+
const newTextW = Math.max(LABEL_MIN_WIDTH, measured.width) * stageScaleX;
|
|
6082
|
+
const pillW = newTextW + scaledPadH * 2;
|
|
6083
|
+
const pillH = measured.height * stageScaleX + scaledPadV * 2;
|
|
6084
|
+
textarea.style.width = `${pillW}px`;
|
|
6085
|
+
textarea.style.height = `${pillH}px`;
|
|
6086
|
+
textarea.style.left = `${connMidScreenX - pillW / 2}px`;
|
|
6087
|
+
textarea.style.top = `${connMidScreenY - pillH / 2}px`;
|
|
6088
|
+
};
|
|
6089
|
+
textarea.addEventListener("input", autoGrow);
|
|
6090
|
+
requestAnimationFrame(autoGrow);
|
|
6091
|
+
textNode.hide();
|
|
6092
|
+
(_a = bgRectRef.current) == null ? void 0 : _a.hide();
|
|
6093
|
+
stage.batchDraw();
|
|
6094
|
+
textarea.focus();
|
|
6095
|
+
textarea.select();
|
|
6096
|
+
let cancelled = false;
|
|
6097
|
+
const finishEdit = () => {
|
|
6098
|
+
var _a2;
|
|
6099
|
+
if (!isEditingRef.current) return;
|
|
6100
|
+
isEditingRef.current = false;
|
|
6101
|
+
setIsEditingState(false);
|
|
6102
|
+
const newText = cancelled ? originalText : textarea.value;
|
|
6103
|
+
const isEmpty = newText.trim() === "";
|
|
6104
|
+
textarea.removeEventListener("input", autoGrow);
|
|
6105
|
+
textarea.removeEventListener("blur", handleBlur);
|
|
6106
|
+
textarea.removeEventListener("keydown", handleKeyDown);
|
|
6107
|
+
if (textarea.parentNode) {
|
|
6108
|
+
textarea.parentNode.removeChild(textarea);
|
|
6109
|
+
}
|
|
6110
|
+
textNode.show();
|
|
6111
|
+
(_a2 = bgRectRef.current) == null ? void 0 : _a2.show();
|
|
6112
|
+
stage.batchDraw();
|
|
6113
|
+
if (!cancelled) {
|
|
6114
|
+
const measured = measureLabelText(newText || " ", labelFontSize, style.fontFamily);
|
|
6115
|
+
onChange(id, {
|
|
6116
|
+
text: newText,
|
|
6117
|
+
width: measured.width,
|
|
6118
|
+
height: measured.height
|
|
6119
|
+
});
|
|
6120
|
+
}
|
|
6121
|
+
onEditEnd == null ? void 0 : onEditEnd(id, isEmpty);
|
|
6122
|
+
};
|
|
6123
|
+
const handleBlur = () => finishEdit();
|
|
6124
|
+
const handleKeyDown = (e) => {
|
|
6125
|
+
e.stopPropagation();
|
|
6126
|
+
if (e.key === "Escape") {
|
|
6127
|
+
cancelled = true;
|
|
6128
|
+
textarea.blur();
|
|
6129
|
+
}
|
|
6130
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
6131
|
+
e.preventDefault();
|
|
6132
|
+
textarea.blur();
|
|
6133
|
+
}
|
|
6134
|
+
if (e.key === "Tab") {
|
|
6135
|
+
e.preventDefault();
|
|
6136
|
+
textarea.blur();
|
|
6137
|
+
}
|
|
6138
|
+
};
|
|
6139
|
+
textarea.addEventListener("blur", handleBlur);
|
|
6140
|
+
textarea.addEventListener("keydown", handleKeyDown);
|
|
6141
|
+
}, [id, text, style, onChange, onEditStart, onEditEnd]);
|
|
6142
|
+
useEffect(() => {
|
|
6143
|
+
if (!autoEdit) {
|
|
6144
|
+
autoEditDoneRef.current = false;
|
|
6145
|
+
}
|
|
6146
|
+
}, [autoEdit]);
|
|
6147
|
+
useEffect(() => {
|
|
6148
|
+
if (autoEdit && !autoEditDoneRef.current && textRef.current) {
|
|
6149
|
+
autoEditDoneRef.current = true;
|
|
6150
|
+
openEditor();
|
|
6151
|
+
}
|
|
6152
|
+
}, [autoEdit, openEditor]);
|
|
6153
|
+
const pillTextW = Math.max(LABEL_MIN_WIDTH, width || 60);
|
|
6154
|
+
const labelW = pillTextW + LABEL_PADDING_H * 2;
|
|
6155
|
+
const labelH = height + LABEL_PADDING_V * 2;
|
|
6156
|
+
const isVisible = !isEditingState && !(autoEdit && !text);
|
|
6157
|
+
const labelOpacity = text ? style.opacity : 0;
|
|
6158
|
+
return /* @__PURE__ */ jsxs(
|
|
6159
|
+
Group$1,
|
|
6160
|
+
{
|
|
6161
|
+
x: (effectiveX ?? 0) - LABEL_PADDING_H,
|
|
6162
|
+
y: (effectiveY ?? 0) - LABEL_PADDING_V,
|
|
6163
|
+
visible: isVisible,
|
|
6164
|
+
opacity: labelOpacity,
|
|
6165
|
+
listening: false,
|
|
6166
|
+
perfectDrawEnabled: false,
|
|
6167
|
+
children: [
|
|
6168
|
+
/* @__PURE__ */ jsx(
|
|
6169
|
+
Rect,
|
|
6170
|
+
{
|
|
6171
|
+
ref: bgRectRef,
|
|
6172
|
+
width: labelW,
|
|
6173
|
+
height: labelH,
|
|
6174
|
+
fill: "#f8f9fa",
|
|
6175
|
+
cornerRadius: LABEL_CORNER,
|
|
6176
|
+
listening: false,
|
|
6177
|
+
perfectDrawEnabled: false
|
|
6178
|
+
}
|
|
6179
|
+
),
|
|
6180
|
+
/* @__PURE__ */ jsx(
|
|
6181
|
+
Text,
|
|
6182
|
+
{
|
|
6183
|
+
ref: textRef,
|
|
6184
|
+
id,
|
|
6185
|
+
x: LABEL_PADDING_H,
|
|
6186
|
+
y: LABEL_PADDING_V,
|
|
6187
|
+
text: text || "",
|
|
6188
|
+
fontSize: labelFontSize,
|
|
6189
|
+
fontFamily: style.fontFamily,
|
|
6190
|
+
fill: style.strokeColor,
|
|
6191
|
+
lineHeight: LABEL_LINE_HEIGHT,
|
|
6192
|
+
wrap: "none",
|
|
6193
|
+
listening: false,
|
|
6194
|
+
perfectDrawEnabled: false,
|
|
6195
|
+
onDblClick: openEditor,
|
|
6196
|
+
onDblTap: openEditor
|
|
6197
|
+
}
|
|
6198
|
+
)
|
|
6199
|
+
]
|
|
6200
|
+
}
|
|
6201
|
+
);
|
|
6202
|
+
};
|
|
6203
|
+
const TextLabel$1 = React.memo(TextLabel);
|
|
6204
|
+
const IMAGE_CACHE_LIMIT = 50;
|
|
6205
|
+
const _imgCache = /* @__PURE__ */ new Map();
|
|
6206
|
+
function cachedLoadImage(src, onLoad, onError) {
|
|
6207
|
+
const cached = _imgCache.get(src);
|
|
6208
|
+
if (cached) {
|
|
6209
|
+
onLoad(cached);
|
|
6210
|
+
return () => {
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
const img = new window.Image();
|
|
6214
|
+
if (!src.startsWith("data:")) {
|
|
6215
|
+
img.crossOrigin = "anonymous";
|
|
6216
|
+
}
|
|
6217
|
+
img.onload = () => {
|
|
6218
|
+
if (_imgCache.size >= IMAGE_CACHE_LIMIT) {
|
|
6219
|
+
const oldest = _imgCache.keys().next().value;
|
|
6220
|
+
if (oldest !== void 0) _imgCache.delete(oldest);
|
|
6221
|
+
}
|
|
6222
|
+
_imgCache.set(src, img);
|
|
6223
|
+
onLoad(img);
|
|
6224
|
+
};
|
|
6225
|
+
img.onerror = () => onError();
|
|
6226
|
+
img.src = src;
|
|
6227
|
+
return () => {
|
|
6228
|
+
img.onload = null;
|
|
6229
|
+
img.onerror = null;
|
|
6230
|
+
};
|
|
6231
|
+
}
|
|
5899
6232
|
const ImageShape = ({
|
|
5900
6233
|
element,
|
|
5901
6234
|
isSelected,
|
|
@@ -5908,8 +6241,8 @@ const ImageShape = ({
|
|
|
5908
6241
|
onDragSnap
|
|
5909
6242
|
}) => {
|
|
5910
6243
|
const { id, x, y, width, height, rotation, src, style, crop, cornerRadius, scaleMode, isLocked } = element;
|
|
5911
|
-
const [image, setImage] = useState(null);
|
|
5912
|
-
const [loadError, setLoadError] = useState(
|
|
6244
|
+
const [image, setImage] = useState(() => _imgCache.get(src) ?? null);
|
|
6245
|
+
const [loadError, setLoadError] = useState(() => !src);
|
|
5913
6246
|
const [liveSize, setLiveSize] = useState({ w: width, h: height });
|
|
5914
6247
|
useEffect(() => {
|
|
5915
6248
|
setLiveSize({ w: width, h: height });
|
|
@@ -5922,21 +6255,17 @@ const ImageShape = ({
|
|
|
5922
6255
|
setLoadError(true);
|
|
5923
6256
|
return;
|
|
5924
6257
|
}
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
return () => {
|
|
5937
|
-
img.onload = null;
|
|
5938
|
-
img.onerror = null;
|
|
5939
|
-
};
|
|
6258
|
+
return cachedLoadImage(
|
|
6259
|
+
src,
|
|
6260
|
+
(img) => {
|
|
6261
|
+
setImage(img);
|
|
6262
|
+
setLoadError(false);
|
|
6263
|
+
},
|
|
6264
|
+
() => {
|
|
6265
|
+
setImage(null);
|
|
6266
|
+
setLoadError(true);
|
|
6267
|
+
}
|
|
6268
|
+
);
|
|
5940
6269
|
}, [src]);
|
|
5941
6270
|
const handleDragMove = useCallback((e) => {
|
|
5942
6271
|
let nx = e.target.x(), ny = e.target.y();
|
|
@@ -6078,13 +6407,14 @@ const ImageShape = ({
|
|
|
6078
6407
|
shadowBlur: isSelected ? 6 : 0,
|
|
6079
6408
|
shadowOpacity: isSelected ? 0.5 : 0,
|
|
6080
6409
|
children: [
|
|
6081
|
-
|
|
6410
|
+
/* @__PURE__ */ jsx(
|
|
6082
6411
|
Rect,
|
|
6083
6412
|
{
|
|
6084
6413
|
width: rw,
|
|
6085
6414
|
height: rh,
|
|
6086
|
-
fill: style.fillColor === "transparent" ? void 0 : style.fillColor,
|
|
6087
|
-
cornerRadius
|
|
6415
|
+
fill: scaleMode === "fit" ? style.fillColor === "transparent" ? void 0 : style.fillColor : "transparent",
|
|
6416
|
+
cornerRadius,
|
|
6417
|
+
perfectDrawEnabled: false
|
|
6088
6418
|
}
|
|
6089
6419
|
),
|
|
6090
6420
|
/* @__PURE__ */ jsx(
|
|
@@ -6176,7 +6506,22 @@ const CanvasElementRenderer = ({
|
|
|
6176
6506
|
return /* @__PURE__ */ jsx(ArrowShape$1, { element, isSelected, isEditing, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, allElements });
|
|
6177
6507
|
case "freedraw":
|
|
6178
6508
|
return /* @__PURE__ */ jsx(FreeDrawShape$1, { element, isSelected, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, onDragSnap });
|
|
6179
|
-
case "text":
|
|
6509
|
+
case "text": {
|
|
6510
|
+
const connContainer = element.containerId && allElements ? allElements.find((el) => el.id === element.containerId) : null;
|
|
6511
|
+
const isConnectorLabel = connContainer && (connContainer.type === "arrow" || connContainer.type === "line");
|
|
6512
|
+
if (isConnectorLabel) {
|
|
6513
|
+
return /* @__PURE__ */ jsx(
|
|
6514
|
+
TextLabel$1,
|
|
6515
|
+
{
|
|
6516
|
+
element,
|
|
6517
|
+
connector: connContainer,
|
|
6518
|
+
onChange,
|
|
6519
|
+
autoEdit: autoEditText,
|
|
6520
|
+
onEditStart: onTextEditStart,
|
|
6521
|
+
onEditEnd: onTextEditEnd
|
|
6522
|
+
}
|
|
6523
|
+
);
|
|
6524
|
+
}
|
|
6180
6525
|
return /* @__PURE__ */ jsx(
|
|
6181
6526
|
TextShape$1,
|
|
6182
6527
|
{
|
|
@@ -6193,6 +6538,7 @@ const CanvasElementRenderer = ({
|
|
|
6193
6538
|
gridSnap
|
|
6194
6539
|
}
|
|
6195
6540
|
);
|
|
6541
|
+
}
|
|
6196
6542
|
case "image":
|
|
6197
6543
|
return /* @__PURE__ */ jsx(ImageShape$1, { element, isSelected, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, onDragSnap });
|
|
6198
6544
|
default:
|
|
@@ -6529,6 +6875,23 @@ const LinearElementHandles = ({
|
|
|
6529
6875
|
const isCurved = "lineType" in element && element.lineType === "curved";
|
|
6530
6876
|
const isElbow = "lineType" in element && element.lineType === "elbow";
|
|
6531
6877
|
const endpointsOnly = isCurved || isElbow;
|
|
6878
|
+
const labelRect = useMemo(() => {
|
|
6879
|
+
if (!element.boundElements) return null;
|
|
6880
|
+
for (const be of element.boundElements) {
|
|
6881
|
+
if (be.type !== "text") continue;
|
|
6882
|
+
const txt = allElements.find((e) => e.id === be.id);
|
|
6883
|
+
if (!txt || !txt.text) continue;
|
|
6884
|
+
const textW = Math.max(LABEL_MIN_WIDTH, txt.width || 60);
|
|
6885
|
+
const textH = txt.height || 30;
|
|
6886
|
+
return {
|
|
6887
|
+
x: txt.x - LABEL_PADDING_H,
|
|
6888
|
+
y: txt.y - LABEL_PADDING_V,
|
|
6889
|
+
w: textW + LABEL_PADDING_H * 2,
|
|
6890
|
+
h: textH + LABEL_PADDING_V * 2
|
|
6891
|
+
};
|
|
6892
|
+
}
|
|
6893
|
+
return null;
|
|
6894
|
+
}, [element.boundElements, allElements]);
|
|
6532
6895
|
const isEndpoint = useCallback(
|
|
6533
6896
|
(idx) => idx === 0 || idx === pairs.length - 1,
|
|
6534
6897
|
[pairs.length]
|
|
@@ -6708,6 +7071,12 @@ const LinearElementHandles = ({
|
|
|
6708
7071
|
!endpointsOnly && pairs.length >= 2 && pairs.slice(0, -1).map((pt, segIdx) => {
|
|
6709
7072
|
const next = pairs[segIdx + 1];
|
|
6710
7073
|
const mid = midpoint$1(pt, next);
|
|
7074
|
+
if (labelRect) {
|
|
7075
|
+
const wx = x + mid.x, wy = y + mid.y;
|
|
7076
|
+
if (wx >= labelRect.x && wx <= labelRect.x + labelRect.w && wy >= labelRect.y && wy <= labelRect.y + labelRect.h) {
|
|
7077
|
+
return null;
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
6711
7080
|
const isHovered = hoveredMidpointIndex === segIdx;
|
|
6712
7081
|
return /* @__PURE__ */ jsx(
|
|
6713
7082
|
Circle$1,
|
|
@@ -6764,6 +7133,12 @@ const LinearElementHandles = ({
|
|
|
6764
7133
|
const curvature = element.curvature ?? CURVE_RATIO;
|
|
6765
7134
|
const cp = computeCurveControlPoint(localStart, localEnd, curvature);
|
|
6766
7135
|
const curveMid = quadBezierAt(localStart, cp, localEnd, 0.5);
|
|
7136
|
+
if (labelRect) {
|
|
7137
|
+
const wx = x + curveMid.x, wy = y + curveMid.y;
|
|
7138
|
+
if (wx >= labelRect.x && wx <= labelRect.x + labelRect.w && wy >= labelRect.y && wy <= labelRect.y + labelRect.h) {
|
|
7139
|
+
return null;
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
6767
7142
|
return /* @__PURE__ */ jsx(
|
|
6768
7143
|
Circle$1,
|
|
6769
7144
|
{
|
|
@@ -8363,28 +8738,6 @@ const ArrowheadLineIcon = ({ arrowhead, end, color }) => {
|
|
|
8363
8738
|
] }) });
|
|
8364
8739
|
};
|
|
8365
8740
|
const SvgIcon = ({ children, color, size = 12 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children });
|
|
8366
|
-
const LayerIcons = {
|
|
8367
|
-
sendToBack: (color) => /* @__PURE__ */ jsxs(SvgIcon, { color, children: [
|
|
8368
|
-
/* @__PURE__ */ jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
|
|
8369
|
-
/* @__PURE__ */ jsx("path", { d: "M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z", opacity: "0.3" }),
|
|
8370
|
-
/* @__PURE__ */ jsx("polyline", { points: "12 12 12 18" }),
|
|
8371
|
-
/* @__PURE__ */ jsx("polyline", { points: "9 15 12 18 15 15" })
|
|
8372
|
-
] }),
|
|
8373
|
-
sendBackward: (color) => /* @__PURE__ */ jsxs(SvgIcon, { color, children: [
|
|
8374
|
-
/* @__PURE__ */ jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
8375
|
-
/* @__PURE__ */ jsx("polyline", { points: "19 12 12 19 5 12" })
|
|
8376
|
-
] }),
|
|
8377
|
-
bringForward: (color) => /* @__PURE__ */ jsxs(SvgIcon, { color, children: [
|
|
8378
|
-
/* @__PURE__ */ jsx("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
|
|
8379
|
-
/* @__PURE__ */ jsx("polyline", { points: "5 12 12 5 19 12" })
|
|
8380
|
-
] }),
|
|
8381
|
-
bringToFront: (color) => /* @__PURE__ */ jsxs(SvgIcon, { color, children: [
|
|
8382
|
-
/* @__PURE__ */ jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
|
|
8383
|
-
/* @__PURE__ */ jsx("path", { d: "M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z", opacity: "0.3" }),
|
|
8384
|
-
/* @__PURE__ */ jsx("polyline", { points: "12 18 12 12" }),
|
|
8385
|
-
/* @__PURE__ */ jsx("polyline", { points: "9 15 12 12 15 15" })
|
|
8386
|
-
] })
|
|
8387
|
-
};
|
|
8388
8741
|
const ActionIcons = {
|
|
8389
8742
|
duplicate: (color) => /* @__PURE__ */ jsxs(SvgIcon, { color, children: [
|
|
8390
8743
|
/* @__PURE__ */ jsx("rect", { x: "8", y: "8", width: "12", height: "12", rx: "2" }),
|
|
@@ -8418,10 +8771,6 @@ const StylePanel = ({ theme }) => {
|
|
|
8418
8771
|
const toggleLockElements = useCanvasStore((s) => s.toggleLockElements);
|
|
8419
8772
|
const deleteElements = useCanvasStore((s) => s.deleteElements);
|
|
8420
8773
|
const duplicateElements = useCanvasStore((s) => s.duplicateElements);
|
|
8421
|
-
const bringToFront = useCanvasStore((s) => s.bringToFront);
|
|
8422
|
-
const sendToBack = useCanvasStore((s) => s.sendToBack);
|
|
8423
|
-
const bringForward = useCanvasStore((s) => s.bringForward);
|
|
8424
|
-
const sendBackward = useCanvasStore((s) => s.sendBackward);
|
|
8425
8774
|
const activeTool = useCanvasStore((s) => s.activeTool);
|
|
8426
8775
|
const currentLineType = useCanvasStore((s) => s.currentLineType);
|
|
8427
8776
|
const setCurrentLineType = useCanvasStore((s) => s.setCurrentLineType);
|
|
@@ -8914,31 +9263,22 @@ const StylePanel = ({ theme }) => {
|
|
|
8914
9263
|
{
|
|
8915
9264
|
theme,
|
|
8916
9265
|
style: { width: "100%", padding: "5px 0", borderRadius: 4 },
|
|
8917
|
-
onClick: () => {
|
|
8918
|
-
const
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
var _a;
|
|
8923
|
-
const file = (_a = input.files) == null ? void 0 : _a[0];
|
|
9266
|
+
onClick: async () => {
|
|
9267
|
+
const targetId = selectedImage.id;
|
|
9268
|
+
try {
|
|
9269
|
+
const files = await openImageFilePicker();
|
|
9270
|
+
const file = files[0];
|
|
8924
9271
|
if (!file) return;
|
|
8925
|
-
const
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
img.
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
pushHistory();
|
|
8936
|
-
};
|
|
8937
|
-
img.src = dataURL;
|
|
8938
|
-
};
|
|
8939
|
-
reader.readAsDataURL(file);
|
|
8940
|
-
};
|
|
8941
|
-
input.click();
|
|
9272
|
+
const dataURL = await fileToDataURL(file);
|
|
9273
|
+
const img = await loadImage(dataURL);
|
|
9274
|
+
updateElement(targetId, {
|
|
9275
|
+
src: dataURL,
|
|
9276
|
+
naturalWidth: img.naturalWidth,
|
|
9277
|
+
naturalHeight: img.naturalHeight
|
|
9278
|
+
});
|
|
9279
|
+
pushHistory();
|
|
9280
|
+
} catch {
|
|
9281
|
+
}
|
|
8942
9282
|
},
|
|
8943
9283
|
children: "Replace Image…"
|
|
8944
9284
|
}
|
|
@@ -8946,27 +9286,6 @@ const StylePanel = ({ theme }) => {
|
|
|
8946
9286
|
] }),
|
|
8947
9287
|
hasSelection && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8948
9288
|
/* @__PURE__ */ jsx("hr", { style: dividerStyle }),
|
|
8949
|
-
/* @__PURE__ */ jsx(PanelSection, { label: "Layers", theme, children: /* @__PURE__ */ jsx(ButtonRow, { columns: 4, children: [
|
|
8950
|
-
{ fn: sendToBack, icon: LayerIcons.sendToBack, tip: "Send to back" },
|
|
8951
|
-
{ fn: sendBackward, icon: LayerIcons.sendBackward, tip: "Send backward" },
|
|
8952
|
-
{ fn: bringForward, icon: LayerIcons.bringForward, tip: "Bring forward" },
|
|
8953
|
-
{ fn: bringToFront, icon: LayerIcons.bringToFront, tip: "Bring to front" }
|
|
8954
|
-
].map(({ fn, icon, tip }) => /* @__PURE__ */ jsx(
|
|
8955
|
-
PanelButton,
|
|
8956
|
-
{
|
|
8957
|
-
variant: "action",
|
|
8958
|
-
theme,
|
|
8959
|
-
title: tip,
|
|
8960
|
-
width: "100%",
|
|
8961
|
-
height: 32,
|
|
8962
|
-
onClick: () => {
|
|
8963
|
-
fn(selectedIds);
|
|
8964
|
-
pushHistory();
|
|
8965
|
-
},
|
|
8966
|
-
children: (hl) => icon(hl ? theme.activeToolColor : theme.textColor)
|
|
8967
|
-
},
|
|
8968
|
-
tip
|
|
8969
|
-
)) }) }),
|
|
8970
9289
|
/* @__PURE__ */ jsx(PanelSection, { label: "Actions", theme, children: /* @__PURE__ */ jsxs(ButtonRow, { columns: 4, children: [
|
|
8971
9290
|
/* @__PURE__ */ jsx(
|
|
8972
9291
|
PanelButton,
|
|
@@ -9664,48 +9983,14 @@ function computeAlignGuides(movingBounds, others, excludeIds, threshold = SNAP_T
|
|
|
9664
9983
|
return result;
|
|
9665
9984
|
}
|
|
9666
9985
|
function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
9667
|
-
const setActiveTool = useCanvasStore((s) => s.setActiveTool);
|
|
9668
|
-
const undo = useCanvasStore((s) => s.undo);
|
|
9669
|
-
const redo = useCanvasStore((s) => s.redo);
|
|
9670
|
-
const selectedIds = useCanvasStore((s) => s.selectedIds);
|
|
9671
|
-
const elements = useCanvasStore((s) => s.elements);
|
|
9672
|
-
const deleteElements = useCanvasStore((s) => s.deleteElements);
|
|
9673
|
-
const duplicateElements = useCanvasStore((s) => s.duplicateElements);
|
|
9674
|
-
const bringToFront = useCanvasStore((s) => s.bringToFront);
|
|
9675
|
-
const sendToBack = useCanvasStore((s) => s.sendToBack);
|
|
9676
|
-
const bringForward = useCanvasStore((s) => s.bringForward);
|
|
9677
|
-
const sendBackward = useCanvasStore((s) => s.sendBackward);
|
|
9678
|
-
const toggleGrid = useCanvasStore((s) => s.toggleGrid);
|
|
9679
|
-
const showGrid = useCanvasStore((s) => s.showGrid);
|
|
9680
|
-
const zoomIn = useCanvasStore((s) => s.zoomIn);
|
|
9681
|
-
const zoomOut = useCanvasStore((s) => s.zoomOut);
|
|
9682
|
-
const resetZoom = useCanvasStore((s) => s.resetZoom);
|
|
9683
|
-
const zoomToFit = useCanvasStore((s) => s.zoomToFit);
|
|
9684
|
-
const zoomToSelection = useCanvasStore((s) => s.zoomToSelection);
|
|
9685
|
-
const clearSelection = useCanvasStore((s) => s.clearSelection);
|
|
9686
|
-
const updateElement = useCanvasStore((s) => s.updateElement);
|
|
9687
|
-
const addElement = useCanvasStore((s) => s.addElement);
|
|
9688
|
-
const setSelectedIds = useCanvasStore((s) => s.setSelectedIds);
|
|
9689
|
-
const pushHistory = useCanvasStore((s) => s.pushHistory);
|
|
9690
|
-
const linearEdit = useLinearEditStore();
|
|
9691
9986
|
const copyElements = useCallback(() => {
|
|
9987
|
+
const { selectedIds, elements } = useCanvasStore.getState();
|
|
9692
9988
|
if (selectedIds.length === 0) return;
|
|
9693
9989
|
setClipboard(gatherElementsForCopy(selectedIds, elements));
|
|
9694
|
-
}, [
|
|
9695
|
-
const pasteElements = useCallback(() => {
|
|
9696
|
-
const clip = getClipboard();
|
|
9697
|
-
if (clip.length === 0) return;
|
|
9698
|
-
const PASTE_OFFSET = 20;
|
|
9699
|
-
const { clones, selectedCloneIds } = cloneAndRemapElements(clip, clip, PASTE_OFFSET);
|
|
9700
|
-
clones.forEach((el) => addElement(el));
|
|
9701
|
-
setSelectedIds(selectedCloneIds.length > 0 ? selectedCloneIds : clones.map((c) => c.id));
|
|
9702
|
-
pushHistory();
|
|
9703
|
-
setClipboard(
|
|
9704
|
-
clip.map((el) => ({ ...el, x: el.x + PASTE_OFFSET, y: el.y + PASTE_OFFSET }))
|
|
9705
|
-
);
|
|
9706
|
-
}, [addElement, setSelectedIds, pushHistory]);
|
|
9990
|
+
}, []);
|
|
9707
9991
|
const nudge = useCallback(
|
|
9708
9992
|
(dx, dy) => {
|
|
9993
|
+
const { selectedIds, elements, updateElement, pushHistory } = useCanvasStore.getState();
|
|
9709
9994
|
if (selectedIds.length === 0) return;
|
|
9710
9995
|
selectedIds.forEach((id) => {
|
|
9711
9996
|
const el = elements.find((e) => e.id === id);
|
|
@@ -9715,7 +10000,7 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9715
10000
|
});
|
|
9716
10001
|
pushHistory();
|
|
9717
10002
|
},
|
|
9718
|
-
[
|
|
10003
|
+
[]
|
|
9719
10004
|
);
|
|
9720
10005
|
useEffect(() => {
|
|
9721
10006
|
if (!enabled) return;
|
|
@@ -9723,6 +10008,8 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9723
10008
|
const tag = e.target.tagName;
|
|
9724
10009
|
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
9725
10010
|
const isCmd = e.metaKey || e.ctrlKey;
|
|
10011
|
+
const store = useCanvasStore.getState();
|
|
10012
|
+
const linearEdit = useLinearEditStore.getState();
|
|
9726
10013
|
if (!isCmd && !e.shiftKey) {
|
|
9727
10014
|
const toolMap = {
|
|
9728
10015
|
v: "select",
|
|
@@ -9740,30 +10027,29 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9740
10027
|
const tool = toolMap[e.key.toLowerCase()];
|
|
9741
10028
|
if (tool) {
|
|
9742
10029
|
e.preventDefault();
|
|
9743
|
-
setActiveTool(tool);
|
|
10030
|
+
store.setActiveTool(tool);
|
|
9744
10031
|
return;
|
|
9745
10032
|
}
|
|
9746
10033
|
}
|
|
9747
10034
|
if (isCmd && e.key === "z" && !e.shiftKey) {
|
|
9748
10035
|
e.preventDefault();
|
|
9749
|
-
undo();
|
|
10036
|
+
store.undo();
|
|
9750
10037
|
return;
|
|
9751
10038
|
}
|
|
9752
10039
|
if (isCmd && e.key === "z" && e.shiftKey) {
|
|
9753
10040
|
e.preventDefault();
|
|
9754
|
-
redo();
|
|
10041
|
+
store.redo();
|
|
9755
10042
|
return;
|
|
9756
10043
|
}
|
|
9757
10044
|
if (isCmd && e.key === "y") {
|
|
9758
10045
|
e.preventDefault();
|
|
9759
|
-
redo();
|
|
10046
|
+
store.redo();
|
|
9760
10047
|
return;
|
|
9761
10048
|
}
|
|
9762
10049
|
if ((e.key === "Delete" || e.key === "Backspace") && !isCmd) {
|
|
9763
10050
|
if (linearEdit.isEditing && linearEdit.selectedPointIndices.length > 0) {
|
|
9764
10051
|
e.preventDefault();
|
|
9765
|
-
const
|
|
9766
|
-
const el = elements2.find((e2) => e2.id === linearEdit.elementId);
|
|
10052
|
+
const el = store.elements.find((e2) => e2.id === linearEdit.elementId);
|
|
9767
10053
|
if (el) {
|
|
9768
10054
|
const pointCount = el.points.length / 2;
|
|
9769
10055
|
const indicesToDelete = new Set(linearEdit.selectedPointIndices);
|
|
@@ -9801,75 +10087,75 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9801
10087
|
if (indicesToDelete.has(pointCount - 1)) {
|
|
9802
10088
|
pointUpdates.endBinding = null;
|
|
9803
10089
|
}
|
|
9804
|
-
|
|
9805
|
-
|
|
10090
|
+
store.updateElement(el.id, pointUpdates);
|
|
10091
|
+
store.pushHistory();
|
|
9806
10092
|
linearEdit.setSelectedPoints([]);
|
|
9807
10093
|
}
|
|
9808
10094
|
}
|
|
9809
10095
|
return;
|
|
9810
10096
|
}
|
|
9811
|
-
if (selectedIds.length > 0) {
|
|
10097
|
+
if (store.selectedIds.length > 0) {
|
|
9812
10098
|
e.preventDefault();
|
|
9813
|
-
const unlocked = selectedIds.filter((sid) => {
|
|
9814
|
-
const el = elements.find((e2) => e2.id === sid);
|
|
10099
|
+
const unlocked = store.selectedIds.filter((sid) => {
|
|
10100
|
+
const el = store.elements.find((e2) => e2.id === sid);
|
|
9815
10101
|
return el && !el.isLocked;
|
|
9816
10102
|
});
|
|
9817
|
-
if (unlocked.length > 0) deleteElements(unlocked);
|
|
10103
|
+
if (unlocked.length > 0) store.deleteElements(unlocked);
|
|
9818
10104
|
}
|
|
9819
10105
|
return;
|
|
9820
10106
|
}
|
|
9821
10107
|
if (isCmd && e.key === "d") {
|
|
9822
10108
|
e.preventDefault();
|
|
9823
|
-
if (selectedIds.length > 0) {
|
|
9824
|
-
duplicateElements(selectedIds);
|
|
10109
|
+
if (store.selectedIds.length > 0) {
|
|
10110
|
+
store.duplicateElements(store.selectedIds);
|
|
9825
10111
|
}
|
|
9826
10112
|
return;
|
|
9827
10113
|
}
|
|
9828
10114
|
if (e.key === "]" && isCmd && e.shiftKey) {
|
|
9829
10115
|
e.preventDefault();
|
|
9830
|
-
bringToFront(selectedIds);
|
|
10116
|
+
store.bringToFront(store.selectedIds);
|
|
9831
10117
|
return;
|
|
9832
10118
|
}
|
|
9833
10119
|
if (e.key === "[" && isCmd && e.shiftKey) {
|
|
9834
10120
|
e.preventDefault();
|
|
9835
|
-
sendToBack(selectedIds);
|
|
10121
|
+
store.sendToBack(store.selectedIds);
|
|
9836
10122
|
return;
|
|
9837
10123
|
}
|
|
9838
10124
|
if (e.key === "]" && isCmd && !e.shiftKey) {
|
|
9839
10125
|
e.preventDefault();
|
|
9840
|
-
bringForward(selectedIds);
|
|
10126
|
+
store.bringForward(store.selectedIds);
|
|
9841
10127
|
return;
|
|
9842
10128
|
}
|
|
9843
10129
|
if (e.key === "[" && isCmd && !e.shiftKey) {
|
|
9844
10130
|
e.preventDefault();
|
|
9845
|
-
sendBackward(selectedIds);
|
|
10131
|
+
store.sendBackward(store.selectedIds);
|
|
9846
10132
|
return;
|
|
9847
10133
|
}
|
|
9848
10134
|
if (e.key === "g" && !isCmd) {
|
|
9849
10135
|
e.preventDefault();
|
|
9850
|
-
toggleGrid();
|
|
10136
|
+
store.toggleGrid();
|
|
9851
10137
|
return;
|
|
9852
10138
|
}
|
|
9853
10139
|
if (isCmd && (e.key === "=" || e.key === "+")) {
|
|
9854
10140
|
e.preventDefault();
|
|
9855
|
-
zoomIn();
|
|
10141
|
+
store.zoomIn();
|
|
9856
10142
|
return;
|
|
9857
10143
|
}
|
|
9858
10144
|
if (isCmd && e.key === "-") {
|
|
9859
10145
|
e.preventDefault();
|
|
9860
|
-
zoomOut();
|
|
10146
|
+
store.zoomOut();
|
|
9861
10147
|
return;
|
|
9862
10148
|
}
|
|
9863
10149
|
if (isCmd && e.key === "0") {
|
|
9864
10150
|
e.preventDefault();
|
|
9865
|
-
resetZoom();
|
|
10151
|
+
store.resetZoom();
|
|
9866
10152
|
return;
|
|
9867
10153
|
}
|
|
9868
10154
|
if (isCmd && e.shiftKey && e.key === "1") {
|
|
9869
10155
|
e.preventDefault();
|
|
9870
10156
|
if (containerRef == null ? void 0 : containerRef.current) {
|
|
9871
10157
|
const rect = containerRef.current.getBoundingClientRect();
|
|
9872
|
-
zoomToFit(rect.width, rect.height, void 0, { animate: true });
|
|
10158
|
+
store.zoomToFit(rect.width, rect.height, void 0, { animate: true });
|
|
9873
10159
|
}
|
|
9874
10160
|
return;
|
|
9875
10161
|
}
|
|
@@ -9877,14 +10163,14 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9877
10163
|
e.preventDefault();
|
|
9878
10164
|
if (containerRef == null ? void 0 : containerRef.current) {
|
|
9879
10165
|
const rect = containerRef.current.getBoundingClientRect();
|
|
9880
|
-
zoomToSelection(rect.width, rect.height, { animate: true });
|
|
10166
|
+
store.zoomToSelection(rect.width, rect.height, { animate: true });
|
|
9881
10167
|
}
|
|
9882
10168
|
return;
|
|
9883
10169
|
}
|
|
9884
10170
|
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key) && !isCmd) {
|
|
9885
|
-
if (selectedIds.length > 0 && !linearEdit.isEditing) {
|
|
10171
|
+
if (store.selectedIds.length > 0 && !linearEdit.isEditing) {
|
|
9886
10172
|
e.preventDefault();
|
|
9887
|
-
const baseStep = showGrid ? GRID_SIZE : 1;
|
|
10173
|
+
const baseStep = store.showGrid ? GRID_SIZE : 1;
|
|
9888
10174
|
const step = e.shiftKey ? baseStep * 10 : baseStep;
|
|
9889
10175
|
const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
|
|
9890
10176
|
const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
|
|
@@ -9893,7 +10179,7 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9893
10179
|
}
|
|
9894
10180
|
}
|
|
9895
10181
|
if (isCmd && e.key === "c") {
|
|
9896
|
-
if (selectedIds.length > 0) {
|
|
10182
|
+
if (store.selectedIds.length > 0) {
|
|
9897
10183
|
e.preventDefault();
|
|
9898
10184
|
copyElements();
|
|
9899
10185
|
}
|
|
@@ -9903,34 +10189,31 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9903
10189
|
return;
|
|
9904
10190
|
}
|
|
9905
10191
|
if (isCmd && e.key === "x") {
|
|
9906
|
-
if (selectedIds.length > 0) {
|
|
10192
|
+
if (store.selectedIds.length > 0) {
|
|
9907
10193
|
e.preventDefault();
|
|
9908
10194
|
copyElements();
|
|
9909
|
-
deleteElements(selectedIds);
|
|
10195
|
+
store.deleteElements(store.selectedIds);
|
|
9910
10196
|
}
|
|
9911
10197
|
return;
|
|
9912
10198
|
}
|
|
9913
10199
|
if (isCmd && e.key === "g" && !e.shiftKey) {
|
|
9914
10200
|
e.preventDefault();
|
|
9915
|
-
if (selectedIds.length >= 2) {
|
|
9916
|
-
|
|
9917
|
-
groupElements(selectedIds);
|
|
10201
|
+
if (store.selectedIds.length >= 2) {
|
|
10202
|
+
store.groupElements(store.selectedIds);
|
|
9918
10203
|
}
|
|
9919
10204
|
return;
|
|
9920
10205
|
}
|
|
9921
10206
|
if (isCmd && e.key === "g" && e.shiftKey) {
|
|
9922
10207
|
e.preventDefault();
|
|
9923
|
-
if (selectedIds.length > 0) {
|
|
9924
|
-
|
|
9925
|
-
ungroupElements(selectedIds);
|
|
10208
|
+
if (store.selectedIds.length > 0) {
|
|
10209
|
+
store.ungroupElements(store.selectedIds);
|
|
9926
10210
|
}
|
|
9927
10211
|
return;
|
|
9928
10212
|
}
|
|
9929
10213
|
if (isCmd && e.shiftKey && e.key === "l") {
|
|
9930
10214
|
e.preventDefault();
|
|
9931
|
-
if (selectedIds.length > 0) {
|
|
9932
|
-
|
|
9933
|
-
toggleLockElements(selectedIds);
|
|
10215
|
+
if (store.selectedIds.length > 0) {
|
|
10216
|
+
store.toggleLockElements(store.selectedIds);
|
|
9934
10217
|
}
|
|
9935
10218
|
return;
|
|
9936
10219
|
}
|
|
@@ -9940,46 +10223,20 @@ function useKeyboardShortcuts(enabled = true, containerRef) {
|
|
|
9940
10223
|
linearEdit.exitEditMode();
|
|
9941
10224
|
return;
|
|
9942
10225
|
}
|
|
9943
|
-
clearSelection();
|
|
9944
|
-
setActiveTool("select");
|
|
10226
|
+
store.clearSelection();
|
|
10227
|
+
store.setActiveTool("select");
|
|
9945
10228
|
return;
|
|
9946
10229
|
}
|
|
9947
10230
|
if (isCmd && e.key === "a") {
|
|
9948
10231
|
e.preventDefault();
|
|
9949
|
-
const { elements
|
|
9950
|
-
|
|
10232
|
+
const { elements, setSelectedIds } = useCanvasStore.getState();
|
|
10233
|
+
setSelectedIds(elements.map((el) => el.id));
|
|
9951
10234
|
return;
|
|
9952
10235
|
}
|
|
9953
10236
|
};
|
|
9954
10237
|
window.addEventListener("keydown", handleKeyDown);
|
|
9955
10238
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
9956
|
-
}, [
|
|
9957
|
-
enabled,
|
|
9958
|
-
setActiveTool,
|
|
9959
|
-
undo,
|
|
9960
|
-
redo,
|
|
9961
|
-
selectedIds,
|
|
9962
|
-
elements,
|
|
9963
|
-
deleteElements,
|
|
9964
|
-
duplicateElements,
|
|
9965
|
-
bringToFront,
|
|
9966
|
-
sendToBack,
|
|
9967
|
-
bringForward,
|
|
9968
|
-
sendBackward,
|
|
9969
|
-
toggleGrid,
|
|
9970
|
-
showGrid,
|
|
9971
|
-
zoomIn,
|
|
9972
|
-
zoomOut,
|
|
9973
|
-
resetZoom,
|
|
9974
|
-
zoomToFit,
|
|
9975
|
-
zoomToSelection,
|
|
9976
|
-
containerRef,
|
|
9977
|
-
clearSelection,
|
|
9978
|
-
linearEdit,
|
|
9979
|
-
nudge,
|
|
9980
|
-
copyElements,
|
|
9981
|
-
pasteElements
|
|
9982
|
-
]);
|
|
10239
|
+
}, [enabled, containerRef, nudge, copyElements]);
|
|
9983
10240
|
}
|
|
9984
10241
|
function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) {
|
|
9985
10242
|
while (right > left) {
|
|
@@ -10941,6 +11198,70 @@ function disposeExportWorkerManager() {
|
|
|
10941
11198
|
_config = void 0;
|
|
10942
11199
|
}
|
|
10943
11200
|
}
|
|
11201
|
+
const BOUND_TEXT_PADDING = 4;
|
|
11202
|
+
const CONTAINER_TYPES = /* @__PURE__ */ new Set([
|
|
11203
|
+
"rectangle",
|
|
11204
|
+
"ellipse",
|
|
11205
|
+
"diamond",
|
|
11206
|
+
"image"
|
|
11207
|
+
]);
|
|
11208
|
+
function computeBoundTextPosition(container, text) {
|
|
11209
|
+
const tw = Math.max(20, container.width - BOUND_TEXT_PADDING * 2);
|
|
11210
|
+
let ty;
|
|
11211
|
+
if (text.verticalAlign === "top") {
|
|
11212
|
+
ty = container.y + BOUND_TEXT_PADDING;
|
|
11213
|
+
} else if (text.verticalAlign === "bottom") {
|
|
11214
|
+
ty = container.y + container.height - text.height - BOUND_TEXT_PADDING;
|
|
11215
|
+
} else {
|
|
11216
|
+
ty = container.y + (container.height - text.height) / 2;
|
|
11217
|
+
}
|
|
11218
|
+
return { x: container.x + BOUND_TEXT_PADDING, y: ty, width: tw };
|
|
11219
|
+
}
|
|
11220
|
+
function syncAfterDrag(movedIds, elements, skipIds) {
|
|
11221
|
+
const elMap = /* @__PURE__ */ new Map();
|
|
11222
|
+
for (const el of elements) elMap.set(el.id, el);
|
|
11223
|
+
const updates = [];
|
|
11224
|
+
const processedConnectors = /* @__PURE__ */ new Set();
|
|
11225
|
+
for (const id of movedIds) {
|
|
11226
|
+
const connectors = findConnectorsForElement(id, elements);
|
|
11227
|
+
for (const conn of connectors) {
|
|
11228
|
+
if (processedConnectors.has(conn.id)) continue;
|
|
11229
|
+
processedConnectors.add(conn.id);
|
|
11230
|
+
if (skipIds == null ? void 0 : skipIds.has(conn.id)) continue;
|
|
11231
|
+
const freshConn = elMap.get(conn.id);
|
|
11232
|
+
if (!freshConn) continue;
|
|
11233
|
+
const recomputed = recomputeBoundPoints(freshConn, elements);
|
|
11234
|
+
if (recomputed) updates.push({ id: freshConn.id, updates: recomputed });
|
|
11235
|
+
}
|
|
11236
|
+
const el = elMap.get(id);
|
|
11237
|
+
if ((el == null ? void 0 : el.boundElements) && CONTAINER_TYPES.has(el.type)) {
|
|
11238
|
+
for (const be of el.boundElements) {
|
|
11239
|
+
if (be.type !== "text") continue;
|
|
11240
|
+
if (skipIds == null ? void 0 : skipIds.has(be.id)) continue;
|
|
11241
|
+
const txt = elMap.get(be.id);
|
|
11242
|
+
if (!txt) continue;
|
|
11243
|
+
updates.push({ id: be.id, updates: computeBoundTextPosition(el, txt) });
|
|
11244
|
+
}
|
|
11245
|
+
}
|
|
11246
|
+
}
|
|
11247
|
+
if (processedConnectors.size > 0 && updates.length > 0) {
|
|
11248
|
+
const tempMap = new Map(elMap);
|
|
11249
|
+
for (const u of updates) {
|
|
11250
|
+
const existing = tempMap.get(u.id);
|
|
11251
|
+
if (existing) {
|
|
11252
|
+
tempMap.set(u.id, { ...existing, ...u.updates });
|
|
11253
|
+
}
|
|
11254
|
+
}
|
|
11255
|
+
const labelUpdates = syncConnectorLabels(
|
|
11256
|
+
processedConnectors,
|
|
11257
|
+
tempMap
|
|
11258
|
+
);
|
|
11259
|
+
for (const lu of labelUpdates) {
|
|
11260
|
+
updates.push(lu);
|
|
11261
|
+
}
|
|
11262
|
+
}
|
|
11263
|
+
return { updates, processedConnectorIds: processedConnectors };
|
|
11264
|
+
}
|
|
10944
11265
|
const DEFAULT_THEME = {
|
|
10945
11266
|
canvasBackground: "#f8f9fa",
|
|
10946
11267
|
gridColor: "#e5e5e5",
|
|
@@ -11661,6 +11982,86 @@ const CursorOverlay = ({
|
|
|
11661
11982
|
}) });
|
|
11662
11983
|
};
|
|
11663
11984
|
const CursorOverlay$1 = React.memo(CursorOverlay);
|
|
11985
|
+
const AnnotationItem = React.memo(({
|
|
11986
|
+
element: el,
|
|
11987
|
+
viewport,
|
|
11988
|
+
renderAnnotation
|
|
11989
|
+
}) => {
|
|
11990
|
+
const screenX = el.x * viewport.scale + viewport.x;
|
|
11991
|
+
const screenY = el.y * viewport.scale + viewport.y;
|
|
11992
|
+
const screenW = el.width * viewport.scale;
|
|
11993
|
+
const screenH = el.height * viewport.scale;
|
|
11994
|
+
const ctx = {
|
|
11995
|
+
element: el,
|
|
11996
|
+
screenBounds: { x: screenX, y: screenY, width: screenW, height: screenH },
|
|
11997
|
+
scale: viewport.scale
|
|
11998
|
+
};
|
|
11999
|
+
const annotation = renderAnnotation(ctx);
|
|
12000
|
+
if (!annotation) return null;
|
|
12001
|
+
const transforms = [`scale(${viewport.scale})`];
|
|
12002
|
+
if (el.rotation) transforms.push(`rotate(${el.rotation}deg)`);
|
|
12003
|
+
return /* @__PURE__ */ jsx(
|
|
12004
|
+
"div",
|
|
12005
|
+
{
|
|
12006
|
+
style: {
|
|
12007
|
+
position: "absolute",
|
|
12008
|
+
left: screenX,
|
|
12009
|
+
top: screenY,
|
|
12010
|
+
width: el.width,
|
|
12011
|
+
// world-space; CSS scale handles screen sizing
|
|
12012
|
+
height: el.height,
|
|
12013
|
+
// world-space
|
|
12014
|
+
pointerEvents: "none",
|
|
12015
|
+
transform: transforms.join(" "),
|
|
12016
|
+
transformOrigin: "top left"
|
|
12017
|
+
},
|
|
12018
|
+
children: annotation
|
|
12019
|
+
}
|
|
12020
|
+
);
|
|
12021
|
+
});
|
|
12022
|
+
AnnotationItem.displayName = "AnnotationItem";
|
|
12023
|
+
const CONTAINER_STYLE = {
|
|
12024
|
+
position: "absolute",
|
|
12025
|
+
top: 0,
|
|
12026
|
+
left: 0,
|
|
12027
|
+
width: "100%",
|
|
12028
|
+
height: "100%",
|
|
12029
|
+
pointerEvents: "none",
|
|
12030
|
+
overflow: "hidden",
|
|
12031
|
+
zIndex: 10
|
|
12032
|
+
};
|
|
12033
|
+
const AnnotationsOverlay = React.memo(({
|
|
12034
|
+
elements,
|
|
12035
|
+
viewport,
|
|
12036
|
+
containerWidth,
|
|
12037
|
+
containerHeight,
|
|
12038
|
+
renderAnnotation
|
|
12039
|
+
}) => {
|
|
12040
|
+
const visibleElements = useMemo(() => {
|
|
12041
|
+
const margin = 100;
|
|
12042
|
+
return elements.filter((el) => {
|
|
12043
|
+
if (el.type === "text" && el.containerId) return false;
|
|
12044
|
+
if (!el.isVisible) return false;
|
|
12045
|
+
const sx = el.x * viewport.scale + viewport.x;
|
|
12046
|
+
const sy = el.y * viewport.scale + viewport.y;
|
|
12047
|
+
const sw = el.width * viewport.scale;
|
|
12048
|
+
const sh = el.height * viewport.scale;
|
|
12049
|
+
if (sx + sw < -margin || sy + sh < -margin) return false;
|
|
12050
|
+
if (sx > containerWidth + margin || sy > containerHeight + margin) return false;
|
|
12051
|
+
return true;
|
|
12052
|
+
});
|
|
12053
|
+
}, [elements, viewport.scale, viewport.x, viewport.y, containerWidth, containerHeight]);
|
|
12054
|
+
if (visibleElements.length === 0) return null;
|
|
12055
|
+
return /* @__PURE__ */ jsx("div", { style: CONTAINER_STYLE, children: visibleElements.map((el) => /* @__PURE__ */ jsx(
|
|
12056
|
+
AnnotationItem,
|
|
12057
|
+
{
|
|
12058
|
+
element: el,
|
|
12059
|
+
viewport,
|
|
12060
|
+
renderAnnotation
|
|
12061
|
+
},
|
|
12062
|
+
el.id
|
|
12063
|
+
)) });
|
|
12064
|
+
});
|
|
11664
12065
|
Konva.showWarnings = false;
|
|
11665
12066
|
function arraysShallowEqual(a, b) {
|
|
11666
12067
|
if (a.length !== b.length) return false;
|
|
@@ -11687,9 +12088,11 @@ const StaticElementsLayer = ({
|
|
|
11687
12088
|
onGroupDragEnd
|
|
11688
12089
|
}) => {
|
|
11689
12090
|
const layerRef = useRef(null);
|
|
11690
|
-
|
|
12091
|
+
useLayoutEffect(() => {
|
|
11691
12092
|
const layer = layerRef.current;
|
|
11692
|
-
if (!layer
|
|
12093
|
+
if (!layer) return;
|
|
12094
|
+
layer.clearCache();
|
|
12095
|
+
if (elements.length === 0) return;
|
|
11693
12096
|
const dpr2 = window.devicePixelRatio || 1;
|
|
11694
12097
|
const cachePixelRatio = dpr2 * Math.max(1, viewportScale);
|
|
11695
12098
|
const rafId = requestAnimationFrame(() => {
|
|
@@ -11708,7 +12111,6 @@ const StaticElementsLayer = ({
|
|
|
11708
12111
|
});
|
|
11709
12112
|
return () => {
|
|
11710
12113
|
cancelAnimationFrame(rafId);
|
|
11711
|
-
layer.clearCache();
|
|
11712
12114
|
};
|
|
11713
12115
|
}, [elements, viewportScale, autoEditTextId]);
|
|
11714
12116
|
const { ungrouped, groups } = useMemo(() => {
|
|
@@ -11859,6 +12261,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11859
12261
|
className,
|
|
11860
12262
|
contextMenuItems: contextMenuItemsProp,
|
|
11861
12263
|
renderContextMenu,
|
|
12264
|
+
renderAnnotation,
|
|
11862
12265
|
collaboration: collaborationConfig,
|
|
11863
12266
|
workerConfig,
|
|
11864
12267
|
customElementTypes
|
|
@@ -11917,7 +12320,9 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11917
12320
|
const { isDraggingPoint: isLinearDragging, elementId: linearEditId } = linearEdit;
|
|
11918
12321
|
const prevResolvedRef = useRef([]);
|
|
11919
12322
|
const resolvedElements = useMemo(() => {
|
|
12323
|
+
var _a, _b;
|
|
11920
12324
|
let result = null;
|
|
12325
|
+
const changedConnectorIds = /* @__PURE__ */ new Set();
|
|
11921
12326
|
for (let i = 0; i < elements.length; i++) {
|
|
11922
12327
|
const el = elements[i];
|
|
11923
12328
|
if (el.type !== "line" && el.type !== "arrow") {
|
|
@@ -11926,10 +12331,18 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11926
12331
|
}
|
|
11927
12332
|
const conn = el;
|
|
11928
12333
|
if (!conn.startBinding && !conn.endBinding) {
|
|
12334
|
+
if ((_a = conn.boundElements) == null ? void 0 : _a.some((be) => be.type === "text")) {
|
|
12335
|
+
changedConnectorIds.add(conn.id);
|
|
12336
|
+
if (!result) result = elements.slice(0, i);
|
|
12337
|
+
}
|
|
11929
12338
|
if (result) result[i] = el;
|
|
11930
12339
|
continue;
|
|
11931
12340
|
}
|
|
11932
12341
|
if (isLinearDragging && linearEditId === el.id) {
|
|
12342
|
+
if ((_b = conn.boundElements) == null ? void 0 : _b.some((be) => be.type === "text")) {
|
|
12343
|
+
if (!result) result = elements.slice(0, i);
|
|
12344
|
+
changedConnectorIds.add(conn.id);
|
|
12345
|
+
}
|
|
11933
12346
|
if (result) result[i] = el;
|
|
11934
12347
|
continue;
|
|
11935
12348
|
}
|
|
@@ -11939,10 +12352,31 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11939
12352
|
result = elements.slice(0, i);
|
|
11940
12353
|
}
|
|
11941
12354
|
result[i] = { ...conn, ...recomputed };
|
|
12355
|
+
changedConnectorIds.add(conn.id);
|
|
11942
12356
|
} else {
|
|
11943
12357
|
if (result) result[i] = el;
|
|
11944
12358
|
}
|
|
11945
12359
|
}
|
|
12360
|
+
if (changedConnectorIds.size > 0 && result) {
|
|
12361
|
+
const patchMap = /* @__PURE__ */ new Map();
|
|
12362
|
+
for (const el of result) patchMap.set(el.id, el);
|
|
12363
|
+
for (const connId of changedConnectorIds) {
|
|
12364
|
+
const conn = patchMap.get(connId);
|
|
12365
|
+
if (!(conn == null ? void 0 : conn.boundElements)) continue;
|
|
12366
|
+
for (const be of conn.boundElements) {
|
|
12367
|
+
if (be.type !== "text") continue;
|
|
12368
|
+
const txtIdx = result.findIndex((e) => e.id === be.id);
|
|
12369
|
+
if (txtIdx === -1) continue;
|
|
12370
|
+
const txt = result[txtIdx];
|
|
12371
|
+
const textW = Math.max(10, txt.width || 60);
|
|
12372
|
+
const textH = txt.height || 30;
|
|
12373
|
+
const pos = computeConnectorLabelPosition(conn, textW, textH);
|
|
12374
|
+
if (Math.abs(txt.x - pos.x) > 0.01 || Math.abs(txt.y - pos.y) > 0.01) {
|
|
12375
|
+
result[txtIdx] = { ...txt, x: pos.x, y: pos.y };
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
}
|
|
12379
|
+
}
|
|
11946
12380
|
const finalResult = result ?? elements;
|
|
11947
12381
|
const prev = prevResolvedRef.current;
|
|
11948
12382
|
if (finalResult.length === prev.length) {
|
|
@@ -11974,7 +12408,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11974
12408
|
const prevStaticRef = useRef([]);
|
|
11975
12409
|
const prevInteractiveRef = useRef([]);
|
|
11976
12410
|
const { staticElements, interactiveElements } = useMemo(() => {
|
|
11977
|
-
var _a, _b;
|
|
12411
|
+
var _a, _b, _c, _d;
|
|
11978
12412
|
let effectiveSelected = drawingElementId ? /* @__PURE__ */ new Set([...selectedIdsSet, drawingElementId]) : selectedIdsSet;
|
|
11979
12413
|
if (effectiveSelected.size > 0) {
|
|
11980
12414
|
const expanded = new Set(effectiveSelected);
|
|
@@ -11988,6 +12422,32 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
11988
12422
|
}
|
|
11989
12423
|
}
|
|
11990
12424
|
}
|
|
12425
|
+
for (const el of visibleElements) {
|
|
12426
|
+
if (!expanded.has(el.id)) continue;
|
|
12427
|
+
if (el.boundElements) {
|
|
12428
|
+
for (const be of el.boundElements) {
|
|
12429
|
+
if (be.type === "text") expanded.add(be.id);
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12432
|
+
if (el.type === "text" && el.containerId) {
|
|
12433
|
+
expanded.add(el.containerId);
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12436
|
+
for (const el of visibleElements) {
|
|
12437
|
+
if (el.type !== "line" && el.type !== "arrow") continue;
|
|
12438
|
+
if (expanded.has(el.id)) continue;
|
|
12439
|
+
const conn = el;
|
|
12440
|
+
const startBound = (_c = conn.startBinding) == null ? void 0 : _c.elementId;
|
|
12441
|
+
const endBound = (_d = conn.endBinding) == null ? void 0 : _d.elementId;
|
|
12442
|
+
if (startBound && expanded.has(startBound) || endBound && expanded.has(endBound)) {
|
|
12443
|
+
expanded.add(el.id);
|
|
12444
|
+
if (el.boundElements) {
|
|
12445
|
+
for (const be of el.boundElements) {
|
|
12446
|
+
if (be.type === "text") expanded.add(be.id);
|
|
12447
|
+
}
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
}
|
|
11991
12451
|
if (expanded.size !== effectiveSelected.size) {
|
|
11992
12452
|
effectiveSelected = expanded;
|
|
11993
12453
|
}
|
|
@@ -12146,7 +12606,6 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12146
12606
|
const files = getImageFilesFromDataTransfer(e.dataTransfer);
|
|
12147
12607
|
if (files.length === 0) return;
|
|
12148
12608
|
const rect = container.getBoundingClientRect();
|
|
12149
|
-
stageRef.current;
|
|
12150
12609
|
const vp = useCanvasStore.getState().viewport;
|
|
12151
12610
|
const dropX = (e.clientX - rect.left - vp.x) / vp.scale;
|
|
12152
12611
|
const dropY = (e.clientY - rect.top - vp.y) / vp.scale;
|
|
@@ -12312,6 +12771,12 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12312
12771
|
if (!pos) return;
|
|
12313
12772
|
if (contextMenu) setContextMenu(null);
|
|
12314
12773
|
if (ctx.activeTool === "hand" || isSpacePanning) return;
|
|
12774
|
+
if (editingTextId) {
|
|
12775
|
+
const activeEl = document.activeElement;
|
|
12776
|
+
if ((activeEl == null ? void 0 : activeEl.tagName) === "TEXTAREA") {
|
|
12777
|
+
activeEl.blur();
|
|
12778
|
+
}
|
|
12779
|
+
}
|
|
12315
12780
|
const handler = getToolHandler(ctx.activeTool);
|
|
12316
12781
|
handler == null ? void 0 : handler.onMouseDown(e, pos, ctx);
|
|
12317
12782
|
if (currentElementIdRef.current) {
|
|
@@ -12319,7 +12784,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12319
12784
|
}
|
|
12320
12785
|
},
|
|
12321
12786
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12322
|
-
[readOnly, contextMenu, isSpacePanning]
|
|
12787
|
+
[readOnly, contextMenu, isSpacePanning, editingTextId]
|
|
12323
12788
|
);
|
|
12324
12789
|
const handleMouseMoveCore = useCallback((e) => {
|
|
12325
12790
|
if (spaceKeyRef.current) return;
|
|
@@ -12366,7 +12831,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12366
12831
|
if (!stage) return;
|
|
12367
12832
|
const pointer = stage.getPointerPosition();
|
|
12368
12833
|
if (!pointer) return;
|
|
12369
|
-
const scaleBy = 1.
|
|
12834
|
+
const scaleBy = 1.02;
|
|
12370
12835
|
const dir2 = e.evt.deltaY > 0 ? -1 : 1;
|
|
12371
12836
|
const targetScale = Math.min(MAX_ZOOM, Math.max(
|
|
12372
12837
|
MIN_ZOOM,
|
|
@@ -12376,13 +12841,14 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12376
12841
|
},
|
|
12377
12842
|
[viewport, setViewport]
|
|
12378
12843
|
);
|
|
12379
|
-
const
|
|
12844
|
+
const handleStageDragMove = useCallback(
|
|
12380
12845
|
(e) => {
|
|
12381
12846
|
if (activeTool !== "hand" && !spaceKeyRef.current && !isSpacePanning) return;
|
|
12382
12847
|
setViewport({ x: e.target.x(), y: e.target.y() });
|
|
12383
12848
|
},
|
|
12384
12849
|
[activeTool, setViewport, isSpacePanning]
|
|
12385
12850
|
);
|
|
12851
|
+
const handleStageDragEnd = handleStageDragMove;
|
|
12386
12852
|
const handleElementSelect = useCallback(
|
|
12387
12853
|
(id) => {
|
|
12388
12854
|
var _a;
|
|
@@ -12430,6 +12896,32 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12430
12896
|
useCanvasStore.getState().batchUpdateElements(updates);
|
|
12431
12897
|
}, []);
|
|
12432
12898
|
const unboundConnectorIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
12899
|
+
const syncBoundTextNodes = useCallback(
|
|
12900
|
+
(el, newX, newY, newW, newH) => {
|
|
12901
|
+
var _a, _b;
|
|
12902
|
+
if (!el.boundElements || !stageRef.current) return;
|
|
12903
|
+
if (!CONTAINER_TYPES.has(el.type)) return;
|
|
12904
|
+
const elements2 = useCanvasStore.getState().elements;
|
|
12905
|
+
const shapeW = newW ?? el.width;
|
|
12906
|
+
const shapeH = newH ?? el.height;
|
|
12907
|
+
for (const be of el.boundElements) {
|
|
12908
|
+
if (be.type !== "text") continue;
|
|
12909
|
+
const txt = elements2.find((e) => e.id === be.id);
|
|
12910
|
+
if (!txt) continue;
|
|
12911
|
+
const textNode = stageRef.current.findOne("#" + be.id);
|
|
12912
|
+
if (!textNode) continue;
|
|
12913
|
+
const textNodeH = ((_a = textNode.height) == null ? void 0 : _a.call(textNode)) ?? txt.height;
|
|
12914
|
+
const pos = computeBoundTextPosition(
|
|
12915
|
+
{ x: newX, y: newY, width: shapeW, height: shapeH },
|
|
12916
|
+
{ height: textNodeH, verticalAlign: txt.verticalAlign }
|
|
12917
|
+
);
|
|
12918
|
+
(_b = textNode.width) == null ? void 0 : _b.call(textNode, pos.width);
|
|
12919
|
+
textNode.x(pos.x);
|
|
12920
|
+
textNode.y(pos.y);
|
|
12921
|
+
}
|
|
12922
|
+
},
|
|
12923
|
+
[]
|
|
12924
|
+
);
|
|
12433
12925
|
const handleElementDragMove = useCallback(
|
|
12434
12926
|
(id, updates) => {
|
|
12435
12927
|
if (readOnly) return;
|
|
@@ -12440,6 +12932,12 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12440
12932
|
const { elements: elements2, selectedIds: selectedIds2 } = useCanvasStore.getState();
|
|
12441
12933
|
if (selectedIds2.length > MULTI_DRAG_STORE_SKIP_THRESHOLD) {
|
|
12442
12934
|
isMultiDragSkippingRef.current = true;
|
|
12935
|
+
const el2 = elements2.find((e) => e.id === id);
|
|
12936
|
+
if (el2) {
|
|
12937
|
+
const nx = updates.x ?? el2.x;
|
|
12938
|
+
const ny = updates.y ?? el2.y;
|
|
12939
|
+
syncBoundTextNodes(el2, nx, ny);
|
|
12940
|
+
}
|
|
12443
12941
|
return;
|
|
12444
12942
|
}
|
|
12445
12943
|
isMultiDragSkippingRef.current = false;
|
|
@@ -12466,12 +12964,19 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12466
12964
|
}
|
|
12467
12965
|
if (!dragBatchRef.current) dragBatchRef.current = /* @__PURE__ */ new Map();
|
|
12468
12966
|
dragBatchRef.current.set(id, updates);
|
|
12967
|
+
if (el) {
|
|
12968
|
+
const nx = updates.x ?? el.x;
|
|
12969
|
+
const ny = updates.y ?? el.y;
|
|
12970
|
+
const nw = updates.width;
|
|
12971
|
+
const nh = updates.height;
|
|
12972
|
+
syncBoundTextNodes(el, nx, ny, nw, nh);
|
|
12973
|
+
}
|
|
12469
12974
|
if (!dragFlushScheduledRef.current) {
|
|
12470
12975
|
dragFlushScheduledRef.current = true;
|
|
12471
12976
|
queueMicrotask(flushDragBatch);
|
|
12472
12977
|
}
|
|
12473
12978
|
},
|
|
12474
|
-
[readOnly, flushDragBatch]
|
|
12979
|
+
[readOnly, flushDragBatch, syncBoundTextNodes]
|
|
12475
12980
|
);
|
|
12476
12981
|
const handleDragSnap = useCallback(
|
|
12477
12982
|
(id, bounds) => {
|
|
@@ -12503,39 +13008,11 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12503
13008
|
store2.batchUpdateElements(entries.map(([id, upd]) => ({ id, updates: upd })));
|
|
12504
13009
|
setAlignGuides([]);
|
|
12505
13010
|
const freshElements = useCanvasStore.getState().elements;
|
|
12506
|
-
const
|
|
12507
|
-
for (const
|
|
12508
|
-
const
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
unboundConnectorIdsRef.current.delete(id);
|
|
12512
|
-
const connectors = findConnectorsForElement(id, freshElements);
|
|
12513
|
-
for (const conn of connectors) {
|
|
12514
|
-
if (processedConnectors.has(conn.id)) continue;
|
|
12515
|
-
processedConnectors.add(conn.id);
|
|
12516
|
-
const freshConn = elMap.get(conn.id);
|
|
12517
|
-
if (!freshConn) continue;
|
|
12518
|
-
const recomputed = recomputeBoundPoints(freshConn, freshElements);
|
|
12519
|
-
if (recomputed) connectorUpdates.push({ id: freshConn.id, updates: recomputed });
|
|
12520
|
-
}
|
|
12521
|
-
const el = elMap.get(id);
|
|
12522
|
-
if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
|
|
12523
|
-
const PADDING2 = 4;
|
|
12524
|
-
for (const be of el.boundElements) {
|
|
12525
|
-
if (be.type !== "text") continue;
|
|
12526
|
-
const txt = elMap.get(be.id);
|
|
12527
|
-
if (!txt) continue;
|
|
12528
|
-
const tw = Math.max(20, el.width - PADDING2 * 2);
|
|
12529
|
-
let ty;
|
|
12530
|
-
if (txt.verticalAlign === "top") ty = el.y + PADDING2;
|
|
12531
|
-
else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
|
|
12532
|
-
else ty = el.y + (el.height - txt.height) / 2;
|
|
12533
|
-
connectorUpdates.push({ id: be.id, updates: { x: el.x + PADDING2, y: ty, width: tw } });
|
|
12534
|
-
}
|
|
12535
|
-
}
|
|
12536
|
-
}
|
|
12537
|
-
if (connectorUpdates.length > 0) {
|
|
12538
|
-
useCanvasStore.getState().batchUpdateElements(connectorUpdates);
|
|
13011
|
+
const movedIds = entries.map(([id]) => id);
|
|
13012
|
+
for (const id of movedIds) unboundConnectorIdsRef.current.delete(id);
|
|
13013
|
+
const { updates: syncUpdates } = syncAfterDrag(movedIds, freshElements);
|
|
13014
|
+
if (syncUpdates.length > 0) {
|
|
13015
|
+
useCanvasStore.getState().batchUpdateElements(syncUpdates);
|
|
12539
13016
|
}
|
|
12540
13017
|
useCanvasStore.getState().pushHistory();
|
|
12541
13018
|
if (savedPixelRatioRef.current !== null) {
|
|
@@ -12560,46 +13037,22 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12560
13037
|
setAlignGuides([]);
|
|
12561
13038
|
unboundConnectorIdsRef.current.delete(id);
|
|
12562
13039
|
const freshElements = useCanvasStore.getState().elements;
|
|
13040
|
+
const { updates: syncUpdates } = syncAfterDrag([id], freshElements);
|
|
13041
|
+
for (const su of syncUpdates) updateElement(su.id, su.updates);
|
|
12563
13042
|
const elMap = /* @__PURE__ */ new Map();
|
|
12564
|
-
for (const e of
|
|
12565
|
-
const connectors = findConnectorsForElement(id, freshElements);
|
|
12566
|
-
for (const conn of connectors) {
|
|
12567
|
-
const freshConn = elMap.get(conn.id);
|
|
12568
|
-
if (!freshConn) continue;
|
|
12569
|
-
const recomputed = recomputeBoundPoints(freshConn, freshElements);
|
|
12570
|
-
if (recomputed) updateElement(freshConn.id, recomputed);
|
|
12571
|
-
}
|
|
13043
|
+
for (const e of useCanvasStore.getState().elements) elMap.set(e.id, e);
|
|
12572
13044
|
const el = elMap.get(id);
|
|
12573
|
-
if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
|
|
12574
|
-
const PADDING2 = 4;
|
|
12575
|
-
for (const be of el.boundElements) {
|
|
12576
|
-
if (be.type !== "text") continue;
|
|
12577
|
-
const txt = elMap.get(be.id);
|
|
12578
|
-
if (!txt) continue;
|
|
12579
|
-
const tw = Math.max(20, el.width - PADDING2 * 2);
|
|
12580
|
-
let ty;
|
|
12581
|
-
if (txt.verticalAlign === "top") ty = el.y + PADDING2;
|
|
12582
|
-
else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
|
|
12583
|
-
else ty = el.y + (el.height - txt.height) / 2;
|
|
12584
|
-
updateElement(be.id, { x: el.x + PADDING2, y: ty, width: tw });
|
|
12585
|
-
}
|
|
12586
|
-
}
|
|
12587
13045
|
if ((el == null ? void 0 : el.type) === "text") {
|
|
12588
13046
|
const txt = el;
|
|
12589
13047
|
if (txt.containerId) {
|
|
12590
13048
|
const ctr = elMap.get(txt.containerId);
|
|
12591
|
-
if (ctr &&
|
|
12592
|
-
const
|
|
12593
|
-
const minH = txt.height + PADDING2 * 2;
|
|
13049
|
+
if (ctr && CONTAINER_TYPES.has(ctr.type)) {
|
|
13050
|
+
const minH = txt.height + BOUND_TEXT_PADDING * 2;
|
|
12594
13051
|
if (ctr.height < minH) {
|
|
12595
13052
|
updateElement(ctr.id, { height: minH });
|
|
12596
13053
|
const updatedElements = useCanvasStore.getState().elements;
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
if (!fc) continue;
|
|
12600
|
-
const r = recomputeBoundPoints(fc, updatedElements);
|
|
12601
|
-
if (r) updateElement(fc.id, r);
|
|
12602
|
-
}
|
|
13054
|
+
const { updates: resizeSync } = syncAfterDrag([ctr.id], updatedElements);
|
|
13055
|
+
for (const su of resizeSync) updateElement(su.id, su.updates);
|
|
12603
13056
|
}
|
|
12604
13057
|
}
|
|
12605
13058
|
}
|
|
@@ -12630,41 +13083,15 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12630
13083
|
}));
|
|
12631
13084
|
store2.batchUpdateElements(posUpdates);
|
|
12632
13085
|
const freshElements = useCanvasStore.getState().elements;
|
|
12633
|
-
const elMap = /* @__PURE__ */ new Map();
|
|
12634
|
-
for (const el of freshElements) elMap.set(el.id, el);
|
|
12635
13086
|
const memberIds = new Set(members.map((m) => m.id));
|
|
12636
|
-
const
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
const freshConn = elMap.get(conn.id);
|
|
12645
|
-
if (!freshConn) continue;
|
|
12646
|
-
const recomputed = recomputeBoundPoints(freshConn, freshElements);
|
|
12647
|
-
if (recomputed) connectorUpdates.push({ id: freshConn.id, updates: recomputed });
|
|
12648
|
-
}
|
|
12649
|
-
const el = elMap.get(member.id);
|
|
12650
|
-
if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
|
|
12651
|
-
const PADDING2 = 4;
|
|
12652
|
-
for (const be of el.boundElements) {
|
|
12653
|
-
if (be.type !== "text") continue;
|
|
12654
|
-
if (memberIds.has(be.id)) continue;
|
|
12655
|
-
const txt = elMap.get(be.id);
|
|
12656
|
-
if (!txt) continue;
|
|
12657
|
-
const tw = Math.max(20, el.width - PADDING2 * 2);
|
|
12658
|
-
let ty;
|
|
12659
|
-
if (txt.verticalAlign === "top") ty = el.y + PADDING2;
|
|
12660
|
-
else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
|
|
12661
|
-
else ty = el.y + (el.height - txt.height) / 2;
|
|
12662
|
-
connectorUpdates.push({ id: be.id, updates: { x: el.x + PADDING2, y: ty, width: tw } });
|
|
12663
|
-
}
|
|
12664
|
-
}
|
|
12665
|
-
}
|
|
12666
|
-
if (connectorUpdates.length > 0) {
|
|
12667
|
-
useCanvasStore.getState().batchUpdateElements(connectorUpdates);
|
|
13087
|
+
const { updates: syncUpdates } = syncAfterDrag(
|
|
13088
|
+
memberIds,
|
|
13089
|
+
freshElements,
|
|
13090
|
+
memberIds
|
|
13091
|
+
// skip group-internal connectors & text (already moved)
|
|
13092
|
+
);
|
|
13093
|
+
if (syncUpdates.length > 0) {
|
|
13094
|
+
useCanvasStore.getState().batchUpdateElements(syncUpdates);
|
|
12668
13095
|
}
|
|
12669
13096
|
store2.pushHistory();
|
|
12670
13097
|
},
|
|
@@ -12695,24 +13122,12 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12695
13122
|
}
|
|
12696
13123
|
const textId = generateId();
|
|
12697
13124
|
const conn = el;
|
|
12698
|
-
const
|
|
12699
|
-
const startPt = { x: pts[0], y: pts[1] };
|
|
12700
|
-
const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
|
|
12701
|
-
let midX, midY;
|
|
12702
|
-
if (conn.lineType === "curved") {
|
|
12703
|
-
const cp = computeCurveControlPoint(startPt, endPt, conn.curvature ?? CURVE_RATIO);
|
|
12704
|
-
const mid = quadBezierAt(startPt, cp, endPt, 0.5);
|
|
12705
|
-
midX = conn.x + mid.x;
|
|
12706
|
-
midY = conn.y + mid.y;
|
|
12707
|
-
} else {
|
|
12708
|
-
midX = conn.x + (startPt.x + endPt.x) / 2;
|
|
12709
|
-
midY = conn.y + (startPt.y + endPt.y) / 2;
|
|
12710
|
-
}
|
|
13125
|
+
const labelPos = computeConnectorLabelPosition(conn, 100, 30);
|
|
12711
13126
|
const textEl = {
|
|
12712
13127
|
id: textId,
|
|
12713
13128
|
type: "text",
|
|
12714
|
-
x:
|
|
12715
|
-
y:
|
|
13129
|
+
x: labelPos.x,
|
|
13130
|
+
y: labelPos.y,
|
|
12716
13131
|
width: 100,
|
|
12717
13132
|
height: 30,
|
|
12718
13133
|
rotation: 0,
|
|
@@ -12738,7 +13153,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12738
13153
|
if (el.type === "rectangle" || el.type === "ellipse" || el.type === "diamond" || el.type === "image") {
|
|
12739
13154
|
const existingTextBinding = (_b = el.boundElements) == null ? void 0 : _b.find((be) => be.type === "text");
|
|
12740
13155
|
if (existingTextBinding) {
|
|
12741
|
-
setSel([existingTextBinding.id]);
|
|
13156
|
+
setSel([existingTextBinding.id, id]);
|
|
12742
13157
|
setAutoEditTextId(existingTextBinding.id);
|
|
12743
13158
|
return;
|
|
12744
13159
|
}
|
|
@@ -12766,7 +13181,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12766
13181
|
update(id, {
|
|
12767
13182
|
boundElements: [...currentBound, { id: textId, type: "text" }]
|
|
12768
13183
|
});
|
|
12769
|
-
setSel([textId]);
|
|
13184
|
+
setSel([textId, id]);
|
|
12770
13185
|
setAutoEditTextId(textId);
|
|
12771
13186
|
return;
|
|
12772
13187
|
}
|
|
@@ -12817,11 +13232,17 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12817
13232
|
(id, isEmpty) => {
|
|
12818
13233
|
setEditingTextId(null);
|
|
12819
13234
|
setAutoEditTextId(null);
|
|
13235
|
+
const {
|
|
13236
|
+
elements: els,
|
|
13237
|
+
selectedIds: currentSel,
|
|
13238
|
+
setSelectedIds: setSel,
|
|
13239
|
+
updateElement: update,
|
|
13240
|
+
deleteElements: del
|
|
13241
|
+
} = useCanvasStore.getState();
|
|
13242
|
+
const textEl = els.find((e) => e.id === id);
|
|
13243
|
+
const containerId = (textEl == null ? void 0 : textEl.type) === "text" ? textEl.containerId : null;
|
|
12820
13244
|
if (isEmpty) {
|
|
12821
|
-
|
|
12822
|
-
const textEl = els.find((e) => e.id === id);
|
|
12823
|
-
if ((textEl == null ? void 0 : textEl.type) === "text" && textEl.containerId) {
|
|
12824
|
-
const containerId = textEl.containerId;
|
|
13245
|
+
if (containerId) {
|
|
12825
13246
|
const container = els.find((e) => e.id === containerId);
|
|
12826
13247
|
if (container == null ? void 0 : container.boundElements) {
|
|
12827
13248
|
update(containerId, {
|
|
@@ -12834,6 +13255,11 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12834
13255
|
} else {
|
|
12835
13256
|
useCanvasStore.getState().pushHistory();
|
|
12836
13257
|
}
|
|
13258
|
+
if (containerId && currentSel.includes(containerId)) {
|
|
13259
|
+
setSel([containerId]);
|
|
13260
|
+
} else if (containerId) {
|
|
13261
|
+
setSel(currentSel.filter((sid) => sid !== id));
|
|
13262
|
+
}
|
|
12837
13263
|
},
|
|
12838
13264
|
[onElementDelete]
|
|
12839
13265
|
);
|
|
@@ -12901,6 +13327,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
12901
13327
|
);
|
|
12902
13328
|
const contextMenuItems = useMemo(() => {
|
|
12903
13329
|
var _a;
|
|
13330
|
+
if (!contextMenu) return [];
|
|
12904
13331
|
const hasSelection = selectedIds.length > 0;
|
|
12905
13332
|
const isMac = navigator.platform.includes("Mac");
|
|
12906
13333
|
const mod = isMac ? "⌘" : "Ctrl+";
|
|
@@ -13202,6 +13629,7 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
13202
13629
|
onMouseMove: handleMouseMove,
|
|
13203
13630
|
onMouseUp: handleMouseUp,
|
|
13204
13631
|
onWheel: handleWheel,
|
|
13632
|
+
onDragMove: handleStageDragMove,
|
|
13205
13633
|
onDragEnd: handleStageDragEnd,
|
|
13206
13634
|
onTouchStart: handleMouseDown,
|
|
13207
13635
|
onTouchMove: handleMouseMove,
|
|
@@ -13263,6 +13691,10 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
13263
13691
|
if (el.type === "line" || el.type === "arrow") return false;
|
|
13264
13692
|
if (el.type === "text" && el.containerId) return false;
|
|
13265
13693
|
if (sid === editingTextId) return false;
|
|
13694
|
+
if (editingTextId) {
|
|
13695
|
+
const editingEl = resolvedElementMap.get(editingTextId);
|
|
13696
|
+
if ((editingEl == null ? void 0 : editingEl.type) === "text" && editingEl.containerId === sid) return false;
|
|
13697
|
+
}
|
|
13266
13698
|
return true;
|
|
13267
13699
|
});
|
|
13268
13700
|
if (transformableIds.length === 0) return null;
|
|
@@ -13378,6 +13810,16 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
13378
13810
|
theme
|
|
13379
13811
|
}
|
|
13380
13812
|
)),
|
|
13813
|
+
renderAnnotation && /* @__PURE__ */ jsx(
|
|
13814
|
+
AnnotationsOverlay,
|
|
13815
|
+
{
|
|
13816
|
+
elements: resolvedElements,
|
|
13817
|
+
viewport,
|
|
13818
|
+
containerWidth: dimensions.width,
|
|
13819
|
+
containerHeight: dimensions.height,
|
|
13820
|
+
renderAnnotation
|
|
13821
|
+
}
|
|
13822
|
+
),
|
|
13381
13823
|
showStatusBar && /* @__PURE__ */ jsx(StatusBar, { theme })
|
|
13382
13824
|
]
|
|
13383
13825
|
}
|
|
@@ -13385,9 +13827,17 @@ const FlowCanvas = forwardRef((props, ref) => {
|
|
|
13385
13827
|
});
|
|
13386
13828
|
FlowCanvas.displayName = "FlowCanvas";
|
|
13387
13829
|
const StatusBar = React.memo(({ theme }) => {
|
|
13388
|
-
const elementCount = useCanvasStore(
|
|
13830
|
+
const elementCount = useCanvasStore(
|
|
13831
|
+
(s) => s.elements.filter((el) => !(el.type === "text" && el.containerId)).length
|
|
13832
|
+
);
|
|
13389
13833
|
const activeTool = useCanvasStore((s) => s.activeTool);
|
|
13390
|
-
const selectedCount = useCanvasStore((s) =>
|
|
13834
|
+
const selectedCount = useCanvasStore((s) => {
|
|
13835
|
+
const els = s.elements;
|
|
13836
|
+
return s.selectedIds.filter((id) => {
|
|
13837
|
+
const el = els.find((e) => e.id === id);
|
|
13838
|
+
return el && !(el.type === "text" && el.containerId);
|
|
13839
|
+
}).length;
|
|
13840
|
+
});
|
|
13391
13841
|
return /* @__PURE__ */ jsxs(
|
|
13392
13842
|
"div",
|
|
13393
13843
|
{
|
|
@@ -13592,6 +14042,11 @@ function getSharedSpatialSoA() {
|
|
|
13592
14042
|
}
|
|
13593
14043
|
const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
13594
14044
|
const BASE = DIGITS.length;
|
|
14045
|
+
function trimTrailingZeros(s) {
|
|
14046
|
+
let end = s.length;
|
|
14047
|
+
while (end > 0 && s[end - 1] === "0") end--;
|
|
14048
|
+
return end === s.length ? s : s.slice(0, end);
|
|
14049
|
+
}
|
|
13595
14050
|
const SMALLEST_CHAR = DIGITS[0];
|
|
13596
14051
|
const LARGEST_CHAR = DIGITS[BASE - 1];
|
|
13597
14052
|
function midChar(a, b) {
|
|
@@ -13664,7 +14119,7 @@ function midpoint(a, b) {
|
|
|
13664
14119
|
if (mid !== null) {
|
|
13665
14120
|
return commonPrefix + mid;
|
|
13666
14121
|
}
|
|
13667
|
-
return commonPrefix + aPad[i] + incrementKey(aPad.slice(i + 1)
|
|
14122
|
+
return commonPrefix + aPad[i] + incrementKey(trimTrailingZeros(aPad.slice(i + 1)) || SMALLEST_CHAR);
|
|
13668
14123
|
}
|
|
13669
14124
|
return a + DIGITS[Math.floor(BASE / 2)];
|
|
13670
14125
|
}
|
|
@@ -15298,6 +15753,11 @@ export {
|
|
|
15298
15753
|
ExportWorkerManager,
|
|
15299
15754
|
FILL_COLORS,
|
|
15300
15755
|
FlowCanvas,
|
|
15756
|
+
LABEL_CORNER,
|
|
15757
|
+
LABEL_LINE_HEIGHT,
|
|
15758
|
+
LABEL_MIN_WIDTH,
|
|
15759
|
+
LABEL_PADDING_H,
|
|
15760
|
+
LABEL_PADDING_V,
|
|
15301
15761
|
LINE_TYPES,
|
|
15302
15762
|
OperationLog,
|
|
15303
15763
|
ROUGHNESS_CONFIGS,
|
|
@@ -15335,6 +15795,7 @@ export {
|
|
|
15335
15795
|
computeElbowRoute,
|
|
15336
15796
|
computeFixedPoint,
|
|
15337
15797
|
computeImageElementDimensions,
|
|
15798
|
+
computePillSize,
|
|
15338
15799
|
computeZoomToFit,
|
|
15339
15800
|
createCollaborationProvider,
|
|
15340
15801
|
createImageElement,
|
|
@@ -15399,6 +15860,7 @@ export {
|
|
|
15399
15860
|
isValidFractionalIndex,
|
|
15400
15861
|
isWorkerSupported,
|
|
15401
15862
|
loadImage,
|
|
15863
|
+
measureLabelText,
|
|
15402
15864
|
normalizeRect,
|
|
15403
15865
|
onStatusChange,
|
|
15404
15866
|
opAdd,
|