@d34dman/flowdrop 0.0.46 → 0.0.47
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 +6 -0
- package/dist/components/ConfigForm.svelte +52 -2
- package/dist/components/ConfigForm.svelte.d.ts +11 -1
- package/dist/components/SettingsPanel.svelte +5 -2
- package/dist/components/WorkflowEditor.svelte +26 -0
- package/dist/components/WorkflowEditor.svelte.d.ts +1 -0
- package/dist/components/form/FormField.svelte +13 -11
- package/dist/components/form/FormFieldLight.svelte +16 -12
- package/dist/components/form/FormTemplateEditor.svelte +124 -10
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +17 -1
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.js +2 -0
- package/dist/components/form/templateAutocomplete.d.ts +38 -0
- package/dist/components/form/templateAutocomplete.js +309 -0
- package/dist/components/form/types.d.ts +34 -2
- package/dist/registry/nodeComponentRegistry.d.ts +9 -9
- package/dist/registry/nodeComponentRegistry.js +10 -10
- package/dist/services/variableService.d.ts +100 -0
- package/dist/services/variableService.js +367 -0
- package/dist/types/index.d.ts +120 -0
- package/dist/utils/nodeTypes.d.ts +15 -10
- package/dist/utils/nodeTypes.js +24 -22
- package/package.json +2 -2
|
@@ -756,6 +756,8 @@
|
|
|
756
756
|
<ConfigForm
|
|
757
757
|
node={currentNode}
|
|
758
758
|
workflowId={$workflowStore?.id}
|
|
759
|
+
workflowNodes={$workflowStore?.nodes}
|
|
760
|
+
workflowEdges={$workflowStore?.edges}
|
|
759
761
|
onChange={async (updatedConfig, uiExtensions) => {
|
|
760
762
|
// Sync config changes to workflow immediately on field blur
|
|
761
763
|
if (selectedNodeId && currentNode) {
|
|
@@ -780,6 +782,10 @@
|
|
|
780
782
|
|
|
781
783
|
workflowActions.updateNode(selectedNodeId, nodeUpdates);
|
|
782
784
|
|
|
785
|
+
// Update the local editor state to reflect config changes immediately
|
|
786
|
+
// This is needed for nodeType changes to take effect visually
|
|
787
|
+
workflowEditorRef.updateNodeData(selectedNodeId, updatedData);
|
|
788
|
+
|
|
783
789
|
// Refresh edge positions in case config changes affect handles
|
|
784
790
|
await workflowEditorRef.refreshEdgePositions(selectedNodeId);
|
|
785
791
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import type {
|
|
23
23
|
ConfigSchema,
|
|
24
24
|
WorkflowNode,
|
|
25
|
+
WorkflowEdge,
|
|
25
26
|
NodeUIExtensions,
|
|
26
27
|
ConfigEditOptions
|
|
27
28
|
} from '../types/index.js';
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
type DynamicSchemaResult
|
|
36
37
|
} from '../services/dynamicSchemaService.js';
|
|
37
38
|
import { globalSaveWorkflow } from '../services/globalSave.js';
|
|
39
|
+
import { getAvailableVariables } from '../services/variableService.js';
|
|
38
40
|
|
|
39
41
|
interface Props {
|
|
40
42
|
/** Optional workflow node (if provided, schema and values are derived from it) */
|
|
@@ -49,6 +51,16 @@
|
|
|
49
51
|
workflowId?: string;
|
|
50
52
|
/** Whether to also save the workflow when saving config */
|
|
51
53
|
saveWorkflowWhenSavingConfig?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* All workflow nodes (used for deriving template variables from connected nodes).
|
|
56
|
+
* When provided along with workflowEdges, enables autocomplete for template fields.
|
|
57
|
+
*/
|
|
58
|
+
workflowNodes?: WorkflowNode[];
|
|
59
|
+
/**
|
|
60
|
+
* All workflow edges (used for finding connections to derive template variables).
|
|
61
|
+
* When provided along with workflowNodes, enables autocomplete for template fields.
|
|
62
|
+
*/
|
|
63
|
+
workflowEdges?: WorkflowEdge[];
|
|
52
64
|
/** Callback when any field value changes (fired on blur for immediate sync) */
|
|
53
65
|
onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
54
66
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
@@ -64,6 +76,8 @@
|
|
|
64
76
|
showUIExtensions = true,
|
|
65
77
|
workflowId,
|
|
66
78
|
saveWorkflowWhenSavingConfig = false,
|
|
79
|
+
workflowNodes = [],
|
|
80
|
+
workflowEdges = [],
|
|
67
81
|
onChange,
|
|
68
82
|
onSave,
|
|
69
83
|
onCancel
|
|
@@ -364,10 +378,46 @@
|
|
|
364
378
|
}
|
|
365
379
|
|
|
366
380
|
/**
|
|
367
|
-
* Convert ConfigProperty to FieldSchema for FormField component
|
|
381
|
+
* Convert ConfigProperty to FieldSchema for FormField component.
|
|
382
|
+
* Processes template fields to inject computed variable schema.
|
|
383
|
+
*
|
|
384
|
+
* For template fields, the `variables` config controls which input ports
|
|
385
|
+
* provide variables for autocomplete.
|
|
368
386
|
*/
|
|
369
387
|
function toFieldSchema(property: Record<string, unknown>): FieldSchema {
|
|
370
|
-
|
|
388
|
+
const fieldSchema = property as FieldSchema;
|
|
389
|
+
|
|
390
|
+
// Process template fields to compute variable schema
|
|
391
|
+
if (fieldSchema.format === 'template' && node && workflowNodes.length > 0 && workflowEdges.length > 0) {
|
|
392
|
+
// Get the variables config (may be undefined or partially defined)
|
|
393
|
+
const variablesConfig = fieldSchema.variables;
|
|
394
|
+
|
|
395
|
+
// Compute the variable schema with optional port filtering and port name prefixing
|
|
396
|
+
const computedSchema = getAvailableVariables(node, workflowNodes, workflowEdges, {
|
|
397
|
+
targetPortIds: variablesConfig?.ports,
|
|
398
|
+
includePortName: variablesConfig?.includePortName
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Merge computed schema with any pre-defined schema
|
|
402
|
+
const mergedSchema = variablesConfig?.schema
|
|
403
|
+
? {
|
|
404
|
+
variables: {
|
|
405
|
+
...computedSchema.variables,
|
|
406
|
+
...variablesConfig.schema.variables
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
: computedSchema;
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
...fieldSchema,
|
|
413
|
+
variables: {
|
|
414
|
+
...variablesConfig,
|
|
415
|
+
schema: mergedSchema
|
|
416
|
+
}
|
|
417
|
+
} as FieldSchema;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return fieldSchema;
|
|
371
421
|
}
|
|
372
422
|
</script>
|
|
373
423
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigSchema, WorkflowNode, NodeUIExtensions } from '../types/index.js';
|
|
1
|
+
import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions } 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,16 @@ 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[];
|
|
15
25
|
/** Callback when any field value changes (fired on blur for immediate sync) */
|
|
16
26
|
onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
17
27
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
@@ -89,8 +89,11 @@
|
|
|
89
89
|
type: 'string',
|
|
90
90
|
title: 'Theme Preference',
|
|
91
91
|
description: 'Choose your preferred color scheme',
|
|
92
|
-
|
|
93
|
-
|
|
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>;
|
|
@@ -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.
|
|
21
|
-
9. format: '
|
|
22
|
-
10.
|
|
23
|
-
11. type: '
|
|
24
|
-
12. type: '
|
|
25
|
-
13.
|
|
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
|
-->
|
|
@@ -122,6 +122,12 @@
|
|
|
122
122
|
return 'select-enum';
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// oneOf with labeled options (standard JSON Schema) or legacy options -> select
|
|
126
|
+
// Must be checked before basic type checks since oneOf schemas often have type: 'string'
|
|
127
|
+
if ((schema.oneOf && schema.oneOf.length > 0) || schema.options) {
|
|
128
|
+
return 'select-options';
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
// Multiline string -> textarea
|
|
126
132
|
if (schema.type === 'string' && schema.format === 'multiline') {
|
|
127
133
|
return 'textarea';
|
|
@@ -147,11 +153,6 @@
|
|
|
147
153
|
return 'toggle';
|
|
148
154
|
}
|
|
149
155
|
|
|
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
156
|
// Future: Array type support
|
|
156
157
|
if (schema.type === 'array') {
|
|
157
158
|
return 'array';
|
|
@@ -350,6 +351,7 @@
|
|
|
350
351
|
{required}
|
|
351
352
|
height={(schema.height as string | undefined) ?? '250px'}
|
|
352
353
|
darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
|
|
354
|
+
variables={schema.variables}
|
|
353
355
|
variableHints={(schema.variableHints as string[] | undefined) ?? []}
|
|
354
356
|
placeholderExample={(schema.placeholderExample as string | undefined) ??
|
|
355
357
|
'Hello {{ name }}, your order #{{ order_id }} is ready!'}
|
|
@@ -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.
|
|
26
|
-
6. format: '
|
|
27
|
-
7.
|
|
28
|
-
8. type: '
|
|
29
|
-
9. type: '
|
|
30
|
-
10.
|
|
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,11 +236,12 @@
|
|
|
233
236
|
{required}
|
|
234
237
|
ariaDescribedBy={descriptionId}
|
|
235
238
|
height={schema.height as string | undefined}
|
|
236
|
-
darkTheme={schema.darkTheme
|
|
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}
|
|
244
|
+
variables={schema.variables}
|
|
241
245
|
variableHints={schema.variableHints as string[] | undefined}
|
|
242
246
|
placeholderExample={schema.placeholderExample as string | undefined}
|
|
243
247
|
onChange={(val: unknown) => onChange(val)}
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
Features:
|
|
6
6
|
- Custom syntax highlighting for {{ variable }} placeholders
|
|
7
|
+
- Inline autocomplete for template variables (triggered on {{ and .)
|
|
8
|
+
- Support for nested object drilling (user.address.city)
|
|
9
|
+
- Support for array index access (items[0].name)
|
|
7
10
|
- Dark/light theme support
|
|
8
11
|
- Consistent styling with other form components
|
|
9
12
|
- Line wrapping for better template readability
|
|
10
|
-
- Optional variable hints display
|
|
13
|
+
- Optional variable hints display (clickable buttons)
|
|
11
14
|
- Proper ARIA attributes for accessibility
|
|
12
15
|
|
|
13
16
|
Usage:
|
|
@@ -34,6 +37,11 @@
|
|
|
34
37
|
import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
35
38
|
import { syntaxHighlighting, defaultHighlightStyle, indentOnInput } from '@codemirror/language';
|
|
36
39
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
40
|
+
import type { VariableSchema, TemplateVariablesConfig } from '../../types/index.js';
|
|
41
|
+
import {
|
|
42
|
+
createTemplateAutocomplete,
|
|
43
|
+
createSimpleTemplateAutocomplete
|
|
44
|
+
} from './templateAutocomplete.js';
|
|
37
45
|
|
|
38
46
|
interface Props {
|
|
39
47
|
/** Field identifier */
|
|
@@ -48,7 +56,22 @@
|
|
|
48
56
|
darkTheme?: boolean;
|
|
49
57
|
/** Editor height in pixels or CSS value */
|
|
50
58
|
height?: string;
|
|
51
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for template variable autocomplete.
|
|
61
|
+
* Controls which variables are available and how they are displayed.
|
|
62
|
+
*/
|
|
63
|
+
variables?: TemplateVariablesConfig;
|
|
64
|
+
/**
|
|
65
|
+
* Variable schema for advanced autocomplete with nested drilling.
|
|
66
|
+
* When provided, enables dot notation (user.name) and array access (items[0]).
|
|
67
|
+
* @deprecated Use `variables.schema` instead
|
|
68
|
+
*/
|
|
69
|
+
variableSchema?: VariableSchema;
|
|
70
|
+
/**
|
|
71
|
+
* Simple variable names for basic hints (backward compatible).
|
|
72
|
+
* Used when variableSchema is not provided.
|
|
73
|
+
* @deprecated Use `variables.schema` instead
|
|
74
|
+
*/
|
|
52
75
|
variableHints?: string[];
|
|
53
76
|
/** Placeholder variable example for the hint */
|
|
54
77
|
placeholderExample?: string;
|
|
@@ -67,6 +90,8 @@
|
|
|
67
90
|
required = false,
|
|
68
91
|
darkTheme = false,
|
|
69
92
|
height = '250px',
|
|
93
|
+
variables,
|
|
94
|
+
variableSchema,
|
|
70
95
|
variableHints = [],
|
|
71
96
|
placeholderExample = 'Hello {{ name }}, your order #{{ order_id }} is ready!',
|
|
72
97
|
disabled = false,
|
|
@@ -74,6 +99,37 @@
|
|
|
74
99
|
onChange
|
|
75
100
|
}: Props = $props();
|
|
76
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Get the effective variable schema.
|
|
104
|
+
* Prefers variables.schema, falls back to deprecated variableSchema prop.
|
|
105
|
+
*/
|
|
106
|
+
const effectiveVariableSchema = $derived.by<VariableSchema | undefined>(() => {
|
|
107
|
+
if (variables?.schema && Object.keys(variables.schema.variables).length > 0) {
|
|
108
|
+
return variables.schema;
|
|
109
|
+
}
|
|
110
|
+
if (variableSchema && Object.keys(variableSchema.variables).length > 0) {
|
|
111
|
+
return variableSchema;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Whether to show the variable hints section.
|
|
118
|
+
* Controlled by variables.showHints (defaults to true).
|
|
119
|
+
*/
|
|
120
|
+
const showHints = $derived(variables?.showHints !== false);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Derive the list of top-level variable names for the hints display.
|
|
124
|
+
* Prefers effectiveVariableSchema if available, falls back to variableHints.
|
|
125
|
+
*/
|
|
126
|
+
const displayVariables = $derived.by(() => {
|
|
127
|
+
if (effectiveVariableSchema) {
|
|
128
|
+
return Object.keys(effectiveVariableSchema.variables);
|
|
129
|
+
}
|
|
130
|
+
return variableHints;
|
|
131
|
+
});
|
|
132
|
+
|
|
77
133
|
/** Reference to the container element */
|
|
78
134
|
let containerRef: HTMLDivElement | undefined = $state(undefined);
|
|
79
135
|
|
|
@@ -86,10 +142,15 @@
|
|
|
86
142
|
/**
|
|
87
143
|
* Create a MatchDecorator for {{ variable }} patterns
|
|
88
144
|
* This highlights the entire {{ variable }} expression
|
|
145
|
+
* Supports:
|
|
146
|
+
* - Simple variables: {{ name }}
|
|
147
|
+
* - Dot notation: {{ user.name }}, {{ user.address.city }}
|
|
148
|
+
* - Array access: {{ items[0] }}, {{ items[0].name }}
|
|
149
|
+
* - Mixed: {{ orders[0].items[1].price }}
|
|
89
150
|
*/
|
|
90
151
|
const variableMatcher = new MatchDecorator({
|
|
91
|
-
// Match {{ variable_name }} patterns
|
|
92
|
-
regexp: /\{\{\s*[\w
|
|
152
|
+
// Match {{ variable_name }} patterns with dot notation and array indices
|
|
153
|
+
regexp: /\{\{\s*[\w]+(?:\.[\w]+|\[\d+\]|\[\*\])*\s*\}\}/g,
|
|
93
154
|
decoration: Decoration.mark({ class: 'cm-template-variable' })
|
|
94
155
|
});
|
|
95
156
|
|
|
@@ -125,7 +186,7 @@
|
|
|
125
186
|
|
|
126
187
|
/**
|
|
127
188
|
* Create editor extensions array for template editing
|
|
128
|
-
*
|
|
189
|
+
* Includes autocomplete when variables are available
|
|
129
190
|
* When disabled is true, adds readOnly/editable so the editor cannot be modified
|
|
130
191
|
*/
|
|
131
192
|
function createExtensions() {
|
|
@@ -158,7 +219,7 @@
|
|
|
158
219
|
// Update listener (only fires on user edit when not disabled)
|
|
159
220
|
EditorView.updateListener.of(handleUpdate),
|
|
160
221
|
|
|
161
|
-
// Custom theme
|
|
222
|
+
// Custom theme with autocomplete styling
|
|
162
223
|
EditorView.theme({
|
|
163
224
|
'&': {
|
|
164
225
|
height: height,
|
|
@@ -185,20 +246,73 @@
|
|
|
185
246
|
borderRadius: '3px',
|
|
186
247
|
padding: '1px 2px',
|
|
187
248
|
fontWeight: '500'
|
|
249
|
+
},
|
|
250
|
+
// Autocomplete dropdown styling
|
|
251
|
+
'.cm-tooltip.cm-tooltip-autocomplete': {
|
|
252
|
+
backgroundColor: 'var(--fd-background, #ffffff)',
|
|
253
|
+
border: '1px solid var(--fd-border, #e5e7eb)',
|
|
254
|
+
borderRadius: 'var(--fd-radius-lg, 0.5rem)',
|
|
255
|
+
boxShadow: 'var(--fd-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1))',
|
|
256
|
+
padding: '0.25rem',
|
|
257
|
+
maxHeight: '200px',
|
|
258
|
+
overflow: 'auto'
|
|
259
|
+
},
|
|
260
|
+
'.cm-tooltip.cm-tooltip-autocomplete > ul': {
|
|
261
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace",
|
|
262
|
+
fontSize: '0.8125rem'
|
|
263
|
+
},
|
|
264
|
+
'.cm-tooltip.cm-tooltip-autocomplete > ul > li': {
|
|
265
|
+
padding: '0.375rem 0.625rem',
|
|
266
|
+
borderRadius: 'var(--fd-radius-md, 0.375rem)',
|
|
267
|
+
display: 'flex',
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
gap: '0.5rem'
|
|
270
|
+
},
|
|
271
|
+
'.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
|
272
|
+
backgroundColor: 'var(--fd-accent-muted, rgba(168, 85, 247, 0.1))',
|
|
273
|
+
color: 'var(--fd-accent-hover, #7c3aed)'
|
|
274
|
+
},
|
|
275
|
+
'.cm-completionLabel': {
|
|
276
|
+
flex: '1'
|
|
277
|
+
},
|
|
278
|
+
'.cm-completionDetail': {
|
|
279
|
+
fontSize: '0.6875rem',
|
|
280
|
+
color: 'var(--fd-muted-foreground, #6b7280)',
|
|
281
|
+
opacity: '0.8'
|
|
188
282
|
}
|
|
189
283
|
}),
|
|
190
284
|
EditorView.lineWrapping,
|
|
191
285
|
EditorState.tabSize.of(2)
|
|
192
286
|
];
|
|
193
287
|
|
|
288
|
+
// Add autocomplete extension when variables are available (and not disabled)
|
|
289
|
+
if (!disabled) {
|
|
290
|
+
if (effectiveVariableSchema) {
|
|
291
|
+
// Use full autocomplete with nested drilling
|
|
292
|
+
extensions.push(createTemplateAutocomplete(effectiveVariableSchema));
|
|
293
|
+
} else if (variableHints.length > 0) {
|
|
294
|
+
// Fallback to simple autocomplete with just variable names
|
|
295
|
+
extensions.push(createSimpleTemplateAutocomplete(variableHints));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
194
299
|
if (darkTheme) {
|
|
195
300
|
extensions.push(oneDark);
|
|
196
|
-
// Add dark theme override for variable highlighting
|
|
301
|
+
// Add dark theme override for variable highlighting and autocomplete
|
|
197
302
|
extensions.push(
|
|
198
303
|
EditorView.theme({
|
|
199
304
|
'.cm-template-variable': {
|
|
200
305
|
color: '#c084fc',
|
|
201
306
|
backgroundColor: 'rgba(192, 132, 252, 0.15)'
|
|
307
|
+
},
|
|
308
|
+
'.cm-tooltip.cm-tooltip-autocomplete': {
|
|
309
|
+
backgroundColor: '#1e1e1e',
|
|
310
|
+
border: '1px solid #3e4451',
|
|
311
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.3)'
|
|
312
|
+
},
|
|
313
|
+
'.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]': {
|
|
314
|
+
backgroundColor: 'rgba(192, 132, 252, 0.2)',
|
|
315
|
+
color: '#c084fc'
|
|
202
316
|
}
|
|
203
317
|
})
|
|
204
318
|
);
|
|
@@ -298,12 +412,12 @@
|
|
|
298
412
|
aria-label="Template editor"
|
|
299
413
|
></div>
|
|
300
414
|
|
|
301
|
-
<!-- Variable hints section (shown when variables are available) -->
|
|
302
|
-
{#if
|
|
415
|
+
<!-- Variable hints section (shown when variables are available and showHints is true) -->
|
|
416
|
+
{#if showHints && displayVariables.length > 0}
|
|
303
417
|
<div class="form-template-editor__hints">
|
|
304
418
|
<span class="form-template-editor__hints-label">Available variables:</span>
|
|
305
419
|
<div class="form-template-editor__hints-list">
|
|
306
|
-
{#each
|
|
420
|
+
{#each displayVariables as varName (varName)}
|
|
307
421
|
<button
|
|
308
422
|
type="button"
|
|
309
423
|
class="form-template-editor__hint-btn"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VariableSchema, TemplateVariablesConfig } from '../../types/index.js';
|
|
1
2
|
interface Props {
|
|
2
3
|
/** Field identifier */
|
|
3
4
|
id: string;
|
|
@@ -11,7 +12,22 @@ interface Props {
|
|
|
11
12
|
darkTheme?: boolean;
|
|
12
13
|
/** Editor height in pixels or CSS value */
|
|
13
14
|
height?: string;
|
|
14
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Configuration for template variable autocomplete.
|
|
17
|
+
* Controls which variables are available and how they are displayed.
|
|
18
|
+
*/
|
|
19
|
+
variables?: TemplateVariablesConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Variable schema for advanced autocomplete with nested drilling.
|
|
22
|
+
* When provided, enables dot notation (user.name) and array access (items[0]).
|
|
23
|
+
* @deprecated Use `variables.schema` instead
|
|
24
|
+
*/
|
|
25
|
+
variableSchema?: VariableSchema;
|
|
26
|
+
/**
|
|
27
|
+
* Simple variable names for basic hints (backward compatible).
|
|
28
|
+
* Used when variableSchema is not provided.
|
|
29
|
+
* @deprecated Use `variables.schema` instead
|
|
30
|
+
*/
|
|
15
31
|
variableHints?: string[];
|
|
16
32
|
/** Placeholder variable example for the hint */
|
|
17
33
|
placeholderExample?: string;
|
|
@@ -44,3 +44,4 @@ export { default as FormCodeEditor } from './FormCodeEditor.svelte';
|
|
|
44
44
|
export { default as FormMarkdownEditor } from './FormMarkdownEditor.svelte';
|
|
45
45
|
export { default as FormTemplateEditor } from './FormTemplateEditor.svelte';
|
|
46
46
|
export { default as FormAutocomplete } from './FormAutocomplete.svelte';
|
|
47
|
+
export { createTemplateAutocomplete, createSimpleTemplateAutocomplete } from './templateAutocomplete.js';
|
|
@@ -48,3 +48,5 @@ export { default as FormCodeEditor } from './FormCodeEditor.svelte';
|
|
|
48
48
|
export { default as FormMarkdownEditor } from './FormMarkdownEditor.svelte';
|
|
49
49
|
export { default as FormTemplateEditor } from './FormTemplateEditor.svelte';
|
|
50
50
|
export { default as FormAutocomplete } from './FormAutocomplete.svelte';
|
|
51
|
+
// Template autocomplete utilities
|
|
52
|
+
export { createTemplateAutocomplete, createSimpleTemplateAutocomplete } from './templateAutocomplete.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Variable Autocomplete Extension for CodeMirror
|
|
3
|
+
* Provides autocomplete suggestions for {{ variable }} syntax in template editors.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Triggers on `{{` to show top-level variables
|
|
7
|
+
* - Triggers on `.` to show child properties for objects
|
|
8
|
+
* - Triggers on `[` to show array index options
|
|
9
|
+
* - Supports deep nesting (e.g., `user.address.city`)
|
|
10
|
+
*
|
|
11
|
+
* @module components/form/templateAutocomplete
|
|
12
|
+
*/
|
|
13
|
+
import type { Extension } from "@codemirror/state";
|
|
14
|
+
import type { VariableSchema } from "../../types/index.js";
|
|
15
|
+
/**
|
|
16
|
+
* Creates a CodeMirror extension for template variable autocomplete.
|
|
17
|
+
*
|
|
18
|
+
* @param schema - The variable schema containing available variables
|
|
19
|
+
* @returns A CodeMirror extension that provides autocomplete
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const extensions = [
|
|
24
|
+
* // ... other extensions
|
|
25
|
+
* createTemplateAutocomplete(variableSchema)
|
|
26
|
+
* ];
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createTemplateAutocomplete(schema: VariableSchema): Extension;
|
|
30
|
+
/**
|
|
31
|
+
* Creates a simple autocomplete extension that triggers when user types {{
|
|
32
|
+
* and shows top-level variables only (no drilling).
|
|
33
|
+
* Used as a fallback when full variable schema is not available.
|
|
34
|
+
*
|
|
35
|
+
* @param variableHints - Simple array of variable names
|
|
36
|
+
* @returns A CodeMirror extension that provides basic autocomplete
|
|
37
|
+
*/
|
|
38
|
+
export declare function createSimpleTemplateAutocomplete(variableHints: string[]): Extension;
|