@flowdrop/flowdrop 1.2.2 → 1.4.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.
@@ -38,13 +38,7 @@
38
38
  "$ref": "#/$defs/WorkflowMetadata"
39
39
  }
40
40
  },
41
- "required": [
42
- "id",
43
- "name",
44
- "nodes",
45
- "edges",
46
- "metadata"
47
- ],
41
+ "required": ["id", "name", "nodes", "edges", "metadata"],
48
42
  "$defs": {
49
43
  "AutocompleteConfig": {
50
44
  "type": "object",
@@ -95,9 +89,7 @@
95
89
  "description": "Whether to allow multiple selections.\nWhen true, users can select multiple values displayed as tags.\n"
96
90
  }
97
91
  },
98
- "required": [
99
- "url"
100
- ]
92
+ "required": ["url"]
101
93
  },
102
94
  "Branch": {
103
95
  "type": "object",
@@ -125,10 +117,7 @@
125
117
  "description": "Whether this is the default/fallback branch"
126
118
  }
127
119
  },
128
- "required": [
129
- "name",
130
- "label"
131
- ]
120
+ "required": ["name", "label"]
132
121
  },
133
122
  "ConfigProperty": {
134
123
  "type": "object",
@@ -136,14 +125,7 @@
136
125
  "properties": {
137
126
  "type": {
138
127
  "type": "string",
139
- "enum": [
140
- "string",
141
- "number",
142
- "boolean",
143
- "array",
144
- "object",
145
- "integer"
146
- ],
128
+ "enum": ["string", "number", "boolean", "array", "object", "integer"],
147
129
  "description": "JSON Schema type"
148
130
  },
149
131
  "title": {
@@ -269,18 +251,14 @@
269
251
  ]
270
252
  }
271
253
  },
272
- "required": [
273
- "type"
274
- ]
254
+ "required": ["type"]
275
255
  },
276
256
  "ConfigSchema": {
277
257
  "type": "object",
278
258
  "properties": {
279
259
  "type": {
280
260
  "type": "string",
281
- "enum": [
282
- "object"
283
- ]
261
+ "enum": ["object"]
284
262
  },
285
263
  "properties": {
286
264
  "type": "object",
@@ -301,10 +279,7 @@
301
279
  "default": false
302
280
  }
303
281
  },
304
- "required": [
305
- "type",
306
- "properties"
307
- ]
282
+ "required": ["type", "properties"]
308
283
  },
309
284
  "DynamicPort": {
310
285
  "type": "object",
@@ -338,11 +313,7 @@
338
313
  "description": "Whether this port is required for execution"
339
314
  }
340
315
  },
341
- "required": [
342
- "name",
343
- "label",
344
- "dataType"
345
- ]
316
+ "required": ["name", "label", "dataType"]
346
317
  },
347
318
  "NodeCategory": {
348
319
  "type": "string",
@@ -433,11 +404,7 @@
433
404
  "description": "Whether the node is currently being executed"
434
405
  }
435
406
  },
436
- "required": [
437
- "status",
438
- "executionCount",
439
- "isExecuting"
440
- ]
407
+ "required": ["status", "executionCount", "isExecuting"]
441
408
  },
442
409
  "NodeExecutionStatus": {
443
410
  "type": "string",
@@ -581,11 +548,7 @@
581
548
  },
582
549
  "type": {
583
550
  "type": "string",
584
- "enum": [
585
- "input",
586
- "output",
587
- "metadata"
588
- ],
551
+ "enum": ["input", "output", "metadata"],
589
552
  "description": "Port direction (input receives data, output sends data)"
590
553
  },
591
554
  "dataType": {
@@ -614,12 +577,7 @@
614
577
  ]
615
578
  }
616
579
  },
617
- "required": [
618
- "id",
619
- "name",
620
- "type",
621
- "dataType"
622
- ]
580
+ "required": ["id", "name", "type", "dataType"]
623
581
  },
624
582
  "NodeType": {
625
583
  "type": "string",
@@ -677,9 +635,7 @@
677
635
  "description": "Optional description for this option"
678
636
  }
679
637
  },
680
- "required": [
681
- "const"
682
- ]
638
+ "required": ["const"]
683
639
  },
684
640
  "PortDataSchema": {
685
641
  "type": "object",
@@ -687,14 +643,7 @@
687
643
  "properties": {
688
644
  "type": {
689
645
  "type": "string",
690
- "enum": [
691
- "object",
692
- "array",
693
- "string",
694
- "number",
695
- "integer",
696
- "boolean"
697
- ],
646
+ "enum": ["object", "array", "string", "number", "integer", "boolean"],
698
647
  "description": "The JSON Schema type. Use `object` for nested properties,\n`array` for lists with `items` schema.\n"
699
648
  },
700
649
  "title": {
@@ -743,10 +692,7 @@
743
692
  "description": "Y coordinate"
744
693
  }
745
694
  },
746
- "required": [
747
- "x",
748
- "y"
749
- ]
695
+ "required": ["x", "y"]
750
696
  },
751
697
  "TemplateVariable": {
752
698
  "type": "object",
@@ -804,10 +750,7 @@
804
750
  "description": "Source node ID"
805
751
  }
806
752
  },
807
- "required": [
808
- "name",
809
- "type"
810
- ]
753
+ "required": ["name", "type"]
811
754
  },
812
755
  "TemplateVariablesConfig": {
813
756
  "type": "object",
@@ -854,9 +797,7 @@
854
797
  "description": "Map of available variables keyed by variable name"
855
798
  }
856
799
  },
857
- "required": [
858
- "variables"
859
- ]
800
+ "required": ["variables"]
860
801
  },
861
802
  "WorkflowEdge": {
862
803
  "type": "object",
@@ -887,12 +828,7 @@
887
828
  "type": {
888
829
  "type": "string",
889
830
  "description": "Connection line type",
890
- "enum": [
891
- "default",
892
- "straight",
893
- "step",
894
- "smoothstep"
895
- ]
831
+ "enum": ["default", "straight", "step", "smoothstep"]
896
832
  },
897
833
  "selectable": {
898
834
  "type": "boolean",
@@ -919,11 +855,7 @@
919
855
  "properties": {
920
856
  "edgeType": {
921
857
  "type": "string",
922
- "enum": [
923
- "trigger",
924
- "tool",
925
- "data"
926
- ],
858
+ "enum": ["trigger", "tool", "data"],
927
859
  "description": "Edge type for visual styling"
928
860
  },
929
861
  "sourcePortDataType": {
@@ -947,11 +879,7 @@
947
879
  }
948
880
  }
949
881
  },
950
- "required": [
951
- "id",
952
- "source",
953
- "target"
954
- ]
882
+ "required": ["id", "source", "target"]
955
883
  },
956
884
  "WorkflowMetadata": {
957
885
  "type": "object",
@@ -995,11 +923,7 @@
995
923
  "default": "flowdrop"
996
924
  }
997
925
  },
998
- "required": [
999
- "version",
1000
- "createdAt",
1001
- "updatedAt"
1002
- ]
926
+ "required": ["version", "createdAt", "updatedAt"]
1003
927
  },
1004
928
  "WorkflowNode": {
1005
929
  "type": "object",
@@ -1060,19 +984,10 @@
1060
984
  ]
1061
985
  }
1062
986
  },
1063
- "required": [
1064
- "label",
1065
- "config",
1066
- "metadata"
1067
- ]
987
+ "required": ["label", "config", "metadata"]
1068
988
  }
1069
989
  },
1070
- "required": [
1071
- "id",
1072
- "type",
1073
- "position",
1074
- "data"
1075
- ]
990
+ "required": ["id", "type", "position", "data"]
1076
991
  }
1077
992
  }
1078
993
  }
@@ -42,6 +42,14 @@ export const slateSkin = {
42
42
  "sidebar-category-color": "#c0c0d8",
43
43
  // Flat item text: dark for light bg readability
44
44
  "sidebar-flat-item-color": "#4a4a6a",
45
+ // Logo: monochrome purple-grey to match skin
46
+ "logo-bg": "#eeeef8",
47
+ "logo-stroke": "#5a5a7a",
48
+ "logo-line-fill": "#5a5a7a",
49
+ "logo-drop": "#009cde",
50
+ "logo-circle": "#f46351",
51
+ "logo-left": "#ccbaf4",
52
+ "logo-right": "#ffc423",
45
53
  },
46
54
  darkTokens: {
47
55
  // --- Dark mode color palette (deep navy / original slate) ---
@@ -74,5 +82,13 @@ export const slateSkin = {
74
82
  "sidebar-category-color": "#3a3a55",
75
83
  // Flat item text: muted, not full-brightness white
76
84
  "sidebar-flat-item-color": "#8888aa",
85
+ // Logo: monochrome purple-grey to match dark skin
86
+ "logo-bg": "none",
87
+ "logo-stroke": "#9898b8",
88
+ "logo-line-fill": "none",
89
+ "logo-drop": "none",
90
+ "logo-circle": "none",
91
+ "logo-left": "none",
92
+ "logo-right": "none",
77
93
  },
78
94
  };
@@ -46,7 +46,7 @@
46
46
  const SOURCE_ID = "source-node";
47
47
  const TARGET_ID = "target-node";
48
48
 
49
- let nodes = $state<Node[]>([
49
+ let nodes = $derived<Node[]>([
50
50
  {
51
51
  id: SOURCE_ID,
52
52
  type: "universalNode",
@@ -62,7 +62,7 @@
62
62
  ]);
63
63
 
64
64
  // Handle IDs follow the format: {nodeId}-{input|output}-{portId}
65
- let edges = $state<Edge[]>([
65
+ let edges = $derived<Edge[]>([
66
66
  {
67
67
  id: "edge-1",
68
68
  source: SOURCE_ID,
@@ -100,8 +100,8 @@
100
100
 
101
101
  <div class="edge-decorator-wrapper">
102
102
  <SvelteFlow
103
- bind:nodes
104
- bind:edges
103
+ {nodes}
104
+ {edges}
105
105
  {nodeTypes}
106
106
  {edgeTypes}
107
107
  fitView
@@ -30,6 +30,54 @@
30
30
  box-shadow: var(--fd-shadow-md);
31
31
  }
32
32
 
33
+ /* xyflow Controls & MiniMap: wire up to skin tokens so themes can restyle them */
34
+ .svelte-flow {
35
+ --xy-controls-button-background-color: var(
36
+ --fd-controls-button-bg,
37
+ var(--fd-card)
38
+ );
39
+ --xy-controls-button-background-color-hover: var(
40
+ --fd-controls-button-bg-hover,
41
+ var(--fd-muted)
42
+ );
43
+ --xy-controls-button-color: var(
44
+ --fd-controls-button-color,
45
+ var(--fd-foreground)
46
+ );
47
+ --xy-controls-button-color-hover: var(
48
+ --fd-controls-button-color-hover,
49
+ var(--fd-foreground)
50
+ );
51
+ --xy-controls-button-border-color: var(
52
+ --fd-controls-button-border,
53
+ var(--fd-border)
54
+ );
55
+ --xy-controls-box-shadow: var(
56
+ --fd-controls-box-shadow,
57
+ 0 0 2px 1px rgba(0, 0, 0, 0.08)
58
+ );
59
+
60
+ --xy-minimap-background-color: var(--fd-minimap-bg, var(--fd-card));
61
+ --xy-minimap-mask-background-color: var(
62
+ --fd-minimap-mask-bg,
63
+ var(--fd-backdrop)
64
+ );
65
+ --xy-minimap-mask-stroke-color: var(
66
+ --fd-minimap-mask-stroke,
67
+ var(--fd-border)
68
+ );
69
+ --xy-minimap-mask-stroke-width: var(--fd-minimap-mask-stroke-width, 1);
70
+ --xy-minimap-node-background-color: var(
71
+ --fd-minimap-node-bg,
72
+ var(--fd-muted)
73
+ );
74
+ --xy-minimap-node-stroke-color: var(
75
+ --fd-minimap-node-stroke,
76
+ var(--fd-border-muted)
77
+ );
78
+ --xy-minimap-node-stroke-width: var(--fd-minimap-node-stroke-width, 2);
79
+ }
80
+
33
81
  /* Flow node handles: 20px connection area, 12px visible circle (::before)
34
82
  Override xyflow's default background so port color (--fd-handle-fill from inline style) shows.
35
83
  Use `.svelte-flow` parent for higher specificity (0-2-0) to beat xyflow defaults (0-1-0). */
@@ -13,7 +13,7 @@ import type { FlowDropEventHandlers, FlowDropFeatures } from "./types/events.js"
13
13
  import type { FlowDropTheme, FlowDropThemeName } from "./types/theme.js";
14
14
  import type { WorkflowFormatAdapter } from "./registry/workflowFormatRegistry.js";
15
15
  import "./registry/builtinFormats.js";
16
- import type { PartialSettings } from "./types/settings.js";
16
+ import type { PartialSettings, SettingsCategory } from "./types/settings.js";
17
17
  /**
18
18
  * Navbar action configuration
19
19
  */
@@ -74,6 +74,12 @@ export interface FlowDropMountOptions {
74
74
  formatAdapters?: WorkflowFormatAdapter[];
75
75
  /** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
76
76
  theme?: FlowDropTheme | FlowDropThemeName;
77
+ /** Which settings tabs to show in the modal (defaults to all) */
78
+ settingsCategories?: SettingsCategory[];
79
+ /** Show the "Sync to Cloud" button in the settings modal */
80
+ showSettingsSyncButton?: boolean;
81
+ /** Show the reset buttons in the settings modal */
82
+ showSettingsResetButton?: boolean;
77
83
  }
78
84
  /**
79
85
  * Return type for mounted FlowDrop app
@@ -47,7 +47,7 @@ import { globalSaveWorkflow, globalExportWorkflow, } from "./services/globalSave
47
47
  * ```
48
48
  */
49
49
  export async function mountFlowDropApp(container, options = {}) {
50
- const { workflow, nodes, endpointConfig, portConfig, categories, height = "100vh", width = "100%", showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions, showSettings, authProvider, eventHandlers, features: userFeatures, settings: initialSettings, draftStorageKey: customDraftKey, formatAdapters, theme, } = options;
50
+ const { workflow, nodes, endpointConfig, portConfig, categories, height = "100vh", width = "100%", showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions, showSettings, authProvider, eventHandlers, features: userFeatures, settings: initialSettings, draftStorageKey: customDraftKey, formatAdapters, theme, settingsCategories, showSettingsSyncButton, showSettingsResetButton, } = options;
51
51
  // Register custom format adapters before mounting
52
52
  if (formatAdapters) {
53
53
  for (const adapter of formatAdapters) {
@@ -137,6 +137,9 @@ export async function mountFlowDropApp(container, options = {}) {
137
137
  eventHandlers,
138
138
  features,
139
139
  theme,
140
+ settingsCategories,
141
+ showSettingsSyncButton,
142
+ showSettingsResetButton,
140
143
  },
141
144
  });
142
145
  // Set up draft auto-save manager
@@ -484,6 +484,23 @@ export interface ConfigEditOptions {
484
484
  export interface NodeUIExtensions {
485
485
  /** Show/hide unconnected handles (ports) to reduce visual noise */
486
486
  hideUnconnectedHandles?: boolean;
487
+ /**
488
+ * Visual-only port display order (no effect on execution).
489
+ * Arrays of port IDs in the desired render order.
490
+ * Ports not listed appear at the end in metadata order.
491
+ */
492
+ portOrder?: {
493
+ inputs?: string[];
494
+ outputs?: string[];
495
+ };
496
+ /**
497
+ * Manually hidden ports (visual only, no effect on execution).
498
+ * Required ports cannot be added here — enforced in the UI.
499
+ */
500
+ hiddenPorts?: {
501
+ inputs?: string[];
502
+ outputs?: string[];
503
+ };
487
504
  /** Custom styles or theme overrides */
488
505
  style?: Record<string, unknown>;
489
506
  /** Any other UI-specific settings */
@@ -0,0 +1,24 @@
1
+ import type { NodePort } from "../types/index.js";
2
+ /**
3
+ * Sort ports by an ordered array of port IDs.
4
+ * Ports not listed appear at the end in their original order.
5
+ */
6
+ export declare function applyPortOrder(ports: NodePort[], orderedIds: string[] | undefined): NodePort[];
7
+ /**
8
+ * Compute the CSS `top` offset (px) for a port handle.
9
+ * - 1 port: centered at 40px
10
+ * - N ports: 20px start, 40px gap between each
11
+ */
12
+ export declare function getPortTop(index: number, count: number): number;
13
+ /**
14
+ * Determine whether a port should be rendered.
15
+ *
16
+ * Priority:
17
+ * 1. Manual hide (`hiddenPorts`) — always wins, regardless of connections
18
+ * 2. `hideUnconnectedHandles` — hide if not present in the connected handles set
19
+ * 3. Default — visible
20
+ */
21
+ export declare function isPortVisible(port: NodePort, direction: "input" | "output", hiddenPorts: {
22
+ inputs?: string[];
23
+ outputs?: string[];
24
+ }, hideUnconnectedHandles: boolean, connectedHandles: Set<string>, nodeId: string | undefined): boolean;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Sort ports by an ordered array of port IDs.
3
+ * Ports not listed appear at the end in their original order.
4
+ */
5
+ export function applyPortOrder(ports, orderedIds) {
6
+ if (!orderedIds || orderedIds.length === 0)
7
+ return ports;
8
+ const orderMap = new Map(orderedIds.map((id, i) => [id, i]));
9
+ return [...ports].sort((a, b) => {
10
+ const aIdx = orderMap.get(a.id) ?? Infinity;
11
+ const bIdx = orderMap.get(b.id) ?? Infinity;
12
+ // Guard: Infinity - Infinity = NaN, which corrupts sort for two unlisted ports
13
+ return aIdx === bIdx ? 0 : aIdx - bIdx;
14
+ });
15
+ }
16
+ /**
17
+ * Compute the CSS `top` offset (px) for a port handle.
18
+ * - 1 port: centered at 40px
19
+ * - N ports: 20px start, 40px gap between each
20
+ */
21
+ export function getPortTop(index, count) {
22
+ if (count === 1)
23
+ return 40;
24
+ return 20 + index * 40;
25
+ }
26
+ /**
27
+ * Determine whether a port should be rendered.
28
+ *
29
+ * Priority:
30
+ * 1. Manual hide (`hiddenPorts`) — always wins, regardless of connections
31
+ * 2. `hideUnconnectedHandles` — hide if not present in the connected handles set
32
+ * 3. Default — visible
33
+ */
34
+ export function isPortVisible(port, direction, hiddenPorts, hideUnconnectedHandles, connectedHandles, nodeId) {
35
+ const hiddenList = direction === "input" ? hiddenPorts.inputs : hiddenPorts.outputs;
36
+ if (hiddenList?.includes(port.id))
37
+ return false;
38
+ if (hideUnconnectedHandles) {
39
+ return connectedHandles.has(`${nodeId}-${direction}-${port.id}`);
40
+ }
41
+ return true;
42
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
4
4
  "license": "MIT",
5
5
  "private": false,
6
- "version": "1.2.2",
6
+ "version": "1.4.0",
7
7
  "author": "Shibin Das (D34dMan)",
8
8
  "bugs": {
9
9
  "url": "https://github.com/flowdrop-io/flowdrop/issues"
@@ -172,7 +172,6 @@
172
172
  },
173
173
  "devDependencies": {
174
174
  "@chromatic-com/storybook": "^5.0.1",
175
- "@xyflow/svelte": "^1.2",
176
175
  "@codemirror/autocomplete": "^6.20.0",
177
176
  "@codemirror/commands": "^6.10.2",
178
177
  "@codemirror/lang-json": "^6.0.2",
@@ -182,7 +181,6 @@
182
181
  "@codemirror/state": "^6.5.4",
183
182
  "@codemirror/theme-one-dark": "^6.1.3",
184
183
  "@codemirror/view": "^6.39.14",
185
- "codemirror": "^6.0.2",
186
184
  "@eslint/compat": "^1.2.5",
187
185
  "@eslint/js": "^9.18.0",
188
186
  "@iconify/svelte": "^5.0.0",
@@ -204,6 +202,8 @@
204
202
  "@vitest/browser": "^3.2.3",
205
203
  "@vitest/coverage-v8": "^3.2.4",
206
204
  "@vitest/ui": "^3.2.4",
205
+ "@xyflow/svelte": "^1.2",
206
+ "codemirror": "^6.0.2",
207
207
  "eslint": "^9.18.0",
208
208
  "eslint-config-prettier": "^10.0.1",
209
209
  "eslint-plugin-storybook": "^10.2.15",