@aiready/components 0.13.18 → 0.13.20

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.
@@ -1,10 +1,288 @@
1
- import React, { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle } from 'react';
2
- import * as d32 from 'd3';
1
+ import { forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo } from 'react';
2
+ import * as d33 from 'd3';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
6
 
7
- // src/charts/ForceDirectedGraph.tsx
7
+ // src/charts/force-directed/useGraphInteractions.ts
8
+ function pinNode(node) {
9
+ node.fx = node.x;
10
+ node.fy = node.y;
11
+ }
12
+ function unpinNode(node) {
13
+ node.fx = null;
14
+ node.fy = null;
15
+ }
16
+ function unpinAllNodes(nodes) {
17
+ nodes.forEach(unpinNode);
18
+ }
19
+ function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
20
+ useEffect(() => {
21
+ if (!enableZoom || !svgRef.current || !gRef.current) return;
22
+ const svg = d33.select(svgRef.current);
23
+ const g = d33.select(gRef.current);
24
+ const zoom3 = d33.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
25
+ g.attr("transform", event.transform);
26
+ transformRef.current = event.transform;
27
+ setTransform(event.transform);
28
+ });
29
+ svg.call(zoom3);
30
+ return () => {
31
+ svg.on(".zoom", null);
32
+ };
33
+ }, [enableZoom, svgRef, gRef, setTransform, transformRef]);
34
+ }
35
+ function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
36
+ useEffect(() => {
37
+ if (!enableDrag) return;
38
+ const handleWindowMove = (event) => {
39
+ if (!dragActiveRef.current || !dragNodeRef.current) return;
40
+ const svg = svgRef.current;
41
+ if (!svg) return;
42
+ const rect = svg.getBoundingClientRect();
43
+ const t = transformRef.current;
44
+ const x = (event.clientX - rect.left - t.x) / t.k;
45
+ const y = (event.clientY - rect.top - t.y) / t.k;
46
+ dragNodeRef.current.fx = x;
47
+ dragNodeRef.current.fy = y;
48
+ };
49
+ const handleWindowUp = () => {
50
+ if (!dragActiveRef.current) return;
51
+ onDragEnd();
52
+ dragNodeRef.current = null;
53
+ dragActiveRef.current = false;
54
+ };
55
+ const handleWindowLeave = (event) => {
56
+ if (event.relatedTarget === null) handleWindowUp();
57
+ };
58
+ window.addEventListener("mousemove", handleWindowMove);
59
+ window.addEventListener("mouseup", handleWindowUp);
60
+ window.addEventListener("mouseout", handleWindowLeave);
61
+ window.addEventListener("blur", handleWindowUp);
62
+ return () => {
63
+ window.removeEventListener("mousemove", handleWindowMove);
64
+ window.removeEventListener("mouseup", handleWindowUp);
65
+ window.removeEventListener("mouseout", handleWindowLeave);
66
+ window.removeEventListener("blur", handleWindowUp);
67
+ };
68
+ }, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
69
+ }
70
+ function useNodeInteractions(enableDrag, _nodes, _pinnedNodes, setPinnedNodes, restart, stop) {
71
+ const handleDragStart = useCallback(
72
+ (event, node) => {
73
+ if (!enableDrag) return;
74
+ event.preventDefault();
75
+ event.stopPropagation();
76
+ pinNode(node);
77
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
78
+ stop();
79
+ },
80
+ [enableDrag, stop, setPinnedNodes]
81
+ );
82
+ const handleNodeDoubleClick = useCallback(
83
+ (event, node) => {
84
+ event.stopPropagation();
85
+ if (!enableDrag) return;
86
+ if (node.fx === null || node.fx === void 0) {
87
+ pinNode(node);
88
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
89
+ } else {
90
+ unpinNode(node);
91
+ setPinnedNodes((prev) => {
92
+ const next = new Set(prev);
93
+ next.delete(node.id);
94
+ return next;
95
+ });
96
+ }
97
+ restart();
98
+ },
99
+ [enableDrag, restart, setPinnedNodes]
100
+ );
101
+ return { handleDragStart, handleNodeDoubleClick };
102
+ }
103
+
104
+ // src/charts/constants.ts
105
+ var DEFAULT_NODE_COLOR = "#64748b";
106
+ var DEFAULT_NODE_SIZE = 10;
107
+ var DEFAULT_LINK_COLOR = "#94a3b8";
108
+ var DEFAULT_LINK_WIDTH = 1;
109
+ var CIRCULAR_LAYOUT_RADIUS_RATIO = 0.35;
110
+ var FIT_VIEW_PADDING = 40;
111
+ var TRANSITION_DURATION_MS = 300;
112
+ var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
113
+ var PACKAGE_BOUNDARY_STROKE = "#475569";
114
+ var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
115
+ var PACKAGE_BOUNDARY_DASH = "6 6";
116
+ var PACKAGE_LABEL_FONT_SIZE = 11;
117
+ var PACKAGE_LABEL_COLOR = "#475569";
118
+
119
+ // src/charts/layout-utils.ts
120
+ function applyCircularLayout(nodes, width, height) {
121
+ const centerX = width / 2;
122
+ const centerY = height / 2;
123
+ const radius = Math.min(width, height) * CIRCULAR_LAYOUT_RADIUS_RATIO;
124
+ nodes.forEach((node, i) => {
125
+ const angle = 2 * Math.PI * i / nodes.length;
126
+ node.fx = centerX + Math.cos(angle) * radius;
127
+ node.fy = centerY + Math.sin(angle) * radius;
128
+ node.x = node.fx;
129
+ node.y = node.fy;
130
+ });
131
+ }
132
+ function applyHierarchicalLayout(nodes, width, height) {
133
+ const groups = /* @__PURE__ */ new Map();
134
+ nodes.forEach((n) => {
135
+ const key = n.packageGroup || n.group || "root";
136
+ if (!groups.has(key)) groups.set(key, []);
137
+ groups.get(key).push(n);
138
+ });
139
+ const groupArray = Array.from(groups.entries());
140
+ const cols = Math.ceil(Math.sqrt(groupArray.length));
141
+ const groupSpacingX = width * 0.8 / cols;
142
+ const groupSpacingY = height * 0.8 / Math.ceil(groupArray.length / cols);
143
+ groupArray.forEach(([groupKey, groupNodes], gi) => {
144
+ const col = gi % cols;
145
+ const row = Math.floor(gi / cols);
146
+ const groupX = (col + 0.5) * groupSpacingX;
147
+ const groupY = (row + 0.5) * groupSpacingY;
148
+ if (groupKey.startsWith("pkg:") || groupKey === groupKey) {
149
+ groupNodes.forEach((n, ni) => {
150
+ const angle = 2 * Math.PI * ni / groupNodes.length;
151
+ const r = Math.min(80, 20 + groupNodes.length * 8);
152
+ n.fx = groupX + Math.cos(angle) * r;
153
+ n.fy = groupY + Math.sin(angle) * r;
154
+ n.x = n.fx;
155
+ n.y = n.fy;
156
+ });
157
+ }
158
+ });
159
+ }
160
+ function applyInitialForceLayout(nodes, width, height) {
161
+ nodes.forEach((node) => {
162
+ if (node.fx === void 0 || node.fx === null) {
163
+ node.x = Math.random() * width;
164
+ node.y = Math.random() * height;
165
+ }
166
+ });
167
+ }
168
+
169
+ // src/charts/force-directed/useGraphLayout.ts
170
+ function useGraphLayout(initialNodes, width, height, layout, restart) {
171
+ const nodes = useMemo(() => {
172
+ if (!initialNodes || !initialNodes.length) return initialNodes;
173
+ const copy = initialNodes.map((n) => ({ ...n }));
174
+ if (layout === "circular") applyCircularLayout(copy, width, height);
175
+ else if (layout === "hierarchical")
176
+ applyHierarchicalLayout(copy, width, height);
177
+ else applyInitialForceLayout(copy, width, height);
178
+ return copy;
179
+ }, [initialNodes, width, height, layout]);
180
+ useEffect(() => {
181
+ if (!nodes || nodes.length === 0) return;
182
+ if (layout === "circular") applyCircularLayout(nodes, width, height);
183
+ else if (layout === "hierarchical")
184
+ applyHierarchicalLayout(nodes, width, height);
185
+ restart();
186
+ }, [layout, nodes, width, height, restart]);
187
+ return { nodes };
188
+ }
189
+ function useSimulationControls() {
190
+ const restart = useCallback(() => {
191
+ }, []);
192
+ const stop = useCallback(() => {
193
+ }, []);
194
+ const setForcesEnabled = useCallback((enabled) => {
195
+ }, []);
196
+ return { restart, stop, setForcesEnabled };
197
+ }
198
+ function useImperativeHandleMethods({
199
+ nodes,
200
+ pinnedNodes,
201
+ setPinnedNodes,
202
+ restart,
203
+ width,
204
+ height,
205
+ layout,
206
+ handleLayoutChange,
207
+ svgRef,
208
+ gRef,
209
+ setTransform,
210
+ internalDragEnabledRef
211
+ }) {
212
+ const pinAll = useCallback(() => {
213
+ const newPinned = /* @__PURE__ */ new Set();
214
+ nodes.forEach((node) => {
215
+ pinNode(node);
216
+ newPinned.add(node.id);
217
+ });
218
+ setPinnedNodes(newPinned);
219
+ restart();
220
+ }, [nodes, setPinnedNodes, restart]);
221
+ const unpinAll = useCallback(() => {
222
+ unpinAllNodes(nodes);
223
+ setPinnedNodes(/* @__PURE__ */ new Set());
224
+ restart();
225
+ }, [nodes, setPinnedNodes, restart]);
226
+ const resetLayout = useCallback(() => {
227
+ unpinAllNodes(nodes);
228
+ setPinnedNodes(/* @__PURE__ */ new Set());
229
+ restart();
230
+ }, [nodes, setPinnedNodes, restart]);
231
+ const fitView = useCallback(() => {
232
+ if (!svgRef.current || !nodes.length) return;
233
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
234
+ nodes.forEach((node) => {
235
+ if (node.x !== void 0 && node.y !== void 0) {
236
+ const size = node.size || DEFAULT_NODE_SIZE;
237
+ minX = Math.min(minX, node.x - size);
238
+ maxX = Math.max(maxX, node.x + size);
239
+ minY = Math.min(minY, node.y - size);
240
+ maxY = Math.max(maxY, node.y + size);
241
+ }
242
+ });
243
+ if (!isFinite(minX)) return;
244
+ const scale = Math.min(
245
+ (width - FIT_VIEW_PADDING * 2) / (maxX - minX),
246
+ (height - FIT_VIEW_PADDING * 2) / (maxY - minY),
247
+ 10
248
+ );
249
+ const x = width / 2 - (minX + maxX) / 2 * scale;
250
+ const y = height / 2 - (minY + maxY) / 2 * scale;
251
+ if (gRef.current && svgRef.current) {
252
+ const svg = d33.select(svgRef.current);
253
+ const newTransform = d33.zoomIdentity.translate(x, y).scale(scale);
254
+ svg.transition().duration(TRANSITION_DURATION_MS).call(d33.zoom().transform, newTransform);
255
+ setTransform(newTransform);
256
+ }
257
+ }, [nodes, width, height, svgRef, gRef, setTransform]);
258
+ const getPinnedNodes = useCallback(
259
+ () => Array.from(pinnedNodes),
260
+ [pinnedNodes]
261
+ );
262
+ const setDragMode = useCallback(
263
+ (enabled) => {
264
+ internalDragEnabledRef.current = enabled;
265
+ },
266
+ [internalDragEnabledRef]
267
+ );
268
+ const setLayoutMethod = useCallback(
269
+ (newLayout) => {
270
+ handleLayoutChange(newLayout);
271
+ },
272
+ [handleLayoutChange]
273
+ );
274
+ const getLayout = useCallback(() => layout, [layout]);
275
+ return {
276
+ pinAll,
277
+ unpinAll,
278
+ resetLayout,
279
+ fitView,
280
+ getPinnedNodes,
281
+ setDragMode,
282
+ setLayout: setLayoutMethod,
283
+ getLayout
284
+ };
285
+ }
8
286
  function cn(...inputs) {
9
287
  return twMerge(clsx(inputs));
10
288
  }
@@ -137,21 +415,6 @@ var LinkItem = ({
137
415
  ] });
138
416
  };
139
417
  var LinkItem_default = LinkItem;
140
-
141
- // src/charts/constants.ts
142
- var DEFAULT_NODE_COLOR = "#64748b";
143
- var DEFAULT_NODE_SIZE = 10;
144
- var DEFAULT_LINK_COLOR = "#94a3b8";
145
- var DEFAULT_LINK_WIDTH = 1;
146
- var CIRCULAR_LAYOUT_RADIUS_RATIO = 0.35;
147
- var FIT_VIEW_PADDING = 40;
148
- var TRANSITION_DURATION_MS = 300;
149
- var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
150
- var PACKAGE_BOUNDARY_STROKE = "#475569";
151
- var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
152
- var PACKAGE_BOUNDARY_DASH = "6 6";
153
- var PACKAGE_LABEL_FONT_SIZE = 11;
154
- var PACKAGE_LABEL_COLOR = "#475569";
155
418
  var PackageBoundaries = ({
156
419
  packageBounds
157
420
  }) => {
@@ -185,118 +448,94 @@ var PackageBoundaries = ({
185
448
  ] }, pid)) });
186
449
  };
187
450
  PackageBoundaries.displayName = "PackageBoundaries";
188
-
189
- // src/charts/layout-utils.ts
190
- function applyCircularLayout(nodes, width, height) {
191
- const centerX = width / 2;
192
- const centerY = height / 2;
193
- const radius = Math.min(width, height) * CIRCULAR_LAYOUT_RADIUS_RATIO;
194
- nodes.forEach((node, i) => {
195
- const angle = 2 * Math.PI * i / nodes.length;
196
- node.fx = centerX + Math.cos(angle) * radius;
197
- node.fy = centerY + Math.sin(angle) * radius;
198
- node.x = node.fx;
199
- node.y = node.fy;
200
- });
201
- }
202
- function applyHierarchicalLayout(nodes, width, height) {
203
- const groups = /* @__PURE__ */ new Map();
204
- nodes.forEach((n) => {
205
- const key = n.packageGroup || n.group || "root";
206
- if (!groups.has(key)) groups.set(key, []);
207
- groups.get(key).push(n);
208
- });
209
- const groupArray = Array.from(groups.entries());
210
- const cols = Math.ceil(Math.sqrt(groupArray.length));
211
- const groupSpacingX = width * 0.8 / cols;
212
- const groupSpacingY = height * 0.8 / Math.ceil(groupArray.length / cols);
213
- groupArray.forEach(([groupKey, groupNodes], gi) => {
214
- const col = gi % cols;
215
- const row = Math.floor(gi / cols);
216
- const groupX = (col + 0.5) * groupSpacingX;
217
- const groupY = (row + 0.5) * groupSpacingY;
218
- if (groupKey.startsWith("pkg:") || groupKey === groupKey) {
219
- groupNodes.forEach((n, ni) => {
220
- const angle = 2 * Math.PI * ni / groupNodes.length;
221
- const r = Math.min(80, 20 + groupNodes.length * 8);
222
- n.fx = groupX + Math.cos(angle) * r;
223
- n.fy = groupY + Math.sin(angle) * r;
224
- n.x = n.fx;
225
- n.y = n.fy;
226
- });
227
- }
228
- });
229
- }
230
- function applyInitialForceLayout(nodes, width, height) {
231
- nodes.forEach((node) => {
232
- if (node.fx === void 0 || node.fx === null) {
233
- node.x = Math.random() * width;
234
- node.y = Math.random() * height;
451
+ var GraphCanvas = ({
452
+ svgRef,
453
+ gRef,
454
+ width,
455
+ height,
456
+ className,
457
+ nodes,
458
+ links,
459
+ pinnedNodes,
460
+ selectedNodeId,
461
+ hoveredNodeId,
462
+ defaultNodeColor = DEFAULT_NODE_COLOR,
463
+ defaultNodeSize = DEFAULT_NODE_SIZE,
464
+ defaultLinkColor = DEFAULT_LINK_COLOR,
465
+ defaultLinkWidth = DEFAULT_LINK_WIDTH,
466
+ showNodeLabels = true,
467
+ showLinkLabels = false,
468
+ onNodeClick,
469
+ onNodeHover,
470
+ onLinkClick,
471
+ packageBounds,
472
+ handleNodeDoubleClick,
473
+ handleDragStart,
474
+ restart,
475
+ setPinnedNodes
476
+ }) => {
477
+ return /* @__PURE__ */ jsxs(
478
+ "svg",
479
+ {
480
+ ref: svgRef,
481
+ width,
482
+ height,
483
+ className: cn("bg-white dark:bg-gray-900", className),
484
+ onDoubleClick: () => {
485
+ unpinAllNodes(nodes);
486
+ setPinnedNodes(/* @__PURE__ */ new Set());
487
+ restart();
488
+ },
489
+ children: [
490
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
491
+ "marker",
492
+ {
493
+ id: "arrow",
494
+ viewBox: "0 0 10 10",
495
+ refX: "20",
496
+ refY: "5",
497
+ markerWidth: "6",
498
+ markerHeight: "6",
499
+ orient: "auto",
500
+ children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: defaultLinkColor })
501
+ }
502
+ ) }),
503
+ /* @__PURE__ */ jsxs("g", { ref: gRef, children: [
504
+ links.map((link, i) => /* @__PURE__ */ jsx(
505
+ LinkItem_default,
506
+ {
507
+ link,
508
+ onClick: onLinkClick,
509
+ defaultWidth: defaultLinkWidth,
510
+ showLabel: showLinkLabels,
511
+ nodes
512
+ },
513
+ `link-${i}`
514
+ )),
515
+ nodes.map((node) => /* @__PURE__ */ jsx(
516
+ NodeItem_default,
517
+ {
518
+ node,
519
+ isSelected: selectedNodeId === node.id,
520
+ isHovered: hoveredNodeId === node.id,
521
+ pinned: pinnedNodes.has(node.id),
522
+ defaultNodeSize,
523
+ defaultNodeColor,
524
+ showLabel: showNodeLabels,
525
+ onClick: onNodeClick,
526
+ onDoubleClick: handleNodeDoubleClick,
527
+ onMouseEnter: (n) => onNodeHover?.(n),
528
+ onMouseLeave: () => onNodeHover?.(null),
529
+ onMouseDown: handleDragStart
530
+ },
531
+ node.id
532
+ )),
533
+ /* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
534
+ ] })
535
+ ]
235
536
  }
236
- });
237
- }
238
- function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
239
- useEffect(() => {
240
- if (!enableZoom || !svgRef.current || !gRef.current) return;
241
- const svg = d32.select(svgRef.current);
242
- const g = d32.select(gRef.current);
243
- const zoom3 = d32.zoom().scaleExtent([0.1, 10]).on("zoom", (event) => {
244
- g.attr("transform", event.transform);
245
- transformRef.current = event.transform;
246
- setTransform(event.transform);
247
- });
248
- svg.call(zoom3);
249
- return () => {
250
- svg.on(".zoom", null);
251
- };
252
- }, [enableZoom, svgRef, gRef, setTransform, transformRef]);
253
- }
254
- function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
255
- useEffect(() => {
256
- if (!enableDrag) return;
257
- const handleWindowMove = (event) => {
258
- if (!dragActiveRef.current || !dragNodeRef.current) return;
259
- const svg = svgRef.current;
260
- if (!svg) return;
261
- const rect = svg.getBoundingClientRect();
262
- const t = transformRef.current;
263
- const x = (event.clientX - rect.left - t.x) / t.k;
264
- const y = (event.clientY - rect.top - t.y) / t.k;
265
- dragNodeRef.current.fx = x;
266
- dragNodeRef.current.fy = y;
267
- };
268
- const handleWindowUp = () => {
269
- if (!dragActiveRef.current) return;
270
- onDragEnd();
271
- dragNodeRef.current = null;
272
- dragActiveRef.current = false;
273
- };
274
- const handleWindowLeave = (event) => {
275
- if (event.relatedTarget === null) handleWindowUp();
276
- };
277
- window.addEventListener("mousemove", handleWindowMove);
278
- window.addEventListener("mouseup", handleWindowUp);
279
- window.addEventListener("mouseout", handleWindowLeave);
280
- window.addEventListener("blur", handleWindowUp);
281
- return () => {
282
- window.removeEventListener("mousemove", handleWindowMove);
283
- window.removeEventListener("mouseup", handleWindowUp);
284
- window.removeEventListener("mouseout", handleWindowLeave);
285
- window.removeEventListener("blur", handleWindowUp);
286
- };
287
- }, [enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd]);
288
- }
289
- function pinNode(node) {
290
- node.fx = node.x;
291
- node.fy = node.y;
292
- }
293
- function unpinNode(node) {
294
- node.fx = null;
295
- node.fy = null;
296
- }
297
- function unpinAllNodes(nodes) {
298
- nodes.forEach(unpinNode);
299
- }
537
+ );
538
+ };
300
539
  var ForceDirectedGraph = forwardRef(
301
540
  ({
302
541
  nodes: initialNodes,
@@ -310,12 +549,12 @@ var ForceDirectedGraph = forwardRef(
310
549
  onLinkClick,
311
550
  selectedNodeId,
312
551
  hoveredNodeId,
313
- defaultNodeColor = DEFAULT_NODE_COLOR,
314
- defaultNodeSize = DEFAULT_NODE_SIZE,
315
- defaultLinkColor = DEFAULT_LINK_COLOR,
316
- defaultLinkWidth = DEFAULT_LINK_WIDTH,
317
- showNodeLabels = true,
318
- showLinkLabels = false,
552
+ defaultNodeColor,
553
+ defaultNodeSize,
554
+ defaultLinkColor,
555
+ defaultLinkWidth,
556
+ showNodeLabels,
557
+ showLinkLabels,
319
558
  className,
320
559
  manualLayout = false,
321
560
  onManualLayoutChange,
@@ -333,9 +572,8 @@ var ForceDirectedGraph = forwardRef(
333
572
  const internalDragEnabledRef = useRef(enableDrag);
334
573
  const [layout, setLayout] = useState(externalLayout || "force");
335
574
  useEffect(() => {
336
- if (externalLayout && externalLayout !== layout) {
575
+ if (externalLayout && externalLayout !== layout)
337
576
  setLayout(externalLayout);
338
- }
339
577
  }, [externalLayout, layout]);
340
578
  const handleLayoutChange = useCallback(
341
579
  (newLayout) => {
@@ -347,87 +585,32 @@ var ForceDirectedGraph = forwardRef(
347
585
  useEffect(() => {
348
586
  internalDragEnabledRef.current = enableDrag;
349
587
  }, [enableDrag]);
350
- const nodes = React.useMemo(() => {
351
- if (!initialNodes || !initialNodes.length) return initialNodes;
352
- const copy = initialNodes.map((n) => ({ ...n }));
353
- if (layout === "circular") applyCircularLayout(copy, width, height);
354
- else if (layout === "hierarchical")
355
- applyHierarchicalLayout(copy, width, height);
356
- else applyInitialForceLayout(copy, width, height);
357
- return copy;
358
- }, [initialNodes, width, height, layout]);
359
- const restart = React.useCallback(() => {
360
- }, []);
361
- const stop = React.useCallback(() => {
362
- }, []);
363
- const setForcesEnabled = React.useCallback((enabled) => {
364
- }, []);
365
- useEffect(() => {
366
- if (!nodes || nodes.length === 0) return;
367
- if (layout === "circular") applyCircularLayout(nodes, width, height);
368
- else if (layout === "hierarchical")
369
- applyHierarchicalLayout(nodes, width, height);
370
- restart();
371
- }, [layout, nodes, width, height, restart]);
588
+ const { restart, stop, setForcesEnabled } = useSimulationControls();
589
+ const { nodes } = useGraphLayout(
590
+ initialNodes,
591
+ width,
592
+ height,
593
+ layout,
594
+ restart
595
+ );
372
596
  useEffect(() => {
373
- if (manualLayout || pinnedNodes.size > 0) setForcesEnabled(false);
374
- else setForcesEnabled(true);
597
+ setForcesEnabled(!(manualLayout || pinnedNodes.size > 0));
375
598
  }, [manualLayout, pinnedNodes, setForcesEnabled]);
376
599
  useImperativeHandle(
377
600
  ref,
378
- () => ({
379
- pinAll: () => {
380
- const newPinned = /* @__PURE__ */ new Set();
381
- nodes.forEach((node) => {
382
- pinNode(node);
383
- newPinned.add(node.id);
384
- });
385
- setPinnedNodes(newPinned);
386
- restart();
387
- },
388
- unpinAll: () => {
389
- unpinAllNodes(nodes);
390
- setPinnedNodes(/* @__PURE__ */ new Set());
391
- restart();
392
- },
393
- resetLayout: () => {
394
- unpinAllNodes(nodes);
395
- setPinnedNodes(/* @__PURE__ */ new Set());
396
- restart();
397
- },
398
- fitView: () => {
399
- if (!svgRef.current || !nodes.length) return;
400
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
401
- nodes.forEach((node) => {
402
- if (node.x !== void 0 && node.y !== void 0) {
403
- const size = node.size || DEFAULT_NODE_SIZE;
404
- minX = Math.min(minX, node.x - size);
405
- maxX = Math.max(maxX, node.x + size);
406
- minY = Math.min(minY, node.y - size);
407
- maxY = Math.max(maxY, node.y + size);
408
- }
409
- });
410
- if (!isFinite(minX)) return;
411
- const scale = Math.min(
412
- (width - FIT_VIEW_PADDING * 2) / (maxX - minX),
413
- (height - FIT_VIEW_PADDING * 2) / (maxY - minY),
414
- 10
415
- );
416
- const x = width / 2 - (minX + maxX) / 2 * scale;
417
- const y = height / 2 - (minY + maxY) / 2 * scale;
418
- if (gRef.current && svgRef.current) {
419
- const svg = d32.select(svgRef.current);
420
- const newTransform = d32.zoomIdentity.translate(x, y).scale(scale);
421
- svg.transition().duration(TRANSITION_DURATION_MS).call(d32.zoom().transform, newTransform);
422
- setTransform(newTransform);
423
- }
424
- },
425
- getPinnedNodes: () => Array.from(pinnedNodes),
426
- setDragMode: (enabled) => {
427
- internalDragEnabledRef.current = enabled;
428
- },
429
- setLayout: (newLayout) => handleLayoutChange(newLayout),
430
- getLayout: () => layout
601
+ () => useImperativeHandleMethods({
602
+ nodes,
603
+ pinnedNodes,
604
+ setPinnedNodes,
605
+ restart,
606
+ width,
607
+ height,
608
+ layout,
609
+ handleLayoutChange,
610
+ svgRef,
611
+ gRef,
612
+ setTransform,
613
+ internalDragEnabledRef
431
614
  }),
432
615
  [
433
616
  nodes,
@@ -441,8 +624,7 @@ var ForceDirectedGraph = forwardRef(
441
624
  ]
442
625
  );
443
626
  useEffect(() => {
444
- if (typeof onManualLayoutChange === "function")
445
- onManualLayoutChange(manualLayout);
627
+ onManualLayoutChange?.(manualLayout);
446
628
  }, [manualLayout, onManualLayoutChange]);
447
629
  useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
448
630
  useWindowDrag(
@@ -458,57 +640,48 @@ var ForceDirectedGraph = forwardRef(
458
640
  );
459
641
  useEffect(() => {
460
642
  if (!gRef.current) return;
461
- const g = d32.select(gRef.current);
643
+ const g = d33.select(gRef.current);
462
644
  g.selectAll("g.node").each(function() {
463
- const datum = d32.select(this).datum();
464
- if (!datum) return;
465
- d32.select(this).attr(
466
- "transform",
467
- `translate(${datum.x || 0},${datum.y || 0})`
468
- );
645
+ const d = d33.select(this).datum();
646
+ if (d)
647
+ d33.select(this).attr(
648
+ "transform",
649
+ `translate(${d.x || 0},${d.y || 0})`
650
+ );
469
651
  });
470
652
  g.selectAll("line").each(function() {
471
- const l = d32.select(this).datum();
653
+ const l = d33.select(this).datum();
472
654
  if (!l) return;
473
- const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source) || l.source;
474
- const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target) || l.target;
475
- if (!s || !t) return;
476
- d32.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
655
+ const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source);
656
+ const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target);
657
+ if (s && t)
658
+ d33.select(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
477
659
  });
478
660
  }, [nodes, initialLinks]);
479
- const handleDragStart = useCallback(
480
- (event, node) => {
481
- if (!enableDrag) return;
482
- event.preventDefault();
483
- event.stopPropagation();
484
- dragActiveRef.current = true;
485
- dragNodeRef.current = node;
486
- pinNode(node);
487
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
488
- stop();
489
- },
490
- [enableDrag, stop]
661
+ const { handleDragStart, handleNodeDoubleClick } = useNodeInteractions(
662
+ enableDrag,
663
+ nodes,
664
+ pinnedNodes,
665
+ setPinnedNodes,
666
+ restart,
667
+ stop
491
668
  );
492
669
  useEffect(() => {
493
670
  if (!gRef.current || !enableDrag) return;
494
- const g = d32.select(gRef.current);
495
- const dragBehavior = d32.drag().on("start", (event) => {
496
- const target = event.sourceEvent && event.sourceEvent.target || event.target;
497
- const grp = target.closest?.("g.node");
498
- const id = grp?.getAttribute("data-id");
671
+ const g = d33.select(gRef.current);
672
+ const dragBehavior = d33.drag().on("start", (event) => {
673
+ const target = event.sourceEvent?.target || event.target;
674
+ const id = target.closest?.("g.node")?.getAttribute("data-id");
499
675
  if (!id || !internalDragEnabledRef.current) return;
500
676
  const node = nodes.find((n) => n.id === id);
501
677
  if (!node) return;
502
678
  if (!event.active) restart();
503
679
  dragActiveRef.current = true;
504
680
  dragNodeRef.current = node;
505
- pinNode(node);
506
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
507
681
  }).on("drag", (event) => {
508
- if (!dragActiveRef.current || !dragNodeRef.current) return;
509
- const svg = svgRef.current;
510
- if (!svg) return;
511
- const rect = svg.getBoundingClientRect();
682
+ if (!dragActiveRef.current || !dragNodeRef.current || !svgRef.current)
683
+ return;
684
+ const rect = svgRef.current.getBoundingClientRect();
512
685
  dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
513
686
  dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
514
687
  }).on("end", () => {
@@ -519,99 +692,40 @@ var ForceDirectedGraph = forwardRef(
519
692
  return () => {
520
693
  g.selectAll("g.node").on(".drag", null);
521
694
  };
522
- }, [
523
- gRef,
524
- enableDrag,
525
- nodes,
526
- transform,
527
- restart,
528
- setForcesEnabled,
529
- internalDragEnabledRef
530
- ]);
531
- const handleNodeDoubleClick = useCallback(
532
- (event, node) => {
533
- event.stopPropagation();
534
- if (!enableDrag) return;
535
- if (node.fx === null || node.fx === void 0) {
536
- pinNode(node);
537
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
538
- } else {
539
- unpinNode(node);
540
- setPinnedNodes((prev) => {
541
- const next = new Set(prev);
542
- next.delete(node.id);
543
- return next;
544
- });
545
- }
546
- restart();
547
- },
548
- [enableDrag, restart]
549
- );
550
- return /* @__PURE__ */ jsxs(
551
- "svg",
695
+ }, [gRef, enableDrag, nodes, transform, restart, setForcesEnabled]);
696
+ return /* @__PURE__ */ jsx(
697
+ GraphCanvas,
552
698
  {
553
- ref: svgRef,
699
+ svgRef,
700
+ gRef,
554
701
  width,
555
702
  height,
556
- className: cn("bg-white dark:bg-gray-900", className),
557
- onDoubleClick: () => {
558
- unpinAllNodes(nodes);
559
- setPinnedNodes(/* @__PURE__ */ new Set());
560
- restart();
561
- },
562
- children: [
563
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx(
564
- "marker",
565
- {
566
- id: "arrow",
567
- viewBox: "0 0 10 10",
568
- refX: "20",
569
- refY: "5",
570
- markerWidth: "6",
571
- markerHeight: "6",
572
- orient: "auto",
573
- children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: defaultLinkColor })
574
- }
575
- ) }),
576
- /* @__PURE__ */ jsxs("g", { ref: gRef, children: [
577
- initialLinks.map((link, i) => /* @__PURE__ */ jsx(
578
- LinkItem_default,
579
- {
580
- link,
581
- onClick: onLinkClick,
582
- defaultWidth: defaultLinkWidth,
583
- showLabel: showLinkLabels,
584
- nodes
585
- },
586
- `link-${i}`
587
- )),
588
- nodes.map((node) => /* @__PURE__ */ jsx(
589
- NodeItem_default,
590
- {
591
- node,
592
- isSelected: selectedNodeId === node.id,
593
- isHovered: hoveredNodeId === node.id,
594
- pinned: pinnedNodes.has(node.id),
595
- defaultNodeSize,
596
- defaultNodeColor,
597
- showLabel: showNodeLabels,
598
- onClick: onNodeClick,
599
- onDoubleClick: handleNodeDoubleClick,
600
- onMouseEnter: (n) => onNodeHover?.(n),
601
- onMouseLeave: () => onNodeHover?.(null),
602
- onMouseDown: handleDragStart
603
- },
604
- node.id
605
- )),
606
- /* @__PURE__ */ jsx(PackageBoundaries, { packageBounds: packageBounds || {} })
607
- ] })
608
- ]
703
+ className,
704
+ nodes,
705
+ links: initialLinks,
706
+ pinnedNodes,
707
+ selectedNodeId,
708
+ hoveredNodeId,
709
+ defaultNodeColor,
710
+ defaultNodeSize,
711
+ defaultLinkColor,
712
+ defaultLinkWidth,
713
+ showNodeLabels,
714
+ showLinkLabels,
715
+ onNodeClick,
716
+ onNodeHover,
717
+ onLinkClick,
718
+ packageBounds,
719
+ handleNodeDoubleClick,
720
+ handleDragStart,
721
+ restart,
722
+ setPinnedNodes
609
723
  }
610
724
  );
611
725
  }
612
726
  );
613
727
  ForceDirectedGraph.displayName = "ForceDirectedGraph";
614
728
 
615
- export { ForceDirectedGraph };
729
+ export { ForceDirectedGraph, ForceDirectedGraph as default };
616
730
  //# sourceMappingURL=ForceDirectedGraph.js.map
617
731
  //# sourceMappingURL=ForceDirectedGraph.js.map