@petrarca/sonnet-graph 0.4.0 → 0.4.2

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.
package/dist/index.js CHANGED
@@ -436,7 +436,8 @@ var useGraphFilter = createGraphFilterStore();
436
436
 
437
437
  // src/stores/GraphExploration.ts
438
438
  import { create as create4 } from "zustand";
439
- import { devLog as devLog3 } from "@petrarca/sonnet-core";
439
+ import { devLog as devLog3, errorLog } from "@petrarca/sonnet-core";
440
+ import { toast } from "sonner";
440
441
  function collectReachable(startId, direction, allEdges, includeStart) {
441
442
  const nodes = /* @__PURE__ */ new Set();
442
443
  const edges = /* @__PURE__ */ new Set();
@@ -542,6 +543,23 @@ function ensureNodeLabelInFilters(enrichedNodes, nodeId, graphFilterStore) {
542
543
  graphFilterStore.getState().setNodeLabelFilters([...currentFilters, nodeLabel]);
543
544
  }
544
545
  }
546
+ function mergeExpandedLabelsIntoFilters(stores, enrichedNodes, nodeId) {
547
+ const graphFilterState = stores.graphFilter.getState();
548
+ const currentFilters = graphFilterState.nodeLabelFilters;
549
+ if (currentFilters.length === 0 || enrichedNodes.length === 0) return;
550
+ const newLabels = new Set(enrichedNodes.map((n) => n.label_));
551
+ const labelsToAdd = Array.from(newLabels).filter(
552
+ (label) => !currentFilters.includes(label)
553
+ );
554
+ if (labelsToAdd.length > 0) {
555
+ devLog3("[GraphExploration] Adding expanded node labels to active filters", {
556
+ nodeId,
557
+ labelsToAdd,
558
+ currentFilters
559
+ });
560
+ stores.graphFilter.getState().setNodeLabelFilters([...currentFilters, ...labelsToAdd]);
561
+ }
562
+ }
545
563
  function createGraphExplorationStore(deps) {
546
564
  const stores = deps;
547
565
  return create4()((set, get) => ({
@@ -841,10 +859,10 @@ function createGraphExplorationStore(deps) {
841
859
  isNodeExpanded: (nodeId) => {
842
860
  return get().expandedNodeIds.has(nodeId);
843
861
  },
844
- expandNode: async (nodeId) => {
845
- devLog3("[GraphExploration] expandNode", { nodeId });
862
+ expandNode: async (nodeId, options) => {
863
+ devLog3("[GraphExploration] expandNode", { nodeId, options });
846
864
  try {
847
- const result = await stores.expandNode(Number(nodeId));
865
+ const result = await stores.expandNode(Number(nodeId), options);
848
866
  devLog3("[GraphExploration] Expansion API result", {
849
867
  nodeId,
850
868
  newNodes: result.nodes.length,
@@ -866,25 +884,7 @@ function createGraphExplorationStore(deps) {
866
884
  );
867
885
  const enrichedEdges = enrichEdges(result.edges);
868
886
  stores.graphData.getState().mergeExpandedData(enrichedNodes, enrichedEdges);
869
- const graphFilterState = stores.graphFilter.getState();
870
- const currentFilters = graphFilterState.nodeLabelFilters;
871
- if (currentFilters.length > 0 && enrichedNodes.length > 0) {
872
- const newLabels = new Set(enrichedNodes.map((n) => n.label_));
873
- const labelsToAdd = Array.from(newLabels).filter(
874
- (label) => !currentFilters.includes(label)
875
- );
876
- if (labelsToAdd.length > 0) {
877
- devLog3(
878
- "[GraphExploration] Adding expanded node labels to active filters",
879
- {
880
- nodeId,
881
- labelsToAdd,
882
- currentFilters
883
- }
884
- );
885
- stores.graphFilter.getState().setNodeLabelFilters([...currentFilters, ...labelsToAdd]);
886
- }
887
- }
887
+ mergeExpandedLabelsIntoFilters(stores, enrichedNodes, nodeId);
888
888
  get().markNodeExpanded(nodeId);
889
889
  const currentState = get();
890
890
  if (currentState.isFocusMode) {
@@ -900,13 +900,19 @@ function createGraphExplorationStore(deps) {
900
900
  });
901
901
  set({ focusedNodeIds: newFocusedIds });
902
902
  }
903
+ if (result.has_more) {
904
+ toast.info(
905
+ `Showing first ${enrichedNodes.length} neighbors \u2014 more exist. Refine filters or increase the limit.`
906
+ );
907
+ }
903
908
  devLog3("[GraphExploration] Expansion complete", {
904
909
  nodeId,
905
910
  addedNodes: enrichedNodes.length,
906
- addedEdges: enrichedEdges.length
911
+ addedEdges: enrichedEdges.length,
912
+ hasMore: result.has_more
907
913
  });
908
914
  } catch (error) {
909
- devLog3("[GraphExploration] Expansion failed", { nodeId, error });
915
+ errorLog("[GraphExploration] Expansion failed", { nodeId, error });
910
916
  throw error;
911
917
  }
912
918
  },
@@ -1439,10 +1445,10 @@ function GraphRendererProvider({
1439
1445
 
1440
1446
  // src/components/rea/ReaGraph.tsx
1441
1447
  import {
1442
- useEffect as useEffect2,
1443
- useRef,
1444
- useState as useState3,
1445
- useCallback as useCallback2
1448
+ useEffect as useEffect3,
1449
+ useRef as useRef2,
1450
+ useState as useState5,
1451
+ useCallback as useCallback3
1446
1452
  } from "react";
1447
1453
  import { devLog as devLog8 } from "@petrarca/sonnet-core";
1448
1454
  import {
@@ -1662,16 +1668,201 @@ function NodeHoverInfo({ node }) {
1662
1668
  var NodeHoverInfo_default = NodeHoverInfo;
1663
1669
 
1664
1670
  // src/components/rea/ReaContextMenu.tsx
1665
- import React2 from "react";
1671
+ import React3, { useState as useState4 } from "react";
1666
1672
  import { devLog as devLog7 } from "@petrarca/sonnet-core";
1673
+
1674
+ // src/components/rea/ExpandOptionsPanel.tsx
1675
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState3 } from "react";
1676
+ import { Checkbox, Input, Label } from "@petrarca/sonnet-ui";
1667
1677
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1678
+ function ExpandOptionsPanel({
1679
+ nodeLabel,
1680
+ onExpand,
1681
+ onBack,
1682
+ getEdgeTypes
1683
+ }) {
1684
+ const [direction, setDirection] = useState3("both");
1685
+ const [limit, setLimit] = useState3(100);
1686
+ const [edgeTypes, setEdgeTypes] = useState3([]);
1687
+ const [selectedTypes, setSelectedTypes] = useState3([]);
1688
+ const [loading, setLoading] = useState3(!!getEdgeTypes);
1689
+ const [offset, setOffset] = useState3({ x: 0, y: 0 });
1690
+ const expandBtnRef = useRef(null);
1691
+ const dragRef = useRef(null);
1692
+ const onPointerDown = useCallback2(
1693
+ (e) => {
1694
+ if (e.target.closest("button")) return;
1695
+ e.preventDefault();
1696
+ e.target.setPointerCapture(e.pointerId);
1697
+ dragRef.current = {
1698
+ startX: e.clientX,
1699
+ startY: e.clientY,
1700
+ origX: offset.x,
1701
+ origY: offset.y
1702
+ };
1703
+ },
1704
+ [offset]
1705
+ );
1706
+ const onPointerMove = useCallback2((e) => {
1707
+ if (!dragRef.current) return;
1708
+ const dx = e.clientX - dragRef.current.startX;
1709
+ const dy = e.clientY - dragRef.current.startY;
1710
+ setOffset({ x: dragRef.current.origX + dx, y: dragRef.current.origY + dy });
1711
+ }, []);
1712
+ const onPointerUp = useCallback2(() => {
1713
+ dragRef.current = null;
1714
+ }, []);
1715
+ useEffect2(() => {
1716
+ if (!getEdgeTypes) return;
1717
+ setLoading(true);
1718
+ getEdgeTypes().then((types) => {
1719
+ setEdgeTypes(types);
1720
+ setSelectedTypes(types);
1721
+ }).finally(() => setLoading(false));
1722
+ }, [getEdgeTypes]);
1723
+ const handleToggleType = (type) => {
1724
+ setSelectedTypes(
1725
+ (prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]
1726
+ );
1727
+ };
1728
+ const handleExpand = () => {
1729
+ const options = {};
1730
+ if (direction !== "both") options.direction = direction;
1731
+ const clampedLimit = Math.max(1, Math.min(1e4, limit));
1732
+ if (clampedLimit !== 100) options.limit = clampedLimit;
1733
+ if (selectedTypes.length > 0 && selectedTypes.length < edgeTypes.length) {
1734
+ options.relTypes = selectedTypes;
1735
+ }
1736
+ onExpand(options);
1737
+ };
1738
+ const allSelected = selectedTypes.length === edgeTypes.length;
1739
+ const noneSelected = edgeTypes.length > 0 && selectedTypes.length === 0;
1740
+ useEffect2(() => {
1741
+ if (!loading && !noneSelected) {
1742
+ expandBtnRef.current?.focus();
1743
+ }
1744
+ }, [loading, noneSelected]);
1745
+ return /* @__PURE__ */ jsxs4(
1746
+ "div",
1747
+ {
1748
+ className: "bg-background border border-border rounded-md shadow-md p-2.5 min-w-48 space-y-2",
1749
+ style: { transform: `translate(${offset.x}px, ${offset.y}px)` },
1750
+ children: [
1751
+ /* @__PURE__ */ jsxs4(
1752
+ "div",
1753
+ {
1754
+ className: "flex items-center justify-between gap-2 cursor-grab active:cursor-grabbing select-none",
1755
+ onPointerDown,
1756
+ onPointerMove,
1757
+ onPointerUp,
1758
+ children: [
1759
+ /* @__PURE__ */ jsx5(
1760
+ "button",
1761
+ {
1762
+ className: "text-[11px] text-muted-foreground hover:text-foreground cursor-pointer",
1763
+ onClick: onBack,
1764
+ children: "Back"
1765
+ }
1766
+ ),
1767
+ /* @__PURE__ */ jsx5("span", { className: "text-[11px] font-medium text-foreground truncate max-w-32", children: nodeLabel })
1768
+ ]
1769
+ }
1770
+ ),
1771
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1772
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Direction" }),
1773
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-0.5", children: ["both", "out", "in"].map((dir) => /* @__PURE__ */ jsx5(
1774
+ "button",
1775
+ {
1776
+ className: `flex-1 px-2 py-1 text-[11px] capitalize rounded border cursor-pointer ${direction === dir ? "bg-primary/10 text-primary border-primary/40 font-medium" : "bg-background text-muted-foreground border-border hover:bg-muted"}`,
1777
+ onClick: () => setDirection(dir),
1778
+ children: dir
1779
+ },
1780
+ dir
1781
+ )) })
1782
+ ] }),
1783
+ !loading && edgeTypes.length > 1 && /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1784
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between", children: [
1785
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Rel Types" }),
1786
+ /* @__PURE__ */ jsx5(
1787
+ "button",
1788
+ {
1789
+ className: "text-[10px] text-muted-foreground hover:text-foreground cursor-pointer",
1790
+ onClick: () => setSelectedTypes(allSelected ? [] : [...edgeTypes]),
1791
+ children: allSelected ? "None" : "All"
1792
+ }
1793
+ )
1794
+ ] }),
1795
+ /* @__PURE__ */ jsx5("div", { className: "space-y-0.5 max-h-32 overflow-y-auto", children: edgeTypes.map((type) => /* @__PURE__ */ jsxs4(
1796
+ "label",
1797
+ {
1798
+ className: "flex items-center gap-1.5 text-[11px] cursor-pointer",
1799
+ children: [
1800
+ /* @__PURE__ */ jsx5(
1801
+ Checkbox,
1802
+ {
1803
+ checked: selectedTypes.includes(type),
1804
+ onCheckedChange: () => handleToggleType(type),
1805
+ className: "h-3 w-3"
1806
+ }
1807
+ ),
1808
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: type })
1809
+ ]
1810
+ },
1811
+ type
1812
+ )) })
1813
+ ] }),
1814
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
1815
+ /* @__PURE__ */ jsx5(Label, { className: "text-[11px] text-muted-foreground", children: "Limit" }),
1816
+ /* @__PURE__ */ jsx5(
1817
+ Input,
1818
+ {
1819
+ type: "number",
1820
+ min: 1,
1821
+ max: 1e4,
1822
+ value: limit,
1823
+ onChange: (e) => {
1824
+ const v = Number(e.target.value);
1825
+ if (!Number.isNaN(v)) setLimit(Math.max(1, Math.min(1e4, v)));
1826
+ },
1827
+ className: "h-6 text-[11px]"
1828
+ }
1829
+ )
1830
+ ] }),
1831
+ /* @__PURE__ */ jsx5(
1832
+ "button",
1833
+ {
1834
+ ref: expandBtnRef,
1835
+ className: "w-full py-1.5 text-[11px] font-medium rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer",
1836
+ onClick: handleExpand,
1837
+ disabled: loading || noneSelected,
1838
+ title: noneSelected ? "Select at least one relationship type" : void 0,
1839
+ children: "Expand"
1840
+ }
1841
+ )
1842
+ ]
1843
+ }
1844
+ );
1845
+ }
1846
+ var ExpandOptionsPanel_default = ExpandOptionsPanel;
1847
+
1848
+ // src/components/rea/ReaContextMenu.tsx
1849
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1668
1850
  function ReaContextMenu({
1669
1851
  data,
1670
1852
  onClose,
1671
1853
  elementType,
1672
1854
  useGraphExploration,
1673
- onCopyNode
1855
+ onCopyNode,
1856
+ getNodeEdgeTypes
1674
1857
  }) {
1858
+ const [showExpandPanel, setShowExpandPanel] = useState4(false);
1859
+ React3.useEffect(() => {
1860
+ const handleKey = (e) => {
1861
+ if (e.key === "Escape") onClose();
1862
+ };
1863
+ document.addEventListener("keydown", handleKey);
1864
+ return () => document.removeEventListener("keydown", handleKey);
1865
+ }, [onClose]);
1675
1866
  const handleFocus = () => {
1676
1867
  if (elementType === "node") {
1677
1868
  const nodeId = data.id;
@@ -1724,11 +1915,11 @@ function ReaContextMenu({
1724
1915
  }
1725
1916
  onClose();
1726
1917
  };
1727
- const handleExpand = () => {
1918
+ const handleExpandWithOptions = (options) => {
1728
1919
  if (elementType === "node") {
1729
1920
  const nodeId = data.id;
1730
- devLog7("[ReaContextMenu] Expand node:", nodeId);
1731
- useGraphExploration.getState().expandNode(nodeId);
1921
+ devLog7("[ReaContextMenu] Expand node with options:", nodeId, options);
1922
+ useGraphExploration.getState().expandNode(nodeId, options);
1732
1923
  }
1733
1924
  onClose();
1734
1925
  };
@@ -1763,7 +1954,7 @@ function ReaContextMenu({
1763
1954
  { label: "Hide Node", action: handleHideNode },
1764
1955
  { label: "Hide Incoming", action: handleHideIncoming },
1765
1956
  { label: "Hide Outgoing", action: handleHideOutgoing },
1766
- { label: "Expand", action: handleExpand }
1957
+ { label: "Expand...", action: () => setShowExpandPanel(true) }
1767
1958
  ];
1768
1959
  const edgeMenuItems = [
1769
1960
  { label: "Focus", action: handleFocus },
@@ -1773,23 +1964,46 @@ function ReaContextMenu({
1773
1964
  { label: "Hide Reverse Path", action: handleHideReversePath }
1774
1965
  ];
1775
1966
  const menuItems = elementType === "node" ? nodeMenuItems : edgeMenuItems;
1776
- return /* @__PURE__ */ jsx5("div", { className: "bg-white border border-gray-200 rounded-md shadow-lg py-1 min-w-48", children: menuItems.map((item, index) => /* @__PURE__ */ jsxs4(React2.Fragment, { children: [
1777
- /* @__PURE__ */ jsx5(
1967
+ if (showExpandPanel && elementType === "node") {
1968
+ const nodeLabel = data.label || data.id;
1969
+ return /* @__PURE__ */ jsx6(
1970
+ ExpandOptionsPanel_default,
1971
+ {
1972
+ nodeLabel: String(nodeLabel),
1973
+ onExpand: handleExpandWithOptions,
1974
+ onBack: () => setShowExpandPanel(false),
1975
+ getEdgeTypes: getNodeEdgeTypes ? () => getNodeEdgeTypes(Number(data.id)) : void 0
1976
+ }
1977
+ );
1978
+ }
1979
+ return /* @__PURE__ */ jsxs5("div", { className: "bg-background border border-border rounded-md shadow-lg py-1 min-w-48", children: [
1980
+ menuItems.map((item, index) => /* @__PURE__ */ jsxs5(React3.Fragment, { children: [
1981
+ /* @__PURE__ */ jsx6(
1982
+ "button",
1983
+ {
1984
+ className: "w-full text-left px-3 py-2 text-sm hover:bg-muted/50 cursor-pointer",
1985
+ onClick: item.action,
1986
+ children: item.label
1987
+ }
1988
+ ),
1989
+ index === 1 && /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" })
1990
+ ] }, item.label)),
1991
+ /* @__PURE__ */ jsx6("div", { className: "border-t border-border my-1" }),
1992
+ /* @__PURE__ */ jsx6(
1778
1993
  "button",
1779
1994
  {
1780
- className: "w-full text-left px-3 py-2 text-sm hover:bg-gray-100 cursor-pointer",
1781
- onClick: item.action,
1782
- children: item.label
1995
+ className: "w-full text-left px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted/50 cursor-pointer",
1996
+ onClick: onClose,
1997
+ children: "Cancel"
1783
1998
  }
1784
- ),
1785
- index === 1 && /* @__PURE__ */ jsx5("div", { className: "border-t border-gray-200 my-1" })
1786
- ] }, item.label)) });
1999
+ )
2000
+ ] });
1787
2001
  }
1788
2002
  var ReaContextMenu_default = ReaContextMenu;
1789
2003
 
1790
2004
  // src/components/rea/ReaGraph.tsx
1791
- import { toast } from "sonner";
1792
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2005
+ import { toast as toast2 } from "sonner";
2006
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1793
2007
  function findElementInGraphData(elementId, elementType, nodes, edges) {
1794
2008
  if (elementType === "node") {
1795
2009
  const node = nodes.find((n) => n.id === elementId);
@@ -1851,22 +2065,26 @@ function processFocusAction(focusAction, canvasRef, nodes, edges, setSelectedNod
1851
2065
  }
1852
2066
  return true;
1853
2067
  }
1854
- function ReaGraph({ onCopyNode, extraToolbarContent }) {
1855
- const isMountedRef = useRef(false);
2068
+ function ReaGraph({
2069
+ onCopyNode,
2070
+ extraToolbarContent,
2071
+ getNodeEdgeTypes
2072
+ }) {
2073
+ const isMountedRef = useRef2(false);
1856
2074
  const { nodes, edges } = useReagraphData();
1857
2075
  const { normalized } = useLiveGraphData();
1858
- useEffect2(() => {
2076
+ useEffect3(() => {
1859
2077
  devLog8("[ReGraph] counts", { nodes: nodes.length, edges: edges.length });
1860
2078
  if (nodes.length > 0) devLog8("[ReGraph] sample node", nodes[0]);
1861
2079
  if (edges.length > 0) devLog8("[ReGraph] sample edge", edges[0]);
1862
2080
  }, [nodes, edges]);
1863
- const canvas = useRef(null);
1864
- const [hoveredNode, setHoveredNode] = useState3(
2081
+ const canvas = useRef2(null);
2082
+ const [hoveredNode, setHoveredNode] = useState5(
1865
2083
  void 0
1866
2084
  );
1867
- const [layout, setLayout] = useState3("forceDirected2d");
1868
- const [fullscreen, setFullscreen] = useState3(false);
1869
- const [pathSelectionType, setPathSelectionType] = useState3("direct");
2085
+ const [layout, setLayout] = useState5("forceDirected2d");
2086
+ const [fullscreen, setFullscreen] = useState5(false);
2087
+ const [pathSelectionType, setPathSelectionType] = useState5("direct");
1870
2088
  const useGraphSelection = useWorkspaceSelection();
1871
2089
  const useGraphFilter2 = useWorkspaceGraphFilter();
1872
2090
  const useGraphExploration = useWorkspaceGraphExploration();
@@ -1892,7 +2110,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1892
2110
  pathSelectionType,
1893
2111
  type: "multi"
1894
2112
  });
1895
- useEffect2(() => {
2113
+ useEffect3(() => {
1896
2114
  const unsubscribe = GraphFocus_default.subscribe((state) => {
1897
2115
  const focusAction = state.focusAction;
1898
2116
  if (!focusAction || !canvas.current) return;
@@ -1916,12 +2134,12 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1916
2134
  });
1917
2135
  return unsubscribe;
1918
2136
  }, [nodes, edges, setSelectedNodeIds, setSelectedEdge]);
1919
- useEffect2(() => {
2137
+ useEffect3(() => {
1920
2138
  if (!canvas.current) return;
1921
2139
  clearSelections();
1922
2140
  clearSelectionStore();
1923
2141
  }, [lastDataChangeTime, clearSelections, clearSelectionStore]);
1924
- const fitToScreen = useCallback2(
2142
+ const fitToScreen = useCallback3(
1925
2143
  (reason) => {
1926
2144
  if (!isMountedRef.current) return;
1927
2145
  if (nodes.length === 0) return;
@@ -1947,7 +2165,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1947
2165
  },
1948
2166
  [nodes.length]
1949
2167
  );
1950
- useEffect2(() => {
2168
+ useEffect3(() => {
1951
2169
  isMountedRef.current = true;
1952
2170
  fitToScreen("mount");
1953
2171
  const state = useGraphSelection.getState();
@@ -1967,7 +2185,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
1967
2185
  isMountedRef.current = false;
1968
2186
  };
1969
2187
  }, [fitToScreen, setSelections, setSelectedEdge, useGraphSelection]);
1970
- useEffect2(() => {
2188
+ useEffect3(() => {
1971
2189
  if (isMountedRef.current) fitToScreen("counts-changed");
1972
2190
  }, [nodes.length, edges.length, fitToScreen]);
1973
2191
  function updateLayout(next) {
@@ -2009,14 +2227,14 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2009
2227
  setSelectedEdge(null);
2010
2228
  if (onCanvasClick) onCanvasClick(e);
2011
2229
  }
2012
- useEffect2(() => {
2230
+ useEffect3(() => {
2013
2231
  if (!isMountedRef.current) return;
2014
2232
  if (selections.length > 0 && selections.length <= 2) {
2015
2233
  const selectedNodes = selections.map((id) => nodes.find((n) => n.id === id)).filter(Boolean);
2016
2234
  setSelectedNodeIds(selectedNodes.map((n) => n.id));
2017
2235
  }
2018
2236
  }, [selections, nodes]);
2019
- useEffect2(() => {
2237
+ useEffect3(() => {
2020
2238
  if (!isMountedRef.current) return;
2021
2239
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2022
2240
  if (!currentSelectedIds.length) return;
@@ -2035,7 +2253,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2035
2253
  setSelectedEdge,
2036
2254
  useGraphSelection
2037
2255
  ]);
2038
- useEffect2(() => {
2256
+ useEffect3(() => {
2039
2257
  if (!isMountedRef.current) return;
2040
2258
  const currentSelectedIds = useGraphSelection.getState().selectedNodeIds;
2041
2259
  if (!currentSelectedIds.length) return;
@@ -2047,7 +2265,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2047
2265
  if (stillPresentIds.length < 2) setSelectedEdge(null);
2048
2266
  }
2049
2267
  }, [nodes, setSelectedNodeIds, setSelectedEdge, useGraphSelection]);
2050
- useEffect2(() => {
2268
+ useEffect3(() => {
2051
2269
  if (!fullscreen) return;
2052
2270
  const prevOverflow = document.body.style.overflow;
2053
2271
  document.body.style.overflow = "hidden";
@@ -2061,7 +2279,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2061
2279
  window.removeEventListener("keydown", handleKey);
2062
2280
  };
2063
2281
  }, [fullscreen, fitToScreen]);
2064
- useEffect2(() => {
2282
+ useEffect3(() => {
2065
2283
  if (!fullscreen) return;
2066
2284
  const id = setTimeout(() => {
2067
2285
  if (canvas.current) {
@@ -2070,7 +2288,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2070
2288
  }, 50);
2071
2289
  return () => clearTimeout(id);
2072
2290
  }, [fullscreen, fitToScreen]);
2073
- useEffect2(() => {
2291
+ useEffect3(() => {
2074
2292
  if (!isMountedRef.current) return;
2075
2293
  const handleResize = () => {
2076
2294
  if (isMountedRef.current && canvas.current) {
@@ -2082,8 +2300,8 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2082
2300
  window.removeEventListener("resize", handleResize);
2083
2301
  };
2084
2302
  }, [fitToScreen]);
2085
- const firstDataRef = useRef(true);
2086
- useEffect2(() => {
2303
+ const firstDataRef = useRef2(true);
2304
+ useEffect3(() => {
2087
2305
  if (!isMountedRef.current) return;
2088
2306
  if (nodes.length > 0) {
2089
2307
  if (firstDataRef.current) {
@@ -2096,18 +2314,18 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2096
2314
  }, [nodes.length, fitToScreen]);
2097
2315
  if (nodes.length === 0) {
2098
2316
  devLog8("[ReGraph] empty state render");
2099
- return /* @__PURE__ */ jsxs5(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2100
- /* @__PURE__ */ jsx6("div", { children: "No data to display." }),
2101
- /* @__PURE__ */ jsxs5("div", { children: [
2317
+ return /* @__PURE__ */ jsxs6(Card2, { className: "relative w-full h-full items-center justify-center text-slate-500 gap-2 shadow-xs p-4", children: [
2318
+ /* @__PURE__ */ jsx7("div", { children: "No data to display." }),
2319
+ /* @__PURE__ */ jsxs6("div", { children: [
2102
2320
  "Please",
2103
2321
  " ",
2104
- /* @__PURE__ */ jsx6("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2322
+ /* @__PURE__ */ jsx7("span", { className: "font-semibold", children: "run a query, check filters, or add a node" }),
2105
2323
  " ",
2106
2324
  "to the workbench to populate the graph."
2107
2325
  ] })
2108
2326
  ] });
2109
2327
  }
2110
- return /* @__PURE__ */ jsxs5(
2328
+ return /* @__PURE__ */ jsxs6(
2111
2329
  "div",
2112
2330
  {
2113
2331
  className: cn(
@@ -2115,7 +2333,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2115
2333
  fullscreen ? "fixed inset-0 z-50 w-screen h-screen p-0 m-0" : "relative w-full h-full"
2116
2334
  ),
2117
2335
  children: [
2118
- /* @__PURE__ */ jsx6(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx6(
2336
+ /* @__PURE__ */ jsx7(Card2, { className: "h-full shadow-xs p-4", children: /* @__PURE__ */ jsx7(
2119
2337
  GraphCanvas,
2120
2338
  {
2121
2339
  ref: canvas,
@@ -2140,21 +2358,22 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2140
2358
  elementId: data.id,
2141
2359
  elementType
2142
2360
  });
2143
- return /* @__PURE__ */ jsx6(
2361
+ return /* @__PURE__ */ jsx7(
2144
2362
  ReaContextMenu_default,
2145
2363
  {
2146
2364
  data,
2147
2365
  onClose,
2148
2366
  elementType,
2149
2367
  useGraphExploration,
2150
- onCopyNode
2368
+ onCopyNode,
2369
+ getNodeEdgeTypes
2151
2370
  }
2152
2371
  );
2153
2372
  }
2154
2373
  }
2155
2374
  ) }),
2156
- hoveredNode && /* @__PURE__ */ jsx6(NodeHoverInfo_default, { node: hoveredNode }),
2157
- /* @__PURE__ */ jsx6(
2375
+ hoveredNode && /* @__PURE__ */ jsx7(NodeHoverInfo_default, { node: hoveredNode }),
2376
+ /* @__PURE__ */ jsx7(
2158
2377
  LayoutAndCameraControls_default,
2159
2378
  {
2160
2379
  fitToScreen,
@@ -2166,7 +2385,7 @@ function ReaGraph({ onCopyNode, extraToolbarContent }) {
2166
2385
  isFocusMode,
2167
2386
  onResetView: () => {
2168
2387
  useGraphExploration.getState().resetView();
2169
- toast.success(
2388
+ toast2.success(
2170
2389
  isFocusMode ? "Focus mode cleared" : "Hidden elements restored"
2171
2390
  );
2172
2391
  },
@@ -2181,12 +2400,12 @@ var ReaGraph_default = ReaGraph;
2181
2400
 
2182
2401
  // src/components/cyto/CytoscapeGraph.tsx
2183
2402
  import {
2184
- useEffect as useEffect3,
2185
- useRef as useRef2,
2186
- useState as useState4,
2187
- useCallback as useCallback3
2403
+ useEffect as useEffect4,
2404
+ useRef as useRef3,
2405
+ useState as useState6,
2406
+ useCallback as useCallback4
2188
2407
  } from "react";
2189
- import { devLog as devLog10, errorLog as errorLog2 } from "@petrarca/sonnet-core";
2408
+ import { devLog as devLog10, errorLog as errorLog3 } from "@petrarca/sonnet-core";
2190
2409
  import cytoscape from "cytoscape";
2191
2410
  import fcose from "cytoscape-fcose";
2192
2411
  import cola from "cytoscape-cola";
@@ -2208,7 +2427,7 @@ import {
2208
2427
  SimpleTooltip as SimpleTooltip3
2209
2428
  } from "@petrarca/sonnet-ui";
2210
2429
  import { LayoutPanelTop as LayoutPanelTop2, Monitor, Frame, Eye as Eye2, Minus } from "lucide-react";
2211
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2430
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2212
2431
  function ViewControls2({
2213
2432
  zoomIn,
2214
2433
  zoomOut,
@@ -2219,17 +2438,17 @@ function ViewControls2({
2219
2438
  isFocusMode,
2220
2439
  onResetView
2221
2440
  }) {
2222
- return /* @__PURE__ */ jsxs6(Fragment2, { children: [
2223
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Zoom In", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: zoomIn, children: /* @__PURE__ */ jsx7("span", { style: { fontWeight: 600 }, children: "+" }) }) }),
2224
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx7(Minus, {}) }) }),
2225
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx7(Monitor, {}) : /* @__PURE__ */ jsx7(Frame, {}) }) }),
2226
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx7(Frame, {}) }) }),
2227
- (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx7(
2441
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
2442
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Zoom In", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: zoomIn, children: /* @__PURE__ */ jsx8("span", { style: { fontWeight: 600 }, children: "+" }) }) }),
2443
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Zoom Out", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: zoomOut, children: /* @__PURE__ */ jsx8(Minus, {}) }) }),
2444
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Fullscreen", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: toggleFullscreen, children: !fullscreen ? /* @__PURE__ */ jsx8(Monitor, {}) : /* @__PURE__ */ jsx8(Frame, {}) }) }),
2445
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Fit to screen", side: "left", children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: fitToScreen, children: /* @__PURE__ */ jsx8(Frame, {}) }) }),
2446
+ (hasHiddenElements || isFocusMode) && onResetView && /* @__PURE__ */ jsx8(
2228
2447
  SimpleTooltip3,
2229
2448
  {
2230
2449
  label: isFocusMode ? "Reset Focus" : "Show All Hidden",
2231
2450
  side: "left",
2232
- children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx7(Eye2, {}) })
2451
+ children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", onClick: onResetView, children: /* @__PURE__ */ jsx8(Eye2, {}) })
2233
2452
  }
2234
2453
  )
2235
2454
  ] });
@@ -2264,12 +2483,12 @@ var LAYOUT_GROUPS = [
2264
2483
  }
2265
2484
  ];
2266
2485
  function LayoutDropdown2({ currentLayout, updateLayout }) {
2267
- return /* @__PURE__ */ jsxs6(DropdownMenu3, { children: [
2268
- /* @__PURE__ */ jsx7(SimpleTooltip3, { label: "Layout", side: "left", children: /* @__PURE__ */ jsx7(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx7(Button3, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx7(LayoutPanelTop2, {}) }) }) }),
2269
- /* @__PURE__ */ jsx7(DropdownMenuContent3, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs6("div", { children: [
2270
- groupIdx > 0 && /* @__PURE__ */ jsx7(DropdownMenuSeparator2, {}),
2271
- /* @__PURE__ */ jsx7(DropdownMenuLabel2, { children: group.label }),
2272
- group.items.map((item) => /* @__PURE__ */ jsx7(
2486
+ return /* @__PURE__ */ jsxs7(DropdownMenu3, { children: [
2487
+ /* @__PURE__ */ jsx8(SimpleTooltip3, { label: "Layout", side: "left", children: /* @__PURE__ */ jsx8(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx8(Button3, { variant: "ghost", size: "icon", children: /* @__PURE__ */ jsx8(LayoutPanelTop2, {}) }) }) }),
2488
+ /* @__PURE__ */ jsx8(DropdownMenuContent3, { side: "left", align: "start", children: LAYOUT_GROUPS.map((group, groupIdx) => /* @__PURE__ */ jsxs7("div", { children: [
2489
+ groupIdx > 0 && /* @__PURE__ */ jsx8(DropdownMenuSeparator2, {}),
2490
+ /* @__PURE__ */ jsx8(DropdownMenuLabel2, { children: group.label }),
2491
+ group.items.map((item) => /* @__PURE__ */ jsx8(
2273
2492
  DropdownMenuItem3,
2274
2493
  {
2275
2494
  onClick: () => updateLayout(item.layout),
@@ -2294,9 +2513,9 @@ function CytoscapeControls({
2294
2513
  onResetView,
2295
2514
  extraToolbarContent
2296
2515
  }) {
2297
- return /* @__PURE__ */ jsxs6("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2298
- /* @__PURE__ */ jsx7(RendererDropdown, {}),
2299
- /* @__PURE__ */ jsx7(
2516
+ return /* @__PURE__ */ jsxs7("div", { className: "absolute right-2 top-2 flex flex-col border bg-white rounded-md shadow-sm z-10", children: [
2517
+ /* @__PURE__ */ jsx8(RendererDropdown, {}),
2518
+ /* @__PURE__ */ jsx8(
2300
2519
  ViewControls2,
2301
2520
  {
2302
2521
  zoomIn,
@@ -2310,7 +2529,7 @@ function CytoscapeControls({
2310
2529
  }
2311
2530
  ),
2312
2531
  extraToolbarContent,
2313
- /* @__PURE__ */ jsx7(
2532
+ /* @__PURE__ */ jsx8(
2314
2533
  LayoutDropdown2,
2315
2534
  {
2316
2535
  currentLayout,
@@ -2590,12 +2809,13 @@ function getLayoutPreset(options) {
2590
2809
  }
2591
2810
 
2592
2811
  // src/components/cyto/contextMenuConfig.ts
2593
- import { devLog as devLog9, errorLog } from "@petrarca/sonnet-core";
2594
- import { toast as toast2 } from "sonner";
2812
+ import { devLog as devLog9, errorLog as errorLog2 } from "@petrarca/sonnet-core";
2813
+ import { toast as toast3 } from "sonner";
2595
2814
  var createNodeContextMenu = ({
2596
2815
  cy,
2597
2816
  useGraphExploration,
2598
- onCopyNode
2817
+ onCopyNode,
2818
+ onExpandRequest
2599
2819
  }) => {
2600
2820
  devLog9("[CxtMenu] Initializing node context menu");
2601
2821
  return cy.cxtmenu({
@@ -2617,7 +2837,7 @@ var createNodeContextMenu = ({
2617
2837
  if (visualGraph) {
2618
2838
  onCopyNode?.(visualGraph);
2619
2839
  } else {
2620
- errorLog("[CxtMenu] visualGraph not found in element data");
2840
+ errorLog2("[CxtMenu] visualGraph not found in element data");
2621
2841
  }
2622
2842
  }
2623
2843
  },
@@ -2647,21 +2867,23 @@ var createNodeContextMenu = ({
2647
2867
  },
2648
2868
  {
2649
2869
  content: "Expand",
2650
- select: async (ele) => {
2870
+ select: (ele) => {
2651
2871
  const nodeId = ele.id();
2652
- devLog9("[CxtMenu] Expanding node:", nodeId);
2653
- try {
2654
- await useGraphExploration.getState().expandNode(nodeId);
2655
- devLog9("[CxtMenu] Expansion complete");
2656
- } catch (error) {
2657
- errorLog("[CxtMenu] Expansion failed:", error);
2658
- toast2.error("Failed to expand node");
2872
+ devLog9("[CxtMenu] Expand request for node:", nodeId);
2873
+ if (onExpandRequest) {
2874
+ const pos = ele.renderedPosition();
2875
+ const label = ele.data("label") || nodeId;
2876
+ onExpandRequest({ nodeId, nodeLabel: label, x: pos.x, y: pos.y });
2877
+ } else {
2878
+ useGraphExploration.getState().expandNode(nodeId).catch((error) => {
2879
+ errorLog2("[CxtMenu] Expansion failed:", error);
2880
+ toast3.error("Failed to expand node");
2881
+ });
2659
2882
  }
2660
2883
  }
2661
2884
  }
2662
2885
  ],
2663
2886
  menuRadius: 60,
2664
- // Increased for more items (default is 100)
2665
2887
  fillColor: "rgba(0, 0, 0, 0.75)",
2666
2888
  activeFillColor: "rgba(59, 130, 246, 0.75)",
2667
2889
  // blue-500
@@ -2746,8 +2968,49 @@ var createEdgeContextMenu = ({
2746
2968
  };
2747
2969
 
2748
2970
  // src/components/cyto/CytoscapeGraph.tsx
2749
- import { toast as toast3 } from "sonner";
2750
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2971
+ import { toast as toast4 } from "sonner";
2972
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2973
+ function CytoscapeExpandOverlay({
2974
+ expandRequest,
2975
+ containerRef,
2976
+ useGraphExploration,
2977
+ getNodeEdgeTypes,
2978
+ onClose
2979
+ }) {
2980
+ return /* @__PURE__ */ jsx9(
2981
+ "div",
2982
+ {
2983
+ className: "absolute z-50",
2984
+ style: {
2985
+ left: Math.min(
2986
+ Math.max(expandRequest.x, 120),
2987
+ (containerRef.current?.clientWidth ?? 800) - 120
2988
+ ),
2989
+ top: Math.min(
2990
+ Math.max(expandRequest.y, 10),
2991
+ (containerRef.current?.clientHeight ?? 600) - 260
2992
+ ),
2993
+ transform: "translate(-50%, 12px)"
2994
+ },
2995
+ children: /* @__PURE__ */ jsx9(
2996
+ ExpandOptionsPanel_default,
2997
+ {
2998
+ nodeLabel: expandRequest.nodeLabel,
2999
+ onExpand: (options) => {
3000
+ const { nodeId } = expandRequest;
3001
+ onClose();
3002
+ useGraphExploration.getState().expandNode(nodeId, options).catch((err) => {
3003
+ errorLog3("[CxtMenu] Expansion failed:", err);
3004
+ toast4.error("Failed to expand node");
3005
+ });
3006
+ },
3007
+ onBack: onClose,
3008
+ getEdgeTypes: getNodeEdgeTypes ? () => getNodeEdgeTypes(Number(expandRequest.nodeId)) : void 0
3009
+ }
3010
+ )
3011
+ }
3012
+ );
3013
+ }
2751
3014
  try {
2752
3015
  cytoscape.use(fcose);
2753
3016
  cytoscape.use(cola);
@@ -2825,12 +3088,12 @@ function applyLayout({
2825
3088
  try {
2826
3089
  cy.layout(layoutOpts).run();
2827
3090
  } catch (e) {
2828
- errorLog2("Layout error, falling back to dagre:", e);
3091
+ errorLog3("Layout error, falling back to dagre:", e);
2829
3092
  if (chosen === "klay") {
2830
3093
  try {
2831
3094
  cy.layout(getPreset("dagre")).run();
2832
3095
  } catch (inner) {
2833
- errorLog2("Fallback layout also failed, using grid:", inner);
3096
+ errorLog3("Fallback layout also failed, using grid:", inner);
2834
3097
  cy.layout(getPreset("grid")).run();
2835
3098
  }
2836
3099
  } else {
@@ -3037,8 +3300,8 @@ function computeGraphViewState({
3037
3300
  };
3038
3301
  }
3039
3302
  function useFullscreen(fitToScreen, cyRef) {
3040
- const [fullscreen, setFullscreen] = useState4(false);
3041
- useEffect3(() => {
3303
+ const [fullscreen, setFullscreen] = useState6(false);
3304
+ useEffect4(() => {
3042
3305
  if (fullscreen) {
3043
3306
  const prevOverflow = document.body.style.overflow;
3044
3307
  document.body.style.overflow = "hidden";
@@ -3053,7 +3316,7 @@ function useFullscreen(fitToScreen, cyRef) {
3053
3316
  };
3054
3317
  }
3055
3318
  }, [fullscreen, fitToScreen]);
3056
- useEffect3(() => {
3319
+ useEffect4(() => {
3057
3320
  if (!fullscreen) return;
3058
3321
  const id = setTimeout(() => {
3059
3322
  if (cyRef.current) {
@@ -3066,7 +3329,7 @@ function useFullscreen(fitToScreen, cyRef) {
3066
3329
  return {
3067
3330
  fullscreen,
3068
3331
  setFullscreen,
3069
- toggleFullscreen: useCallback3(() => setFullscreen((f) => !f), [])
3332
+ toggleFullscreen: useCallback4(() => setFullscreen((f) => !f), [])
3070
3333
  };
3071
3334
  }
3072
3335
  function setupCytoscapeEventHandlers({
@@ -3078,7 +3341,8 @@ function setupCytoscapeEventHandlers({
3078
3341
  setSelectedEdgeStore,
3079
3342
  clearSelectionStore,
3080
3343
  useGraphExploration,
3081
- onCopyNode
3344
+ onCopyNode,
3345
+ onExpandRequest
3082
3346
  }) {
3083
3347
  const updateSelectedNodeIds = (nodeIds) => {
3084
3348
  setSelectedNodeIdsStore(nodeIds);
@@ -3166,22 +3430,31 @@ function setupCytoscapeEventHandlers({
3166
3430
  cy.on("tap", (evt) => {
3167
3431
  if (evt.target === cy) clearAll();
3168
3432
  });
3169
- createNodeContextMenu({ cy, useGraphExploration, onCopyNode });
3433
+ createNodeContextMenu({
3434
+ cy,
3435
+ useGraphExploration,
3436
+ onCopyNode,
3437
+ onExpandRequest
3438
+ });
3170
3439
  createEdgeContextMenu({ cy, useGraphExploration });
3171
3440
  }
3172
3441
  function CytoscapeGraph({
3173
3442
  layout: initialLayout = "auto",
3174
3443
  fitPadding = 30,
3175
3444
  onCopyNode,
3176
- extraToolbarContent
3445
+ extraToolbarContent,
3446
+ getNodeEdgeTypes
3177
3447
  }) {
3178
- const containerRef = useRef2(null);
3179
- const cyRef = useRef2(null);
3180
- const isInitializedRef = useRef2(false);
3181
- const [currentLayout, setCurrentLayout] = useState4(initialLayout);
3182
- const nodesRef = useRef2([]);
3183
- const edgesRef = useRef2([]);
3184
- const pendingFocusActionRef = useRef2(null);
3448
+ const containerRef = useRef3(null);
3449
+ const cyRef = useRef3(null);
3450
+ const isInitializedRef = useRef3(false);
3451
+ const [currentLayout, setCurrentLayout] = useState6(initialLayout);
3452
+ const [expandRequest, setExpandRequest] = useState6(
3453
+ null
3454
+ );
3455
+ const nodesRef = useRef3([]);
3456
+ const edgesRef = useRef3([]);
3457
+ const pendingFocusActionRef = useRef3(null);
3185
3458
  const cytoData = useCytoscapeData();
3186
3459
  const live = useLiveGraphData();
3187
3460
  const nodes = live.nodes;
@@ -3201,13 +3474,13 @@ function CytoscapeGraph({
3201
3474
  setSelectedEdge: setSelectedEdgeStore,
3202
3475
  clear: clearSelectionStore
3203
3476
  } = useSelection3.getState();
3204
- const selectedNodeIdsRef = useRef2(
3477
+ const selectedNodeIdsRef = useRef3(
3205
3478
  useSelection3.getState().selectedNodeIds
3206
3479
  );
3207
- const selectedEdgeRef = useRef2(
3480
+ const selectedEdgeRef = useRef3(
3208
3481
  useSelection3.getState().selectedEdge
3209
3482
  );
3210
- useEffect3(() => {
3483
+ useEffect4(() => {
3211
3484
  const state = useSelection3.getState();
3212
3485
  selectedNodeIdsRef.current = state.selectedNodeIds;
3213
3486
  selectedEdgeRef.current = state.selectedEdge;
@@ -3227,13 +3500,13 @@ function CytoscapeGraph({
3227
3500
  });
3228
3501
  }
3229
3502
  }, [useSelection3]);
3230
- useEffect3(() => {
3503
+ useEffect4(() => {
3231
3504
  nodesRef.current = nodes;
3232
3505
  }, [nodes]);
3233
- useEffect3(() => {
3506
+ useEffect4(() => {
3234
3507
  edgesRef.current = edges;
3235
3508
  }, [edges]);
3236
- useEffect3(() => {
3509
+ useEffect4(() => {
3237
3510
  if (!containerRef.current || cyRef.current) return;
3238
3511
  isInitializedRef.current = true;
3239
3512
  cyRef.current = cytoscape({
@@ -3384,7 +3657,8 @@ function CytoscapeGraph({
3384
3657
  setSelectedEdgeStore,
3385
3658
  clearSelectionStore,
3386
3659
  useGraphExploration,
3387
- onCopyNode
3660
+ onCopyNode,
3661
+ onExpandRequest: setExpandRequest
3388
3662
  });
3389
3663
  }
3390
3664
  return () => {
@@ -3392,10 +3666,10 @@ function CytoscapeGraph({
3392
3666
  cyRef.current = null;
3393
3667
  };
3394
3668
  }, []);
3395
- const elementIdsRef = useRef2("");
3396
- const lastLayoutRef = useRef2("");
3397
- const lastFitPaddingRef = useRef2(fitPadding);
3398
- useEffect3(() => {
3669
+ const elementIdsRef = useRef3("");
3670
+ const lastLayoutRef = useRef3("");
3671
+ const lastFitPaddingRef = useRef3(fitPadding);
3672
+ useEffect4(() => {
3399
3673
  const cy = cyRef.current;
3400
3674
  if (!cy || !isInitializedRef.current) return;
3401
3675
  const currentIds = nodes.map((n) => n.id_).sort().join(",") + "|" + edges.map((e) => e.id_).sort().join(",");
@@ -3468,12 +3742,12 @@ function CytoscapeGraph({
3468
3742
  });
3469
3743
  }
3470
3744
  }, [nodes, edges, currentLayout, fitPadding]);
3471
- useEffect3(() => {
3745
+ useEffect4(() => {
3472
3746
  if (!cyRef.current) return;
3473
3747
  cyRef.current.elements().unselect();
3474
3748
  clearSelectionStore();
3475
3749
  }, [lastDataChangeTime, clearSelectionStore]);
3476
- useEffect3(() => {
3750
+ useEffect4(() => {
3477
3751
  const unsubscribe = GraphFocus_default.subscribe((state) => {
3478
3752
  handleFocusStateChange({
3479
3753
  focusAction: state.focusAction,
@@ -3492,15 +3766,15 @@ function CytoscapeGraph({
3492
3766
  const updateLayout = (newLayout) => {
3493
3767
  setCurrentLayout(newLayout);
3494
3768
  };
3495
- const fitToScreen = useCallback3(() => {
3769
+ const fitToScreen = useCallback4(() => {
3496
3770
  if (cyRef.current) {
3497
3771
  cyRef.current.fit(void 0, fitPadding);
3498
3772
  }
3499
3773
  }, [fitPadding]);
3500
- useEffect3(() => {
3774
+ useEffect4(() => {
3501
3775
  fitToScreen();
3502
3776
  }, [nodes.length, fitToScreen]);
3503
- useEffect3(() => {
3777
+ useEffect4(() => {
3504
3778
  if (!isInitializedRef.current) return;
3505
3779
  const handleResize = () => cyRef.current?.resize();
3506
3780
  window.addEventListener("resize", handleResize);
@@ -3519,18 +3793,28 @@ function CytoscapeGraph({
3519
3793
  isFocusMode,
3520
3794
  fullscreen
3521
3795
  });
3522
- return /* @__PURE__ */ jsx8("div", { className: outerClass, children: /* @__PURE__ */ jsxs7("div", { className: innerClass, children: [
3523
- /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "absolute inset-0" }),
3524
- showEmptyMessage && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: "No graph data to visualize (run a query or check the filters)" }),
3525
- showFilteredMessage && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: 'All elements are hidden or filtered (click "Reset Focus" to restore)' }),
3526
- showControls && /* @__PURE__ */ jsx8(
3796
+ return /* @__PURE__ */ jsx9("div", { className: outerClass, children: /* @__PURE__ */ jsxs8("div", { className: innerClass, children: [
3797
+ /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "absolute inset-0" }),
3798
+ expandRequest && /* @__PURE__ */ jsx9(
3799
+ CytoscapeExpandOverlay,
3800
+ {
3801
+ expandRequest,
3802
+ containerRef,
3803
+ useGraphExploration,
3804
+ getNodeEdgeTypes,
3805
+ onClose: () => setExpandRequest(null)
3806
+ }
3807
+ ),
3808
+ showEmptyMessage && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: "No graph data to visualize (run a query or check the filters)" }),
3809
+ showFilteredMessage && /* @__PURE__ */ jsx9("div", { className: "absolute inset-0 flex items-center justify-center text-xs text-gray-500 pointer-events-none select-none", children: 'All elements are hidden or filtered (click "Reset Focus" to restore)' }),
3810
+ showControls && /* @__PURE__ */ jsx9(
3527
3811
  CytoscapeControls_default,
3528
3812
  {
3529
3813
  updateLayout,
3530
3814
  fitToScreen,
3531
3815
  onResetView: () => {
3532
3816
  resetView();
3533
- toast3.success(
3817
+ toast4.success(
3534
3818
  isFocusMode ? "Focus mode cleared" : "Hidden elements restored"
3535
3819
  );
3536
3820
  },
@@ -3571,26 +3855,29 @@ function CytoscapeGraph({
3571
3855
  var CytoscapeGraph_default = CytoscapeGraph;
3572
3856
 
3573
3857
  // src/components/GraphVisualizer.tsx
3574
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3858
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3575
3859
  function GraphVisualizer({
3576
3860
  onCopyNode,
3577
- extraToolbarContent
3861
+ extraToolbarContent,
3862
+ getNodeEdgeTypes
3578
3863
  }) {
3579
3864
  const { renderer } = useGraphRenderer();
3580
- return /* @__PURE__ */ jsx9("div", { className: "relative w-full h-full flex flex-col overflow-hidden", children: /* @__PURE__ */ jsxs8("div", { className: "flex-1 h-full", children: [
3581
- renderer === "reagraph" && /* @__PURE__ */ jsx9(
3865
+ return /* @__PURE__ */ jsx10("div", { className: "relative w-full h-full flex flex-col overflow-hidden", children: /* @__PURE__ */ jsxs9("div", { className: "flex-1 h-full", children: [
3866
+ renderer === "reagraph" && /* @__PURE__ */ jsx10(
3582
3867
  ReaGraph_default,
3583
3868
  {
3584
3869
  onCopyNode,
3585
- extraToolbarContent
3870
+ extraToolbarContent,
3871
+ getNodeEdgeTypes
3586
3872
  },
3587
3873
  "reagraph"
3588
3874
  ),
3589
- renderer === "cytoscape" && /* @__PURE__ */ jsx9(
3875
+ renderer === "cytoscape" && /* @__PURE__ */ jsx10(
3590
3876
  CytoscapeGraph_default,
3591
3877
  {
3592
3878
  onCopyNode,
3593
- extraToolbarContent
3879
+ extraToolbarContent,
3880
+ getNodeEdgeTypes
3594
3881
  },
3595
3882
  "cytoscape"
3596
3883
  )
@@ -3602,18 +3889,19 @@ var GraphVisualizer_default = GraphVisualizer;
3602
3889
  import { Card as Card5 } from "@petrarca/sonnet-ui";
3603
3890
 
3604
3891
  // src/components/ActionPanel/NodeInfoCard.tsx
3605
- import { useState as useState5 } from "react";
3892
+ import { useState as useState7 } from "react";
3606
3893
  import {
3607
3894
  Card as Card3,
3608
3895
  Collapsible,
3609
3896
  CollapsibleContent,
3610
- CollapsibleTrigger
3897
+ CollapsibleTrigger,
3898
+ PropertyList
3611
3899
  } from "@petrarca/sonnet-ui";
3612
3900
  import { copyTextToClipboard } from "@petrarca/sonnet-core";
3613
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3901
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3614
3902
  function NodeInfoCard({ node }) {
3615
- const [showProperties, setShowProperties] = useState5(false);
3616
- return /* @__PURE__ */ jsxs9(
3903
+ const [showProperties, setShowProperties] = useState7(false);
3904
+ return /* @__PURE__ */ jsxs10(
3617
3905
  Card3,
3618
3906
  {
3619
3907
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -3621,9 +3909,9 @@ function NodeInfoCard({ node }) {
3621
3909
  borderLeft: `4px solid ${node.visual.color}`
3622
3910
  },
3623
3911
  children: [
3624
- /* @__PURE__ */ jsx10("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
3625
- /* @__PURE__ */ jsx10("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
3626
- /* @__PURE__ */ jsx10(
3912
+ /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight font-medium mb-0", children: node.label_ }),
3913
+ /* @__PURE__ */ jsx11("p", { className: "text-sm leading-tight mb-1", children: node.visual.displayName }),
3914
+ /* @__PURE__ */ jsx11(
3627
3915
  "p",
3628
3916
  {
3629
3917
  className: "text-xs text-muted-foreground cursor-pointer select-all mb-2",
@@ -3632,23 +3920,9 @@ function NodeInfoCard({ node }) {
3632
3920
  children: node.id_
3633
3921
  }
3634
3922
  ),
3635
- /* @__PURE__ */ jsxs9(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
3636
- /* @__PURE__ */ jsx10(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx10("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3637
- /* @__PURE__ */ jsx10(CollapsibleContent, { children: /* @__PURE__ */ jsxs9("ul", { className: "list-none mt-2", children: [
3638
- /* @__PURE__ */ jsx10("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1", children: "Properties" }),
3639
- Object.entries(node.properties_ || {}).map(([key, value]) => /* @__PURE__ */ jsxs9("li", { className: "mb-2 leading-tight", children: [
3640
- /* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground uppercase tracking-wide", children: key }),
3641
- /* @__PURE__ */ jsx10(
3642
- "p",
3643
- {
3644
- className: "text-sm truncate max-w-[190px] cursor-pointer",
3645
- onClick: () => copyTextToClipboard(String(value)),
3646
- title: "Click to copy value",
3647
- children: String(value)
3648
- }
3649
- )
3650
- ] }, key))
3651
- ] }) })
3923
+ /* @__PURE__ */ jsxs10(Collapsible, { open: showProperties, onOpenChange: setShowProperties, children: [
3924
+ /* @__PURE__ */ jsx11(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsx11("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3925
+ /* @__PURE__ */ jsx11(CollapsibleContent, { children: /* @__PURE__ */ jsx11("div", { className: "mt-2", children: /* @__PURE__ */ jsx11(PropertyList, { properties: node.properties_ || {}, showHeader: true }) }) })
3652
3926
  ] })
3653
3927
  ]
3654
3928
  }
@@ -3657,19 +3931,20 @@ function NodeInfoCard({ node }) {
3657
3931
  var NodeInfoCard_default = NodeInfoCard;
3658
3932
 
3659
3933
  // src/components/ActionPanel/EdgeInfoCard.tsx
3660
- import { useState as useState6 } from "react";
3934
+ import { useState as useState8 } from "react";
3661
3935
  import {
3662
3936
  Card as Card4,
3663
3937
  Collapsible as Collapsible2,
3664
3938
  CollapsibleContent as CollapsibleContent2,
3665
- CollapsibleTrigger as CollapsibleTrigger2
3939
+ CollapsibleTrigger as CollapsibleTrigger2,
3940
+ PropertyList as PropertyList2
3666
3941
  } from "@petrarca/sonnet-ui";
3667
3942
  import { ArrowDown } from "lucide-react";
3668
3943
  import { copyTextToClipboard as copyTextToClipboard2 } from "@petrarca/sonnet-core";
3669
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3944
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3670
3945
  function EdgeInfoCard({ edge }) {
3671
- const [showProperties, setShowProperties] = useState6(false);
3672
- return /* @__PURE__ */ jsxs10(
3946
+ const [showProperties, setShowProperties] = useState8(false);
3947
+ return /* @__PURE__ */ jsxs11(
3673
3948
  Card4,
3674
3949
  {
3675
3950
  className: "w-full rounded-lg overflow-hidden bg-muted/30 border shadow-none p-3",
@@ -3677,11 +3952,11 @@ function EdgeInfoCard({ edge }) {
3677
3952
  borderLeft: `4px solid ${edge.visual.color}`
3678
3953
  },
3679
3954
  children: [
3680
- /* @__PURE__ */ jsxs10("p", { className: "text-sm leading-tight mb-1", children: [
3681
- /* @__PURE__ */ jsx11(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
3682
- /* @__PURE__ */ jsx11("strong", { className: "font-medium align-middle", children: edge.label_ })
3955
+ /* @__PURE__ */ jsxs11("p", { className: "text-sm leading-tight mb-1", children: [
3956
+ /* @__PURE__ */ jsx12(ArrowDown, { className: "inline-block mr-1 opacity-70 align-middle h-3.5 w-3.5" }),
3957
+ /* @__PURE__ */ jsx12("strong", { className: "font-medium align-middle", children: edge.label_ })
3683
3958
  ] }),
3684
- edge.id_ && /* @__PURE__ */ jsx11(
3959
+ edge.id_ && /* @__PURE__ */ jsx12(
3685
3960
  "p",
3686
3961
  {
3687
3962
  className: "text-xs text-muted-foreground select-all cursor-pointer mt-1 mb-2",
@@ -3690,23 +3965,9 @@ function EdgeInfoCard({ edge }) {
3690
3965
  children: edge.id_
3691
3966
  }
3692
3967
  ),
3693
- edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs10(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
3694
- /* @__PURE__ */ jsx11(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx11("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3695
- /* @__PURE__ */ jsx11(CollapsibleContent2, { children: /* @__PURE__ */ jsxs10("ul", { className: "list-none mt-2", children: [
3696
- /* @__PURE__ */ jsx11("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-1", children: "Properties" }),
3697
- Object.entries(edge.properties_ || {}).map(([key, value]) => /* @__PURE__ */ jsxs10("li", { className: "mb-2 leading-tight", children: [
3698
- /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground uppercase tracking-wide", children: key }),
3699
- /* @__PURE__ */ jsx11(
3700
- "p",
3701
- {
3702
- className: "text-sm truncate max-w-[190px] cursor-pointer",
3703
- onClick: () => copyTextToClipboard2(String(value)),
3704
- title: "Click to copy value",
3705
- children: String(value)
3706
- }
3707
- )
3708
- ] }, key))
3709
- ] }) })
3968
+ edge.properties_ && Object.keys(edge.properties_).length > 0 && /* @__PURE__ */ jsxs11(Collapsible2, { open: showProperties, onOpenChange: setShowProperties, children: [
3969
+ /* @__PURE__ */ jsx12(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsx12("button", { className: "text-xs cursor-pointer", children: showProperties ? "Hide" : "Show more" }) }),
3970
+ /* @__PURE__ */ jsx12(CollapsibleContent2, { children: /* @__PURE__ */ jsx12("div", { className: "mt-2", children: /* @__PURE__ */ jsx12(PropertyList2, { properties: edge.properties_ || {}, showHeader: true }) }) })
3710
3971
  ] })
3711
3972
  ]
3712
3973
  }
@@ -3718,7 +3979,7 @@ var EdgeInfoCard_default = EdgeInfoCard;
3718
3979
  import { Badge } from "@petrarca/sonnet-ui";
3719
3980
  import { formatNumber, devLog as devLog11 } from "@petrarca/sonnet-core";
3720
3981
  import { useMemo as useMemo4 } from "react";
3721
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3982
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3722
3983
  function computeEdgeToNodeLabels(nodesById, edgesById) {
3723
3984
  const mapping = {};
3724
3985
  for (const edge of Object.values(edgesById)) {
@@ -3751,7 +4012,7 @@ function AllToggleBadge({
3751
4012
  badgeKey
3752
4013
  }) {
3753
4014
  const bgColor = allDisabled ? colorPalette["faded"] : colorPalette["neutral"];
3754
- return /* @__PURE__ */ jsxs11(
4015
+ return /* @__PURE__ */ jsxs12(
3755
4016
  Badge,
3756
4017
  {
3757
4018
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3786,7 +4047,7 @@ function NodeLabelBadges({
3786
4047
  colorPalette,
3787
4048
  label
3788
4049
  );
3789
- return /* @__PURE__ */ jsxs11(
4050
+ return /* @__PURE__ */ jsxs12(
3790
4051
  Badge,
3791
4052
  {
3792
4053
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3824,7 +4085,7 @@ function EdgeLabelBadges({
3824
4085
  colorPalette,
3825
4086
  "neutral"
3826
4087
  );
3827
- return /* @__PURE__ */ jsxs11(
4088
+ return /* @__PURE__ */ jsxs12(
3828
4089
  Badge,
3829
4090
  {
3830
4091
  className: "cursor-pointer text-xs px-2.5 py-0.5",
@@ -3901,12 +4162,12 @@ function GraphOverview({
3901
4162
  toggleAllEdgeLabelsDisabled();
3902
4163
  }
3903
4164
  };
3904
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
3905
- /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
3906
- allLabels.length > 0 && /* @__PURE__ */ jsxs11("div", { children: [
3907
- /* @__PURE__ */ jsx12("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Node Labels" }),
3908
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-start gap-2", children: [
3909
- /* @__PURE__ */ jsx12(
4165
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4166
+ /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
4167
+ allLabels.length > 0 && /* @__PURE__ */ jsxs12("div", { children: [
4168
+ /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Node Labels" }),
4169
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4170
+ /* @__PURE__ */ jsx13(
3910
4171
  AllToggleBadge,
3911
4172
  {
3912
4173
  allDisabled: allLabelsDisabled,
@@ -3916,7 +4177,7 @@ function GraphOverview({
3916
4177
  badgeKey: "all"
3917
4178
  }
3918
4179
  ),
3919
- /* @__PURE__ */ jsx12(
4180
+ /* @__PURE__ */ jsx13(
3920
4181
  NodeLabelBadges,
3921
4182
  {
3922
4183
  labels: allLabels,
@@ -3929,10 +4190,10 @@ function GraphOverview({
3929
4190
  )
3930
4191
  ] })
3931
4192
  ] }),
3932
- allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs11("div", { children: [
3933
- /* @__PURE__ */ jsx12("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Edge Types" }),
3934
- /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-start gap-2", children: [
3935
- /* @__PURE__ */ jsx12(
4193
+ allEdgeLabels.length > 0 && /* @__PURE__ */ jsxs12("div", { children: [
4194
+ /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide mb-2", children: "Edge Types" }),
4195
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-col items-start gap-2", children: [
4196
+ /* @__PURE__ */ jsx13(
3936
4197
  AllToggleBadge,
3937
4198
  {
3938
4199
  allDisabled: allEdgeLabelsDisabled,
@@ -3942,7 +4203,7 @@ function GraphOverview({
3942
4203
  badgeKey: "edge-all"
3943
4204
  }
3944
4205
  ),
3945
- /* @__PURE__ */ jsx12(
4206
+ /* @__PURE__ */ jsx13(
3946
4207
  EdgeLabelBadges,
3947
4208
  {
3948
4209
  labels: allEdgeLabels,
@@ -3968,16 +4229,16 @@ function Overview() {
3968
4229
  );
3969
4230
  }, [baseNormalized]);
3970
4231
  if (stats && stats.values != null && stats.values > 0) {
3971
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
3972
- /* @__PURE__ */ jsx12("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
3973
- /* @__PURE__ */ jsxs11("p", { className: "text-sm text-muted-foreground", children: [
4232
+ return /* @__PURE__ */ jsxs12(Fragment3, { children: [
4233
+ /* @__PURE__ */ jsx13("p", { className: "mt-2 text-sm font-semibold", children: "Overview" }),
4234
+ /* @__PURE__ */ jsxs12("p", { className: "text-sm text-muted-foreground", children: [
3974
4235
  formatNumber(stats.values),
3975
4236
  " Values"
3976
4237
  ] })
3977
4238
  ] });
3978
4239
  }
3979
4240
  if (stats && stats.nodes > 0 && colorPalette) {
3980
- return /* @__PURE__ */ jsx12(
4241
+ return /* @__PURE__ */ jsx13(
3981
4242
  GraphOverview,
3982
4243
  {
3983
4244
  stats,
@@ -3994,7 +4255,7 @@ var Overview_default = Overview;
3994
4255
 
3995
4256
  // src/components/ActionPanel/ActionPanel.tsx
3996
4257
  import { useMemo as useMemo5 } from "react";
3997
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
4258
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
3998
4259
  function ActionPanel({ actions }) {
3999
4260
  const useSelection3 = useWorkspaceSelection();
4000
4261
  const selectedNodeIds = useSelection3(
@@ -4010,18 +4271,18 @@ function ActionPanel({ actions }) {
4010
4271
  const secondNode = selectedNodes?.[1];
4011
4272
  const hasEdge = !!selectedEdge;
4012
4273
  const showOverview = !firstNode && !hasEdge;
4013
- return /* @__PURE__ */ jsxs12(
4274
+ return /* @__PURE__ */ jsxs13(
4014
4275
  Card5,
4015
4276
  {
4016
4277
  className: "h-full min-w-64 flex flex-col bg-card shadow-none p-4",
4017
4278
  style: { maxHeight: "100%", overflow: "hidden" },
4018
4279
  children: [
4019
- actions && /* @__PURE__ */ jsx13("div", { className: "flex-shrink-0 mb-2", children: actions }),
4020
- /* @__PURE__ */ jsx13("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-3", children: [
4021
- firstNode && /* @__PURE__ */ jsx13(NodeInfoCard_default, { node: firstNode }),
4022
- hasEdge && selectedEdge && /* @__PURE__ */ jsx13(EdgeInfoCard_default, { edge: selectedEdge }),
4023
- secondNode && /* @__PURE__ */ jsx13(NodeInfoCard_default, { node: secondNode }),
4024
- showOverview && /* @__PURE__ */ jsx13(Overview_default, {})
4280
+ actions && /* @__PURE__ */ jsx14("div", { className: "flex-shrink-0 mb-2", children: actions }),
4281
+ /* @__PURE__ */ jsx14("div", { className: "flex-1 overflow-auto pr-1", children: /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-3", children: [
4282
+ firstNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: firstNode }),
4283
+ hasEdge && selectedEdge && /* @__PURE__ */ jsx14(EdgeInfoCard_default, { edge: selectedEdge }),
4284
+ secondNode && /* @__PURE__ */ jsx14(NodeInfoCard_default, { node: secondNode }),
4285
+ showOverview && /* @__PURE__ */ jsx14(Overview_default, {})
4025
4286
  ] }) })
4026
4287
  ]
4027
4288
  }
@@ -4030,10 +4291,10 @@ function ActionPanel({ actions }) {
4030
4291
  var ActionPanel_default = ActionPanel;
4031
4292
 
4032
4293
  // src/components/GraphA11yList/GraphA11yList.tsx
4033
- import { useMemo as useMemo6, useRef as useRef3 } from "react";
4294
+ import { useMemo as useMemo6, useRef as useRef4 } from "react";
4034
4295
  import { useVirtualizer } from "@tanstack/react-virtual";
4035
4296
  import { cn as cn2 } from "@petrarca/sonnet-core";
4036
- import { jsx as jsx14 } from "react/jsx-runtime";
4297
+ import { jsx as jsx15 } from "react/jsx-runtime";
4037
4298
  var SR_ONLY = "absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip:rect(0,0,0,0)] [clip-path:inset(50%)] -m-px";
4038
4299
  function GraphA11yList({
4039
4300
  visible = false,
@@ -4068,7 +4329,7 @@ function GraphA11yList({
4068
4329
  }));
4069
4330
  return [...nodeRows, ...edgeRows];
4070
4331
  }, [live.nodes, live.edges]);
4071
- const parentRef = useRef3(null);
4332
+ const parentRef = useRef4(null);
4072
4333
  const virtualizer = useVirtualizer({
4073
4334
  count: rows.length,
4074
4335
  getScrollElement: () => parentRef.current,
@@ -4085,18 +4346,18 @@ function GraphA11yList({
4085
4346
  }
4086
4347
  };
4087
4348
  const isSelected = (row) => row.kind === "node" ? selectedNodeIds.includes(row.id) : selectedEdge?.id_ === row.edge.id_;
4088
- return /* @__PURE__ */ jsx14(
4349
+ return /* @__PURE__ */ jsx15(
4089
4350
  "div",
4090
4351
  {
4091
4352
  role: "listbox",
4092
4353
  "aria-label": "Graph elements",
4093
4354
  className: cn2(visible ? "rounded-md border" : SR_ONLY, className),
4094
- children: /* @__PURE__ */ jsx14(
4355
+ children: /* @__PURE__ */ jsx15(
4095
4356
  "div",
4096
4357
  {
4097
4358
  ref: parentRef,
4098
4359
  style: { maxHeight: visible ? maxHeight : void 0, overflow: "auto" },
4099
- children: /* @__PURE__ */ jsx14(
4360
+ children: /* @__PURE__ */ jsx15(
4100
4361
  "div",
4101
4362
  {
4102
4363
  style: {
@@ -4108,7 +4369,7 @@ function GraphA11yList({
4108
4369
  const row = rows[vi.index];
4109
4370
  if (!row) return null;
4110
4371
  const selected = isSelected(row);
4111
- return /* @__PURE__ */ jsx14(
4372
+ return /* @__PURE__ */ jsx15(
4112
4373
  "button",
4113
4374
  {
4114
4375
  type: "button",
@@ -4126,7 +4387,7 @@ function GraphA11yList({
4126
4387
  height: `${vi.size}px`,
4127
4388
  transform: `translateY(${vi.start}px)`
4128
4389
  },
4129
- children: /* @__PURE__ */ jsx14("span", { className: "truncate", children: row.label })
4390
+ children: /* @__PURE__ */ jsx15("span", { className: "truncate", children: row.label })
4130
4391
  },
4131
4392
  vi.key
4132
4393
  );