@d34dman/flowdrop 0.0.50 → 0.0.52

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.
Files changed (39) hide show
  1. package/dist/api/client.d.ts +5 -1
  2. package/dist/api/client.js +10 -0
  3. package/dist/components/NodeSidebar.svelte +4 -21
  4. package/dist/components/SettingsPanel.svelte +15 -0
  5. package/dist/components/ThemeToggle.svelte +1 -2
  6. package/dist/components/WorkflowEditor.svelte +97 -1
  7. package/dist/components/form/FormField.svelte +1 -1
  8. package/dist/components/form/FormFieldLight.svelte +1 -1
  9. package/dist/components/nodes/ToolNode.svelte +12 -1
  10. package/dist/config/defaultCategories.d.ts +7 -0
  11. package/dist/config/defaultCategories.js +126 -0
  12. package/dist/config/endpoints.d.ts +1 -0
  13. package/dist/config/endpoints.js +1 -0
  14. package/dist/core/index.d.ts +1 -1
  15. package/dist/editor/index.d.ts +1 -0
  16. package/dist/editor/index.js +1 -0
  17. package/dist/helpers/proximityConnect.d.ts +78 -0
  18. package/dist/helpers/proximityConnect.js +224 -0
  19. package/dist/helpers/workflowEditorHelper.js +9 -0
  20. package/dist/index.d.ts +1 -5
  21. package/dist/index.js +2 -11
  22. package/dist/services/categoriesApi.d.ts +14 -0
  23. package/dist/services/categoriesApi.js +41 -0
  24. package/dist/settings/index.d.ts +24 -0
  25. package/dist/settings/index.js +32 -0
  26. package/dist/stores/categoriesStore.d.ts +32 -0
  27. package/dist/stores/categoriesStore.js +80 -0
  28. package/dist/stores/settingsStore.d.ts +1 -30
  29. package/dist/stores/settingsStore.js +12 -17
  30. package/dist/svelte-app.d.ts +4 -1
  31. package/dist/svelte-app.js +30 -2
  32. package/dist/types/index.d.ts +38 -3
  33. package/dist/types/settings.d.ts +4 -0
  34. package/dist/types/settings.js +3 -1
  35. package/dist/utils/colors.d.ts +5 -2
  36. package/dist/utils/colors.js +7 -3
  37. package/dist/utils/icons.d.ts +7 -3
  38. package/dist/utils/icons.js +11 -6
  39. package/package.json +6 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * API Client for FlowDrop Workflow Library
3
3
  */
4
- import type { NodeMetadata, Workflow, ExecutionResult, PortConfig } from '../types/index.js';
4
+ import type { NodeMetadata, Workflow, ExecutionResult, PortConfig, CategoryDefinition } from '../types/index.js';
5
5
  /**
6
6
  * HTTP API client for FlowDrop
7
7
  */
@@ -80,6 +80,10 @@ export declare class FlowDropApiClient {
80
80
  * Fetch port configuration
81
81
  */
82
82
  getPortConfig(): Promise<PortConfig>;
83
+ /**
84
+ * Fetch category definitions
85
+ */
86
+ getCategories(): Promise<CategoryDefinition[]>;
83
87
  /**
84
88
  * Fetch pipeline data including job information and status
85
89
  */
@@ -215,6 +215,16 @@ export class FlowDropApiClient {
215
215
  }
216
216
  return response.data;
217
217
  }
218
+ /**
219
+ * Fetch category definitions
220
+ */
221
+ async getCategories() {
222
+ const response = await this.request('/categories');
223
+ if (!response.success || !response.data) {
224
+ throw new Error(response.error || 'Failed to fetch categories');
225
+ }
226
+ return response.data;
227
+ }
218
228
  /**
219
229
  * Fetch pipeline data including job information and status
220
230
  */
@@ -10,6 +10,7 @@
10
10
  import Icon from '@iconify/svelte';
11
11
  import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
12
12
  import { getCategoryColorToken } from '../utils/colors.js';
13
+ import { getCategoryLabel } from '../stores/categoriesStore.js';
13
14
  import { SvelteSet } from 'svelte/reactivity';
14
15
  import { uiSettings, updateSettings } from '../stores/settingsStore.js';
15
16
 
@@ -142,29 +143,11 @@
142
143
  }
143
144
 
144
145
  /**
145
- * Get category display name
146
+ * Get category display name from the categories store.
147
+ * Falls back to auto-capitalizing the category machine name.
146
148
  */
147
149
  function getCategoryDisplayName(category: NodeCategory): string {
148
- const names: Record<NodeCategory, string> = {
149
- triggers: 'Triggers',
150
- inputs: 'Inputs',
151
- outputs: 'Outputs',
152
- prompts: 'Prompts',
153
- models: 'Models',
154
- processing: 'Processing',
155
- logic: 'Logic',
156
- data: 'Data',
157
- tools: 'Tools',
158
- helpers: 'Helpers',
159
- 'vector stores': 'Vector Stores',
160
- embeddings: 'Embeddings',
161
- memories: 'Memories',
162
- agents: 'Agents',
163
- ai: 'AI',
164
- interrupts: 'Interrupts',
165
- bundles: 'Bundles'
166
- };
167
- return names[category] || category;
150
+ return getCategoryLabel(category);
168
151
  }
169
152
 
170
153
  /**
@@ -140,6 +140,21 @@
140
140
  title: 'Fit View on Load',
141
141
  description: 'Automatically fit workflow to view when loading',
142
142
  default: true
143
+ },
144
+ proximityConnect: {
145
+ type: 'boolean',
146
+ title: 'Proximity Connect',
147
+ description:
148
+ 'Auto-connect compatible ports when dragging nodes near each other',
149
+ default: false
150
+ },
151
+ proximityConnectDistance: {
152
+ type: 'number',
153
+ title: 'Proximity Distance',
154
+ description: 'Distance threshold in pixels for proximity connect',
155
+ minimum: 50,
156
+ maximum: 500,
157
+ default: 150
143
158
  }
144
159
  }
145
160
  },
@@ -7,8 +7,7 @@
7
7
 
8
8
  <script lang="ts">
9
9
  import Icon from '@iconify/svelte';
10
- // Use settingsStore for theme (themeStore is deprecated)
11
- import { theme, resolvedTheme, cycleTheme } from '../stores/settingsStore.js';
10
+ import { theme, resolvedTheme, cycleTheme } from '../stores/themeStore.js';
12
11
  import type { ThemePreference } from '../types/settings.js';
13
12
 
14
13
  /**
@@ -16,7 +16,8 @@
16
16
  type ColorMode
17
17
  } from '@xyflow/svelte';
18
18
  import '@xyflow/svelte/dist/style.css';
19
- import { resolvedTheme, editorSettings, behaviorSettings } from '../stores/settingsStore.js';
19
+ import { resolvedTheme } from '../stores/themeStore.js';
20
+ import { editorSettings, behaviorSettings } from '../stores/settingsStore.js';
20
21
  import type {
21
22
  WorkflowNode as WorkflowNodeType,
22
23
  NodeMetadata,
@@ -42,6 +43,10 @@
42
43
  import { areNodeArraysEqual, areEdgeArraysEqual, throttle } from '../utils/performanceUtils.js';
43
44
  import { Toaster } from 'svelte-5-french-toast';
44
45
  import { flowdropToastOptions, FLOWDROP_TOASTER_CLASS } from '../services/toastService.js';
46
+ import {
47
+ ProximityConnectHelper,
48
+ type ProximityEdgeCandidate
49
+ } from '../helpers/proximityConnect.js';
45
50
 
46
51
  interface Props {
47
52
  nodes?: NodeMetadata[];
@@ -69,6 +74,9 @@
69
74
  // Track if we're currently dragging a node (for history debouncing)
70
75
  let isDraggingNode = $state(false);
71
76
 
77
+ // Proximity connect state
78
+ let currentProximityCandidates = $state<ProximityEdgeCandidate[]>([]);
79
+
72
80
  // Track the workflow ID we're currently editing to detect workflow switches
73
81
  let currentWorkflowId: string | null = null;
74
82
 
@@ -334,6 +342,46 @@
334
342
  */
335
343
  function handleNodeDragStart(): void {
336
344
  isDraggingNode = true;
345
+ // Clear any leftover proximity previews
346
+ currentProximityCandidates = [];
347
+ }
348
+
349
+ /**
350
+ * Handle node drag - compute proximity connect preview edges
351
+ * Called continuously during drag if proximity connect is enabled
352
+ */
353
+ function handleNodeDrag({
354
+ targetNode
355
+ }: {
356
+ targetNode: WorkflowNodeType | null;
357
+ nodes: WorkflowNodeType[];
358
+ event: MouseEvent | TouchEvent;
359
+ }): void {
360
+ if (!$editorSettings.proximityConnect || !targetNode || props.readOnly || props.lockWorkflow) {
361
+ if (currentProximityCandidates.length > 0) {
362
+ flowEdges = ProximityConnectHelper.removePreviewEdges(flowEdges);
363
+ currentProximityCandidates = [];
364
+ }
365
+ return;
366
+ }
367
+
368
+ // Remove previous preview edges
369
+ const baseEdges = ProximityConnectHelper.removePreviewEdges(flowEdges);
370
+
371
+ // Find the best compatible edge with nearby nodes
372
+ const candidates = ProximityConnectHelper.findCompatibleEdges(
373
+ targetNode,
374
+ flowNodes,
375
+ baseEdges,
376
+ $editorSettings.proximityConnectDistance
377
+ );
378
+
379
+ // Create preview edges
380
+ const previews = ProximityConnectHelper.createPreviewEdges(candidates);
381
+
382
+ // Update state
383
+ currentProximityCandidates = candidates;
384
+ flowEdges = [...baseEdges, ...previews];
337
385
  }
338
386
 
339
387
  /**
@@ -344,6 +392,38 @@
344
392
  */
345
393
  function handleNodeDragStop(): void {
346
394
  isDraggingNode = false;
395
+
396
+ // Finalize proximity connect if there are candidates
397
+ if ($editorSettings.proximityConnect && currentProximityCandidates.length > 0) {
398
+ // Remove all preview edges
399
+ const baseEdges = ProximityConnectHelper.removePreviewEdges(flowEdges);
400
+
401
+ // Create permanent edges from candidates
402
+ const permanentEdges = ProximityConnectHelper.createPermanentEdges(
403
+ currentProximityCandidates
404
+ );
405
+
406
+ // Apply proper styling to each new permanent edge
407
+ for (const edge of permanentEdges) {
408
+ const sourceNode = flowNodes.find((n) => n.id === edge.source);
409
+ const targetNode = flowNodes.find((n) => n.id === edge.target);
410
+ if (sourceNode && targetNode) {
411
+ EdgeStylingHelper.applyConnectionStyling(edge, sourceNode, targetNode);
412
+ }
413
+ }
414
+
415
+ // Set final edges
416
+ flowEdges = [...baseEdges, ...permanentEdges];
417
+
418
+ // Clear proximity state
419
+ currentProximityCandidates = [];
420
+
421
+ // Update workflow
422
+ if (currentWorkflow) {
423
+ updateCurrentWorkflowFromSvelteFlow();
424
+ }
425
+ }
426
+
347
427
  // Push the current state AFTER the drag completed
348
428
  if (currentWorkflow) {
349
429
  workflowActions.pushHistory('Move node', currentWorkflow);
@@ -636,6 +716,7 @@
636
716
  onbeforedelete={handleBeforeDelete}
637
717
  ondelete={handleNodesDelete}
638
718
  onnodedragstart={handleNodeDragStart}
719
+ onnodedrag={handleNodeDrag}
639
720
  onnodedragstop={handleNodeDragStop}
640
721
  minZoom={0.2}
641
722
  maxZoom={3}
@@ -884,4 +965,19 @@
884
965
  filter: drop-shadow(0 0 3px rgba(139, 92, 246, 0.4));
885
966
  opacity: 1;
886
967
  }
968
+
969
+ /* Proximity Connect Preview Edge: animated dashed line */
970
+ :global(.flowdrop--edge--proximity-preview path.svelte-flow__edge-path) {
971
+ stroke: var(--fd-primary);
972
+ stroke-width: 2;
973
+ stroke-dasharray: 5 5;
974
+ opacity: 0.6;
975
+ animation: flowdrop-proximity-dash 0.5s linear infinite;
976
+ }
977
+
978
+ @keyframes flowdrop-proximity-dash {
979
+ to {
980
+ stroke-dashoffset: -10;
981
+ }
982
+ }
887
983
  </style>
@@ -44,7 +44,7 @@
44
44
  import type { FieldSchema } from './types.js';
45
45
  import { getSchemaOptions } from './types.js';
46
46
  import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
47
- import { resolvedTheme } from '../../stores/settingsStore.js';
47
+ import { resolvedTheme } from '../../stores/themeStore.js';
48
48
 
49
49
  interface Props {
50
50
  /** Unique key/id for the field */
@@ -43,7 +43,7 @@
43
43
  import FormCheckboxGroup from './FormCheckboxGroup.svelte';
44
44
  import FormArray from './FormArray.svelte';
45
45
  import { resolveFieldComponent } from '../../form/fieldRegistry.js';
46
- import { resolvedTheme } from '../../stores/settingsStore.js';
46
+ import { resolvedTheme } from '../../stores/themeStore.js';
47
47
  import type { FieldSchema } from './types.js';
48
48
  import { getSchemaOptions } from './types.js';
49
49
 
@@ -61,6 +61,17 @@
61
61
  'Tool'
62
62
  );
63
63
 
64
+ /**
65
+ * Instance-specific badge label override from config.
66
+ * Falls back to metadata badge or default 'TOOL' if not set.
67
+ * This allows users to customize the badge text per-instance via config.
68
+ */
69
+ const displayBadge = $derived(
70
+ (props.data.config?.instanceBadge as string) ||
71
+ (props.data.metadata?.badge as string) ||
72
+ 'TOOL'
73
+ );
74
+
64
75
  /**
65
76
  * Instance-specific description override from config.
66
77
  * Falls back to metadata description or toolDescription config if not set.
@@ -185,7 +196,7 @@
185
196
  </div>
186
197
 
187
198
  <!-- Tool Badge - tinted style matching icon wrappers -->
188
- <div class="flowdrop-tool-node__badge">TOOL</div>
199
+ <div class="flowdrop-tool-node__badge">{displayBadge}</div>
189
200
  </div>
190
201
 
191
202
  <!-- Tool Description - uses instanceDescription override if set -->
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Default category definitions for FlowDrop
3
+ * Provides built-in categories with icons, colors, and display labels.
4
+ * These serve as fallbacks when the /categories API endpoint is unavailable.
5
+ */
6
+ import type { CategoryDefinition } from '../types/index.js';
7
+ export declare const DEFAULT_CATEGORIES: CategoryDefinition[];
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Default category definitions for FlowDrop
3
+ * Provides built-in categories with icons, colors, and display labels.
4
+ * These serve as fallbacks when the /categories API endpoint is unavailable.
5
+ */
6
+ export const DEFAULT_CATEGORIES = [
7
+ {
8
+ name: 'triggers',
9
+ label: 'Triggers',
10
+ icon: 'mdi:lightning-bolt',
11
+ color: 'var(--fd-node-cyan)',
12
+ weight: 0
13
+ },
14
+ {
15
+ name: 'inputs',
16
+ label: 'Inputs',
17
+ icon: 'mdi:arrow-down-circle',
18
+ color: 'var(--fd-node-emerald)',
19
+ weight: 1
20
+ },
21
+ {
22
+ name: 'outputs',
23
+ label: 'Outputs',
24
+ icon: 'mdi:arrow-up-circle',
25
+ color: 'var(--fd-node-blue)',
26
+ weight: 2
27
+ },
28
+ {
29
+ name: 'prompts',
30
+ label: 'Prompts',
31
+ icon: 'mdi:message-text',
32
+ color: 'var(--fd-node-amber)',
33
+ weight: 3
34
+ },
35
+ {
36
+ name: 'models',
37
+ label: 'Models',
38
+ icon: 'mdi:robot',
39
+ color: 'var(--fd-node-indigo)',
40
+ weight: 4
41
+ },
42
+ {
43
+ name: 'processing',
44
+ label: 'Processing',
45
+ icon: 'mdi:cog',
46
+ color: 'var(--fd-node-teal)',
47
+ weight: 5
48
+ },
49
+ {
50
+ name: 'logic',
51
+ label: 'Logic',
52
+ icon: 'mdi:source-branch',
53
+ color: 'var(--fd-node-purple)',
54
+ weight: 6
55
+ },
56
+ {
57
+ name: 'data',
58
+ label: 'Data',
59
+ icon: 'mdi:database',
60
+ color: 'var(--fd-node-orange)',
61
+ weight: 7
62
+ },
63
+ {
64
+ name: 'tools',
65
+ label: 'Tools',
66
+ icon: 'mdi:wrench',
67
+ color: 'var(--fd-node-amber)',
68
+ weight: 8
69
+ },
70
+ {
71
+ name: 'helpers',
72
+ label: 'Helpers',
73
+ icon: 'mdi:help-circle',
74
+ color: 'var(--fd-node-slate)',
75
+ weight: 9
76
+ },
77
+ {
78
+ name: 'vector stores',
79
+ label: 'Vector Stores',
80
+ icon: 'mdi:vector-square',
81
+ color: 'var(--fd-node-emerald)',
82
+ weight: 10
83
+ },
84
+ {
85
+ name: 'embeddings',
86
+ label: 'Embeddings',
87
+ icon: 'mdi:vector-polygon',
88
+ color: 'var(--fd-node-indigo)',
89
+ weight: 11
90
+ },
91
+ {
92
+ name: 'memories',
93
+ label: 'Memories',
94
+ icon: 'mdi:brain',
95
+ color: 'var(--fd-node-blue)',
96
+ weight: 12
97
+ },
98
+ {
99
+ name: 'agents',
100
+ label: 'Agents',
101
+ icon: 'mdi:account-cog',
102
+ color: 'var(--fd-node-teal)',
103
+ weight: 13
104
+ },
105
+ {
106
+ name: 'ai',
107
+ label: 'AI',
108
+ icon: 'mdi:shimmer',
109
+ color: 'var(--fd-node-purple)',
110
+ weight: 14
111
+ },
112
+ {
113
+ name: 'interrupts',
114
+ label: 'Interrupts',
115
+ icon: 'mdi:hand-back-left',
116
+ color: 'var(--fd-node-red)',
117
+ weight: 15
118
+ },
119
+ {
120
+ name: 'bundles',
121
+ label: 'Bundles',
122
+ icon: 'mdi:package-variant',
123
+ color: 'var(--fd-node-slate)',
124
+ weight: 16
125
+ }
126
+ ];
@@ -14,6 +14,7 @@ export interface EndpointConfig {
14
14
  metadata: string;
15
15
  };
16
16
  portConfig: string;
17
+ categories: string;
17
18
  workflows: {
18
19
  list: string;
19
20
  get: string;
@@ -15,6 +15,7 @@ export const defaultEndpointConfig = {
15
15
  metadata: '/nodes/{id}/metadata'
16
16
  },
17
17
  portConfig: '/port-config',
18
+ categories: '/categories',
18
19
  workflows: {
19
20
  list: '/workflows',
20
21
  get: '/workflows/{id}',
@@ -14,7 +14,7 @@
14
14
  * import { getStatusColor, createDefaultExecutionInfo } from "@d34dman/flowdrop/core";
15
15
  * ```
16
16
  */
17
- export type { NodeCategory, NodeDataType, NodePort, DynamicPort, Branch, NodeMetadata, NodeExtensions, NodeUIExtensions, ConfigValues, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType, PortConfig, PortCompatibilityRule, ConfigSchema, ConfigProperty, HttpMethod, DynamicSchemaEndpoint, ExternalEditLink, ConfigEditOptions, EdgeCategory } from '../types/index.js';
17
+ export type { NodeCategory, BuiltinNodeCategory, CategoryDefinition, NodeDataType, NodePort, DynamicPort, Branch, NodeMetadata, NodeExtensions, NodeUIExtensions, ConfigValues, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType, PortConfig, PortCompatibilityRule, ConfigSchema, ConfigProperty, HttpMethod, DynamicSchemaEndpoint, ExternalEditLink, ConfigEditOptions, EdgeCategory } from '../types/index.js';
18
18
  export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig } from '../types/config.js';
19
19
  export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from '../types/auth.js';
20
20
  export type { WorkflowChangeType, FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
@@ -74,6 +74,7 @@ export { currentSession, sessions, messages, isExecuting, isLoading, error as pl
74
74
  export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from '../services/workflowStorage.js';
75
75
  export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from '../services/globalSave.js';
76
76
  export { fetchPortConfig, validatePortConfig } from '../services/portConfigApi.js';
77
+ export { fetchCategories, validateCategories } from '../services/categoriesApi.js';
77
78
  export { fetchDynamicSchema, resolveExternalEditUrl, getEffectiveConfigEditOptions, clearSchemaCache, invalidateSchemaCache, hasConfigEditOptions, shouldShowExternalEdit, shouldUseDynamicSchema } from '../services/dynamicSchemaService.js';
78
79
  export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from '../services/draftStorage.js';
79
80
  export { FlowDropApiClient } from '../api/client.js';
@@ -115,6 +115,7 @@ export { currentSession, sessions, messages, isExecuting, isLoading, error as pl
115
115
  export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from '../services/workflowStorage.js';
116
116
  export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from '../services/globalSave.js';
117
117
  export { fetchPortConfig, validatePortConfig } from '../services/portConfigApi.js';
118
+ export { fetchCategories, validateCategories } from '../services/categoriesApi.js';
118
119
  export { fetchDynamicSchema, resolveExternalEditUrl, getEffectiveConfigEditOptions, clearSchemaCache, invalidateSchemaCache, hasConfigEditOptions, shouldShowExternalEdit, shouldUseDynamicSchema } from '../services/dynamicSchemaService.js';
119
120
  export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from '../services/draftStorage.js';
120
121
  // ============================================================================
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Proximity Connect Helper
3
+ *
4
+ * Provides type-aware proximity connect logic for the workflow editor.
5
+ * When a node is dragged near another node, this helper finds the best
6
+ * compatible port pair and creates a preview/permanent edge.
7
+ */
8
+ import type { WorkflowNode as WorkflowNodeType, WorkflowEdge, NodePort } from '../types/index.js';
9
+ /** A candidate proximity edge before it is finalized */
10
+ export interface ProximityEdgeCandidate {
11
+ id: string;
12
+ source: string;
13
+ target: string;
14
+ sourceHandle: string;
15
+ targetHandle: string;
16
+ sourcePortDataType: string;
17
+ targetPortDataType: string;
18
+ }
19
+ export declare class ProximityConnectHelper {
20
+ /**
21
+ * Get ALL ports (static + dynamic + gateway branches) for a node.
22
+ */
23
+ static getAllPorts(node: WorkflowNodeType, direction: 'input' | 'output'): NodePort[];
24
+ /**
25
+ * Build handle ID in the standard format.
26
+ */
27
+ static buildHandleId(nodeId: string, direction: 'input' | 'output', portId: string): string;
28
+ /**
29
+ * Calculate center-to-center Euclidean distance between two nodes.
30
+ */
31
+ static getNodeDistance(nodeA: {
32
+ position: {
33
+ x: number;
34
+ y: number;
35
+ };
36
+ measured?: {
37
+ width?: number;
38
+ height?: number;
39
+ };
40
+ }, nodeB: {
41
+ position: {
42
+ x: number;
43
+ y: number;
44
+ };
45
+ measured?: {
46
+ width?: number;
47
+ height?: number;
48
+ };
49
+ }): number;
50
+ /**
51
+ * Find the single best compatible edge between a dragged node and nearby nodes.
52
+ *
53
+ * Algorithm:
54
+ * 1. Find the closest node within minDistance
55
+ * 2. Check both directions (dragged->nearby and nearby->dragged)
56
+ * 3. Return the first exact-type match, or first compatible match
57
+ * 4. Skip pairs where an edge already exists or input handle is already connected
58
+ *
59
+ * @returns Array with at most ONE ProximityEdgeCandidate
60
+ */
61
+ static findCompatibleEdges(draggedNode: WorkflowNodeType, allNodes: WorkflowNodeType[], existingEdges: WorkflowEdge[], minDistance: number): ProximityEdgeCandidate[];
62
+ /**
63
+ * Convert candidates to temporary (preview) WorkflowEdge objects with dashed styling.
64
+ */
65
+ static createPreviewEdges(candidates: ProximityEdgeCandidate[]): WorkflowEdge[];
66
+ /**
67
+ * Convert candidates to permanent WorkflowEdge objects.
68
+ */
69
+ static createPermanentEdges(candidates: ProximityEdgeCandidate[]): WorkflowEdge[];
70
+ /**
71
+ * Check if an edge is a temporary proximity preview edge.
72
+ */
73
+ static isProximityPreviewEdge(edge: WorkflowEdge): boolean;
74
+ /**
75
+ * Remove all proximity preview edges from an edge array.
76
+ */
77
+ static removePreviewEdges(edges: WorkflowEdge[]): WorkflowEdge[];
78
+ }