nodalix 0.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.mjs ADDED
@@ -0,0 +1,3840 @@
1
+ "use client";
2
+
3
+ // src/index.js
4
+ import React3, { useEffect as useEffect2, useState as useState2, useMemo as useMemo2 } from "react";
5
+
6
+ // src/Components/Graph.js
7
+ import React2, { useRef, useEffect, useState, useMemo } from "react";
8
+ import { DataSet, Network } from "vis";
9
+
10
+ // src/Components/Options.js
11
+ var getGridTheme = (isDark) => isDark ? {
12
+ background: "#0b1220",
13
+ minor: "rgba(148,163,184,0.14)",
14
+ major: "rgba(148,163,184,0.28)"
15
+ } : {
16
+ background: "#f8fafc",
17
+ minor: "rgba(15,23,42,0.07)",
18
+ major: "rgba(15,23,42,0.14)"
19
+ };
20
+ var getNiceGridStep = (scale) => {
21
+ const targetPx = 32;
22
+ const raw = targetPx / Math.max(scale, 0.05);
23
+ const pow = Math.pow(10, Math.floor(Math.log10(raw)));
24
+ const norm = raw / pow;
25
+ let nice;
26
+ if (norm <= 1) nice = 1;
27
+ else if (norm <= 2) nice = 2;
28
+ else if (norm <= 5) nice = 5;
29
+ else nice = 10;
30
+ return nice * pow;
31
+ };
32
+ var drawNetworkGrid = (ctx, network, isDark) => {
33
+ if (!ctx || !network) return;
34
+ const theme = getGridTheme(isDark);
35
+ const canvas = ctx.canvas;
36
+ const w = canvas.clientWidth;
37
+ const h = canvas.clientHeight;
38
+ if (!w || !h) return;
39
+ const tl = network.DOMtoCanvas({ x: 0, y: 0 });
40
+ const br = network.DOMtoCanvas({ x: w, y: h });
41
+ const minX = Math.min(tl.x, br.x);
42
+ const maxX = Math.max(tl.x, br.x);
43
+ const minY = Math.min(tl.y, br.y);
44
+ const maxY = Math.max(tl.y, br.y);
45
+ const scale = network.getScale();
46
+ const step = getNiceGridStep(scale);
47
+ const majorEvery = 5;
48
+ const lineWidth = 1 / Math.max(scale, 0.05);
49
+ ctx.save();
50
+ ctx.fillStyle = theme.background;
51
+ ctx.fillRect(minX, minY, maxX - minX, maxY - minY);
52
+ const startX = Math.floor(minX / step) * step;
53
+ const startY = Math.floor(minY / step) * step;
54
+ let col = 0;
55
+ for (let x = startX; x <= maxX; x += step, col++) {
56
+ const isMajor = col % majorEvery === 0;
57
+ ctx.strokeStyle = isMajor ? theme.major : theme.minor;
58
+ ctx.lineWidth = isMajor ? lineWidth * 1.35 : lineWidth;
59
+ ctx.beginPath();
60
+ ctx.moveTo(x, minY);
61
+ ctx.lineTo(x, maxY);
62
+ ctx.stroke();
63
+ }
64
+ let row = 0;
65
+ for (let y = startY; y <= maxY; y += step, row++) {
66
+ const isMajor = row % majorEvery === 0;
67
+ ctx.strokeStyle = isMajor ? theme.major : theme.minor;
68
+ ctx.lineWidth = isMajor ? lineWidth * 1.35 : lineWidth;
69
+ ctx.beginPath();
70
+ ctx.moveTo(minX, y);
71
+ ctx.lineTo(maxX, y);
72
+ ctx.stroke();
73
+ }
74
+ ctx.restore();
75
+ };
76
+ var buildInteractionOptions = (dragNodes = true) => ({
77
+ selectable: true,
78
+ multiselect: true,
79
+ dragNodes: !!dragNodes,
80
+ dragView: true,
81
+ zoomView: true,
82
+ hover: true,
83
+ hoverConnectedEdges: false,
84
+ tooltipDelay: 100
85
+ });
86
+ var buildOptions = (isDark, { dragNodes = true } = {}) => {
87
+ const palette = isDark ? {
88
+ text: "#e5e7eb",
89
+ subtext: "#9ca3af",
90
+ edge: "#6ea8ff",
91
+ nodeBorder: "#93c5fd",
92
+ nodeBg: "#0b1220",
93
+ boxBorder: "#374151",
94
+ labelBg: "rgba(17,24,39,.6)",
95
+ mainBorder: "#60a5fa",
96
+ mainBg: "#0f172a"
97
+ } : {
98
+ text: "#111827",
99
+ subtext: "#4b5563",
100
+ edge: "rgb(50, 87, 155)",
101
+ nodeBorder: "#1f3a5f",
102
+ nodeBg: "#ffffff",
103
+ boxBorder: "rgb(200,200,200)",
104
+ labelBg: "#ffffff",
105
+ mainBorder: "#1d4ed8",
106
+ mainBg: "#ffffff"
107
+ };
108
+ return {
109
+ layout: { hierarchical: false },
110
+ // فیزیک خاموش برای چیدمان قطعی
111
+ physics: { enabled: false },
112
+ interaction: buildInteractionOptions(dragNodes),
113
+ edges: {
114
+ width: 2,
115
+ smooth: {
116
+ type: "cubicBezier",
117
+ forceDirection: "horizontal",
118
+ roundness: 0.2
119
+ },
120
+ arrows: { to: { enabled: true } },
121
+ font: {
122
+ size: 14,
123
+ align: "middle",
124
+ color: palette.text,
125
+ background: palette.labelBg,
126
+ face: "Vazir"
127
+ },
128
+ color: {
129
+ color: palette.edge,
130
+ highlight: palette.edge,
131
+ hover: palette.edge,
132
+ inherit: false
133
+ },
134
+ chosen: {
135
+ edge(values, id, selected) {
136
+ if (selected) {
137
+ values.shadow = true;
138
+ values.shadowColor = values.color || "rgba(0,0,0,1)";
139
+ values.shadowSize = 10;
140
+ values.shadowX = 0;
141
+ values.shadowY = 0;
142
+ values.width = 3;
143
+ }
144
+ }
145
+ }
146
+ },
147
+ // پیش‌فرض نودها
148
+ nodes: {
149
+ shape: "dot",
150
+ size: 14,
151
+ borderWidth: 1,
152
+ borderWidthSelected: 2,
153
+ font: {
154
+ size: 12,
155
+ face: "Vazir",
156
+ color: palette.text
157
+ },
158
+ color: {
159
+ border: palette.nodeBorder,
160
+ background: palette.nodeBg,
161
+ highlight: {
162
+ border: palette.mainBorder,
163
+ background: palette.nodeBg
164
+ },
165
+ hover: {
166
+ border: palette.mainBorder,
167
+ background: palette.nodeBg
168
+ }
169
+ },
170
+ shadow: false
171
+ },
172
+ groups: {
173
+ /**
174
+ * MAIN nodes (type: "main") — formerly "address"
175
+ */
176
+ main: {
177
+ shape: "circularImage",
178
+ icon: { face: "FontAwesome", code: "\uF007", size: 50, color: "black" },
179
+ font: { size: 13, face: "Vazir", color: palette.edge, align: "left" },
180
+ borderWidth: 2,
181
+ borderColor: "#FF5733",
182
+ color: { background: "#ffffff" },
183
+ shapeProperties: { useBorderWithImage: true },
184
+ align: "horizontal",
185
+ chosen: {
186
+ node(values) {
187
+ values.borderWidth = 3;
188
+ values.size = 22;
189
+ values.shadow = true;
190
+ values.shadowColor = "rgba(0,0,0,.25)";
191
+ values.shadowSize = 14;
192
+ values.shadowX = 0;
193
+ values.shadowY = 2;
194
+ }
195
+ }
196
+ },
197
+ /**
198
+ * EDGE INFO GROUP (نودهای اطلاعات یال / Tr)
199
+ */
200
+ edgeInfo: {
201
+ shape: "dot",
202
+ size: 8,
203
+ borderWidth: 1,
204
+ font: {
205
+ size: 10,
206
+ face: "Vazir",
207
+ color: palette.subtext,
208
+ align: "center"
209
+ },
210
+ color: {
211
+ border: "rgba(107,114,128,0.55)",
212
+ background: "rgba(107,114,128,0.28)",
213
+ highlight: {
214
+ border: "rgba(107,114,128,0.7)",
215
+ background: "rgba(107,114,128,0.35)"
216
+ },
217
+ hover: {
218
+ border: "rgba(107,114,128,0.7)",
219
+ background: "rgba(107,114,128,0.35)"
220
+ }
221
+ },
222
+ chosen: {
223
+ node(values) {
224
+ values.borderWidth = 1;
225
+ values.size = 8;
226
+ values.shadow = false;
227
+ }
228
+ }
229
+ },
230
+ sub: {
231
+ shape: "dot",
232
+ size: 6,
233
+ font: { size: 13, family: "Arial", color: palette.edge },
234
+ color: {
235
+ border: palette.edge,
236
+ background: palette.edge,
237
+ highlight: { background: palette.edge, border: palette.edge }
238
+ },
239
+ borderWidth: 0
240
+ },
241
+ hotWallet: {
242
+ shape: "text",
243
+ font: { size: 12, face: "Vazir", color: palette.edge, align: "left" },
244
+ borderWidth: 0,
245
+ size: 0,
246
+ color: {
247
+ background: "transparent",
248
+ border: "transparent",
249
+ highlight: { background: "transparent", border: "transparent" }
250
+ }
251
+ },
252
+ labelNode: {
253
+ font: { size: 12, face: "Vazir", color: palette.subtext, align: "center" },
254
+ borderWidth: 1,
255
+ borderColor: "#FF5733",
256
+ align: "horizontal",
257
+ shape: "box",
258
+ color: {
259
+ border: palette.boxBorder,
260
+ background: isDark ? "rgba(31,41,55,.6)" : "#fff"
261
+ },
262
+ size: 15
263
+ },
264
+ Arrow: {
265
+ font: { size: 22, face: "Vazir", color: palette.edge, align: "center" },
266
+ borderWidth: 0,
267
+ align: "center",
268
+ shape: "text",
269
+ color: {
270
+ background: "transparent",
271
+ border: "transparent",
272
+ highlight: { background: "transparent", border: "transparent" }
273
+ },
274
+ size: 16
275
+ }
276
+ }
277
+ };
278
+ };
279
+
280
+ // src/Components/miladiCalendar.js
281
+ function MiladiCalendar(time) {
282
+ if (time.toString().length === 10) {
283
+ time = time * 1e3;
284
+ }
285
+ const date = new Date(time);
286
+ const year = date.getFullYear();
287
+ const month = date.getMonth() + 1;
288
+ const day = date.getDate();
289
+ const hour = date.getHours();
290
+ const minute = date.getMinutes();
291
+ const second = date.getSeconds();
292
+ return {
293
+ year,
294
+ month,
295
+ day,
296
+ hour,
297
+ minute,
298
+ second
299
+ };
300
+ }
301
+
302
+ // src/graphConfig.js
303
+ var NODE_TYPE = {
304
+ MAIN: "main",
305
+ SUB: "sub"
306
+ };
307
+ var EDGE_LABEL_FIELDS = {
308
+ VALUE: "value",
309
+ SUB_TEXT: "subText",
310
+ SUB_VALUE: "subValue",
311
+ TIME: "time"
312
+ };
313
+ var DEFAULT_EDGE_LABEL_FIELDS = [
314
+ EDGE_LABEL_FIELDS.VALUE,
315
+ EDGE_LABEL_FIELDS.SUB_TEXT,
316
+ EDGE_LABEL_FIELDS.TIME
317
+ ];
318
+ var EDGE_LINE_STYLE = {
319
+ SOLID: "solid",
320
+ DASHED: "dashed",
321
+ DOTTED: "dotted"
322
+ };
323
+ var DEFAULT_EDGE_LINE_STYLE = EDGE_LINE_STYLE.SOLID;
324
+ function normalizeEdgeLineStyle(style) {
325
+ const allowed = new Set(Object.values(EDGE_LINE_STYLE));
326
+ return allowed.has(style) ? style : DEFAULT_EDGE_LINE_STYLE;
327
+ }
328
+ function resolveEdgeVisStyle(options = {}) {
329
+ const {
330
+ isDark = false,
331
+ paintedColor = null,
332
+ paintedLineStyle = null
333
+ } = options;
334
+ const lineStyle = normalizeEdgeLineStyle(paintedLineStyle || DEFAULT_EDGE_LINE_STYLE);
335
+ const defaultColor = isDark ? "#6ea8ff" : "rgb(50, 87, 155)";
336
+ const color = paintedColor || defaultColor;
337
+ let dashes = false;
338
+ if (lineStyle === EDGE_LINE_STYLE.DASHED) dashes = [10, 8];
339
+ if (lineStyle === EDGE_LINE_STYLE.DOTTED) dashes = [2, 7];
340
+ return {
341
+ width: 2,
342
+ dashes,
343
+ smooth: {
344
+ type: "cubicBezier",
345
+ forceDirection: "horizontal",
346
+ roundness: 0.2
347
+ },
348
+ arrows: { to: { enabled: true, scaleFactor: 0.85 } },
349
+ color: {
350
+ color,
351
+ highlight: color,
352
+ hover: color,
353
+ inherit: false
354
+ }
355
+ };
356
+ }
357
+ function mirrorEdgeRef(edge, overrides = {}) {
358
+ return {
359
+ id: overrides.id ?? (edge == null ? void 0 : edge.id),
360
+ value: (edge == null ? void 0 : edge.value) ?? null,
361
+ subText: (edge == null ? void 0 : edge.subText) ?? null,
362
+ subValue: (edge == null ? void 0 : edge.subValue) ?? null,
363
+ time: (edge == null ? void 0 : edge.time) ?? Math.floor(Date.now() / 1e3)
364
+ };
365
+ }
366
+ var DEFAULT_RATE_COLOR_RANGES = [
367
+ { min: 70, max: Infinity, color: "red" },
368
+ { min: 50, max: 70, color: "orange" }
369
+ ];
370
+ function normalizeEdgeLabelFields(fields) {
371
+ if (!Array.isArray(fields) || !fields.length) return [...DEFAULT_EDGE_LABEL_FIELDS];
372
+ const allowed = new Set(Object.values(EDGE_LABEL_FIELDS));
373
+ const normalized = fields.filter((f) => allowed.has(f));
374
+ return normalized.length ? normalized : [...DEFAULT_EDGE_LABEL_FIELDS];
375
+ }
376
+ function normalizeRateColorRanges(ranges) {
377
+ if (!Array.isArray(ranges) || !ranges.length) return [...DEFAULT_RATE_COLOR_RANGES];
378
+ return ranges.filter((r) => r && r.color).map((r) => ({
379
+ min: r.min ?? -Infinity,
380
+ max: r.max ?? Infinity,
381
+ color: r.color
382
+ }));
383
+ }
384
+ function getRateBorderColor(rate, ranges, defaultColor) {
385
+ if (rate == null || !Number.isFinite(Number(rate))) return defaultColor;
386
+ const value = Number(rate);
387
+ const bands = normalizeRateColorRanges(ranges);
388
+ for (const band of bands) {
389
+ const min = band.min ?? -Infinity;
390
+ const max = band.max ?? Infinity;
391
+ if (max === Infinity) {
392
+ if (value >= min) return band.color;
393
+ } else if (value >= min && value < max) {
394
+ return band.color;
395
+ }
396
+ }
397
+ return defaultColor;
398
+ }
399
+ var formatTime = (timestamp) => {
400
+ const m = MiladiCalendar(timestamp);
401
+ return `\u200E ${m.year}/${m.month}/${m.day} - ${m.hour}:${m.minute}`;
402
+ };
403
+ function buildEdgeInfoLabel(edgeData, fields = DEFAULT_EDGE_LABEL_FIELDS) {
404
+ const list = normalizeEdgeLabelFields(fields);
405
+ if (!edgeData) return "";
406
+ const lines = [];
407
+ const hasValue = list.includes(EDGE_LABEL_FIELDS.VALUE);
408
+ const hasSubText = list.includes(EDGE_LABEL_FIELDS.SUB_TEXT);
409
+ const hasSubValue = list.includes(EDGE_LABEL_FIELDS.SUB_VALUE);
410
+ const hasTime = list.includes(EDGE_LABEL_FIELDS.TIME);
411
+ if (hasValue) {
412
+ let line = `\u200E${Number(edgeData.value || 0).toLocaleString()}`;
413
+ if (hasSubText && edgeData.subText) line += ` ${edgeData.subText}`;
414
+ lines.push(line);
415
+ } else if (hasSubText && edgeData.subText) {
416
+ lines.push(String(edgeData.subText));
417
+ }
418
+ if (hasSubValue) {
419
+ const v = edgeData.subValue;
420
+ lines.push(
421
+ typeof v === "number" && !isNaN(v) ? `\u200E${v.toLocaleString()} USD` : "\u0642\u06CC\u0645\u062A \u0646\u0627\u0645\u0634\u062E\u0635"
422
+ );
423
+ }
424
+ if (hasTime) {
425
+ const t = edgeData.time;
426
+ lines.push(
427
+ typeof t === "number" && !isNaN(t) ? formatTime(t) : "\u0632\u0645\u0627\u0646 \u0646\u0627\u0645\u0634\u062E\u0635"
428
+ );
429
+ }
430
+ return lines.join(" \n ");
431
+ }
432
+ function getNodeDisplayLabel(item) {
433
+ if (!item) return "";
434
+ if (item.type === NODE_TYPE.SUB) return item.subText ?? "";
435
+ if (item.label != null) return item.label;
436
+ if (item.entity != null) return item.entity.name ?? "";
437
+ const text = item.text ?? "";
438
+ return `...${String(text).substring(0, 7)}`;
439
+ }
440
+ var DEFAULT_NODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
441
+ <circle cx="32" cy="32" r="31" fill="#f1f5f9" stroke="#cbd5e1" stroke-width="2"/>
442
+ <circle cx="32" cy="24" r="9" fill="#94a3b8"/>
443
+ <path fill="#94a3b8" d="M14 52c3.2-9.6 11.2-14 18-14s14.8 4.4 18 14H14z"/>
444
+ </svg>`;
445
+ var DEFAULT_NODE_IMAGE = `data:image/svg+xml,${encodeURIComponent(
446
+ DEFAULT_NODE_ICON_SVG
447
+ )}`;
448
+ var hasEntityImage = (value) => typeof value === "string" && value.trim().length > 0;
449
+ var GRAPH_LANGUAGE = {
450
+ FA: "fa",
451
+ EN: "en"
452
+ };
453
+ function normalizeGraphLanguage(value) {
454
+ return value === GRAPH_LANGUAGE.EN ? GRAPH_LANGUAGE.EN : GRAPH_LANGUAGE.FA;
455
+ }
456
+ function getNodeEntityImage(item) {
457
+ var _a, _b, _c;
458
+ if (!(item == null ? void 0 : item.entity)) return DEFAULT_NODE_IMAGE;
459
+ const fromMetadata = (_b = (_a = item.entity) == null ? void 0 : _a.metadata) == null ? void 0 : _b.image;
460
+ const fromEntity = (_c = item.entity) == null ? void 0 : _c.image;
461
+ if (hasEntityImage(fromMetadata)) return fromMetadata.trim();
462
+ if (hasEntityImage(fromEntity)) return fromEntity.trim();
463
+ return DEFAULT_NODE_IMAGE;
464
+ }
465
+
466
+ // src/Components/SetEdgesData.js
467
+ var SetEdgesData = (GraphData) => {
468
+ const CreatedEdges = [];
469
+ const nodeIdMap = GraphData.reduce((map, node) => {
470
+ map[node.id] = node.id;
471
+ return map;
472
+ }, {});
473
+ GraphData.forEach((node) => {
474
+ if (node.type !== NODE_TYPE.MAIN) return;
475
+ if (Array.isArray(node.inputs)) {
476
+ node.inputs.forEach((edgeData) => {
477
+ const sourceId = nodeIdMap[edgeData.id];
478
+ if (sourceId !== void 0) {
479
+ CreatedEdges.push({ from: sourceId, to: node.id });
480
+ }
481
+ });
482
+ }
483
+ if (Array.isArray(node.outputs)) {
484
+ node.outputs.forEach((edgeData) => {
485
+ const targetId = nodeIdMap[edgeData.id];
486
+ if (targetId !== void 0) {
487
+ CreatedEdges.push({ from: node.id, to: targetId });
488
+ }
489
+ });
490
+ }
491
+ });
492
+ return CreatedEdges;
493
+ };
494
+
495
+ // src/Components/Graph.js
496
+ import html2canvas from "html2canvas";
497
+ import * as XLSX from "xlsx";
498
+
499
+ // src/graphGuide.js
500
+ function getGraphGuide(isFa) {
501
+ if (isFa) {
502
+ return {
503
+ title: "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u0627\u0632 \u06AF\u0631\u0627\u0641",
504
+ close: "\u0628\u0633\u062A\u0646",
505
+ sections: [
506
+ {
507
+ title: "\u062D\u0631\u06A9\u062A \u062F\u0631 \u0628\u0648\u0645",
508
+ items: [
509
+ "\u0628\u0631\u0627\u06CC \u062C\u0627\u0628\u0647\u200C\u062C\u0627\u06CC\u06CC \u0646\u0645\u0627\u060C \u0631\u0648\u06CC \u0641\u0636\u0627\u06CC \u062E\u0627\u0644\u06CC \u06A9\u0644\u06CC\u06A9 \u06A9\u0631\u062F\u0647 \u0648 \u0628\u06A9\u0634\u06CC\u062F.",
510
+ "\u0628\u0627 \u0627\u0633\u06A9\u0631\u0648\u0644 \u0645\u0627\u0648\u0633 \u0628\u0632\u0631\u06AF\u200C\u0646\u0645\u0627\u06CC\u06CC \u0648 \u06A9\u0648\u0686\u06A9\u200C\u0646\u0645\u0627\u06CC\u06CC \u06A9\u0646\u06CC\u062F."
511
+ ]
512
+ },
513
+ {
514
+ title: "\u0627\u0646\u062A\u062E\u0627\u0628 \u0648 \u0648\u06CC\u0631\u0627\u06CC\u0634",
515
+ items: [
516
+ "\u0631\u0648\u06CC \u06AF\u0631\u0647 \u06CC\u0627 \u06CC\u0627\u0644 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F \u062A\u0627 \u0627\u0646\u062A\u062E\u0627\u0628 \u0634\u0648\u062F.",
517
+ "\u0628\u0631\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u0686\u0646\u062F\u062A\u0627\u06CC\u06CC\u060C \u06A9\u0644\u06CC\u062F Ctrl \u0631\u0627 \u0646\u06AF\u0647 \u062F\u0627\u0631\u06CC\u062F \u0648 \u06A9\u0644\u06CC\u06A9 \u06A9\u0646\u06CC\u062F.",
518
+ "\u06AF\u0631\u0647\u200C\u0647\u0627\u06CC \u0627\u0635\u0644\u06CC (main) \u0631\u0627 \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u0628\u0627 \u06A9\u0634\u06CC\u062F\u0646 \u062C\u0627\u0628\u0647\u200C\u062C\u0627 \u06A9\u0646\u06CC\u062F."
519
+ ]
520
+ },
521
+ {
522
+ title: "\u067E\u0646\u0644 \u0627\u0628\u0632\u0627\u0631\u0647\u0627 (\u06AF\u0648\u0634\u0647 \u0628\u0627\u0644\u0627)",
523
+ items: [
524
+ "\u062C\u0633\u062A\u062C\u0648: \u0647\u0627\u06CC\u0644\u0627\u06CC\u062A \u06AF\u0631\u0647 \u0628\u0631 \u0627\u0633\u0627\u0633 \u0634\u0646\u0627\u0633\u0647 \u06CC\u0627 \u0628\u0631\u0686\u0633\u0628.",
525
+ "\u0646\u0645\u0627\u06CC\u0634 \u0627\u0637\u0644\u0627\u0639\u0627\u062A \u06CC\u0627\u0644: \u0646\u0645\u0627\u06CC\u0634 \u06CC\u0627 \u0645\u062E\u0641\u06CC \u06A9\u0631\u062F\u0646 \u06AF\u0631\u0647\u200C\u0647\u0627\u06CC \u0645\u06CC\u0627\u0646\u06CC \u0631\u0648\u06CC \u06CC\u0627\u0644\u200C\u0647\u0627.",
526
+ "\u06AF\u0631\u06CC\u062F: \u0646\u0645\u0627\u06CC\u0634 \u0634\u0628\u06A9\u0647\u0654 \u067E\u0633\u200C\u0632\u0645\u06CC\u0646\u0647.",
527
+ "\u0631\u0646\u06AF \u06CC\u0627\u0644: \u0631\u0646\u06AF\u200C\u0622\u0645\u06CC\u0632\u06CC \u06CC\u0627\u0644\u200C\u0647\u0627\u06CC \u0627\u0646\u062A\u062E\u0627\u0628\u200C\u0634\u062F\u0647.",
528
+ "\u0645\u0631\u062A\u0628\u200C\u0633\u0627\u0632\u06CC: \u0686\u06CC\u062F\u0645\u0627\u0646 \u062E\u0648\u062F\u06A9\u0627\u0631 \u06AF\u0631\u0647\u200C\u0647\u0627.",
529
+ "\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A \u0648 \u062E\u0631\u0648\u062C\u06CC Excel.",
530
+ "\u0647\u0627\u06CC\u0644\u0627\u06CC\u062A \u0645\u0633\u06CC\u0631\u0647\u0627: \u067E\u06CC\u062F\u0627 \u06A9\u0631\u062F\u0646 \u0645\u0633\u06CC\u0631 \u0628\u06CC\u0646 \u062F\u0648 \u0634\u0646\u0627\u0633\u0647 \u06AF\u0631\u0647."
531
+ ]
532
+ },
533
+ {
534
+ title: "\u0627\u0637\u0644\u0627\u0639\u0627\u062A \u0647\u0646\u06AF\u0627\u0645 \u0647\u0627\u0648\u0631",
535
+ items: [
536
+ "\u0628\u0627 \u0642\u0631\u0627\u0631 \u062F\u0627\u062F\u0646 \u0645\u0648\u0633 \u0631\u0648\u06CC \u06AF\u0631\u0647\u060C \u062C\u0632\u0626\u06CC\u0627\u062A \u0622\u0646 \u0646\u0645\u0627\u06CC\u0634 \u062F\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F.",
537
+ "\u0627\u06AF\u0631 \u062A\u0648\u0633\u0639\u0647\u200C\u062F\u0647\u0646\u062F\u0647 \u0641\u0639\u0627\u0644 \u06A9\u0631\u062F\u0647 \u0628\u0627\u0634\u062F\u060C \u0647\u0627\u0648\u0631 \u0631\u0648\u06CC \u06CC\u0627\u0644 \u0647\u0645 \u0627\u0637\u0644\u0627\u0639\u0627\u062A \u0627\u062A\u0635\u0627\u0644 \u0631\u0627 \u0646\u0634\u0627\u0646 \u0645\u06CC\u200C\u062F\u0647\u062F."
538
+ ]
539
+ },
540
+ {
541
+ title: "\u0632\u0628\u0627\u0646",
542
+ items: [
543
+ "\u0627\u0632 \u0628\u062E\u0634 \u0632\u0628\u0627\u0646 \u062F\u0631 \u067E\u0646\u0644 \u0627\u0628\u0632\u0627\u0631\u0647\u0627 \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u0628\u06CC\u0646 \u0641\u0627\u0631\u0633\u06CC \u0648 \u0627\u0646\u06AF\u0644\u06CC\u0633\u06CC \u062C\u0627\u0628\u0647\u200C\u062C\u0627 \u0634\u0648\u06CC\u062F."
544
+ ]
545
+ }
546
+ ]
547
+ };
548
+ }
549
+ return {
550
+ title: "Graph Usage Guide",
551
+ close: "Close",
552
+ sections: [
553
+ {
554
+ title: "Canvas navigation",
555
+ items: [
556
+ "Click and drag empty space to pan the view.",
557
+ "Use the mouse wheel to zoom in and out."
558
+ ]
559
+ },
560
+ {
561
+ title: "Selection and editing",
562
+ items: [
563
+ "Click a node or edge to select it.",
564
+ "Hold Ctrl and click for multi-selection.",
565
+ "Main nodes can be repositioned by dragging."
566
+ ]
567
+ },
568
+ {
569
+ title: "Tools panel (top corner)",
570
+ items: [
571
+ "Search: highlight nodes by ID or label.",
572
+ "Edge info nodes: show or hide mid-edge label nodes.",
573
+ "Grid: toggle background grid.",
574
+ "Edge color: paint selected edges.",
575
+ "Arrange: auto-layout nodes.",
576
+ "Screenshot and Excel export.",
577
+ "Path highlight: find routes between two node IDs."
578
+ ]
579
+ },
580
+ {
581
+ title: "Hover information",
582
+ items: [
583
+ "Hover a node to see its details.",
584
+ "If enabled by the developer, hovering an edge shows connection data."
585
+ ]
586
+ },
587
+ {
588
+ title: "Language",
589
+ items: [
590
+ "Switch between Persian and English from the language section in the tools panel."
591
+ ]
592
+ }
593
+ ]
594
+ };
595
+ }
596
+
597
+ // src/branding.js
598
+ var FOOTER_TEXT = "powered by nodalix";
599
+
600
+ // src/edition.js
601
+ var NODALIX_EDITION = "full";
602
+ var NODALIX_MAX_NODES = Infinity;
603
+ var isBasicEdition = () => NODALIX_EDITION === "basic";
604
+ var isFullEdition = () => NODALIX_EDITION === "full";
605
+ var hasNodeLimit = () => Number.isFinite(NODALIX_MAX_NODES);
606
+
607
+ // src/graphNodeLimits.js
608
+ var S = (v) => String(v);
609
+ function isHelperNodeId(id) {
610
+ const s = S(id);
611
+ return s.endsWith("_Arrow") || s.endsWith("hotWallet") || s.includes("Tr");
612
+ }
613
+ function isUserGraphNode(node) {
614
+ return Boolean(node == null ? void 0 : node.id) && !isHelperNodeId(node.id);
615
+ }
616
+ function countGraphNodes(nodes) {
617
+ if (!Array.isArray(nodes)) return 0;
618
+ return nodes.filter(isUserGraphNode).length;
619
+ }
620
+ function enforceNodeLimit(nodes, maxNodes) {
621
+ if (!Array.isArray(nodes)) return [];
622
+ if (!Number.isFinite(maxNodes)) return [...nodes];
623
+ let keptCount = 0;
624
+ const kept = [];
625
+ for (const node of nodes) {
626
+ if (!isUserGraphNode(node)) {
627
+ kept.push(node);
628
+ continue;
629
+ }
630
+ if (keptCount >= maxNodes) continue;
631
+ kept.push(node);
632
+ keptCount += 1;
633
+ }
634
+ return kept;
635
+ }
636
+ function canAddGraphNode(nodes, maxNodes) {
637
+ if (!Number.isFinite(maxNodes)) return true;
638
+ return countGraphNodes(nodes) < maxNodes;
639
+ }
640
+ function getNodeLimitError(maxNodes) {
641
+ return `Graph node limit reached (${maxNodes} max in nodalix-basic).`;
642
+ }
643
+
644
+ // src/Components/graphPanelIcons.js
645
+ import React from "react";
646
+ var stroke = {
647
+ fill: "none",
648
+ stroke: "currentColor",
649
+ strokeWidth: 2,
650
+ strokeLinecap: "round",
651
+ strokeLinejoin: "round"
652
+ };
653
+ function IconCamera({ size = 16 }) {
654
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
655
+ "path",
656
+ {
657
+ ...stroke,
658
+ d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"
659
+ }
660
+ ), /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "12", cy: "13", r: "4" }));
661
+ }
662
+ function IconTable({ size = 16 }) {
663
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { ...stroke, x: "3", y: "4", width: "18", height: "16", rx: "2" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M3 10h18M9 4v16M15 4v16" }));
664
+ }
665
+ function IconRoute({ size = 16 }) {
666
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "6", cy: "19", r: "2" }), /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "18", cy: "5", r: "2" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M8 17c4-2 6-5 8-10" }));
667
+ }
668
+ function IconEraser({ size = 16 }) {
669
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
670
+ "path",
671
+ {
672
+ ...stroke,
673
+ d: "M20 20H8l-6-6 9.5-9.5a2.8 2.8 0 0 1 4 0L20 10v10z"
674
+ }
675
+ ), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M6.5 13.5L14 6" }));
676
+ }
677
+ function IconSearch({ size = 16 }) {
678
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "11", cy: "11", r: "7" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M20 20l-3.5-3.5" }));
679
+ }
680
+ function IconGlobe({ size = 14 }) {
681
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("circle", { ...stroke, cx: "12", cy: "12", r: "9" }), /* @__PURE__ */ React.createElement("path", { ...stroke, d: "M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18" }));
682
+ }
683
+ function IconChevron({ size = 14, up = true }) {
684
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
685
+ "path",
686
+ {
687
+ ...stroke,
688
+ d: up ? "M6 15l6-6 6 6" : "M6 9l6 6 6-6"
689
+ }
690
+ ));
691
+ }
692
+ function IconLineDash({ size = 28 }) {
693
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
694
+ "line",
695
+ {
696
+ x1: "4",
697
+ y1: "14",
698
+ x2: "24",
699
+ y2: "14",
700
+ stroke: "currentColor",
701
+ strokeWidth: "2.5",
702
+ strokeDasharray: "5 4",
703
+ strokeLinecap: "round"
704
+ }
705
+ ));
706
+ }
707
+ function IconLineDot({ size = 28 }) {
708
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
709
+ "line",
710
+ {
711
+ x1: "4",
712
+ y1: "14",
713
+ x2: "24",
714
+ y2: "14",
715
+ stroke: "currentColor",
716
+ strokeWidth: "2.5",
717
+ strokeDasharray: "1.5 4",
718
+ strokeLinecap: "round"
719
+ }
720
+ ));
721
+ }
722
+ function IconLineSolid({ size = 28 }) {
723
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 28 28", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
724
+ "line",
725
+ {
726
+ x1: "4",
727
+ y1: "14",
728
+ x2: "24",
729
+ y2: "14",
730
+ stroke: "currentColor",
731
+ strokeWidth: "2.5",
732
+ strokeLinecap: "round"
733
+ }
734
+ ));
735
+ }
736
+ function IconButtonContent({ icon, label }) {
737
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
738
+ "span",
739
+ {
740
+ style: {
741
+ display: "inline-flex",
742
+ alignItems: "center",
743
+ justifyContent: "center",
744
+ flexShrink: 0
745
+ }
746
+ },
747
+ icon
748
+ ), label ? /* @__PURE__ */ React.createElement("span", null, label) : null);
749
+ }
750
+
751
+ // src/Components/Graph.js
752
+ var S2 = (v) => String(v);
753
+ var Nodalix_graph_engine = ({
754
+ Data,
755
+ NodesPosition,
756
+ SetNodesPosition,
757
+ Reload,
758
+ SetReload,
759
+ SetData,
760
+ SetScale,
761
+ Scale,
762
+ SetXPosition,
763
+ XPosition,
764
+ SetYPosition,
765
+ YPosition,
766
+ edgeLabelFields,
767
+ rateColorRanges,
768
+ showEdgeHoverInfo = false,
769
+ showHelpButton = true,
770
+ gridUserConfigurable = true,
771
+ showGrid: showGridProp = true,
772
+ nodesDraggableUserConfigurable = true,
773
+ nodesDraggable: nodesDraggableProp = true,
774
+ edgeInfoNodesUserConfigurable = true,
775
+ showEdgeInfoNodes: showEdgeInfoNodesProp = true,
776
+ languageUserConfigurable = true,
777
+ language: languageProp = "fa",
778
+ TakeSceenShot,
779
+ SetSelectedEdges,
780
+ PaintedEdges,
781
+ SetPaintedEdges,
782
+ setSelectedNodes,
783
+ SetNode,
784
+ onNodeClickRef
785
+ }) => {
786
+ var _a;
787
+ const resolvedEdgeLabelFields = useMemo(
788
+ () => normalizeEdgeLabelFields(edgeLabelFields),
789
+ [edgeLabelFields]
790
+ );
791
+ const resolvedRateColorRanges = useMemo(
792
+ () => normalizeRateColorRanges(rateColorRanges),
793
+ [rateColorRanges]
794
+ );
795
+ useEffect(() => {
796
+ console.log(Data);
797
+ }, [Data]);
798
+ const [isDarkMode, setIsDark] = useState(false);
799
+ const [isStart, setIsStart] = useState(false);
800
+ const [screenShot, setScreenShot] = useState(TakeSceenShot);
801
+ const [isCapturing, setIsCapturing] = useState(false);
802
+ const [AddPositionLoading, setAddPositionLoading] = useState(false);
803
+ const [searchTerm, setSearchTerm] = useState("");
804
+ const [userShowEdgeInfoNodes, setUserShowEdgeInfoNodes] = useState(
805
+ !!showEdgeInfoNodesProp
806
+ );
807
+ const [userShowGrid, setUserShowGrid] = useState(!!showGridProp);
808
+ const [userNodesDraggable, setUserNodesDraggable] = useState(
809
+ !!nodesDraggableProp
810
+ );
811
+ const [userLang, setUserLang] = useState(
812
+ () => normalizeGraphLanguage(languageProp)
813
+ );
814
+ const [pathFromId, setPathFromId] = useState("");
815
+ const [pathToId, setPathToId] = useState("");
816
+ const [pathMessage, setPathMessage] = useState("");
817
+ const [hoverInfo, setHoverInfo] = useState({
818
+ visible: false,
819
+ x: 0,
820
+ y: 0,
821
+ node: null
822
+ });
823
+ const [edgeHoverInfo, setEdgeHoverInfo] = useState({
824
+ visible: false,
825
+ x: 0,
826
+ y: 0,
827
+ fromId: "",
828
+ toId: "",
829
+ summary: ""
830
+ });
831
+ const [showGuide, setShowGuide] = useState(false);
832
+ const [panelOpen, setPanelOpen] = useState(true);
833
+ const showEdgeHoverInfoRef = useRef(!!showEdgeHoverInfo);
834
+ const edgeLabelFieldsRef = useRef(resolvedEdgeLabelFields);
835
+ const containerRef = useRef(null);
836
+ const networkInstanceRef = useRef(null);
837
+ const showGridRef = useRef(!!showGridProp);
838
+ const nodesDraggableRef = useRef(!!nodesDraggableProp);
839
+ const isDarkModeRef = useRef(
840
+ typeof document !== "undefined" && document.documentElement.classList.contains("dark")
841
+ );
842
+ const nodesRef = useRef(null);
843
+ const edgesRef = useRef(null);
844
+ const initializedRef = useRef(false);
845
+ const viewportInitializedRef = useRef(false);
846
+ const showEdgeInfoNodes = edgeInfoNodesUserConfigurable ? userShowEdgeInfoNodes : !!showEdgeInfoNodesProp;
847
+ const ShowGrid = gridUserConfigurable ? userShowGrid : !!showGridProp;
848
+ const nodesDraggableEnabled = nodesDraggableUserConfigurable ? userNodesDraggable : !!nodesDraggableProp;
849
+ const lang = languageUserConfigurable ? userLang : normalizeGraphLanguage(languageProp);
850
+ const isFa = lang === "fa";
851
+ const guideContent = useMemo(() => getGraphGuide(isFa), [isFa]);
852
+ useEffect(() => {
853
+ showEdgeHoverInfoRef.current = !!showEdgeHoverInfo;
854
+ if (!showEdgeHoverInfo) {
855
+ setEdgeHoverInfo({
856
+ visible: false,
857
+ x: 0,
858
+ y: 0,
859
+ fromId: "",
860
+ toId: "",
861
+ summary: ""
862
+ });
863
+ }
864
+ }, [showEdgeHoverInfo]);
865
+ useEffect(() => {
866
+ edgeLabelFieldsRef.current = resolvedEdgeLabelFields;
867
+ }, [resolvedEdgeLabelFields]);
868
+ useEffect(() => {
869
+ const el = document.documentElement;
870
+ const update = () => setIsDark(el.classList.contains("dark"));
871
+ update();
872
+ const obs = new MutationObserver(update);
873
+ obs.observe(el, { attributes: true, attributeFilter: ["class"] });
874
+ return () => obs.disconnect();
875
+ }, []);
876
+ useEffect(() => {
877
+ if (edgeInfoNodesUserConfigurable) {
878
+ setUserShowEdgeInfoNodes(!!showEdgeInfoNodesProp);
879
+ }
880
+ }, [showEdgeInfoNodesProp, edgeInfoNodesUserConfigurable]);
881
+ useEffect(() => {
882
+ if (gridUserConfigurable) {
883
+ setUserShowGrid(!!showGridProp);
884
+ }
885
+ }, [showGridProp, gridUserConfigurable]);
886
+ useEffect(() => {
887
+ if (nodesDraggableUserConfigurable) {
888
+ setUserNodesDraggable(!!nodesDraggableProp);
889
+ }
890
+ }, [nodesDraggableProp, nodesDraggableUserConfigurable]);
891
+ useEffect(() => {
892
+ if (languageUserConfigurable) {
893
+ setUserLang(normalizeGraphLanguage(languageProp));
894
+ }
895
+ }, [languageProp, languageUserConfigurable]);
896
+ useEffect(() => {
897
+ var _a2;
898
+ showGridRef.current = !!ShowGrid;
899
+ (_a2 = networkInstanceRef.current) == null ? void 0 : _a2.redraw();
900
+ }, [ShowGrid]);
901
+ useEffect(() => {
902
+ nodesDraggableRef.current = !!nodesDraggableEnabled;
903
+ const instance = networkInstanceRef.current;
904
+ if (!instance) return;
905
+ try {
906
+ instance.setOptions({
907
+ interaction: buildInteractionOptions(nodesDraggableEnabled)
908
+ });
909
+ } catch (_) {
910
+ }
911
+ }, [nodesDraggableEnabled]);
912
+ useEffect(() => {
913
+ var _a2;
914
+ isDarkModeRef.current = !!isDarkMode;
915
+ (_a2 = networkInstanceRef.current) == null ? void 0 : _a2.redraw();
916
+ }, [isDarkMode]);
917
+ const graphNodeCount = useMemo(() => countGraphNodes(Data), [Data]);
918
+ const nodeLimitReached = isBasicEdition() && Number.isFinite(NODALIX_MAX_NODES) && graphNodeCount >= NODALIX_MAX_NODES;
919
+ const dataRef = useRef(Data);
920
+ const newPositionsRef = useRef(
921
+ Array.isArray(NodesPosition) ? NodesPosition : []
922
+ );
923
+ const selectedNodeIdsRef = useRef([]);
924
+ const selectedEdgeIdsRef = useRef([]);
925
+ const findEdgePayload = (fromId, toId) => {
926
+ const data = dataRef.current || [];
927
+ const fromNode = data.find((n) => S2(n.id) === fromId);
928
+ if (fromNode == null ? void 0 : fromNode.outputs) {
929
+ const match = fromNode.outputs.find((o) => S2(o == null ? void 0 : o.id) === toId);
930
+ if (match) return match;
931
+ }
932
+ const toNode = data.find((n) => S2(n.id) === toId);
933
+ if (toNode == null ? void 0 : toNode.inputs) {
934
+ const match = toNode.inputs.find((i) => S2(i == null ? void 0 : i.id) === fromId);
935
+ if (match) return match;
936
+ }
937
+ return null;
938
+ };
939
+ const getNodeLabelById = (id) => {
940
+ var _a2;
941
+ const fromDs = (_a2 = nodesRef.current) == null ? void 0 : _a2.get(id);
942
+ if (fromDs == null ? void 0 : fromDs.label) return S2(fromDs.label);
943
+ const fromData = (dataRef.current || []).find((n) => S2(n.id) === id);
944
+ if (fromData) return getNodeDisplayLabel(fromData) || S2(fromData.text || id);
945
+ return S2(id);
946
+ };
947
+ const baseNodeStyleRef = useRef(/* @__PURE__ */ new Map());
948
+ const baseEdgeStyleRef = useRef(/* @__PURE__ */ new Map());
949
+ useEffect(() => {
950
+ dataRef.current = Data;
951
+ }, [Data]);
952
+ useEffect(() => {
953
+ if (Array.isArray(NodesPosition)) {
954
+ newPositionsRef.current = NodesPosition.map((p) => ({
955
+ ...p,
956
+ id: S2(p.id)
957
+ }));
958
+ }
959
+ }, [NodesPosition]);
960
+ const wait = (ms) => new Promise((r) => setTimeout(r, ms));
961
+ const snapToGridIfNeeded = (x, y, instance) => {
962
+ if (!showGridRef.current || !instance) return { x, y };
963
+ const step = getNiceGridStep(instance.getScale());
964
+ if (!isFinite(step) || step <= 0) return { x, y };
965
+ return {
966
+ x: Math.round(x / step) * step,
967
+ y: Math.round(y / step) * step
968
+ };
969
+ };
970
+ const emitNodeClick = (nodePayload) => {
971
+ if (!nodePayload) return;
972
+ SetNode(nodePayload);
973
+ if (typeof (onNodeClickRef == null ? void 0 : onNodeClickRef.current) === "function") {
974
+ onNodeClickRef.current(nodePayload);
975
+ }
976
+ };
977
+ const downloadBlob = (blob, filename) => {
978
+ const url = URL.createObjectURL(blob);
979
+ const a = document.createElement("a");
980
+ a.href = url;
981
+ a.download = filename;
982
+ document.body.appendChild(a);
983
+ a.click();
984
+ a.remove();
985
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
986
+ };
987
+ const getPaintedMap = () => {
988
+ const map = /* @__PURE__ */ new Map();
989
+ (PaintedEdges || []).forEach((p) => {
990
+ const id = S2(p == null ? void 0 : p.id);
991
+ if (!id) return;
992
+ const entry = {
993
+ color: (p == null ? void 0 : p.color) || null,
994
+ lineStyle: (p == null ? void 0 : p.lineStyle) || null
995
+ };
996
+ if (!entry.color && !entry.lineStyle) return;
997
+ map.set(id, entry);
998
+ });
999
+ return map;
1000
+ };
1001
+ const compactPaintedEntry = (entry) => {
1002
+ if (!entry) return null;
1003
+ const next = {};
1004
+ if (entry.color) next.color = entry.color;
1005
+ if (entry.lineStyle && entry.lineStyle !== EDGE_LINE_STYLE.SOLID) {
1006
+ next.lineStyle = entry.lineStyle;
1007
+ }
1008
+ return Object.keys(next).length ? next : null;
1009
+ };
1010
+ const syncPaintedEdgesToParent = (map) => {
1011
+ if (typeof SetPaintedEdges !== "function") return;
1012
+ const arr = Array.from(map.entries()).map(([id, entry]) => {
1013
+ const compact = compactPaintedEntry(entry);
1014
+ return compact ? { id, ...compact } : null;
1015
+ }).filter(Boolean);
1016
+ SetPaintedEdges(arr);
1017
+ };
1018
+ const buildPaintedEdgeVisUpdate = (id, entry) => ({
1019
+ id,
1020
+ ...resolveEdgeVisStyle({
1021
+ isDark: isDarkMode,
1022
+ paintedColor: (entry == null ? void 0 : entry.color) || null,
1023
+ paintedLineStyle: (entry == null ? void 0 : entry.lineStyle) || null
1024
+ })
1025
+ });
1026
+ const applyColorToSelectedEdges = (colorOrNull) => {
1027
+ const instance = networkInstanceRef.current;
1028
+ const edgesDS = edgesRef.current;
1029
+ if (!instance || !edgesDS) return;
1030
+ const selectedIds = instance.getSelectedEdges().map(S2);
1031
+ if (!selectedIds.length) return;
1032
+ const map = getPaintedMap();
1033
+ const updates = [];
1034
+ selectedIds.forEach((id) => {
1035
+ const edge = edgesDS.get(id);
1036
+ if (!edge) return;
1037
+ const existing = map.get(id) || { color: null, lineStyle: null };
1038
+ const nextEntry = { ...existing, color: colorOrNull || null };
1039
+ const compact = compactPaintedEntry(nextEntry);
1040
+ if (compact) map.set(id, nextEntry);
1041
+ else map.delete(id);
1042
+ updates.push(
1043
+ buildPaintedEdgeVisUpdate(
1044
+ id,
1045
+ compact ? nextEntry : { color: null, lineStyle: null }
1046
+ )
1047
+ );
1048
+ });
1049
+ if (updates.length) edgesDS.update(updates);
1050
+ syncPaintedEdgesToParent(map);
1051
+ captureBaseStyles();
1052
+ paintSelectionStyles();
1053
+ };
1054
+ const applyLineStyleToSelectedEdges = (lineStyleOrNull) => {
1055
+ const instance = networkInstanceRef.current;
1056
+ const edgesDS = edgesRef.current;
1057
+ if (!instance || !edgesDS) return;
1058
+ const selectedIds = instance.getSelectedEdges().map(S2);
1059
+ if (!selectedIds.length) return;
1060
+ const map = getPaintedMap();
1061
+ const updates = [];
1062
+ selectedIds.forEach((id) => {
1063
+ const edge = edgesDS.get(id);
1064
+ if (!edge) return;
1065
+ const existing = map.get(id) || { color: null, lineStyle: null };
1066
+ const nextEntry = {
1067
+ ...existing,
1068
+ lineStyle: lineStyleOrNull && lineStyleOrNull !== EDGE_LINE_STYLE.SOLID ? normalizeEdgeLineStyle(lineStyleOrNull) : null
1069
+ };
1070
+ const compact = compactPaintedEntry(nextEntry);
1071
+ if (compact) map.set(id, nextEntry);
1072
+ else map.delete(id);
1073
+ updates.push(
1074
+ buildPaintedEdgeVisUpdate(
1075
+ id,
1076
+ compact ? nextEntry : { color: null, lineStyle: null }
1077
+ )
1078
+ );
1079
+ });
1080
+ if (updates.length) edgesDS.update(updates);
1081
+ syncPaintedEdgesToParent(map);
1082
+ captureBaseStyles();
1083
+ paintSelectionStyles();
1084
+ };
1085
+ const runSearch = (term) => {
1086
+ var _a2, _b;
1087
+ const instance = networkInstanceRef.current;
1088
+ const nodesDS = nodesRef.current;
1089
+ if (!instance || !nodesDS) return;
1090
+ const q = S2(term || "").trim().toLowerCase();
1091
+ if (!q) return;
1092
+ const all = nodesDS.get().filter((n) => !isHelperNodeId(n.id));
1093
+ const hits = all.filter((n) => {
1094
+ const id = S2(n.id).toLowerCase();
1095
+ const label = S2(n.label || "").toLowerCase();
1096
+ return id.includes(q) || label.includes(q);
1097
+ });
1098
+ if (!hits.length) return;
1099
+ const hitIds = hits.map((h) => S2(h.id));
1100
+ instance.setSelection(
1101
+ { nodes: hitIds, edges: [] },
1102
+ { unselectAll: true, highlightEdges: false }
1103
+ );
1104
+ selectedNodeIdsRef.current = [...hitIds];
1105
+ selectedEdgeIdsRef.current = [];
1106
+ setSelectedNodes(hitIds.map((id) => nodesDS.get(id)).filter(Boolean));
1107
+ SetSelectedEdges([]);
1108
+ paintSelectionStyles();
1109
+ if (hitIds.length === 1) {
1110
+ instance.focus(hitIds[0], {
1111
+ scale: Math.max(instance.getScale(), 1.1),
1112
+ animation: { duration: 450, easingFunction: "easeInOutQuad" }
1113
+ });
1114
+ return;
1115
+ }
1116
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1117
+ hitIds.forEach((id) => {
1118
+ const bb = instance.getBoundingBox(id);
1119
+ if (!bb) return;
1120
+ minX = Math.min(minX, bb.left);
1121
+ minY = Math.min(minY, bb.top);
1122
+ maxX = Math.max(maxX, bb.right);
1123
+ maxY = Math.max(maxY, bb.bottom);
1124
+ });
1125
+ if (isFinite(minX) && isFinite(minY) && isFinite(maxX) && isFinite(maxY)) {
1126
+ const pad = 100;
1127
+ const cw = ((_a2 = containerRef.current) == null ? void 0 : _a2.clientWidth) || 1e3;
1128
+ const ch = ((_b = containerRef.current) == null ? void 0 : _b.clientHeight) || 700;
1129
+ const w = Math.max(1, maxX - minX + pad * 2);
1130
+ const h = Math.max(1, maxY - minY + pad * 2);
1131
+ const scale = Math.min(cw / w, ch / h);
1132
+ const center = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
1133
+ instance.moveTo({
1134
+ position: center,
1135
+ scale,
1136
+ animation: { duration: 500, easingFunction: "easeInOutQuad" }
1137
+ });
1138
+ }
1139
+ };
1140
+ const takeGraphExcelExport = () => {
1141
+ const nodesDS = nodesRef.current;
1142
+ const edgesDS = edgesRef.current;
1143
+ if (!nodesDS || !edgesDS) return;
1144
+ const mainNodes = nodesDS.get().filter((n) => !isHelperNodeId(n.id)).map((n) => ({
1145
+ id: S2(n.id),
1146
+ label: S2(n.label ?? ""),
1147
+ type: S2(n.type ?? n.group ?? ""),
1148
+ address: S2(n.address ?? n.text ?? ""),
1149
+ subText: S2(n.subText ?? ""),
1150
+ rate: n.rate ?? "",
1151
+ metadata: S2(n.metadata ?? ""),
1152
+ inputs: JSON.stringify(Array.isArray(n.inputs) ? n.inputs : []),
1153
+ outputs: JSON.stringify(Array.isArray(n.outputs) ? n.outputs : []),
1154
+ inputCount: Array.isArray(n.inputs) ? n.inputs.length : 0,
1155
+ outputCount: Array.isArray(n.outputs) ? n.outputs.length : 0,
1156
+ x: Number(n.x ?? 0),
1157
+ y: Number(n.y ?? 0)
1158
+ }));
1159
+ const edges = edgesDS.get().map((e) => ({
1160
+ id: S2(e.id),
1161
+ from: S2(e.from),
1162
+ to: S2(e.to),
1163
+ label: S2(e.label ?? ""),
1164
+ value: e.value ?? "",
1165
+ subText: S2(e.subText ?? ""),
1166
+ subValue: e.subValue ?? "",
1167
+ time: e.time ?? "",
1168
+ width: e.width ?? ""
1169
+ }));
1170
+ const workbook = XLSX.utils.book_new();
1171
+ const nodeSheet = XLSX.utils.json_to_sheet(mainNodes);
1172
+ const edgeSheet = XLSX.utils.json_to_sheet(edges);
1173
+ XLSX.utils.book_append_sheet(workbook, nodeSheet, "Nodes");
1174
+ XLSX.utils.book_append_sheet(workbook, edgeSheet, "Edges");
1175
+ const array = XLSX.write(workbook, {
1176
+ type: "array",
1177
+ bookType: "xlsx",
1178
+ compression: true
1179
+ });
1180
+ const blob = new Blob([array], {
1181
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1182
+ });
1183
+ downloadBlob(blob, `graph-export-${Date.now()}.xlsx`);
1184
+ };
1185
+ const findAndHighlightAllPaths = () => {
1186
+ const instance = networkInstanceRef.current;
1187
+ const nodesDS = nodesRef.current;
1188
+ const edgesDS = edgesRef.current;
1189
+ const fromId = S2(pathFromId).trim();
1190
+ const toId = S2(pathToId).trim();
1191
+ if (!instance || !nodesDS || !edgesDS) return;
1192
+ if (!fromId || !toId) {
1193
+ setPathMessage(
1194
+ isFa ? "\u0644\u0637\u0641\u0627\u064B \u0622\u06CC\u062F\u06CC \u0645\u0628\u062F\u0627 \u0648 \u0645\u0642\u0635\u062F \u0631\u0627 \u0648\u0627\u0631\u062F \u06A9\u0646\u06CC\u062F." : "Please provide both source and destination ids."
1195
+ );
1196
+ return;
1197
+ }
1198
+ if (!nodesDS.get(fromId) || !nodesDS.get(toId)) {
1199
+ setPathMessage(
1200
+ isFa ? "\u06CC\u06A9\u06CC \u0627\u0632 \u0622\u06CC\u062F\u06CC\u200C\u0647\u0627 \u062F\u0631 \u06AF\u0631\u0627\u0641 \u0648\u062C\u0648\u062F \u0646\u062F\u0627\u0631\u062F." : "One of the node ids does not exist in graph."
1201
+ );
1202
+ return;
1203
+ }
1204
+ const edgeRows = edgesDS.get();
1205
+ const adjacency = /* @__PURE__ */ new Map();
1206
+ const edgeIdsByPair = /* @__PURE__ */ new Map();
1207
+ edgeRows.forEach((e) => {
1208
+ const from = S2(e.from);
1209
+ const to = S2(e.to);
1210
+ if (!adjacency.has(from)) adjacency.set(from, /* @__PURE__ */ new Set());
1211
+ adjacency.get(from).add(to);
1212
+ const key = `${from}__${to}`;
1213
+ if (!edgeIdsByPair.has(key)) edgeIdsByPair.set(key, []);
1214
+ edgeIdsByPair.get(key).push(S2(e.id));
1215
+ });
1216
+ const MAX_PATHS = 3e3;
1217
+ const MAX_DEPTH = Math.max(nodesDS.getIds().length || 0, 1);
1218
+ const allPaths = [];
1219
+ const nodePath = [fromId];
1220
+ const visited = /* @__PURE__ */ new Set([fromId]);
1221
+ const dfs = (current, depth) => {
1222
+ if (allPaths.length >= MAX_PATHS || depth > MAX_DEPTH) return;
1223
+ if (current === toId) {
1224
+ allPaths.push([...nodePath]);
1225
+ return;
1226
+ }
1227
+ const nextSet = adjacency.get(current);
1228
+ if (!nextSet || !nextSet.size) return;
1229
+ for (const next of nextSet) {
1230
+ if (visited.has(next)) continue;
1231
+ visited.add(next);
1232
+ nodePath.push(next);
1233
+ dfs(next, depth + 1);
1234
+ nodePath.pop();
1235
+ visited.delete(next);
1236
+ }
1237
+ };
1238
+ dfs(fromId, 0);
1239
+ if (!allPaths.length) {
1240
+ instance.setSelection(
1241
+ { nodes: [fromId, toId], edges: [] },
1242
+ { unselectAll: true, highlightEdges: false }
1243
+ );
1244
+ selectedNodeIdsRef.current = [fromId, toId];
1245
+ selectedEdgeIdsRef.current = [];
1246
+ setSelectedNodes([nodesDS.get(fromId), nodesDS.get(toId)].filter(Boolean));
1247
+ SetSelectedEdges([]);
1248
+ paintSelectionStyles();
1249
+ setPathMessage(
1250
+ isFa ? "\u0645\u0633\u06CC\u0631 \u0645\u0633\u062A\u0642\u06CC\u0645\u06CC \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F." : "No directed path was found."
1251
+ );
1252
+ return;
1253
+ }
1254
+ const selectedNodes = /* @__PURE__ */ new Set();
1255
+ const selectedEdges = /* @__PURE__ */ new Set();
1256
+ allPaths.forEach((path) => {
1257
+ path.forEach((id) => selectedNodes.add(id));
1258
+ for (let i = 0; i < path.length - 1; i++) {
1259
+ const ids = edgeIdsByPair.get(`${path[i]}__${path[i + 1]}`) || [];
1260
+ ids.forEach((id) => selectedEdges.add(id));
1261
+ }
1262
+ });
1263
+ const selectedNodeIds = Array.from(selectedNodes);
1264
+ const selectedEdgeIds = Array.from(selectedEdges);
1265
+ instance.setSelection(
1266
+ { nodes: selectedNodeIds, edges: selectedEdgeIds },
1267
+ { unselectAll: true, highlightEdges: false }
1268
+ );
1269
+ selectedNodeIdsRef.current = selectedNodeIds;
1270
+ selectedEdgeIdsRef.current = selectedEdgeIds;
1271
+ setSelectedNodes(selectedNodeIds.map((id) => nodesDS.get(id)).filter(Boolean));
1272
+ SetSelectedEdges(selectedEdgeIds.map((id) => edgesDS.get(id)).filter(Boolean));
1273
+ paintSelectionStyles();
1274
+ setPathMessage(
1275
+ isFa ? `${allPaths.length.toLocaleString()} \u0645\u0633\u06CC\u0631 \u067E\u06CC\u062F\u0627 \u0634\u062F.` : `${allPaths.length.toLocaleString()} path(s) found.`
1276
+ );
1277
+ };
1278
+ const handleAutoArrangeGraph = async () => {
1279
+ var _a2;
1280
+ const instance = networkInstanceRef.current;
1281
+ const nodesDS = nodesRef.current;
1282
+ const edgesDS = edgesRef.current;
1283
+ if (!instance || !nodesDS || !edgesDS) return;
1284
+ if (handleAutoArrangeGraph.__running) return;
1285
+ handleAutoArrangeGraph.__running = true;
1286
+ setAddPositionLoading(true);
1287
+ const raf = () => new Promise((r) => requestAnimationFrame(r));
1288
+ const S3 = (v) => String(v ?? "");
1289
+ try {
1290
+ try {
1291
+ instance.setOptions({ physics: { enabled: false } });
1292
+ } catch (_) {
1293
+ }
1294
+ const allNodes = nodesDS.get();
1295
+ const allEdges = edgesDS.get();
1296
+ const byId = new Map(allNodes.map((n) => [S3(n.id), n]));
1297
+ const mainNodes = allNodes.filter((n) => !isHelperNodeId(n.id));
1298
+ if (!mainNodes.length) return;
1299
+ const mainIds = new Set(mainNodes.map((n) => S3(n.id)));
1300
+ const mainEdges = allEdges.filter(
1301
+ (e) => mainIds.has(S3(e.from)) && mainIds.has(S3(e.to))
1302
+ );
1303
+ const BOX_MARGIN = 14;
1304
+ const INFO_BOX_MARGIN = 16;
1305
+ const clamp = (v, min, max) => Math.min(Math.max(v, min), max);
1306
+ const boxesOverlap = (x1, y1, hw1, hh1, x2, y2, hw2, hh2, margin = 0) => Math.abs(x1 - x2) < hw1 + hw2 + margin && Math.abs(y1 - y2) < hh1 + hh2 + margin;
1307
+ const estimateMainHalfSize = (mainId) => {
1308
+ var _a3;
1309
+ const node = byId.get(mainId) || {};
1310
+ const size = Number((node == null ? void 0 : node.size) ?? 18);
1311
+ const labelLen = S3((node == null ? void 0 : node.label) ?? "").length;
1312
+ const fontSize = Number(((_a3 = node == null ? void 0 : node.font) == null ? void 0 : _a3.size) ?? 13);
1313
+ return {
1314
+ x: clamp(26 + size + labelLen * (fontSize * 0.38), 38, 130),
1315
+ y: clamp(24 + size * 1.05, 34, 88)
1316
+ };
1317
+ };
1318
+ const estimateInfoHalfSize = (infoNode) => {
1319
+ const lines = S3((infoNode == null ? void 0 : infoNode.label) ?? "").split("\n");
1320
+ const maxChars = lines.reduce((m, l) => Math.max(m, l.trim().length), 0);
1321
+ const fontSize = 12;
1322
+ const lineHeight = 15;
1323
+ const pad = 18;
1324
+ return {
1325
+ x: clamp(pad + maxChars * (fontSize * 0.58), 64, 300),
1326
+ y: clamp(pad + lines.length * lineHeight, 30, 130)
1327
+ };
1328
+ };
1329
+ const getMainHalf = (mainId) => estimateMainHalfSize(mainId);
1330
+ const getInfoHalf = (infoNode) => estimateInfoHalfSize(infoNode);
1331
+ const overlapsAnyMainBox = (x, y, hw, hh, mainPosMap2, margin = BOX_MARGIN) => {
1332
+ for (const [mid, p] of Object.entries(mainPosMap2)) {
1333
+ if (!p) continue;
1334
+ const mh = getMainHalf(mid);
1335
+ if (boxesOverlap(x, y, hw, hh, p.x, p.y, mh.x, mh.y, margin))
1336
+ return true;
1337
+ }
1338
+ return false;
1339
+ };
1340
+ const overlapsAnyInfoBox = (id, x, y, hw, hh, infoPlaced2, infoHalfMap2, margin = INFO_BOX_MARGIN) => {
1341
+ for (const [oid, p] of Object.entries(infoPlaced2)) {
1342
+ if (!p || oid === id) continue;
1343
+ const oh = infoHalfMap2[oid] || { x: 80, y: 40 };
1344
+ if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
1345
+ return true;
1346
+ }
1347
+ return false;
1348
+ };
1349
+ const overlapsPlacedHelpers = (id, x, y, hw, hh, placedMap2, halfMap, margin = BOX_MARGIN) => {
1350
+ for (const [oid, p] of Object.entries(placedMap2)) {
1351
+ if (!p || oid === id) continue;
1352
+ const oh = halfMap[oid];
1353
+ if (!oh) continue;
1354
+ if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
1355
+ return true;
1356
+ }
1357
+ return false;
1358
+ };
1359
+ const collidesMapBox = (id, x, y, hw, hh, map, halfMap, margin = BOX_MARGIN) => {
1360
+ for (const [oid, p] of Object.entries(map)) {
1361
+ if (!p || oid === id) continue;
1362
+ const oh = halfMap[oid] || { x: hw, y: hh };
1363
+ if (boxesOverlap(x, y, hw, hh, p.x, p.y, oh.x, oh.y, margin))
1364
+ return true;
1365
+ }
1366
+ return false;
1367
+ };
1368
+ const placeAroundBox = (id, bx, by, halfX, halfY, placedMap2, halfMap, opts = {}) => {
1369
+ const margin = opts.margin ?? BOX_MARGIN;
1370
+ const ringStep = opts.ringStep ?? 20;
1371
+ const maxRings = opts.maxRings ?? 48;
1372
+ const angles = opts.angles ?? 48;
1373
+ if (!collidesMapBox(id, bx, by, halfX, halfY, placedMap2, halfMap, margin))
1374
+ return { x: bx, y: by };
1375
+ for (let r = 1; r <= maxRings; r++) {
1376
+ const rad = r * ringStep;
1377
+ for (let k = 0; k < angles; k++) {
1378
+ const a = 2 * Math.PI * k / angles;
1379
+ const x = bx + Math.cos(a) * rad;
1380
+ const y = by + Math.sin(a) * rad;
1381
+ if (!collidesMapBox(id, x, y, halfX, halfY, placedMap2, halfMap, margin))
1382
+ return { x, y };
1383
+ }
1384
+ }
1385
+ return { x: bx, y: by };
1386
+ };
1387
+ const infoPosIsValid = (id, x, y, infoNode, infoHalf, mainPosMap2, infoPlaced2, infoHalfMap2, placedMap2, placedHalfMap2) => !overlapsAnyMainBox(
1388
+ x,
1389
+ y,
1390
+ infoHalf.x,
1391
+ infoHalf.y,
1392
+ mainPosMap2,
1393
+ INFO_BOX_MARGIN
1394
+ ) && !overlapsAnyInfoBox(
1395
+ id,
1396
+ x,
1397
+ y,
1398
+ infoHalf.x,
1399
+ infoHalf.y,
1400
+ infoPlaced2,
1401
+ infoHalfMap2,
1402
+ INFO_BOX_MARGIN
1403
+ ) && !overlapsPlacedHelpers(
1404
+ id,
1405
+ x,
1406
+ y,
1407
+ infoHalf.x,
1408
+ infoHalf.y,
1409
+ placedMap2,
1410
+ placedHalfMap2,
1411
+ BOX_MARGIN
1412
+ );
1413
+ const pushOutFromMainForbidden = (id, p, infoNode, infoHalf, mainPosMap2, infoPlaced2, infoHalfMap2, placedMap2, placedHalfMap2) => {
1414
+ let x = p.x, y = p.y;
1415
+ const isBad = (cx, cy) => !infoPosIsValid(
1416
+ id,
1417
+ cx,
1418
+ cy,
1419
+ infoNode,
1420
+ infoHalf,
1421
+ mainPosMap2,
1422
+ infoPlaced2,
1423
+ infoHalfMap2,
1424
+ placedMap2,
1425
+ placedHalfMap2
1426
+ );
1427
+ if (!isBad(x, y)) return { x, y };
1428
+ const step = Math.max(24, Math.min(infoHalf.x, infoHalf.y) * 0.35);
1429
+ for (let k = 1; k <= 180; k++) {
1430
+ const d = k * step;
1431
+ const tries = [
1432
+ { x, y: y + d },
1433
+ { x, y: y - d },
1434
+ { x: x + d, y },
1435
+ { x: x - d, y },
1436
+ { x: x + d, y: y + d },
1437
+ { x: x - d, y: y - d },
1438
+ { x: x + d, y: y - d },
1439
+ { x: x - d, y: y + d }
1440
+ ];
1441
+ for (const t of tries) {
1442
+ if (!isBad(t.x, t.y)) return t;
1443
+ }
1444
+ }
1445
+ for (let r = step * 2; r <= 5600; r += step * 1.6) {
1446
+ for (let a = 0; a < 40; a++) {
1447
+ const ang = 2 * Math.PI * a / 40;
1448
+ const cx = x + Math.cos(ang) * r;
1449
+ const cy = y + Math.sin(ang) * r;
1450
+ if (!isBad(cx, cy)) return { x: cx, y: cy };
1451
+ }
1452
+ }
1453
+ return { x, y };
1454
+ };
1455
+ const inDeg = /* @__PURE__ */ Object.create(null);
1456
+ const outDeg = /* @__PURE__ */ Object.create(null);
1457
+ const adj = /* @__PURE__ */ Object.create(null);
1458
+ const revAdj = /* @__PURE__ */ Object.create(null);
1459
+ for (const n of mainNodes) {
1460
+ const id = S3(n.id);
1461
+ inDeg[id] = 0;
1462
+ outDeg[id] = 0;
1463
+ adj[id] = [];
1464
+ revAdj[id] = [];
1465
+ }
1466
+ for (const e of mainEdges) {
1467
+ const f = S3(e.from), t = S3(e.to);
1468
+ if (inDeg[f] == null || inDeg[t] == null) continue;
1469
+ adj[f].push(t);
1470
+ revAdj[t].push(f);
1471
+ outDeg[f] += 1;
1472
+ inDeg[t] += 1;
1473
+ }
1474
+ const q = [];
1475
+ const inTmp = { ...inDeg };
1476
+ const layer = /* @__PURE__ */ Object.create(null);
1477
+ for (const id of Object.keys(inTmp)) {
1478
+ if (inTmp[id] === 0) {
1479
+ q.push(id);
1480
+ layer[id] = 0;
1481
+ }
1482
+ }
1483
+ if (!q.length) {
1484
+ const ids = Object.keys(inTmp).sort();
1485
+ q.push(ids[0]);
1486
+ layer[ids[0]] = 0;
1487
+ }
1488
+ while (q.length) {
1489
+ const u = q.shift();
1490
+ const nextL = (layer[u] ?? 0) + 1;
1491
+ for (const v of adj[u]) {
1492
+ if (layer[v] == null || nextL > layer[v]) layer[v] = nextL;
1493
+ inTmp[v] -= 1;
1494
+ if (inTmp[v] === 0) q.push(v);
1495
+ }
1496
+ }
1497
+ let changed = true;
1498
+ while (changed) {
1499
+ changed = false;
1500
+ for (const id of Object.keys(inDeg)) {
1501
+ if (layer[id] != null) continue;
1502
+ const parents = revAdj[id] || [];
1503
+ if (!parents.length) {
1504
+ layer[id] = 0;
1505
+ changed = true;
1506
+ continue;
1507
+ }
1508
+ const known = parents.filter((p) => layer[p] != null);
1509
+ if (!known.length) continue;
1510
+ layer[id] = Math.max(...known.map((p) => layer[p])) + 1;
1511
+ changed = true;
1512
+ }
1513
+ }
1514
+ for (const id of Object.keys(inDeg)) {
1515
+ if (layer[id] == null) layer[id] = 0;
1516
+ }
1517
+ const layers = /* @__PURE__ */ Object.create(null);
1518
+ for (const [id, l] of Object.entries(layer)) {
1519
+ if (!layers[l]) layers[l] = [];
1520
+ layers[l].push(id);
1521
+ }
1522
+ const layerKeys = Object.keys(layers).map(Number).sort((a, b) => a - b);
1523
+ const oldPos = /* @__PURE__ */ Object.create(null);
1524
+ for (const n of allNodes) oldPos[S3(n.id)] = { x: n.x || 0, y: n.y || 0 };
1525
+ for (const lk of layerKeys) {
1526
+ layers[lk].sort((a, b) => {
1527
+ var _a3, _b;
1528
+ return (((_a3 = oldPos[a]) == null ? void 0 : _a3.y) ?? 0) - (((_b = oldPos[b]) == null ? void 0 : _b.y) ?? 0);
1529
+ });
1530
+ }
1531
+ const nCount = mainNodes.length;
1532
+ const medianOf = (arr) => {
1533
+ if (!arr.length) return Number.POSITIVE_INFINITY;
1534
+ const sorted = [...arr].sort((a, b) => a - b);
1535
+ const mid = Math.floor(sorted.length / 2);
1536
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
1537
+ };
1538
+ const barySort = (ids, getNeighbors) => {
1539
+ return [...ids].sort((a, b) => {
1540
+ var _a3, _b;
1541
+ const na = getNeighbors(a);
1542
+ const nb = getNeighbors(b);
1543
+ const ba = medianOf(na);
1544
+ const bb = medianOf(nb);
1545
+ if (ba !== bb) return ba - bb;
1546
+ return (((_a3 = oldPos[a]) == null ? void 0 : _a3.y) ?? 0) - (((_b = oldPos[b]) == null ? void 0 : _b.y) ?? 0);
1547
+ });
1548
+ };
1549
+ const indexInLayer = () => {
1550
+ const idx = /* @__PURE__ */ Object.create(null);
1551
+ for (const lk of layerKeys) {
1552
+ const arr = layers[lk] || [];
1553
+ const m = /* @__PURE__ */ Object.create(null);
1554
+ for (let i = 0; i < arr.length; i++) m[arr[i]] = i;
1555
+ idx[lk] = m;
1556
+ }
1557
+ return idx;
1558
+ };
1559
+ const crossSweeps = Math.min(
1560
+ 16,
1561
+ 8 + Math.floor(layerKeys.length / 2) + Math.floor(nCount / 80)
1562
+ );
1563
+ for (let sweep = 0; sweep < crossSweeps; sweep++) {
1564
+ let idxMap2 = indexInLayer();
1565
+ for (let i = 1; i < layerKeys.length; i++) {
1566
+ const lk = layerKeys[i];
1567
+ layers[lk] = barySort(
1568
+ layers[lk],
1569
+ (id) => (revAdj[id] || []).filter(
1570
+ (p) => {
1571
+ var _a3;
1572
+ return (layer[p] ?? 999) < lk && ((_a3 = idxMap2[layer[p]]) == null ? void 0 : _a3[p]) != null;
1573
+ }
1574
+ ).map((p) => idxMap2[layer[p]][p])
1575
+ );
1576
+ }
1577
+ idxMap2 = indexInLayer();
1578
+ for (let i = layerKeys.length - 2; i >= 0; i--) {
1579
+ const lk = layerKeys[i];
1580
+ layers[lk] = barySort(
1581
+ layers[lk],
1582
+ (id) => (adj[id] || []).filter(
1583
+ (c) => {
1584
+ var _a3;
1585
+ return (layer[c] ?? -1) > lk && ((_a3 = idxMap2[layer[c]]) == null ? void 0 : _a3[c]) != null;
1586
+ }
1587
+ ).map((c) => idxMap2[layer[c]][c])
1588
+ );
1589
+ }
1590
+ }
1591
+ const countAllCrossings = () => {
1592
+ var _a3, _b, _c, _d;
1593
+ const idx = indexInLayer();
1594
+ let c = 0;
1595
+ for (let li = 0; li < layerKeys.length; li++) {
1596
+ for (let lj = li + 1; lj < layerKeys.length; lj++) {
1597
+ const L = layerKeys[li];
1598
+ const R = layerKeys[lj];
1599
+ const edgesLR = mainEdges.filter((e) => {
1600
+ const f = S3(e.from);
1601
+ const t = S3(e.to);
1602
+ return layer[f] === L && layer[t] === R;
1603
+ });
1604
+ for (let i = 0; i < edgesLR.length; i++) {
1605
+ for (let j = i + 1; j < edgesLR.length; j++) {
1606
+ const f1 = S3(edgesLR[i].from);
1607
+ const t1 = S3(edgesLR[i].to);
1608
+ const f2 = S3(edgesLR[j].from);
1609
+ const t2 = S3(edgesLR[j].to);
1610
+ const y1f = ((_a3 = idx[L]) == null ? void 0 : _a3[f1]) ?? 0;
1611
+ const y2f = ((_b = idx[L]) == null ? void 0 : _b[f2]) ?? 0;
1612
+ const y1t = ((_c = idx[R]) == null ? void 0 : _c[t1]) ?? 0;
1613
+ const y2t = ((_d = idx[R]) == null ? void 0 : _d[t2]) ?? 0;
1614
+ if ((y1f - y2f) * (y1t - y2t) < 0) c++;
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ return c;
1620
+ };
1621
+ const optimizeLayerOrders = () => {
1622
+ const maxPasses = Math.min(10, 3 + Math.floor(nCount / 60));
1623
+ for (let pass = 0; pass < maxPasses; pass++) {
1624
+ let improved = false;
1625
+ for (const lk of layerKeys) {
1626
+ const arr = layers[lk];
1627
+ if (!arr || arr.length < 2) continue;
1628
+ for (let i = 0; i < arr.length - 1; i++) {
1629
+ const before = countAllCrossings();
1630
+ [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
1631
+ const after = countAllCrossings();
1632
+ if (after < before) improved = true;
1633
+ else [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
1634
+ }
1635
+ }
1636
+ if (!improved) break;
1637
+ }
1638
+ };
1639
+ optimizeLayerOrders();
1640
+ const maxLayerSize = Math.max(
1641
+ 1,
1642
+ ...layerKeys.map((lk) => (layers[lk] || []).length)
1643
+ );
1644
+ const numLayers = Math.max(1, layerKeys.length);
1645
+ const NODE_GAP_Y = clamp(
1646
+ 165 + Math.sqrt(maxLayerSize) * 36 + Math.log2(nCount + 1) * 16,
1647
+ 175,
1648
+ 380
1649
+ );
1650
+ const BASE_LAYER_GAP_X = clamp(
1651
+ 300 + Math.sqrt(nCount) * 8 + Math.sqrt(maxLayerSize) * 6,
1652
+ 300,
1653
+ 480
1654
+ );
1655
+ const edgeCountBetween = /* @__PURE__ */ Object.create(null);
1656
+ for (const e of mainEdges) {
1657
+ const f = S3(e.from);
1658
+ const t = S3(e.to);
1659
+ const lf = layer[f];
1660
+ const lt = layer[t];
1661
+ if (lf == null || lt == null || lf >= lt) continue;
1662
+ const key = `${lf}|${lt}`;
1663
+ edgeCountBetween[key] = (edgeCountBetween[key] || 0) + 1;
1664
+ }
1665
+ const colX = [];
1666
+ let xAcc = 0;
1667
+ for (let i = 0; i < layerKeys.length; i++) {
1668
+ colX.push(xAcc);
1669
+ if (i >= layerKeys.length - 1) break;
1670
+ const leftL = layerKeys[i];
1671
+ const rightL = layerKeys[i + 1];
1672
+ let denseEdges = 0;
1673
+ for (let l = leftL; l < rightL; l++) {
1674
+ for (let r = l + 1; r <= rightL; r++) {
1675
+ denseEdges = Math.max(denseEdges, edgeCountBetween[`${l}|${r}`] || 0);
1676
+ }
1677
+ }
1678
+ xAcc += BASE_LAYER_GAP_X + Math.sqrt(denseEdges) * 18 + (((_a2 = layers[rightL]) == null ? void 0 : _a2.length) ?? 0) * 3;
1679
+ }
1680
+ const newPosMap = /* @__PURE__ */ Object.create(null);
1681
+ const placedMap = /* @__PURE__ */ Object.create(null);
1682
+ const placedHalfMap = /* @__PURE__ */ Object.create(null);
1683
+ const mainPosMap = /* @__PURE__ */ Object.create(null);
1684
+ const mainHalfMap = /* @__PURE__ */ Object.create(null);
1685
+ for (let cx = 0; cx < layerKeys.length; cx++) {
1686
+ const lk = layerKeys[cx];
1687
+ const ids = layers[lk] || [];
1688
+ const bx = colX[cx];
1689
+ const nodeHalfs = ids.map((id) => estimateMainHalfSize(id));
1690
+ let totalH = 0;
1691
+ for (let i = 0; i < ids.length; i++) {
1692
+ totalH += nodeHalfs[i].y * 2;
1693
+ if (i < ids.length - 1) totalH += NODE_GAP_Y;
1694
+ }
1695
+ let yCursor = -totalH / 2;
1696
+ for (let i = 0; i < ids.length; i++) {
1697
+ const id = ids[i];
1698
+ const half = nodeHalfs[i];
1699
+ const by = yCursor + half.y;
1700
+ yCursor += half.y * 2 + NODE_GAP_Y;
1701
+ const p = placeAroundBox(
1702
+ id,
1703
+ bx,
1704
+ by,
1705
+ half.x,
1706
+ half.y,
1707
+ placedMap,
1708
+ placedHalfMap,
1709
+ { ringStep: 22, maxRings: 52, angles: 48 }
1710
+ );
1711
+ newPosMap[id] = { id, x: p.x, y: p.y };
1712
+ placedMap[id] = { x: p.x, y: p.y };
1713
+ placedHalfMap[id] = { x: half.x, y: half.y };
1714
+ mainPosMap[id] = { x: p.x, y: p.y };
1715
+ mainHalfMap[id] = { x: half.x, y: half.y };
1716
+ }
1717
+ if ((cx & 7) === 0) await raf();
1718
+ }
1719
+ for (const n of allNodes) {
1720
+ const id = S3(n.id);
1721
+ if (!id.endsWith("_Arrow")) continue;
1722
+ const base = id.replace("_Arrow", "");
1723
+ if (!newPosMap[base]) continue;
1724
+ const p = placeAroundBox(
1725
+ id,
1726
+ newPosMap[base].x,
1727
+ newPosMap[base].y - 46,
1728
+ 28,
1729
+ 28,
1730
+ placedMap,
1731
+ placedHalfMap,
1732
+ { ringStep: 14, maxRings: 30, angles: 36 }
1733
+ );
1734
+ newPosMap[id] = { id, x: p.x, y: p.y };
1735
+ placedMap[id] = { x: p.x, y: p.y };
1736
+ placedHalfMap[id] = { x: 28, y: 28 };
1737
+ }
1738
+ for (const n of allNodes) {
1739
+ const id = S3(n.id);
1740
+ if (!id.endsWith("hotWallet")) continue;
1741
+ const base = id.replace("hotWallet", "");
1742
+ if (!newPosMap[base]) continue;
1743
+ const owner = byId.get(base);
1744
+ const by = (owner == null ? void 0 : owner.main) ? newPosMap[base].y - 50 : newPosMap[base].y - 30;
1745
+ const p = placeAroundBox(
1746
+ id,
1747
+ newPosMap[base].x,
1748
+ by,
1749
+ 36,
1750
+ 20,
1751
+ placedMap,
1752
+ placedHalfMap,
1753
+ { ringStep: 14, maxRings: 30, angles: 36 }
1754
+ );
1755
+ newPosMap[id] = { id, x: p.x, y: p.y };
1756
+ placedMap[id] = { x: p.x, y: p.y };
1757
+ placedHalfMap[id] = { x: 36, y: 20 };
1758
+ }
1759
+ const infoNodes = allNodes.filter((n) => {
1760
+ const fromId = S3(n.from);
1761
+ const toId = S3(n.to);
1762
+ return fromId && toId && mainIds.has(fromId) && mainIds.has(toId);
1763
+ });
1764
+ const infoNodeById = new Map(infoNodes.map((n) => [S3(n.id), n]));
1765
+ const infoPlaced = /* @__PURE__ */ Object.create(null);
1766
+ const infoHalfMap = /* @__PURE__ */ Object.create(null);
1767
+ const infoByEdge = /* @__PURE__ */ Object.create(null);
1768
+ for (const n of infoNodes) {
1769
+ const key = `${S3(n.from)}|${S3(n.to)}`;
1770
+ if (!infoByEdge[key]) infoByEdge[key] = [];
1771
+ infoByEdge[key].push(n);
1772
+ }
1773
+ const sortedInfoNodes = [...infoNodes].sort((a, b) => {
1774
+ const ha = estimateInfoHalfSize(a);
1775
+ const hb = estimateInfoHalfSize(b);
1776
+ return hb.x * hb.y - ha.x * ha.y;
1777
+ });
1778
+ const edgeSlotIndex = /* @__PURE__ */ Object.create(null);
1779
+ for (let idx = 0; idx < sortedInfoNodes.length; idx++) {
1780
+ const n = sortedInfoNodes[idx];
1781
+ const id = S3(n.id);
1782
+ const fromId = S3(n.from);
1783
+ const toId = S3(n.to);
1784
+ const edgeKey = `${fromId}|${toId}`;
1785
+ const slot = edgeSlotIndex[edgeKey] ?? 0;
1786
+ edgeSlotIndex[edgeKey] = slot + 1;
1787
+ if (!newPosMap[fromId] || !newPosMap[toId]) continue;
1788
+ const infoHalf = estimateInfoHalfSize(n);
1789
+ const fx = newPosMap[fromId].x, fy = newPosMap[fromId].y;
1790
+ const tx = newPosMap[toId].x, ty = newPosMap[toId].y;
1791
+ const mx = (fx + tx) / 2;
1792
+ const my = (fy + ty) / 2;
1793
+ const dx = tx - fx;
1794
+ const dy = ty - fy;
1795
+ const len = Math.hypot(dx, dy) || 1;
1796
+ const ux = dx / len;
1797
+ const uy = dy / len;
1798
+ let nx = -uy, ny = ux;
1799
+ if (!isFinite(nx) || !isFinite(ny)) {
1800
+ nx = 0;
1801
+ ny = -1;
1802
+ }
1803
+ const fromHalf = getMainHalf(fromId);
1804
+ const toHalf = getMainHalf(toId);
1805
+ const minClearance = Math.max(fromHalf.x, fromHalf.y, toHalf.x, toHalf.y) + Math.max(infoHalf.x, infoHalf.y) + INFO_BOX_MARGIN + 24;
1806
+ const totalOnEdge = (infoByEdge[edgeKey] || []).length;
1807
+ const slotShift = (slot - (totalOnEdge - 1) / 2) * (infoHalf.y * 2 + 32);
1808
+ const tangShift = slot * (infoHalf.x * 0.55 + 20);
1809
+ let chosen = null;
1810
+ const baseOff = clamp(minClearance + edgeSlotIndex[edgeKey] * 18, minClearance, 320);
1811
+ for (let lvl = 0; lvl <= 120 && !chosen; lvl++) {
1812
+ const off = baseOff + lvl * 24;
1813
+ const tang = tangShift + lvl * 12;
1814
+ const cands = [
1815
+ { x: mx + nx * off + ux * tang, y: my + ny * off + uy * tang + slotShift },
1816
+ { x: mx - nx * off - ux * tang, y: my - ny * off - uy * tang - slotShift },
1817
+ { x: mx + nx * off - ux * tang, y: my + ny * off - uy * tang + slotShift },
1818
+ { x: mx - nx * off + ux * tang, y: my - ny * off + uy * tang - slotShift },
1819
+ { x: mx + ux * tang + nx * (off * 0.65), y: my + uy * tang + ny * (off * 0.65) },
1820
+ { x: mx - ux * tang - nx * (off * 0.65), y: my - uy * tang - ny * (off * 0.65) }
1821
+ ];
1822
+ for (const c of cands) {
1823
+ if (infoPosIsValid(
1824
+ id,
1825
+ c.x,
1826
+ c.y,
1827
+ n,
1828
+ infoHalf,
1829
+ mainPosMap,
1830
+ infoPlaced,
1831
+ infoHalfMap,
1832
+ placedMap,
1833
+ placedHalfMap
1834
+ )) {
1835
+ chosen = c;
1836
+ break;
1837
+ }
1838
+ }
1839
+ }
1840
+ if (!chosen) {
1841
+ for (let off = minClearance; off <= 5600 && !chosen; off += 48) {
1842
+ for (let side = -1; side <= 1; side += 2) {
1843
+ const c = {
1844
+ x: mx + nx * off * side + ux * tangShift,
1845
+ y: my + ny * off * side + uy * tangShift + slotShift
1846
+ };
1847
+ if (infoPosIsValid(
1848
+ id,
1849
+ c.x,
1850
+ c.y,
1851
+ n,
1852
+ infoHalf,
1853
+ mainPosMap,
1854
+ infoPlaced,
1855
+ infoHalfMap,
1856
+ placedMap,
1857
+ placedHalfMap
1858
+ )) {
1859
+ chosen = c;
1860
+ break;
1861
+ }
1862
+ }
1863
+ }
1864
+ }
1865
+ if (!chosen) {
1866
+ for (let r = minClearance; r <= 5600 && !chosen; r += 64) {
1867
+ for (let a = 0; a < 48; a++) {
1868
+ const ang = 2 * Math.PI * a / 48;
1869
+ const c = {
1870
+ x: mx + Math.cos(ang) * r + slot * 30,
1871
+ y: my + Math.sin(ang) * r + slotShift
1872
+ };
1873
+ if (infoPosIsValid(
1874
+ id,
1875
+ c.x,
1876
+ c.y,
1877
+ n,
1878
+ infoHalf,
1879
+ mainPosMap,
1880
+ infoPlaced,
1881
+ infoHalfMap,
1882
+ placedMap,
1883
+ placedHalfMap
1884
+ )) {
1885
+ chosen = c;
1886
+ break;
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1891
+ if (!chosen) chosen = { x: mx + nx * minClearance, y: my + ny * minClearance };
1892
+ chosen = pushOutFromMainForbidden(
1893
+ id,
1894
+ chosen,
1895
+ n,
1896
+ infoHalf,
1897
+ mainPosMap,
1898
+ infoPlaced,
1899
+ infoHalfMap,
1900
+ placedMap,
1901
+ placedHalfMap
1902
+ );
1903
+ newPosMap[id] = { id, x: chosen.x, y: chosen.y };
1904
+ placedMap[id] = { x: chosen.x, y: chosen.y };
1905
+ placedHalfMap[id] = { x: infoHalf.x, y: infoHalf.y };
1906
+ infoPlaced[id] = { x: chosen.x, y: chosen.y };
1907
+ infoHalfMap[id] = { x: infoHalf.x, y: infoHalf.y };
1908
+ if ((idx & 31) === 0) await raf();
1909
+ }
1910
+ if (ShowGrid) {
1911
+ Object.entries(newPosMap).forEach(([id, p]) => {
1912
+ const snapped = snapToGridIfNeeded(p.x, p.y, instance);
1913
+ newPosMap[id] = { id, x: snapped.x, y: snapped.y };
1914
+ placedMap[id] = { x: snapped.x, y: snapped.y };
1915
+ });
1916
+ }
1917
+ for (const [id, infoNode] of infoNodeById.entries()) {
1918
+ const pos = newPosMap[id];
1919
+ if (!pos) continue;
1920
+ const infoHalf = infoHalfMap[id] || estimateInfoHalfSize(infoNode);
1921
+ let finalPos = { ...pos };
1922
+ for (let attempt = 0; attempt < 8; attempt++) {
1923
+ finalPos = pushOutFromMainForbidden(
1924
+ id,
1925
+ finalPos,
1926
+ infoNode,
1927
+ infoHalf,
1928
+ mainPosMap,
1929
+ infoPlaced,
1930
+ infoHalfMap,
1931
+ placedMap,
1932
+ placedHalfMap
1933
+ );
1934
+ if (ShowGrid) {
1935
+ finalPos = snapToGridIfNeeded(finalPos.x, finalPos.y, instance);
1936
+ }
1937
+ const stillBad = overlapsAnyMainBox(
1938
+ finalPos.x,
1939
+ finalPos.y,
1940
+ infoHalf.x,
1941
+ infoHalf.y,
1942
+ mainPosMap,
1943
+ INFO_BOX_MARGIN
1944
+ );
1945
+ if (!stillBad) break;
1946
+ }
1947
+ newPosMap[id] = { id, x: finalPos.x, y: finalPos.y };
1948
+ placedMap[id] = { x: finalPos.x, y: finalPos.y };
1949
+ infoPlaced[id] = { x: finalPos.x, y: finalPos.y };
1950
+ }
1951
+ const defaultEdgeSmooth = {
1952
+ type: "cubicBezier",
1953
+ forceDirection: "horizontal",
1954
+ roundness: 0.2
1955
+ };
1956
+ const edgeSmoothById = /* @__PURE__ */ Object.create(null);
1957
+ const mainToMainEdges = allEdges.filter(
1958
+ (e) => mainIds.has(S3(e.from)) && mainIds.has(S3(e.to))
1959
+ );
1960
+ const getEdgeMid = (e) => {
1961
+ var _a3, _b, _c, _d;
1962
+ const f = S3(e.from);
1963
+ const t = S3(e.to);
1964
+ const fx = ((_a3 = newPosMap[f]) == null ? void 0 : _a3.x) ?? 0;
1965
+ const fy = ((_b = newPosMap[f]) == null ? void 0 : _b.y) ?? 0;
1966
+ const tx = ((_c = newPosMap[t]) == null ? void 0 : _c.x) ?? 0;
1967
+ const ty = ((_d = newPosMap[t]) == null ? void 0 : _d.y) ?? 0;
1968
+ return { fx, fy, tx, ty, mx: (fx + tx) / 2, my: (fy + ty) / 2 };
1969
+ };
1970
+ const setEdgeSmooth = (e, smooth) => {
1971
+ edgeSmoothById[S3(e.id)] = smooth;
1972
+ };
1973
+ const pairGroups = /* @__PURE__ */ Object.create(null);
1974
+ for (const e of mainToMainEdges) {
1975
+ const key = `${S3(e.from)}|${S3(e.to)}`;
1976
+ if (!pairGroups[key]) pairGroups[key] = [];
1977
+ pairGroups[key].push(e);
1978
+ }
1979
+ for (const group of Object.values(pairGroups)) {
1980
+ if (group.length <= 1) continue;
1981
+ const sorted = [...group].sort((a, b) => S3(a.id).localeCompare(S3(b.id)));
1982
+ sorted.forEach((e, i) => {
1983
+ const tier = Math.floor(i / 2);
1984
+ const roundness = clamp(0.2 + tier * 0.24, 0.18, 0.85);
1985
+ const type = i % 2 === 0 ? "curvedCW" : "curvedCCW";
1986
+ setEdgeSmooth(e, { type, roundness });
1987
+ });
1988
+ }
1989
+ const channelBucket = Math.max(36, NODE_GAP_Y * 0.28);
1990
+ const channelGroups = /* @__PURE__ */ Object.create(null);
1991
+ for (const e of mainToMainEdges) {
1992
+ if (edgeSmoothById[S3(e.id)]) continue;
1993
+ const f = S3(e.from);
1994
+ const t = S3(e.to);
1995
+ const lf = layer[f];
1996
+ const lt = layer[t];
1997
+ if (lf == null || lt == null) continue;
1998
+ const mid = getEdgeMid(e);
1999
+ let channelKey;
2000
+ if (lf === lt) {
2001
+ channelKey = `col|${lf}|${Math.round(mid.mx / channelBucket)}`;
2002
+ } else {
2003
+ const leftL = Math.min(lf, lt);
2004
+ const rightL = Math.max(lf, lt);
2005
+ channelKey = `lr|${leftL}|${rightL}|${Math.round(mid.my / channelBucket)}`;
2006
+ }
2007
+ if (!channelGroups[channelKey]) channelGroups[channelKey] = [];
2008
+ channelGroups[channelKey].push(e);
2009
+ }
2010
+ for (const group of Object.values(channelGroups)) {
2011
+ if (group.length <= 1) continue;
2012
+ const sorted = [...group].sort((a, b) => getEdgeMid(a).my - getEdgeMid(b).my);
2013
+ const n = sorted.length;
2014
+ sorted.forEach((e, i) => {
2015
+ if (edgeSmoothById[S3(e.id)]) return;
2016
+ const offset = i - (n - 1) / 2;
2017
+ if (Math.abs(offset) < 0.01) {
2018
+ setEdgeSmooth(e, { ...defaultEdgeSmooth, roundness: 0.08 });
2019
+ return;
2020
+ }
2021
+ const type = offset < 0 ? "curvedCCW" : "curvedCW";
2022
+ const roundness = clamp(0.14 + Math.abs(offset) * 0.18, 0.12, 0.75);
2023
+ setEdgeSmooth(e, { type, roundness });
2024
+ });
2025
+ }
2026
+ const edgeSmoothUpdates = mainToMainEdges.map((e) => ({
2027
+ id: e.id,
2028
+ smooth: edgeSmoothById[S3(e.id)] || defaultEdgeSmooth
2029
+ }));
2030
+ const updates = Object.values(newPosMap).map((p) => {
2031
+ const id = S3(p.id);
2032
+ const isMain = mainIds.has(id);
2033
+ return {
2034
+ id: p.id,
2035
+ x: p.x,
2036
+ y: p.y,
2037
+ physics: false,
2038
+ // main nodes must stay draggable; only helpers stay fixed
2039
+ fixed: isMain ? false : { x: true, y: true }
2040
+ };
2041
+ });
2042
+ const CHUNK = updates.length > 4e3 ? 300 : updates.length > 1500 ? 500 : 800;
2043
+ for (let i = 0; i < updates.length; i += CHUNK) {
2044
+ nodesDS.update(updates.slice(i, i + CHUNK));
2045
+ await raf();
2046
+ }
2047
+ if (edgeSmoothUpdates.length) {
2048
+ for (let i = 0; i < edgeSmoothUpdates.length; i += CHUNK) {
2049
+ edgesDS.update(edgeSmoothUpdates.slice(i, i + CHUNK));
2050
+ await raf();
2051
+ }
2052
+ }
2053
+ const nextPositionsArr = Object.values(newPosMap);
2054
+ newPositionsRef.current = nextPositionsArr;
2055
+ SetNodesPosition(nextPositionsArr);
2056
+ const dataCopy = Array.isArray(dataRef.current) ? [...dataRef.current] : [];
2057
+ const idxMap = new Map(dataCopy.map((d, i) => [S3(d.id), i]));
2058
+ for (const [id, p] of Object.entries(newPosMap)) {
2059
+ const i = idxMap.get(id);
2060
+ if (i != null)
2061
+ dataCopy[i] = { ...dataCopy[i], x: p.x, y: p.y, physics: false };
2062
+ }
2063
+ SetData(dataCopy);
2064
+ dataRef.current = dataCopy;
2065
+ try {
2066
+ instance.setOptions({
2067
+ physics: { enabled: false },
2068
+ interaction: buildInteractionOptions(nodesDraggableRef.current)
2069
+ });
2070
+ } catch (_) {
2071
+ }
2072
+ if (mainNodes.length <= 1200) {
2073
+ instance.fit({
2074
+ animation: { duration: 350, easingFunction: "easeInOutQuad" }
2075
+ });
2076
+ } else {
2077
+ instance.moveTo({
2078
+ animation: { duration: 180, easingFunction: "easeInOutQuad" }
2079
+ });
2080
+ }
2081
+ try {
2082
+ instance.storePositions();
2083
+ } catch (_) {
2084
+ }
2085
+ } catch (err) {
2086
+ console.error("Auto arrange failed:", err);
2087
+ } finally {
2088
+ setAddPositionLoading(false);
2089
+ handleAutoArrangeGraph.__running = false;
2090
+ }
2091
+ };
2092
+ const takeFullGraphScreenshot = async () => {
2093
+ const instance = networkInstanceRef.current;
2094
+ const container = containerRef.current;
2095
+ const nodesDS = nodesRef.current;
2096
+ if (!instance || !container || !nodesDS || isCapturing) return;
2097
+ setIsCapturing(true);
2098
+ const mainNodeIds = nodesDS.get().map((n) => S2(n.id)).filter((id) => !isHelperNodeId(id));
2099
+ if (!mainNodeIds.length) {
2100
+ setIsCapturing(false);
2101
+ return;
2102
+ }
2103
+ const prevScale = instance.getScale();
2104
+ const prevPos = instance.getViewPosition();
2105
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2106
+ mainNodeIds.forEach((id) => {
2107
+ const bb = instance.getBoundingBox(id);
2108
+ if (!bb) return;
2109
+ minX = Math.min(minX, bb.left);
2110
+ minY = Math.min(minY, bb.top);
2111
+ maxX = Math.max(maxX, bb.right);
2112
+ maxY = Math.max(maxY, bb.bottom);
2113
+ });
2114
+ if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
2115
+ setIsCapturing(false);
2116
+ return;
2117
+ }
2118
+ const padding = 120;
2119
+ minX -= padding;
2120
+ minY -= padding;
2121
+ maxX += padding;
2122
+ maxY += padding;
2123
+ const graphW = Math.max(1, maxX - minX);
2124
+ const graphH = Math.max(1, maxY - minY);
2125
+ const cw = container.clientWidth || 1200;
2126
+ const ch = container.clientHeight || 800;
2127
+ const targetScale = Math.min(cw / graphW, ch / graphH);
2128
+ const center = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
2129
+ try {
2130
+ instance.moveTo({
2131
+ position: center,
2132
+ scale: targetScale,
2133
+ animation: { duration: 320, easingFunction: "easeInOutQuad" }
2134
+ });
2135
+ await wait(500);
2136
+ const canvas = await html2canvas(container, {
2137
+ backgroundColor: "#ffffff",
2138
+ useCORS: true,
2139
+ scale: Math.min((window.devicePixelRatio || 2) + 1, 4),
2140
+ logging: false
2141
+ });
2142
+ canvas.toBlob(
2143
+ (blob) => {
2144
+ if (!blob) return;
2145
+ downloadBlob(blob, `graph-full-${Date.now()}.png`);
2146
+ },
2147
+ "image/png",
2148
+ 1
2149
+ );
2150
+ } finally {
2151
+ instance.moveTo({
2152
+ position: prevPos,
2153
+ scale: prevScale,
2154
+ animation: { duration: 220, easingFunction: "easeInOutQuad" }
2155
+ });
2156
+ setIsCapturing(false);
2157
+ }
2158
+ };
2159
+ const syncAttachedNodesLive = (movedNodeIds) => {
2160
+ const instance = networkInstanceRef.current;
2161
+ const nodesDS = nodesRef.current;
2162
+ if (!instance || !nodesDS || !(movedNodeIds == null ? void 0 : movedNodeIds.length)) return;
2163
+ const ids = movedNodeIds.map(S2);
2164
+ const pos = instance.getPositions(ids);
2165
+ ids.forEach((id) => {
2166
+ const p = pos[id];
2167
+ if (!p) return;
2168
+ const arrowId = `${id}_Arrow`;
2169
+ if (nodesDS.get(arrowId)) instance.moveNode(arrowId, p.x, p.y - 46);
2170
+ const hotId = `${id}hotWallet`;
2171
+ if (nodesDS.get(hotId)) {
2172
+ const owner = nodesDS.get(id);
2173
+ instance.moveNode(hotId, p.x, (owner == null ? void 0 : owner.main) ? p.y - 45 : p.y - 25);
2174
+ }
2175
+ });
2176
+ };
2177
+ const syncEdgeLabelNodesLive = (movedNodeIds) => {
2178
+ const instance = networkInstanceRef.current;
2179
+ const nodesDS = nodesRef.current;
2180
+ if (!instance || !nodesDS || !(movedNodeIds == null ? void 0 : movedNodeIds.length)) return;
2181
+ const ids = new Set(movedNodeIds.map(S2));
2182
+ const relatedLabels = nodesDS.get().filter(
2183
+ (n) => n.group === "labelNode" && (ids.has(S2(n.from)) || ids.has(S2(n.to)))
2184
+ );
2185
+ relatedLabels.forEach((labelNode) => {
2186
+ const fromId = S2(labelNode.from);
2187
+ const toId = S2(labelNode.to);
2188
+ const fp = instance.getPositions([fromId])[fromId];
2189
+ const tp = instance.getPositions([toId])[toId];
2190
+ if (!fp || !tp) return;
2191
+ instance.moveNode(labelNode.id, (fp.x + tp.x) / 2, (fp.y + tp.y) / 2);
2192
+ });
2193
+ };
2194
+ const captureBaseStyles = () => {
2195
+ const nodesDS = nodesRef.current;
2196
+ const edgesDS = edgesRef.current;
2197
+ if (!nodesDS || !edgesDS) return;
2198
+ baseNodeStyleRef.current.clear();
2199
+ baseEdgeStyleRef.current.clear();
2200
+ nodesDS.get().forEach((n) => {
2201
+ baseNodeStyleRef.current.set(S2(n.id), {
2202
+ borderWidth: n.borderWidth ?? 1.5,
2203
+ color: n.color ?? void 0,
2204
+ shadow: n.shadow ?? false
2205
+ });
2206
+ });
2207
+ edgesDS.get().forEach((e) => {
2208
+ baseEdgeStyleRef.current.set(S2(e.id), {
2209
+ width: e.width ?? 2,
2210
+ color: e.color ?? void 0,
2211
+ shadow: e.shadow ?? false,
2212
+ dashes: e.dashes ?? false
2213
+ });
2214
+ });
2215
+ };
2216
+ const paintSelectionStyles = () => {
2217
+ const instance = networkInstanceRef.current;
2218
+ const nodesDS = nodesRef.current;
2219
+ const edgesDS = edgesRef.current;
2220
+ if (!instance || !nodesDS || !edgesDS) return;
2221
+ const selectedNodeIds = new Set(instance.getSelectedNodes().map(S2));
2222
+ const selectedEdgeIds = new Set(instance.getSelectedEdges().map(S2));
2223
+ const nodeUpdates = [];
2224
+ nodesDS.getIds().map(S2).forEach((id) => {
2225
+ const base = baseNodeStyleRef.current.get(id) || {};
2226
+ if (selectedNodeIds.has(id)) {
2227
+ nodeUpdates.push({
2228
+ id,
2229
+ borderWidth: 6,
2230
+ shadow: {
2231
+ enabled: true,
2232
+ color: "rgba(0,168,255,0.55)",
2233
+ size: 20,
2234
+ x: 0,
2235
+ y: 0
2236
+ },
2237
+ color: {
2238
+ ...typeof base.color === "object" ? base.color : {},
2239
+ border: "#00A8FF"
2240
+ }
2241
+ });
2242
+ } else {
2243
+ nodeUpdates.push({
2244
+ id,
2245
+ borderWidth: base.borderWidth ?? 1.5,
2246
+ shadow: base.shadow ?? false,
2247
+ color: base.color
2248
+ });
2249
+ }
2250
+ });
2251
+ const edgeUpdates = [];
2252
+ edgesDS.getIds().map(S2).forEach((id) => {
2253
+ var _a2;
2254
+ const base = baseEdgeStyleRef.current.get(id) || {};
2255
+ if (selectedEdgeIds.has(id)) {
2256
+ const baseColor = typeof base.color === "object" ? (_a2 = base.color) == null ? void 0 : _a2.color : void 0;
2257
+ const c = baseColor || "#00A8FF";
2258
+ edgeUpdates.push({
2259
+ id,
2260
+ width: Math.max(base.width ?? 2, 4),
2261
+ shadow: {
2262
+ enabled: true,
2263
+ color: "rgba(0,168,255,0.65)",
2264
+ size: 14,
2265
+ x: 0,
2266
+ y: 0
2267
+ },
2268
+ color: { color: c, highlight: c, hover: c, inherit: false },
2269
+ dashes: base.dashes ?? false
2270
+ });
2271
+ } else {
2272
+ edgeUpdates.push({
2273
+ id,
2274
+ width: base.width ?? 2,
2275
+ shadow: base.shadow ?? false,
2276
+ color: base.color,
2277
+ dashes: base.dashes ?? false
2278
+ });
2279
+ }
2280
+ });
2281
+ if (nodeUpdates.length) nodesDS.update(nodeUpdates);
2282
+ if (edgeUpdates.length) edgesDS.update(edgeUpdates);
2283
+ };
2284
+ const edgeColorPalette = useMemo(
2285
+ () => [
2286
+ "#ef4444",
2287
+ "#f97316",
2288
+ "#f59e0b",
2289
+ "#eab308",
2290
+ "#22c55e",
2291
+ "#14b8a6",
2292
+ "#06b6d4",
2293
+ "#3b82f6",
2294
+ "#8b5cf6",
2295
+ "#ec4899"
2296
+ ],
2297
+ []
2298
+ );
2299
+ const getPanelShellStyle = () => ({
2300
+ position: "absolute",
2301
+ top: 12,
2302
+ left: 12,
2303
+ zIndex: 99999,
2304
+ width: 300,
2305
+ maxWidth: "calc(100% - 24px)",
2306
+ borderRadius: 16,
2307
+ padding: 10,
2308
+ backdropFilter: "blur(12px)",
2309
+ WebkitBackdropFilter: "blur(12px)",
2310
+ background: isDarkMode ? "linear-gradient(160deg, rgba(15,23,42,0.92), rgba(30,41,59,0.86))" : "linear-gradient(160deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
2311
+ border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.24)" : "rgba(15,23,42,0.08)"}`,
2312
+ boxShadow: isDarkMode ? "0 14px 40px rgba(2,6,23,.48)" : "0 14px 36px rgba(15,23,42,.10)",
2313
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
2314
+ overflow: "hidden"
2315
+ });
2316
+ const panelInsetStyle = {
2317
+ padding: "8px 10px",
2318
+ borderRadius: 10,
2319
+ background: isDarkMode ? "rgba(15,23,42,.72)" : "rgba(255,255,255,.82)",
2320
+ border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.2)" : "rgba(15,23,42,0.08)"}`,
2321
+ boxShadow: isDarkMode ? "0 10px 24px rgba(2,6,23,.18)" : "0 10px 24px rgba(15,23,42,.06)"
2322
+ };
2323
+ const panelToggleRowStyle = {
2324
+ display: "flex",
2325
+ alignItems: "center",
2326
+ justifyContent: "space-between",
2327
+ gap: 6,
2328
+ padding: "0 1px 5px"
2329
+ };
2330
+ const panelCollapseBodyStyle = (open) => ({
2331
+ maxHeight: open ? 620 : 0,
2332
+ opacity: open ? 1 : 0,
2333
+ transition: "max-height 260ms ease, opacity 180ms ease",
2334
+ overflow: "hidden",
2335
+ pointerEvents: open ? "auto" : "none"
2336
+ });
2337
+ const compactInputStyle = {
2338
+ width: "100%",
2339
+ borderRadius: 8,
2340
+ border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
2341
+ background: isDarkMode ? "rgba(15,23,42,.92)" : "#fff",
2342
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
2343
+ padding: "8px 10px",
2344
+ outline: "none",
2345
+ fontSize: 12,
2346
+ boxSizing: "border-box"
2347
+ };
2348
+ const compactSectionLabel = {
2349
+ fontSize: 12,
2350
+ fontWeight: 900,
2351
+ marginBottom: 8,
2352
+ opacity: 1,
2353
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
2354
+ background: isDarkMode ? "rgba(14,165,233,.12)" : "rgba(14,165,233,.10)",
2355
+ padding: "4px 8px",
2356
+ borderRadius: 999,
2357
+ border: `1px solid ${isDarkMode ? "rgba(56,189,248,.22)" : "rgba(14,165,233,.18)"}`
2358
+ };
2359
+ useEffect(() => {
2360
+ if (!containerRef.current || initializedRef.current) return;
2361
+ nodesRef.current = new DataSet([]);
2362
+ edgesRef.current = new DataSet([]);
2363
+ const baseOptions = buildOptions(!!isDarkMode, {
2364
+ dragNodes: nodesDraggableRef.current
2365
+ });
2366
+ const instance = new Network(
2367
+ containerRef.current,
2368
+ { nodes: nodesRef.current, edges: edgesRef.current },
2369
+ {
2370
+ ...baseOptions,
2371
+ physics: { enabled: false },
2372
+ interaction: buildInteractionOptions(nodesDraggableRef.current)
2373
+ }
2374
+ );
2375
+ networkInstanceRef.current = instance;
2376
+ initializedRef.current = true;
2377
+ instance.on("beforeDrawing", (ctx) => {
2378
+ if (!showGridRef.current) return;
2379
+ drawNetworkGrid(ctx, instance, isDarkModeRef.current);
2380
+ });
2381
+ const syncSelection = () => {
2382
+ const nodeIds = instance.getSelectedNodes().map(S2);
2383
+ const edgeIds = instance.getSelectedEdges().map(S2);
2384
+ selectedNodeIdsRef.current = [...nodeIds];
2385
+ selectedEdgeIdsRef.current = [...edgeIds];
2386
+ setSelectedNodes(
2387
+ nodeIds.map((id) => nodesRef.current.get(id)).filter(Boolean)
2388
+ );
2389
+ SetSelectedEdges(
2390
+ edgeIds.map((id) => edgesRef.current.get(id)).filter(Boolean)
2391
+ );
2392
+ paintSelectionStyles();
2393
+ };
2394
+ instance.on("zoom", (params) => SetScale(params.scale));
2395
+ instance.on("dragging", (params) => {
2396
+ var _a2;
2397
+ if (!((_a2 = params == null ? void 0 : params.nodes) == null ? void 0 : _a2.length)) return;
2398
+ const movedMain = params.nodes.map(S2).filter((id) => !isHelperNodeId(id));
2399
+ if (!movedMain.length) return;
2400
+ syncAttachedNodesLive(movedMain);
2401
+ syncEdgeLabelNodesLive(movedMain);
2402
+ });
2403
+ instance.on("dragEnd", (params) => {
2404
+ var _a2;
2405
+ const viewPos = instance.getViewPosition();
2406
+ SetXPosition(viewPos.x);
2407
+ SetYPosition(viewPos.y);
2408
+ if ((_a2 = params == null ? void 0 : params.nodes) == null ? void 0 : _a2.length) {
2409
+ const movedIds = params.nodes.map(S2).filter((id) => !isHelperNodeId(id));
2410
+ if (!movedIds.length) {
2411
+ syncSelection();
2412
+ return;
2413
+ }
2414
+ const currentPositions = instance.getPositions(movedIds);
2415
+ const dataCopy = [...dataRef.current];
2416
+ const posMap = new Map(
2417
+ (newPositionsRef.current || []).map((p) => [
2418
+ S2(p.id),
2419
+ { ...p, id: S2(p.id) }
2420
+ ])
2421
+ );
2422
+ movedIds.forEach((id) => {
2423
+ const p = currentPositions[id];
2424
+ if (!p) return;
2425
+ const gridOn = !!showGridRef.current;
2426
+ const finalPos = gridOn ? snapToGridIfNeeded(p.x, p.y, instance) : { x: p.x, y: p.y };
2427
+ if (gridOn) {
2428
+ instance.moveNode(id, finalPos.x, finalPos.y);
2429
+ }
2430
+ const i = dataCopy.findIndex((d) => S2(d.id) === id);
2431
+ if (i >= 0)
2432
+ dataCopy[i] = { ...dataCopy[i], x: finalPos.x, y: finalPos.y };
2433
+ posMap.set(id, { id, x: finalPos.x, y: finalPos.y });
2434
+ const arrowId = `${id}_Arrow`;
2435
+ if (nodesRef.current.get(arrowId)) {
2436
+ const arrowPos = gridOn ? snapToGridIfNeeded(finalPos.x, finalPos.y - 46, instance) : { x: finalPos.x, y: finalPos.y - 46 };
2437
+ instance.moveNode(arrowId, arrowPos.x, arrowPos.y);
2438
+ posMap.set(arrowId, { id: arrowId, x: arrowPos.x, y: arrowPos.y });
2439
+ }
2440
+ const hotId = `${id}hotWallet`;
2441
+ if (nodesRef.current.get(hotId)) {
2442
+ const owner = nodesRef.current.get(id);
2443
+ const hotY = (owner == null ? void 0 : owner.main) ? finalPos.y - 45 : finalPos.y - 25;
2444
+ const hotPos = gridOn ? snapToGridIfNeeded(finalPos.x, hotY, instance) : { x: finalPos.x, y: hotY };
2445
+ instance.moveNode(hotId, hotPos.x, hotPos.y);
2446
+ posMap.set(hotId, { id: hotId, x: hotPos.x, y: hotPos.y });
2447
+ }
2448
+ });
2449
+ syncEdgeLabelNodesLive(movedIds);
2450
+ const nextPositions = Array.from(posMap.values());
2451
+ newPositionsRef.current = nextPositions;
2452
+ SetData(dataCopy);
2453
+ dataRef.current = dataCopy;
2454
+ SetNodesPosition(nextPositions);
2455
+ try {
2456
+ instance.storePositions();
2457
+ } catch (_) {
2458
+ }
2459
+ }
2460
+ syncSelection();
2461
+ });
2462
+ instance.on("hoverNode", (params) => {
2463
+ var _a2;
2464
+ const id = S2(params.node);
2465
+ if (isHelperNodeId(id)) {
2466
+ setHoverInfo({ visible: false, x: 0, y: 0, node: null });
2467
+ return;
2468
+ }
2469
+ setEdgeHoverInfo({
2470
+ visible: false,
2471
+ x: 0,
2472
+ y: 0,
2473
+ fromId: "",
2474
+ toId: "",
2475
+ summary: ""
2476
+ });
2477
+ const n = (_a2 = nodesRef.current) == null ? void 0 : _a2.get(id);
2478
+ if (!n) return;
2479
+ const dom = instance.canvasToDOM(instance.getPositions([id])[id]);
2480
+ setHoverInfo({ visible: true, x: dom.x, y: dom.y - 18, node: n });
2481
+ });
2482
+ instance.on(
2483
+ "blurNode",
2484
+ () => setHoverInfo({ visible: false, x: 0, y: 0, node: null })
2485
+ );
2486
+ instance.on("hoverEdge", (params) => {
2487
+ var _a2, _b, _c, _d;
2488
+ if (!showEdgeHoverInfoRef.current) return;
2489
+ const edge = (_a2 = edgesRef.current) == null ? void 0 : _a2.get(S2(params.edge));
2490
+ if (!edge) return;
2491
+ const fromId = S2(edge.from);
2492
+ const toId = S2(edge.to);
2493
+ const payload = findEdgePayload(fromId, toId);
2494
+ const summary = payload ? buildEdgeInfoLabel(payload, edgeLabelFieldsRef.current) : "";
2495
+ const rect = (_b = containerRef.current) == null ? void 0 : _b.getBoundingClientRect();
2496
+ const clientX = ((_c = params == null ? void 0 : params.event) == null ? void 0 : _c.clientX) ?? 0;
2497
+ const clientY = ((_d = params == null ? void 0 : params.event) == null ? void 0 : _d.clientY) ?? 0;
2498
+ const x = rect ? clientX - rect.left : clientX;
2499
+ const y = rect ? clientY - rect.top : clientY;
2500
+ setHoverInfo({ visible: false, x: 0, y: 0, node: null });
2501
+ setEdgeHoverInfo({
2502
+ visible: true,
2503
+ x,
2504
+ y: y - 12,
2505
+ fromId,
2506
+ toId,
2507
+ summary
2508
+ });
2509
+ });
2510
+ instance.on("blurEdge", () => {
2511
+ if (!showEdgeHoverInfoRef.current) return;
2512
+ setEdgeHoverInfo({
2513
+ visible: false,
2514
+ x: 0,
2515
+ y: 0,
2516
+ fromId: "",
2517
+ toId: "",
2518
+ summary: ""
2519
+ });
2520
+ });
2521
+ instance.on("selectNode", syncSelection);
2522
+ instance.on("selectEdge", syncSelection);
2523
+ instance.on("deselectNode", syncSelection);
2524
+ instance.on("deselectEdge", syncSelection);
2525
+ return () => {
2526
+ if (networkInstanceRef.current) {
2527
+ try {
2528
+ networkInstanceRef.current.destroy();
2529
+ } catch (_) {
2530
+ }
2531
+ }
2532
+ networkInstanceRef.current = null;
2533
+ initializedRef.current = false;
2534
+ viewportInitializedRef.current = false;
2535
+ };
2536
+ }, []);
2537
+ useEffect(() => {
2538
+ if (!initializedRef.current) return;
2539
+ const instance = networkInstanceRef.current;
2540
+ const nodesDS = nodesRef.current;
2541
+ const edgesDS = edgesRef.current;
2542
+ if (!instance || !nodesDS || !edgesDS) return;
2543
+ const posMap = new Map(
2544
+ (newPositionsRef.current || []).map((p) => [S2(p.id), p])
2545
+ );
2546
+ const nodesArr = [];
2547
+ const edgesArr = [];
2548
+ const defaultBorder = isDarkMode ? "#93c5fd" : "rgb(50, 87, 155)";
2549
+ const paintedMap = /* @__PURE__ */ new Map();
2550
+ (PaintedEdges || []).forEach((p) => {
2551
+ const id = S2(p.id);
2552
+ if (!id) return;
2553
+ const entry = {
2554
+ color: p.color || null,
2555
+ lineStyle: p.lineStyle || null
2556
+ };
2557
+ if (entry.color || entry.lineStyle) paintedMap.set(id, entry);
2558
+ });
2559
+ Data.forEach((item) => {
2560
+ const id = S2(item.id);
2561
+ const p = posMap.get(id);
2562
+ const x = Number((p == null ? void 0 : p.x) ?? item.x ?? 0);
2563
+ const y = Number((p == null ? void 0 : p.y) ?? item.y ?? 0);
2564
+ const label = getNodeDisplayLabel(item);
2565
+ const borderColor = getRateBorderColor(
2566
+ item.rate,
2567
+ resolvedRateColorRanges,
2568
+ defaultBorder
2569
+ );
2570
+ nodesArr.push({
2571
+ ...item,
2572
+ id,
2573
+ x,
2574
+ y,
2575
+ group: item.type,
2576
+ selectable: true,
2577
+ main: !!item.main,
2578
+ borderWidth: 2,
2579
+ color: { border: borderColor },
2580
+ rate: item.rate ?? null,
2581
+ address: item.text,
2582
+ image: getNodeEntityImage(item),
2583
+ label
2584
+ });
2585
+ if (item.metadata != null) {
2586
+ nodesArr.push({
2587
+ id: `${id}hotWallet`,
2588
+ x,
2589
+ y: item.main ? y - 45 : y - 25,
2590
+ group: "hotWallet",
2591
+ image: "/images/fire.png",
2592
+ label: item.metadata,
2593
+ selectable: false,
2594
+ physics: false,
2595
+ fixed: { x: true, y: true }
2596
+ });
2597
+ }
2598
+ if (item.main) {
2599
+ nodesArr.push({
2600
+ id: `${id}_Arrow`,
2601
+ x,
2602
+ y: y - 46,
2603
+ group: "Arrow",
2604
+ label: "\u2193",
2605
+ selectable: false,
2606
+ physics: false,
2607
+ fixed: { x: true, y: true }
2608
+ });
2609
+ }
2610
+ });
2611
+ const rawEdges = SetEdgesData(Data).map((e, idx) => {
2612
+ const from = S2(e.from);
2613
+ const to = S2(e.to);
2614
+ const id = e.id != null ? S2(e.id) : `${from}__${to}__${S2(e.label ?? "")}__${idx}`;
2615
+ const painted = paintedMap.get(id) || { color: null, lineStyle: null };
2616
+ const visStyle = resolveEdgeVisStyle({
2617
+ isDark: isDarkMode,
2618
+ paintedColor: painted.color,
2619
+ paintedLineStyle: painted.lineStyle
2620
+ });
2621
+ return {
2622
+ id,
2623
+ from,
2624
+ to,
2625
+ label: e.label != null ? S2(e.label) : "",
2626
+ selectable: true,
2627
+ ...visStyle
2628
+ };
2629
+ });
2630
+ const seen = /* @__PURE__ */ new Set();
2631
+ const uniqueEdges = rawEdges.filter((e) => {
2632
+ const key = `${e.from}__${e.to}__${S2(e.label ?? "")}`;
2633
+ if (seen.has(key)) return false;
2634
+ seen.add(key);
2635
+ return true;
2636
+ });
2637
+ edgesArr.push(...uniqueEdges);
2638
+ if (showEdgeInfoNodes && resolvedEdgeLabelFields.length) {
2639
+ Data.forEach((node) => {
2640
+ if (node.type !== NODE_TYPE.MAIN) return;
2641
+ const nodeId = S2(node.id);
2642
+ const selfPos = posMap.get(nodeId) || {
2643
+ x: node.x ?? 0,
2644
+ y: node.y ?? 0
2645
+ };
2646
+ const makeLabel = (edgeData) => buildEdgeInfoLabel(edgeData, resolvedEdgeLabelFields);
2647
+ (node.inputs || []).forEach((inp) => {
2648
+ const other = Data.find((d) => S2(d.id) === S2(inp.id));
2649
+ if (!other) return;
2650
+ const otherId = S2(other.id);
2651
+ const otherPos = posMap.get(otherId) || {
2652
+ x: other.x ?? 0,
2653
+ y: other.y ?? 0
2654
+ };
2655
+ nodesArr.push({
2656
+ id: `${nodeId}Tr${otherId}_in`,
2657
+ x: (selfPos.x + otherPos.x) / 2,
2658
+ y: (selfPos.y + otherPos.y) / 2,
2659
+ from: nodeId,
2660
+ to: otherId,
2661
+ group: "labelNode",
2662
+ label: makeLabel(inp),
2663
+ selectable: false,
2664
+ physics: false,
2665
+ fixed: { x: true, y: true }
2666
+ });
2667
+ });
2668
+ (node.outputs || []).forEach((out) => {
2669
+ const other = Data.find((d) => S2(d.id) === S2(out.id));
2670
+ if (!other) return;
2671
+ const otherId = S2(other.id);
2672
+ const otherPos = posMap.get(otherId) || {
2673
+ x: other.x ?? 0,
2674
+ y: other.y ?? 0
2675
+ };
2676
+ nodesArr.push({
2677
+ id: `${nodeId}Tr${otherId}_out`,
2678
+ x: (selfPos.x + otherPos.x) / 2,
2679
+ y: (selfPos.y + otherPos.y) / 2,
2680
+ from: nodeId,
2681
+ to: otherId,
2682
+ group: "labelNode",
2683
+ label: makeLabel(out),
2684
+ selectable: false,
2685
+ physics: false,
2686
+ fixed: { x: true, y: true }
2687
+ });
2688
+ });
2689
+ });
2690
+ }
2691
+ nodesDS.clear();
2692
+ edgesDS.clear();
2693
+ nodesDS.add(nodesArr);
2694
+ edgesDS.add(edgesArr);
2695
+ const validNodeIds = selectedNodeIdsRef.current.map(S2).filter((id) => !!nodesDS.get(id));
2696
+ const validEdgeIds = selectedEdgeIdsRef.current.map(S2).filter((id) => !!edgesDS.get(id));
2697
+ instance.setSelection(
2698
+ { nodes: validNodeIds, edges: validEdgeIds },
2699
+ { unselectAll: true, highlightEdges: false }
2700
+ );
2701
+ captureBaseStyles();
2702
+ paintSelectionStyles();
2703
+ if (!viewportInitializedRef.current) {
2704
+ instance.moveTo({
2705
+ scale: Scale,
2706
+ position: { x: XPosition, y: YPosition }
2707
+ });
2708
+ viewportInitializedRef.current = true;
2709
+ }
2710
+ }, [
2711
+ Data,
2712
+ Reload,
2713
+ resolvedEdgeLabelFields,
2714
+ resolvedRateColorRanges,
2715
+ PaintedEdges,
2716
+ isDarkMode,
2717
+ Scale,
2718
+ XPosition,
2719
+ YPosition,
2720
+ showEdgeInfoNodes
2721
+ ]);
2722
+ useEffect(() => {
2723
+ if (!networkInstanceRef.current) return;
2724
+ const baseOptions = buildOptions(!!isDarkMode, {
2725
+ dragNodes: nodesDraggableRef.current
2726
+ });
2727
+ networkInstanceRef.current.setOptions({
2728
+ ...baseOptions,
2729
+ physics: { enabled: false },
2730
+ interaction: buildInteractionOptions(nodesDraggableRef.current)
2731
+ });
2732
+ paintSelectionStyles();
2733
+ }, [isDarkMode]);
2734
+ useEffect(() => {
2735
+ if (!isStart) {
2736
+ SetReload(!Reload);
2737
+ setIsStart(true);
2738
+ }
2739
+ }, []);
2740
+ useEffect(() => {
2741
+ if (screenShot !== TakeSceenShot) {
2742
+ takeFullGraphScreenshot();
2743
+ setScreenShot(TakeSceenShot);
2744
+ }
2745
+ }, [TakeSceenShot]);
2746
+ useEffect(() => {
2747
+ const t = setTimeout(() => {
2748
+ if (searchTerm.trim()) runSearch(searchTerm);
2749
+ }, 180);
2750
+ return () => clearTimeout(t);
2751
+ }, [searchTerm]);
2752
+ useEffect(() => {
2753
+ const network = networkInstanceRef.current;
2754
+ const nodesDS = nodesRef.current;
2755
+ if (!network || !nodesDS) return;
2756
+ const S3 = (v) => String(v ?? "");
2757
+ const isIgnoredNode = (id) => {
2758
+ const s = S3(id);
2759
+ return s.endsWith("hotWallet") || // helper
2760
+ s.endsWith("_Arrow") || // helper
2761
+ s.includes("Tr") || // edgeInfo-like
2762
+ s.includes("edgeInfo");
2763
+ };
2764
+ const handleNodeClick = (params) => {
2765
+ if (!(params == null ? void 0 : params.nodes) || params.nodes.length === 0) {
2766
+ SetNode(null);
2767
+ return;
2768
+ }
2769
+ const clickedId = params.nodes[0];
2770
+ if (!clickedId) {
2771
+ SetNode(null);
2772
+ return;
2773
+ }
2774
+ if (isIgnoredNode(clickedId)) {
2775
+ SetNode(null);
2776
+ return;
2777
+ }
2778
+ const nodeData = nodesDS.get(clickedId);
2779
+ if (!nodeData) {
2780
+ SetNode(null);
2781
+ return;
2782
+ }
2783
+ const rawNode = (dataRef.current || []).find(
2784
+ (d) => S3(d.id) === S3(clickedId)
2785
+ );
2786
+ emitNodeClick(rawNode ? { ...rawNode } : { ...nodeData });
2787
+ };
2788
+ network.on("click", handleNodeClick);
2789
+ return () => {
2790
+ network.off("click", handleNodeClick);
2791
+ };
2792
+ }, [SetNode]);
2793
+ const actionButtonBase = {
2794
+ border: "none",
2795
+ borderRadius: 10,
2796
+ padding: "8px 12px",
2797
+ cursor: "pointer",
2798
+ fontWeight: 700,
2799
+ fontSize: 11,
2800
+ color: "#fff",
2801
+ display: "inline-flex",
2802
+ alignItems: "center",
2803
+ justifyContent: "center",
2804
+ gap: 7,
2805
+ boxShadow: "0 4px 14px rgba(2,6,23,.2)",
2806
+ transition: "transform 120ms ease, filter 160ms ease, box-shadow 160ms ease"
2807
+ };
2808
+ const getGlassShellStyle = () => ({
2809
+ backdropFilter: "blur(12px)",
2810
+ WebkitBackdropFilter: "blur(12px)",
2811
+ background: isDarkMode ? "linear-gradient(160deg, rgba(15,23,42,0.92), rgba(30,41,59,0.86))" : "linear-gradient(160deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
2812
+ border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.24)" : "rgba(15,23,42,0.08)"}`,
2813
+ boxShadow: isDarkMode ? "0 14px 40px rgba(2,6,23,.48)" : "0 14px 36px rgba(15,23,42,.10)",
2814
+ color: isDarkMode ? "#e2e8f0" : "#0f172a"
2815
+ });
2816
+ const toolbarButtonStyle = (bg, extra = {}) => ({
2817
+ ...actionButtonBase,
2818
+ background: bg,
2819
+ ...extra
2820
+ });
2821
+ const ghostIconButtonStyle = (extra = {}) => ({
2822
+ display: "inline-flex",
2823
+ alignItems: "center",
2824
+ justifyContent: "center",
2825
+ gap: 6,
2826
+ border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
2827
+ borderRadius: 12,
2828
+ padding: "7px 10px",
2829
+ cursor: "pointer",
2830
+ fontWeight: 700,
2831
+ fontSize: 12,
2832
+ background: isDarkMode ? "rgba(15,23,42,.65)" : "rgba(255,255,255,.9)",
2833
+ color: isDarkMode ? "#f8fafc" : "#0f172a",
2834
+ transition: "background 140ms ease, border-color 140ms ease",
2835
+ ...extra
2836
+ });
2837
+ return /* @__PURE__ */ React2.createElement(
2838
+ "div",
2839
+ {
2840
+ style: {
2841
+ position: "relative",
2842
+ width: "100%",
2843
+ height: "100%",
2844
+ minHeight: 400
2845
+ }
2846
+ },
2847
+ /* @__PURE__ */ React2.createElement(
2848
+ "div",
2849
+ {
2850
+ id: "myGraphDiv",
2851
+ ref: containerRef,
2852
+ className: "outline-none",
2853
+ style: { width: "100%", height: "100%", outline: "none" }
2854
+ }
2855
+ ),
2856
+ /* @__PURE__ */ React2.createElement(
2857
+ "div",
2858
+ {
2859
+ dir: isFa ? "rtl" : "ltr",
2860
+ style: {
2861
+ position: "absolute",
2862
+ top: 12,
2863
+ right: 12,
2864
+ zIndex: 1e5,
2865
+ display: "flex",
2866
+ flexWrap: "wrap",
2867
+ gap: 8,
2868
+ padding: 7,
2869
+ borderRadius: 14,
2870
+ ...getGlassShellStyle()
2871
+ }
2872
+ },
2873
+ /* @__PURE__ */ React2.createElement(
2874
+ "button",
2875
+ {
2876
+ type: "button",
2877
+ onClick: takeFullGraphScreenshot,
2878
+ disabled: isCapturing,
2879
+ title: isFa ? "\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A" : "Screenshot",
2880
+ style: toolbarButtonStyle(
2881
+ isCapturing ? "#64748b" : isDarkMode ? "linear-gradient(135deg,#0ea5e9,#0284c7)" : "linear-gradient(135deg,#0284c7,#0369a1)",
2882
+ {
2883
+ cursor: isCapturing ? "not-allowed" : "pointer",
2884
+ opacity: isCapturing ? 0.9 : 1
2885
+ }
2886
+ )
2887
+ },
2888
+ /* @__PURE__ */ React2.createElement(
2889
+ IconButtonContent,
2890
+ {
2891
+ icon: /* @__PURE__ */ React2.createElement(IconCamera, { size: 15 }),
2892
+ label: isCapturing ? isFa ? "..." : "..." : isFa ? "\u0627\u0633\u06A9\u0631\u06CC\u0646\u200C\u0634\u0627\u062A" : "Screenshot"
2893
+ }
2894
+ )
2895
+ ),
2896
+ /* @__PURE__ */ React2.createElement(
2897
+ "button",
2898
+ {
2899
+ type: "button",
2900
+ onClick: takeGraphExcelExport,
2901
+ title: isFa ? "\u062E\u0631\u0648\u062C\u06CC \u0627\u06A9\u0633\u0644" : "Export Excel",
2902
+ style: toolbarButtonStyle(
2903
+ isDarkMode ? "linear-gradient(135deg,#0f766e,#0d9488)" : "linear-gradient(135deg,#0f766e,#0f766e)"
2904
+ )
2905
+ },
2906
+ /* @__PURE__ */ React2.createElement(
2907
+ IconButtonContent,
2908
+ {
2909
+ icon: /* @__PURE__ */ React2.createElement(IconTable, { size: 15 }),
2910
+ label: isFa ? "\u0627\u06A9\u0633\u0644" : "Excel"
2911
+ }
2912
+ )
2913
+ )
2914
+ ),
2915
+ /* @__PURE__ */ React2.createElement("div", { dir: isFa ? "rtl" : "ltr", style: getPanelShellStyle() }, /* @__PURE__ */ React2.createElement("div", { style: panelToggleRowStyle }, /* @__PURE__ */ React2.createElement(
2916
+ "div",
2917
+ {
2918
+ style: {
2919
+ display: "flex",
2920
+ alignItems: "center",
2921
+ gap: 6,
2922
+ minWidth: 0
2923
+ }
2924
+ },
2925
+ /* @__PURE__ */ React2.createElement("div", { style: { fontSize: 13, fontWeight: 800, letterSpacing: 0.1 } }, isFa ? "\u067E\u0646\u0644 \u06AF\u0631\u0627\u0641" : "Graph Panel"),
2926
+ /* @__PURE__ */ React2.createElement(
2927
+ "span",
2928
+ {
2929
+ style: {
2930
+ fontSize: 11,
2931
+ fontWeight: 800,
2932
+ padding: "3px 8px",
2933
+ borderRadius: 999,
2934
+ background: isDarkMode ? "rgba(15,23,42,.7)" : "rgba(226,232,240,.95)",
2935
+ whiteSpace: "nowrap"
2936
+ }
2937
+ },
2938
+ isBasicEdition() && Number.isFinite(NODALIX_MAX_NODES) ? `${graphNodeCount}/${NODALIX_MAX_NODES}` : graphNodeCount
2939
+ )
2940
+ ), /* @__PURE__ */ React2.createElement(
2941
+ "button",
2942
+ {
2943
+ type: "button",
2944
+ onClick: () => setPanelOpen((p) => !p),
2945
+ title: panelOpen ? isFa ? "\u0628\u0633\u062A\u0646" : "Collapse" : isFa ? "\u0628\u0627\u0632 \u06A9\u0631\u062F\u0646" : "Expand",
2946
+ style: {
2947
+ width: 30,
2948
+ height: 30,
2949
+ minWidth: 30,
2950
+ borderRadius: 8,
2951
+ border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
2952
+ background: isDarkMode ? "#0f172a" : "#ffffff",
2953
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
2954
+ cursor: "pointer",
2955
+ display: "inline-flex",
2956
+ alignItems: "center",
2957
+ justifyContent: "center"
2958
+ }
2959
+ },
2960
+ /* @__PURE__ */ React2.createElement(IconChevron, { size: 15, up: panelOpen })
2961
+ )), /* @__PURE__ */ React2.createElement("div", { style: panelCollapseBodyStyle(panelOpen) }, /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 7 } }, nodeLimitReached ? /* @__PURE__ */ React2.createElement(
2962
+ "div",
2963
+ {
2964
+ style: {
2965
+ ...panelInsetStyle,
2966
+ background: isDarkMode ? "rgba(127,29,29,.35)" : "rgba(254,226,226,.95)",
2967
+ border: `1px solid ${isDarkMode ? "#b91c1c" : "#fca5a5"}`,
2968
+ color: isDarkMode ? "#fecaca" : "#991b1b",
2969
+ fontSize: 11,
2970
+ fontWeight: 700,
2971
+ lineHeight: 1.4
2972
+ }
2973
+ },
2974
+ isFa ? `Basic: \u062D\u062F\u0627\u06A9\u062B\u0631 ${NODALIX_MAX_NODES} \u06AF\u0631\u0647` : `Basic: max ${NODALIX_MAX_NODES} nodes`
2975
+ ) : null, languageUserConfigurable ? /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { dir: "ltr", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 } }, /* @__PURE__ */ React2.createElement(
2976
+ "button",
2977
+ {
2978
+ type: "button",
2979
+ onClick: () => setUserLang("fa"),
2980
+ style: {
2981
+ ...ghostIconButtonStyle({
2982
+ border: "none",
2983
+ background: lang === "fa" ? "#0ea5e9" : "transparent",
2984
+ color: lang === "fa" ? "#fff" : isDarkMode ? "#e2e8f0" : "#0f172a"
2985
+ })
2986
+ }
2987
+ },
2988
+ /* @__PURE__ */ React2.createElement(IconButtonContent, { icon: /* @__PURE__ */ React2.createElement(IconGlobe, { size: 14 }), label: "FA" })
2989
+ ), /* @__PURE__ */ React2.createElement(
2990
+ "button",
2991
+ {
2992
+ type: "button",
2993
+ onClick: () => setUserLang("en"),
2994
+ style: {
2995
+ ...ghostIconButtonStyle({
2996
+ border: "none",
2997
+ background: lang === "en" ? "#0ea5e9" : "transparent",
2998
+ color: lang === "en" ? "#fff" : isDarkMode ? "#e2e8f0" : "#0f172a"
2999
+ })
3000
+ }
3001
+ },
3002
+ /* @__PURE__ */ React2.createElement(IconButtonContent, { icon: /* @__PURE__ */ React2.createElement(IconGlobe, { size: 14 }), label: "EN" })
3003
+ ))) : null, /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement(
3004
+ "div",
3005
+ {
3006
+ style: {
3007
+ display: "flex",
3008
+ alignItems: "center",
3009
+ gap: 8,
3010
+ color: isDarkMode ? "#94a3b8" : "#64748b"
3011
+ }
3012
+ },
3013
+ /* @__PURE__ */ React2.createElement("span", { style: { display: "inline-flex", flexShrink: 0 } }, /* @__PURE__ */ React2.createElement(IconSearch, { size: 15 })),
3014
+ /* @__PURE__ */ React2.createElement(
3015
+ "input",
3016
+ {
3017
+ dir: isFa ? "rtl" : "ltr",
3018
+ value: searchTerm,
3019
+ onChange: (e) => setSearchTerm(e.target.value),
3020
+ placeholder: isFa ? "\u062C\u0633\u062A\u062C\u0648: ID \u06CC\u0627 Label" : "Search: ID or Label",
3021
+ style: {
3022
+ ...compactInputStyle,
3023
+ border: "none",
3024
+ background: "transparent",
3025
+ padding: 0,
3026
+ flex: 1
3027
+ }
3028
+ }
3029
+ )
3030
+ )), edgeInfoNodesUserConfigurable || gridUserConfigurable || nodesDraggableUserConfigurable ? /* @__PURE__ */ React2.createElement(
3031
+ "div",
3032
+ {
3033
+ style: {
3034
+ ...panelInsetStyle,
3035
+ display: "flex",
3036
+ flexWrap: "wrap",
3037
+ gap: "6px 12px",
3038
+ alignItems: "center"
3039
+ }
3040
+ },
3041
+ edgeInfoNodesUserConfigurable ? /* @__PURE__ */ React2.createElement(
3042
+ "label",
3043
+ {
3044
+ style: {
3045
+ display: "flex",
3046
+ alignItems: "center",
3047
+ gap: 4,
3048
+ cursor: "pointer",
3049
+ fontSize: 12,
3050
+ fontWeight: 700
3051
+ }
3052
+ },
3053
+ /* @__PURE__ */ React2.createElement(
3054
+ "input",
3055
+ {
3056
+ type: "checkbox",
3057
+ checked: showEdgeInfoNodes,
3058
+ onChange: (e) => setUserShowEdgeInfoNodes(e.target.checked),
3059
+ style: { width: 14, height: 14 }
3060
+ }
3061
+ ),
3062
+ isFa ? "\u06CC\u0627\u0644" : "Edge"
3063
+ ) : null,
3064
+ gridUserConfigurable ? /* @__PURE__ */ React2.createElement(
3065
+ "label",
3066
+ {
3067
+ style: {
3068
+ display: "flex",
3069
+ alignItems: "center",
3070
+ gap: 4,
3071
+ cursor: "pointer",
3072
+ fontSize: 12,
3073
+ fontWeight: 700
3074
+ }
3075
+ },
3076
+ /* @__PURE__ */ React2.createElement(
3077
+ "input",
3078
+ {
3079
+ type: "checkbox",
3080
+ checked: ShowGrid,
3081
+ onChange: (e) => setUserShowGrid(e.target.checked),
3082
+ style: { width: 14, height: 14 }
3083
+ }
3084
+ ),
3085
+ isFa ? "\u06AF\u0631\u06CC\u062F" : "Grid"
3086
+ ) : null,
3087
+ nodesDraggableUserConfigurable ? /* @__PURE__ */ React2.createElement(
3088
+ "label",
3089
+ {
3090
+ style: {
3091
+ display: "flex",
3092
+ alignItems: "center",
3093
+ gap: 4,
3094
+ cursor: "pointer",
3095
+ fontSize: 12,
3096
+ fontWeight: 700
3097
+ }
3098
+ },
3099
+ /* @__PURE__ */ React2.createElement(
3100
+ "input",
3101
+ {
3102
+ type: "checkbox",
3103
+ checked: nodesDraggableEnabled,
3104
+ onChange: (e) => setUserNodesDraggable(e.target.checked),
3105
+ style: { width: 14, height: 14 }
3106
+ }
3107
+ ),
3108
+ isFa ? "\u062C\u0627\u0628\u0647\u200C\u062C\u0627\u06CC\u06CC" : "Drag"
3109
+ ) : null
3110
+ ) : null, /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0631\u0646\u06AF \u06CC\u0627\u0644" : "Edge color"), /* @__PURE__ */ React2.createElement(
3111
+ "div",
3112
+ {
3113
+ style: {
3114
+ display: "grid",
3115
+ gridTemplateColumns: "repeat(5, 1fr)",
3116
+ gap: 6,
3117
+ justifyItems: "center",
3118
+ marginBottom: 6
3119
+ }
3120
+ },
3121
+ edgeColorPalette.map((c) => /* @__PURE__ */ React2.createElement(
3122
+ "button",
3123
+ {
3124
+ key: c,
3125
+ type: "button",
3126
+ onClick: () => applyColorToSelectedEdges(c),
3127
+ title: c,
3128
+ style: {
3129
+ width: 28,
3130
+ height: 28,
3131
+ borderRadius: 7,
3132
+ border: "1.5px solid rgba(255,255,255,0.9)",
3133
+ background: c,
3134
+ cursor: "pointer",
3135
+ padding: 0
3136
+ }
3137
+ }
3138
+ ))
3139
+ ), /* @__PURE__ */ React2.createElement(
3140
+ "button",
3141
+ {
3142
+ type: "button",
3143
+ onClick: () => applyColorToSelectedEdges(null),
3144
+ style: { ...ghostIconButtonStyle({ width: "100%" }) }
3145
+ },
3146
+ /* @__PURE__ */ React2.createElement(
3147
+ IconButtonContent,
3148
+ {
3149
+ icon: /* @__PURE__ */ React2.createElement(IconEraser, { size: 14 }),
3150
+ label: isFa ? "\u062D\u0630\u0641 \u0631\u0646\u06AF" : "Clear color"
3151
+ }
3152
+ )
3153
+ )), /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0627\u0633\u062A\u0627\u06CC\u0644 \u062E\u0637" : "Line style"), /* @__PURE__ */ React2.createElement(
3154
+ "div",
3155
+ {
3156
+ style: {
3157
+ display: "grid",
3158
+ gridTemplateColumns: "1fr 1fr 1fr",
3159
+ gap: 6
3160
+ }
3161
+ },
3162
+ [
3163
+ {
3164
+ style: EDGE_LINE_STYLE.DASHED,
3165
+ label: isFa ? "\u0686\u06CC\u0646" : "Dash",
3166
+ icon: /* @__PURE__ */ React2.createElement(IconLineDash, { size: 24 })
3167
+ },
3168
+ {
3169
+ style: EDGE_LINE_STYLE.DOTTED,
3170
+ label: isFa ? "\u0646\u0642\u0637\u0647" : "Dot",
3171
+ icon: /* @__PURE__ */ React2.createElement(IconLineDot, { size: 24 })
3172
+ },
3173
+ {
3174
+ style: null,
3175
+ label: isFa ? "\u0633\u0627\u062F\u0647" : "Solid",
3176
+ icon: /* @__PURE__ */ React2.createElement(IconLineSolid, { size: 24 })
3177
+ }
3178
+ ].map((item) => /* @__PURE__ */ React2.createElement(
3179
+ "button",
3180
+ {
3181
+ key: item.label,
3182
+ type: "button",
3183
+ onClick: () => applyLineStyleToSelectedEdges(item.style),
3184
+ title: item.label,
3185
+ style: {
3186
+ ...ghostIconButtonStyle({
3187
+ flexDirection: "column",
3188
+ gap: 2,
3189
+ padding: "6px 4px",
3190
+ background: isDarkMode ? "#0f172a" : "#fff"
3191
+ })
3192
+ }
3193
+ },
3194
+ item.icon,
3195
+ /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, fontWeight: 800 } }, item.label)
3196
+ ))
3197
+ )), /* @__PURE__ */ React2.createElement("div", { style: panelInsetStyle }, /* @__PURE__ */ React2.createElement("div", { style: compactSectionLabel }, isFa ? "\u0645\u0633\u06CC\u0631" : "Path"), /* @__PURE__ */ React2.createElement(
3198
+ "div",
3199
+ {
3200
+ style: {
3201
+ display: "grid",
3202
+ gridTemplateColumns: "1fr 1fr",
3203
+ gap: 6,
3204
+ marginBottom: 6
3205
+ }
3206
+ },
3207
+ /* @__PURE__ */ React2.createElement(
3208
+ "input",
3209
+ {
3210
+ dir: "ltr",
3211
+ value: pathFromId,
3212
+ onChange: (e) => setPathFromId(e.target.value),
3213
+ placeholder: isFa ? "\u0627\u0632" : "From",
3214
+ style: compactInputStyle
3215
+ }
3216
+ ),
3217
+ /* @__PURE__ */ React2.createElement(
3218
+ "input",
3219
+ {
3220
+ dir: "ltr",
3221
+ value: pathToId,
3222
+ onChange: (e) => setPathToId(e.target.value),
3223
+ placeholder: isFa ? "\u0628\u0647" : "To",
3224
+ style: compactInputStyle
3225
+ }
3226
+ )
3227
+ ), /* @__PURE__ */ React2.createElement(
3228
+ "div",
3229
+ {
3230
+ style: {
3231
+ display: "grid",
3232
+ gridTemplateColumns: "1fr 1fr",
3233
+ gap: 6
3234
+ }
3235
+ },
3236
+ /* @__PURE__ */ React2.createElement(
3237
+ "button",
3238
+ {
3239
+ type: "button",
3240
+ onClick: findAndHighlightAllPaths,
3241
+ style: toolbarButtonStyle(
3242
+ isDarkMode ? "linear-gradient(135deg,#7c3aed,#6d28d9)" : "linear-gradient(135deg,#6d28d9,#5b21b6)",
3243
+ { width: "100%" }
3244
+ )
3245
+ },
3246
+ /* @__PURE__ */ React2.createElement(
3247
+ IconButtonContent,
3248
+ {
3249
+ icon: /* @__PURE__ */ React2.createElement(IconRoute, { size: 14 }),
3250
+ label: isFa ? "\u0645\u0633\u06CC\u0631" : "Find"
3251
+ }
3252
+ )
3253
+ ),
3254
+ /* @__PURE__ */ React2.createElement(
3255
+ "button",
3256
+ {
3257
+ type: "button",
3258
+ onClick: () => {
3259
+ const instance = networkInstanceRef.current;
3260
+ if (!instance) return;
3261
+ instance.unselectAll();
3262
+ selectedNodeIdsRef.current = [];
3263
+ selectedEdgeIdsRef.current = [];
3264
+ setSelectedNodes([]);
3265
+ SetSelectedEdges([]);
3266
+ paintSelectionStyles();
3267
+ setPathMessage("");
3268
+ },
3269
+ style: { ...ghostIconButtonStyle({ width: "100%" }) }
3270
+ },
3271
+ /* @__PURE__ */ React2.createElement(
3272
+ IconButtonContent,
3273
+ {
3274
+ icon: /* @__PURE__ */ React2.createElement(IconEraser, { size: 14 }),
3275
+ label: isFa ? "\u067E\u0627\u06A9" : "Clear"
3276
+ }
3277
+ )
3278
+ )
3279
+ ), pathMessage ? /* @__PURE__ */ React2.createElement(
3280
+ "div",
3281
+ {
3282
+ style: {
3283
+ marginTop: 4,
3284
+ fontSize: 11,
3285
+ fontWeight: 700,
3286
+ color: isDarkMode ? "#93c5fd" : "#1d4ed8",
3287
+ lineHeight: 1.35
3288
+ }
3289
+ },
3290
+ pathMessage
3291
+ ) : null)))),
3292
+ showHelpButton ? /* @__PURE__ */ React2.createElement(
3293
+ "button",
3294
+ {
3295
+ type: "button",
3296
+ onClick: () => setShowGuide(true),
3297
+ title: isFa ? "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647" : "Usage guide",
3298
+ "aria-label": isFa ? "\u0631\u0627\u0647\u0646\u0645\u0627\u06CC \u0627\u0633\u062A\u0641\u0627\u062F\u0647" : "Usage guide",
3299
+ style: {
3300
+ position: "absolute",
3301
+ bottom: 12,
3302
+ right: 12,
3303
+ zIndex: 99995,
3304
+ width: 40,
3305
+ height: 40,
3306
+ borderRadius: 999,
3307
+ border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.35)" : "rgba(15,23,42,0.12)"}`,
3308
+ background: isDarkMode ? "linear-gradient(135deg, rgba(15,23,42,0.95), rgba(30,41,59,0.9))" : "linear-gradient(135deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92))",
3309
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
3310
+ fontSize: 18,
3311
+ fontWeight: 900,
3312
+ cursor: "pointer",
3313
+ boxShadow: isDarkMode ? "0 8px 24px rgba(2,6,23,.45)" : "0 8px 20px rgba(15,23,42,.14)"
3314
+ }
3315
+ },
3316
+ "?"
3317
+ ) : null,
3318
+ showGuide ? /* @__PURE__ */ React2.createElement(
3319
+ "div",
3320
+ {
3321
+ role: "dialog",
3322
+ "aria-modal": "true",
3323
+ "aria-label": guideContent.title,
3324
+ onClick: () => setShowGuide(false),
3325
+ style: {
3326
+ position: "absolute",
3327
+ inset: 0,
3328
+ zIndex: 100001,
3329
+ background: "rgba(2,6,23,0.45)",
3330
+ display: "grid",
3331
+ placeItems: "center",
3332
+ padding: 16
3333
+ }
3334
+ },
3335
+ /* @__PURE__ */ React2.createElement(
3336
+ "div",
3337
+ {
3338
+ dir: isFa ? "rtl" : "ltr",
3339
+ onClick: (e) => e.stopPropagation(),
3340
+ style: {
3341
+ width: "min(520px, 100%)",
3342
+ maxHeight: "min(78vh, 720px)",
3343
+ overflow: "auto",
3344
+ borderRadius: 16,
3345
+ padding: 16,
3346
+ background: isDarkMode ? "linear-gradient(135deg, rgba(15,23,42,0.98), rgba(30,41,59,0.96))" : "linear-gradient(135deg, rgba(255,255,255,0.98), rgba(248,250,252,0.96))",
3347
+ border: `1px solid ${isDarkMode ? "rgba(148,163,184,0.25)" : "rgba(15,23,42,0.10)"}`,
3348
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
3349
+ boxShadow: "0 18px 48px rgba(2,6,23,.35)"
3350
+ }
3351
+ },
3352
+ /* @__PURE__ */ React2.createElement(
3353
+ "div",
3354
+ {
3355
+ style: {
3356
+ display: "flex",
3357
+ alignItems: "center",
3358
+ justifyContent: "space-between",
3359
+ gap: 12,
3360
+ marginBottom: 12
3361
+ }
3362
+ },
3363
+ /* @__PURE__ */ React2.createElement("h3", { style: { margin: 0, fontSize: 16, fontWeight: 900 } }, guideContent.title),
3364
+ /* @__PURE__ */ React2.createElement(
3365
+ "button",
3366
+ {
3367
+ type: "button",
3368
+ onClick: () => setShowGuide(false),
3369
+ style: {
3370
+ border: `1px solid ${isDarkMode ? "#475569" : "#cbd5e1"}`,
3371
+ borderRadius: 10,
3372
+ padding: "6px 10px",
3373
+ cursor: "pointer",
3374
+ fontWeight: 700,
3375
+ fontSize: 12,
3376
+ background: isDarkMode ? "#0f172a" : "#fff",
3377
+ color: isDarkMode ? "#e2e8f0" : "#0f172a"
3378
+ }
3379
+ },
3380
+ guideContent.close
3381
+ )
3382
+ ),
3383
+ guideContent.sections.map((section) => /* @__PURE__ */ React2.createElement("div", { key: section.title, style: { marginBottom: 14 } }, /* @__PURE__ */ React2.createElement(
3384
+ "div",
3385
+ {
3386
+ style: {
3387
+ fontSize: 13,
3388
+ fontWeight: 800,
3389
+ marginBottom: 6,
3390
+ color: isDarkMode ? "#93c5fd" : "#1d4ed8"
3391
+ }
3392
+ },
3393
+ section.title
3394
+ ), /* @__PURE__ */ React2.createElement(
3395
+ "ul",
3396
+ {
3397
+ style: {
3398
+ margin: 0,
3399
+ paddingInlineStart: 18,
3400
+ fontSize: 12,
3401
+ lineHeight: 1.65
3402
+ }
3403
+ },
3404
+ section.items.map((item) => /* @__PURE__ */ React2.createElement("li", { key: item, style: { marginBottom: 4 } }, item))
3405
+ )))
3406
+ )
3407
+ ) : null,
3408
+ /* @__PURE__ */ React2.createElement(
3409
+ "div",
3410
+ {
3411
+ style: {
3412
+ position: "absolute",
3413
+ left: "50%",
3414
+ bottom: 8,
3415
+ transform: "translateX(-50%)",
3416
+ zIndex: 99990,
3417
+ pointerEvents: "none",
3418
+ fontSize: 11,
3419
+ fontWeight: 700,
3420
+ letterSpacing: 0.4,
3421
+ color: isDarkMode ? "rgba(148,163,184,.95)" : "rgba(15,23,42,.75)",
3422
+ background: isDarkMode ? "rgba(2,6,23,.45)" : "rgba(255,255,255,.72)",
3423
+ border: `1px solid ${isDarkMode ? "rgba(71,85,105,.45)" : "rgba(148,163,184,.45)"}`,
3424
+ borderRadius: 999,
3425
+ padding: "4px 10px",
3426
+ backdropFilter: "blur(4px)"
3427
+ }
3428
+ },
3429
+ FOOTER_TEXT
3430
+ ),
3431
+ hoverInfo.visible && hoverInfo.node && /* @__PURE__ */ React2.createElement(
3432
+ "div",
3433
+ {
3434
+ dir: isFa ? "rtl" : "ltr",
3435
+ style: {
3436
+ position: "absolute",
3437
+ left: hoverInfo.x,
3438
+ top: hoverInfo.y,
3439
+ transform: "translate(-50%, -100%)",
3440
+ zIndex: 1e5,
3441
+ pointerEvents: "none",
3442
+ maxWidth: 360,
3443
+ background: isDarkMode ? "rgba(2,6,23,0.96)" : "rgba(255,255,255,0.98)",
3444
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
3445
+ border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
3446
+ borderRadius: 10,
3447
+ padding: 10,
3448
+ boxShadow: "0 8px 24px rgba(0,0,0,.22)",
3449
+ fontSize: 12,
3450
+ lineHeight: 1.5,
3451
+ whiteSpace: "pre-wrap",
3452
+ wordBreak: "break-word"
3453
+ }
3454
+ },
3455
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0634\u0646\u0627\u0633\u0647" : "ID", ":"), " ", S2(hoverInfo.node.id)),
3456
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0628\u0631\u0686\u0633\u0628" : "Label", ":"), " ", S2(hoverInfo.node.label || "-")),
3457
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0646\u0648\u0639" : "Type", ":"), " ", S2(hoverInfo.node.type || hoverInfo.node.group || "-")),
3458
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0622\u062F\u0631\u0633" : "Address", ":"), " ", S2(hoverInfo.node.address || hoverInfo.node.text || "-")),
3459
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0645\u062A\u0627\u062F\u06CC\u062A\u0627" : "Metadata", ":"), " ", S2(hoverInfo.node.metadata || "-")),
3460
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0631\u06CC\u062A" : "Rate", ":"), " ", S2(hoverInfo.node.rate ?? "-")),
3461
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0632\u06CC\u0631\u0645\u062A\u0646" : "Sub Text", ":"), " ", S2(hoverInfo.node.subText || "-")),
3462
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0646\u0647\u0627\u062F" : "Entity", ":"), " ", S2(((_a = hoverInfo.node.entity) == null ? void 0 : _a.name) || "-")),
3463
+ /* @__PURE__ */ React2.createElement("details", null, /* @__PURE__ */ React2.createElement("summary", { style: { cursor: "default" } }, isFa ? "\u062F\u0627\u062F\u0647 \u062E\u0627\u0645" : "raw"), /* @__PURE__ */ React2.createElement("pre", { style: { margin: 0, fontSize: 11 } }, JSON.stringify(hoverInfo.node, null, 2)))
3464
+ ),
3465
+ showEdgeHoverInfo && edgeHoverInfo.visible ? /* @__PURE__ */ React2.createElement(
3466
+ "div",
3467
+ {
3468
+ dir: isFa ? "rtl" : "ltr",
3469
+ style: {
3470
+ position: "absolute",
3471
+ left: edgeHoverInfo.x,
3472
+ top: edgeHoverInfo.y,
3473
+ transform: "translate(-50%, -100%)",
3474
+ zIndex: 1e5,
3475
+ pointerEvents: "none",
3476
+ maxWidth: 360,
3477
+ background: isDarkMode ? "rgba(2,6,23,0.96)" : "rgba(255,255,255,0.98)",
3478
+ color: isDarkMode ? "#e2e8f0" : "#0f172a",
3479
+ border: `1px solid ${isDarkMode ? "#334155" : "#cbd5e1"}`,
3480
+ borderRadius: 10,
3481
+ padding: 10,
3482
+ boxShadow: "0 8px 24px rgba(0,0,0,.22)",
3483
+ fontSize: 12,
3484
+ lineHeight: 1.5,
3485
+ whiteSpace: "pre-wrap",
3486
+ wordBreak: "break-word"
3487
+ }
3488
+ },
3489
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0627\u0632" : "From", ":"), " ", getNodeLabelById(edgeHoverInfo.fromId)),
3490
+ /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("b", null, isFa ? "\u0628\u0647" : "To", ":"), " ", getNodeLabelById(edgeHoverInfo.toId)),
3491
+ edgeHoverInfo.summary ? /* @__PURE__ */ React2.createElement("div", { style: { marginTop: 6 } }, edgeHoverInfo.summary) : /* @__PURE__ */ React2.createElement("div", { style: { marginTop: 6, opacity: 0.75 } }, isFa ? "\u0627\u0637\u0644\u0627\u0639\u0627\u062A \u06CC\u0627\u0644 \u0645\u0648\u062C\u0648\u062F \u0646\u06CC\u0633\u062A" : "No edge data available")
3492
+ ) : null
3493
+ );
3494
+ };
3495
+ var Graph_default = Nodalix_graph_engine;
3496
+
3497
+ // src/index.js
3498
+ var __NODALIX_STORE__ = {
3499
+ data: [],
3500
+ setData: null,
3501
+ // توسط Nodalix هنگام mount مقداردهی می‌شود
3502
+ node: null,
3503
+ // نود انتخاب شده
3504
+ setNode: null,
3505
+ // setter نود انتخاب شده
3506
+ onNodeClick: null
3507
+ // callback کلیک روی نود
3508
+ };
3509
+ var nodeClickHandlerRef = { current: null };
3510
+ var scheduleNodalixUpdate = (fn) => {
3511
+ if (typeof queueMicrotask === "function") {
3512
+ queueMicrotask(fn);
3513
+ return;
3514
+ }
3515
+ Promise.resolve().then(fn);
3516
+ };
3517
+ function GetSelectedNode() {
3518
+ return __NODALIX_STORE__.node ?? null;
3519
+ }
3520
+ function SetSelectedNode(node) {
3521
+ if (typeof __NODALIX_STORE__.setNode !== "function") {
3522
+ return { success: false, error: "Nodalix instance is not mounted yet." };
3523
+ }
3524
+ const next = node ?? null;
3525
+ __NODALIX_STORE__.node = next;
3526
+ __NODALIX_STORE__.setNode(next);
3527
+ return { success: true, node: next };
3528
+ }
3529
+ function OnNodeClick(handler) {
3530
+ if (handler != null && typeof handler !== "function") {
3531
+ return { success: false, error: "handler must be a function or null." };
3532
+ }
3533
+ nodeClickHandlerRef.current = handler ?? null;
3534
+ __NODALIX_STORE__.onNodeClick = nodeClickHandlerRef.current;
3535
+ return { success: true };
3536
+ }
3537
+ function getSafeNodePosition(params = {}) {
3538
+ const {
3539
+ node = {},
3540
+ nodes = [],
3541
+ inputAnchorIds = [],
3542
+ outputAnchorIds = [],
3543
+ minGapY = 100,
3544
+ yStep = 100
3545
+ } = params;
3546
+ const minGapX = params.minGapX ?? params.minGap ?? 300;
3547
+ const positionedNodes = nodes.filter(
3548
+ (n) => typeof (n == null ? void 0 : n.x) === "number" && typeof (n == null ? void 0 : n.y) === "number"
3549
+ );
3550
+ const baseWidth = typeof node.width === "number" ? node.width : 220;
3551
+ const inputCount = Array.isArray(node.inputs) ? node.inputs.length : 0;
3552
+ const outputCount = Array.isArray(node.outputs) ? node.outputs.length : 0;
3553
+ const ioBoost = Math.min(inputCount + outputCount, 6) * 28;
3554
+ const computedWidth = baseWidth + ioBoost;
3555
+ const avg = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
3556
+ const isPlacementSafe = (x2, y2) => positionedNodes.every(
3557
+ (n) => Math.abs(n.x - x2) >= minGapX || Math.abs(n.y - y2) >= minGapY
3558
+ );
3559
+ if (!positionedNodes.length) {
3560
+ return {
3561
+ x: typeof node.x === "number" ? node.x : 0,
3562
+ y: typeof node.y === "number" ? node.y : 0,
3563
+ width: computedWidth
3564
+ };
3565
+ }
3566
+ const resolveAnchors = (ids) => ids.map((id) => positionedNodes.find((n) => n.id === id)).filter(Boolean);
3567
+ const inputAnchors = resolveAnchors(inputAnchorIds);
3568
+ const outputAnchors = resolveAnchors(outputAnchorIds);
3569
+ const connectedById = /* @__PURE__ */ new Map();
3570
+ for (const anchor of [...inputAnchors, ...outputAnchors]) {
3571
+ connectedById.set(anchor.id, anchor);
3572
+ }
3573
+ const connected = Array.from(connectedById.values());
3574
+ const hasInput = inputAnchors.length > 0;
3575
+ const hasOutput = outputAnchors.length > 0;
3576
+ let x;
3577
+ let baseY;
3578
+ if (hasInput && hasOutput) {
3579
+ x = avg(connected.map((n) => n.x));
3580
+ baseY = avg(connected.map((n) => n.y));
3581
+ } else if (hasInput) {
3582
+ x = avg(inputAnchors.map((n) => n.x)) + minGapX;
3583
+ baseY = avg(inputAnchors.map((n) => n.y));
3584
+ } else if (hasOutput) {
3585
+ x = avg(outputAnchors.map((n) => n.x)) - minGapX;
3586
+ baseY = avg(outputAnchors.map((n) => n.y));
3587
+ } else {
3588
+ x = typeof node.x === "number" ? node.x : avg(positionedNodes.map((n) => n.x));
3589
+ baseY = typeof node.y === "number" ? node.y : avg(positionedNodes.map((n) => n.y));
3590
+ }
3591
+ let y = baseY;
3592
+ const maxSteps = 200;
3593
+ for (let step = 0; step <= maxSteps && !isPlacementSafe(x, y); step++) {
3594
+ y = baseY - step * yStep;
3595
+ }
3596
+ return { x, y, width: computedWidth };
3597
+ }
3598
+ var applyNodeLimit = (nodes) => enforceNodeLimit(nodes, NODALIX_MAX_NODES);
3599
+ function AddNewNode(rawNode, options = {}) {
3600
+ const minGapX = options.minGapX ?? options.minGap ?? 300;
3601
+ const minGapY = options.minGapY ?? 100;
3602
+ const yStep = options.yStep ?? 100;
3603
+ if (typeof __NODALIX_STORE__.setData !== "function") {
3604
+ return { success: false, error: "Nodalix instance is not mounted yet." };
3605
+ }
3606
+ if (!rawNode || typeof rawNode !== "object") {
3607
+ return { success: false, error: "Invalid node object." };
3608
+ }
3609
+ if (!rawNode.id || typeof rawNode.id !== "string" || !rawNode.id.trim()) {
3610
+ return {
3611
+ success: false,
3612
+ error: "Node id is required and must be a non-empty string."
3613
+ };
3614
+ }
3615
+ const current = Array.isArray(__NODALIX_STORE__.data) ? __NODALIX_STORE__.data : [];
3616
+ if (!canAddGraphNode(current, NODALIX_MAX_NODES)) {
3617
+ return { success: false, error: getNodeLimitError(NODALIX_MAX_NODES) };
3618
+ }
3619
+ if (current.some((n) => n.id === rawNode.id)) {
3620
+ return { success: false, error: "Duplicate node id." };
3621
+ }
3622
+ const inEdges = Array.isArray(rawNode.inputs) ? rawNode.inputs.filter(Boolean) : [];
3623
+ const outEdges = Array.isArray(rawNode.outputs) ? rawNode.outputs.filter(Boolean) : [];
3624
+ const inputAnchorIds = inEdges.map((e) => e == null ? void 0 : e.id).filter((id) => id && current.some((n) => n.id === id));
3625
+ const outputAnchorIds = outEdges.map((e) => e == null ? void 0 : e.id).filter((id) => id && current.some((n) => n.id === id));
3626
+ const { x, y } = getSafeNodePosition({
3627
+ node: rawNode,
3628
+ nodes: current,
3629
+ inputAnchorIds,
3630
+ outputAnchorIds,
3631
+ minGapX,
3632
+ minGapY,
3633
+ yStep
3634
+ });
3635
+ const newNode = {
3636
+ ...rawNode,
3637
+ x,
3638
+ y,
3639
+ inputs: inEdges,
3640
+ outputs: outEdges
3641
+ };
3642
+ let updated = current.map((n) => ({ ...n }));
3643
+ const indexById = new Map(updated.map((n, i) => [n.id, i]));
3644
+ for (const e of newNode.inputs) {
3645
+ const srcId = e == null ? void 0 : e.id;
3646
+ if (!srcId || !indexById.has(srcId)) continue;
3647
+ const srcNode = updated[indexById.get(srcId)];
3648
+ const exists = Array.isArray(srcNode.outputs) ? srcNode.outputs.some((o) => (o == null ? void 0 : o.id) === newNode.id) : false;
3649
+ if (!exists) {
3650
+ const mirrored = mirrorEdgeRef(e, {
3651
+ id: newNode.id,
3652
+ time: (e == null ? void 0 : e.time) ?? Math.floor(Date.now() / 1e3)
3653
+ });
3654
+ srcNode.outputs = Array.isArray(srcNode.outputs) ? [...srcNode.outputs, mirrored] : [mirrored];
3655
+ }
3656
+ }
3657
+ for (const e of newNode.outputs) {
3658
+ const tgtId = e == null ? void 0 : e.id;
3659
+ if (!tgtId || !indexById.has(tgtId)) continue;
3660
+ const tgtNode = updated[indexById.get(tgtId)];
3661
+ const exists = Array.isArray(tgtNode.inputs) ? tgtNode.inputs.some((i) => (i == null ? void 0 : i.id) === newNode.id) : false;
3662
+ if (!exists) {
3663
+ const mirrored = mirrorEdgeRef(e, {
3664
+ id: newNode.id,
3665
+ time: (e == null ? void 0 : e.time) ?? Math.floor(Date.now() / 1e3)
3666
+ });
3667
+ tgtNode.inputs = Array.isArray(tgtNode.inputs) ? [...tgtNode.inputs, mirrored] : [mirrored];
3668
+ }
3669
+ }
3670
+ updated = [...updated, newNode];
3671
+ updated = applyNodeLimit(updated);
3672
+ const violates = updated.some(
3673
+ (n) => n.id !== newNode.id && Math.abs(n.x - newNode.x) < minGapX && Math.abs(n.y - newNode.y) < minGapY
3674
+ );
3675
+ if (violates) {
3676
+ return { success: false, error: "Failed to find a safe position for the new node." };
3677
+ }
3678
+ __NODALIX_STORE__.data = updated;
3679
+ __NODALIX_STORE__.setData(updated);
3680
+ return { success: true, node: newNode, data: updated, length: updated.length };
3681
+ }
3682
+ function Graph_Engine({
3683
+ NewData,
3684
+ onNodeClick,
3685
+ edgeLabelFields = DEFAULT_EDGE_LABEL_FIELDS,
3686
+ rateColorRanges = DEFAULT_RATE_COLOR_RANGES,
3687
+ showEdgeHoverInfo = false,
3688
+ showHelpButton = true,
3689
+ gridUserConfigurable = true,
3690
+ showGrid = true,
3691
+ nodesDraggableUserConfigurable = true,
3692
+ nodesDraggable = true,
3693
+ edgeInfoNodesUserConfigurable = true,
3694
+ showEdgeInfoNodes = true,
3695
+ languageUserConfigurable = true,
3696
+ language = "fa"
3697
+ }) {
3698
+ const resolvedEdgeLabelFields = useMemo2(
3699
+ () => normalizeEdgeLabelFields(edgeLabelFields),
3700
+ [edgeLabelFields]
3701
+ );
3702
+ const resolvedRateColorRanges = useMemo2(
3703
+ () => normalizeRateColorRanges(rateColorRanges),
3704
+ [rateColorRanges]
3705
+ );
3706
+ const [data, setData] = useState2([]);
3707
+ const [reload, setReload] = useState2(false);
3708
+ const [nodesPosition, setNodesPosition] = useState2([]);
3709
+ const [scale, setScale] = useState2(1);
3710
+ const [xPosition, setXPosition] = useState2(0);
3711
+ const [yPosition, setYPosition] = useState2(0);
3712
+ const [selectedEdges, setSelectedEdges] = useState2([]);
3713
+ const [selectedNodes, setSelectedNodes] = useState2([]);
3714
+ const [Node, SetNode] = useState2(null);
3715
+ const [PaintedEdges, SetPaintedEdges] = useState2([]);
3716
+ useEffect2(() => {
3717
+ setData(applyNodeLimit(Array.isArray(NewData) ? NewData : []));
3718
+ }, [NewData]);
3719
+ useEffect2(() => {
3720
+ __NODALIX_STORE__.setData = (updater) => {
3721
+ scheduleNodalixUpdate(() => {
3722
+ if (typeof updater === "function") {
3723
+ setData((prev) => {
3724
+ const next = applyNodeLimit(updater(prev));
3725
+ __NODALIX_STORE__.data = next;
3726
+ return next;
3727
+ });
3728
+ } else {
3729
+ const next = applyNodeLimit(updater);
3730
+ setData(next);
3731
+ __NODALIX_STORE__.data = next;
3732
+ }
3733
+ });
3734
+ };
3735
+ return () => {
3736
+ if (__NODALIX_STORE__.setData) __NODALIX_STORE__.setData = null;
3737
+ };
3738
+ }, []);
3739
+ useEffect2(() => {
3740
+ __NODALIX_STORE__.data = data;
3741
+ }, [data]);
3742
+ useEffect2(() => {
3743
+ __NODALIX_STORE__.setNode = (updater) => {
3744
+ scheduleNodalixUpdate(() => {
3745
+ if (typeof updater === "function") {
3746
+ SetNode((prev) => {
3747
+ const next = updater(prev);
3748
+ __NODALIX_STORE__.node = next ?? null;
3749
+ return next ?? null;
3750
+ });
3751
+ } else {
3752
+ SetNode(updater ?? null);
3753
+ __NODALIX_STORE__.node = updater ?? null;
3754
+ }
3755
+ });
3756
+ };
3757
+ return () => {
3758
+ if (__NODALIX_STORE__.setNode) __NODALIX_STORE__.setNode = null;
3759
+ };
3760
+ }, []);
3761
+ useEffect2(() => {
3762
+ __NODALIX_STORE__.node = Node ?? null;
3763
+ }, [Node]);
3764
+ useEffect2(() => {
3765
+ if (typeof onNodeClick === "function") {
3766
+ OnNodeClick(onNodeClick);
3767
+ }
3768
+ }, [onNodeClick]);
3769
+ return /* @__PURE__ */ React3.createElement("div", { style: { direction: "ltr", width: "100%", height: "100%" } }, /* @__PURE__ */ React3.createElement(
3770
+ Graph_default,
3771
+ {
3772
+ Data: data,
3773
+ SetData: setData,
3774
+ Reload: reload,
3775
+ SetReload: setReload,
3776
+ NodesPosition: nodesPosition,
3777
+ SetNodesPosition: setNodesPosition,
3778
+ Scale: scale,
3779
+ SetScale: setScale,
3780
+ XPosition: xPosition,
3781
+ SetXPosition: setXPosition,
3782
+ YPosition: yPosition,
3783
+ SetYPosition: setYPosition,
3784
+ edgeLabelFields: resolvedEdgeLabelFields,
3785
+ rateColorRanges: resolvedRateColorRanges,
3786
+ showEdgeHoverInfo: !!showEdgeHoverInfo,
3787
+ showHelpButton: !!showHelpButton,
3788
+ gridUserConfigurable: !!gridUserConfigurable,
3789
+ showGrid: !!showGrid,
3790
+ nodesDraggableUserConfigurable: !!nodesDraggableUserConfigurable,
3791
+ nodesDraggable: !!nodesDraggable,
3792
+ edgeInfoNodesUserConfigurable: !!edgeInfoNodesUserConfigurable,
3793
+ showEdgeInfoNodes: !!showEdgeInfoNodes,
3794
+ languageUserConfigurable: !!languageUserConfigurable,
3795
+ language,
3796
+ TakeSceenShot: true,
3797
+ SetSelectedEdges: setSelectedEdges,
3798
+ setSelectedNodes,
3799
+ PaintedEdges,
3800
+ SetPaintedEdges,
3801
+ SetNode,
3802
+ onNodeClickRef: nodeClickHandlerRef
3803
+ }
3804
+ ));
3805
+ }
3806
+ export {
3807
+ AddNewNode,
3808
+ DEFAULT_EDGE_LABEL_FIELDS,
3809
+ DEFAULT_NODE_IMAGE,
3810
+ DEFAULT_RATE_COLOR_RANGES,
3811
+ EDGE_LABEL_FIELDS,
3812
+ EDGE_LINE_STYLE,
3813
+ GRAPH_LANGUAGE,
3814
+ GetSelectedNode,
3815
+ Graph_Engine,
3816
+ NODALIX_EDITION,
3817
+ NODALIX_MAX_NODES,
3818
+ NODE_TYPE,
3819
+ OnNodeClick,
3820
+ SetSelectedNode,
3821
+ buildEdgeInfoLabel,
3822
+ canAddGraphNode,
3823
+ countGraphNodes,
3824
+ enforceNodeLimit,
3825
+ getGraphGuide,
3826
+ getNodeDisplayLabel,
3827
+ getNodeEntityImage,
3828
+ getRateBorderColor,
3829
+ getSafeNodePosition,
3830
+ hasNodeLimit,
3831
+ isBasicEdition,
3832
+ isFullEdition,
3833
+ isHelperNodeId,
3834
+ mirrorEdgeRef,
3835
+ normalizeEdgeLabelFields,
3836
+ normalizeEdgeLineStyle,
3837
+ normalizeGraphLanguage,
3838
+ normalizeRateColorRanges,
3839
+ resolveEdgeVisStyle
3840
+ };