@flowdrop/flowdrop 1.14.0 → 2.0.0-beta.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/CHANGELOG.md +475 -0
- package/MIGRATION-2.0.md +472 -0
- package/README.md +23 -23
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/WorkflowAdapter.js +14 -8
- package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
- package/dist/chat/batchFeedback.d.ts +39 -0
- package/dist/chat/batchFeedback.js +51 -0
- package/dist/commands/executor.js +15 -1
- package/dist/commands/storeIntegration.svelte.d.ts +4 -1
- package/dist/commands/storeIntegration.svelte.js +26 -21
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/App.svelte +162 -192
- package/dist/components/App.svelte.d.ts +47 -8
- package/dist/components/ConfigForm.svelte +110 -66
- package/dist/components/ConfigModal.svelte +7 -2
- package/dist/components/ConnectionLine.svelte +4 -2
- package/dist/components/Navbar.svelte +61 -1
- package/dist/components/NodeSidebar.svelte +27 -45
- package/dist/components/NodeStatusOverlay.svelte +94 -6
- package/dist/components/NodeSwapPicker.svelte +10 -8
- package/dist/components/PipelineStatus.svelte +16 -67
- package/dist/components/PortCoordinateTracker.svelte +5 -6
- package/dist/components/SchemaForm.stories.svelte +1 -3
- package/dist/components/SchemaForm.svelte +45 -40
- package/dist/components/SchemaForm.svelte.d.ts +0 -8
- package/dist/components/SettingsModal.svelte +8 -3
- package/dist/components/SettingsPanel.svelte +20 -4
- package/dist/components/SwapMappingEditor.svelte +67 -49
- package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
- package/dist/components/UniversalNode.svelte +9 -7
- package/dist/components/WorkflowEditor.svelte +118 -111
- package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
- package/dist/components/chat/AIChatPanel.svelte +93 -89
- package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
- package/dist/components/chat/CommandPreview.svelte +2 -1
- package/dist/components/console/CommandConsole.svelte +7 -5
- package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
- package/dist/components/console/ConsoleInput.svelte +15 -6
- package/dist/components/console/ConsoleOutput.svelte +2 -1
- package/dist/components/form/FormArray.svelte +5 -9
- package/dist/components/form/FormArray.svelte.d.ts +2 -1
- package/dist/components/form/FormAutocomplete.svelte +29 -13
- package/dist/components/form/FormField.svelte +4 -2
- package/dist/components/form/FormFieldLight.svelte +4 -2
- package/dist/components/form/FormMarkdownEditor.svelte +9 -4
- package/dist/components/form/FormRangeField.svelte +1 -0
- package/dist/components/form/FormTemplateEditor.svelte +11 -3
- package/dist/components/form/FormToggle.svelte +5 -12
- package/dist/components/form/FormToggle.svelte.d.ts +4 -2
- package/dist/components/form/templateAutocomplete.js +1 -5
- package/dist/components/form/types.d.ts +1 -14
- package/dist/components/interrupt/FormPrompt.svelte +3 -2
- package/dist/components/interrupt/InterruptBubble.svelte +16 -17
- package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
- package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
- package/dist/components/layouts/MainLayout.svelte +20 -13
- package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
- package/dist/components/nodes/AtomNode.svelte +292 -0
- package/dist/components/nodes/AtomNode.svelte.d.ts +26 -0
- package/dist/components/nodes/GatewayNode.svelte +19 -10
- package/dist/components/nodes/IdeaNode.svelte +7 -0
- package/dist/components/nodes/SimpleNode.svelte +11 -6
- package/dist/components/nodes/SquareNode.svelte +15 -8
- package/dist/components/nodes/TerminalNode.svelte +9 -4
- package/dist/components/nodes/ToolNode.svelte +7 -1
- package/dist/components/nodes/WorkflowNode.svelte +16 -7
- package/dist/components/playground/ChatInput.svelte +11 -14
- package/dist/components/playground/ChatPanel.svelte +6 -49
- package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
- package/dist/components/playground/ControlPanel.svelte +134 -123
- package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
- package/dist/components/playground/ExecutionLogs.svelte +11 -9
- package/dist/components/playground/InputCollector.svelte +11 -9
- package/dist/components/playground/MessageStream.svelte +17 -23
- package/dist/components/playground/PipelineKanbanView.svelte +65 -6
- package/dist/components/playground/PipelinePanel.svelte +11 -5
- package/dist/components/playground/PipelineTableView.svelte +186 -44
- package/dist/components/playground/Playground.svelte +95 -92
- package/dist/components/playground/Playground.svelte.d.ts +2 -0
- package/dist/components/playground/PlaygroundApp.svelte +6 -1
- package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
- package/dist/components/playground/PlaygroundModal.svelte +13 -3
- package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
- package/dist/components/playground/PlaygroundStudio.svelte +34 -32
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
- package/dist/components/playground/SessionManager.svelte +9 -12
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
- package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
- package/dist/config/endpoints.d.ts +0 -7
- package/dist/config/endpoints.js +2 -10
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +6 -6
- package/dist/display/index.d.ts +0 -2
- package/dist/display/index.js +0 -6
- package/dist/editor/index.d.ts +19 -20
- package/dist/editor/index.js +25 -35
- package/dist/form/code.d.ts +25 -15
- package/dist/form/code.js +44 -41
- package/dist/form/fieldRegistry.d.ts +17 -13
- package/dist/form/fieldRegistry.js +32 -12
- package/dist/form/full.d.ts +17 -13
- package/dist/form/full.js +22 -27
- package/dist/form/index.d.ts +3 -3
- package/dist/form/index.js +3 -3
- package/dist/form/markdown.d.ts +13 -8
- package/dist/form/markdown.js +22 -23
- package/dist/helpers/proximityConnect.d.ts +7 -3
- package/dist/helpers/proximityConnect.js +19 -6
- package/dist/helpers/workflowEditorHelper.d.ts +12 -5
- package/dist/helpers/workflowEditorHelper.js +27 -25
- package/dist/index.d.ts +28 -24
- package/dist/index.js +27 -50
- package/dist/messages/defaults.d.ts +2 -5
- package/dist/messages/defaults.js +3 -6
- package/dist/messages/index.d.ts +0 -1
- package/dist/messages/index.js +0 -1
- package/dist/mocks/app-forms.d.ts +6 -2
- package/dist/mocks/app-forms.js +11 -4
- package/dist/openapi/v1/openapi.yaml +227 -164
- package/dist/playground/index.d.ts +2 -3
- package/dist/playground/index.js +2 -30
- package/dist/playground/mount.d.ts +15 -0
- package/dist/playground/mount.js +46 -20
- package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
- package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
- package/dist/registry/builtinFormats.d.ts +9 -18
- package/dist/registry/builtinFormats.js +9 -39
- package/dist/registry/builtinNodes.d.ts +1 -26
- package/dist/registry/builtinNodes.js +14 -50
- package/dist/registry/index.d.ts +3 -4
- package/dist/registry/index.js +4 -6
- package/dist/registry/nodeComponentRegistry.d.ts +182 -15
- package/dist/registry/nodeComponentRegistry.js +235 -17
- package/dist/registry/workflowFormatRegistry.d.ts +14 -9
- package/dist/registry/workflowFormatRegistry.js +24 -8
- package/dist/{schema → schemas}/index.d.ts +2 -2
- package/dist/{schema → schemas}/index.js +2 -2
- package/dist/schemas/v1/workflow.schema.json +53 -6
- package/dist/services/agentSpecExecutionService.js +0 -1
- package/dist/services/apiVariableService.d.ts +2 -1
- package/dist/services/apiVariableService.js +5 -22
- package/dist/services/autoSaveService.d.ts +7 -0
- package/dist/services/autoSaveService.js +6 -4
- package/dist/services/chatService.d.ts +8 -4
- package/dist/services/chatService.js +15 -15
- package/dist/services/draftStorage.d.ts +129 -13
- package/dist/services/draftStorage.js +185 -37
- package/dist/services/dynamicSchemaService.d.ts +2 -1
- package/dist/services/dynamicSchemaService.js +5 -22
- package/dist/services/globalSave.d.ts +13 -12
- package/dist/services/globalSave.js +29 -51
- package/dist/services/historyService.d.ts +9 -3
- package/dist/services/historyService.js +9 -3
- package/dist/services/interruptService.d.ts +14 -9
- package/dist/services/interruptService.js +27 -27
- package/dist/services/nodeExecutionService.d.ts +18 -3
- package/dist/services/nodeExecutionService.js +71 -45
- package/dist/services/playgroundService.d.ts +14 -9
- package/dist/services/playgroundService.js +31 -30
- package/dist/services/variableService.d.ts +2 -1
- package/dist/services/variableService.js +2 -2
- package/dist/services/workflowStorage.js +6 -6
- package/dist/stores/apiContext.d.ts +45 -0
- package/dist/stores/apiContext.js +65 -0
- package/dist/stores/categoriesStore.svelte.d.ts +28 -23
- package/dist/stores/categoriesStore.svelte.js +70 -64
- package/dist/stores/getInstance.svelte.d.ts +39 -0
- package/dist/stores/getInstance.svelte.js +65 -0
- package/dist/stores/historyStore.svelte.d.ts +77 -93
- package/dist/stores/historyStore.svelte.js +134 -160
- package/dist/stores/instanceContainer.svelte.d.ts +111 -0
- package/dist/stores/instanceContainer.svelte.js +114 -0
- package/dist/stores/interruptStore.svelte.d.ts +112 -82
- package/dist/stores/interruptStore.svelte.js +253 -226
- package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
- package/dist/stores/pipelinePanelStore.svelte.js +61 -14
- package/dist/stores/playgroundStore.svelte.d.ts +169 -216
- package/dist/stores/playgroundStore.svelte.js +515 -572
- package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
- package/dist/stores/portCoordinateStore.svelte.js +109 -98
- package/dist/stores/settingsStore.svelte.d.ts +4 -1
- package/dist/stores/settingsStore.svelte.js +47 -12
- package/dist/stores/workflowStore.svelte.d.ts +178 -213
- package/dist/stores/workflowStore.svelte.js +449 -501
- package/dist/stories/EdgeDecorator.svelte +5 -2
- package/dist/stories/NodeDecorator.svelte +5 -3
- package/dist/svelte-app.d.ts +60 -10
- package/dist/svelte-app.js +157 -53
- package/dist/types/events.d.ts +6 -3
- package/dist/types/index.d.ts +71 -6
- package/dist/types/navbar.d.ts +7 -0
- package/dist/types/playground.d.ts +18 -3
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.js +1 -0
- package/dist/utils/colors.d.ts +47 -21
- package/dist/utils/colors.js +69 -68
- package/dist/utils/connections.d.ts +9 -15
- package/dist/utils/connections.js +13 -32
- package/dist/utils/duration.d.ts +13 -0
- package/dist/utils/duration.js +45 -0
- package/dist/utils/formMerge.d.ts +36 -0
- package/dist/utils/formMerge.js +70 -0
- package/dist/utils/icons.d.ts +5 -2
- package/dist/utils/icons.js +6 -5
- package/dist/utils/nodeSwap.d.ts +6 -2
- package/dist/utils/nodeSwap.js +62 -126
- package/dist/utils/nodeTypes.d.ts +17 -8
- package/dist/utils/nodeTypes.js +27 -19
- package/dist/utils/performanceUtils.js +7 -0
- package/package.json +6 -5
- package/dist/messages/deprecation.d.ts +0 -20
- package/dist/messages/deprecation.js +0 -33
- package/dist/registry/plugin.d.ts +0 -215
- package/dist/registry/plugin.js +0 -249
- package/dist/services/api.d.ts +0 -129
- package/dist/services/api.js +0 -217
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Atom Node Component
|
|
3
|
+
Minimalist, label-only node for "supplies a value" atoms (Constant now, Cast later).
|
|
4
|
+
Renders as a pill that hugs its content with handles driven by the node's ports.
|
|
5
|
+
|
|
6
|
+
The body text and the output port's dataType are both driven by config via
|
|
7
|
+
`extensions.ui.atom` (see AtomUIConfig). This component owns no domain semantics —
|
|
8
|
+
meaning comes entirely from the node's NodeMetadata.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import { Position, Handle } from '@xyflow/svelte';
|
|
13
|
+
import type {
|
|
14
|
+
ConfigValues,
|
|
15
|
+
ConfigSchema,
|
|
16
|
+
NodeMetadata,
|
|
17
|
+
NodeExtensions,
|
|
18
|
+
NodePort,
|
|
19
|
+
AtomUIConfig,
|
|
20
|
+
WorkflowNode as WorkflowNodeType
|
|
21
|
+
} from '../../types/index.js';
|
|
22
|
+
import { getDataTypeColor } from '../../utils/colors.js';
|
|
23
|
+
import { getInstance } from '../../stores/getInstance.svelte.js';
|
|
24
|
+
import { applyPortOrder, isPortVisible } from '../../utils/portUtils.js';
|
|
25
|
+
import { ProximityConnectHelper } from '../../helpers/proximityConnect.js';
|
|
26
|
+
|
|
27
|
+
interface AtomNodeData {
|
|
28
|
+
label: string;
|
|
29
|
+
config: ConfigValues;
|
|
30
|
+
metadata: NodeMetadata;
|
|
31
|
+
nodeId?: string;
|
|
32
|
+
extensions?: NodeExtensions;
|
|
33
|
+
onConfigOpen?: (node: {
|
|
34
|
+
id: string;
|
|
35
|
+
type: string;
|
|
36
|
+
data: { label: string; config: ConfigValues; metadata: NodeMetadata };
|
|
37
|
+
}) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Props {
|
|
41
|
+
data: AtomNodeData;
|
|
42
|
+
selected?: boolean;
|
|
43
|
+
isProcessing?: boolean;
|
|
44
|
+
isError?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let { data, selected, isProcessing, isError }: Props = $props();
|
|
48
|
+
|
|
49
|
+
const fd = getInstance();
|
|
50
|
+
const checker = fd.portCompatibility;
|
|
51
|
+
|
|
52
|
+
const nodeId = $derived(data.nodeId ?? 'unknown');
|
|
53
|
+
const nodeType = $derived(data.metadata?.type ?? 'atom');
|
|
54
|
+
|
|
55
|
+
// Instance extensions override node-type defaults.
|
|
56
|
+
const atomCfg = $derived<AtomUIConfig>(
|
|
57
|
+
data.extensions?.ui?.atom ?? data.metadata?.extensions?.ui?.atom ?? {}
|
|
58
|
+
);
|
|
59
|
+
const hideUnconnectedHandles = $derived(
|
|
60
|
+
data.extensions?.ui?.hideUnconnectedHandles ??
|
|
61
|
+
data.metadata?.extensions?.ui?.hideUnconnectedHandles ??
|
|
62
|
+
false
|
|
63
|
+
);
|
|
64
|
+
const hiddenPorts = $derived(
|
|
65
|
+
data.extensions?.ui?.hiddenPorts ?? data.metadata?.extensions?.ui?.hiddenPorts ?? {}
|
|
66
|
+
);
|
|
67
|
+
const portOrder = $derived(
|
|
68
|
+
data.extensions?.ui?.portOrder ?? data.metadata?.extensions?.ui?.portOrder ?? {}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Optional, server-driven accent. When unset the inline custom property is
|
|
72
|
+
// omitted, so CSS falls back to the neutral border — uncolored atoms are
|
|
73
|
+
// unchanged. Mirrors ToolNode: node definition (metadata) wins over instance.
|
|
74
|
+
const atomColor = $derived(data.metadata?.color ?? (data.config?.color as string | undefined));
|
|
75
|
+
const isRect = $derived(atomCfg.shape === 'rectangle');
|
|
76
|
+
|
|
77
|
+
// Only the dynamic bits live inline; max-width and accent both optional.
|
|
78
|
+
const nodeStyle = $derived(
|
|
79
|
+
[
|
|
80
|
+
atomCfg.maxWidth ? `max-width: ${atomCfg.maxWidth}px` : '',
|
|
81
|
+
atomColor ? `--fd-atom-node-color: ${atomColor}` : ''
|
|
82
|
+
]
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join('; ')
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// The node slice getAllPorts needs — keeps port resolution in one place,
|
|
88
|
+
// shared with proximity-connect and coordinate/validation logic. No cast: the
|
|
89
|
+
// helper's signature documents that `type` + `data` are all it reads.
|
|
90
|
+
const nodeLike = $derived<Pick<WorkflowNodeType, 'type' | 'data'>>({ type: nodeType, data });
|
|
91
|
+
|
|
92
|
+
const inPorts = $derived(
|
|
93
|
+
applyPortOrder(ProximityConnectHelper.getAllPorts(nodeLike, 'input'), portOrder.inputs).filter(
|
|
94
|
+
(p: NodePort) =>
|
|
95
|
+
isPortVisible(
|
|
96
|
+
p,
|
|
97
|
+
'input',
|
|
98
|
+
hiddenPorts,
|
|
99
|
+
hideUnconnectedHandles,
|
|
100
|
+
fd.workflow.connectedHandles,
|
|
101
|
+
nodeId
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
const outPorts = $derived(
|
|
106
|
+
applyPortOrder(
|
|
107
|
+
ProximityConnectHelper.getAllPorts(nodeLike, 'output'),
|
|
108
|
+
portOrder.outputs
|
|
109
|
+
).filter((p: NodePort) =>
|
|
110
|
+
isPortVisible(
|
|
111
|
+
p,
|
|
112
|
+
'output',
|
|
113
|
+
hiddenPorts,
|
|
114
|
+
hideUnconnectedHandles,
|
|
115
|
+
fd.workflow.connectedHandles,
|
|
116
|
+
nodeId
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
/** Friendly label for a value, using the field's oneOf titles when present. */
|
|
122
|
+
function resolveDisplay(schema: ConfigSchema | undefined, key: string, raw: unknown): string {
|
|
123
|
+
const prop = schema?.properties?.[key] as
|
|
124
|
+
| { oneOf?: Array<{ const: unknown; title?: string }> }
|
|
125
|
+
| undefined;
|
|
126
|
+
const match = prop?.oneOf?.find((o) => o.const === raw);
|
|
127
|
+
if (match?.title) return match.title;
|
|
128
|
+
if (typeof raw === 'boolean') return raw ? 'true' : 'false';
|
|
129
|
+
return String(raw);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Body text: config[valueKey] (label-resolved), else node label. When neither
|
|
133
|
+
// resolves, fall back to the placeholder and render it dimmed.
|
|
134
|
+
const display = $derived.by(() => {
|
|
135
|
+
const key = atomCfg.valueKey;
|
|
136
|
+
const raw = key ? data.config?.[key] : undefined;
|
|
137
|
+
if (key && raw !== undefined && raw !== null && raw !== '') {
|
|
138
|
+
return { text: resolveDisplay(data.metadata?.configSchema, key, raw), empty: false };
|
|
139
|
+
}
|
|
140
|
+
if (data.label) return { text: data.label, empty: false };
|
|
141
|
+
return { text: atomCfg.placeholder ?? '', empty: true };
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Pill height is content-driven (~28px), so fixed px offsets don't fit.
|
|
145
|
+
// Distribute handles as a % of node height: 50% for one port, evenly otherwise.
|
|
146
|
+
function portTopPct(index: number, count: number): number {
|
|
147
|
+
return ((index + 1) / (count + 1)) * 100;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function openConfig(): void {
|
|
151
|
+
data.onConfigOpen?.({ id: nodeId, type: nodeType, data });
|
|
152
|
+
}
|
|
153
|
+
function handleKeydown(event: KeyboardEvent): void {
|
|
154
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
openConfig();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
{#each inPorts as port, index (port.id)}
|
|
162
|
+
<Handle
|
|
163
|
+
type="target"
|
|
164
|
+
position={Position.Left}
|
|
165
|
+
id={`${nodeId}-input-${port.id}`}
|
|
166
|
+
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
167
|
+
checker,
|
|
168
|
+
port.dataType
|
|
169
|
+
)}); top: {portTopPct(index, inPorts.length)}%;"
|
|
170
|
+
/>
|
|
171
|
+
{/each}
|
|
172
|
+
|
|
173
|
+
<div
|
|
174
|
+
class="flowdrop-atom-node"
|
|
175
|
+
class:flowdrop-atom-node--selected={selected}
|
|
176
|
+
class:flowdrop-atom-node--processing={isProcessing}
|
|
177
|
+
class:flowdrop-atom-node--error={isError}
|
|
178
|
+
class:flowdrop-atom-node--empty={display.empty}
|
|
179
|
+
class:flowdrop-atom-node--rect={isRect}
|
|
180
|
+
style={nodeStyle}
|
|
181
|
+
onclick={openConfig}
|
|
182
|
+
onkeydown={handleKeydown}
|
|
183
|
+
role="button"
|
|
184
|
+
tabindex="0"
|
|
185
|
+
>
|
|
186
|
+
{#if atomCfg.prefix && !display.empty}
|
|
187
|
+
<span class="flowdrop-atom-node__prefix" aria-hidden="true">{atomCfg.prefix}</span>
|
|
188
|
+
{/if}
|
|
189
|
+
<span class="flowdrop-atom-node__body" title={display.text}>{display.text}</span>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{#each outPorts as port, index (port.id)}
|
|
193
|
+
<Handle
|
|
194
|
+
type="source"
|
|
195
|
+
position={Position.Right}
|
|
196
|
+
id={`${nodeId}-output-${port.id}`}
|
|
197
|
+
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
198
|
+
checker,
|
|
199
|
+
port.dataType
|
|
200
|
+
)}); top: {portTopPct(index, outPorts.length)}%;"
|
|
201
|
+
/>
|
|
202
|
+
{/each}
|
|
203
|
+
|
|
204
|
+
<style>
|
|
205
|
+
.flowdrop-atom-node {
|
|
206
|
+
position: relative;
|
|
207
|
+
display: inline-flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
width: fit-content;
|
|
210
|
+
min-width: 2rem;
|
|
211
|
+
min-height: 28px;
|
|
212
|
+
padding: 2px var(--fd-space-sm);
|
|
213
|
+
background-color: var(--fd-card);
|
|
214
|
+
/* --fd-atom-node-color is set inline only when the server provides a color;
|
|
215
|
+
otherwise it falls back to the neutral border token. */
|
|
216
|
+
border: 1.5px solid var(--fd-atom-node-color, var(--fd-node-border));
|
|
217
|
+
border-radius: 999px;
|
|
218
|
+
box-shadow: var(--fd-shadow-sm);
|
|
219
|
+
color: var(--fd-foreground);
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition:
|
|
222
|
+
box-shadow var(--fd-transition-fast),
|
|
223
|
+
border-color var(--fd-transition-fast);
|
|
224
|
+
z-index: 10;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.flowdrop-atom-node--rect {
|
|
228
|
+
border-radius: var(--fd-radius-md);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.flowdrop-atom-node:hover {
|
|
232
|
+
box-shadow: var(--fd-shadow-md);
|
|
233
|
+
border-color: var(--fd-atom-node-color, var(--fd-node-border-hover));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.flowdrop-atom-node--selected {
|
|
237
|
+
box-shadow:
|
|
238
|
+
0 0 0 2px color-mix(in srgb, var(--fd-atom-node-color, var(--fd-primary)) 30%, transparent),
|
|
239
|
+
var(--fd-shadow-md);
|
|
240
|
+
border-color: var(--fd-atom-node-color, var(--fd-primary));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.flowdrop-atom-node:focus-visible {
|
|
244
|
+
outline: 2px solid var(--fd-ring);
|
|
245
|
+
outline-offset: 2px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.flowdrop-atom-node--processing {
|
|
249
|
+
opacity: 0.7;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.flowdrop-atom-node--error {
|
|
253
|
+
border-color: var(--fd-error) !important;
|
|
254
|
+
background-color: var(--fd-error-muted) !important;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.flowdrop-atom-node--empty .flowdrop-atom-node__body {
|
|
258
|
+
color: var(--fd-muted-foreground);
|
|
259
|
+
font-style: italic;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.flowdrop-atom-node__prefix {
|
|
263
|
+
flex-shrink: 0;
|
|
264
|
+
margin-right: 2px;
|
|
265
|
+
color: var(--fd-muted-foreground);
|
|
266
|
+
font-size: var(--fd-text-sm);
|
|
267
|
+
line-height: 1.2;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.flowdrop-atom-node__body {
|
|
271
|
+
/* min-width:0 lets the body ellipsize as a flex sibling of the prefix */
|
|
272
|
+
min-width: 0;
|
|
273
|
+
font-size: var(--fd-text-sm);
|
|
274
|
+
line-height: 1.2;
|
|
275
|
+
white-space: nowrap;
|
|
276
|
+
overflow: hidden;
|
|
277
|
+
text-overflow: ellipsis;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* `top` is set inline (dynamic, must beat svelte-flow defaults); transform and
|
|
281
|
+
stacking live here so the hover rule can compose instead of fighting !important. */
|
|
282
|
+
:global(.svelte-flow__node-atom .svelte-flow__handle) {
|
|
283
|
+
--fd-handle-border-color: var(--fd-handle-border);
|
|
284
|
+
transform: translateY(-50%);
|
|
285
|
+
z-index: 20 !important;
|
|
286
|
+
pointer-events: auto !important;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
:global(.svelte-flow__node-atom .svelte-flow__handle:hover) {
|
|
290
|
+
transform: translateY(-50%) scale(1.2);
|
|
291
|
+
}
|
|
292
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ConfigValues, NodeMetadata, NodeExtensions } from '../../types/index.js';
|
|
2
|
+
interface AtomNodeData {
|
|
3
|
+
label: string;
|
|
4
|
+
config: ConfigValues;
|
|
5
|
+
metadata: NodeMetadata;
|
|
6
|
+
nodeId?: string;
|
|
7
|
+
extensions?: NodeExtensions;
|
|
8
|
+
onConfigOpen?: (node: {
|
|
9
|
+
id: string;
|
|
10
|
+
type: string;
|
|
11
|
+
data: {
|
|
12
|
+
label: string;
|
|
13
|
+
config: ConfigValues;
|
|
14
|
+
metadata: NodeMetadata;
|
|
15
|
+
};
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
interface Props {
|
|
19
|
+
data: AtomNodeData;
|
|
20
|
+
selected?: boolean;
|
|
21
|
+
isProcessing?: boolean;
|
|
22
|
+
isError?: boolean;
|
|
23
|
+
}
|
|
24
|
+
declare const AtomNode: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type AtomNode = ReturnType<typeof AtomNode>;
|
|
26
|
+
export default AtomNode;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
getCategoryColorToken,
|
|
19
19
|
getPortBackgroundColor
|
|
20
20
|
} from '../../utils/colors.js';
|
|
21
|
-
import {
|
|
21
|
+
import { getInstance } from '../../stores/getInstance.svelte.js';
|
|
22
22
|
import { m } from '../../messages/index.js';
|
|
23
23
|
|
|
24
24
|
interface Props {
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
|
|
32
32
|
let props: Props = $props();
|
|
33
33
|
|
|
34
|
+
const fd = getInstance();
|
|
35
|
+
const checker = fd.portCompatibility;
|
|
36
|
+
|
|
34
37
|
// Hoist the graph branch — three reads in the template, two inside
|
|
35
38
|
// {#each port} / {#each branch} loops. One getter walk per render.
|
|
36
39
|
const graph = $derived(m().nodes.graph);
|
|
@@ -80,7 +83,7 @@
|
|
|
80
83
|
|
|
81
84
|
// Check if port is connected
|
|
82
85
|
const handleId = `${props.data.nodeId}-${type}-${port.id}`;
|
|
83
|
-
return
|
|
86
|
+
return fd.workflow.connectedHandles.has(handleId);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
/**
|
|
@@ -103,7 +106,7 @@
|
|
|
103
106
|
|
|
104
107
|
// Check if branch output is connected
|
|
105
108
|
const handleId = `${props.data.nodeId}-output-${branchName}`;
|
|
106
|
-
return
|
|
109
|
+
return fd.workflow.connectedHandles.has(handleId);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
// Gateway-specific data - branches are calculated at runtime from config
|
|
@@ -173,10 +176,10 @@
|
|
|
173
176
|
<!-- Node Icon with Squircle Background -->
|
|
174
177
|
<div
|
|
175
178
|
class="flowdrop-workflow-node__icon-wrapper"
|
|
176
|
-
style="--_icon-color: {getCategoryColorToken(props.data.metadata.category)}"
|
|
179
|
+
style="--_icon-color: {getCategoryColorToken(fd.categories, props.data.metadata.category)}"
|
|
177
180
|
>
|
|
178
181
|
<Icon
|
|
179
|
-
icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)}
|
|
182
|
+
icon={getNodeIcon(fd.categories, props.data.metadata.icon, props.data.metadata.category)}
|
|
180
183
|
class="flowdrop-workflow-node__icon"
|
|
181
184
|
/>
|
|
182
185
|
</div>
|
|
@@ -208,6 +211,7 @@
|
|
|
208
211
|
id={`${props.data.nodeId}-input-${port.id}`}
|
|
209
212
|
class="flowdrop-workflow-node__handle"
|
|
210
213
|
style="top: 50%; transform: translateY(-50%); --fd-handle-fill: {getDataTypeColorToken(
|
|
214
|
+
checker,
|
|
211
215
|
port.dataType
|
|
212
216
|
)}; --fd-handle-border-color: var(--fd-handle-border);"
|
|
213
217
|
tabindex={-1}
|
|
@@ -220,11 +224,13 @@
|
|
|
220
224
|
<span
|
|
221
225
|
class="flowdrop-badge flowdrop-badge--sm"
|
|
222
226
|
style="background-color: {getPortBackgroundColor(
|
|
227
|
+
checker,
|
|
223
228
|
port.dataType,
|
|
224
229
|
15
|
|
225
230
|
)}; color: {getDataTypeColorToken(
|
|
231
|
+
checker,
|
|
226
232
|
port.dataType
|
|
227
|
-
)}; border: 1px solid {getPortBackgroundColor(port.dataType, 30)};"
|
|
233
|
+
)}; border: 1px solid {getPortBackgroundColor(checker, port.dataType, 30)};"
|
|
228
234
|
>
|
|
229
235
|
{port.dataType}
|
|
230
236
|
</span>
|
|
@@ -250,7 +256,7 @@
|
|
|
250
256
|
{#if visibleBranches.length > 0}
|
|
251
257
|
<div class="flowdrop-workflow-node__ports">
|
|
252
258
|
<div class="flowdrop-workflow-node__ports-list">
|
|
253
|
-
{#each visibleBranches as branch
|
|
259
|
+
{#each visibleBranches as branch (branch.name)}
|
|
254
260
|
{@const isActive = isBranchActive(branch.name)}
|
|
255
261
|
<div class="flowdrop-workflow-node__port">
|
|
256
262
|
<!-- Port Info: padding lives here so handle position is simple -->
|
|
@@ -261,7 +267,7 @@
|
|
|
261
267
|
class="flowdrop-flex flowdrop-gap--2 flowdrop-justify--end flowdrop-items--center"
|
|
262
268
|
>
|
|
263
269
|
{#if isActive}
|
|
264
|
-
<span style="color: {getDataTypeColorToken('trigger')};">
|
|
270
|
+
<span style="color: {getDataTypeColorToken(checker, 'trigger')};">
|
|
265
271
|
<Icon icon="mdi:check-circle" />
|
|
266
272
|
</span>
|
|
267
273
|
{/if}
|
|
@@ -274,11 +280,13 @@
|
|
|
274
280
|
<span
|
|
275
281
|
class="flowdrop-badge flowdrop-badge--sm"
|
|
276
282
|
style="background-color: {getPortBackgroundColor(
|
|
283
|
+
checker,
|
|
277
284
|
'trigger',
|
|
278
285
|
15
|
|
279
286
|
)}; color: {getDataTypeColorToken(
|
|
287
|
+
checker,
|
|
280
288
|
'trigger'
|
|
281
|
-
)}; border: 1px solid {getPortBackgroundColor('trigger', 30)};"
|
|
289
|
+
)}; border: 1px solid {getPortBackgroundColor(checker, 'trigger', 30)};"
|
|
282
290
|
>
|
|
283
291
|
trigger
|
|
284
292
|
</span>
|
|
@@ -292,8 +300,9 @@
|
|
|
292
300
|
id={`${props.data.nodeId}-output-${branch.name}`}
|
|
293
301
|
class={`flowdrop-workflow-node__handle ${isActive ? 'flowdrop-workflow-node__handle--active' : ''}`}
|
|
294
302
|
style="top: 50%; transform: translateY(-50%); --fd-handle-fill: {isActive
|
|
295
|
-
? getDataTypeColorToken('trigger')
|
|
303
|
+
? getDataTypeColorToken(checker, 'trigger')
|
|
296
304
|
: getDataTypeColorToken(
|
|
305
|
+
checker,
|
|
297
306
|
'trigger'
|
|
298
307
|
)}; --fd-handle-border-color: var(--fd-handle-border);"
|
|
299
308
|
tabindex={-1}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import type { ConfigValues, NodeMetadata } from '../../types/index.js';
|
|
12
12
|
import Icon from '@iconify/svelte';
|
|
13
13
|
import { getDataTypeColor } from '../../utils/colors.js';
|
|
14
|
+
import { getInstance } from '../../stores/getInstance.svelte.js';
|
|
14
15
|
import { m } from '../../messages/index.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -34,6 +35,8 @@
|
|
|
34
35
|
isError?: boolean;
|
|
35
36
|
}>();
|
|
36
37
|
|
|
38
|
+
const checker = getInstance().portCompatibility;
|
|
39
|
+
|
|
37
40
|
/**
|
|
38
41
|
* Instance-specific title override from config.
|
|
39
42
|
* Falls back to the original label if not set.
|
|
@@ -151,6 +154,7 @@
|
|
|
151
154
|
type="target"
|
|
152
155
|
position={Position.Left}
|
|
153
156
|
style="--fd-handle-fill: {getDataTypeColor(
|
|
157
|
+
checker,
|
|
154
158
|
IDEA_DATA_TYPE
|
|
155
159
|
)}; --fd-handle-border-color: var(--fd-handle-border); top: 40px; transform: translateY(-50%); z-index: 30;"
|
|
156
160
|
id={`${props.data.nodeId}-input-left`}
|
|
@@ -163,6 +167,7 @@
|
|
|
163
167
|
type="target"
|
|
164
168
|
position={Position.Top}
|
|
165
169
|
style="--fd-handle-fill: {getDataTypeColor(
|
|
170
|
+
checker,
|
|
166
171
|
IDEA_DATA_TYPE
|
|
167
172
|
)}; --fd-handle-border-color: var(--fd-handle-border); left: 150px; transform: translateX(-50%); z-index: 30;"
|
|
168
173
|
id={`${props.data.nodeId}-input-top`}
|
|
@@ -215,6 +220,7 @@
|
|
|
215
220
|
type="source"
|
|
216
221
|
position={Position.Right}
|
|
217
222
|
style="--fd-handle-fill: {getDataTypeColor(
|
|
223
|
+
checker,
|
|
218
224
|
IDEA_DATA_TYPE
|
|
219
225
|
)}; --fd-handle-border-color: var(--fd-handle-border); top: 40px; transform: translateY(-50%); z-index: 30;"
|
|
220
226
|
id={`${props.data.nodeId}-output-right`}
|
|
@@ -227,6 +233,7 @@
|
|
|
227
233
|
type="source"
|
|
228
234
|
position={Position.Bottom}
|
|
229
235
|
style="--fd-handle-fill: {getDataTypeColor(
|
|
236
|
+
checker,
|
|
230
237
|
IDEA_DATA_TYPE
|
|
231
238
|
)}; --fd-handle-border-color: var(--fd-handle-border); left: 150px; transform: translateX(-50%); z-index: 30;"
|
|
232
239
|
id={`${props.data.nodeId}-output-bottom`}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { dynamicPortToNodePort } from '../../types/index.js';
|
|
22
22
|
import Icon from '@iconify/svelte';
|
|
23
23
|
import { getDataTypeColor, getCategoryColorToken } from '../../utils/colors.js';
|
|
24
|
-
import {
|
|
24
|
+
import { getInstance } from '../../stores/getInstance.svelte.js';
|
|
25
25
|
import { applyPortOrder, getPortTop, isPortVisible } from '../../utils/portUtils.js';
|
|
26
26
|
import CogIcon from '../icons/CogIcon.svelte';
|
|
27
27
|
import AlertCircleIcon from '../icons/AlertCircleIcon.svelte';
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
isError?: boolean;
|
|
45
45
|
}>();
|
|
46
46
|
|
|
47
|
+
const fd = getInstance();
|
|
48
|
+
const checker = fd.portCompatibility;
|
|
49
|
+
|
|
47
50
|
/**
|
|
48
51
|
* Get UI extension settings from extensions, merging node type defaults with instance overrides.
|
|
49
52
|
*/
|
|
@@ -141,7 +144,7 @@
|
|
|
141
144
|
'input',
|
|
142
145
|
hiddenPorts,
|
|
143
146
|
hideUnconnectedHandles,
|
|
144
|
-
|
|
147
|
+
fd.workflow.connectedHandles,
|
|
145
148
|
props.data.nodeId
|
|
146
149
|
)
|
|
147
150
|
)
|
|
@@ -160,7 +163,7 @@
|
|
|
160
163
|
'output',
|
|
161
164
|
hiddenPorts,
|
|
162
165
|
hideUnconnectedHandles,
|
|
163
|
-
|
|
166
|
+
fd.workflow.connectedHandles,
|
|
164
167
|
props.data.nodeId
|
|
165
168
|
)
|
|
166
169
|
)
|
|
@@ -178,11 +181,12 @@
|
|
|
178
181
|
</script>
|
|
179
182
|
|
|
180
183
|
<!-- Input Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
181
|
-
{#each visibleInputPorts as port, index}
|
|
184
|
+
{#each visibleInputPorts as port, index (port.id)}
|
|
182
185
|
<Handle
|
|
183
186
|
type="target"
|
|
184
187
|
position={Position.Left}
|
|
185
188
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
189
|
+
checker,
|
|
186
190
|
port.dataType
|
|
187
191
|
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
188
192
|
index,
|
|
@@ -214,7 +218,7 @@
|
|
|
214
218
|
<!-- Node Icon (circle dot) — visibility controlled by --fd-node-circle-display -->
|
|
215
219
|
<span
|
|
216
220
|
class="flowdrop-simple-node__color-dot"
|
|
217
|
-
style="background: {getCategoryColorToken(props.data.metadata?.category)}"
|
|
221
|
+
style="background: {getCategoryColorToken(fd.categories, props.data.metadata?.category)}"
|
|
218
222
|
></span>
|
|
219
223
|
|
|
220
224
|
<!-- Node Title -->
|
|
@@ -256,11 +260,12 @@
|
|
|
256
260
|
</div>
|
|
257
261
|
|
|
258
262
|
<!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
259
|
-
{#each visibleOutputPorts as port, index}
|
|
263
|
+
{#each visibleOutputPorts as port, index (port.id)}
|
|
260
264
|
<Handle
|
|
261
265
|
type="source"
|
|
262
266
|
position={Position.Right}
|
|
263
267
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
268
|
+
checker,
|
|
264
269
|
port.dataType
|
|
265
270
|
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
266
271
|
index,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import Icon from '@iconify/svelte';
|
|
23
23
|
import { getDataTypeColor, getCategoryColorToken } from '../../utils/colors.js';
|
|
24
24
|
import { getNodeIcon } from '../../utils/icons.js';
|
|
25
|
-
import {
|
|
25
|
+
import { getInstance } from '../../stores/getInstance.svelte.js';
|
|
26
26
|
import { applyPortOrder, getPortTop, isPortVisible } from '../../utils/portUtils.js';
|
|
27
27
|
import CogIcon from '../icons/CogIcon.svelte';
|
|
28
28
|
import AlertCircleIcon from '../icons/AlertCircleIcon.svelte';
|
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
isError?: boolean;
|
|
46
46
|
}>();
|
|
47
47
|
|
|
48
|
+
const fd = getInstance();
|
|
49
|
+
const checker = fd.portCompatibility;
|
|
50
|
+
|
|
48
51
|
/**
|
|
49
52
|
* Get UI extension settings from extensions, merging node type defaults with instance overrides.
|
|
50
53
|
*/
|
|
@@ -66,13 +69,15 @@
|
|
|
66
69
|
* Get icon using the same resolution as WorkflowNode
|
|
67
70
|
* Uses getNodeIcon utility with category fallback
|
|
68
71
|
*/
|
|
69
|
-
let squareIcon = $derived(
|
|
72
|
+
let squareIcon = $derived(
|
|
73
|
+
getNodeIcon(fd.categories, props.data.metadata?.icon, props.data.metadata?.category)
|
|
74
|
+
);
|
|
70
75
|
|
|
71
76
|
/**
|
|
72
77
|
* Get icon color using category-based color tokens for consistency
|
|
73
78
|
* Falls back to primary color if category not available
|
|
74
79
|
*/
|
|
75
|
-
let squareColor = $derived(getCategoryColorToken(props.data.metadata?.category));
|
|
80
|
+
let squareColor = $derived(getCategoryColorToken(fd.categories, props.data.metadata?.category));
|
|
76
81
|
|
|
77
82
|
// Handle configuration sidebar - now using global ConfigSidebar
|
|
78
83
|
function openConfigSidebar(): void {
|
|
@@ -129,7 +134,7 @@
|
|
|
129
134
|
'input',
|
|
130
135
|
hiddenPorts,
|
|
131
136
|
hideUnconnectedHandles,
|
|
132
|
-
|
|
137
|
+
fd.workflow.connectedHandles,
|
|
133
138
|
props.data.nodeId
|
|
134
139
|
)
|
|
135
140
|
)
|
|
@@ -148,7 +153,7 @@
|
|
|
148
153
|
'output',
|
|
149
154
|
hiddenPorts,
|
|
150
155
|
hideUnconnectedHandles,
|
|
151
|
-
|
|
156
|
+
fd.workflow.connectedHandles,
|
|
152
157
|
props.data.nodeId
|
|
153
158
|
)
|
|
154
159
|
)
|
|
@@ -167,11 +172,12 @@
|
|
|
167
172
|
</script>
|
|
168
173
|
|
|
169
174
|
<!-- Input Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
170
|
-
{#each visibleInputPorts as port, index}
|
|
175
|
+
{#each visibleInputPorts as port, index (port.id)}
|
|
171
176
|
<Handle
|
|
172
177
|
type="target"
|
|
173
178
|
position={Position.Left}
|
|
174
179
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
180
|
+
checker,
|
|
175
181
|
port.dataType
|
|
176
182
|
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
177
183
|
index,
|
|
@@ -203,7 +209,7 @@
|
|
|
203
209
|
<!-- Circle dot — visibility controlled by --fd-node-circle-display -->
|
|
204
210
|
<span
|
|
205
211
|
class="flowdrop-square-node__color-dot"
|
|
206
|
-
style="background: {getCategoryColorToken(props.data.metadata?.category)}"
|
|
212
|
+
style="background: {getCategoryColorToken(fd.categories, props.data.metadata?.category)}"
|
|
207
213
|
></span>
|
|
208
214
|
</div>
|
|
209
215
|
|
|
@@ -232,11 +238,12 @@
|
|
|
232
238
|
</div>
|
|
233
239
|
|
|
234
240
|
<!-- Output Handles: 1 port centered at 40px; N ports at 20px start, 40px gap -->
|
|
235
|
-
{#each visibleOutputPorts as port, index}
|
|
241
|
+
{#each visibleOutputPorts as port, index (port.id)}
|
|
236
242
|
<Handle
|
|
237
243
|
type="source"
|
|
238
244
|
position={Position.Right}
|
|
239
245
|
style="--fd-handle-fill: var(--fd-port-skin-color, {getDataTypeColor(
|
|
246
|
+
checker,
|
|
240
247
|
port.dataType
|
|
241
248
|
)}); --fd-handle-border-color: var(--fd-handle-border); top: {getPortTop(
|
|
242
249
|
index,
|