@d34dman/flowdrop 0.0.22 → 0.0.23
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/App.svelte +26 -25
- package/dist/components/ConfigForm.svelte +140 -519
- package/dist/components/ConfigForm.svelte.d.ts +5 -3
- package/dist/components/form/FormArray.svelte +1049 -0
- package/dist/components/form/FormArray.svelte.d.ts +22 -0
- package/dist/components/form/FormCheckboxGroup.svelte +152 -0
- package/dist/components/form/FormCheckboxGroup.svelte.d.ts +15 -0
- package/dist/components/form/FormField.svelte +279 -0
- package/dist/components/form/FormField.svelte.d.ts +18 -0
- package/dist/components/form/FormFieldWrapper.svelte +133 -0
- package/dist/components/form/FormFieldWrapper.svelte.d.ts +18 -0
- package/dist/components/form/FormNumberField.svelte +109 -0
- package/dist/components/form/FormNumberField.svelte.d.ts +23 -0
- package/dist/components/form/FormSelect.svelte +126 -0
- package/dist/components/form/FormSelect.svelte.d.ts +18 -0
- package/dist/components/form/FormTextField.svelte +88 -0
- package/dist/components/form/FormTextField.svelte.d.ts +17 -0
- package/dist/components/form/FormTextarea.svelte +94 -0
- package/dist/components/form/FormTextarea.svelte.d.ts +19 -0
- package/dist/components/form/FormToggle.svelte +123 -0
- package/dist/components/form/FormToggle.svelte.d.ts +17 -0
- package/dist/components/form/index.d.ts +41 -0
- package/dist/components/form/index.js +45 -0
- package/dist/components/form/types.d.ts +208 -0
- package/dist/components/form/types.js +29 -0
- package/dist/components/nodes/GatewayNode.svelte +84 -12
- package/dist/components/nodes/SimpleNode.svelte +41 -5
- package/dist/components/nodes/SimpleNode.svelte.d.ts +2 -1
- package/dist/components/nodes/SquareNode.svelte +41 -5
- package/dist/components/nodes/SquareNode.svelte.d.ts +2 -1
- package/dist/components/nodes/WorkflowNode.svelte +88 -5
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -3
- package/dist/stores/workflowStore.d.ts +15 -0
- package/dist/stores/workflowStore.js +28 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.js +16 -0
- package/package.json +3 -3
- package/dist/config/demo.d.ts +0 -58
- package/dist/config/demo.js +0 -142
- package/dist/data/samples.d.ts +0 -51
- package/dist/data/samples.js +0 -3245
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormArray Component
|
|
3
|
+
Dynamic array field that allows adding, removing, and reordering items
|
|
4
|
+
Generates sub-forms for each item based on the schema's items property
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Add/remove items with animated transitions
|
|
8
|
+
- Supports simple types (string, number, boolean) and complex types (objects)
|
|
9
|
+
- Recursively uses FormField for item rendering
|
|
10
|
+
- Drag handle for future reordering support
|
|
11
|
+
- Collapsible items for complex object arrays
|
|
12
|
+
- Empty state with helpful prompt
|
|
13
|
+
|
|
14
|
+
Accessibility:
|
|
15
|
+
- Proper ARIA labels for add/remove buttons
|
|
16
|
+
- Keyboard navigation support
|
|
17
|
+
- Screen reader friendly item descriptions
|
|
18
|
+
-->
|
|
19
|
+
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import Icon from '@iconify/svelte';
|
|
22
|
+
import type { FieldSchema } from './types.js';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Field identifier */
|
|
26
|
+
id: string;
|
|
27
|
+
/** Current array value */
|
|
28
|
+
value: unknown[];
|
|
29
|
+
/** Schema for array items */
|
|
30
|
+
itemSchema: FieldSchema;
|
|
31
|
+
/** Minimum number of items required */
|
|
32
|
+
minItems?: number;
|
|
33
|
+
/** Maximum number of items allowed */
|
|
34
|
+
maxItems?: number;
|
|
35
|
+
/** Label for add button */
|
|
36
|
+
addLabel?: string;
|
|
37
|
+
/** Whether the field is disabled */
|
|
38
|
+
disabled?: boolean;
|
|
39
|
+
/** Callback when value changes */
|
|
40
|
+
onChange: (value: unknown[]) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
id,
|
|
45
|
+
value = [],
|
|
46
|
+
itemSchema,
|
|
47
|
+
minItems = 0,
|
|
48
|
+
maxItems,
|
|
49
|
+
addLabel = 'Add Item',
|
|
50
|
+
disabled = false,
|
|
51
|
+
onChange
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ensure value is always an array
|
|
56
|
+
*/
|
|
57
|
+
const items = $derived(Array.isArray(value) ? value : []);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if we can add more items
|
|
61
|
+
*/
|
|
62
|
+
const canAddItem = $derived(maxItems === undefined || items.length < maxItems);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if we can remove items
|
|
66
|
+
*/
|
|
67
|
+
const canRemoveItem = $derived(items.length > minItems);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Determine if items are simple (primitive) or complex (objects)
|
|
71
|
+
*/
|
|
72
|
+
const isSimpleType = $derived(
|
|
73
|
+
itemSchema.type === 'string' ||
|
|
74
|
+
itemSchema.type === 'number' ||
|
|
75
|
+
itemSchema.type === 'integer' ||
|
|
76
|
+
itemSchema.type === 'boolean'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the default value for a new item based on schema
|
|
81
|
+
*/
|
|
82
|
+
function getDefaultValue(): unknown {
|
|
83
|
+
if (itemSchema.default !== undefined) {
|
|
84
|
+
return itemSchema.default;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
switch (itemSchema.type) {
|
|
88
|
+
case 'string':
|
|
89
|
+
return '';
|
|
90
|
+
case 'number':
|
|
91
|
+
case 'integer':
|
|
92
|
+
return 0;
|
|
93
|
+
case 'boolean':
|
|
94
|
+
return false;
|
|
95
|
+
case 'object':
|
|
96
|
+
// Create default object from properties
|
|
97
|
+
if (itemSchema.properties) {
|
|
98
|
+
const defaultObj: Record<string, unknown> = {};
|
|
99
|
+
Object.entries(itemSchema.properties).forEach(([key, propSchema]) => {
|
|
100
|
+
if (propSchema.default !== undefined) {
|
|
101
|
+
defaultObj[key] = propSchema.default;
|
|
102
|
+
} else {
|
|
103
|
+
defaultObj[key] = getDefaultForType(propSchema.type);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return defaultObj;
|
|
107
|
+
}
|
|
108
|
+
return {};
|
|
109
|
+
case 'array':
|
|
110
|
+
return [];
|
|
111
|
+
default:
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get default value for a specific type
|
|
118
|
+
*/
|
|
119
|
+
function getDefaultForType(type: string | undefined): unknown {
|
|
120
|
+
switch (type) {
|
|
121
|
+
case 'string':
|
|
122
|
+
return '';
|
|
123
|
+
case 'number':
|
|
124
|
+
case 'integer':
|
|
125
|
+
return 0;
|
|
126
|
+
case 'boolean':
|
|
127
|
+
return false;
|
|
128
|
+
case 'object':
|
|
129
|
+
return {};
|
|
130
|
+
case 'array':
|
|
131
|
+
return [];
|
|
132
|
+
default:
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Add a new item to the array
|
|
139
|
+
*/
|
|
140
|
+
function addItem(): void {
|
|
141
|
+
if (!canAddItem || disabled) return;
|
|
142
|
+
const newValue = [...items, getDefaultValue()];
|
|
143
|
+
onChange(newValue);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Remove an item at the specified index
|
|
148
|
+
*/
|
|
149
|
+
function removeItem(index: number): void {
|
|
150
|
+
if (!canRemoveItem || disabled) return;
|
|
151
|
+
const newValue = items.filter((_, i) => i !== index);
|
|
152
|
+
onChange(newValue);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Update an item at the specified index
|
|
157
|
+
*/
|
|
158
|
+
function updateItem(index: number, newItemValue: unknown): void {
|
|
159
|
+
const newValue = items.map((item, i) => (i === index ? newItemValue : item));
|
|
160
|
+
onChange(newValue);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Update a property of an object item
|
|
165
|
+
*/
|
|
166
|
+
function updateObjectProperty(index: number, propertyKey: string, propertyValue: unknown): void {
|
|
167
|
+
const currentItem = items[index] as Record<string, unknown>;
|
|
168
|
+
const updatedItem = { ...currentItem, [propertyKey]: propertyValue };
|
|
169
|
+
updateItem(index, updatedItem);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Move an item up in the array
|
|
174
|
+
*/
|
|
175
|
+
function moveItemUp(index: number): void {
|
|
176
|
+
if (index === 0 || disabled) return;
|
|
177
|
+
const newValue = [...items];
|
|
178
|
+
[newValue[index - 1], newValue[index]] = [newValue[index], newValue[index - 1]];
|
|
179
|
+
onChange(newValue);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Move an item down in the array
|
|
184
|
+
*/
|
|
185
|
+
function moveItemDown(index: number): void {
|
|
186
|
+
if (index === items.length - 1 || disabled) return;
|
|
187
|
+
const newValue = [...items];
|
|
188
|
+
[newValue[index], newValue[index + 1]] = [newValue[index + 1], newValue[index]];
|
|
189
|
+
onChange(newValue);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get item label for display
|
|
194
|
+
*/
|
|
195
|
+
function getItemLabel(index: number, item: unknown): string {
|
|
196
|
+
if (isSimpleType) {
|
|
197
|
+
const itemStr = String(item);
|
|
198
|
+
return itemStr.length > 30
|
|
199
|
+
? `${itemStr.substring(0, 30)}...`
|
|
200
|
+
: itemStr || `Item ${index + 1}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// For objects, try to find a name/label/title property
|
|
204
|
+
if (typeof item === 'object' && item !== null) {
|
|
205
|
+
const obj = item as Record<string, unknown>;
|
|
206
|
+
const labelKey = Object.keys(obj).find((k) =>
|
|
207
|
+
['name', 'label', 'title', 'id'].includes(k.toLowerCase())
|
|
208
|
+
);
|
|
209
|
+
if (labelKey && obj[labelKey]) {
|
|
210
|
+
return String(obj[labelKey]);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return `Item ${index + 1}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Track collapsed state for complex items
|
|
219
|
+
*/
|
|
220
|
+
let collapsedItems = $state<Set<number>>(new Set());
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Toggle collapsed state for an item
|
|
224
|
+
*/
|
|
225
|
+
function toggleCollapse(index: number): void {
|
|
226
|
+
const newCollapsed = new Set(collapsedItems);
|
|
227
|
+
if (newCollapsed.has(index)) {
|
|
228
|
+
newCollapsed.delete(index);
|
|
229
|
+
} else {
|
|
230
|
+
newCollapsed.add(index);
|
|
231
|
+
}
|
|
232
|
+
collapsedItems = newCollapsed;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if an item is collapsed
|
|
237
|
+
*/
|
|
238
|
+
function isCollapsed(index: number): boolean {
|
|
239
|
+
return collapsedItems.has(index);
|
|
240
|
+
}
|
|
241
|
+
</script>
|
|
242
|
+
|
|
243
|
+
<div class="form-array" class:form-array--disabled={disabled}>
|
|
244
|
+
<!-- Array Items -->
|
|
245
|
+
{#if items.length > 0}
|
|
246
|
+
<div class="form-array__items">
|
|
247
|
+
{#each items as item, index (index)}
|
|
248
|
+
<div
|
|
249
|
+
class="form-array__item"
|
|
250
|
+
class:form-array__item--simple={isSimpleType}
|
|
251
|
+
class:form-array__item--complex={!isSimpleType}
|
|
252
|
+
style="animation-delay: {index * 50}ms"
|
|
253
|
+
>
|
|
254
|
+
<!-- Item Header -->
|
|
255
|
+
<div class="form-array__item-header">
|
|
256
|
+
<!-- Item index/label -->
|
|
257
|
+
{#if !isSimpleType}
|
|
258
|
+
<button
|
|
259
|
+
type="button"
|
|
260
|
+
class="form-array__item-toggle"
|
|
261
|
+
onclick={() => toggleCollapse(index)}
|
|
262
|
+
aria-expanded={!isCollapsed(index)}
|
|
263
|
+
aria-label={isCollapsed(index) ? 'Expand item' : 'Collapse item'}
|
|
264
|
+
>
|
|
265
|
+
<Icon
|
|
266
|
+
icon={isCollapsed(index) ? 'heroicons:chevron-right' : 'heroicons:chevron-down'}
|
|
267
|
+
class="form-array__toggle-icon"
|
|
268
|
+
/>
|
|
269
|
+
<span class="form-array__item-label">{getItemLabel(index, item)}</span>
|
|
270
|
+
</button>
|
|
271
|
+
{:else}
|
|
272
|
+
<span class="form-array__item-number">#{index + 1}</span>
|
|
273
|
+
{/if}
|
|
274
|
+
|
|
275
|
+
<!-- Action buttons group -->
|
|
276
|
+
<div class="form-array__actions">
|
|
277
|
+
<!-- Move Up button -->
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
class="form-array__action-btn form-array__action-btn--move"
|
|
281
|
+
onclick={() => moveItemUp(index)}
|
|
282
|
+
disabled={index === 0 || disabled}
|
|
283
|
+
aria-label="Move item {index + 1} up"
|
|
284
|
+
title="Move up"
|
|
285
|
+
>
|
|
286
|
+
<Icon icon="heroicons:arrow-up" />
|
|
287
|
+
</button>
|
|
288
|
+
|
|
289
|
+
<!-- Move Down button -->
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
class="form-array__action-btn form-array__action-btn--move"
|
|
293
|
+
onclick={() => moveItemDown(index)}
|
|
294
|
+
disabled={index === items.length - 1 || disabled}
|
|
295
|
+
aria-label="Move item {index + 1} down"
|
|
296
|
+
title="Move down"
|
|
297
|
+
>
|
|
298
|
+
<Icon icon="heroicons:arrow-down" />
|
|
299
|
+
</button>
|
|
300
|
+
|
|
301
|
+
<!-- Delete button -->
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
class="form-array__action-btn form-array__action-btn--delete"
|
|
305
|
+
onclick={() => removeItem(index)}
|
|
306
|
+
disabled={!canRemoveItem || disabled}
|
|
307
|
+
aria-label="Delete item {index + 1}"
|
|
308
|
+
title="Delete item"
|
|
309
|
+
>
|
|
310
|
+
<Icon icon="heroicons:trash" />
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<!-- Item Content -->
|
|
316
|
+
<div
|
|
317
|
+
class="form-array__item-content"
|
|
318
|
+
class:form-array__item-content--collapsed={!isSimpleType && isCollapsed(index)}
|
|
319
|
+
>
|
|
320
|
+
{#if isSimpleType}
|
|
321
|
+
<!-- Simple type: render inline input -->
|
|
322
|
+
{#if itemSchema.type === 'string'}
|
|
323
|
+
{#if itemSchema.format === 'multiline'}
|
|
324
|
+
<textarea
|
|
325
|
+
class="form-array__input form-array__textarea"
|
|
326
|
+
value={String(item ?? '')}
|
|
327
|
+
placeholder={itemSchema.placeholder ?? ''}
|
|
328
|
+
rows={3}
|
|
329
|
+
oninput={(e) => updateItem(index, e.currentTarget.value)}
|
|
330
|
+
{disabled}
|
|
331
|
+
></textarea>
|
|
332
|
+
{:else}
|
|
333
|
+
<input
|
|
334
|
+
type="text"
|
|
335
|
+
class="form-array__input"
|
|
336
|
+
value={String(item ?? '')}
|
|
337
|
+
placeholder={itemSchema.placeholder ?? ''}
|
|
338
|
+
oninput={(e) => updateItem(index, e.currentTarget.value)}
|
|
339
|
+
{disabled}
|
|
340
|
+
/>
|
|
341
|
+
{/if}
|
|
342
|
+
{:else if itemSchema.type === 'number' || itemSchema.type === 'integer'}
|
|
343
|
+
<input
|
|
344
|
+
type="number"
|
|
345
|
+
class="form-array__input form-array__input--number"
|
|
346
|
+
value={item as number}
|
|
347
|
+
placeholder={itemSchema.placeholder ?? ''}
|
|
348
|
+
min={itemSchema.minimum}
|
|
349
|
+
max={itemSchema.maximum}
|
|
350
|
+
oninput={(e) => {
|
|
351
|
+
const val = e.currentTarget.value;
|
|
352
|
+
updateItem(index, val === '' ? '' : Number(val));
|
|
353
|
+
}}
|
|
354
|
+
{disabled}
|
|
355
|
+
/>
|
|
356
|
+
{:else if itemSchema.type === 'boolean'}
|
|
357
|
+
<label class="form-array__toggle-wrapper">
|
|
358
|
+
<input
|
|
359
|
+
type="checkbox"
|
|
360
|
+
class="form-array__checkbox-input"
|
|
361
|
+
checked={Boolean(item)}
|
|
362
|
+
onchange={(e) => updateItem(index, e.currentTarget.checked)}
|
|
363
|
+
{disabled}
|
|
364
|
+
/>
|
|
365
|
+
<span class="form-array__toggle-track">
|
|
366
|
+
<span class="form-array__toggle-thumb"></span>
|
|
367
|
+
</span>
|
|
368
|
+
<span class="form-array__toggle-label">
|
|
369
|
+
{item ? 'Yes' : 'No'}
|
|
370
|
+
</span>
|
|
371
|
+
</label>
|
|
372
|
+
{:else if itemSchema.enum}
|
|
373
|
+
<!-- Enum: render select -->
|
|
374
|
+
<select
|
|
375
|
+
class="form-array__select"
|
|
376
|
+
value={String(item ?? '')}
|
|
377
|
+
onchange={(e) => updateItem(index, e.currentTarget.value)}
|
|
378
|
+
{disabled}
|
|
379
|
+
>
|
|
380
|
+
{#each itemSchema.enum as option}
|
|
381
|
+
<option value={String(option)}>{String(option)}</option>
|
|
382
|
+
{/each}
|
|
383
|
+
</select>
|
|
384
|
+
{:else}
|
|
385
|
+
<!-- Fallback to text -->
|
|
386
|
+
<input
|
|
387
|
+
type="text"
|
|
388
|
+
class="form-array__input"
|
|
389
|
+
value={String(item ?? '')}
|
|
390
|
+
placeholder={itemSchema.placeholder ?? ''}
|
|
391
|
+
oninput={(e) => updateItem(index, e.currentTarget.value)}
|
|
392
|
+
{disabled}
|
|
393
|
+
/>
|
|
394
|
+
{/if}
|
|
395
|
+
{:else if itemSchema.type === 'object' && itemSchema.properties}
|
|
396
|
+
<!-- Complex type: render sub-form for object properties -->
|
|
397
|
+
{#if !isCollapsed(index)}
|
|
398
|
+
<div class="form-array__subform">
|
|
399
|
+
{#each Object.entries(itemSchema.properties) as [propKey, propSchema], propIndex (propKey)}
|
|
400
|
+
{@const propValue = (item as Record<string, unknown>)?.[propKey]}
|
|
401
|
+
{@const isRequired = itemSchema.required?.includes(propKey) ?? false}
|
|
402
|
+
{@const propFieldSchema = propSchema as FieldSchema}
|
|
403
|
+
|
|
404
|
+
<div
|
|
405
|
+
class="form-array__subform-field"
|
|
406
|
+
style="animation-delay: {propIndex * 20}ms"
|
|
407
|
+
>
|
|
408
|
+
<label class="form-array__subform-label" for="{id}-{index}-{propKey}">
|
|
409
|
+
<span class="form-array__subform-label-text">
|
|
410
|
+
{propFieldSchema.title ?? propKey}
|
|
411
|
+
</span>
|
|
412
|
+
{#if isRequired}
|
|
413
|
+
<span class="form-array__required">*</span>
|
|
414
|
+
{/if}
|
|
415
|
+
</label>
|
|
416
|
+
|
|
417
|
+
<div class="form-array__subform-input">
|
|
418
|
+
{#if propFieldSchema.enum}
|
|
419
|
+
<select
|
|
420
|
+
id="{id}-{index}-{propKey}"
|
|
421
|
+
class="form-array__select"
|
|
422
|
+
value={String(propValue ?? '')}
|
|
423
|
+
onchange={(e) =>
|
|
424
|
+
updateObjectProperty(index, propKey, e.currentTarget.value)}
|
|
425
|
+
{disabled}
|
|
426
|
+
>
|
|
427
|
+
{#each propFieldSchema.enum as option}
|
|
428
|
+
<option value={String(option)}>{String(option)}</option>
|
|
429
|
+
{/each}
|
|
430
|
+
</select>
|
|
431
|
+
{:else if propFieldSchema.type === 'string' && propFieldSchema.format === 'multiline'}
|
|
432
|
+
<textarea
|
|
433
|
+
id="{id}-{index}-{propKey}"
|
|
434
|
+
class="form-array__input form-array__textarea"
|
|
435
|
+
value={String(propValue ?? '')}
|
|
436
|
+
placeholder={propFieldSchema.placeholder ?? ''}
|
|
437
|
+
rows={3}
|
|
438
|
+
oninput={(e) =>
|
|
439
|
+
updateObjectProperty(index, propKey, e.currentTarget.value)}
|
|
440
|
+
{disabled}
|
|
441
|
+
></textarea>
|
|
442
|
+
{:else if propFieldSchema.type === 'string'}
|
|
443
|
+
<input
|
|
444
|
+
id="{id}-{index}-{propKey}"
|
|
445
|
+
type="text"
|
|
446
|
+
class="form-array__input"
|
|
447
|
+
value={String(propValue ?? '')}
|
|
448
|
+
placeholder={propFieldSchema.placeholder ?? ''}
|
|
449
|
+
oninput={(e) =>
|
|
450
|
+
updateObjectProperty(index, propKey, e.currentTarget.value)}
|
|
451
|
+
{disabled}
|
|
452
|
+
/>
|
|
453
|
+
{:else if propFieldSchema.type === 'number' || propFieldSchema.type === 'integer'}
|
|
454
|
+
<input
|
|
455
|
+
id="{id}-{index}-{propKey}"
|
|
456
|
+
type="number"
|
|
457
|
+
class="form-array__input form-array__input--number"
|
|
458
|
+
value={propValue as number}
|
|
459
|
+
placeholder={propFieldSchema.placeholder ?? ''}
|
|
460
|
+
min={propFieldSchema.minimum}
|
|
461
|
+
max={propFieldSchema.maximum}
|
|
462
|
+
oninput={(e) => {
|
|
463
|
+
const val = e.currentTarget.value;
|
|
464
|
+
updateObjectProperty(index, propKey, val === '' ? '' : Number(val));
|
|
465
|
+
}}
|
|
466
|
+
{disabled}
|
|
467
|
+
/>
|
|
468
|
+
{:else if propFieldSchema.type === 'boolean'}
|
|
469
|
+
<label class="form-array__toggle-wrapper">
|
|
470
|
+
<input
|
|
471
|
+
id="{id}-{index}-{propKey}"
|
|
472
|
+
type="checkbox"
|
|
473
|
+
class="form-array__checkbox-input"
|
|
474
|
+
checked={Boolean(propValue)}
|
|
475
|
+
onchange={(e) =>
|
|
476
|
+
updateObjectProperty(index, propKey, e.currentTarget.checked)}
|
|
477
|
+
{disabled}
|
|
478
|
+
/>
|
|
479
|
+
<span class="form-array__toggle-track">
|
|
480
|
+
<span class="form-array__toggle-thumb"></span>
|
|
481
|
+
</span>
|
|
482
|
+
<span class="form-array__toggle-label">
|
|
483
|
+
{propValue ? 'Yes' : 'No'}
|
|
484
|
+
</span>
|
|
485
|
+
</label>
|
|
486
|
+
{:else}
|
|
487
|
+
<input
|
|
488
|
+
id="{id}-{index}-{propKey}"
|
|
489
|
+
type="text"
|
|
490
|
+
class="form-array__input"
|
|
491
|
+
value={String(propValue ?? '')}
|
|
492
|
+
placeholder={propFieldSchema.placeholder ?? ''}
|
|
493
|
+
oninput={(e) =>
|
|
494
|
+
updateObjectProperty(index, propKey, e.currentTarget.value)}
|
|
495
|
+
{disabled}
|
|
496
|
+
/>
|
|
497
|
+
{/if}
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
{#if propFieldSchema.description && propFieldSchema.title}
|
|
501
|
+
<p class="form-array__subform-description">{propFieldSchema.description}</p>
|
|
502
|
+
{/if}
|
|
503
|
+
</div>
|
|
504
|
+
{/each}
|
|
505
|
+
</div>
|
|
506
|
+
{/if}
|
|
507
|
+
{:else}
|
|
508
|
+
<!-- Unknown complex type -->
|
|
509
|
+
<div class="form-array__unsupported">
|
|
510
|
+
<p>Complex item type "{itemSchema.type}" is not fully supported.</p>
|
|
511
|
+
</div>
|
|
512
|
+
{/if}
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
{/each}
|
|
516
|
+
</div>
|
|
517
|
+
{:else}
|
|
518
|
+
<!-- Empty State -->
|
|
519
|
+
<div class="form-array__empty">
|
|
520
|
+
<Icon icon="heroicons:squares-plus" class="form-array__empty-icon" />
|
|
521
|
+
<p class="form-array__empty-text">No items yet</p>
|
|
522
|
+
</div>
|
|
523
|
+
{/if}
|
|
524
|
+
|
|
525
|
+
<!-- Add Button -->
|
|
526
|
+
<button
|
|
527
|
+
type="button"
|
|
528
|
+
class="form-array__add-btn"
|
|
529
|
+
onclick={addItem}
|
|
530
|
+
disabled={!canAddItem || disabled}
|
|
531
|
+
aria-label={addLabel}
|
|
532
|
+
>
|
|
533
|
+
<Icon icon="heroicons:plus" />
|
|
534
|
+
<span>{addLabel}</span>
|
|
535
|
+
</button>
|
|
536
|
+
|
|
537
|
+
<!-- Item count and limits -->
|
|
538
|
+
{#if minItems > 0 || maxItems !== undefined}
|
|
539
|
+
<div class="form-array__info">
|
|
540
|
+
<span class="form-array__count">{items.length} item{items.length !== 1 ? 's' : ''}</span>
|
|
541
|
+
{#if minItems > 0}
|
|
542
|
+
<span class="form-array__limit">Min: {minItems}</span>
|
|
543
|
+
{/if}
|
|
544
|
+
{#if maxItems !== undefined}
|
|
545
|
+
<span class="form-array__limit">Max: {maxItems}</span>
|
|
546
|
+
{/if}
|
|
547
|
+
</div>
|
|
548
|
+
{/if}
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<style>
|
|
552
|
+
/* ============================================
|
|
553
|
+
FORM ARRAY CONTAINER
|
|
554
|
+
============================================ */
|
|
555
|
+
|
|
556
|
+
.form-array {
|
|
557
|
+
display: flex;
|
|
558
|
+
flex-direction: column;
|
|
559
|
+
gap: 0.75rem;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.form-array--disabled {
|
|
563
|
+
opacity: 0.6;
|
|
564
|
+
pointer-events: none;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/* ============================================
|
|
568
|
+
ITEMS CONTAINER
|
|
569
|
+
============================================ */
|
|
570
|
+
|
|
571
|
+
.form-array__items {
|
|
572
|
+
display: flex;
|
|
573
|
+
flex-direction: column;
|
|
574
|
+
gap: 0.5rem;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/* ============================================
|
|
578
|
+
INDIVIDUAL ITEM
|
|
579
|
+
============================================ */
|
|
580
|
+
|
|
581
|
+
.form-array__item {
|
|
582
|
+
display: flex;
|
|
583
|
+
flex-direction: column;
|
|
584
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
585
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
586
|
+
border-radius: 0.5rem;
|
|
587
|
+
overflow: hidden;
|
|
588
|
+
animation: itemFadeIn 0.25s ease-out forwards;
|
|
589
|
+
opacity: 0;
|
|
590
|
+
transform: translateY(-8px);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
@keyframes itemFadeIn {
|
|
594
|
+
to {
|
|
595
|
+
opacity: 1;
|
|
596
|
+
transform: translateY(0);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.form-array__item--simple .form-array__item-content {
|
|
601
|
+
padding: 0.5rem 0.75rem 0.75rem;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.form-array__item--complex .form-array__item-content {
|
|
605
|
+
padding: 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* ============================================
|
|
609
|
+
ITEM HEADER
|
|
610
|
+
============================================ */
|
|
611
|
+
|
|
612
|
+
.form-array__item-header {
|
|
613
|
+
display: flex;
|
|
614
|
+
align-items: center;
|
|
615
|
+
gap: 0.625rem;
|
|
616
|
+
padding: 0.625rem 0.75rem;
|
|
617
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
618
|
+
border-bottom: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.form-array__item--simple .form-array__item-header {
|
|
622
|
+
padding: 0.5rem 0.625rem;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* ============================================
|
|
626
|
+
ITEM NUMBER/LABEL
|
|
627
|
+
============================================ */
|
|
628
|
+
|
|
629
|
+
.form-array__item-number {
|
|
630
|
+
font-size: 0.75rem;
|
|
631
|
+
font-weight: 600;
|
|
632
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
633
|
+
min-width: 1.75rem;
|
|
634
|
+
padding: 0.125rem 0.375rem;
|
|
635
|
+
background-color: var(--color-ref-gray-200, #e5e7eb);
|
|
636
|
+
border-radius: 0.25rem;
|
|
637
|
+
text-align: center;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.form-array__item-toggle {
|
|
641
|
+
display: flex;
|
|
642
|
+
align-items: center;
|
|
643
|
+
gap: 0.5rem;
|
|
644
|
+
flex: 1;
|
|
645
|
+
padding: 0.375rem 0.5rem;
|
|
646
|
+
margin: -0.25rem;
|
|
647
|
+
border: 1px solid transparent;
|
|
648
|
+
background: transparent;
|
|
649
|
+
cursor: pointer;
|
|
650
|
+
text-align: left;
|
|
651
|
+
border-radius: 0.375rem;
|
|
652
|
+
transition: all 0.15s;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.form-array__item-toggle:hover {
|
|
656
|
+
background-color: var(--color-ref-gray-200, #e5e7eb);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.form-array__item-toggle:focus-visible {
|
|
660
|
+
outline: none;
|
|
661
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
662
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.form-array__item-toggle :global(svg) {
|
|
666
|
+
width: 1.125rem;
|
|
667
|
+
height: 1.125rem;
|
|
668
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
669
|
+
transition: transform 0.2s;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.form-array__item-label {
|
|
673
|
+
font-size: 0.8125rem;
|
|
674
|
+
font-weight: 600;
|
|
675
|
+
color: var(--color-ref-gray-700, #374151);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* ============================================
|
|
679
|
+
ACTION BUTTONS GROUP
|
|
680
|
+
============================================ */
|
|
681
|
+
|
|
682
|
+
.form-array__actions {
|
|
683
|
+
display: flex;
|
|
684
|
+
align-items: center;
|
|
685
|
+
gap: 0.375rem;
|
|
686
|
+
margin-left: auto;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.form-array__action-btn {
|
|
690
|
+
display: flex;
|
|
691
|
+
align-items: center;
|
|
692
|
+
justify-content: center;
|
|
693
|
+
width: 2rem;
|
|
694
|
+
height: 2rem;
|
|
695
|
+
padding: 0;
|
|
696
|
+
border: 1px solid transparent;
|
|
697
|
+
border-radius: 0.375rem;
|
|
698
|
+
cursor: pointer;
|
|
699
|
+
transition: all 0.15s;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.form-array__action-btn :global(svg) {
|
|
703
|
+
width: 1rem;
|
|
704
|
+
height: 1rem;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.form-array__action-btn:focus-visible {
|
|
708
|
+
outline: none;
|
|
709
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.form-array__action-btn:disabled {
|
|
713
|
+
opacity: 0.35;
|
|
714
|
+
cursor: not-allowed;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/* Move Up/Down buttons - Blue semantic color */
|
|
718
|
+
.form-array__action-btn--move {
|
|
719
|
+
background-color: var(--color-ref-blue-50, #eff6ff);
|
|
720
|
+
border-color: var(--color-ref-blue-200, #bfdbfe);
|
|
721
|
+
color: var(--color-ref-blue-600, #2563eb);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.form-array__action-btn--move:hover:not(:disabled) {
|
|
725
|
+
background-color: var(--color-ref-blue-100, #dbeafe);
|
|
726
|
+
border-color: var(--color-ref-blue-300, #93c5fd);
|
|
727
|
+
color: var(--color-ref-blue-700, #1d4ed8);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.form-array__action-btn--move:active:not(:disabled) {
|
|
731
|
+
background-color: var(--color-ref-blue-200, #bfdbfe);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* Delete button - Red/Warning semantic color */
|
|
735
|
+
.form-array__action-btn--delete {
|
|
736
|
+
background-color: var(--color-ref-red-50, #fef2f2);
|
|
737
|
+
border-color: var(--color-ref-red-200, #fecaca);
|
|
738
|
+
color: var(--color-ref-red-600, #dc2626);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.form-array__action-btn--delete:hover:not(:disabled) {
|
|
742
|
+
background-color: var(--color-ref-red-100, #fee2e2);
|
|
743
|
+
border-color: var(--color-ref-red-300, #fca5a5);
|
|
744
|
+
color: var(--color-ref-red-700, #b91c1c);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.form-array__action-btn--delete:active:not(:disabled) {
|
|
748
|
+
background-color: var(--color-ref-red-200, #fecaca);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* ============================================
|
|
752
|
+
ITEM CONTENT
|
|
753
|
+
============================================ */
|
|
754
|
+
|
|
755
|
+
.form-array__item-content {
|
|
756
|
+
transition: all 0.2s ease-out;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.form-array__item-content--collapsed {
|
|
760
|
+
height: 0;
|
|
761
|
+
overflow: hidden;
|
|
762
|
+
padding: 0 !important;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/* ============================================
|
|
766
|
+
INPUTS (Simple Types)
|
|
767
|
+
============================================ */
|
|
768
|
+
|
|
769
|
+
.form-array__input {
|
|
770
|
+
width: 100%;
|
|
771
|
+
padding: 0.5rem 0.75rem;
|
|
772
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
773
|
+
border-radius: 0.375rem;
|
|
774
|
+
font-size: 0.875rem;
|
|
775
|
+
font-family: inherit;
|
|
776
|
+
color: var(--color-ref-gray-900, #111827);
|
|
777
|
+
background-color: #ffffff;
|
|
778
|
+
transition: all 0.2s;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.form-array__input::placeholder {
|
|
782
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.form-array__input:hover {
|
|
786
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.form-array__input:focus {
|
|
790
|
+
outline: none;
|
|
791
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
792
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.form-array__input--number {
|
|
796
|
+
font-variant-numeric: tabular-nums;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.form-array__textarea {
|
|
800
|
+
resize: vertical;
|
|
801
|
+
min-height: 4rem;
|
|
802
|
+
line-height: 1.5;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.form-array__select {
|
|
806
|
+
width: 100%;
|
|
807
|
+
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
808
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
809
|
+
border-radius: 0.375rem;
|
|
810
|
+
font-size: 0.875rem;
|
|
811
|
+
font-family: inherit;
|
|
812
|
+
color: var(--color-ref-gray-900, #111827);
|
|
813
|
+
background-color: #ffffff;
|
|
814
|
+
cursor: pointer;
|
|
815
|
+
appearance: none;
|
|
816
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%239ca3af'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
|
817
|
+
background-repeat: no-repeat;
|
|
818
|
+
background-position: right 0.5rem center;
|
|
819
|
+
background-size: 1rem;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.form-array__select:hover {
|
|
823
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
.form-array__select:focus {
|
|
827
|
+
outline: none;
|
|
828
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
829
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/* ============================================
|
|
833
|
+
TOGGLE (Boolean in Array)
|
|
834
|
+
============================================ */
|
|
835
|
+
|
|
836
|
+
.form-array__toggle-wrapper {
|
|
837
|
+
display: flex;
|
|
838
|
+
align-items: center;
|
|
839
|
+
gap: 0.625rem;
|
|
840
|
+
cursor: pointer;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
.form-array__checkbox-input {
|
|
844
|
+
position: absolute;
|
|
845
|
+
opacity: 0;
|
|
846
|
+
width: 0;
|
|
847
|
+
height: 0;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.form-array__toggle-track {
|
|
851
|
+
position: relative;
|
|
852
|
+
width: 2.25rem;
|
|
853
|
+
height: 1.25rem;
|
|
854
|
+
background-color: var(--color-ref-gray-300, #d1d5db);
|
|
855
|
+
border-radius: 0.625rem;
|
|
856
|
+
transition: background-color 0.2s;
|
|
857
|
+
flex-shrink: 0;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.form-array__toggle-thumb {
|
|
861
|
+
position: absolute;
|
|
862
|
+
top: 0.125rem;
|
|
863
|
+
left: 0.125rem;
|
|
864
|
+
width: 1rem;
|
|
865
|
+
height: 1rem;
|
|
866
|
+
background-color: #ffffff;
|
|
867
|
+
border-radius: 50%;
|
|
868
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
869
|
+
transition: transform 0.2s;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.form-array__checkbox-input:checked + .form-array__toggle-track {
|
|
873
|
+
background-color: var(--color-ref-blue-500, #3b82f6);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.form-array__checkbox-input:checked + .form-array__toggle-track .form-array__toggle-thumb {
|
|
877
|
+
transform: translateX(1rem);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.form-array__toggle-label {
|
|
881
|
+
font-size: 0.8125rem;
|
|
882
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/* ============================================
|
|
886
|
+
SUBFORM (Complex Types)
|
|
887
|
+
============================================ */
|
|
888
|
+
|
|
889
|
+
.form-array__subform {
|
|
890
|
+
display: flex;
|
|
891
|
+
flex-direction: column;
|
|
892
|
+
gap: 0.75rem;
|
|
893
|
+
padding: 0.75rem;
|
|
894
|
+
background-color: #ffffff;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.form-array__subform-field {
|
|
898
|
+
display: flex;
|
|
899
|
+
flex-direction: column;
|
|
900
|
+
gap: 0.375rem;
|
|
901
|
+
animation: subfieldFadeIn 0.2s ease-out forwards;
|
|
902
|
+
opacity: 0;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
@keyframes subfieldFadeIn {
|
|
906
|
+
to {
|
|
907
|
+
opacity: 1;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.form-array__subform-label {
|
|
912
|
+
display: flex;
|
|
913
|
+
align-items: center;
|
|
914
|
+
gap: 0.25rem;
|
|
915
|
+
font-size: 0.75rem;
|
|
916
|
+
font-weight: 600;
|
|
917
|
+
color: var(--color-ref-gray-600, #4b5563);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
.form-array__subform-label-text {
|
|
921
|
+
line-height: 1.4;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.form-array__required {
|
|
925
|
+
color: var(--color-ref-red-500, #ef4444);
|
|
926
|
+
font-weight: 500;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.form-array__subform-description {
|
|
930
|
+
margin: 0;
|
|
931
|
+
font-size: 0.6875rem;
|
|
932
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
933
|
+
line-height: 1.4;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/* ============================================
|
|
937
|
+
EMPTY STATE
|
|
938
|
+
============================================ */
|
|
939
|
+
|
|
940
|
+
.form-array__empty {
|
|
941
|
+
display: flex;
|
|
942
|
+
flex-direction: column;
|
|
943
|
+
align-items: center;
|
|
944
|
+
justify-content: center;
|
|
945
|
+
padding: 2rem 1rem;
|
|
946
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
947
|
+
border: 2px dashed var(--color-ref-gray-300, #d1d5db);
|
|
948
|
+
border-radius: 0.625rem;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.form-array__empty :global(svg) {
|
|
952
|
+
width: 2.5rem;
|
|
953
|
+
height: 2.5rem;
|
|
954
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
955
|
+
margin-bottom: 0.625rem;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.form-array__empty-text {
|
|
959
|
+
margin: 0;
|
|
960
|
+
font-size: 0.875rem;
|
|
961
|
+
font-weight: 500;
|
|
962
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/* ============================================
|
|
966
|
+
ADD BUTTON
|
|
967
|
+
============================================ */
|
|
968
|
+
|
|
969
|
+
.form-array__add-btn {
|
|
970
|
+
display: inline-flex;
|
|
971
|
+
align-items: center;
|
|
972
|
+
justify-content: center;
|
|
973
|
+
gap: 0.5rem;
|
|
974
|
+
padding: 0.625rem 1rem;
|
|
975
|
+
border: 1px solid var(--color-ref-green-300, #86efac);
|
|
976
|
+
border-radius: 0.5rem;
|
|
977
|
+
background-color: var(--color-ref-green-50, #f0fdf4);
|
|
978
|
+
color: var(--color-ref-green-700, #15803d);
|
|
979
|
+
font-size: 0.8125rem;
|
|
980
|
+
font-weight: 600;
|
|
981
|
+
font-family: inherit;
|
|
982
|
+
cursor: pointer;
|
|
983
|
+
transition: all 0.15s;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
.form-array__add-btn:hover:not(:disabled) {
|
|
987
|
+
background-color: var(--color-ref-green-100, #dcfce7);
|
|
988
|
+
border-color: var(--color-ref-green-400, #4ade80);
|
|
989
|
+
color: var(--color-ref-green-800, #166534);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.form-array__add-btn:focus-visible {
|
|
993
|
+
outline: none;
|
|
994
|
+
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.form-array__add-btn:active:not(:disabled) {
|
|
998
|
+
background-color: var(--color-ref-green-200, #bbf7d0);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.form-array__add-btn:disabled {
|
|
1002
|
+
opacity: 0.5;
|
|
1003
|
+
cursor: not-allowed;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.form-array__add-btn :global(svg) {
|
|
1007
|
+
width: 1.125rem;
|
|
1008
|
+
height: 1.125rem;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* ============================================
|
|
1012
|
+
INFO BAR
|
|
1013
|
+
============================================ */
|
|
1014
|
+
|
|
1015
|
+
.form-array__info {
|
|
1016
|
+
display: flex;
|
|
1017
|
+
align-items: center;
|
|
1018
|
+
gap: 0.75rem;
|
|
1019
|
+
font-size: 0.6875rem;
|
|
1020
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.form-array__count {
|
|
1024
|
+
font-weight: 500;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.form-array__limit {
|
|
1028
|
+
padding: 0.125rem 0.375rem;
|
|
1029
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
1030
|
+
border-radius: 0.25rem;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* ============================================
|
|
1034
|
+
UNSUPPORTED TYPE
|
|
1035
|
+
============================================ */
|
|
1036
|
+
|
|
1037
|
+
.form-array__unsupported {
|
|
1038
|
+
padding: 0.75rem;
|
|
1039
|
+
background-color: var(--color-ref-amber-50, #fffbeb);
|
|
1040
|
+
border: 1px solid var(--color-ref-amber-200, #fde68a);
|
|
1041
|
+
border-radius: 0.375rem;
|
|
1042
|
+
color: var(--color-ref-amber-800, #92400e);
|
|
1043
|
+
font-size: 0.75rem;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.form-array__unsupported p {
|
|
1047
|
+
margin: 0;
|
|
1048
|
+
}
|
|
1049
|
+
</style>
|