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/f1ow.umd.cjs CHANGED
@@ -886,6 +886,58 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
886
886
  result.push(points[points.length - 2], points[points.length - 1]);
887
887
  return result;
888
888
  }
889
+ const CURVE_RATIO = 0.2;
890
+ function computeCurveControlPoint(start, end, ratio = CURVE_RATIO) {
891
+ const mx = (start.x + end.x) / 2;
892
+ const my = (start.y + end.y) / 2;
893
+ const dx = end.x - start.x;
894
+ const dy = end.y - start.y;
895
+ const len2 = Math.sqrt(dx * dx + dy * dy);
896
+ if (len2 === 0) return { x: mx, y: my };
897
+ const nx = -dy / len2;
898
+ const ny = dx / len2;
899
+ const offset = len2 * ratio;
900
+ return { x: mx + nx * offset, y: my + ny * offset };
901
+ }
902
+ function quadBezierAt(p0, cp, p2, t) {
903
+ const mt = 1 - t;
904
+ return {
905
+ x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * p2.x,
906
+ y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * p2.y
907
+ };
908
+ }
909
+ function quadBezierTangent(p0, cp, p2, t) {
910
+ const mt = 1 - t;
911
+ return {
912
+ x: 2 * mt * (cp.x - p0.x) + 2 * t * (p2.x - cp.x),
913
+ y: 2 * mt * (cp.y - p0.y) + 2 * t * (p2.y - cp.y)
914
+ };
915
+ }
916
+ function curveArrowPrev(start, cp, end, atEnd) {
917
+ if (atEnd) {
918
+ const tan = quadBezierTangent(start, cp, end, 1);
919
+ const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
920
+ return { x: end.x - tan.x / len2 * 20, y: end.y - tan.y / len2 * 20 };
921
+ } else {
922
+ const tan = quadBezierTangent(start, cp, end, 0);
923
+ const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
924
+ return { x: start.x + tan.x / len2 * 20, y: start.y + tan.y / len2 * 20 };
925
+ }
926
+ }
927
+ function curvatureFromDragPoint(start, end, dragWorld) {
928
+ const dx = end.x - start.x;
929
+ const dy = end.y - start.y;
930
+ const len2 = Math.sqrt(dx * dx + dy * dy);
931
+ if (len2 === 0) return CURVE_RATIO;
932
+ const nx = -dy / len2;
933
+ const ny = dx / len2;
934
+ const mx = (start.x + end.x) / 2;
935
+ const my = (start.y + end.y) / 2;
936
+ const vx = dragWorld.x - mx;
937
+ const vy = dragWorld.y - my;
938
+ const projDist = vx * nx + vy * ny;
939
+ return 2 * projDist / len2;
940
+ }
889
941
  const CONNECTABLE_TYPES = /* @__PURE__ */ new Set(["rectangle", "ellipse", "diamond", "text", "image"]);
890
942
  function isConnectable(el) {
891
943
  return CONNECTABLE_TYPES.has(el.type);
@@ -1312,6 +1364,71 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1312
1364
  }
1313
1365
  }
1314
1366
  }
1367
+ function computeConnectorLabelPosition(connector, textWidth, textHeight) {
1368
+ const pts = connector.points;
1369
+ const startPt = { x: pts[0], y: pts[1] };
1370
+ const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
1371
+ let midX;
1372
+ let midY;
1373
+ if (connector.lineType === "curved") {
1374
+ const curvature = connector.curvature ?? CURVE_RATIO;
1375
+ const cp = computeCurveControlPoint(startPt, endPt, curvature);
1376
+ const mid = quadBezierAt(startPt, cp, endPt, 0.5);
1377
+ midX = connector.x + mid.x;
1378
+ midY = connector.y + mid.y;
1379
+ } else if (connector.lineType === "elbow" && pts.length >= 4) {
1380
+ const segCount = pts.length / 2 - 1;
1381
+ let totalLen = 0;
1382
+ for (let i = 0; i < segCount; i++) {
1383
+ const dx = pts[(i + 1) * 2] - pts[i * 2];
1384
+ const dy = pts[(i + 1) * 2 + 1] - pts[i * 2 + 1];
1385
+ totalLen += Math.sqrt(dx * dx + dy * dy);
1386
+ }
1387
+ const half = totalLen / 2;
1388
+ let walked = 0;
1389
+ midX = connector.x + (startPt.x + endPt.x) / 2;
1390
+ midY = connector.y + (startPt.y + endPt.y) / 2;
1391
+ for (let i = 0; i < segCount; i++) {
1392
+ const ax = pts[i * 2], ay = pts[i * 2 + 1];
1393
+ const bx = pts[(i + 1) * 2], by = pts[(i + 1) * 2 + 1];
1394
+ const dx = bx - ax, dy = by - ay;
1395
+ const segLen = Math.sqrt(dx * dx + dy * dy);
1396
+ if (walked + segLen >= half && segLen > 0) {
1397
+ const t = (half - walked) / segLen;
1398
+ midX = connector.x + ax + dx * t;
1399
+ midY = connector.y + ay + dy * t;
1400
+ break;
1401
+ }
1402
+ walked += segLen;
1403
+ }
1404
+ } else {
1405
+ midX = connector.x + (startPt.x + endPt.x) / 2;
1406
+ midY = connector.y + (startPt.y + endPt.y) / 2;
1407
+ }
1408
+ return {
1409
+ x: midX - textWidth / 2,
1410
+ y: midY - textHeight / 2
1411
+ };
1412
+ }
1413
+ function syncConnectorLabels(connectorIds, elMap) {
1414
+ const updates = [];
1415
+ for (const connId of connectorIds) {
1416
+ const conn = elMap.get(connId);
1417
+ if (!conn || conn.type !== "arrow" && conn.type !== "line") continue;
1418
+ if (!conn.boundElements) continue;
1419
+ const connector = conn;
1420
+ for (const be of conn.boundElements) {
1421
+ if (be.type !== "text") continue;
1422
+ const txt = elMap.get(be.id);
1423
+ if (!txt) continue;
1424
+ const textW = Math.max(10, txt.width || 60);
1425
+ const textH = txt.height || 30;
1426
+ const pos = computeConnectorLabelPosition(connector, textW, textH);
1427
+ updates.push({ id: txt.id, updates: { x: pos.x, y: pos.y } });
1428
+ }
1429
+ }
1430
+ return updates;
1431
+ }
1315
1432
  const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
1316
1433
  let nanoid = (size = 21) => {
1317
1434
  let id = "";
@@ -1836,7 +1953,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1836
1953
  function toSet(arr) {
1837
1954
  return new Set(arr);
1838
1955
  }
1839
- const ZOOM_STEPS = [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5];
1956
+ 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];
1840
1957
  const DEFAULT_ANIMATION_DURATION = 280;
1841
1958
  function zoomAtPoint({ viewport, point, targetScale }) {
1842
1959
  const clampedScale = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, targetScale));
@@ -2581,58 +2698,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2581
2698
  return [];
2582
2699
  }
2583
2700
  }
2584
- const CURVE_RATIO = 0.2;
2585
- function computeCurveControlPoint(start, end, ratio = CURVE_RATIO) {
2586
- const mx = (start.x + end.x) / 2;
2587
- const my = (start.y + end.y) / 2;
2588
- const dx = end.x - start.x;
2589
- const dy = end.y - start.y;
2590
- const len2 = Math.sqrt(dx * dx + dy * dy);
2591
- if (len2 === 0) return { x: mx, y: my };
2592
- const nx = -dy / len2;
2593
- const ny = dx / len2;
2594
- const offset = len2 * ratio;
2595
- return { x: mx + nx * offset, y: my + ny * offset };
2596
- }
2597
- function quadBezierAt(p0, cp, p2, t) {
2598
- const mt = 1 - t;
2599
- return {
2600
- x: mt * mt * p0.x + 2 * mt * t * cp.x + t * t * p2.x,
2601
- y: mt * mt * p0.y + 2 * mt * t * cp.y + t * t * p2.y
2602
- };
2603
- }
2604
- function quadBezierTangent(p0, cp, p2, t) {
2605
- const mt = 1 - t;
2606
- return {
2607
- x: 2 * mt * (cp.x - p0.x) + 2 * t * (p2.x - cp.x),
2608
- y: 2 * mt * (cp.y - p0.y) + 2 * t * (p2.y - cp.y)
2609
- };
2610
- }
2611
- function curveArrowPrev(start, cp, end, atEnd) {
2612
- if (atEnd) {
2613
- const tan = quadBezierTangent(start, cp, end, 1);
2614
- const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
2615
- return { x: end.x - tan.x / len2 * 20, y: end.y - tan.y / len2 * 20 };
2616
- } else {
2617
- const tan = quadBezierTangent(start, cp, end, 0);
2618
- const len2 = Math.sqrt(tan.x * tan.x + tan.y * tan.y) || 1;
2619
- return { x: start.x + tan.x / len2 * 20, y: start.y + tan.y / len2 * 20 };
2620
- }
2621
- }
2622
- function curvatureFromDragPoint(start, end, dragWorld) {
2623
- const dx = end.x - start.x;
2624
- const dy = end.y - start.y;
2625
- const len2 = Math.sqrt(dx * dx + dy * dy);
2626
- if (len2 === 0) return CURVE_RATIO;
2627
- const nx = -dy / len2;
2628
- const ny = dx / len2;
2629
- const mx = (start.x + end.x) / 2;
2630
- const my = (start.y + end.y) / 2;
2631
- const vx = dragWorld.x - mx;
2632
- const vy = dragWorld.y - my;
2633
- const projDist = vx * nx + vy * ny;
2634
- return 2 * projDist / len2;
2635
- }
2636
2701
  const selectTool = {
2637
2702
  name: "select",
2638
2703
  onMouseDown(e, pos, ctx) {
@@ -3335,7 +3400,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3335
3400
  width: dims.width,
3336
3401
  height: dims.height,
3337
3402
  rotation: 0,
3338
- style: { ...style, fillColor: "transparent" },
3403
+ style: { ...style, fillColor: "transparent", strokeColor: "transparent", strokeWidth: 0 },
3339
3404
  isLocked: false,
3340
3405
  isVisible: true,
3341
3406
  boundElements: null,
@@ -5587,7 +5652,34 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5587
5652
  );
5588
5653
  };
5589
5654
  const FreeDrawShape = React.memo(FreeDrawShape$1);
5590
- const LINE_HEIGHT = 1.18;
5655
+ const LABEL_PADDING_H = 8;
5656
+ const LABEL_PADDING_V = 4;
5657
+ const LABEL_CORNER = 4;
5658
+ const LABEL_LINE_HEIGHT = 1.18;
5659
+ const LABEL_MIN_WIDTH = 10;
5660
+ let _measureCanvas$1 = null;
5661
+ function getMeasureCtx() {
5662
+ if (!_measureCanvas$1) {
5663
+ _measureCanvas$1 = document.createElement("canvas");
5664
+ }
5665
+ return _measureCanvas$1.getContext("2d");
5666
+ }
5667
+ function measureLabelText(text, fontSize, fontFamily) {
5668
+ const ctx = getMeasureCtx();
5669
+ ctx.font = `${fontSize}px ${fontFamily}`;
5670
+ const metrics = ctx.measureText(text || " ");
5671
+ return {
5672
+ width: Math.ceil(metrics.width),
5673
+ height: Math.ceil(fontSize * LABEL_LINE_HEIGHT)
5674
+ };
5675
+ }
5676
+ function computePillSize(textWidth, textHeight) {
5677
+ return {
5678
+ width: Math.max(LABEL_MIN_WIDTH, textWidth) + LABEL_PADDING_H * 2,
5679
+ height: textHeight + LABEL_PADDING_V * 2
5680
+ };
5681
+ }
5682
+ const LINE_HEIGHT = LABEL_LINE_HEIGHT;
5591
5683
  const TextShape$1 = ({
5592
5684
  element,
5593
5685
  isSelected,
@@ -5604,6 +5696,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5604
5696
  const { id, x, y, width, height, rotation, style, text, containerId, textAlign, verticalAlign, isLocked } = element;
5605
5697
  const textRef = React.useRef(null);
5606
5698
  const isEditingRef = React.useRef(false);
5699
+ const [isEditingState, setIsEditingState] = React.useState(false);
5607
5700
  const autoEditDoneRef = React.useRef(false);
5608
5701
  const isBound = !!containerId;
5609
5702
  const isDraggable = !isBound && !isLocked && !isGrouped;
@@ -5612,36 +5705,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5612
5705
  return allElements.find((el) => el.id === containerId) ?? null;
5613
5706
  }, [containerId, allElements]);
5614
5707
  const boundPos = React.useMemo(() => {
5615
- var _a, _b;
5616
5708
  if (!container) return { x, y };
5617
- if (container.type === "arrow" || container.type === "line") {
5618
- const conn = container;
5619
- const pts = conn.points;
5620
- const startPt = { x: pts[0], y: pts[1] };
5621
- const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
5622
- let midX, midY;
5623
- if (conn.lineType === "curved") {
5624
- const cp = computeCurveControlPoint(startPt, endPt, conn.curvature ?? CURVE_RATIO);
5625
- const mid = quadBezierAt(startPt, cp, endPt, 0.5);
5626
- midX = conn.x + mid.x;
5627
- midY = conn.y + mid.y;
5628
- } else {
5629
- midX = conn.x + (startPt.x + endPt.x) / 2;
5630
- midY = conn.y + (startPt.y + endPt.y) / 2;
5631
- }
5632
- const textWidth2 = Math.max(80, width || 80);
5633
- const textHeight = ((_a = textRef.current) == null ? void 0 : _a.height()) ?? height;
5634
- return {
5635
- x: midX - textWidth2 / 2,
5636
- y: midY - textHeight / 2,
5637
- width: textWidth2
5638
- };
5639
- }
5640
5709
  const PADDING2 = 4;
5641
5710
  const cw = container.width - PADDING2 * 2;
5642
5711
  const textWidth = Math.max(20, cw);
5643
5712
  const bx = container.x + PADDING2;
5644
- const textActualHeight = ((_b = textRef.current) == null ? void 0 : _b.height()) ?? height;
5713
+ const textActualHeight = height;
5645
5714
  let by;
5646
5715
  if (verticalAlign === "top") {
5647
5716
  by = container.y + PADDING2;
@@ -5651,7 +5720,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5651
5720
  by = container.y + (container.height - textActualHeight) / 2;
5652
5721
  }
5653
5722
  return { x: bx, y: by, width: textWidth };
5654
- }, [container, x, y, height, verticalAlign]);
5723
+ }, [container, x, y, width, height, verticalAlign]);
5655
5724
  const syncSize = React.useCallback(() => {
5656
5725
  const node = textRef.current;
5657
5726
  if (!node || isEditingRef.current) return;
@@ -5673,8 +5742,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5673
5742
  onChange(id, updates);
5674
5743
  }
5675
5744
  }, [id, height, width, isBound, onChange]);
5745
+ const syncSizeInitRef = React.useRef(true);
5676
5746
  React.useEffect(() => {
5677
- requestAnimationFrame(syncSize);
5747
+ if (syncSizeInitRef.current) {
5748
+ syncSizeInitRef.current = false;
5749
+ if (text && height > 0) return;
5750
+ }
5751
+ const id2 = requestAnimationFrame(syncSize);
5752
+ return () => cancelAnimationFrame(id2);
5678
5753
  }, [text, style.fontSize, style.fontFamily, syncSize]);
5679
5754
  const openEditor = React.useCallback(() => {
5680
5755
  const textNode = textRef.current;
@@ -5682,6 +5757,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5682
5757
  const stage = textNode.getStage();
5683
5758
  if (!stage) return;
5684
5759
  isEditingRef.current = true;
5760
+ setIsEditingState(true);
5685
5761
  onEditStart == null ? void 0 : onEditStart(id);
5686
5762
  const stageContainer = stage.container();
5687
5763
  const absTransform = textNode.getAbsoluteTransform().copy();
@@ -5704,7 +5780,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5704
5780
  containerScreenH = container.height * stageScaleX;
5705
5781
  }
5706
5782
  const nodeWidth = textNode.width();
5707
- const screenWidth = isBound ? containerScreenW - PADDING_SCREEN * 2 : Math.max(nodeWidth, 100) * stageScaleX;
5783
+ const screenWidth = isBound ? containerScreenW - PADDING_SCREEN * 2 : Math.max(nodeWidth, 60) * stageScaleX;
5708
5784
  const screenLeft = isBound ? containerScreenLeft + PADDING_SCREEN : absPos.x;
5709
5785
  const originalText = text;
5710
5786
  const textarea = document.createElement("textarea");
@@ -5778,7 +5854,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5778
5854
  }
5779
5855
  if (!isBound) {
5780
5856
  textarea.style.width = "auto";
5781
- textarea.style.width = `${Math.max(textarea.scrollWidth, 100 * stageScaleX)}px`;
5857
+ const minW = 100 * stageScaleX;
5858
+ textarea.style.width = `${Math.max(textarea.scrollWidth, minW)}px`;
5782
5859
  }
5783
5860
  };
5784
5861
  textarea.addEventListener("input", autoGrow);
@@ -5791,6 +5868,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5791
5868
  const finishEdit = () => {
5792
5869
  if (!isEditingRef.current) return;
5793
5870
  isEditingRef.current = false;
5871
+ setIsEditingState(false);
5794
5872
  const newText = cancelled ? originalText : textarea.value;
5795
5873
  const isEmpty = newText.trim() === "";
5796
5874
  textarea.removeEventListener("input", autoGrow);
@@ -5806,9 +5884,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5806
5884
  }
5807
5885
  onEditEnd == null ? void 0 : onEditEnd(id, isEmpty);
5808
5886
  };
5809
- const handleBlur = () => {
5810
- finishEdit();
5811
- };
5887
+ const handleBlur = () => finishEdit();
5812
5888
  const handleKeyDown = (e) => {
5813
5889
  e.stopPropagation();
5814
5890
  if (e.key === "Escape") {
@@ -5859,7 +5935,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5859
5935
  wrap: isBound ? "word" : "none",
5860
5936
  rotation: isBound ? (container == null ? void 0 : container.rotation) ?? rotation : rotation,
5861
5937
  transformsEnabled: (isBound ? (container == null ? void 0 : container.rotation) ?? rotation : rotation) ? "all" : "position",
5862
- visible: !(autoEdit && !isEditingRef.current && !text),
5938
+ visible: !isEditingState && !(autoEdit && !text),
5863
5939
  opacity: text ? style.opacity : isBound ? 0 : 0.4,
5864
5940
  draggable: isDraggable,
5865
5941
  listening: !isBound,
@@ -5909,6 +5985,263 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5909
5985
  );
5910
5986
  };
5911
5987
  const TextShape = React.memo(TextShape$1);
5988
+ const TextLabel$1 = ({
5989
+ element,
5990
+ connector,
5991
+ onChange,
5992
+ autoEdit,
5993
+ onEditStart,
5994
+ onEditEnd
5995
+ }) => {
5996
+ const { id, x, y, width, height, style, text } = element;
5997
+ const labelFontSize = style.fontSize * 0.9;
5998
+ const textRef = React.useRef(null);
5999
+ const bgRectRef = React.useRef(null);
6000
+ const isEditingRef = React.useRef(false);
6001
+ const [isEditingState, setIsEditingState] = React.useState(false);
6002
+ const autoEditDoneRef = React.useRef(false);
6003
+ const boundPos = React.useMemo(() => {
6004
+ const textWidth = Math.max(LABEL_MIN_WIDTH, width || 60);
6005
+ const pos = computeConnectorLabelPosition(connector, textWidth, height);
6006
+ return { x: pos.x, y: pos.y, width: textWidth };
6007
+ }, [connector, width, height]);
6008
+ const effectiveX = boundPos.x;
6009
+ const effectiveY = boundPos.y;
6010
+ const syncSize = React.useCallback(() => {
6011
+ const node = textRef.current;
6012
+ if (!node || isEditingRef.current) return;
6013
+ const measuredHeight = node.height();
6014
+ const updates = {};
6015
+ let needsUpdate = false;
6016
+ if (Math.abs(measuredHeight - height) > 1) {
6017
+ updates.height = measuredHeight;
6018
+ needsUpdate = true;
6019
+ }
6020
+ const measuredWidth = measureLabelText(text || " ", labelFontSize, style.fontFamily).width;
6021
+ if (Math.abs(measuredWidth - width) > 1) {
6022
+ updates.width = measuredWidth;
6023
+ needsUpdate = true;
6024
+ }
6025
+ if (needsUpdate) {
6026
+ onChange(id, updates);
6027
+ }
6028
+ }, [id, text, height, width, labelFontSize, style.fontFamily, onChange]);
6029
+ const syncSizeInitRef = React.useRef(true);
6030
+ React.useEffect(() => {
6031
+ if (syncSizeInitRef.current) {
6032
+ syncSizeInitRef.current = false;
6033
+ if (text && height > 0) return;
6034
+ }
6035
+ const rafId = requestAnimationFrame(syncSize);
6036
+ return () => cancelAnimationFrame(rafId);
6037
+ }, [text, labelFontSize, style.fontFamily, syncSize]);
6038
+ const openEditor = React.useCallback(() => {
6039
+ var _a;
6040
+ const textNode = textRef.current;
6041
+ if (!textNode || isEditingRef.current) return;
6042
+ const stage = textNode.getStage();
6043
+ if (!stage) return;
6044
+ isEditingRef.current = true;
6045
+ setIsEditingState(true);
6046
+ onEditStart == null ? void 0 : onEditStart(id);
6047
+ const stageContainer = stage.container();
6048
+ const absTransform = textNode.getAbsoluteTransform().copy();
6049
+ const absPos = absTransform.point({ x: 0, y: 0 });
6050
+ const stageScaleX = stage.scaleX();
6051
+ const screenFontSize = labelFontSize * stageScaleX;
6052
+ const nodeWidth = textNode.width();
6053
+ const scaledPadH = LABEL_PADDING_H * stageScaleX;
6054
+ const scaledPadV = LABEL_PADDING_V * stageScaleX;
6055
+ const initMeasured = measureLabelText(text || " ", labelFontSize, style.fontFamily);
6056
+ const initPillW = Math.max(LABEL_MIN_WIDTH, initMeasured.width) * stageScaleX + scaledPadH * 2;
6057
+ const initPillH = initMeasured.height * stageScaleX + scaledPadV * 2;
6058
+ const connMidScreenX = absPos.x + nodeWidth * stageScaleX / 2;
6059
+ const connMidScreenY = absPos.y + textNode.height() * stageScaleX / 2;
6060
+ const originalText = text;
6061
+ const textarea = document.createElement("textarea");
6062
+ stageContainer.appendChild(textarea);
6063
+ textarea.value = text;
6064
+ textarea.style.position = "absolute";
6065
+ textarea.style.top = `${connMidScreenY - initPillH / 2}px`;
6066
+ textarea.style.left = `${connMidScreenX - initPillW / 2}px`;
6067
+ textarea.style.width = `${initPillW}px`;
6068
+ textarea.style.height = `${initPillH}px`;
6069
+ textarea.style.fontSize = `${screenFontSize}px`;
6070
+ textarea.style.fontFamily = style.fontFamily;
6071
+ textarea.style.color = style.strokeColor;
6072
+ textarea.style.lineHeight = `${LABEL_LINE_HEIGHT}`;
6073
+ textarea.style.border = "none";
6074
+ textarea.style.margin = "0";
6075
+ textarea.style.outline = "none";
6076
+ textarea.style.resize = "none";
6077
+ textarea.style.overflow = "hidden";
6078
+ textarea.style.zIndex = "1000";
6079
+ textarea.rows = 1;
6080
+ textarea.style.minHeight = `${Math.max(20, screenFontSize * LABEL_LINE_HEIGHT)}px`;
6081
+ textarea.style.boxSizing = "border-box";
6082
+ textarea.style.transformOrigin = "left top";
6083
+ textarea.style.letterSpacing = "normal";
6084
+ textarea.style.caretColor = style.strokeColor;
6085
+ textarea.style.background = "#f8f9fa";
6086
+ textarea.style.borderRadius = `${LABEL_CORNER * stageScaleX}px`;
6087
+ textarea.style.padding = `${scaledPadV}px ${scaledPadH}px`;
6088
+ textarea.style.textAlign = "center";
6089
+ textarea.style.whiteSpace = "nowrap";
6090
+ textarea.style.wordBreak = "normal";
6091
+ const autoGrow = () => {
6092
+ const currentText = textarea.value || " ";
6093
+ const measured = measureLabelText(currentText, labelFontSize, style.fontFamily);
6094
+ const newTextW = Math.max(LABEL_MIN_WIDTH, measured.width) * stageScaleX;
6095
+ const pillW = newTextW + scaledPadH * 2;
6096
+ const pillH = measured.height * stageScaleX + scaledPadV * 2;
6097
+ textarea.style.width = `${pillW}px`;
6098
+ textarea.style.height = `${pillH}px`;
6099
+ textarea.style.left = `${connMidScreenX - pillW / 2}px`;
6100
+ textarea.style.top = `${connMidScreenY - pillH / 2}px`;
6101
+ };
6102
+ textarea.addEventListener("input", autoGrow);
6103
+ requestAnimationFrame(autoGrow);
6104
+ textNode.hide();
6105
+ (_a = bgRectRef.current) == null ? void 0 : _a.hide();
6106
+ stage.batchDraw();
6107
+ textarea.focus();
6108
+ textarea.select();
6109
+ let cancelled = false;
6110
+ const finishEdit = () => {
6111
+ var _a2;
6112
+ if (!isEditingRef.current) return;
6113
+ isEditingRef.current = false;
6114
+ setIsEditingState(false);
6115
+ const newText = cancelled ? originalText : textarea.value;
6116
+ const isEmpty = newText.trim() === "";
6117
+ textarea.removeEventListener("input", autoGrow);
6118
+ textarea.removeEventListener("blur", handleBlur);
6119
+ textarea.removeEventListener("keydown", handleKeyDown);
6120
+ if (textarea.parentNode) {
6121
+ textarea.parentNode.removeChild(textarea);
6122
+ }
6123
+ textNode.show();
6124
+ (_a2 = bgRectRef.current) == null ? void 0 : _a2.show();
6125
+ stage.batchDraw();
6126
+ if (!cancelled) {
6127
+ const measured = measureLabelText(newText || " ", labelFontSize, style.fontFamily);
6128
+ onChange(id, {
6129
+ text: newText,
6130
+ width: measured.width,
6131
+ height: measured.height
6132
+ });
6133
+ }
6134
+ onEditEnd == null ? void 0 : onEditEnd(id, isEmpty);
6135
+ };
6136
+ const handleBlur = () => finishEdit();
6137
+ const handleKeyDown = (e) => {
6138
+ e.stopPropagation();
6139
+ if (e.key === "Escape") {
6140
+ cancelled = true;
6141
+ textarea.blur();
6142
+ }
6143
+ if (e.key === "Enter" && !e.shiftKey) {
6144
+ e.preventDefault();
6145
+ textarea.blur();
6146
+ }
6147
+ if (e.key === "Tab") {
6148
+ e.preventDefault();
6149
+ textarea.blur();
6150
+ }
6151
+ };
6152
+ textarea.addEventListener("blur", handleBlur);
6153
+ textarea.addEventListener("keydown", handleKeyDown);
6154
+ }, [id, text, style, onChange, onEditStart, onEditEnd]);
6155
+ React.useEffect(() => {
6156
+ if (!autoEdit) {
6157
+ autoEditDoneRef.current = false;
6158
+ }
6159
+ }, [autoEdit]);
6160
+ React.useEffect(() => {
6161
+ if (autoEdit && !autoEditDoneRef.current && textRef.current) {
6162
+ autoEditDoneRef.current = true;
6163
+ openEditor();
6164
+ }
6165
+ }, [autoEdit, openEditor]);
6166
+ const pillTextW = Math.max(LABEL_MIN_WIDTH, width || 60);
6167
+ const labelW = pillTextW + LABEL_PADDING_H * 2;
6168
+ const labelH = height + LABEL_PADDING_V * 2;
6169
+ const isVisible = !isEditingState && !(autoEdit && !text);
6170
+ const labelOpacity = text ? style.opacity : 0;
6171
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6172
+ reactKonva.Group,
6173
+ {
6174
+ x: (effectiveX ?? 0) - LABEL_PADDING_H,
6175
+ y: (effectiveY ?? 0) - LABEL_PADDING_V,
6176
+ visible: isVisible,
6177
+ opacity: labelOpacity,
6178
+ listening: false,
6179
+ perfectDrawEnabled: false,
6180
+ children: [
6181
+ /* @__PURE__ */ jsxRuntime.jsx(
6182
+ reactKonva.Rect,
6183
+ {
6184
+ ref: bgRectRef,
6185
+ width: labelW,
6186
+ height: labelH,
6187
+ fill: "#f8f9fa",
6188
+ cornerRadius: LABEL_CORNER,
6189
+ listening: false,
6190
+ perfectDrawEnabled: false
6191
+ }
6192
+ ),
6193
+ /* @__PURE__ */ jsxRuntime.jsx(
6194
+ reactKonva.Text,
6195
+ {
6196
+ ref: textRef,
6197
+ id,
6198
+ x: LABEL_PADDING_H,
6199
+ y: LABEL_PADDING_V,
6200
+ text: text || "",
6201
+ fontSize: labelFontSize,
6202
+ fontFamily: style.fontFamily,
6203
+ fill: style.strokeColor,
6204
+ lineHeight: LABEL_LINE_HEIGHT,
6205
+ wrap: "none",
6206
+ listening: false,
6207
+ perfectDrawEnabled: false,
6208
+ onDblClick: openEditor,
6209
+ onDblTap: openEditor
6210
+ }
6211
+ )
6212
+ ]
6213
+ }
6214
+ );
6215
+ };
6216
+ const TextLabel = React.memo(TextLabel$1);
6217
+ const IMAGE_CACHE_LIMIT = 50;
6218
+ const _imgCache = /* @__PURE__ */ new Map();
6219
+ function cachedLoadImage(src, onLoad, onError) {
6220
+ const cached = _imgCache.get(src);
6221
+ if (cached) {
6222
+ onLoad(cached);
6223
+ return () => {
6224
+ };
6225
+ }
6226
+ const img = new window.Image();
6227
+ if (!src.startsWith("data:")) {
6228
+ img.crossOrigin = "anonymous";
6229
+ }
6230
+ img.onload = () => {
6231
+ if (_imgCache.size >= IMAGE_CACHE_LIMIT) {
6232
+ const oldest = _imgCache.keys().next().value;
6233
+ if (oldest !== void 0) _imgCache.delete(oldest);
6234
+ }
6235
+ _imgCache.set(src, img);
6236
+ onLoad(img);
6237
+ };
6238
+ img.onerror = () => onError();
6239
+ img.src = src;
6240
+ return () => {
6241
+ img.onload = null;
6242
+ img.onerror = null;
6243
+ };
6244
+ }
5912
6245
  const ImageShape$1 = ({
5913
6246
  element,
5914
6247
  isSelected,
@@ -5921,8 +6254,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5921
6254
  onDragSnap
5922
6255
  }) => {
5923
6256
  const { id, x, y, width, height, rotation, src, style, crop, cornerRadius, scaleMode, isLocked } = element;
5924
- const [image, setImage] = React.useState(null);
5925
- const [loadError, setLoadError] = React.useState(false);
6257
+ const [image, setImage] = React.useState(() => _imgCache.get(src) ?? null);
6258
+ const [loadError, setLoadError] = React.useState(() => !src);
5926
6259
  const [liveSize, setLiveSize] = React.useState({ w: width, h: height });
5927
6260
  React.useEffect(() => {
5928
6261
  setLiveSize({ w: width, h: height });
@@ -5935,21 +6268,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5935
6268
  setLoadError(true);
5936
6269
  return;
5937
6270
  }
5938
- const img = new window.Image();
5939
- img.crossOrigin = "anonymous";
5940
- img.onload = () => {
5941
- setImage(img);
5942
- setLoadError(false);
5943
- };
5944
- img.onerror = () => {
5945
- setImage(null);
5946
- setLoadError(true);
5947
- };
5948
- img.src = src;
5949
- return () => {
5950
- img.onload = null;
5951
- img.onerror = null;
5952
- };
6271
+ return cachedLoadImage(
6272
+ src,
6273
+ (img) => {
6274
+ setImage(img);
6275
+ setLoadError(false);
6276
+ },
6277
+ () => {
6278
+ setImage(null);
6279
+ setLoadError(true);
6280
+ }
6281
+ );
5953
6282
  }, [src]);
5954
6283
  const handleDragMove = React.useCallback((e) => {
5955
6284
  let nx = e.target.x(), ny = e.target.y();
@@ -6091,13 +6420,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6091
6420
  shadowBlur: isSelected ? 6 : 0,
6092
6421
  shadowOpacity: isSelected ? 0.5 : 0,
6093
6422
  children: [
6094
- scaleMode === "fit" && /* @__PURE__ */ jsxRuntime.jsx(
6423
+ /* @__PURE__ */ jsxRuntime.jsx(
6095
6424
  reactKonva.Rect,
6096
6425
  {
6097
6426
  width: rw,
6098
6427
  height: rh,
6099
- fill: style.fillColor === "transparent" ? void 0 : style.fillColor,
6100
- cornerRadius
6428
+ fill: scaleMode === "fit" ? style.fillColor === "transparent" ? void 0 : style.fillColor : "transparent",
6429
+ cornerRadius,
6430
+ perfectDrawEnabled: false
6101
6431
  }
6102
6432
  ),
6103
6433
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -6189,7 +6519,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6189
6519
  return /* @__PURE__ */ jsxRuntime.jsx(ArrowShape, { element, isSelected, isEditing, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, allElements });
6190
6520
  case "freedraw":
6191
6521
  return /* @__PURE__ */ jsxRuntime.jsx(FreeDrawShape, { element, isSelected, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, onDragSnap });
6192
- case "text":
6522
+ case "text": {
6523
+ const connContainer = element.containerId && allElements ? allElements.find((el) => el.id === element.containerId) : null;
6524
+ const isConnectorLabel = connContainer && (connContainer.type === "arrow" || connContainer.type === "line");
6525
+ if (isConnectorLabel) {
6526
+ return /* @__PURE__ */ jsxRuntime.jsx(
6527
+ TextLabel,
6528
+ {
6529
+ element,
6530
+ connector: connContainer,
6531
+ onChange,
6532
+ autoEdit: autoEditText,
6533
+ onEditStart: onTextEditStart,
6534
+ onEditEnd: onTextEditEnd
6535
+ }
6536
+ );
6537
+ }
6193
6538
  return /* @__PURE__ */ jsxRuntime.jsx(
6194
6539
  TextShape,
6195
6540
  {
@@ -6206,6 +6551,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6206
6551
  gridSnap
6207
6552
  }
6208
6553
  );
6554
+ }
6209
6555
  case "image":
6210
6556
  return /* @__PURE__ */ jsxRuntime.jsx(ImageShape, { element, isSelected, isGrouped, onSelect, onChange, onDragMove, onDoubleClick, gridSnap, onDragSnap });
6211
6557
  default:
@@ -6542,6 +6888,23 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6542
6888
  const isCurved = "lineType" in element && element.lineType === "curved";
6543
6889
  const isElbow = "lineType" in element && element.lineType === "elbow";
6544
6890
  const endpointsOnly = isCurved || isElbow;
6891
+ const labelRect = React.useMemo(() => {
6892
+ if (!element.boundElements) return null;
6893
+ for (const be of element.boundElements) {
6894
+ if (be.type !== "text") continue;
6895
+ const txt = allElements.find((e) => e.id === be.id);
6896
+ if (!txt || !txt.text) continue;
6897
+ const textW = Math.max(LABEL_MIN_WIDTH, txt.width || 60);
6898
+ const textH = txt.height || 30;
6899
+ return {
6900
+ x: txt.x - LABEL_PADDING_H,
6901
+ y: txt.y - LABEL_PADDING_V,
6902
+ w: textW + LABEL_PADDING_H * 2,
6903
+ h: textH + LABEL_PADDING_V * 2
6904
+ };
6905
+ }
6906
+ return null;
6907
+ }, [element.boundElements, allElements]);
6545
6908
  const isEndpoint = React.useCallback(
6546
6909
  (idx) => idx === 0 || idx === pairs.length - 1,
6547
6910
  [pairs.length]
@@ -6721,6 +7084,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6721
7084
  !endpointsOnly && pairs.length >= 2 && pairs.slice(0, -1).map((pt, segIdx) => {
6722
7085
  const next = pairs[segIdx + 1];
6723
7086
  const mid = midpoint$1(pt, next);
7087
+ if (labelRect) {
7088
+ const wx = x + mid.x, wy = y + mid.y;
7089
+ if (wx >= labelRect.x && wx <= labelRect.x + labelRect.w && wy >= labelRect.y && wy <= labelRect.y + labelRect.h) {
7090
+ return null;
7091
+ }
7092
+ }
6724
7093
  const isHovered = hoveredMidpointIndex === segIdx;
6725
7094
  return /* @__PURE__ */ jsxRuntime.jsx(
6726
7095
  reactKonva.Circle,
@@ -6777,6 +7146,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
6777
7146
  const curvature = element.curvature ?? CURVE_RATIO;
6778
7147
  const cp = computeCurveControlPoint(localStart, localEnd, curvature);
6779
7148
  const curveMid = quadBezierAt(localStart, cp, localEnd, 0.5);
7149
+ if (labelRect) {
7150
+ const wx = x + curveMid.x, wy = y + curveMid.y;
7151
+ if (wx >= labelRect.x && wx <= labelRect.x + labelRect.w && wy >= labelRect.y && wy <= labelRect.y + labelRect.h) {
7152
+ return null;
7153
+ }
7154
+ }
6780
7155
  return /* @__PURE__ */ jsxRuntime.jsx(
6781
7156
  reactKonva.Circle,
6782
7157
  {
@@ -8376,28 +8751,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
8376
8751
  ] }) });
8377
8752
  };
8378
8753
  const SvgIcon = ({ children, color, size = 12 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children });
8379
- const LayerIcons = {
8380
- sendToBack: (color) => /* @__PURE__ */ jsxRuntime.jsxs(SvgIcon, { color, children: [
8381
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
8382
- /* @__PURE__ */ jsxRuntime.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" }),
8383
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 12 12 18" }),
8384
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 15 12 18 15 15" })
8385
- ] }),
8386
- sendBackward: (color) => /* @__PURE__ */ jsxRuntime.jsxs(SvgIcon, { color, children: [
8387
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
8388
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "19 12 12 19 5 12" })
8389
- ] }),
8390
- bringForward: (color) => /* @__PURE__ */ jsxRuntime.jsxs(SvgIcon, { color, children: [
8391
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "5" }),
8392
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "5 12 12 5 19 12" })
8393
- ] }),
8394
- bringToFront: (color) => /* @__PURE__ */ jsxRuntime.jsxs(SvgIcon, { color, children: [
8395
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
8396
- /* @__PURE__ */ jsxRuntime.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" }),
8397
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 18 12 12" }),
8398
- /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "9 15 12 12 15 15" })
8399
- ] })
8400
- };
8401
8754
  const ActionIcons = {
8402
8755
  duplicate: (color) => /* @__PURE__ */ jsxRuntime.jsxs(SvgIcon, { color, children: [
8403
8756
  /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8", y: "8", width: "12", height: "12", rx: "2" }),
@@ -8431,10 +8784,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
8431
8784
  const toggleLockElements = useCanvasStore((s) => s.toggleLockElements);
8432
8785
  const deleteElements = useCanvasStore((s) => s.deleteElements);
8433
8786
  const duplicateElements = useCanvasStore((s) => s.duplicateElements);
8434
- const bringToFront = useCanvasStore((s) => s.bringToFront);
8435
- const sendToBack = useCanvasStore((s) => s.sendToBack);
8436
- const bringForward = useCanvasStore((s) => s.bringForward);
8437
- const sendBackward = useCanvasStore((s) => s.sendBackward);
8438
8787
  const activeTool = useCanvasStore((s) => s.activeTool);
8439
8788
  const currentLineType = useCanvasStore((s) => s.currentLineType);
8440
8789
  const setCurrentLineType = useCanvasStore((s) => s.setCurrentLineType);
@@ -8927,31 +9276,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
8927
9276
  {
8928
9277
  theme,
8929
9278
  style: { width: "100%", padding: "5px 0", borderRadius: 4 },
8930
- onClick: () => {
8931
- const input = document.createElement("input");
8932
- input.type = "file";
8933
- input.accept = "image/*";
8934
- input.onchange = async () => {
8935
- var _a;
8936
- const file = (_a = input.files) == null ? void 0 : _a[0];
9279
+ onClick: async () => {
9280
+ const targetId = selectedImage.id;
9281
+ try {
9282
+ const files = await openImageFilePicker();
9283
+ const file = files[0];
8937
9284
  if (!file) return;
8938
- const reader = new FileReader();
8939
- reader.onload = () => {
8940
- const dataURL = reader.result;
8941
- const img = new window.Image();
8942
- img.onload = () => {
8943
- updateElement(selectedImage.id, {
8944
- src: dataURL,
8945
- naturalWidth: img.naturalWidth,
8946
- naturalHeight: img.naturalHeight
8947
- });
8948
- pushHistory();
8949
- };
8950
- img.src = dataURL;
8951
- };
8952
- reader.readAsDataURL(file);
8953
- };
8954
- input.click();
9285
+ const dataURL = await fileToDataURL(file);
9286
+ const img = await loadImage(dataURL);
9287
+ updateElement(targetId, {
9288
+ src: dataURL,
9289
+ naturalWidth: img.naturalWidth,
9290
+ naturalHeight: img.naturalHeight
9291
+ });
9292
+ pushHistory();
9293
+ } catch {
9294
+ }
8955
9295
  },
8956
9296
  children: "Replace Image…"
8957
9297
  }
@@ -8959,27 +9299,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
8959
9299
  ] }),
8960
9300
  hasSelection && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8961
9301
  /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle }),
8962
- /* @__PURE__ */ jsxRuntime.jsx(PanelSection, { label: "Layers", theme, children: /* @__PURE__ */ jsxRuntime.jsx(ButtonRow, { columns: 4, children: [
8963
- { fn: sendToBack, icon: LayerIcons.sendToBack, tip: "Send to back" },
8964
- { fn: sendBackward, icon: LayerIcons.sendBackward, tip: "Send backward" },
8965
- { fn: bringForward, icon: LayerIcons.bringForward, tip: "Bring forward" },
8966
- { fn: bringToFront, icon: LayerIcons.bringToFront, tip: "Bring to front" }
8967
- ].map(({ fn, icon, tip }) => /* @__PURE__ */ jsxRuntime.jsx(
8968
- PanelButton,
8969
- {
8970
- variant: "action",
8971
- theme,
8972
- title: tip,
8973
- width: "100%",
8974
- height: 32,
8975
- onClick: () => {
8976
- fn(selectedIds);
8977
- pushHistory();
8978
- },
8979
- children: (hl) => icon(hl ? theme.activeToolColor : theme.textColor)
8980
- },
8981
- tip
8982
- )) }) }),
8983
9302
  /* @__PURE__ */ jsxRuntime.jsx(PanelSection, { label: "Actions", theme, children: /* @__PURE__ */ jsxRuntime.jsxs(ButtonRow, { columns: 4, children: [
8984
9303
  /* @__PURE__ */ jsxRuntime.jsx(
8985
9304
  PanelButton,
@@ -9677,48 +9996,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9677
9996
  return result;
9678
9997
  }
9679
9998
  function useKeyboardShortcuts(enabled = true, containerRef) {
9680
- const setActiveTool = useCanvasStore((s) => s.setActiveTool);
9681
- const undo = useCanvasStore((s) => s.undo);
9682
- const redo = useCanvasStore((s) => s.redo);
9683
- const selectedIds = useCanvasStore((s) => s.selectedIds);
9684
- const elements = useCanvasStore((s) => s.elements);
9685
- const deleteElements = useCanvasStore((s) => s.deleteElements);
9686
- const duplicateElements = useCanvasStore((s) => s.duplicateElements);
9687
- const bringToFront = useCanvasStore((s) => s.bringToFront);
9688
- const sendToBack = useCanvasStore((s) => s.sendToBack);
9689
- const bringForward = useCanvasStore((s) => s.bringForward);
9690
- const sendBackward = useCanvasStore((s) => s.sendBackward);
9691
- const toggleGrid = useCanvasStore((s) => s.toggleGrid);
9692
- const showGrid = useCanvasStore((s) => s.showGrid);
9693
- const zoomIn = useCanvasStore((s) => s.zoomIn);
9694
- const zoomOut = useCanvasStore((s) => s.zoomOut);
9695
- const resetZoom = useCanvasStore((s) => s.resetZoom);
9696
- const zoomToFit = useCanvasStore((s) => s.zoomToFit);
9697
- const zoomToSelection = useCanvasStore((s) => s.zoomToSelection);
9698
- const clearSelection = useCanvasStore((s) => s.clearSelection);
9699
- const updateElement = useCanvasStore((s) => s.updateElement);
9700
- const addElement = useCanvasStore((s) => s.addElement);
9701
- const setSelectedIds = useCanvasStore((s) => s.setSelectedIds);
9702
- const pushHistory = useCanvasStore((s) => s.pushHistory);
9703
- const linearEdit = useLinearEditStore();
9704
9999
  const copyElements = React.useCallback(() => {
10000
+ const { selectedIds, elements } = useCanvasStore.getState();
9705
10001
  if (selectedIds.length === 0) return;
9706
10002
  setClipboard(gatherElementsForCopy(selectedIds, elements));
9707
- }, [selectedIds, elements]);
9708
- const pasteElements = React.useCallback(() => {
9709
- const clip = getClipboard();
9710
- if (clip.length === 0) return;
9711
- const PASTE_OFFSET = 20;
9712
- const { clones, selectedCloneIds } = cloneAndRemapElements(clip, clip, PASTE_OFFSET);
9713
- clones.forEach((el) => addElement(el));
9714
- setSelectedIds(selectedCloneIds.length > 0 ? selectedCloneIds : clones.map((c) => c.id));
9715
- pushHistory();
9716
- setClipboard(
9717
- clip.map((el) => ({ ...el, x: el.x + PASTE_OFFSET, y: el.y + PASTE_OFFSET }))
9718
- );
9719
- }, [addElement, setSelectedIds, pushHistory]);
10003
+ }, []);
9720
10004
  const nudge = React.useCallback(
9721
10005
  (dx, dy) => {
10006
+ const { selectedIds, elements, updateElement, pushHistory } = useCanvasStore.getState();
9722
10007
  if (selectedIds.length === 0) return;
9723
10008
  selectedIds.forEach((id) => {
9724
10009
  const el = elements.find((e) => e.id === id);
@@ -9728,7 +10013,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9728
10013
  });
9729
10014
  pushHistory();
9730
10015
  },
9731
- [selectedIds, elements, updateElement, pushHistory]
10016
+ []
9732
10017
  );
9733
10018
  React.useEffect(() => {
9734
10019
  if (!enabled) return;
@@ -9736,6 +10021,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9736
10021
  const tag = e.target.tagName;
9737
10022
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
9738
10023
  const isCmd = e.metaKey || e.ctrlKey;
10024
+ const store = useCanvasStore.getState();
10025
+ const linearEdit = useLinearEditStore.getState();
9739
10026
  if (!isCmd && !e.shiftKey) {
9740
10027
  const toolMap = {
9741
10028
  v: "select",
@@ -9753,30 +10040,29 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9753
10040
  const tool = toolMap[e.key.toLowerCase()];
9754
10041
  if (tool) {
9755
10042
  e.preventDefault();
9756
- setActiveTool(tool);
10043
+ store.setActiveTool(tool);
9757
10044
  return;
9758
10045
  }
9759
10046
  }
9760
10047
  if (isCmd && e.key === "z" && !e.shiftKey) {
9761
10048
  e.preventDefault();
9762
- undo();
10049
+ store.undo();
9763
10050
  return;
9764
10051
  }
9765
10052
  if (isCmd && e.key === "z" && e.shiftKey) {
9766
10053
  e.preventDefault();
9767
- redo();
10054
+ store.redo();
9768
10055
  return;
9769
10056
  }
9770
10057
  if (isCmd && e.key === "y") {
9771
10058
  e.preventDefault();
9772
- redo();
10059
+ store.redo();
9773
10060
  return;
9774
10061
  }
9775
10062
  if ((e.key === "Delete" || e.key === "Backspace") && !isCmd) {
9776
10063
  if (linearEdit.isEditing && linearEdit.selectedPointIndices.length > 0) {
9777
10064
  e.preventDefault();
9778
- const { elements: elements2, updateElement: updateElement2, pushHistory: pushHistory2 } = useCanvasStore.getState();
9779
- const el = elements2.find((e2) => e2.id === linearEdit.elementId);
10065
+ const el = store.elements.find((e2) => e2.id === linearEdit.elementId);
9780
10066
  if (el) {
9781
10067
  const pointCount = el.points.length / 2;
9782
10068
  const indicesToDelete = new Set(linearEdit.selectedPointIndices);
@@ -9814,75 +10100,75 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9814
10100
  if (indicesToDelete.has(pointCount - 1)) {
9815
10101
  pointUpdates.endBinding = null;
9816
10102
  }
9817
- updateElement2(el.id, pointUpdates);
9818
- pushHistory2();
10103
+ store.updateElement(el.id, pointUpdates);
10104
+ store.pushHistory();
9819
10105
  linearEdit.setSelectedPoints([]);
9820
10106
  }
9821
10107
  }
9822
10108
  return;
9823
10109
  }
9824
- if (selectedIds.length > 0) {
10110
+ if (store.selectedIds.length > 0) {
9825
10111
  e.preventDefault();
9826
- const unlocked = selectedIds.filter((sid) => {
9827
- const el = elements.find((e2) => e2.id === sid);
10112
+ const unlocked = store.selectedIds.filter((sid) => {
10113
+ const el = store.elements.find((e2) => e2.id === sid);
9828
10114
  return el && !el.isLocked;
9829
10115
  });
9830
- if (unlocked.length > 0) deleteElements(unlocked);
10116
+ if (unlocked.length > 0) store.deleteElements(unlocked);
9831
10117
  }
9832
10118
  return;
9833
10119
  }
9834
10120
  if (isCmd && e.key === "d") {
9835
10121
  e.preventDefault();
9836
- if (selectedIds.length > 0) {
9837
- duplicateElements(selectedIds);
10122
+ if (store.selectedIds.length > 0) {
10123
+ store.duplicateElements(store.selectedIds);
9838
10124
  }
9839
10125
  return;
9840
10126
  }
9841
10127
  if (e.key === "]" && isCmd && e.shiftKey) {
9842
10128
  e.preventDefault();
9843
- bringToFront(selectedIds);
10129
+ store.bringToFront(store.selectedIds);
9844
10130
  return;
9845
10131
  }
9846
10132
  if (e.key === "[" && isCmd && e.shiftKey) {
9847
10133
  e.preventDefault();
9848
- sendToBack(selectedIds);
10134
+ store.sendToBack(store.selectedIds);
9849
10135
  return;
9850
10136
  }
9851
10137
  if (e.key === "]" && isCmd && !e.shiftKey) {
9852
10138
  e.preventDefault();
9853
- bringForward(selectedIds);
10139
+ store.bringForward(store.selectedIds);
9854
10140
  return;
9855
10141
  }
9856
10142
  if (e.key === "[" && isCmd && !e.shiftKey) {
9857
10143
  e.preventDefault();
9858
- sendBackward(selectedIds);
10144
+ store.sendBackward(store.selectedIds);
9859
10145
  return;
9860
10146
  }
9861
10147
  if (e.key === "g" && !isCmd) {
9862
10148
  e.preventDefault();
9863
- toggleGrid();
10149
+ store.toggleGrid();
9864
10150
  return;
9865
10151
  }
9866
10152
  if (isCmd && (e.key === "=" || e.key === "+")) {
9867
10153
  e.preventDefault();
9868
- zoomIn();
10154
+ store.zoomIn();
9869
10155
  return;
9870
10156
  }
9871
10157
  if (isCmd && e.key === "-") {
9872
10158
  e.preventDefault();
9873
- zoomOut();
10159
+ store.zoomOut();
9874
10160
  return;
9875
10161
  }
9876
10162
  if (isCmd && e.key === "0") {
9877
10163
  e.preventDefault();
9878
- resetZoom();
10164
+ store.resetZoom();
9879
10165
  return;
9880
10166
  }
9881
10167
  if (isCmd && e.shiftKey && e.key === "1") {
9882
10168
  e.preventDefault();
9883
10169
  if (containerRef == null ? void 0 : containerRef.current) {
9884
10170
  const rect = containerRef.current.getBoundingClientRect();
9885
- zoomToFit(rect.width, rect.height, void 0, { animate: true });
10171
+ store.zoomToFit(rect.width, rect.height, void 0, { animate: true });
9886
10172
  }
9887
10173
  return;
9888
10174
  }
@@ -9890,14 +10176,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9890
10176
  e.preventDefault();
9891
10177
  if (containerRef == null ? void 0 : containerRef.current) {
9892
10178
  const rect = containerRef.current.getBoundingClientRect();
9893
- zoomToSelection(rect.width, rect.height, { animate: true });
10179
+ store.zoomToSelection(rect.width, rect.height, { animate: true });
9894
10180
  }
9895
10181
  return;
9896
10182
  }
9897
10183
  if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key) && !isCmd) {
9898
- if (selectedIds.length > 0 && !linearEdit.isEditing) {
10184
+ if (store.selectedIds.length > 0 && !linearEdit.isEditing) {
9899
10185
  e.preventDefault();
9900
- const baseStep = showGrid ? GRID_SIZE : 1;
10186
+ const baseStep = store.showGrid ? GRID_SIZE : 1;
9901
10187
  const step = e.shiftKey ? baseStep * 10 : baseStep;
9902
10188
  const dx = e.key === "ArrowLeft" ? -step : e.key === "ArrowRight" ? step : 0;
9903
10189
  const dy = e.key === "ArrowUp" ? -step : e.key === "ArrowDown" ? step : 0;
@@ -9906,7 +10192,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9906
10192
  }
9907
10193
  }
9908
10194
  if (isCmd && e.key === "c") {
9909
- if (selectedIds.length > 0) {
10195
+ if (store.selectedIds.length > 0) {
9910
10196
  e.preventDefault();
9911
10197
  copyElements();
9912
10198
  }
@@ -9916,34 +10202,31 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9916
10202
  return;
9917
10203
  }
9918
10204
  if (isCmd && e.key === "x") {
9919
- if (selectedIds.length > 0) {
10205
+ if (store.selectedIds.length > 0) {
9920
10206
  e.preventDefault();
9921
10207
  copyElements();
9922
- deleteElements(selectedIds);
10208
+ store.deleteElements(store.selectedIds);
9923
10209
  }
9924
10210
  return;
9925
10211
  }
9926
10212
  if (isCmd && e.key === "g" && !e.shiftKey) {
9927
10213
  e.preventDefault();
9928
- if (selectedIds.length >= 2) {
9929
- const { groupElements } = useCanvasStore.getState();
9930
- groupElements(selectedIds);
10214
+ if (store.selectedIds.length >= 2) {
10215
+ store.groupElements(store.selectedIds);
9931
10216
  }
9932
10217
  return;
9933
10218
  }
9934
10219
  if (isCmd && e.key === "g" && e.shiftKey) {
9935
10220
  e.preventDefault();
9936
- if (selectedIds.length > 0) {
9937
- const { ungroupElements } = useCanvasStore.getState();
9938
- ungroupElements(selectedIds);
10221
+ if (store.selectedIds.length > 0) {
10222
+ store.ungroupElements(store.selectedIds);
9939
10223
  }
9940
10224
  return;
9941
10225
  }
9942
10226
  if (isCmd && e.shiftKey && e.key === "l") {
9943
10227
  e.preventDefault();
9944
- if (selectedIds.length > 0) {
9945
- const { toggleLockElements } = useCanvasStore.getState();
9946
- toggleLockElements(selectedIds);
10228
+ if (store.selectedIds.length > 0) {
10229
+ store.toggleLockElements(store.selectedIds);
9947
10230
  }
9948
10231
  return;
9949
10232
  }
@@ -9953,46 +10236,20 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
9953
10236
  linearEdit.exitEditMode();
9954
10237
  return;
9955
10238
  }
9956
- clearSelection();
9957
- setActiveTool("select");
10239
+ store.clearSelection();
10240
+ store.setActiveTool("select");
9958
10241
  return;
9959
10242
  }
9960
10243
  if (isCmd && e.key === "a") {
9961
10244
  e.preventDefault();
9962
- const { elements: elements2, setSelectedIds: setSelectedIds2 } = useCanvasStore.getState();
9963
- setSelectedIds2(elements2.map((el) => el.id));
10245
+ const { elements, setSelectedIds } = useCanvasStore.getState();
10246
+ setSelectedIds(elements.map((el) => el.id));
9964
10247
  return;
9965
10248
  }
9966
10249
  };
9967
10250
  window.addEventListener("keydown", handleKeyDown);
9968
10251
  return () => window.removeEventListener("keydown", handleKeyDown);
9969
- }, [
9970
- enabled,
9971
- setActiveTool,
9972
- undo,
9973
- redo,
9974
- selectedIds,
9975
- elements,
9976
- deleteElements,
9977
- duplicateElements,
9978
- bringToFront,
9979
- sendToBack,
9980
- bringForward,
9981
- sendBackward,
9982
- toggleGrid,
9983
- showGrid,
9984
- zoomIn,
9985
- zoomOut,
9986
- resetZoom,
9987
- zoomToFit,
9988
- zoomToSelection,
9989
- containerRef,
9990
- clearSelection,
9991
- linearEdit,
9992
- nudge,
9993
- copyElements,
9994
- pasteElements
9995
- ]);
10252
+ }, [enabled, containerRef, nudge, copyElements]);
9996
10253
  }
9997
10254
  function quickselect(arr, k, left = 0, right = arr.length - 1, compare = defaultCompare) {
9998
10255
  while (right > left) {
@@ -10954,6 +11211,70 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
10954
11211
  _config = void 0;
10955
11212
  }
10956
11213
  }
11214
+ const BOUND_TEXT_PADDING = 4;
11215
+ const CONTAINER_TYPES = /* @__PURE__ */ new Set([
11216
+ "rectangle",
11217
+ "ellipse",
11218
+ "diamond",
11219
+ "image"
11220
+ ]);
11221
+ function computeBoundTextPosition(container, text) {
11222
+ const tw = Math.max(20, container.width - BOUND_TEXT_PADDING * 2);
11223
+ let ty;
11224
+ if (text.verticalAlign === "top") {
11225
+ ty = container.y + BOUND_TEXT_PADDING;
11226
+ } else if (text.verticalAlign === "bottom") {
11227
+ ty = container.y + container.height - text.height - BOUND_TEXT_PADDING;
11228
+ } else {
11229
+ ty = container.y + (container.height - text.height) / 2;
11230
+ }
11231
+ return { x: container.x + BOUND_TEXT_PADDING, y: ty, width: tw };
11232
+ }
11233
+ function syncAfterDrag(movedIds, elements, skipIds) {
11234
+ const elMap = /* @__PURE__ */ new Map();
11235
+ for (const el of elements) elMap.set(el.id, el);
11236
+ const updates = [];
11237
+ const processedConnectors = /* @__PURE__ */ new Set();
11238
+ for (const id of movedIds) {
11239
+ const connectors = findConnectorsForElement(id, elements);
11240
+ for (const conn of connectors) {
11241
+ if (processedConnectors.has(conn.id)) continue;
11242
+ processedConnectors.add(conn.id);
11243
+ if (skipIds == null ? void 0 : skipIds.has(conn.id)) continue;
11244
+ const freshConn = elMap.get(conn.id);
11245
+ if (!freshConn) continue;
11246
+ const recomputed = recomputeBoundPoints(freshConn, elements);
11247
+ if (recomputed) updates.push({ id: freshConn.id, updates: recomputed });
11248
+ }
11249
+ const el = elMap.get(id);
11250
+ if ((el == null ? void 0 : el.boundElements) && CONTAINER_TYPES.has(el.type)) {
11251
+ for (const be of el.boundElements) {
11252
+ if (be.type !== "text") continue;
11253
+ if (skipIds == null ? void 0 : skipIds.has(be.id)) continue;
11254
+ const txt = elMap.get(be.id);
11255
+ if (!txt) continue;
11256
+ updates.push({ id: be.id, updates: computeBoundTextPosition(el, txt) });
11257
+ }
11258
+ }
11259
+ }
11260
+ if (processedConnectors.size > 0 && updates.length > 0) {
11261
+ const tempMap = new Map(elMap);
11262
+ for (const u of updates) {
11263
+ const existing = tempMap.get(u.id);
11264
+ if (existing) {
11265
+ tempMap.set(u.id, { ...existing, ...u.updates });
11266
+ }
11267
+ }
11268
+ const labelUpdates = syncConnectorLabels(
11269
+ processedConnectors,
11270
+ tempMap
11271
+ );
11272
+ for (const lu of labelUpdates) {
11273
+ updates.push(lu);
11274
+ }
11275
+ }
11276
+ return { updates, processedConnectorIds: processedConnectors };
11277
+ }
10957
11278
  const DEFAULT_THEME = {
10958
11279
  canvasBackground: "#f8f9fa",
10959
11280
  gridColor: "#e5e5e5",
@@ -11674,6 +11995,86 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11674
11995
  }) });
11675
11996
  };
11676
11997
  const CursorOverlay = React.memo(CursorOverlay$1);
11998
+ const AnnotationItem = React.memo(({
11999
+ element: el,
12000
+ viewport,
12001
+ renderAnnotation
12002
+ }) => {
12003
+ const screenX = el.x * viewport.scale + viewport.x;
12004
+ const screenY = el.y * viewport.scale + viewport.y;
12005
+ const screenW = el.width * viewport.scale;
12006
+ const screenH = el.height * viewport.scale;
12007
+ const ctx = {
12008
+ element: el,
12009
+ screenBounds: { x: screenX, y: screenY, width: screenW, height: screenH },
12010
+ scale: viewport.scale
12011
+ };
12012
+ const annotation = renderAnnotation(ctx);
12013
+ if (!annotation) return null;
12014
+ const transforms = [`scale(${viewport.scale})`];
12015
+ if (el.rotation) transforms.push(`rotate(${el.rotation}deg)`);
12016
+ return /* @__PURE__ */ jsxRuntime.jsx(
12017
+ "div",
12018
+ {
12019
+ style: {
12020
+ position: "absolute",
12021
+ left: screenX,
12022
+ top: screenY,
12023
+ width: el.width,
12024
+ // world-space; CSS scale handles screen sizing
12025
+ height: el.height,
12026
+ // world-space
12027
+ pointerEvents: "none",
12028
+ transform: transforms.join(" "),
12029
+ transformOrigin: "top left"
12030
+ },
12031
+ children: annotation
12032
+ }
12033
+ );
12034
+ });
12035
+ AnnotationItem.displayName = "AnnotationItem";
12036
+ const CONTAINER_STYLE = {
12037
+ position: "absolute",
12038
+ top: 0,
12039
+ left: 0,
12040
+ width: "100%",
12041
+ height: "100%",
12042
+ pointerEvents: "none",
12043
+ overflow: "hidden",
12044
+ zIndex: 10
12045
+ };
12046
+ const AnnotationsOverlay = React.memo(({
12047
+ elements,
12048
+ viewport,
12049
+ containerWidth,
12050
+ containerHeight,
12051
+ renderAnnotation
12052
+ }) => {
12053
+ const visibleElements = React.useMemo(() => {
12054
+ const margin = 100;
12055
+ return elements.filter((el) => {
12056
+ if (el.type === "text" && el.containerId) return false;
12057
+ if (!el.isVisible) return false;
12058
+ const sx = el.x * viewport.scale + viewport.x;
12059
+ const sy = el.y * viewport.scale + viewport.y;
12060
+ const sw = el.width * viewport.scale;
12061
+ const sh = el.height * viewport.scale;
12062
+ if (sx + sw < -margin || sy + sh < -margin) return false;
12063
+ if (sx > containerWidth + margin || sy > containerHeight + margin) return false;
12064
+ return true;
12065
+ });
12066
+ }, [elements, viewport.scale, viewport.x, viewport.y, containerWidth, containerHeight]);
12067
+ if (visibleElements.length === 0) return null;
12068
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: CONTAINER_STYLE, children: visibleElements.map((el) => /* @__PURE__ */ jsxRuntime.jsx(
12069
+ AnnotationItem,
12070
+ {
12071
+ element: el,
12072
+ viewport,
12073
+ renderAnnotation
12074
+ },
12075
+ el.id
12076
+ )) });
12077
+ });
11677
12078
  Konva.showWarnings = false;
11678
12079
  function arraysShallowEqual(a, b) {
11679
12080
  if (a.length !== b.length) return false;
@@ -11700,9 +12101,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11700
12101
  onGroupDragEnd
11701
12102
  }) => {
11702
12103
  const layerRef = React.useRef(null);
11703
- React.useEffect(() => {
12104
+ React.useLayoutEffect(() => {
11704
12105
  const layer = layerRef.current;
11705
- if (!layer || elements.length === 0) return;
12106
+ if (!layer) return;
12107
+ layer.clearCache();
12108
+ if (elements.length === 0) return;
11706
12109
  const dpr2 = window.devicePixelRatio || 1;
11707
12110
  const cachePixelRatio = dpr2 * Math.max(1, viewportScale);
11708
12111
  const rafId = requestAnimationFrame(() => {
@@ -11721,7 +12124,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11721
12124
  });
11722
12125
  return () => {
11723
12126
  cancelAnimationFrame(rafId);
11724
- layer.clearCache();
11725
12127
  };
11726
12128
  }, [elements, viewportScale, autoEditTextId]);
11727
12129
  const { ungrouped, groups } = React.useMemo(() => {
@@ -11872,6 +12274,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11872
12274
  className,
11873
12275
  contextMenuItems: contextMenuItemsProp,
11874
12276
  renderContextMenu,
12277
+ renderAnnotation,
11875
12278
  collaboration: collaborationConfig,
11876
12279
  workerConfig,
11877
12280
  customElementTypes
@@ -11930,7 +12333,9 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11930
12333
  const { isDraggingPoint: isLinearDragging, elementId: linearEditId } = linearEdit;
11931
12334
  const prevResolvedRef = React.useRef([]);
11932
12335
  const resolvedElements = React.useMemo(() => {
12336
+ var _a, _b;
11933
12337
  let result = null;
12338
+ const changedConnectorIds = /* @__PURE__ */ new Set();
11934
12339
  for (let i = 0; i < elements.length; i++) {
11935
12340
  const el = elements[i];
11936
12341
  if (el.type !== "line" && el.type !== "arrow") {
@@ -11939,10 +12344,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11939
12344
  }
11940
12345
  const conn = el;
11941
12346
  if (!conn.startBinding && !conn.endBinding) {
12347
+ if ((_a = conn.boundElements) == null ? void 0 : _a.some((be) => be.type === "text")) {
12348
+ changedConnectorIds.add(conn.id);
12349
+ if (!result) result = elements.slice(0, i);
12350
+ }
11942
12351
  if (result) result[i] = el;
11943
12352
  continue;
11944
12353
  }
11945
12354
  if (isLinearDragging && linearEditId === el.id) {
12355
+ if ((_b = conn.boundElements) == null ? void 0 : _b.some((be) => be.type === "text")) {
12356
+ if (!result) result = elements.slice(0, i);
12357
+ changedConnectorIds.add(conn.id);
12358
+ }
11946
12359
  if (result) result[i] = el;
11947
12360
  continue;
11948
12361
  }
@@ -11952,10 +12365,31 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11952
12365
  result = elements.slice(0, i);
11953
12366
  }
11954
12367
  result[i] = { ...conn, ...recomputed };
12368
+ changedConnectorIds.add(conn.id);
11955
12369
  } else {
11956
12370
  if (result) result[i] = el;
11957
12371
  }
11958
12372
  }
12373
+ if (changedConnectorIds.size > 0 && result) {
12374
+ const patchMap = /* @__PURE__ */ new Map();
12375
+ for (const el of result) patchMap.set(el.id, el);
12376
+ for (const connId of changedConnectorIds) {
12377
+ const conn = patchMap.get(connId);
12378
+ if (!(conn == null ? void 0 : conn.boundElements)) continue;
12379
+ for (const be of conn.boundElements) {
12380
+ if (be.type !== "text") continue;
12381
+ const txtIdx = result.findIndex((e) => e.id === be.id);
12382
+ if (txtIdx === -1) continue;
12383
+ const txt = result[txtIdx];
12384
+ const textW = Math.max(10, txt.width || 60);
12385
+ const textH = txt.height || 30;
12386
+ const pos = computeConnectorLabelPosition(conn, textW, textH);
12387
+ if (Math.abs(txt.x - pos.x) > 0.01 || Math.abs(txt.y - pos.y) > 0.01) {
12388
+ result[txtIdx] = { ...txt, x: pos.x, y: pos.y };
12389
+ }
12390
+ }
12391
+ }
12392
+ }
11959
12393
  const finalResult = result ?? elements;
11960
12394
  const prev = prevResolvedRef.current;
11961
12395
  if (finalResult.length === prev.length) {
@@ -11987,7 +12421,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11987
12421
  const prevStaticRef = React.useRef([]);
11988
12422
  const prevInteractiveRef = React.useRef([]);
11989
12423
  const { staticElements, interactiveElements } = React.useMemo(() => {
11990
- var _a, _b;
12424
+ var _a, _b, _c, _d;
11991
12425
  let effectiveSelected = drawingElementId ? /* @__PURE__ */ new Set([...selectedIdsSet, drawingElementId]) : selectedIdsSet;
11992
12426
  if (effectiveSelected.size > 0) {
11993
12427
  const expanded = new Set(effectiveSelected);
@@ -12001,6 +12435,32 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12001
12435
  }
12002
12436
  }
12003
12437
  }
12438
+ for (const el of visibleElements) {
12439
+ if (!expanded.has(el.id)) continue;
12440
+ if (el.boundElements) {
12441
+ for (const be of el.boundElements) {
12442
+ if (be.type === "text") expanded.add(be.id);
12443
+ }
12444
+ }
12445
+ if (el.type === "text" && el.containerId) {
12446
+ expanded.add(el.containerId);
12447
+ }
12448
+ }
12449
+ for (const el of visibleElements) {
12450
+ if (el.type !== "line" && el.type !== "arrow") continue;
12451
+ if (expanded.has(el.id)) continue;
12452
+ const conn = el;
12453
+ const startBound = (_c = conn.startBinding) == null ? void 0 : _c.elementId;
12454
+ const endBound = (_d = conn.endBinding) == null ? void 0 : _d.elementId;
12455
+ if (startBound && expanded.has(startBound) || endBound && expanded.has(endBound)) {
12456
+ expanded.add(el.id);
12457
+ if (el.boundElements) {
12458
+ for (const be of el.boundElements) {
12459
+ if (be.type === "text") expanded.add(be.id);
12460
+ }
12461
+ }
12462
+ }
12463
+ }
12004
12464
  if (expanded.size !== effectiveSelected.size) {
12005
12465
  effectiveSelected = expanded;
12006
12466
  }
@@ -12159,7 +12619,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12159
12619
  const files = getImageFilesFromDataTransfer(e.dataTransfer);
12160
12620
  if (files.length === 0) return;
12161
12621
  const rect = container.getBoundingClientRect();
12162
- stageRef.current;
12163
12622
  const vp = useCanvasStore.getState().viewport;
12164
12623
  const dropX = (e.clientX - rect.left - vp.x) / vp.scale;
12165
12624
  const dropY = (e.clientY - rect.top - vp.y) / vp.scale;
@@ -12325,6 +12784,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12325
12784
  if (!pos) return;
12326
12785
  if (contextMenu) setContextMenu(null);
12327
12786
  if (ctx.activeTool === "hand" || isSpacePanning) return;
12787
+ if (editingTextId) {
12788
+ const activeEl = document.activeElement;
12789
+ if ((activeEl == null ? void 0 : activeEl.tagName) === "TEXTAREA") {
12790
+ activeEl.blur();
12791
+ }
12792
+ }
12328
12793
  const handler = getToolHandler(ctx.activeTool);
12329
12794
  handler == null ? void 0 : handler.onMouseDown(e, pos, ctx);
12330
12795
  if (currentElementIdRef.current) {
@@ -12332,7 +12797,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12332
12797
  }
12333
12798
  },
12334
12799
  // eslint-disable-next-line react-hooks/exhaustive-deps
12335
- [readOnly, contextMenu, isSpacePanning]
12800
+ [readOnly, contextMenu, isSpacePanning, editingTextId]
12336
12801
  );
12337
12802
  const handleMouseMoveCore = React.useCallback((e) => {
12338
12803
  if (spaceKeyRef.current) return;
@@ -12379,7 +12844,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12379
12844
  if (!stage) return;
12380
12845
  const pointer = stage.getPointerPosition();
12381
12846
  if (!pointer) return;
12382
- const scaleBy = 1.05;
12847
+ const scaleBy = 1.02;
12383
12848
  const dir2 = e.evt.deltaY > 0 ? -1 : 1;
12384
12849
  const targetScale = Math.min(MAX_ZOOM, Math.max(
12385
12850
  MIN_ZOOM,
@@ -12389,13 +12854,14 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12389
12854
  },
12390
12855
  [viewport, setViewport]
12391
12856
  );
12392
- const handleStageDragEnd = React.useCallback(
12857
+ const handleStageDragMove = React.useCallback(
12393
12858
  (e) => {
12394
12859
  if (activeTool !== "hand" && !spaceKeyRef.current && !isSpacePanning) return;
12395
12860
  setViewport({ x: e.target.x(), y: e.target.y() });
12396
12861
  },
12397
12862
  [activeTool, setViewport, isSpacePanning]
12398
12863
  );
12864
+ const handleStageDragEnd = handleStageDragMove;
12399
12865
  const handleElementSelect = React.useCallback(
12400
12866
  (id) => {
12401
12867
  var _a;
@@ -12443,6 +12909,32 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12443
12909
  useCanvasStore.getState().batchUpdateElements(updates);
12444
12910
  }, []);
12445
12911
  const unboundConnectorIdsRef = React.useRef(/* @__PURE__ */ new Set());
12912
+ const syncBoundTextNodes = React.useCallback(
12913
+ (el, newX, newY, newW, newH) => {
12914
+ var _a, _b;
12915
+ if (!el.boundElements || !stageRef.current) return;
12916
+ if (!CONTAINER_TYPES.has(el.type)) return;
12917
+ const elements2 = useCanvasStore.getState().elements;
12918
+ const shapeW = newW ?? el.width;
12919
+ const shapeH = newH ?? el.height;
12920
+ for (const be of el.boundElements) {
12921
+ if (be.type !== "text") continue;
12922
+ const txt = elements2.find((e) => e.id === be.id);
12923
+ if (!txt) continue;
12924
+ const textNode = stageRef.current.findOne("#" + be.id);
12925
+ if (!textNode) continue;
12926
+ const textNodeH = ((_a = textNode.height) == null ? void 0 : _a.call(textNode)) ?? txt.height;
12927
+ const pos = computeBoundTextPosition(
12928
+ { x: newX, y: newY, width: shapeW, height: shapeH },
12929
+ { height: textNodeH, verticalAlign: txt.verticalAlign }
12930
+ );
12931
+ (_b = textNode.width) == null ? void 0 : _b.call(textNode, pos.width);
12932
+ textNode.x(pos.x);
12933
+ textNode.y(pos.y);
12934
+ }
12935
+ },
12936
+ []
12937
+ );
12446
12938
  const handleElementDragMove = React.useCallback(
12447
12939
  (id, updates) => {
12448
12940
  if (readOnly) return;
@@ -12453,6 +12945,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12453
12945
  const { elements: elements2, selectedIds: selectedIds2 } = useCanvasStore.getState();
12454
12946
  if (selectedIds2.length > MULTI_DRAG_STORE_SKIP_THRESHOLD) {
12455
12947
  isMultiDragSkippingRef.current = true;
12948
+ const el2 = elements2.find((e) => e.id === id);
12949
+ if (el2) {
12950
+ const nx = updates.x ?? el2.x;
12951
+ const ny = updates.y ?? el2.y;
12952
+ syncBoundTextNodes(el2, nx, ny);
12953
+ }
12456
12954
  return;
12457
12955
  }
12458
12956
  isMultiDragSkippingRef.current = false;
@@ -12479,12 +12977,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12479
12977
  }
12480
12978
  if (!dragBatchRef.current) dragBatchRef.current = /* @__PURE__ */ new Map();
12481
12979
  dragBatchRef.current.set(id, updates);
12980
+ if (el) {
12981
+ const nx = updates.x ?? el.x;
12982
+ const ny = updates.y ?? el.y;
12983
+ const nw = updates.width;
12984
+ const nh = updates.height;
12985
+ syncBoundTextNodes(el, nx, ny, nw, nh);
12986
+ }
12482
12987
  if (!dragFlushScheduledRef.current) {
12483
12988
  dragFlushScheduledRef.current = true;
12484
12989
  queueMicrotask(flushDragBatch);
12485
12990
  }
12486
12991
  },
12487
- [readOnly, flushDragBatch]
12992
+ [readOnly, flushDragBatch, syncBoundTextNodes]
12488
12993
  );
12489
12994
  const handleDragSnap = React.useCallback(
12490
12995
  (id, bounds) => {
@@ -12516,39 +13021,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12516
13021
  store2.batchUpdateElements(entries.map(([id, upd]) => ({ id, updates: upd })));
12517
13022
  setAlignGuides([]);
12518
13023
  const freshElements = useCanvasStore.getState().elements;
12519
- const elMap = /* @__PURE__ */ new Map();
12520
- for (const el of freshElements) elMap.set(el.id, el);
12521
- const connectorUpdates = [];
12522
- const processedConnectors = /* @__PURE__ */ new Set();
12523
- for (const [id] of entries) {
12524
- unboundConnectorIdsRef.current.delete(id);
12525
- const connectors = findConnectorsForElement(id, freshElements);
12526
- for (const conn of connectors) {
12527
- if (processedConnectors.has(conn.id)) continue;
12528
- processedConnectors.add(conn.id);
12529
- const freshConn = elMap.get(conn.id);
12530
- if (!freshConn) continue;
12531
- const recomputed = recomputeBoundPoints(freshConn, freshElements);
12532
- if (recomputed) connectorUpdates.push({ id: freshConn.id, updates: recomputed });
12533
- }
12534
- const el = elMap.get(id);
12535
- if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
12536
- const PADDING2 = 4;
12537
- for (const be of el.boundElements) {
12538
- if (be.type !== "text") continue;
12539
- const txt = elMap.get(be.id);
12540
- if (!txt) continue;
12541
- const tw = Math.max(20, el.width - PADDING2 * 2);
12542
- let ty;
12543
- if (txt.verticalAlign === "top") ty = el.y + PADDING2;
12544
- else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
12545
- else ty = el.y + (el.height - txt.height) / 2;
12546
- connectorUpdates.push({ id: be.id, updates: { x: el.x + PADDING2, y: ty, width: tw } });
12547
- }
12548
- }
12549
- }
12550
- if (connectorUpdates.length > 0) {
12551
- useCanvasStore.getState().batchUpdateElements(connectorUpdates);
13024
+ const movedIds = entries.map(([id]) => id);
13025
+ for (const id of movedIds) unboundConnectorIdsRef.current.delete(id);
13026
+ const { updates: syncUpdates } = syncAfterDrag(movedIds, freshElements);
13027
+ if (syncUpdates.length > 0) {
13028
+ useCanvasStore.getState().batchUpdateElements(syncUpdates);
12552
13029
  }
12553
13030
  useCanvasStore.getState().pushHistory();
12554
13031
  if (savedPixelRatioRef.current !== null) {
@@ -12573,46 +13050,22 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12573
13050
  setAlignGuides([]);
12574
13051
  unboundConnectorIdsRef.current.delete(id);
12575
13052
  const freshElements = useCanvasStore.getState().elements;
13053
+ const { updates: syncUpdates } = syncAfterDrag([id], freshElements);
13054
+ for (const su of syncUpdates) updateElement(su.id, su.updates);
12576
13055
  const elMap = /* @__PURE__ */ new Map();
12577
- for (const e of freshElements) elMap.set(e.id, e);
12578
- const connectors = findConnectorsForElement(id, freshElements);
12579
- for (const conn of connectors) {
12580
- const freshConn = elMap.get(conn.id);
12581
- if (!freshConn) continue;
12582
- const recomputed = recomputeBoundPoints(freshConn, freshElements);
12583
- if (recomputed) updateElement(freshConn.id, recomputed);
12584
- }
13056
+ for (const e of useCanvasStore.getState().elements) elMap.set(e.id, e);
12585
13057
  const el = elMap.get(id);
12586
- if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
12587
- const PADDING2 = 4;
12588
- for (const be of el.boundElements) {
12589
- if (be.type !== "text") continue;
12590
- const txt = elMap.get(be.id);
12591
- if (!txt) continue;
12592
- const tw = Math.max(20, el.width - PADDING2 * 2);
12593
- let ty;
12594
- if (txt.verticalAlign === "top") ty = el.y + PADDING2;
12595
- else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
12596
- else ty = el.y + (el.height - txt.height) / 2;
12597
- updateElement(be.id, { x: el.x + PADDING2, y: ty, width: tw });
12598
- }
12599
- }
12600
13058
  if ((el == null ? void 0 : el.type) === "text") {
12601
13059
  const txt = el;
12602
13060
  if (txt.containerId) {
12603
13061
  const ctr = elMap.get(txt.containerId);
12604
- if (ctr && ["rectangle", "ellipse", "diamond", "image"].includes(ctr.type)) {
12605
- const PADDING2 = 4;
12606
- const minH = txt.height + PADDING2 * 2;
13062
+ if (ctr && CONTAINER_TYPES.has(ctr.type)) {
13063
+ const minH = txt.height + BOUND_TEXT_PADDING * 2;
12607
13064
  if (ctr.height < minH) {
12608
13065
  updateElement(ctr.id, { height: minH });
12609
13066
  const updatedElements = useCanvasStore.getState().elements;
12610
- for (const c of findConnectorsForElement(ctr.id, updatedElements)) {
12611
- const fc = updatedElements.find((e) => e.id === c.id);
12612
- if (!fc) continue;
12613
- const r = recomputeBoundPoints(fc, updatedElements);
12614
- if (r) updateElement(fc.id, r);
12615
- }
13067
+ const { updates: resizeSync } = syncAfterDrag([ctr.id], updatedElements);
13068
+ for (const su of resizeSync) updateElement(su.id, su.updates);
12616
13069
  }
12617
13070
  }
12618
13071
  }
@@ -12643,41 +13096,15 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12643
13096
  }));
12644
13097
  store2.batchUpdateElements(posUpdates);
12645
13098
  const freshElements = useCanvasStore.getState().elements;
12646
- const elMap = /* @__PURE__ */ new Map();
12647
- for (const el of freshElements) elMap.set(el.id, el);
12648
13099
  const memberIds = new Set(members.map((m) => m.id));
12649
- const connectorUpdates = [];
12650
- const processedConnectors = /* @__PURE__ */ new Set();
12651
- for (const member of members) {
12652
- const connectors = findConnectorsForElement(member.id, freshElements);
12653
- for (const conn of connectors) {
12654
- if (processedConnectors.has(conn.id)) continue;
12655
- processedConnectors.add(conn.id);
12656
- if (memberIds.has(conn.id)) continue;
12657
- const freshConn = elMap.get(conn.id);
12658
- if (!freshConn) continue;
12659
- const recomputed = recomputeBoundPoints(freshConn, freshElements);
12660
- if (recomputed) connectorUpdates.push({ id: freshConn.id, updates: recomputed });
12661
- }
12662
- const el = elMap.get(member.id);
12663
- if ((el == null ? void 0 : el.boundElements) && ["rectangle", "ellipse", "diamond", "image"].includes(el.type)) {
12664
- const PADDING2 = 4;
12665
- for (const be of el.boundElements) {
12666
- if (be.type !== "text") continue;
12667
- if (memberIds.has(be.id)) continue;
12668
- const txt = elMap.get(be.id);
12669
- if (!txt) continue;
12670
- const tw = Math.max(20, el.width - PADDING2 * 2);
12671
- let ty;
12672
- if (txt.verticalAlign === "top") ty = el.y + PADDING2;
12673
- else if (txt.verticalAlign === "bottom") ty = el.y + el.height - txt.height - PADDING2;
12674
- else ty = el.y + (el.height - txt.height) / 2;
12675
- connectorUpdates.push({ id: be.id, updates: { x: el.x + PADDING2, y: ty, width: tw } });
12676
- }
12677
- }
12678
- }
12679
- if (connectorUpdates.length > 0) {
12680
- useCanvasStore.getState().batchUpdateElements(connectorUpdates);
13100
+ const { updates: syncUpdates } = syncAfterDrag(
13101
+ memberIds,
13102
+ freshElements,
13103
+ memberIds
13104
+ // skip group-internal connectors & text (already moved)
13105
+ );
13106
+ if (syncUpdates.length > 0) {
13107
+ useCanvasStore.getState().batchUpdateElements(syncUpdates);
12681
13108
  }
12682
13109
  store2.pushHistory();
12683
13110
  },
@@ -12708,24 +13135,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12708
13135
  }
12709
13136
  const textId = generateId();
12710
13137
  const conn = el;
12711
- const pts = conn.points;
12712
- const startPt = { x: pts[0], y: pts[1] };
12713
- const endPt = { x: pts[pts.length - 2], y: pts[pts.length - 1] };
12714
- let midX, midY;
12715
- if (conn.lineType === "curved") {
12716
- const cp = computeCurveControlPoint(startPt, endPt, conn.curvature ?? CURVE_RATIO);
12717
- const mid = quadBezierAt(startPt, cp, endPt, 0.5);
12718
- midX = conn.x + mid.x;
12719
- midY = conn.y + mid.y;
12720
- } else {
12721
- midX = conn.x + (startPt.x + endPt.x) / 2;
12722
- midY = conn.y + (startPt.y + endPt.y) / 2;
12723
- }
13138
+ const labelPos = computeConnectorLabelPosition(conn, 100, 30);
12724
13139
  const textEl = {
12725
13140
  id: textId,
12726
13141
  type: "text",
12727
- x: midX,
12728
- y: midY,
13142
+ x: labelPos.x,
13143
+ y: labelPos.y,
12729
13144
  width: 100,
12730
13145
  height: 30,
12731
13146
  rotation: 0,
@@ -12751,7 +13166,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12751
13166
  if (el.type === "rectangle" || el.type === "ellipse" || el.type === "diamond" || el.type === "image") {
12752
13167
  const existingTextBinding = (_b = el.boundElements) == null ? void 0 : _b.find((be) => be.type === "text");
12753
13168
  if (existingTextBinding) {
12754
- setSel([existingTextBinding.id]);
13169
+ setSel([existingTextBinding.id, id]);
12755
13170
  setAutoEditTextId(existingTextBinding.id);
12756
13171
  return;
12757
13172
  }
@@ -12779,7 +13194,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12779
13194
  update(id, {
12780
13195
  boundElements: [...currentBound, { id: textId, type: "text" }]
12781
13196
  });
12782
- setSel([textId]);
13197
+ setSel([textId, id]);
12783
13198
  setAutoEditTextId(textId);
12784
13199
  return;
12785
13200
  }
@@ -12830,11 +13245,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12830
13245
  (id, isEmpty) => {
12831
13246
  setEditingTextId(null);
12832
13247
  setAutoEditTextId(null);
13248
+ const {
13249
+ elements: els,
13250
+ selectedIds: currentSel,
13251
+ setSelectedIds: setSel,
13252
+ updateElement: update,
13253
+ deleteElements: del
13254
+ } = useCanvasStore.getState();
13255
+ const textEl = els.find((e) => e.id === id);
13256
+ const containerId = (textEl == null ? void 0 : textEl.type) === "text" ? textEl.containerId : null;
12833
13257
  if (isEmpty) {
12834
- const { elements: els, updateElement: update, deleteElements: del, pushHistory: push } = useCanvasStore.getState();
12835
- const textEl = els.find((e) => e.id === id);
12836
- if ((textEl == null ? void 0 : textEl.type) === "text" && textEl.containerId) {
12837
- const containerId = textEl.containerId;
13258
+ if (containerId) {
12838
13259
  const container = els.find((e) => e.id === containerId);
12839
13260
  if (container == null ? void 0 : container.boundElements) {
12840
13261
  update(containerId, {
@@ -12847,6 +13268,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12847
13268
  } else {
12848
13269
  useCanvasStore.getState().pushHistory();
12849
13270
  }
13271
+ if (containerId && currentSel.includes(containerId)) {
13272
+ setSel([containerId]);
13273
+ } else if (containerId) {
13274
+ setSel(currentSel.filter((sid) => sid !== id));
13275
+ }
12850
13276
  },
12851
13277
  [onElementDelete]
12852
13278
  );
@@ -12914,6 +13340,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
12914
13340
  );
12915
13341
  const contextMenuItems = React.useMemo(() => {
12916
13342
  var _a;
13343
+ if (!contextMenu) return [];
12917
13344
  const hasSelection = selectedIds.length > 0;
12918
13345
  const isMac = navigator.platform.includes("Mac");
12919
13346
  const mod = isMac ? "⌘" : "Ctrl+";
@@ -13215,6 +13642,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13215
13642
  onMouseMove: handleMouseMove,
13216
13643
  onMouseUp: handleMouseUp,
13217
13644
  onWheel: handleWheel,
13645
+ onDragMove: handleStageDragMove,
13218
13646
  onDragEnd: handleStageDragEnd,
13219
13647
  onTouchStart: handleMouseDown,
13220
13648
  onTouchMove: handleMouseMove,
@@ -13276,6 +13704,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13276
13704
  if (el.type === "line" || el.type === "arrow") return false;
13277
13705
  if (el.type === "text" && el.containerId) return false;
13278
13706
  if (sid === editingTextId) return false;
13707
+ if (editingTextId) {
13708
+ const editingEl = resolvedElementMap.get(editingTextId);
13709
+ if ((editingEl == null ? void 0 : editingEl.type) === "text" && editingEl.containerId === sid) return false;
13710
+ }
13279
13711
  return true;
13280
13712
  });
13281
13713
  if (transformableIds.length === 0) return null;
@@ -13391,6 +13823,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13391
13823
  theme
13392
13824
  }
13393
13825
  )),
13826
+ renderAnnotation && /* @__PURE__ */ jsxRuntime.jsx(
13827
+ AnnotationsOverlay,
13828
+ {
13829
+ elements: resolvedElements,
13830
+ viewport,
13831
+ containerWidth: dimensions.width,
13832
+ containerHeight: dimensions.height,
13833
+ renderAnnotation
13834
+ }
13835
+ ),
13394
13836
  showStatusBar && /* @__PURE__ */ jsxRuntime.jsx(StatusBar, { theme })
13395
13837
  ]
13396
13838
  }
@@ -13398,9 +13840,17 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13398
13840
  });
13399
13841
  FlowCanvas.displayName = "FlowCanvas";
13400
13842
  const StatusBar = React.memo(({ theme }) => {
13401
- const elementCount = useCanvasStore((s) => s.elements.length);
13843
+ const elementCount = useCanvasStore(
13844
+ (s) => s.elements.filter((el) => !(el.type === "text" && el.containerId)).length
13845
+ );
13402
13846
  const activeTool = useCanvasStore((s) => s.activeTool);
13403
- const selectedCount = useCanvasStore((s) => s.selectedIds.length);
13847
+ const selectedCount = useCanvasStore((s) => {
13848
+ const els = s.elements;
13849
+ return s.selectedIds.filter((id) => {
13850
+ const el = els.find((e) => e.id === id);
13851
+ return el && !(el.type === "text" && el.containerId);
13852
+ }).length;
13853
+ });
13404
13854
  return /* @__PURE__ */ jsxRuntime.jsxs(
13405
13855
  "div",
13406
13856
  {
@@ -13605,6 +14055,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13605
14055
  }
13606
14056
  const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
13607
14057
  const BASE = DIGITS.length;
14058
+ function trimTrailingZeros(s) {
14059
+ let end = s.length;
14060
+ while (end > 0 && s[end - 1] === "0") end--;
14061
+ return end === s.length ? s : s.slice(0, end);
14062
+ }
13608
14063
  const SMALLEST_CHAR = DIGITS[0];
13609
14064
  const LARGEST_CHAR = DIGITS[BASE - 1];
13610
14065
  function midChar(a, b) {
@@ -13677,7 +14132,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
13677
14132
  if (mid !== null) {
13678
14133
  return commonPrefix + mid;
13679
14134
  }
13680
- return commonPrefix + aPad[i] + incrementKey(aPad.slice(i + 1).replace(/0+$/, "") || SMALLEST_CHAR);
14135
+ return commonPrefix + aPad[i] + incrementKey(trimTrailingZeros(aPad.slice(i + 1)) || SMALLEST_CHAR);
13681
14136
  }
13682
14137
  return a + DIGITS[Math.floor(BASE / 2)];
13683
14138
  }
@@ -15310,6 +15765,11 @@ void main() {
15310
15765
  exports2.ExportWorkerManager = ExportWorkerManager;
15311
15766
  exports2.FILL_COLORS = FILL_COLORS;
15312
15767
  exports2.FlowCanvas = FlowCanvas;
15768
+ exports2.LABEL_CORNER = LABEL_CORNER;
15769
+ exports2.LABEL_LINE_HEIGHT = LABEL_LINE_HEIGHT;
15770
+ exports2.LABEL_MIN_WIDTH = LABEL_MIN_WIDTH;
15771
+ exports2.LABEL_PADDING_H = LABEL_PADDING_H;
15772
+ exports2.LABEL_PADDING_V = LABEL_PADDING_V;
15313
15773
  exports2.LINE_TYPES = LINE_TYPES;
15314
15774
  exports2.OperationLog = OperationLog;
15315
15775
  exports2.ROUGHNESS_CONFIGS = ROUGHNESS_CONFIGS;
@@ -15347,6 +15807,7 @@ void main() {
15347
15807
  exports2.computeElbowRoute = computeElbowRoute;
15348
15808
  exports2.computeFixedPoint = computeFixedPoint;
15349
15809
  exports2.computeImageElementDimensions = computeImageElementDimensions;
15810
+ exports2.computePillSize = computePillSize;
15350
15811
  exports2.computeZoomToFit = computeZoomToFit;
15351
15812
  exports2.createCollaborationProvider = createCollaborationProvider;
15352
15813
  exports2.createImageElement = createImageElement;
@@ -15411,6 +15872,7 @@ void main() {
15411
15872
  exports2.isValidFractionalIndex = isValidFractionalIndex;
15412
15873
  exports2.isWorkerSupported = isWorkerSupported;
15413
15874
  exports2.loadImage = loadImage;
15875
+ exports2.measureLabelText = measureLabelText;
15414
15876
  exports2.normalizeRect = normalizeRect;
15415
15877
  exports2.onStatusChange = onStatusChange;
15416
15878
  exports2.opAdd = opAdd;