@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,22 @@
|
|
|
1
|
+
import type { FieldSchema } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Field identifier */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Current array value */
|
|
6
|
+
value: unknown[];
|
|
7
|
+
/** Schema for array items */
|
|
8
|
+
itemSchema: FieldSchema;
|
|
9
|
+
/** Minimum number of items required */
|
|
10
|
+
minItems?: number;
|
|
11
|
+
/** Maximum number of items allowed */
|
|
12
|
+
maxItems?: number;
|
|
13
|
+
/** Label for add button */
|
|
14
|
+
addLabel?: string;
|
|
15
|
+
/** Whether the field is disabled */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Callback when value changes */
|
|
18
|
+
onChange: (value: unknown[]) => void;
|
|
19
|
+
}
|
|
20
|
+
declare const FormArray: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type FormArray = ReturnType<typeof FormArray>;
|
|
22
|
+
export default FormArray;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormCheckboxGroup Component
|
|
3
|
+
Checkbox group for multiple value selection (enum with multiple=true)
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Custom styled checkboxes with check icon
|
|
7
|
+
- Animated checkbox state transitions
|
|
8
|
+
- Focus-visible states for keyboard navigation
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import Icon from "@iconify/svelte";
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Field identifier (used for ARIA) */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Current selected values */
|
|
18
|
+
value: string[];
|
|
19
|
+
/** Available options */
|
|
20
|
+
options: string[];
|
|
21
|
+
/** ARIA description ID */
|
|
22
|
+
ariaDescribedBy?: string;
|
|
23
|
+
/** Callback when value changes */
|
|
24
|
+
onChange: (value: string[]) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
id,
|
|
29
|
+
value = [],
|
|
30
|
+
options = [],
|
|
31
|
+
ariaDescribedBy,
|
|
32
|
+
onChange
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Handle checkbox toggle
|
|
37
|
+
*/
|
|
38
|
+
function handleCheckboxChange(option: string, checked: boolean): void {
|
|
39
|
+
const currentValues = Array.isArray(value) ? [...value] : [];
|
|
40
|
+
|
|
41
|
+
if (checked) {
|
|
42
|
+
if (!currentValues.includes(option)) {
|
|
43
|
+
onChange([...currentValues, option]);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
onChange(currentValues.filter((v) => v !== option));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div
|
|
52
|
+
class="form-checkbox-group"
|
|
53
|
+
role="group"
|
|
54
|
+
aria-labelledby="{id}-label"
|
|
55
|
+
aria-describedby={ariaDescribedBy}
|
|
56
|
+
>
|
|
57
|
+
{#each options as option (option)}
|
|
58
|
+
{@const isChecked = Array.isArray(value) && value.includes(option)}
|
|
59
|
+
<label class="form-checkbox-item">
|
|
60
|
+
<input
|
|
61
|
+
type="checkbox"
|
|
62
|
+
class="form-checkbox__input"
|
|
63
|
+
value={option}
|
|
64
|
+
checked={isChecked}
|
|
65
|
+
onchange={(e) => handleCheckboxChange(option, e.currentTarget.checked)}
|
|
66
|
+
/>
|
|
67
|
+
<span class="form-checkbox__custom" aria-hidden="true">
|
|
68
|
+
<Icon icon="heroicons:check" />
|
|
69
|
+
</span>
|
|
70
|
+
<span class="form-checkbox__label">
|
|
71
|
+
{option}
|
|
72
|
+
</span>
|
|
73
|
+
</label>
|
|
74
|
+
{/each}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.form-checkbox-group {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: 0.625rem;
|
|
82
|
+
padding: 0.75rem;
|
|
83
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
84
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
85
|
+
border-radius: 0.5rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.form-checkbox-item {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 0.625rem;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
padding: 0.375rem;
|
|
94
|
+
margin: -0.375rem;
|
|
95
|
+
border-radius: 0.375rem;
|
|
96
|
+
transition: background-color 0.15s;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.form-checkbox-item:hover {
|
|
100
|
+
background-color: var(--color-ref-gray-100, #f3f4f6);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.form-checkbox__input {
|
|
104
|
+
position: absolute;
|
|
105
|
+
opacity: 0;
|
|
106
|
+
width: 0;
|
|
107
|
+
height: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.form-checkbox__custom {
|
|
111
|
+
width: 1.125rem;
|
|
112
|
+
height: 1.125rem;
|
|
113
|
+
border: 1.5px solid var(--color-ref-gray-300, #d1d5db);
|
|
114
|
+
border-radius: 0.25rem;
|
|
115
|
+
background-color: #ffffff;
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
transition: all 0.15s;
|
|
120
|
+
flex-shrink: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.form-checkbox__custom :global(svg) {
|
|
124
|
+
width: 0.75rem;
|
|
125
|
+
height: 0.75rem;
|
|
126
|
+
color: #ffffff;
|
|
127
|
+
opacity: 0;
|
|
128
|
+
transform: scale(0.5);
|
|
129
|
+
transition: all 0.15s;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.form-checkbox__input:checked + .form-checkbox__custom {
|
|
133
|
+
background-color: var(--color-ref-blue-500, #3b82f6);
|
|
134
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.form-checkbox__input:checked + .form-checkbox__custom :global(svg) {
|
|
138
|
+
opacity: 1;
|
|
139
|
+
transform: scale(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.form-checkbox__input:focus-visible + .form-checkbox__custom {
|
|
143
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.form-checkbox__label {
|
|
147
|
+
font-size: 0.875rem;
|
|
148
|
+
color: var(--color-ref-gray-700, #374151);
|
|
149
|
+
line-height: 1.4;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
152
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier (used for ARIA) */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current selected values */
|
|
5
|
+
value: string[];
|
|
6
|
+
/** Available options */
|
|
7
|
+
options: string[];
|
|
8
|
+
/** ARIA description ID */
|
|
9
|
+
ariaDescribedBy?: string;
|
|
10
|
+
/** Callback when value changes */
|
|
11
|
+
onChange: (value: string[]) => void;
|
|
12
|
+
}
|
|
13
|
+
declare const FormCheckboxGroup: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type FormCheckboxGroup = ReturnType<typeof FormCheckboxGroup>;
|
|
15
|
+
export default FormCheckboxGroup;
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormField Component
|
|
3
|
+
Factory component that renders the appropriate field based on schema type
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Automatically selects the correct field component based on schema
|
|
7
|
+
- Wraps fields with FormFieldWrapper for consistent layout
|
|
8
|
+
- Supports all current field types (string, number, boolean, select, checkbox group)
|
|
9
|
+
- Extensible architecture for future complex types (array, object)
|
|
10
|
+
|
|
11
|
+
Type Resolution Order:
|
|
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
|
|
21
|
+
-->
|
|
22
|
+
|
|
23
|
+
<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";
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
/** Unique key/id for the field */
|
|
36
|
+
fieldKey: string;
|
|
37
|
+
/** Field schema definition */
|
|
38
|
+
schema: FieldSchema;
|
|
39
|
+
/** Current field value */
|
|
40
|
+
value: unknown;
|
|
41
|
+
/** Whether the field is required */
|
|
42
|
+
required?: boolean;
|
|
43
|
+
/** Animation delay index for staggered animations */
|
|
44
|
+
animationIndex?: number;
|
|
45
|
+
/** Callback when the field value changes */
|
|
46
|
+
onChange: (value: unknown) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
fieldKey,
|
|
51
|
+
schema,
|
|
52
|
+
value,
|
|
53
|
+
required = false,
|
|
54
|
+
animationIndex = 0,
|
|
55
|
+
onChange
|
|
56
|
+
}: Props = $props();
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Computed description ID for ARIA association
|
|
60
|
+
*/
|
|
61
|
+
const descriptionId = $derived(schema.description && schema.title ? `${fieldKey}-description` : undefined);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Animation delay based on index
|
|
65
|
+
*/
|
|
66
|
+
const animationDelay = $derived(animationIndex * 30);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Field label - prefer title, fall back to description, then key
|
|
70
|
+
*/
|
|
71
|
+
const fieldLabel = $derived(String(schema.title ?? schema.description ?? fieldKey));
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Determine the field type to render
|
|
75
|
+
*/
|
|
76
|
+
const fieldType = $derived.by(() => {
|
|
77
|
+
// Hidden fields should not be rendered
|
|
78
|
+
if (schema.format === "hidden") {
|
|
79
|
+
return "hidden";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Enum with multiple selection -> checkbox group
|
|
83
|
+
if (schema.enum && schema.multiple) {
|
|
84
|
+
return "checkbox-group";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Enum with single selection -> select
|
|
88
|
+
if (schema.enum) {
|
|
89
|
+
return "select-enum";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Multiline string -> textarea
|
|
93
|
+
if (schema.type === "string" && schema.format === "multiline") {
|
|
94
|
+
return "textarea";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// String -> text field
|
|
98
|
+
if (schema.type === "string") {
|
|
99
|
+
return "text";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Number or integer -> number field
|
|
103
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
104
|
+
return "number";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Boolean -> toggle
|
|
108
|
+
if (schema.type === "boolean") {
|
|
109
|
+
return "toggle";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Select type or has options -> select
|
|
113
|
+
if (schema.type === "select" || schema.options) {
|
|
114
|
+
return "select-options";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Future: Array type support
|
|
118
|
+
if (schema.type === "array") {
|
|
119
|
+
return "array";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Future: Object type support
|
|
123
|
+
if (schema.type === "object") {
|
|
124
|
+
return "object";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback to text
|
|
128
|
+
return "text";
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get enum options as string array for select/checkbox components
|
|
133
|
+
*/
|
|
134
|
+
const enumOptions = $derived.by((): string[] => {
|
|
135
|
+
if (!schema.enum) return [];
|
|
136
|
+
return schema.enum.map((opt) => String(opt));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get select options for select-options type
|
|
141
|
+
*/
|
|
142
|
+
const selectOptions = $derived.by((): FieldOption[] => {
|
|
143
|
+
if (!schema.options) return [];
|
|
144
|
+
return schema.options as FieldOption[];
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get current value as the appropriate type
|
|
149
|
+
*/
|
|
150
|
+
const stringValue = $derived(String(value ?? ""));
|
|
151
|
+
const numberValue = $derived(value as number | string);
|
|
152
|
+
const booleanValue = $derived(Boolean(value ?? schema.default ?? false));
|
|
153
|
+
const arrayValue = $derived.by((): string[] => {
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
return value.map((v) => String(v));
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
});
|
|
159
|
+
const arrayItems = $derived.by((): unknown[] => {
|
|
160
|
+
if (Array.isArray(value)) {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
return [];
|
|
164
|
+
});
|
|
165
|
+
</script>
|
|
166
|
+
|
|
167
|
+
{#if fieldType !== "hidden"}
|
|
168
|
+
<FormFieldWrapper
|
|
169
|
+
id={fieldKey}
|
|
170
|
+
label={fieldLabel}
|
|
171
|
+
{required}
|
|
172
|
+
description={schema.title ? schema.description : undefined}
|
|
173
|
+
{animationDelay}
|
|
174
|
+
>
|
|
175
|
+
{#if fieldType === "checkbox-group"}
|
|
176
|
+
<FormCheckboxGroup
|
|
177
|
+
id={fieldKey}
|
|
178
|
+
value={arrayValue}
|
|
179
|
+
options={enumOptions}
|
|
180
|
+
ariaDescribedBy={descriptionId}
|
|
181
|
+
onChange={(val) => onChange(val)}
|
|
182
|
+
/>
|
|
183
|
+
{:else if fieldType === "select-enum"}
|
|
184
|
+
<FormSelect
|
|
185
|
+
id={fieldKey}
|
|
186
|
+
value={stringValue}
|
|
187
|
+
options={enumOptions}
|
|
188
|
+
{required}
|
|
189
|
+
ariaDescribedBy={descriptionId}
|
|
190
|
+
onChange={(val) => onChange(val)}
|
|
191
|
+
/>
|
|
192
|
+
{:else if fieldType === "textarea"}
|
|
193
|
+
<FormTextarea
|
|
194
|
+
id={fieldKey}
|
|
195
|
+
value={stringValue}
|
|
196
|
+
placeholder={schema.placeholder ?? ""}
|
|
197
|
+
{required}
|
|
198
|
+
ariaDescribedBy={descriptionId}
|
|
199
|
+
onChange={(val) => onChange(val)}
|
|
200
|
+
/>
|
|
201
|
+
{:else if fieldType === "text"}
|
|
202
|
+
<FormTextField
|
|
203
|
+
id={fieldKey}
|
|
204
|
+
value={stringValue}
|
|
205
|
+
placeholder={schema.placeholder ?? ""}
|
|
206
|
+
{required}
|
|
207
|
+
ariaDescribedBy={descriptionId}
|
|
208
|
+
onChange={(val) => onChange(val)}
|
|
209
|
+
/>
|
|
210
|
+
{:else if fieldType === "number"}
|
|
211
|
+
<FormNumberField
|
|
212
|
+
id={fieldKey}
|
|
213
|
+
value={numberValue}
|
|
214
|
+
placeholder={schema.placeholder ?? ""}
|
|
215
|
+
min={schema.minimum}
|
|
216
|
+
max={schema.maximum}
|
|
217
|
+
{required}
|
|
218
|
+
ariaDescribedBy={descriptionId}
|
|
219
|
+
onChange={(val) => onChange(val)}
|
|
220
|
+
/>
|
|
221
|
+
{:else if fieldType === "toggle"}
|
|
222
|
+
<FormToggle
|
|
223
|
+
id={fieldKey}
|
|
224
|
+
value={booleanValue}
|
|
225
|
+
ariaDescribedBy={descriptionId}
|
|
226
|
+
onChange={(val) => onChange(val)}
|
|
227
|
+
/>
|
|
228
|
+
{:else if fieldType === "select-options"}
|
|
229
|
+
<FormSelect
|
|
230
|
+
id={fieldKey}
|
|
231
|
+
value={stringValue}
|
|
232
|
+
options={selectOptions}
|
|
233
|
+
{required}
|
|
234
|
+
ariaDescribedBy={descriptionId}
|
|
235
|
+
onChange={(val) => onChange(val)}
|
|
236
|
+
/>
|
|
237
|
+
{:else if fieldType === "array" && schema.items}
|
|
238
|
+
<FormArray
|
|
239
|
+
id={fieldKey}
|
|
240
|
+
value={arrayItems}
|
|
241
|
+
itemSchema={schema.items}
|
|
242
|
+
minItems={schema.minItems}
|
|
243
|
+
maxItems={schema.maxItems}
|
|
244
|
+
addLabel={`Add ${schema.items.title ?? "Item"}`}
|
|
245
|
+
onChange={(val) => onChange(val)}
|
|
246
|
+
/>
|
|
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
|
+
{:else}
|
|
253
|
+
<!-- Fallback to text input -->
|
|
254
|
+
<FormTextField
|
|
255
|
+
id={fieldKey}
|
|
256
|
+
value={stringValue}
|
|
257
|
+
placeholder={schema.placeholder ?? ""}
|
|
258
|
+
ariaDescribedBy={descriptionId}
|
|
259
|
+
onChange={(val) => onChange(val)}
|
|
260
|
+
/>
|
|
261
|
+
{/if}
|
|
262
|
+
</FormFieldWrapper>
|
|
263
|
+
{/if}
|
|
264
|
+
|
|
265
|
+
<style>
|
|
266
|
+
.form-field__unsupported {
|
|
267
|
+
padding: 0.75rem;
|
|
268
|
+
background-color: var(--color-ref-amber-50, #fffbeb);
|
|
269
|
+
border: 1px solid var(--color-ref-amber-200, #fde68a);
|
|
270
|
+
border-radius: 0.5rem;
|
|
271
|
+
color: var(--color-ref-amber-800, #92400e);
|
|
272
|
+
font-size: 0.8125rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.form-field__unsupported p {
|
|
276
|
+
margin: 0;
|
|
277
|
+
}
|
|
278
|
+
</style>
|
|
279
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FieldSchema } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Unique key/id for the field */
|
|
4
|
+
fieldKey: string;
|
|
5
|
+
/** Field schema definition */
|
|
6
|
+
schema: FieldSchema;
|
|
7
|
+
/** Current field value */
|
|
8
|
+
value: unknown;
|
|
9
|
+
/** Whether the field is required */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Animation delay index for staggered animations */
|
|
12
|
+
animationIndex?: number;
|
|
13
|
+
/** Callback when the field value changes */
|
|
14
|
+
onChange: (value: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const FormField: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type FormField = ReturnType<typeof FormField>;
|
|
18
|
+
export default FormField;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormFieldWrapper Component
|
|
3
|
+
Provides consistent layout for form fields with label, description, and animations
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Proper label associations with for/id attributes
|
|
7
|
+
- ARIA describedby for field descriptions
|
|
8
|
+
- Staggered fade-in animations
|
|
9
|
+
- Required field indicators
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { Snippet } from "svelte";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/** Field identifier for label association */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Display label text */
|
|
19
|
+
label: string;
|
|
20
|
+
/** Whether the field is required */
|
|
21
|
+
required?: boolean;
|
|
22
|
+
/** Description/help text for the field */
|
|
23
|
+
description?: string;
|
|
24
|
+
/** Animation delay in milliseconds */
|
|
25
|
+
animationDelay?: number;
|
|
26
|
+
/** Slot content for the field input */
|
|
27
|
+
children: Snippet;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
id,
|
|
32
|
+
label,
|
|
33
|
+
required = false,
|
|
34
|
+
description,
|
|
35
|
+
animationDelay = 0,
|
|
36
|
+
children
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Computed description ID for ARIA association
|
|
41
|
+
*/
|
|
42
|
+
const descriptionId = $derived(description ? `${id}-description` : undefined);
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div class="form-field" style="animation-delay: {animationDelay}ms">
|
|
46
|
+
<!-- Field Label -->
|
|
47
|
+
<label class="form-field__label" for={id}>
|
|
48
|
+
<span class="form-field__label-text">
|
|
49
|
+
{label}
|
|
50
|
+
</span>
|
|
51
|
+
{#if required}
|
|
52
|
+
<span class="form-field__required" aria-label="required">*</span>
|
|
53
|
+
{/if}
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<!-- Field Input Container -->
|
|
57
|
+
<div class="form-field__input-wrapper">
|
|
58
|
+
{@render children()}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Field Description -->
|
|
62
|
+
{#if description}
|
|
63
|
+
<p id={descriptionId} class="form-field__description">
|
|
64
|
+
{description}
|
|
65
|
+
</p>
|
|
66
|
+
{/if}
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
/* ============================================
|
|
71
|
+
FIELD CONTAINER
|
|
72
|
+
============================================ */
|
|
73
|
+
|
|
74
|
+
.form-field {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
gap: 0.5rem;
|
|
78
|
+
animation: fieldFadeIn 0.3s ease-out forwards;
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transform: translateY(4px);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@keyframes fieldFadeIn {
|
|
84
|
+
to {
|
|
85
|
+
opacity: 1;
|
|
86
|
+
transform: translateY(0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ============================================
|
|
91
|
+
LABELS
|
|
92
|
+
============================================ */
|
|
93
|
+
|
|
94
|
+
.form-field__label {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 0.25rem;
|
|
98
|
+
font-size: 0.8125rem;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
color: var(--color-ref-gray-700, #374151);
|
|
101
|
+
letter-spacing: -0.01em;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.form-field__label-text {
|
|
105
|
+
line-height: 1.4;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.form-field__required {
|
|
109
|
+
color: var(--color-ref-red-500, #ef4444);
|
|
110
|
+
font-weight: 500;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ============================================
|
|
114
|
+
INPUT WRAPPER
|
|
115
|
+
============================================ */
|
|
116
|
+
|
|
117
|
+
.form-field__input-wrapper {
|
|
118
|
+
position: relative;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ============================================
|
|
122
|
+
FIELD DESCRIPTION
|
|
123
|
+
============================================ */
|
|
124
|
+
|
|
125
|
+
.form-field__description {
|
|
126
|
+
margin: 0;
|
|
127
|
+
font-size: 0.75rem;
|
|
128
|
+
color: var(--color-ref-gray-500, #6b7280);
|
|
129
|
+
line-height: 1.5;
|
|
130
|
+
padding-left: 0.125rem;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
133
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Field identifier for label association */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Display label text */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Whether the field is required */
|
|
8
|
+
required?: boolean;
|
|
9
|
+
/** Description/help text for the field */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Animation delay in milliseconds */
|
|
12
|
+
animationDelay?: number;
|
|
13
|
+
/** Slot content for the field input */
|
|
14
|
+
children: Snippet;
|
|
15
|
+
}
|
|
16
|
+
declare const FormFieldWrapper: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type FormFieldWrapper = ReturnType<typeof FormFieldWrapper>;
|
|
18
|
+
export default FormFieldWrapper;
|