@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 CHANGED
@@ -1,116 +1,116 @@
1
- {
2
- "name": "@flowuent-org/diagramming-core",
3
- "version": "1.1.9",
4
- "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "scripts": {
9
- "dev-diagramming": "nx serve diagramming",
10
- "build-diagrams": "nx build diagrams"
11
- },
12
- "dependencies": {
13
- "@dnd-kit/core": "^6.2.0",
14
- "@dnd-kit/modifiers": "^8.0.0",
15
- "@dnd-kit/sortable": "^9.0.0",
16
- "@dnd-kit/utilities": "^3.2.2",
17
- "@emotion/react": "^11.13.5",
18
- "@emotion/styled": "^11.13.5",
19
- "@fontsource/roboto": "^5.1.0",
20
- "@hookform/resolvers": "^3.9.1",
21
- "@mui/base": "5.0.0-beta.62",
22
- "@mui/icons-material": "^6.1.8",
23
- "@mui/material": "^6.1.8",
24
- "@mui/types": "^7.2.19",
25
- "@mui/x-data-grid": "^7.22.3",
26
- "@tabler/icons-react": "^3.22.0",
27
- "@welldone-software/why-did-you-render": "^8.0.3",
28
- "@xyflow/react": "^12.3.5",
29
- "axios": "^1.7.8",
30
- "elkjs": "^0.9.3",
31
- "file-saver": "^2.0.5",
32
- "formik": "^2.4.6",
33
- "framer-motion": "^11.11.17",
34
- "gapi-script": "^1.2.0",
35
- "html-to-image": "^1.11.11",
36
- "i18next": "^24.0.2",
37
- "immer": "^10.1.1",
38
- "jotai": "^2.10.3",
39
- "js-beautify": "^1.15.1",
40
- "lodash": "^4.17.21",
41
- "nanoid": "^5.0.9",
42
- "react": "19.0.0",
43
- "react-color": "^2.19.3",
44
- "react-dnd": "^16.0.1",
45
- "react-dom": "19.0.0",
46
- "react-from-json": "^0.8.0",
47
- "react-hook-form": "^7.53.2",
48
- "react-i18next": "^15.1.2",
49
- "react-icons": "^5.3.0",
50
- "react-json-view": "^1.21.3",
51
- "react-rnd": "^10.4.13",
52
- "react-router-dom": "^7.0.1",
53
- "react-syntax-highlighter": "^15.6.1",
54
- "reactflow": "^11.11.4",
55
- "tslib": "^2.8.1",
56
- "use-undo": "^1.1.1",
57
- "uuid": "^11.1.0",
58
- "xlsx": "^0.18.5",
59
- "yup": "^1.4.0",
60
- "zustand": "^5.0.1"
61
- },
62
- "devDependencies": {
63
- "@babel/core": "^7.26.0",
64
- "@babel/preset-react": "^7.25.9",
65
- "@nx/cypress": "20.6.4",
66
- "@nx/eslint": "20.6.4",
67
- "@nx/eslint-plugin": "20.6.4",
68
- "@nx/js": "20.6.4",
69
- "@nx/react": "20.6.4",
70
- "@nx/rollup": "20.6.4",
71
- "@nx/vite": "20.6.4",
72
- "@nx/web": "20.6.4",
73
- "@nx/webpack": "20.6.4",
74
- "@nx/workspace": "20.6.4",
75
- "@rollup/plugin-typescript": "^12.1.1",
76
- "@rollup/plugin-url": "^8.0.2",
77
- "@svgr/rollup": "^8.1.0",
78
- "@svgr/webpack": "^8.1.0",
79
- "@swc-node/register": "~1.10.9",
80
- "@swc/cli": "~0.5.1",
81
- "@swc/core": "~1.9.3",
82
- "@swc/helpers": "~0.5.15",
83
- "@testing-library/react": "16.1.0",
84
- "@types/file-saver": "^2.0.7",
85
- "@types/jest": "^29.5.14",
86
- "@types/js-beautify": "^1.14.3",
87
- "@types/lodash": "^4.17.13",
88
- "@types/node": "22.10.0",
89
- "@types/react": "18.3.12",
90
- "@types/react-color": "^3.0.12",
91
- "@types/react-dom": "18.3.1",
92
- "@types/uuid": "^10.0.0",
93
- "@typescript-eslint/eslint-plugin": "^8.16.0",
94
- "@typescript-eslint/parser": "^8.16.0",
95
- "@vitejs/plugin-react": "^4.3.4",
96
- "@vitest/coverage-v8": "^2.1.6",
97
- "@vitest/ui": "^2.1.6",
98
- "eslint": "~9.15.0",
99
- "eslint-config-prettier": "^9.1.0",
100
- "eslint-plugin-cypress": "^4.1.0",
101
- "eslint-plugin-import": "2.31.0",
102
- "eslint-plugin-jsx-a11y": "6.10.2",
103
- "eslint-plugin-react": "7.37.2",
104
- "eslint-plugin-react-hooks": "5.0.0",
105
- "eslint-plugin-react-refresh": "^0.4.14",
106
- "jsdom": "~25.0.1",
107
- "nx": "20.6.4",
108
- "prettier": "^3.4.1",
109
- "rollup-preserve-directives": "^1.1.3",
110
- "swc-loader": "0.1.15",
111
- "typescript": "~5.7.2",
112
- "vite": "^6.3.6",
113
- "vitest": "^2.1.6",
114
- "webpack-cli": "^5.1.4"
115
- }
116
- }
1
+ {
2
+ "name": "@flowuent-org/diagramming-core",
3
+ "version": "1.2.1",
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "scripts": {
9
+ "dev-diagramming": "nx serve diagramming",
10
+ "build-diagrams": "nx build diagrams"
11
+ },
12
+ "dependencies": {
13
+ "@dnd-kit/core": "^6.2.0",
14
+ "@dnd-kit/modifiers": "^8.0.0",
15
+ "@dnd-kit/sortable": "^9.0.0",
16
+ "@dnd-kit/utilities": "^3.2.2",
17
+ "@emotion/react": "^11.13.5",
18
+ "@emotion/styled": "^11.13.5",
19
+ "@fontsource/roboto": "^5.1.0",
20
+ "@hookform/resolvers": "^3.9.1",
21
+ "@mui/base": "5.0.0-beta.62",
22
+ "@mui/icons-material": "^6.1.8",
23
+ "@mui/material": "^6.1.8",
24
+ "@mui/types": "^7.2.19",
25
+ "@mui/x-data-grid": "^7.22.3",
26
+ "@tabler/icons-react": "^3.22.0",
27
+ "@welldone-software/why-did-you-render": "^8.0.3",
28
+ "@xyflow/react": "^12.3.5",
29
+ "axios": "^1.7.8",
30
+ "elkjs": "^0.9.3",
31
+ "file-saver": "^2.0.5",
32
+ "formik": "^2.4.6",
33
+ "framer-motion": "^11.11.17",
34
+ "gapi-script": "^1.2.0",
35
+ "html-to-image": "^1.11.11",
36
+ "i18next": "^24.0.2",
37
+ "immer": "^10.1.1",
38
+ "jotai": "^2.10.3",
39
+ "js-beautify": "^1.15.1",
40
+ "lodash": "^4.17.21",
41
+ "nanoid": "^5.0.9",
42
+ "react": "19.0.0",
43
+ "react-color": "^2.19.3",
44
+ "react-dnd": "^16.0.1",
45
+ "react-dom": "19.0.0",
46
+ "react-from-json": "^0.8.0",
47
+ "react-hook-form": "^7.53.2",
48
+ "react-i18next": "^15.1.2",
49
+ "react-icons": "^5.3.0",
50
+ "react-json-view": "^1.21.3",
51
+ "react-rnd": "^10.4.13",
52
+ "react-router-dom": "^7.0.1",
53
+ "react-syntax-highlighter": "^15.6.1",
54
+ "reactflow": "^11.11.4",
55
+ "tslib": "^2.8.1",
56
+ "use-undo": "^1.1.1",
57
+ "uuid": "^11.1.0",
58
+ "xlsx": "^0.18.5",
59
+ "yup": "^1.4.0",
60
+ "zustand": "^5.0.1"
61
+ },
62
+ "devDependencies": {
63
+ "@babel/core": "^7.26.0",
64
+ "@babel/preset-react": "^7.25.9",
65
+ "@nx/cypress": "20.6.4",
66
+ "@nx/eslint": "20.6.4",
67
+ "@nx/eslint-plugin": "20.6.4",
68
+ "@nx/js": "20.6.4",
69
+ "@nx/react": "20.6.4",
70
+ "@nx/rollup": "20.6.4",
71
+ "@nx/vite": "20.6.4",
72
+ "@nx/web": "20.6.4",
73
+ "@nx/webpack": "20.6.4",
74
+ "@nx/workspace": "20.6.4",
75
+ "@rollup/plugin-typescript": "^12.1.1",
76
+ "@rollup/plugin-url": "^8.0.2",
77
+ "@svgr/rollup": "^8.1.0",
78
+ "@svgr/webpack": "^8.1.0",
79
+ "@swc-node/register": "~1.10.9",
80
+ "@swc/cli": "~0.5.1",
81
+ "@swc/core": "~1.9.3",
82
+ "@swc/helpers": "~0.5.15",
83
+ "@testing-library/react": "16.1.0",
84
+ "@types/file-saver": "^2.0.7",
85
+ "@types/jest": "^29.5.14",
86
+ "@types/js-beautify": "^1.14.3",
87
+ "@types/lodash": "^4.17.13",
88
+ "@types/node": "22.10.0",
89
+ "@types/react": "18.3.12",
90
+ "@types/react-color": "^3.0.12",
91
+ "@types/react-dom": "18.3.1",
92
+ "@types/uuid": "^10.0.0",
93
+ "@typescript-eslint/eslint-plugin": "^8.16.0",
94
+ "@typescript-eslint/parser": "^8.16.0",
95
+ "@vitejs/plugin-react": "^4.3.4",
96
+ "@vitest/coverage-v8": "^2.1.6",
97
+ "@vitest/ui": "^2.1.6",
98
+ "eslint": "~9.15.0",
99
+ "eslint-config-prettier": "^9.1.0",
100
+ "eslint-plugin-cypress": "^4.1.0",
101
+ "eslint-plugin-import": "2.31.0",
102
+ "eslint-plugin-jsx-a11y": "6.10.2",
103
+ "eslint-plugin-react": "7.37.2",
104
+ "eslint-plugin-react-hooks": "5.0.0",
105
+ "eslint-plugin-react-refresh": "^0.4.14",
106
+ "jsdom": "~25.0.1",
107
+ "nx": "20.6.4",
108
+ "prettier": "^3.4.1",
109
+ "rollup-preserve-directives": "^1.1.3",
110
+ "swc-loader": "0.1.15",
111
+ "typescript": "~5.7.2",
112
+ "vite": "^6.3.6",
113
+ "vitest": "^2.1.6",
114
+ "webpack-cli": "^5.1.4"
115
+ }
116
+ }
@@ -7,6 +7,7 @@ export * from './lib/atoms/AddParallelColButton';
7
7
  export * from './lib/atoms/BendpointNode';
8
8
  export * from './lib/atoms/CardBlockTypeSelector';
9
9
  export * from './lib/atoms/CardMainContent';
10
+ export * from './lib/atoms/ConnectionPoints';
10
11
  export * from './lib/atoms/FloatingConnectionLine';
11
12
  export * from './lib/atoms/MarkerSelector';
12
13
  export * from './lib/atoms/PropertyInput';
@@ -0,0 +1,149 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Handle, Position, useNodeId } from '@xyflow/react';
3
+ import { Box } from '@mui/material';
4
+ import { useSelectedNode } from '../contexts/DiagramProvider';
5
+
6
+ export type ConnectionPointPosition = 'top' | 'bottom' | 'left' | 'right';
7
+
8
+ export interface ConnectionPointConfig {
9
+ position: ConnectionPointPosition;
10
+ type: 'source' | 'target' | 'both';
11
+ offset?: number;
12
+ }
13
+
14
+ interface ConnectionPointsProps {
15
+ /** Show handles regardless of node selection */
16
+ alwaysVisible?: boolean;
17
+ /** Custom positions configuration */
18
+ positions?: ConnectionPointConfig[];
19
+ /** Custom handle size */
20
+ handleSize?: number;
21
+ /** Custom handle color */
22
+ handleColor?: string;
23
+ /** Custom background color */
24
+ backgroundColor?: string;
25
+ }
26
+
27
+ const positionMap: Record<ConnectionPointPosition, Position> = {
28
+ top: Position.Top,
29
+ bottom: Position.Bottom,
30
+ left: Position.Left,
31
+ right: Position.Right,
32
+ };
33
+
34
+ const defaultPositions: ConnectionPointConfig[] = [
35
+ { position: 'top', type: 'target' },
36
+ { position: 'bottom', type: 'source' },
37
+ { position: 'left', type: 'target' },
38
+ { position: 'right', type: 'source' },
39
+ ];
40
+
41
+ /**
42
+ * ConnectionPoints component displays interactive connection handles
43
+ * on a node when it's selected. Users can drag from these points to
44
+ * create edges connecting to other nodes.
45
+ */
46
+ export const ConnectionPoints: React.FC<ConnectionPointsProps> = React.memo(({
47
+ alwaysVisible = false,
48
+ positions = defaultPositions,
49
+ handleSize = 14,
50
+ handleColor = '#10B981',
51
+ backgroundColor = '#1a1a2e',
52
+ }) => {
53
+ const nodeId = useNodeId();
54
+ const selectedNode = useSelectedNode();
55
+
56
+ const isSelected = selectedNode === nodeId;
57
+ const shouldShow = alwaysVisible || isSelected;
58
+
59
+ const handleStyle = useMemo(() => ({
60
+ width: `${handleSize}px`,
61
+ height: `${handleSize}px`,
62
+ borderRadius: '50%',
63
+ border: `3px solid ${handleColor}`,
64
+ backgroundColor: backgroundColor,
65
+ transition: 'all 0.2s ease-in-out',
66
+ cursor: 'crosshair',
67
+ zIndex: 10,
68
+ }), [handleSize, handleColor, backgroundColor]);
69
+
70
+ const getPositionStyle = (position: ConnectionPointPosition): React.CSSProperties => {
71
+ const offset = -Math.floor(handleSize / 2);
72
+ switch (position) {
73
+ case 'top': return { top: `${offset}px` };
74
+ case 'bottom': return { bottom: `${offset}px` };
75
+ case 'left': return { left: `${offset}px` };
76
+ case 'right': return { right: `${offset}px` };
77
+ }
78
+ };
79
+
80
+ const renderHandle = (config: ConnectionPointConfig, index: number) => {
81
+ const { position, type, offset } = config;
82
+ const baseId = `${nodeId}-${position}`;
83
+
84
+ if (type === 'both') {
85
+ // Render both source and target handles at the same position
86
+ return (
87
+ <React.Fragment key={`${position}-${index}`}>
88
+ <Handle
89
+ type="source"
90
+ id={`${baseId}-source`}
91
+ position={positionMap[position]}
92
+ className="connection-handle"
93
+ style={{
94
+ ...handleStyle,
95
+ ...getPositionStyle(position),
96
+ }}
97
+ />
98
+ <Handle
99
+ type="target"
100
+ id={`${baseId}-target`}
101
+ position={positionMap[position]}
102
+ className="connection-handle"
103
+ style={{
104
+ ...handleStyle,
105
+ ...getPositionStyle(position),
106
+ opacity: 0, // Hidden but functional
107
+ pointerEvents: shouldShow ? 'auto' : 'none',
108
+ }}
109
+ />
110
+ </React.Fragment>
111
+ );
112
+ }
113
+
114
+ return (
115
+ <Handle
116
+ key={`${position}-${type}-${index}`}
117
+ type={type}
118
+ id={baseId}
119
+ position={positionMap[position]}
120
+ className="connection-handle"
121
+ style={{
122
+ ...handleStyle,
123
+ ...getPositionStyle(position),
124
+ }}
125
+ />
126
+ );
127
+ };
128
+
129
+ return (
130
+ <Box
131
+ className="connection-points-container"
132
+ sx={{
133
+ opacity: shouldShow ? 1 : 0,
134
+ transition: 'opacity 0.2s ease-in-out',
135
+ pointerEvents: shouldShow ? 'auto' : 'none',
136
+ '& .react-flow__handle': {
137
+ visibility: shouldShow ? 'visible' : 'hidden',
138
+ },
139
+ }}
140
+ >
141
+ {positions.map((config, index) => renderHandle(config, index))}
142
+ </Box>
143
+ );
144
+ });
145
+
146
+ ConnectionPoints.displayName = 'ConnectionPoints';
147
+
148
+ export default ConnectionPoints;
149
+
@@ -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 AutomationApiNodeProps {
24
25
  data: {
@@ -66,6 +67,9 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
66
67
  const nodeId = useNodeId();
67
68
  const setSelectedNode = useDiagram((state) => state.setSelectedNode);
68
69
  const enableJson = useDiagram((state) => state.enableNodeJsonPopover ?? true);
70
+ const onNodesChange = useDiagram((state) => state.onNodesChange);
71
+ const nodes = useDiagram((state) => state.nodes);
72
+ const setNodes = useDiagram((state) => state.setNodes);
69
73
 
70
74
  // Get the icon component based on the iconName
71
75
  const IconComponent = getIconByName(data.iconName);
@@ -525,35 +529,169 @@ export const AutomationApiNode: React.FC<AutomationApiNodeProps> = ({ data, sele
525
529
  </Box>
526
530
  </Box>
527
531
 
528
- {/* Handles - Hidden but functional */}
532
+ {/* Connection Handles - Bidirectional (source + target at each position) */}
533
+ {/* Top - Source */}
534
+ <Handle
535
+ type="source"
536
+ position={Position.Top}
537
+ id="top-source"
538
+ className="connection-handle"
539
+ style={{
540
+ background: selected ? '#10B981' : '#1a1a2e',
541
+ width: '14px',
542
+ height: '14px',
543
+ border: '3px solid #10B981',
544
+ top: '-8px',
545
+ opacity: selected ? 1 : 0,
546
+ transition: 'all 0.2s ease-in-out',
547
+ cursor: 'crosshair',
548
+ zIndex: 10,
549
+ }}
550
+ />
551
+ {/* Top - Target (hidden but functional) */}
552
+ <Handle
553
+ type="target"
554
+ position={Position.Top}
555
+ id="top-target"
556
+ style={{
557
+ background: 'transparent',
558
+ width: '14px',
559
+ height: '14px',
560
+ border: 'none',
561
+ top: '-8px',
562
+ opacity: 0,
563
+ pointerEvents: selected ? 'all' : 'none',
564
+ }}
565
+ />
566
+ {/* Bottom - Source */}
567
+ <Handle
568
+ type="source"
569
+ position={Position.Bottom}
570
+ id="bottom-source"
571
+ className="connection-handle"
572
+ style={{
573
+ background: selected ? '#10B981' : '#1a1a2e',
574
+ width: '14px',
575
+ height: '14px',
576
+ border: '3px solid #10B981',
577
+ bottom: '-8px',
578
+ opacity: selected ? 1 : 0,
579
+ transition: 'all 0.2s ease-in-out',
580
+ cursor: 'crosshair',
581
+ zIndex: 10,
582
+ }}
583
+ />
584
+ {/* Bottom - Target (hidden but functional) */}
585
+ <Handle
586
+ type="target"
587
+ position={Position.Bottom}
588
+ id="bottom-target"
589
+ style={{
590
+ background: 'transparent',
591
+ width: '14px',
592
+ height: '14px',
593
+ border: 'none',
594
+ bottom: '-8px',
595
+ opacity: 0,
596
+ pointerEvents: selected ? 'all' : 'none',
597
+ }}
598
+ />
599
+ {/* Left - Source */}
600
+ <Handle
601
+ type="source"
602
+ position={Position.Left}
603
+ id="left-source"
604
+ className="connection-handle"
605
+ style={{
606
+ background: selected ? '#10B981' : '#1a1a2e',
607
+ width: '14px',
608
+ height: '14px',
609
+ border: '3px solid #10B981',
610
+ left: '-8px',
611
+ opacity: selected ? 1 : 0,
612
+ transition: 'all 0.2s ease-in-out',
613
+ cursor: 'crosshair',
614
+ zIndex: 10,
615
+ }}
616
+ />
617
+ {/* Left - Target (hidden but functional) */}
529
618
  <Handle
530
619
  type="target"
531
620
  position={Position.Left}
532
- id="left"
621
+ id="left-target"
533
622
  style={{
534
- background: '#3b82f6',
535
- width: '12px',
536
- height: '12px',
537
- border: '2px solid white',
623
+ background: 'transparent',
624
+ width: '14px',
625
+ height: '14px',
626
+ border: 'none',
538
627
  left: '-8px',
539
- opacity: 0, // Hidden but functional
628
+ opacity: 0,
629
+ pointerEvents: selected ? 'all' : 'none',
540
630
  }}
541
631
  />
632
+ {/* Right - Source */}
542
633
  <Handle
543
634
  type="source"
544
635
  position={Position.Right}
545
- id="right"
636
+ id="right-source"
637
+ className="connection-handle"
638
+ style={{
639
+ background: selected ? '#10B981' : '#1a1a2e',
640
+ width: '14px',
641
+ height: '14px',
642
+ border: '3px solid #10B981',
643
+ right: '-8px',
644
+ opacity: selected ? 1 : 0,
645
+ transition: 'all 0.2s ease-in-out',
646
+ cursor: 'crosshair',
647
+ zIndex: 10,
648
+ }}
649
+ />
650
+ {/* Right - Target (hidden but functional) */}
651
+ <Handle
652
+ type="target"
653
+ position={Position.Right}
654
+ id="right-target"
546
655
  style={{
547
- background: '#3b82f6',
548
- width: '12px',
549
- height: '12px',
550
- border: '2px solid white',
656
+ background: 'transparent',
657
+ width: '14px',
658
+ height: '14px',
659
+ border: 'none',
551
660
  right: '-8px',
552
- opacity: 0, // Hidden but functional
661
+ opacity: 0,
662
+ pointerEvents: selected ? 'all' : 'none',
553
663
  }}
554
664
  />
665
+
555
666
  </Box>
556
667
 
668
+ {/* Node Action Buttons - Shows when selected */}
669
+ <NodeActionButtons
670
+ selected={selected}
671
+ onDelete={() => {
672
+ if (nodeId && onNodesChange) {
673
+ onNodesChange([{ id: nodeId, type: 'remove' }]);
674
+ }
675
+ }}
676
+ onDuplicate={() => {
677
+ if (nodeId) {
678
+ const currentNode = nodes.find(n => n.id === nodeId);
679
+ if (currentNode) {
680
+ const newNode = {
681
+ ...currentNode,
682
+ id: `${currentNode.id}-copy-${Date.now()}`,
683
+ position: {
684
+ x: currentNode.position.x + 50,
685
+ y: currentNode.position.y + 50,
686
+ },
687
+ selected: false,
688
+ };
689
+ setNodes([...nodes, newNode]);
690
+ }
691
+ }
692
+ }}
693
+ />
694
+
557
695
  {/* AI Suggestions Button - Positioned below the node box */}
558
696
  {data.formData?.aiSuggestionsCount !== undefined && data.formData.aiSuggestionsCount > 0 && (
559
697
  <Box