@d34dman/flowdrop 0.0.25 → 0.0.27

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 (40) hide show
  1. package/README.md +52 -62
  2. package/dist/components/App.svelte +12 -2
  3. package/dist/components/ConfigForm.svelte +500 -9
  4. package/dist/components/ConfigForm.svelte.d.ts +2 -0
  5. package/dist/components/ConfigModal.svelte +4 -70
  6. package/dist/components/ConfigPanel.svelte +4 -9
  7. package/dist/components/EdgeRefresher.svelte +41 -0
  8. package/dist/components/EdgeRefresher.svelte.d.ts +9 -0
  9. package/dist/components/ReadOnlyDetails.svelte +3 -1
  10. package/dist/components/UniversalNode.svelte +6 -3
  11. package/dist/components/WorkflowEditor.svelte +30 -0
  12. package/dist/components/WorkflowEditor.svelte.d.ts +3 -1
  13. package/dist/components/form/FormCheckboxGroup.svelte +2 -9
  14. package/dist/components/form/FormField.svelte +1 -12
  15. package/dist/components/form/FormFieldWrapper.svelte +2 -10
  16. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  17. package/dist/components/form/FormMarkdownEditor.svelte +0 -2
  18. package/dist/components/form/FormNumberField.svelte +5 -6
  19. package/dist/components/form/FormRangeField.svelte +3 -13
  20. package/dist/components/form/FormSelect.svelte +4 -5
  21. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  22. package/dist/components/form/FormTextField.svelte +3 -4
  23. package/dist/components/form/FormTextarea.svelte +3 -4
  24. package/dist/components/form/FormToggle.svelte +2 -3
  25. package/dist/components/form/index.d.ts +14 -14
  26. package/dist/components/form/index.js +14 -14
  27. package/dist/components/form/types.d.ts +2 -2
  28. package/dist/components/form/types.js +1 -1
  29. package/dist/components/nodes/NotesNode.svelte +39 -45
  30. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  31. package/dist/components/nodes/SimpleNode.svelte +92 -142
  32. package/dist/components/nodes/SquareNode.svelte +75 -58
  33. package/dist/components/nodes/WorkflowNode.svelte +1 -3
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.js +2 -0
  36. package/dist/services/dynamicSchemaService.d.ts +108 -0
  37. package/dist/services/dynamicSchemaService.js +445 -0
  38. package/dist/styles/base.css +1 -1
  39. package/dist/types/index.d.ts +213 -0
  40. package/package.json +163 -155
@@ -8,6 +8,7 @@
8
8
  - Dynamic form generation from JSON Schema using modular form components
9
9
  - UI Extensions support for display settings (e.g., hide unconnected handles)
10
10
  - Extensible architecture for complex schema types (array, object)
11
+ - Admin/Edit support for external configuration links and dynamic schema fetching
11
12
 
12
13
  Accessibility features:
13
14
  - Proper label associations with for/id attributes
@@ -18,9 +19,21 @@
18
19
 
19
20
  <script lang="ts">
20
21
  import Icon from '@iconify/svelte';
21
- import type { ConfigSchema, WorkflowNode, NodeUIExtensions } from '../types/index.js';
22
+ import type {
23
+ ConfigSchema,
24
+ WorkflowNode,
25
+ NodeUIExtensions,
26
+ ConfigEditOptions
27
+ } from '../types/index.js';
22
28
  import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
23
29
  import type { FieldSchema } from './form/index.js';
30
+ import {
31
+ getEffectiveConfigEditOptions,
32
+ fetchDynamicSchema,
33
+ resolveExternalEditUrl,
34
+ invalidateSchemaCache,
35
+ type DynamicSchemaResult
36
+ } from '../services/dynamicSchemaService.js';
24
37
 
25
38
  interface Props {
26
39
  /** Optional workflow node (if provided, schema and values are derived from it) */
@@ -31,20 +44,81 @@
31
44
  values?: Record<string, unknown>;
32
45
  /** Whether to show UI extension settings section */
33
46
  showUIExtensions?: boolean;
47
+ /** Optional workflow ID for context in external links */
48
+ workflowId?: string;
34
49
  /** Callback when form is saved (includes both config and extensions if enabled) */
35
50
  onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
36
51
  /** Callback when form is cancelled */
37
52
  onCancel: () => void;
38
53
  }
39
54
 
40
- let { node, schema, values, showUIExtensions = true, onSave, onCancel }: Props = $props();
55
+ let {
56
+ node,
57
+ schema,
58
+ values,
59
+ showUIExtensions = true,
60
+ workflowId,
61
+ onSave,
62
+ onCancel
63
+ }: Props = $props();
41
64
 
42
65
  /**
43
- * Get the configuration schema from node metadata or direct prop
66
+ * State for dynamic schema loading
44
67
  */
45
- const configSchema = $derived(
46
- schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined)
47
- );
68
+ let dynamicSchemaLoading = $state(false);
69
+ let dynamicSchemaError = $state<string | null>(null);
70
+ let fetchedDynamicSchema = $state<ConfigSchema | null>(null);
71
+
72
+ /**
73
+ * Get the admin edit configuration for the node
74
+ */
75
+ const configEditOptions = $derived.by<ConfigEditOptions | undefined>(() => {
76
+ if (!node) return undefined;
77
+ return getEffectiveConfigEditOptions(node);
78
+ });
79
+
80
+ /**
81
+ * Determine if we should show the external edit link
82
+ */
83
+ const showExternalEditLink = $derived.by(() => {
84
+ if (!configEditOptions?.externalEditLink) return false;
85
+ // Show if no dynamic schema, or if both exist but preferDynamicSchema is false
86
+ if (!configEditOptions.dynamicSchema) return true;
87
+ return !configEditOptions.preferDynamicSchema;
88
+ });
89
+
90
+ /**
91
+ * Determine if we should use/fetch dynamic schema
92
+ */
93
+ const useDynamicSchema = $derived.by(() => {
94
+ if (!configEditOptions?.dynamicSchema) return false;
95
+ // Use if no external link, or if both exist and preferDynamicSchema is true
96
+ if (!configEditOptions.externalEditLink) return true;
97
+ return configEditOptions.preferDynamicSchema === true;
98
+ });
99
+
100
+ /**
101
+ * Get the configuration schema from node metadata, direct prop, or fetched dynamic schema
102
+ * Priority: fetchedDynamicSchema > direct schema prop > node metadata configSchema
103
+ */
104
+ const configSchema = $derived.by<ConfigSchema | undefined>(() => {
105
+ // If we have a fetched dynamic schema, use it
106
+ if (fetchedDynamicSchema) {
107
+ return fetchedDynamicSchema;
108
+ }
109
+ // Otherwise use the direct prop or node metadata
110
+ return schema ?? (node?.data.metadata?.configSchema as ConfigSchema | undefined);
111
+ });
112
+
113
+ /**
114
+ * Check if the node has no static schema and needs dynamic loading
115
+ */
116
+ const needsDynamicSchemaLoad = $derived.by(() => {
117
+ if (!node) return false;
118
+ const staticSchema = schema ?? node.data.metadata?.configSchema;
119
+ // Need to load if: no static schema AND dynamic schema is configured
120
+ return !staticSchema && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
121
+ });
48
122
 
49
123
  /**
50
124
  * Get the current configuration from node or direct prop
@@ -74,6 +148,85 @@
74
148
  return { ...typeDefaults, ...instanceOverrides };
75
149
  });
76
150
 
151
+ /**
152
+ * Fetch dynamic schema when needed
153
+ */
154
+ async function loadDynamicSchema(): Promise<void> {
155
+ if (!node || !configEditOptions?.dynamicSchema) return;
156
+
157
+ dynamicSchemaLoading = true;
158
+ dynamicSchemaError = null;
159
+
160
+ try {
161
+ const result: DynamicSchemaResult = await fetchDynamicSchema(
162
+ configEditOptions.dynamicSchema,
163
+ node,
164
+ workflowId
165
+ );
166
+
167
+ if (result.success && result.schema) {
168
+ fetchedDynamicSchema = result.schema;
169
+ } else {
170
+ dynamicSchemaError =
171
+ result.error ?? configEditOptions.errorMessage ?? 'Failed to load configuration schema';
172
+ }
173
+ } catch (err) {
174
+ dynamicSchemaError =
175
+ err instanceof Error
176
+ ? err.message
177
+ : (configEditOptions.errorMessage ?? 'Failed to load configuration schema');
178
+ } finally {
179
+ dynamicSchemaLoading = false;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Refresh the dynamic schema (invalidate cache and reload)
185
+ */
186
+ async function refreshDynamicSchema(): Promise<void> {
187
+ if (!node || !configEditOptions?.dynamicSchema) return;
188
+
189
+ // Invalidate the cache first
190
+ invalidateSchemaCache(node, configEditOptions.dynamicSchema);
191
+ fetchedDynamicSchema = null;
192
+
193
+ // Reload the schema
194
+ await loadDynamicSchema();
195
+ }
196
+
197
+ /**
198
+ * Get the resolved external edit URL
199
+ */
200
+ function getExternalEditUrl(): string {
201
+ if (!node || !configEditOptions?.externalEditLink) return '#';
202
+ return resolveExternalEditUrl(configEditOptions.externalEditLink, node, workflowId);
203
+ }
204
+
205
+ /**
206
+ * Handle opening external edit link
207
+ */
208
+ function handleExternalEditClick(): void {
209
+ if (!node || !configEditOptions?.externalEditLink) return;
210
+
211
+ const url = getExternalEditUrl();
212
+ const openInNewTab = configEditOptions.externalEditLink.openInNewTab !== false;
213
+
214
+ if (openInNewTab) {
215
+ window.open(url, '_blank', 'noopener,noreferrer');
216
+ } else {
217
+ window.location.href = url;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Auto-load dynamic schema on mount if needed
223
+ */
224
+ $effect(() => {
225
+ if (needsDynamicSchemaLoad) {
226
+ loadDynamicSchema();
227
+ }
228
+ });
229
+
77
230
  /**
78
231
  * Initialize config values when node/schema changes
79
232
  */
@@ -131,7 +284,10 @@
131
284
  if (inputEl.id && !inputEl.id.startsWith('ext-')) {
132
285
  if (inputEl instanceof HTMLInputElement && inputEl.type === 'checkbox') {
133
286
  updatedConfig[inputEl.id] = inputEl.checked;
134
- } else if (inputEl instanceof HTMLInputElement && (inputEl.type === 'number' || inputEl.type === 'range')) {
287
+ } else if (
288
+ inputEl instanceof HTMLInputElement &&
289
+ (inputEl.type === 'number' || inputEl.type === 'range')
290
+ ) {
135
291
  updatedConfig[inputEl.id] = inputEl.value ? Number(inputEl.value) : inputEl.value;
136
292
  } else if (inputEl instanceof HTMLInputElement && inputEl.type === 'hidden') {
137
293
  // Parse hidden field values that might be JSON
@@ -176,7 +332,74 @@
176
332
  }
177
333
  </script>
178
334
 
179
- {#if configSchema}
335
+ <!-- External Edit Link Section (shown when configured and preferred) -->
336
+ {#if showExternalEditLink && configEditOptions?.externalEditLink}
337
+ <div class="config-form__admin-edit">
338
+ <div class="config-form__admin-edit-header">
339
+ <Icon icon="heroicons:arrow-top-right-on-square" />
340
+ <span>External Configuration</span>
341
+ </div>
342
+ <div class="config-form__admin-edit-content">
343
+ <p class="config-form__admin-edit-description">
344
+ {configEditOptions.externalEditLink.description ??
345
+ 'This node requires external configuration. Click the button below to open the configuration panel.'}
346
+ </p>
347
+ <button
348
+ type="button"
349
+ class="config-form__button config-form__button--external"
350
+ onclick={handleExternalEditClick}
351
+ >
352
+ <Icon
353
+ icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
354
+ />
355
+ <span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
356
+ </button>
357
+ </div>
358
+ </div>
359
+ {/if}
360
+
361
+ <!-- Dynamic Schema Loading State -->
362
+ {#if dynamicSchemaLoading}
363
+ <div class="config-form__loading">
364
+ <div class="config-form__loading-spinner"></div>
365
+ <p class="config-form__loading-text">
366
+ {configEditOptions?.loadingMessage ?? 'Loading configuration options...'}
367
+ </p>
368
+ </div>
369
+ {:else if dynamicSchemaError}
370
+ <div class="config-form__error">
371
+ <div class="config-form__error-header">
372
+ <Icon icon="heroicons:exclamation-triangle" />
373
+ <span>Configuration Error</span>
374
+ </div>
375
+ <div class="config-form__error-content">
376
+ <p class="config-form__error-message">{dynamicSchemaError}</p>
377
+ <div class="config-form__error-actions">
378
+ <button
379
+ type="button"
380
+ class="config-form__button config-form__button--secondary"
381
+ onclick={refreshDynamicSchema}
382
+ >
383
+ <Icon icon="heroicons:arrow-path" />
384
+ <span>Retry</span>
385
+ </button>
386
+ {#if configEditOptions?.externalEditLink}
387
+ <button
388
+ type="button"
389
+ class="config-form__button config-form__button--external"
390
+ onclick={handleExternalEditClick}
391
+ >
392
+ <Icon
393
+ icon={configEditOptions.externalEditLink.icon ??
394
+ 'heroicons:arrow-top-right-on-square'}
395
+ />
396
+ <span>{configEditOptions.externalEditLink.label ?? 'Use External Editor'}</span>
397
+ </button>
398
+ {/if}
399
+ </div>
400
+ </div>
401
+ </div>
402
+ {:else if configSchema}
180
403
  <form
181
404
  class="config-form"
182
405
  onsubmit={(e) => {
@@ -184,6 +407,35 @@
184
407
  handleSave();
185
408
  }}
186
409
  >
410
+ <!-- Dynamic Schema Refresh Button -->
411
+ {#if fetchedDynamicSchema && configEditOptions?.showRefreshButton !== false}
412
+ <div class="config-form__schema-actions">
413
+ <button
414
+ type="button"
415
+ class="config-form__schema-refresh"
416
+ onclick={refreshDynamicSchema}
417
+ title="Refresh configuration schema"
418
+ >
419
+ <Icon icon="heroicons:arrow-path" />
420
+ <span>Refresh Schema</span>
421
+ </button>
422
+ {#if configEditOptions?.externalEditLink}
423
+ <button
424
+ type="button"
425
+ class="config-form__schema-external"
426
+ onclick={handleExternalEditClick}
427
+ title={configEditOptions.externalEditLink.description ?? 'Open external editor'}
428
+ >
429
+ <Icon
430
+ icon={configEditOptions.externalEditLink.icon ??
431
+ 'heroicons:arrow-top-right-on-square'}
432
+ />
433
+ <span>{configEditOptions.externalEditLink.label ?? 'External Editor'}</span>
434
+ </button>
435
+ {/if}
436
+ </div>
437
+ {/if}
438
+
187
439
  {#if configSchema.properties}
188
440
  <div class="config-form__fields">
189
441
  {#each Object.entries(configSchema.properties) as [key, field], index (key)}
@@ -256,12 +508,24 @@
256
508
  </button>
257
509
  </div>
258
510
  </form>
259
- {:else}
511
+ {:else if !dynamicSchemaLoading && !showExternalEditLink}
260
512
  <div class="config-form__empty">
261
513
  <div class="config-form__empty-icon">
262
514
  <Icon icon="heroicons:cog-6-tooth" />
263
515
  </div>
264
516
  <p class="config-form__empty-text">No configuration options available for this node.</p>
517
+ {#if configEditOptions?.externalEditLink}
518
+ <button
519
+ type="button"
520
+ class="config-form__button config-form__button--external config-form__empty-button"
521
+ onclick={handleExternalEditClick}
522
+ >
523
+ <Icon
524
+ icon={configEditOptions.externalEditLink.icon ?? 'heroicons:arrow-top-right-on-square'}
525
+ />
526
+ <span>{configEditOptions.externalEditLink.label ?? 'Configure Externally'}</span>
527
+ </button>
528
+ {/if}
265
529
  </div>
266
530
  {/if}
267
531
 
@@ -479,4 +743,231 @@
479
743
  font-style: italic;
480
744
  line-height: 1.5;
481
745
  }
746
+
747
+ .config-form__empty-button {
748
+ margin-top: 1rem;
749
+ }
750
+
751
+ /* ============================================
752
+ ADMIN/EDIT SECTION - External Configuration
753
+ ============================================ */
754
+
755
+ .config-form__admin-edit {
756
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
757
+ border: 1px solid var(--color-ref-blue-200, #bfdbfe);
758
+ border-radius: 0.625rem;
759
+ overflow: hidden;
760
+ margin-bottom: 1rem;
761
+ }
762
+
763
+ .config-form__admin-edit-header {
764
+ display: flex;
765
+ align-items: center;
766
+ gap: 0.5rem;
767
+ padding: 0.75rem 1rem;
768
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
769
+ border-bottom: 1px solid var(--color-ref-blue-200, #bfdbfe);
770
+ font-size: 0.8125rem;
771
+ font-weight: 600;
772
+ color: var(--color-ref-blue-800, #1e40af);
773
+ }
774
+
775
+ .config-form__admin-edit-header :global(svg) {
776
+ width: 1rem;
777
+ height: 1rem;
778
+ color: var(--color-ref-blue-600, #2563eb);
779
+ }
780
+
781
+ .config-form__admin-edit-content {
782
+ padding: 1rem;
783
+ display: flex;
784
+ flex-direction: column;
785
+ gap: 0.75rem;
786
+ }
787
+
788
+ .config-form__admin-edit-description {
789
+ margin: 0;
790
+ font-size: 0.8125rem;
791
+ color: var(--color-ref-blue-700, #1d4ed8);
792
+ line-height: 1.5;
793
+ }
794
+
795
+ /* ============================================
796
+ LOADING STATE
797
+ ============================================ */
798
+
799
+ .config-form__loading {
800
+ display: flex;
801
+ flex-direction: column;
802
+ align-items: center;
803
+ justify-content: center;
804
+ padding: 3rem 1.5rem;
805
+ gap: 1rem;
806
+ }
807
+
808
+ .config-form__loading-spinner {
809
+ width: 2.5rem;
810
+ height: 2.5rem;
811
+ border: 3px solid var(--color-ref-blue-100, #dbeafe);
812
+ border-top-color: var(--color-ref-blue-500, #3b82f6);
813
+ border-radius: 50%;
814
+ animation: config-form-spin 0.8s linear infinite;
815
+ }
816
+
817
+ @keyframes config-form-spin {
818
+ to {
819
+ transform: rotate(360deg);
820
+ }
821
+ }
822
+
823
+ .config-form__loading-text {
824
+ margin: 0;
825
+ font-size: 0.875rem;
826
+ color: var(--color-ref-gray-600, #4b5563);
827
+ }
828
+
829
+ /* ============================================
830
+ ERROR STATE
831
+ ============================================ */
832
+
833
+ .config-form__error {
834
+ background-color: var(--color-ref-red-50, #fef2f2);
835
+ border: 1px solid var(--color-ref-red-200, #fecaca);
836
+ border-radius: 0.5rem;
837
+ overflow: hidden;
838
+ }
839
+
840
+ .config-form__error-header {
841
+ display: flex;
842
+ align-items: center;
843
+ gap: 0.5rem;
844
+ padding: 0.75rem 1rem;
845
+ background-color: var(--color-ref-red-100, #fee2e2);
846
+ border-bottom: 1px solid var(--color-ref-red-200, #fecaca);
847
+ font-size: 0.8125rem;
848
+ font-weight: 600;
849
+ color: var(--color-ref-red-800, #991b1b);
850
+ }
851
+
852
+ .config-form__error-header :global(svg) {
853
+ width: 1rem;
854
+ height: 1rem;
855
+ color: var(--color-ref-red-600, #dc2626);
856
+ }
857
+
858
+ .config-form__error-content {
859
+ padding: 1rem;
860
+ display: flex;
861
+ flex-direction: column;
862
+ gap: 0.75rem;
863
+ }
864
+
865
+ .config-form__error-message {
866
+ margin: 0;
867
+ font-size: 0.8125rem;
868
+ color: var(--color-ref-red-700, #b91c1c);
869
+ line-height: 1.5;
870
+ }
871
+
872
+ .config-form__error-actions {
873
+ display: flex;
874
+ gap: 0.5rem;
875
+ flex-wrap: wrap;
876
+ }
877
+
878
+ /* ============================================
879
+ SCHEMA ACTIONS (Refresh, External Editor)
880
+ ============================================ */
881
+
882
+ .config-form__schema-actions {
883
+ display: flex;
884
+ gap: 0.5rem;
885
+ margin-bottom: 1rem;
886
+ padding-bottom: 0.75rem;
887
+ border-bottom: 1px solid var(--color-ref-gray-100, #f3f4f6);
888
+ }
889
+
890
+ .config-form__schema-refresh,
891
+ .config-form__schema-external {
892
+ display: inline-flex;
893
+ align-items: center;
894
+ gap: 0.375rem;
895
+ padding: 0.375rem 0.625rem;
896
+ font-size: 0.75rem;
897
+ font-weight: 500;
898
+ font-family: inherit;
899
+ border-radius: 0.375rem;
900
+ cursor: pointer;
901
+ transition: all 0.15s ease;
902
+ border: 1px solid transparent;
903
+ }
904
+
905
+ .config-form__schema-refresh {
906
+ background-color: var(--color-ref-gray-50, #f9fafb);
907
+ border-color: var(--color-ref-gray-200, #e5e7eb);
908
+ color: var(--color-ref-gray-600, #4b5563);
909
+ }
910
+
911
+ .config-form__schema-refresh:hover {
912
+ background-color: var(--color-ref-gray-100, #f3f4f6);
913
+ border-color: var(--color-ref-gray-300, #d1d5db);
914
+ color: var(--color-ref-gray-700, #374151);
915
+ }
916
+
917
+ .config-form__schema-refresh :global(svg),
918
+ .config-form__schema-external :global(svg) {
919
+ width: 0.875rem;
920
+ height: 0.875rem;
921
+ }
922
+
923
+ .config-form__schema-external {
924
+ background-color: var(--color-ref-blue-50, #eff6ff);
925
+ border-color: var(--color-ref-blue-200, #bfdbfe);
926
+ color: var(--color-ref-blue-700, #1d4ed8);
927
+ }
928
+
929
+ .config-form__schema-external:hover {
930
+ background-color: var(--color-ref-blue-100, #dbeafe);
931
+ border-color: var(--color-ref-blue-300, #93c5fd);
932
+ color: var(--color-ref-blue-800, #1e40af);
933
+ }
934
+
935
+ /* ============================================
936
+ EXTERNAL BUTTON STYLE
937
+ ============================================ */
938
+
939
+ .config-form__button--external {
940
+ background: linear-gradient(
941
+ 135deg,
942
+ var(--color-ref-indigo-500, #6366f1) 0%,
943
+ var(--color-ref-blue-600, #2563eb) 100%
944
+ );
945
+ color: #ffffff;
946
+ box-shadow:
947
+ 0 1px 3px rgba(99, 102, 241, 0.3),
948
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
949
+ }
950
+
951
+ .config-form__button--external:hover {
952
+ background: linear-gradient(
953
+ 135deg,
954
+ var(--color-ref-indigo-600, #4f46e5) 0%,
955
+ var(--color-ref-blue-700, #1d4ed8) 100%
956
+ );
957
+ box-shadow:
958
+ 0 4px 12px rgba(99, 102, 241, 0.35),
959
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
960
+ transform: translateY(-1px);
961
+ }
962
+
963
+ .config-form__button--external:active {
964
+ transform: translateY(0);
965
+ }
966
+
967
+ .config-form__button--external:focus-visible {
968
+ outline: none;
969
+ box-shadow:
970
+ 0 0 0 3px rgba(99, 102, 241, 0.4),
971
+ 0 4px 12px rgba(99, 102, 241, 0.35);
972
+ }
482
973
  </style>
@@ -8,6 +8,8 @@ interface Props {
8
8
  values?: Record<string, unknown>;
9
9
  /** Whether to show UI extension settings section */
10
10
  showUIExtensions?: boolean;
11
+ /** Optional workflow ID for context in external links */
12
+ workflowId?: string;
11
13
  /** Callback when form is saved (includes both config and extensions if enabled) */
12
14
  onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
13
15
  /** Callback when form is cancelled */
@@ -19,10 +19,6 @@
19
19
  cancel: void;
20
20
  }>();
21
21
 
22
- function handleSave() {
23
- dispatch('save', { values: localConfigValues });
24
- }
25
-
26
22
  function handleCancel() {
27
23
  dispatch('cancel');
28
24
  }
@@ -79,29 +75,13 @@
79
75
  <ConfigForm
80
76
  schema={props.configSchema}
81
77
  values={localConfigValues}
82
- on:change={({ detail }) => {
83
- localConfigValues = detail.values;
78
+ showUIExtensions={false}
79
+ onSave={(config) => {
80
+ dispatch('save', { values: config });
84
81
  }}
82
+ onCancel={handleCancel}
85
83
  />
86
84
  </div>
87
-
88
- <!-- Modal Footer -->
89
- <div class="config-modal__footer">
90
- <button
91
- type="button"
92
- class="config-modal__btn config-modal__btn--secondary"
93
- onclick={handleCancel}
94
- >
95
- Cancel
96
- </button>
97
- <button
98
- type="button"
99
- class="config-modal__btn config-modal__btn--primary"
100
- onclick={handleSave}
101
- >
102
- Save Configuration
103
- </button>
104
- </div>
105
85
  </div>
106
86
  </div>
107
87
  {/if}
@@ -179,48 +159,6 @@
179
159
  min-height: 0;
180
160
  }
181
161
 
182
- .config-modal__footer {
183
- display: flex;
184
- align-items: center;
185
- justify-content: flex-end;
186
- gap: 0.75rem;
187
- padding: 1rem 1.5rem 1.5rem 1.5rem;
188
- border-top: 1px solid #e5e7eb;
189
- background-color: #f9fafb;
190
- }
191
-
192
- .config-modal__btn {
193
- padding: 0.5rem 1rem;
194
- border-radius: 0.375rem;
195
- font-size: 0.875rem;
196
- font-weight: 500;
197
- cursor: pointer;
198
- transition: all 0.2s;
199
- border: 1px solid transparent;
200
- }
201
-
202
- .config-modal__btn--primary {
203
- background-color: #3b82f6;
204
- color: white;
205
- border-color: #3b82f6;
206
- }
207
-
208
- .config-modal__btn--primary:hover {
209
- background-color: #2563eb;
210
- border-color: #2563eb;
211
- }
212
-
213
- .config-modal__btn--secondary {
214
- background-color: white;
215
- color: #374151;
216
- border-color: #d1d5db;
217
- }
218
-
219
- .config-modal__btn--secondary:hover {
220
- background-color: #f9fafb;
221
- border-color: #9ca3af;
222
- }
223
-
224
162
  /* Responsive adjustments */
225
163
  @media (max-width: 1024px) {
226
164
  .config-modal {
@@ -242,10 +180,6 @@
242
180
  .config-modal__content {
243
181
  padding: 1rem;
244
182
  }
245
-
246
- .config-modal__footer {
247
- padding: 0.75rem 1rem 1rem 1rem;
248
- }
249
183
  }
250
184
 
251
185
  @media (max-width: 640px) {