@sandeepsj0000/react-graph-visualizer 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,8 @@ export declare class GraphEngine {
18
18
  getNodes(): GraphNode[];
19
19
  /** Get all edges */
20
20
  getEdges(): GraphEdge[];
21
+ /** Search nodes by label or id (case-insensitive substring match) */
22
+ searchNodes(query: string): GraphNode[];
21
23
  /** Check if a node exists */
22
24
  hasNode(id: string): boolean;
23
25
  /** Check if an edge exists between source and target */
package/dist/index.cjs.js CHANGED
@@ -4485,6 +4485,17 @@ class GraphEngine {
4485
4485
  getEdges() {
4486
4486
  return [...this.edges];
4487
4487
  }
4488
+ /** Search nodes by label or id (case-insensitive substring match) */
4489
+ searchNodes(query) {
4490
+ if (!query.trim())
4491
+ return [];
4492
+ const q = query.toLowerCase();
4493
+ return this.nodes.filter((n) => {
4494
+ var _a;
4495
+ return ((_a = n.label) !== null && _a !== void 0 ? _a : n.id).toLowerCase().includes(q) ||
4496
+ n.id.toLowerCase().includes(q);
4497
+ });
4498
+ }
4488
4499
  /** Check if a node exists */
4489
4500
  hasNode(id) {
4490
4501
  return this.adjacency.has(id);
@@ -4896,6 +4907,9 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
4896
4907
  const [invertedNodes, setInvertedNodes] = react.useState([]);
4897
4908
  const [paths, setPaths] = react.useState([]);
4898
4909
  const [hoveredNode, setHoveredNode] = react.useState(null);
4910
+ const [searchQuery, setSearchQuery] = react.useState("");
4911
+ const [searchFocused, setSearchFocused] = react.useState(false);
4912
+ const searchRef = react.useRef(null);
4899
4913
  const engine = react.useMemo(() => new GraphEngine(data), [data]);
4900
4914
  const directed = (_a = data.directed) !== null && _a !== void 0 ? _a : false;
4901
4915
  // Build simulation data
@@ -5265,6 +5279,34 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
5265
5279
  }
5266
5280
  return [];
5267
5281
  }, [selectedArray, mode, connectedNodes, invertedNodes, engine]);
5282
+ const searchResults = react.useMemo(() => {
5283
+ if (!searchQuery.trim())
5284
+ return [];
5285
+ const q = searchQuery.toLowerCase();
5286
+ return data.nodes.filter((n) => {
5287
+ var _a;
5288
+ return ((_a = n.label) !== null && _a !== void 0 ? _a : n.id).toLowerCase().includes(q) ||
5289
+ n.id.toLowerCase().includes(q);
5290
+ });
5291
+ }, [searchQuery, data.nodes]);
5292
+ const handleSearchSelect = react.useCallback((nodeId, event) => {
5293
+ var _a;
5294
+ if (event.ctrlKey || event.metaKey) {
5295
+ setSelectedNodes((prev) => {
5296
+ const next = new Set(prev);
5297
+ if (next.has(nodeId))
5298
+ next.delete(nodeId);
5299
+ else
5300
+ next.add(nodeId);
5301
+ return next;
5302
+ });
5303
+ }
5304
+ else {
5305
+ setSelectedNodes(new Set([nodeId]));
5306
+ }
5307
+ setSearchQuery("");
5308
+ (_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.blur();
5309
+ }, []);
5268
5310
  const handleExportCSV = () => {
5269
5311
  if (mode === "paths" && paths.length > 0) {
5270
5312
  exportPathsAsCSV(paths);
@@ -5306,7 +5348,42 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
5306
5348
  flexDirection: "column",
5307
5349
  gap: 12,
5308
5350
  fontSize: 13,
5309
- }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [jsxRuntime.jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569" }, children: "Selection Mode" }), jsxRuntime.jsx("div", { style: { display: "flex", gap: 4 }, children: [
5351
+ }, children: [jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [jsxRuntime.jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569", display: "block", marginBottom: 4 }, children: "Search Nodes" }), jsxRuntime.jsx("input", { ref: searchRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onFocus: () => setSearchFocused(true), onBlur: () => setTimeout(() => setSearchFocused(false), 150), placeholder: "Type to search...", style: {
5352
+ width: "100%",
5353
+ padding: "7px 10px",
5354
+ border: "1px solid #d1d5db",
5355
+ borderRadius: 6,
5356
+ fontSize: 12,
5357
+ outline: "none",
5358
+ boxSizing: "border-box",
5359
+ transition: "border-color 0.15s",
5360
+ borderColor: searchFocused ? "#4f46e5" : "#d1d5db",
5361
+ } }), searchFocused && searchQuery.trim() && (jsxRuntime.jsx("div", { style: {
5362
+ position: "absolute",
5363
+ top: "100%",
5364
+ left: 0,
5365
+ right: 0,
5366
+ marginTop: 4,
5367
+ background: "#fff",
5368
+ border: "1px solid #e2e8f0",
5369
+ borderRadius: 6,
5370
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
5371
+ maxHeight: 200,
5372
+ overflow: "auto",
5373
+ zIndex: 10,
5374
+ }, children: searchResults.length === 0 ? (jsxRuntime.jsx("div", { style: { padding: "8px 10px", color: "#94a3b8", fontSize: 12 }, children: "No nodes found" })) : (searchResults.map((n, i) => {
5375
+ var _a;
5376
+ return (jsxRuntime.jsxs("div", { onMouseDown: (e) => handleSearchSelect(n.id, e), style: {
5377
+ padding: "6px 10px",
5378
+ cursor: "pointer",
5379
+ display: "flex",
5380
+ justifyContent: "space-between",
5381
+ alignItems: "center",
5382
+ borderBottom: i < searchResults.length - 1 ? "1px solid #f1f5f9" : "none",
5383
+ background: selectedNodes.has(n.id) ? "#f0f0ff" : "#fff",
5384
+ fontSize: 12,
5385
+ }, onMouseEnter: (e) => (e.currentTarget.style.background = selectedNodes.has(n.id) ? "#e8e8ff" : "#f8fafc"), onMouseLeave: (e) => (e.currentTarget.style.background = selectedNodes.has(n.id) ? "#f0f0ff" : "#fff"), children: [jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: (_a = n.label) !== null && _a !== void 0 ? _a : n.id }), jsxRuntime.jsx("span", { style: { color: "#94a3b8", fontSize: 11 }, children: n.id })] }, n.id));
5386
+ })) }))] }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [jsxRuntime.jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569" }, children: "Selection Mode" }), jsxRuntime.jsx("div", { style: { display: "flex", gap: 4 }, children: [
5310
5387
  ["connected", "Connected"],
5311
5388
  ["inverted", "Inverted"],
5312
5389
  ["paths", "Paths"],