@almadar/ui 5.58.0 → 5.59.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.
@@ -38419,6 +38419,9 @@ var init_GraphCanvas = __esm({
38419
38419
  actions,
38420
38420
  onNodeClick,
38421
38421
  nodeClickEvent,
38422
+ selectedNodeId,
38423
+ repulsion = 800,
38424
+ linkDistance = 100,
38422
38425
  layout = "force",
38423
38426
  entity,
38424
38427
  isLoading = false,
@@ -38434,6 +38437,35 @@ var init_GraphCanvas = __esm({
38434
38437
  const [hoveredNode, setHoveredNode] = React76.useState(null);
38435
38438
  const nodesRef = React76.useRef([]);
38436
38439
  const [, forceUpdate] = React76.useState(0);
38440
+ const interactionRef = React76.useRef({
38441
+ mode: "none",
38442
+ dragNodeId: null,
38443
+ startMouse: { x: 0, y: 0 },
38444
+ startOffset: { x: 0, y: 0 },
38445
+ downPos: { x: 0, y: 0 }
38446
+ });
38447
+ const toCoords = React76.useCallback(
38448
+ (e) => {
38449
+ const canvas = canvasRef.current;
38450
+ if (!canvas) return null;
38451
+ const rect = canvas.getBoundingClientRect();
38452
+ const screenX = e.clientX - rect.left;
38453
+ const screenY = e.clientY - rect.top;
38454
+ return {
38455
+ screenX,
38456
+ screenY,
38457
+ graphX: (screenX - offset.x) / zoom,
38458
+ graphY: (screenY - offset.y) / zoom
38459
+ };
38460
+ },
38461
+ [offset, zoom]
38462
+ );
38463
+ const nodeAt = React76.useCallback((graphX, graphY) => {
38464
+ return nodesRef.current.find((n) => {
38465
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
38466
+ return dist < (n.size || 8) + 4;
38467
+ });
38468
+ }, []);
38437
38469
  const handleAction = React76.useCallback(
38438
38470
  (action) => {
38439
38471
  if (action.event) {
@@ -38499,7 +38531,7 @@ var init_GraphCanvas = __esm({
38499
38531
  const dx = nodes[j].x - nodes[i].x;
38500
38532
  const dy = nodes[j].y - nodes[i].y;
38501
38533
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38502
- const force = 800 / (dist * dist);
38534
+ const force = repulsion / (dist * dist);
38503
38535
  const fx = dx / dist * force;
38504
38536
  const fy = dy / dist * force;
38505
38537
  nodes[i].fx -= fx;
@@ -38515,7 +38547,7 @@ var init_GraphCanvas = __esm({
38515
38547
  const dx = target.x - source.x;
38516
38548
  const dy = target.y - source.y;
38517
38549
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38518
- const force = (dist - 100) * 0.05;
38550
+ const force = (dist - linkDistance) * 0.05;
38519
38551
  const fx = dx / dist * force;
38520
38552
  const fy = dy / dist * force;
38521
38553
  source.fx += fx;
@@ -38549,7 +38581,7 @@ var init_GraphCanvas = __esm({
38549
38581
  return () => {
38550
38582
  cancelAnimationFrame(animRef.current);
38551
38583
  };
38552
- }, [propNodes, propEdges, layout]);
38584
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38553
38585
  React76.useEffect(() => {
38554
38586
  const canvas = canvasRef.current;
38555
38587
  if (!canvas) return;
@@ -38585,18 +38617,25 @@ var init_GraphCanvas = __esm({
38585
38617
  const size = node.size || 8;
38586
38618
  const color = node.color || getGroupColor(node.group, groups);
38587
38619
  const isHovered = hoveredNode === node.id;
38620
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38621
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38588
38622
  ctx.beginPath();
38589
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
38623
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38590
38624
  ctx.fillStyle = color;
38591
38625
  ctx.fill();
38592
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38593
- ctx.lineWidth = isHovered ? 2 : 1;
38626
+ if (isSelected) {
38627
+ ctx.strokeStyle = "var(--color-accent)";
38628
+ ctx.lineWidth = 3;
38629
+ } else {
38630
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38631
+ ctx.lineWidth = isHovered ? 2 : 1;
38632
+ }
38594
38633
  ctx.stroke();
38595
38634
  if (showLabels && node.label) {
38596
38635
  ctx.fillStyle = "#666666";
38597
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
38636
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
38598
38637
  ctx.textAlign = "center";
38599
- ctx.fillText(node.label, node.x, node.y + size + 12);
38638
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
38600
38639
  }
38601
38640
  }
38602
38641
  ctx.restore();
@@ -38607,6 +38646,97 @@ var init_GraphCanvas = __esm({
38607
38646
  setZoom(1);
38608
38647
  setOffset({ x: 0, y: 0 });
38609
38648
  }, []);
38649
+ const handleWheel = React76.useCallback(
38650
+ (e) => {
38651
+ if (!interactive) return;
38652
+ e.preventDefault();
38653
+ const coords = toCoords(e);
38654
+ if (!coords) return;
38655
+ const oldZoom = zoom;
38656
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38657
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38658
+ if (newZoom === oldZoom) return;
38659
+ setOffset((o) => ({
38660
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38661
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38662
+ }));
38663
+ setZoom(newZoom);
38664
+ },
38665
+ [interactive, toCoords, zoom]
38666
+ );
38667
+ const handleMouseDown = React76.useCallback(
38668
+ (e) => {
38669
+ const coords = toCoords(e);
38670
+ if (!coords) return;
38671
+ const node = nodeAt(coords.graphX, coords.graphY);
38672
+ const state = interactionRef.current;
38673
+ state.downPos = { x: e.clientX, y: e.clientY };
38674
+ state.startMouse = { x: e.clientX, y: e.clientY };
38675
+ state.startOffset = { ...offset };
38676
+ if (draggable && node) {
38677
+ state.mode = "dragging";
38678
+ state.dragNodeId = node.id;
38679
+ } else if (interactive) {
38680
+ state.mode = "panning";
38681
+ state.dragNodeId = null;
38682
+ } else {
38683
+ state.mode = "none";
38684
+ state.dragNodeId = null;
38685
+ }
38686
+ },
38687
+ [toCoords, nodeAt, draggable, interactive, offset]
38688
+ );
38689
+ const handleMouseMove = React76.useCallback(
38690
+ (e) => {
38691
+ const state = interactionRef.current;
38692
+ if (state.mode === "panning") {
38693
+ const dx = e.clientX - state.startMouse.x;
38694
+ const dy = e.clientY - state.startMouse.y;
38695
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
38696
+ return;
38697
+ }
38698
+ if (state.mode === "dragging" && state.dragNodeId) {
38699
+ const coords2 = toCoords(e);
38700
+ if (!coords2) return;
38701
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
38702
+ if (node2) {
38703
+ node2.x = coords2.graphX;
38704
+ node2.y = coords2.graphY;
38705
+ node2.vx = 0;
38706
+ node2.vy = 0;
38707
+ forceUpdate((n) => n + 1);
38708
+ }
38709
+ return;
38710
+ }
38711
+ const coords = toCoords(e);
38712
+ if (!coords) return;
38713
+ const node = nodeAt(coords.graphX, coords.graphY);
38714
+ setHoveredNode(node?.id ?? null);
38715
+ },
38716
+ [toCoords, nodeAt]
38717
+ );
38718
+ const handleMouseUp = React76.useCallback(
38719
+ (e) => {
38720
+ const state = interactionRef.current;
38721
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38722
+ state.mode = "none";
38723
+ state.dragNodeId = null;
38724
+ if (moved < 4) {
38725
+ const coords = toCoords(e);
38726
+ if (!coords) return;
38727
+ const node = nodeAt(coords.graphX, coords.graphY);
38728
+ if (node) {
38729
+ handleNodeClick(node);
38730
+ }
38731
+ }
38732
+ },
38733
+ [toCoords, nodeAt, handleNodeClick]
38734
+ );
38735
+ const handleMouseLeave = React76.useCallback(() => {
38736
+ interactionRef.current.mode = "none";
38737
+ interactionRef.current.dragNodeId = null;
38738
+ setHoveredNode(null);
38739
+ }, []);
38610
38740
  if (isLoading) {
38611
38741
  return /* @__PURE__ */ jsxRuntime.jsx(exports.LoadingState, { message: t("common.loading"), className });
38612
38742
  }
@@ -38668,20 +38798,11 @@ var init_GraphCanvas = __esm({
38668
38798
  height,
38669
38799
  className: "w-full cursor-grab active:cursor-grabbing",
38670
38800
  style: { height },
38671
- onClick: (e) => {
38672
- const canvas = canvasRef.current;
38673
- if (!canvas) return;
38674
- const rect = canvas.getBoundingClientRect();
38675
- const x = (e.clientX - rect.left - offset.x) / zoom;
38676
- const y = (e.clientY - rect.top - offset.y) / zoom;
38677
- const clickedNode = nodesRef.current.find((n) => {
38678
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
38679
- return dist < (n.size || 8) + 4;
38680
- });
38681
- if (clickedNode) {
38682
- handleNodeClick(clickedNode);
38683
- }
38684
- }
38801
+ onWheel: handleWheel,
38802
+ onMouseDown: handleMouseDown,
38803
+ onMouseMove: handleMouseMove,
38804
+ onMouseUp: handleMouseUp,
38805
+ onMouseLeave: handleMouseLeave
38685
38806
  }
38686
38807
  ) }),
38687
38808
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", align: "center", children: [
@@ -38373,6 +38373,9 @@ var init_GraphCanvas = __esm({
38373
38373
  actions,
38374
38374
  onNodeClick,
38375
38375
  nodeClickEvent,
38376
+ selectedNodeId,
38377
+ repulsion = 800,
38378
+ linkDistance = 100,
38376
38379
  layout = "force",
38377
38380
  entity,
38378
38381
  isLoading = false,
@@ -38388,6 +38391,35 @@ var init_GraphCanvas = __esm({
38388
38391
  const [hoveredNode, setHoveredNode] = useState(null);
38389
38392
  const nodesRef = useRef([]);
38390
38393
  const [, forceUpdate] = useState(0);
38394
+ const interactionRef = useRef({
38395
+ mode: "none",
38396
+ dragNodeId: null,
38397
+ startMouse: { x: 0, y: 0 },
38398
+ startOffset: { x: 0, y: 0 },
38399
+ downPos: { x: 0, y: 0 }
38400
+ });
38401
+ const toCoords = useCallback(
38402
+ (e) => {
38403
+ const canvas = canvasRef.current;
38404
+ if (!canvas) return null;
38405
+ const rect = canvas.getBoundingClientRect();
38406
+ const screenX = e.clientX - rect.left;
38407
+ const screenY = e.clientY - rect.top;
38408
+ return {
38409
+ screenX,
38410
+ screenY,
38411
+ graphX: (screenX - offset.x) / zoom,
38412
+ graphY: (screenY - offset.y) / zoom
38413
+ };
38414
+ },
38415
+ [offset, zoom]
38416
+ );
38417
+ const nodeAt = useCallback((graphX, graphY) => {
38418
+ return nodesRef.current.find((n) => {
38419
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
38420
+ return dist < (n.size || 8) + 4;
38421
+ });
38422
+ }, []);
38391
38423
  const handleAction = useCallback(
38392
38424
  (action) => {
38393
38425
  if (action.event) {
@@ -38453,7 +38485,7 @@ var init_GraphCanvas = __esm({
38453
38485
  const dx = nodes[j].x - nodes[i].x;
38454
38486
  const dy = nodes[j].y - nodes[i].y;
38455
38487
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38456
- const force = 800 / (dist * dist);
38488
+ const force = repulsion / (dist * dist);
38457
38489
  const fx = dx / dist * force;
38458
38490
  const fy = dy / dist * force;
38459
38491
  nodes[i].fx -= fx;
@@ -38469,7 +38501,7 @@ var init_GraphCanvas = __esm({
38469
38501
  const dx = target.x - source.x;
38470
38502
  const dy = target.y - source.y;
38471
38503
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38472
- const force = (dist - 100) * 0.05;
38504
+ const force = (dist - linkDistance) * 0.05;
38473
38505
  const fx = dx / dist * force;
38474
38506
  const fy = dy / dist * force;
38475
38507
  source.fx += fx;
@@ -38503,7 +38535,7 @@ var init_GraphCanvas = __esm({
38503
38535
  return () => {
38504
38536
  cancelAnimationFrame(animRef.current);
38505
38537
  };
38506
- }, [propNodes, propEdges, layout]);
38538
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38507
38539
  useEffect(() => {
38508
38540
  const canvas = canvasRef.current;
38509
38541
  if (!canvas) return;
@@ -38539,18 +38571,25 @@ var init_GraphCanvas = __esm({
38539
38571
  const size = node.size || 8;
38540
38572
  const color = node.color || getGroupColor(node.group, groups);
38541
38573
  const isHovered = hoveredNode === node.id;
38574
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38575
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38542
38576
  ctx.beginPath();
38543
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
38577
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38544
38578
  ctx.fillStyle = color;
38545
38579
  ctx.fill();
38546
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38547
- ctx.lineWidth = isHovered ? 2 : 1;
38580
+ if (isSelected) {
38581
+ ctx.strokeStyle = "var(--color-accent)";
38582
+ ctx.lineWidth = 3;
38583
+ } else {
38584
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38585
+ ctx.lineWidth = isHovered ? 2 : 1;
38586
+ }
38548
38587
  ctx.stroke();
38549
38588
  if (showLabels && node.label) {
38550
38589
  ctx.fillStyle = "#666666";
38551
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
38590
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
38552
38591
  ctx.textAlign = "center";
38553
- ctx.fillText(node.label, node.x, node.y + size + 12);
38592
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
38554
38593
  }
38555
38594
  }
38556
38595
  ctx.restore();
@@ -38561,6 +38600,97 @@ var init_GraphCanvas = __esm({
38561
38600
  setZoom(1);
38562
38601
  setOffset({ x: 0, y: 0 });
38563
38602
  }, []);
38603
+ const handleWheel = useCallback(
38604
+ (e) => {
38605
+ if (!interactive) return;
38606
+ e.preventDefault();
38607
+ const coords = toCoords(e);
38608
+ if (!coords) return;
38609
+ const oldZoom = zoom;
38610
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38611
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38612
+ if (newZoom === oldZoom) return;
38613
+ setOffset((o) => ({
38614
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38615
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38616
+ }));
38617
+ setZoom(newZoom);
38618
+ },
38619
+ [interactive, toCoords, zoom]
38620
+ );
38621
+ const handleMouseDown = useCallback(
38622
+ (e) => {
38623
+ const coords = toCoords(e);
38624
+ if (!coords) return;
38625
+ const node = nodeAt(coords.graphX, coords.graphY);
38626
+ const state = interactionRef.current;
38627
+ state.downPos = { x: e.clientX, y: e.clientY };
38628
+ state.startMouse = { x: e.clientX, y: e.clientY };
38629
+ state.startOffset = { ...offset };
38630
+ if (draggable && node) {
38631
+ state.mode = "dragging";
38632
+ state.dragNodeId = node.id;
38633
+ } else if (interactive) {
38634
+ state.mode = "panning";
38635
+ state.dragNodeId = null;
38636
+ } else {
38637
+ state.mode = "none";
38638
+ state.dragNodeId = null;
38639
+ }
38640
+ },
38641
+ [toCoords, nodeAt, draggable, interactive, offset]
38642
+ );
38643
+ const handleMouseMove = useCallback(
38644
+ (e) => {
38645
+ const state = interactionRef.current;
38646
+ if (state.mode === "panning") {
38647
+ const dx = e.clientX - state.startMouse.x;
38648
+ const dy = e.clientY - state.startMouse.y;
38649
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
38650
+ return;
38651
+ }
38652
+ if (state.mode === "dragging" && state.dragNodeId) {
38653
+ const coords2 = toCoords(e);
38654
+ if (!coords2) return;
38655
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
38656
+ if (node2) {
38657
+ node2.x = coords2.graphX;
38658
+ node2.y = coords2.graphY;
38659
+ node2.vx = 0;
38660
+ node2.vy = 0;
38661
+ forceUpdate((n) => n + 1);
38662
+ }
38663
+ return;
38664
+ }
38665
+ const coords = toCoords(e);
38666
+ if (!coords) return;
38667
+ const node = nodeAt(coords.graphX, coords.graphY);
38668
+ setHoveredNode(node?.id ?? null);
38669
+ },
38670
+ [toCoords, nodeAt]
38671
+ );
38672
+ const handleMouseUp = useCallback(
38673
+ (e) => {
38674
+ const state = interactionRef.current;
38675
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38676
+ state.mode = "none";
38677
+ state.dragNodeId = null;
38678
+ if (moved < 4) {
38679
+ const coords = toCoords(e);
38680
+ if (!coords) return;
38681
+ const node = nodeAt(coords.graphX, coords.graphY);
38682
+ if (node) {
38683
+ handleNodeClick(node);
38684
+ }
38685
+ }
38686
+ },
38687
+ [toCoords, nodeAt, handleNodeClick]
38688
+ );
38689
+ const handleMouseLeave = useCallback(() => {
38690
+ interactionRef.current.mode = "none";
38691
+ interactionRef.current.dragNodeId = null;
38692
+ setHoveredNode(null);
38693
+ }, []);
38564
38694
  if (isLoading) {
38565
38695
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
38566
38696
  }
@@ -38622,20 +38752,11 @@ var init_GraphCanvas = __esm({
38622
38752
  height,
38623
38753
  className: "w-full cursor-grab active:cursor-grabbing",
38624
38754
  style: { height },
38625
- onClick: (e) => {
38626
- const canvas = canvasRef.current;
38627
- if (!canvas) return;
38628
- const rect = canvas.getBoundingClientRect();
38629
- const x = (e.clientX - rect.left - offset.x) / zoom;
38630
- const y = (e.clientY - rect.top - offset.y) / zoom;
38631
- const clickedNode = nodesRef.current.find((n) => {
38632
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
38633
- return dist < (n.size || 8) + 4;
38634
- });
38635
- if (clickedNode) {
38636
- handleNodeClick(clickedNode);
38637
- }
38638
- }
38755
+ onWheel: handleWheel,
38756
+ onMouseDown: handleMouseDown,
38757
+ onMouseMove: handleMouseMove,
38758
+ onMouseUp: handleMouseUp,
38759
+ onMouseLeave: handleMouseLeave
38639
38760
  }
38640
38761
  ) }),
38641
38762
  groups.length > 1 && /* @__PURE__ */ jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
@@ -38000,6 +38000,9 @@ var init_GraphCanvas = __esm({
38000
38000
  actions,
38001
38001
  onNodeClick,
38002
38002
  nodeClickEvent,
38003
+ selectedNodeId,
38004
+ repulsion = 800,
38005
+ linkDistance = 100,
38003
38006
  layout = "force",
38004
38007
  entity,
38005
38008
  isLoading = false,
@@ -38015,6 +38018,35 @@ var init_GraphCanvas = __esm({
38015
38018
  const [hoveredNode, setHoveredNode] = React82.useState(null);
38016
38019
  const nodesRef = React82.useRef([]);
38017
38020
  const [, forceUpdate] = React82.useState(0);
38021
+ const interactionRef = React82.useRef({
38022
+ mode: "none",
38023
+ dragNodeId: null,
38024
+ startMouse: { x: 0, y: 0 },
38025
+ startOffset: { x: 0, y: 0 },
38026
+ downPos: { x: 0, y: 0 }
38027
+ });
38028
+ const toCoords = React82.useCallback(
38029
+ (e) => {
38030
+ const canvas = canvasRef.current;
38031
+ if (!canvas) return null;
38032
+ const rect = canvas.getBoundingClientRect();
38033
+ const screenX = e.clientX - rect.left;
38034
+ const screenY = e.clientY - rect.top;
38035
+ return {
38036
+ screenX,
38037
+ screenY,
38038
+ graphX: (screenX - offset.x) / zoom,
38039
+ graphY: (screenY - offset.y) / zoom
38040
+ };
38041
+ },
38042
+ [offset, zoom]
38043
+ );
38044
+ const nodeAt = React82.useCallback((graphX, graphY) => {
38045
+ return nodesRef.current.find((n) => {
38046
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
38047
+ return dist < (n.size || 8) + 4;
38048
+ });
38049
+ }, []);
38018
38050
  const handleAction = React82.useCallback(
38019
38051
  (action) => {
38020
38052
  if (action.event) {
@@ -38080,7 +38112,7 @@ var init_GraphCanvas = __esm({
38080
38112
  const dx = nodes[j].x - nodes[i].x;
38081
38113
  const dy = nodes[j].y - nodes[i].y;
38082
38114
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38083
- const force = 800 / (dist * dist);
38115
+ const force = repulsion / (dist * dist);
38084
38116
  const fx = dx / dist * force;
38085
38117
  const fy = dy / dist * force;
38086
38118
  nodes[i].fx -= fx;
@@ -38096,7 +38128,7 @@ var init_GraphCanvas = __esm({
38096
38128
  const dx = target.x - source.x;
38097
38129
  const dy = target.y - source.y;
38098
38130
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38099
- const force = (dist - 100) * 0.05;
38131
+ const force = (dist - linkDistance) * 0.05;
38100
38132
  const fx = dx / dist * force;
38101
38133
  const fy = dy / dist * force;
38102
38134
  source.fx += fx;
@@ -38130,7 +38162,7 @@ var init_GraphCanvas = __esm({
38130
38162
  return () => {
38131
38163
  cancelAnimationFrame(animRef.current);
38132
38164
  };
38133
- }, [propNodes, propEdges, layout]);
38165
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38134
38166
  React82.useEffect(() => {
38135
38167
  const canvas = canvasRef.current;
38136
38168
  if (!canvas) return;
@@ -38166,18 +38198,25 @@ var init_GraphCanvas = __esm({
38166
38198
  const size = node.size || 8;
38167
38199
  const color = node.color || getGroupColor(node.group, groups);
38168
38200
  const isHovered = hoveredNode === node.id;
38201
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38202
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38169
38203
  ctx.beginPath();
38170
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
38204
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38171
38205
  ctx.fillStyle = color;
38172
38206
  ctx.fill();
38173
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38174
- ctx.lineWidth = isHovered ? 2 : 1;
38207
+ if (isSelected) {
38208
+ ctx.strokeStyle = "var(--color-accent)";
38209
+ ctx.lineWidth = 3;
38210
+ } else {
38211
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38212
+ ctx.lineWidth = isHovered ? 2 : 1;
38213
+ }
38175
38214
  ctx.stroke();
38176
38215
  if (showLabels && node.label) {
38177
38216
  ctx.fillStyle = "#666666";
38178
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
38217
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
38179
38218
  ctx.textAlign = "center";
38180
- ctx.fillText(node.label, node.x, node.y + size + 12);
38219
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
38181
38220
  }
38182
38221
  }
38183
38222
  ctx.restore();
@@ -38188,6 +38227,97 @@ var init_GraphCanvas = __esm({
38188
38227
  setZoom(1);
38189
38228
  setOffset({ x: 0, y: 0 });
38190
38229
  }, []);
38230
+ const handleWheel = React82.useCallback(
38231
+ (e) => {
38232
+ if (!interactive) return;
38233
+ e.preventDefault();
38234
+ const coords = toCoords(e);
38235
+ if (!coords) return;
38236
+ const oldZoom = zoom;
38237
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38238
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38239
+ if (newZoom === oldZoom) return;
38240
+ setOffset((o) => ({
38241
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38242
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38243
+ }));
38244
+ setZoom(newZoom);
38245
+ },
38246
+ [interactive, toCoords, zoom]
38247
+ );
38248
+ const handleMouseDown = React82.useCallback(
38249
+ (e) => {
38250
+ const coords = toCoords(e);
38251
+ if (!coords) return;
38252
+ const node = nodeAt(coords.graphX, coords.graphY);
38253
+ const state = interactionRef.current;
38254
+ state.downPos = { x: e.clientX, y: e.clientY };
38255
+ state.startMouse = { x: e.clientX, y: e.clientY };
38256
+ state.startOffset = { ...offset };
38257
+ if (draggable && node) {
38258
+ state.mode = "dragging";
38259
+ state.dragNodeId = node.id;
38260
+ } else if (interactive) {
38261
+ state.mode = "panning";
38262
+ state.dragNodeId = null;
38263
+ } else {
38264
+ state.mode = "none";
38265
+ state.dragNodeId = null;
38266
+ }
38267
+ },
38268
+ [toCoords, nodeAt, draggable, interactive, offset]
38269
+ );
38270
+ const handleMouseMove = React82.useCallback(
38271
+ (e) => {
38272
+ const state = interactionRef.current;
38273
+ if (state.mode === "panning") {
38274
+ const dx = e.clientX - state.startMouse.x;
38275
+ const dy = e.clientY - state.startMouse.y;
38276
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
38277
+ return;
38278
+ }
38279
+ if (state.mode === "dragging" && state.dragNodeId) {
38280
+ const coords2 = toCoords(e);
38281
+ if (!coords2) return;
38282
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
38283
+ if (node2) {
38284
+ node2.x = coords2.graphX;
38285
+ node2.y = coords2.graphY;
38286
+ node2.vx = 0;
38287
+ node2.vy = 0;
38288
+ forceUpdate((n) => n + 1);
38289
+ }
38290
+ return;
38291
+ }
38292
+ const coords = toCoords(e);
38293
+ if (!coords) return;
38294
+ const node = nodeAt(coords.graphX, coords.graphY);
38295
+ setHoveredNode(node?.id ?? null);
38296
+ },
38297
+ [toCoords, nodeAt]
38298
+ );
38299
+ const handleMouseUp = React82.useCallback(
38300
+ (e) => {
38301
+ const state = interactionRef.current;
38302
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38303
+ state.mode = "none";
38304
+ state.dragNodeId = null;
38305
+ if (moved < 4) {
38306
+ const coords = toCoords(e);
38307
+ if (!coords) return;
38308
+ const node = nodeAt(coords.graphX, coords.graphY);
38309
+ if (node) {
38310
+ handleNodeClick(node);
38311
+ }
38312
+ }
38313
+ },
38314
+ [toCoords, nodeAt, handleNodeClick]
38315
+ );
38316
+ const handleMouseLeave = React82.useCallback(() => {
38317
+ interactionRef.current.mode = "none";
38318
+ interactionRef.current.dragNodeId = null;
38319
+ setHoveredNode(null);
38320
+ }, []);
38191
38321
  if (isLoading) {
38192
38322
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
38193
38323
  }
@@ -38249,20 +38379,11 @@ var init_GraphCanvas = __esm({
38249
38379
  height,
38250
38380
  className: "w-full cursor-grab active:cursor-grabbing",
38251
38381
  style: { height },
38252
- onClick: (e) => {
38253
- const canvas = canvasRef.current;
38254
- if (!canvas) return;
38255
- const rect = canvas.getBoundingClientRect();
38256
- const x = (e.clientX - rect.left - offset.x) / zoom;
38257
- const y = (e.clientY - rect.top - offset.y) / zoom;
38258
- const clickedNode = nodesRef.current.find((n) => {
38259
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
38260
- return dist < (n.size || 8) + 4;
38261
- });
38262
- if (clickedNode) {
38263
- handleNodeClick(clickedNode);
38264
- }
38265
- }
38382
+ onWheel: handleWheel,
38383
+ onMouseDown: handleMouseDown,
38384
+ onMouseMove: handleMouseMove,
38385
+ onMouseUp: handleMouseUp,
38386
+ onMouseLeave: handleMouseLeave
38266
38387
  }
38267
38388
  ) }),
38268
38389
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [