@flowuent-org/diagramming-core 1.1.9 → 1.2.1
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/package.json +116 -116
- package/packages/diagrams/src/index.ts +1 -0
- package/packages/diagrams/src/lib/atoms/ConnectionPoints.tsx +149 -0
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +151 -13
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +158 -7
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +151 -13
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +152 -13
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +151 -13
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +166 -7
- package/packages/diagrams/src/lib/components/automation/NodeActionButtons.tsx +145 -0
- package/packages/diagrams/src/lib/components/automation/index.ts +1 -0
- package/packages/diagrams/src/lib/molecules/SideHandles.tsx +177 -12
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +10 -5
- package/packages/diagrams/src/lib/styles.css +53 -0
- package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +59 -0
- package/packages/diagrams/src/lib/templates/Diagramming.tsx +25 -24
- package/packages/diagrams/src/lib/types/edge-types.ts +17 -0
- package/packages/diagrams/src/lib/utils/generateEdgesFromNodeOrder.ts +113 -0
|
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
10
10
|
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
11
11
|
import { AISuggestion } from './AISuggestionsModal';
|
|
12
12
|
import { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
13
|
+
import { NodeActionButtons } from './NodeActionButtons';
|
|
13
14
|
|
|
14
15
|
interface AutomationEndNodeProps {
|
|
15
16
|
data: {
|
|
@@ -46,6 +47,9 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
46
47
|
const nodeId = useNodeId();
|
|
47
48
|
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
48
49
|
const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
|
|
50
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
51
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
52
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
49
53
|
|
|
50
54
|
// Get the icon component based on the iconName
|
|
51
55
|
const IconComponent = getIconByName(data.iconName);
|
|
@@ -337,22 +341,169 @@ export const AutomationEndNode: React.FC<AutomationEndNodeProps> = ({ data, sele
|
|
|
337
341
|
</Box>
|
|
338
342
|
</Box>
|
|
339
343
|
|
|
340
|
-
{/*
|
|
344
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
345
|
+
{/* Top - Source */}
|
|
346
|
+
<Handle
|
|
347
|
+
type="source"
|
|
348
|
+
position={Position.Top}
|
|
349
|
+
id="top-source"
|
|
350
|
+
className="connection-handle"
|
|
351
|
+
style={{
|
|
352
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
353
|
+
width: '14px',
|
|
354
|
+
height: '14px',
|
|
355
|
+
border: '3px solid #10B981',
|
|
356
|
+
top: '-8px',
|
|
357
|
+
opacity: selected ? 1 : 0,
|
|
358
|
+
transition: 'all 0.2s ease-in-out',
|
|
359
|
+
cursor: 'crosshair',
|
|
360
|
+
zIndex: 10,
|
|
361
|
+
}}
|
|
362
|
+
/>
|
|
363
|
+
{/* Top - Target (hidden but functional) */}
|
|
364
|
+
<Handle
|
|
365
|
+
type="target"
|
|
366
|
+
position={Position.Top}
|
|
367
|
+
id="top-target"
|
|
368
|
+
style={{
|
|
369
|
+
background: 'transparent',
|
|
370
|
+
width: '14px',
|
|
371
|
+
height: '14px',
|
|
372
|
+
border: 'none',
|
|
373
|
+
top: '-8px',
|
|
374
|
+
opacity: 0,
|
|
375
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
{/* Bottom - Source */}
|
|
379
|
+
<Handle
|
|
380
|
+
type="source"
|
|
381
|
+
position={Position.Bottom}
|
|
382
|
+
id="bottom-source"
|
|
383
|
+
className="connection-handle"
|
|
384
|
+
style={{
|
|
385
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
386
|
+
width: '14px',
|
|
387
|
+
height: '14px',
|
|
388
|
+
border: '3px solid #10B981',
|
|
389
|
+
bottom: '-8px',
|
|
390
|
+
opacity: selected ? 1 : 0,
|
|
391
|
+
transition: 'all 0.2s ease-in-out',
|
|
392
|
+
cursor: 'crosshair',
|
|
393
|
+
zIndex: 10,
|
|
394
|
+
}}
|
|
395
|
+
/>
|
|
396
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
341
397
|
<Handle
|
|
342
398
|
type="target"
|
|
399
|
+
position={Position.Bottom}
|
|
400
|
+
id="bottom-target"
|
|
401
|
+
style={{
|
|
402
|
+
background: 'transparent',
|
|
403
|
+
width: '14px',
|
|
404
|
+
height: '14px',
|
|
405
|
+
border: 'none',
|
|
406
|
+
bottom: '-8px',
|
|
407
|
+
opacity: 0,
|
|
408
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
409
|
+
}}
|
|
410
|
+
/>
|
|
411
|
+
{/* Left - Source */}
|
|
412
|
+
<Handle
|
|
413
|
+
type="source"
|
|
343
414
|
position={Position.Left}
|
|
344
|
-
id="left"
|
|
415
|
+
id="left-source"
|
|
416
|
+
className="connection-handle"
|
|
345
417
|
style={{
|
|
346
|
-
background: '#
|
|
347
|
-
width: '
|
|
348
|
-
height: '
|
|
349
|
-
border: '
|
|
418
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
419
|
+
width: '14px',
|
|
420
|
+
height: '14px',
|
|
421
|
+
border: '3px solid #10B981',
|
|
350
422
|
left: '-8px',
|
|
351
|
-
opacity:
|
|
423
|
+
opacity: selected ? 1 : 0,
|
|
424
|
+
transition: 'all 0.2s ease-in-out',
|
|
425
|
+
cursor: 'crosshair',
|
|
426
|
+
zIndex: 10,
|
|
352
427
|
}}
|
|
353
428
|
/>
|
|
429
|
+
{/* Left - Target (hidden but functional) */}
|
|
430
|
+
<Handle
|
|
431
|
+
type="target"
|
|
432
|
+
position={Position.Left}
|
|
433
|
+
id="left-target"
|
|
434
|
+
style={{
|
|
435
|
+
background: 'transparent',
|
|
436
|
+
width: '14px',
|
|
437
|
+
height: '14px',
|
|
438
|
+
border: 'none',
|
|
439
|
+
left: '-8px',
|
|
440
|
+
opacity: 0,
|
|
441
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
442
|
+
}}
|
|
443
|
+
/>
|
|
444
|
+
{/* Right - Source */}
|
|
445
|
+
<Handle
|
|
446
|
+
type="source"
|
|
447
|
+
position={Position.Right}
|
|
448
|
+
id="right-source"
|
|
449
|
+
className="connection-handle"
|
|
450
|
+
style={{
|
|
451
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
452
|
+
width: '14px',
|
|
453
|
+
height: '14px',
|
|
454
|
+
border: '3px solid #10B981',
|
|
455
|
+
right: '-8px',
|
|
456
|
+
opacity: selected ? 1 : 0,
|
|
457
|
+
transition: 'all 0.2s ease-in-out',
|
|
458
|
+
cursor: 'crosshair',
|
|
459
|
+
zIndex: 10,
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
462
|
+
{/* Right - Target (hidden but functional) */}
|
|
463
|
+
<Handle
|
|
464
|
+
type="target"
|
|
465
|
+
position={Position.Right}
|
|
466
|
+
id="right-target"
|
|
467
|
+
style={{
|
|
468
|
+
background: 'transparent',
|
|
469
|
+
width: '14px',
|
|
470
|
+
height: '14px',
|
|
471
|
+
border: 'none',
|
|
472
|
+
right: '-8px',
|
|
473
|
+
opacity: 0,
|
|
474
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
475
|
+
}}
|
|
476
|
+
/>
|
|
477
|
+
|
|
354
478
|
</Box>
|
|
355
479
|
|
|
480
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
481
|
+
<NodeActionButtons
|
|
482
|
+
selected={selected}
|
|
483
|
+
onDelete={() => {
|
|
484
|
+
if (nodeId && onNodesChange) {
|
|
485
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
486
|
+
}
|
|
487
|
+
}}
|
|
488
|
+
onDuplicate={() => {
|
|
489
|
+
if (nodeId) {
|
|
490
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
491
|
+
if (currentNode) {
|
|
492
|
+
const newNode = {
|
|
493
|
+
...currentNode,
|
|
494
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
495
|
+
position: {
|
|
496
|
+
x: currentNode.position.x + 50,
|
|
497
|
+
y: currentNode.position.y + 50,
|
|
498
|
+
},
|
|
499
|
+
selected: false,
|
|
500
|
+
};
|
|
501
|
+
setNodes([...nodes, newNode]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}}
|
|
505
|
+
/>
|
|
506
|
+
|
|
356
507
|
{/* AI Suggestions Button - Positioned below the node box */}
|
|
357
508
|
{data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
|
|
358
509
|
<Box
|
|
@@ -19,6 +19,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
19
19
|
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
20
20
|
import { AISuggestion } from './AISuggestionsModal';
|
|
21
21
|
import { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
22
|
+
import { NodeActionButtons } from './NodeActionButtons';
|
|
22
23
|
|
|
23
24
|
interface AutomationFormattingNodeProps {
|
|
24
25
|
data: {
|
|
@@ -71,6 +72,9 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
71
72
|
const nodeId = useNodeId();
|
|
72
73
|
const setSelectedNode = useDiagram((state) => state.setSelectedNode);
|
|
73
74
|
const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
|
|
75
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
76
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
77
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
74
78
|
|
|
75
79
|
// Get the icon component based on the iconName
|
|
76
80
|
const IconComponent = getIconByName(data.iconName);
|
|
@@ -562,35 +566,169 @@ export const AutomationFormattingNode: React.FC<AutomationFormattingNodeProps> =
|
|
|
562
566
|
</Box>
|
|
563
567
|
</Box>
|
|
564
568
|
|
|
565
|
-
{/* Handles -
|
|
569
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
570
|
+
{/* Top - Source */}
|
|
571
|
+
<Handle
|
|
572
|
+
type="source"
|
|
573
|
+
position={Position.Top}
|
|
574
|
+
id="top-source"
|
|
575
|
+
className="connection-handle"
|
|
576
|
+
style={{
|
|
577
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
578
|
+
width: '14px',
|
|
579
|
+
height: '14px',
|
|
580
|
+
border: '3px solid #10B981',
|
|
581
|
+
top: '-8px',
|
|
582
|
+
opacity: selected ? 1 : 0,
|
|
583
|
+
transition: 'all 0.2s ease-in-out',
|
|
584
|
+
cursor: 'crosshair',
|
|
585
|
+
zIndex: 10,
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
{/* Top - Target (hidden but functional) */}
|
|
589
|
+
<Handle
|
|
590
|
+
type="target"
|
|
591
|
+
position={Position.Top}
|
|
592
|
+
id="top-target"
|
|
593
|
+
style={{
|
|
594
|
+
background: 'transparent',
|
|
595
|
+
width: '14px',
|
|
596
|
+
height: '14px',
|
|
597
|
+
border: 'none',
|
|
598
|
+
top: '-8px',
|
|
599
|
+
opacity: 0,
|
|
600
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
601
|
+
}}
|
|
602
|
+
/>
|
|
603
|
+
{/* Bottom - Source */}
|
|
604
|
+
<Handle
|
|
605
|
+
type="source"
|
|
606
|
+
position={Position.Bottom}
|
|
607
|
+
id="bottom-source"
|
|
608
|
+
className="connection-handle"
|
|
609
|
+
style={{
|
|
610
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
611
|
+
width: '14px',
|
|
612
|
+
height: '14px',
|
|
613
|
+
border: '3px solid #10B981',
|
|
614
|
+
bottom: '-8px',
|
|
615
|
+
opacity: selected ? 1 : 0,
|
|
616
|
+
transition: 'all 0.2s ease-in-out',
|
|
617
|
+
cursor: 'crosshair',
|
|
618
|
+
zIndex: 10,
|
|
619
|
+
}}
|
|
620
|
+
/>
|
|
621
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
622
|
+
<Handle
|
|
623
|
+
type="target"
|
|
624
|
+
position={Position.Bottom}
|
|
625
|
+
id="bottom-target"
|
|
626
|
+
style={{
|
|
627
|
+
background: 'transparent',
|
|
628
|
+
width: '14px',
|
|
629
|
+
height: '14px',
|
|
630
|
+
border: 'none',
|
|
631
|
+
bottom: '-8px',
|
|
632
|
+
opacity: 0,
|
|
633
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
634
|
+
}}
|
|
635
|
+
/>
|
|
636
|
+
{/* Left - Source */}
|
|
637
|
+
<Handle
|
|
638
|
+
type="source"
|
|
639
|
+
position={Position.Left}
|
|
640
|
+
id="left-source"
|
|
641
|
+
className="connection-handle"
|
|
642
|
+
style={{
|
|
643
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
644
|
+
width: '14px',
|
|
645
|
+
height: '14px',
|
|
646
|
+
border: '3px solid #10B981',
|
|
647
|
+
left: '-8px',
|
|
648
|
+
opacity: selected ? 1 : 0,
|
|
649
|
+
transition: 'all 0.2s ease-in-out',
|
|
650
|
+
cursor: 'crosshair',
|
|
651
|
+
zIndex: 10,
|
|
652
|
+
}}
|
|
653
|
+
/>
|
|
654
|
+
{/* Left - Target (hidden but functional) */}
|
|
566
655
|
<Handle
|
|
567
656
|
type="target"
|
|
568
657
|
position={Position.Left}
|
|
569
|
-
id="left"
|
|
658
|
+
id="left-target"
|
|
570
659
|
style={{
|
|
571
|
-
background: '
|
|
572
|
-
width: '
|
|
573
|
-
height: '
|
|
574
|
-
border: '
|
|
660
|
+
background: 'transparent',
|
|
661
|
+
width: '14px',
|
|
662
|
+
height: '14px',
|
|
663
|
+
border: 'none',
|
|
575
664
|
left: '-8px',
|
|
576
|
-
opacity: 0,
|
|
665
|
+
opacity: 0,
|
|
666
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
577
667
|
}}
|
|
578
668
|
/>
|
|
669
|
+
{/* Right - Source */}
|
|
579
670
|
<Handle
|
|
580
671
|
type="source"
|
|
581
672
|
position={Position.Right}
|
|
582
|
-
id="right"
|
|
673
|
+
id="right-source"
|
|
674
|
+
className="connection-handle"
|
|
675
|
+
style={{
|
|
676
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
677
|
+
width: '14px',
|
|
678
|
+
height: '14px',
|
|
679
|
+
border: '3px solid #10B981',
|
|
680
|
+
right: '-8px',
|
|
681
|
+
opacity: selected ? 1 : 0,
|
|
682
|
+
transition: 'all 0.2s ease-in-out',
|
|
683
|
+
cursor: 'crosshair',
|
|
684
|
+
zIndex: 10,
|
|
685
|
+
}}
|
|
686
|
+
/>
|
|
687
|
+
{/* Right - Target (hidden but functional) */}
|
|
688
|
+
<Handle
|
|
689
|
+
type="target"
|
|
690
|
+
position={Position.Right}
|
|
691
|
+
id="right-target"
|
|
583
692
|
style={{
|
|
584
|
-
background: '
|
|
585
|
-
width: '
|
|
586
|
-
height: '
|
|
587
|
-
border: '
|
|
693
|
+
background: 'transparent',
|
|
694
|
+
width: '14px',
|
|
695
|
+
height: '14px',
|
|
696
|
+
border: 'none',
|
|
588
697
|
right: '-8px',
|
|
589
|
-
opacity: 0,
|
|
698
|
+
opacity: 0,
|
|
699
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
590
700
|
}}
|
|
591
701
|
/>
|
|
702
|
+
|
|
592
703
|
</Box>
|
|
593
704
|
|
|
705
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
706
|
+
<NodeActionButtons
|
|
707
|
+
selected={selected}
|
|
708
|
+
onDelete={() => {
|
|
709
|
+
if (nodeId && onNodesChange) {
|
|
710
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
711
|
+
}
|
|
712
|
+
}}
|
|
713
|
+
onDuplicate={() => {
|
|
714
|
+
if (nodeId) {
|
|
715
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
716
|
+
if (currentNode) {
|
|
717
|
+
const newNode = {
|
|
718
|
+
...currentNode,
|
|
719
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
720
|
+
position: {
|
|
721
|
+
x: currentNode.position.x + 50,
|
|
722
|
+
y: currentNode.position.y + 50,
|
|
723
|
+
},
|
|
724
|
+
selected: false,
|
|
725
|
+
};
|
|
726
|
+
setNodes([...nodes, newNode]);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}}
|
|
730
|
+
/>
|
|
731
|
+
|
|
594
732
|
{/* AI Suggestions Button - Positioned below the node box */}
|
|
595
733
|
{data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
|
|
596
734
|
<Box
|
|
@@ -5,6 +5,8 @@ import { Description as DescriptionIcon, Lightbulb as LightbulbIcon } from '@mui
|
|
|
5
5
|
import { getIconByName } from '../../utils/iconMapper';
|
|
6
6
|
import { AISuggestion } from './AISuggestionsModal';
|
|
7
7
|
import { AISuggestionsPanel } from './AISuggestionsPanel';
|
|
8
|
+
import { NodeActionButtons } from './NodeActionButtons';
|
|
9
|
+
import { useDiagram } from '../../contexts/DiagramProvider';
|
|
8
10
|
|
|
9
11
|
interface AutomationNoteNodeProps {
|
|
10
12
|
data: {
|
|
@@ -27,6 +29,9 @@ export const AutomationNoteNode: React.FC<AutomationNoteNodeProps> = ({ data, se
|
|
|
27
29
|
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
28
30
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
29
31
|
const nodeId = useNodeId();
|
|
32
|
+
const onNodesChange = useDiagram((state) => state.onNodesChange);
|
|
33
|
+
const nodes = useDiagram((state) => state.nodes);
|
|
34
|
+
const setNodes = useDiagram((state) => state.setNodes);
|
|
30
35
|
|
|
31
36
|
// Get the icon component based on the iconName, default to DescriptionIcon
|
|
32
37
|
const IconComponent = getIconByName(data.iconName || 'Description');
|
|
@@ -150,35 +155,169 @@ export const AutomationNoteNode: React.FC<AutomationNoteNodeProps> = ({ data, se
|
|
|
150
155
|
{data.noteType || 'note'}
|
|
151
156
|
</Box>
|
|
152
157
|
|
|
153
|
-
{/* Handles -
|
|
158
|
+
{/* Connection Handles - Bidirectional (source + target at each position) */}
|
|
159
|
+
{/* Top - Source */}
|
|
160
|
+
<Handle
|
|
161
|
+
type="source"
|
|
162
|
+
position={Position.Top}
|
|
163
|
+
id="top-source"
|
|
164
|
+
className="connection-handle"
|
|
165
|
+
style={{
|
|
166
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
167
|
+
width: '14px',
|
|
168
|
+
height: '14px',
|
|
169
|
+
border: '3px solid #10B981',
|
|
170
|
+
top: '-8px',
|
|
171
|
+
opacity: selected ? 1 : 0,
|
|
172
|
+
transition: 'all 0.2s ease-in-out',
|
|
173
|
+
cursor: 'crosshair',
|
|
174
|
+
zIndex: 10,
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
{/* Top - Target (hidden but functional) */}
|
|
178
|
+
<Handle
|
|
179
|
+
type="target"
|
|
180
|
+
position={Position.Top}
|
|
181
|
+
id="top-target"
|
|
182
|
+
style={{
|
|
183
|
+
background: 'transparent',
|
|
184
|
+
width: '14px',
|
|
185
|
+
height: '14px',
|
|
186
|
+
border: 'none',
|
|
187
|
+
top: '-8px',
|
|
188
|
+
opacity: 0,
|
|
189
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
{/* Bottom - Source */}
|
|
193
|
+
<Handle
|
|
194
|
+
type="source"
|
|
195
|
+
position={Position.Bottom}
|
|
196
|
+
id="bottom-source"
|
|
197
|
+
className="connection-handle"
|
|
198
|
+
style={{
|
|
199
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
200
|
+
width: '14px',
|
|
201
|
+
height: '14px',
|
|
202
|
+
border: '3px solid #10B981',
|
|
203
|
+
bottom: '-8px',
|
|
204
|
+
opacity: selected ? 1 : 0,
|
|
205
|
+
transition: 'all 0.2s ease-in-out',
|
|
206
|
+
cursor: 'crosshair',
|
|
207
|
+
zIndex: 10,
|
|
208
|
+
}}
|
|
209
|
+
/>
|
|
210
|
+
{/* Bottom - Target (hidden but functional) */}
|
|
154
211
|
<Handle
|
|
155
212
|
type="target"
|
|
213
|
+
position={Position.Bottom}
|
|
214
|
+
id="bottom-target"
|
|
215
|
+
style={{
|
|
216
|
+
background: 'transparent',
|
|
217
|
+
width: '14px',
|
|
218
|
+
height: '14px',
|
|
219
|
+
border: 'none',
|
|
220
|
+
bottom: '-8px',
|
|
221
|
+
opacity: 0,
|
|
222
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
{/* Left - Source */}
|
|
226
|
+
<Handle
|
|
227
|
+
type="source"
|
|
156
228
|
position={Position.Left}
|
|
157
|
-
id="left"
|
|
229
|
+
id="left-source"
|
|
230
|
+
className="connection-handle"
|
|
158
231
|
style={{
|
|
159
|
-
background:
|
|
160
|
-
width: '
|
|
161
|
-
height: '
|
|
162
|
-
border: '
|
|
232
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
233
|
+
width: '14px',
|
|
234
|
+
height: '14px',
|
|
235
|
+
border: '3px solid #10B981',
|
|
163
236
|
left: '-8px',
|
|
164
|
-
opacity:
|
|
237
|
+
opacity: selected ? 1 : 0,
|
|
238
|
+
transition: 'all 0.2s ease-in-out',
|
|
239
|
+
cursor: 'crosshair',
|
|
240
|
+
zIndex: 10,
|
|
165
241
|
}}
|
|
166
242
|
/>
|
|
243
|
+
{/* Left - Target (hidden but functional) */}
|
|
244
|
+
<Handle
|
|
245
|
+
type="target"
|
|
246
|
+
position={Position.Left}
|
|
247
|
+
id="left-target"
|
|
248
|
+
style={{
|
|
249
|
+
background: 'transparent',
|
|
250
|
+
width: '14px',
|
|
251
|
+
height: '14px',
|
|
252
|
+
border: 'none',
|
|
253
|
+
left: '-8px',
|
|
254
|
+
opacity: 0,
|
|
255
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
256
|
+
}}
|
|
257
|
+
/>
|
|
258
|
+
{/* Right - Source */}
|
|
167
259
|
<Handle
|
|
168
260
|
type="source"
|
|
169
261
|
position={Position.Right}
|
|
170
|
-
id="right"
|
|
262
|
+
id="right-source"
|
|
263
|
+
className="connection-handle"
|
|
171
264
|
style={{
|
|
172
|
-
background:
|
|
173
|
-
width: '
|
|
174
|
-
height: '
|
|
175
|
-
border: '
|
|
265
|
+
background: selected ? '#10B981' : '#1a1a2e',
|
|
266
|
+
width: '14px',
|
|
267
|
+
height: '14px',
|
|
268
|
+
border: '3px solid #10B981',
|
|
176
269
|
right: '-8px',
|
|
177
|
-
opacity:
|
|
270
|
+
opacity: selected ? 1 : 0,
|
|
271
|
+
transition: 'all 0.2s ease-in-out',
|
|
272
|
+
cursor: 'crosshair',
|
|
273
|
+
zIndex: 10,
|
|
178
274
|
}}
|
|
179
275
|
/>
|
|
276
|
+
{/* Right - Target (hidden but functional) */}
|
|
277
|
+
<Handle
|
|
278
|
+
type="target"
|
|
279
|
+
position={Position.Right}
|
|
280
|
+
id="right-target"
|
|
281
|
+
style={{
|
|
282
|
+
background: 'transparent',
|
|
283
|
+
width: '14px',
|
|
284
|
+
height: '14px',
|
|
285
|
+
border: 'none',
|
|
286
|
+
right: '-8px',
|
|
287
|
+
opacity: 0,
|
|
288
|
+
pointerEvents: selected ? 'all' : 'none',
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
|
|
180
292
|
</Box>
|
|
181
293
|
|
|
294
|
+
{/* Node Action Buttons - Shows when selected */}
|
|
295
|
+
<NodeActionButtons
|
|
296
|
+
selected={selected}
|
|
297
|
+
onDelete={() => {
|
|
298
|
+
if (nodeId && onNodesChange) {
|
|
299
|
+
onNodesChange([{ id: nodeId, type: 'remove' }]);
|
|
300
|
+
}
|
|
301
|
+
}}
|
|
302
|
+
onDuplicate={() => {
|
|
303
|
+
if (nodeId) {
|
|
304
|
+
const currentNode = nodes.find(n => n.id === nodeId);
|
|
305
|
+
if (currentNode) {
|
|
306
|
+
const newNode = {
|
|
307
|
+
...currentNode,
|
|
308
|
+
id: `${currentNode.id}-copy-${Date.now()}`,
|
|
309
|
+
position: {
|
|
310
|
+
x: currentNode.position.x + 50,
|
|
311
|
+
y: currentNode.position.y + 50,
|
|
312
|
+
},
|
|
313
|
+
selected: false,
|
|
314
|
+
};
|
|
315
|
+
setNodes([...nodes, newNode]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
|
|
182
321
|
{/* AI Suggestions Button - Positioned below the node box */}
|
|
183
322
|
{data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
|
|
184
323
|
<Box
|