@flowdrop/flowdrop 1.3.0 → 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.
@@ -158,3 +158,67 @@
158
158
  })}
159
159
  />
160
160
  </Story>
161
+
162
+ <Story name="Dynamic Ports">
163
+ <NodeDecorator
164
+ data={createSampleNodeData({
165
+ label: "Custom Function",
166
+ config: {
167
+ dynamicInputs: [
168
+ {
169
+ name: "param_a",
170
+ label: "Parameter A",
171
+ description: "First parameter",
172
+ dataType: "string",
173
+ required: true,
174
+ },
175
+ {
176
+ name: "param_b",
177
+ label: "Parameter B",
178
+ description: "Second parameter",
179
+ dataType: "number",
180
+ required: false,
181
+ },
182
+ ],
183
+ dynamicOutputs: [
184
+ {
185
+ name: "result",
186
+ label: "Result",
187
+ description: "Function result",
188
+ dataType: "json",
189
+ required: false,
190
+ },
191
+ ],
192
+ },
193
+ metadata: {
194
+ id: "custom_function",
195
+ name: "Custom Function",
196
+ description: "A node with dynamic input and output ports",
197
+ category: "processing",
198
+ version: "1.0.0",
199
+ type: "simple",
200
+ supportedTypes: ["simple", "square", "default"],
201
+ icon: "mdi:function-variant",
202
+ color: "#8b5cf6",
203
+ inputs: [
204
+ {
205
+ id: "trigger",
206
+ name: "Trigger",
207
+ type: "input",
208
+ dataType: "trigger",
209
+ required: false,
210
+ },
211
+ ],
212
+ outputs: [
213
+ {
214
+ id: "done",
215
+ name: "Done",
216
+ type: "output",
217
+ dataType: "trigger",
218
+ required: false,
219
+ },
220
+ ],
221
+ },
222
+ })}
223
+ />
224
+ </Story>
@@ -16,7 +16,9 @@
16
16
  NodeMetadata,
17
17
  NodeExtensions,
18
18
  NodePort,
19
+ DynamicPort,
19
20
  } from "../../types/index.js";
21
+ import { dynamicPortToNodePort } from "../../types/index.js";
20
22
  import Icon from "@iconify/svelte";
21
23
  import {
22
24
  getDataTypeColor,
@@ -133,20 +135,34 @@
133
135
  }
134
136
  }
135
137
 
138
+ const dynamicInputs = $derived(
139
+ ((props.data.config?.dynamicInputs as DynamicPort[]) || []).map((port) =>
140
+ dynamicPortToNodePort(port, "input"),
141
+ ),
142
+ );
143
+
144
+ const dynamicOutputs = $derived(
145
+ ((props.data.config?.dynamicOutputs as DynamicPort[]) || []).map((port) =>
146
+ dynamicPortToNodePort(port, "output"),
147
+ ),
148
+ );
149
+
136
150
  /**
137
151
  * All visible input ports in user-defined order.
138
152
  */
139
153
  const visibleInputPorts = $derived(
140
- applyPortOrder(props.data.metadata?.inputs ?? [], portOrder.inputs).filter(
141
- (p: NodePort) =>
142
- isPortVisible(
143
- p,
144
- "input",
145
- hiddenPorts,
146
- hideUnconnectedHandles,
147
- getConnectedHandles(),
148
- props.data.nodeId,
149
- ),
154
+ applyPortOrder(
155
+ [...(props.data.metadata?.inputs ?? []), ...dynamicInputs],
156
+ portOrder.inputs,
157
+ ).filter((p: NodePort) =>
158
+ isPortVisible(
159
+ p,
160
+ "input",
161
+ hiddenPorts,
162
+ hideUnconnectedHandles,
163
+ getConnectedHandles(),
164
+ props.data.nodeId,
165
+ ),
150
166
  ),
151
167
  );
152
168
 
@@ -155,7 +171,7 @@
155
171
  */
156
172
  const visibleOutputPorts = $derived(
157
173
  applyPortOrder(
158
- props.data.metadata?.outputs ?? [],
174
+ [...(props.data.metadata?.outputs ?? []), ...dynamicOutputs],
159
175
  portOrder.outputs,
160
176
  ).filter((p: NodePort) =>
161
177
  isPortVisible(
@@ -80,3 +80,48 @@
80
80
  selected={true}
81
81
  />
82
82
  </Story>
83
+
84
+ <Story name="Dynamic Ports">
85
+ <NodeDecorator
86
+ data={createSampleNodeData({
87
+ label: "Data Mapper",
88
+ config: {
89
+ dynamicInputs: [
90
+ {
91
+ name: "source",
92
+ label: "Source Data",
93
+ dataType: "json",
94
+ required: true,
95
+ },
96
+ ],
97
+ dynamicOutputs: [
98
+ {
99
+ name: "mapped",
100
+ label: "Mapped Output",
101
+ dataType: "json",
102
+ required: false,
103
+ },
104
+ {
105
+ name: "errors",
106
+ label: "Errors",
107
+ dataType: "string",
108
+ required: false,
109
+ },
110
+ ],
111
+ },
112
+ metadata: {
113
+ id: "data_mapper",
114
+ name: "Data Mapper",
115
+ description: "Map data between formats",
116
+ category: "processing",
117
+ version: "1.0.0",
118
+ type: "square",
119
+ supportedTypes: ["square"],
120
+ icon: "mdi:swap-horizontal",
121
+ color: "#0ea5e9",
122
+ inputs: [],
123
+ outputs: [],
124
+ },
125
+ })}
126
+ />
127
+ </Story>
@@ -16,7 +16,9 @@
16
16
  NodeMetadata,
17
17
  NodeExtensions,
18
18
  NodePort,
19
+ DynamicPort,
19
20
  } from "../../types/index.js";
21
+ import { dynamicPortToNodePort } from "../../types/index.js";
20
22
  import Icon from "@iconify/svelte";
21
23
  import {
22
24
  getDataTypeColor,
@@ -117,20 +119,34 @@
117
119
  openConfigSidebar();
118
120
  }
119
121
  }
122
+ const dynamicInputs = $derived(
123
+ ((props.data.config?.dynamicInputs as DynamicPort[]) || []).map((port) =>
124
+ dynamicPortToNodePort(port, "input"),
125
+ ),
126
+ );
127
+
128
+ const dynamicOutputs = $derived(
129
+ ((props.data.config?.dynamicOutputs as DynamicPort[]) || []).map((port) =>
130
+ dynamicPortToNodePort(port, "output"),
131
+ ),
132
+ );
133
+
120
134
  /**
121
135
  * All visible input ports in user-defined order.
122
136
  */
123
137
  const visibleInputPorts = $derived(
124
- applyPortOrder(props.data.metadata?.inputs ?? [], portOrder.inputs).filter(
125
- (p: NodePort) =>
126
- isPortVisible(
127
- p,
128
- "input",
129
- hiddenPorts,
130
- hideUnconnectedHandles,
131
- getConnectedHandles(),
132
- props.data.nodeId,
133
- ),
138
+ applyPortOrder(
139
+ [...(props.data.metadata?.inputs ?? []), ...dynamicInputs],
140
+ portOrder.inputs,
141
+ ).filter((p: NodePort) =>
142
+ isPortVisible(
143
+ p,
144
+ "input",
145
+ hiddenPorts,
146
+ hideUnconnectedHandles,
147
+ getConnectedHandles(),
148
+ props.data.nodeId,
149
+ ),
134
150
  ),
135
151
  );
136
152
 
@@ -139,7 +155,7 @@
139
155
  */
140
156
  const visibleOutputPorts = $derived(
141
157
  applyPortOrder(
142
- props.data.metadata?.outputs ?? [],
158
+ [...(props.data.metadata?.outputs ?? []), ...dynamicOutputs],
143
159
  portOrder.outputs,
144
160
  ).filter((p: NodePort) =>
145
161
  isPortVisible(
@@ -77,3 +77,66 @@
77
77
  selected={true}
78
78
  />
79
79
  </Story>
80
+
81
+ <Story name="Dynamic Ports">
82
+ <NodeDecorator
83
+ data={createSampleNodeData({
84
+ label: "Custom Function",
85
+ config: {
86
+ dynamicInputs: [
87
+ {
88
+ name: "input_1",
89
+ label: "First Input",
90
+ description: "The first input parameter",
91
+ dataType: "string",
92
+ required: true,
93
+ },
94
+ {
95
+ name: "input_2",
96
+ label: "Second Input",
97
+ description: "The second input parameter",
98
+ dataType: "number",
99
+ required: false,
100
+ },
101
+ ],
102
+ dynamicOutputs: [
103
+ {
104
+ name: "output_1",
105
+ label: "Primary Output",
106
+ description: "The main output value",
107
+ dataType: "string",
108
+ required: false,
109
+ },
110
+ ],
111
+ },
112
+ metadata: {
113
+ id: "custom-function",
114
+ name: "Custom Function",
115
+ description:
116
+ "Execute a custom function with dynamic inputs and outputs",
117
+ category: "processing",
118
+ version: "1.0.0",
119
+ type: "workflow",
120
+ icon: "mdi:function-variant",
121
+ inputs: [
122
+ {
123
+ id: "trigger",
124
+ name: "Trigger",
125
+ type: "input",
126
+ dataType: "trigger",
127
+ required: false,
128
+ },
129
+ ],
130
+ outputs: [
131
+ {
132
+ id: "done",
133
+ name: "Done",
134
+ type: "output",
135
+ dataType: "trigger",
136
+ required: false,
137
+ },
138
+ ],
139
+ },
140
+ })}
141
+ />
142
+ </Story>
@@ -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
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.3.0",
6
+ "version": "1.4.0",
7
7
  "author": "Shibin Das (D34dMan)",
8
8
  "bugs": {
9
9
  "url": "https://github.com/flowdrop-io/flowdrop/issues"