@aiready/visualizer 0.6.14 → 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,67 @@ function superRefine(fn) {
24895
24895
  return /* @__PURE__ */ _superRefine(fn);
24896
24896
  }
24897
24897
  //#endregion
24898
- //#region ../../core/dist/chunk-CGOS2J6T.mjs
24898
+ //#region ../../core/dist/chunk-SQHS6PFL.mjs
24899
+ var LeadSource = /* @__PURE__ */ ((LeadSource2) => {
24900
+ LeadSource2["ClawMoreHero"] = "clawmore-hero";
24901
+ LeadSource2["ClawMoreWaitlist"] = "clawmore-waitlist";
24902
+ LeadSource2["ClawMoreBeta"] = "clawmore-beta";
24903
+ LeadSource2["AiReadyPlatform"] = "aiready-platform";
24904
+ return LeadSource2;
24905
+ })(LeadSource || {});
24906
+ var LeadSourceSchema = nativeEnum(LeadSource);
24907
+ object({
24908
+ id: string(),
24909
+ email: string().email(),
24910
+ name: string().min(1),
24911
+ interest: string().default("General"),
24912
+ notes: string().optional(),
24913
+ timestamp: string().datetime(),
24914
+ source: LeadSourceSchema,
24915
+ status: _enum([
24916
+ "new",
24917
+ "contacted",
24918
+ "qualified",
24919
+ "converted",
24920
+ "archived"
24921
+ ]).default("new")
24922
+ }).omit({
24923
+ id: true,
24924
+ timestamp: true,
24925
+ status: true
24926
+ });
24927
+ object({
24928
+ id: string(),
24929
+ accountId: string(),
24930
+ userId: string(),
24931
+ stripeSubscriptionId: string(),
24932
+ tokenStrategy: _enum(["managed", "byok"]).default("managed"),
24933
+ byokConfig: object({
24934
+ openaiKey: string().optional(),
24935
+ anthropicKey: string().optional(),
24936
+ openrouterKey: string().optional()
24937
+ }).optional(),
24938
+ baseFeeCents: number().default(2900),
24939
+ includedComputeCents: number().default(1500),
24940
+ includedTokenCents: number().default(500),
24941
+ prepaidTokenBalanceCents: number().default(0),
24942
+ currentMonthlyTokenSpendCents: number().default(0),
24943
+ status: _enum([
24944
+ "provisioning",
24945
+ "active",
24946
+ "warning",
24947
+ "quarantined",
24948
+ "suspended"
24949
+ ]).default("provisioning"),
24950
+ lastCostSyncAt: string().datetime().optional(),
24951
+ region: string().default("ap-southeast-2"),
24952
+ alertThresholds: array$1(number()).default([
24953
+ 50,
24954
+ 80,
24955
+ 100,
24956
+ 150
24957
+ ])
24958
+ });
24899
24959
  var Severity = /* @__PURE__ */ ((Severity2) => {
24900
24960
  Severity2["Critical"] = "critical";
24901
24961
  Severity2["Major"] = "major";
@@ -25069,66 +25129,6 @@ object({
25069
25129
  }).optional()
25070
25130
  }).optional()
25071
25131
  }).catchall(any());
25072
- var LeadSource = /* @__PURE__ */ ((LeadSource2) => {
25073
- LeadSource2["ClawMoreHero"] = "clawmore-hero";
25074
- LeadSource2["ClawMoreWaitlist"] = "clawmore-waitlist";
25075
- LeadSource2["ClawMoreBeta"] = "clawmore-beta";
25076
- LeadSource2["AiReadyPlatform"] = "aiready-platform";
25077
- return LeadSource2;
25078
- })(LeadSource || {});
25079
- var LeadSourceSchema = nativeEnum(LeadSource);
25080
- object({
25081
- id: string(),
25082
- email: string().email(),
25083
- name: string().min(1),
25084
- interest: string().default("General"),
25085
- notes: string().optional(),
25086
- timestamp: string().datetime(),
25087
- source: LeadSourceSchema,
25088
- status: _enum([
25089
- "new",
25090
- "contacted",
25091
- "qualified",
25092
- "converted",
25093
- "archived"
25094
- ]).default("new")
25095
- }).omit({
25096
- id: true,
25097
- timestamp: true,
25098
- status: true
25099
- });
25100
- object({
25101
- id: string(),
25102
- accountId: string(),
25103
- userId: string(),
25104
- stripeSubscriptionId: string(),
25105
- tokenStrategy: _enum(["managed", "byok"]).default("managed"),
25106
- byokConfig: object({
25107
- openaiKey: string().optional(),
25108
- anthropicKey: string().optional(),
25109
- openrouterKey: string().optional()
25110
- }).optional(),
25111
- baseFeeCents: number().default(2900),
25112
- includedComputeCents: number().default(1500),
25113
- includedTokenCents: number().default(500),
25114
- prepaidTokenBalanceCents: number().default(0),
25115
- currentMonthlyTokenSpendCents: number().default(0),
25116
- status: _enum([
25117
- "provisioning",
25118
- "active",
25119
- "warning",
25120
- "quarantined",
25121
- "suspended"
25122
- ]).default("provisioning"),
25123
- lastCostSyncAt: string().datetime().optional(),
25124
- region: string().default("ap-southeast-2"),
25125
- alertThresholds: array$1(number()).default([
25126
- 50,
25127
- 80,
25128
- 100,
25129
- 150
25130
- ])
25131
- });
25132
25132
  //#endregion
25133
25133
  //#region ../../../node_modules/.pnpm/d3-dispatch@3.0.1/node_modules/d3-dispatch/src/dispatch.js
25134
25134
  var noop = { value: () => {} };
@@ -28335,182 +28335,16 @@ function ScoreCircle({ score, progress: customProgress, isInView = true, size =
28335
28335
  });
28336
28336
  }
28337
28337
  (0, import_react.createContext)(void 0);
28338
- var NodeItem = ({ node, isSelected, isHovered, pinned, defaultNodeSize, defaultNodeColor, showLabel = true, onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseDown }) => {
28339
- const nodeSize = node.size || defaultNodeSize;
28340
- const nodeColor = node.color || defaultNodeColor;
28341
- const x = node.x ?? 0;
28342
- const y = node.y ?? 0;
28343
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
28344
- className: "cursor-pointer node",
28345
- "data-id": node.id,
28346
- transform: `translate(${x},${y})`,
28347
- onClick: () => onClick?.(node),
28348
- onDoubleClick: (e) => onDoubleClick?.(e, node),
28349
- onMouseEnter: () => onMouseEnter?.(node),
28350
- onMouseLeave: () => onMouseLeave?.(),
28351
- onMouseDown: (e) => onMouseDown?.(e, node),
28352
- children: [
28353
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28354
- r: nodeSize,
28355
- fill: nodeColor,
28356
- stroke: isSelected ? "#000" : isHovered ? "#666" : "none",
28357
- strokeWidth: pinned ? 3 : isSelected ? 2.5 : isHovered ? 2 : 1.5,
28358
- opacity: isHovered || isSelected ? 1 : .9
28359
- }),
28360
- pinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28361
- r: nodeSize + 4,
28362
- fill: "none",
28363
- stroke: "#ff6b6b",
28364
- strokeWidth: 1,
28365
- opacity: .5,
28366
- className: "pointer-events-none"
28367
- }),
28368
- showLabel && node.label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28369
- y: nodeSize + 15,
28370
- fill: "#333",
28371
- fontSize: "12",
28372
- textAnchor: "middle",
28373
- dominantBaseline: "middle",
28374
- pointerEvents: "none",
28375
- className: "select-none",
28376
- children: node.label
28377
- })
28378
- ]
28379
- }, node.id);
28380
- };
28381
- var NodeItem_default = NodeItem;
28382
- var LinkItem = ({ link, onClick, defaultWidth, showLabel = true, nodes = [] }) => {
28383
- const src = link.source?.id ?? (typeof link.source === "string" ? link.source : void 0);
28384
- const tgt = link.target?.id ?? (typeof link.target === "string" ? link.target : void 0);
28385
- const getNodePosition = (nodeOrId) => {
28386
- if (typeof nodeOrId === "object" && nodeOrId !== null) {
28387
- const node = nodeOrId;
28388
- return {
28389
- x: node.x ?? 0,
28390
- y: node.y ?? 0
28391
- };
28392
- } else if (typeof nodeOrId === "string") {
28393
- const found = nodes.find((n) => n.id === nodeOrId);
28394
- if (found) return {
28395
- x: found.x ?? 0,
28396
- y: found.y ?? 0
28397
- };
28398
- }
28399
- return null;
28400
- };
28401
- const sourcePos = getNodePosition(link.source);
28402
- const targetPos = getNodePosition(link.target);
28403
- if (!sourcePos || !targetPos) return null;
28404
- const midX = (sourcePos.x + targetPos.x) / 2;
28405
- const midY = (sourcePos.y + targetPos.y) / 2;
28406
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", {
28407
- x1: sourcePos.x,
28408
- y1: sourcePos.y,
28409
- x2: targetPos.x,
28410
- y2: targetPos.y,
28411
- "data-source": src,
28412
- "data-target": tgt,
28413
- stroke: link.color,
28414
- strokeWidth: link.width ?? defaultWidth ?? 1,
28415
- opacity: .6,
28416
- className: "cursor-pointer transition-opacity hover:opacity-100",
28417
- onClick: () => onClick?.(link)
28418
- }), showLabel && link.label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28419
- x: midX,
28420
- y: midY,
28421
- fill: "#666",
28422
- fontSize: "10",
28423
- textAnchor: "middle",
28424
- dominantBaseline: "middle",
28425
- pointerEvents: "none",
28426
- children: link.label
28427
- })] });
28428
- };
28429
- 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
- var PackageBoundaries = ({ packageBounds }) => {
28444
- if (!packageBounds || Object.keys(packageBounds).length === 0) return null;
28445
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", {
28446
- className: "package-boundaries",
28447
- pointerEvents: "none",
28448
- children: Object.entries(packageBounds).map(([pid, b]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28449
- cx: b.x,
28450
- cy: b.y,
28451
- r: b.r,
28452
- fill: PACKAGE_BOUNDARY_FILL,
28453
- stroke: PACKAGE_BOUNDARY_STROKE,
28454
- strokeWidth: PACKAGE_BOUNDARY_STROKE_WIDTH,
28455
- strokeDasharray: PACKAGE_BOUNDARY_DASH,
28456
- opacity: .9
28457
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28458
- x: b.x,
28459
- y: Math.max(12, b.y - b.r + 14),
28460
- fill: PACKAGE_LABEL_COLOR,
28461
- fontSize: PACKAGE_LABEL_FONT_SIZE,
28462
- textAnchor: "middle",
28463
- pointerEvents: "none",
28464
- children: pid.replace(/^pkg:/, "")
28465
- })] }, pid))
28466
- });
28467
- };
28468
- 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
- });
28338
+ function pinNode(node) {
28339
+ node.fx = node.x;
28340
+ node.fy = node.y;
28480
28341
  }
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
- });
28342
+ function unpinNode(node) {
28343
+ node.fx = null;
28344
+ node.fy = null;
28506
28345
  }
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
- });
28346
+ function unpinAllNodes(nodes) {
28347
+ nodes.forEach(unpinNode);
28514
28348
  }
28515
28349
  function useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef) {
28516
28350
  (0, import_react.useEffect)(() => {
@@ -28576,42 +28410,103 @@ function useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNode
28576
28410
  onDragEnd
28577
28411
  ]);
28578
28412
  }
28579
- function pinNode(node) {
28580
- node.fx = node.x;
28581
- node.fy = node.y;
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
+ };
28582
28448
  }
28583
- function unpinNode(node) {
28584
- node.fx = null;
28585
- node.fy = null;
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
+ });
28586
28473
  }
28587
- function unpinAllNodes(nodes) {
28588
- nodes.forEach(unpinNode);
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
+ });
28589
28499
  }
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) => {
28591
- const svgRef = (0, import_react.useRef)(null);
28592
- const gRef = (0, import_react.useRef)(null);
28593
- const [transform, setTransform] = (0, import_react.useState)({
28594
- k: 1,
28595
- x: 0,
28596
- y: 0
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
+ }
28597
28506
  });
28598
- const transformRef = (0, import_react.useRef)(transform);
28599
- const dragNodeRef = (0, import_react.useRef)(null);
28600
- const dragActiveRef = (0, import_react.useRef)(false);
28601
- const [pinnedNodes, setPinnedNodes] = (0, import_react.useState)(/* @__PURE__ */ new Set());
28602
- const internalDragEnabledRef = (0, import_react.useRef)(enableDrag);
28603
- const [layout, setLayout] = (0, import_react.useState)(externalLayout || "force");
28604
- (0, import_react.useEffect)(() => {
28605
- if (externalLayout && externalLayout !== layout) setLayout(externalLayout);
28606
- }, [externalLayout, layout]);
28607
- const handleLayoutChange = (0, import_react.useCallback)((newLayout) => {
28608
- setLayout(newLayout);
28609
- onLayoutChange?.(newLayout);
28610
- }, [onLayoutChange]);
28611
- (0, import_react.useEffect)(() => {
28612
- internalDragEnabledRef.current = enableDrag;
28613
- }, [enableDrag]);
28614
- const nodes = import_react.useMemo(() => {
28507
+ }
28508
+ function useGraphLayout(initialNodes, width, height, layout, restart) {
28509
+ const nodes = (0, import_react.useMemo)(() => {
28615
28510
  if (!initialNodes || !initialNodes.length) return initialNodes;
28616
28511
  const copy = initialNodes.map((n) => ({ ...n }));
28617
28512
  if (layout === "circular") applyCircularLayout(copy, width, height);
@@ -28624,9 +28519,6 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28624
28519
  height,
28625
28520
  layout
28626
28521
  ]);
28627
- const restart = import_react.useCallback(() => {}, []);
28628
- const stop = import_react.useCallback(() => {}, []);
28629
- const setForcesEnabled = import_react.useCallback((enabled) => {}, []);
28630
28522
  (0, import_react.useEffect)(() => {
28631
28523
  if (!nodes || nodes.length === 0) return;
28632
28524
  if (layout === "circular") applyCircularLayout(nodes, width, height);
@@ -28639,16 +28531,18 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28639
28531
  height,
28640
28532
  restart
28641
28533
  ]);
28642
- (0, import_react.useEffect)(() => {
28643
- if (manualLayout || pinnedNodes.size > 0) setForcesEnabled(false);
28644
- else setForcesEnabled(true);
28645
- }, [
28646
- manualLayout,
28647
- pinnedNodes,
28648
- setForcesEnabled
28649
- ]);
28650
- (0, import_react.useImperativeHandle)(ref, () => ({
28651
- pinAll: () => {
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)(() => {
28652
28546
  const newPinned = /* @__PURE__ */ new Set();
28653
28547
  nodes.forEach((node) => {
28654
28548
  pinNode(node);
@@ -28656,18 +28550,30 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28656
28550
  });
28657
28551
  setPinnedNodes(newPinned);
28658
28552
  restart();
28659
- },
28660
- unpinAll: () => {
28553
+ }, [
28554
+ nodes,
28555
+ setPinnedNodes,
28556
+ restart
28557
+ ]),
28558
+ unpinAll: (0, import_react.useCallback)(() => {
28661
28559
  unpinAllNodes(nodes);
28662
28560
  setPinnedNodes(/* @__PURE__ */ new Set());
28663
28561
  restart();
28664
- },
28665
- resetLayout: () => {
28562
+ }, [
28563
+ nodes,
28564
+ setPinnedNodes,
28565
+ restart
28566
+ ]),
28567
+ resetLayout: (0, import_react.useCallback)(() => {
28666
28568
  unpinAllNodes(nodes);
28667
28569
  setPinnedNodes(/* @__PURE__ */ new Set());
28668
28570
  restart();
28669
- },
28670
- fitView: () => {
28571
+ }, [
28572
+ nodes,
28573
+ setPinnedNodes,
28574
+ restart
28575
+ ]),
28576
+ fitView: (0, import_react.useCallback)(() => {
28671
28577
  if (!svgRef.current || !nodes.length) return;
28672
28578
  let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
28673
28579
  nodes.forEach((node) => {
@@ -28689,111 +28595,233 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28689
28595
  svg.transition().duration(TRANSITION_DURATION_MS).call(zoom_default().transform, newTransform);
28690
28596
  setTransform(newTransform);
28691
28597
  }
28692
- },
28693
- getPinnedNodes: () => Array.from(pinnedNodes),
28694
- setDragMode: (enabled) => {
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) => {
28695
28608
  internalDragEnabledRef.current = enabled;
28696
- },
28697
- setLayout: (newLayout) => handleLayoutChange(newLayout),
28698
- getLayout: () => layout
28699
- }), [
28700
- nodes,
28701
- pinnedNodes,
28702
- restart,
28703
- width,
28704
- height,
28705
- layout,
28706
- handleLayoutChange,
28707
- setForcesEnabled
28708
- ]);
28709
- (0, import_react.useEffect)(() => {
28710
- if (typeof onManualLayoutChange === "function") onManualLayoutChange(manualLayout);
28711
- }, [manualLayout, onManualLayoutChange]);
28712
- useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
28713
- useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, () => {
28714
- setForcesEnabled(true);
28715
- restart();
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
+ })]
28716
28703
  });
28717
- (0, import_react.useEffect)(() => {
28718
- if (!gRef.current) return;
28719
- const g = select_default$1(gRef.current);
28720
- 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})`);
28724
- });
28725
- g.selectAll("line").each(function() {
28726
- const l = select_default$1(this).datum();
28727
- 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);
28732
- });
28733
- }, [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]);
28744
- (0, import_react.useEffect)(() => {
28745
- if (!gRef.current || !enableDrag) return;
28746
- const g = select_default$1(gRef.current);
28747
- const dragBehavior = drag_default().on("start", (event) => {
28748
- const id = ((event.sourceEvent && event.sourceEvent.target || event.target).closest?.("g.node"))?.getAttribute("data-id");
28749
- if (!id || !internalDragEnabledRef.current) return;
28750
- const node = nodes.find((n) => n.id === id);
28751
- if (!node) return;
28752
- if (!event.active) restart();
28753
- dragActiveRef.current = true;
28754
- dragNodeRef.current = node;
28755
- pinNode(node);
28756
- setPinnedNodes((prev) => /* @__PURE__ */ new Set([...prev, node.id]));
28757
- }).on("drag", (event) => {
28758
- if (!dragActiveRef.current || !dragNodeRef.current) return;
28759
- const svg = svgRef.current;
28760
- if (!svg) return;
28761
- const rect = svg.getBoundingClientRect();
28762
- dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
28763
- dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
28764
- }).on("end", () => {
28765
- setForcesEnabled(true);
28766
- restart();
28767
- });
28768
- g.selectAll("g.node").call(dragBehavior);
28769
- return () => {
28770
- g.selectAll("g.node").on(".drag", null);
28771
- };
28772
- }, [
28773
- gRef,
28774
- enableDrag,
28775
- nodes,
28776
- transform,
28777
- restart,
28778
- setForcesEnabled,
28779
- internalDragEnabledRef
28780
- ]);
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
- });
28704
+ };
28705
+ GraphControls.displayName = "GraphControls";
28706
+ var NodeItem = ({ node, isSelected, isHovered, pinned, defaultNodeSize, defaultNodeColor, showLabel = true, onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseDown }) => {
28707
+ const nodeSize = node.size || defaultNodeSize;
28708
+ const nodeColor = node.color || defaultNodeColor;
28709
+ const x = node.x ?? 0;
28710
+ const y = node.y ?? 0;
28711
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
28712
+ className: "cursor-pointer node",
28713
+ "data-id": node.id,
28714
+ transform: `translate(${x},${y})`,
28715
+ onClick: () => onClick?.(node),
28716
+ onDoubleClick: (e) => onDoubleClick?.(e, node),
28717
+ onMouseEnter: () => onMouseEnter?.(node),
28718
+ onMouseLeave: () => onMouseLeave?.(),
28719
+ onMouseDown: (e) => onMouseDown?.(e, node),
28720
+ children: [
28721
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28722
+ r: nodeSize,
28723
+ fill: nodeColor,
28724
+ stroke: isSelected ? "#000" : isHovered ? "#666" : "none",
28725
+ strokeWidth: pinned ? 3 : isSelected ? 2.5 : isHovered ? 2 : 1.5,
28726
+ opacity: isHovered || isSelected ? 1 : .9
28727
+ }),
28728
+ pinned && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28729
+ r: nodeSize + 4,
28730
+ fill: "none",
28731
+ stroke: "#ff6b6b",
28732
+ strokeWidth: 1,
28733
+ opacity: .5,
28734
+ className: "pointer-events-none"
28735
+ }),
28736
+ showLabel && node.label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28737
+ y: nodeSize + 15,
28738
+ fill: "#333",
28739
+ fontSize: "12",
28740
+ textAnchor: "middle",
28741
+ dominantBaseline: "middle",
28742
+ pointerEvents: "none",
28743
+ className: "select-none",
28744
+ children: node.label
28745
+ })
28746
+ ]
28747
+ }, node.id);
28748
+ };
28749
+ var NodeItem_default = NodeItem;
28750
+ var LinkItem = ({ link, onClick, defaultWidth, showLabel = true, nodes = [] }) => {
28751
+ const src = link.source?.id ?? (typeof link.source === "string" ? link.source : void 0);
28752
+ const tgt = link.target?.id ?? (typeof link.target === "string" ? link.target : void 0);
28753
+ const getNodePosition = (nodeOrId) => {
28754
+ if (typeof nodeOrId === "object" && nodeOrId !== null) {
28755
+ const node = nodeOrId;
28756
+ return {
28757
+ x: node.x ?? 0,
28758
+ y: node.y ?? 0
28759
+ };
28760
+ } else if (typeof nodeOrId === "string") {
28761
+ const found = nodes.find((n) => n.id === nodeOrId);
28762
+ if (found) return {
28763
+ x: found.x ?? 0,
28764
+ y: found.y ?? 0
28765
+ };
28794
28766
  }
28795
- restart();
28796
- }, [enableDrag, restart]);
28767
+ return null;
28768
+ };
28769
+ const sourcePos = getNodePosition(link.source);
28770
+ const targetPos = getNodePosition(link.target);
28771
+ if (!sourcePos || !targetPos) return null;
28772
+ const midX = (sourcePos.x + targetPos.x) / 2;
28773
+ const midY = (sourcePos.y + targetPos.y) / 2;
28774
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", {
28775
+ x1: sourcePos.x,
28776
+ y1: sourcePos.y,
28777
+ x2: targetPos.x,
28778
+ y2: targetPos.y,
28779
+ "data-source": src,
28780
+ "data-target": tgt,
28781
+ stroke: link.color,
28782
+ strokeWidth: link.width ?? defaultWidth ?? 1,
28783
+ opacity: .6,
28784
+ className: "cursor-pointer transition-opacity hover:opacity-100",
28785
+ onClick: () => onClick?.(link)
28786
+ }), showLabel && link.label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28787
+ x: midX,
28788
+ y: midY,
28789
+ fill: "#666",
28790
+ fontSize: "10",
28791
+ textAnchor: "middle",
28792
+ dominantBaseline: "middle",
28793
+ pointerEvents: "none",
28794
+ children: link.label
28795
+ })] });
28796
+ };
28797
+ var LinkItem_default = LinkItem;
28798
+ var PackageBoundaries = ({ packageBounds }) => {
28799
+ if (!packageBounds || Object.keys(packageBounds).length === 0) return null;
28800
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", {
28801
+ className: "package-boundaries",
28802
+ pointerEvents: "none",
28803
+ children: Object.entries(packageBounds).map(([pid, b]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", {
28804
+ cx: b.x,
28805
+ cy: b.y,
28806
+ r: b.r,
28807
+ fill: PACKAGE_BOUNDARY_FILL,
28808
+ stroke: PACKAGE_BOUNDARY_STROKE,
28809
+ strokeWidth: PACKAGE_BOUNDARY_STROKE_WIDTH,
28810
+ strokeDasharray: PACKAGE_BOUNDARY_DASH,
28811
+ opacity: .9
28812
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", {
28813
+ x: b.x,
28814
+ y: Math.max(12, b.y - b.r + 14),
28815
+ fill: PACKAGE_LABEL_COLOR,
28816
+ fontSize: PACKAGE_LABEL_FONT_SIZE,
28817
+ textAnchor: "middle",
28818
+ pointerEvents: "none",
28819
+ children: pid.replace(/^pkg:/, "")
28820
+ })] }, pid))
28821
+ });
28822
+ };
28823
+ PackageBoundaries.displayName = "PackageBoundaries";
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 }) => {
28797
28825
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", {
28798
28826
  ref: svgRef,
28799
28827
  width,
@@ -28819,7 +28847,7 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28819
28847
  }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", {
28820
28848
  ref: gRef,
28821
28849
  children: [
28822
- initialLinks.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkItem_default, {
28850
+ links.map((link, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkItem_default, {
28823
28851
  link,
28824
28852
  onClick: onLinkClick,
28825
28853
  defaultWidth: defaultLinkWidth,
@@ -28844,114 +28872,147 @@ var ForceDirectedGraph = (0, import_react.forwardRef)(({ nodes: initialNodes, li
28844
28872
  ]
28845
28873
  })]
28846
28874
  });
28847
- });
28848
- 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
- })]
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) => {
28877
+ const svgRef = (0, import_react.useRef)(null);
28878
+ const gRef = (0, import_react.useRef)(null);
28879
+ const [transform, setTransform] = (0, import_react.useState)({
28880
+ k: 1,
28881
+ x: 0,
28882
+ y: 0
28872
28883
  });
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
- })]
28884
+ const transformRef = (0, import_react.useRef)(transform);
28885
+ const dragNodeRef = (0, import_react.useRef)(null);
28886
+ const dragActiveRef = (0, import_react.useRef)(false);
28887
+ const [pinnedNodes, setPinnedNodes] = (0, import_react.useState)(/* @__PURE__ */ new Set());
28888
+ const internalDragEnabledRef = (0, import_react.useRef)(enableDrag);
28889
+ const [layout, setLayout] = (0, import_react.useState)(externalLayout || "force");
28890
+ (0, import_react.useEffect)(() => {
28891
+ if (externalLayout && externalLayout !== layout) setLayout(externalLayout);
28892
+ }, [externalLayout, layout]);
28893
+ const handleLayoutChange = (0, import_react.useCallback)((newLayout) => {
28894
+ setLayout(newLayout);
28895
+ onLayoutChange?.(newLayout);
28896
+ }, [onLayoutChange]);
28897
+ (0, import_react.useEffect)(() => {
28898
+ internalDragEnabledRef.current = enableDrag;
28899
+ }, [enableDrag]);
28900
+ const { restart, stop, setForcesEnabled } = useSimulationControls();
28901
+ const { nodes } = useGraphLayout(initialNodes, width, height, layout, restart);
28902
+ (0, import_react.useEffect)(() => {
28903
+ setForcesEnabled(!(manualLayout || pinnedNodes.size > 0));
28904
+ }, [
28905
+ manualLayout,
28906
+ pinnedNodes,
28907
+ setForcesEnabled
28908
+ ]);
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
28922
+ }), [
28923
+ nodes,
28924
+ pinnedNodes,
28925
+ restart,
28926
+ width,
28927
+ height,
28928
+ layout,
28929
+ handleLayoutChange,
28930
+ setForcesEnabled
28931
+ ]);
28932
+ (0, import_react.useEffect)(() => {
28933
+ onManualLayoutChange?.(manualLayout);
28934
+ }, [manualLayout, onManualLayoutChange]);
28935
+ useGraphZoom(svgRef, gRef, enableZoom, setTransform, transformRef);
28936
+ useWindowDrag(enableDrag, svgRef, transformRef, dragActiveRef, dragNodeRef, () => {
28937
+ setForcesEnabled(true);
28938
+ restart();
28952
28939
  });
28953
- };
28954
- GraphControls.displayName = "GraphControls";
28940
+ (0, import_react.useEffect)(() => {
28941
+ if (!gRef.current) return;
28942
+ const g = select_default$1(gRef.current);
28943
+ g.selectAll("g.node").each(function() {
28944
+ const d = select_default$1(this).datum();
28945
+ if (d) select_default$1(this).attr("transform", `translate(${d.x || 0},${d.y || 0})`);
28946
+ });
28947
+ g.selectAll("line").each(function() {
28948
+ const l = select_default$1(this).datum();
28949
+ if (!l) return;
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);
28953
+ });
28954
+ }, [nodes, initialLinks]);
28955
+ const { handleDragStart, handleNodeDoubleClick } = useNodeInteractions(enableDrag, nodes, pinnedNodes, setPinnedNodes, restart, stop);
28956
+ (0, import_react.useEffect)(() => {
28957
+ if (!gRef.current || !enableDrag) return;
28958
+ const g = select_default$1(gRef.current);
28959
+ const dragBehavior = drag_default().on("start", (event) => {
28960
+ const id = (event.sourceEvent?.target || event.target).closest?.("g.node")?.getAttribute("data-id");
28961
+ if (!id || !internalDragEnabledRef.current) return;
28962
+ const node = nodes.find((n) => n.id === id);
28963
+ if (!node) return;
28964
+ if (!event.active) restart();
28965
+ dragActiveRef.current = true;
28966
+ dragNodeRef.current = node;
28967
+ }).on("drag", (event) => {
28968
+ if (!dragActiveRef.current || !dragNodeRef.current || !svgRef.current) return;
28969
+ const rect = svgRef.current.getBoundingClientRect();
28970
+ dragNodeRef.current.fx = (event.sourceEvent.clientX - rect.left - transform.x) / transform.k;
28971
+ dragNodeRef.current.fy = (event.sourceEvent.clientY - rect.top - transform.y) / transform.k;
28972
+ }).on("end", () => {
28973
+ setForcesEnabled(true);
28974
+ restart();
28975
+ });
28976
+ g.selectAll("g.node").call(dragBehavior);
28977
+ return () => {
28978
+ g.selectAll("g.node").on(".drag", null);
28979
+ };
28980
+ }, [
28981
+ gRef,
28982
+ enableDrag,
28983
+ nodes,
28984
+ transform,
28985
+ restart,
28986
+ setForcesEnabled
28987
+ ]);
28988
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GraphCanvas, {
28989
+ svgRef,
28990
+ gRef,
28991
+ width,
28992
+ height,
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
29013
+ });
29014
+ });
29015
+ ForceDirectedGraph.displayName = "ForceDirectedGraph";
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-Zc8kVQDw.js.map
29350
+ //# sourceMappingURL=index-CkabxYJ5.js.map