@d34dman/flowdrop 0.0.23 → 0.0.25
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/ConfigForm.svelte +1 -1
- package/dist/components/form/FormCodeEditor.svelte +415 -0
- package/dist/components/form/FormCodeEditor.svelte.d.ts +23 -0
- package/dist/components/form/FormField.svelte +137 -68
- package/dist/components/form/FormField.svelte.d.ts +1 -1
- package/dist/components/form/FormMarkdownEditor.svelte +553 -0
- package/dist/components/form/FormMarkdownEditor.svelte.d.ts +29 -0
- package/dist/components/form/FormRangeField.svelte +252 -0
- package/dist/components/form/FormRangeField.svelte.d.ts +21 -0
- package/dist/components/form/FormTemplateEditor.svelte +463 -0
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +25 -0
- package/dist/components/form/index.d.ts +4 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/form/types.d.ts +70 -1
- package/dist/components/nodes/GatewayNode.svelte +1 -13
- package/dist/index.d.ts +1 -1
- package/dist/types/index.d.ts +99 -1
- package/package.json +9 -3
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
if (inputEl.id && !inputEl.id.startsWith('ext-')) {
|
|
132
132
|
if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
|
|
133
133
|
updatedConfig[inputEl.id] = inputEl.checked;
|
|
134
|
-
} else if (inputEl instanceof HTMLInputElement && inputEl.type === 'number') {
|
|
134
|
+
} else if (inputEl instanceof HTMLInputElement && (inputEl.type === 'number' || inputEl.type === 'range')) {
|
|
135
135
|
updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
|
|
136
136
|
} else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
|
|
137
137
|
// Parse hidden field values that might be JSON
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormCodeEditor Component
|
|
3
|
+
CodeMirror-based code editor for JSON and other structured data
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- JSON syntax highlighting with CodeMirror 6
|
|
7
|
+
- Real-time JSON validation with error display
|
|
8
|
+
- Auto-formatting on blur (optional)
|
|
9
|
+
- Dark/light theme support
|
|
10
|
+
- Consistent styling with other form components
|
|
11
|
+
- Proper ARIA attributes for accessibility
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
Use with schema format: "json" or format: "code" to render this editor
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import { onMount, onDestroy } from 'svelte';
|
|
19
|
+
import { EditorView, basicSetup } from 'codemirror';
|
|
20
|
+
import { EditorState } from '@codemirror/state';
|
|
21
|
+
import { json, jsonParseLinter } from '@codemirror/lang-json';
|
|
22
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
23
|
+
import { linter, lintGutter } from '@codemirror/lint';
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** Field identifier */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Current value - can be string (raw JSON) or object */
|
|
29
|
+
value: unknown;
|
|
30
|
+
/** Placeholder text shown when empty */
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
/** Whether the field is required */
|
|
33
|
+
required?: boolean;
|
|
34
|
+
/** Whether to use dark theme */
|
|
35
|
+
darkTheme?: boolean;
|
|
36
|
+
/** Editor height in pixels or CSS value */
|
|
37
|
+
height?: string;
|
|
38
|
+
/** Whether to auto-format JSON on blur */
|
|
39
|
+
autoFormat?: boolean;
|
|
40
|
+
/** ARIA description ID */
|
|
41
|
+
ariaDescribedBy?: string;
|
|
42
|
+
/** Callback when value changes */
|
|
43
|
+
onChange: (value: unknown) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
id,
|
|
48
|
+
value = '',
|
|
49
|
+
placeholder = '{}',
|
|
50
|
+
required = false,
|
|
51
|
+
darkTheme = false,
|
|
52
|
+
height = '200px',
|
|
53
|
+
autoFormat = true,
|
|
54
|
+
ariaDescribedBy,
|
|
55
|
+
onChange
|
|
56
|
+
}: Props = $props();
|
|
57
|
+
|
|
58
|
+
/** Reference to the container element */
|
|
59
|
+
let containerRef: HTMLDivElement | undefined = $state(undefined);
|
|
60
|
+
|
|
61
|
+
/** CodeMirror editor instance */
|
|
62
|
+
let editorView: EditorView | undefined = $state(undefined);
|
|
63
|
+
|
|
64
|
+
/** Current validation error message */
|
|
65
|
+
let validationError: string | undefined = $state(undefined);
|
|
66
|
+
|
|
67
|
+
/** Flag to prevent update loops */
|
|
68
|
+
let isInternalUpdate = false;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert value to JSON string for editor display
|
|
72
|
+
*/
|
|
73
|
+
function valueToString(val: unknown): string {
|
|
74
|
+
if (val === undefined || val === null) {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
if (typeof val === 'string') {
|
|
78
|
+
// Check if it's already a valid JSON string
|
|
79
|
+
try {
|
|
80
|
+
JSON.parse(val);
|
|
81
|
+
return val;
|
|
82
|
+
} catch {
|
|
83
|
+
// Not valid JSON, return as-is
|
|
84
|
+
return val;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Convert object to formatted JSON string
|
|
88
|
+
return JSON.stringify(val, null, 2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate JSON and return parsed value or undefined if invalid
|
|
93
|
+
*/
|
|
94
|
+
function validateAndParse(content: string): { valid: boolean; value?: unknown; error?: string } {
|
|
95
|
+
if (!content.trim()) {
|
|
96
|
+
return { valid: true, value: undefined };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(content);
|
|
101
|
+
return { valid: true, value: parsed };
|
|
102
|
+
} catch (e) {
|
|
103
|
+
const error = e instanceof Error ? e.message : 'Invalid JSON';
|
|
104
|
+
return { valid: false, error };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handle editor content changes
|
|
110
|
+
*/
|
|
111
|
+
function handleUpdate(update: { docChanged: boolean; state: EditorState }): void {
|
|
112
|
+
if (!update.docChanged || isInternalUpdate) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const content = update.state.doc.toString();
|
|
117
|
+
const result = validateAndParse(content);
|
|
118
|
+
|
|
119
|
+
if (result.valid) {
|
|
120
|
+
validationError = undefined;
|
|
121
|
+
// Emit the parsed value (object) not the string
|
|
122
|
+
onChange(result.value);
|
|
123
|
+
} else {
|
|
124
|
+
validationError = result.error;
|
|
125
|
+
// Still emit the raw string so user can continue editing
|
|
126
|
+
onChange(content);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Format JSON content (used on blur if autoFormat is enabled)
|
|
132
|
+
*/
|
|
133
|
+
function formatContent(): void {
|
|
134
|
+
if (!editorView || !autoFormat) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const content = editorView.state.doc.toString();
|
|
139
|
+
const result = validateAndParse(content);
|
|
140
|
+
|
|
141
|
+
if (result.valid && result.value !== undefined) {
|
|
142
|
+
const formatted = JSON.stringify(result.value, null, 2);
|
|
143
|
+
if (formatted !== content) {
|
|
144
|
+
isInternalUpdate = true;
|
|
145
|
+
editorView.dispatch({
|
|
146
|
+
changes: {
|
|
147
|
+
from: 0,
|
|
148
|
+
to: editorView.state.doc.length,
|
|
149
|
+
insert: formatted
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
isInternalUpdate = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create editor extensions array
|
|
159
|
+
*/
|
|
160
|
+
function createExtensions() {
|
|
161
|
+
const extensions = [
|
|
162
|
+
basicSetup,
|
|
163
|
+
json(),
|
|
164
|
+
linter(jsonParseLinter()),
|
|
165
|
+
lintGutter(),
|
|
166
|
+
EditorView.updateListener.of(handleUpdate),
|
|
167
|
+
EditorView.theme({
|
|
168
|
+
'&': {
|
|
169
|
+
height: height,
|
|
170
|
+
fontSize: '0.8125rem',
|
|
171
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace"
|
|
172
|
+
},
|
|
173
|
+
'.cm-scroller': {
|
|
174
|
+
overflow: 'auto'
|
|
175
|
+
},
|
|
176
|
+
'.cm-content': {
|
|
177
|
+
minHeight: '100px'
|
|
178
|
+
},
|
|
179
|
+
'&.cm-focused': {
|
|
180
|
+
outline: 'none'
|
|
181
|
+
}
|
|
182
|
+
}),
|
|
183
|
+
EditorView.lineWrapping
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
if (darkTheme) {
|
|
187
|
+
extensions.push(oneDark);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return extensions;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Initialize CodeMirror editor on mount
|
|
195
|
+
*/
|
|
196
|
+
onMount(() => {
|
|
197
|
+
if (!containerRef) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const initialContent = valueToString(value);
|
|
202
|
+
|
|
203
|
+
editorView = new EditorView({
|
|
204
|
+
state: EditorState.create({
|
|
205
|
+
doc: initialContent,
|
|
206
|
+
extensions: createExtensions()
|
|
207
|
+
}),
|
|
208
|
+
parent: containerRef
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Validate initial content
|
|
212
|
+
if (initialContent) {
|
|
213
|
+
const result = validateAndParse(initialContent);
|
|
214
|
+
if (!result.valid) {
|
|
215
|
+
validationError = result.error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Clean up editor on destroy
|
|
222
|
+
*/
|
|
223
|
+
onDestroy(() => {
|
|
224
|
+
if (editorView) {
|
|
225
|
+
editorView.destroy();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Update editor content when value prop changes externally
|
|
231
|
+
*/
|
|
232
|
+
$effect(() => {
|
|
233
|
+
if (!editorView) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const newContent = valueToString(value);
|
|
238
|
+
const currentContent = editorView.state.doc.toString();
|
|
239
|
+
|
|
240
|
+
// Only update if content actually changed and wasn't from internal edit
|
|
241
|
+
if (newContent !== currentContent && !isInternalUpdate) {
|
|
242
|
+
isInternalUpdate = true;
|
|
243
|
+
editorView.dispatch({
|
|
244
|
+
changes: {
|
|
245
|
+
from: 0,
|
|
246
|
+
to: editorView.state.doc.length,
|
|
247
|
+
insert: newContent
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
isInternalUpdate = false;
|
|
251
|
+
|
|
252
|
+
// Validate new content
|
|
253
|
+
const result = validateAndParse(newContent);
|
|
254
|
+
validationError = result.valid ? undefined : result.error;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
</script>
|
|
258
|
+
|
|
259
|
+
<div class="form-code-editor" class:form-code-editor--error={validationError}>
|
|
260
|
+
<!-- Hidden input for form submission compatibility -->
|
|
261
|
+
<input
|
|
262
|
+
type="hidden"
|
|
263
|
+
{id}
|
|
264
|
+
name={id}
|
|
265
|
+
value={typeof value === 'string' ? value : JSON.stringify(value)}
|
|
266
|
+
aria-describedby={ariaDescribedBy}
|
|
267
|
+
aria-required={required}
|
|
268
|
+
/>
|
|
269
|
+
|
|
270
|
+
<!-- CodeMirror container -->
|
|
271
|
+
<div
|
|
272
|
+
bind:this={containerRef}
|
|
273
|
+
class="form-code-editor__container"
|
|
274
|
+
class:form-code-editor__container--dark={darkTheme}
|
|
275
|
+
role="textbox"
|
|
276
|
+
aria-multiline="true"
|
|
277
|
+
aria-label="JSON editor"
|
|
278
|
+
onblur={formatContent}
|
|
279
|
+
></div>
|
|
280
|
+
|
|
281
|
+
<!-- Validation error display -->
|
|
282
|
+
{#if validationError}
|
|
283
|
+
<div class="form-code-editor__error" role="alert">
|
|
284
|
+
<svg
|
|
285
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
286
|
+
viewBox="0 0 20 20"
|
|
287
|
+
fill="currentColor"
|
|
288
|
+
class="form-code-editor__error-icon"
|
|
289
|
+
>
|
|
290
|
+
<path
|
|
291
|
+
fill-rule="evenodd"
|
|
292
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
|
|
293
|
+
clip-rule="evenodd"
|
|
294
|
+
/>
|
|
295
|
+
</svg>
|
|
296
|
+
<span>{validationError}</span>
|
|
297
|
+
</div>
|
|
298
|
+
{/if}
|
|
299
|
+
|
|
300
|
+
<!-- Placeholder hint when empty -->
|
|
301
|
+
{#if !value && placeholder}
|
|
302
|
+
<div class="form-code-editor__placeholder">
|
|
303
|
+
Start typing or paste JSON. Example: <code>{placeholder}</code>
|
|
304
|
+
</div>
|
|
305
|
+
{/if}
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<style>
|
|
309
|
+
.form-code-editor {
|
|
310
|
+
position: relative;
|
|
311
|
+
width: 100%;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.form-code-editor__container {
|
|
315
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
316
|
+
border-radius: 0.5rem;
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
319
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
320
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.form-code-editor__container:hover {
|
|
324
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
325
|
+
background-color: #ffffff;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.form-code-editor__container:focus-within {
|
|
329
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
330
|
+
background-color: #ffffff;
|
|
331
|
+
box-shadow:
|
|
332
|
+
0 0 0 3px rgba(59, 130, 246, 0.12),
|
|
333
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.form-code-editor--error .form-code-editor__container {
|
|
337
|
+
border-color: var(--color-ref-red-400, #f87171);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.form-code-editor--error .form-code-editor__container:focus-within {
|
|
341
|
+
border-color: var(--color-ref-red-500, #ef4444);
|
|
342
|
+
box-shadow:
|
|
343
|
+
0 0 0 3px rgba(239, 68, 68, 0.12),
|
|
344
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* Dark theme overrides */
|
|
348
|
+
.form-code-editor__container--dark {
|
|
349
|
+
background-color: #282c34;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.form-code-editor__container--dark:hover,
|
|
353
|
+
.form-code-editor__container--dark:focus-within {
|
|
354
|
+
background-color: #282c34;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* CodeMirror styling overrides */
|
|
358
|
+
.form-code-editor__container :global(.cm-editor) {
|
|
359
|
+
border-radius: 0.5rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.form-code-editor__container :global(.cm-gutters) {
|
|
363
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
364
|
+
border-right: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
365
|
+
border-radius: 0.5rem 0 0 0.5rem;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.form-code-editor__container--dark :global(.cm-gutters) {
|
|
369
|
+
background-color: #21252b;
|
|
370
|
+
border-right-color: #3e4451;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Error message */
|
|
374
|
+
.form-code-editor__error {
|
|
375
|
+
display: flex;
|
|
376
|
+
align-items: flex-start;
|
|
377
|
+
gap: 0.375rem;
|
|
378
|
+
margin-top: 0.5rem;
|
|
379
|
+
padding: 0.5rem 0.75rem;
|
|
380
|
+
background-color: var(--color-ref-red-50, #fef2f2);
|
|
381
|
+
border: 1px solid var(--color-ref-red-200, #fecaca);
|
|
382
|
+
border-radius: 0.375rem;
|
|
383
|
+
color: var(--color-ref-red-700, #b91c1c);
|
|
384
|
+
font-size: 0.75rem;
|
|
385
|
+
line-height: 1.4;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.form-code-editor__error-icon {
|
|
389
|
+
width: 1rem;
|
|
390
|
+
height: 1rem;
|
|
391
|
+
flex-shrink: 0;
|
|
392
|
+
margin-top: 0.0625rem;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.form-code-editor__error span {
|
|
396
|
+
word-break: break-word;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Placeholder hint */
|
|
400
|
+
.form-code-editor__placeholder {
|
|
401
|
+
margin-top: 0.5rem;
|
|
402
|
+
font-size: 0.75rem;
|
|
403
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
404
|
+
font-style: italic;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.form-code-editor__placeholder code {
|
|
408
|
+
padding: 0.125rem 0.375rem;
|
|
409
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
410
|
+
border-radius: 0.25rem;
|
|
411
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
412
|
+
font-size: 0.6875rem;
|
|
413
|
+
font-style: normal;
|
|
414
|
+
}
|
|
415
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current value - can be string (raw JSON) or object */
|
|
5
|
+
value: unknown;
|
|
6
|
+
/** Placeholder text shown when empty */
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Whether the field is required */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** Whether to use dark theme */
|
|
11
|
+
darkTheme?: boolean;
|
|
12
|
+
/** Editor height in pixels or CSS value */
|
|
13
|
+
height?: string;
|
|
14
|
+
/** Whether to auto-format JSON on blur */
|
|
15
|
+
autoFormat?: boolean;
|
|
16
|
+
/** ARIA description ID */
|
|
17
|
+
ariaDescribedBy?: string;
|
|
18
|
+
/** Callback when value changes */
|
|
19
|
+
onChange: (value: unknown) => void;
|
|
20
|
+
}
|
|
21
|
+
declare const FormCodeEditor: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type FormCodeEditor = ReturnType<typeof FormCodeEditor>;
|
|
23
|
+
export default FormCodeEditor;
|