@flowuent-org/diagramming-core 1.2.0 → 1.2.2

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.
@@ -0,0 +1,78 @@
1
+ import React, { createContext, useContext, useState, ReactNode } from 'react';
2
+
3
+ interface SearchContextType {
4
+ searchQuery: string;
5
+ setSearchQuery: (query: string) => void;
6
+ highlightText: (text: string) => React.ReactNode;
7
+ }
8
+
9
+ const SearchContext = createContext<SearchContextType | undefined>(undefined);
10
+
11
+ export const SearchProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
12
+ const [searchQuery, setSearchQuery] = useState('');
13
+
14
+ const highlightText = (text: string): React.ReactNode => {
15
+ if (!searchQuery.trim() || !text) {
16
+ return text;
17
+ }
18
+
19
+ const searchLower = searchQuery.toLowerCase();
20
+ const textLower = String(text).toLowerCase();
21
+ const parts: React.ReactNode[] = [];
22
+ let lastIndex = 0;
23
+ let index = textLower.indexOf(searchLower, lastIndex);
24
+
25
+ // Highlight all occurrences
26
+ while (index !== -1) {
27
+ // Add text before match
28
+ if (index > lastIndex) {
29
+ parts.push(String(text).substring(lastIndex, index));
30
+ }
31
+
32
+ // Add highlighted match
33
+ parts.push(
34
+ <mark
35
+ key={index}
36
+ style={{
37
+ backgroundColor: '#FFD700',
38
+ color: '#000',
39
+ padding: '2px 4px',
40
+ borderRadius: '3px',
41
+ fontWeight: 'bold',
42
+ }}
43
+ >
44
+ {String(text).substring(index, index + searchQuery.length)}
45
+ </mark>
46
+ );
47
+
48
+ lastIndex = index + searchQuery.length;
49
+ index = textLower.indexOf(searchLower, lastIndex);
50
+ }
51
+
52
+ // Add remaining text
53
+ if (lastIndex < String(text).length) {
54
+ parts.push(String(text).substring(lastIndex));
55
+ }
56
+
57
+ return parts.length > 0 ? <>{parts}</> : text;
58
+ };
59
+
60
+ return (
61
+ <SearchContext.Provider value={{ searchQuery, setSearchQuery, highlightText }}>
62
+ {children}
63
+ </SearchContext.Provider>
64
+ );
65
+ };
66
+
67
+ export const useSearch = () => {
68
+ const context = useContext(SearchContext);
69
+ if (!context) {
70
+ return {
71
+ searchQuery: '',
72
+ setSearchQuery: () => {},
73
+ highlightText: (text: string) => text,
74
+ };
75
+ }
76
+ return context;
77
+ };
78
+
@@ -1,76 +1,79 @@
1
- import React from 'react';
2
- import { ReactFlowProps, ReactFlowProvider } from '@xyflow/react';
3
- import '@xyflow/react/dist/style.css';
4
- import { DiagramContent } from './DiagramContent';
5
-
6
- interface DiagramContainerProps
7
- extends Omit<ReactFlowProps, 'nodes' | 'edges' | 'onChange'> {
8
- showAutomationExecutionPanel?: boolean;
9
- }
10
-
11
- // Default arrow marker definitions for edges
12
- const EdgeMarkerDefinitions = () => (
13
- <svg style={{ position: 'absolute', width: 0, height: 0 }}>
14
- <defs>
15
- {/* Arrow marker - Green */}
16
- <marker
17
- id="arrow"
18
- viewBox="0 0 10 10"
19
- refX="8"
20
- refY="5"
21
- markerWidth="6"
22
- markerHeight="6"
23
- orient="auto-start-reverse"
24
- >
25
- <path d="M 0 0 L 10 5 L 0 10 z" fill="#10B981" />
26
- </marker>
27
-
28
- {/* Arrow marker - Blue */}
29
- <marker
30
- id="arrow-blue"
31
- viewBox="0 0 10 10"
32
- refX="8"
33
- refY="5"
34
- markerWidth="6"
35
- markerHeight="6"
36
- orient="auto-start-reverse"
37
- >
38
- <path d="M 0 0 L 10 5 L 0 10 z" fill="#4858E9" />
39
- </marker>
40
-
41
- {/* Arrow marker - White */}
42
- <marker
43
- id="arrow-white"
44
- viewBox="0 0 10 10"
45
- refX="8"
46
- refY="5"
47
- markerWidth="6"
48
- markerHeight="6"
49
- orient="auto-start-reverse"
50
- >
51
- <path d="M 0 0 L 10 5 L 0 10 z" fill="#ffffff" />
52
- </marker>
53
-
54
- {/* Circle marker */}
55
- <marker
56
- id="circle"
57
- viewBox="0 0 10 10"
58
- refX="5"
59
- refY="5"
60
- markerWidth="5"
61
- markerHeight="5"
62
- >
63
- <circle cx="5" cy="5" r="4" fill="#10B981" />
64
- </marker>
65
- </defs>
66
- </svg>
67
- );
68
-
69
- export const DiagramContainer: React.FC<DiagramContainerProps> = (props) => {
70
- return (
71
- <ReactFlowProvider>
72
- <EdgeMarkerDefinitions />
73
- <DiagramContent {...props} />
74
- </ReactFlowProvider>
75
- );
76
- };
1
+ import React from 'react';
2
+ import { ReactFlowProps, ReactFlowProvider } from '@xyflow/react';
3
+ import '@xyflow/react/dist/style.css';
4
+ import { DiagramContent } from './DiagramContent';
5
+ import { SearchProvider } from '../contexts/SearchContext';
6
+
7
+ interface DiagramContainerProps
8
+ extends Omit<ReactFlowProps, 'nodes' | 'edges' | 'onChange'> {
9
+ showAutomationExecutionPanel?: boolean;
10
+ }
11
+
12
+ // Default arrow marker definitions for edges
13
+ const EdgeMarkerDefinitions = () => (
14
+ <svg style={{ position: 'absolute', width: 0, height: 0 }}>
15
+ <defs>
16
+ {/* Arrow marker - Green */}
17
+ <marker
18
+ id="arrow"
19
+ viewBox="0 0 10 10"
20
+ refX="8"
21
+ refY="5"
22
+ markerWidth="6"
23
+ markerHeight="6"
24
+ orient="auto-start-reverse"
25
+ >
26
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#10B981" />
27
+ </marker>
28
+
29
+ {/* Arrow marker - Blue */}
30
+ <marker
31
+ id="arrow-blue"
32
+ viewBox="0 0 10 10"
33
+ refX="8"
34
+ refY="5"
35
+ markerWidth="6"
36
+ markerHeight="6"
37
+ orient="auto-start-reverse"
38
+ >
39
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#4858E9" />
40
+ </marker>
41
+
42
+ {/* Arrow marker - White */}
43
+ <marker
44
+ id="arrow-white"
45
+ viewBox="0 0 10 10"
46
+ refX="8"
47
+ refY="5"
48
+ markerWidth="6"
49
+ markerHeight="6"
50
+ orient="auto-start-reverse"
51
+ >
52
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#ffffff" />
53
+ </marker>
54
+
55
+ {/* Circle marker */}
56
+ <marker
57
+ id="circle"
58
+ viewBox="0 0 10 10"
59
+ refX="5"
60
+ refY="5"
61
+ markerWidth="5"
62
+ markerHeight="5"
63
+ >
64
+ <circle cx="5" cy="5" r="4" fill="#10B981" />
65
+ </marker>
66
+ </defs>
67
+ </svg>
68
+ );
69
+
70
+ export const DiagramContainer: React.FC<DiagramContainerProps> = (props) => {
71
+ return (
72
+ <ReactFlowProvider>
73
+ <SearchProvider>
74
+ <EdgeMarkerDefinitions />
75
+ <DiagramContent {...props} />
76
+ </SearchProvider>
77
+ </ReactFlowProvider>
78
+ );
79
+ };
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useState, useMemo, useCallback } from 'react';
2
- import { ConnectionMode, ReactFlow, ReactFlowProps, NodeChange, applyNodeChanges } from '@xyflow/react';
1
+ import React, { useEffect, useState, useMemo, useCallback, useRef } from 'react';
2
+ import { ConnectionMode, ReactFlow, ReactFlowProps, NodeChange, applyNodeChanges, useReactFlow, Node } from '@xyflow/react';
3
3
  import { useTriggerEvent } from '../utils/event-hooks';
4
4
  import { IBlock, ICardNodeData } from '../types/card-node';
5
5
  import {
@@ -45,6 +45,7 @@ import CollaborationDiagram from './collaborationDiagram/CollaborationDiagram';
45
45
  import Header from '../components/Header';
46
46
  import { PaneMouseHandler } from '../utils/event-store';
47
47
  import { AutomationExecutionPanel } from '../components/automation/AutomationExecutionPanel';
48
+ import { CanvasSearchBar } from '../components/CanvasSearchBar';
48
49
 
49
50
  interface DiagramContentProps
50
51
  extends Omit<ReactFlowProps, 'nodes' | 'edges' | 'onChange'> {
@@ -85,7 +86,117 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
85
86
  const canUndo = useCanUndo();
86
87
  const canRedo = useCanRedo();
87
88
 
88
- // Handle keyboard shortcuts for undo/redo
89
+ // Zoom hooks - get zoom functions from ReactFlow
90
+ const { zoomIn, zoomOut, fitView, setViewport, getViewport, getNodes } = useReactFlow();
91
+
92
+ // Search bar state
93
+ const [showSearchBar, setShowSearchBar] = useState(false);
94
+
95
+ // Clipboard state for copy/paste
96
+ const [copiedNode, setCopiedNode] = useState<Node | null>(null);
97
+
98
+ // Rename state
99
+ const [renamingNodeId, setRenamingNodeId] = useState<string | null>(null);
100
+ const [renameValue, setRenameValue] = useState('');
101
+
102
+ // Filter out hidden nodes for ReactFlow rendering
103
+ const visibleNodes = useMemo(() => {
104
+ return nodes.filter(node => !node.data?.isHidden);
105
+ }, [nodes]);
106
+
107
+ // Search canvas functionality - opens search bar
108
+ const handleSearchCanvas = useCallback(() => {
109
+ setShowSearchBar(true);
110
+ }, []);
111
+
112
+ // Copy selected node
113
+ const handleCopyNode = useCallback(() => {
114
+ const nodeToCopy = visibleNodes.find(node => node.id === selectedNode || node.selected);
115
+ if (nodeToCopy) {
116
+ setCopiedNode(nodeToCopy);
117
+ }
118
+ }, [visibleNodes, selectedNode]);
119
+
120
+ // Paste node
121
+ const handlePasteNode = useCallback(() => {
122
+ if (!copiedNode) return;
123
+
124
+ const newNode = {
125
+ ...copiedNode,
126
+ id: `${copiedNode.id}-copy-${Date.now()}`,
127
+ position: {
128
+ x: copiedNode.position.x + 50,
129
+ y: copiedNode.position.y + 50,
130
+ },
131
+ selected: false,
132
+ };
133
+
134
+ setNodes([...nodes, newNode as any]);
135
+ setSelectedNode(newNode.id);
136
+ }, [copiedNode, nodes, setNodes, setSelectedNode]);
137
+
138
+ // Delete selected node
139
+ const handleDeleteNode = useCallback(() => {
140
+ const nodeToDelete = visibleNodes.find(node => node.id === selectedNode || node.selected);
141
+ if (nodeToDelete && onNodesChange) {
142
+ onNodesChange([{ id: nodeToDelete.id, type: 'remove' }]);
143
+ setSelectedNode(null);
144
+ }
145
+ }, [visibleNodes, selectedNode, onNodesChange, setSelectedNode]);
146
+
147
+ // Rename node
148
+ const handleRenameNode = useCallback(() => {
149
+ const nodeToRename = visibleNodes.find(node => node.id === selectedNode || node.selected);
150
+ if (nodeToRename) {
151
+ setRenamingNodeId(nodeToRename.id);
152
+ setRenameValue((nodeToRename.data as any)?.label || nodeToRename.id);
153
+ }
154
+ }, [visibleNodes, selectedNode]);
155
+
156
+ // Save rename
157
+ const handleSaveRename = useCallback(() => {
158
+ if (!renamingNodeId || !renameValue.trim()) {
159
+ setRenamingNodeId(null);
160
+ return;
161
+ }
162
+
163
+ const updatedNodes = nodes.map(node => {
164
+ if (node.id === renamingNodeId) {
165
+ return {
166
+ ...node,
167
+ data: {
168
+ ...node.data,
169
+ label: renameValue.trim(),
170
+ },
171
+ };
172
+ }
173
+ return node;
174
+ });
175
+
176
+ setNodes(updatedNodes);
177
+ setRenamingNodeId(null);
178
+ setRenameValue('');
179
+ }, [renamingNodeId, renameValue, nodes, setNodes]);
180
+
181
+ // Cancel rename
182
+ const handleCancelRename = useCallback(() => {
183
+ setRenamingNodeId(null);
184
+ setRenameValue('');
185
+ }, []);
186
+
187
+ // Edit node (open node configuration/form)
188
+ const handleEditNode = useCallback(() => {
189
+ const nodeToEdit = visibleNodes.find(node => node.id === selectedNode || node.selected);
190
+ if (nodeToEdit) {
191
+ // For automation nodes, we can trigger a double-click or open a form
192
+ // This will depend on the specific node type implementation
193
+ // For now, we'll just ensure the node is selected and could trigger a modal
194
+ setSelectedNode(nodeToEdit.id);
195
+ // You can add specific edit logic here based on node type
196
+ }
197
+ }, [visibleNodes, selectedNode, setSelectedNode]);
198
+
199
+ // Handle keyboard shortcuts for undo/redo and zoom
89
200
  useEffect(() => {
90
201
  const handleKeyDown = (event: KeyboardEvent) => {
91
202
  // Don't trigger if user is typing in an input field
@@ -115,18 +226,116 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
115
226
  redo();
116
227
  }
117
228
  }
229
+
230
+ // + or = for Zoom In (handles both + key and = key with shift)
231
+ if (event.key === '+' || event.key === '=') {
232
+ event.preventDefault();
233
+ zoomIn();
234
+ }
235
+
236
+ // - for Zoom Out
237
+ if (event.key === '-') {
238
+ event.preventDefault();
239
+ zoomOut();
240
+ }
241
+
242
+ // 0 for Reset Zoom (reset to zoom level 1)
243
+ if (event.key === '0' && !event.ctrlKey && !event.metaKey && !event.altKey) {
244
+ event.preventDefault();
245
+ const viewport = getViewport();
246
+ setViewport({ x: viewport.x, y: viewport.y, zoom: 1 });
247
+ }
248
+
249
+ // 1 for Fit to View
250
+ if (event.key === '1' && !event.ctrlKey && !event.metaKey && !event.altKey) {
251
+ event.preventDefault();
252
+ fitView();
253
+ }
254
+
255
+ // Ctrl+G or Cmd+G for Search Canvas (toggle search bar)
256
+ if (
257
+ (event.ctrlKey || event.metaKey) &&
258
+ event.key === 'g' &&
259
+ !event.shiftKey &&
260
+ !event.altKey
261
+ ) {
262
+ event.preventDefault();
263
+ if (showSearchBar) {
264
+ // Close search bar if already open
265
+ setShowSearchBar(false);
266
+ } else {
267
+ // Open search bar
268
+ handleSearchCanvas();
269
+ }
270
+ }
271
+
272
+ // Ctrl+C or Cmd+C for Copy selected node
273
+ if (
274
+ (event.ctrlKey || event.metaKey) &&
275
+ event.key === 'c' &&
276
+ !event.shiftKey &&
277
+ !event.altKey
278
+ ) {
279
+ event.preventDefault();
280
+ handleCopyNode();
281
+ }
282
+
283
+ // Ctrl+V or Cmd+V for Paste node
284
+ if (
285
+ (event.ctrlKey || event.metaKey) &&
286
+ event.key === 'v' &&
287
+ !event.shiftKey &&
288
+ !event.altKey
289
+ ) {
290
+ event.preventDefault();
291
+ handlePasteNode();
292
+ }
293
+
294
+ // Delete key for Delete selected node
295
+ if (event.key === 'Delete' || event.key === 'Backspace') {
296
+ if (selectedNode || visibleNodes.some(node => node.selected)) {
297
+ event.preventDefault();
298
+ handleDeleteNode();
299
+ }
300
+ }
301
+
302
+ // F2 for Rename node
303
+ if (event.key === 'F2') {
304
+ event.preventDefault();
305
+ handleRenameNode();
306
+ }
307
+
308
+ // Enter for Edit node
309
+ if (event.key === 'Enter' && !event.shiftKey) {
310
+ if (selectedNode || visibleNodes.some(node => node.selected)) {
311
+ event.preventDefault();
312
+ handleEditNode();
313
+ }
314
+ }
315
+
316
+ // Escape to cancel rename
317
+ if (event.key === 'Escape' && renamingNodeId) {
318
+ event.preventDefault();
319
+ handleCancelRename();
320
+ }
321
+
322
+ // Enter to save rename
323
+ if (event.key === 'Enter' && renamingNodeId) {
324
+ event.preventDefault();
325
+ handleSaveRename();
326
+ }
118
327
  };
119
328
 
120
329
  document.addEventListener('keydown', handleKeyDown);
121
330
  return () => {
122
331
  document.removeEventListener('keydown', handleKeyDown);
123
332
  };
124
- }, [undo, redo, canUndo, canRedo]);
125
-
126
- // Filter out hidden nodes for ReactFlow rendering
127
- const visibleNodes = useMemo(() => {
128
- return nodes.filter(node => !node.data?.isHidden);
129
- }, [nodes]);
333
+ }, [
334
+ undo, redo, canUndo, canRedo, zoomIn, zoomOut, fitView, setViewport, getViewport,
335
+ handleSearchCanvas, showSearchBar, handleCopyNode, handlePasteNode, handleDeleteNode,
336
+ handleRenameNode, handleEditNode, handleSaveRename, handleCancelRename,
337
+ selectedNode, visibleNodes, renamingNodeId
338
+ ]);
130
339
 
131
340
  // Filter out edges connected to hidden nodes
132
341
  const visibleEdges = useMemo(() => {
@@ -214,58 +423,6 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
214
423
  setActiveCard,
215
424
  });
216
425
 
217
- // Handle keyboard deletion of selected nodes (only for automation diagram)
218
- useEffect(() => {
219
- if (diagramType !== 'automation') return;
220
-
221
- const handleKeyDown = (event: KeyboardEvent) => {
222
- // Check if Delete or Backspace key is pressed
223
- if (event.key === 'Delete' || event.key === 'Backspace') {
224
- // Don't trigger deletion if user is typing in an input field
225
- if (
226
- event.target instanceof HTMLInputElement ||
227
- event.target instanceof HTMLTextAreaElement ||
228
- (event.target as any)?.isContentEditable
229
- ) {
230
- return;
231
- }
232
-
233
- // Check if a node is selected (either from store or ReactFlow's selection)
234
- const selectedNodeId = selectedNode || visibleNodes.find(node => node.selected)?.id;
235
-
236
- if (selectedNodeId) {
237
- event.preventDefault();
238
- event.stopPropagation();
239
-
240
- // Find the node to delete
241
- const nodeToDelete = nodes.find((node) => node.id === selectedNodeId);
242
- if (!nodeToDelete) return;
243
-
244
- // Create a remove change for the node
245
- const nodeChanges: NodeChange[] = [
246
- {
247
- id: selectedNodeId,
248
- type: 'remove',
249
- },
250
- ];
251
-
252
- // Apply the changes through onNodesChange (which handles edge cleanup)
253
- onNodesChange(nodeChanges);
254
-
255
- // Clear the selected node
256
- setSelectedNode(null);
257
- }
258
- }
259
- };
260
-
261
- // Add event listener
262
- document.addEventListener('keydown', handleKeyDown);
263
-
264
- // Cleanup on unmount
265
- return () => {
266
- document.removeEventListener('keydown', handleKeyDown);
267
- };
268
- }, [diagramType, selectedNode, nodes, visibleNodes, onNodesChange, setSelectedNode]);
269
426
 
270
427
  const renderDynamicDiagram = () => {
271
428
  if (diagramType === 'sequence') {
@@ -540,6 +697,64 @@ export const DiagramContent: React.FC<DiagramContentProps> = ({
540
697
  onDragCancel={onDragCancel}
541
698
  sensors={sensors}
542
699
  >
700
+ {/* Canvas Search Bar - Available for all diagram types */}
701
+ {showSearchBar && (
702
+ <CanvasSearchBar
703
+ nodes={visibleNodes}
704
+ onClose={() => setShowSearchBar(false)}
705
+ onNodeSelect={setSelectedNode}
706
+ onViewportChange={(nodeId) => {
707
+ // Viewport change is handled inside CanvasSearchBar
708
+ }}
709
+ getViewport={getViewport}
710
+ setViewport={setViewport}
711
+ />
712
+ )}
713
+
714
+ {/* Rename Input - Appears when F2 is pressed */}
715
+ {renamingNodeId && (() => {
716
+ const nodeToRename = visibleNodes.find(n => n.id === renamingNodeId);
717
+ if (!nodeToRename) return null;
718
+ const viewport = getViewport();
719
+ return (
720
+ <Box
721
+ sx={{
722
+ position: 'fixed',
723
+ top: `${nodeToRename.position.y * viewport.zoom + viewport.y + 20}px`,
724
+ left: `${nodeToRename.position.x * viewport.zoom + viewport.x + 20}px`,
725
+ zIndex: 1000,
726
+ backgroundColor: '#1e293b',
727
+ border: '2px solid #3b82f6',
728
+ borderRadius: '8px',
729
+ padding: '8px 12px',
730
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.5)',
731
+ }}
732
+ >
733
+ <input
734
+ type="text"
735
+ value={renameValue}
736
+ onChange={(e) => setRenameValue(e.target.value)}
737
+ onKeyDown={(e) => {
738
+ if (e.key === 'Enter') {
739
+ handleSaveRename();
740
+ } else if (e.key === 'Escape') {
741
+ handleCancelRename();
742
+ }
743
+ }}
744
+ onBlur={handleSaveRename}
745
+ autoFocus
746
+ style={{
747
+ backgroundColor: 'transparent',
748
+ border: 'none',
749
+ color: '#fff',
750
+ fontSize: '14px',
751
+ outline: 'none',
752
+ width: '200px',
753
+ }}
754
+ />
755
+ </Box>
756
+ );
757
+ })()}
543
758
  {renderDynamicDiagram()}
544
759
  </DndContext>
545
760
  );
@@ -2,7 +2,7 @@
2
2
  export interface AutomationNodeFormData {
3
3
  nodeId: string;
4
4
  title: string;
5
- type: 'start' | 'api' | 'formatting' | 'sheets' | 'end';
5
+ type: 'start' | 'api' | 'formatting' | 'sheets' | 'end' | 'navigation';
6
6
  isPinned?: boolean;
7
7
  isBlock?: boolean;
8
8
  blocks?: Array<{
@@ -245,12 +245,32 @@ export interface AutomationEndNodeForm extends AutomationNodeFormData {
245
245
  };
246
246
  }
247
247
 
248
+ export interface AutomationNavigationNodeForm extends AutomationNodeFormData {
249
+ type: 'navigation';
250
+ navigationType: 'navigate' | 'click' | 'scroll' | 'wait' | 'extract';
251
+ url?: string;
252
+ selector?: string;
253
+ action?: string;
254
+ waitTime?: number;
255
+ scrollDirection?: 'up' | 'down' | 'to-element';
256
+ extractSelector?: string;
257
+ outputVariable?: string;
258
+ timeout?: number;
259
+ retryCount?: number;
260
+ errorHandling?: {
261
+ onError: 'stop' | 'retry' | 'continue';
262
+ maxRetries?: number;
263
+ fallbackAction?: 'skip' | 'log' | 'notify';
264
+ };
265
+ }
266
+
248
267
  export type AutomationNodeForm =
249
268
  | AutomationStartNodeForm
250
269
  | AutomationApiNodeForm
251
270
  | AutomationFormattingNodeForm
252
271
  | AutomationSheetsNodeForm
253
- | AutomationEndNodeForm;
272
+ | AutomationEndNodeForm
273
+ | AutomationNavigationNodeForm;
254
274
 
255
275
  // Automation Workflow Data Structure
256
276
  export interface AutomationWorkflowData {
@@ -10,6 +10,7 @@ import {
10
10
  AutomationSheetsNode,
11
11
  AutomationEndNode,
12
12
  AutomationNoteNode,
13
+ AutomationNavigationNode,
13
14
  AutomationAISuggestionNode,
14
15
  } from '../components/automation';
15
16
 
@@ -25,5 +26,6 @@ export default {
25
26
  AutomationSheetsNode,
26
27
  AutomationEndNode,
27
28
  AutomationNoteNode,
29
+ AutomationNavigationNode,
28
30
  AutomationAISuggestionNode,
29
31
  };