@d34dman/flowdrop 0.0.25 → 0.0.27

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 (40) hide show
  1. package/README.md +52 -62
  2. package/dist/components/App.svelte +12 -2
  3. package/dist/components/ConfigForm.svelte +500 -9
  4. package/dist/components/ConfigForm.svelte.d.ts +2 -0
  5. package/dist/components/ConfigModal.svelte +4 -70
  6. package/dist/components/ConfigPanel.svelte +4 -9
  7. package/dist/components/EdgeRefresher.svelte +41 -0
  8. package/dist/components/EdgeRefresher.svelte.d.ts +9 -0
  9. package/dist/components/ReadOnlyDetails.svelte +3 -1
  10. package/dist/components/UniversalNode.svelte +6 -3
  11. package/dist/components/WorkflowEditor.svelte +30 -0
  12. package/dist/components/WorkflowEditor.svelte.d.ts +3 -1
  13. package/dist/components/form/FormCheckboxGroup.svelte +2 -9
  14. package/dist/components/form/FormField.svelte +1 -12
  15. package/dist/components/form/FormFieldWrapper.svelte +2 -10
  16. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  17. package/dist/components/form/FormMarkdownEditor.svelte +0 -2
  18. package/dist/components/form/FormNumberField.svelte +5 -6
  19. package/dist/components/form/FormRangeField.svelte +3 -13
  20. package/dist/components/form/FormSelect.svelte +4 -5
  21. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  22. package/dist/components/form/FormTextField.svelte +3 -4
  23. package/dist/components/form/FormTextarea.svelte +3 -4
  24. package/dist/components/form/FormToggle.svelte +2 -3
  25. package/dist/components/form/index.d.ts +14 -14
  26. package/dist/components/form/index.js +14 -14
  27. package/dist/components/form/types.d.ts +2 -2
  28. package/dist/components/form/types.js +1 -1
  29. package/dist/components/nodes/NotesNode.svelte +39 -45
  30. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  31. package/dist/components/nodes/SimpleNode.svelte +92 -142
  32. package/dist/components/nodes/SquareNode.svelte +75 -58
  33. package/dist/components/nodes/WorkflowNode.svelte +1 -3
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.js +2 -0
  36. package/dist/services/dynamicSchemaService.d.ts +108 -0
  37. package/dist/services/dynamicSchemaService.js +445 -0
  38. package/dist/styles/base.css +1 -1
  39. package/dist/types/index.d.ts +213 -0
  40. package/package.json +163 -155
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
- import type { ConfigValues, NodeMetadata } from "../../types/index.js";
3
- import Icon from "@iconify/svelte";
4
- import MarkdownDisplay from "../MarkdownDisplay.svelte";
2
+ import type { ConfigValues, NodeMetadata } from '../../types/index.js';
3
+ import Icon from '@iconify/svelte';
4
+ import MarkdownDisplay from '../MarkdownDisplay.svelte';
5
5
 
6
6
  /**
7
7
  * NotesNode component props
@@ -25,63 +25,57 @@
25
25
  }>();
26
26
 
27
27
  /** Note content derived from config */
28
- const noteContent = $derived(
29
- (props.data.config?.content as string) || "Add your notes here..."
30
- );
28
+ const noteContent = $derived((props.data.config?.content as string) || 'Add your notes here...');
31
29
 
32
30
  /** Note type derived from config */
33
- const noteType = $derived(
34
- (props.data.config?.noteType as string) || "info"
35
- );
31
+ const noteType = $derived((props.data.config?.noteType as string) || 'info');
36
32
 
37
33
  /** Note type configuration with styling for each type */
38
34
  const noteTypes = {
39
35
  info: {
40
- name: "Info",
41
- bgColor: "bg-blue-50",
42
- borderColor: "border-blue-200",
43
- textColor: "text-blue-800",
44
- iconColor: "text-blue-500",
45
- icon: "mdi:information"
36
+ name: 'Info',
37
+ bgColor: 'bg-blue-50',
38
+ borderColor: 'border-blue-200',
39
+ textColor: 'text-blue-800',
40
+ iconColor: 'text-blue-500',
41
+ icon: 'mdi:information'
46
42
  },
47
43
  warning: {
48
- name: "Warning",
49
- bgColor: "bg-yellow-50",
50
- borderColor: "border-yellow-200",
51
- textColor: "text-yellow-800",
52
- iconColor: "text-yellow-500",
53
- icon: "mdi:alert"
44
+ name: 'Warning',
45
+ bgColor: 'bg-yellow-50',
46
+ borderColor: 'border-yellow-200',
47
+ textColor: 'text-yellow-800',
48
+ iconColor: 'text-yellow-500',
49
+ icon: 'mdi:alert'
54
50
  },
55
51
  success: {
56
- name: "Success",
57
- bgColor: "bg-green-50",
58
- borderColor: "border-green-200",
59
- textColor: "text-green-800",
60
- iconColor: "text-green-500",
61
- icon: "mdi:check-circle"
52
+ name: 'Success',
53
+ bgColor: 'bg-green-50',
54
+ borderColor: 'border-green-200',
55
+ textColor: 'text-green-800',
56
+ iconColor: 'text-green-500',
57
+ icon: 'mdi:check-circle'
62
58
  },
63
59
  error: {
64
- name: "Error",
65
- bgColor: "bg-red-50",
66
- borderColor: "border-red-200",
67
- textColor: "text-red-800",
68
- iconColor: "text-red-500",
69
- icon: "mdi:close-circle"
60
+ name: 'Error',
61
+ bgColor: 'bg-red-50',
62
+ borderColor: 'border-red-200',
63
+ textColor: 'text-red-800',
64
+ iconColor: 'text-red-500',
65
+ icon: 'mdi:close-circle'
70
66
  },
71
67
  note: {
72
- name: "Note",
73
- bgColor: "bg-gray-50",
74
- borderColor: "border-gray-200",
75
- textColor: "text-gray-800",
76
- iconColor: "text-gray-500",
77
- icon: "mdi:note-text"
68
+ name: 'Note',
69
+ bgColor: 'bg-gray-50',
70
+ borderColor: 'border-gray-200',
71
+ textColor: 'text-gray-800',
72
+ iconColor: 'text-gray-500',
73
+ icon: 'mdi:note-text'
78
74
  }
79
75
  };
80
76
 
81
77
  /** Current note type configuration based on selected type */
82
- const currentType = $derived(
83
- noteTypes[noteType as keyof typeof noteTypes] || noteTypes.info
84
- );
78
+ const currentType = $derived(noteTypes[noteType as keyof typeof noteTypes] || noteTypes.info);
85
79
 
86
80
  /**
87
81
  * Opens the configuration sidebar for editing note properties
@@ -89,8 +83,8 @@
89
83
  function openConfigSidebar(): void {
90
84
  if (props.data.onConfigOpen) {
91
85
  const nodeForConfig = {
92
- id: props.data.nodeId || "unknown",
93
- type: "note",
86
+ id: props.data.nodeId || 'unknown',
87
+ type: 'note',
94
88
  data: props.data
95
89
  };
96
90
  props.data.onConfigOpen(nodeForConfig);
@@ -109,7 +103,7 @@
109
103
  * @param event - The keyboard event
110
104
  */
111
105
  function handleKeydown(event: KeyboardEvent): void {
112
- if (event.key === "Enter" || event.key === " ") {
106
+ if (event.key === 'Enter' || event.key === ' ') {
113
107
  event.preventDefault();
114
108
  handleDoubleClick();
115
109
  }
@@ -1,4 +1,4 @@
1
- import type { ConfigValues, NodeMetadata } from "../../types/index.js";
1
+ import type { ConfigValues, NodeMetadata } from '../../types/index.js';
2
2
  type $$ComponentProps = {
3
3
  data: {
4
4
  label: string;
@@ -42,27 +42,6 @@
42
42
  return instanceOverride ?? typeDefault;
43
43
  });
44
44
 
45
- /**
46
- * Check if a trigger port is connected
47
- * @param portId - The port ID to check
48
- * @param type - Whether this is an 'input' or 'output' port
49
- */
50
- function isTriggerPortConnected(portId: string, type: 'input' | 'output'): boolean {
51
- const handleId = `${props.data.nodeId}-${type}-${portId}`;
52
- return $connectedHandles.has(handleId);
53
- }
54
-
55
- /**
56
- * Check if a trigger port should be visible
57
- * Always shows if hideUnconnectedHandles is disabled or if port is connected
58
- */
59
- function shouldShowTriggerPort(portId: string, type: 'input' | 'output'): boolean {
60
- if (!hideUnconnectedHandles()) {
61
- return true;
62
- }
63
- return isTriggerPortConnected(portId, type);
64
- }
65
-
66
45
  // Removed local config state - now using global ConfigSidebar
67
46
 
68
47
  // Prioritize metadata icon over config icon for simple nodes (metadata is the node definition)
@@ -72,28 +51,6 @@
72
51
  let nodeColor = $derived(
73
52
  (props.data.metadata?.color as string) || (props.data.config?.color as string) || '#6366f1'
74
53
  );
75
- let nodeLayout = $derived((props.data.config?.layout as string) || 'normal');
76
-
77
- // Layout configurations
78
- const layoutConfig = {
79
- compact: {
80
- width: '80px',
81
- height: '80px',
82
- iconSize: '2rem',
83
- showHeader: false
84
- },
85
- normal: {
86
- width: '18rem',
87
- height: 'auto',
88
- iconSize: '1rem',
89
- showHeader: true
90
- }
91
- };
92
-
93
- let currentLayout = $derived(
94
- layoutConfig[nodeLayout as keyof typeof layoutConfig] || layoutConfig.normal
95
- );
96
- let isCompact = $derived(nodeLayout === 'compact');
97
54
 
98
55
  // Handle configuration sidebar - now using global ConfigSidebar
99
56
  function openConfigSidebar(): void {
@@ -126,6 +83,28 @@
126
83
  }
127
84
  }
128
85
 
86
+ /**
87
+ * Check if a port is connected
88
+ * @param portId - The port ID to check
89
+ * @param type - Whether this is an 'input' or 'output' port
90
+ * @returns true if the port is connected
91
+ */
92
+ function isPortConnected(portId: string, type: 'input' | 'output'): boolean {
93
+ const handleId = `${props.data.nodeId}-${type}-${portId}`;
94
+ return $connectedHandles.has(handleId);
95
+ }
96
+
97
+ /**
98
+ * Check if a trigger port should be visible
99
+ * Always shows if hideUnconnectedHandles is disabled or if port is connected
100
+ */
101
+ function shouldShowTriggerPort(portId: string, type: 'input' | 'output'): boolean {
102
+ if (!hideUnconnectedHandles()) {
103
+ return true;
104
+ }
105
+ return isPortConnected(portId, type);
106
+ }
107
+
129
108
  // Get first input/output ports for simple node representation
130
109
  // Special handling for trigger ports - they should always be shown if present
131
110
  let triggerInputPort = $derived(
@@ -136,47 +115,71 @@
136
115
  );
137
116
 
138
117
  // Get first non-trigger ports for data connections
118
+ let firstConnectedDataInputPort = $derived(
119
+ props.data.metadata?.inputs?.find(
120
+ (port) => port.dataType !== 'trigger' && isPortConnected(port.id, 'input')
121
+ )
122
+ );
123
+
139
124
  let firstDataInputPort = $derived(
140
125
  props.data.metadata?.inputs?.find((port) => port.dataType !== 'trigger')
141
126
  );
127
+
128
+ let firstConnectedDataOutputPort = $derived(
129
+ props.data.metadata?.outputs?.find(
130
+ (port) => port.dataType !== 'trigger' && isPortConnected(port.id, 'output')
131
+ )
132
+ );
142
133
  let firstDataOutputPort = $derived(
143
134
  props.data.metadata?.outputs?.find((port) => port.dataType !== 'trigger')
144
135
  );
145
136
 
146
- // Check if we need to show both trigger and data ports
147
- let hasBothInputTypes = $derived(!!triggerInputPort && !!firstDataInputPort);
148
- let hasBothOutputTypes = $derived(!!triggerOutputPort && !!firstDataOutputPort);
137
+ let inputPorts = $derived.by(() => {
138
+ return [
139
+ ...(firstConnectedDataInputPort
140
+ ? [firstConnectedDataInputPort]
141
+ : firstDataInputPort
142
+ ? [firstDataInputPort]
143
+ : []),
144
+ ...(triggerInputPort && shouldShowTriggerPort(triggerInputPort.id, 'input')
145
+ ? [triggerInputPort]
146
+ : [])
147
+ ];
148
+ });
149
+ let outputPorts = $derived.by(() => {
150
+ return [
151
+ ...(firstConnectedDataOutputPort
152
+ ? [firstConnectedDataOutputPort]
153
+ : firstDataOutputPort
154
+ ? [firstDataOutputPort]
155
+ : []),
156
+ ...(triggerOutputPort && shouldShowTriggerPort(triggerOutputPort.id, 'output')
157
+ ? [triggerOutputPort]
158
+ : [])
159
+ ];
160
+ });
149
161
  </script>
150
162
 
151
163
  <!-- Input Handles -->
152
- {#if firstDataInputPort}
164
+ {#each inputPorts as port, index}
153
165
  <!-- Data Input - positioned at top-left if both types exist, otherwise center -->
154
166
  <Handle
155
167
  type="target"
156
168
  position={Position.Left}
157
169
  style="background-color: {getDataTypeColor(
158
- firstDataInputPort.dataType
159
- )}; border-color: '#ffffff'; top: {hasBothInputTypes ? '25%' : '50%'}; z-index: 30;"
160
- id={`${props.data.nodeId}-input-${firstDataInputPort.id}`}
170
+ port.dataType
171
+ )}; border-color: '#ffffff'; top: {inputPorts.length > 1
172
+ ? index === 0
173
+ ? '25%'
174
+ : '75%'
175
+ : '50%'}; z-index: 30;"
176
+ id={`${props.data.nodeId}-input-${port.id}`}
161
177
  />
162
- {/if}
163
- {#if triggerInputPort && shouldShowTriggerPort(triggerInputPort.id, 'input')}
164
- <!-- Trigger Input - positioned at bottom-left (hidden if hideUnconnectedHandles enabled and not connected) -->
165
- <Handle
166
- type="target"
167
- position={Position.Left}
168
- style="background-color: {getDataTypeColor(
169
- triggerInputPort.dataType
170
- )}; border-color: '#ffffff'; top: {hasBothInputTypes ? '75%' : '50%'}; z-index: 30;"
171
- id={`${props.data.nodeId}-input-${triggerInputPort.id}`}
172
- />
173
- {/if}
178
+ {/each}
174
179
 
175
180
  <!-- Simple Node -->
176
181
  <div
177
- class="flowdrop-simple-node"
178
- class:flowdrop-simple-node--compact={isCompact}
179
- class:flowdrop-simple-node--normal={!isCompact}
182
+ class="flowdrop-simple-node flowdrop-simple-node--normal"
180
183
  class:flowdrop-simple-node--selected={props.selected}
181
184
  class:flowdrop-simple-node--processing={props.isProcessing}
182
185
  class:flowdrop-simple-node--error={props.isError}
@@ -186,36 +189,24 @@
186
189
  role="button"
187
190
  tabindex="0"
188
191
  >
189
- {#if isCompact}
190
- <!-- Compact Layout: Just centered icon -->
191
- <div class="flowdrop-simple-node__compact-content">
192
- <Icon
193
- icon={nodeIcon}
194
- class="flowdrop-simple-node__compact-icon"
195
- style="color: {nodeColor}; font-size: {currentLayout.iconSize};"
196
- />
197
- </div>
198
- {:else}
199
- <!-- Normal Layout: Header with title and description -->
200
- <div class="flowdrop-simple-node__header">
201
- <div class="flowdrop-simple-node__header-content">
202
- <!-- Node Icon -->
203
- <div class="flowdrop-simple-node__icon-container" style="background-color: {nodeColor}">
204
- <Icon icon={nodeIcon} class="flowdrop-simple-node__icon" />
205
- </div>
206
-
207
- <!-- Node Title -->
208
- <h3 class="flowdrop-simple-node__title">
209
- {props.data.label}
210
- </h3>
192
+ <div class="flowdrop-simple-node__header">
193
+ <div class="flowdrop-simple-node__header-content">
194
+ <!-- Node Icon -->
195
+ <div class="flowdrop-simple-node__icon-container" style="background-color: {nodeColor}">
196
+ <Icon icon={nodeIcon} class="flowdrop-simple-node__icon" />
211
197
  </div>
212
198
 
213
- <!-- Node Description -->
214
- <p class="flowdrop-simple-node__description">
215
- {props.data.metadata?.description || 'A configurable simple node'}
216
- </p>
199
+ <!-- Node Title -->
200
+ <h3 class="flowdrop-simple-node__title">
201
+ {props.data.label}
202
+ </h3>
217
203
  </div>
218
- {/if}
204
+
205
+ <!-- Node Description -->
206
+ <p class="flowdrop-simple-node__description">
207
+ {props.data.metadata?.description || 'A configurable simple node'}
208
+ </p>
209
+ </div>
219
210
 
220
211
  <!-- Processing indicator -->
221
212
  {#if props.isProcessing}
@@ -242,28 +233,21 @@
242
233
  </div>
243
234
 
244
235
  <!-- Output Handles -->
245
- {#if firstDataOutputPort}
236
+ {#each outputPorts as port, index}
246
237
  <!-- Data Output - positioned at top-right if both types exist, otherwise center -->
247
238
  <Handle
248
239
  type="source"
249
240
  position={Position.Right}
250
- id={`${props.data.nodeId}-output-${firstDataOutputPort.id}`}
251
241
  style="background-color: {getDataTypeColor(
252
- firstDataOutputPort.dataType
253
- )}; border-color: '#ffffff'; top: {hasBothOutputTypes ? '25%' : '50%'}; z-index: 30;"
242
+ port.dataType
243
+ )}; border-color: '#ffffff'; top: {outputPorts.length > 1
244
+ ? index === 0
245
+ ? '25%'
246
+ : '75%'
247
+ : '50%'}; z-index: 30;"
248
+ id={`${props.data.nodeId}-output-${port.id}`}
254
249
  />
255
- {/if}
256
- {#if triggerOutputPort && shouldShowTriggerPort(triggerOutputPort.id, 'output')}
257
- <!-- Trigger Output - positioned at bottom-right (hidden if hideUnconnectedHandles enabled and not connected) -->
258
- <Handle
259
- type="source"
260
- position={Position.Right}
261
- id={`${props.data.nodeId}-output-${triggerOutputPort.id}`}
262
- style="background-color: {getDataTypeColor(
263
- triggerOutputPort.dataType
264
- )}; border-color: '#ffffff'; top: {hasBothOutputTypes ? '75%' : '50%'}; z-index: 30;"
265
- />
266
- {/if}
250
+ {/each}
267
251
 
268
252
  <!-- ConfigSidebar removed - now using global ConfigSidebar in WorkflowEditor -->
269
253
 
@@ -287,14 +271,6 @@
287
271
  width: 18rem;
288
272
  }
289
273
 
290
- /* Compact layout */
291
- .flowdrop-simple-node--compact {
292
- width: 80px;
293
- height: 80px;
294
- justify-content: center;
295
- align-items: center;
296
- }
297
-
298
274
  .flowdrop-simple-node:hover {
299
275
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
300
276
  }
@@ -335,19 +311,6 @@
335
311
  flex-shrink: 0;
336
312
  }
337
313
 
338
- /* Compact layout styles */
339
- .flowdrop-simple-node__compact-content {
340
- display: flex;
341
- align-items: center;
342
- justify-content: center;
343
- width: 100%;
344
- height: 100%;
345
- }
346
-
347
- :global(.flowdrop-simple-node__compact-icon) {
348
- filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
349
- }
350
-
351
314
  .flowdrop-simple-node__title {
352
315
  font-size: 0.875rem;
353
316
  font-weight: 500;
@@ -365,19 +328,6 @@
365
328
  line-height: 1.3;
366
329
  }
367
330
 
368
- /* Compact layout text constraints */
369
- .flowdrop-simple-node--compact .flowdrop-simple-node__title {
370
- overflow: hidden;
371
- text-overflow: ellipsis;
372
- white-space: nowrap;
373
- }
374
-
375
- .flowdrop-simple-node--compact .flowdrop-simple-node__description {
376
- overflow: hidden;
377
- text-overflow: ellipsis;
378
- white-space: nowrap;
379
- }
380
-
381
331
  :global(.flowdrop-simple-node__icon) {
382
332
  color: white;
383
333
  font-size: 1rem;
@@ -42,27 +42,6 @@
42
42
  return instanceOverride ?? typeDefault;
43
43
  });
44
44
 
45
- /**
46
- * Check if a trigger port is connected
47
- * @param portId - The port ID to check
48
- * @param type - Whether this is an 'input' or 'output' port
49
- */
50
- function isTriggerPortConnected(portId: string, type: 'input' | 'output'): boolean {
51
- const handleId = `${props.data.nodeId}-${type}-${portId}`;
52
- return $connectedHandles.has(handleId);
53
- }
54
-
55
- /**
56
- * Check if a trigger port should be visible
57
- * Always shows if hideUnconnectedHandles is disabled or if port is connected
58
- */
59
- function shouldShowTriggerPort(portId: string, type: 'input' | 'output'): boolean {
60
- if (!hideUnconnectedHandles()) {
61
- return true;
62
- }
63
- return isTriggerPortConnected(portId, type);
64
- }
65
-
66
45
  // Removed local config state - now using global ConfigSidebar
67
46
 
68
47
  // Prioritize metadata icon over config icon for square nodes (metadata is the node definition)
@@ -111,6 +90,27 @@
111
90
  openConfigSidebar();
112
91
  }
113
92
  }
93
+ /**
94
+ * Check if a port is connected
95
+ * @param portId - The port ID to check
96
+ * @param type - Whether this is an 'input' or 'output' port
97
+ * @returns true if the port is connected
98
+ */
99
+ function isPortConnected(portId: string, type: 'input' | 'output'): boolean {
100
+ const handleId = `${props.data.nodeId}-${type}-${portId}`;
101
+ return $connectedHandles.has(handleId);
102
+ }
103
+
104
+ /**
105
+ * Check if a trigger port should be visible
106
+ * Always shows if hideUnconnectedHandles is disabled or if port is connected
107
+ */
108
+ function shouldShowTriggerPort(portId: string, type: 'input' | 'output'): boolean {
109
+ if (!hideUnconnectedHandles()) {
110
+ return true;
111
+ }
112
+ return isPortConnected(portId, type);
113
+ }
114
114
 
115
115
  // Get first input/output ports for square node representation
116
116
  // Special handling for trigger ports - they should always be shown if present
@@ -122,41 +122,67 @@
122
122
  );
123
123
 
124
124
  // Get first non-trigger ports for data connections
125
+ let firstConnectedDataInputPort = $derived(
126
+ props.data.metadata?.inputs?.find(
127
+ (port) => port.dataType !== 'trigger' && isPortConnected(port.id, 'input')
128
+ )
129
+ );
130
+
125
131
  let firstDataInputPort = $derived(
126
132
  props.data.metadata?.inputs?.find((port) => port.dataType !== 'trigger')
127
133
  );
134
+
135
+ let firstConnectedDataOutputPort = $derived(
136
+ props.data.metadata?.outputs?.find(
137
+ (port) => port.dataType !== 'trigger' && isPortConnected(port.id, 'output')
138
+ )
139
+ );
128
140
  let firstDataOutputPort = $derived(
129
141
  props.data.metadata?.outputs?.find((port) => port.dataType !== 'trigger')
130
142
  );
131
143
 
132
- // Check if we need to show both trigger and data ports
133
- let hasBothInputTypes = $derived(!!triggerInputPort && !!firstDataInputPort);
134
- let hasBothOutputTypes = $derived(!!triggerOutputPort && !!firstDataOutputPort);
144
+ let inputPorts = $derived.by(() => {
145
+ return [
146
+ ...(firstConnectedDataInputPort
147
+ ? [firstConnectedDataInputPort]
148
+ : firstDataInputPort
149
+ ? [firstDataInputPort]
150
+ : []),
151
+ ...(triggerInputPort && shouldShowTriggerPort(triggerInputPort.id, 'input')
152
+ ? [triggerInputPort]
153
+ : [])
154
+ ];
155
+ });
156
+ let outputPorts = $derived.by(() => {
157
+ return [
158
+ ...(firstConnectedDataOutputPort
159
+ ? [firstConnectedDataOutputPort]
160
+ : firstDataOutputPort
161
+ ? [firstDataOutputPort]
162
+ : []),
163
+ ...(triggerOutputPort && shouldShowTriggerPort(triggerOutputPort.id, 'output')
164
+ ? [triggerOutputPort]
165
+ : [])
166
+ ];
167
+ });
135
168
  </script>
136
169
 
137
170
  <!-- Input Handles -->
138
- {#if firstDataInputPort}
171
+ {#each inputPorts as port, index}
139
172
  <!-- Data Input - positioned at top-left if both types exist, otherwise center -->
140
173
  <Handle
141
174
  type="target"
142
175
  position={Position.Left}
143
176
  style="background-color: {getDataTypeColor(
144
- firstDataInputPort.dataType
145
- )}; border-color: '#ffffff'; top: {hasBothInputTypes ? '25%' : '50%'}; z-index: 30;"
146
- id={`${props.data.nodeId}-input-${firstDataInputPort.id}`}
147
- />
148
- {/if}
149
- {#if triggerInputPort && shouldShowTriggerPort(triggerInputPort.id, 'input')}
150
- <!-- Trigger Input - positioned at bottom-left (hidden if hideUnconnectedHandles enabled and not connected) -->
151
- <Handle
152
- type="target"
153
- position={Position.Left}
154
- style="background-color: {getDataTypeColor(
155
- triggerInputPort.dataType
156
- )}; border-color: '#ffffff'; top: {hasBothInputTypes ? '75%' : '50%'}; z-index: 30;"
157
- id={`${props.data.nodeId}-input-${triggerInputPort.id}`}
177
+ port.dataType
178
+ )}; border-color: '#ffffff'; top: {inputPorts.length > 1
179
+ ? index === 0
180
+ ? '25%'
181
+ : '75%'
182
+ : '50%'}; z-index: 30;"
183
+ id={`${props.data.nodeId}-input-${port.id}`}
158
184
  />
159
- {/if}
185
+ {/each}
160
186
 
161
187
  <!-- Square Node -->
162
188
  <div
@@ -204,30 +230,21 @@
204
230
  </div>
205
231
 
206
232
  <!-- Output Handles -->
207
- {#if firstDataOutputPort}
233
+ {#each outputPorts as port, index}
208
234
  <!-- Data Output - positioned at top-right if both types exist, otherwise center -->
209
235
  <Handle
210
236
  type="source"
211
237
  position={Position.Right}
212
- id={`${props.data.nodeId}-output-${firstDataOutputPort.id}`}
213
238
  style="background-color: {getDataTypeColor(
214
- firstDataOutputPort.dataType
215
- )}; border-color: '#ffffff'; top: {hasBothOutputTypes ? '25%' : '50%'}; z-index: 30;"
239
+ port.dataType
240
+ )}; border-color: '#ffffff'; top: {outputPorts.length > 1
241
+ ? index === 0
242
+ ? '25%'
243
+ : '75%'
244
+ : '50%'}; z-index: 30;"
245
+ id={`${props.data.nodeId}-output-${port.id}`}
216
246
  />
217
- {/if}
218
- {#if triggerOutputPort && shouldShowTriggerPort(triggerOutputPort.id, 'output')}
219
- <!-- Trigger Output - positioned at bottom-right (hidden if hideUnconnectedHandles enabled and not connected) -->
220
- <Handle
221
- type="source"
222
- position={Position.Right}
223
- id={`${props.data.nodeId}-output-${triggerOutputPort.id}`}
224
- style="background-color: {getDataTypeColor(
225
- triggerOutputPort.dataType
226
- )}; border-color: '#ffffff'; top: {hasBothOutputTypes ? '75%' : '50%'}; z-index: 30;"
227
- />
228
- {/if}
229
-
230
- <!-- ConfigSidebar removed - now using global ConfigSidebar in WorkflowEditor -->
247
+ {/each}
231
248
 
232
249
  <style>
233
250
  .flowdrop-square-node {
@@ -94,9 +94,7 @@
94
94
  * Derived list of visible input ports based on hideUnconnectedHandles setting
95
95
  * Now includes both static and dynamic inputs
96
96
  */
97
- const visibleInputPorts = $derived(
98
- allInputPorts.filter((port) => isPortVisible(port, 'input'))
99
- );
97
+ const visibleInputPorts = $derived(allInputPorts.filter((port) => isPortVisible(port, 'input')));
100
98
 
101
99
  /**
102
100
  * Derived list of visible output ports based on hideUnconnectedHandles setting
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import './styles/base.css';
6
6
  import './registry/builtinNodes.js';
7
- export type { NodeCategory, NodeDataType, NodePort, DynamicPort, Branch, NodeMetadata, NodeExtensions, NodeUIExtensions, ConfigValues, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType } from './types/index.js';
7
+ export type { NodeCategory, NodeDataType, NodePort, DynamicPort, Branch, NodeMetadata, NodeExtensions, NodeUIExtensions, ConfigValues, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType, HttpMethod, DynamicSchemaEndpoint, ExternalEditLink, ConfigEditOptions } from './types/index.js';
8
8
  export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig } from './types/config.js';
9
9
  export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from './types/auth.js';
10
10
  export { StaticAuthProvider, CallbackAuthProvider, NoAuthProvider } from './types/auth.js';
@@ -53,6 +53,8 @@ export { NodeExecutionService, nodeExecutionService } from './services/nodeExecu
53
53
  export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from './services/workflowStorage.js';
54
54
  export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from './services/globalSave.js';
55
55
  export { fetchPortConfig, validatePortConfig } from './services/portConfigApi.js';
56
+ export { fetchDynamicSchema, resolveExternalEditUrl, getEffectiveConfigEditOptions, clearSchemaCache, invalidateSchemaCache, hasConfigEditOptions, shouldShowExternalEdit, shouldUseDynamicSchema } from './services/dynamicSchemaService.js';
57
+ export type { DynamicSchemaResult } from './services/dynamicSchemaService.js';
56
58
  export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from './services/draftStorage.js';
57
59
  export { EdgeStylingHelper, NodeOperationsHelper, WorkflowOperationsHelper, ConfigurationHelper } from './helpers/workflowEditorHelper.js';
58
60
  export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, connectedHandles, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from './stores/workflowStore.js';
package/dist/index.js CHANGED
@@ -60,6 +60,8 @@ export { NodeExecutionService, nodeExecutionService } from './services/nodeExecu
60
60
  export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from './services/workflowStorage.js';
61
61
  export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from './services/globalSave.js';
62
62
  export { fetchPortConfig, validatePortConfig } from './services/portConfigApi.js';
63
+ // Export dynamic schema service for config edit functionality
64
+ export { fetchDynamicSchema, resolveExternalEditUrl, getEffectiveConfigEditOptions, clearSchemaCache, invalidateSchemaCache, hasConfigEditOptions, shouldShowExternalEdit, shouldUseDynamicSchema } from './services/dynamicSchemaService.js';
63
65
  // Export draft storage service
64
66
  export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from './services/draftStorage.js';
65
67
  // Export helpers