@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.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.
Files changed (235) hide show
  1. package/CHANGELOG.md +508 -0
  2. package/MIGRATION-2.0.md +629 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/api/enhanced-client.js +6 -11
  8. package/dist/chat/batchFeedback.d.ts +39 -0
  9. package/dist/chat/batchFeedback.js +51 -0
  10. package/dist/commands/executor.js +15 -1
  11. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  12. package/dist/commands/storeIntegration.svelte.js +26 -21
  13. package/dist/commands/types.d.ts +2 -0
  14. package/dist/components/App.svelte +163 -192
  15. package/dist/components/App.svelte.d.ts +47 -8
  16. package/dist/components/ConfigForm.svelte +77 -49
  17. package/dist/components/ConfigModal.svelte +7 -2
  18. package/dist/components/ConnectionLine.svelte +4 -2
  19. package/dist/components/Navbar.svelte +61 -1
  20. package/dist/components/NodeSidebar.svelte +27 -45
  21. package/dist/components/NodeStatusOverlay.svelte +94 -6
  22. package/dist/components/NodeSwapPicker.svelte +10 -8
  23. package/dist/components/PipelineStatus.svelte +22 -68
  24. package/dist/components/PipelineStatus.svelte.d.ts +3 -0
  25. package/dist/components/PortCoordinateTracker.svelte +5 -6
  26. package/dist/components/SchemaForm.stories.svelte +1 -3
  27. package/dist/components/SchemaForm.svelte +22 -27
  28. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  29. package/dist/components/SettingsModal.svelte +8 -3
  30. package/dist/components/SettingsPanel.svelte +20 -4
  31. package/dist/components/SwapMappingEditor.svelte +67 -49
  32. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  33. package/dist/components/UniversalNode.svelte +9 -7
  34. package/dist/components/WorkflowEditor.svelte +121 -111
  35. package/dist/components/WorkflowEditor.svelte.d.ts +21 -10
  36. package/dist/components/chat/AIChatPanel.svelte +98 -89
  37. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  38. package/dist/components/chat/CommandPreview.svelte +2 -1
  39. package/dist/components/console/CommandConsole.svelte +7 -5
  40. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  41. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  42. package/dist/components/console/ConsoleInput.svelte +15 -6
  43. package/dist/components/console/ConsoleOutput.svelte +2 -1
  44. package/dist/components/form/FormArray.svelte +5 -9
  45. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  46. package/dist/components/form/FormAutocomplete.svelte +16 -15
  47. package/dist/components/form/FormField.svelte +4 -2
  48. package/dist/components/form/FormFieldLight.svelte +34 -3
  49. package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
  50. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  51. package/dist/components/form/FormRangeField.svelte +1 -0
  52. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  53. package/dist/components/form/FormToggle.svelte +5 -12
  54. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  55. package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
  56. package/dist/components/form/templateAutocomplete.js +1 -5
  57. package/dist/components/form/types.d.ts +1 -14
  58. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  59. package/dist/components/interrupt/InterruptBubble.svelte +25 -17
  60. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  61. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  62. package/dist/components/layouts/MainLayout.svelte +20 -13
  63. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  64. package/dist/components/nodes/AtomNode.svelte +17 -5
  65. package/dist/components/nodes/GatewayNode.svelte +19 -10
  66. package/dist/components/nodes/IdeaNode.svelte +7 -0
  67. package/dist/components/nodes/SimpleNode.svelte +11 -6
  68. package/dist/components/nodes/SquareNode.svelte +15 -8
  69. package/dist/components/nodes/TerminalNode.svelte +9 -4
  70. package/dist/components/nodes/ToolNode.svelte +7 -1
  71. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  72. package/dist/components/playground/ChatInput.svelte +11 -14
  73. package/dist/components/playground/ChatPanel.svelte +6 -49
  74. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  75. package/dist/components/playground/ControlPanel.svelte +134 -123
  76. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  77. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  78. package/dist/components/playground/InputCollector.svelte +11 -9
  79. package/dist/components/playground/MessageStream.svelte +17 -23
  80. package/dist/components/playground/PipelineKanbanView.svelte +69 -8
  81. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
  82. package/dist/components/playground/PipelinePanel.svelte +31 -8
  83. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
  84. package/dist/components/playground/PipelineTableView.svelte +188 -44
  85. package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
  86. package/dist/components/playground/Playground.svelte +154 -105
  87. package/dist/components/playground/Playground.svelte.d.ts +5 -0
  88. package/dist/components/playground/PlaygroundApp.svelte +11 -1
  89. package/dist/components/playground/PlaygroundApp.svelte.d.ts +6 -0
  90. package/dist/components/playground/PlaygroundModal.svelte +18 -3
  91. package/dist/components/playground/PlaygroundModal.svelte.d.ts +6 -0
  92. package/dist/components/playground/PlaygroundStudio.svelte +40 -32
  93. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +6 -0
  94. package/dist/components/playground/SessionManager.svelte +9 -12
  95. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +30 -1
  96. package/dist/components/playground/pipelineViewUtils.svelte.js +40 -3
  97. package/dist/config/endpoints.d.ts +23 -7
  98. package/dist/config/endpoints.js +30 -10
  99. package/dist/core/index.d.ts +5 -6
  100. package/dist/core/index.js +8 -12
  101. package/dist/display/index.d.ts +6 -3
  102. package/dist/display/index.js +7 -5
  103. package/dist/editor/index.d.ts +20 -21
  104. package/dist/editor/index.js +26 -36
  105. package/dist/form/code.d.ts +25 -15
  106. package/dist/form/code.js +44 -41
  107. package/dist/form/fieldRegistry.d.ts +17 -13
  108. package/dist/form/fieldRegistry.js +32 -12
  109. package/dist/form/full.d.ts +19 -14
  110. package/dist/form/full.js +26 -28
  111. package/dist/form/index.d.ts +3 -4
  112. package/dist/form/index.js +6 -5
  113. package/dist/form/markdown.d.ts +13 -8
  114. package/dist/form/markdown.js +22 -23
  115. package/dist/helpers/proximityConnect.d.ts +3 -2
  116. package/dist/helpers/proximityConnect.js +2 -5
  117. package/dist/helpers/workflowEditorHelper.d.ts +14 -5
  118. package/dist/helpers/workflowEditorHelper.js +28 -25
  119. package/dist/index.d.ts +28 -24
  120. package/dist/index.js +27 -50
  121. package/dist/messages/defaults.d.ts +2 -5
  122. package/dist/messages/defaults.js +3 -6
  123. package/dist/messages/index.d.ts +0 -1
  124. package/dist/messages/index.js +0 -1
  125. package/dist/mocks/app-forms.d.ts +6 -2
  126. package/dist/mocks/app-forms.js +11 -4
  127. package/dist/openapi/v1/openapi.yaml +3 -3
  128. package/dist/playground/index.d.ts +4 -5
  129. package/dist/playground/index.js +4 -32
  130. package/dist/playground/mount.d.ts +25 -0
  131. package/dist/playground/mount.js +50 -20
  132. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  133. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  134. package/dist/registry/builtinFormats.d.ts +9 -18
  135. package/dist/registry/builtinFormats.js +9 -39
  136. package/dist/registry/builtinNodeTypes.d.ts +53 -0
  137. package/dist/registry/builtinNodeTypes.js +67 -0
  138. package/dist/registry/builtinNodes.d.ts +2 -64
  139. package/dist/registry/builtinNodes.js +7 -103
  140. package/dist/registry/index.d.ts +3 -4
  141. package/dist/registry/index.js +4 -6
  142. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  143. package/dist/registry/nodeComponentRegistry.js +235 -17
  144. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  145. package/dist/registry/workflowFormatRegistry.js +24 -8
  146. package/dist/{schema → schemas}/index.d.ts +2 -2
  147. package/dist/{schema → schemas}/index.js +2 -2
  148. package/dist/schemas/v1/workflow.schema.json +3 -3
  149. package/dist/services/agentSpecExecutionService.d.ts +0 -2
  150. package/dist/services/agentSpecExecutionService.js +0 -3
  151. package/dist/services/apiVariableService.d.ts +2 -1
  152. package/dist/services/apiVariableService.js +16 -47
  153. package/dist/services/autoSaveService.d.ts +7 -0
  154. package/dist/services/autoSaveService.js +6 -4
  155. package/dist/services/categoriesApi.js +3 -6
  156. package/dist/services/chatService.d.ts +9 -4
  157. package/dist/services/chatService.js +23 -28
  158. package/dist/services/draftStorage.d.ts +129 -13
  159. package/dist/services/draftStorage.js +185 -37
  160. package/dist/services/dynamicSchemaService.d.ts +2 -1
  161. package/dist/services/dynamicSchemaService.js +5 -22
  162. package/dist/services/globalSave.d.ts +13 -12
  163. package/dist/services/globalSave.js +29 -51
  164. package/dist/services/historyService.d.ts +9 -3
  165. package/dist/services/historyService.js +9 -3
  166. package/dist/services/interruptService.d.ts +15 -9
  167. package/dist/services/interruptService.js +35 -37
  168. package/dist/services/nodeExecutionService.d.ts +18 -3
  169. package/dist/services/nodeExecutionService.js +71 -45
  170. package/dist/services/playgroundService.d.ts +16 -10
  171. package/dist/services/playgroundService.js +42 -43
  172. package/dist/services/portConfigApi.js +3 -6
  173. package/dist/services/settingsService.d.ts +9 -4
  174. package/dist/services/settingsService.js +23 -12
  175. package/dist/services/variableService.d.ts +2 -1
  176. package/dist/services/variableService.js +2 -2
  177. package/dist/services/workflowStorage.js +6 -6
  178. package/dist/stores/apiContext.d.ts +56 -0
  179. package/dist/stores/apiContext.js +80 -0
  180. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  181. package/dist/stores/categoriesStore.svelte.js +69 -64
  182. package/dist/stores/getInstance.svelte.d.ts +39 -0
  183. package/dist/stores/getInstance.svelte.js +65 -0
  184. package/dist/stores/historyStore.svelte.d.ts +77 -93
  185. package/dist/stores/historyStore.svelte.js +134 -160
  186. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  187. package/dist/stores/instanceContainer.svelte.js +114 -0
  188. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  189. package/dist/stores/interruptStore.svelte.js +253 -226
  190. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  191. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  192. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  193. package/dist/stores/playgroundStore.svelte.js +513 -580
  194. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  195. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  196. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  197. package/dist/stores/settingsStore.svelte.js +47 -12
  198. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  199. package/dist/stores/workflowStore.svelte.js +449 -501
  200. package/dist/stories/EdgeDecorator.svelte +5 -2
  201. package/dist/stories/NodeDecorator.svelte +5 -3
  202. package/dist/svelte-app.d.ts +60 -10
  203. package/dist/svelte-app.js +159 -54
  204. package/dist/types/auth.d.ts +9 -51
  205. package/dist/types/auth.js +4 -54
  206. package/dist/types/events.d.ts +6 -3
  207. package/dist/types/index.d.ts +37 -5
  208. package/dist/types/index.js +0 -1
  209. package/dist/types/navbar.d.ts +7 -0
  210. package/dist/types/playground.d.ts +18 -3
  211. package/dist/types/settings.d.ts +13 -0
  212. package/dist/types/settings.js +1 -0
  213. package/dist/utils/colors.d.ts +47 -21
  214. package/dist/utils/colors.js +69 -68
  215. package/dist/utils/connections.d.ts +9 -15
  216. package/dist/utils/connections.js +13 -32
  217. package/dist/utils/duration.d.ts +13 -0
  218. package/dist/utils/duration.js +45 -0
  219. package/dist/utils/edgeStyling.js +9 -5
  220. package/dist/utils/fetchWithAuth.d.ts +36 -15
  221. package/dist/utils/fetchWithAuth.js +53 -23
  222. package/dist/utils/icons.d.ts +5 -2
  223. package/dist/utils/icons.js +6 -5
  224. package/dist/utils/nodeSwap.d.ts +6 -2
  225. package/dist/utils/nodeSwap.js +62 -126
  226. package/dist/utils/nodeTypes.d.ts +17 -8
  227. package/dist/utils/nodeTypes.js +27 -20
  228. package/dist/utils/performanceUtils.js +7 -0
  229. package/package.json +7 -5
  230. package/dist/messages/deprecation.d.ts +0 -20
  231. package/dist/messages/deprecation.js +0 -33
  232. package/dist/registry/plugin.d.ts +0 -215
  233. package/dist/registry/plugin.js +0 -249
  234. package/dist/services/api.d.ts +0 -129
  235. package/dist/services/api.js +0 -217
@@ -4,6 +4,7 @@ import type { EndpointConfig } from '../config/endpoints.js';
4
4
  import type { AuthProvider } from '../types/auth.js';
5
5
  import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
6
6
  import type { FlowDropTheme, FlowDropThemeName } from '../types/theme.js';
7
+ import type { FlowDropInstance } from '../stores/instanceContainer.svelte.js';
7
8
  import type { SettingsCategory } from '../types/settings.js';
8
9
  import type { MessagesOverride } from '../messages/index.js';
9
10
  /**
@@ -22,14 +23,33 @@ interface Props {
22
23
  showNavbar?: boolean;
23
24
  /** Disable the node sidebar */
24
25
  disableSidebar?: boolean;
25
- /** Lock the workflow (prevent changes) */
26
- lockWorkflow?: boolean;
27
- /** Read-only mode */
28
- readOnly?: boolean;
29
- /** Node execution statuses */
30
- nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
26
+ /**
27
+ * Editor interaction mode. Replaces the former `readOnly` + `lockWorkflow`
28
+ * boolean pair (2.0 breaking change).
29
+ *
30
+ * | mode | node drag / connect / select | proximity-connect | node swap | bottom console panel + toggle |
31
+ * |--------------|------------------------------|-------------------|-----------|-------------------------------|
32
+ * | `'edit'` | enabled | enabled | enabled | available |
33
+ * | `'readonly'` | disabled | disabled | disabled | hidden |
34
+ * | `'locked'` | disabled | disabled | disabled | hidden |
35
+ *
36
+ * In 1.x `readOnly` and `lockWorkflow` gated the exact same set of
37
+ * interactions and were always combined as `!readOnly && !lockWorkflow`,
38
+ * so any combination of the two booleans collapsed to "edit" (both false)
39
+ * or "disabled" (either true). `'readonly'` and `'locked'` therefore behave
40
+ * identically today; the two names are kept as distinct intents so future
41
+ * versions can differentiate them without another breaking change.
42
+ *
43
+ * Migration: `readOnly` → `mode="readonly"`; `lockWorkflow` →
44
+ * `mode="locked"`; both `false` (or unset) → `mode="edit"` (the default).
45
+ *
46
+ * @default 'edit'
47
+ */
48
+ mode?: 'edit' | 'readonly' | 'locked';
31
49
  /** Pipeline ID for fetching node execution info */
32
50
  pipelineId?: string;
51
+ /** Increments to force a refresh of pipeline node status from the server */
52
+ refreshTrigger?: number;
33
53
  /** Custom navbar title */
34
54
  navbarTitle?: string;
35
55
  /** Custom navbar actions */
@@ -42,14 +62,31 @@ interface Props {
42
62
  }>;
43
63
  /** Show settings gear icon in navbar */
44
64
  showSettings?: boolean;
65
+ /** Show the "Connected" status indicator in the navbar (default: true) */
66
+ showStatus?: boolean;
45
67
  /** API base URL */
46
68
  apiBaseUrl?: string;
47
69
  /** Endpoint configuration */
48
70
  endpointConfig?: EndpointConfig;
49
71
  /** Authentication provider */
50
72
  authProvider?: AuthProvider;
51
- /** Event handlers */
52
- eventHandlers?: FlowDropEventHandlers;
73
+ /**
74
+ * Called before save — return false to cancel. Forwarded to the save
75
+ * pipeline. (Flattened from the former `eventHandlers` object in 2.0.)
76
+ */
77
+ onBeforeSave?: FlowDropEventHandlers['onBeforeSave'];
78
+ /** Called after a successful save. */
79
+ onAfterSave?: FlowDropEventHandlers['onAfterSave'];
80
+ /** Called when a save fails. */
81
+ onSaveError?: FlowDropEventHandlers['onSaveError'];
82
+ /** Called on any API error — return true to suppress the default toast. */
83
+ onApiError?: FlowDropEventHandlers['onApiError'];
84
+ /** Called after a workflow is loaded/imported. */
85
+ onWorkflowLoad?: FlowDropEventHandlers['onWorkflowLoad'];
86
+ /** Called before a node swap — return false to cancel. */
87
+ onBeforeSwap?: FlowDropEventHandlers['onBeforeSwap'];
88
+ /** Called after a node swap is applied. */
89
+ onAfterSwap?: FlowDropEventHandlers['onAfterSwap'];
53
90
  /** Feature configuration */
54
91
  features?: FlowDropFeatures;
55
92
  /** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
@@ -76,6 +113,8 @@ interface Props {
76
113
  * function call you'd rather not invoke unless the prop is actually read.
77
114
  */
78
115
  messages?: MessagesOverride | (() => MessagesOverride);
116
+ /** Per-instance state container (created by mount functions). Defaults to the page-default instance. */
117
+ instance?: FlowDropInstance;
79
118
  }
80
119
  declare const App: import("svelte").Component<Props, {}, "">;
81
120
  type App = ReturnType<typeof App>;
@@ -32,9 +32,13 @@
32
32
  } from '../types/index.js';
33
33
  import { dynamicPortToNodePort } from '../types/index.js';
34
34
  import type { UISchemaElement } from '../types/uischema.js';
35
- import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
35
+ // Import the light, registry-based field factory and light fields directly
36
+ // (not via the form barrel, which aggregates the heavy CodeMirror editors).
37
+ import FormField from './form/FormFieldLight.svelte';
38
+ import FormFieldWrapper from './form/FormFieldWrapper.svelte';
39
+ import FormToggle from './form/FormToggle.svelte';
36
40
  import FormUISchemaRenderer from './form/FormUISchemaRenderer.svelte';
37
- import type { FieldSchema } from './form/index.js';
41
+ import type { FieldSchema } from './form/types.js';
38
42
  import {
39
43
  getEffectiveConfigEditOptions,
40
44
  fetchDynamicSchema,
@@ -43,6 +47,7 @@
43
47
  type DynamicSchemaResult
44
48
  } from '../services/dynamicSchemaService.js';
45
49
  import { globalSaveWorkflow } from '../services/globalSave.js';
50
+ import { provideInstance } from '../stores/getInstance.svelte.js';
46
51
  import { getAvailableVariables } from '../services/variableService.js';
47
52
  import { logger } from '../utils/logger.js';
48
53
  import { getDataTypeColorToken, getPortBackgroundColor } from '../utils/colors.js';
@@ -105,6 +110,17 @@
105
110
  onCancel
106
111
  }: Props = $props();
107
112
 
113
+ // Resolve the active instance for endpoint configuration (dynamic schema fetch).
114
+ // ConfigForm is a standalone-capable container (exported from /editor and able
115
+ // to render bare, e.g. with a direct `schema`/`values`). Self-provide so its
116
+ // leaf <FormField>s resolve and SSR doesn't throw. provideInstance() reuses an
117
+ // ancestor's context instance when nested in <App>/<WorkflowEditor>, returns
118
+ // the shared page-default in the browser, and creates a fresh per-render
119
+ // instance on the server (no cross-request leakage); no destroy here for the
120
+ // same reasons as SchemaForm (shared/default in the browser, no SSR teardown).
121
+ const fd = provideInstance();
122
+ const checker = fd.portCompatibility;
123
+
108
124
  // Set context for child components (e.g., FormAutocomplete)
109
125
  // Use getter functions to ensure child components always get the current prop value,
110
126
  // even if the prop changes after initial mount
@@ -203,7 +219,8 @@
203
219
  // (configSchema flips from undefined → loaded) and "different node opened"
204
220
  // (node prop change → metadata.configSchema reference change). Identity
205
221
  // comparison only — value churn in `initialConfig` preserves in-flight edits.
206
- // svelte-ignore state_referenced_locally — capturing the initial derived reference is intentional; later changes are picked up by the effect below
222
+ // capturing the initial derived reference is intentional; later changes are picked up by the effect below
223
+ // svelte-ignore state_referenced_locally
207
224
  let prevSchemaRef = configSchema;
208
225
  $effect.pre(() => {
209
226
  if (configSchema !== prevSchemaRef) {
@@ -212,12 +229,6 @@
212
229
  }
213
230
  });
214
231
 
215
- /**
216
- * UI Extension values for display settings
217
- * Merges node type defaults with instance overrides
218
- */
219
- let uiExtensionValues = $state<NodeUIExtensions>({});
220
-
221
232
  /**
222
233
  * Flag to track if workflow save is in progress
223
234
  */
@@ -234,6 +245,38 @@
234
245
  return { ...typeDefaults, ...instanceOverrides };
235
246
  });
236
247
 
248
+ /**
249
+ * UI Extension values for display settings.
250
+ * Writable derived: recomputes from the node when it changes (covering both
251
+ * "different node opened" and the post-save round-trip through node.data),
252
+ * while local port-management edits overwrite it wholesale via reassignment.
253
+ * NOTE: the derived value is not a deep $state proxy — update it only by
254
+ * reassigning the whole object, never by mutating a property.
255
+ */
256
+ let uiExtensionValues = $derived.by<NodeUIExtensions>(() => ({
257
+ hideUnconnectedHandles: initialUIExtensions.hideUnconnectedHandles ?? false,
258
+ portOrder: initialUIExtensions.portOrder
259
+ ? {
260
+ inputs: initialUIExtensions.portOrder.inputs
261
+ ? [...initialUIExtensions.portOrder.inputs]
262
+ : undefined,
263
+ outputs: initialUIExtensions.portOrder.outputs
264
+ ? [...initialUIExtensions.portOrder.outputs]
265
+ : undefined
266
+ }
267
+ : undefined,
268
+ hiddenPorts: initialUIExtensions.hiddenPorts
269
+ ? {
270
+ inputs: initialUIExtensions.hiddenPorts.inputs
271
+ ? [...initialUIExtensions.hiddenPorts.inputs]
272
+ : undefined,
273
+ outputs: initialUIExtensions.hiddenPorts.outputs
274
+ ? [...initialUIExtensions.hiddenPorts.outputs]
275
+ : undefined
276
+ }
277
+ : undefined
278
+ }));
279
+
237
280
  /**
238
281
  * Fetch dynamic schema when needed
239
282
  */
@@ -245,6 +288,7 @@
245
288
 
246
289
  try {
247
290
  const result: DynamicSchemaResult = await fetchDynamicSchema(
291
+ fd.api.config,
248
292
  configEditOptions.dynamicSchema,
249
293
  node,
250
294
  workflowId
@@ -313,35 +357,6 @@
313
357
  }
314
358
  });
315
359
 
316
- /**
317
- * Initialize UI extension values when node changes
318
- */
319
- $effect(() => {
320
- uiExtensionValues = {
321
- hideUnconnectedHandles: initialUIExtensions.hideUnconnectedHandles ?? false,
322
- portOrder: initialUIExtensions.portOrder
323
- ? {
324
- inputs: initialUIExtensions.portOrder.inputs
325
- ? [...initialUIExtensions.portOrder.inputs]
326
- : undefined,
327
- outputs: initialUIExtensions.portOrder.outputs
328
- ? [...initialUIExtensions.portOrder.outputs]
329
- : undefined
330
- }
331
- : undefined,
332
- hiddenPorts: initialUIExtensions.hiddenPorts
333
- ? {
334
- inputs: initialUIExtensions.hiddenPorts.inputs
335
- ? [...initialUIExtensions.hiddenPorts.inputs]
336
- : undefined,
337
- outputs: initialUIExtensions.hiddenPorts.outputs
338
- ? [...initialUIExtensions.hiddenPorts.outputs]
339
- : undefined
340
- }
341
- : undefined
342
- };
343
- });
344
-
345
360
  /**
346
361
  * All input ports in current display order for the port management UI.
347
362
  * Combines static metadata inputs + dynamic config inputs, sorted by portOrder.
@@ -379,9 +394,12 @@
379
394
  if (newIdx < 0 || newIdx >= list.length) return;
380
395
  const newOrder = list.map((p) => p.id);
381
396
  [newOrder[idx], newOrder[newIdx]] = [newOrder[newIdx], newOrder[idx]];
382
- uiExtensionValues.portOrder = {
383
- ...uiExtensionValues.portOrder,
384
- [direction]: newOrder
397
+ uiExtensionValues = {
398
+ ...uiExtensionValues,
399
+ portOrder: {
400
+ ...uiExtensionValues.portOrder,
401
+ [direction]: newOrder
402
+ }
385
403
  };
386
404
  handleFormBlur();
387
405
  }
@@ -393,9 +411,12 @@
393
411
  const current = uiExtensionValues.hiddenPorts?.[direction] ?? [];
394
412
  const isHidden = current.includes(portId);
395
413
  const next = isHidden ? current.filter((id) => id !== portId) : [...current, portId];
396
- uiExtensionValues.hiddenPorts = {
397
- ...uiExtensionValues.hiddenPorts,
398
- [direction]: next.length > 0 ? next : undefined
414
+ uiExtensionValues = {
415
+ ...uiExtensionValues,
416
+ hiddenPorts: {
417
+ ...uiExtensionValues.hiddenPorts,
418
+ [direction]: next.length > 0 ? next : undefined
419
+ }
399
420
  };
400
421
  handleFormBlur();
401
422
  }
@@ -408,8 +429,11 @@
408
429
  const hidden = { ...uiExtensionValues.hiddenPorts };
409
430
  delete order[direction];
410
431
  delete hidden[direction];
411
- uiExtensionValues.portOrder = Object.keys(order).length > 0 ? order : undefined;
412
- uiExtensionValues.hiddenPorts = Object.keys(hidden).length > 0 ? hidden : undefined;
432
+ uiExtensionValues = {
433
+ ...uiExtensionValues,
434
+ portOrder: Object.keys(order).length > 0 ? order : undefined,
435
+ hiddenPorts: Object.keys(hidden).length > 0 ? hidden : undefined
436
+ };
413
437
  handleFormBlur();
414
438
  }
415
439
 
@@ -751,7 +775,7 @@
751
775
  offLabel="Visible"
752
776
  ariaDescribedBy="ext-hideUnconnectedHandles-description"
753
777
  onChange={(val) => {
754
- uiExtensionValues.hideUnconnectedHandles = val;
778
+ uiExtensionValues = { ...uiExtensionValues, hideUnconnectedHandles: val };
755
779
  handleFormBlur();
756
780
  }}
757
781
  />
@@ -787,11 +811,13 @@
787
811
  <span
788
812
  class="config-form__port-order-badge"
789
813
  style="background-color:{getPortBackgroundColor(
814
+ checker,
790
815
  port.dataType,
791
816
  15
792
817
  )};color:{getDataTypeColorToken(
818
+ checker,
793
819
  port.dataType
794
- )};border:1px solid {getPortBackgroundColor(port.dataType, 30)}"
820
+ )};border:1px solid {getPortBackgroundColor(checker, port.dataType, 30)}"
795
821
  >
796
822
  {port.dataType}
797
823
  </span>
@@ -863,11 +889,13 @@
863
889
  <span
864
890
  class="config-form__port-order-badge"
865
891
  style="background-color:{getPortBackgroundColor(
892
+ checker,
866
893
  port.dataType,
867
894
  15
868
895
  )};color:{getDataTypeColorToken(
896
+ checker,
869
897
  port.dataType
870
- )};border:1px solid {getPortBackgroundColor(port.dataType, 30)}"
898
+ )};border:1px solid {getPortBackgroundColor(checker, port.dataType, 30)}"
871
899
  >
872
900
  {port.dataType}
873
901
  </span>
@@ -14,6 +14,11 @@
14
14
  }
15
15
 
16
16
  let { onClose, onSave, onCancel, ...props }: Props = $props();
17
+
18
+ // Unique per component instance so two FlowDrop editors on one page
19
+ // don't render colliding DOM ids (a11y).
20
+ const uid = $props.id();
21
+ const titleId = `${uid}-config-modal-title`;
17
22
  let localConfigValues = $derived.by(() => ({ ...props.configValues }));
18
23
 
19
24
  function handleCancel() {
@@ -47,14 +52,14 @@
47
52
  onkeydown={handleKeydown}
48
53
  role="dialog"
49
54
  aria-modal="true"
50
- aria-labelledby="config-modal-title"
55
+ aria-labelledby={titleId}
51
56
  tabindex="-1"
52
57
  >
53
58
  <!-- Modal Container -->
54
59
  <div class="config-modal">
55
60
  <!-- Modal Header -->
56
61
  <div class="config-modal__header">
57
- <h2 id="config-modal-title" class="config-modal__title">
62
+ <h2 id={titleId} class="config-modal__title">
58
63
  Configure: {props.nodeLabel}
59
64
  </h2>
60
65
  <button
@@ -1,8 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { getDataTypeColor } from '../utils/colors';
3
+ import { getInstance } from '../stores/getInstance.svelte.js';
3
4
  import { useConnection } from '@xyflow/svelte';
4
5
 
5
6
  const connection = useConnection();
7
+ const checker = getInstance().portCompatibility;
6
8
 
7
9
  let path: string | null = $derived.by(() => {
8
10
  if (connection.current.inProgress) {
@@ -18,7 +20,7 @@
18
20
  fill="none"
19
21
  stroke-width={1.5}
20
22
  class="animated"
21
- stroke={getDataTypeColor(connection.current.fromHandle.id ?? '')}
23
+ stroke={getDataTypeColor(checker, connection.current.fromHandle.id ?? '')}
22
24
  d={path}
23
25
  />
24
26
  <circle
@@ -26,7 +28,7 @@
26
28
  cy={connection.current.to.y}
27
29
  fill="#fff"
28
30
  r={3}
29
- stroke={getDataTypeColor(connection.current.fromHandle.id ?? '')}
31
+ stroke={getDataTypeColor(checker, connection.current.fromHandle.id ?? '')}
30
32
  stroke-width={1.5}
31
33
  />
32
34
  {/if}
@@ -59,6 +59,25 @@
59
59
  // Hoist the navigation branch — six reads in the template.
60
60
  const nav = $derived(m().navigation);
61
61
 
62
+ // Flyout structure: actions after the first split into ungrouped (rendered
63
+ // flat at the top) and groups (rendered as labeled sections). Group order
64
+ // follows first occurrence in the source array.
65
+ const dropdownActions = $derived(primaryActions.slice(1));
66
+ const ungroupedActions = $derived(dropdownActions.filter((a) => !a.group));
67
+ const groupedActions = $derived.by(() => {
68
+ const groups = new Map<string, NavbarAction[]>();
69
+ for (const action of dropdownActions) {
70
+ if (!action.group) continue;
71
+ let bucket = groups.get(action.group);
72
+ if (!bucket) {
73
+ bucket = [];
74
+ groups.set(action.group, bucket);
75
+ }
76
+ bucket.push(action);
77
+ }
78
+ return Array.from(groups, ([label, items]) => ({ label, items }));
79
+ });
80
+
62
81
  // Close dropdown when clicking outside
63
82
  function handleClickOutside(event: MouseEvent) {
64
83
  const target = event.target as HTMLElement;
@@ -203,7 +222,7 @@
203
222
 
204
223
  {#if isDropdownOpen}
205
224
  <div class="flowdrop-navbar__dropdown-menu">
206
- {#each primaryActions.slice(1) as action (action.label)}
225
+ {#each ungroupedActions as action (action.label)}
207
226
  <a
208
227
  href={action.href}
209
228
  class="flowdrop-navbar__dropdown-item"
@@ -223,6 +242,32 @@
223
242
  {/if}
224
243
  </a>
225
244
  {/each}
245
+ {#each groupedActions as group, groupIndex (group.label)}
246
+ {#if groupIndex > 0 || ungroupedActions.length > 0}
247
+ <div class="flowdrop-navbar__dropdown-divider" role="separator"></div>
248
+ {/if}
249
+ <div class="flowdrop-navbar__dropdown-group-header">{group.label}</div>
250
+ {#each group.items as action (action.label)}
251
+ <a
252
+ href={action.href}
253
+ class="flowdrop-navbar__dropdown-item"
254
+ onclick={(e) => {
255
+ action.onclick?.(e);
256
+ isDropdownOpen = false;
257
+ }}
258
+ target={action.external ? '_blank' : undefined}
259
+ rel={action.external ? 'noopener noreferrer' : undefined}
260
+ >
261
+ {#if action.icon}
262
+ <Icon icon={action.icon} class="w-4 h-4" />
263
+ {/if}
264
+ <span>{action.label}</span>
265
+ {#if action.external}
266
+ <Icon icon="mdi:open-in-new" class="w-3 h-3" />
267
+ {/if}
268
+ </a>
269
+ {/each}
270
+ {/each}
226
271
  </div>
227
272
  {/if}
228
273
  </div>
@@ -568,6 +613,21 @@
568
613
  border-bottom: none;
569
614
  }
570
615
 
616
+ .flowdrop-navbar__dropdown-divider {
617
+ height: 1px;
618
+ background-color: var(--fd-border);
619
+ margin: 0.25rem 0;
620
+ }
621
+
622
+ .flowdrop-navbar__dropdown-group-header {
623
+ padding: 0.5rem 1rem 0.25rem;
624
+ font-size: var(--fd-text-xs);
625
+ font-weight: 600;
626
+ text-transform: uppercase;
627
+ letter-spacing: 0.04em;
628
+ color: var(--fd-muted-foreground);
629
+ }
630
+
571
631
  .flowdrop-navbar__action {
572
632
  display: flex;
573
633
  align-items: center;
@@ -10,7 +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.svelte.js';
13
+ import { getInstance } from '../stores/getInstance.svelte.js';
14
14
  import { getUiSettings } from '../stores/settingsStore.svelte.js';
15
15
  import { extractConfigDefaults } from '../utils/nodeIds.js';
16
16
  import { m } from '../messages/index.js';
@@ -26,8 +26,10 @@
26
26
  }
27
27
 
28
28
  let props: Props = $props();
29
+ const fd = getInstance();
29
30
  let searchInput = $state('');
30
- // svelte-ignore state_referenced_locally — initial default, user selects interactively
31
+ // initial default, user selects interactively
32
+ // svelte-ignore state_referenced_locally
31
33
  let selectedCategory = $state(props.selectedCategory || 'all');
32
34
 
33
35
  /**
@@ -140,26 +142,12 @@
140
142
  }
141
143
  }
142
144
 
143
- /**
144
- * Handle search input change
145
- */
146
- function handleSearchChange(): void {
147
- // Search is handled reactively through the derived filteredNodes
148
- }
149
-
150
- /**
151
- * Handle node click
152
- */
153
- function handleNodeClick(nodeType: NodeMetadata): void {
154
- // Handle node click - could be used for preview or configuration
155
- }
156
-
157
145
  /**
158
146
  * Get category display name from the categories store.
159
147
  * Falls back to auto-capitalizing the category machine name.
160
148
  */
161
149
  function getCategoryDisplayName(category: NodeCategory): string {
162
- return getCategoryLabel(category);
150
+ return fd.categories.getLabel(category);
163
151
  }
164
152
  </script>
165
153
 
@@ -180,7 +168,6 @@
180
168
  placeholder={m().layout.searchComponents}
181
169
  class="flowdrop-input flowdrop-join__item flowdrop-w--full"
182
170
  bind:value={searchInput}
183
- oninput={handleSearchChange}
184
171
  />
185
172
  </div>
186
173
  <button class="flowdrop-btn flowdrop-join__item" aria-label={m().layout.searchComponents}>
@@ -273,9 +260,12 @@
273
260
  <!-- Node Type Icon with Squircle Background -->
274
261
  <span
275
262
  class="flowdrop-node-icon"
276
- style="--_icon-color: {getCategoryColorToken(nodeType.category)}"
263
+ style="--_icon-color: {getCategoryColorToken(
264
+ fd.categories,
265
+ nodeType.category
266
+ )}"
277
267
  >
278
- <Icon icon={getNodeIcon(nodeType.icon, nodeType.category)} />
268
+ <Icon icon={getNodeIcon(fd.categories, nodeType.icon, nodeType.category)} />
279
269
  </span>
280
270
 
281
271
  <!-- Node Type Info - Icon and Title only -->
@@ -309,25 +299,20 @@
309
299
  <div class="fd-sidebar-flat-category">
310
300
  {getCategoryDisplayName(category).toUpperCase()}
311
301
  </div>
312
- <div class="fd-sidebar-flat-list">
302
+ <div class="fd-sidebar-flat-list" role="list">
313
303
  {#each categoryNodes as nodeType (nodeType.id)}
314
304
  <div
315
305
  class="fd-sidebar-flat-item"
306
+ role="listitem"
316
307
  draggable="true"
317
308
  ondragstart={(e) => handleNodeDragStart(e, nodeType)}
318
- onclick={() => handleNodeClick(nodeType)}
319
- role="button"
320
- tabindex="0"
321
- onkeydown={(e) => {
322
- if (e.key === 'Enter' || e.key === ' ') {
323
- e.preventDefault();
324
- handleNodeClick(nodeType);
325
- }
326
- }}
327
309
  >
328
310
  <span
329
311
  class="fd-sidebar-flat-dot"
330
- style="background: {getCategoryColorToken(nodeType.category)}"
312
+ style="background: {getCategoryColorToken(
313
+ fd.categories,
314
+ nodeType.category
315
+ )}"
331
316
  ></span>
332
317
  <span class="fd-sidebar-flat-name">{nodeType.name}</span>
333
318
  </div>
@@ -343,9 +328,9 @@
343
328
  <div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
344
329
  <span
345
330
  class="flowdrop-node-icon"
346
- style="--_icon-color: {getCategoryColorToken(category)}"
331
+ style="--_icon-color: {getCategoryColorToken(fd.categories, category)}"
347
332
  >
348
- <Icon icon={getCategoryIcon(category)} />
333
+ <Icon icon={getCategoryIcon(fd.categories, category)} />
349
334
  </span>
350
335
  <span>{getCategoryDisplayName(category)}</span>
351
336
  </div>
@@ -354,30 +339,27 @@
354
339
  </div>
355
340
  </summary>
356
341
  <div class="flowdrop-details__content">
357
- <div class="flowdrop-node-list">
342
+ <div class="flowdrop-node-list" role="list">
358
343
  {#each categoryNodes as nodeType (nodeType.id)}
359
344
  <div
360
345
  class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
346
+ role="listitem"
361
347
  draggable="true"
362
348
  ondragstart={(e) => handleNodeDragStart(e, nodeType)}
363
- onclick={() => handleNodeClick(nodeType)}
364
- role="button"
365
- tabindex="0"
366
- onkeydown={(e) => {
367
- if (e.key === 'Enter' || e.key === ' ') {
368
- e.preventDefault();
369
- handleNodeClick(nodeType);
370
- }
371
- }}
372
349
  >
373
350
  <div class="flowdrop-card__body flowdrop-p--1 flowdrop-py--1">
374
351
  <div class="flowdrop-flex flowdrop-gap--2 flowdrop-items--center">
375
352
  <!-- Node Type Icon with Squircle Background -->
376
353
  <span
377
354
  class="flowdrop-node-icon"
378
- style="--_icon-color: {getCategoryColorToken(nodeType.category)}"
355
+ style="--_icon-color: {getCategoryColorToken(
356
+ fd.categories,
357
+ nodeType.category
358
+ )}"
379
359
  >
380
- <Icon icon={getNodeIcon(nodeType.icon, nodeType.category)} />
360
+ <Icon
361
+ icon={getNodeIcon(fd.categories, nodeType.icon, nodeType.category)}
362
+ />
381
363
  </span>
382
364
 
383
365
  <!-- Node Type Info - Icon and Title only -->