@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.
@@ -5,33 +5,40 @@
5
5
  Features:
6
6
  - Automatically selects the correct field component based on schema
7
7
  - Wraps fields with FormFieldWrapper for consistent layout
8
- - Supports all current field types (string, number, boolean, select, checkbox group, range)
8
+ - Supports all current field types (string, number, boolean, select, checkbox group, range, json, markdown, template)
9
9
  - Extensible architecture for future complex types (array, object)
10
10
 
11
11
  Type Resolution Order:
12
12
  1. format: 'hidden' -> skip rendering (return nothing)
13
- 2. enum with multiple: true -> FormCheckboxGroup
14
- 3. enum -> FormSelect
15
- 4. format: 'multiline' -> FormTextarea
16
- 5. format: 'range' (number/integer) -> FormRangeField
17
- 6. type: 'string' -> FormTextField
18
- 7. type: 'number' or 'integer' -> FormNumberField
19
- 8. type: 'boolean' -> FormToggle
20
- 9. type: 'select' or has options -> FormSelect
21
- 10. fallback -> FormTextField
13
+ 2. format: 'json' or 'code' -> FormCodeEditor (CodeMirror JSON editor)
14
+ 3. format: 'markdown' -> FormMarkdownEditor (SimpleMDE Markdown editor)
15
+ 4. format: 'template' -> FormTemplateEditor (CodeMirror with Twig/Liquid syntax)
16
+ 5. enum with multiple: true -> FormCheckboxGroup
17
+ 6. enum -> FormSelect
18
+ 7. format: 'multiline' -> FormTextarea
19
+ 8. format: 'range' (number/integer) -> FormRangeField
20
+ 9. type: 'string' -> FormTextField
21
+ 10. type: 'number' or 'integer' -> FormNumberField
22
+ 11. type: 'boolean' -> FormToggle
23
+ 12. type: 'select' or has options -> FormSelect
24
+ 13. type: 'object' (without format) -> FormCodeEditor (for JSON objects)
25
+ 14. fallback -> FormTextField
22
26
  -->
23
27
 
24
28
  <script lang="ts">
25
- import FormFieldWrapper from "./FormFieldWrapper.svelte";
26
- import FormTextField from "./FormTextField.svelte";
27
- import FormTextarea from "./FormTextarea.svelte";
28
- import FormNumberField from "./FormNumberField.svelte";
29
- import FormRangeField from "./FormRangeField.svelte";
30
- import FormToggle from "./FormToggle.svelte";
31
- import FormSelect from "./FormSelect.svelte";
32
- import FormCheckboxGroup from "./FormCheckboxGroup.svelte";
33
- import FormArray from "./FormArray.svelte";
34
- import type { FieldSchema, FieldOption } from "./types.js";
29
+ import FormFieldWrapper from './FormFieldWrapper.svelte';
30
+ import FormTextField from './FormTextField.svelte';
31
+ import FormTextarea from './FormTextarea.svelte';
32
+ import FormNumberField from './FormNumberField.svelte';
33
+ import FormRangeField from './FormRangeField.svelte';
34
+ import FormToggle from './FormToggle.svelte';
35
+ import FormSelect from './FormSelect.svelte';
36
+ import FormCheckboxGroup from './FormCheckboxGroup.svelte';
37
+ import FormArray from './FormArray.svelte';
38
+ import FormCodeEditor from './FormCodeEditor.svelte';
39
+ import FormMarkdownEditor from './FormMarkdownEditor.svelte';
40
+ import FormTemplateEditor from './FormTemplateEditor.svelte';
41
+ import type { FieldSchema, FieldOption } from './types.js';
35
42
 
36
43
  interface Props {
37
44
  /** Unique key/id for the field */
@@ -48,19 +55,14 @@
48
55
  onChange: (value: unknown) => void;
49
56
  }
50
57
 
51
- let {
52
- fieldKey,
53
- schema,
54
- value,
55
- required = false,
56
- animationIndex = 0,
57
- onChange
58
- }: Props = $props();
58
+ let { fieldKey, schema, value, required = false, animationIndex = 0, onChange }: Props = $props();
59
59
 
60
60
  /**
61
61
  * Computed description ID for ARIA association
62
62
  */
63
- const descriptionId = $derived(schema.description && schema.title ? `${fieldKey}-description` : undefined);
63
+ const descriptionId = $derived(
64
+ schema.description && schema.title ? `${fieldKey}-description` : undefined
65
+ );
64
66
 
65
67
  /**
66
68
  * Animation delay based on index
@@ -77,62 +79,77 @@
77
79
  */
78
80
  const fieldType = $derived.by(() => {
79
81
  // Hidden fields should not be rendered
80
- if (schema.format === "hidden") {
81
- return "hidden";
82
+ if (schema.format === 'hidden') {
83
+ return 'hidden';
84
+ }
85
+
86
+ // JSON/code editor for format: "json" or "code"
87
+ if (schema.format === 'json' || schema.format === 'code') {
88
+ return 'code-editor';
89
+ }
90
+
91
+ // Markdown editor for format: "markdown"
92
+ if (schema.format === 'markdown') {
93
+ return 'markdown-editor';
94
+ }
95
+
96
+ // Template editor for format: "template" (Twig/Liquid syntax)
97
+ if (schema.format === 'template') {
98
+ return 'template-editor';
82
99
  }
83
100
 
84
101
  // Enum with multiple selection -> checkbox group
85
102
  if (schema.enum && schema.multiple) {
86
- return "checkbox-group";
103
+ return 'checkbox-group';
87
104
  }
88
105
 
89
106
  // Enum with single selection -> select
90
107
  if (schema.enum) {
91
- return "select-enum";
108
+ return 'select-enum';
92
109
  }
93
110
 
94
111
  // Multiline string -> textarea
95
- if (schema.type === "string" && schema.format === "multiline") {
96
- return "textarea";
112
+ if (schema.type === 'string' && schema.format === 'multiline') {
113
+ return 'textarea';
97
114
  }
98
115
 
99
116
  // Range slider for number/integer with format: "range"
100
- if ((schema.type === "number" || schema.type === "integer") && schema.format === "range") {
101
- return "range";
117
+ if ((schema.type === 'number' || schema.type === 'integer') && schema.format === 'range') {
118
+ return 'range';
102
119
  }
103
120
 
104
121
  // String -> text field
105
- if (schema.type === "string") {
106
- return "text";
122
+ if (schema.type === 'string') {
123
+ return 'text';
107
124
  }
108
125
 
109
126
  // Number or integer -> number field
110
- if (schema.type === "number" || schema.type === "integer") {
111
- return "number";
127
+ if (schema.type === 'number' || schema.type === 'integer') {
128
+ return 'number';
112
129
  }
113
130
 
114
131
  // Boolean -> toggle
115
- if (schema.type === "boolean") {
116
- return "toggle";
132
+ if (schema.type === 'boolean') {
133
+ return 'toggle';
117
134
  }
118
135
 
119
136
  // Select type or has options -> select
120
- if (schema.type === "select" || schema.options) {
121
- return "select-options";
137
+ if (schema.type === 'select' || schema.options) {
138
+ return 'select-options';
122
139
  }
123
140
 
124
141
  // Future: Array type support
125
- if (schema.type === "array") {
126
- return "array";
142
+ if (schema.type === 'array') {
143
+ return 'array';
127
144
  }
128
145
 
129
- // Future: Object type support
130
- if (schema.type === "object") {
131
- return "object";
146
+ // Object type without specific format -> CodeMirror JSON editor
147
+ if (schema.type === 'object') {
148
+ return 'code-editor';
132
149
  }
133
150
 
134
151
  // Fallback to text
135
- return "text";
152
+ return 'text';
136
153
  });
137
154
 
138
155
  /**
@@ -154,7 +171,7 @@
154
171
  /**
155
172
  * Get current value as the appropriate type
156
173
  */
157
- const stringValue = $derived(String(value ?? ""));
174
+ const stringValue = $derived(String(value ?? ''));
158
175
  const numberValue = $derived(value as number | string);
159
176
  const booleanValue = $derived(Boolean(value ?? schema.default ?? false));
160
177
  const arrayValue = $derived.by((): string[] => {
@@ -171,7 +188,7 @@
171
188
  });
172
189
  </script>
173
190
 
174
- {#if fieldType !== "hidden"}
191
+ {#if fieldType !== 'hidden'}
175
192
  <FormFieldWrapper
176
193
  id={fieldKey}
177
194
  label={fieldLabel}
@@ -179,7 +196,7 @@
179
196
  description={schema.title ? schema.description : undefined}
180
197
  {animationDelay}
181
198
  >
182
- {#if fieldType === "checkbox-group"}
199
+ {#if fieldType === 'checkbox-group'}
183
200
  <FormCheckboxGroup
184
201
  id={fieldKey}
185
202
  value={arrayValue}
@@ -187,7 +204,7 @@
187
204
  ariaDescribedBy={descriptionId}
188
205
  onChange={(val) => onChange(val)}
189
206
  />
190
- {:else if fieldType === "select-enum"}
207
+ {:else if fieldType === 'select-enum'}
191
208
  <FormSelect
192
209
  id={fieldKey}
193
210
  value={stringValue}
@@ -196,36 +213,36 @@
196
213
  ariaDescribedBy={descriptionId}
197
214
  onChange={(val) => onChange(val)}
198
215
  />
199
- {:else if fieldType === "textarea"}
216
+ {:else if fieldType === 'textarea'}
200
217
  <FormTextarea
201
218
  id={fieldKey}
202
219
  value={stringValue}
203
- placeholder={schema.placeholder ?? ""}
220
+ placeholder={schema.placeholder ?? ''}
204
221
  {required}
205
222
  ariaDescribedBy={descriptionId}
206
223
  onChange={(val) => onChange(val)}
207
224
  />
208
- {:else if fieldType === "text"}
225
+ {:else if fieldType === 'text'}
209
226
  <FormTextField
210
227
  id={fieldKey}
211
228
  value={stringValue}
212
- placeholder={schema.placeholder ?? ""}
229
+ placeholder={schema.placeholder ?? ''}
213
230
  {required}
214
231
  ariaDescribedBy={descriptionId}
215
232
  onChange={(val) => onChange(val)}
216
233
  />
217
- {:else if fieldType === "number"}
234
+ {:else if fieldType === 'number'}
218
235
  <FormNumberField
219
236
  id={fieldKey}
220
237
  value={numberValue}
221
- placeholder={schema.placeholder ?? ""}
238
+ placeholder={schema.placeholder ?? ''}
222
239
  min={schema.minimum}
223
240
  max={schema.maximum}
224
241
  {required}
225
242
  ariaDescribedBy={descriptionId}
226
243
  onChange={(val) => onChange(val)}
227
244
  />
228
- {:else if fieldType === "range"}
245
+ {:else if fieldType === 'range'}
229
246
  <FormRangeField
230
247
  id={fieldKey}
231
248
  value={numberValue}
@@ -236,14 +253,14 @@
236
253
  ariaDescribedBy={descriptionId}
237
254
  onChange={(val) => onChange(val)}
238
255
  />
239
- {:else if fieldType === "toggle"}
256
+ {:else if fieldType === 'toggle'}
240
257
  <FormToggle
241
258
  id={fieldKey}
242
259
  value={booleanValue}
243
260
  ariaDescribedBy={descriptionId}
244
261
  onChange={(val) => onChange(val)}
245
262
  />
246
- {:else if fieldType === "select-options"}
263
+ {:else if fieldType === 'select-options'}
247
264
  <FormSelect
248
265
  id={fieldKey}
249
266
  value={stringValue}
@@ -252,27 +269,62 @@
252
269
  ariaDescribedBy={descriptionId}
253
270
  onChange={(val) => onChange(val)}
254
271
  />
255
- {:else if fieldType === "array" && schema.items}
272
+ {:else if fieldType === 'array' && schema.items}
256
273
  <FormArray
257
274
  id={fieldKey}
258
275
  value={arrayItems}
259
276
  itemSchema={schema.items}
260
277
  minItems={schema.minItems}
261
278
  maxItems={schema.maxItems}
262
- addLabel={`Add ${schema.items.title ?? "Item"}`}
279
+ addLabel={`Add ${schema.items.title ?? 'Item'}`}
280
+ onChange={(val) => onChange(val)}
281
+ />
282
+ {:else if fieldType === 'code-editor'}
283
+ <FormCodeEditor
284
+ id={fieldKey}
285
+ {value}
286
+ placeholder={schema.placeholder ?? '{}'}
287
+ {required}
288
+ height={(schema.height as string | undefined) ?? '200px'}
289
+ darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
290
+ autoFormat={(schema.autoFormat as boolean | undefined) ?? true}
291
+ ariaDescribedBy={descriptionId}
292
+ onChange={(val) => onChange(val)}
293
+ />
294
+ {:else if fieldType === 'markdown-editor'}
295
+ <FormMarkdownEditor
296
+ id={fieldKey}
297
+ value={stringValue}
298
+ placeholder={schema.placeholder ?? 'Write your markdown here...'}
299
+ {required}
300
+ height={(schema.height as string | undefined) ?? '300px'}
301
+ showToolbar={(schema.showToolbar as boolean | undefined) ?? true}
302
+ showStatusBar={(schema.showStatusBar as boolean | undefined) ?? true}
303
+ spellChecker={(schema.spellChecker as boolean | undefined) ?? false}
304
+ ariaDescribedBy={descriptionId}
305
+ onChange={(val) => onChange(val)}
306
+ />
307
+ {:else if fieldType === 'template-editor'}
308
+ <FormTemplateEditor
309
+ id={fieldKey}
310
+ value={stringValue}
311
+ placeholder={schema.placeholder ??
312
+ 'Enter your template here...\nUse {{ variable }} for dynamic values.'}
313
+ {required}
314
+ height={(schema.height as string | undefined) ?? '250px'}
315
+ darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
316
+ variableHints={(schema.variableHints as string[] | undefined) ?? []}
317
+ placeholderExample={(schema.placeholderExample as string | undefined) ??
318
+ 'Hello {{ name }}, your order #{{ order_id }} is ready!'}
319
+ ariaDescribedBy={descriptionId}
263
320
  onChange={(val) => onChange(val)}
264
321
  />
265
- {:else if fieldType === "object"}
266
- <!-- Future: Object field component -->
267
- <div class="form-field__unsupported">
268
- <p>Object fields are not yet supported. Coming soon!</p>
269
- </div>
270
322
  {:else}
271
323
  <!-- Fallback to text input -->
272
324
  <FormTextField
273
325
  id={fieldKey}
274
326
  value={stringValue}
275
- placeholder={schema.placeholder ?? ""}
327
+ placeholder={schema.placeholder ?? ''}
276
328
  ariaDescribedBy={descriptionId}
277
329
  onChange={(val) => onChange(val)}
278
330
  />
@@ -294,4 +346,3 @@
294
346
  margin: 0;
295
347
  }
296
348
  </style>
297
-
@@ -1,4 +1,4 @@
1
- import type { FieldSchema } from "./types.js";
1
+ import type { FieldSchema } from './types.js';
2
2
  interface Props {
3
3
  /** Unique key/id for the field */
4
4
  fieldKey: string;