@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
@@ -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>