@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.
package/dist/index.esm.js CHANGED
@@ -4483,6 +4483,17 @@ class GraphEngine {
4483
4483
  getEdges() {
4484
4484
  return [...this.edges];
4485
4485
  }
4486
+ /** Search nodes by label or id (case-insensitive substring match) */
4487
+ searchNodes(query) {
4488
+ if (!query.trim())
4489
+ return [];
4490
+ const q = query.toLowerCase();
4491
+ return this.nodes.filter((n) => {
4492
+ var _a;
4493
+ return ((_a = n.label) !== null && _a !== void 0 ? _a : n.id).toLowerCase().includes(q) ||
4494
+ n.id.toLowerCase().includes(q);
4495
+ });
4496
+ }
4486
4497
  /** Check if a node exists */
4487
4498
  hasNode(id) {
4488
4499
  return this.adjacency.has(id);
@@ -4894,6 +4905,9 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
4894
4905
  const [invertedNodes, setInvertedNodes] = useState([]);
4895
4906
  const [paths, setPaths] = useState([]);
4896
4907
  const [hoveredNode, setHoveredNode] = useState(null);
4908
+ const [searchQuery, setSearchQuery] = useState("");
4909
+ const [searchFocused, setSearchFocused] = useState(false);
4910
+ const searchRef = useRef(null);
4897
4911
  const engine = useMemo(() => new GraphEngine(data), [data]);
4898
4912
  const directed = (_a = data.directed) !== null && _a !== void 0 ? _a : false;
4899
4913
  // Build simulation data
@@ -5263,6 +5277,34 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
5263
5277
  }
5264
5278
  return [];
5265
5279
  }, [selectedArray, mode, connectedNodes, invertedNodes, engine]);
5280
+ const searchResults = useMemo(() => {
5281
+ if (!searchQuery.trim())
5282
+ return [];
5283
+ const q = searchQuery.toLowerCase();
5284
+ return data.nodes.filter((n) => {
5285
+ var _a;
5286
+ return ((_a = n.label) !== null && _a !== void 0 ? _a : n.id).toLowerCase().includes(q) ||
5287
+ n.id.toLowerCase().includes(q);
5288
+ });
5289
+ }, [searchQuery, data.nodes]);
5290
+ const handleSearchSelect = useCallback((nodeId, event) => {
5291
+ var _a;
5292
+ if (event.ctrlKey || event.metaKey) {
5293
+ setSelectedNodes((prev) => {
5294
+ const next = new Set(prev);
5295
+ if (next.has(nodeId))
5296
+ next.delete(nodeId);
5297
+ else
5298
+ next.add(nodeId);
5299
+ return next;
5300
+ });
5301
+ }
5302
+ else {
5303
+ setSelectedNodes(new Set([nodeId]));
5304
+ }
5305
+ setSearchQuery("");
5306
+ (_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.blur();
5307
+ }, []);
5266
5308
  const handleExportCSV = () => {
5267
5309
  if (mode === "paths" && paths.length > 0) {
5268
5310
  exportPathsAsCSV(paths);
@@ -5304,7 +5346,42 @@ const GraphVisualizer = ({ data, width = 900, height = 600, onSelectionChange, n
5304
5346
  flexDirection: "column",
5305
5347
  gap: 12,
5306
5348
  fontSize: 13,
5307
- }, children: [jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569" }, children: "Selection Mode" }), jsx("div", { style: { display: "flex", gap: 4 }, children: [
5349
+ }, children: [jsxs("div", { style: { position: "relative" }, children: [jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569", display: "block", marginBottom: 4 }, children: "Search Nodes" }), 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: {
5350
+ width: "100%",
5351
+ padding: "7px 10px",
5352
+ border: "1px solid #d1d5db",
5353
+ borderRadius: 6,
5354
+ fontSize: 12,
5355
+ outline: "none",
5356
+ boxSizing: "border-box",
5357
+ transition: "border-color 0.15s",
5358
+ borderColor: searchFocused ? "#4f46e5" : "#d1d5db",
5359
+ } }), searchFocused && searchQuery.trim() && (jsx("div", { style: {
5360
+ position: "absolute",
5361
+ top: "100%",
5362
+ left: 0,
5363
+ right: 0,
5364
+ marginTop: 4,
5365
+ background: "#fff",
5366
+ border: "1px solid #e2e8f0",
5367
+ borderRadius: 6,
5368
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
5369
+ maxHeight: 200,
5370
+ overflow: "auto",
5371
+ zIndex: 10,
5372
+ }, children: searchResults.length === 0 ? (jsx("div", { style: { padding: "8px 10px", color: "#94a3b8", fontSize: 12 }, children: "No nodes found" })) : (searchResults.map((n, i) => {
5373
+ var _a;
5374
+ return (jsxs("div", { onMouseDown: (e) => handleSearchSelect(n.id, e), style: {
5375
+ padding: "6px 10px",
5376
+ cursor: "pointer",
5377
+ display: "flex",
5378
+ justifyContent: "space-between",
5379
+ alignItems: "center",
5380
+ borderBottom: i < searchResults.length - 1 ? "1px solid #f1f5f9" : "none",
5381
+ background: selectedNodes.has(n.id) ? "#f0f0ff" : "#fff",
5382
+ fontSize: 12,
5383
+ }, 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: [jsx("span", { style: { fontWeight: 500 }, children: (_a = n.label) !== null && _a !== void 0 ? _a : n.id }), jsx("span", { style: { color: "#94a3b8", fontSize: 11 }, children: n.id })] }, n.id));
5384
+ })) }))] }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [jsx("label", { style: { fontWeight: 600, fontSize: 12, color: "#475569" }, children: "Selection Mode" }), jsx("div", { style: { display: "flex", gap: 4 }, children: [
5308
5385
  ["connected", "Connected"],
5309
5386
  ["inverted", "Inverted"],
5310
5387
  ["paths", "Paths"],