@flowuent-org/diagramming-core 1.1.2 → 1.1.3

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowuent-org/diagramming-core",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,16 +18,56 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
18
18
  onClose,
19
19
  }) => {
20
20
  const { getNodes, setNodes, setEdges, getNode } = useReactFlow();
21
+ const isAddingRef = React.useRef(false); // Prevent multiple simultaneous additions
22
+
23
+ const handleSuggestionClick = (suggestion: AISuggestion, event?: React.MouseEvent) => {
24
+ // Prevent event propagation
25
+ if (event) {
26
+ event.stopPropagation();
27
+ event.preventDefault();
28
+ }
29
+
30
+ // Prevent multiple rapid clicks
31
+ if (isAddingRef.current) {
32
+ return;
33
+ }
34
+ isAddingRef.current = true;
35
+
36
+ // Close the panel immediately to prevent multiple clicks
37
+ if (onClose) {
38
+ onClose();
39
+ }
21
40
 
22
- const handleSuggestionClick = (suggestion: AISuggestion) => {
23
41
  const nodes = getNodes();
24
42
  const parentNode = getNode(parentNodeId);
25
43
  if (!parentNode) return;
26
44
 
27
- // Calculate position for new node - to the right of the parent node
28
- // Node width + spacing
29
- const nodeWidth = parentNode.width || 336;
30
- const newNodeX = parentNode.position.x + nodeWidth + 50;
45
+ // Find the rightmost node in the workflow by X position
46
+ // This ensures suggestion nodes are added horizontally after the last node
47
+ // We'll find the node with the highest X position + width
48
+ const findRightmostNode = (): Node => {
49
+ // Get all nodes and find the one with the rightmost position
50
+ const allNodes = getNodes();
51
+ let rightmostNode = parentNode;
52
+ let maxRightEdge = parentNode.position.x + (parentNode.width || 336);
53
+
54
+ allNodes.forEach((node) => {
55
+ const nodeRightEdge = node.position.x + (node.width || 336);
56
+ if (nodeRightEdge > maxRightEdge) {
57
+ maxRightEdge = nodeRightEdge;
58
+ rightmostNode = node;
59
+ }
60
+ });
61
+
62
+ return rightmostNode;
63
+ };
64
+
65
+ const rightmostNode = findRightmostNode();
66
+
67
+ // Calculate position for new node - to the right of the rightmost node
68
+ const nodeWidth = rightmostNode.width || 336;
69
+ const spacing = 50; // Spacing between nodes
70
+ const newNodeX = rightmostNode.position.x + nodeWidth + spacing;
31
71
 
32
72
  // Create new AI Suggestion node
33
73
  const newNodeId = `ai-suggestion-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -71,10 +111,27 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
71
111
  measured: { width: 336, height: 150 },
72
112
  };
73
113
 
114
+ // Check if a suggestion node with the same title already exists to prevent duplicates
115
+ const existingSuggestionNode = nodes.find(
116
+ (n) => n.type === 'AutomationAISuggestionNode' &&
117
+ n.data?.label === suggestion.title &&
118
+ !(n.data?.formData as any)?.isConfirmed
119
+ );
120
+
121
+ if (existingSuggestionNode) {
122
+ // If a similar unconfirmed suggestion already exists, don't add another
123
+ isAddingRef.current = false;
124
+ if (onClose) {
125
+ onClose();
126
+ }
127
+ return;
128
+ }
129
+
74
130
  // Add the new node
75
- setNodes([...nodes, newNode]);
131
+ setNodes((prevNodes) => [...prevNodes, newNode]);
76
132
 
77
- // Create edge from parent to new node with dotted line (unconfirmed)
133
+ // Create edge from parent node to new node with dotted line (unconfirmed)
134
+ // The suggestion is for the parent node, but the new node is positioned after the rightmost node
78
135
  const edgeId = `edge-${parentNodeId}-${newNodeId}`;
79
136
  const newEdge: Edge = {
80
137
  id: edgeId,
@@ -108,10 +165,10 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
108
165
  onSuggestionClick(suggestion);
109
166
  }
110
167
 
111
- // Close the panel
112
- if (onClose) {
113
- onClose();
114
- }
168
+ // Reset the flag after a delay to allow the layout to settle
169
+ setTimeout(() => {
170
+ isAddingRef.current = false;
171
+ }, 1000);
115
172
  };
116
173
 
117
174
  return (
@@ -154,7 +211,7 @@ export const AISuggestionsPanel: React.FC<AISuggestionsPanelProps> = ({
154
211
  {/* Suggestions List */}
155
212
  <Box sx={{ }}>
156
213
  {suggestions.map((suggestion) => (
157
- <Card key={suggestion.id} onClick={() => handleSuggestionClick(suggestion)} sx={{ mb: 1, bgcolor: '#2563EB1A', border: '1px solid #2563EB', borderRadius: '8px', cursor: 'pointer', transition: 'all 0.2s ease', '&:last-child': { mb: 0, } }} >
214
+ <Card key={suggestion.id} onClick={(e) => handleSuggestionClick(suggestion, e)} sx={{ mb: 1, bgcolor: '#2563EB1A', border: '1px solid #2563EB', borderRadius: '8px', cursor: 'pointer', transition: 'all 0.2s ease', '&:last-child': { mb: 0, } }} >
158
215
  <CardContent>
159
216
  <Box sx={{ display: 'flex' }}>
160
217
 
@@ -115,6 +115,7 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
115
115
  const { getLayoutedElements } = useElk();
116
116
  const layoutDirection = useLayoutDirection();
117
117
  const setLayoutDirection = useSetLayoutDirection();
118
+ const layoutTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
118
119
 
119
120
  // Set layout direction to RIGHT for automation diagrams
120
121
  useEffect(() => {
@@ -123,10 +124,23 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
123
124
  }
124
125
  }, [diagramType, layoutDirection, setLayoutDirection]);
125
126
 
127
+ // Debounce layout recalculation to prevent continuous propagation
126
128
  useEffect(() => {
127
- getLayoutedElements();
129
+ // Clear any pending layout recalculation
130
+ if (layoutTimeoutRef.current) {
131
+ clearTimeout(layoutTimeoutRef.current);
132
+ }
133
+
134
+ // Debounce the layout recalculation
135
+ layoutTimeoutRef.current = setTimeout(() => {
136
+ getLayoutedElements();
137
+ }, 300); // 300ms debounce delay
128
138
 
129
- return () => { };
139
+ return () => {
140
+ if (layoutTimeoutRef.current) {
141
+ clearTimeout(layoutTimeoutRef.current);
142
+ }
143
+ };
130
144
  }, [nodes.length, edges.length, contentHeights, diagramType, layoutDirection, getLayoutedElements]);
131
145
 
132
146
  // Listen for layout direction changes