@d34dman/flowdrop 0.0.46 → 0.0.48

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 (34) hide show
  1. package/dist/components/App.svelte +10 -0
  2. package/dist/components/App.svelte.d.ts +2 -0
  3. package/dist/components/ConfigForm.svelte +72 -6
  4. package/dist/components/ConfigForm.svelte.d.ts +13 -1
  5. package/dist/components/SchemaForm.svelte +4 -2
  6. package/dist/components/SettingsPanel.svelte +5 -2
  7. package/dist/components/WorkflowEditor.svelte +26 -0
  8. package/dist/components/WorkflowEditor.svelte.d.ts +1 -0
  9. package/dist/components/form/FormAutocomplete.svelte +7 -4
  10. package/dist/components/form/FormField.svelte +42 -13
  11. package/dist/components/form/FormField.svelte.d.ts +11 -0
  12. package/dist/components/form/FormFieldLight.svelte +16 -13
  13. package/dist/components/form/FormTemplateEditor.svelte +343 -21
  14. package/dist/components/form/FormTemplateEditor.svelte.d.ts +22 -2
  15. package/dist/components/form/index.d.ts +1 -0
  16. package/dist/components/form/index.js +2 -0
  17. package/dist/components/form/templateAutocomplete.d.ts +29 -0
  18. package/dist/components/form/templateAutocomplete.js +253 -0
  19. package/dist/components/form/types.d.ts +29 -3
  20. package/dist/components/nodes/TerminalNode.svelte +27 -15
  21. package/dist/registry/nodeComponentRegistry.d.ts +9 -9
  22. package/dist/registry/nodeComponentRegistry.js +10 -10
  23. package/dist/services/apiVariableService.d.ts +116 -0
  24. package/dist/services/apiVariableService.js +338 -0
  25. package/dist/services/variableService.d.ts +141 -0
  26. package/dist/services/variableService.js +462 -0
  27. package/dist/svelte-app.d.ts +2 -0
  28. package/dist/svelte-app.js +2 -1
  29. package/dist/types/index.d.ts +257 -0
  30. package/dist/utils/handlePositioning.d.ts +31 -0
  31. package/dist/utils/handlePositioning.js +35 -0
  32. package/dist/utils/nodeTypes.d.ts +15 -10
  33. package/dist/utils/nodeTypes.js +24 -22
  34. package/package.json +7 -3
@@ -71,6 +71,8 @@
71
71
  variant?: 'primary' | 'secondary' | 'outline';
72
72
  onclick?: (event: Event) => void;
73
73
  }>;
74
+ /** Show settings gear icon in navbar */
75
+ showSettings?: boolean;
74
76
  /** API base URL */
75
77
  apiBaseUrl?: string;
76
78
  /** Endpoint configuration */
@@ -96,6 +98,7 @@
96
98
  pipelineId,
97
99
  navbarTitle,
98
100
  navbarActions = [],
101
+ showSettings = true,
99
102
  apiBaseUrl,
100
103
  endpointConfig: propEndpointConfig,
101
104
  authProvider,
@@ -705,6 +708,7 @@
705
708
  }
706
709
  ]}
707
710
  showStatus={true}
711
+ {showSettings}
708
712
  />
709
713
  {/snippet}
710
714
 
@@ -756,6 +760,8 @@
756
760
  <ConfigForm
757
761
  node={currentNode}
758
762
  workflowId={$workflowStore?.id}
763
+ workflowNodes={$workflowStore?.nodes}
764
+ workflowEdges={$workflowStore?.edges}
759
765
  onChange={async (updatedConfig, uiExtensions) => {
760
766
  // Sync config changes to workflow immediately on field blur
761
767
  if (selectedNodeId && currentNode) {
@@ -780,6 +786,10 @@
780
786
 
781
787
  workflowActions.updateNode(selectedNodeId, nodeUpdates);
782
788
 
789
+ // Update the local editor state to reflect config changes immediately
790
+ // This is needed for nodeType changes to take effect visually
791
+ workflowEditorRef.updateNodeData(selectedNodeId, updatedData);
792
+
783
793
  // Refresh edge positions in case config changes affect handles
784
794
  await workflowEditorRef.refreshEdgePositions(selectedNodeId);
785
795
  }
@@ -36,6 +36,8 @@ interface Props {
36
36
  variant?: 'primary' | 'secondary' | 'outline';
37
37
  onclick?: (event: Event) => void;
38
38
  }>;
39
+ /** Show settings gear icon in navbar */
40
+ showSettings?: boolean;
39
41
  /** API base URL */
40
42
  apiBaseUrl?: string;
41
43
  /** Endpoint configuration */
@@ -18,12 +18,15 @@
18
18
  -->
19
19
 
20
20
  <script lang="ts">
21
+ import { setContext } from 'svelte';
21
22
  import Icon from '@iconify/svelte';
22
23
  import type {
23
24
  ConfigSchema,
24
25
  WorkflowNode,
26
+ WorkflowEdge,
25
27
  NodeUIExtensions,
26
- ConfigEditOptions
28
+ ConfigEditOptions,
29
+ AuthProvider
27
30
  } from '../types/index.js';
28
31
  import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
29
32
  import type { FieldSchema } from './form/index.js';
@@ -35,6 +38,7 @@
35
38
  type DynamicSchemaResult
36
39
  } from '../services/dynamicSchemaService.js';
37
40
  import { globalSaveWorkflow } from '../services/globalSave.js';
41
+ import { getAvailableVariables } from '../services/variableService.js';
38
42
 
39
43
  interface Props {
40
44
  /** Optional workflow node (if provided, schema and values are derived from it) */
@@ -49,6 +53,18 @@
49
53
  workflowId?: string;
50
54
  /** Whether to also save the workflow when saving config */
51
55
  saveWorkflowWhenSavingConfig?: boolean;
56
+ /**
57
+ * All workflow nodes (used for deriving template variables from connected nodes).
58
+ * When provided along with workflowEdges, enables autocomplete for template fields.
59
+ */
60
+ workflowNodes?: WorkflowNode[];
61
+ /**
62
+ * All workflow edges (used for finding connections to derive template variables).
63
+ * When provided along with workflowNodes, enables autocomplete for template fields.
64
+ */
65
+ workflowEdges?: WorkflowEdge[];
66
+ /** Auth provider for API requests (used for template variable API mode) */
67
+ authProvider?: AuthProvider;
52
68
  /** Callback when any field value changes (fired on blur for immediate sync) */
53
69
  onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
54
70
  /** Callback when form is saved (includes both config and extensions if enabled) */
@@ -64,11 +80,19 @@
64
80
  showUIExtensions = true,
65
81
  workflowId,
66
82
  saveWorkflowWhenSavingConfig = false,
83
+ workflowNodes = [],
84
+ workflowEdges = [],
85
+ authProvider,
67
86
  onChange,
68
87
  onSave,
69
88
  onCancel
70
89
  }: Props = $props();
71
90
 
91
+ // Set context for child components (e.g., FormAutocomplete)
92
+ // Use getter functions to ensure child components always get the current prop value,
93
+ // even if the prop changes after initial mount
94
+ setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
95
+
72
96
  /**
73
97
  * State for dynamic schema loading
74
98
  */
@@ -118,13 +142,14 @@
118
142
  });
119
143
 
120
144
  /**
121
- * Check if the node has no static schema and needs dynamic loading
145
+ * Check if the node needs dynamic schema loading
146
+ * Loads when: no static schema OR preferDynamicSchema is true
122
147
  */
123
148
  const needsDynamicSchemaLoad = $derived.by(() => {
124
149
  if (!node) return false;
125
150
  const staticSchema = schema ?? node.data.metadata?.configSchema;
126
- // Need to load if: no static schema AND dynamic schema is configured
127
- return !staticSchema && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
151
+ // Need to load if: (no static schema OR preferDynamicSchema is true) AND dynamic schema is configured
152
+ return (!staticSchema || configEditOptions?.preferDynamicSchema === true) && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
128
153
  });
129
154
 
130
155
  /**
@@ -364,10 +389,46 @@
364
389
  }
365
390
 
366
391
  /**
367
- * Convert ConfigProperty to FieldSchema for FormField component
392
+ * Convert ConfigProperty to FieldSchema for FormField component.
393
+ * Processes template fields to inject computed variable schema.
394
+ *
395
+ * For template fields, the `variables` config controls which input ports
396
+ * provide variables for autocomplete.
368
397
  */
369
398
  function toFieldSchema(property: Record<string, unknown>): FieldSchema {
370
- return property as FieldSchema;
399
+ const fieldSchema = property as FieldSchema;
400
+
401
+ // Process template fields to compute variable schema
402
+ if (fieldSchema.format === 'template' && node && workflowNodes.length > 0 && workflowEdges.length > 0) {
403
+ // Get the variables config (may be undefined or partially defined)
404
+ const variablesConfig = fieldSchema.variables;
405
+
406
+ // Compute the variable schema with optional port filtering and port name prefixing
407
+ const computedSchema = getAvailableVariables(node, workflowNodes, workflowEdges, {
408
+ targetPortIds: variablesConfig?.ports,
409
+ includePortName: variablesConfig?.includePortName
410
+ });
411
+
412
+ // Merge computed schema with any pre-defined schema
413
+ const mergedSchema = variablesConfig?.schema
414
+ ? {
415
+ variables: {
416
+ ...computedSchema.variables,
417
+ ...variablesConfig.schema.variables
418
+ }
419
+ }
420
+ : computedSchema;
421
+
422
+ return {
423
+ ...fieldSchema,
424
+ variables: {
425
+ ...variablesConfig,
426
+ schema: mergedSchema
427
+ }
428
+ } as FieldSchema;
429
+ }
430
+
431
+ return fieldSchema;
371
432
  }
372
433
  </script>
373
434
 
@@ -487,6 +548,11 @@
487
548
  value={configValues[key]}
488
549
  {required}
489
550
  animationIndex={index}
551
+ {node}
552
+ nodes={workflowNodes}
553
+ edges={workflowEdges}
554
+ {workflowId}
555
+ {authProvider}
490
556
  onChange={(val) => handleFieldChange(key, val)}
491
557
  />
492
558
  {/each}
@@ -1,4 +1,4 @@
1
- import type { ConfigSchema, WorkflowNode, NodeUIExtensions } from '../types/index.js';
1
+ import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions, AuthProvider } from '../types/index.js';
2
2
  interface Props {
3
3
  /** Optional workflow node (if provided, schema and values are derived from it) */
4
4
  node?: WorkflowNode;
@@ -12,6 +12,18 @@ interface Props {
12
12
  workflowId?: string;
13
13
  /** Whether to also save the workflow when saving config */
14
14
  saveWorkflowWhenSavingConfig?: boolean;
15
+ /**
16
+ * All workflow nodes (used for deriving template variables from connected nodes).
17
+ * When provided along with workflowEdges, enables autocomplete for template fields.
18
+ */
19
+ workflowNodes?: WorkflowNode[];
20
+ /**
21
+ * All workflow edges (used for finding connections to derive template variables).
22
+ * When provided along with workflowNodes, enables autocomplete for template fields.
23
+ */
24
+ workflowEdges?: WorkflowEdge[];
25
+ /** Auth provider for API requests (used for template variable API mode) */
26
+ authProvider?: AuthProvider;
15
27
  /** Callback when any field value changes (fired on blur for immediate sync) */
16
28
  onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
17
29
  /** Callback when form is saved (includes both config and extensions if enabled) */
@@ -157,8 +157,10 @@
157
157
  }: Props = $props();
158
158
 
159
159
  // Set context for child components (e.g., FormAutocomplete)
160
- setContext<AuthProvider | undefined>('flowdrop:authProvider', authProvider);
161
- setContext<string>('flowdrop:baseUrl', baseUrl);
160
+ // Use getter functions to ensure child components always get the current prop value,
161
+ // even if the prop changes after initial mount
162
+ setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
163
+ setContext<() => string>('flowdrop:getBaseUrl', () => baseUrl);
162
164
 
163
165
  /**
164
166
  * Internal reactive state for form values
@@ -89,8 +89,11 @@
89
89
  type: 'string',
90
90
  title: 'Theme Preference',
91
91
  description: 'Choose your preferred color scheme',
92
- enum: ['light', 'dark', 'auto'],
93
- enumLabels: ['Light', 'Dark', 'Auto (System)'],
92
+ oneOf: [
93
+ { const: 'light', title: 'Light' },
94
+ { const: 'dark', title: 'Dark' },
95
+ { const: 'auto', title: 'Auto (System)' }
96
+ ],
94
97
  default: 'auto'
95
98
  }
96
99
  }
@@ -515,6 +515,32 @@
515
515
  */
516
516
  let nodeIdToRefresh = $state<string | null>(null);
517
517
 
518
+ /**
519
+ * Update a node's data in the local editor state.
520
+ * This should be called after updating the node in the global store to ensure
521
+ * the visual representation is updated immediately (e.g., for nodeType changes).
522
+ *
523
+ * @param nodeId - The ID of the node to update
524
+ * @param dataUpdates - Partial data updates to merge into the node's data
525
+ */
526
+ export function updateNodeData(
527
+ nodeId: string,
528
+ dataUpdates: Partial<WorkflowNodeType['data']>
529
+ ): void {
530
+ flowNodes = flowNodes.map((node) => {
531
+ if (node.id === nodeId) {
532
+ return {
533
+ ...node,
534
+ data: {
535
+ ...node.data,
536
+ ...dataUpdates
537
+ }
538
+ };
539
+ }
540
+ return node;
541
+ });
542
+ }
543
+
518
544
  /**
519
545
  * Force edge position recalculation after node config changes
520
546
  * This should be called after saving gateway/switch node configs where branches are reordered
@@ -16,6 +16,7 @@ interface Props {
16
16
  pipelineId?: string;
17
17
  }
18
18
  declare const WorkflowEditor: import("svelte").Component<Props, {
19
+ updateNodeData: (nodeId: string, dataUpdates: Partial<WorkflowNodeType["data"]>) => void;
19
20
  refreshEdgePositions: (nodeId: string) => Promise<void>;
20
21
  }, "">;
21
22
  type WorkflowEditor = ReturnType<typeof WorkflowEditor>;
@@ -59,9 +59,10 @@
59
59
  onChange
60
60
  }: Props = $props();
61
61
 
62
- // Get AuthProvider from context (set by SchemaForm or parent)
63
- const authProvider = getContext<AuthProvider | undefined>('flowdrop:authProvider');
64
- const baseUrl = getContext<string | undefined>('flowdrop:baseUrl') ?? '';
62
+ // Get AuthProvider and baseUrl from context via getter functions
63
+ // This pattern ensures we always get the current value, even if props change after mount
64
+ const getAuthProvider = getContext<(() => AuthProvider | undefined) | undefined>('flowdrop:getAuthProvider');
65
+ const getBaseUrl = getContext<(() => string) | undefined>('flowdrop:getBaseUrl');
65
66
 
66
67
  // Configuration with defaults
67
68
  const queryParam = $derived(autocomplete.queryParam ?? 'q');
@@ -142,6 +143,7 @@
142
143
  * @returns Full URL with query parameter
143
144
  */
144
145
  function buildUrl(query: string): string {
146
+ const baseUrl = getBaseUrl?.() ?? '';
145
147
  const url = autocomplete.url.startsWith('http')
146
148
  ? autocomplete.url
147
149
  : `${baseUrl}${autocomplete.url}`;
@@ -194,7 +196,8 @@
194
196
  'Content-Type': 'application/json'
195
197
  };
196
198
 
197
- // Add auth headers if provider is available
199
+ // Add auth headers if provider is available (call getter to get current value)
200
+ const authProvider = getAuthProvider?.();
198
201
  if (authProvider) {
199
202
  const authHeaders = await authProvider.getAuthHeaders();
200
203
  Object.assign(headers, authHeaders);
@@ -17,12 +17,12 @@
17
17
  5. format: 'template' -> FormTemplateEditor (CodeMirror with Twig/Liquid syntax)
18
18
  6. enum with multiple: true -> FormCheckboxGroup
19
19
  7. enum -> FormSelect (simple values without labels)
20
- 8. format: 'multiline' -> FormTextarea
21
- 9. format: 'range' (number/integer) -> FormRangeField
22
- 10. type: 'string' -> FormTextField
23
- 11. type: 'number' or 'integer' -> FormNumberField
24
- 12. type: 'boolean' -> FormToggle
25
- 13. oneOf with const/title (labeled options) -> FormSelect
20
+ 8. oneOf with const/title (labeled options) -> FormSelect
21
+ 9. format: 'multiline' -> FormTextarea
22
+ 10. format: 'range' (number/integer) -> FormRangeField
23
+ 11. type: 'string' -> FormTextField
24
+ 12. type: 'number' or 'integer' -> FormNumberField
25
+ 13. type: 'boolean' -> FormToggle
26
26
  14. type: 'object' (without format) -> FormCodeEditor (for JSON objects)
27
27
  15. fallback -> FormTextField
28
28
  -->
@@ -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 type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
46
47
 
47
48
  interface Props {
48
49
  /** Unique key/id for the field */
@@ -57,9 +58,31 @@
57
58
  animationIndex?: number;
58
59
  /** Callback when the field value changes */
59
60
  onChange: (value: unknown) => void;
61
+ /** Current workflow node (optional, used for template variable API mode) */
62
+ node?: WorkflowNode;
63
+ /** All workflow nodes (optional, used for port-derived variables) */
64
+ nodes?: WorkflowNode[];
65
+ /** All workflow edges (optional, used for port-derived variables) */
66
+ edges?: WorkflowEdge[];
67
+ /** Workflow ID (optional, used for template variable API mode) */
68
+ workflowId?: string;
69
+ /** Auth provider (optional, used for API requests) */
70
+ authProvider?: AuthProvider;
60
71
  }
61
72
 
62
- let { fieldKey, schema, value, required = false, animationIndex = 0, onChange }: Props = $props();
73
+ let {
74
+ fieldKey,
75
+ schema,
76
+ value,
77
+ required = false,
78
+ animationIndex = 0,
79
+ onChange,
80
+ node,
81
+ nodes,
82
+ edges,
83
+ workflowId,
84
+ authProvider
85
+ }: Props = $props();
63
86
 
64
87
  /**
65
88
  * When schema.readOnly is true, disable all inputs (no editing).
@@ -122,6 +145,12 @@
122
145
  return 'select-enum';
123
146
  }
124
147
 
148
+ // oneOf with labeled options (standard JSON Schema) or legacy options -> select
149
+ // Must be checked before basic type checks since oneOf schemas often have type: 'string'
150
+ if ((schema.oneOf && schema.oneOf.length > 0) || schema.options) {
151
+ return 'select-options';
152
+ }
153
+
125
154
  // Multiline string -> textarea
126
155
  if (schema.type === 'string' && schema.format === 'multiline') {
127
156
  return 'textarea';
@@ -147,11 +176,6 @@
147
176
  return 'toggle';
148
177
  }
149
178
 
150
- // oneOf with labeled options (standard JSON Schema) or legacy options -> select
151
- if ((schema.oneOf && schema.oneOf.length > 0) || schema.options) {
152
- return 'select-options';
153
- }
154
-
155
179
  // Future: Array type support
156
180
  if (schema.type === 'array') {
157
181
  return 'array';
@@ -350,11 +374,16 @@
350
374
  {required}
351
375
  height={(schema.height as string | undefined) ?? '250px'}
352
376
  darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
353
- variableHints={(schema.variableHints as string[] | undefined) ?? []}
377
+ variables={schema.variables}
354
378
  placeholderExample={(schema.placeholderExample as string | undefined) ??
355
379
  'Hello {{ name }}, your order #{{ order_id }} is ready!'}
356
380
  ariaDescribedBy={descriptionId}
357
381
  disabled={isReadOnly}
382
+ {node}
383
+ {nodes}
384
+ {edges}
385
+ {workflowId}
386
+ {authProvider}
358
387
  onChange={(val) => onChange(val)}
359
388
  />
360
389
  {:else if fieldType === 'autocomplete' && schema.autocomplete}
@@ -1,4 +1,5 @@
1
1
  import type { FieldSchema } from './types.js';
2
+ import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
2
3
  interface Props {
3
4
  /** Unique key/id for the field */
4
5
  fieldKey: string;
@@ -12,6 +13,16 @@ interface Props {
12
13
  animationIndex?: number;
13
14
  /** Callback when the field value changes */
14
15
  onChange: (value: unknown) => void;
16
+ /** Current workflow node (optional, used for template variable API mode) */
17
+ node?: WorkflowNode;
18
+ /** All workflow nodes (optional, used for port-derived variables) */
19
+ nodes?: WorkflowNode[];
20
+ /** All workflow edges (optional, used for port-derived variables) */
21
+ edges?: WorkflowEdge[];
22
+ /** Workflow ID (optional, used for template variable API mode) */
23
+ workflowId?: string;
24
+ /** Auth provider (optional, used for API requests) */
25
+ authProvider?: AuthProvider;
15
26
  }
16
27
  declare const FormField: import("svelte").Component<Props, {}, "">;
17
28
  type FormField = ReturnType<typeof FormField>;
@@ -22,12 +22,12 @@
22
22
  2. format: 'hidden' -> skip rendering (return nothing)
23
23
  3. enum with multiple: true -> FormCheckboxGroup
24
24
  4. enum -> FormSelect (simple values without labels)
25
- 5. format: 'multiline' -> FormTextarea
26
- 6. format: 'range' (number/integer) -> FormRangeField
27
- 7. type: 'string' -> FormTextField
28
- 8. type: 'number' or 'integer' -> FormNumberField
29
- 9. type: 'boolean' -> FormToggle
30
- 10. oneOf with const/title (labeled options) -> FormSelect
25
+ 5. oneOf with const/title (labeled options) -> FormSelect
26
+ 6. format: 'multiline' -> FormTextarea
27
+ 7. format: 'range' (number/integer) -> FormRangeField
28
+ 8. type: 'string' -> FormTextField
29
+ 9. type: 'number' or 'integer' -> FormNumberField
30
+ 10. type: 'boolean' -> FormToggle
31
31
  11. type: 'array' -> FormArray
32
32
  12. fallback -> FormTextField
33
33
  -->
@@ -43,6 +43,7 @@
43
43
  import FormCheckboxGroup from './FormCheckboxGroup.svelte';
44
44
  import FormArray from './FormArray.svelte';
45
45
  import { resolveFieldComponent } from '../../form/fieldRegistry.js';
46
+ import { resolvedTheme } from '../../stores/settingsStore.js';
46
47
  import type { FieldSchema } from './types.js';
47
48
  import { getSchemaOptions } from './types.js';
48
49
 
@@ -127,6 +128,12 @@
127
128
  return 'select-enum';
128
129
  }
129
130
 
131
+ // oneOf with labeled options (standard JSON Schema) or legacy options -> select
132
+ // Must be checked before basic type checks since oneOf schemas often have type: 'string'
133
+ if ((schema.oneOf && schema.oneOf.length > 0) || schema.options) {
134
+ return 'select-options';
135
+ }
136
+
130
137
  // Multiline string -> textarea
131
138
  if (schema.type === 'string' && schema.format === 'multiline') {
132
139
  return 'textarea';
@@ -152,11 +159,6 @@
152
159
  return 'toggle';
153
160
  }
154
161
 
155
- // oneOf with labeled options (standard JSON Schema) or legacy options -> select
156
- if ((schema.oneOf && schema.oneOf.length > 0) || schema.options) {
157
- return 'select-options';
158
- }
159
-
160
162
  // Array type
161
163
  if (schema.type === 'array') {
162
164
  return 'array';
@@ -226,6 +228,7 @@
226
228
  >
227
229
  {#if fieldType === 'registered' && registeredComponent}
228
230
  <!-- Render registered custom component -->
231
+ <!-- darkTheme: use schema value if explicitly set, otherwise derive from resolved theme -->
229
232
  <registeredComponent.component
230
233
  id={fieldKey}
231
234
  {value}
@@ -233,12 +236,12 @@
233
236
  {required}
234
237
  ariaDescribedBy={descriptionId}
235
238
  height={schema.height as string | undefined}
236
- darkTheme={schema.darkTheme as boolean | undefined}
239
+ darkTheme={schema.darkTheme ?? $resolvedTheme === 'dark'}
237
240
  autoFormat={schema.autoFormat as boolean | undefined}
238
241
  showToolbar={schema.showToolbar as boolean | undefined}
239
242
  showStatusBar={schema.showStatusBar as boolean | undefined}
240
243
  spellChecker={schema.spellChecker as boolean | undefined}
241
- variableHints={schema.variableHints as string[] | undefined}
244
+ variables={schema.variables}
242
245
  placeholderExample={schema.placeholderExample as string | undefined}
243
246
  onChange={(val: unknown) => onChange(val)}
244
247
  />