@flowdrop/flowdrop 1.0.0 → 1.1.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 (388) 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 +1081 -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 +46 -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 +197 -0
  36. package/dist/components/FlowDropEdge.stories.svelte.d.ts +26 -0
  37. package/dist/components/FlowDropEdge.svelte +168 -0
  38. package/dist/components/FlowDropEdge.svelte.d.ts +4 -0
  39. package/dist/components/FlowDropZone.svelte +63 -60
  40. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  41. package/dist/components/LoadingSpinner.stories.svelte +19 -19
  42. package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
  43. package/dist/components/LoadingSpinner.svelte +21 -21
  44. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  45. package/dist/components/Logo.stories.svelte +13 -13
  46. package/dist/components/Logo.stories.svelte.d.ts +1 -1
  47. package/dist/components/Logo.svelte +101 -95
  48. package/dist/components/LogsSidebar.svelte +553 -546
  49. package/dist/components/LogsSidebar.svelte.d.ts +1 -1
  50. package/dist/components/MarkdownDisplay.stories.svelte +29 -23
  51. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
  52. package/dist/components/MarkdownDisplay.svelte +16 -14
  53. package/dist/components/Navbar.stories.svelte +43 -38
  54. package/dist/components/Navbar.stories.svelte.d.ts +1 -1
  55. package/dist/components/Navbar.svelte +760 -706
  56. package/dist/components/Navbar.svelte.d.ts +1 -1
  57. package/dist/components/NodeSidebar.svelte +900 -746
  58. package/dist/components/NodeSidebar.svelte.d.ts +3 -1
  59. package/dist/components/NodeStatusOverlay.stories.svelte +82 -70
  60. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
  61. package/dist/components/NodeStatusOverlay.svelte +295 -280
  62. package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
  63. package/dist/components/PipelineStatus.svelte +326 -300
  64. package/dist/components/PipelineStatus.svelte.d.ts +4 -4
  65. package/dist/components/PortCoordinateTracker.svelte +49 -47
  66. package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
  67. package/dist/components/ReadOnlyDetails.svelte +156 -156
  68. package/dist/components/SchemaForm.stories.svelte +106 -98
  69. package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
  70. package/dist/components/SchemaForm.svelte +490 -463
  71. package/dist/components/SchemaForm.svelte.d.ts +2 -2
  72. package/dist/components/SettingsModal.svelte +226 -223
  73. package/dist/components/SettingsModal.svelte.d.ts +1 -1
  74. package/dist/components/SettingsPanel.svelte +637 -601
  75. package/dist/components/SettingsPanel.svelte.d.ts +1 -1
  76. package/dist/components/StatusIcon.stories.svelte +62 -49
  77. package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
  78. package/dist/components/StatusIcon.svelte +87 -87
  79. package/dist/components/StatusIcon.svelte.d.ts +2 -2
  80. package/dist/components/StatusLabel.stories.svelte +12 -12
  81. package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
  82. package/dist/components/StatusLabel.svelte +19 -19
  83. package/dist/components/ThemeToggle.stories.svelte +16 -16
  84. package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
  85. package/dist/components/ThemeToggle.svelte +180 -169
  86. package/dist/components/ThemeToggle.svelte.d.ts +1 -1
  87. package/dist/components/UniversalNode.svelte +150 -138
  88. package/dist/components/UniversalNode.svelte.d.ts +3 -3
  89. package/dist/components/WorkflowEditor.svelte +1069 -1007
  90. package/dist/components/WorkflowEditor.svelte.d.ts +4 -4
  91. package/dist/components/form/FormArray.svelte +1034 -973
  92. package/dist/components/form/FormArray.svelte.d.ts +1 -1
  93. package/dist/components/form/FormAutocomplete.svelte +1021 -978
  94. package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
  95. package/dist/components/form/FormCheckboxGroup.stories.svelte +23 -20
  96. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
  97. package/dist/components/form/FormCheckboxGroup.svelte +136 -136
  98. package/dist/components/form/FormCodeEditor.svelte +452 -434
  99. package/dist/components/form/FormField.svelte +366 -355
  100. package/dist/components/form/FormField.svelte.d.ts +2 -2
  101. package/dist/components/form/FormFieldLight.svelte +400 -384
  102. package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
  103. package/dist/components/form/FormFieldWrapper.stories.svelte +42 -42
  104. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
  105. package/dist/components/form/FormFieldWrapper.svelte +100 -93
  106. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  107. package/dist/components/form/FormFieldset.svelte +108 -108
  108. package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
  109. package/dist/components/form/FormMarkdownEditor.svelte +758 -725
  110. package/dist/components/form/FormNumberField.stories.svelte +25 -25
  111. package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
  112. package/dist/components/form/FormNumberField.svelte +88 -88
  113. package/dist/components/form/FormRangeField.stories.svelte +20 -20
  114. package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
  115. package/dist/components/form/FormRangeField.svelte +234 -226
  116. package/dist/components/form/FormSelect.stories.svelte +38 -38
  117. package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
  118. package/dist/components/form/FormSelect.svelte +101 -101
  119. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  120. package/dist/components/form/FormTemplateEditor.svelte +847 -798
  121. package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
  122. package/dist/components/form/FormTextField.stories.svelte +29 -23
  123. package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
  124. package/dist/components/form/FormTextField.svelte +68 -68
  125. package/dist/components/form/FormTextarea.stories.svelte +28 -25
  126. package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
  127. package/dist/components/form/FormTextarea.svelte +74 -74
  128. package/dist/components/form/FormToggle.stories.svelte +23 -20
  129. package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
  130. package/dist/components/form/FormToggle.svelte +98 -98
  131. package/dist/components/form/FormUISchemaRenderer.svelte +120 -113
  132. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
  133. package/dist/components/form/index.d.ts +19 -19
  134. package/dist/components/form/index.js +18 -18
  135. package/dist/components/form/templateAutocomplete.d.ts +2 -2
  136. package/dist/components/form/templateAutocomplete.js +64 -55
  137. package/dist/components/form/types.d.ts +6 -6
  138. package/dist/components/form/types.js +9 -4
  139. package/dist/components/icons/AlertCircleIcon.svelte +11 -0
  140. package/dist/components/icons/AlertCircleIcon.svelte.d.ts +26 -0
  141. package/dist/components/icons/CogIcon.svelte +11 -0
  142. package/dist/components/icons/CogIcon.svelte.d.ts +26 -0
  143. package/dist/components/interrupt/ChoicePrompt.stories.svelte +54 -38
  144. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
  145. package/dist/components/interrupt/ChoicePrompt.svelte +407 -383
  146. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
  147. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +48 -48
  148. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
  149. package/dist/components/interrupt/ConfirmationPrompt.svelte +280 -274
  150. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
  151. package/dist/components/interrupt/FormPrompt.svelte +223 -218
  152. package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
  153. package/dist/components/interrupt/InterruptBubble.svelte +617 -583
  154. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
  155. package/dist/components/interrupt/ReviewPrompt.stories.svelte +66 -56
  156. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
  157. package/dist/components/interrupt/ReviewPrompt.svelte +861 -841
  158. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
  159. package/dist/components/interrupt/TextInputPrompt.stories.svelte +38 -33
  160. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
  161. package/dist/components/interrupt/TextInputPrompt.svelte +333 -328
  162. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
  163. package/dist/components/interrupt/index.d.ts +5 -5
  164. package/dist/components/interrupt/index.js +5 -5
  165. package/dist/components/layouts/MainLayout.svelte +724 -691
  166. package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
  167. package/dist/components/nodes/GatewayNode.stories.svelte +100 -99
  168. package/dist/components/nodes/GatewayNode.svelte +605 -571
  169. package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
  170. package/dist/components/nodes/IdeaNode.stories.svelte +44 -43
  171. package/dist/components/nodes/IdeaNode.svelte +451 -437
  172. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  173. package/dist/components/nodes/NotesNode.stories.svelte +65 -64
  174. package/dist/components/nodes/NotesNode.svelte +380 -369
  175. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  176. package/dist/components/nodes/SimpleNode.stories.svelte +145 -144
  177. package/dist/components/nodes/SimpleNode.svelte +486 -424
  178. package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
  179. package/dist/components/nodes/SquareNode.stories.svelte +73 -73
  180. package/dist/components/nodes/SquareNode.svelte +439 -380
  181. package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
  182. package/dist/components/nodes/TerminalNode.stories.svelte +13 -13
  183. package/dist/components/nodes/TerminalNode.svelte +709 -670
  184. package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
  185. package/dist/components/nodes/ToolNode.stories.svelte +181 -180
  186. package/dist/components/nodes/ToolNode.svelte +505 -447
  187. package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
  188. package/dist/components/nodes/WorkflowNode.stories.svelte +70 -46
  189. package/dist/components/nodes/WorkflowNode.svelte +621 -551
  190. package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
  191. package/dist/components/playground/ChatPanel.svelte +945 -889
  192. package/dist/components/playground/ExecutionLogs.svelte +495 -472
  193. package/dist/components/playground/InputCollector.svelte +449 -428
  194. package/dist/components/playground/MessageBubble.stories.svelte +47 -47
  195. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
  196. package/dist/components/playground/MessageBubble.svelte +626 -610
  197. package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
  198. package/dist/components/playground/Playground.svelte +1088 -1057
  199. package/dist/components/playground/Playground.svelte.d.ts +3 -3
  200. package/dist/components/playground/PlaygroundModal.svelte +208 -204
  201. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  202. package/dist/components/playground/SessionManager.svelte +527 -521
  203. package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
  204. package/dist/config/agentSpecEndpoints.d.ts +1 -1
  205. package/dist/config/agentSpecEndpoints.js +20 -20
  206. package/dist/config/constants.d.ts +8 -0
  207. package/dist/config/constants.js +10 -2
  208. package/dist/config/defaultCategories.d.ts +1 -1
  209. package/dist/config/defaultCategories.js +86 -86
  210. package/dist/config/defaultPortConfig.d.ts +1 -1
  211. package/dist/config/defaultPortConfig.js +144 -144
  212. package/dist/config/endpoints.d.ts +4 -4
  213. package/dist/config/endpoints.js +65 -65
  214. package/dist/config/runtimeConfig.d.ts +2 -2
  215. package/dist/config/runtimeConfig.js +8 -8
  216. package/dist/core/index.d.ts +63 -59
  217. package/dist/core/index.js +35 -33
  218. package/dist/display/index.d.ts +2 -2
  219. package/dist/display/index.js +2 -2
  220. package/dist/editor/index.d.ts +62 -62
  221. package/dist/editor/index.js +53 -53
  222. package/dist/form/code.d.ts +5 -5
  223. package/dist/form/code.js +14 -14
  224. package/dist/form/fieldRegistry.d.ts +3 -3
  225. package/dist/form/fieldRegistry.js +11 -9
  226. package/dist/form/full.d.ts +8 -8
  227. package/dist/form/full.js +9 -9
  228. package/dist/form/index.d.ts +18 -18
  229. package/dist/form/index.js +16 -16
  230. package/dist/form/markdown.d.ts +4 -4
  231. package/dist/form/markdown.js +8 -8
  232. package/dist/helpers/proximityConnect.d.ts +3 -3
  233. package/dist/helpers/proximityConnect.js +34 -32
  234. package/dist/helpers/workflowEditorHelper.d.ts +5 -5
  235. package/dist/helpers/workflowEditorHelper.js +108 -96
  236. package/dist/index.d.ts +6 -6
  237. package/dist/index.js +6 -6
  238. package/dist/mocks/app-environment.js +2 -2
  239. package/dist/mocks/app-forms.js +9 -9
  240. package/dist/mocks/app-navigation.js +11 -11
  241. package/dist/mocks/app-stores.js +8 -8
  242. package/dist/playground/index.d.ts +19 -19
  243. package/dist/playground/index.js +16 -16
  244. package/dist/playground/mount.d.ts +3 -3
  245. package/dist/playground/mount.js +24 -24
  246. package/dist/registry/builtinFormats.js +13 -13
  247. package/dist/registry/builtinNodes.d.ts +2 -2
  248. package/dist/registry/builtinNodes.js +77 -77
  249. package/dist/registry/index.d.ts +4 -4
  250. package/dist/registry/index.js +4 -4
  251. package/dist/registry/nodeComponentRegistry.d.ts +8 -8
  252. package/dist/registry/nodeComponentRegistry.js +11 -9
  253. package/dist/registry/plugin.d.ts +2 -2
  254. package/dist/registry/plugin.js +11 -11
  255. package/dist/registry/workflowFormatRegistry.d.ts +3 -3
  256. package/dist/registry/workflowFormatRegistry.js +2 -2
  257. package/dist/schema/index.d.ts +1 -1
  258. package/dist/schema/index.js +2 -2
  259. package/dist/schemas/v1/workflow.schema.json +22 -107
  260. package/dist/services/agentSpecExecutionService.d.ts +3 -3
  261. package/dist/services/agentSpecExecutionService.js +59 -55
  262. package/dist/services/api.d.ts +18 -4
  263. package/dist/services/api.js +46 -43
  264. package/dist/services/apiVariableService.d.ts +1 -1
  265. package/dist/services/apiVariableService.js +41 -34
  266. package/dist/services/autoSaveService.js +8 -8
  267. package/dist/services/categoriesApi.d.ts +2 -2
  268. package/dist/services/categoriesApi.js +8 -8
  269. package/dist/services/draftStorage.d.ts +1 -1
  270. package/dist/services/draftStorage.js +11 -11
  271. package/dist/services/dynamicSchemaService.d.ts +1 -1
  272. package/dist/services/dynamicSchemaService.js +41 -39
  273. package/dist/services/globalSave.d.ts +2 -2
  274. package/dist/services/globalSave.js +53 -42
  275. package/dist/services/historyService.d.ts +1 -1
  276. package/dist/services/historyService.js +8 -8
  277. package/dist/services/interruptService.d.ts +1 -1
  278. package/dist/services/interruptService.js +35 -29
  279. package/dist/services/nodeExecutionService.d.ts +1 -1
  280. package/dist/services/nodeExecutionService.js +45 -44
  281. package/dist/services/playgroundService.d.ts +1 -1
  282. package/dist/services/playgroundService.js +29 -29
  283. package/dist/services/portConfigApi.d.ts +2 -2
  284. package/dist/services/portConfigApi.js +8 -8
  285. package/dist/services/settingsService.d.ts +2 -2
  286. package/dist/services/settingsService.js +25 -19
  287. package/dist/services/toastService.d.ts +4 -4
  288. package/dist/services/toastService.js +33 -33
  289. package/dist/services/variableService.d.ts +1 -1
  290. package/dist/services/variableService.js +36 -36
  291. package/dist/services/workflowStorage.d.ts +2 -2
  292. package/dist/services/workflowStorage.js +13 -13
  293. package/dist/settings/index.d.ts +7 -7
  294. package/dist/settings/index.js +6 -6
  295. package/dist/skins/default.d.ts +2 -0
  296. package/dist/skins/default.js +1 -0
  297. package/dist/skins/index.d.ts +13 -0
  298. package/dist/skins/index.js +30 -0
  299. package/dist/skins/slate.d.ts +2 -0
  300. package/dist/skins/slate.js +78 -0
  301. package/dist/stores/categoriesStore.svelte.d.ts +1 -1
  302. package/dist/stores/categoriesStore.svelte.js +5 -5
  303. package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
  304. package/dist/stores/editorStateMachine.svelte.js +65 -33
  305. package/dist/stores/historyStore.svelte.d.ts +4 -4
  306. package/dist/stores/historyStore.svelte.js +4 -4
  307. package/dist/stores/interruptStore.svelte.d.ts +3 -3
  308. package/dist/stores/interruptStore.svelte.js +21 -21
  309. package/dist/stores/playgroundStore.svelte.d.ts +2 -2
  310. package/dist/stores/playgroundStore.svelte.js +25 -18
  311. package/dist/stores/portCoordinateStore.svelte.d.ts +2 -2
  312. package/dist/stores/portCoordinateStore.svelte.js +15 -8
  313. package/dist/stores/settingsStore.svelte.d.ts +2 -2
  314. package/dist/stores/settingsStore.svelte.js +62 -57
  315. package/dist/stores/workflowStore.svelte.d.ts +3 -3
  316. package/dist/stores/workflowStore.svelte.js +50 -47
  317. package/dist/stories/CanvasDecorator.svelte +35 -32
  318. package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
  319. package/dist/stories/EdgeDecorator.svelte +125 -0
  320. package/dist/stories/EdgeDecorator.svelte.d.ts +17 -0
  321. package/dist/stories/NodeDecorator.svelte +59 -53
  322. package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
  323. package/dist/stories/utils.d.ts +2 -2
  324. package/dist/stories/utils.js +105 -67
  325. package/dist/styles/base.css +599 -595
  326. package/dist/styles/toast.css +14 -14
  327. package/dist/styles/tokens.css +409 -378
  328. package/dist/svelte-app.d.ts +9 -9
  329. package/dist/svelte-app.js +39 -39
  330. package/dist/themes/default.d.ts +2 -0
  331. package/dist/themes/default.js +9 -0
  332. package/dist/themes/index.d.ts +13 -0
  333. package/dist/themes/index.js +44 -0
  334. package/dist/themes/minimal.d.ts +2 -0
  335. package/dist/themes/minimal.js +11 -0
  336. package/dist/types/agentspec.d.ts +18 -18
  337. package/dist/types/agentspec.js +2 -2
  338. package/dist/types/auth.d.ts +1 -1
  339. package/dist/types/auth.js +6 -6
  340. package/dist/types/config.d.ts +6 -6
  341. package/dist/types/events.d.ts +2 -2
  342. package/dist/types/events.js +2 -2
  343. package/dist/types/index.d.ts +32 -32
  344. package/dist/types/index.js +6 -6
  345. package/dist/types/interrupt.d.ts +6 -6
  346. package/dist/types/interrupt.js +21 -21
  347. package/dist/types/interruptState.d.ts +12 -12
  348. package/dist/types/interruptState.js +66 -66
  349. package/dist/types/playground.d.ts +7 -7
  350. package/dist/types/playground.js +14 -14
  351. package/dist/types/settings.d.ts +5 -3
  352. package/dist/types/settings.js +25 -18
  353. package/dist/types/skin.d.ts +31 -0
  354. package/dist/types/skin.js +1 -0
  355. package/dist/types/theme.d.ts +35 -0
  356. package/dist/types/theme.js +1 -0
  357. package/dist/types/uischema.d.ts +4 -4
  358. package/dist/types/uischema.js +3 -3
  359. package/dist/utils/colors.d.ts +1 -1
  360. package/dist/utils/colors.js +97 -95
  361. package/dist/utils/config.d.ts +2 -2
  362. package/dist/utils/config.js +48 -48
  363. package/dist/utils/connections.d.ts +2 -2
  364. package/dist/utils/connections.js +15 -15
  365. package/dist/utils/errors.js +3 -3
  366. package/dist/utils/fetchWithAuth.d.ts +1 -1
  367. package/dist/utils/fetchWithAuth.js +2 -2
  368. package/dist/utils/handleIds.d.ts +2 -2
  369. package/dist/utils/handleIds.js +8 -8
  370. package/dist/utils/handlePositioning.d.ts +1 -1
  371. package/dist/utils/handlePositioning.js +2 -2
  372. package/dist/utils/icons.d.ts +1 -1
  373. package/dist/utils/icons.js +74 -74
  374. package/dist/utils/logger.d.ts +1 -1
  375. package/dist/utils/logger.js +7 -7
  376. package/dist/utils/nodeStatus.d.ts +1 -1
  377. package/dist/utils/nodeStatus.js +48 -48
  378. package/dist/utils/nodeTypes.d.ts +1 -1
  379. package/dist/utils/nodeTypes.js +21 -20
  380. package/dist/utils/nodeWrapper.d.ts +7 -7
  381. package/dist/utils/nodeWrapper.js +21 -19
  382. package/dist/utils/performanceUtils.d.ts +1 -1
  383. package/dist/utils/performanceUtils.js +2 -1
  384. package/dist/utils/sanitize.js +1 -1
  385. package/dist/utils/uischema.d.ts +2 -2
  386. package/dist/utils/uischema.js +8 -8
  387. package/dist/utils/validation.js +20 -8
  388. package/package.json +296 -291
@@ -17,736 +17,769 @@
17
17
  -->
18
18
 
19
19
  <script lang="ts">
20
- import { onMount, onDestroy } from 'svelte';
21
- import { EditorView, lineNumbers, drawSelection, keymap } from '@codemirror/view';
22
- import { EditorState, Compartment } from '@codemirror/state';
23
- import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
24
- import { highlightSpecialChars, highlightActiveLine } from '@codemirror/view';
25
- import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
26
- import { markdown } from '@codemirror/lang-markdown';
27
- import { oneDark } from '@codemirror/theme-one-dark';
28
-
29
- interface Props {
30
- /** Field identifier */
31
- id: string;
32
- /** Current value (markdown string) */
33
- value: string;
34
- /** Placeholder text shown when empty */
35
- placeholder?: string;
36
- /** Whether the field is required */
37
- required?: boolean;
38
- /** Editor height - "auto" or specific value like "300px" */
39
- height?: string;
40
- /** Whether to show the toolbar */
41
- showToolbar?: boolean;
42
- /** Whether to show the status bar */
43
- showStatusBar?: boolean;
44
- /** Whether to enable spell checking */
45
- spellChecker?: boolean;
46
- /** Whether to enable autosave */
47
- autosave?: boolean;
48
- /** Autosave delay in milliseconds */
49
- autosaveDelay?: number;
50
- /** Whether the field is disabled (read-only) */
51
- disabled?: boolean;
52
- /** Whether to use dark theme */
53
- darkTheme?: boolean;
54
- /** ARIA description ID */
55
- ariaDescribedBy?: string;
56
- /** Callback when value changes */
57
- onChange: (value: string) => void;
58
- }
59
-
60
- let {
61
- id,
62
- value = '',
63
- placeholder = 'Write your markdown here...',
64
- required = false,
65
- height = '300px',
66
- showToolbar = true,
67
- showStatusBar = true,
68
- spellChecker = false,
69
- autosave = false,
70
- autosaveDelay = 10000,
71
- disabled = false,
72
- darkTheme = false,
73
- ariaDescribedBy,
74
- onChange
75
- }: Props = $props();
76
-
77
- /** Reference to the editor container element */
78
- let containerRef: HTMLDivElement | undefined = $state(undefined);
79
-
80
- /** CodeMirror editor instance */
81
- let editorView: EditorView | undefined = $state(undefined);
82
-
83
- /** Flag to prevent update loops */
84
- let isInternalUpdate = false;
85
-
86
- /** Flag to skip $effect when change originated from the editor */
87
- let isEditorUpdate = false;
88
-
89
- /** Status bar stats */
90
- let wordCount = $state(0);
91
- let lineCount = $state(0);
92
- let charCount = $state(0);
93
-
94
- /** Autosave timer */
95
- let autosaveTimer: ReturnType<typeof setTimeout> | undefined;
96
-
97
- /** Theme compartment for dynamic theme switching */
98
- const themeCompartment = new Compartment();
99
-
100
- // ── Toolbar actions ──────────────────────────────────────
101
-
102
- type ToolbarAction = {
103
- id: string;
104
- label: string;
105
- icon: string;
106
- /** If true, icon is an SVG string rendered with {@html} */
107
- isSvg?: boolean;
108
- shortcut?: string;
109
- action: () => void;
110
- };
111
-
112
- // Inline SVG icons (heroicons outline, 16x16)
113
- const icons = {
114
- link: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z"/><path d="M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z"/></svg>',
115
- image:
116
- '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0L2.5 11.06Zm6.5-3.31a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd"/></svg>',
117
- table:
118
- '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm8.26 4.51v3.75h1.5v-3.75h-1.5Zm1.5-1.5v-3.75h-1.5v3.75h1.5Zm-3-3.75H3.25a.75.75 0 0 0-.75.75v3h5.25v-3.75Zm-5.25 5.25v3.75c0 .414.336.75.75.75h4.5v-4.5H2.5Zm14.5 0h-5.25v4.5h4.5a.75.75 0 0 0 .75-.75v-3.75Zm0-1.5v-3a.75.75 0 0 0-.75-.75h-4.5v3.75H17Z" clip-rule="evenodd"/></svg>'
119
- };
120
-
121
- function wrapSelection(before: string, after: string) {
122
- if (!editorView) return;
123
- const { from, to } = editorView.state.selection.main;
124
- const selected = editorView.state.sliceDoc(from, to);
125
- const replacement = `${before}${selected || 'text'}${after}`;
126
- editorView.dispatch({
127
- changes: { from, to, insert: replacement },
128
- selection: {
129
- anchor: selected ? from + before.length : from + before.length,
130
- head: selected ? from + before.length + selected.length : from + before.length + 4
131
- }
132
- });
133
- editorView.focus();
134
- }
135
-
136
- function prefixLine(prefix: string) {
137
- if (!editorView) return;
138
- const { from } = editorView.state.selection.main;
139
- const line = editorView.state.doc.lineAt(from);
140
- const currentText = line.text;
141
-
142
- // If already has this prefix, remove it (toggle)
143
- if (currentText.startsWith(prefix)) {
144
- editorView.dispatch({
145
- changes: { from: line.from, to: line.from + prefix.length, insert: '' }
146
- });
147
- } else {
148
- // Remove any existing heading prefix before adding new one
149
- const headingMatch = currentText.match(/^#{1,6}\s/);
150
- const removeLen = headingMatch ? headingMatch[0].length : 0;
151
- editorView.dispatch({
152
- changes: { from: line.from, to: line.from + removeLen, insert: prefix }
153
- });
154
- }
155
- editorView.focus();
156
- }
157
-
158
- function insertAtCursor(text: string) {
159
- if (!editorView) return;
160
- const { from, to } = editorView.state.selection.main;
161
- editorView.dispatch({
162
- changes: { from, to, insert: text },
163
- selection: { anchor: from + text.length }
164
- });
165
- editorView.focus();
166
- }
167
-
168
- const toolbarActions: (ToolbarAction | '|')[] = [
169
- {
170
- id: 'bold',
171
- label: 'Bold',
172
- icon: 'B',
173
- shortcut: 'Mod-b',
174
- action: () => wrapSelection('**', '**')
175
- },
176
- {
177
- id: 'italic',
178
- label: 'Italic',
179
- icon: 'I',
180
- shortcut: 'Mod-i',
181
- action: () => wrapSelection('_', '_')
182
- },
183
- {
184
- id: 'strikethrough',
185
- label: 'Strikethrough',
186
- icon: 'S',
187
- action: () => wrapSelection('~~', '~~')
188
- },
189
- '|',
190
- {
191
- id: 'heading-1',
192
- label: 'Heading 1',
193
- icon: 'H1',
194
- action: () => prefixLine('# ')
195
- },
196
- {
197
- id: 'heading-2',
198
- label: 'Heading 2',
199
- icon: 'H2',
200
- action: () => prefixLine('## ')
201
- },
202
- {
203
- id: 'heading-3',
204
- label: 'Heading 3',
205
- icon: 'H3',
206
- action: () => prefixLine('### ')
207
- },
208
- '|',
209
- {
210
- id: 'quote',
211
- label: 'Quote',
212
- icon: '"',
213
- action: () => prefixLine('> ')
214
- },
215
- {
216
- id: 'unordered-list',
217
- label: 'Unordered List',
218
- icon: '•',
219
- action: () => prefixLine('- ')
220
- },
221
- {
222
- id: 'ordered-list',
223
- label: 'Ordered List',
224
- icon: '1.',
225
- action: () => prefixLine('1. ')
226
- },
227
- '|',
228
- {
229
- id: 'link',
230
- label: 'Link',
231
- icon: icons.link,
232
- isSvg: true,
233
- shortcut: 'Mod-k',
234
- action: () => {
235
- if (!editorView) return;
236
- const { from, to } = editorView.state.selection.main;
237
- const selected = editorView.state.sliceDoc(from, to);
238
- const text = selected || 'link text';
239
- const replacement = `[${text}](url)`;
240
- editorView.dispatch({
241
- changes: { from, to, insert: replacement },
242
- selection: {
243
- anchor: from + text.length + 3,
244
- head: from + text.length + 6
245
- }
246
- });
247
- editorView.focus();
248
- }
249
- },
250
- {
251
- id: 'image',
252
- label: 'Image',
253
- icon: icons.image,
254
- isSvg: true,
255
- action: () => insertAtCursor('![alt text](image-url)')
256
- },
257
- {
258
- id: 'table',
259
- label: 'Table',
260
- icon: icons.table,
261
- isSvg: true,
262
- action: () =>
263
- insertAtCursor('\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |\n')
264
- }
265
- ];
266
-
267
- // ── CM6 Keyboard shortcuts for toolbar actions ───────────
268
-
269
- function createToolbarKeymap() {
270
- return keymap.of([
271
- {
272
- key: 'Mod-b',
273
- run: () => {
274
- wrapSelection('**', '**');
275
- return true;
276
- }
277
- },
278
- {
279
- key: 'Mod-i',
280
- run: () => {
281
- wrapSelection('_', '_');
282
- return true;
283
- }
284
- },
285
- {
286
- key: 'Mod-k',
287
- run: () => {
288
- const action = toolbarActions.find((a) => a !== '|' && a.id === 'link');
289
- if (action && action !== '|') action.action();
290
- return true;
291
- }
292
- },
293
- {
294
- key: 'Mod-h',
295
- run: () => {
296
- prefixLine('## ');
297
- return true;
298
- }
299
- },
300
- {
301
- key: "Mod-'",
302
- run: () => {
303
- prefixLine('> ');
304
- return true;
305
- }
306
- },
307
- {
308
- key: 'Mod-l',
309
- run: () => {
310
- prefixLine('- ');
311
- return true;
312
- }
313
- }
314
- ]);
315
- }
316
-
317
- // ── Stats computation ────────────────────────────────────
318
-
319
- function updateStats(doc: { toString: () => string; lines: number }) {
320
- const text = doc.toString();
321
- charCount = text.length;
322
- lineCount = doc.lines;
323
- const trimmed = text.trim();
324
- wordCount = trimmed ? trimmed.split(/\s+/).length : 0;
325
- }
326
-
327
- // ── Editor setup ─────────────────────────────────────────
328
-
329
- function createExtensions() {
330
- const extensions = [
331
- lineNumbers(),
332
- highlightSpecialChars(),
333
- highlightActiveLine(),
334
- drawSelection(),
335
-
336
- // Editing features (skip when read-only)
337
- ...(disabled
338
- ? [EditorState.readOnly.of(true), EditorView.editable.of(false)]
339
- : [
340
- history(),
341
- keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
342
- createToolbarKeymap()
343
- ]),
344
-
345
- // Theme
346
- themeCompartment.of(
347
- darkTheme ? oneDark : syntaxHighlighting(defaultHighlightStyle, { fallback: true })
348
- ),
349
-
350
- // Markdown language support
351
- markdown(),
352
-
353
- // Update listener
354
- EditorView.updateListener.of((update) => {
355
- if (!update.docChanged || isInternalUpdate) return;
356
-
357
- const content = update.state.doc.toString();
358
- isEditorUpdate = true;
359
- onChange(content);
360
-
361
- updateStats(update.state.doc);
362
-
363
- // Autosave
364
- if (autosave) {
365
- clearTimeout(autosaveTimer);
366
- autosaveTimer = setTimeout(() => {
367
- try {
368
- localStorage.setItem(`flowdrop-markdown-${id}`, content);
369
- } catch {
370
- // localStorage may be full or unavailable
371
- }
372
- }, autosaveDelay);
373
- }
374
- }),
375
-
376
- // Custom theme
377
- EditorView.theme({
378
- '&': {
379
- height: height,
380
- fontSize: 'var(--fd-text-sm, 0.8125rem)',
381
- fontFamily: "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace"
382
- },
383
- '.cm-scroller': {
384
- overflow: 'auto'
385
- },
386
- '.cm-content': {
387
- minHeight: '100px',
388
- padding: '0.5rem 0'
389
- },
390
- '&.cm-focused': {
391
- outline: 'none'
392
- }
393
- }),
394
- EditorView.lineWrapping,
395
-
396
- // Accessibility
397
- EditorView.contentAttributes.of({
398
- 'aria-label': 'Markdown editor',
399
- 'aria-multiline': 'true'
400
- })
401
- ];
402
-
403
- return extensions;
404
- }
405
-
406
- onMount(() => {
407
- if (!containerRef) return;
408
-
409
- // Load autosaved content if available
410
- let initialContent = value;
411
- if (autosave) {
412
- try {
413
- const saved = localStorage.getItem(`flowdrop-markdown-${id}`);
414
- if (saved !== null) {
415
- initialContent = saved;
416
- onChange(saved);
417
- }
418
- } catch {
419
- // localStorage unavailable
420
- }
421
- }
422
-
423
- editorView = new EditorView({
424
- state: EditorState.create({
425
- doc: initialContent,
426
- extensions: createExtensions()
427
- }),
428
- parent: containerRef
429
- });
430
-
431
- updateStats(editorView.state.doc);
432
- });
433
-
434
- onDestroy(() => {
435
- if (autosaveTimer) clearTimeout(autosaveTimer);
436
- if (editorView) editorView.destroy();
437
- });
438
-
439
- /**
440
- * Update editor content when value prop changes externally
441
- */
442
- $effect(() => {
443
- if (!editorView) return;
444
-
445
- // Skip if the change originated from the editor itself
446
- if (isEditorUpdate) {
447
- isEditorUpdate = false;
448
- return;
449
- }
450
-
451
- const currentContent = editorView.state.doc.toString();
452
- if (value !== currentContent && !isInternalUpdate) {
453
- isInternalUpdate = true;
454
- editorView.dispatch({
455
- changes: {
456
- from: 0,
457
- to: editorView.state.doc.length,
458
- insert: value
459
- }
460
- });
461
- isInternalUpdate = false;
462
- updateStats(editorView.state.doc);
463
- }
464
- });
20
+ import { onMount, onDestroy } from "svelte";
21
+ import {
22
+ EditorView,
23
+ lineNumbers,
24
+ drawSelection,
25
+ keymap,
26
+ } from "@codemirror/view";
27
+ import { EditorState, Compartment } from "@codemirror/state";
28
+ import {
29
+ history,
30
+ historyKeymap,
31
+ defaultKeymap,
32
+ indentWithTab,
33
+ } from "@codemirror/commands";
34
+ import { highlightSpecialChars, highlightActiveLine } from "@codemirror/view";
35
+ import {
36
+ syntaxHighlighting,
37
+ defaultHighlightStyle,
38
+ } from "@codemirror/language";
39
+ import { markdown } from "@codemirror/lang-markdown";
40
+ import { oneDark } from "@codemirror/theme-one-dark";
41
+
42
+ interface Props {
43
+ /** Field identifier */
44
+ id: string;
45
+ /** Current value (markdown string) */
46
+ value: string;
47
+ /** Placeholder text shown when empty */
48
+ placeholder?: string;
49
+ /** Whether the field is required */
50
+ required?: boolean;
51
+ /** Editor height - "auto" or specific value like "300px" */
52
+ height?: string;
53
+ /** Whether to show the toolbar */
54
+ showToolbar?: boolean;
55
+ /** Whether to show the status bar */
56
+ showStatusBar?: boolean;
57
+ /** Whether to enable spell checking */
58
+ spellChecker?: boolean;
59
+ /** Whether to enable autosave */
60
+ autosave?: boolean;
61
+ /** Autosave delay in milliseconds */
62
+ autosaveDelay?: number;
63
+ /** Whether the field is disabled (read-only) */
64
+ disabled?: boolean;
65
+ /** Whether to use dark theme */
66
+ darkTheme?: boolean;
67
+ /** ARIA description ID */
68
+ ariaDescribedBy?: string;
69
+ /** Callback when value changes */
70
+ onChange: (value: string) => void;
71
+ }
72
+
73
+ let {
74
+ id,
75
+ value = "",
76
+ placeholder = "Write your markdown here...",
77
+ required = false,
78
+ height = "300px",
79
+ showToolbar = true,
80
+ showStatusBar = true,
81
+ spellChecker = false,
82
+ autosave = false,
83
+ autosaveDelay = 10000,
84
+ disabled = false,
85
+ darkTheme = false,
86
+ ariaDescribedBy,
87
+ onChange,
88
+ }: Props = $props();
89
+
90
+ /** Reference to the editor container element */
91
+ let containerRef: HTMLDivElement | undefined = $state(undefined);
92
+
93
+ /** CodeMirror editor instance */
94
+ let editorView: EditorView | undefined = $state(undefined);
95
+
96
+ /** Flag to prevent update loops */
97
+ let isInternalUpdate = false;
98
+
99
+ /** Flag to skip $effect when change originated from the editor */
100
+ let isEditorUpdate = false;
101
+
102
+ /** Status bar stats */
103
+ let wordCount = $state(0);
104
+ let lineCount = $state(0);
105
+ let charCount = $state(0);
106
+
107
+ /** Autosave timer */
108
+ let autosaveTimer: ReturnType<typeof setTimeout> | undefined;
109
+
110
+ /** Theme compartment for dynamic theme switching */
111
+ const themeCompartment = new Compartment();
112
+
113
+ // ── Toolbar actions ──────────────────────────────────────
114
+
115
+ type ToolbarAction = {
116
+ id: string;
117
+ label: string;
118
+ icon: string;
119
+ /** If true, icon is an SVG string rendered with {@html} */
120
+ isSvg?: boolean;
121
+ shortcut?: string;
122
+ action: () => void;
123
+ };
124
+
125
+ // Inline SVG icons (heroicons outline, 16x16)
126
+ const icons = {
127
+ link: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z"/><path d="M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z"/></svg>',
128
+ image:
129
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0L2.5 11.06Zm6.5-3.31a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd"/></svg>',
130
+ table:
131
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm8.26 4.51v3.75h1.5v-3.75h-1.5Zm1.5-1.5v-3.75h-1.5v3.75h1.5Zm-3-3.75H3.25a.75.75 0 0 0-.75.75v3h5.25v-3.75Zm-5.25 5.25v3.75c0 .414.336.75.75.75h4.5v-4.5H2.5Zm14.5 0h-5.25v4.5h4.5a.75.75 0 0 0 .75-.75v-3.75Zm0-1.5v-3a.75.75 0 0 0-.75-.75h-4.5v3.75H17Z" clip-rule="evenodd"/></svg>',
132
+ };
133
+
134
+ function wrapSelection(before: string, after: string) {
135
+ if (!editorView) return;
136
+ const { from, to } = editorView.state.selection.main;
137
+ const selected = editorView.state.sliceDoc(from, to);
138
+ const replacement = `${before}${selected || "text"}${after}`;
139
+ editorView.dispatch({
140
+ changes: { from, to, insert: replacement },
141
+ selection: {
142
+ anchor: selected ? from + before.length : from + before.length,
143
+ head: selected
144
+ ? from + before.length + selected.length
145
+ : from + before.length + 4,
146
+ },
147
+ });
148
+ editorView.focus();
149
+ }
150
+
151
+ function prefixLine(prefix: string) {
152
+ if (!editorView) return;
153
+ const { from } = editorView.state.selection.main;
154
+ const line = editorView.state.doc.lineAt(from);
155
+ const currentText = line.text;
156
+
157
+ // If already has this prefix, remove it (toggle)
158
+ if (currentText.startsWith(prefix)) {
159
+ editorView.dispatch({
160
+ changes: { from: line.from, to: line.from + prefix.length, insert: "" },
161
+ });
162
+ } else {
163
+ // Remove any existing heading prefix before adding new one
164
+ const headingMatch = currentText.match(/^#{1,6}\s/);
165
+ const removeLen = headingMatch ? headingMatch[0].length : 0;
166
+ editorView.dispatch({
167
+ changes: { from: line.from, to: line.from + removeLen, insert: prefix },
168
+ });
169
+ }
170
+ editorView.focus();
171
+ }
172
+
173
+ function insertAtCursor(text: string) {
174
+ if (!editorView) return;
175
+ const { from, to } = editorView.state.selection.main;
176
+ editorView.dispatch({
177
+ changes: { from, to, insert: text },
178
+ selection: { anchor: from + text.length },
179
+ });
180
+ editorView.focus();
181
+ }
182
+
183
+ const toolbarActions: (ToolbarAction | "|")[] = [
184
+ {
185
+ id: "bold",
186
+ label: "Bold",
187
+ icon: "B",
188
+ shortcut: "Mod-b",
189
+ action: () => wrapSelection("**", "**"),
190
+ },
191
+ {
192
+ id: "italic",
193
+ label: "Italic",
194
+ icon: "I",
195
+ shortcut: "Mod-i",
196
+ action: () => wrapSelection("_", "_"),
197
+ },
198
+ {
199
+ id: "strikethrough",
200
+ label: "Strikethrough",
201
+ icon: "S",
202
+ action: () => wrapSelection("~~", "~~"),
203
+ },
204
+ "|",
205
+ {
206
+ id: "heading-1",
207
+ label: "Heading 1",
208
+ icon: "H1",
209
+ action: () => prefixLine("# "),
210
+ },
211
+ {
212
+ id: "heading-2",
213
+ label: "Heading 2",
214
+ icon: "H2",
215
+ action: () => prefixLine("## "),
216
+ },
217
+ {
218
+ id: "heading-3",
219
+ label: "Heading 3",
220
+ icon: "H3",
221
+ action: () => prefixLine("### "),
222
+ },
223
+ "|",
224
+ {
225
+ id: "quote",
226
+ label: "Quote",
227
+ icon: '"',
228
+ action: () => prefixLine("> "),
229
+ },
230
+ {
231
+ id: "unordered-list",
232
+ label: "Unordered List",
233
+ icon: "•",
234
+ action: () => prefixLine("- "),
235
+ },
236
+ {
237
+ id: "ordered-list",
238
+ label: "Ordered List",
239
+ icon: "1.",
240
+ action: () => prefixLine("1. "),
241
+ },
242
+ "|",
243
+ {
244
+ id: "link",
245
+ label: "Link",
246
+ icon: icons.link,
247
+ isSvg: true,
248
+ shortcut: "Mod-k",
249
+ action: () => {
250
+ if (!editorView) return;
251
+ const { from, to } = editorView.state.selection.main;
252
+ const selected = editorView.state.sliceDoc(from, to);
253
+ const text = selected || "link text";
254
+ const replacement = `[${text}](url)`;
255
+ editorView.dispatch({
256
+ changes: { from, to, insert: replacement },
257
+ selection: {
258
+ anchor: from + text.length + 3,
259
+ head: from + text.length + 6,
260
+ },
261
+ });
262
+ editorView.focus();
263
+ },
264
+ },
265
+ {
266
+ id: "image",
267
+ label: "Image",
268
+ icon: icons.image,
269
+ isSvg: true,
270
+ action: () => insertAtCursor("![alt text](image-url)"),
271
+ },
272
+ {
273
+ id: "table",
274
+ label: "Table",
275
+ icon: icons.table,
276
+ isSvg: true,
277
+ action: () =>
278
+ insertAtCursor(
279
+ "\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |\n",
280
+ ),
281
+ },
282
+ ];
283
+
284
+ // ── CM6 Keyboard shortcuts for toolbar actions ───────────
285
+
286
+ function createToolbarKeymap() {
287
+ return keymap.of([
288
+ {
289
+ key: "Mod-b",
290
+ run: () => {
291
+ wrapSelection("**", "**");
292
+ return true;
293
+ },
294
+ },
295
+ {
296
+ key: "Mod-i",
297
+ run: () => {
298
+ wrapSelection("_", "_");
299
+ return true;
300
+ },
301
+ },
302
+ {
303
+ key: "Mod-k",
304
+ run: () => {
305
+ const action = toolbarActions.find(
306
+ (a) => a !== "|" && a.id === "link",
307
+ );
308
+ if (action && action !== "|") action.action();
309
+ return true;
310
+ },
311
+ },
312
+ {
313
+ key: "Mod-h",
314
+ run: () => {
315
+ prefixLine("## ");
316
+ return true;
317
+ },
318
+ },
319
+ {
320
+ key: "Mod-'",
321
+ run: () => {
322
+ prefixLine("> ");
323
+ return true;
324
+ },
325
+ },
326
+ {
327
+ key: "Mod-l",
328
+ run: () => {
329
+ prefixLine("- ");
330
+ return true;
331
+ },
332
+ },
333
+ ]);
334
+ }
335
+
336
+ // ── Stats computation ────────────────────────────────────
337
+
338
+ function updateStats(doc: { toString: () => string; lines: number }) {
339
+ const text = doc.toString();
340
+ charCount = text.length;
341
+ lineCount = doc.lines;
342
+ const trimmed = text.trim();
343
+ wordCount = trimmed ? trimmed.split(/\s+/).length : 0;
344
+ }
345
+
346
+ // ── Editor setup ─────────────────────────────────────────
347
+
348
+ function createExtensions() {
349
+ const extensions = [
350
+ lineNumbers(),
351
+ highlightSpecialChars(),
352
+ highlightActiveLine(),
353
+ drawSelection(),
354
+
355
+ // Editing features (skip when read-only)
356
+ ...(disabled
357
+ ? [EditorState.readOnly.of(true), EditorView.editable.of(false)]
358
+ : [
359
+ history(),
360
+ keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
361
+ createToolbarKeymap(),
362
+ ]),
363
+
364
+ // Theme
365
+ themeCompartment.of(
366
+ darkTheme
367
+ ? oneDark
368
+ : syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
369
+ ),
370
+
371
+ // Markdown language support
372
+ markdown(),
373
+
374
+ // Update listener
375
+ EditorView.updateListener.of((update) => {
376
+ if (!update.docChanged || isInternalUpdate) return;
377
+
378
+ const content = update.state.doc.toString();
379
+ isEditorUpdate = true;
380
+ onChange(content);
381
+
382
+ updateStats(update.state.doc);
383
+
384
+ // Autosave
385
+ if (autosave) {
386
+ clearTimeout(autosaveTimer);
387
+ autosaveTimer = setTimeout(() => {
388
+ try {
389
+ localStorage.setItem(`flowdrop-markdown-${id}`, content);
390
+ } catch {
391
+ // localStorage may be full or unavailable
392
+ }
393
+ }, autosaveDelay);
394
+ }
395
+ }),
396
+
397
+ // Custom theme
398
+ EditorView.theme({
399
+ "&": {
400
+ height: height,
401
+ fontSize: "var(--fd-text-sm, 0.8125rem)",
402
+ fontFamily:
403
+ "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace",
404
+ },
405
+ ".cm-scroller": {
406
+ overflow: "auto",
407
+ },
408
+ ".cm-content": {
409
+ minHeight: "100px",
410
+ padding: "0.5rem 0",
411
+ },
412
+ "&.cm-focused": {
413
+ outline: "none",
414
+ },
415
+ }),
416
+ EditorView.lineWrapping,
417
+
418
+ // Accessibility
419
+ EditorView.contentAttributes.of({
420
+ "aria-label": "Markdown editor",
421
+ "aria-multiline": "true",
422
+ }),
423
+ ];
424
+
425
+ return extensions;
426
+ }
427
+
428
+ onMount(() => {
429
+ if (!containerRef) return;
430
+
431
+ // Load autosaved content if available
432
+ let initialContent = value;
433
+ if (autosave) {
434
+ try {
435
+ const saved = localStorage.getItem(`flowdrop-markdown-${id}`);
436
+ if (saved !== null) {
437
+ initialContent = saved;
438
+ onChange(saved);
439
+ }
440
+ } catch {
441
+ // localStorage unavailable
442
+ }
443
+ }
444
+
445
+ editorView = new EditorView({
446
+ state: EditorState.create({
447
+ doc: initialContent,
448
+ extensions: createExtensions(),
449
+ }),
450
+ parent: containerRef,
451
+ });
452
+
453
+ updateStats(editorView.state.doc);
454
+ });
455
+
456
+ onDestroy(() => {
457
+ if (autosaveTimer) clearTimeout(autosaveTimer);
458
+ if (editorView) editorView.destroy();
459
+ });
460
+
461
+ /**
462
+ * Update editor content when value prop changes externally
463
+ */
464
+ $effect(() => {
465
+ if (!editorView) return;
466
+
467
+ // Skip if the change originated from the editor itself
468
+ if (isEditorUpdate) {
469
+ isEditorUpdate = false;
470
+ return;
471
+ }
472
+
473
+ const currentContent = editorView.state.doc.toString();
474
+ if (value !== currentContent && !isInternalUpdate) {
475
+ isInternalUpdate = true;
476
+ editorView.dispatch({
477
+ changes: {
478
+ from: 0,
479
+ to: editorView.state.doc.length,
480
+ insert: value,
481
+ },
482
+ });
483
+ isInternalUpdate = false;
484
+ updateStats(editorView.state.doc);
485
+ }
486
+ });
465
487
  </script>
466
488
 
467
489
  <div
468
- class="form-markdown-editor"
469
- class:form-markdown-editor--dark={darkTheme}
470
- style="--editor-height: {height}"
490
+ class="form-markdown-editor"
491
+ class:form-markdown-editor--dark={darkTheme}
492
+ style="--editor-height: {height}"
471
493
  >
472
- <!-- Hidden input for form submission compatibility -->
473
- <input
474
- type="hidden"
475
- {id}
476
- name={id}
477
- {value}
478
- aria-describedby={ariaDescribedBy}
479
- aria-required={required}
480
- />
481
-
482
- <!-- Toolbar -->
483
- {#if showToolbar && !disabled}
484
- <div class="form-markdown-editor__toolbar" role="toolbar" aria-label="Markdown formatting">
485
- {#each toolbarActions as item}
486
- {#if item === '|'}
487
- <span class="form-markdown-editor__separator"></span>
488
- {:else}
489
- <button
490
- type="button"
491
- class="form-markdown-editor__btn"
492
- title="{item.label}{item.shortcut ? ` (${item.shortcut.replace('Mod', '⌘')})` : ''}"
493
- onclick={item.action}
494
- >
495
- {#if item.isSvg}
496
- <span class="form-markdown-editor__btn-svg">{@html item.icon}</span>
497
- {:else}
498
- <span
499
- class="form-markdown-editor__btn-icon"
500
- class:form-markdown-editor__btn-icon--bold={item.id === 'bold'}
501
- class:form-markdown-editor__btn-icon--italic={item.id === 'italic'}
502
- class:form-markdown-editor__btn-icon--strike={item.id === 'strikethrough'}
503
- >{item.icon}</span
504
- >
505
- {/if}
506
- </button>
507
- {/if}
508
- {/each}
509
- </div>
510
- {/if}
511
-
512
- <!-- CodeMirror container -->
513
- <div bind:this={containerRef} class="form-markdown-editor__body"></div>
514
-
515
- <!-- Status bar -->
516
- {#if showStatusBar}
517
- <div class="form-markdown-editor__status">
518
- <span>words: {wordCount}</span>
519
- <span>lines: {lineCount}</span>
520
- <span>characters: {charCount}</span>
521
- </div>
522
- {/if}
494
+ <!-- Hidden input for form submission compatibility -->
495
+ <input
496
+ type="hidden"
497
+ {id}
498
+ name={id}
499
+ {value}
500
+ aria-describedby={ariaDescribedBy}
501
+ aria-required={required}
502
+ />
503
+
504
+ <!-- Toolbar -->
505
+ {#if showToolbar && !disabled}
506
+ <div
507
+ class="form-markdown-editor__toolbar"
508
+ role="toolbar"
509
+ aria-label="Markdown formatting"
510
+ >
511
+ {#each toolbarActions as item}
512
+ {#if item === "|"}
513
+ <span class="form-markdown-editor__separator"></span>
514
+ {:else}
515
+ <button
516
+ type="button"
517
+ class="form-markdown-editor__btn"
518
+ title="{item.label}{item.shortcut
519
+ ? ` (${item.shortcut.replace('Mod', '⌘')})`
520
+ : ''}"
521
+ onclick={item.action}
522
+ >
523
+ {#if item.isSvg}
524
+ <span class="form-markdown-editor__btn-svg"
525
+ >{@html item.icon}</span
526
+ >
527
+ {:else}
528
+ <span
529
+ class="form-markdown-editor__btn-icon"
530
+ class:form-markdown-editor__btn-icon--bold={item.id === "bold"}
531
+ class:form-markdown-editor__btn-icon--italic={item.id ===
532
+ "italic"}
533
+ class:form-markdown-editor__btn-icon--strike={item.id ===
534
+ "strikethrough"}>{item.icon}</span
535
+ >
536
+ {/if}
537
+ </button>
538
+ {/if}
539
+ {/each}
540
+ </div>
541
+ {/if}
542
+
543
+ <!-- CodeMirror container -->
544
+ <div bind:this={containerRef} class="form-markdown-editor__body"></div>
545
+
546
+ <!-- Status bar -->
547
+ {#if showStatusBar}
548
+ <div class="form-markdown-editor__status">
549
+ <span>words: {wordCount}</span>
550
+ <span>lines: {lineCount}</span>
551
+ <span>characters: {charCount}</span>
552
+ </div>
553
+ {/if}
523
554
  </div>
524
555
 
525
556
  <style>
526
- .form-markdown-editor {
527
- position: relative;
528
- width: 100%;
529
- }
530
-
531
- /* ── Toolbar ───────────────────────────────────── */
532
-
533
- .form-markdown-editor__toolbar {
534
- display: flex;
535
- align-items: center;
536
- gap: 0.125rem;
537
- border: 1px solid var(--fd-border);
538
- border-bottom: none;
539
- border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
540
- background-color: var(--fd-subtle);
541
- padding: 0.375rem 0.5rem;
542
- }
543
-
544
- .form-markdown-editor__btn {
545
- display: flex;
546
- align-items: center;
547
- justify-content: center;
548
- width: 2rem;
549
- height: 2rem;
550
- border: none;
551
- border-radius: var(--fd-radius-md);
552
- background: none;
553
- color: var(--fd-muted-foreground);
554
- cursor: pointer;
555
- font-size: 0.8125rem;
556
- transition: all var(--fd-transition-fast);
557
- }
558
-
559
- .form-markdown-editor__btn:hover {
560
- background-color: var(--fd-border);
561
- color: var(--fd-foreground);
562
- }
563
-
564
- .form-markdown-editor__btn-icon--bold {
565
- font-weight: 700;
566
- }
567
-
568
- .form-markdown-editor__btn-icon--italic {
569
- font-style: italic;
570
- }
571
-
572
- .form-markdown-editor__btn-icon--strike {
573
- text-decoration: line-through;
574
- }
575
-
576
- .form-markdown-editor__btn-svg {
577
- display: flex;
578
- align-items: center;
579
- justify-content: center;
580
- }
581
-
582
- .form-markdown-editor__separator {
583
- width: 1px;
584
- height: 1.25rem;
585
- background-color: var(--fd-border-strong);
586
- margin: 0 0.25rem;
587
- }
588
-
589
- /* ── Editor body ───────────────────────────────── */
590
-
591
- .form-markdown-editor__body {
592
- border: 1px solid var(--fd-border);
593
- border-radius: var(--fd-radius-lg);
594
- overflow: hidden;
595
- background-color: var(--fd-muted);
596
- transition: border-color var(--fd-transition-normal);
597
- }
598
-
599
- /* When toolbar is present, remove top radius */
600
- .form-markdown-editor__toolbar + .form-markdown-editor__body {
601
- border-top: none;
602
- border-radius: 0;
603
- }
604
-
605
- .form-markdown-editor__body:hover {
606
- border-color: var(--fd-border-strong);
607
- }
608
-
609
- .form-markdown-editor__body:focus-within {
610
- border-color: var(--fd-primary);
611
- background-color: var(--fd-background);
612
- box-shadow:
613
- 0 0 0 3px var(--fd-primary-muted),
614
- var(--fd-shadow-sm);
615
- }
616
-
617
- /* ── Status bar ────────────────────────────────── */
618
-
619
- .form-markdown-editor__status {
620
- display: flex;
621
- gap: 1rem;
622
- justify-content: flex-end;
623
- border: 1px solid var(--fd-border);
624
- border-top: none;
625
- border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
626
- background-color: var(--fd-muted);
627
- padding: 0.375rem 0.75rem;
628
- font-size: var(--fd-text-xs);
629
- color: var(--fd-muted-foreground);
630
- }
631
-
632
- /* When no toolbar, body gets top radius */
633
- .form-markdown-editor:not(:has(.form-markdown-editor__toolbar)) .form-markdown-editor__body {
634
- border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
635
- }
636
-
637
- /* When no status bar, body gets bottom radius */
638
- .form-markdown-editor:not(:has(.form-markdown-editor__status)) .form-markdown-editor__body {
639
- border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
640
- }
641
-
642
- /* When no toolbar AND no status bar, body gets full radius */
643
- .form-markdown-editor:not(:has(.form-markdown-editor__toolbar)):not(
644
- :has(.form-markdown-editor__status)
645
- )
646
- .form-markdown-editor__body {
647
- border-radius: var(--fd-radius-lg);
648
- }
649
-
650
- /* ── CM6 overrides ─────────────────────────────── */
651
- /* Design tokens (--fd-*) auto-resolve for dark mode via [data-theme='dark'] */
652
- /* !important needed to override oneDark's JS-injected styles */
653
-
654
- .form-markdown-editor__body :global(.cm-editor) {
655
- height: var(--editor-height, 300px);
656
- background-color: var(--fd-muted) !important;
657
- color: var(--fd-foreground) !important;
658
- }
659
-
660
- .form-markdown-editor__body :global(.cm-scroller) {
661
- overflow: auto;
662
- }
663
-
664
- .form-markdown-editor__body :global(.cm-content) {
665
- color: var(--fd-foreground) !important;
666
- caret-color: var(--fd-foreground) !important;
667
- }
668
-
669
- .form-markdown-editor__body :global(.cm-line) {
670
- color: var(--fd-foreground) !important;
671
- }
672
-
673
- .form-markdown-editor__body :global(.cm-gutters) {
674
- background-color: var(--fd-subtle) !important;
675
- border-right: 1px solid var(--fd-border);
676
- }
677
-
678
- .form-markdown-editor__body :global(.cm-linenumber) {
679
- color: var(--fd-muted-foreground) !important;
680
- }
681
-
682
- .form-markdown-editor__body :global(.cm-cursor) {
683
- border-left-color: var(--fd-muted-foreground) !important;
684
- }
685
-
686
- .form-markdown-editor__body :global(.cm-activeLine) {
687
- background-color: var(--fd-subtle) !important;
688
- }
689
-
690
- .form-markdown-editor__body :global(.cm-activeLineGutter) {
691
- background-color: var(--fd-subtle) !important;
692
- }
693
-
694
- /* ── Markdown syntax styling ───────────────────── */
695
-
696
- .form-markdown-editor__body :global(.cm-header-1) {
697
- font-size: 1.25rem;
698
- line-height: 1.4;
699
- }
700
-
701
- .form-markdown-editor__body :global(.cm-header-2) {
702
- font-size: 1.125rem;
703
- line-height: 1.4;
704
- }
705
-
706
- .form-markdown-editor__body :global(.cm-header-3) {
707
- font-size: 1rem;
708
- line-height: 1.4;
709
- }
710
-
711
- .form-markdown-editor__body :global(.cm-header) {
712
- font-weight: 600;
713
- color: var(--fd-foreground) !important;
714
- }
715
-
716
- .form-markdown-editor__body :global(.cm-processingInstruction) {
717
- color: var(--fd-success) !important;
718
- }
719
-
720
- .form-markdown-editor__body :global(.cm-emphasis) {
721
- color: var(--fd-foreground) !important;
722
- font-style: italic;
723
- }
724
-
725
- .form-markdown-editor__body :global(.cm-strong) {
726
- color: var(--fd-foreground) !important;
727
- font-weight: 700;
728
- }
729
-
730
- .form-markdown-editor__body :global(.cm-strikethrough) {
731
- color: var(--fd-muted-foreground) !important;
732
- text-decoration: line-through;
733
- }
734
-
735
- .form-markdown-editor__body :global(.cm-url) {
736
- color: var(--fd-primary) !important;
737
- }
738
-
739
- .form-markdown-editor__body :global(.cm-link) {
740
- color: var(--fd-primary) !important;
741
- text-decoration: underline;
742
- }
743
-
744
- .form-markdown-editor__body :global(.cm-meta) {
745
- color: var(--fd-muted-foreground) !important;
746
- }
747
-
748
- .form-markdown-editor__body :global(.cm-quote) {
749
- color: var(--fd-success) !important;
750
- font-style: italic;
751
- }
557
+ .form-markdown-editor {
558
+ position: relative;
559
+ width: 100%;
560
+ }
561
+
562
+ /* ── Toolbar ───────────────────────────────────── */
563
+
564
+ .form-markdown-editor__toolbar {
565
+ display: flex;
566
+ align-items: center;
567
+ gap: 0.125rem;
568
+ border: 1px solid var(--fd-border);
569
+ border-bottom: none;
570
+ border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
571
+ background-color: var(--fd-subtle);
572
+ padding: 0.375rem 0.5rem;
573
+ }
574
+
575
+ .form-markdown-editor__btn {
576
+ display: flex;
577
+ align-items: center;
578
+ justify-content: center;
579
+ width: 2rem;
580
+ height: 2rem;
581
+ border: none;
582
+ border-radius: var(--fd-radius-md);
583
+ background: none;
584
+ color: var(--fd-muted-foreground);
585
+ cursor: pointer;
586
+ font-size: 0.8125rem;
587
+ transition: all var(--fd-transition-fast);
588
+ }
589
+
590
+ .form-markdown-editor__btn:hover {
591
+ background-color: var(--fd-border);
592
+ color: var(--fd-foreground);
593
+ }
594
+
595
+ .form-markdown-editor__btn-icon--bold {
596
+ font-weight: 700;
597
+ }
598
+
599
+ .form-markdown-editor__btn-icon--italic {
600
+ font-style: italic;
601
+ }
602
+
603
+ .form-markdown-editor__btn-icon--strike {
604
+ text-decoration: line-through;
605
+ }
606
+
607
+ .form-markdown-editor__btn-svg {
608
+ display: flex;
609
+ align-items: center;
610
+ justify-content: center;
611
+ }
612
+
613
+ .form-markdown-editor__separator {
614
+ width: 1px;
615
+ height: 1.25rem;
616
+ background-color: var(--fd-border-strong);
617
+ margin: 0 0.25rem;
618
+ }
619
+
620
+ /* ── Editor body ───────────────────────────────── */
621
+
622
+ .form-markdown-editor__body {
623
+ border: 1px solid var(--fd-border);
624
+ border-radius: var(--fd-radius-lg);
625
+ overflow: hidden;
626
+ background-color: var(--fd-muted);
627
+ transition: border-color var(--fd-transition-normal);
628
+ }
629
+
630
+ /* When toolbar is present, remove top radius */
631
+ .form-markdown-editor__toolbar + .form-markdown-editor__body {
632
+ border-top: none;
633
+ border-radius: 0;
634
+ }
635
+
636
+ .form-markdown-editor__body:hover {
637
+ border-color: var(--fd-border-strong);
638
+ }
639
+
640
+ .form-markdown-editor__body:focus-within {
641
+ border-color: var(--fd-primary);
642
+ background-color: var(--fd-background);
643
+ box-shadow:
644
+ 0 0 0 3px var(--fd-primary-muted),
645
+ var(--fd-shadow-sm);
646
+ }
647
+
648
+ /* ── Status bar ────────────────────────────────── */
649
+
650
+ .form-markdown-editor__status {
651
+ display: flex;
652
+ gap: 1rem;
653
+ justify-content: flex-end;
654
+ border: 1px solid var(--fd-border);
655
+ border-top: none;
656
+ border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
657
+ background-color: var(--fd-muted);
658
+ padding: 0.375rem 0.75rem;
659
+ font-size: var(--fd-text-xs);
660
+ color: var(--fd-muted-foreground);
661
+ }
662
+
663
+ /* When no toolbar, body gets top radius */
664
+ .form-markdown-editor:not(:has(.form-markdown-editor__toolbar))
665
+ .form-markdown-editor__body {
666
+ border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
667
+ }
668
+
669
+ /* When no status bar, body gets bottom radius */
670
+ .form-markdown-editor:not(:has(.form-markdown-editor__status))
671
+ .form-markdown-editor__body {
672
+ border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
673
+ }
674
+
675
+ /* When no toolbar AND no status bar, body gets full radius */
676
+ .form-markdown-editor:not(:has(.form-markdown-editor__toolbar)):not(
677
+ :has(.form-markdown-editor__status)
678
+ )
679
+ .form-markdown-editor__body {
680
+ border-radius: var(--fd-radius-lg);
681
+ }
682
+
683
+ /* ── CM6 overrides ─────────────────────────────── */
684
+ /* Design tokens (--fd-*) auto-resolve for dark mode via [data-theme='dark'] */
685
+ /* !important needed to override oneDark's JS-injected styles */
686
+
687
+ .form-markdown-editor__body :global(.cm-editor) {
688
+ height: var(--editor-height, 300px);
689
+ background-color: var(--fd-muted) !important;
690
+ color: var(--fd-foreground) !important;
691
+ }
692
+
693
+ .form-markdown-editor__body :global(.cm-scroller) {
694
+ overflow: auto;
695
+ }
696
+
697
+ .form-markdown-editor__body :global(.cm-content) {
698
+ color: var(--fd-foreground) !important;
699
+ caret-color: var(--fd-foreground) !important;
700
+ }
701
+
702
+ .form-markdown-editor__body :global(.cm-line) {
703
+ color: var(--fd-foreground) !important;
704
+ }
705
+
706
+ .form-markdown-editor__body :global(.cm-gutters) {
707
+ background-color: var(--fd-subtle) !important;
708
+ border-right: 1px solid var(--fd-border);
709
+ }
710
+
711
+ .form-markdown-editor__body :global(.cm-linenumber) {
712
+ color: var(--fd-muted-foreground) !important;
713
+ }
714
+
715
+ .form-markdown-editor__body :global(.cm-cursor) {
716
+ border-left-color: var(--fd-muted-foreground) !important;
717
+ }
718
+
719
+ .form-markdown-editor__body :global(.cm-activeLine) {
720
+ background-color: var(--fd-subtle) !important;
721
+ }
722
+
723
+ .form-markdown-editor__body :global(.cm-activeLineGutter) {
724
+ background-color: var(--fd-subtle) !important;
725
+ }
726
+
727
+ /* ── Markdown syntax styling ───────────────────── */
728
+
729
+ .form-markdown-editor__body :global(.cm-header-1) {
730
+ font-size: 1.25rem;
731
+ line-height: 1.4;
732
+ }
733
+
734
+ .form-markdown-editor__body :global(.cm-header-2) {
735
+ font-size: 1.125rem;
736
+ line-height: 1.4;
737
+ }
738
+
739
+ .form-markdown-editor__body :global(.cm-header-3) {
740
+ font-size: 1rem;
741
+ line-height: 1.4;
742
+ }
743
+
744
+ .form-markdown-editor__body :global(.cm-header) {
745
+ font-weight: 600;
746
+ color: var(--fd-foreground) !important;
747
+ }
748
+
749
+ .form-markdown-editor__body :global(.cm-processingInstruction) {
750
+ color: var(--fd-success) !important;
751
+ }
752
+
753
+ .form-markdown-editor__body :global(.cm-emphasis) {
754
+ color: var(--fd-foreground) !important;
755
+ font-style: italic;
756
+ }
757
+
758
+ .form-markdown-editor__body :global(.cm-strong) {
759
+ color: var(--fd-foreground) !important;
760
+ font-weight: 700;
761
+ }
762
+
763
+ .form-markdown-editor__body :global(.cm-strikethrough) {
764
+ color: var(--fd-muted-foreground) !important;
765
+ text-decoration: line-through;
766
+ }
767
+
768
+ .form-markdown-editor__body :global(.cm-url) {
769
+ color: var(--fd-primary) !important;
770
+ }
771
+
772
+ .form-markdown-editor__body :global(.cm-link) {
773
+ color: var(--fd-primary) !important;
774
+ text-decoration: underline;
775
+ }
776
+
777
+ .form-markdown-editor__body :global(.cm-meta) {
778
+ color: var(--fd-muted-foreground) !important;
779
+ }
780
+
781
+ .form-markdown-editor__body :global(.cm-quote) {
782
+ color: var(--fd-success) !important;
783
+ font-style: italic;
784
+ }
752
785
  </style>