@d34dman/flowdrop 0.0.1 → 0.0.3

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 (120) hide show
  1. package/README.md +307 -215
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +30 -30
  4. package/dist/api/client.d.ts +24 -1
  5. package/dist/api/client.js +55 -38
  6. package/dist/api/enhanced-client.d.ts +46 -0
  7. package/dist/api/enhanced-client.js +211 -0
  8. package/dist/clients/ApiClient.d.ts +19 -23
  9. package/dist/clients/ApiClient.js +36 -34
  10. package/dist/components/App.svelte +1299 -230
  11. package/dist/components/App.svelte.d.ts +21 -1
  12. package/dist/components/CanvasBanner.svelte +50 -44
  13. package/dist/components/CanvasBanner.svelte.d.ts +5 -19
  14. package/dist/components/ConfigForm.svelte +555 -0
  15. package/dist/components/ConfigForm.svelte.d.ts +32 -0
  16. package/dist/components/ConfigModal.svelte +261 -0
  17. package/dist/components/ConfigModal.svelte.d.ts +31 -0
  18. package/dist/components/ConfigSidebar.svelte +934 -0
  19. package/dist/components/ConfigSidebar.svelte.d.ts +51 -0
  20. package/dist/components/ConnectionLine.svelte +32 -0
  21. package/dist/components/ConnectionLine.svelte.d.ts +3 -0
  22. package/dist/components/GatewayNode.svelte +471 -0
  23. package/dist/components/GatewayNode.svelte.d.ts +15 -0
  24. package/dist/components/LoadingSpinner.svelte +23 -23
  25. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  26. package/dist/components/Logo.svelte +82 -0
  27. package/dist/components/Logo.svelte.d.ts +26 -0
  28. package/dist/components/LogsSidebar.svelte +565 -0
  29. package/dist/components/LogsSidebar.svelte.d.ts +34 -0
  30. package/dist/components/MarkdownDisplay.svelte +28 -0
  31. package/dist/components/MarkdownDisplay.svelte.d.ts +7 -0
  32. package/dist/components/Navbar.svelte +663 -0
  33. package/dist/components/Navbar.svelte.d.ts +21 -0
  34. package/dist/components/NodeSidebar.svelte +629 -488
  35. package/dist/components/NodeSidebar.svelte.d.ts +1 -2
  36. package/dist/components/NodeStatusOverlay.svelte +327 -0
  37. package/dist/components/NodeStatusOverlay.svelte.d.ts +11 -0
  38. package/dist/components/NotesNode.svelte +566 -0
  39. package/dist/components/NotesNode.svelte.d.ts +43 -0
  40. package/dist/components/PipelineStatus.svelte +331 -0
  41. package/dist/components/PipelineStatus.svelte.d.ts +18 -0
  42. package/dist/components/SimpleNode.svelte +447 -0
  43. package/dist/components/SimpleNode.svelte.d.ts +24 -0
  44. package/dist/components/SquareNode.svelte +346 -0
  45. package/dist/components/SquareNode.svelte.d.ts +24 -0
  46. package/dist/components/StatusIcon.svelte +112 -0
  47. package/dist/components/StatusIcon.svelte.d.ts +10 -0
  48. package/dist/components/StatusLabel.svelte +33 -0
  49. package/dist/components/StatusLabel.svelte.d.ts +7 -0
  50. package/dist/components/ToolNode.svelte +385 -0
  51. package/dist/components/ToolNode.svelte.d.ts +36 -0
  52. package/dist/components/UniversalNode.svelte +126 -0
  53. package/dist/components/UniversalNode.svelte.d.ts +15 -0
  54. package/dist/components/WorkflowEditor.svelte +871 -528
  55. package/dist/components/WorkflowEditor.svelte.d.ts +15 -5
  56. package/dist/components/WorkflowNode.svelte +428 -542
  57. package/dist/components/WorkflowNode.svelte.d.ts +7 -3
  58. package/dist/config/apiConfig.d.ts +33 -0
  59. package/dist/config/apiConfig.js +39 -0
  60. package/dist/config/defaultPortConfig.d.ts +6 -0
  61. package/dist/config/defaultPortConfig.js +192 -0
  62. package/dist/config/demo.d.ts +58 -0
  63. package/dist/config/demo.js +142 -0
  64. package/dist/config/endpoints.d.ts +106 -0
  65. package/dist/config/endpoints.js +128 -0
  66. package/dist/data/samples.d.ts +38 -4
  67. package/dist/data/samples.js +2789 -737
  68. package/dist/examples/adapter-usage.d.ts +4 -4
  69. package/dist/examples/adapter-usage.js +21 -26
  70. package/dist/examples/api-client-usage.d.ts +6 -6
  71. package/dist/examples/api-client-usage.js +55 -54
  72. package/dist/index.d.ts +23 -15
  73. package/dist/index.js +23 -15
  74. package/dist/mocks/app-environment.d.ts +8 -0
  75. package/dist/mocks/app-environment.js +16 -0
  76. package/dist/mocks/app-forms.d.ts +2 -0
  77. package/dist/mocks/app-forms.js +21 -0
  78. package/dist/mocks/app-navigation.d.ts +5 -0
  79. package/dist/mocks/app-navigation.js +34 -0
  80. package/dist/mocks/app-stores.d.ts +14 -0
  81. package/dist/mocks/app-stores.js +26 -0
  82. package/dist/services/api.d.ts +13 -3
  83. package/dist/services/api.js +91 -36
  84. package/dist/services/globalSave.d.ts +20 -0
  85. package/dist/services/globalSave.js +165 -0
  86. package/dist/services/nodeExecutionService.d.ts +63 -0
  87. package/dist/services/nodeExecutionService.js +261 -0
  88. package/dist/services/portConfigApi.d.ts +14 -0
  89. package/dist/services/portConfigApi.js +69 -0
  90. package/dist/services/toastService.d.ts +147 -0
  91. package/dist/services/toastService.js +235 -0
  92. package/dist/services/workflowStorage.d.ts +2 -2
  93. package/dist/services/workflowStorage.js +10 -10
  94. package/dist/stores/workflowStore.d.ts +53 -0
  95. package/dist/stores/workflowStore.js +264 -0
  96. package/dist/styles/base.css +896 -363
  97. package/dist/svelte-app.d.ts +52 -5
  98. package/dist/svelte-app.js +128 -6
  99. package/dist/types/config.d.ts +291 -0
  100. package/dist/types/config.js +4 -0
  101. package/dist/types/index.d.ts +231 -19
  102. package/dist/types/index.js +1 -1
  103. package/dist/utils/colors.d.ts +67 -33
  104. package/dist/utils/colors.js +183 -118
  105. package/dist/utils/config.d.ts +41 -0
  106. package/dist/utils/config.js +248 -0
  107. package/dist/utils/connections.d.ts +40 -3
  108. package/dist/utils/connections.js +115 -44
  109. package/dist/utils/icons.d.ts +1 -1
  110. package/dist/utils/icons.js +71 -70
  111. package/dist/utils/nodeStatus.d.ts +53 -0
  112. package/dist/utils/nodeStatus.js +183 -0
  113. package/dist/utils/nodeTypes.d.ts +57 -0
  114. package/dist/utils/nodeTypes.js +109 -0
  115. package/dist/utils/nodeWrapper.d.ts +39 -0
  116. package/dist/utils/nodeWrapper.js +62 -0
  117. package/package.json +132 -97
  118. package/dist/app.css +0 -0
  119. package/dist/components/Node.svelte +0 -38
  120. package/dist/components/Node.svelte.d.ts +0 -4
@@ -1,3 +1,23 @@
1
- declare const App: import("svelte").Component<Record<string, never>, {}, "">;
1
+ import type { Workflow } from '../types/index.js';
2
+ interface Props {
3
+ workflow?: Workflow;
4
+ height?: string | number;
5
+ width?: string | number;
6
+ showNavbar?: boolean;
7
+ disableSidebar?: boolean;
8
+ lockWorkflow?: boolean;
9
+ readOnly?: boolean;
10
+ nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
11
+ pipelineId?: string;
12
+ navbarTitle?: string;
13
+ navbarActions?: Array<{
14
+ label: string;
15
+ href: string;
16
+ icon?: string;
17
+ variant?: 'primary' | 'secondary' | 'outline';
18
+ onclick?: (event: Event) => void;
19
+ }>;
20
+ }
21
+ declare const App: import("svelte").Component<Props, {}, "">;
2
22
  type App = ReturnType<typeof App>;
3
23
  export default App;
@@ -1,51 +1,57 @@
1
1
  <script lang="ts">
2
- import Icon from '@iconify/svelte';
3
- export let title: string;
4
- export let description: string;
5
- export let iconName: string | undefined;
2
+ import Icon from '@iconify/svelte';
3
+ let {
4
+ title,
5
+ description,
6
+ iconName
7
+ }: {
8
+ title: string;
9
+ description: string;
10
+ iconName?: string;
11
+ } = $props();
6
12
  </script>
7
13
 
8
14
  <div class="flowdrop-canvas-banner">
9
- <div class="flowdrop-card">
10
- <div class="flowdrop-card__body flowdrop-text--center">
11
- <div class="flowdrop-canvas-banner__icon">
12
- {#if iconName}
13
- <Icon icon={iconName} class="flowdrop-canvas-banner__icon-svg" />
14
- {/if}
15
- </div>
16
- <h3 class="flowdrop-canvas-banner__title">{title}</h3>
17
- <p class="flowdrop-canvas-banner__description">{description}</p>
18
- </div>
19
- </div>
15
+ <div class="flowdrop-card">
16
+ <div class="flowdrop-card__body flowdrop-text--center">
17
+ <div class="flowdrop-canvas-banner__icon">
18
+ {#if iconName}
19
+ <Icon icon={iconName} class="flowdrop-canvas-banner__icon-svg" />
20
+ {/if}
21
+ </div>
22
+ <h3 class="flowdrop-canvas-banner__title">{title}</h3>
23
+ <p class="flowdrop-canvas-banner__description">{description}</p>
24
+ </div>
25
+ </div>
20
26
  </div>
21
27
 
22
28
  <style>
23
- .flowdrop-canvas-banner {
24
- position: absolute;
25
- inset: 0;
26
- display: flex;
27
- align-items: center;
28
- justify-content: center;
29
- pointer-events: none;
30
- }
31
-
32
- .flowdrop-canvas-banner__icon {
33
- font-size: 3.75rem;
34
- margin-bottom: 1rem;
35
- display: flex;
36
- justify-content: center;
37
- align-items: center;
38
- }
39
-
40
- .flowdrop-canvas-banner__title {
41
- font-size: 1.125rem;
42
- font-weight: 700;
43
- margin-bottom: 0.5rem;
44
- color: #111827;
45
- }
46
-
47
- .flowdrop-canvas-banner__description {
48
- font-size: 0.875rem;
49
- color: #6b7280;
50
- }
51
- </style>
29
+ .flowdrop-canvas-banner {
30
+ position: absolute;
31
+ inset: 0;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ pointer-events: none;
36
+ }
37
+
38
+ .flowdrop-canvas-banner__icon {
39
+ font-size: 3.75rem;
40
+ margin-bottom: 1rem;
41
+ display: flex;
42
+ justify-content: center;
43
+ align-items: center;
44
+ }
45
+
46
+ .flowdrop-canvas-banner__title {
47
+ font-size: 1.125rem;
48
+ font-weight: 700;
49
+ margin-bottom: 0.5rem;
50
+ color: #111827;
51
+ }
52
+
53
+ .flowdrop-canvas-banner__description {
54
+ font-size: 0.875rem;
55
+ color: #6b7280;
56
+ }
57
+ </style>
@@ -1,22 +1,8 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: Props & {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const CanvasBanner: $$__sveltets_2_IsomorphicComponent<{
1
+ type $$ComponentProps = {
15
2
  title: string;
16
3
  description: string;
17
- iconName: string | undefined;
18
- }, {
19
- [evt: string]: CustomEvent<any>;
20
- }, {}, {}, string>;
21
- type CanvasBanner = InstanceType<typeof CanvasBanner>;
4
+ iconName?: string;
5
+ };
6
+ declare const CanvasBanner: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type CanvasBanner = ReturnType<typeof CanvasBanner>;
22
8
  export default CanvasBanner;
@@ -0,0 +1,555 @@
1
+ <!--
2
+ ConfigForm Component
3
+ Generates configuration forms from JSON Schema and manages user values
4
+ Separates schema (form structure) from values (user input)
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import type { ConfigSchema, ConfigProperty, ConfigValues } from '../types/index.js';
9
+ import { createEventDispatcher } from 'svelte';
10
+
11
+ interface Props {
12
+ schema: ConfigSchema;
13
+ values: ConfigValues;
14
+ disabled?: boolean;
15
+ }
16
+
17
+ let props: Props = $props();
18
+ let dispatch = createEventDispatcher<{
19
+ change: { values: ConfigValues };
20
+ validate: { isValid: boolean; errors: string[] };
21
+ }>();
22
+
23
+ // Local copy of values for editing
24
+ let localValues = $state<ConfigValues>({ ...props.values });
25
+
26
+ // Validation errors
27
+ let validationErrors = $state<Record<string, string>>({});
28
+
29
+ /**
30
+ * Validate a single field against its schema
31
+ */
32
+ function validateField(
33
+ fieldName: string,
34
+ value: unknown,
35
+ property: ConfigProperty
36
+ ): string | null {
37
+ // Check if schema exists
38
+ if (!props.schema) {
39
+ return null;
40
+ }
41
+
42
+ // Required field validation
43
+ if (
44
+ props.schema.required?.includes(fieldName) &&
45
+ (value === null || value === undefined || value === '')
46
+ ) {
47
+ return `${property.title || fieldName} is required`;
48
+ }
49
+
50
+ // Type validation
51
+ if (value !== null && value !== undefined) {
52
+ switch (property.type) {
53
+ case 'string':
54
+ if (typeof value !== 'string') {
55
+ return `${property.title || fieldName} must be a string`;
56
+ }
57
+ if (property.minLength && value.length < property.minLength) {
58
+ return `${property.title || fieldName} must be at least ${property.minLength} characters`;
59
+ }
60
+ if (property.maxLength && value.length > property.maxLength) {
61
+ return `${property.title || fieldName} must be at most ${property.maxLength} characters`;
62
+ }
63
+ if (property.pattern && !new RegExp(property.pattern).test(value)) {
64
+ return `${property.title || fieldName} format is invalid`;
65
+ }
66
+ break;
67
+
68
+ case 'number':
69
+ if (typeof value !== 'number') {
70
+ return `${property.title || fieldName} must be a number`;
71
+ }
72
+ if (property.minimum !== undefined && value < property.minimum) {
73
+ return `${property.title || fieldName} must be at least ${property.minimum}`;
74
+ }
75
+ if (property.maximum !== undefined && value > property.maximum) {
76
+ return `${property.title || fieldName} must be at most ${property.maximum}`;
77
+ }
78
+ break;
79
+
80
+ case 'boolean':
81
+ if (typeof value !== 'boolean') {
82
+ return `${property.title || fieldName} must be a boolean`;
83
+ }
84
+ break;
85
+
86
+ case 'array':
87
+ if (!Array.isArray(value)) {
88
+ return `${property.title || fieldName} must be an array`;
89
+ }
90
+ break;
91
+ }
92
+ }
93
+
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Validate all fields
99
+ */
100
+ function validateForm(): { isValid: boolean; errors: string[] } {
101
+ const errors: string[] = [];
102
+ const newValidationErrors: Record<string, string> = {};
103
+
104
+ // Check if schema and properties exist
105
+ if (!props.schema || !props.schema.properties || typeof props.schema.properties !== 'object') {
106
+ console.warn('ConfigForm: Invalid schema or properties:', {
107
+ schema: props.schema,
108
+ properties: props.schema?.properties
109
+ });
110
+ return { isValid: true, errors: [] };
111
+ }
112
+
113
+ Object.entries(props.schema.properties).forEach(([fieldName, property]) => {
114
+ const value = localValues[fieldName];
115
+ const error = validateField(fieldName, value, property);
116
+
117
+ if (error) {
118
+ errors.push(error);
119
+ newValidationErrors[fieldName] = error;
120
+ }
121
+ });
122
+
123
+ validationErrors = newValidationErrors;
124
+
125
+ const isValid = errors.length === 0;
126
+ dispatch('validate', { isValid, errors });
127
+
128
+ return { isValid, errors };
129
+ }
130
+
131
+ /**
132
+ * Handle field value change
133
+ * Preserves hidden field values from the original configuration
134
+ */
135
+ function handleFieldChange(fieldName: string, value: unknown): void {
136
+ localValues[fieldName] = value;
137
+
138
+ // Validate the changed field
139
+ if (props.schema && props.schema.properties && props.schema.properties[fieldName]) {
140
+ const property = props.schema.properties[fieldName];
141
+ const error = validateField(fieldName, value, property);
142
+
143
+ if (error) {
144
+ validationErrors[fieldName] = error;
145
+ } else {
146
+ delete validationErrors[fieldName];
147
+ }
148
+ }
149
+
150
+ // Merge hidden field values from original props to ensure they're preserved
151
+ const hiddenFieldValues: ConfigValues = {};
152
+ if (props.schema?.properties) {
153
+ Object.entries(props.schema.properties).forEach(([key, property]) => {
154
+ if (property.format === 'hidden' && key in props.values) {
155
+ hiddenFieldValues[key] = props.values[key];
156
+ }
157
+ });
158
+ }
159
+
160
+ // Emit change event with hidden fields preserved
161
+ dispatch('change', { values: { ...localValues, ...hiddenFieldValues } });
162
+
163
+ // Validate entire form
164
+ validateForm();
165
+ }
166
+
167
+ /**
168
+ * Get default value for a field
169
+ */
170
+ function getDefaultValue(property: ConfigProperty): unknown {
171
+ if (property.default !== undefined) {
172
+ return property.default;
173
+ }
174
+
175
+ switch (property.type) {
176
+ case 'string':
177
+ // If enum with multiple selection, default to empty array
178
+ if (property.enum && property.multiple) {
179
+ return [];
180
+ }
181
+ return '';
182
+ case 'number':
183
+ return 0;
184
+ case 'boolean':
185
+ return false;
186
+ case 'array':
187
+ return [];
188
+ case 'object':
189
+ return {};
190
+ default:
191
+ return null;
192
+ }
193
+ }
194
+
195
+ // Sync local values with props and initialize with defaults
196
+ $effect(() => {
197
+ // Update local values when props change
198
+ localValues = { ...props.values };
199
+
200
+ if (props.schema) {
201
+ Object.entries(props.schema.properties).forEach(([fieldName, property]) => {
202
+ if (localValues[fieldName] === undefined) {
203
+ localValues[fieldName] = getDefaultValue(property);
204
+ }
205
+ });
206
+
207
+ // Validate on initialization
208
+ validateForm();
209
+ }
210
+ });
211
+ </script>
212
+
213
+ <div class="flowdrop-config-form">
214
+ {#if props.schema && props.schema.properties && typeof props.schema.properties === 'object'}
215
+ <form class="flowdrop-form" onsubmit={(e) => e.preventDefault()}>
216
+ {#each Object.entries(props.schema.properties) as [fieldName, property] (fieldName)}
217
+ {#if property.format === 'hidden'}
218
+ <!-- Hidden field to preserve value -->
219
+ <input
220
+ type="hidden"
221
+ id={fieldName}
222
+ value={typeof (localValues[fieldName] ?? property.default) === 'object'
223
+ ? JSON.stringify(localValues[fieldName] ?? property.default ?? {})
224
+ : (localValues[fieldName] ?? property.default ?? '')}
225
+ />
226
+ {:else}
227
+ <div class="flowdrop-form-field">
228
+ <label class="flowdrop-form-label" for={fieldName}>
229
+ {property.title || fieldName}
230
+ {#if props.schema.required?.includes(fieldName)}
231
+ <span class="flowdrop-form-required">*</span>
232
+ {/if}
233
+ </label>
234
+
235
+ {#if property.type === 'string'}
236
+ {#if property.enum && property.multiple}
237
+ <!-- Checkboxes for enum with multiple selection -->
238
+ <div class="flowdrop-form-checkbox-group">
239
+ {#each property.enum as option (option)}
240
+ <label class="flowdrop-form-checkbox">
241
+ <input
242
+ type="checkbox"
243
+ class="flowdrop-form-checkbox__input"
244
+ value={option}
245
+ checked={Array.isArray(localValues[fieldName]) &&
246
+ localValues[fieldName].includes(option)}
247
+ disabled={props.disabled || false}
248
+ onchange={(e) => {
249
+ const checked = (e.target as HTMLInputElement).checked;
250
+ const currentValues = Array.isArray(localValues[fieldName])
251
+ ? [...localValues[fieldName]]
252
+ : [];
253
+ if (checked) {
254
+ if (!currentValues.includes(option)) {
255
+ handleFieldChange(fieldName, [...currentValues, option]);
256
+ }
257
+ } else {
258
+ handleFieldChange(
259
+ fieldName,
260
+ currentValues.filter((v) => v !== option)
261
+ );
262
+ }
263
+ }}
264
+ />
265
+ <span class="flowdrop-form-checkbox__label">{option}</span>
266
+ </label>
267
+ {/each}
268
+ </div>
269
+ {:else if property.enum}
270
+ <!-- Select field for enum with single selection -->
271
+ <select
272
+ id={fieldName}
273
+ class="flowdrop-form-select {validationErrors[fieldName]
274
+ ? 'flowdrop-form-select--error'
275
+ : ''}"
276
+ disabled={props.disabled || false}
277
+ onchange={(e) =>
278
+ handleFieldChange(fieldName, (e.target as HTMLSelectElement).value)}
279
+ >
280
+ {#each property.enum as option (option)}
281
+ <option value={option} selected={localValues[fieldName] === option}>
282
+ {option}
283
+ </option>
284
+ {/each}
285
+ </select>
286
+ {:else if property.format === 'multiline' || (property.maxLength && property.maxLength > 100)}
287
+ <!-- Textarea for multiline or long text -->
288
+ <textarea
289
+ id={fieldName}
290
+ class="flowdrop-form-textarea {validationErrors[fieldName]
291
+ ? 'flowdrop-form-textarea--error'
292
+ : ''}"
293
+ placeholder={property.description || ''}
294
+ rows="4"
295
+ disabled={props.disabled || false}
296
+ onchange={(e) =>
297
+ handleFieldChange(fieldName, (e.target as HTMLTextAreaElement).value)}
298
+ >{localValues[fieldName] || ''}</textarea
299
+ >
300
+ {:else}
301
+ <!-- Regular text input -->
302
+ <input
303
+ id={fieldName}
304
+ type="text"
305
+ class="flowdrop-form-input {validationErrors[fieldName]
306
+ ? 'flowdrop-form-input--error'
307
+ : ''}"
308
+ value={localValues[fieldName] || ''}
309
+ placeholder={property.description || ''}
310
+ disabled={props.disabled || false}
311
+ onchange={(e) =>
312
+ handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
313
+ />
314
+ {/if}
315
+ {:else if property.type === 'number'}
316
+ <!-- Number input as text field -->
317
+ <input
318
+ id={fieldName}
319
+ type="text"
320
+ class="flowdrop-form-input {validationErrors[fieldName]
321
+ ? 'flowdrop-form-input--error'
322
+ : ''}"
323
+ value={localValues[fieldName] || ''}
324
+ placeholder="Enter a number"
325
+ disabled={props.disabled || false}
326
+ oninput={(e) => {
327
+ const value = (e.target as HTMLInputElement).value;
328
+ const numValue = value === '' ? 0 : parseFloat(value);
329
+ if (!isNaN(numValue)) {
330
+ handleFieldChange(fieldName, numValue);
331
+ }
332
+ }}
333
+ onblur={(e) => {
334
+ const value = (e.target as HTMLInputElement).value;
335
+ const numValue = value === '' ? 0 : parseFloat(value);
336
+ if (!isNaN(numValue)) {
337
+ handleFieldChange(fieldName, numValue);
338
+ }
339
+ }}
340
+ />
341
+ {:else if property.type === 'boolean'}
342
+ <!-- Checkbox -->
343
+ <label class="flowdrop-form-checkbox">
344
+ <input
345
+ id={fieldName}
346
+ type="checkbox"
347
+ class="flowdrop-form-checkbox__input"
348
+ checked={Boolean(localValues[fieldName])}
349
+ disabled={props.disabled || false}
350
+ onchange={(e) =>
351
+ handleFieldChange(fieldName, (e.target as HTMLInputElement).checked)}
352
+ />
353
+ <span class="flowdrop-form-checkbox__label">
354
+ {property.description || ''}
355
+ </span>
356
+ </label>
357
+ {:else if property.type === 'array'}
358
+ <!-- Array input (comma-separated) -->
359
+ <textarea
360
+ id={fieldName}
361
+ class="flowdrop-form-textarea {validationErrors[fieldName]
362
+ ? 'flowdrop-form-textarea--error'
363
+ : ''}"
364
+ placeholder="Enter values separated by commas"
365
+ rows="3"
366
+ disabled={props.disabled || false}
367
+ onchange={(e) =>
368
+ handleFieldChange(
369
+ fieldName,
370
+ (e.target as HTMLTextAreaElement).value
371
+ .split(',')
372
+ .map((v) => v.trim())
373
+ .filter((v) => v)
374
+ )}
375
+ >{Array.isArray(localValues[fieldName])
376
+ ? localValues[fieldName].join(', ')
377
+ : ''}</textarea
378
+ >
379
+ {:else if property.type === 'object'}
380
+ <!-- JSON object input -->
381
+ <textarea
382
+ id={fieldName}
383
+ class="flowdrop-form-textarea {validationErrors[fieldName]
384
+ ? 'flowdrop-form-textarea--error'
385
+ : ''}"
386
+ placeholder="Enter JSON object"
387
+ rows="4"
388
+ disabled={props.disabled || false}
389
+ onchange={(e) => {
390
+ try {
391
+ handleFieldChange(
392
+ fieldName,
393
+ JSON.parse((e.target as HTMLTextAreaElement).value)
394
+ );
395
+ } catch {
396
+ // Handle JSON parse error
397
+ }
398
+ }}
399
+ >{typeof localValues[fieldName] === 'object'
400
+ ? JSON.stringify(localValues[fieldName], null, 2)
401
+ : ''}</textarea
402
+ >
403
+ {:else}
404
+ <!-- Default text input -->
405
+ <input
406
+ id={fieldName}
407
+ type="text"
408
+ class="flowdrop-form-input {validationErrors[fieldName]
409
+ ? 'flowdrop-form-input--error'
410
+ : ''}"
411
+ value={localValues[fieldName] || ''}
412
+ placeholder={property.description || ''}
413
+ disabled={props.disabled || false}
414
+ onchange={(e) => handleFieldChange(fieldName, (e.target as HTMLInputElement).value)}
415
+ />
416
+ {/if}
417
+
418
+ {#if validationErrors[fieldName]}
419
+ <div class="flowdrop-form-error">{validationErrors[fieldName]}</div>
420
+ {/if}
421
+
422
+ {#if property.description}
423
+ <div class="flowdrop-form-help">{property.description}</div>
424
+ {/if}
425
+ </div>
426
+ {/if}
427
+ {/each}
428
+ </form>
429
+ {:else}
430
+ <div class="flowdrop-form-empty">
431
+ <p class="flowdrop-text--sm flowdrop-text--gray">
432
+ No configuration schema available for this node.
433
+ </p>
434
+ </div>
435
+ {/if}
436
+ </div>
437
+
438
+ <style>
439
+ .flowdrop-config-form {
440
+ padding: 1rem;
441
+ }
442
+
443
+ .flowdrop-form {
444
+ display: flex;
445
+ flex-direction: column;
446
+ gap: 1rem;
447
+ }
448
+
449
+ .flowdrop-form-field {
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: 0.5rem;
453
+ }
454
+
455
+ .flowdrop-form-label {
456
+ font-size: 0.875rem;
457
+ font-weight: 500;
458
+ color: #374151;
459
+ display: flex;
460
+ align-items: center;
461
+ gap: 0.25rem;
462
+ }
463
+
464
+ .flowdrop-form-required {
465
+ color: #dc2626;
466
+ font-weight: 700;
467
+ }
468
+
469
+ .flowdrop-form-input,
470
+ .flowdrop-form-textarea,
471
+ .flowdrop-form-select {
472
+ padding: 0.5rem 0.75rem;
473
+ border: 1px solid #d1d5db;
474
+ border-radius: 0.375rem;
475
+ font-size: 0.875rem;
476
+ background-color: #ffffff;
477
+ transition: all 0.2s ease-in-out;
478
+ }
479
+
480
+ .flowdrop-form-input:focus,
481
+ .flowdrop-form-textarea:focus,
482
+ .flowdrop-form-select:focus {
483
+ outline: none;
484
+ border-color: #3b82f6;
485
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
486
+ }
487
+
488
+ .flowdrop-form-input--error,
489
+ .flowdrop-form-textarea--error,
490
+ .flowdrop-form-select--error {
491
+ border-color: #dc2626;
492
+ }
493
+
494
+ .flowdrop-form-input--error:focus,
495
+ .flowdrop-form-textarea--error:focus,
496
+ .flowdrop-form-select--error:focus {
497
+ border-color: #dc2626;
498
+ box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
499
+ }
500
+
501
+ .flowdrop-form-textarea {
502
+ resize: vertical;
503
+ min-height: 4rem;
504
+ }
505
+
506
+ .flowdrop-form-checkbox-group {
507
+ display: flex;
508
+ flex-direction: column;
509
+ gap: 0.5rem;
510
+ }
511
+
512
+ .flowdrop-form-checkbox {
513
+ display: flex;
514
+ align-items: center;
515
+ gap: 0.5rem;
516
+ cursor: pointer;
517
+ }
518
+
519
+ .flowdrop-form-checkbox__input {
520
+ width: 1rem;
521
+ height: 1rem;
522
+ accent-color: #3b82f6;
523
+ }
524
+
525
+ .flowdrop-form-checkbox__label {
526
+ font-size: 0.875rem;
527
+ color: #374151;
528
+ }
529
+
530
+ .flowdrop-form-error {
531
+ font-size: 0.75rem;
532
+ color: #dc2626;
533
+ margin-top: 0.25rem;
534
+ }
535
+
536
+ .flowdrop-form-help {
537
+ font-size: 0.75rem;
538
+ color: #6b7280;
539
+ margin-top: 0.25rem;
540
+ }
541
+
542
+ .flowdrop-form-empty {
543
+ text-align: center;
544
+ padding: 2rem;
545
+ color: #6b7280;
546
+ }
547
+
548
+ .flowdrop-form-input:disabled,
549
+ .flowdrop-form-textarea:disabled,
550
+ .flowdrop-form-select:disabled {
551
+ background-color: #f9fafb;
552
+ color: #9ca3af;
553
+ cursor: not-allowed;
554
+ }
555
+ </style>