@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.
@@ -37953,6 +37953,9 @@ var init_GraphCanvas = __esm({
37953
37953
  actions,
37954
37954
  onNodeClick,
37955
37955
  nodeClickEvent,
37956
+ selectedNodeId,
37957
+ repulsion = 800,
37958
+ linkDistance = 100,
37956
37959
  layout = "force",
37957
37960
  entity,
37958
37961
  isLoading = false,
@@ -37968,6 +37971,35 @@ var init_GraphCanvas = __esm({
37968
37971
  const [hoveredNode, setHoveredNode] = useState(null);
37969
37972
  const nodesRef = useRef([]);
37970
37973
  const [, forceUpdate] = useState(0);
37974
+ const interactionRef = useRef({
37975
+ mode: "none",
37976
+ dragNodeId: null,
37977
+ startMouse: { x: 0, y: 0 },
37978
+ startOffset: { x: 0, y: 0 },
37979
+ downPos: { x: 0, y: 0 }
37980
+ });
37981
+ const toCoords = useCallback(
37982
+ (e) => {
37983
+ const canvas = canvasRef.current;
37984
+ if (!canvas) return null;
37985
+ const rect = canvas.getBoundingClientRect();
37986
+ const screenX = e.clientX - rect.left;
37987
+ const screenY = e.clientY - rect.top;
37988
+ return {
37989
+ screenX,
37990
+ screenY,
37991
+ graphX: (screenX - offset.x) / zoom,
37992
+ graphY: (screenY - offset.y) / zoom
37993
+ };
37994
+ },
37995
+ [offset, zoom]
37996
+ );
37997
+ const nodeAt = useCallback((graphX, graphY) => {
37998
+ return nodesRef.current.find((n) => {
37999
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
38000
+ return dist < (n.size || 8) + 4;
38001
+ });
38002
+ }, []);
37971
38003
  const handleAction = useCallback(
37972
38004
  (action) => {
37973
38005
  if (action.event) {
@@ -38033,7 +38065,7 @@ var init_GraphCanvas = __esm({
38033
38065
  const dx = nodes[j].x - nodes[i].x;
38034
38066
  const dy = nodes[j].y - nodes[i].y;
38035
38067
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38036
- const force = 800 / (dist * dist);
38068
+ const force = repulsion / (dist * dist);
38037
38069
  const fx = dx / dist * force;
38038
38070
  const fy = dy / dist * force;
38039
38071
  nodes[i].fx -= fx;
@@ -38049,7 +38081,7 @@ var init_GraphCanvas = __esm({
38049
38081
  const dx = target.x - source.x;
38050
38082
  const dy = target.y - source.y;
38051
38083
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38052
- const force = (dist - 100) * 0.05;
38084
+ const force = (dist - linkDistance) * 0.05;
38053
38085
  const fx = dx / dist * force;
38054
38086
  const fy = dy / dist * force;
38055
38087
  source.fx += fx;
@@ -38083,7 +38115,7 @@ var init_GraphCanvas = __esm({
38083
38115
  return () => {
38084
38116
  cancelAnimationFrame(animRef.current);
38085
38117
  };
38086
- }, [propNodes, propEdges, layout]);
38118
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38087
38119
  useEffect(() => {
38088
38120
  const canvas = canvasRef.current;
38089
38121
  if (!canvas) return;
@@ -38119,18 +38151,25 @@ var init_GraphCanvas = __esm({
38119
38151
  const size = node.size || 8;
38120
38152
  const color = node.color || getGroupColor(node.group, groups);
38121
38153
  const isHovered = hoveredNode === node.id;
38154
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38155
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38122
38156
  ctx.beginPath();
38123
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
38157
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38124
38158
  ctx.fillStyle = color;
38125
38159
  ctx.fill();
38126
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38127
- ctx.lineWidth = isHovered ? 2 : 1;
38160
+ if (isSelected) {
38161
+ ctx.strokeStyle = "var(--color-accent)";
38162
+ ctx.lineWidth = 3;
38163
+ } else {
38164
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38165
+ ctx.lineWidth = isHovered ? 2 : 1;
38166
+ }
38128
38167
  ctx.stroke();
38129
38168
  if (showLabels && node.label) {
38130
38169
  ctx.fillStyle = "#666666";
38131
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
38170
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
38132
38171
  ctx.textAlign = "center";
38133
- ctx.fillText(node.label, node.x, node.y + size + 12);
38172
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
38134
38173
  }
38135
38174
  }
38136
38175
  ctx.restore();
@@ -38141,6 +38180,97 @@ var init_GraphCanvas = __esm({
38141
38180
  setZoom(1);
38142
38181
  setOffset({ x: 0, y: 0 });
38143
38182
  }, []);
38183
+ const handleWheel = useCallback(
38184
+ (e) => {
38185
+ if (!interactive) return;
38186
+ e.preventDefault();
38187
+ const coords = toCoords(e);
38188
+ if (!coords) return;
38189
+ const oldZoom = zoom;
38190
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38191
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38192
+ if (newZoom === oldZoom) return;
38193
+ setOffset((o) => ({
38194
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38195
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38196
+ }));
38197
+ setZoom(newZoom);
38198
+ },
38199
+ [interactive, toCoords, zoom]
38200
+ );
38201
+ const handleMouseDown = useCallback(
38202
+ (e) => {
38203
+ const coords = toCoords(e);
38204
+ if (!coords) return;
38205
+ const node = nodeAt(coords.graphX, coords.graphY);
38206
+ const state = interactionRef.current;
38207
+ state.downPos = { x: e.clientX, y: e.clientY };
38208
+ state.startMouse = { x: e.clientX, y: e.clientY };
38209
+ state.startOffset = { ...offset };
38210
+ if (draggable && node) {
38211
+ state.mode = "dragging";
38212
+ state.dragNodeId = node.id;
38213
+ } else if (interactive) {
38214
+ state.mode = "panning";
38215
+ state.dragNodeId = null;
38216
+ } else {
38217
+ state.mode = "none";
38218
+ state.dragNodeId = null;
38219
+ }
38220
+ },
38221
+ [toCoords, nodeAt, draggable, interactive, offset]
38222
+ );
38223
+ const handleMouseMove = useCallback(
38224
+ (e) => {
38225
+ const state = interactionRef.current;
38226
+ if (state.mode === "panning") {
38227
+ const dx = e.clientX - state.startMouse.x;
38228
+ const dy = e.clientY - state.startMouse.y;
38229
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
38230
+ return;
38231
+ }
38232
+ if (state.mode === "dragging" && state.dragNodeId) {
38233
+ const coords2 = toCoords(e);
38234
+ if (!coords2) return;
38235
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
38236
+ if (node2) {
38237
+ node2.x = coords2.graphX;
38238
+ node2.y = coords2.graphY;
38239
+ node2.vx = 0;
38240
+ node2.vy = 0;
38241
+ forceUpdate((n) => n + 1);
38242
+ }
38243
+ return;
38244
+ }
38245
+ const coords = toCoords(e);
38246
+ if (!coords) return;
38247
+ const node = nodeAt(coords.graphX, coords.graphY);
38248
+ setHoveredNode(node?.id ?? null);
38249
+ },
38250
+ [toCoords, nodeAt]
38251
+ );
38252
+ const handleMouseUp = useCallback(
38253
+ (e) => {
38254
+ const state = interactionRef.current;
38255
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38256
+ state.mode = "none";
38257
+ state.dragNodeId = null;
38258
+ if (moved < 4) {
38259
+ const coords = toCoords(e);
38260
+ if (!coords) return;
38261
+ const node = nodeAt(coords.graphX, coords.graphY);
38262
+ if (node) {
38263
+ handleNodeClick(node);
38264
+ }
38265
+ }
38266
+ },
38267
+ [toCoords, nodeAt, handleNodeClick]
38268
+ );
38269
+ const handleMouseLeave = useCallback(() => {
38270
+ interactionRef.current.mode = "none";
38271
+ interactionRef.current.dragNodeId = null;
38272
+ setHoveredNode(null);
38273
+ }, []);
38144
38274
  if (isLoading) {
38145
38275
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
38146
38276
  }
@@ -38202,20 +38332,11 @@ var init_GraphCanvas = __esm({
38202
38332
  height,
38203
38333
  className: "w-full cursor-grab active:cursor-grabbing",
38204
38334
  style: { height },
38205
- onClick: (e) => {
38206
- const canvas = canvasRef.current;
38207
- if (!canvas) return;
38208
- const rect = canvas.getBoundingClientRect();
38209
- const x = (e.clientX - rect.left - offset.x) / zoom;
38210
- const y = (e.clientY - rect.top - offset.y) / zoom;
38211
- const clickedNode = nodesRef.current.find((n) => {
38212
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
38213
- return dist < (n.size || 8) + 4;
38214
- });
38215
- if (clickedNode) {
38216
- handleNodeClick(clickedNode);
38217
- }
38218
- }
38335
+ onWheel: handleWheel,
38336
+ onMouseDown: handleMouseDown,
38337
+ onMouseMove: handleMouseMove,
38338
+ onMouseUp: handleMouseUp,
38339
+ onMouseLeave: handleMouseLeave
38219
38340
  }
38220
38341
  ) }),
38221
38342
  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: [
@@ -37698,6 +37698,9 @@ var init_GraphCanvas = __esm({
37698
37698
  actions,
37699
37699
  onNodeClick,
37700
37700
  nodeClickEvent,
37701
+ selectedNodeId,
37702
+ repulsion = 800,
37703
+ linkDistance = 100,
37701
37704
  layout = "force",
37702
37705
  entity,
37703
37706
  isLoading = false,
@@ -37713,6 +37716,35 @@ var init_GraphCanvas = __esm({
37713
37716
  const [hoveredNode, setHoveredNode] = React81.useState(null);
37714
37717
  const nodesRef = React81.useRef([]);
37715
37718
  const [, forceUpdate] = React81.useState(0);
37719
+ const interactionRef = React81.useRef({
37720
+ mode: "none",
37721
+ dragNodeId: null,
37722
+ startMouse: { x: 0, y: 0 },
37723
+ startOffset: { x: 0, y: 0 },
37724
+ downPos: { x: 0, y: 0 }
37725
+ });
37726
+ const toCoords = React81.useCallback(
37727
+ (e) => {
37728
+ const canvas = canvasRef.current;
37729
+ if (!canvas) return null;
37730
+ const rect = canvas.getBoundingClientRect();
37731
+ const screenX = e.clientX - rect.left;
37732
+ const screenY = e.clientY - rect.top;
37733
+ return {
37734
+ screenX,
37735
+ screenY,
37736
+ graphX: (screenX - offset.x) / zoom,
37737
+ graphY: (screenY - offset.y) / zoom
37738
+ };
37739
+ },
37740
+ [offset, zoom]
37741
+ );
37742
+ const nodeAt = React81.useCallback((graphX, graphY) => {
37743
+ return nodesRef.current.find((n) => {
37744
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
37745
+ return dist < (n.size || 8) + 4;
37746
+ });
37747
+ }, []);
37716
37748
  const handleAction = React81.useCallback(
37717
37749
  (action) => {
37718
37750
  if (action.event) {
@@ -37778,7 +37810,7 @@ var init_GraphCanvas = __esm({
37778
37810
  const dx = nodes[j].x - nodes[i].x;
37779
37811
  const dy = nodes[j].y - nodes[i].y;
37780
37812
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37781
- const force = 800 / (dist * dist);
37813
+ const force = repulsion / (dist * dist);
37782
37814
  const fx = dx / dist * force;
37783
37815
  const fy = dy / dist * force;
37784
37816
  nodes[i].fx -= fx;
@@ -37794,7 +37826,7 @@ var init_GraphCanvas = __esm({
37794
37826
  const dx = target.x - source.x;
37795
37827
  const dy = target.y - source.y;
37796
37828
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37797
- const force = (dist - 100) * 0.05;
37829
+ const force = (dist - linkDistance) * 0.05;
37798
37830
  const fx = dx / dist * force;
37799
37831
  const fy = dy / dist * force;
37800
37832
  source.fx += fx;
@@ -37828,7 +37860,7 @@ var init_GraphCanvas = __esm({
37828
37860
  return () => {
37829
37861
  cancelAnimationFrame(animRef.current);
37830
37862
  };
37831
- }, [propNodes, propEdges, layout]);
37863
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
37832
37864
  React81.useEffect(() => {
37833
37865
  const canvas = canvasRef.current;
37834
37866
  if (!canvas) return;
@@ -37864,18 +37896,25 @@ var init_GraphCanvas = __esm({
37864
37896
  const size = node.size || 8;
37865
37897
  const color = node.color || getGroupColor(node.group, groups);
37866
37898
  const isHovered = hoveredNode === node.id;
37899
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
37900
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
37867
37901
  ctx.beginPath();
37868
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
37902
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
37869
37903
  ctx.fillStyle = color;
37870
37904
  ctx.fill();
37871
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37872
- ctx.lineWidth = isHovered ? 2 : 1;
37905
+ if (isSelected) {
37906
+ ctx.strokeStyle = "var(--color-accent)";
37907
+ ctx.lineWidth = 3;
37908
+ } else {
37909
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37910
+ ctx.lineWidth = isHovered ? 2 : 1;
37911
+ }
37873
37912
  ctx.stroke();
37874
37913
  if (showLabels && node.label) {
37875
37914
  ctx.fillStyle = "#666666";
37876
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
37915
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
37877
37916
  ctx.textAlign = "center";
37878
- ctx.fillText(node.label, node.x, node.y + size + 12);
37917
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
37879
37918
  }
37880
37919
  }
37881
37920
  ctx.restore();
@@ -37886,6 +37925,97 @@ var init_GraphCanvas = __esm({
37886
37925
  setZoom(1);
37887
37926
  setOffset({ x: 0, y: 0 });
37888
37927
  }, []);
37928
+ const handleWheel = React81.useCallback(
37929
+ (e) => {
37930
+ if (!interactive) return;
37931
+ e.preventDefault();
37932
+ const coords = toCoords(e);
37933
+ if (!coords) return;
37934
+ const oldZoom = zoom;
37935
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
37936
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
37937
+ if (newZoom === oldZoom) return;
37938
+ setOffset((o) => ({
37939
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
37940
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
37941
+ }));
37942
+ setZoom(newZoom);
37943
+ },
37944
+ [interactive, toCoords, zoom]
37945
+ );
37946
+ const handleMouseDown = React81.useCallback(
37947
+ (e) => {
37948
+ const coords = toCoords(e);
37949
+ if (!coords) return;
37950
+ const node = nodeAt(coords.graphX, coords.graphY);
37951
+ const state = interactionRef.current;
37952
+ state.downPos = { x: e.clientX, y: e.clientY };
37953
+ state.startMouse = { x: e.clientX, y: e.clientY };
37954
+ state.startOffset = { ...offset };
37955
+ if (draggable && node) {
37956
+ state.mode = "dragging";
37957
+ state.dragNodeId = node.id;
37958
+ } else if (interactive) {
37959
+ state.mode = "panning";
37960
+ state.dragNodeId = null;
37961
+ } else {
37962
+ state.mode = "none";
37963
+ state.dragNodeId = null;
37964
+ }
37965
+ },
37966
+ [toCoords, nodeAt, draggable, interactive, offset]
37967
+ );
37968
+ const handleMouseMove = React81.useCallback(
37969
+ (e) => {
37970
+ const state = interactionRef.current;
37971
+ if (state.mode === "panning") {
37972
+ const dx = e.clientX - state.startMouse.x;
37973
+ const dy = e.clientY - state.startMouse.y;
37974
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
37975
+ return;
37976
+ }
37977
+ if (state.mode === "dragging" && state.dragNodeId) {
37978
+ const coords2 = toCoords(e);
37979
+ if (!coords2) return;
37980
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
37981
+ if (node2) {
37982
+ node2.x = coords2.graphX;
37983
+ node2.y = coords2.graphY;
37984
+ node2.vx = 0;
37985
+ node2.vy = 0;
37986
+ forceUpdate((n) => n + 1);
37987
+ }
37988
+ return;
37989
+ }
37990
+ const coords = toCoords(e);
37991
+ if (!coords) return;
37992
+ const node = nodeAt(coords.graphX, coords.graphY);
37993
+ setHoveredNode(node?.id ?? null);
37994
+ },
37995
+ [toCoords, nodeAt]
37996
+ );
37997
+ const handleMouseUp = React81.useCallback(
37998
+ (e) => {
37999
+ const state = interactionRef.current;
38000
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38001
+ state.mode = "none";
38002
+ state.dragNodeId = null;
38003
+ if (moved < 4) {
38004
+ const coords = toCoords(e);
38005
+ if (!coords) return;
38006
+ const node = nodeAt(coords.graphX, coords.graphY);
38007
+ if (node) {
38008
+ handleNodeClick(node);
38009
+ }
38010
+ }
38011
+ },
38012
+ [toCoords, nodeAt, handleNodeClick]
38013
+ );
38014
+ const handleMouseLeave = React81.useCallback(() => {
38015
+ interactionRef.current.mode = "none";
38016
+ interactionRef.current.dragNodeId = null;
38017
+ setHoveredNode(null);
38018
+ }, []);
37889
38019
  if (isLoading) {
37890
38020
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
37891
38021
  }
@@ -37947,20 +38077,11 @@ var init_GraphCanvas = __esm({
37947
38077
  height,
37948
38078
  className: "w-full cursor-grab active:cursor-grabbing",
37949
38079
  style: { height },
37950
- onClick: (e) => {
37951
- const canvas = canvasRef.current;
37952
- if (!canvas) return;
37953
- const rect = canvas.getBoundingClientRect();
37954
- const x = (e.clientX - rect.left - offset.x) / zoom;
37955
- const y = (e.clientY - rect.top - offset.y) / zoom;
37956
- const clickedNode = nodesRef.current.find((n) => {
37957
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
37958
- return dist < (n.size || 8) + 4;
37959
- });
37960
- if (clickedNode) {
37961
- handleNodeClick(clickedNode);
37962
- }
37963
- }
38080
+ onWheel: handleWheel,
38081
+ onMouseDown: handleMouseDown,
38082
+ onMouseMove: handleMouseMove,
38083
+ onMouseUp: handleMouseUp,
38084
+ onMouseLeave: handleMouseLeave
37964
38085
  }
37965
38086
  ) }),
37966
38087
  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: [
@@ -37651,6 +37651,9 @@ var init_GraphCanvas = __esm({
37651
37651
  actions,
37652
37652
  onNodeClick,
37653
37653
  nodeClickEvent,
37654
+ selectedNodeId,
37655
+ repulsion = 800,
37656
+ linkDistance = 100,
37654
37657
  layout = "force",
37655
37658
  entity,
37656
37659
  isLoading = false,
@@ -37666,6 +37669,35 @@ var init_GraphCanvas = __esm({
37666
37669
  const [hoveredNode, setHoveredNode] = useState(null);
37667
37670
  const nodesRef = useRef([]);
37668
37671
  const [, forceUpdate] = useState(0);
37672
+ const interactionRef = useRef({
37673
+ mode: "none",
37674
+ dragNodeId: null,
37675
+ startMouse: { x: 0, y: 0 },
37676
+ startOffset: { x: 0, y: 0 },
37677
+ downPos: { x: 0, y: 0 }
37678
+ });
37679
+ const toCoords = useCallback(
37680
+ (e) => {
37681
+ const canvas = canvasRef.current;
37682
+ if (!canvas) return null;
37683
+ const rect = canvas.getBoundingClientRect();
37684
+ const screenX = e.clientX - rect.left;
37685
+ const screenY = e.clientY - rect.top;
37686
+ return {
37687
+ screenX,
37688
+ screenY,
37689
+ graphX: (screenX - offset.x) / zoom,
37690
+ graphY: (screenY - offset.y) / zoom
37691
+ };
37692
+ },
37693
+ [offset, zoom]
37694
+ );
37695
+ const nodeAt = useCallback((graphX, graphY) => {
37696
+ return nodesRef.current.find((n) => {
37697
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
37698
+ return dist < (n.size || 8) + 4;
37699
+ });
37700
+ }, []);
37669
37701
  const handleAction = useCallback(
37670
37702
  (action) => {
37671
37703
  if (action.event) {
@@ -37731,7 +37763,7 @@ var init_GraphCanvas = __esm({
37731
37763
  const dx = nodes[j].x - nodes[i].x;
37732
37764
  const dy = nodes[j].y - nodes[i].y;
37733
37765
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37734
- const force = 800 / (dist * dist);
37766
+ const force = repulsion / (dist * dist);
37735
37767
  const fx = dx / dist * force;
37736
37768
  const fy = dy / dist * force;
37737
37769
  nodes[i].fx -= fx;
@@ -37747,7 +37779,7 @@ var init_GraphCanvas = __esm({
37747
37779
  const dx = target.x - source.x;
37748
37780
  const dy = target.y - source.y;
37749
37781
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37750
- const force = (dist - 100) * 0.05;
37782
+ const force = (dist - linkDistance) * 0.05;
37751
37783
  const fx = dx / dist * force;
37752
37784
  const fy = dy / dist * force;
37753
37785
  source.fx += fx;
@@ -37781,7 +37813,7 @@ var init_GraphCanvas = __esm({
37781
37813
  return () => {
37782
37814
  cancelAnimationFrame(animRef.current);
37783
37815
  };
37784
- }, [propNodes, propEdges, layout]);
37816
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
37785
37817
  useEffect(() => {
37786
37818
  const canvas = canvasRef.current;
37787
37819
  if (!canvas) return;
@@ -37817,18 +37849,25 @@ var init_GraphCanvas = __esm({
37817
37849
  const size = node.size || 8;
37818
37850
  const color = node.color || getGroupColor(node.group, groups);
37819
37851
  const isHovered = hoveredNode === node.id;
37852
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
37853
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
37820
37854
  ctx.beginPath();
37821
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
37855
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
37822
37856
  ctx.fillStyle = color;
37823
37857
  ctx.fill();
37824
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37825
- ctx.lineWidth = isHovered ? 2 : 1;
37858
+ if (isSelected) {
37859
+ ctx.strokeStyle = "var(--color-accent)";
37860
+ ctx.lineWidth = 3;
37861
+ } else {
37862
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37863
+ ctx.lineWidth = isHovered ? 2 : 1;
37864
+ }
37826
37865
  ctx.stroke();
37827
37866
  if (showLabels && node.label) {
37828
37867
  ctx.fillStyle = "#666666";
37829
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
37868
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
37830
37869
  ctx.textAlign = "center";
37831
- ctx.fillText(node.label, node.x, node.y + size + 12);
37870
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
37832
37871
  }
37833
37872
  }
37834
37873
  ctx.restore();
@@ -37839,6 +37878,97 @@ var init_GraphCanvas = __esm({
37839
37878
  setZoom(1);
37840
37879
  setOffset({ x: 0, y: 0 });
37841
37880
  }, []);
37881
+ const handleWheel = useCallback(
37882
+ (e) => {
37883
+ if (!interactive) return;
37884
+ e.preventDefault();
37885
+ const coords = toCoords(e);
37886
+ if (!coords) return;
37887
+ const oldZoom = zoom;
37888
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
37889
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
37890
+ if (newZoom === oldZoom) return;
37891
+ setOffset((o) => ({
37892
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
37893
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
37894
+ }));
37895
+ setZoom(newZoom);
37896
+ },
37897
+ [interactive, toCoords, zoom]
37898
+ );
37899
+ const handleMouseDown = useCallback(
37900
+ (e) => {
37901
+ const coords = toCoords(e);
37902
+ if (!coords) return;
37903
+ const node = nodeAt(coords.graphX, coords.graphY);
37904
+ const state = interactionRef.current;
37905
+ state.downPos = { x: e.clientX, y: e.clientY };
37906
+ state.startMouse = { x: e.clientX, y: e.clientY };
37907
+ state.startOffset = { ...offset };
37908
+ if (draggable && node) {
37909
+ state.mode = "dragging";
37910
+ state.dragNodeId = node.id;
37911
+ } else if (interactive) {
37912
+ state.mode = "panning";
37913
+ state.dragNodeId = null;
37914
+ } else {
37915
+ state.mode = "none";
37916
+ state.dragNodeId = null;
37917
+ }
37918
+ },
37919
+ [toCoords, nodeAt, draggable, interactive, offset]
37920
+ );
37921
+ const handleMouseMove = useCallback(
37922
+ (e) => {
37923
+ const state = interactionRef.current;
37924
+ if (state.mode === "panning") {
37925
+ const dx = e.clientX - state.startMouse.x;
37926
+ const dy = e.clientY - state.startMouse.y;
37927
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
37928
+ return;
37929
+ }
37930
+ if (state.mode === "dragging" && state.dragNodeId) {
37931
+ const coords2 = toCoords(e);
37932
+ if (!coords2) return;
37933
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
37934
+ if (node2) {
37935
+ node2.x = coords2.graphX;
37936
+ node2.y = coords2.graphY;
37937
+ node2.vx = 0;
37938
+ node2.vy = 0;
37939
+ forceUpdate((n) => n + 1);
37940
+ }
37941
+ return;
37942
+ }
37943
+ const coords = toCoords(e);
37944
+ if (!coords) return;
37945
+ const node = nodeAt(coords.graphX, coords.graphY);
37946
+ setHoveredNode(node?.id ?? null);
37947
+ },
37948
+ [toCoords, nodeAt]
37949
+ );
37950
+ const handleMouseUp = useCallback(
37951
+ (e) => {
37952
+ const state = interactionRef.current;
37953
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
37954
+ state.mode = "none";
37955
+ state.dragNodeId = null;
37956
+ if (moved < 4) {
37957
+ const coords = toCoords(e);
37958
+ if (!coords) return;
37959
+ const node = nodeAt(coords.graphX, coords.graphY);
37960
+ if (node) {
37961
+ handleNodeClick(node);
37962
+ }
37963
+ }
37964
+ },
37965
+ [toCoords, nodeAt, handleNodeClick]
37966
+ );
37967
+ const handleMouseLeave = useCallback(() => {
37968
+ interactionRef.current.mode = "none";
37969
+ interactionRef.current.dragNodeId = null;
37970
+ setHoveredNode(null);
37971
+ }, []);
37842
37972
  if (isLoading) {
37843
37973
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37844
37974
  }
@@ -37900,20 +38030,11 @@ var init_GraphCanvas = __esm({
37900
38030
  height,
37901
38031
  className: "w-full cursor-grab active:cursor-grabbing",
37902
38032
  style: { height },
37903
- onClick: (e) => {
37904
- const canvas = canvasRef.current;
37905
- if (!canvas) return;
37906
- const rect = canvas.getBoundingClientRect();
37907
- const x = (e.clientX - rect.left - offset.x) / zoom;
37908
- const y = (e.clientY - rect.top - offset.y) / zoom;
37909
- const clickedNode = nodesRef.current.find((n) => {
37910
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
37911
- return dist < (n.size || 8) + 4;
37912
- });
37913
- if (clickedNode) {
37914
- handleNodeClick(clickedNode);
37915
- }
37916
- }
38033
+ onWheel: handleWheel,
38034
+ onMouseDown: handleMouseDown,
38035
+ onMouseMove: handleMouseMove,
38036
+ onMouseUp: handleMouseUp,
38037
+ onMouseLeave: handleMouseLeave
37917
38038
  }
37918
38039
  ) }),
37919
38040
  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: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.58.0",
3
+ "version": "5.59.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [