@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.
@@ -5,31 +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)
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. type: 'string' -> FormTextField
17
- 6. type: 'number' or 'integer' -> FormNumberField
18
- 7. type: 'boolean' -> FormToggle
19
- 8. type: 'select' or has options -> FormSelect
20
- 9. 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
21
26
  -->
22
27
 
23
28
  <script lang="ts">
24
- import FormFieldWrapper from "./FormFieldWrapper.svelte";
25
- import FormTextField from "./FormTextField.svelte";
26
- import FormTextarea from "./FormTextarea.svelte";
27
- import FormNumberField from "./FormNumberField.svelte";
28
- import FormToggle from "./FormToggle.svelte";
29
- import FormSelect from "./FormSelect.svelte";
30
- import FormCheckboxGroup from "./FormCheckboxGroup.svelte";
31
- import FormArray from "./FormArray.svelte";
32
- 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';
33
42
 
34
43
  interface Props {
35
44
  /** Unique key/id for the field */
@@ -46,19 +55,14 @@
46
55
  onChange: (value: unknown) => void;
47
56
  }
48
57
 
49
- let {
50
- fieldKey,
51
- schema,
52
- value,
53
- required = false,
54
- animationIndex = 0,
55
- onChange
56
- }: Props = $props();
58
+ let { fieldKey, schema, value, required = false, animationIndex = 0, onChange }: Props = $props();
57
59
 
58
60
  /**
59
61
  * Computed description ID for ARIA association
60
62
  */
61
- const descriptionId = $derived(schema.description && schema.title ? `${fieldKey}-description` : undefined);
63
+ const descriptionId = $derived(
64
+ schema.description && schema.title ? `${fieldKey}-description` : undefined
65
+ );
62
66
 
63
67
  /**
64
68
  * Animation delay based on index
@@ -75,57 +79,77 @@
75
79
  */
76
80
  const fieldType = $derived.by(() => {
77
81
  // Hidden fields should not be rendered
78
- if (schema.format === "hidden") {
79
- 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';
80
99
  }
81
100
 
82
101
  // Enum with multiple selection -> checkbox group
83
102
  if (schema.enum && schema.multiple) {
84
- return "checkbox-group";
103
+ return 'checkbox-group';
85
104
  }
86
105
 
87
106
  // Enum with single selection -> select
88
107
  if (schema.enum) {
89
- return "select-enum";
108
+ return 'select-enum';
90
109
  }
91
110
 
92
111
  // Multiline string -> textarea
93
- if (schema.type === "string" && schema.format === "multiline") {
94
- return "textarea";
112
+ if (schema.type === 'string' && schema.format === 'multiline') {
113
+ return 'textarea';
114
+ }
115
+
116
+ // Range slider for number/integer with format: "range"
117
+ if ((schema.type === 'number' || schema.type === 'integer') && schema.format === 'range') {
118
+ return 'range';
95
119
  }
96
120
 
97
121
  // String -> text field
98
- if (schema.type === "string") {
99
- return "text";
122
+ if (schema.type === 'string') {
123
+ return 'text';
100
124
  }
101
125
 
102
126
  // Number or integer -> number field
103
- if (schema.type === "number" || schema.type === "integer") {
104
- return "number";
127
+ if (schema.type === 'number' || schema.type === 'integer') {
128
+ return 'number';
105
129
  }
106
130
 
107
131
  // Boolean -> toggle
108
- if (schema.type === "boolean") {
109
- return "toggle";
132
+ if (schema.type === 'boolean') {
133
+ return 'toggle';
110
134
  }
111
135
 
112
136
  // Select type or has options -> select
113
- if (schema.type === "select" || schema.options) {
114
- return "select-options";
137
+ if (schema.type === 'select' || schema.options) {
138
+ return 'select-options';
115
139
  }
116
140
 
117
141
  // Future: Array type support
118
- if (schema.type === "array") {
119
- return "array";
142
+ if (schema.type === 'array') {
143
+ return 'array';
120
144
  }
121
145
 
122
- // Future: Object type support
123
- if (schema.type === "object") {
124
- return "object";
146
+ // Object type without specific format -> CodeMirror JSON editor
147
+ if (schema.type === 'object') {
148
+ return 'code-editor';
125
149
  }
126
150
 
127
151
  // Fallback to text
128
- return "text";
152
+ return 'text';
129
153
  });
130
154
 
131
155
  /**
@@ -147,7 +171,7 @@
147
171
  /**
148
172
  * Get current value as the appropriate type
149
173
  */
150
- const stringValue = $derived(String(value ?? ""));
174
+ const stringValue = $derived(String(value ?? ''));
151
175
  const numberValue = $derived(value as number | string);
152
176
  const booleanValue = $derived(Boolean(value ?? schema.default ?? false));
153
177
  const arrayValue = $derived.by((): string[] => {
@@ -164,7 +188,7 @@
164
188
  });
165
189
  </script>
166
190
 
167
- {#if fieldType !== "hidden"}
191
+ {#if fieldType !== 'hidden'}
168
192
  <FormFieldWrapper
169
193
  id={fieldKey}
170
194
  label={fieldLabel}
@@ -172,7 +196,7 @@
172
196
  description={schema.title ? schema.description : undefined}
173
197
  {animationDelay}
174
198
  >
175
- {#if fieldType === "checkbox-group"}
199
+ {#if fieldType === 'checkbox-group'}
176
200
  <FormCheckboxGroup
177
201
  id={fieldKey}
178
202
  value={arrayValue}
@@ -180,7 +204,7 @@
180
204
  ariaDescribedBy={descriptionId}
181
205
  onChange={(val) => onChange(val)}
182
206
  />
183
- {:else if fieldType === "select-enum"}
207
+ {:else if fieldType === 'select-enum'}
184
208
  <FormSelect
185
209
  id={fieldKey}
186
210
  value={stringValue}
@@ -189,43 +213,54 @@
189
213
  ariaDescribedBy={descriptionId}
190
214
  onChange={(val) => onChange(val)}
191
215
  />
192
- {:else if fieldType === "textarea"}
216
+ {:else if fieldType === 'textarea'}
193
217
  <FormTextarea
194
218
  id={fieldKey}
195
219
  value={stringValue}
196
- placeholder={schema.placeholder ?? ""}
220
+ placeholder={schema.placeholder ?? ''}
197
221
  {required}
198
222
  ariaDescribedBy={descriptionId}
199
223
  onChange={(val) => onChange(val)}
200
224
  />
201
- {:else if fieldType === "text"}
225
+ {:else if fieldType === 'text'}
202
226
  <FormTextField
203
227
  id={fieldKey}
204
228
  value={stringValue}
205
- placeholder={schema.placeholder ?? ""}
229
+ placeholder={schema.placeholder ?? ''}
206
230
  {required}
207
231
  ariaDescribedBy={descriptionId}
208
232
  onChange={(val) => onChange(val)}
209
233
  />
210
- {:else if fieldType === "number"}
234
+ {:else if fieldType === 'number'}
211
235
  <FormNumberField
212
236
  id={fieldKey}
213
237
  value={numberValue}
214
- placeholder={schema.placeholder ?? ""}
238
+ placeholder={schema.placeholder ?? ''}
215
239
  min={schema.minimum}
216
240
  max={schema.maximum}
217
241
  {required}
218
242
  ariaDescribedBy={descriptionId}
219
243
  onChange={(val) => onChange(val)}
220
244
  />
221
- {:else if fieldType === "toggle"}
245
+ {:else if fieldType === 'range'}
246
+ <FormRangeField
247
+ id={fieldKey}
248
+ value={numberValue}
249
+ min={schema.minimum}
250
+ max={schema.maximum}
251
+ step={schema.step}
252
+ {required}
253
+ ariaDescribedBy={descriptionId}
254
+ onChange={(val) => onChange(val)}
255
+ />
256
+ {:else if fieldType === 'toggle'}
222
257
  <FormToggle
223
258
  id={fieldKey}
224
259
  value={booleanValue}
225
260
  ariaDescribedBy={descriptionId}
226
261
  onChange={(val) => onChange(val)}
227
262
  />
228
- {:else if fieldType === "select-options"}
263
+ {:else if fieldType === 'select-options'}
229
264
  <FormSelect
230
265
  id={fieldKey}
231
266
  value={stringValue}
@@ -234,27 +269,62 @@
234
269
  ariaDescribedBy={descriptionId}
235
270
  onChange={(val) => onChange(val)}
236
271
  />
237
- {:else if fieldType === "array" && schema.items}
272
+ {:else if fieldType === 'array' && schema.items}
238
273
  <FormArray
239
274
  id={fieldKey}
240
275
  value={arrayItems}
241
276
  itemSchema={schema.items}
242
277
  minItems={schema.minItems}
243
278
  maxItems={schema.maxItems}
244
- 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}
245
320
  onChange={(val) => onChange(val)}
246
321
  />
247
- {:else if fieldType === "object"}
248
- <!-- Future: Object field component -->
249
- <div class="form-field__unsupported">
250
- <p>Object fields are not yet supported. Coming soon!</p>
251
- </div>
252
322
  {:else}
253
323
  <!-- Fallback to text input -->
254
324
  <FormTextField
255
325
  id={fieldKey}
256
326
  value={stringValue}
257
- placeholder={schema.placeholder ?? ""}
327
+ placeholder={schema.placeholder ?? ''}
258
328
  ariaDescribedBy={descriptionId}
259
329
  onChange={(val) => onChange(val)}
260
330
  />
@@ -276,4 +346,3 @@
276
346
  margin: 0;
277
347
  }
278
348
  </style>
279
-
@@ -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;