@d34dman/flowdrop 0.0.44 → 0.0.46

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 (67) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ConfigForm.svelte +4 -20
  3. package/dist/components/Navbar.svelte +6 -7
  4. package/dist/components/NodeSidebar.svelte +6 -2
  5. package/dist/components/SchemaForm.svelte +2 -10
  6. package/dist/components/WorkflowEditor.svelte +143 -13
  7. package/dist/components/form/FormAutocomplete.svelte +5 -9
  8. package/dist/components/form/FormCheckboxGroup.svelte +11 -1
  9. package/dist/components/form/FormCheckboxGroup.svelte.d.ts +2 -0
  10. package/dist/components/form/FormCodeEditor.svelte +16 -7
  11. package/dist/components/form/FormCodeEditor.svelte.d.ts +2 -0
  12. package/dist/components/form/FormField.svelte +20 -1
  13. package/dist/components/form/FormMarkdownEditor.svelte +29 -19
  14. package/dist/components/form/FormMarkdownEditor.svelte.d.ts +2 -0
  15. package/dist/components/form/FormNumberField.svelte +4 -0
  16. package/dist/components/form/FormNumberField.svelte.d.ts +2 -0
  17. package/dist/components/form/FormRangeField.svelte +4 -0
  18. package/dist/components/form/FormRangeField.svelte.d.ts +2 -0
  19. package/dist/components/form/FormSelect.svelte +4 -0
  20. package/dist/components/form/FormSelect.svelte.d.ts +2 -0
  21. package/dist/components/form/FormTemplateEditor.svelte +16 -7
  22. package/dist/components/form/FormTemplateEditor.svelte.d.ts +2 -0
  23. package/dist/components/form/FormTextField.svelte +4 -0
  24. package/dist/components/form/FormTextField.svelte.d.ts +2 -0
  25. package/dist/components/form/FormTextarea.svelte +4 -0
  26. package/dist/components/form/FormTextarea.svelte.d.ts +2 -0
  27. package/dist/components/form/FormToggle.svelte +4 -0
  28. package/dist/components/form/FormToggle.svelte.d.ts +2 -0
  29. package/dist/components/form/types.d.ts +5 -0
  30. package/dist/components/form/types.js +1 -1
  31. package/dist/components/layouts/MainLayout.svelte +5 -2
  32. package/dist/components/nodes/GatewayNode.svelte +99 -86
  33. package/dist/components/nodes/IdeaNode.svelte +20 -35
  34. package/dist/components/nodes/NotesNode.svelte +6 -2
  35. package/dist/components/nodes/SimpleNode.svelte +32 -31
  36. package/dist/components/nodes/SquareNode.svelte +35 -45
  37. package/dist/components/nodes/TerminalNode.svelte +25 -61
  38. package/dist/components/nodes/ToolNode.svelte +36 -18
  39. package/dist/components/nodes/WorkflowNode.svelte +97 -73
  40. package/dist/components/playground/Playground.svelte +43 -38
  41. package/dist/editor/index.d.ts +3 -1
  42. package/dist/editor/index.js +5 -1
  43. package/dist/helpers/nodeLayoutHelper.d.ts +14 -0
  44. package/dist/helpers/nodeLayoutHelper.js +19 -0
  45. package/dist/helpers/workflowEditorHelper.js +1 -2
  46. package/dist/services/autoSaveService.js +5 -5
  47. package/dist/services/historyService.d.ts +207 -0
  48. package/dist/services/historyService.js +317 -0
  49. package/dist/services/settingsService.d.ts +2 -2
  50. package/dist/services/settingsService.js +15 -21
  51. package/dist/services/toastService.d.ts +1 -1
  52. package/dist/services/toastService.js +10 -10
  53. package/dist/stores/historyStore.d.ts +133 -0
  54. package/dist/stores/historyStore.js +188 -0
  55. package/dist/stores/settingsStore.d.ts +1 -1
  56. package/dist/stores/settingsStore.js +40 -42
  57. package/dist/stores/themeStore.d.ts +2 -2
  58. package/dist/stores/themeStore.js +30 -32
  59. package/dist/stores/workflowStore.d.ts +52 -2
  60. package/dist/stores/workflowStore.js +102 -2
  61. package/dist/styles/base.css +67 -7
  62. package/dist/styles/toast.css +3 -1
  63. package/dist/styles/tokens.css +38 -2
  64. package/dist/types/settings.d.ts +3 -3
  65. package/dist/types/settings.js +13 -19
  66. package/dist/utils/colors.js +18 -18
  67. package/package.json +1 -1
@@ -20,11 +20,11 @@
20
20
  *
21
21
  * @module stores/themeStore
22
22
  */
23
- import { writable, derived, get } from "svelte/store";
23
+ import { writable, derived, get } from 'svelte/store';
24
24
  /** localStorage key for persisting theme preference */
25
- const STORAGE_KEY = "flowdrop-theme";
25
+ const STORAGE_KEY = 'flowdrop-theme';
26
26
  /** Default theme preference when none is stored */
27
- const DEFAULT_THEME = "auto";
27
+ const DEFAULT_THEME = 'auto';
28
28
  // =========================================================================
29
29
  // System Preference Detection
30
30
  // =========================================================================
@@ -34,30 +34,28 @@ const DEFAULT_THEME = "auto";
34
34
  * @returns 'dark' if system prefers dark mode, 'light' otherwise
35
35
  */
36
36
  function getSystemTheme() {
37
- if (typeof window === "undefined") {
38
- return "light";
37
+ if (typeof window === 'undefined') {
38
+ return 'light';
39
39
  }
40
- return window.matchMedia("(prefers-color-scheme: dark)").matches
41
- ? "dark"
42
- : "light";
40
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
43
41
  }
44
42
  /**
45
43
  * Store for system theme preference
46
44
  * Updates when system preference changes
47
45
  */
48
- const systemTheme = writable(typeof window !== "undefined" ? getSystemTheme() : "light");
46
+ const systemTheme = writable(typeof window !== 'undefined' ? getSystemTheme() : 'light');
49
47
  // Listen for system theme changes
50
- if (typeof window !== "undefined") {
51
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
48
+ if (typeof window !== 'undefined') {
49
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
52
50
  /**
53
51
  * Handler for system theme preference changes
54
52
  */
55
53
  const handleSystemThemeChange = (event) => {
56
- systemTheme.set(event.matches ? "dark" : "light");
54
+ systemTheme.set(event.matches ? 'dark' : 'light');
57
55
  };
58
56
  // Modern browsers use addEventListener
59
57
  if (mediaQuery.addEventListener) {
60
- mediaQuery.addEventListener("change", handleSystemThemeChange);
58
+ mediaQuery.addEventListener('change', handleSystemThemeChange);
61
59
  }
62
60
  else {
63
61
  // Fallback for older browsers
@@ -73,18 +71,18 @@ if (typeof window !== "undefined") {
73
71
  * @returns Saved theme preference or default
74
72
  */
75
73
  function loadSavedTheme() {
76
- if (typeof window === "undefined") {
74
+ if (typeof window === 'undefined') {
77
75
  return DEFAULT_THEME;
78
76
  }
79
77
  try {
80
78
  const saved = localStorage.getItem(STORAGE_KEY);
81
- if (saved === "light" || saved === "dark" || saved === "auto") {
79
+ if (saved === 'light' || saved === 'dark' || saved === 'auto') {
82
80
  return saved;
83
81
  }
84
82
  }
85
83
  catch {
86
84
  // localStorage may be unavailable (e.g., private browsing)
87
- console.warn("Failed to load theme from localStorage");
85
+ console.warn('Failed to load theme from localStorage');
88
86
  }
89
87
  return DEFAULT_THEME;
90
88
  }
@@ -94,7 +92,7 @@ function loadSavedTheme() {
94
92
  * @param theme - Theme preference to save
95
93
  */
96
94
  function saveTheme(theme) {
97
- if (typeof window === "undefined") {
95
+ if (typeof window === 'undefined') {
98
96
  return;
99
97
  }
100
98
  try {
@@ -102,7 +100,7 @@ function saveTheme(theme) {
102
100
  }
103
101
  catch {
104
102
  // localStorage may be unavailable
105
- console.warn("Failed to save theme to localStorage");
103
+ console.warn('Failed to save theme to localStorage');
106
104
  }
107
105
  }
108
106
  /**
@@ -119,7 +117,7 @@ export const theme = writable(loadSavedTheme());
119
117
  * When theme is 'auto', this reflects the system preference
120
118
  */
121
119
  export const resolvedTheme = derived([theme, systemTheme], ([$theme, $systemTheme]) => {
122
- if ($theme === "auto") {
120
+ if ($theme === 'auto') {
123
121
  return $systemTheme;
124
122
  }
125
123
  return $theme;
@@ -134,10 +132,10 @@ export const resolvedTheme = derived([theme, systemTheme], ([$theme, $systemThem
134
132
  * @param resolved - The resolved theme to apply
135
133
  */
136
134
  function applyTheme(resolved) {
137
- if (typeof document === "undefined") {
135
+ if (typeof document === 'undefined') {
138
136
  return;
139
137
  }
140
- document.documentElement.setAttribute("data-theme", resolved);
138
+ document.documentElement.setAttribute('data-theme', resolved);
141
139
  }
142
140
  /**
143
141
  * Set the theme preference
@@ -155,13 +153,13 @@ export function setTheme(newTheme) {
155
153
  export function toggleTheme() {
156
154
  const currentTheme = get(theme);
157
155
  const currentResolved = get(resolvedTheme);
158
- if (currentTheme === "auto") {
156
+ if (currentTheme === 'auto') {
159
157
  // Switch to opposite of system preference
160
- setTheme(currentResolved === "dark" ? "light" : "dark");
158
+ setTheme(currentResolved === 'dark' ? 'light' : 'dark');
161
159
  }
162
160
  else {
163
161
  // Toggle between light and dark
164
- setTheme(currentTheme === "dark" ? "light" : "dark");
162
+ setTheme(currentTheme === 'dark' ? 'light' : 'dark');
165
163
  }
166
164
  }
167
165
  /**
@@ -170,14 +168,14 @@ export function toggleTheme() {
170
168
  export function cycleTheme() {
171
169
  const currentTheme = get(theme);
172
170
  switch (currentTheme) {
173
- case "light":
174
- setTheme("dark");
171
+ case 'light':
172
+ setTheme('dark');
175
173
  break;
176
- case "dark":
177
- setTheme("auto");
174
+ case 'dark':
175
+ setTheme('auto');
178
176
  break;
179
- case "auto":
180
- setTheme("light");
177
+ case 'auto':
178
+ setTheme('light');
181
179
  break;
182
180
  }
183
181
  }
@@ -208,8 +206,8 @@ export function initializeTheme() {
208
206
  * @returns true if running in browser and theme is applied
209
207
  */
210
208
  export function isThemeInitialized() {
211
- if (typeof document === "undefined") {
209
+ if (typeof document === 'undefined') {
212
210
  return false;
213
211
  }
214
- return document.documentElement.hasAttribute("data-theme");
212
+ return document.documentElement.hasAttribute('data-theme');
215
213
  }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Workflow Store for FlowDrop
3
3
  *
4
- * Provides global state management for workflows with dirty state tracking.
4
+ * Provides global state management for workflows with dirty state tracking
5
+ * and undo/redo history integration.
5
6
  *
6
7
  * @module stores/workflowStore
7
8
  */
@@ -40,6 +41,28 @@ export declare function markAsSaved(): void;
40
41
  * @returns true if there are unsaved changes
41
42
  */
42
43
  export declare function isDirty(): boolean;
44
+ /**
45
+ * Enable or disable history recording
46
+ *
47
+ * Useful for bulk operations where you don't want individual history entries.
48
+ *
49
+ * @param enabled - Whether history should be recorded
50
+ */
51
+ export declare function setHistoryEnabled(enabled: boolean): void;
52
+ /**
53
+ * Check if history recording is enabled
54
+ *
55
+ * @returns true if history is being recorded
56
+ */
57
+ export declare function isHistoryEnabled(): boolean;
58
+ /**
59
+ * Set the restoring from history flag
60
+ *
61
+ * Used internally by the history store when performing undo/redo.
62
+ *
63
+ * @param restoring - Whether we're currently restoring from history
64
+ */
65
+ export declare function setRestoringFromHistory(restoring: boolean): void;
43
66
  /**
44
67
  * Get the current workflow
45
68
  *
@@ -74,13 +97,22 @@ export declare const workflowActions: {
74
97
  /**
75
98
  * Initialize workflow (from load or new)
76
99
  *
77
- * This sets the initial saved snapshot and clears dirty state.
100
+ * This sets the initial saved snapshot, clears dirty state, and initializes history.
78
101
  */
79
102
  initialize: (workflow: Workflow) => void;
80
103
  /**
81
104
  * Update the entire workflow
105
+ *
106
+ * Note: This is typically called from SvelteFlow sync and should not push to history
107
+ * for every small change. History is pushed by specific actions or drag handlers.
82
108
  */
83
109
  updateWorkflow: (workflow: Workflow) => void;
110
+ /**
111
+ * Restore workflow from history (undo/redo)
112
+ *
113
+ * This bypasses history recording to prevent recursive loops.
114
+ */
115
+ restoreFromHistory: (workflow: Workflow) => void;
84
116
  /**
85
117
  * Update nodes
86
118
  */
@@ -99,6 +131,9 @@ export declare const workflowActions: {
99
131
  addNode: (node: WorkflowNode) => void;
100
132
  /**
101
133
  * Remove a node
134
+ *
135
+ * This is an atomic operation that also removes connected edges.
136
+ * A single undo will restore both the node and its edges.
102
137
  */
103
138
  removeNode: (nodeId: string) => void;
104
139
  /**
@@ -111,10 +146,14 @@ export declare const workflowActions: {
111
146
  removeEdge: (edgeId: string) => void;
112
147
  /**
113
148
  * Update a specific node
149
+ *
150
+ * Used for config changes. Pushes to history for undo support.
114
151
  */
115
152
  updateNode: (nodeId: string, updates: Partial<WorkflowNode>) => void;
116
153
  /**
117
154
  * Clear the workflow
155
+ *
156
+ * Resets the workflow and clears history.
118
157
  */
119
158
  clear: () => void;
120
159
  /**
@@ -125,6 +164,7 @@ export declare const workflowActions: {
125
164
  * Batch update nodes and edges
126
165
  *
127
166
  * Useful for complex operations that update multiple things at once.
167
+ * Creates a single history entry for the entire batch.
128
168
  */
129
169
  batchUpdate: (updates: {
130
170
  nodes?: WorkflowNode[];
@@ -133,6 +173,16 @@ export declare const workflowActions: {
133
173
  description?: string;
134
174
  metadata?: Partial<Workflow["metadata"]>;
135
175
  }) => void;
176
+ /**
177
+ * Push current state to history manually
178
+ *
179
+ * Use this before operations that modify the workflow through other means
180
+ * (e.g., drag operations handled by SvelteFlow directly).
181
+ *
182
+ * @param description - Description of the upcoming change
183
+ * @param workflow - Optional workflow to push (uses store state if not provided)
184
+ */
185
+ pushHistory: (description?: string, workflow?: Workflow) => void;
136
186
  };
137
187
  /** Derived store for workflow changes (useful for triggering saves) */
138
188
  export declare const workflowChanged: import("svelte/store").Readable<{
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Workflow Store for FlowDrop
3
3
  *
4
- * Provides global state management for workflows with dirty state tracking.
4
+ * Provides global state management for workflows with dirty state tracking
5
+ * and undo/redo history integration.
5
6
  *
6
7
  * @module stores/workflowStore
7
8
  */
8
9
  import { writable, derived, get } from 'svelte/store';
10
+ import { historyService } from '../services/historyService.js';
9
11
  // =========================================================================
10
12
  // Core Workflow Store
11
13
  // =========================================================================
@@ -39,6 +41,18 @@ let onDirtyStateChangeCallback = null;
39
41
  * Set by the App component to notify parent application.
40
42
  */
41
43
  let onWorkflowChangeCallback = null;
44
+ /**
45
+ * Flag to track if we're currently restoring from history (undo/redo)
46
+ *
47
+ * When true, prevents pushing to history to avoid recursive loops.
48
+ */
49
+ let isRestoringFromHistory = false;
50
+ /**
51
+ * Flag to track if history recording is enabled
52
+ *
53
+ * Can be disabled for bulk operations or when history is not needed.
54
+ */
55
+ let historyEnabled = true;
42
56
  /**
43
57
  * Set the dirty state change callback
44
58
  *
@@ -136,6 +150,49 @@ export function markAsSaved() {
136
150
  export function isDirty() {
137
151
  return get(isDirtyStore);
138
152
  }
153
+ /**
154
+ * Enable or disable history recording
155
+ *
156
+ * Useful for bulk operations where you don't want individual history entries.
157
+ *
158
+ * @param enabled - Whether history should be recorded
159
+ */
160
+ export function setHistoryEnabled(enabled) {
161
+ historyEnabled = enabled;
162
+ }
163
+ /**
164
+ * Check if history recording is enabled
165
+ *
166
+ * @returns true if history is being recorded
167
+ */
168
+ export function isHistoryEnabled() {
169
+ return historyEnabled;
170
+ }
171
+ /**
172
+ * Set the restoring from history flag
173
+ *
174
+ * Used internally by the history store when performing undo/redo.
175
+ *
176
+ * @param restoring - Whether we're currently restoring from history
177
+ */
178
+ export function setRestoringFromHistory(restoring) {
179
+ isRestoringFromHistory = restoring;
180
+ }
181
+ /**
182
+ * Push current state to history before making changes
183
+ *
184
+ * @param description - Description of the change about to be made
185
+ * @param workflow - Optional workflow to push (uses store if not provided)
186
+ */
187
+ function pushToHistory(description, workflow) {
188
+ if (!historyEnabled || isRestoringFromHistory) {
189
+ return;
190
+ }
191
+ const workflowToPush = workflow ?? get(workflowStore);
192
+ if (workflowToPush) {
193
+ historyService.push(workflowToPush, { description });
194
+ }
195
+ }
139
196
  /**
140
197
  * Get the current workflow
141
198
  *
@@ -218,7 +275,7 @@ export const workflowActions = {
218
275
  /**
219
276
  * Initialize workflow (from load or new)
220
277
  *
221
- * This sets the initial saved snapshot and clears dirty state.
278
+ * This sets the initial saved snapshot, clears dirty state, and initializes history.
222
279
  */
223
280
  initialize: (workflow) => {
224
281
  workflowStore.set(workflow);
@@ -228,14 +285,30 @@ export const workflowActions = {
228
285
  if (onDirtyStateChangeCallback) {
229
286
  onDirtyStateChangeCallback(false);
230
287
  }
288
+ // Initialize history with the loaded workflow
289
+ historyService.initialize(workflow);
231
290
  },
232
291
  /**
233
292
  * Update the entire workflow
293
+ *
294
+ * Note: This is typically called from SvelteFlow sync and should not push to history
295
+ * for every small change. History is pushed by specific actions or drag handlers.
234
296
  */
235
297
  updateWorkflow: (workflow) => {
236
298
  workflowStore.set(workflow);
237
299
  notifyWorkflowChange('metadata');
238
300
  },
301
+ /**
302
+ * Restore workflow from history (undo/redo)
303
+ *
304
+ * This bypasses history recording to prevent recursive loops.
305
+ */
306
+ restoreFromHistory: (workflow) => {
307
+ isRestoringFromHistory = true;
308
+ workflowStore.set(workflow);
309
+ notifyWorkflowChange('metadata');
310
+ isRestoringFromHistory = false;
311
+ },
239
312
  /**
240
313
  * Update nodes
241
314
  */
@@ -310,6 +383,7 @@ export const workflowActions = {
310
383
  * Add a node
311
384
  */
312
385
  addNode: (node) => {
386
+ pushToHistory('Add node');
313
387
  workflowStore.update(($workflow) => {
314
388
  if (!$workflow)
315
389
  return null;
@@ -326,8 +400,12 @@ export const workflowActions = {
326
400
  },
327
401
  /**
328
402
  * Remove a node
403
+ *
404
+ * This is an atomic operation that also removes connected edges.
405
+ * A single undo will restore both the node and its edges.
329
406
  */
330
407
  removeNode: (nodeId) => {
408
+ pushToHistory('Delete node');
331
409
  workflowStore.update(($workflow) => {
332
410
  if (!$workflow)
333
411
  return null;
@@ -347,6 +425,7 @@ export const workflowActions = {
347
425
  * Add an edge
348
426
  */
349
427
  addEdge: (edge) => {
428
+ pushToHistory('Add connection');
350
429
  workflowStore.update(($workflow) => {
351
430
  if (!$workflow)
352
431
  return null;
@@ -365,6 +444,7 @@ export const workflowActions = {
365
444
  * Remove an edge
366
445
  */
367
446
  removeEdge: (edgeId) => {
447
+ pushToHistory('Delete connection');
368
448
  workflowStore.update(($workflow) => {
369
449
  if (!$workflow)
370
450
  return null;
@@ -381,8 +461,11 @@ export const workflowActions = {
381
461
  },
382
462
  /**
383
463
  * Update a specific node
464
+ *
465
+ * Used for config changes. Pushes to history for undo support.
384
466
  */
385
467
  updateNode: (nodeId, updates) => {
468
+ pushToHistory('Update node config');
386
469
  workflowStore.update(($workflow) => {
387
470
  if (!$workflow)
388
471
  return null;
@@ -399,11 +482,14 @@ export const workflowActions = {
399
482
  },
400
483
  /**
401
484
  * Clear the workflow
485
+ *
486
+ * Resets the workflow and clears history.
402
487
  */
403
488
  clear: () => {
404
489
  workflowStore.set(null);
405
490
  savedSnapshot = null;
406
491
  isDirtyStore.set(false);
492
+ historyService.clear();
407
493
  if (onDirtyStateChangeCallback) {
408
494
  onDirtyStateChangeCallback(false);
409
495
  }
@@ -430,8 +516,10 @@ export const workflowActions = {
430
516
  * Batch update nodes and edges
431
517
  *
432
518
  * Useful for complex operations that update multiple things at once.
519
+ * Creates a single history entry for the entire batch.
433
520
  */
434
521
  batchUpdate: (updates) => {
522
+ pushToHistory('Batch update');
435
523
  workflowStore.update(($workflow) => {
436
524
  if (!$workflow)
437
525
  return null;
@@ -449,6 +537,18 @@ export const workflowActions = {
449
537
  };
450
538
  });
451
539
  notifyWorkflowChange('metadata');
540
+ },
541
+ /**
542
+ * Push current state to history manually
543
+ *
544
+ * Use this before operations that modify the workflow through other means
545
+ * (e.g., drag operations handled by SvelteFlow directly).
546
+ *
547
+ * @param description - Description of the upcoming change
548
+ * @param workflow - Optional workflow to push (uses store state if not provided)
549
+ */
550
+ pushHistory: (description, workflow) => {
551
+ pushToHistory(description, workflow);
452
552
  }
453
553
  };
454
554
  // =========================================================================
@@ -30,6 +30,46 @@
30
30
  box-shadow: var(--fd-shadow-md);
31
31
  }
32
32
 
33
+ /* Flow node handles: 20px connection area, 12px visible circle (::before)
34
+ Override xyflow's default background so port color (--fd-handle-fill from inline style) shows */
35
+ :global(.svelte-flow__handle) {
36
+ --fd-handle-fill: var(--fd-muted-foreground);
37
+ --fd-handle-border-color: var(--fd-handle-border);
38
+ width: var(--fd-handle-size);
39
+ height: var(--fd-handle-size);
40
+ background: transparent !important;
41
+ background-color: transparent !important;
42
+ border: none;
43
+ border-radius: 50%;
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ transition: transform var(--fd-transition-normal);
48
+ cursor: pointer;
49
+ }
50
+
51
+ :global(.svelte-flow__handle::before) {
52
+ content: '';
53
+ position: absolute;
54
+ inset: 0;
55
+ margin: auto;
56
+ width: var(--fd-handle-visual-size);
57
+ height: var(--fd-handle-visual-size);
58
+ background-color: var(--fd-handle-fill) !important;
59
+ border: 2px solid var(--fd-handle-border-color);
60
+ border-radius: 50%;
61
+ pointer-events: none;
62
+ }
63
+
64
+ :global(.svelte-flow__handle:hover::before) {
65
+ background-color: var(--fd-primary);
66
+ }
67
+
68
+ :global(.svelte-flow__handle:focus) {
69
+ outline: 2px solid var(--fd-ring);
70
+ outline-offset: 2px;
71
+ }
72
+
33
73
  /* Button styles */
34
74
  .flowdrop-btn {
35
75
  display: inline-flex;
@@ -874,7 +914,11 @@
874
914
  ========================================================================= */
875
915
 
876
916
  /* Interrupt state: Pending (awaiting user response) */
877
- --fd-interrupt-pending-bg: linear-gradient(135deg, var(--fd-warning-muted) 0%, var(--fd-warning-muted) 100%);
917
+ --fd-interrupt-pending-bg: linear-gradient(
918
+ 135deg,
919
+ var(--fd-warning-muted) 0%,
920
+ var(--fd-warning-muted) 100%
921
+ );
878
922
  --fd-interrupt-pending-border: var(--fd-warning);
879
923
  --fd-interrupt-pending-shadow: rgba(245, 158, 11, 0.15);
880
924
  --fd-interrupt-pending-avatar: var(--fd-warning);
@@ -882,7 +926,11 @@
882
926
  --fd-interrupt-pending-text-light: var(--fd-warning-hover);
883
927
 
884
928
  /* Interrupt state: Completed (response received - neutral) */
885
- --fd-interrupt-completed-bg: linear-gradient(135deg, var(--fd-info-muted) 0%, var(--fd-primary-muted) 100%);
929
+ --fd-interrupt-completed-bg: linear-gradient(
930
+ 135deg,
931
+ var(--fd-info-muted) 0%,
932
+ var(--fd-primary-muted) 100%
933
+ );
886
934
  --fd-interrupt-completed-border: var(--fd-primary);
887
935
  --fd-interrupt-completed-shadow: rgba(59, 130, 246, 0.15);
888
936
  --fd-interrupt-completed-avatar: var(--fd-primary);
@@ -898,7 +946,11 @@
898
946
  --fd-interrupt-cancelled-text-light: var(--fd-muted-foreground);
899
947
 
900
948
  /* Interrupt state: Error */
901
- --fd-interrupt-error-bg: linear-gradient(135deg, var(--fd-error-muted) 0%, var(--fd-error-muted) 100%);
949
+ --fd-interrupt-error-bg: linear-gradient(
950
+ 135deg,
951
+ var(--fd-error-muted) 0%,
952
+ var(--fd-error-muted) 100%
953
+ );
902
954
  --fd-interrupt-error-border: var(--fd-error);
903
955
  --fd-interrupt-error-shadow: rgba(239, 68, 68, 0.15);
904
956
  --fd-interrupt-error-avatar: var(--fd-error);
@@ -913,8 +965,16 @@
913
965
  --fd-interrupt-prompt-border-error: rgba(239, 68, 68, 0.2);
914
966
 
915
967
  /* Interrupt button tokens */
916
- --fd-interrupt-btn-primary-bg: linear-gradient(135deg, var(--fd-primary) 0%, var(--fd-primary-hover) 100%);
917
- --fd-interrupt-btn-primary-bg-hover: linear-gradient(135deg, var(--fd-primary-hover) 0%, var(--fd-primary-hover) 100%);
968
+ --fd-interrupt-btn-primary-bg: linear-gradient(
969
+ 135deg,
970
+ var(--fd-primary) 0%,
971
+ var(--fd-primary-hover) 100%
972
+ );
973
+ --fd-interrupt-btn-primary-bg-hover: linear-gradient(
974
+ 135deg,
975
+ var(--fd-primary-hover) 0%,
976
+ var(--fd-primary-hover) 100%
977
+ );
918
978
  --fd-interrupt-btn-primary-shadow: rgba(59, 130, 246, 0.3);
919
979
  --fd-interrupt-btn-secondary-bg: var(--fd-muted);
920
980
  --fd-interrupt-btn-secondary-border: var(--fd-border);
@@ -947,7 +1007,7 @@
947
1007
  }
948
1008
 
949
1009
  /* Dark mode overrides for interrupt component tokens */
950
- [data-theme="dark"] {
1010
+ [data-theme='dark'] {
951
1011
  /* Interrupt prompt inner styling - dark background for dark mode */
952
1012
  --fd-interrupt-prompt-bg: rgba(30, 30, 35, 0.95);
953
1013
  --fd-interrupt-prompt-border-pending: rgba(251, 191, 36, 0.25);
@@ -1176,4 +1236,4 @@
1176
1236
 
1177
1237
  .markdown-display--large h3 {
1178
1238
  font-size: 1.25rem;
1179
- }
1239
+ }
@@ -28,6 +28,8 @@
28
28
  max-width: 350px;
29
29
  font-size: var(--fd-text-sm);
30
30
  line-height: 1.3;
31
- transition: background var(--fd-transition-fast), border-color var(--fd-transition-fast),
31
+ transition:
32
+ background var(--fd-transition-fast),
33
+ border-color var(--fd-transition-fast),
32
34
  box-shadow var(--fd-transition-fast);
33
35
  }
@@ -218,6 +218,9 @@
218
218
 
219
219
  /** Icon on node squircle: dark in light theme, white in dark theme */
220
220
  --fd-node-icon: var(--_gray-9);
221
+ /** Opacity of the icon squircle background (color-mix percentage). Light mode: 15% / 22% hover; dark mode uses higher values. */
222
+ --fd-node-icon-bg-opacity: 20%;
223
+ --fd-node-icon-bg-opacity-hover: 50%;
221
224
 
222
225
  /* ----- EDGE TOKENS ----- */
223
226
  --fd-edge-trigger: var(--_gray-9);
@@ -254,6 +257,22 @@
254
257
  /* ----- HANDLE (Node connection points) ----- */
255
258
  /* White ring around handles for contrast against node backgrounds */
256
259
  --fd-handle-border: #ffffff;
260
+ /* Outer size = connection/hit area (px); visual = visible circle (px) */
261
+ --fd-handle-size: 20px;
262
+ --fd-handle-visual-size: 12px;
263
+
264
+ /* ----- NODE LAYOUT (Dimensions and port alignment; multiples of 10 for grid) ----- */
265
+ --fd-node-grid-step: 10;
266
+ --fd-node-default-width: 290px;
267
+ --fd-node-header-height: 60px;
268
+ --fd-node-header-title-height: 40px;
269
+ --fd-node-header-desc-line: 20px;
270
+ --fd-node-header-gap: 10px;
271
+ --fd-node-port-row-height: 20px;
272
+ --fd-node-terminal-size: 80px;
273
+ --fd-node-square-size: 80px;
274
+ --fd-node-simple-height: 80px;
275
+ --fd-node-tool-min-height: 80px;
257
276
 
258
277
  /* ----- LAYOUT BACKGROUND (Main content area gradient) ----- */
259
278
  --fd-layout-background: linear-gradient(135deg, #f9fafb 0%, #e0e7ff 50%, #c7d2fe 100%);
@@ -265,7 +284,7 @@
265
284
  Designed for softer contrast and reduced eye strain
266
285
  ========================================================================= */
267
286
 
268
- [data-theme="dark"] {
287
+ [data-theme='dark'] {
269
288
  /* ----- SURFACES (Backgrounds) - Softer contrast ----- */
270
289
  --fd-background: #1a1a1e;
271
290
  --fd-foreground: var(--_gray-2);
@@ -296,8 +315,25 @@
296
315
  --fd-node-border: #4a4a52;
297
316
  --fd-node-border-hover: #5a5a62;
298
317
 
318
+ /* ----- NODE COLORS (port type labels/badges - lighter for readability on dark surfaces) ----- */
319
+ --fd-node-emerald: #34d399;
320
+ --fd-node-blue: #60a5fa;
321
+ --fd-node-amber: #fbbf24;
322
+ --fd-node-orange: #fb923c;
323
+ --fd-node-red: #f87171;
324
+ --fd-node-pink: #f472b6;
325
+ --fd-node-indigo: #818cf8;
326
+ --fd-node-teal: #2dd4bf;
327
+ --fd-node-cyan: #22d3ee;
328
+ --fd-node-lime: #a3e635;
329
+ --fd-node-slate: #94a3b8;
330
+ --fd-node-purple: #c084fc;
331
+
299
332
  /** Icon on node squircle: white in dark theme */
300
333
  --fd-node-icon: #ffffff;
334
+ /** Icon squircle background opacity: higher in dark mode for visibility on dark surfaces */
335
+ --fd-node-icon-bg-opacity: 50%;
336
+ --fd-node-icon-bg-opacity-hover: 80%;
301
337
 
302
338
  /* ----- PRIMARY (Interactive/Brand) ----- */
303
339
  --fd-primary: #60a5fa;
@@ -363,4 +399,4 @@
363
399
 
364
400
  /* ----- LAYOUT BACKGROUND (darker gradient for dark mode) ----- */
365
401
  --fd-layout-background: linear-gradient(135deg, #141418 0%, #1a1a2e 50%, #16162a 100%);
366
- }
402
+ }