@d34dman/flowdrop 0.0.24 → 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/form/FormCodeEditor.svelte +415 -0
- package/dist/components/form/FormCodeEditor.svelte.d.ts +23 -0
- package/dist/components/form/FormField.svelte +124 -73
- 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/FormTemplateEditor.svelte +463 -0
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +25 -0
- package/dist/components/form/index.d.ts +3 -0
- package/dist/components/form/index.js +3 -0
- package/dist/components/form/types.d.ts +54 -1
- package/package.json +9 -3
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormTemplateEditor Component
|
|
3
|
+
CodeMirror-based template editor for Twig/Liquid-style templates
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Custom syntax highlighting for {{ variable }} placeholders
|
|
7
|
+
- Dark/light theme support
|
|
8
|
+
- Consistent styling with other form components
|
|
9
|
+
- Line wrapping for better template readability
|
|
10
|
+
- Optional variable hints display
|
|
11
|
+
- Proper ARIA attributes for accessibility
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
Use with schema format: "template" 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 {
|
|
22
|
+
Decoration,
|
|
23
|
+
type DecorationSet,
|
|
24
|
+
ViewPlugin,
|
|
25
|
+
type ViewUpdate,
|
|
26
|
+
MatchDecorator
|
|
27
|
+
} from '@codemirror/view';
|
|
28
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
/** Field identifier */
|
|
32
|
+
id: string;
|
|
33
|
+
/** Current template value */
|
|
34
|
+
value: string;
|
|
35
|
+
/** Placeholder text shown when empty */
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** Whether the field is required */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
/** Whether to use dark theme */
|
|
40
|
+
darkTheme?: boolean;
|
|
41
|
+
/** Editor height in pixels or CSS value */
|
|
42
|
+
height?: string;
|
|
43
|
+
/** Available variable names for hints (optional) */
|
|
44
|
+
variableHints?: string[];
|
|
45
|
+
/** Placeholder variable example for the hint */
|
|
46
|
+
placeholderExample?: string;
|
|
47
|
+
/** ARIA description ID */
|
|
48
|
+
ariaDescribedBy?: string;
|
|
49
|
+
/** Callback when value changes */
|
|
50
|
+
onChange: (value: string) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let {
|
|
54
|
+
id,
|
|
55
|
+
value = '',
|
|
56
|
+
placeholder = 'Enter your template here...\nUse {{ variable }} for dynamic values.',
|
|
57
|
+
required = false,
|
|
58
|
+
darkTheme = false,
|
|
59
|
+
height = '250px',
|
|
60
|
+
variableHints = [],
|
|
61
|
+
placeholderExample = 'Hello {{ name }}, your order #{{ order_id }} is ready!',
|
|
62
|
+
ariaDescribedBy,
|
|
63
|
+
onChange
|
|
64
|
+
}: Props = $props();
|
|
65
|
+
|
|
66
|
+
/** Reference to the container element */
|
|
67
|
+
let containerRef: HTMLDivElement | undefined = $state(undefined);
|
|
68
|
+
|
|
69
|
+
/** CodeMirror editor instance */
|
|
70
|
+
let editorView: EditorView | undefined = $state(undefined);
|
|
71
|
+
|
|
72
|
+
/** Flag to prevent update loops */
|
|
73
|
+
let isInternalUpdate = false;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a MatchDecorator for {{ variable }} patterns
|
|
77
|
+
* This highlights the entire {{ variable }} expression
|
|
78
|
+
*/
|
|
79
|
+
const variableMatcher = new MatchDecorator({
|
|
80
|
+
// Match {{ variable_name }} patterns (with optional whitespace)
|
|
81
|
+
regexp: /\{\{\s*[\w.]+\s*\}\}/g,
|
|
82
|
+
decoration: Decoration.mark({ class: 'cm-template-variable' })
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ViewPlugin that applies the variable highlighting decorations
|
|
87
|
+
*/
|
|
88
|
+
const variableHighlighter = ViewPlugin.fromClass(
|
|
89
|
+
class {
|
|
90
|
+
decorations: DecorationSet;
|
|
91
|
+
constructor(view: EditorView) {
|
|
92
|
+
this.decorations = variableMatcher.createDeco(view);
|
|
93
|
+
}
|
|
94
|
+
update(update: ViewUpdate) {
|
|
95
|
+
this.decorations = variableMatcher.updateDeco(update, this.decorations);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
decorations: (v) => v.decorations
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle editor content changes
|
|
105
|
+
*/
|
|
106
|
+
function handleUpdate(update: { docChanged: boolean; state: EditorState }): void {
|
|
107
|
+
if (!update.docChanged || isInternalUpdate) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const content = update.state.doc.toString();
|
|
112
|
+
onChange(content);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create editor extensions array for template editing
|
|
117
|
+
*/
|
|
118
|
+
function createExtensions() {
|
|
119
|
+
const extensions = [
|
|
120
|
+
basicSetup,
|
|
121
|
+
variableHighlighter,
|
|
122
|
+
EditorView.updateListener.of(handleUpdate),
|
|
123
|
+
EditorView.theme({
|
|
124
|
+
'&': {
|
|
125
|
+
height: height,
|
|
126
|
+
fontSize: '0.875rem',
|
|
127
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace"
|
|
128
|
+
},
|
|
129
|
+
'.cm-scroller': {
|
|
130
|
+
overflow: 'auto'
|
|
131
|
+
},
|
|
132
|
+
'.cm-content': {
|
|
133
|
+
minHeight: '100px',
|
|
134
|
+
padding: '0.5rem 0'
|
|
135
|
+
},
|
|
136
|
+
'&.cm-focused': {
|
|
137
|
+
outline: 'none'
|
|
138
|
+
},
|
|
139
|
+
'.cm-line': {
|
|
140
|
+
padding: '0 0.5rem'
|
|
141
|
+
},
|
|
142
|
+
// Style for the highlighted {{ variable }} pattern
|
|
143
|
+
'.cm-template-variable': {
|
|
144
|
+
color: '#a855f7',
|
|
145
|
+
backgroundColor: 'rgba(168, 85, 247, 0.1)',
|
|
146
|
+
borderRadius: '3px',
|
|
147
|
+
padding: '1px 2px',
|
|
148
|
+
fontWeight: '500'
|
|
149
|
+
}
|
|
150
|
+
}),
|
|
151
|
+
EditorView.lineWrapping,
|
|
152
|
+
EditorState.tabSize.of(2)
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
if (darkTheme) {
|
|
156
|
+
extensions.push(oneDark);
|
|
157
|
+
// Add dark theme override for variable highlighting
|
|
158
|
+
extensions.push(
|
|
159
|
+
EditorView.theme({
|
|
160
|
+
'.cm-template-variable': {
|
|
161
|
+
color: '#c084fc',
|
|
162
|
+
backgroundColor: 'rgba(192, 132, 252, 0.15)'
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return extensions;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Insert a variable placeholder at current cursor position
|
|
173
|
+
*/
|
|
174
|
+
function insertVariable(varName: string): void {
|
|
175
|
+
if (!editorView) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const insertText = `{{ ${varName} }}`;
|
|
180
|
+
const { from, to } = editorView.state.selection.main;
|
|
181
|
+
|
|
182
|
+
editorView.dispatch({
|
|
183
|
+
changes: { from, to, insert: insertText },
|
|
184
|
+
selection: { anchor: from + insertText.length }
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
editorView.focus();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Initialize CodeMirror editor on mount
|
|
192
|
+
*/
|
|
193
|
+
onMount(() => {
|
|
194
|
+
if (!containerRef) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
editorView = new EditorView({
|
|
199
|
+
state: EditorState.create({
|
|
200
|
+
doc: value,
|
|
201
|
+
extensions: createExtensions()
|
|
202
|
+
}),
|
|
203
|
+
parent: containerRef
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Clean up editor on destroy
|
|
209
|
+
*/
|
|
210
|
+
onDestroy(() => {
|
|
211
|
+
if (editorView) {
|
|
212
|
+
editorView.destroy();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update editor content when value prop changes externally
|
|
218
|
+
*/
|
|
219
|
+
$effect(() => {
|
|
220
|
+
if (!editorView) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const currentContent = editorView.state.doc.toString();
|
|
225
|
+
|
|
226
|
+
// Only update if content actually changed and wasn't from internal edit
|
|
227
|
+
if (value !== currentContent && !isInternalUpdate) {
|
|
228
|
+
isInternalUpdate = true;
|
|
229
|
+
editorView.dispatch({
|
|
230
|
+
changes: {
|
|
231
|
+
from: 0,
|
|
232
|
+
to: editorView.state.doc.length,
|
|
233
|
+
insert: value
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
isInternalUpdate = false;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
</script>
|
|
240
|
+
|
|
241
|
+
<div class="form-template-editor">
|
|
242
|
+
<!-- Hidden input for form submission compatibility -->
|
|
243
|
+
<input
|
|
244
|
+
type="hidden"
|
|
245
|
+
{id}
|
|
246
|
+
name={id}
|
|
247
|
+
{value}
|
|
248
|
+
aria-describedby={ariaDescribedBy}
|
|
249
|
+
aria-required={required}
|
|
250
|
+
/>
|
|
251
|
+
|
|
252
|
+
<!-- CodeMirror container -->
|
|
253
|
+
<div
|
|
254
|
+
bind:this={containerRef}
|
|
255
|
+
class="form-template-editor__container"
|
|
256
|
+
class:form-template-editor__container--dark={darkTheme}
|
|
257
|
+
role="textbox"
|
|
258
|
+
aria-multiline="true"
|
|
259
|
+
aria-label="Template editor"
|
|
260
|
+
></div>
|
|
261
|
+
|
|
262
|
+
<!-- Variable hints section (shown when variables are available) -->
|
|
263
|
+
{#if variableHints.length > 0}
|
|
264
|
+
<div class="form-template-editor__hints">
|
|
265
|
+
<span class="form-template-editor__hints-label">Available variables:</span>
|
|
266
|
+
<div class="form-template-editor__hints-list">
|
|
267
|
+
{#each variableHints as varName (varName)}
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
class="form-template-editor__hint-btn"
|
|
271
|
+
onclick={() => insertVariable(varName)}
|
|
272
|
+
title={`Insert {{ ${varName} }}`}
|
|
273
|
+
>
|
|
274
|
+
<code>{'{{ '}{varName}{' }}'}</code>
|
|
275
|
+
</button>
|
|
276
|
+
{/each}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
{/if}
|
|
280
|
+
|
|
281
|
+
<!-- Placeholder hint when empty -->
|
|
282
|
+
{#if !value && placeholderExample}
|
|
283
|
+
<div class="form-template-editor__placeholder">
|
|
284
|
+
<span class="form-template-editor__placeholder-label">Example template:</span>
|
|
285
|
+
<code class="form-template-editor__placeholder-example">{placeholderExample}</code>
|
|
286
|
+
</div>
|
|
287
|
+
{/if}
|
|
288
|
+
|
|
289
|
+
<!-- Syntax help -->
|
|
290
|
+
<div class="form-template-editor__help">
|
|
291
|
+
<svg
|
|
292
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
293
|
+
viewBox="0 0 20 20"
|
|
294
|
+
fill="currentColor"
|
|
295
|
+
class="form-template-editor__help-icon"
|
|
296
|
+
>
|
|
297
|
+
<path
|
|
298
|
+
fill-rule="evenodd"
|
|
299
|
+
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"
|
|
300
|
+
clip-rule="evenodd"
|
|
301
|
+
/>
|
|
302
|
+
</svg>
|
|
303
|
+
<span
|
|
304
|
+
>Use <code>{'{{ variable }}'}</code> syntax to insert dynamic values from the data input</span
|
|
305
|
+
>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<style>
|
|
310
|
+
.form-template-editor {
|
|
311
|
+
position: relative;
|
|
312
|
+
width: 100%;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.form-template-editor__container {
|
|
316
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
317
|
+
border-radius: 0.5rem;
|
|
318
|
+
overflow: hidden;
|
|
319
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
320
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
321
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.form-template-editor__container:hover {
|
|
325
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
326
|
+
background-color: #ffffff;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.form-template-editor__container:focus-within {
|
|
330
|
+
border-color: var(--color-ref-purple-500, #a855f7);
|
|
331
|
+
background-color: #ffffff;
|
|
332
|
+
box-shadow:
|
|
333
|
+
0 0 0 3px rgba(168, 85, 247, 0.12),
|
|
334
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Dark theme overrides */
|
|
338
|
+
.form-template-editor__container--dark {
|
|
339
|
+
background-color: #282c34;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.form-template-editor__container--dark:hover,
|
|
343
|
+
.form-template-editor__container--dark:focus-within {
|
|
344
|
+
background-color: #282c34;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* CodeMirror styling overrides */
|
|
348
|
+
.form-template-editor__container :global(.cm-editor) {
|
|
349
|
+
border-radius: 0.5rem;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.form-template-editor__container :global(.cm-gutters) {
|
|
353
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
354
|
+
border-right: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
355
|
+
border-radius: 0.5rem 0 0 0.5rem;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.form-template-editor__container--dark :global(.cm-gutters) {
|
|
359
|
+
background-color: #21252b;
|
|
360
|
+
border-right-color: #3e4451;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* Variable hints section */
|
|
364
|
+
.form-template-editor__hints {
|
|
365
|
+
margin-top: 0.625rem;
|
|
366
|
+
padding: 0.625rem;
|
|
367
|
+
background-color: var(--color-ref-purple-50, #faf5ff);
|
|
368
|
+
border: 1px solid var(--color-ref-purple-200, #e9d5ff);
|
|
369
|
+
border-radius: 0.375rem;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.form-template-editor__hints-label {
|
|
373
|
+
display: block;
|
|
374
|
+
font-size: 0.6875rem;
|
|
375
|
+
font-weight: 500;
|
|
376
|
+
color: var(--color-ref-purple-700, #7e22ce);
|
|
377
|
+
text-transform: uppercase;
|
|
378
|
+
letter-spacing: 0.05em;
|
|
379
|
+
margin-bottom: 0.375rem;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.form-template-editor__hints-list {
|
|
383
|
+
display: flex;
|
|
384
|
+
flex-wrap: wrap;
|
|
385
|
+
gap: 0.375rem;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.form-template-editor__hint-btn {
|
|
389
|
+
padding: 0.25rem 0.5rem;
|
|
390
|
+
background-color: var(--color-ref-purple-100, #f3e8ff);
|
|
391
|
+
border: 1px solid var(--color-ref-purple-300, #d8b4fe);
|
|
392
|
+
border-radius: 0.25rem;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
transition: all 0.15s ease;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.form-template-editor__hint-btn:hover {
|
|
398
|
+
background-color: var(--color-ref-purple-200, #e9d5ff);
|
|
399
|
+
border-color: var(--color-ref-purple-400, #c084fc);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.form-template-editor__hint-btn:active {
|
|
403
|
+
transform: scale(0.98);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.form-template-editor__hint-btn code {
|
|
407
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
408
|
+
font-size: 0.6875rem;
|
|
409
|
+
color: var(--color-ref-purple-800, #6b21a8);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* Placeholder hint */
|
|
413
|
+
.form-template-editor__placeholder {
|
|
414
|
+
margin-top: 0.5rem;
|
|
415
|
+
padding: 0.5rem 0.75rem;
|
|
416
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
417
|
+
border: 1px dashed var(--color-ref-gray-300, #d1d5db);
|
|
418
|
+
border-radius: 0.375rem;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.form-template-editor__placeholder-label {
|
|
422
|
+
display: block;
|
|
423
|
+
font-size: 0.6875rem;
|
|
424
|
+
font-weight: 500;
|
|
425
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
426
|
+
text-transform: uppercase;
|
|
427
|
+
letter-spacing: 0.05em;
|
|
428
|
+
margin-bottom: 0.25rem;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.form-template-editor__placeholder-example {
|
|
432
|
+
display: block;
|
|
433
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
434
|
+
font-size: 0.75rem;
|
|
435
|
+
color: var(--color-ref-gray-700, #374151);
|
|
436
|
+
word-break: break-all;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Help text */
|
|
440
|
+
.form-template-editor__help {
|
|
441
|
+
display: flex;
|
|
442
|
+
align-items: flex-start;
|
|
443
|
+
gap: 0.375rem;
|
|
444
|
+
margin-top: 0.5rem;
|
|
445
|
+
font-size: 0.6875rem;
|
|
446
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.form-template-editor__help-icon {
|
|
450
|
+
width: 0.875rem;
|
|
451
|
+
height: 0.875rem;
|
|
452
|
+
flex-shrink: 0;
|
|
453
|
+
margin-top: 0.0625rem;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.form-template-editor__help code {
|
|
457
|
+
padding: 0.0625rem 0.25rem;
|
|
458
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
459
|
+
border-radius: 0.1875rem;
|
|
460
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
461
|
+
font-size: 0.625rem;
|
|
462
|
+
}
|
|
463
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current template value */
|
|
5
|
+
value: string;
|
|
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
|
+
/** Available variable names for hints (optional) */
|
|
15
|
+
variableHints?: string[];
|
|
16
|
+
/** Placeholder variable example for the hint */
|
|
17
|
+
placeholderExample?: string;
|
|
18
|
+
/** ARIA description ID */
|
|
19
|
+
ariaDescribedBy?: string;
|
|
20
|
+
/** Callback when value changes */
|
|
21
|
+
onChange: (value: string) => void;
|
|
22
|
+
}
|
|
23
|
+
declare const FormTemplateEditor: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type FormTemplateEditor = ReturnType<typeof FormTemplateEditor>;
|
|
25
|
+
export default FormTemplateEditor;
|
|
@@ -40,3 +40,6 @@ export { default as FormToggle } from "./FormToggle.svelte";
|
|
|
40
40
|
export { default as FormSelect } from "./FormSelect.svelte";
|
|
41
41
|
export { default as FormCheckboxGroup } from "./FormCheckboxGroup.svelte";
|
|
42
42
|
export { default as FormArray } from "./FormArray.svelte";
|
|
43
|
+
export { default as FormCodeEditor } from "./FormCodeEditor.svelte";
|
|
44
|
+
export { default as FormMarkdownEditor } from "./FormMarkdownEditor.svelte";
|
|
45
|
+
export { default as FormTemplateEditor } from "./FormTemplateEditor.svelte";
|
|
@@ -44,3 +44,6 @@ export { default as FormToggle } from "./FormToggle.svelte";
|
|
|
44
44
|
export { default as FormSelect } from "./FormSelect.svelte";
|
|
45
45
|
export { default as FormCheckboxGroup } from "./FormCheckboxGroup.svelte";
|
|
46
46
|
export { default as FormArray } from "./FormArray.svelte";
|
|
47
|
+
export { default as FormCodeEditor } from "./FormCodeEditor.svelte";
|
|
48
|
+
export { default as FormMarkdownEditor } from "./FormMarkdownEditor.svelte";
|
|
49
|
+
export { default as FormTemplateEditor } from "./FormTemplateEditor.svelte";
|
|
@@ -15,8 +15,12 @@ export type FieldType = "string" | "number" | "integer" | "boolean" | "select" |
|
|
|
15
15
|
* - multiline: Renders as textarea
|
|
16
16
|
* - hidden: Field is hidden from UI but included in form submission
|
|
17
17
|
* - range: Renders as range slider for numeric values
|
|
18
|
+
* - json: Renders as CodeMirror JSON editor
|
|
19
|
+
* - code: Alias for json, renders as CodeMirror editor
|
|
20
|
+
* - markdown: Renders as SimpleMDE Markdown editor
|
|
21
|
+
* - template: Renders as CodeMirror editor with Twig/Liquid syntax highlighting
|
|
18
22
|
*/
|
|
19
|
-
export type FieldFormat = "multiline" | "hidden" | "range" | string;
|
|
23
|
+
export type FieldFormat = "multiline" | "hidden" | "range" | "json" | "code" | "markdown" | "template" | string;
|
|
20
24
|
/**
|
|
21
25
|
* Option type for select and checkbox group fields
|
|
22
26
|
*/
|
|
@@ -131,6 +135,55 @@ export interface ArrayFieldProps extends BaseFieldProps {
|
|
|
131
135
|
addLabel?: string;
|
|
132
136
|
onChange: (value: unknown[]) => void;
|
|
133
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Properties for code editor fields (CodeMirror-based)
|
|
140
|
+
*/
|
|
141
|
+
export interface CodeEditorFieldProps extends BaseFieldProps {
|
|
142
|
+
/** Current value - can be string (raw JSON) or object */
|
|
143
|
+
value: unknown;
|
|
144
|
+
/** Whether to use dark theme */
|
|
145
|
+
darkTheme?: boolean;
|
|
146
|
+
/** Editor height in pixels or CSS value */
|
|
147
|
+
height?: string;
|
|
148
|
+
/** Whether to auto-format JSON on blur */
|
|
149
|
+
autoFormat?: boolean;
|
|
150
|
+
/** Callback when value changes */
|
|
151
|
+
onChange: (value: unknown) => void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Properties for markdown editor fields (SimpleMDE-based)
|
|
155
|
+
*/
|
|
156
|
+
export interface MarkdownEditorFieldProps extends BaseFieldProps {
|
|
157
|
+
/** Current value (markdown string) */
|
|
158
|
+
value: string;
|
|
159
|
+
/** Editor height in pixels or CSS value */
|
|
160
|
+
height?: string;
|
|
161
|
+
/** Whether to show the toolbar */
|
|
162
|
+
showToolbar?: boolean;
|
|
163
|
+
/** Whether to show the status bar */
|
|
164
|
+
showStatusBar?: boolean;
|
|
165
|
+
/** Whether to enable spell checking */
|
|
166
|
+
spellChecker?: boolean;
|
|
167
|
+
/** Callback when value changes */
|
|
168
|
+
onChange: (value: string) => void;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Properties for template editor fields (CodeMirror with Twig/Liquid syntax)
|
|
172
|
+
*/
|
|
173
|
+
export interface TemplateEditorFieldProps extends BaseFieldProps {
|
|
174
|
+
/** Current template value */
|
|
175
|
+
value: string;
|
|
176
|
+
/** Whether to use dark theme */
|
|
177
|
+
darkTheme?: boolean;
|
|
178
|
+
/** Editor height in pixels or CSS value */
|
|
179
|
+
height?: string;
|
|
180
|
+
/** Available variable names for hints (optional) */
|
|
181
|
+
variableHints?: string[];
|
|
182
|
+
/** Placeholder variable example for the hint */
|
|
183
|
+
placeholderExample?: string;
|
|
184
|
+
/** Callback when value changes */
|
|
185
|
+
onChange: (value: string) => void;
|
|
186
|
+
}
|
|
134
187
|
/**
|
|
135
188
|
* Field schema definition derived from JSON Schema property
|
|
136
189
|
* Used to determine which field component to render
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@d34dman/flowdrop",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.25",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite dev",
|
|
8
8
|
"build": "vite build && npm run prepack",
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
105
105
|
"@types/marked": "^6.0.0",
|
|
106
106
|
"@types/node": "^20",
|
|
107
|
+
"@types/uuid": "^10.0.0",
|
|
107
108
|
"@vitest/browser": "^3.2.3",
|
|
108
109
|
"eslint": "^9.18.0",
|
|
109
110
|
"eslint-config-prettier": "^10.0.1",
|
|
@@ -125,8 +126,7 @@
|
|
|
125
126
|
"vite": "^6.2.6",
|
|
126
127
|
"vite-plugin-devtools-json": "^0.2.1",
|
|
127
128
|
"vitest": "^3.2.3",
|
|
128
|
-
"vitest-browser-svelte": "^0.1.0"
|
|
129
|
-
"@types/uuid": "^10.0.0"
|
|
129
|
+
"vitest-browser-svelte": "^0.1.0"
|
|
130
130
|
},
|
|
131
131
|
"overrides": {
|
|
132
132
|
"@sveltejs/kit": {
|
|
@@ -137,7 +137,13 @@
|
|
|
137
137
|
"svelte"
|
|
138
138
|
],
|
|
139
139
|
"dependencies": {
|
|
140
|
+
"@codemirror/autocomplete": "^6.20.0",
|
|
141
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
142
|
+
"@codemirror/lint": "^6.9.2",
|
|
143
|
+
"@codemirror/theme-one-dark": "^6.1.3",
|
|
140
144
|
"@xyflow/svelte": "~1.2",
|
|
145
|
+
"codemirror": "^6.0.2",
|
|
146
|
+
"easymde": "^2.20.0",
|
|
141
147
|
"marked": "^16.1.1",
|
|
142
148
|
"svelte-5-french-toast": "^2.0.6",
|
|
143
149
|
"uuid": "^11.1.0"
|