@flowdrop/flowdrop 1.0.1 → 1.2.0

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 (385) hide show
  1. package/README.md +50 -50
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +25 -25
  4. package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +2 -2
  5. package/dist/adapters/agentspec/AgentSpecAdapter.js +133 -122
  6. package/dist/adapters/agentspec/agentAdapter.d.ts +2 -2
  7. package/dist/adapters/agentspec/agentAdapter.js +10 -10
  8. package/dist/adapters/agentspec/autoLayout.d.ts +1 -1
  9. package/dist/adapters/agentspec/autoLayout.js +2 -2
  10. package/dist/adapters/agentspec/componentTypeDefaults.d.ts +1 -1
  11. package/dist/adapters/agentspec/componentTypeDefaults.js +120 -120
  12. package/dist/adapters/agentspec/defaultNodeTypes.d.ts +2 -2
  13. package/dist/adapters/agentspec/defaultNodeTypes.js +307 -307
  14. package/dist/adapters/agentspec/index.d.ts +10 -10
  15. package/dist/adapters/agentspec/index.js +6 -6
  16. package/dist/adapters/agentspec/validator.d.ts +2 -2
  17. package/dist/adapters/agentspec/validator.js +22 -20
  18. package/dist/api/enhanced-client.d.ts +3 -3
  19. package/dist/api/enhanced-client.js +73 -72
  20. package/dist/components/App.svelte +1090 -961
  21. package/dist/components/App.svelte.d.ts +9 -6
  22. package/dist/components/CanvasBanner.stories.svelte +23 -20
  23. package/dist/components/CanvasBanner.stories.svelte.d.ts +1 -1
  24. package/dist/components/CanvasBanner.svelte +52 -46
  25. package/dist/components/ConfigForm.svelte +1164 -1065
  26. package/dist/components/ConfigForm.svelte.d.ts +2 -2
  27. package/dist/components/ConfigModal.svelte +180 -180
  28. package/dist/components/ConfigModal.svelte.d.ts +1 -1
  29. package/dist/components/ConfigPanel.stories.svelte +35 -35
  30. package/dist/components/ConfigPanel.stories.svelte.d.ts +1 -1
  31. package/dist/components/ConfigPanel.svelte +178 -167
  32. package/dist/components/ConfigPanel.svelte.d.ts +1 -1
  33. package/dist/components/ConnectionLine.svelte +25 -25
  34. package/dist/components/EdgeRefresher.svelte +26 -26
  35. package/dist/components/FlowDropEdge.stories.svelte +179 -143
  36. package/dist/components/FlowDropEdge.svelte +147 -147
  37. package/dist/components/FlowDropEdge.svelte.d.ts +1 -1
  38. package/dist/components/FlowDropZone.svelte +63 -60
  39. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  40. package/dist/components/LoadingSpinner.stories.svelte +19 -19
  41. package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
  42. package/dist/components/LoadingSpinner.svelte +21 -21
  43. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  44. package/dist/components/Logo.stories.svelte +13 -13
  45. package/dist/components/Logo.stories.svelte.d.ts +1 -1
  46. package/dist/components/Logo.svelte +101 -95
  47. package/dist/components/LogsSidebar.svelte +553 -546
  48. package/dist/components/LogsSidebar.svelte.d.ts +1 -1
  49. package/dist/components/MarkdownDisplay.stories.svelte +29 -23
  50. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
  51. package/dist/components/MarkdownDisplay.svelte +16 -14
  52. package/dist/components/Navbar.stories.svelte +43 -38
  53. package/dist/components/Navbar.stories.svelte.d.ts +1 -1
  54. package/dist/components/Navbar.svelte +760 -706
  55. package/dist/components/Navbar.svelte.d.ts +1 -1
  56. package/dist/components/NodeSidebar.svelte +905 -746
  57. package/dist/components/NodeSidebar.svelte.d.ts +5 -1
  58. package/dist/components/NodeStatusOverlay.stories.svelte +82 -70
  59. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
  60. package/dist/components/NodeStatusOverlay.svelte +295 -280
  61. package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
  62. package/dist/components/PipelineStatus.svelte +326 -300
  63. package/dist/components/PipelineStatus.svelte.d.ts +4 -4
  64. package/dist/components/PortCoordinateTracker.svelte +49 -47
  65. package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
  66. package/dist/components/ReadOnlyDetails.svelte +156 -156
  67. package/dist/components/SchemaForm.stories.svelte +106 -98
  68. package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
  69. package/dist/components/SchemaForm.svelte +490 -463
  70. package/dist/components/SchemaForm.svelte.d.ts +2 -2
  71. package/dist/components/SettingsModal.svelte +226 -223
  72. package/dist/components/SettingsModal.svelte.d.ts +1 -1
  73. package/dist/components/SettingsPanel.svelte +637 -601
  74. package/dist/components/SettingsPanel.svelte.d.ts +1 -1
  75. package/dist/components/StatusIcon.stories.svelte +62 -49
  76. package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
  77. package/dist/components/StatusIcon.svelte +87 -87
  78. package/dist/components/StatusIcon.svelte.d.ts +2 -2
  79. package/dist/components/StatusLabel.stories.svelte +12 -12
  80. package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
  81. package/dist/components/StatusLabel.svelte +19 -19
  82. package/dist/components/ThemeToggle.stories.svelte +16 -16
  83. package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
  84. package/dist/components/ThemeToggle.svelte +180 -169
  85. package/dist/components/ThemeToggle.svelte.d.ts +1 -1
  86. package/dist/components/UniversalNode.svelte +150 -138
  87. package/dist/components/UniversalNode.svelte.d.ts +3 -3
  88. package/dist/components/WorkflowEditor.svelte +1069 -1014
  89. package/dist/components/WorkflowEditor.svelte.d.ts +4 -4
  90. package/dist/components/form/FormArray.svelte +1034 -973
  91. package/dist/components/form/FormArray.svelte.d.ts +1 -1
  92. package/dist/components/form/FormAutocomplete.svelte +1021 -978
  93. package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
  94. package/dist/components/form/FormCheckboxGroup.stories.svelte +23 -20
  95. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
  96. package/dist/components/form/FormCheckboxGroup.svelte +136 -136
  97. package/dist/components/form/FormCodeEditor.svelte +452 -434
  98. package/dist/components/form/FormField.svelte +366 -355
  99. package/dist/components/form/FormField.svelte.d.ts +2 -2
  100. package/dist/components/form/FormFieldLight.svelte +400 -384
  101. package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
  102. package/dist/components/form/FormFieldWrapper.stories.svelte +42 -42
  103. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
  104. package/dist/components/form/FormFieldWrapper.svelte +100 -93
  105. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  106. package/dist/components/form/FormFieldset.svelte +108 -108
  107. package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
  108. package/dist/components/form/FormMarkdownEditor.svelte +758 -725
  109. package/dist/components/form/FormNumberField.stories.svelte +25 -25
  110. package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
  111. package/dist/components/form/FormNumberField.svelte +88 -88
  112. package/dist/components/form/FormRangeField.stories.svelte +20 -20
  113. package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
  114. package/dist/components/form/FormRangeField.svelte +234 -226
  115. package/dist/components/form/FormSelect.stories.svelte +38 -38
  116. package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
  117. package/dist/components/form/FormSelect.svelte +101 -101
  118. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  119. package/dist/components/form/FormTemplateEditor.svelte +847 -798
  120. package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
  121. package/dist/components/form/FormTextField.stories.svelte +29 -23
  122. package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
  123. package/dist/components/form/FormTextField.svelte +68 -68
  124. package/dist/components/form/FormTextarea.stories.svelte +28 -25
  125. package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
  126. package/dist/components/form/FormTextarea.svelte +74 -74
  127. package/dist/components/form/FormToggle.stories.svelte +23 -20
  128. package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
  129. package/dist/components/form/FormToggle.svelte +98 -98
  130. package/dist/components/form/FormUISchemaRenderer.svelte +120 -113
  131. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
  132. package/dist/components/form/index.d.ts +19 -19
  133. package/dist/components/form/index.js +18 -18
  134. package/dist/components/form/templateAutocomplete.d.ts +2 -2
  135. package/dist/components/form/templateAutocomplete.js +64 -55
  136. package/dist/components/form/types.d.ts +6 -6
  137. package/dist/components/form/types.js +9 -4
  138. package/dist/components/icons/AlertCircleIcon.svelte +11 -0
  139. package/dist/components/icons/AlertCircleIcon.svelte.d.ts +26 -0
  140. package/dist/components/icons/CogIcon.svelte +11 -0
  141. package/dist/components/icons/CogIcon.svelte.d.ts +26 -0
  142. package/dist/components/interrupt/ChoicePrompt.stories.svelte +54 -38
  143. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
  144. package/dist/components/interrupt/ChoicePrompt.svelte +407 -383
  145. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
  146. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +48 -48
  147. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
  148. package/dist/components/interrupt/ConfirmationPrompt.svelte +280 -274
  149. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
  150. package/dist/components/interrupt/FormPrompt.svelte +223 -218
  151. package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
  152. package/dist/components/interrupt/InterruptBubble.svelte +617 -583
  153. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
  154. package/dist/components/interrupt/ReviewPrompt.stories.svelte +66 -56
  155. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
  156. package/dist/components/interrupt/ReviewPrompt.svelte +861 -841
  157. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
  158. package/dist/components/interrupt/TextInputPrompt.stories.svelte +38 -33
  159. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
  160. package/dist/components/interrupt/TextInputPrompt.svelte +333 -328
  161. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
  162. package/dist/components/interrupt/index.d.ts +5 -5
  163. package/dist/components/interrupt/index.js +5 -5
  164. package/dist/components/layouts/MainLayout.svelte +724 -691
  165. package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
  166. package/dist/components/nodes/GatewayNode.stories.svelte +100 -99
  167. package/dist/components/nodes/GatewayNode.svelte +605 -571
  168. package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
  169. package/dist/components/nodes/IdeaNode.stories.svelte +44 -43
  170. package/dist/components/nodes/IdeaNode.svelte +451 -437
  171. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  172. package/dist/components/nodes/NotesNode.stories.svelte +65 -64
  173. package/dist/components/nodes/NotesNode.svelte +380 -369
  174. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  175. package/dist/components/nodes/SimpleNode.stories.svelte +145 -144
  176. package/dist/components/nodes/SimpleNode.svelte +486 -424
  177. package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
  178. package/dist/components/nodes/SquareNode.stories.svelte +73 -73
  179. package/dist/components/nodes/SquareNode.svelte +439 -380
  180. package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
  181. package/dist/components/nodes/TerminalNode.stories.svelte +13 -13
  182. package/dist/components/nodes/TerminalNode.svelte +709 -670
  183. package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
  184. package/dist/components/nodes/ToolNode.stories.svelte +181 -180
  185. package/dist/components/nodes/ToolNode.svelte +505 -447
  186. package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
  187. package/dist/components/nodes/WorkflowNode.stories.svelte +70 -46
  188. package/dist/components/nodes/WorkflowNode.svelte +621 -551
  189. package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
  190. package/dist/components/playground/ChatPanel.svelte +945 -889
  191. package/dist/components/playground/ExecutionLogs.svelte +495 -472
  192. package/dist/components/playground/InputCollector.svelte +449 -428
  193. package/dist/components/playground/MessageBubble.stories.svelte +47 -47
  194. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
  195. package/dist/components/playground/MessageBubble.svelte +626 -610
  196. package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
  197. package/dist/components/playground/Playground.svelte +1088 -1057
  198. package/dist/components/playground/Playground.svelte.d.ts +3 -3
  199. package/dist/components/playground/PlaygroundModal.svelte +208 -204
  200. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  201. package/dist/components/playground/SessionManager.svelte +527 -521
  202. package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
  203. package/dist/config/agentSpecEndpoints.d.ts +1 -1
  204. package/dist/config/agentSpecEndpoints.js +20 -20
  205. package/dist/config/constants.js +2 -2
  206. package/dist/config/defaultCategories.d.ts +1 -1
  207. package/dist/config/defaultCategories.js +86 -86
  208. package/dist/config/defaultPortConfig.d.ts +1 -1
  209. package/dist/config/defaultPortConfig.js +144 -144
  210. package/dist/config/endpoints.d.ts +4 -4
  211. package/dist/config/endpoints.js +65 -65
  212. package/dist/config/runtimeConfig.d.ts +2 -2
  213. package/dist/config/runtimeConfig.js +8 -8
  214. package/dist/core/index.d.ts +63 -59
  215. package/dist/core/index.js +35 -33
  216. package/dist/display/index.d.ts +2 -2
  217. package/dist/display/index.js +2 -2
  218. package/dist/editor/index.d.ts +62 -62
  219. package/dist/editor/index.js +53 -53
  220. package/dist/form/code.d.ts +5 -5
  221. package/dist/form/code.js +14 -14
  222. package/dist/form/fieldRegistry.d.ts +3 -3
  223. package/dist/form/fieldRegistry.js +11 -9
  224. package/dist/form/full.d.ts +8 -8
  225. package/dist/form/full.js +9 -9
  226. package/dist/form/index.d.ts +18 -18
  227. package/dist/form/index.js +16 -16
  228. package/dist/form/markdown.d.ts +4 -4
  229. package/dist/form/markdown.js +8 -8
  230. package/dist/helpers/proximityConnect.d.ts +3 -3
  231. package/dist/helpers/proximityConnect.js +34 -32
  232. package/dist/helpers/workflowEditorHelper.d.ts +5 -5
  233. package/dist/helpers/workflowEditorHelper.js +108 -96
  234. package/dist/index.d.ts +6 -6
  235. package/dist/index.js +6 -6
  236. package/dist/mocks/app-environment.js +2 -2
  237. package/dist/mocks/app-forms.js +9 -9
  238. package/dist/mocks/app-navigation.js +11 -11
  239. package/dist/mocks/app-stores.js +8 -8
  240. package/dist/playground/index.d.ts +19 -19
  241. package/dist/playground/index.js +16 -16
  242. package/dist/playground/mount.d.ts +3 -3
  243. package/dist/playground/mount.js +24 -24
  244. package/dist/registry/builtinFormats.js +13 -13
  245. package/dist/registry/builtinNodes.d.ts +2 -2
  246. package/dist/registry/builtinNodes.js +77 -77
  247. package/dist/registry/index.d.ts +4 -4
  248. package/dist/registry/index.js +4 -4
  249. package/dist/registry/nodeComponentRegistry.d.ts +8 -8
  250. package/dist/registry/nodeComponentRegistry.js +11 -9
  251. package/dist/registry/plugin.d.ts +2 -2
  252. package/dist/registry/plugin.js +11 -11
  253. package/dist/registry/workflowFormatRegistry.d.ts +3 -3
  254. package/dist/registry/workflowFormatRegistry.js +2 -2
  255. package/dist/schema/index.d.ts +1 -1
  256. package/dist/schema/index.js +2 -2
  257. package/dist/services/agentSpecExecutionService.d.ts +3 -3
  258. package/dist/services/agentSpecExecutionService.js +59 -55
  259. package/dist/services/api.d.ts +2 -2
  260. package/dist/services/api.js +37 -37
  261. package/dist/services/apiVariableService.d.ts +1 -1
  262. package/dist/services/apiVariableService.js +41 -34
  263. package/dist/services/autoSaveService.js +8 -8
  264. package/dist/services/categoriesApi.d.ts +2 -2
  265. package/dist/services/categoriesApi.js +8 -8
  266. package/dist/services/draftStorage.d.ts +1 -1
  267. package/dist/services/draftStorage.js +11 -11
  268. package/dist/services/dynamicSchemaService.d.ts +1 -1
  269. package/dist/services/dynamicSchemaService.js +41 -39
  270. package/dist/services/globalSave.d.ts +2 -2
  271. package/dist/services/globalSave.js +41 -38
  272. package/dist/services/historyService.d.ts +1 -1
  273. package/dist/services/historyService.js +8 -8
  274. package/dist/services/interruptService.d.ts +1 -1
  275. package/dist/services/interruptService.js +35 -29
  276. package/dist/services/nodeExecutionService.d.ts +1 -1
  277. package/dist/services/nodeExecutionService.js +45 -44
  278. package/dist/services/playgroundService.d.ts +1 -1
  279. package/dist/services/playgroundService.js +29 -29
  280. package/dist/services/portConfigApi.d.ts +2 -2
  281. package/dist/services/portConfigApi.js +8 -8
  282. package/dist/services/settingsService.d.ts +2 -2
  283. package/dist/services/settingsService.js +25 -19
  284. package/dist/services/toastService.d.ts +4 -4
  285. package/dist/services/toastService.js +33 -33
  286. package/dist/services/variableService.d.ts +1 -1
  287. package/dist/services/variableService.js +36 -36
  288. package/dist/services/workflowStorage.d.ts +2 -2
  289. package/dist/services/workflowStorage.js +13 -13
  290. package/dist/settings/index.d.ts +7 -7
  291. package/dist/settings/index.js +6 -6
  292. package/dist/skins/default.d.ts +2 -0
  293. package/dist/skins/default.js +1 -0
  294. package/dist/skins/index.d.ts +13 -0
  295. package/dist/skins/index.js +30 -0
  296. package/dist/skins/slate.d.ts +2 -0
  297. package/dist/skins/slate.js +78 -0
  298. package/dist/stores/categoriesStore.svelte.d.ts +1 -1
  299. package/dist/stores/categoriesStore.svelte.js +5 -5
  300. package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
  301. package/dist/stores/editorStateMachine.svelte.js +65 -33
  302. package/dist/stores/historyStore.svelte.d.ts +4 -4
  303. package/dist/stores/historyStore.svelte.js +4 -4
  304. package/dist/stores/interruptStore.svelte.d.ts +3 -3
  305. package/dist/stores/interruptStore.svelte.js +21 -21
  306. package/dist/stores/playgroundStore.svelte.d.ts +2 -2
  307. package/dist/stores/playgroundStore.svelte.js +25 -18
  308. package/dist/stores/portCoordinateStore.svelte.d.ts +2 -2
  309. package/dist/stores/portCoordinateStore.svelte.js +15 -8
  310. package/dist/stores/settingsStore.svelte.d.ts +2 -2
  311. package/dist/stores/settingsStore.svelte.js +62 -57
  312. package/dist/stores/workflowStore.svelte.d.ts +3 -3
  313. package/dist/stores/workflowStore.svelte.js +50 -47
  314. package/dist/stories/CanvasDecorator.svelte +35 -32
  315. package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
  316. package/dist/stories/EdgeDecorator.svelte +102 -99
  317. package/dist/stories/EdgeDecorator.svelte.d.ts +1 -1
  318. package/dist/stories/NodeDecorator.svelte +59 -53
  319. package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
  320. package/dist/stories/utils.d.ts +2 -2
  321. package/dist/stories/utils.js +105 -67
  322. package/dist/styles/base.css +599 -595
  323. package/dist/styles/toast.css +14 -14
  324. package/dist/styles/tokens.css +409 -378
  325. package/dist/svelte-app.d.ts +12 -9
  326. package/dist/svelte-app.js +40 -39
  327. package/dist/themes/default.d.ts +2 -0
  328. package/dist/themes/default.js +9 -0
  329. package/dist/themes/index.d.ts +13 -0
  330. package/dist/themes/index.js +44 -0
  331. package/dist/themes/minimal.d.ts +2 -0
  332. package/dist/themes/minimal.js +11 -0
  333. package/dist/types/agentspec.d.ts +18 -18
  334. package/dist/types/agentspec.js +2 -2
  335. package/dist/types/auth.d.ts +1 -1
  336. package/dist/types/auth.js +6 -6
  337. package/dist/types/config.d.ts +6 -6
  338. package/dist/types/events.d.ts +2 -2
  339. package/dist/types/events.js +2 -2
  340. package/dist/types/index.d.ts +32 -32
  341. package/dist/types/index.js +6 -6
  342. package/dist/types/interrupt.d.ts +6 -6
  343. package/dist/types/interrupt.js +21 -21
  344. package/dist/types/interruptState.d.ts +12 -12
  345. package/dist/types/interruptState.js +66 -66
  346. package/dist/types/playground.d.ts +7 -7
  347. package/dist/types/playground.js +14 -14
  348. package/dist/types/settings.d.ts +5 -3
  349. package/dist/types/settings.js +25 -18
  350. package/dist/types/skin.d.ts +31 -0
  351. package/dist/types/skin.js +1 -0
  352. package/dist/types/theme.d.ts +35 -0
  353. package/dist/types/theme.js +1 -0
  354. package/dist/types/uischema.d.ts +4 -4
  355. package/dist/types/uischema.js +3 -3
  356. package/dist/utils/colors.d.ts +1 -1
  357. package/dist/utils/colors.js +97 -95
  358. package/dist/utils/config.d.ts +2 -2
  359. package/dist/utils/config.js +48 -48
  360. package/dist/utils/connections.d.ts +2 -2
  361. package/dist/utils/connections.js +15 -15
  362. package/dist/utils/errors.js +3 -3
  363. package/dist/utils/fetchWithAuth.d.ts +1 -1
  364. package/dist/utils/fetchWithAuth.js +2 -2
  365. package/dist/utils/handleIds.d.ts +2 -2
  366. package/dist/utils/handleIds.js +8 -8
  367. package/dist/utils/handlePositioning.d.ts +1 -1
  368. package/dist/utils/handlePositioning.js +2 -2
  369. package/dist/utils/icons.d.ts +1 -1
  370. package/dist/utils/icons.js +74 -74
  371. package/dist/utils/logger.d.ts +1 -1
  372. package/dist/utils/logger.js +7 -7
  373. package/dist/utils/nodeStatus.d.ts +1 -1
  374. package/dist/utils/nodeStatus.js +48 -48
  375. package/dist/utils/nodeTypes.d.ts +1 -1
  376. package/dist/utils/nodeTypes.js +21 -20
  377. package/dist/utils/nodeWrapper.d.ts +7 -7
  378. package/dist/utils/nodeWrapper.js +21 -19
  379. package/dist/utils/performanceUtils.d.ts +1 -1
  380. package/dist/utils/performanceUtils.js +2 -1
  381. package/dist/utils/sanitize.js +1 -1
  382. package/dist/utils/uischema.d.ts +2 -2
  383. package/dist/utils/uischema.js +8 -8
  384. package/dist/utils/validation.js +20 -8
  385. package/package.json +1 -1
@@ -18,1121 +18,1220 @@
18
18
  -->
19
19
 
20
20
  <script lang="ts">
21
- import { setContext } from 'svelte';
22
- import Icon from '@iconify/svelte';
23
- import type {
24
- ConfigSchema,
25
- WorkflowNode,
26
- WorkflowEdge,
27
- NodeUIExtensions,
28
- ConfigEditOptions,
29
- AuthProvider
30
- } from '../types/index.js';
31
- import type { UISchemaElement } from '../types/uischema.js';
32
- import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
33
- import FormUISchemaRenderer from './form/FormUISchemaRenderer.svelte';
34
- import type { FieldSchema } from './form/index.js';
35
- import {
36
- getEffectiveConfigEditOptions,
37
- fetchDynamicSchema,
38
- resolveExternalEditUrl,
39
- invalidateSchemaCache,
40
- type DynamicSchemaResult
41
- } from '../services/dynamicSchemaService.js';
42
- import { globalSaveWorkflow } from '../services/globalSave.js';
43
- import { getAvailableVariables } from '../services/variableService.js';
44
- import { logger } from '../utils/logger.js';
45
-
46
- interface Props {
47
- /** Optional workflow node (if provided, schema and values are derived from it) */
48
- node?: WorkflowNode;
49
- /** Direct config schema (used when node is not provided) */
50
- schema?: ConfigSchema;
51
- /**
52
- * Optional UI Schema that controls field layout and grouping.
53
- * When provided, fields render according to the UISchema tree structure.
54
- * When absent, falls back to node.data.metadata.uiSchema, then flat rendering.
55
- * @see https://jsonforms.io/docs/uischema
56
- */
57
- uiSchema?: UISchemaElement;
58
- /** Direct config values (used when node is not provided) */
59
- values?: Record<string, unknown>;
60
- /** Whether to show UI extension settings section */
61
- showUIExtensions?: boolean;
62
- /** Optional workflow ID for context in external links */
63
- workflowId?: string;
64
- /** Whether to also save the workflow when saving config */
65
- saveWorkflowWhenSavingConfig?: boolean;
66
- /**
67
- * All workflow nodes (used for deriving template variables from connected nodes).
68
- * When provided along with workflowEdges, enables autocomplete for template fields.
69
- */
70
- workflowNodes?: WorkflowNode[];
71
- /**
72
- * All workflow edges (used for finding connections to derive template variables).
73
- * When provided along with workflowNodes, enables autocomplete for template fields.
74
- */
75
- workflowEdges?: WorkflowEdge[];
76
- /** Auth provider for API requests (used for template variable API mode) */
77
- authProvider?: AuthProvider;
78
- /** Callback when any field value changes (fired on blur for immediate sync) */
79
- onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
80
- /** Callback when form is saved (includes both config and extensions if enabled) */
81
- onSave?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
82
- /** Callback when form is cancelled */
83
- onCancel?: () => void;
84
- }
85
-
86
- let {
87
- node,
88
- schema,
89
- uiSchema,
90
- values,
91
- showUIExtensions = true,
92
- workflowId,
93
- saveWorkflowWhenSavingConfig = false,
94
- workflowNodes = [],
95
- workflowEdges = [],
96
- authProvider,
97
- onChange,
98
- onSave,
99
- onCancel
100
- }: Props = $props();
101
-
102
- // Set context for child components (e.g., FormAutocomplete)
103
- // Use getter functions to ensure child components always get the current prop value,
104
- // even if the prop changes after initial mount
105
- setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
106
-
107
- /**
108
- * State for dynamic schema loading
109
- */
110
- let dynamicSchemaLoading = $state(false);
111
- let dynamicSchemaError = $state<string | null>(null);
112
- let fetchedDynamicSchema = $state<ConfigSchema | null>(null);
113
-
114
- /**
115
- * Get the admin edit configuration for the node
116
- */
117
- const configEditOptions = $derived.by<ConfigEditOptions | undefined>(() => {
118
- if (!node) return undefined;
119
- return getEffectiveConfigEditOptions(node);
120
- });
121
-
122
- /**
123
- * Determine if we should show the external edit link
124
- */
125
- const showExternalEditLink = $derived.by(() => {
126
- if (!configEditOptions?.externalEditLink) return false;
127
- // Show if no dynamic schema, or if both exist but preferDynamicSchema is false
128
- if (!configEditOptions.dynamicSchema) return true;
129
- return !configEditOptions.preferDynamicSchema;
130
- });
131
-
132
- /**
133
- * Determine if we should use/fetch dynamic schema
134
- */
135
- const useDynamicSchema = $derived.by(() => {
136
- if (!configEditOptions?.dynamicSchema) return false;
137
- // Use if no external link, or if both exist and preferDynamicSchema is true
138
- if (!configEditOptions.externalEditLink) return true;
139
- return configEditOptions.preferDynamicSchema === true;
140
- });
141
-
142
- /**
143
- * Get the configuration schema from node metadata, direct prop, or fetched dynamic schema
144
- * Priority: fetchedDynamicSchema > direct schema prop > node metadata configSchema
145
- */
146
- const configSchema = $derived.by<ConfigSchema | undefined>(() => {
147
- // If we have a fetched dynamic schema, use it
148
- if (fetchedDynamicSchema) {
149
- return fetchedDynamicSchema;
150
- }
151
- // Otherwise use the direct prop or node metadata
152
- return schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined);
153
- });
154
-
155
- /**
156
- * Get the UI schema from direct prop or node metadata
157
- * Priority: direct uiSchema prop > node metadata uiSchema
158
- */
159
- const configUISchema = $derived.by<UISchemaElement | undefined>(() => {
160
- return uiSchema ?? (node?.data.metadata?.uiSchema as UISchemaElement | undefined);
161
- });
162
-
163
- /**
164
- * Check if the node needs dynamic schema loading
165
- * Loads when: no static schema OR preferDynamicSchema is true
166
- */
167
- const needsDynamicSchemaLoad = $derived.by(() => {
168
- if (!node) return false;
169
- const staticSchema = schema ?? node.data.metadata?.configSchema;
170
- // Need to load if: (no static schema OR preferDynamicSchema is true) AND dynamic schema is configured
171
- return (
172
- (!staticSchema || configEditOptions?.preferDynamicSchema === true) &&
173
- useDynamicSchema &&
174
- !fetchedDynamicSchema &&
175
- !dynamicSchemaLoading
176
- );
177
- });
178
-
179
- /**
180
- * Get the current configuration from node or direct prop
181
- */
182
- const initialConfig = $derived(values ?? node?.data.config ?? {});
183
-
184
- /**
185
- * Create reactive configuration values using $state
186
- * This fixes the Svelte 5 reactivity warnings
187
- */
188
- let configValues = $state<Record<string, unknown>>({});
189
-
190
- /**
191
- * UI Extension values for display settings
192
- * Merges node type defaults with instance overrides
193
- */
194
- let uiExtensionValues = $state<NodeUIExtensions>({});
195
-
196
- /**
197
- * Flag to track if workflow save is in progress
198
- */
199
- let isSavingWorkflow = $state(false);
200
-
201
- /**
202
- * Get initial UI extensions from node (instance level overrides type level)
203
- */
204
- const initialUIExtensions = $derived.by<NodeUIExtensions>(() => {
205
- if (!node) return {};
206
- // Merge type-level defaults with instance-level overrides
207
- const typeDefaults = node.data.metadata?.extensions?.ui ?? {};
208
- const instanceOverrides = node.data.extensions?.ui ?? {};
209
- return { ...typeDefaults, ...instanceOverrides };
210
- });
211
-
212
- /**
213
- * Fetch dynamic schema when needed
214
- */
215
- async function loadDynamicSchema(): Promise<void> {
216
- if (!node || !configEditOptions?.dynamicSchema) return;
217
-
218
- dynamicSchemaLoading = true;
219
- dynamicSchemaError = null;
220
-
221
- try {
222
- const result: DynamicSchemaResult = await fetchDynamicSchema(
223
- configEditOptions.dynamicSchema,
224
- node,
225
- workflowId
226
- );
227
-
228
- if (result.success && result.schema) {
229
- fetchedDynamicSchema = result.schema;
230
- } else {
231
- dynamicSchemaError =
232
- result.error ?? configEditOptions.errorMessage ?? 'Failed to load configuration schema';
233
- }
234
- } catch (err) {
235
- dynamicSchemaError =
236
- err instanceof Error
237
- ? err.message
238
- : (configEditOptions.errorMessage ?? 'Failed to load configuration schema');
239
- } finally {
240
- dynamicSchemaLoading = false;
241
- }
242
- }
243
-
244
- /**
245
- * Refresh the dynamic schema (invalidate cache and reload)
246
- */
247
- async function refreshDynamicSchema(): Promise<void> {
248
- if (!node || !configEditOptions?.dynamicSchema) return;
249
-
250
- // Invalidate the cache first
251
- invalidateSchemaCache(node, configEditOptions.dynamicSchema);
252
- fetchedDynamicSchema = null;
253
-
254
- // Reload the schema
255
- await loadDynamicSchema();
256
- }
257
-
258
- /**
259
- * Get the resolved external edit URL
260
- */
261
- function getExternalEditUrl(): string {
262
- if (!node || !configEditOptions?.externalEditLink) return '#';
263
- return resolveExternalEditUrl(configEditOptions.externalEditLink, node, workflowId);
264
- }
265
-
266
- /**
267
- * Handle opening external edit link
268
- */
269
- function handleExternalEditClick(): void {
270
- if (!node || !configEditOptions?.externalEditLink) return;
271
-
272
- const url = getExternalEditUrl();
273
- const openInNewTab = configEditOptions.externalEditLink.openInNewTab !== false;
274
-
275
- if (openInNewTab) {
276
- window.open(url, '_blank', 'noopener,noreferrer');
277
- } else {
278
- window.location.href = url;
279
- }
280
- }
281
-
282
- /**
283
- * Auto-load dynamic schema on mount if needed
284
- */
285
- $effect(() => {
286
- if (needsDynamicSchemaLoad) {
287
- loadDynamicSchema();
288
- }
289
- });
290
-
291
- /**
292
- * Initialize config values when node/schema changes
293
- */
294
- $effect(() => {
295
- if (configSchema?.properties) {
296
- const mergedConfig: Record<string, unknown> = {};
297
- Object.entries(configSchema.properties).forEach(([key, field]) => {
298
- const fieldConfig = field as Record<string, unknown>;
299
- // Use existing value if available, otherwise use default
300
- mergedConfig[key] =
301
- initialConfig[key] !== undefined ? initialConfig[key] : fieldConfig.default;
302
- });
303
- configValues = mergedConfig;
304
- }
305
- });
306
-
307
- /**
308
- * Initialize UI extension values when node changes
309
- */
310
- $effect(() => {
311
- uiExtensionValues = {
312
- hideUnconnectedHandles: initialUIExtensions.hideUnconnectedHandles ?? false
313
- };
314
- });
315
-
316
- /**
317
- * Check if a field is required based on schema
318
- */
319
- function isFieldRequired(key: string): boolean {
320
- if (!configSchema?.required) return false;
321
- return configSchema.required.includes(key);
322
- }
323
-
324
- /**
325
- * Handle field value changes from FormField components
326
- */
327
- function handleFieldChange(key: string, value: unknown): void {
328
- configValues[key] = value;
329
- }
330
-
331
- /**
332
- * Handle form field blur - sync changes to workflow immediately
333
- * Uses focusout which bubbles from child elements
334
- * This enables auto-save behavior without requiring explicit Save button clicks
335
- */
336
- function handleFormBlur(): void {
337
- if (onChange) {
338
- const extensions = showUIExtensions && node ? uiExtensionValues : undefined;
339
- onChange({ ...configValues }, extensions);
340
- }
341
- }
342
-
343
- /**
344
- * Handle form submission
345
- * Collects both config values and UI extension values
346
- * Optionally saves the workflow if the option is enabled
347
- */
348
- async function handleSave(): Promise<void> {
349
- // Collect all form values including hidden fields
350
- const form = document.querySelector('.config-form');
351
- const updatedConfig: Record<string, unknown> = { ...configValues };
352
-
353
- if (form) {
354
- const inputs = form.querySelectorAll('input, select, textarea');
355
- inputs.forEach((input: Element) => {
356
- const inputEl = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
357
- // Skip UI extension fields (prefixed with ext-)
358
- if (inputEl.id && !inputEl.id.startsWith('ext-')) {
359
- if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
360
- updatedConfig[inputEl.id] = inputEl.checked;
361
- } else if (
362
- inputEl instanceof HTMLInputElement &&
363
- (inputEl.type === 'number' || inputEl.type === 'range')
364
- ) {
365
- updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
366
- } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
367
- // Parse hidden field values that might be JSON
368
- try {
369
- const parsed = JSON.parse(inputEl.value);
370
- updatedConfig[inputEl.id] = parsed;
371
- } catch {
372
- // If not JSON, use raw value
373
- updatedConfig[inputEl.id] = inputEl.value;
374
- }
375
- } else {
376
- updatedConfig[inputEl.id] = inputEl.value;
377
- }
378
- }
379
- });
380
- }
381
-
382
- // Preserve hidden field values from original config if not collected from form
383
- if (initialConfig && configSchema?.properties) {
384
- Object.entries(configSchema.properties).forEach(
385
- ([key, property]: [string, Record<string, unknown>]) => {
386
- if (property.format === 'hidden' && !(key in updatedConfig) && key in initialConfig) {
387
- updatedConfig[key] = initialConfig[key];
388
- }
389
- }
390
- );
391
- }
392
-
393
- // Pass UI extensions only if enabled
394
- if (onSave) {
395
- if (showUIExtensions && node) {
396
- onSave(updatedConfig, uiExtensionValues);
397
- } else {
398
- onSave(updatedConfig);
399
- }
400
- }
401
-
402
- // Save workflow if the option is enabled
403
- if (saveWorkflowWhenSavingConfig) {
404
- isSavingWorkflow = true;
405
- try {
406
- await globalSaveWorkflow();
407
- } catch (error) {
408
- logger.error('Failed to save workflow after config save:', error);
409
- } finally {
410
- isSavingWorkflow = false;
411
- }
412
- }
413
- }
414
-
415
- /**
416
- * Convert ConfigProperty to FieldSchema for FormField component.
417
- * Processes template fields to inject computed variable schema.
418
- *
419
- * For template fields, the `variables` config controls which input ports
420
- * provide variables for autocomplete.
421
- */
422
- function toFieldSchema(property: Record<string, unknown>): FieldSchema {
423
- const fieldSchema = property as FieldSchema;
424
-
425
- // Process template fields to compute variable schema
426
- if (
427
- fieldSchema.format === 'template' &&
428
- node &&
429
- workflowNodes.length > 0 &&
430
- workflowEdges.length > 0
431
- ) {
432
- // Get the variables config (may be undefined or partially defined)
433
- const variablesConfig = fieldSchema.variables;
434
-
435
- // Compute the variable schema with optional port filtering and port name prefixing
436
- const computedSchema = getAvailableVariables(node, workflowNodes, workflowEdges, {
437
- targetPortIds: variablesConfig?.ports,
438
- includePortName: variablesConfig?.includePortName
439
- });
440
-
441
- // Merge computed schema with any pre-defined schema
442
- const mergedSchema = variablesConfig?.schema
443
- ? {
444
- variables: {
445
- ...computedSchema.variables,
446
- ...variablesConfig.schema.variables
447
- }
448
- }
449
- : computedSchema;
450
-
451
- return {
452
- ...fieldSchema,
453
- variables: {
454
- ...variablesConfig,
455
- schema: mergedSchema
456
- }
457
- } as FieldSchema;
458
- }
459
-
460
- return fieldSchema;
461
- }
21
+ import { setContext } from "svelte";
22
+ import Icon from "@iconify/svelte";
23
+ import type {
24
+ ConfigSchema,
25
+ WorkflowNode,
26
+ WorkflowEdge,
27
+ NodeUIExtensions,
28
+ ConfigEditOptions,
29
+ AuthProvider,
30
+ } from "../types/index.js";
31
+ import type { UISchemaElement } from "../types/uischema.js";
32
+ import {
33
+ FormField,
34
+ FormFieldWrapper,
35
+ FormToggle,
36
+ } from "./form/index.js";
37
+ import FormUISchemaRenderer from "./form/FormUISchemaRenderer.svelte";
38
+ import type { FieldSchema } from "./form/index.js";
39
+ import {
40
+ getEffectiveConfigEditOptions,
41
+ fetchDynamicSchema,
42
+ resolveExternalEditUrl,
43
+ invalidateSchemaCache,
44
+ type DynamicSchemaResult,
45
+ } from "../services/dynamicSchemaService.js";
46
+ import { globalSaveWorkflow } from "../services/globalSave.js";
47
+ import { getAvailableVariables } from "../services/variableService.js";
48
+ import { logger } from "../utils/logger.js";
49
+
50
+ interface Props {
51
+ /** Optional workflow node (if provided, schema and values are derived from it) */
52
+ node?: WorkflowNode;
53
+ /** Direct config schema (used when node is not provided) */
54
+ schema?: ConfigSchema;
55
+ /**
56
+ * Optional UI Schema that controls field layout and grouping.
57
+ * When provided, fields render according to the UISchema tree structure.
58
+ * When absent, falls back to node.data.metadata.uiSchema, then flat rendering.
59
+ * @see https://jsonforms.io/docs/uischema
60
+ */
61
+ uiSchema?: UISchemaElement;
62
+ /** Direct config values (used when node is not provided) */
63
+ values?: Record<string, unknown>;
64
+ /** Whether to show UI extension settings section */
65
+ showUIExtensions?: boolean;
66
+ /** Optional workflow ID for context in external links */
67
+ workflowId?: string;
68
+ /** Whether to also save the workflow when saving config */
69
+ saveWorkflowWhenSavingConfig?: boolean;
70
+ /**
71
+ * All workflow nodes (used for deriving template variables from connected nodes).
72
+ * When provided along with workflowEdges, enables autocomplete for template fields.
73
+ */
74
+ workflowNodes?: WorkflowNode[];
75
+ /**
76
+ * All workflow edges (used for finding connections to derive template variables).
77
+ * When provided along with workflowNodes, enables autocomplete for template fields.
78
+ */
79
+ workflowEdges?: WorkflowEdge[];
80
+ /** Auth provider for API requests (used for template variable API mode) */
81
+ authProvider?: AuthProvider;
82
+ /** Callback when any field value changes (fired on blur for immediate sync) */
83
+ onChange?: (
84
+ config: Record<string, unknown>,
85
+ uiExtensions?: NodeUIExtensions,
86
+ ) => void;
87
+ /** Callback when form is saved (includes both config and extensions if enabled) */
88
+ onSave?: (
89
+ config: Record<string, unknown>,
90
+ uiExtensions?: NodeUIExtensions,
91
+ ) => void;
92
+ /** Callback when form is cancelled */
93
+ onCancel?: () => void;
94
+ }
95
+
96
+ let {
97
+ node,
98
+ schema,
99
+ uiSchema,
100
+ values,
101
+ showUIExtensions = true,
102
+ workflowId,
103
+ saveWorkflowWhenSavingConfig = false,
104
+ workflowNodes = [],
105
+ workflowEdges = [],
106
+ authProvider,
107
+ onChange,
108
+ onSave,
109
+ onCancel,
110
+ }: Props = $props();
111
+
112
+ // Set context for child components (e.g., FormAutocomplete)
113
+ // Use getter functions to ensure child components always get the current prop value,
114
+ // even if the prop changes after initial mount
115
+ setContext<() => AuthProvider | undefined>(
116
+ "flowdrop:getAuthProvider",
117
+ () => authProvider,
118
+ );
119
+
120
+ /**
121
+ * State for dynamic schema loading
122
+ */
123
+ let dynamicSchemaLoading = $state(false);
124
+ let dynamicSchemaError = $state<string | null>(null);
125
+ let fetchedDynamicSchema = $state<ConfigSchema | null>(null);
126
+
127
+ /**
128
+ * Get the admin edit configuration for the node
129
+ */
130
+ const configEditOptions = $derived.by<ConfigEditOptions | undefined>(() => {
131
+ if (!node) return undefined;
132
+ return getEffectiveConfigEditOptions(node);
133
+ });
134
+
135
+ /**
136
+ * Determine if we should show the external edit link
137
+ */
138
+ const showExternalEditLink = $derived.by(() => {
139
+ if (!configEditOptions?.externalEditLink) return false;
140
+ // Show if no dynamic schema, or if both exist but preferDynamicSchema is false
141
+ if (!configEditOptions.dynamicSchema) return true;
142
+ return !configEditOptions.preferDynamicSchema;
143
+ });
144
+
145
+ /**
146
+ * Determine if we should use/fetch dynamic schema
147
+ */
148
+ const useDynamicSchema = $derived.by(() => {
149
+ if (!configEditOptions?.dynamicSchema) return false;
150
+ // Use if no external link, or if both exist and preferDynamicSchema is true
151
+ if (!configEditOptions.externalEditLink) return true;
152
+ return configEditOptions.preferDynamicSchema === true;
153
+ });
154
+
155
+ /**
156
+ * Get the configuration schema from node metadata, direct prop, or fetched dynamic schema
157
+ * Priority: fetchedDynamicSchema > direct schema prop > node metadata configSchema
158
+ */
159
+ const configSchema = $derived.by<ConfigSchema | undefined>(() => {
160
+ // If we have a fetched dynamic schema, use it
161
+ if (fetchedDynamicSchema) {
162
+ return fetchedDynamicSchema;
163
+ }
164
+ // Otherwise use the direct prop or node metadata
165
+ return (
166
+ schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined)
167
+ );
168
+ });
169
+
170
+ /**
171
+ * Get the UI schema from direct prop or node metadata
172
+ * Priority: direct uiSchema prop > node metadata uiSchema
173
+ */
174
+ const configUISchema = $derived.by<UISchemaElement | undefined>(() => {
175
+ return (
176
+ uiSchema ?? (node?.data.metadata?.uiSchema as UISchemaElement | undefined)
177
+ );
178
+ });
179
+
180
+ /**
181
+ * Check if the node needs dynamic schema loading
182
+ * Loads when: no static schema OR preferDynamicSchema is true
183
+ */
184
+ const needsDynamicSchemaLoad = $derived.by(() => {
185
+ if (!node) return false;
186
+ const staticSchema = schema ?? node.data.metadata?.configSchema;
187
+ // Need to load if: (no static schema OR preferDynamicSchema is true) AND dynamic schema is configured
188
+ return (
189
+ (!staticSchema || configEditOptions?.preferDynamicSchema === true) &&
190
+ useDynamicSchema &&
191
+ !fetchedDynamicSchema &&
192
+ !dynamicSchemaLoading
193
+ );
194
+ });
195
+
196
+ /**
197
+ * Get the current configuration from node or direct prop
198
+ */
199
+ const initialConfig = $derived(values ?? node?.data.config ?? {});
200
+
201
+ /**
202
+ * Create reactive configuration values using $state
203
+ * This fixes the Svelte 5 reactivity warnings
204
+ */
205
+ let configValues = $state<Record<string, unknown>>({});
206
+
207
+ /**
208
+ * UI Extension values for display settings
209
+ * Merges node type defaults with instance overrides
210
+ */
211
+ let uiExtensionValues = $state<NodeUIExtensions>({});
212
+
213
+ /**
214
+ * Flag to track if workflow save is in progress
215
+ */
216
+ let isSavingWorkflow = $state(false);
217
+
218
+ /**
219
+ * Get initial UI extensions from node (instance level overrides type level)
220
+ */
221
+ const initialUIExtensions = $derived.by<NodeUIExtensions>(() => {
222
+ if (!node) return {};
223
+ // Merge type-level defaults with instance-level overrides
224
+ const typeDefaults = node.data.metadata?.extensions?.ui ?? {};
225
+ const instanceOverrides = node.data.extensions?.ui ?? {};
226
+ return { ...typeDefaults, ...instanceOverrides };
227
+ });
228
+
229
+ /**
230
+ * Fetch dynamic schema when needed
231
+ */
232
+ async function loadDynamicSchema(): Promise<void> {
233
+ if (!node || !configEditOptions?.dynamicSchema) return;
234
+
235
+ dynamicSchemaLoading = true;
236
+ dynamicSchemaError = null;
237
+
238
+ try {
239
+ const result: DynamicSchemaResult = await fetchDynamicSchema(
240
+ configEditOptions.dynamicSchema,
241
+ node,
242
+ workflowId,
243
+ );
244
+
245
+ if (result.success && result.schema) {
246
+ fetchedDynamicSchema = result.schema;
247
+ } else {
248
+ dynamicSchemaError =
249
+ result.error ??
250
+ configEditOptions.errorMessage ??
251
+ "Failed to load configuration schema";
252
+ }
253
+ } catch (err) {
254
+ dynamicSchemaError =
255
+ err instanceof Error
256
+ ? err.message
257
+ : (configEditOptions.errorMessage ??
258
+ "Failed to load configuration schema");
259
+ } finally {
260
+ dynamicSchemaLoading = false;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Refresh the dynamic schema (invalidate cache and reload)
266
+ */
267
+ async function refreshDynamicSchema(): Promise<void> {
268
+ if (!node || !configEditOptions?.dynamicSchema) return;
269
+
270
+ // Invalidate the cache first
271
+ invalidateSchemaCache(node, configEditOptions.dynamicSchema);
272
+ fetchedDynamicSchema = null;
273
+
274
+ // Reload the schema
275
+ await loadDynamicSchema();
276
+ }
277
+
278
+ /**
279
+ * Get the resolved external edit URL
280
+ */
281
+ function getExternalEditUrl(): string {
282
+ if (!node || !configEditOptions?.externalEditLink) return "#";
283
+ return resolveExternalEditUrl(
284
+ configEditOptions.externalEditLink,
285
+ node,
286
+ workflowId,
287
+ );
288
+ }
289
+
290
+ /**
291
+ * Handle opening external edit link
292
+ */
293
+ function handleExternalEditClick(): void {
294
+ if (!node || !configEditOptions?.externalEditLink) return;
295
+
296
+ const url = getExternalEditUrl();
297
+ const openInNewTab =
298
+ configEditOptions.externalEditLink.openInNewTab !== false;
299
+
300
+ if (openInNewTab) {
301
+ window.open(url, "_blank", "noopener,noreferrer");
302
+ } else {
303
+ window.location.href = url;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Auto-load dynamic schema on mount if needed
309
+ */
310
+ $effect(() => {
311
+ if (needsDynamicSchemaLoad) {
312
+ loadDynamicSchema();
313
+ }
314
+ });
315
+
316
+ /**
317
+ * Initialize config values when node/schema changes
318
+ */
319
+ $effect(() => {
320
+ if (configSchema?.properties) {
321
+ const mergedConfig: Record<string, unknown> = {};
322
+ Object.entries(configSchema.properties).forEach(([key, field]) => {
323
+ const fieldConfig = field as Record<string, unknown>;
324
+ // Use existing value if available, otherwise use default
325
+ mergedConfig[key] =
326
+ initialConfig[key] !== undefined
327
+ ? initialConfig[key]
328
+ : fieldConfig.default;
329
+ });
330
+ configValues = mergedConfig;
331
+ }
332
+ });
333
+
334
+ /**
335
+ * Initialize UI extension values when node changes
336
+ */
337
+ $effect(() => {
338
+ uiExtensionValues = {
339
+ hideUnconnectedHandles:
340
+ initialUIExtensions.hideUnconnectedHandles ?? false,
341
+ };
342
+ });
343
+
344
+ /**
345
+ * Check if a field is required based on schema
346
+ */
347
+ function isFieldRequired(key: string): boolean {
348
+ if (!configSchema?.required) return false;
349
+ return configSchema.required.includes(key);
350
+ }
351
+
352
+ /**
353
+ * Handle field value changes from FormField components
354
+ */
355
+ function handleFieldChange(key: string, value: unknown): void {
356
+ configValues[key] = value;
357
+ }
358
+
359
+ /**
360
+ * Handle form field blur - sync changes to workflow immediately
361
+ * Uses focusout which bubbles from child elements
362
+ * This enables auto-save behavior without requiring explicit Save button clicks
363
+ */
364
+ function handleFormBlur(): void {
365
+ if (onChange) {
366
+ const extensions =
367
+ showUIExtensions && node ? uiExtensionValues : undefined;
368
+ onChange({ ...configValues }, extensions);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Handle form submission
374
+ * Collects both config values and UI extension values
375
+ * Optionally saves the workflow if the option is enabled
376
+ */
377
+ async function handleSave(): Promise<void> {
378
+ // Collect all form values including hidden fields
379
+ const form = document.querySelector(".config-form");
380
+ const updatedConfig: Record<string, unknown> = { ...configValues };
381
+
382
+ if (form) {
383
+ const inputs = form.querySelectorAll("input, select, textarea");
384
+ inputs.forEach((input: Element) => {
385
+ const inputEl = input as
386
+ | HTMLInputElement
387
+ | HTMLSelectElement
388
+ | HTMLTextAreaElement;
389
+ // Skip UI extension fields (prefixed with ext-)
390
+ if (inputEl.id && !inputEl.id.startsWith("ext-")) {
391
+ if (
392
+ inputEl instanceof HTMLInputElement &&
393
+ inputEl.type === "checkbox"
394
+ ) {
395
+ updatedConfig[inputEl.id] = inputEl.checked;
396
+ } else if (
397
+ inputEl instanceof HTMLInputElement &&
398
+ (inputEl.type === "number" || inputEl.type === "range")
399
+ ) {
400
+ updatedConfig[inputEl.id] = inputEl.value
401
+ ? Number(inputEl.value)
402
+ : inputEl.value;
403
+ } else if (
404
+ inputEl instanceof HTMLInputElement &&
405
+ inputEl.type === "hidden"
406
+ ) {
407
+ // Parse hidden field values that might be JSON
408
+ try {
409
+ const parsed = JSON.parse(inputEl.value);
410
+ updatedConfig[inputEl.id] = parsed;
411
+ } catch {
412
+ // If not JSON, use raw value
413
+ updatedConfig[inputEl.id] = inputEl.value;
414
+ }
415
+ } else {
416
+ updatedConfig[inputEl.id] = inputEl.value;
417
+ }
418
+ }
419
+ });
420
+ }
421
+
422
+ // Preserve hidden field values from original config if not collected from form
423
+ if (initialConfig && configSchema?.properties) {
424
+ Object.entries(configSchema.properties).forEach(
425
+ ([key, property]: [string, Record<string, unknown>]) => {
426
+ if (
427
+ property.format === "hidden" &&
428
+ !(key in updatedConfig) &&
429
+ key in initialConfig
430
+ ) {
431
+ updatedConfig[key] = initialConfig[key];
432
+ }
433
+ },
434
+ );
435
+ }
436
+
437
+ // Pass UI extensions only if enabled
438
+ if (onSave) {
439
+ if (showUIExtensions && node) {
440
+ onSave(updatedConfig, uiExtensionValues);
441
+ } else {
442
+ onSave(updatedConfig);
443
+ }
444
+ }
445
+
446
+ // Save workflow if the option is enabled
447
+ if (saveWorkflowWhenSavingConfig) {
448
+ isSavingWorkflow = true;
449
+ try {
450
+ await globalSaveWorkflow();
451
+ } catch (error) {
452
+ logger.error("Failed to save workflow after config save:", error);
453
+ } finally {
454
+ isSavingWorkflow = false;
455
+ }
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Convert ConfigProperty to FieldSchema for FormField component.
461
+ * Processes template fields to inject computed variable schema.
462
+ *
463
+ * For template fields, the `variables` config controls which input ports
464
+ * provide variables for autocomplete.
465
+ */
466
+ function toFieldSchema(property: Record<string, unknown>): FieldSchema {
467
+ const fieldSchema = property as FieldSchema;
468
+
469
+ // Process template fields to compute variable schema
470
+ if (
471
+ fieldSchema.format === "template" &&
472
+ node &&
473
+ workflowNodes.length > 0 &&
474
+ workflowEdges.length > 0
475
+ ) {
476
+ // Get the variables config (may be undefined or partially defined)
477
+ const variablesConfig = fieldSchema.variables;
478
+
479
+ // Compute the variable schema with optional port filtering and port name prefixing
480
+ const computedSchema = getAvailableVariables(
481
+ node,
482
+ workflowNodes,
483
+ workflowEdges,
484
+ {
485
+ targetPortIds: variablesConfig?.ports,
486
+ includePortName: variablesConfig?.includePortName,
487
+ },
488
+ );
489
+
490
+ // Merge computed schema with any pre-defined schema
491
+ const mergedSchema = variablesConfig?.schema
492
+ ? {
493
+ variables: {
494
+ ...computedSchema.variables,
495
+ ...variablesConfig.schema.variables,
496
+ },
497
+ }
498
+ : computedSchema;
499
+
500
+ return {
501
+ ...fieldSchema,
502
+ variables: {
503
+ ...variablesConfig,
504
+ schema: mergedSchema,
505
+ },
506
+ } as FieldSchema;
507
+ }
508
+
509
+ return fieldSchema;
510
+ }
462
511
  </script>
463
512
 
464
513
  <!-- External Edit Link Section (shown when configured and preferred) -->
465
514
  {#if showExternalEditLink && configEditOptions?.externalEditLink}
466
- <div class="config-form__admin-edit">
467
- <div class="config-form__admin-edit-header">
468
- <Icon icon="heroicons:arrow-top-right-on-square" />
469
- <span>External Configuration</span>
470
- </div>
471
- <div class="config-form__admin-edit-content">
472
- <p class="config-form__admin-edit-description">
473
- {configEditOptions.externalEditLink.description ??
474
- 'This node requires external configuration. Click the button below to open the configuration panel.'}
475
- </p>
476
- <button
477
- type="button"
478
- class="config-form__button config-form__button--external"
479
- onclick={handleExternalEditClick}
480
- >
481
- <Icon
482
- icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
483
- />
484
- <span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
485
- </button>
486
- </div>
487
- </div>
515
+ <div class="config-form__admin-edit">
516
+ <div class="config-form__admin-edit-header">
517
+ <Icon icon="heroicons:arrow-top-right-on-square" />
518
+ <span>External Configuration</span>
519
+ </div>
520
+ <div class="config-form__admin-edit-content">
521
+ <p class="config-form__admin-edit-description">
522
+ {configEditOptions.externalEditLink.description ??
523
+ "This node requires external configuration. Click the button below to open the configuration panel."}
524
+ </p>
525
+ <button
526
+ type="button"
527
+ class="config-form__button config-form__button--external"
528
+ onclick={handleExternalEditClick}
529
+ >
530
+ <Icon
531
+ icon={configEditOptions.externalEditLink.icon ??
532
+ "heroicons:arrow-top-right-on-square"}
533
+ />
534
+ <span
535
+ >{configEditOptions.externalEditLink.label ??
536
+ "Configure Externally"}</span
537
+ >
538
+ </button>
539
+ </div>
540
+ </div>
488
541
  {/if}
489
542
 
490
543
  <!-- Dynamic Schema Loading State -->
491
544
  {#if dynamicSchemaLoading}
492
- <div class="config-form__loading">
493
- <div class="config-form__loading-spinner"></div>
494
- <p class="config-form__loading-text">
495
- {configEditOptions?.loadingMessage ?? 'Loading configuration options...'}
496
- </p>
497
- </div>
545
+ <div class="config-form__loading">
546
+ <div class="config-form__loading-spinner"></div>
547
+ <p class="config-form__loading-text">
548
+ {configEditOptions?.loadingMessage ?? "Loading configuration options..."}
549
+ </p>
550
+ </div>
498
551
  {:else if dynamicSchemaError}
499
- <div class="config-form__error">
500
- <div class="config-form__error-header">
501
- <Icon icon="heroicons:exclamation-triangle" />
502
- <span>Configuration Error</span>
503
- </div>
504
- <div class="config-form__error-content">
505
- <p class="config-form__error-message">{dynamicSchemaError}</p>
506
- <div class="config-form__error-actions">
507
- <button
508
- type="button"
509
- class="config-form__button config-form__button--secondary"
510
- onclick={refreshDynamicSchema}
511
- >
512
- <Icon icon="heroicons:arrow-path" />
513
- <span>Retry</span>
514
- </button>
515
- {#if configEditOptions?.externalEditLink}
516
- <button
517
- type="button"
518
- class="config-form__button config-form__button--external"
519
- onclick={handleExternalEditClick}
520
- >
521
- <Icon
522
- icon={configEditOptions.externalEditLink.icon ??
523
- 'heroicons:arrow-top-right-on-square'}
524
- />
525
- <span>{configEditOptions.externalEditLink.label ?? 'Use External Editor'}</span>
526
- </button>
527
- {/if}
528
- </div>
529
- </div>
530
- </div>
552
+ <div class="config-form__error">
553
+ <div class="config-form__error-header">
554
+ <Icon icon="heroicons:exclamation-triangle" />
555
+ <span>Configuration Error</span>
556
+ </div>
557
+ <div class="config-form__error-content">
558
+ <p class="config-form__error-message">{dynamicSchemaError}</p>
559
+ <div class="config-form__error-actions">
560
+ <button
561
+ type="button"
562
+ class="config-form__button config-form__button--secondary"
563
+ onclick={refreshDynamicSchema}
564
+ >
565
+ <Icon icon="heroicons:arrow-path" />
566
+ <span>Retry</span>
567
+ </button>
568
+ {#if configEditOptions?.externalEditLink}
569
+ <button
570
+ type="button"
571
+ class="config-form__button config-form__button--external"
572
+ onclick={handleExternalEditClick}
573
+ >
574
+ <Icon
575
+ icon={configEditOptions.externalEditLink.icon ??
576
+ "heroicons:arrow-top-right-on-square"}
577
+ />
578
+ <span
579
+ >{configEditOptions.externalEditLink.label ??
580
+ "Use External Editor"}</span
581
+ >
582
+ </button>
583
+ {/if}
584
+ </div>
585
+ </div>
586
+ </div>
531
587
  {:else if configSchema}
532
- <form
533
- class="config-form"
534
- onfocusout={handleFormBlur}
535
- onsubmit={(e) => {
536
- e.preventDefault();
537
- }}
538
- >
539
- <!-- Dynamic Schema Refresh Button -->
540
- {#if fetchedDynamicSchema && configEditOptions?.showRefreshButton !== false}
541
- <div class="config-form__schema-actions">
542
- <button
543
- type="button"
544
- class="config-form__schema-refresh"
545
- onclick={refreshDynamicSchema}
546
- title="Refresh configuration schema"
547
- >
548
- <Icon icon="heroicons:arrow-path" />
549
- <span>Refresh Schema</span>
550
- </button>
551
- {#if configEditOptions?.externalEditLink}
552
- <button
553
- type="button"
554
- class="config-form__schema-external"
555
- onclick={handleExternalEditClick}
556
- title={configEditOptions.externalEditLink.description ?? 'Open external editor'}
557
- >
558
- <Icon
559
- icon={configEditOptions.externalEditLink.icon ??
560
- 'heroicons:arrow-top-right-on-square'}
561
- />
562
- <span>{configEditOptions.externalEditLink.label ?? 'External Editor'}</span>
563
- </button>
564
- {/if}
565
- </div>
566
- {/if}
567
-
568
- {#if configSchema.properties}
569
- <div class="config-form__fields">
570
- {#if configUISchema}
571
- <FormUISchemaRenderer
572
- element={configUISchema}
573
- schema={configSchema}
574
- values={configValues}
575
- requiredFields={configSchema.required ?? []}
576
- onFieldChange={handleFieldChange}
577
- {toFieldSchema}
578
- {node}
579
- nodes={workflowNodes}
580
- edges={workflowEdges}
581
- {workflowId}
582
- {authProvider}
583
- />
584
- {:else}
585
- {#each Object.entries(configSchema.properties) as [key, field], index (key)}
586
- {@const fieldSchema = toFieldSchema(field as Record<string, unknown>)}
587
- {@const required = isFieldRequired(key)}
588
-
589
- <FormField
590
- fieldKey={key}
591
- schema={fieldSchema}
592
- value={configValues[key]}
593
- {required}
594
- animationIndex={index}
595
- {node}
596
- nodes={workflowNodes}
597
- edges={workflowEdges}
598
- {workflowId}
599
- {authProvider}
600
- onChange={(val) => handleFieldChange(key, val)}
601
- />
602
- {/each}
603
- {/if}
604
- </div>
605
- {:else}
606
- <!-- If no properties, show the raw schema for debugging -->
607
- <div class="config-form__debug">
608
- <div class="config-form__debug-header">
609
- <Icon icon="heroicons:bug-ant" class="config-form__debug-icon" />
610
- <span>Debug - Config Schema</span>
611
- </div>
612
- <pre class="config-form__debug-content">{JSON.stringify(configSchema, null, 2)}</pre>
613
- </div>
614
- {/if}
615
-
616
- <!-- UI Extensions Section -->
617
- {#if showUIExtensions && node}
618
- <div class="config-form__extensions">
619
- <div class="config-form__extensions-header">
620
- <Icon icon="heroicons:adjustments-horizontal" class="config-form__extensions-icon" />
621
- <span>Display Settings</span>
622
- </div>
623
- <div class="config-form__extensions-content">
624
- <!-- Hide Unconnected Handles Toggle -->
625
- <FormFieldWrapper
626
- id="ext-hideUnconnectedHandles"
627
- label="Hide Unconnected Ports"
628
- description="Hide input and output ports that are not connected to reduce visual clutter"
629
- >
630
- <FormToggle
631
- id="ext-hideUnconnectedHandles"
632
- value={Boolean(uiExtensionValues.hideUnconnectedHandles)}
633
- onLabel="Hidden"
634
- offLabel="Visible"
635
- ariaDescribedBy="ext-hideUnconnectedHandles-description"
636
- onChange={(val) => {
637
- uiExtensionValues.hideUnconnectedHandles = val;
638
- handleFormBlur();
639
- }}
640
- />
641
- </FormFieldWrapper>
642
- </div>
643
- </div>
644
- {/if}
645
-
646
- <!-- Footer Actions - Only shown when onSave is provided and onChange is not -->
647
- <!-- With onChange (on-blur sync), changes are saved automatically, so no Save button needed -->
648
- {#if onSave && !onChange}
649
- <div class="config-form__footer">
650
- <button
651
- type="button"
652
- class="config-form__button config-form__button--secondary"
653
- onclick={onCancel}
654
- disabled={isSavingWorkflow}
655
- >
656
- <Icon icon="heroicons:x-mark" class="config-form__button-icon" />
657
- <span>Cancel</span>
658
- </button>
659
- <button
660
- type="submit"
661
- class="config-form__button config-form__button--primary"
662
- onclick={handleSave}
663
- disabled={isSavingWorkflow}
664
- >
665
- {#if isSavingWorkflow}
666
- <span class="config-form__button-spinner"></span>
667
- <span>Saving...</span>
668
- {:else}
669
- <Icon icon="heroicons:check" class="config-form__button-icon" />
670
- <span>Save Changes</span>
671
- {/if}
672
- </button>
673
- </div>
674
- {/if}
675
- </form>
588
+ <form
589
+ class="config-form"
590
+ onfocusout={handleFormBlur}
591
+ onsubmit={(e) => {
592
+ e.preventDefault();
593
+ }}
594
+ >
595
+ <!-- Dynamic Schema Refresh Button -->
596
+ {#if fetchedDynamicSchema && configEditOptions?.showRefreshButton !== false}
597
+ <div class="config-form__schema-actions">
598
+ <button
599
+ type="button"
600
+ class="config-form__schema-refresh"
601
+ onclick={refreshDynamicSchema}
602
+ title="Refresh configuration schema"
603
+ >
604
+ <Icon icon="heroicons:arrow-path" />
605
+ <span>Refresh Schema</span>
606
+ </button>
607
+ {#if configEditOptions?.externalEditLink}
608
+ <button
609
+ type="button"
610
+ class="config-form__schema-external"
611
+ onclick={handleExternalEditClick}
612
+ title={configEditOptions.externalEditLink.description ??
613
+ "Open external editor"}
614
+ >
615
+ <Icon
616
+ icon={configEditOptions.externalEditLink.icon ??
617
+ "heroicons:arrow-top-right-on-square"}
618
+ />
619
+ <span
620
+ >{configEditOptions.externalEditLink.label ??
621
+ "External Editor"}</span
622
+ >
623
+ </button>
624
+ {/if}
625
+ </div>
626
+ {/if}
627
+
628
+ {#if configSchema.properties}
629
+ <div class="config-form__fields">
630
+ {#if configUISchema}
631
+ <FormUISchemaRenderer
632
+ element={configUISchema}
633
+ schema={configSchema}
634
+ values={configValues}
635
+ requiredFields={configSchema.required ?? []}
636
+ onFieldChange={handleFieldChange}
637
+ {toFieldSchema}
638
+ {node}
639
+ nodes={workflowNodes}
640
+ edges={workflowEdges}
641
+ {workflowId}
642
+ {authProvider}
643
+ />
644
+ {:else}
645
+ {#each Object.entries(configSchema.properties) as [key, field], index (key)}
646
+ {@const fieldSchema = toFieldSchema(
647
+ field as Record<string, unknown>,
648
+ )}
649
+ {@const required = isFieldRequired(key)}
650
+
651
+ <FormField
652
+ fieldKey={key}
653
+ schema={fieldSchema}
654
+ value={configValues[key]}
655
+ {required}
656
+ animationIndex={index}
657
+ {node}
658
+ nodes={workflowNodes}
659
+ edges={workflowEdges}
660
+ {workflowId}
661
+ {authProvider}
662
+ onChange={(val) => handleFieldChange(key, val)}
663
+ />
664
+ {/each}
665
+ {/if}
666
+ </div>
667
+ {:else}
668
+ <!-- If no properties, show the raw schema for debugging -->
669
+ <div class="config-form__debug">
670
+ <div class="config-form__debug-header">
671
+ <Icon icon="heroicons:bug-ant" class="config-form__debug-icon" />
672
+ <span>Debug - Config Schema</span>
673
+ </div>
674
+ <pre class="config-form__debug-content">{JSON.stringify(
675
+ configSchema,
676
+ null,
677
+ 2,
678
+ )}</pre>
679
+ </div>
680
+ {/if}
681
+
682
+ <!-- UI Extensions Section -->
683
+ {#if showUIExtensions && node}
684
+ <div class="config-form__extensions">
685
+ <div class="config-form__extensions-header">
686
+ <Icon
687
+ icon="heroicons:adjustments-horizontal"
688
+ class="config-form__extensions-icon"
689
+ />
690
+ <span>Display Settings</span>
691
+ </div>
692
+ <div class="config-form__extensions-content">
693
+ <!-- Hide Unconnected Handles Toggle -->
694
+ <FormFieldWrapper
695
+ id="ext-hideUnconnectedHandles"
696
+ label="Hide Unconnected Ports"
697
+ description="Hide input and output ports that are not connected to reduce visual clutter"
698
+ >
699
+ <FormToggle
700
+ id="ext-hideUnconnectedHandles"
701
+ value={Boolean(uiExtensionValues.hideUnconnectedHandles)}
702
+ onLabel="Hidden"
703
+ offLabel="Visible"
704
+ ariaDescribedBy="ext-hideUnconnectedHandles-description"
705
+ onChange={(val) => {
706
+ uiExtensionValues.hideUnconnectedHandles = val;
707
+ handleFormBlur();
708
+ }}
709
+ />
710
+ </FormFieldWrapper>
711
+ </div>
712
+ </div>
713
+ {/if}
714
+
715
+ <!-- Footer Actions - Only shown when onSave is provided and onChange is not -->
716
+ <!-- With onChange (on-blur sync), changes are saved automatically, so no Save button needed -->
717
+ {#if onSave && !onChange}
718
+ <div class="config-form__footer">
719
+ <button
720
+ type="button"
721
+ class="config-form__button config-form__button--secondary"
722
+ onclick={onCancel}
723
+ disabled={isSavingWorkflow}
724
+ >
725
+ <Icon icon="heroicons:x-mark" class="config-form__button-icon" />
726
+ <span>Cancel</span>
727
+ </button>
728
+ <button
729
+ type="submit"
730
+ class="config-form__button config-form__button--primary"
731
+ onclick={handleSave}
732
+ disabled={isSavingWorkflow}
733
+ >
734
+ {#if isSavingWorkflow}
735
+ <span class="config-form__button-spinner"></span>
736
+ <span>Saving...</span>
737
+ {:else}
738
+ <Icon icon="heroicons:check" class="config-form__button-icon" />
739
+ <span>Save Changes</span>
740
+ {/if}
741
+ </button>
742
+ </div>
743
+ {/if}
744
+ </form>
676
745
  {:else if !dynamicSchemaLoading && !showExternalEditLink}
677
- <div class="config-form__empty">
678
- <div class="config-form__empty-icon">
679
- <Icon icon="heroicons:cog-6-tooth" />
680
- </div>
681
- <p class="config-form__empty-text">No configuration options available for this node.</p>
682
- {#if configEditOptions?.externalEditLink}
683
- <button
684
- type="button"
685
- class="config-form__button config-form__button--external config-form__empty-button"
686
- onclick={handleExternalEditClick}
687
- >
688
- <Icon
689
- icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
690
- />
691
- <span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
692
- </button>
693
- {/if}
694
- </div>
746
+ <div class="config-form__empty">
747
+ <div class="config-form__empty-icon">
748
+ <Icon icon="heroicons:cog-6-tooth" />
749
+ </div>
750
+ <p class="config-form__empty-text">
751
+ No configuration options available for this node.
752
+ </p>
753
+ {#if configEditOptions?.externalEditLink}
754
+ <button
755
+ type="button"
756
+ class="config-form__button config-form__button--external config-form__empty-button"
757
+ onclick={handleExternalEditClick}
758
+ >
759
+ <Icon
760
+ icon={configEditOptions.externalEditLink.icon ??
761
+ "heroicons:arrow-top-right-on-square"}
762
+ />
763
+ <span
764
+ >{configEditOptions.externalEditLink.label ??
765
+ "Configure Externally"}</span
766
+ >
767
+ </button>
768
+ {/if}
769
+ </div>
695
770
  {/if}
696
771
 
697
772
  <style>
698
- /* ============================================
773
+ /* ============================================
699
774
  CONFIG FORM - Container Styles
700
775
  Individual field styles are in form/ components
701
776
  ============================================ */
702
777
 
703
- .config-form {
704
- display: flex;
705
- flex-direction: column;
706
- gap: var(--fd-space-3xl);
707
- }
778
+ .config-form {
779
+ display: flex;
780
+ flex-direction: column;
781
+ gap: var(--fd-space-3xl);
782
+ }
708
783
 
709
- .config-form__fields {
710
- display: flex;
711
- flex-direction: column;
712
- gap: var(--fd-space-2xl);
713
- }
784
+ .config-form__fields {
785
+ display: flex;
786
+ flex-direction: column;
787
+ gap: var(--fd-space-2xl);
788
+ }
714
789
 
715
- /* ============================================
790
+ /* ============================================
716
791
  FOOTER ACTIONS
717
792
  Only shown when onSave is provided (legacy mode without onChange)
718
793
  ============================================ */
719
794
 
720
- .config-form__footer {
721
- display: flex;
722
- gap: var(--fd-space-md);
723
- justify-content: flex-end;
724
- padding-top: var(--fd-space-xl);
725
- border-top: 1px solid var(--fd-border-muted);
726
- margin-top: var(--fd-space-xs);
727
- }
728
-
729
- /* Button Spinner */
730
- .config-form__button-spinner {
731
- width: 1rem;
732
- height: 1rem;
733
- border: 2px solid rgba(255, 255, 255, 0.3);
734
- border-top-color: #ffffff;
735
- border-radius: 50%;
736
- animation: config-form-spin 0.6s linear infinite;
737
- }
738
-
739
- /* ============================================
795
+ .config-form__footer {
796
+ display: flex;
797
+ gap: var(--fd-space-md);
798
+ justify-content: flex-end;
799
+ padding-top: var(--fd-space-xl);
800
+ border-top: 1px solid var(--fd-border-muted);
801
+ margin-top: var(--fd-space-xs);
802
+ }
803
+
804
+ /* Button Spinner */
805
+ .config-form__button-spinner {
806
+ width: 1rem;
807
+ height: 1rem;
808
+ border: 2px solid rgba(255, 255, 255, 0.3);
809
+ border-top-color: #ffffff;
810
+ border-radius: 50%;
811
+ animation: config-form-spin 0.6s linear infinite;
812
+ }
813
+
814
+ /* ============================================
740
815
  SHARED BUTTON STYLES
741
816
  Used by error actions, external config buttons, and footer
742
817
  ============================================ */
743
818
 
744
- .config-form__button {
745
- display: inline-flex;
746
- align-items: center;
747
- justify-content: center;
748
- gap: var(--fd-space-xs);
749
- padding: 0.625rem var(--fd-space-xl);
750
- border-radius: var(--fd-radius-lg);
751
- font-size: var(--fd-text-sm);
752
- font-weight: 600;
753
- font-family: inherit;
754
- cursor: pointer;
755
- transition: all var(--fd-transition-normal);
756
- border: 1px solid transparent;
757
- min-height: 2.5rem;
758
- }
759
-
760
- .config-form__button :global(svg) {
761
- width: 1rem;
762
- height: 1rem;
763
- flex-shrink: 0;
764
- }
765
-
766
- .config-form__button--secondary {
767
- background-color: var(--fd-background);
768
- border-color: var(--fd-border);
769
- color: var(--fd-foreground);
770
- box-shadow: var(--fd-shadow-sm);
771
- }
772
-
773
- .config-form__button--secondary:hover {
774
- background-color: var(--fd-muted);
775
- border-color: var(--fd-border-strong);
776
- color: var(--fd-foreground);
777
- }
778
-
779
- .config-form__button--secondary:focus-visible {
780
- outline: none;
781
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
782
- }
783
-
784
- .config-form__button--primary {
785
- background: linear-gradient(135deg, var(--fd-primary) 0%, var(--fd-primary-hover) 100%);
786
- color: var(--fd-primary-foreground);
787
- box-shadow:
788
- 0 1px 3px rgba(59, 130, 246, 0.3),
789
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
790
- }
791
-
792
- .config-form__button--primary:hover {
793
- background: linear-gradient(135deg, var(--fd-primary-hover) 0%, var(--fd-primary-hover) 100%);
794
- box-shadow:
795
- 0 4px 12px rgba(59, 130, 246, 0.35),
796
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
797
- transform: translateY(-1px);
798
- }
799
-
800
- .config-form__button--primary:active {
801
- transform: translateY(0);
802
- }
803
-
804
- .config-form__button--primary:focus-visible {
805
- outline: none;
806
- box-shadow:
807
- 0 0 0 3px rgba(59, 130, 246, 0.4),
808
- 0 4px 12px rgba(59, 130, 246, 0.35);
809
- }
810
-
811
- /* ============================================
819
+ .config-form__button {
820
+ display: inline-flex;
821
+ align-items: center;
822
+ justify-content: center;
823
+ gap: var(--fd-space-xs);
824
+ padding: 0.625rem var(--fd-space-xl);
825
+ border-radius: var(--fd-radius-lg);
826
+ font-size: var(--fd-text-sm);
827
+ font-weight: 600;
828
+ font-family: inherit;
829
+ cursor: pointer;
830
+ transition: all var(--fd-transition-normal);
831
+ border: 1px solid transparent;
832
+ min-height: 2.5rem;
833
+ }
834
+
835
+ .config-form__button :global(svg) {
836
+ width: 1rem;
837
+ height: 1rem;
838
+ flex-shrink: 0;
839
+ }
840
+
841
+ .config-form__button--secondary {
842
+ background-color: var(--fd-background);
843
+ border-color: var(--fd-border);
844
+ color: var(--fd-foreground);
845
+ box-shadow: var(--fd-shadow-sm);
846
+ }
847
+
848
+ .config-form__button--secondary:hover {
849
+ background-color: var(--fd-muted);
850
+ border-color: var(--fd-border-strong);
851
+ color: var(--fd-foreground);
852
+ }
853
+
854
+ .config-form__button--secondary:focus-visible {
855
+ outline: none;
856
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
857
+ }
858
+
859
+ .config-form__button--primary {
860
+ background: linear-gradient(
861
+ 135deg,
862
+ var(--fd-primary) 0%,
863
+ var(--fd-primary-hover) 100%
864
+ );
865
+ color: var(--fd-primary-foreground);
866
+ box-shadow:
867
+ 0 1px 3px rgba(59, 130, 246, 0.3),
868
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
869
+ }
870
+
871
+ .config-form__button--primary:hover {
872
+ background: linear-gradient(
873
+ 135deg,
874
+ var(--fd-primary-hover) 0%,
875
+ var(--fd-primary-hover) 100%
876
+ );
877
+ box-shadow:
878
+ 0 4px 12px rgba(59, 130, 246, 0.35),
879
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
880
+ transform: translateY(-1px);
881
+ }
882
+
883
+ .config-form__button--primary:active {
884
+ transform: translateY(0);
885
+ }
886
+
887
+ .config-form__button--primary:focus-visible {
888
+ outline: none;
889
+ box-shadow:
890
+ 0 0 0 3px rgba(59, 130, 246, 0.4),
891
+ 0 4px 12px rgba(59, 130, 246, 0.35);
892
+ }
893
+
894
+ /* ============================================
812
895
  UI EXTENSIONS SECTION
813
896
  ============================================ */
814
897
 
815
- .config-form__extensions {
816
- background-color: var(--fd-muted);
817
- border: 1px solid var(--fd-border);
818
- border-radius: var(--fd-radius-lg);
819
- overflow: hidden;
820
- margin-top: var(--fd-space-xs);
821
- }
822
-
823
- .config-form__extensions-header {
824
- display: flex;
825
- align-items: center;
826
- gap: var(--fd-space-xs);
827
- padding: var(--fd-space-md) var(--fd-space-xl);
828
- background-color: var(--fd-subtle);
829
- border-bottom: 1px solid var(--fd-border);
830
- font-size: 0.8125rem;
831
- font-weight: 600;
832
- color: var(--fd-foreground);
833
- }
834
-
835
- .config-form__extensions-header :global(svg) {
836
- width: 1rem;
837
- height: 1rem;
838
- color: var(--fd-muted-foreground);
839
- }
840
-
841
- .config-form__extensions-content {
842
- padding: var(--fd-space-xl);
843
- display: flex;
844
- flex-direction: column;
845
- gap: var(--fd-space-xl);
846
- }
847
-
848
- /* ============================================
898
+ .config-form__extensions {
899
+ background-color: var(--fd-muted);
900
+ border: 1px solid var(--fd-border);
901
+ border-radius: var(--fd-radius-lg);
902
+ overflow: hidden;
903
+ margin-top: var(--fd-space-xs);
904
+ }
905
+
906
+ .config-form__extensions-header {
907
+ display: flex;
908
+ align-items: center;
909
+ gap: var(--fd-space-xs);
910
+ padding: var(--fd-space-md) var(--fd-space-xl);
911
+ background-color: var(--fd-subtle);
912
+ border-bottom: 1px solid var(--fd-border);
913
+ font-size: 0.8125rem;
914
+ font-weight: 600;
915
+ color: var(--fd-foreground);
916
+ }
917
+
918
+ .config-form__extensions-header :global(svg) {
919
+ width: 1rem;
920
+ height: 1rem;
921
+ color: var(--fd-muted-foreground);
922
+ }
923
+
924
+ .config-form__extensions-content {
925
+ padding: var(--fd-space-xl);
926
+ display: flex;
927
+ flex-direction: column;
928
+ gap: var(--fd-space-xl);
929
+ }
930
+
931
+ /* ============================================
849
932
  DEBUG SECTION
850
933
  ============================================ */
851
934
 
852
- .config-form__debug {
853
- background-color: var(--fd-warning-muted);
854
- border: 1px solid var(--fd-warning);
855
- border-radius: var(--fd-radius-lg);
856
- overflow: hidden;
857
- }
858
-
859
- .config-form__debug-header {
860
- display: flex;
861
- align-items: center;
862
- gap: var(--fd-space-xs);
863
- padding: var(--fd-space-md) var(--fd-space-xl);
864
- background-color: var(--fd-warning-muted);
865
- border-bottom: 1px solid var(--fd-warning);
866
- font-size: 0.8125rem;
867
- font-weight: 600;
868
- color: var(--fd-warning-hover);
869
- }
870
-
871
- .config-form__debug-header :global(svg) {
872
- width: 1rem;
873
- height: 1rem;
874
- }
875
-
876
- .config-form__debug-content {
877
- margin: 0;
878
- padding: var(--fd-space-xl);
879
- font-size: var(--fd-text-xs);
880
- font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
881
- color: var(--fd-foreground);
882
- overflow-x: auto;
883
- background-color: var(--fd-background);
884
- line-height: 1.5;
885
- }
886
-
887
- /* ============================================
935
+ .config-form__debug {
936
+ background-color: var(--fd-warning-muted);
937
+ border: 1px solid var(--fd-warning);
938
+ border-radius: var(--fd-radius-lg);
939
+ overflow: hidden;
940
+ }
941
+
942
+ .config-form__debug-header {
943
+ display: flex;
944
+ align-items: center;
945
+ gap: var(--fd-space-xs);
946
+ padding: var(--fd-space-md) var(--fd-space-xl);
947
+ background-color: var(--fd-warning-muted);
948
+ border-bottom: 1px solid var(--fd-warning);
949
+ font-size: 0.8125rem;
950
+ font-weight: 600;
951
+ color: var(--fd-warning-hover);
952
+ }
953
+
954
+ .config-form__debug-header :global(svg) {
955
+ width: 1rem;
956
+ height: 1rem;
957
+ }
958
+
959
+ .config-form__debug-content {
960
+ margin: 0;
961
+ padding: var(--fd-space-xl);
962
+ font-size: var(--fd-text-xs);
963
+ font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
964
+ color: var(--fd-foreground);
965
+ overflow-x: auto;
966
+ background-color: var(--fd-background);
967
+ line-height: 1.5;
968
+ }
969
+
970
+ /* ============================================
888
971
  EMPTY STATE
889
972
  ============================================ */
890
973
 
891
- .config-form__empty {
892
- display: flex;
893
- flex-direction: column;
894
- align-items: center;
895
- justify-content: center;
896
- padding: var(--fd-space-6xl) var(--fd-space-3xl);
897
- text-align: center;
898
- }
899
-
900
- .config-form__empty-icon {
901
- width: 3rem;
902
- height: 3rem;
903
- margin-bottom: var(--fd-space-xl);
904
- color: var(--fd-border);
905
- }
906
-
907
- .config-form__empty-icon :global(svg) {
908
- width: 100%;
909
- height: 100%;
910
- }
911
-
912
- .config-form__empty-text {
913
- margin: 0;
914
- font-size: var(--fd-text-sm);
915
- color: var(--fd-muted-foreground);
916
- font-style: italic;
917
- line-height: 1.5;
918
- }
919
-
920
- .config-form__empty-button {
921
- margin-top: var(--fd-space-xl);
922
- }
923
-
924
- /* ============================================
974
+ .config-form__empty {
975
+ display: flex;
976
+ flex-direction: column;
977
+ align-items: center;
978
+ justify-content: center;
979
+ padding: var(--fd-space-6xl) var(--fd-space-3xl);
980
+ text-align: center;
981
+ }
982
+
983
+ .config-form__empty-icon {
984
+ width: 3rem;
985
+ height: 3rem;
986
+ margin-bottom: var(--fd-space-xl);
987
+ color: var(--fd-border);
988
+ }
989
+
990
+ .config-form__empty-icon :global(svg) {
991
+ width: 100%;
992
+ height: 100%;
993
+ }
994
+
995
+ .config-form__empty-text {
996
+ margin: 0;
997
+ font-size: var(--fd-text-sm);
998
+ color: var(--fd-muted-foreground);
999
+ font-style: italic;
1000
+ line-height: 1.5;
1001
+ }
1002
+
1003
+ .config-form__empty-button {
1004
+ margin-top: var(--fd-space-xl);
1005
+ }
1006
+
1007
+ /* ============================================
925
1008
  ADMIN/EDIT SECTION - External Configuration
926
1009
  ============================================ */
927
1010
 
928
- .config-form__admin-edit {
929
- background: linear-gradient(135deg, var(--fd-info-muted) 0%, var(--fd-primary-muted) 100%);
930
- border: 1px solid var(--fd-primary);
931
- border-radius: 0.625rem;
932
- overflow: hidden;
933
- margin-bottom: var(--fd-space-xl);
934
- }
935
-
936
- .config-form__admin-edit-header {
937
- display: flex;
938
- align-items: center;
939
- gap: var(--fd-space-xs);
940
- padding: var(--fd-space-md) var(--fd-space-xl);
941
- background: linear-gradient(135deg, var(--fd-primary-muted) 0%, var(--fd-primary-muted) 100%);
942
- border-bottom: 1px solid var(--fd-primary);
943
- font-size: 0.8125rem;
944
- font-weight: 600;
945
- color: var(--fd-primary-hover);
946
- }
947
-
948
- .config-form__admin-edit-header :global(svg) {
949
- width: 1rem;
950
- height: 1rem;
951
- color: var(--fd-primary);
952
- }
953
-
954
- .config-form__admin-edit-content {
955
- padding: var(--fd-space-xl);
956
- display: flex;
957
- flex-direction: column;
958
- gap: var(--fd-space-md);
959
- }
960
-
961
- .config-form__admin-edit-description {
962
- margin: 0;
963
- font-size: 0.8125rem;
964
- color: var(--fd-primary-hover);
965
- line-height: 1.5;
966
- }
967
-
968
- /* ============================================
1011
+ .config-form__admin-edit {
1012
+ background: linear-gradient(
1013
+ 135deg,
1014
+ var(--fd-info-muted) 0%,
1015
+ var(--fd-primary-muted) 100%
1016
+ );
1017
+ border: 1px solid var(--fd-primary);
1018
+ border-radius: 0.625rem;
1019
+ overflow: hidden;
1020
+ margin-bottom: var(--fd-space-xl);
1021
+ }
1022
+
1023
+ .config-form__admin-edit-header {
1024
+ display: flex;
1025
+ align-items: center;
1026
+ gap: var(--fd-space-xs);
1027
+ padding: var(--fd-space-md) var(--fd-space-xl);
1028
+ background: linear-gradient(
1029
+ 135deg,
1030
+ var(--fd-primary-muted) 0%,
1031
+ var(--fd-primary-muted) 100%
1032
+ );
1033
+ border-bottom: 1px solid var(--fd-primary);
1034
+ font-size: 0.8125rem;
1035
+ font-weight: 600;
1036
+ color: var(--fd-primary-hover);
1037
+ }
1038
+
1039
+ .config-form__admin-edit-header :global(svg) {
1040
+ width: 1rem;
1041
+ height: 1rem;
1042
+ color: var(--fd-primary);
1043
+ }
1044
+
1045
+ .config-form__admin-edit-content {
1046
+ padding: var(--fd-space-xl);
1047
+ display: flex;
1048
+ flex-direction: column;
1049
+ gap: var(--fd-space-md);
1050
+ }
1051
+
1052
+ .config-form__admin-edit-description {
1053
+ margin: 0;
1054
+ font-size: 0.8125rem;
1055
+ color: var(--fd-primary-hover);
1056
+ line-height: 1.5;
1057
+ }
1058
+
1059
+ /* ============================================
969
1060
  LOADING STATE
970
1061
  ============================================ */
971
1062
 
972
- .config-form__loading {
973
- display: flex;
974
- flex-direction: column;
975
- align-items: center;
976
- justify-content: center;
977
- padding: var(--fd-space-6xl) var(--fd-space-3xl);
978
- gap: var(--fd-space-xl);
979
- }
980
-
981
- .config-form__loading-spinner {
982
- width: 2.5rem;
983
- height: 2.5rem;
984
- border: 3px solid var(--fd-primary-muted);
985
- border-top-color: var(--fd-primary);
986
- border-radius: 50%;
987
- animation: config-form-spin 0.8s linear infinite;
988
- }
989
-
990
- @keyframes config-form-spin {
991
- to {
992
- transform: rotate(360deg);
993
- }
994
- }
995
-
996
- .config-form__loading-text {
997
- margin: 0;
998
- font-size: var(--fd-text-sm);
999
- color: var(--fd-muted-foreground);
1000
- }
1001
-
1002
- /* ============================================
1063
+ .config-form__loading {
1064
+ display: flex;
1065
+ flex-direction: column;
1066
+ align-items: center;
1067
+ justify-content: center;
1068
+ padding: var(--fd-space-6xl) var(--fd-space-3xl);
1069
+ gap: var(--fd-space-xl);
1070
+ }
1071
+
1072
+ .config-form__loading-spinner {
1073
+ width: 2.5rem;
1074
+ height: 2.5rem;
1075
+ border: 3px solid var(--fd-primary-muted);
1076
+ border-top-color: var(--fd-primary);
1077
+ border-radius: 50%;
1078
+ animation: config-form-spin 0.8s linear infinite;
1079
+ }
1080
+
1081
+ @keyframes config-form-spin {
1082
+ to {
1083
+ transform: rotate(360deg);
1084
+ }
1085
+ }
1086
+
1087
+ .config-form__loading-text {
1088
+ margin: 0;
1089
+ font-size: var(--fd-text-sm);
1090
+ color: var(--fd-muted-foreground);
1091
+ }
1092
+
1093
+ /* ============================================
1003
1094
  ERROR STATE
1004
1095
  ============================================ */
1005
1096
 
1006
- .config-form__error {
1007
- background-color: var(--fd-error-muted);
1008
- border: 1px solid var(--fd-error);
1009
- border-radius: var(--fd-radius-lg);
1010
- overflow: hidden;
1011
- }
1012
-
1013
- .config-form__error-header {
1014
- display: flex;
1015
- align-items: center;
1016
- gap: var(--fd-space-xs);
1017
- padding: var(--fd-space-md) var(--fd-space-xl);
1018
- background-color: var(--fd-error-muted);
1019
- border-bottom: 1px solid var(--fd-error);
1020
- font-size: 0.8125rem;
1021
- font-weight: 600;
1022
- color: var(--fd-error-hover);
1023
- }
1024
-
1025
- .config-form__error-header :global(svg) {
1026
- width: 1rem;
1027
- height: 1rem;
1028
- color: var(--fd-error);
1029
- }
1030
-
1031
- .config-form__error-content {
1032
- padding: var(--fd-space-xl);
1033
- display: flex;
1034
- flex-direction: column;
1035
- gap: var(--fd-space-md);
1036
- }
1037
-
1038
- .config-form__error-message {
1039
- margin: 0;
1040
- font-size: 0.8125rem;
1041
- color: var(--fd-error);
1042
- line-height: 1.5;
1043
- }
1044
-
1045
- .config-form__error-actions {
1046
- display: flex;
1047
- gap: var(--fd-space-xs);
1048
- flex-wrap: wrap;
1049
- }
1050
-
1051
- /* ============================================
1097
+ .config-form__error {
1098
+ background-color: var(--fd-error-muted);
1099
+ border: 1px solid var(--fd-error);
1100
+ border-radius: var(--fd-radius-lg);
1101
+ overflow: hidden;
1102
+ }
1103
+
1104
+ .config-form__error-header {
1105
+ display: flex;
1106
+ align-items: center;
1107
+ gap: var(--fd-space-xs);
1108
+ padding: var(--fd-space-md) var(--fd-space-xl);
1109
+ background-color: var(--fd-error-muted);
1110
+ border-bottom: 1px solid var(--fd-error);
1111
+ font-size: 0.8125rem;
1112
+ font-weight: 600;
1113
+ color: var(--fd-error-hover);
1114
+ }
1115
+
1116
+ .config-form__error-header :global(svg) {
1117
+ width: 1rem;
1118
+ height: 1rem;
1119
+ color: var(--fd-error);
1120
+ }
1121
+
1122
+ .config-form__error-content {
1123
+ padding: var(--fd-space-xl);
1124
+ display: flex;
1125
+ flex-direction: column;
1126
+ gap: var(--fd-space-md);
1127
+ }
1128
+
1129
+ .config-form__error-message {
1130
+ margin: 0;
1131
+ font-size: 0.8125rem;
1132
+ color: var(--fd-error);
1133
+ line-height: 1.5;
1134
+ }
1135
+
1136
+ .config-form__error-actions {
1137
+ display: flex;
1138
+ gap: var(--fd-space-xs);
1139
+ flex-wrap: wrap;
1140
+ }
1141
+
1142
+ /* ============================================
1052
1143
  SCHEMA ACTIONS (Refresh, External Editor)
1053
1144
  ============================================ */
1054
1145
 
1055
- .config-form__schema-actions {
1056
- display: flex;
1057
- gap: var(--fd-space-xs);
1058
- margin-bottom: var(--fd-space-xl);
1059
- padding-bottom: var(--fd-space-md);
1060
- border-bottom: 1px solid var(--fd-border-muted);
1061
- }
1062
-
1063
- .config-form__schema-refresh,
1064
- .config-form__schema-external {
1065
- display: inline-flex;
1066
- align-items: center;
1067
- gap: var(--fd-space-3xs);
1068
- padding: var(--fd-space-3xs) var(--fd-space-xs);
1069
- font-size: var(--fd-text-xs);
1070
- font-weight: 500;
1071
- font-family: inherit;
1072
- border-radius: var(--fd-radius-md);
1073
- cursor: pointer;
1074
- transition: all var(--fd-transition-fast);
1075
- border: 1px solid transparent;
1076
- }
1077
-
1078
- .config-form__schema-refresh {
1079
- background-color: var(--fd-muted);
1080
- border-color: var(--fd-border);
1081
- color: var(--fd-muted-foreground);
1082
- }
1083
-
1084
- .config-form__schema-refresh:hover {
1085
- background-color: var(--fd-subtle);
1086
- border-color: var(--fd-border-strong);
1087
- color: var(--fd-foreground);
1088
- }
1089
-
1090
- .config-form__schema-refresh :global(svg),
1091
- .config-form__schema-external :global(svg) {
1092
- width: 0.875rem;
1093
- height: 0.875rem;
1094
- }
1095
-
1096
- .config-form__schema-external {
1097
- background-color: var(--fd-primary-muted);
1098
- border-color: var(--fd-primary);
1099
- color: var(--fd-primary-hover);
1100
- }
1101
-
1102
- .config-form__schema-external:hover {
1103
- background-color: var(--fd-primary-muted);
1104
- border-color: var(--fd-primary-hover);
1105
- color: var(--fd-primary-hover);
1106
- }
1107
-
1108
- /* ============================================
1146
+ .config-form__schema-actions {
1147
+ display: flex;
1148
+ gap: var(--fd-space-xs);
1149
+ margin-bottom: var(--fd-space-xl);
1150
+ padding-bottom: var(--fd-space-md);
1151
+ border-bottom: 1px solid var(--fd-border-muted);
1152
+ }
1153
+
1154
+ .config-form__schema-refresh,
1155
+ .config-form__schema-external {
1156
+ display: inline-flex;
1157
+ align-items: center;
1158
+ gap: var(--fd-space-3xs);
1159
+ padding: var(--fd-space-3xs) var(--fd-space-xs);
1160
+ font-size: var(--fd-text-xs);
1161
+ font-weight: 500;
1162
+ font-family: inherit;
1163
+ border-radius: var(--fd-radius-md);
1164
+ cursor: pointer;
1165
+ transition: all var(--fd-transition-fast);
1166
+ border: 1px solid transparent;
1167
+ }
1168
+
1169
+ .config-form__schema-refresh {
1170
+ background-color: var(--fd-muted);
1171
+ border-color: var(--fd-border);
1172
+ color: var(--fd-muted-foreground);
1173
+ }
1174
+
1175
+ .config-form__schema-refresh:hover {
1176
+ background-color: var(--fd-subtle);
1177
+ border-color: var(--fd-border-strong);
1178
+ color: var(--fd-foreground);
1179
+ }
1180
+
1181
+ .config-form__schema-refresh :global(svg),
1182
+ .config-form__schema-external :global(svg) {
1183
+ width: 0.875rem;
1184
+ height: 0.875rem;
1185
+ }
1186
+
1187
+ .config-form__schema-external {
1188
+ background-color: var(--fd-primary-muted);
1189
+ border-color: var(--fd-primary);
1190
+ color: var(--fd-primary-hover);
1191
+ }
1192
+
1193
+ .config-form__schema-external:hover {
1194
+ background-color: var(--fd-primary-muted);
1195
+ border-color: var(--fd-primary-hover);
1196
+ color: var(--fd-primary-hover);
1197
+ }
1198
+
1199
+ /* ============================================
1109
1200
  EXTERNAL BUTTON STYLE
1110
1201
  ============================================ */
1111
1202
 
1112
- .config-form__button--external {
1113
- background: linear-gradient(135deg, var(--fd-accent) 0%, var(--fd-primary) 100%);
1114
- color: var(--fd-accent-foreground);
1115
- box-shadow:
1116
- 0 1px 3px rgba(99, 102, 241, 0.3),
1117
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
1118
- }
1119
-
1120
- .config-form__button--external:hover {
1121
- background: linear-gradient(135deg, var(--fd-accent-hover) 0%, var(--fd-primary-hover) 100%);
1122
- box-shadow:
1123
- 0 4px 12px rgba(99, 102, 241, 0.35),
1124
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
1125
- transform: translateY(-1px);
1126
- }
1127
-
1128
- .config-form__button--external:active {
1129
- transform: translateY(0);
1130
- }
1131
-
1132
- .config-form__button--external:focus-visible {
1133
- outline: none;
1134
- box-shadow:
1135
- 0 0 0 3px rgba(99, 102, 241, 0.4),
1136
- 0 4px 12px rgba(99, 102, 241, 0.35);
1137
- }
1203
+ .config-form__button--external {
1204
+ background: linear-gradient(
1205
+ 135deg,
1206
+ var(--fd-accent) 0%,
1207
+ var(--fd-primary) 100%
1208
+ );
1209
+ color: var(--fd-accent-foreground);
1210
+ box-shadow:
1211
+ 0 1px 3px rgba(99, 102, 241, 0.3),
1212
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
1213
+ }
1214
+
1215
+ .config-form__button--external:hover {
1216
+ background: linear-gradient(
1217
+ 135deg,
1218
+ var(--fd-accent-hover) 0%,
1219
+ var(--fd-primary-hover) 100%
1220
+ );
1221
+ box-shadow:
1222
+ 0 4px 12px rgba(99, 102, 241, 0.35),
1223
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
1224
+ transform: translateY(-1px);
1225
+ }
1226
+
1227
+ .config-form__button--external:active {
1228
+ transform: translateY(0);
1229
+ }
1230
+
1231
+ .config-form__button--external:focus-visible {
1232
+ outline: none;
1233
+ box-shadow:
1234
+ 0 0 0 3px rgba(99, 102, 241, 0.4),
1235
+ 0 4px 12px rgba(99, 102, 241, 0.35);
1236
+ }
1138
1237
  </style>