@aiready/visualizer 0.6.18 → 0.6.19

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.
@@ -24895,7 +24895,7 @@ function superRefine(fn) {
24895
24895
  return /* @__PURE__ */ _superRefine(fn);
24896
24896
  }
24897
24897
  //#endregion
24898
- //#region ../../core/dist/chunk-RMH2TPAT.mjs
24898
+ //#region ../../core/dist/chunk-SQHS6PFL.mjs
24899
24899
  var LeadSource = /* @__PURE__ */ ((LeadSource2) => {
24900
24900
  LeadSource2["ClawMoreHero"] = "clawmore-hero";
24901
24901
  LeadSource2["ClawMoreWaitlist"] = "clawmore-waitlist";
@@ -28335,6 +28335,374 @@ function ScoreCircle({ score, progress: customProgress, isInView = true, size =
28335
28335
  });
28336
28336
  }
28337
28337
  (0, import_react.createContext)(void 0);
28338
+ function pinNode(node) {
28339
+ node.fx = node.x;
28340
+ node.fy = node.y;
28341
+ }
28342
+ function unpinNode(node) {
28343
+ node.fx = null;
28344
+ node.fy = null;
28345
+ }
28346
+ function unpinAllNodes(nodes) {
28347
+ nodes.forEach(unpinNode);
28348
+ }
28349
+ function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
28350
+ (0, import_react.useEffect)(() => {
28351
+ if (!enableZoom || !svgRef.current || !gRef.current) return;
28352
+ const svg = select_default$1(svgRef.current);
28353
+ const g = select_default$1(gRef.current);
28354
+ const zoom3 = zoom_default().scaleExtent([.1, 10]).on("zoom", (event) => {
28355
+ g.attr("transform", event.transform);
28356
+ transformRef.current = event.transform;
28357
+ setTransform(event.transform);
28358
+ });
28359
+ svg.call(zoom3);
28360
+ return () => {
28361
+ svg.on(".zoom", null);
28362
+ };
28363
+ }, [
28364
+ enableZoom,
28365
+ svgRef,
28366
+ gRef,
28367
+ setTransform,
28368
+ transformRef
28369
+ ]);
28370
+ }
28371
+ function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
28372
+ (0, import_react.useEffect)(() => {
28373
+ if (!enableDrag) return;
28374
+ const handleWindowMove = (event) => {
28375
+ if (!dragActiveRef.current || !dragNodeRef.current) return;
28376
+ const svg = svgRef.current;
28377
+ if (!svg) return;
28378
+ const rect = svg.getBoundingClientRect();
28379
+ const t = transformRef.current;
28380
+ const x = (event.clientX - rect.left - t.x) / t.k;
28381
+ const y = (event.clientY - rect.top - t.y) / t.k;
28382
+ dragNodeRef.current.fx = x;
28383
+ dragNodeRef.current.fy = y;
28384
+ };
28385
+ const handleWindowUp = () => {
28386
+ if (!dragActiveRef.current) return;
28387
+ onDragEnd();
28388
+ dragNodeRef.current = null;
28389
+ dragActiveRef.current = false;
28390
+ };
28391
+ const handleWindowLeave = (event) => {
28392
+ if (event.relatedTarget === null) handleWindowUp();
28393
+ };
28394
+ window.addEventListener("mousemove", handleWindowMove);
28395
+ window.addEventListener("mouseup", handleWindowUp);
28396
+ window.addEventListener("mouseout", handleWindowLeave);
28397
+ window.addEventListener("blur", handleWindowUp);
28398
+ return () => {
28399
+ window.removeEventListener("mousemove", handleWindowMove);
28400
+ window.removeEventListener("mouseup", handleWindowUp);
28401
+ window.removeEventListener("mouseout", handleWindowLeave);
28402
+ window.removeEventListener("blur", handleWindowUp);
28403
+ };
28404
+ }, [
28405
+ enableDrag,
28406
+ svgRef,
28407
+ transformRef,
28408
+ dragActiveRef,
28409
+ dragNodeRef,
28410
+ onDragEnd
28411
+ ]);
28412
+ }
28413
+ function useNodeInteractions(enableDrag, _nodes, _pinnedNodes, setPinnedNodes, restart, stop) {
28414
+ return {
28415
+ handleDragStart: (0, import_react.useCallback)((event, node) => {
28416
+ if (!enableDrag) return;
28417
+ event.preventDefault();
28418
+ event.stopPropagation();
28419
+ pinNode(node);
28420
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28421
+ stop();
28422
+ }, [
28423
+ enableDrag,
28424
+ stop,
28425
+ setPinnedNodes
28426
+ ]),
28427
+ handleNodeDoubleClick: (0, import_react.useCallback)((event, node) => {
28428
+ event.stopPropagation();
28429
+ if (!enableDrag) return;
28430
+ if (node.fx === null || node.fx === void 0) {
28431
+ pinNode(node);
28432
+ setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28433
+ } else {
28434
+ unpinNode(node);
28435
+ setPinnedNodes((prev) => {
28436
+ const next = new Set(prev);
28437
+ next.delete(node.id);
28438
+ return next;
28439
+ });
28440
+ }
28441
+ restart();
28442
+ }, [
28443
+ enableDrag,
28444
+ restart,
28445
+ setPinnedNodes
28446
+ ])
28447
+ };
28448
+ }
28449
+ var DEFAULT_NODE_COLOR = "#64748b";
28450
+ var DEFAULT_NODE_SIZE = 10;
28451
+ var DEFAULT_LINK_COLOR = "#94a3b8";
28452
+ var DEFAULT_LINK_WIDTH = 1;
28453
+ var CIRCULAR_LAYOUT_RADIUS_RATIO = .35;
28454
+ var FIT_VIEW_PADDING = 40;
28455
+ var TRANSITION_DURATION_MS = 300;
28456
+ var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
28457
+ var PACKAGE_BOUNDARY_STROKE = "#475569";
28458
+ var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
28459
+ var PACKAGE_BOUNDARY_DASH = "6 6";
28460
+ var PACKAGE_LABEL_FONT_SIZE = 11;
28461
+ var PACKAGE_LABEL_COLOR = "#475569";
28462
+ function applyCircularLayout(nodes, width, height) {
28463
+ const centerX = width / 2;
28464
+ const centerY = height / 2;
28465
+ const radius = Math.min(width, height) * CIRCULAR_LAYOUT_RADIUS_RATIO;
28466
+ nodes.forEach((node, i) => {
28467
+ const angle = 2 * Math.PI * i / nodes.length;
28468
+ node.fx = centerX + Math.cos(angle) * radius;
28469
+ node.fy = centerY + Math.sin(angle) * radius;
28470
+ node.x = node.fx;
28471
+ node.y = node.fy;
28472
+ });
28473
+ }
28474
+ function applyHierarchicalLayout(nodes, width, height) {
28475
+ const groups = /* @__PURE__ */ new Map();
28476
+ nodes.forEach((n) => {
28477
+ const key = n.packageGroup || n.group || "root";
28478
+ if (!groups.has(key)) groups.set(key, []);
28479
+ groups.get(key).push(n);
28480
+ });
28481
+ const groupArray = Array.from(groups.entries());
28482
+ const cols = Math.ceil(Math.sqrt(groupArray.length));
28483
+ const groupSpacingX = width * .8 / cols;
28484
+ const groupSpacingY = height * .8 / Math.ceil(groupArray.length / cols);
28485
+ groupArray.forEach(([groupKey, groupNodes], gi) => {
28486
+ const col = gi % cols;
28487
+ const row = Math.floor(gi / cols);
28488
+ const groupX = (col + .5) * groupSpacingX;
28489
+ const groupY = (row + .5) * groupSpacingY;
28490
+ if (groupKey.startsWith("pkg:") || groupKey === groupKey) groupNodes.forEach((n, ni) => {
28491
+ const angle = 2 * Math.PI * ni / groupNodes.length;
28492
+ const r = Math.min(80, 20 + groupNodes.length * 8);
28493
+ n.fx = groupX + Math.cos(angle) * r;
28494
+ n.fy = groupY + Math.sin(angle) * r;
28495
+ n.x = n.fx;
28496
+ n.y = n.fy;
28497
+ });
28498
+ });
28499
+ }
28500
+ function applyInitialForceLayout(nodes, width, height) {
28501
+ nodes.forEach((node) => {
28502
+ if (node.fx === void 0 || node.fx === null) {
28503
+ node.x = Math.random() * width;
28504
+ node.y = Math.random() * height;
28505
+ }
28506
+ });
28507
+ }
28508
+ function useGraphLayout(initialNodes, width, height, layout, restart) {
28509
+ const nodes = (0, import_react.useMemo)(() => {
28510
+ if (!initialNodes || !initialNodes.length) return initialNodes;
28511
+ const copy = initialNodes.map((n) => ({ ...n }));
28512
+ if (layout === "circular") applyCircularLayout(copy, width, height);
28513
+ else if (layout === "hierarchical") applyHierarchicalLayout(copy, width, height);
28514
+ else applyInitialForceLayout(copy, width, height);
28515
+ return copy;
28516
+ }, [
28517
+ initialNodes,
28518
+ width,
28519
+ height,
28520
+ layout
28521
+ ]);
28522
+ (0, import_react.useEffect)(() => {
28523
+ if (!nodes || nodes.length === 0) return;
28524
+ if (layout === "circular") applyCircularLayout(nodes, width, height);
28525
+ else if (layout === "hierarchical") applyHierarchicalLayout(nodes, width, height);
28526
+ restart();
28527
+ }, [
28528
+ layout,
28529
+ nodes,
28530
+ width,
28531
+ height,
28532
+ restart
28533
+ ]);
28534
+ return { nodes };
28535
+ }
28536
+ function useSimulationControls() {
28537
+ return {
28538
+ restart: (0, import_react.useCallback)(() => {}, []),
28539
+ stop: (0, import_react.useCallback)(() => {}, []),
28540
+ setForcesEnabled: (0, import_react.useCallback)((enabled) => {}, [])
28541
+ };
28542
+ }
28543
+ function useImperativeHandleMethods({ nodes, pinnedNodes, setPinnedNodes, restart, width, height, layout, handleLayoutChange, svgRef, gRef, setTransform, internalDragEnabledRef }) {
28544
+ return {
28545
+ pinAll: (0, import_react.useCallback)(() => {
28546
+ const newPinned = /* @__PURE__ */ new Set();
28547
+ nodes.forEach((node) => {
28548
+ pinNode(node);
28549
+ newPinned.add(node.id);
28550
+ });
28551
+ setPinnedNodes(newPinned);
28552
+ restart();
28553
+ }, [
28554
+ nodes,
28555
+ setPinnedNodes,
28556
+ restart
28557
+ ]),
28558
+ unpinAll: (0, import_react.useCallback)(() => {
28559
+ unpinAllNodes(nodes);
28560
+ setPinnedNodes(/* @__PURE__ */ new Set());
28561
+ restart();
28562
+ }, [
28563
+ nodes,
28564
+ setPinnedNodes,
28565
+ restart
28566
+ ]),
28567
+ resetLayout: (0, import_react.useCallback)(() => {
28568
+ unpinAllNodes(nodes);
28569
+ setPinnedNodes(/* @__PURE__ */ new Set());
28570
+ restart();
28571
+ }, [
28572
+ nodes,
28573
+ setPinnedNodes,
28574
+ restart
28575
+ ]),
28576
+ fitView: (0, import_react.useCallback)(() => {
28577
+ if (!svgRef.current || !nodes.length) return;
28578
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
28579
+ nodes.forEach((node) => {
28580
+ if (node.x !== void 0 && node.y !== void 0) {
28581
+ const size = node.size || DEFAULT_NODE_SIZE;
28582
+ minX = Math.min(minX, node.x - size);
28583
+ maxX = Math.max(maxX, node.x + size);
28584
+ minY = Math.min(minY, node.y - size);
28585
+ maxY = Math.max(maxY, node.y + size);
28586
+ }
28587
+ });
28588
+ if (!isFinite(minX)) return;
28589
+ const scale = Math.min((width - FIT_VIEW_PADDING * 2) / (maxX - minX), (height - FIT_VIEW_PADDING * 2) / (maxY - minY), 10);
28590
+ const x = width / 2 - (minX + maxX) / 2 * scale;
28591
+ const y = height / 2 - (minY + maxY) / 2 * scale;
28592
+ if (gRef.current && svgRef.current) {
28593
+ const svg = select_default$1(svgRef.current);
28594
+ const newTransform = identity.translate(x, y).scale(scale);
28595
+ svg.transition().duration(TRANSITION_DURATION_MS).call(zoom_default().transform, newTransform);
28596
+ setTransform(newTransform);
28597
+ }
28598
+ }, [
28599
+ nodes,
28600
+ width,
28601
+ height,
28602
+ svgRef,
28603
+ gRef,
28604
+ setTransform
28605
+ ]),
28606
+ getPinnedNodes: (0, import_react.useCallback)(() => Array.from(pinnedNodes), [pinnedNodes]),
28607
+ setDragMode: (0, import_react.useCallback)((enabled) => {
28608
+ internalDragEnabledRef.current = enabled;
28609
+ }, [internalDragEnabledRef]),
28610
+ setLayout: (0, import_react.useCallback)((newLayout) => {
28611
+ handleLayoutChange(newLayout);
28612
+ }, [handleLayoutChange]),
28613
+ getLayout: (0, import_react.useCallback)(() => layout, [layout])
28614
+ };
28615
+ }
28616
+ var ControlButton = ({ onClick, active = false, icon, label, disabled = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28617
+ className: "relative group",
28618
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
28619
+ onClick,
28620
+ disabled,
28621
+ className: cn("p-2 rounded-lg transition-all duration-200", active ? "bg-blue-500 text-white shadow-md hover:bg-blue-600" : "bg-gray-100 text-gray-700 hover:bg-gray-200", disabled && "opacity-50 cursor-not-allowed hover:bg-gray-100", "dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:active:bg-blue-600"),
28622
+ title: label,
28623
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
28624
+ className: "text-lg",
28625
+ children: icon
28626
+ })
28627
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
28628
+ className: "absolute left-full ml-2 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50",
28629
+ children: label
28630
+ })]
28631
+ });
28632
+ var GraphControls = ({ dragEnabled = true, onDragToggle, manualLayout = false, onManualLayoutToggle, onPinAll, onUnpinAll, onReset, onFitView, pinnedCount = 0, totalNodes = 0, visible = true, position = "top-left", className }) => {
28633
+ if (!visible) return null;
28634
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28635
+ className: cn("fixed z-40 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-2 border border-gray-200 dark:border-gray-700", {
28636
+ "top-left": "top-4 left-4",
28637
+ "top-right": "top-4 right-4",
28638
+ "bottom-left": "bottom-4 left-4",
28639
+ "bottom-right": "bottom-4 right-4"
28640
+ }[position], className),
28641
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28642
+ className: "flex flex-col gap-2",
28643
+ children: [
28644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28645
+ onClick: () => onDragToggle?.(!dragEnabled),
28646
+ active: dragEnabled,
28647
+ icon: "✋",
28648
+ label: dragEnabled ? "Drag enabled" : "Drag disabled"
28649
+ }),
28650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28651
+ onClick: () => onManualLayoutToggle?.(!manualLayout),
28652
+ active: manualLayout,
28653
+ icon: "🔧",
28654
+ label: manualLayout ? "Manual layout: ON" : "Manual layout: OFF"
28655
+ }),
28656
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-8 h-px bg-gray-300 dark:bg-gray-600 mx-auto my-1" }),
28657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28658
+ className: "flex gap-1",
28659
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28660
+ onClick: () => onPinAll?.(),
28661
+ disabled: totalNodes === 0,
28662
+ icon: "📌",
28663
+ label: `Pin all nodes (${totalNodes})`
28664
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28665
+ onClick: () => onUnpinAll?.(),
28666
+ disabled: pinnedCount === 0,
28667
+ icon: "📍",
28668
+ label: `Unpin all (${pinnedCount} pinned)`
28669
+ })]
28670
+ }),
28671
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-8 h-px bg-gray-300 dark:bg-gray-600 mx-auto my-1" }),
28672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28673
+ onClick: () => onFitView?.(),
28674
+ disabled: totalNodes === 0,
28675
+ icon: "🎯",
28676
+ label: "Fit all nodes in view"
28677
+ }),
28678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28679
+ onClick: () => onReset?.(),
28680
+ disabled: totalNodes === 0,
28681
+ icon: "↺",
28682
+ label: "Reset to auto-layout"
28683
+ })
28684
+ ]
28685
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28686
+ className: "mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-600 dark:text-gray-400",
28687
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28688
+ className: "whitespace-nowrap",
28689
+ children: [
28690
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Nodes:" }),
28691
+ " ",
28692
+ totalNodes
28693
+ ]
28694
+ }), pinnedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28695
+ className: "whitespace-nowrap",
28696
+ children: [
28697
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Pinned:" }),
28698
+ " ",
28699
+ pinnedCount
28700
+ ]
28701
+ })]
28702
+ })]
28703
+ });
28704
+ };
28705
+ GraphControls.displayName = "GraphControls";
28338
28706
  var NodeItem = ({ node, isSelected, isHovered, pinned, defaultNodeSize, defaultNodeColor, showLabel = true, onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseDown }) => {
28339
28707
  const nodeSize = node.size || defaultNodeSize;
28340
28708
  const nodeColor = node.color || defaultNodeColor;
@@ -28427,19 +28795,6 @@ var LinkItem = ({ link, onClick, defaultWidth, showLabel = true, nodes = [] }) =
28427
28795
  })] });
28428
28796
  };
28429
28797
  var LinkItem_default = LinkItem;
28430
- var DEFAULT_NODE_COLOR = "#64748b";
28431
- var DEFAULT_NODE_SIZE = 10;
28432
- var DEFAULT_LINK_COLOR = "#94a3b8";
28433
- var DEFAULT_LINK_WIDTH = 1;
28434
- var CIRCULAR_LAYOUT_RADIUS_RATIO = .35;
28435
- var FIT_VIEW_PADDING = 40;
28436
- var TRANSITION_DURATION_MS = 300;
28437
- var PACKAGE_BOUNDARY_FILL = "rgba(148,163,184,0.06)";
28438
- var PACKAGE_BOUNDARY_STROKE = "#475569";
28439
- var PACKAGE_BOUNDARY_STROKE_WIDTH = 2;
28440
- var PACKAGE_BOUNDARY_DASH = "6 6";
28441
- var PACKAGE_LABEL_FONT_SIZE = 11;
28442
- var PACKAGE_LABEL_COLOR = "#475569";
28443
28798
  var PackageBoundaries = ({ packageBounds }) => {
28444
28799
  if (!packageBounds || Object.keys(packageBounds).length === 0) return null;
28445
28800
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", {
@@ -28466,128 +28821,59 @@ var PackageBoundaries = ({ packageBounds }) => {
28466
28821
  });
28467
28822
  };
28468
28823
  PackageBoundaries.displayName = "PackageBoundaries";
28469
- function applyCircularLayout(nodes, width, height) {
28470
- const centerX = width / 2;
28471
- const centerY = height / 2;
28472
- const radius = Math.min(width, height) * CIRCULAR_LAYOUT_RADIUS_RATIO;
28473
- nodes.forEach((node, i) => {
28474
- const angle = 2 * Math.PI * i / nodes.length;
28475
- node.fx = centerX + Math.cos(angle) * radius;
28476
- node.fy = centerY + Math.sin(angle) * radius;
28477
- node.x = node.fx;
28478
- node.y = node.fy;
28479
- });
28480
- }
28481
- function applyHierarchicalLayout(nodes, width, height) {
28482
- const groups = /* @__PURE__ */ new Map();
28483
- nodes.forEach((n) => {
28484
- const key = n.packageGroup || n.group || "root";
28485
- if (!groups.has(key)) groups.set(key, []);
28486
- groups.get(key).push(n);
28487
- });
28488
- const groupArray = Array.from(groups.entries());
28489
- const cols = Math.ceil(Math.sqrt(groupArray.length));
28490
- const groupSpacingX = width * .8 / cols;
28491
- const groupSpacingY = height * .8 / Math.ceil(groupArray.length / cols);
28492
- groupArray.forEach(([groupKey, groupNodes], gi) => {
28493
- const col = gi % cols;
28494
- const row = Math.floor(gi / cols);
28495
- const groupX = (col + .5) * groupSpacingX;
28496
- const groupY = (row + .5) * groupSpacingY;
28497
- if (groupKey.startsWith("pkg:") || groupKey === groupKey) groupNodes.forEach((n, ni) => {
28498
- const angle = 2 * Math.PI * ni / groupNodes.length;
28499
- const r = Math.min(80, 20 + groupNodes.length * 8);
28500
- n.fx = groupX + Math.cos(angle) * r;
28501
- n.fy = groupY + Math.sin(angle) * r;
28502
- n.x = n.fx;
28503
- n.y = n.fy;
28504
- });
28505
- });
28506
- }
28507
- function applyInitialForceLayout(nodes, width, height) {
28508
- nodes.forEach((node) => {
28509
- if (node.fx === void 0 || node.fx === null) {
28510
- node.x = Math.random() * width;
28511
- node.y = Math.random() * height;
28512
- }
28513
- });
28514
- }
28515
- function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
28516
- (0, import_react.useEffect)(() => {
28517
- if (!enableZoom || !svgRef.current || !gRef.current) return;
28518
- const svg = select_default$1(svgRef.current);
28519
- const g = select_default$1(gRef.current);
28520
- const zoom3 = zoom_default().scaleExtent([.1, 10]).on("zoom", (event) => {
28521
- g.attr("transform", event.transform);
28522
- transformRef.current = event.transform;
28523
- setTransform(event.transform);
28524
- });
28525
- svg.call(zoom3);
28526
- return () => {
28527
- svg.on(".zoom", null);
28528
- };
28529
- }, [
28530
- enableZoom,
28531
- svgRef,
28532
- gRef,
28533
- setTransform,
28534
- transformRef
28535
- ]);
28536
- }
28537
- function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, onDragEnd) {
28538
- (0, import_react.useEffect)(() => {
28539
- if (!enableDrag) return;
28540
- const handleWindowMove = (event) => {
28541
- if (!dragActiveRef.current || !dragNodeRef.current) return;
28542
- const svg = svgRef.current;
28543
- if (!svg) return;
28544
- const rect = svg.getBoundingClientRect();
28545
- const t = transformRef.current;
28546
- const x = (event.clientX - rect.left - t.x) / t.k;
28547
- const y = (event.clientY - rect.top - t.y) / t.k;
28548
- dragNodeRef.current.fx = x;
28549
- dragNodeRef.current.fy = y;
28550
- };
28551
- const handleWindowUp = () => {
28552
- if (!dragActiveRef.current) return;
28553
- onDragEnd();
28554
- dragNodeRef.current = null;
28555
- dragActiveRef.current = false;
28556
- };
28557
- const handleWindowLeave = (event) => {
28558
- if (event.relatedTarget === null) handleWindowUp();
28559
- };
28560
- window.addEventListener("mousemove", handleWindowMove);
28561
- window.addEventListener("mouseup", handleWindowUp);
28562
- window.addEventListener("mouseout", handleWindowLeave);
28563
- window.addEventListener("blur", handleWindowUp);
28564
- return () => {
28565
- window.removeEventListener("mousemove", handleWindowMove);
28566
- window.removeEventListener("mouseup", handleWindowUp);
28567
- window.removeEventListener("mouseout", handleWindowLeave);
28568
- window.removeEventListener("blur", handleWindowUp);
28569
- };
28570
- }, [
28571
- enableDrag,
28572
- svgRef,
28573
- transformRef,
28574
- dragActiveRef,
28575
- dragNodeRef,
28576
- onDragEnd
28577
- ]);
28578
- }
28579
- function pinNode(node) {
28580
- node.fx = node.x;
28581
- node.fy = node.y;
28582
- }
28583
- function unpinNode(node) {
28584
- node.fx = null;
28585
- node.fy = null;
28586
- }
28587
- function unpinAllNodes(nodes) {
28588
- nodes.forEach(unpinNode);
28589
- }
28590
- var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, links: initialLinks, width, height, enableZoom = true, enableDrag = true, onNodeClick, onNodeHover, onLinkClick, selectedNodeId, hoveredNodeId, defaultNodeColor = DEFAULT_NODE_COLOR, defaultNodeSize = DEFAULT_NODE_SIZE, defaultLinkColor = DEFAULT_LINK_COLOR, defaultLinkWidth = DEFAULT_LINK_WIDTH, showNodeLabels = true, showLinkLabels = false, className, manualLayout = false, onManualLayoutChange, packageBounds, layout: externalLayout, onLayoutChange }, ref) => {
28824
+ var GraphCanvas = ({ svgRef, gRef, width, height, className, nodes, links, pinnedNodes, selectedNodeId, hoveredNodeId, defaultNodeColor = DEFAULT_NODE_COLOR, defaultNodeSize = DEFAULT_NODE_SIZE, defaultLinkColor = DEFAULT_LINK_COLOR, defaultLinkWidth = DEFAULT_LINK_WIDTH, showNodeLabels = true, showLinkLabels = false, onNodeClick, onNodeHover, onLinkClick, packageBounds, handleNodeDoubleClick, handleDragStart, restart, setPinnedNodes }) => {
28825
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
28826
+ ref: svgRef,
28827
+ width,
28828
+ height,
28829
+ className: cn("bg-white dark:bg-gray-900", className),
28830
+ onDoubleClick: () => {
28831
+ unpinAllNodes(nodes);
28832
+ setPinnedNodes(/* @__PURE__ */ new Set());
28833
+ restart();
28834
+ },
28835
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("marker", {
28836
+ id: "arrow",
28837
+ viewBox: "0 0 10 10",
28838
+ refX: "20",
28839
+ refY: "5",
28840
+ markerWidth: "6",
28841
+ markerHeight: "6",
28842
+ orient: "auto",
28843
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
28844
+ d: "M 0 0 L 10 5 L 0 10 z",
28845
+ fill: defaultLinkColor
28846
+ })
28847
+ }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
28848
+ ref: gRef,
28849
+ children: [
28850
+ links.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkItem_default, {
28851
+ link,
28852
+ onClick: onLinkClick,
28853
+ defaultWidth: defaultLinkWidth,
28854
+ showLabel: showLinkLabels,
28855
+ nodes
28856
+ }, `link-${i}`)),
28857
+ nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NodeItem_default, {
28858
+ node,
28859
+ isSelected: selectedNodeId === node.id,
28860
+ isHovered: hoveredNodeId === node.id,
28861
+ pinned: pinnedNodes.has(node.id),
28862
+ defaultNodeSize,
28863
+ defaultNodeColor,
28864
+ showLabel: showNodeLabels,
28865
+ onClick: onNodeClick,
28866
+ onDoubleClick: handleNodeDoubleClick,
28867
+ onMouseEnter: (n) => onNodeHover?.(n),
28868
+ onMouseLeave: () => onNodeHover?.(null),
28869
+ onMouseDown: handleDragStart
28870
+ }, node.id)),
28871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PackageBoundaries, { packageBounds: packageBounds || {} })
28872
+ ]
28873
+ })]
28874
+ });
28875
+ };
28876
+ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, links: initialLinks, width, height, enableZoom = true, enableDrag = true, onNodeClick, onNodeHover, onLinkClick, selectedNodeId, hoveredNodeId, defaultNodeColor, defaultNodeSize, defaultLinkColor, defaultLinkWidth, showNodeLabels, showLinkLabels, className, manualLayout = false, onManualLayoutChange, packageBounds, layout: externalLayout, onLayoutChange }, ref) => {
28591
28877
  const svgRef = (0, import_react.useRef)(null);
28592
28878
  const gRef = (0, import_react.useRef)(null);
28593
28879
  const [transform, setTransform] = (0, import_react.useState)({
@@ -28611,91 +28897,28 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28611
28897
  (0, import_react.useEffect)(() => {
28612
28898
  internalDragEnabledRef.current = enableDrag;
28613
28899
  }, [enableDrag]);
28614
- const nodes = import_react.useMemo(() => {
28615
- if (!initialNodes || !initialNodes.length) return initialNodes;
28616
- const copy = initialNodes.map((n) => ({ ...n }));
28617
- if (layout === "circular") applyCircularLayout(copy, width, height);
28618
- else if (layout === "hierarchical") applyHierarchicalLayout(copy, width, height);
28619
- else applyInitialForceLayout(copy, width, height);
28620
- return copy;
28621
- }, [
28622
- initialNodes,
28623
- width,
28624
- height,
28625
- layout
28626
- ]);
28627
- const restart = import_react.useCallback(() => {}, []);
28628
- const stop = import_react.useCallback(() => {}, []);
28629
- const setForcesEnabled = import_react.useCallback((enabled) => {}, []);
28630
- (0, import_react.useEffect)(() => {
28631
- if (!nodes || nodes.length === 0) return;
28632
- if (layout === "circular") applyCircularLayout(nodes, width, height);
28633
- else if (layout === "hierarchical") applyHierarchicalLayout(nodes, width, height);
28634
- restart();
28635
- }, [
28636
- layout,
28637
- nodes,
28638
- width,
28639
- height,
28640
- restart
28641
- ]);
28900
+ const { restart, stop, setForcesEnabled } = useSimulationControls();
28901
+ const { nodes } = useGraphLayout(initialNodes, width, height, layout, restart);
28642
28902
  (0, import_react.useEffect)(() => {
28643
- if (manualLayout || pinnedNodes.size > 0) setForcesEnabled(false);
28644
- else setForcesEnabled(true);
28903
+ setForcesEnabled(!(manualLayout || pinnedNodes.size > 0));
28645
28904
  }, [
28646
28905
  manualLayout,
28647
28906
  pinnedNodes,
28648
28907
  setForcesEnabled
28649
28908
  ]);
28650
- (0, import_react.useImperativeHandle)(ref, () => ({
28651
- pinAll: () => {
28652
- const newPinned = /* @__PURE__ */ new Set();
28653
- nodes.forEach((node) => {
28654
- pinNode(node);
28655
- newPinned.add(node.id);
28656
- });
28657
- setPinnedNodes(newPinned);
28658
- restart();
28659
- },
28660
- unpinAll: () => {
28661
- unpinAllNodes(nodes);
28662
- setPinnedNodes(/* @__PURE__ */ new Set());
28663
- restart();
28664
- },
28665
- resetLayout: () => {
28666
- unpinAllNodes(nodes);
28667
- setPinnedNodes(/* @__PURE__ */ new Set());
28668
- restart();
28669
- },
28670
- fitView: () => {
28671
- if (!svgRef.current || !nodes.length) return;
28672
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
28673
- nodes.forEach((node) => {
28674
- if (node.x !== void 0 && node.y !== void 0) {
28675
- const size = node.size || DEFAULT_NODE_SIZE;
28676
- minX = Math.min(minX, node.x - size);
28677
- maxX = Math.max(maxX, node.x + size);
28678
- minY = Math.min(minY, node.y - size);
28679
- maxY = Math.max(maxY, node.y + size);
28680
- }
28681
- });
28682
- if (!isFinite(minX)) return;
28683
- const scale = Math.min((width - FIT_VIEW_PADDING * 2) / (maxX - minX), (height - FIT_VIEW_PADDING * 2) / (maxY - minY), 10);
28684
- const x = width / 2 - (minX + maxX) / 2 * scale;
28685
- const y = height / 2 - (minY + maxY) / 2 * scale;
28686
- if (gRef.current && svgRef.current) {
28687
- const svg = select_default$1(svgRef.current);
28688
- const newTransform = identity.translate(x, y).scale(scale);
28689
- svg.transition().duration(TRANSITION_DURATION_MS).call(zoom_default().transform, newTransform);
28690
- setTransform(newTransform);
28691
- }
28692
- },
28693
- getPinnedNodes: () => Array.from(pinnedNodes),
28694
- setDragMode: (enabled) => {
28695
- internalDragEnabledRef.current = enabled;
28696
- },
28697
- setLayout: (newLayout) => handleLayoutChange(newLayout),
28698
- getLayout: () => layout
28909
+ (0, import_react.useImperativeHandle)(ref, () => useImperativeHandleMethods({
28910
+ nodes,
28911
+ pinnedNodes,
28912
+ setPinnedNodes,
28913
+ restart,
28914
+ width,
28915
+ height,
28916
+ layout,
28917
+ handleLayoutChange,
28918
+ svgRef,
28919
+ gRef,
28920
+ setTransform,
28921
+ internalDragEnabledRef
28699
28922
  }), [
28700
28923
  nodes,
28701
28924
  pinnedNodes,
@@ -28707,7 +28930,7 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28707
28930
  setForcesEnabled
28708
28931
  ]);
28709
28932
  (0, import_react.useEffect)(() => {
28710
- if (typeof onManualLayoutChange === "function") onManualLayoutChange(manualLayout);
28933
+ onManualLayoutChange?.(manualLayout);
28711
28934
  }, [manualLayout, onManualLayoutChange]);
28712
28935
  useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
28713
28936
  useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, () => {
@@ -28718,47 +28941,32 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28718
28941
  if (!gRef.current) return;
28719
28942
  const g = select_default$1(gRef.current);
28720
28943
  g.selectAll("g.node").each(function() {
28721
- const datum = select_default$1(this).datum();
28722
- if (!datum) return;
28723
- select_default$1(this).attr("transform", `translate(${datum.x || 0},${datum.y || 0})`);
28944
+ const d = select_default$1(this).datum();
28945
+ if (d) select_default$1(this).attr("transform", `translate(${d.x || 0},${d.y || 0})`);
28724
28946
  });
28725
28947
  g.selectAll("line").each(function() {
28726
28948
  const l = select_default$1(this).datum();
28727
28949
  if (!l) return;
28728
- const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source) || l.source;
28729
- const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target) || l.target;
28730
- if (!s || !t) return;
28731
- select_default$1(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
28950
+ const s = typeof l.source === "object" ? l.source : nodes.find((n) => n.id === l.source);
28951
+ const t = typeof l.target === "object" ? l.target : nodes.find((n) => n.id === l.target);
28952
+ if (s && t) select_default$1(this).attr("x1", s.x).attr("y1", s.y).attr("x2", t.x).attr("y2", t.y);
28732
28953
  });
28733
28954
  }, [nodes, initialLinks]);
28734
- const handleDragStart = (0, import_react.useCallback)((event, node) => {
28735
- if (!enableDrag) return;
28736
- event.preventDefault();
28737
- event.stopPropagation();
28738
- dragActiveRef.current = true;
28739
- dragNodeRef.current = node;
28740
- pinNode(node);
28741
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28742
- stop();
28743
- }, [enableDrag, stop]);
28955
+ const { handleDragStart, handleNodeDoubleClick } = useNodeInteractions(enableDrag, nodes, pinnedNodes, setPinnedNodes, restart, stop);
28744
28956
  (0, import_react.useEffect)(() => {
28745
28957
  if (!gRef.current || !enableDrag) return;
28746
28958
  const g = select_default$1(gRef.current);
28747
28959
  const dragBehavior = drag_default().on("start", (event) => {
28748
- const id = ((event.sourceEvent && event.sourceEvent.target || event.target).closest?.("g.node"))?.getAttribute("data-id");
28960
+ const id = (event.sourceEvent?.target || event.target).closest?.("g.node")?.getAttribute("data-id");
28749
28961
  if (!id || !internalDragEnabledRef.current) return;
28750
28962
  const node = nodes.find((n) => n.id === id);
28751
28963
  if (!node) return;
28752
28964
  if (!event.active) restart();
28753
28965
  dragActiveRef.current = true;
28754
28966
  dragNodeRef.current = node;
28755
- pinNode(node);
28756
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28757
28967
  }).on("drag", (event) => {
28758
- if (!dragActiveRef.current || !dragNodeRef.current) return;
28759
- const svg = svgRef.current;
28760
- if (!svg) return;
28761
- const rect = svg.getBoundingClientRect();
28968
+ if (!dragActiveRef.current || !dragNodeRef.current || !svgRef.current) return;
28969
+ const rect = svgRef.current.getBoundingClientRect();
28762
28970
  dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
28763
28971
  dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
28764
28972
  }).on("end", () => {
@@ -28775,183 +28983,36 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28775
28983
  nodes,
28776
28984
  transform,
28777
28985
  restart,
28778
- setForcesEnabled,
28779
- internalDragEnabledRef
28986
+ setForcesEnabled
28780
28987
  ]);
28781
- const handleNodeDoubleClick = (0, import_react.useCallback)((event, node) => {
28782
- event.stopPropagation();
28783
- if (!enableDrag) return;
28784
- if (node.fx === null || node.fx === void 0) {
28785
- pinNode(node);
28786
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28787
- } else {
28788
- unpinNode(node);
28789
- setPinnedNodes((prev) => {
28790
- const next = new Set(prev);
28791
- next.delete(node.id);
28792
- return next;
28793
- });
28794
- }
28795
- restart();
28796
- }, [enableDrag, restart]);
28797
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
28798
- ref: svgRef,
28988
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GraphCanvas, {
28989
+ svgRef,
28990
+ gRef,
28799
28991
  width,
28800
28992
  height,
28801
- className: cn("bg-white dark:bg-gray-900", className),
28802
- onDoubleClick: () => {
28803
- unpinAllNodes(nodes);
28804
- setPinnedNodes(/* @__PURE__ */ new Set());
28805
- restart();
28806
- },
28807
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("marker", {
28808
- id: "arrow",
28809
- viewBox: "0 0 10 10",
28810
- refX: "20",
28811
- refY: "5",
28812
- markerWidth: "6",
28813
- markerHeight: "6",
28814
- orient: "auto",
28815
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", {
28816
- d: "M 0 0 L 10 5 L 0 10 z",
28817
- fill: defaultLinkColor
28818
- })
28819
- }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
28820
- ref: gRef,
28821
- children: [
28822
- initialLinks.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkItem_default, {
28823
- link,
28824
- onClick: onLinkClick,
28825
- defaultWidth: defaultLinkWidth,
28826
- showLabel: showLinkLabels,
28827
- nodes
28828
- }, `link-${i}`)),
28829
- nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NodeItem_default, {
28830
- node,
28831
- isSelected: selectedNodeId === node.id,
28832
- isHovered: hoveredNodeId === node.id,
28833
- pinned: pinnedNodes.has(node.id),
28834
- defaultNodeSize,
28835
- defaultNodeColor,
28836
- showLabel: showNodeLabels,
28837
- onClick: onNodeClick,
28838
- onDoubleClick: handleNodeDoubleClick,
28839
- onMouseEnter: (n) => onNodeHover?.(n),
28840
- onMouseLeave: () => onNodeHover?.(null),
28841
- onMouseDown: handleDragStart
28842
- }, node.id)),
28843
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PackageBoundaries, { packageBounds: packageBounds || {} })
28844
- ]
28845
- })]
28993
+ className,
28994
+ nodes,
28995
+ links: initialLinks,
28996
+ pinnedNodes,
28997
+ selectedNodeId,
28998
+ hoveredNodeId,
28999
+ defaultNodeColor,
29000
+ defaultNodeSize,
29001
+ defaultLinkColor,
29002
+ defaultLinkWidth,
29003
+ showNodeLabels,
29004
+ showLinkLabels,
29005
+ onNodeClick,
29006
+ onNodeHover,
29007
+ onLinkClick,
29008
+ packageBounds,
29009
+ handleNodeDoubleClick,
29010
+ handleDragStart,
29011
+ restart,
29012
+ setPinnedNodes
28846
29013
  });
28847
29014
  });
28848
29015
  ForceDirectedGraph.displayName = "ForceDirectedGraph";
28849
- var GraphControls = ({ dragEnabled = true, onDragToggle, manualLayout = false, onManualLayoutToggle, onPinAll, onUnpinAll, onReset, onFitView, pinnedCount = 0, totalNodes = 0, visible = true, position = "top-left", className }) => {
28850
- if (!visible) return null;
28851
- const positionClasses = {
28852
- "top-left": "top-4 left-4",
28853
- "top-right": "top-4 right-4",
28854
- "bottom-left": "bottom-4 left-4",
28855
- "bottom-right": "bottom-4 right-4"
28856
- };
28857
- const ControlButton = ({ onClick, active = false, icon, label, disabled = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28858
- className: "relative group",
28859
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
28860
- onClick,
28861
- disabled,
28862
- className: cn("p-2 rounded-lg transition-all duration-200", active ? "bg-blue-500 text-white shadow-md hover:bg-blue-600" : "bg-gray-100 text-gray-700 hover:bg-gray-200", disabled && "opacity-50 cursor-not-allowed hover:bg-gray-100", "dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 dark:active:bg-blue-600"),
28863
- title: label,
28864
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
28865
- className: "text-lg",
28866
- children: icon
28867
- })
28868
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
28869
- className: "absolute left-full ml-2 px-2 py-1 bg-gray-900 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50",
28870
- children: label
28871
- })]
28872
- });
28873
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28874
- className: cn("fixed z-40 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-2 border border-gray-200 dark:border-gray-700", positionClasses[position], className),
28875
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28876
- className: "flex flex-col gap-2",
28877
- children: [
28878
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28879
- onClick: () => onDragToggle?.(!dragEnabled),
28880
- active: dragEnabled,
28881
- icon: "✋",
28882
- label: dragEnabled ? "Drag enabled" : "Drag disabled"
28883
- }),
28884
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28885
- onClick: () => onManualLayoutToggle?.(!manualLayout),
28886
- active: manualLayout,
28887
- icon: "🔧",
28888
- label: manualLayout ? "Manual layout: ON (drag freely)" : "Manual layout: OFF (forces active)"
28889
- }),
28890
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-8 h-px bg-gray-300 dark:bg-gray-600 mx-auto my-1" }),
28891
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28892
- className: "flex gap-1",
28893
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28894
- onClick: () => onPinAll?.(),
28895
- disabled: totalNodes === 0,
28896
- icon: "📌",
28897
- label: `Pin all nodes (${totalNodes})`
28898
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28899
- onClick: () => onUnpinAll?.(),
28900
- disabled: pinnedCount === 0,
28901
- icon: "📍",
28902
- label: `Unpin all (${pinnedCount} pinned)`
28903
- })]
28904
- }),
28905
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-8 h-px bg-gray-300 dark:bg-gray-600 mx-auto my-1" }),
28906
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28907
- onClick: () => onFitView?.(),
28908
- disabled: totalNodes === 0,
28909
- icon: "🎯",
28910
- label: "Fit all nodes in view"
28911
- }),
28912
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, {
28913
- onClick: () => onReset?.(),
28914
- disabled: totalNodes === 0,
28915
- icon: "↺",
28916
- label: "Reset to auto-layout"
28917
- })
28918
- ]
28919
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28920
- className: "mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-600 dark:text-gray-400",
28921
- children: [
28922
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28923
- className: "whitespace-nowrap",
28924
- children: [
28925
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Nodes:" }),
28926
- " ",
28927
- totalNodes
28928
- ]
28929
- }),
28930
- pinnedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28931
- className: "whitespace-nowrap",
28932
- children: [
28933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Pinned:" }),
28934
- " ",
28935
- pinnedCount
28936
- ]
28937
- }),
28938
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
28939
- className: "mt-2 text-gray-500 dark:text-gray-500 leading-snug",
28940
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Tips:" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", {
28941
- className: "mt-1 ml-1 space-y-0.5",
28942
- children: [
28943
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "• Drag nodes to reposition" }),
28944
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "• Double-click to pin/unpin" }),
28945
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "• Double-click canvas to unpin all" }),
28946
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: "• Scroll to zoom" })
28947
- ]
28948
- })]
28949
- })
28950
- ]
28951
- })]
28952
- });
28953
- };
28954
- GraphControls.displayName = "GraphControls";
28955
29016
  //#endregion
28956
29017
  //#region src/components/LegendPanel.tsx
28957
29018
  function LegendItemWithToggle({ color, label, isGlow = false, isLine = false, colors, isVisible, onToggle }) {
@@ -29286,4 +29347,4 @@ function App() {
29286
29347
  (0, import_client.createRoot)(document.getElementById("root")).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, {}) }));
29287
29348
  //#endregion
29288
29349
 
29289
- //# sourceMappingURL=index-ZdEvYTav.js.map
29350
+ //# sourceMappingURL=index-CkabxYJ5.js.map