@flowdrop/flowdrop 2.0.0-beta.3 → 2.0.0-beta.4

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -5
  3. package/dist/components/App.svelte +15 -146
  4. package/dist/components/Button.stories.svelte +65 -0
  5. package/dist/components/Button.stories.svelte.d.ts +19 -0
  6. package/dist/components/Button.svelte +62 -0
  7. package/dist/components/Button.svelte.d.ts +24 -0
  8. package/dist/components/ConfigForm.svelte +4 -4
  9. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  10. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  11. package/dist/components/EditorStatusBar.svelte +99 -0
  12. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  13. package/dist/components/IconButton.svelte +80 -0
  14. package/dist/components/IconButton.svelte.d.ts +30 -0
  15. package/dist/components/Input.svelte +74 -0
  16. package/dist/components/Input.svelte.d.ts +17 -0
  17. package/dist/components/Navbar.svelte +9 -4
  18. package/dist/components/Navbar.svelte.d.ts +3 -0
  19. package/dist/components/NodeSidebar.svelte +13 -111
  20. package/dist/components/NodeSwapPicker.svelte +10 -26
  21. package/dist/components/Select.svelte +53 -0
  22. package/dist/components/Select.svelte.d.ts +15 -0
  23. package/dist/components/Textarea.svelte +39 -0
  24. package/dist/components/Textarea.svelte.d.ts +12 -0
  25. package/dist/components/ThemeToggle.svelte +15 -89
  26. package/dist/components/form/FormArray.svelte +37 -157
  27. package/dist/components/form/FormCheckboxGroup.svelte +1 -1
  28. package/dist/components/form/FormField.svelte +5 -44
  29. package/dist/components/form/FormFieldLight.svelte +5 -44
  30. package/dist/components/form/FormFieldset.svelte +1 -1
  31. package/dist/components/form/FormNumberField.svelte +4 -32
  32. package/dist/components/form/FormRangeField.svelte +17 -7
  33. package/dist/components/form/FormSelect.svelte +13 -79
  34. package/dist/components/form/FormTextField.svelte +3 -39
  35. package/dist/components/form/FormTextarea.svelte +4 -43
  36. package/dist/components/form/resolveFieldType.d.ts +24 -0
  37. package/dist/components/form/resolveFieldType.js +55 -0
  38. package/dist/components/icons/CloseIcon.svelte +6 -0
  39. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  40. package/dist/components/playground/InputCollector.svelte +11 -46
  41. package/dist/messages/index.d.ts +1 -1
  42. package/dist/messages/index.js +1 -1
  43. package/dist/openapi/v1/openapi.yaml +2 -2
  44. package/dist/skins/drafter.js +41 -28
  45. package/dist/styles/base.css +247 -5
  46. package/dist/styles/tokens.css +6 -0
  47. package/dist/svelte-app.js +68 -107
  48. package/dist/utils/connections.js +14 -50
  49. package/package.json +1 -1
@@ -1,12 +1,15 @@
1
1
  <!--
2
2
  Theme Toggle Component
3
- A button that cycles through light, dark, and auto theme modes
4
- Displays appropriate icon for current theme state
5
- Styled with BEM syntax
3
+ A button that cycles through light, dark, and auto theme modes.
4
+ Displays the icon for the current theme state.
5
+
6
+ Built on the shared Button primitive so it stays visually and dimensionally
7
+ aligned with every other control (height, focus ring, hover) for free.
6
8
  -->
7
9
 
8
10
  <script lang="ts">
9
11
  import Icon from '@iconify/svelte';
12
+ import Button from './Button.svelte';
10
13
  import { getTheme, getResolvedTheme, cycleTheme } from '../stores/settingsStore.svelte.js';
11
14
  import type { ThemePreference } from '../types/settings.js';
12
15
 
@@ -47,7 +50,7 @@
47
50
  }
48
51
 
49
52
  /**
50
- * Get accessible label for the current theme
53
+ * Get the label text for the current theme
51
54
  */
52
55
  const themeLabel = $derived(getThemeLabel(getTheme()));
53
56
 
@@ -80,100 +83,23 @@
80
83
  const next = currentTheme === 'light' ? 'Dark' : 'Auto';
81
84
  return `Theme: ${currentTheme === 'light' ? 'Light' : 'Dark'}. Click to switch to ${next}`;
82
85
  }
83
-
84
- /**
85
- * Handle click to cycle theme
86
- */
87
- function handleClick(): void {
88
- cycleTheme();
89
- }
90
-
91
- /**
92
- * Handle keyboard events for accessibility
93
- */
94
- function handleKeydown(event: KeyboardEvent): void {
95
- if (event.key === 'Enter' || event.key === ' ') {
96
- event.preventDefault();
97
- cycleTheme();
98
- }
99
- }
100
86
  </script>
101
87
 
102
- <button
103
- class="flowdrop-theme-toggle flowdrop-theme-toggle--{size} {className}"
104
- onclick={handleClick}
105
- onkeydown={handleKeydown}
88
+ <Button
89
+ variant="outline"
90
+ {size}
91
+ class={className}
106
92
  title={tooltipText}
107
- aria-label={tooltipText}
108
- type="button"
93
+ ariaLabel={tooltipText}
94
+ onclick={cycleTheme}
109
95
  >
110
- <span class="flowdrop-theme-toggle__icon">
111
- <Icon icon={themeIcon} />
112
- </span>
96
+ <Icon icon={themeIcon} />
113
97
  {#if showLabel}
114
98
  <span class="flowdrop-theme-toggle__label">{themeLabel}</span>
115
99
  {/if}
116
- </button>
100
+ </Button>
117
101
 
118
102
  <style>
119
- .flowdrop-theme-toggle {
120
- display: inline-flex;
121
- align-items: center;
122
- justify-content: center;
123
- gap: var(--fd-space-xs);
124
- border: 1px solid var(--fd-border);
125
- border-radius: var(--fd-radius-md);
126
- background-color: var(--fd-background);
127
- color: var(--fd-foreground);
128
- cursor: pointer;
129
- transition: all var(--fd-transition-normal);
130
- font-family: inherit;
131
- }
132
-
133
- .flowdrop-theme-toggle:hover {
134
- background-color: var(--fd-muted);
135
- border-color: var(--fd-border-strong);
136
- }
137
-
138
- .flowdrop-theme-toggle:active {
139
- transform: scale(0.98);
140
- }
141
-
142
- /* Size variants */
143
- .flowdrop-theme-toggle--sm {
144
- padding: var(--fd-space-3xs) var(--fd-space-xs);
145
- font-size: var(--fd-text-xs);
146
- }
147
-
148
- .flowdrop-theme-toggle--sm .flowdrop-theme-toggle__icon {
149
- font-size: var(--fd-text-sm);
150
- }
151
-
152
- .flowdrop-theme-toggle--md {
153
- padding: var(--fd-space-xs) var(--fd-space-md);
154
- font-size: var(--fd-text-sm);
155
- }
156
-
157
- .flowdrop-theme-toggle--md .flowdrop-theme-toggle__icon {
158
- font-size: var(--fd-text-base);
159
- }
160
-
161
- .flowdrop-theme-toggle--lg {
162
- padding: var(--fd-space-md) var(--fd-space-xl);
163
- font-size: var(--fd-text-base);
164
- }
165
-
166
- .flowdrop-theme-toggle--lg .flowdrop-theme-toggle__icon {
167
- font-size: var(--fd-text-lg);
168
- }
169
-
170
- .flowdrop-theme-toggle__icon {
171
- display: flex;
172
- align-items: center;
173
- justify-content: center;
174
- line-height: 1;
175
- }
176
-
177
103
  .flowdrop-theme-toggle__label {
178
104
  font-weight: 500;
179
105
  }
@@ -19,6 +19,10 @@
19
19
 
20
20
  <script lang="ts">
21
21
  import Icon from '@iconify/svelte';
22
+ import Input from '../Input.svelte';
23
+ import Select from '../Select.svelte';
24
+ import Textarea from '../Textarea.svelte';
25
+ import IconButton from '../IconButton.svelte';
22
26
  import type { FieldSchema } from './types.js';
23
27
  import { m } from '../../messages/index.js';
24
28
 
@@ -284,40 +288,37 @@
284
288
  <!-- Action buttons group -->
285
289
  <div class="form-array__actions">
286
290
  <!-- Move Up button -->
287
- <button
288
- type="button"
289
- class="form-array__action-btn form-array__action-btn--move"
291
+ <IconButton
292
+ variant="primary"
290
293
  onclick={() => moveItemUp(index)}
291
294
  disabled={index === 0 || disabled}
292
- aria-label={t.moveItemUp({ n: index + 1 })}
295
+ ariaLabel={t.moveItemUp({ n: index + 1 })}
293
296
  title={t.moveUp}
294
297
  >
295
298
  <Icon icon="heroicons:arrow-up" />
296
- </button>
299
+ </IconButton>
297
300
 
298
301
  <!-- Move Down button -->
299
- <button
300
- type="button"
301
- class="form-array__action-btn form-array__action-btn--move"
302
+ <IconButton
303
+ variant="primary"
302
304
  onclick={() => moveItemDown(index)}
303
305
  disabled={index === items.length - 1 || disabled}
304
- aria-label={t.moveItemDown({ n: index + 1 })}
306
+ ariaLabel={t.moveItemDown({ n: index + 1 })}
305
307
  title={t.moveDown}
306
308
  >
307
309
  <Icon icon="heroicons:arrow-down" />
308
- </button>
310
+ </IconButton>
309
311
 
310
312
  <!-- Delete button -->
311
- <button
312
- type="button"
313
- class="form-array__action-btn form-array__action-btn--delete"
313
+ <IconButton
314
+ variant="danger"
314
315
  onclick={() => removeItem(index)}
315
316
  disabled={!canRemoveItem || disabled}
316
- aria-label={t.deleteItem({ n: index + 1 })}
317
+ ariaLabel={t.deleteItem({ n: index + 1 })}
317
318
  title={t.delete}
318
319
  >
319
320
  <Icon icon="heroicons:trash" />
320
- </button>
321
+ </IconButton>
321
322
  </div>
322
323
  </div>
323
324
 
@@ -330,18 +331,16 @@
330
331
  <!-- Simple type: render inline input -->
331
332
  {#if itemSchema.type === 'string'}
332
333
  {#if itemSchema.format === 'multiline'}
333
- <textarea
334
- class="form-array__input form-array__textarea"
334
+ <Textarea
335
335
  value={String(item ?? '')}
336
336
  placeholder={itemSchema.placeholder ?? ''}
337
337
  rows={3}
338
338
  oninput={(e) => updateItem(index, e.currentTarget.value)}
339
339
  {disabled}
340
- ></textarea>
340
+ />
341
341
  {:else}
342
- <input
342
+ <Input
343
343
  type="text"
344
- class="form-array__input"
345
344
  value={String(item ?? '')}
346
345
  placeholder={itemSchema.placeholder ?? ''}
347
346
  oninput={(e) => updateItem(index, e.currentTarget.value)}
@@ -349,9 +348,9 @@
349
348
  />
350
349
  {/if}
351
350
  {:else if itemSchema.type === 'number' || itemSchema.type === 'integer'}
352
- <input
351
+ <Input
353
352
  type="number"
354
- class="form-array__input form-array__input--number"
353
+ class="flowdrop-input--numeric"
355
354
  value={item as number}
356
355
  placeholder={itemSchema.placeholder ?? ''}
357
356
  min={itemSchema.minimum}
@@ -380,8 +379,7 @@
380
379
  </label>
381
380
  {:else if itemSchema.enum}
382
381
  <!-- Enum: render select -->
383
- <select
384
- class="form-array__select"
382
+ <Select
385
383
  value={String(item ?? '')}
386
384
  onchange={(e) => updateItem(index, e.currentTarget.value)}
387
385
  {disabled}
@@ -389,12 +387,11 @@
389
387
  {#each itemSchema.enum as option (option)}
390
388
  <option value={String(option)}>{String(option)}</option>
391
389
  {/each}
392
- </select>
390
+ </Select>
393
391
  {:else}
394
392
  <!-- Fallback to text -->
395
- <input
393
+ <Input
396
394
  type="text"
397
- class="form-array__input"
398
395
  value={String(item ?? '')}
399
396
  placeholder={itemSchema.placeholder ?? ''}
400
397
  oninput={(e) => updateItem(index, e.currentTarget.value)}
@@ -425,9 +422,8 @@
425
422
 
426
423
  <div class="form-array__subform-input">
427
424
  {#if propFieldSchema.enum}
428
- <select
425
+ <Select
429
426
  id="{id}-{index}-{propKey}"
430
- class="form-array__select"
431
427
  value={String(propValue ?? '')}
432
428
  onchange={(e) =>
433
429
  updateObjectProperty(index, propKey, e.currentTarget.value)}
@@ -436,23 +432,21 @@
436
432
  {#each propFieldSchema.enum as option (option)}
437
433
  <option value={String(option)}>{String(option)}</option>
438
434
  {/each}
439
- </select>
435
+ </Select>
440
436
  {:else if propFieldSchema.type === 'string' && propFieldSchema.format === 'multiline'}
441
- <textarea
437
+ <Textarea
442
438
  id="{id}-{index}-{propKey}"
443
- class="form-array__input form-array__textarea"
444
439
  value={String(propValue ?? '')}
445
440
  placeholder={propFieldSchema.placeholder ?? ''}
446
441
  rows={3}
447
442
  oninput={(e) =>
448
443
  updateObjectProperty(index, propKey, e.currentTarget.value)}
449
444
  {disabled}
450
- ></textarea>
445
+ />
451
446
  {:else if propFieldSchema.type === 'string'}
452
- <input
447
+ <Input
453
448
  id="{id}-{index}-{propKey}"
454
449
  type="text"
455
- class="form-array__input"
456
450
  value={String(propValue ?? '')}
457
451
  placeholder={propFieldSchema.placeholder ?? ''}
458
452
  oninput={(e) =>
@@ -460,10 +454,10 @@
460
454
  {disabled}
461
455
  />
462
456
  {:else if propFieldSchema.type === 'number' || propFieldSchema.type === 'integer'}
463
- <input
457
+ <Input
464
458
  id="{id}-{index}-{propKey}"
465
459
  type="number"
466
- class="form-array__input form-array__input--number"
460
+ class="flowdrop-input--numeric"
467
461
  value={propValue as number}
468
462
  placeholder={propFieldSchema.placeholder ?? ''}
469
463
  min={propFieldSchema.minimum}
@@ -493,10 +487,9 @@
493
487
  </span>
494
488
  </label>
495
489
  {:else}
496
- <input
490
+ <Input
497
491
  id="{id}-{index}-{propKey}"
498
492
  type="text"
499
- class="form-array__input"
500
493
  value={String(propValue ?? '')}
501
494
  placeholder={propFieldSchema.placeholder ?? ''}
502
495
  oninput={(e) =>
@@ -596,7 +589,7 @@
596
589
  flex-direction: column;
597
590
  background-color: var(--fd-muted);
598
591
  border: 1px solid var(--fd-border);
599
- border-radius: var(--fd-radius-lg);
592
+ border-radius: var(--fd-control-radius);
600
593
  overflow: hidden;
601
594
  animation: itemFadeIn 0.25s ease-out forwards;
602
595
  opacity: 0;
@@ -697,62 +690,8 @@
697
690
  margin-left: auto;
698
691
  }
699
692
 
700
- .form-array__action-btn {
701
- display: flex;
702
- align-items: center;
703
- justify-content: center;
704
- width: 2rem;
705
- height: 2rem;
706
- padding: 0;
707
- border: 1px solid transparent;
708
- border-radius: 0.375rem;
709
- cursor: pointer;
710
- transition: all 0.15s;
711
- }
712
-
713
- .form-array__action-btn :global(svg) {
714
- width: 1rem;
715
- height: 1rem;
716
- }
717
-
718
- .form-array__action-btn:disabled {
719
- opacity: 0.35;
720
- cursor: not-allowed;
721
- }
722
-
723
- /* Move Up/Down buttons - Blue semantic color */
724
- .form-array__action-btn--move {
725
- background-color: var(--fd-primary-muted);
726
- border-color: var(--fd-primary);
727
- color: var(--fd-primary-hover);
728
- }
729
-
730
- .form-array__action-btn--move:hover:not(:disabled) {
731
- background-color: var(--fd-primary-muted);
732
- border-color: var(--fd-primary-hover);
733
- color: var(--fd-primary-hover);
734
- }
735
-
736
- .form-array__action-btn--move:active:not(:disabled) {
737
- background-color: var(--fd-primary);
738
- }
739
-
740
- /* Delete button - Red/Warning semantic color */
741
- .form-array__action-btn--delete {
742
- background-color: var(--fd-error-muted);
743
- border-color: var(--fd-error);
744
- color: var(--fd-error);
745
- }
746
-
747
- .form-array__action-btn--delete:hover:not(:disabled) {
748
- background-color: var(--fd-error-muted);
749
- border-color: var(--fd-error-hover);
750
- color: var(--fd-error-hover);
751
- }
752
-
753
- .form-array__action-btn--delete:active:not(:disabled) {
754
- background-color: var(--fd-error);
755
- }
693
+ /* Action buttons (move/delete) now render through IconButton.svelte
694
+ geometry + semantic tints live in base.css (.flowdrop-btn--icon*). */
756
695
 
757
696
  /* ============================================
758
697
  ITEM CONTENT
@@ -772,65 +711,6 @@
772
711
  INPUTS (Simple Types)
773
712
  ============================================ */
774
713
 
775
- .form-array__input {
776
- width: 100%;
777
- padding: 0.5rem 0.75rem;
778
- border: 1px solid var(--fd-border);
779
- border-radius: var(--fd-radius-md);
780
- font-size: var(--fd-text-sm);
781
- font-family: inherit;
782
- color: var(--fd-foreground);
783
- background-color: var(--fd-background);
784
- transition: all var(--fd-transition-normal);
785
- }
786
-
787
- .form-array__input::placeholder {
788
- color: var(--fd-muted-foreground);
789
- }
790
-
791
- .form-array__input:hover {
792
- border-color: var(--fd-border-strong);
793
- }
794
-
795
- .form-array__input:focus {
796
- border-color: var(--fd-primary);
797
- }
798
-
799
- .form-array__input--number {
800
- font-variant-numeric: tabular-nums;
801
- }
802
-
803
- .form-array__textarea {
804
- resize: vertical;
805
- min-height: 4rem;
806
- line-height: 1.5;
807
- }
808
-
809
- .form-array__select {
810
- width: 100%;
811
- padding: 0.5rem 2rem 0.5rem 0.75rem;
812
- border: 1px solid var(--fd-border);
813
- border-radius: var(--fd-radius-md);
814
- font-size: var(--fd-text-sm);
815
- font-family: inherit;
816
- color: var(--fd-foreground);
817
- background-color: var(--fd-background);
818
- cursor: pointer;
819
- appearance: none;
820
- 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");
821
- background-repeat: no-repeat;
822
- background-position: right 0.5rem center;
823
- background-size: 1rem;
824
- }
825
-
826
- .form-array__select:hover {
827
- border-color: var(--fd-border-strong);
828
- }
829
-
830
- .form-array__select:focus {
831
- border-color: var(--fd-primary);
832
- }
833
-
834
714
  /* ============================================
835
715
  TOGGLE (Boolean in Array)
836
716
  ============================================ */
@@ -947,7 +827,7 @@
947
827
  padding: 2rem 1rem;
948
828
  background-color: var(--fd-muted);
949
829
  border: 2px dashed var(--fd-border-strong);
950
- border-radius: var(--fd-radius-lg);
830
+ border-radius: var(--fd-control-radius);
951
831
  }
952
832
 
953
833
  .form-array__empty :global(svg) {
@@ -975,7 +855,7 @@
975
855
  gap: 0.5rem;
976
856
  padding: 0.625rem 1rem;
977
857
  border: 1px solid var(--fd-success);
978
- border-radius: var(--fd-radius-lg);
858
+ border-radius: var(--fd-control-radius);
979
859
  background-color: var(--fd-success-muted);
980
860
  color: var(--fd-success-hover);
981
861
  font-size: 0.8125rem;
@@ -86,7 +86,7 @@
86
86
  padding: 0.75rem;
87
87
  background-color: var(--fd-muted);
88
88
  border: 1px solid var(--fd-border);
89
- border-radius: var(--fd-radius-lg);
89
+ border-radius: var(--fd-control-radius);
90
90
  }
91
91
 
92
92
  .form-checkbox-item {
@@ -43,6 +43,7 @@
43
43
  import FormAutocomplete from './FormAutocomplete.svelte';
44
44
  import type { FieldSchema } from './types.js';
45
45
  import { getSchemaOptions } from './types.js';
46
+ import { resolveBaseFieldType } from './resolveFieldType.js';
46
47
  import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
47
48
  import { getResolvedTheme } from '../../stores/settingsStore.svelte.js';
48
49
  import { getInstance } from '../../stores/getInstance.svelte.js';
@@ -139,50 +140,10 @@
139
140
  return 'template-editor';
140
141
  }
141
142
 
142
- // Enum with multiple selection -> checkbox group
143
- if (schema.enum && schema.multiple) {
144
- return 'checkbox-group';
145
- }
146
-
147
- // Enum with single selection -> select
148
- if (schema.enum) {
149
- return 'select-enum';
150
- }
151
-
152
- // oneOf with labeled options (standard JSON Schema) -> select
153
- // Must be checked before basic type checks since oneOf schemas often have type: 'string'
154
- if (schema.oneOf && schema.oneOf.length > 0) {
155
- return 'select-options';
156
- }
157
-
158
- // Multiline string -> textarea
159
- if (schema.type === 'string' && schema.format === 'multiline') {
160
- return 'textarea';
161
- }
162
-
163
- // Range slider for number/integer with format: "range"
164
- if ((schema.type === 'number' || schema.type === 'integer') && schema.format === 'range') {
165
- return 'range';
166
- }
167
-
168
- // String -> text field
169
- if (schema.type === 'string') {
170
- return 'text';
171
- }
172
-
173
- // Number or integer -> number field
174
- if (schema.type === 'number' || schema.type === 'integer') {
175
- return 'number';
176
- }
177
-
178
- // Boolean -> toggle
179
- if (schema.type === 'boolean') {
180
- return 'toggle';
181
- }
182
-
183
- // Future: Array type support
184
- if (schema.type === 'array') {
185
- return 'array';
143
+ // Shared basic field resolution (enum/oneOf/primitive types).
144
+ const base = resolveBaseFieldType(schema);
145
+ if (base) {
146
+ return base;
186
147
  }
187
148
 
188
149
  // Object type without specific format -> CodeMirror JSON editor
@@ -48,6 +48,7 @@
48
48
  const fd = getInstance();
49
49
  import type { FieldSchema } from './types.js';
50
50
  import { getSchemaOptions } from './types.js';
51
+ import { resolveBaseFieldType } from './resolveFieldType.js';
51
52
  import type { WorkflowNode, WorkflowEdge } from '../../types/index.js';
52
53
  import type { AuthProvider } from '../../types/auth.js';
53
54
 
@@ -144,50 +145,10 @@
144
145
  return 'code-editor-fallback';
145
146
  }
146
147
 
147
- // Enum with multiple selection -> checkbox group
148
- if (schema.enum && schema.multiple) {
149
- return 'checkbox-group';
150
- }
151
-
152
- // Enum with single selection -> select
153
- if (schema.enum) {
154
- return 'select-enum';
155
- }
156
-
157
- // oneOf with labeled options (standard JSON Schema) -> select
158
- // Must be checked before basic type checks since oneOf schemas often have type: 'string'
159
- if (schema.oneOf && schema.oneOf.length > 0) {
160
- return 'select-options';
161
- }
162
-
163
- // Multiline string -> textarea
164
- if (schema.type === 'string' && schema.format === 'multiline') {
165
- return 'textarea';
166
- }
167
-
168
- // Range slider for number/integer with format: "range"
169
- if ((schema.type === 'number' || schema.type === 'integer') && schema.format === 'range') {
170
- return 'range';
171
- }
172
-
173
- // String -> text field
174
- if (schema.type === 'string') {
175
- return 'text';
176
- }
177
-
178
- // Number or integer -> number field
179
- if (schema.type === 'number' || schema.type === 'integer') {
180
- return 'number';
181
- }
182
-
183
- // Boolean -> toggle
184
- if (schema.type === 'boolean') {
185
- return 'toggle';
186
- }
187
-
188
- // Array type
189
- if (schema.type === 'array') {
190
- return 'array';
148
+ // Shared basic field resolution (enum/oneOf/primitive types).
149
+ const base = resolveBaseFieldType(schema);
150
+ if (base) {
151
+ return base;
191
152
  }
192
153
 
193
154
  // Fallback to text
@@ -121,7 +121,7 @@
121
121
 
122
122
  .form-fieldset--static {
123
123
  border: 1px solid var(--fd-border-muted);
124
- border-radius: var(--fd-radius-lg);
124
+ border-radius: var(--fd-control-radius);
125
125
  padding: var(--fd-space-xl);
126
126
  margin: 0;
127
127
  }
@@ -9,6 +9,8 @@
9
9
  -->
10
10
 
11
11
  <script lang="ts">
12
+ import Input from '../Input.svelte';
13
+
12
14
  interface Props {
13
15
  /** Field identifier */
14
16
  id: string;
@@ -62,10 +64,10 @@
62
64
  }
63
65
  </script>
64
66
 
65
- <input
67
+ <Input
66
68
  {id}
67
69
  type="number"
68
- class="form-number-field"
70
+ class="flowdrop-input--numeric"
69
71
  value={value ?? ''}
70
72
  {placeholder}
71
73
  {min}
@@ -76,33 +78,3 @@
76
78
  aria-required={required}
77
79
  oninput={handleInput}
78
80
  />
79
-
80
- <style>
81
- .form-number-field {
82
- width: 100%;
83
- padding: 0.625rem 0.875rem;
84
- border: 1px solid var(--fd-border);
85
- border-radius: var(--fd-radius-lg);
86
- font-size: var(--fd-text-sm);
87
- font-family: inherit;
88
- font-variant-numeric: tabular-nums;
89
- color: var(--fd-foreground);
90
- background-color: var(--fd-muted);
91
- transition: all var(--fd-transition-normal);
92
- box-shadow: var(--fd-shadow-sm);
93
- }
94
-
95
- .form-number-field::placeholder {
96
- color: var(--fd-muted-foreground);
97
- }
98
-
99
- .form-number-field:hover {
100
- border-color: var(--fd-border-strong);
101
- background-color: var(--fd-background);
102
- }
103
-
104
- .form-number-field:focus {
105
- border-color: var(--fd-primary);
106
- background-color: var(--fd-background);
107
- }
108
- </style>