@d34dman/flowdrop 0.0.22 → 0.0.24
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 +141 -520
- 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 +297 -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/FormRangeField.svelte +252 -0
- package/dist/components/form/FormRangeField.svelte.d.ts +21 -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 +42 -0
- package/dist/components/form/index.js +46 -0
- package/dist/components/form/types.d.ts +224 -0
- package/dist/components/form/types.js +29 -0
- package/dist/components/nodes/GatewayNode.svelte +76 -16
- 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 +176 -1
- 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,109 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormNumberField Component
|
|
3
|
+
Number input field for numeric values
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Tabular numeric font for alignment
|
|
7
|
+
- Min/max/step support
|
|
8
|
+
- Proper ARIA attributes for accessibility
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Field identifier */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Current value */
|
|
16
|
+
value: number | string;
|
|
17
|
+
/** Placeholder text */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Minimum allowed value */
|
|
20
|
+
min?: number;
|
|
21
|
+
/** Maximum allowed value */
|
|
22
|
+
max?: number;
|
|
23
|
+
/** Step increment */
|
|
24
|
+
step?: number;
|
|
25
|
+
/** Whether the field is required */
|
|
26
|
+
required?: boolean;
|
|
27
|
+
/** ARIA description ID */
|
|
28
|
+
ariaDescribedBy?: string;
|
|
29
|
+
/** Callback when value changes */
|
|
30
|
+
onChange: (value: number | string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
id,
|
|
35
|
+
value = "",
|
|
36
|
+
placeholder = "",
|
|
37
|
+
min,
|
|
38
|
+
max,
|
|
39
|
+
step,
|
|
40
|
+
required = false,
|
|
41
|
+
ariaDescribedBy,
|
|
42
|
+
onChange
|
|
43
|
+
}: Props = $props();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handle input changes
|
|
47
|
+
* Returns the value as a number if valid, otherwise as string
|
|
48
|
+
*/
|
|
49
|
+
function handleInput(event: Event): void {
|
|
50
|
+
const target = event.currentTarget as HTMLInputElement;
|
|
51
|
+
const inputValue = target.value;
|
|
52
|
+
|
|
53
|
+
if (inputValue === "") {
|
|
54
|
+
onChange("");
|
|
55
|
+
} else {
|
|
56
|
+
const numValue = Number(inputValue);
|
|
57
|
+
onChange(isNaN(numValue) ? inputValue : numValue);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<input
|
|
63
|
+
{id}
|
|
64
|
+
type="number"
|
|
65
|
+
class="form-number-field"
|
|
66
|
+
value={value ?? ""}
|
|
67
|
+
{placeholder}
|
|
68
|
+
{min}
|
|
69
|
+
{max}
|
|
70
|
+
{step}
|
|
71
|
+
aria-describedby={ariaDescribedBy}
|
|
72
|
+
aria-required={required}
|
|
73
|
+
oninput={handleInput}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.form-number-field {
|
|
78
|
+
width: 100%;
|
|
79
|
+
padding: 0.625rem 0.875rem;
|
|
80
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
81
|
+
border-radius: 0.5rem;
|
|
82
|
+
font-size: 0.875rem;
|
|
83
|
+
font-family: inherit;
|
|
84
|
+
font-variant-numeric: tabular-nums;
|
|
85
|
+
color: var(--color-ref-gray-900, #111827);
|
|
86
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
87
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
88
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.form-number-field::placeholder {
|
|
92
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.form-number-field:hover {
|
|
96
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
97
|
+
background-color: #ffffff;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.form-number-field:focus {
|
|
101
|
+
outline: none;
|
|
102
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
103
|
+
background-color: #ffffff;
|
|
104
|
+
box-shadow:
|
|
105
|
+
0 0 0 3px rgba(59, 130, 246, 0.12),
|
|
106
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
109
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current value */
|
|
5
|
+
value: number | string;
|
|
6
|
+
/** Placeholder text */
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Minimum allowed value */
|
|
9
|
+
min?: number;
|
|
10
|
+
/** Maximum allowed value */
|
|
11
|
+
max?: number;
|
|
12
|
+
/** Step increment */
|
|
13
|
+
step?: number;
|
|
14
|
+
/** Whether the field is required */
|
|
15
|
+
required?: boolean;
|
|
16
|
+
/** ARIA description ID */
|
|
17
|
+
ariaDescribedBy?: string;
|
|
18
|
+
/** Callback when value changes */
|
|
19
|
+
onChange: (value: number | string) => void;
|
|
20
|
+
}
|
|
21
|
+
declare const FormNumberField: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type FormNumberField = ReturnType<typeof FormNumberField>;
|
|
23
|
+
export default FormNumberField;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormRangeField Component
|
|
3
|
+
Range slider input field for numeric values
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Modern styled range slider with custom track and thumb
|
|
7
|
+
- Min/max/step support for value constraints
|
|
8
|
+
- Real-time value display with tabular numeric font
|
|
9
|
+
- Proper ARIA attributes for accessibility
|
|
10
|
+
- Visual feedback for current value position
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Field identifier */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Current value */
|
|
18
|
+
value: number | string;
|
|
19
|
+
/** Minimum allowed value */
|
|
20
|
+
min?: number;
|
|
21
|
+
/** Maximum allowed value */
|
|
22
|
+
max?: number;
|
|
23
|
+
/** Step increment */
|
|
24
|
+
step?: number;
|
|
25
|
+
/** Whether the field is required */
|
|
26
|
+
required?: boolean;
|
|
27
|
+
/** ARIA description ID */
|
|
28
|
+
ariaDescribedBy?: string;
|
|
29
|
+
/** Callback when value changes */
|
|
30
|
+
onChange: (value: number) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
id,
|
|
35
|
+
value = 0,
|
|
36
|
+
min = 0,
|
|
37
|
+
max = 100,
|
|
38
|
+
step = 1,
|
|
39
|
+
required = false,
|
|
40
|
+
ariaDescribedBy,
|
|
41
|
+
onChange
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compute the current numeric value
|
|
46
|
+
* Handles string values and defaults
|
|
47
|
+
*/
|
|
48
|
+
const numericValue = $derived.by((): number => {
|
|
49
|
+
if (typeof value === "number") {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
const parsed = Number(value);
|
|
53
|
+
return isNaN(parsed) ? min : parsed;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compute the percentage position for the filled track
|
|
58
|
+
* Used to show visual progress of the slider
|
|
59
|
+
*/
|
|
60
|
+
const progressPercentage = $derived.by((): number => {
|
|
61
|
+
const range = max - min;
|
|
62
|
+
if (range === 0) return 0;
|
|
63
|
+
return ((numericValue - min) / range) * 100;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle input changes
|
|
68
|
+
* Converts the value to a number and triggers onChange
|
|
69
|
+
*/
|
|
70
|
+
function handleInput(event: Event): void {
|
|
71
|
+
const target = event.currentTarget as HTMLInputElement;
|
|
72
|
+
const numValue = Number(target.value);
|
|
73
|
+
onChange(numValue);
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div class="form-range-container">
|
|
78
|
+
<div class="form-range-slider">
|
|
79
|
+
<input
|
|
80
|
+
{id}
|
|
81
|
+
type="range"
|
|
82
|
+
class="form-range-field"
|
|
83
|
+
value={numericValue}
|
|
84
|
+
{min}
|
|
85
|
+
{max}
|
|
86
|
+
{step}
|
|
87
|
+
aria-describedby={ariaDescribedBy}
|
|
88
|
+
aria-required={required}
|
|
89
|
+
aria-valuemin={min}
|
|
90
|
+
aria-valuemax={max}
|
|
91
|
+
aria-valuenow={numericValue}
|
|
92
|
+
oninput={handleInput}
|
|
93
|
+
style="--progress: {progressPercentage}%"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="form-range-values">
|
|
97
|
+
<span class="form-range-min">{min}</span>
|
|
98
|
+
<span class="form-range-current">{numericValue}</span>
|
|
99
|
+
<span class="form-range-max">{max}</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<style>
|
|
104
|
+
.form-range-container {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
gap: 0.5rem;
|
|
108
|
+
width: 100%;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.form-range-slider {
|
|
112
|
+
position: relative;
|
|
113
|
+
width: 100%;
|
|
114
|
+
padding: 0.25rem 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.form-range-field {
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 6px;
|
|
120
|
+
border-radius: 3px;
|
|
121
|
+
appearance: none;
|
|
122
|
+
-webkit-appearance: none;
|
|
123
|
+
background: linear-gradient(
|
|
124
|
+
to right,
|
|
125
|
+
var(--color-ref-blue-500, #3b82f6) 0%,
|
|
126
|
+
var(--color-ref-blue-500, #3b82f6) var(--progress, 0%),
|
|
127
|
+
var(--color-ref-gray-200, #e5e7eb) var(--progress, 0%),
|
|
128
|
+
var(--color-ref-gray-200, #e5e7eb) 100%
|
|
129
|
+
);
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
transition: background 0.15s ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Webkit (Chrome, Safari, Edge) - Track */
|
|
135
|
+
.form-range-field::-webkit-slider-runnable-track {
|
|
136
|
+
height: 6px;
|
|
137
|
+
border-radius: 3px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Webkit - Thumb */
|
|
141
|
+
.form-range-field::-webkit-slider-thumb {
|
|
142
|
+
-webkit-appearance: none;
|
|
143
|
+
appearance: none;
|
|
144
|
+
width: 18px;
|
|
145
|
+
height: 18px;
|
|
146
|
+
border-radius: 50%;
|
|
147
|
+
background: linear-gradient(
|
|
148
|
+
135deg,
|
|
149
|
+
#ffffff 0%,
|
|
150
|
+
var(--color-ref-gray-50, #f9fafb) 100%
|
|
151
|
+
);
|
|
152
|
+
border: 2px solid var(--color-ref-blue-500, #3b82f6);
|
|
153
|
+
box-shadow:
|
|
154
|
+
0 2px 6px rgba(59, 130, 246, 0.25),
|
|
155
|
+
0 1px 2px rgba(0, 0, 0, 0.1);
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
margin-top: -6px;
|
|
158
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.form-range-field::-webkit-slider-thumb:hover {
|
|
162
|
+
transform: scale(1.1);
|
|
163
|
+
box-shadow:
|
|
164
|
+
0 4px 12px rgba(59, 130, 246, 0.35),
|
|
165
|
+
0 2px 4px rgba(0, 0, 0, 0.1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.form-range-field:focus::-webkit-slider-thumb {
|
|
169
|
+
box-shadow:
|
|
170
|
+
0 0 0 3px rgba(59, 130, 246, 0.2),
|
|
171
|
+
0 4px 12px rgba(59, 130, 246, 0.35);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Firefox - Track */
|
|
175
|
+
.form-range-field::-moz-range-track {
|
|
176
|
+
height: 6px;
|
|
177
|
+
border-radius: 3px;
|
|
178
|
+
background: var(--color-ref-gray-200, #e5e7eb);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.form-range-field::-moz-range-progress {
|
|
182
|
+
height: 6px;
|
|
183
|
+
border-radius: 3px;
|
|
184
|
+
background: var(--color-ref-blue-500, #3b82f6);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Firefox - Thumb */
|
|
188
|
+
.form-range-field::-moz-range-thumb {
|
|
189
|
+
width: 18px;
|
|
190
|
+
height: 18px;
|
|
191
|
+
border-radius: 50%;
|
|
192
|
+
background: linear-gradient(
|
|
193
|
+
135deg,
|
|
194
|
+
#ffffff 0%,
|
|
195
|
+
var(--color-ref-gray-50, #f9fafb) 100%
|
|
196
|
+
);
|
|
197
|
+
border: 2px solid var(--color-ref-blue-500, #3b82f6);
|
|
198
|
+
box-shadow:
|
|
199
|
+
0 2px 6px rgba(59, 130, 246, 0.25),
|
|
200
|
+
0 1px 2px rgba(0, 0, 0, 0.1);
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.form-range-field::-moz-range-thumb:hover {
|
|
206
|
+
transform: scale(1.1);
|
|
207
|
+
box-shadow:
|
|
208
|
+
0 4px 12px rgba(59, 130, 246, 0.35),
|
|
209
|
+
0 2px 4px rgba(0, 0, 0, 0.1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.form-range-field:focus::-moz-range-thumb {
|
|
213
|
+
box-shadow:
|
|
214
|
+
0 0 0 3px rgba(59, 130, 246, 0.2),
|
|
215
|
+
0 4px 12px rgba(59, 130, 246, 0.35);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Focus styles */
|
|
219
|
+
.form-range-field:focus {
|
|
220
|
+
outline: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.form-range-field:focus-visible {
|
|
224
|
+
outline: none;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Value display row */
|
|
228
|
+
.form-range-values {
|
|
229
|
+
display: flex;
|
|
230
|
+
justify-content: space-between;
|
|
231
|
+
align-items: center;
|
|
232
|
+
font-size: 0.75rem;
|
|
233
|
+
font-variant-numeric: tabular-nums;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.form-range-min,
|
|
237
|
+
.form-range-max {
|
|
238
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
239
|
+
font-weight: 400;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.form-range-current {
|
|
243
|
+
font-weight: 600;
|
|
244
|
+
color: var(--color-ref-blue-600, #2563eb);
|
|
245
|
+
background-color: var(--color-ref-blue-50, #eff6ff);
|
|
246
|
+
padding: 0.125rem 0.5rem;
|
|
247
|
+
border-radius: 0.25rem;
|
|
248
|
+
min-width: 2.5rem;
|
|
249
|
+
text-align: center;
|
|
250
|
+
}
|
|
251
|
+
</style>
|
|
252
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current value */
|
|
5
|
+
value: number | string;
|
|
6
|
+
/** Minimum allowed value */
|
|
7
|
+
min?: number;
|
|
8
|
+
/** Maximum allowed value */
|
|
9
|
+
max?: number;
|
|
10
|
+
/** Step increment */
|
|
11
|
+
step?: number;
|
|
12
|
+
/** Whether the field is required */
|
|
13
|
+
required?: boolean;
|
|
14
|
+
/** ARIA description ID */
|
|
15
|
+
ariaDescribedBy?: string;
|
|
16
|
+
/** Callback when value changes */
|
|
17
|
+
onChange: (value: number) => void;
|
|
18
|
+
}
|
|
19
|
+
declare const FormRangeField: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type FormRangeField = ReturnType<typeof FormRangeField>;
|
|
21
|
+
export default FormRangeField;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormSelect Component
|
|
3
|
+
Dropdown select for single value selection
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Custom styled dropdown with chevron icon
|
|
7
|
+
- Supports both string[] and FieldOption[] for options
|
|
8
|
+
- Proper ARIA attributes for accessibility
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
import Icon from "@iconify/svelte";
|
|
13
|
+
import { normalizeOptions, type FieldOption } from "./types.js";
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/** Field identifier */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Current value */
|
|
19
|
+
value: string;
|
|
20
|
+
/** Available options - can be string[] or FieldOption[] */
|
|
21
|
+
options: FieldOption[] | string[];
|
|
22
|
+
/** Whether the field is required */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** ARIA description ID */
|
|
25
|
+
ariaDescribedBy?: string;
|
|
26
|
+
/** Callback when value changes */
|
|
27
|
+
onChange: (value: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
id,
|
|
32
|
+
value = "",
|
|
33
|
+
options = [],
|
|
34
|
+
required = false,
|
|
35
|
+
ariaDescribedBy,
|
|
36
|
+
onChange
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Normalize options to consistent FieldOption format
|
|
41
|
+
*/
|
|
42
|
+
const normalizedOptions = $derived(normalizeOptions(options));
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handle select changes
|
|
46
|
+
*/
|
|
47
|
+
function handleChange(event: Event): void {
|
|
48
|
+
const target = event.currentTarget as HTMLSelectElement;
|
|
49
|
+
onChange(target.value);
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div class="form-select-wrapper">
|
|
54
|
+
<select
|
|
55
|
+
{id}
|
|
56
|
+
class="form-select"
|
|
57
|
+
value={value ?? ""}
|
|
58
|
+
aria-describedby={ariaDescribedBy}
|
|
59
|
+
aria-required={required}
|
|
60
|
+
onchange={handleChange}
|
|
61
|
+
>
|
|
62
|
+
{#each normalizedOptions as option (option.value)}
|
|
63
|
+
<option value={option.value}>{option.label}</option>
|
|
64
|
+
{/each}
|
|
65
|
+
</select>
|
|
66
|
+
<span class="form-select__icon" aria-hidden="true">
|
|
67
|
+
<Icon icon="heroicons:chevron-down" />
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
.form-select-wrapper {
|
|
73
|
+
position: relative;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.form-select {
|
|
77
|
+
width: 100%;
|
|
78
|
+
padding: 0.625rem 2.5rem 0.625rem 0.875rem;
|
|
79
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
80
|
+
border-radius: 0.5rem;
|
|
81
|
+
font-size: 0.875rem;
|
|
82
|
+
font-family: inherit;
|
|
83
|
+
color: var(--color-ref-gray-900, #111827);
|
|
84
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
85
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
86
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
appearance: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.form-select:hover {
|
|
92
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
93
|
+
background-color: #ffffff;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.form-select:focus {
|
|
97
|
+
outline: none;
|
|
98
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
99
|
+
background-color: #ffffff;
|
|
100
|
+
box-shadow:
|
|
101
|
+
0 0 0 3px rgba(59, 130, 246, 0.12),
|
|
102
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.form-select__icon {
|
|
106
|
+
position: absolute;
|
|
107
|
+
right: 0.75rem;
|
|
108
|
+
top: 50%;
|
|
109
|
+
transform: translateY(-50%);
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
transition: color 0.2s;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.form-select__icon :global(svg) {
|
|
118
|
+
width: 1rem;
|
|
119
|
+
height: 1rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.form-select:focus + .form-select__icon {
|
|
123
|
+
color: var(--color-ref-blue-500, #3b82f6);
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
126
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type FieldOption } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Field identifier */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Current value */
|
|
6
|
+
value: string;
|
|
7
|
+
/** Available options - can be string[] or FieldOption[] */
|
|
8
|
+
options: FieldOption[] | string[];
|
|
9
|
+
/** Whether the field is required */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** ARIA description ID */
|
|
12
|
+
ariaDescribedBy?: string;
|
|
13
|
+
/** Callback when value changes */
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const FormSelect: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type FormSelect = ReturnType<typeof FormSelect>;
|
|
18
|
+
export default FormSelect;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
FormTextField Component
|
|
3
|
+
Text input field for string values
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Standard text input with focus and hover states
|
|
7
|
+
- Proper ARIA attributes for accessibility
|
|
8
|
+
- Placeholder support
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script lang="ts">
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Field identifier */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Current value */
|
|
16
|
+
value: string;
|
|
17
|
+
/** Placeholder text */
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
/** Whether the field is required */
|
|
20
|
+
required?: boolean;
|
|
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
|
+
placeholder = "",
|
|
31
|
+
required = false,
|
|
32
|
+
ariaDescribedBy,
|
|
33
|
+
onChange
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle input changes
|
|
38
|
+
*/
|
|
39
|
+
function handleInput(event: Event): void {
|
|
40
|
+
const target = event.currentTarget as HTMLInputElement;
|
|
41
|
+
onChange(target.value);
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<input
|
|
46
|
+
{id}
|
|
47
|
+
type="text"
|
|
48
|
+
class="form-text-field"
|
|
49
|
+
value={value ?? ""}
|
|
50
|
+
{placeholder}
|
|
51
|
+
aria-describedby={ariaDescribedBy}
|
|
52
|
+
aria-required={required}
|
|
53
|
+
oninput={handleInput}
|
|
54
|
+
/>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.form-text-field {
|
|
58
|
+
width: 100%;
|
|
59
|
+
padding: 0.625rem 0.875rem;
|
|
60
|
+
border: 1px solid var(--color-ref-gray-200, #e5e7eb);
|
|
61
|
+
border-radius: 0.5rem;
|
|
62
|
+
font-size: 0.875rem;
|
|
63
|
+
font-family: inherit;
|
|
64
|
+
color: var(--color-ref-gray-900, #111827);
|
|
65
|
+
background-color: var(--color-ref-gray-50, #f9fafb);
|
|
66
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
67
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.form-text-field::placeholder {
|
|
71
|
+
color: var(--color-ref-gray-400, #9ca3af);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.form-text-field:hover {
|
|
75
|
+
border-color: var(--color-ref-gray-300, #d1d5db);
|
|
76
|
+
background-color: #ffffff;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.form-text-field:focus {
|
|
80
|
+
outline: none;
|
|
81
|
+
border-color: var(--color-ref-blue-500, #3b82f6);
|
|
82
|
+
background-color: #ffffff;
|
|
83
|
+
box-shadow:
|
|
84
|
+
0 0 0 3px rgba(59, 130, 246, 0.12),
|
|
85
|
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
88
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Field identifier */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Current value */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Placeholder text */
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Whether the field is required */
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** ARIA description ID */
|
|
11
|
+
ariaDescribedBy?: string;
|
|
12
|
+
/** Callback when value changes */
|
|
13
|
+
onChange: (value: string) => void;
|
|
14
|
+
}
|
|
15
|
+
declare const FormTextField: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type FormTextField = ReturnType<typeof FormTextField>;
|
|
17
|
+
export default FormTextField;
|