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