@d34dman/flowdrop 0.0.28 → 0.0.30
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.
- package/dist/components/NodeSidebar.svelte +18 -10
- package/dist/components/WorkflowEditor.svelte +4 -0
- package/dist/components/form/FormFieldLight.svelte +422 -0
- package/dist/components/form/FormFieldLight.svelte.d.ts +18 -0
- package/dist/components/nodes/ToolNode.svelte +23 -8
- package/dist/core/index.d.ts +39 -0
- package/dist/core/index.js +50 -0
- package/dist/display/index.d.ts +29 -0
- package/dist/display/index.js +36 -0
- package/dist/editor/index.d.ts +78 -0
- package/dist/editor/index.js +117 -0
- package/dist/form/code.d.ts +100 -0
- package/dist/form/code.js +153 -0
- package/dist/form/fieldRegistry.d.ts +163 -0
- package/dist/form/fieldRegistry.js +175 -0
- package/dist/form/full.d.ts +53 -0
- package/dist/form/full.js +75 -0
- package/dist/form/index.d.ts +57 -0
- package/dist/form/index.js +68 -0
- package/dist/form/markdown.d.ts +68 -0
- package/dist/form/markdown.js +93 -0
- package/dist/index.d.ts +31 -66
- package/dist/index.js +49 -80
- package/dist/styles/base.css +5 -0
- package/dist/utils/colors.d.ts +42 -0
- package/dist/utils/colors.js +77 -0
- package/package.json +46 -3
|
@@ -25,25 +25,34 @@
|
|
|
25
25
|
let categories = $derived(getCategories());
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Get all unique categories from node types
|
|
28
|
+
* Get all unique categories from node types, preserving API order
|
|
29
|
+
* Categories appear in the order their first node appears in the API response
|
|
29
30
|
*/
|
|
30
31
|
function getCategories(): NodeCategory[] {
|
|
31
32
|
const nodes = props.nodes || [];
|
|
32
33
|
if (nodes.length === 0) return [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Use a Set to track uniqueness while preserving insertion order
|
|
35
|
+
const seen = new SvelteSet<NodeCategory>();
|
|
36
|
+
const orderedCategories: NodeCategory[] = [];
|
|
37
|
+
for (const node of nodes) {
|
|
38
|
+
if (!seen.has(node.category)) {
|
|
39
|
+
seen.add(node.category);
|
|
40
|
+
orderedCategories.push(node.category);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return orderedCategories;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
47
|
* Filter node types based on search query and selected category
|
|
48
|
+
* Preserves the API order - no client-side sorting applied
|
|
40
49
|
*/
|
|
41
50
|
function getFilteredNodes(): NodeMetadata[] {
|
|
42
51
|
// Use actual node types from props
|
|
43
52
|
let filtered = props.nodes || [];
|
|
44
53
|
|
|
45
54
|
// Filter by category
|
|
46
|
-
if (selectedCategory !==
|
|
55
|
+
if (selectedCategory !== "all") {
|
|
47
56
|
filtered = filtered.filter((node) => node.category === selectedCategory);
|
|
48
57
|
}
|
|
49
58
|
|
|
@@ -58,8 +67,8 @@
|
|
|
58
67
|
);
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
//
|
|
62
|
-
return
|
|
70
|
+
// Return filtered results preserving API order
|
|
71
|
+
return filtered;
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
/**
|
|
@@ -149,12 +158,11 @@
|
|
|
149
158
|
|
|
150
159
|
/**
|
|
151
160
|
* Get node types for category
|
|
161
|
+
* Preserves the API order - no client-side sorting applied
|
|
152
162
|
*/
|
|
153
163
|
function getNodesForCategory(category: NodeCategory): NodeMetadata[] {
|
|
154
164
|
const nodes = props.nodes || [];
|
|
155
|
-
return
|
|
156
|
-
.filter((node) => node.category === category)
|
|
157
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
return nodes.filter((node) => node.category === category);
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
/**
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
} from '../helpers/workflowEditorHelper.js';
|
|
38
38
|
import type { NodeExecutionInfo } from '../types/index.js';
|
|
39
39
|
import { areNodeArraysEqual, areEdgeArraysEqual, throttle } from '../utils/performanceUtils.js';
|
|
40
|
+
import { Toaster } from 'svelte-5-french-toast';
|
|
40
41
|
|
|
41
42
|
interface Props {
|
|
42
43
|
nodes?: NodeMetadata[];
|
|
@@ -457,6 +458,9 @@
|
|
|
457
458
|
</div>
|
|
458
459
|
</SvelteFlowProvider>
|
|
459
460
|
|
|
461
|
+
<!-- Toast notifications container -->
|
|
462
|
+
<Toaster position="bottom-center" />
|
|
463
|
+
|
|
460
464
|
<style>
|
|
461
465
|
.flowdrop-workflow-editor {
|
|
462
466
|
display: flex;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormFieldLight Component
|
|
3
|
+
Factory component that renders the appropriate field based on schema type.
|
|
4
|
+
|
|
5
|
+
This is the "light" version that uses the field registry for heavy components
|
|
6
|
+
(code editor, markdown editor, template editor) instead of static imports.
|
|
7
|
+
|
|
8
|
+
Heavy components are only loaded if registered via:
|
|
9
|
+
- `registerCodeEditorField()` from "@d34dman/flowdrop/form/code"
|
|
10
|
+
- `registerMarkdownEditorField()` from "@d34dman/flowdrop/form/markdown"
|
|
11
|
+
|
|
12
|
+
Features:
|
|
13
|
+
- Automatically selects the correct field component based on schema
|
|
14
|
+
- Wraps fields with FormFieldWrapper for consistent layout
|
|
15
|
+
- Supports all basic field types (string, number, boolean, select, checkbox group, range)
|
|
16
|
+
- Heavy editors (code, markdown, template) require explicit registration
|
|
17
|
+
- Shows helpful fallback when heavy editors aren't registered
|
|
18
|
+
|
|
19
|
+
Type Resolution Order:
|
|
20
|
+
1. Check field registry for custom/heavy components (highest priority)
|
|
21
|
+
2. format: 'hidden' -> skip rendering (return nothing)
|
|
22
|
+
3. enum with multiple: true -> FormCheckboxGroup
|
|
23
|
+
4. enum -> FormSelect
|
|
24
|
+
5. format: 'multiline' -> FormTextarea
|
|
25
|
+
6. format: 'range' (number/integer) -> FormRangeField
|
|
26
|
+
7. type: 'string' -> FormTextField
|
|
27
|
+
8. type: 'number' or 'integer' -> FormNumberField
|
|
28
|
+
9. type: 'boolean' -> FormToggle
|
|
29
|
+
10. type: 'select' or has options -> FormSelect
|
|
30
|
+
11. type: 'array' -> FormArray
|
|
31
|
+
12. fallback -> FormTextField
|
|
32
|
+
-->
|
|
33
|
+
|
|
34
|
+
<script lang="ts">
|
|
35
|
+
import FormFieldWrapper from "./FormFieldWrapper.svelte";
|
|
36
|
+
import FormTextField from "./FormTextField.svelte";
|
|
37
|
+
import FormTextarea from "./FormTextarea.svelte";
|
|
38
|
+
import FormNumberField from "./FormNumberField.svelte";
|
|
39
|
+
import FormRangeField from "./FormRangeField.svelte";
|
|
40
|
+
import FormToggle from "./FormToggle.svelte";
|
|
41
|
+
import FormSelect from "./FormSelect.svelte";
|
|
42
|
+
import FormCheckboxGroup from "./FormCheckboxGroup.svelte";
|
|
43
|
+
import FormArray from "./FormArray.svelte";
|
|
44
|
+
import { resolveFieldComponent } from "../../form/fieldRegistry.js";
|
|
45
|
+
import type { FieldSchema, FieldOption } from "./types.js";
|
|
46
|
+
|
|
47
|
+
interface Props {
|
|
48
|
+
/** Unique key/id for the field */
|
|
49
|
+
fieldKey: string;
|
|
50
|
+
/** Field schema definition */
|
|
51
|
+
schema: FieldSchema;
|
|
52
|
+
/** Current field value */
|
|
53
|
+
value: unknown;
|
|
54
|
+
/** Whether the field is required */
|
|
55
|
+
required?: boolean;
|
|
56
|
+
/** Animation delay index for staggered animations */
|
|
57
|
+
animationIndex?: number;
|
|
58
|
+
/** Callback when the field value changes */
|
|
59
|
+
onChange: (value: unknown) => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let { fieldKey, schema, value, required = false, animationIndex = 0, onChange }: Props = $props();
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Computed description ID for ARIA association
|
|
66
|
+
*/
|
|
67
|
+
const descriptionId = $derived(
|
|
68
|
+
schema.description && schema.title ? `${fieldKey}-description` : undefined
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Animation delay based on index
|
|
73
|
+
*/
|
|
74
|
+
const animationDelay = $derived(animationIndex * 30);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Field label - prefer title, fall back to description, then key
|
|
78
|
+
*/
|
|
79
|
+
const fieldLabel = $derived(String(schema.title ?? schema.description ?? fieldKey));
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if there's a registered custom component for this schema
|
|
83
|
+
*/
|
|
84
|
+
const registeredComponent = $derived(resolveFieldComponent(schema));
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Determine the field type to render (for non-registered components)
|
|
88
|
+
*/
|
|
89
|
+
const fieldType = $derived.by(() => {
|
|
90
|
+
// If a custom component is registered, use it
|
|
91
|
+
if (registeredComponent) {
|
|
92
|
+
return "registered";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Hidden fields should not be rendered
|
|
96
|
+
if (schema.format === "hidden") {
|
|
97
|
+
return "hidden";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check for heavy editor formats that need registration
|
|
101
|
+
if (schema.format === "json" || schema.format === "code") {
|
|
102
|
+
return "code-editor-fallback";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (schema.format === "markdown") {
|
|
106
|
+
return "markdown-editor-fallback";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (schema.format === "template") {
|
|
110
|
+
return "template-editor-fallback";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Object type without specific format would use code editor
|
|
114
|
+
if (schema.type === "object" && !schema.format) {
|
|
115
|
+
return "code-editor-fallback";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Enum with multiple selection -> checkbox group
|
|
119
|
+
if (schema.enum && schema.multiple) {
|
|
120
|
+
return "checkbox-group";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Enum with single selection -> select
|
|
124
|
+
if (schema.enum) {
|
|
125
|
+
return "select-enum";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Multiline string -> textarea
|
|
129
|
+
if (schema.type === "string" && schema.format === "multiline") {
|
|
130
|
+
return "textarea";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Range slider for number/integer with format: "range"
|
|
134
|
+
if ((schema.type === "number" || schema.type === "integer") && schema.format === "range") {
|
|
135
|
+
return "range";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// String -> text field
|
|
139
|
+
if (schema.type === "string") {
|
|
140
|
+
return "text";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Number or integer -> number field
|
|
144
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
145
|
+
return "number";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Boolean -> toggle
|
|
149
|
+
if (schema.type === "boolean") {
|
|
150
|
+
return "toggle";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Select type or has options -> select
|
|
154
|
+
if (schema.type === "select" || schema.options) {
|
|
155
|
+
return "select-options";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Array type
|
|
159
|
+
if (schema.type === "array") {
|
|
160
|
+
return "array";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Fallback to text
|
|
164
|
+
return "text";
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get enum options as string array for select/checkbox components
|
|
169
|
+
*/
|
|
170
|
+
const enumOptions = $derived.by((): string[] => {
|
|
171
|
+
if (!schema.enum) return [];
|
|
172
|
+
return schema.enum.map((opt) => String(opt));
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get select options for select-options type
|
|
177
|
+
*/
|
|
178
|
+
const selectOptions = $derived.by((): FieldOption[] => {
|
|
179
|
+
if (!schema.options) return [];
|
|
180
|
+
return schema.options as FieldOption[];
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get current value as the appropriate type
|
|
185
|
+
*/
|
|
186
|
+
const stringValue = $derived(String(value ?? ""));
|
|
187
|
+
const numberValue = $derived(value as number | string);
|
|
188
|
+
const booleanValue = $derived(Boolean(value ?? schema.default ?? false));
|
|
189
|
+
const arrayValue = $derived.by((): string[] => {
|
|
190
|
+
if (Array.isArray(value)) {
|
|
191
|
+
return value.map((v) => String(v));
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
});
|
|
195
|
+
const arrayItems = $derived.by((): unknown[] => {
|
|
196
|
+
if (Array.isArray(value)) {
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get helpful message for missing editor registration
|
|
204
|
+
*/
|
|
205
|
+
function getEditorHint(editorType: string): string {
|
|
206
|
+
switch (editorType) {
|
|
207
|
+
case "code-editor-fallback":
|
|
208
|
+
return "Code editor requires: import { registerCodeEditorField } from '@d34dman/flowdrop/form/code'; registerCodeEditorField();";
|
|
209
|
+
case "markdown-editor-fallback":
|
|
210
|
+
return "Markdown editor requires: import { registerMarkdownEditorField } from '@d34dman/flowdrop/form/markdown'; registerMarkdownEditorField();";
|
|
211
|
+
case "template-editor-fallback":
|
|
212
|
+
return "Template editor requires: import { registerTemplateEditorField } from '@d34dman/flowdrop/form/code'; registerTemplateEditorField();";
|
|
213
|
+
default:
|
|
214
|
+
return "This field type requires additional registration.";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
</script>
|
|
218
|
+
|
|
219
|
+
{#if fieldType !== "hidden"}
|
|
220
|
+
<FormFieldWrapper
|
|
221
|
+
id={fieldKey}
|
|
222
|
+
label={fieldLabel}
|
|
223
|
+
{required}
|
|
224
|
+
description={schema.title ? schema.description : undefined}
|
|
225
|
+
{animationDelay}
|
|
226
|
+
>
|
|
227
|
+
{#if fieldType === "registered" && registeredComponent}
|
|
228
|
+
<!-- Render registered custom component -->
|
|
229
|
+
<registeredComponent.component
|
|
230
|
+
id={fieldKey}
|
|
231
|
+
{value}
|
|
232
|
+
placeholder={schema.placeholder ?? ""}
|
|
233
|
+
{required}
|
|
234
|
+
ariaDescribedBy={descriptionId}
|
|
235
|
+
height={schema.height as string | undefined}
|
|
236
|
+
darkTheme={schema.darkTheme as boolean | undefined}
|
|
237
|
+
autoFormat={schema.autoFormat as boolean | undefined}
|
|
238
|
+
showToolbar={schema.showToolbar as boolean | undefined}
|
|
239
|
+
showStatusBar={schema.showStatusBar as boolean | undefined}
|
|
240
|
+
spellChecker={schema.spellChecker as boolean | undefined}
|
|
241
|
+
variableHints={schema.variableHints as string[] | undefined}
|
|
242
|
+
placeholderExample={schema.placeholderExample as string | undefined}
|
|
243
|
+
onChange={(val: unknown) => onChange(val)}
|
|
244
|
+
/>
|
|
245
|
+
{:else if fieldType === "checkbox-group"}
|
|
246
|
+
<FormCheckboxGroup
|
|
247
|
+
id={fieldKey}
|
|
248
|
+
value={arrayValue}
|
|
249
|
+
options={enumOptions}
|
|
250
|
+
ariaDescribedBy={descriptionId}
|
|
251
|
+
onChange={(val) => onChange(val)}
|
|
252
|
+
/>
|
|
253
|
+
{:else if fieldType === "select-enum"}
|
|
254
|
+
<FormSelect
|
|
255
|
+
id={fieldKey}
|
|
256
|
+
value={stringValue}
|
|
257
|
+
options={enumOptions}
|
|
258
|
+
{required}
|
|
259
|
+
ariaDescribedBy={descriptionId}
|
|
260
|
+
onChange={(val) => onChange(val)}
|
|
261
|
+
/>
|
|
262
|
+
{:else if fieldType === "textarea"}
|
|
263
|
+
<FormTextarea
|
|
264
|
+
id={fieldKey}
|
|
265
|
+
value={stringValue}
|
|
266
|
+
placeholder={schema.placeholder ?? ""}
|
|
267
|
+
{required}
|
|
268
|
+
ariaDescribedBy={descriptionId}
|
|
269
|
+
onChange={(val) => onChange(val)}
|
|
270
|
+
/>
|
|
271
|
+
{:else if fieldType === "text"}
|
|
272
|
+
<FormTextField
|
|
273
|
+
id={fieldKey}
|
|
274
|
+
value={stringValue}
|
|
275
|
+
placeholder={schema.placeholder ?? ""}
|
|
276
|
+
{required}
|
|
277
|
+
ariaDescribedBy={descriptionId}
|
|
278
|
+
onChange={(val) => onChange(val)}
|
|
279
|
+
/>
|
|
280
|
+
{:else if fieldType === "number"}
|
|
281
|
+
<FormNumberField
|
|
282
|
+
id={fieldKey}
|
|
283
|
+
value={numberValue}
|
|
284
|
+
placeholder={schema.placeholder ?? ""}
|
|
285
|
+
min={schema.minimum}
|
|
286
|
+
max={schema.maximum}
|
|
287
|
+
{required}
|
|
288
|
+
ariaDescribedBy={descriptionId}
|
|
289
|
+
onChange={(val) => onChange(val)}
|
|
290
|
+
/>
|
|
291
|
+
{:else if fieldType === "range"}
|
|
292
|
+
<FormRangeField
|
|
293
|
+
id={fieldKey}
|
|
294
|
+
value={numberValue}
|
|
295
|
+
min={schema.minimum}
|
|
296
|
+
max={schema.maximum}
|
|
297
|
+
step={schema.step}
|
|
298
|
+
{required}
|
|
299
|
+
ariaDescribedBy={descriptionId}
|
|
300
|
+
onChange={(val) => onChange(val)}
|
|
301
|
+
/>
|
|
302
|
+
{:else if fieldType === "toggle"}
|
|
303
|
+
<FormToggle
|
|
304
|
+
id={fieldKey}
|
|
305
|
+
value={booleanValue}
|
|
306
|
+
ariaDescribedBy={descriptionId}
|
|
307
|
+
onChange={(val) => onChange(val)}
|
|
308
|
+
/>
|
|
309
|
+
{:else if fieldType === "select-options"}
|
|
310
|
+
<FormSelect
|
|
311
|
+
id={fieldKey}
|
|
312
|
+
value={stringValue}
|
|
313
|
+
options={selectOptions}
|
|
314
|
+
{required}
|
|
315
|
+
ariaDescribedBy={descriptionId}
|
|
316
|
+
onChange={(val) => onChange(val)}
|
|
317
|
+
/>
|
|
318
|
+
{:else if fieldType === "array" && schema.items}
|
|
319
|
+
<FormArray
|
|
320
|
+
id={fieldKey}
|
|
321
|
+
value={arrayItems}
|
|
322
|
+
itemSchema={schema.items}
|
|
323
|
+
minItems={schema.minItems}
|
|
324
|
+
maxItems={schema.maxItems}
|
|
325
|
+
addLabel={`Add ${schema.items.title ?? "Item"}`}
|
|
326
|
+
onChange={(val) => onChange(val)}
|
|
327
|
+
/>
|
|
328
|
+
{:else if fieldType.endsWith("-fallback")}
|
|
329
|
+
<!-- Fallback for unregistered heavy editors -->
|
|
330
|
+
<div class="form-field-fallback">
|
|
331
|
+
<div class="form-field-fallback__message">
|
|
332
|
+
<svg
|
|
333
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
334
|
+
viewBox="0 0 20 20"
|
|
335
|
+
fill="currentColor"
|
|
336
|
+
class="form-field-fallback__icon"
|
|
337
|
+
>
|
|
338
|
+
<path
|
|
339
|
+
fill-rule="evenodd"
|
|
340
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
|
|
341
|
+
clip-rule="evenodd"
|
|
342
|
+
/>
|
|
343
|
+
</svg>
|
|
344
|
+
<span>Editor component not registered</span>
|
|
345
|
+
</div>
|
|
346
|
+
<p class="form-field-fallback__hint">
|
|
347
|
+
{getEditorHint(fieldType)}
|
|
348
|
+
</p>
|
|
349
|
+
<!-- Provide a basic textarea fallback for editing -->
|
|
350
|
+
<FormTextarea
|
|
351
|
+
id={fieldKey}
|
|
352
|
+
value={typeof value === "string" ? value : JSON.stringify(value, null, 2)}
|
|
353
|
+
placeholder={schema.placeholder ?? "Enter value..."}
|
|
354
|
+
{required}
|
|
355
|
+
ariaDescribedBy={descriptionId}
|
|
356
|
+
onChange={(val) => {
|
|
357
|
+
// Try to parse as JSON for object types
|
|
358
|
+
if (schema.type === "object" || schema.format === "json") {
|
|
359
|
+
try {
|
|
360
|
+
onChange(JSON.parse(val));
|
|
361
|
+
} catch {
|
|
362
|
+
onChange(val);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
onChange(val);
|
|
366
|
+
}
|
|
367
|
+
}}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
{:else}
|
|
371
|
+
<!-- Fallback to text input -->
|
|
372
|
+
<FormTextField
|
|
373
|
+
id={fieldKey}
|
|
374
|
+
value={stringValue}
|
|
375
|
+
placeholder={schema.placeholder ?? ""}
|
|
376
|
+
ariaDescribedBy={descriptionId}
|
|
377
|
+
onChange={(val) => onChange(val)}
|
|
378
|
+
/>
|
|
379
|
+
{/if}
|
|
380
|
+
</FormFieldWrapper>
|
|
381
|
+
{/if}
|
|
382
|
+
|
|
383
|
+
<style>
|
|
384
|
+
.form-field-fallback {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-direction: column;
|
|
387
|
+
gap: 0.5rem;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.form-field-fallback__message {
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
gap: 0.5rem;
|
|
394
|
+
padding: 0.5rem 0.75rem;
|
|
395
|
+
background-color: var(--color-ref-amber-50, #fffbeb);
|
|
396
|
+
border: 1px solid var(--color-ref-amber-200, #fde68a);
|
|
397
|
+
border-radius: 0.375rem;
|
|
398
|
+
color: var(--color-ref-amber-800, #92400e);
|
|
399
|
+
font-size: 0.8125rem;
|
|
400
|
+
font-weight: 500;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.form-field-fallback__icon {
|
|
404
|
+
width: 1rem;
|
|
405
|
+
height: 1rem;
|
|
406
|
+
flex-shrink: 0;
|
|
407
|
+
color: var(--color-ref-amber-500, #f59e0b);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.form-field-fallback__hint {
|
|
411
|
+
margin: 0;
|
|
412
|
+
padding: 0.5rem 0.75rem;
|
|
413
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
414
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
415
|
+
border-radius: 0.375rem;
|
|
416
|
+
font-family: "JetBrains Mono", "Fira Code", "Monaco", "Menlo", monospace;
|
|
417
|
+
font-size: 0.6875rem;
|
|
418
|
+
line-height: 1.5;
|
|
419
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
420
|
+
word-break: break-word;
|
|
421
|
+
}
|
|
422
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FieldSchema } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Unique key/id for the field */
|
|
4
|
+
fieldKey: string;
|
|
5
|
+
/** Field schema definition */
|
|
6
|
+
schema: FieldSchema;
|
|
7
|
+
/** Current field value */
|
|
8
|
+
value: unknown;
|
|
9
|
+
/** Whether the field is required */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Animation delay index for staggered animations */
|
|
12
|
+
animationIndex?: number;
|
|
13
|
+
/** Callback when the field value changes */
|
|
14
|
+
onChange: (value: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const FormFieldLight: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type FormFieldLight = ReturnType<typeof FormFieldLight>;
|
|
18
|
+
export default FormFieldLight;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import { Position, Handle } from '@xyflow/svelte';
|
|
9
9
|
import Icon from '@iconify/svelte';
|
|
10
|
-
import { getDataTypeColor } from '../../utils/colors';
|
|
10
|
+
import { getDataTypeColor, getColorVariants } from '../../utils/colors';
|
|
11
11
|
import type { NodeMetadata } from '../../types/index.js';
|
|
12
12
|
|
|
13
13
|
interface ToolNodeParameter {
|
|
@@ -64,6 +64,19 @@
|
|
|
64
64
|
'1.0.0'
|
|
65
65
|
);
|
|
66
66
|
|
|
67
|
+
// Generate color variants for theming (light tint for background, border tint for borders)
|
|
68
|
+
let colorVariants = $derived(getColorVariants(toolColor));
|
|
69
|
+
|
|
70
|
+
// Build inline style string for CSS custom properties
|
|
71
|
+
// This allows per-node color overrides while defaulting to global CSS variables
|
|
72
|
+
let nodeStyle = $derived(
|
|
73
|
+
[
|
|
74
|
+
`--flowdrop-tool-node-color: ${colorVariants.base}`,
|
|
75
|
+
`--flowdrop-tool-node-color-light: ${colorVariants.light}`,
|
|
76
|
+
`--flowdrop-tool-node-color-border: ${colorVariants.border}`
|
|
77
|
+
].join('; ')
|
|
78
|
+
);
|
|
79
|
+
|
|
67
80
|
// Check for tool interface ports in metadata
|
|
68
81
|
let hasToolInputPort = $derived(
|
|
69
82
|
props.data.metadata?.inputs?.some((port) => port.dataType === 'tool') || false
|
|
@@ -128,6 +141,7 @@
|
|
|
128
141
|
class:flowdrop-tool-node--selected={props.selected}
|
|
129
142
|
class:flowdrop-tool-node--processing={props.isProcessing}
|
|
130
143
|
class:flowdrop-tool-node--error={props.isError}
|
|
144
|
+
style={nodeStyle}
|
|
131
145
|
onclick={handleClick}
|
|
132
146
|
ondblclick={handleDoubleClick}
|
|
133
147
|
onkeydown={handleKeydown}
|
|
@@ -138,7 +152,7 @@
|
|
|
138
152
|
<div class="flowdrop-tool-node__header">
|
|
139
153
|
<div class="flowdrop-tool-node__header-content">
|
|
140
154
|
<!-- Tool Icon -->
|
|
141
|
-
<div class="flowdrop-tool-node__icon-container"
|
|
155
|
+
<div class="flowdrop-tool-node__icon-container">
|
|
142
156
|
<Icon icon={toolIcon} class="flowdrop-tool-node__icon" />
|
|
143
157
|
</div>
|
|
144
158
|
|
|
@@ -214,7 +228,7 @@
|
|
|
214
228
|
|
|
215
229
|
.flowdrop-tool-node--selected {
|
|
216
230
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
217
|
-
border: 2px solid
|
|
231
|
+
border: 2px solid var(--flowdrop-tool-node-color);
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
.flowdrop-tool-node--processing {
|
|
@@ -228,9 +242,9 @@
|
|
|
228
242
|
|
|
229
243
|
.flowdrop-tool-node__header {
|
|
230
244
|
padding: 1rem;
|
|
231
|
-
background-color:
|
|
245
|
+
background-color: var(--flowdrop-tool-node-color-light);
|
|
232
246
|
border-radius: 0.75rem;
|
|
233
|
-
border: 1px solid
|
|
247
|
+
border: 1px solid var(--flowdrop-tool-node-color-border);
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
.flowdrop-tool-node__header-content {
|
|
@@ -248,6 +262,7 @@
|
|
|
248
262
|
height: 2.5rem;
|
|
249
263
|
border-radius: 0.5rem;
|
|
250
264
|
flex-shrink: 0;
|
|
265
|
+
background-color: var(--flowdrop-tool-node-color);
|
|
251
266
|
}
|
|
252
267
|
|
|
253
268
|
.flowdrop-tool-node__info {
|
|
@@ -271,7 +286,7 @@
|
|
|
271
286
|
}
|
|
272
287
|
|
|
273
288
|
.flowdrop-tool-node__badge {
|
|
274
|
-
background-color:
|
|
289
|
+
background-color: var(--flowdrop-tool-node-color);
|
|
275
290
|
color: white;
|
|
276
291
|
font-size: 0.625rem;
|
|
277
292
|
font-weight: 700;
|
|
@@ -375,11 +390,11 @@
|
|
|
375
390
|
|
|
376
391
|
/* Metadata port hover effects */
|
|
377
392
|
:global(.svelte-flow__node-tool .svelte-flow__handle:hover) {
|
|
378
|
-
box-shadow: 0 0 0 2px
|
|
393
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--flowdrop-tool-node-color) 30%, transparent) !important;
|
|
379
394
|
}
|
|
380
395
|
|
|
381
396
|
:global(.svelte-flow__node-tool .svelte-flow__handle:focus) {
|
|
382
|
-
outline: 2px solid
|
|
397
|
+
outline: 2px solid var(--flowdrop-tool-node-color) !important;
|
|
383
398
|
outline-offset: 2px !important;
|
|
384
399
|
}
|
|
385
400
|
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowDrop Core Module
|
|
3
|
+
*
|
|
4
|
+
* This module exports types, utilities, and lightweight functionality
|
|
5
|
+
* with zero heavy dependencies. Safe to import without bundling
|
|
6
|
+
* @xyflow/svelte, codemirror, easymde, or marked.
|
|
7
|
+
*
|
|
8
|
+
* @module core
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Import types and utilities without heavy dependencies
|
|
13
|
+
* import type { Workflow, WorkflowNode, NodeMetadata } from "@d34dman/flowdrop/core";
|
|
14
|
+
* import { getStatusColor, createDefaultExecutionInfo } from "@d34dman/flowdrop/core";
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export type { NodeCategory, NodeDataType, NodePort, DynamicPort, Branch, NodeMetadata, NodeExtensions, NodeUIExtensions, ConfigValues, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType, PortConfig, PortCompatibilityRule, ConfigSchema, ConfigProperty, HttpMethod, DynamicSchemaEndpoint, ExternalEditLink, ConfigEditOptions } from "../types/index.js";
|
|
18
|
+
export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig } from "../types/config.js";
|
|
19
|
+
export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from "../types/auth.js";
|
|
20
|
+
export type { WorkflowChangeType, FlowDropEventHandlers, FlowDropFeatures } from "../types/events.js";
|
|
21
|
+
export type { FieldSchema, FieldType, FieldFormat, FieldOption, SchemaFormProps, BaseFieldProps, TextFieldProps, TextareaFieldProps, NumberFieldProps, ToggleFieldProps, RangeFieldProps, SelectFieldProps, CheckboxGroupFieldProps, ArrayFieldProps, CodeEditorFieldProps, MarkdownEditorFieldProps, TemplateEditorFieldProps, FormFieldFactoryProps, FormFieldWrapperProps } from "../components/form/types.js";
|
|
22
|
+
export type { NodeComponentProps, NodeComponentRegistration, NodeComponentCategory, StatusPosition, StatusSize, NodeRegistrationFilter, FlowDropPluginConfig, PluginNodeDefinition, PluginRegistrationResult } from "../registry/index.js";
|
|
23
|
+
export type { ToastType, ToastOptions } from "../services/toastService.js";
|
|
24
|
+
export type { DynamicSchemaResult } from "../services/dynamicSchemaService.js";
|
|
25
|
+
export type { EndpointConfig } from "../config/endpoints.js";
|
|
26
|
+
export type { FlowDropMountOptions, MountedFlowDropApp, NavbarAction } from "../svelte-app.js";
|
|
27
|
+
export { StaticAuthProvider, CallbackAuthProvider, NoAuthProvider } from "../types/auth.js";
|
|
28
|
+
export { DEFAULT_FEATURES, mergeFeatures } from "../types/events.js";
|
|
29
|
+
export { getStatusColor, getStatusIcon, getStatusLabel, getStatusBackgroundColor, getStatusTextColor, createDefaultExecutionInfo, updateExecutionStart, updateExecutionComplete, updateExecutionFailed, resetExecutionInfo, formatExecutionDuration, formatLastExecuted } from "../utils/nodeStatus.js";
|
|
30
|
+
export { createNodeWrapperConfig, shouldShowNodeStatus, getOptimalStatusPosition, getOptimalStatusSize, DEFAULT_NODE_STATUS_CONFIG } from "../utils/nodeWrapper.js";
|
|
31
|
+
export type { NodeStatusConfig } from "../utils/nodeWrapper.js";
|
|
32
|
+
export * from "../utils/colors.js";
|
|
33
|
+
export * from "../utils/icons.js";
|
|
34
|
+
export * from "../utils/config.js";
|
|
35
|
+
export * from "../utils/nodeTypes.js";
|
|
36
|
+
export { isFieldOptionArray, normalizeOptions } from "../components/form/types.js";
|
|
37
|
+
export { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
|
|
38
|
+
export { defaultEndpointConfig, createEndpointConfig } from "../config/endpoints.js";
|
|
39
|
+
export * from "../adapters/WorkflowAdapter.js";
|