@esheet/builder 0.0.4-0 → 0.0.4-1
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/index.d.ts +16 -417
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +115 -115
- package/dist/lib/EsheetBuilder.d.ts +46 -0
- package/dist/lib/EsheetBuilder.d.ts.map +1 -0
- package/dist/lib/EsheetBuilder.js +111 -0
- package/dist/lib/builder-tools.d.ts +107 -0
- package/dist/lib/builder-tools.d.ts.map +1 -0
- package/dist/lib/builder-tools.js +321 -0
- package/dist/lib/components/BuilderHeader.d.ts +7 -0
- package/dist/lib/components/BuilderHeader.d.ts.map +1 -0
- package/dist/lib/components/BuilderHeader.js +390 -0
- package/dist/lib/components/Canvas.d.ts +12 -0
- package/dist/lib/components/Canvas.d.ts.map +1 -0
- package/dist/lib/components/Canvas.js +288 -0
- package/dist/lib/components/CodeView.d.ts +12 -0
- package/dist/lib/components/CodeView.d.ts.map +1 -0
- package/dist/lib/components/CodeView.js +197 -0
- package/dist/lib/components/FeedbackModal.d.ts +23 -0
- package/dist/lib/components/FeedbackModal.d.ts.map +1 -0
- package/dist/lib/components/FeedbackModal.js +22 -0
- package/dist/lib/components/FieldWrapper.d.ts +53 -0
- package/dist/lib/components/FieldWrapper.d.ts.map +1 -0
- package/dist/lib/components/FieldWrapper.js +152 -0
- package/dist/lib/components/MobileBottomDrawer.d.ts +9 -0
- package/dist/lib/components/MobileBottomDrawer.d.ts.map +1 -0
- package/dist/lib/components/MobileBottomDrawer.js +6 -0
- package/dist/lib/components/ToolPanel.d.ts +11 -0
- package/dist/lib/components/ToolPanel.d.ts.map +1 -0
- package/dist/lib/components/ToolPanel.js +117 -0
- package/dist/lib/components/edit-panel/CommonEditor.d.ts +16 -0
- package/dist/lib/components/edit-panel/CommonEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/CommonEditor.js +16 -0
- package/dist/lib/components/edit-panel/DraftIdEditor.d.ts +16 -0
- package/dist/lib/components/edit-panel/DraftIdEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/DraftIdEditor.js +32 -0
- package/dist/lib/components/edit-panel/EditPanel.d.ts +10 -0
- package/dist/lib/components/edit-panel/EditPanel.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/EditPanel.js +156 -0
- package/dist/lib/components/edit-panel/InputTypeEditor.d.ts +16 -0
- package/dist/lib/components/edit-panel/InputTypeEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/InputTypeEditor.js +32 -0
- package/dist/lib/components/edit-panel/LogicEditor.d.ts +15 -0
- package/dist/lib/components/edit-panel/LogicEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/LogicEditor.js +392 -0
- package/dist/lib/components/edit-panel/MatrixEditor.d.ts +12 -0
- package/dist/lib/components/edit-panel/MatrixEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/MatrixEditor.js +32 -0
- package/dist/lib/components/edit-panel/OptionListEditor.d.ts +14 -0
- package/dist/lib/components/edit-panel/OptionListEditor.d.ts.map +1 -0
- package/dist/lib/components/edit-panel/OptionListEditor.js +27 -0
- package/dist/lib/hooks/useFormApi.d.ts +55 -0
- package/dist/lib/hooks/useFormApi.d.ts.map +1 -0
- package/dist/lib/hooks/useFormApi.js +80 -0
- package/dist/lib/hooks/useUiApi.d.ts +22 -0
- package/dist/lib/hooks/useUiApi.d.ts.map +1 -0
- package/dist/lib/hooks/useUiApi.js +37 -0
- package/dist/lib/hooks/useVisibleRootIds.d.ts +11 -0
- package/dist/lib/hooks/useVisibleRootIds.d.ts.map +1 -0
- package/dist/lib/hooks/useVisibleRootIds.js +32 -0
- package/dist/lib/icons.d.ts +31 -0
- package/dist/lib/icons.d.ts.map +1 -0
- package/dist/lib/icons.js +47 -0
- package/dist/lib/mcp/index.d.ts +7 -0
- package/dist/lib/mcp/index.d.ts.map +1 -0
- package/dist/lib/mcp/index.js +4 -0
- package/dist/lib/mcp/system-prompt.d.ts +2 -0
- package/dist/lib/mcp/system-prompt.d.ts.map +1 -0
- package/dist/lib/mcp/system-prompt.js +9 -0
- package/dist/lib/mcp/tool-definitions.d.ts +10 -0
- package/dist/lib/mcp/tool-definitions.d.ts.map +1 -0
- package/dist/lib/mcp/tool-definitions.js +436 -0
- package/dist/lib/mcp/tool-executor.d.ts +5 -0
- package/dist/lib/mcp/tool-executor.d.ts.map +1 -0
- package/dist/lib/mcp/tool-executor.js +482 -0
- package/dist/lib/mcp/useBuilderToolBridge.d.ts +31 -0
- package/dist/lib/mcp/useBuilderToolBridge.d.ts.map +1 -0
- package/dist/lib/mcp/useBuilderToolBridge.js +56 -0
- package/dist/lib/register-defaults.d.ts +2 -0
- package/dist/lib/register-defaults.d.ts.map +1 -0
- package/dist/lib/register-defaults.js +37 -0
- package/package.json +4 -4
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import YAML from 'js-yaml';
|
|
4
|
+
import { formatZodValidationError, formDefinitionSchema, isExpressionValid, hasOptions, } from '@esheet/core';
|
|
5
|
+
import { importFromMcp, exportToMcp, convertSurveyJS, isSurveyJSSchema, isMcpElicitationRequest, importFromFhir, exportToFhir, isFhirQuestionnaire, } from '@esheet/adapters';
|
|
6
|
+
import { VEditorIcon, CodeIcon, PreviewIcon, UploadIcon, DownloadIcon, } from '../icons.js';
|
|
7
|
+
import { FeedbackModal } from './FeedbackModal.js';
|
|
8
|
+
import { useUiApi } from '../hooks/useUiApi.js';
|
|
9
|
+
import { useFormApi } from '../hooks/useFormApi.js';
|
|
10
|
+
const MODES = [
|
|
11
|
+
{ value: 'build', label: 'Build', Icon: VEditorIcon },
|
|
12
|
+
{ value: 'code', label: 'Code', Icon: CodeIcon },
|
|
13
|
+
{ value: 'preview', label: 'Preview', Icon: PreviewIcon },
|
|
14
|
+
];
|
|
15
|
+
function sanitizeFormId(value) {
|
|
16
|
+
return value
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
20
|
+
.replace(/^-+|-+$/g, '');
|
|
21
|
+
}
|
|
22
|
+
function flattenFields(fields, pathPrefix = 'fields') {
|
|
23
|
+
const flat = [];
|
|
24
|
+
for (let i = 0; i < fields.length; i += 1) {
|
|
25
|
+
const field = fields[i];
|
|
26
|
+
const path = `${pathPrefix}[${i}]`;
|
|
27
|
+
flat.push({ field, path });
|
|
28
|
+
if (field.fieldType === 'section' &&
|
|
29
|
+
field.fields &&
|
|
30
|
+
field.fields.length > 0) {
|
|
31
|
+
flat.push(...flattenFields(field.fields, `${path}.fields`));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return flat;
|
|
35
|
+
}
|
|
36
|
+
function extractExpressionFieldRefs(expression) {
|
|
37
|
+
const refs = new Set();
|
|
38
|
+
const regex = /\{([^{}]+)\}/g;
|
|
39
|
+
let match = regex.exec(expression);
|
|
40
|
+
while (match) {
|
|
41
|
+
const ref = match[1]?.trim();
|
|
42
|
+
if (ref)
|
|
43
|
+
refs.add(ref);
|
|
44
|
+
match = regex.exec(expression);
|
|
45
|
+
}
|
|
46
|
+
return Array.from(refs);
|
|
47
|
+
}
|
|
48
|
+
function collectImportWarnings(fields) {
|
|
49
|
+
const warnings = [];
|
|
50
|
+
const flat = flattenFields(fields);
|
|
51
|
+
const allIds = new Set(flat.map((entry) => entry.field.id));
|
|
52
|
+
const idCounts = new Map();
|
|
53
|
+
for (const entry of flat) {
|
|
54
|
+
idCounts.set(entry.field.id, (idCounts.get(entry.field.id) ?? 0) + 1);
|
|
55
|
+
}
|
|
56
|
+
for (const [id, count] of idCounts.entries()) {
|
|
57
|
+
if (count > 1) {
|
|
58
|
+
warnings.push(`Duplicate field id '${id}' appears ${count} times.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const entry of flat) {
|
|
62
|
+
const { field, path } = entry;
|
|
63
|
+
if (hasOptions(field)) {
|
|
64
|
+
if (!field.options || field.options.length === 0) {
|
|
65
|
+
warnings.push(`${field.id}: ${path} has no options for fieldType '${field.fieldType}'.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (field.fieldType === 'singlematrix' ||
|
|
69
|
+
field.fieldType === 'multimatrix') {
|
|
70
|
+
if (!field.rows || field.rows.length === 0) {
|
|
71
|
+
warnings.push(`${field.id}: ${path} has no rows.`);
|
|
72
|
+
}
|
|
73
|
+
if (!field.columns || field.columns.length === 0) {
|
|
74
|
+
warnings.push(`${field.id}: ${path} has no columns.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!field.rules || field.rules.length === 0)
|
|
78
|
+
continue;
|
|
79
|
+
for (let r = 0; r < field.rules.length; r += 1) {
|
|
80
|
+
const rule = field.rules[r];
|
|
81
|
+
for (let c = 0; c < rule.conditions.length; c += 1) {
|
|
82
|
+
const cond = rule.conditions[c];
|
|
83
|
+
const condPath = `${path}.rules[${r}].conditions[${c}]`;
|
|
84
|
+
const isExpression = cond.conditionType === 'expression' ||
|
|
85
|
+
(!!cond.expression && cond.expression.trim().length > 0);
|
|
86
|
+
if (isExpression) {
|
|
87
|
+
const expression = cond.expression?.trim() ?? '';
|
|
88
|
+
if (!expression) {
|
|
89
|
+
warnings.push(`${field.id}: ${condPath} has empty expression.`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!isExpressionValid(expression)) {
|
|
93
|
+
warnings.push(`${field.id}: invalid expression at ${condPath} -> ${expression}`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const refs = extractExpressionFieldRefs(expression);
|
|
97
|
+
for (const ref of refs) {
|
|
98
|
+
if (!allIds.has(ref)) {
|
|
99
|
+
warnings.push(`${field.id}: ${condPath} references missing field '{${ref}}'.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!cond.targetId) {
|
|
105
|
+
warnings.push(`${field.id}: ${condPath} is missing targetId.`);
|
|
106
|
+
}
|
|
107
|
+
else if (!allIds.has(cond.targetId)) {
|
|
108
|
+
warnings.push(`${field.id}: ${condPath} targetId '${cond.targetId}' does not exist.`);
|
|
109
|
+
}
|
|
110
|
+
if (!cond.operator) {
|
|
111
|
+
warnings.push(`${field.id}: ${condPath} is missing operator.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return warnings;
|
|
117
|
+
}
|
|
118
|
+
function formatDryRunDetails(result) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.stringify(result, null, 2);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return 'Unable to serialize dry-run output.';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* BuilderHeader — top bar with Build/Code/Preview mode toggle and Import/Export actions.
|
|
128
|
+
*/
|
|
129
|
+
export function BuilderHeader(_props) {
|
|
130
|
+
const fileInputRef = React.useRef(null);
|
|
131
|
+
const [exportIdModalOpen, setExportIdModalOpen] = React.useState(false);
|
|
132
|
+
const [exportIdInput, setExportIdInput] = React.useState('');
|
|
133
|
+
const [exportIdError, setExportIdError] = React.useState('');
|
|
134
|
+
const [exportFormat, setExportFormat] = React.useState('esheet');
|
|
135
|
+
const [feedback, setFeedback] = React.useState({
|
|
136
|
+
open: false,
|
|
137
|
+
title: '',
|
|
138
|
+
message: '',
|
|
139
|
+
details: undefined,
|
|
140
|
+
variant: 'info',
|
|
141
|
+
});
|
|
142
|
+
const [dryRunFeedback, setDryRunFeedback] = React.useState({
|
|
143
|
+
open: false,
|
|
144
|
+
title: '',
|
|
145
|
+
message: '',
|
|
146
|
+
details: undefined,
|
|
147
|
+
variant: 'info',
|
|
148
|
+
});
|
|
149
|
+
const showFeedback = React.useCallback((variant, title, message, details, issues, issuesTitle, issuesHint) => {
|
|
150
|
+
setFeedback({
|
|
151
|
+
open: true,
|
|
152
|
+
variant,
|
|
153
|
+
title,
|
|
154
|
+
message,
|
|
155
|
+
details,
|
|
156
|
+
issues,
|
|
157
|
+
issuesTitle,
|
|
158
|
+
issuesHint,
|
|
159
|
+
});
|
|
160
|
+
}, []);
|
|
161
|
+
const showDryRunFeedback = React.useCallback((variant, title, message, details) => {
|
|
162
|
+
setDryRunFeedback({ open: true, variant, title, message, details });
|
|
163
|
+
}, []);
|
|
164
|
+
const { mode, codeEditorHasError: codeHasError, setMode } = useUiApi();
|
|
165
|
+
const { _form: form } = useFormApi();
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (mode !== 'preview') {
|
|
168
|
+
setDryRunFeedback((prev) => prev.open
|
|
169
|
+
? {
|
|
170
|
+
...prev,
|
|
171
|
+
open: false,
|
|
172
|
+
}
|
|
173
|
+
: prev);
|
|
174
|
+
}
|
|
175
|
+
}, [mode]);
|
|
176
|
+
const finalizeExport = React.useCallback((definition) => {
|
|
177
|
+
const json = JSON.stringify(definition, null, 2);
|
|
178
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
179
|
+
const url = URL.createObjectURL(blob);
|
|
180
|
+
const a = document.createElement('a');
|
|
181
|
+
a.href = url;
|
|
182
|
+
a.download = `${definition.id}.json`;
|
|
183
|
+
a.click();
|
|
184
|
+
URL.revokeObjectURL(url);
|
|
185
|
+
}, []);
|
|
186
|
+
const handleConfirmExportId = React.useCallback(() => {
|
|
187
|
+
if (exportFormat === 'mcp') {
|
|
188
|
+
const definition = form.getState().hydrateDefinition();
|
|
189
|
+
const schema = exportToMcp(definition);
|
|
190
|
+
const filename = (definition.id || 'form') + '-mcp-schema.json';
|
|
191
|
+
const blob = new Blob([JSON.stringify(schema, null, 2)], {
|
|
192
|
+
type: 'application/json',
|
|
193
|
+
});
|
|
194
|
+
const url = URL.createObjectURL(blob);
|
|
195
|
+
const a = document.createElement('a');
|
|
196
|
+
a.href = url;
|
|
197
|
+
a.download = filename;
|
|
198
|
+
a.click();
|
|
199
|
+
URL.revokeObjectURL(url);
|
|
200
|
+
setExportIdModalOpen(false);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (exportFormat === 'fhir') {
|
|
204
|
+
const definition = form.getState().hydrateDefinition();
|
|
205
|
+
const fhirQuestionnaire = exportToFhir(definition);
|
|
206
|
+
const filename = (definition.id || 'form') + '-fhir-questionnaire.json';
|
|
207
|
+
const blob = new Blob([JSON.stringify(fhirQuestionnaire, null, 2)], {
|
|
208
|
+
type: 'application/json',
|
|
209
|
+
});
|
|
210
|
+
const url = URL.createObjectURL(blob);
|
|
211
|
+
const a = document.createElement('a');
|
|
212
|
+
a.href = url;
|
|
213
|
+
a.download = filename;
|
|
214
|
+
a.click();
|
|
215
|
+
URL.revokeObjectURL(url);
|
|
216
|
+
setExportIdModalOpen(false);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const nextId = sanitizeFormId(exportIdInput);
|
|
220
|
+
if (!nextId) {
|
|
221
|
+
setExportIdError('Enter a valid id (letters, numbers, dashes, underscores).');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
form.getState().setFormId(nextId);
|
|
225
|
+
const definition = form.getState().hydrateDefinition();
|
|
226
|
+
finalizeExport(definition);
|
|
227
|
+
setExportIdModalOpen(false);
|
|
228
|
+
setExportIdError('');
|
|
229
|
+
}, [exportFormat, exportIdInput, finalizeExport, form]);
|
|
230
|
+
const handleExport = () => {
|
|
231
|
+
const definition = form.getState().hydrateDefinition();
|
|
232
|
+
const currentId = definition.id.trim();
|
|
233
|
+
const suggested = currentId || sanitizeFormId(definition.title ?? 'form') || 'form';
|
|
234
|
+
setExportIdInput(suggested);
|
|
235
|
+
setExportIdError('');
|
|
236
|
+
setExportIdModalOpen(true);
|
|
237
|
+
};
|
|
238
|
+
const handleImport = (e) => {
|
|
239
|
+
const file = e.currentTarget.files?.[0];
|
|
240
|
+
if (!file)
|
|
241
|
+
return;
|
|
242
|
+
const fileName = file.name.toLowerCase();
|
|
243
|
+
const isYaml = fileName.endsWith('.yaml') || fileName.endsWith('.yml');
|
|
244
|
+
const fileFormat = isYaml ? 'YAML' : 'JSON';
|
|
245
|
+
const reader = new FileReader();
|
|
246
|
+
reader.onload = (ev) => {
|
|
247
|
+
try {
|
|
248
|
+
const content = ev.target?.result;
|
|
249
|
+
const parsed = isYaml ? YAML.load(content) : JSON.parse(content);
|
|
250
|
+
// Auto-detect MCP elicitation schema (JSON only).
|
|
251
|
+
// Accepts: full elicitation/create envelope or raw requestedSchema object.
|
|
252
|
+
if (!isYaml) {
|
|
253
|
+
if (isMcpElicitationRequest(parsed)) {
|
|
254
|
+
const mcpReq = parsed;
|
|
255
|
+
if (mcpReq.params.mode === 'url') {
|
|
256
|
+
showFeedback('error', 'URL Mode Not Supported', 'This MCP elicitation uses URL mode (out-of-band). Only form mode schemas can be imported into the builder.');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const formDef = importFromMcp(mcpReq.params.requestedSchema, {
|
|
260
|
+
formId: form.getState().hydrateDefinition().id || 'mcp-form',
|
|
261
|
+
...(typeof mcpReq.params.message === 'string' &&
|
|
262
|
+
mcpReq.params.message.length > 0
|
|
263
|
+
? { description: mcpReq.params.message }
|
|
264
|
+
: {}),
|
|
265
|
+
mcpId: mcpReq.id,
|
|
266
|
+
mcpMessage: mcpReq.params.message,
|
|
267
|
+
});
|
|
268
|
+
form.getState().loadDefinition(formDef);
|
|
269
|
+
showFeedback('success', 'Import Successful', `Loaded ${formDef.fields.length} field(s) from MCP elicitation schema.`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Raw requestedSchema: { type: 'object', properties: {...} }
|
|
273
|
+
const p = parsed;
|
|
274
|
+
if (p?.type === 'object' && p?.properties) {
|
|
275
|
+
const formDef = importFromMcp(p, {
|
|
276
|
+
formId: form.getState().hydrateDefinition().id || 'mcp-form',
|
|
277
|
+
});
|
|
278
|
+
form.getState().loadDefinition(formDef);
|
|
279
|
+
showFeedback('success', 'Import Successful', `Loaded ${formDef.fields.length} field(s) from MCP elicitation schema.`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!isYaml && isFhirQuestionnaire(parsed)) {
|
|
284
|
+
const formDef = importFromFhir(parsed);
|
|
285
|
+
form.getState().loadDefinition(formDef);
|
|
286
|
+
const meta = formDef._sourceData;
|
|
287
|
+
const allWarnings = meta?._conversionWarnings ?? [];
|
|
288
|
+
// Split by severity: info vs warning/error
|
|
289
|
+
const infoItems = allWarnings.filter((w) => w.severity === 'info');
|
|
290
|
+
const warnItems = allWarnings.filter((w) => w.severity !== 'info');
|
|
291
|
+
const hasActualWarnings = warnItems.length > 0;
|
|
292
|
+
if (allWarnings.length > 0) {
|
|
293
|
+
const items = hasActualWarnings ? warnItems : infoItems;
|
|
294
|
+
const prefix = hasActualWarnings ? '⚠️' : 'ℹ️';
|
|
295
|
+
showFeedback(hasActualWarnings ? 'warning' : 'success', hasActualWarnings
|
|
296
|
+
? 'Import Successful (with warnings)'
|
|
297
|
+
: 'Import Successful', `Loaded ${formDef.fields.length} field(s) from FHIR Questionnaire.`, undefined, items.map((w) => `${prefix} [${w.code}] ${w.itemLinkId ?? 'form'}: ${w.message}`), hasActualWarnings ? 'Import Warnings' : 'Import Notes');
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
showFeedback('success', 'Import Successful', `Loaded ${formDef.fields.length} field(s) from FHIR Questionnaire.`);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!isYaml && isSurveyJSSchema(parsed)) {
|
|
305
|
+
const formDef = convertSurveyJS(parsed);
|
|
306
|
+
form.getState().loadDefinition(formDef);
|
|
307
|
+
showFeedback('success', 'Import Successful', `Loaded ${formDef.fields.length} field(s) from SurveyJS schema.`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const validated = formDefinitionSchema.safeParse(parsed);
|
|
311
|
+
if (!validated.success) {
|
|
312
|
+
const issues = validated.error.issues.map(formatZodValidationError);
|
|
313
|
+
const shownIssues = issues.slice(0, 8);
|
|
314
|
+
showFeedback('error', 'Import Failed', `The file is valid ${fileFormat} but does not match the form schema.`, issues.length > shownIssues.length
|
|
315
|
+
? `Showing ${shownIssues.length} of ${issues.length} issue(s).`
|
|
316
|
+
: undefined, shownIssues, 'Unsupported Form Definition', 'Fix these issues, then try importing again.');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const importWarnings = collectImportWarnings(validated.data.fields);
|
|
320
|
+
if (importWarnings.length > 0) {
|
|
321
|
+
const shownWarnings = importWarnings.slice(0, 10);
|
|
322
|
+
showFeedback('error', 'Import Blocked', `This definition contains ${importWarnings.length} unsupported issue(s) and was not imported.`, importWarnings.length > shownWarnings.length
|
|
323
|
+
? `Showing ${shownWarnings.length} of ${importWarnings.length} issue(s).`
|
|
324
|
+
: undefined, shownWarnings, 'Unsupported Configuration', 'Resolve these issues before importing this file.');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
form.getState().loadDefinition(validated.data);
|
|
328
|
+
showFeedback('success', 'Import Successful', `Loaded ${validated.data.fields.length} field(s).`);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
showFeedback('error', 'Import Failed', `Invalid ${fileFormat} file format.`);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
reader.readAsText(file);
|
|
335
|
+
// Reset so the same file can be re-imported
|
|
336
|
+
e.currentTarget.value = '';
|
|
337
|
+
};
|
|
338
|
+
const handleDryRunSubmit = () => {
|
|
339
|
+
const state = form.getState();
|
|
340
|
+
const errors = state.getErrors();
|
|
341
|
+
if (errors.length > 0) {
|
|
342
|
+
const result = {
|
|
343
|
+
wouldSubmit: false,
|
|
344
|
+
errorCount: errors.length,
|
|
345
|
+
errors,
|
|
346
|
+
response: null,
|
|
347
|
+
};
|
|
348
|
+
showDryRunFeedback('warning', 'Dry Run Submit Failed', `Submit would fail validation with ${errors.length} error(s).`, formatDryRunDetails(result));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const response = state.hydrateResponse({ status: 'draft' });
|
|
352
|
+
const result = {
|
|
353
|
+
wouldSubmit: true,
|
|
354
|
+
errorCount: 0,
|
|
355
|
+
errors: [],
|
|
356
|
+
response,
|
|
357
|
+
};
|
|
358
|
+
showDryRunFeedback('success', 'Dry Run Submit Passed', 'Submit would pass validation.', formatDryRunDetails(result));
|
|
359
|
+
};
|
|
360
|
+
return (_jsxs("header", { className: "builder-header ms:w-full ms:bg-mssurface ms:border ms:border-msborder ms:rounded-lg ms:shadow-sm ms:shrink-0", children: [_jsx(FeedbackModal, { open: exportIdModalOpen, title: "Export Form", message: exportFormat === 'mcp' || exportFormat === 'fhir'
|
|
361
|
+
? 'Choose a format to export your form.'
|
|
362
|
+
: `Do you want to use '${sanitizeFormId(exportIdInput) || exportIdInput}' as the form id? You can edit it below before exporting.`, content: _jsxs("div", { className: "ms:space-y-4 ms:mb-2", children: [_jsxs("div", { className: "ms:flex ms:gap-4", children: [_jsxs("label", { className: "ms:flex ms:items-center ms:gap-2 ms:text-sm ms:text-mstext ms:cursor-pointer", children: [_jsx("input", { type: "radio", name: "export-format", value: "esheet", checked: exportFormat === 'esheet', onChange: () => setExportFormat('esheet') }), "eSheet JSON"] }), _jsxs("label", { className: "ms:flex ms:items-center ms:gap-2 ms:text-sm ms:text-mstext ms:cursor-pointer", children: [_jsx("input", { type: "radio", name: "export-format", value: "mcp", checked: exportFormat === 'mcp', onChange: () => setExportFormat('mcp') }), "MCP Elicitation Schema"] }), _jsxs("label", { className: "ms:flex ms:items-center ms:gap-2 ms:text-sm ms:text-mstext ms:cursor-pointer", children: [_jsx("input", { type: "radio", name: "export-format", value: "fhir", checked: exportFormat === 'fhir', onChange: () => setExportFormat('fhir') }), "FHIR R4 Questionnaire"] })] }), exportFormat === 'esheet' && (_jsxs("div", { className: "ms:space-y-2", children: [_jsx("label", { htmlFor: `${form.getState().instanceId}-export-form-id`, className: "ms:block ms:text-sm ms:font-medium ms:text-mstext", children: "Form ID" }), _jsx("input", { id: `${form.getState().instanceId}-export-form-id`, "aria-label": "Form ID", type: "text", value: exportIdInput, onChange: (e) => {
|
|
363
|
+
setExportIdInput(e.target.value);
|
|
364
|
+
if (exportIdError)
|
|
365
|
+
setExportIdError('');
|
|
366
|
+
}, placeholder: "my-form-id", className: "ms:px-3 ms:py-2 ms:h-10 ms:w-full ms:border ms:border-msborder ms:bg-mssurface ms:text-mstext ms:rounded-lg ms:focus:border-msprimary ms:focus:ring-1 ms:focus:ring-msprimary/30 ms:outline-none ms:transition-colors" }), exportIdError && (_jsx("p", { className: "ms:text-xs ms:text-msdanger", children: exportIdError }))] })), exportFormat === 'mcp' && (_jsxs("p", { className: "ms:text-sm ms:text-mstextmuted", children: ["Exports as a flat", ' ', _jsx("code", { className: "ms:font-mono ms:text-xs ms:bg-msbackground ms:px-1 ms:py-0.5 ms:rounded", children: "requestedSchema" }), ' ', "object. Field IDs, required fields, and supported types are preserved. Unsupported types (matrix, signature, etc.) are omitted."] })), exportFormat === 'fhir' && (_jsxs("p", { className: "ms:text-sm ms:text-mstextmuted", children: ["Exports as a FHIR R4", ' ', _jsx("code", { className: "ms:font-mono ms:text-xs ms:bg-msbackground ms:px-1 ms:py-0.5 ms:rounded", children: "Questionnaire" }), ' ', "resource. Supports standard item types, enableWhen logic, and common extensions. DTR profiles are applied when applicable."] }))] }), variant: "info", confirmLabel: exportFormat === 'mcp'
|
|
367
|
+
? 'Export MCP Schema'
|
|
368
|
+
: exportFormat === 'fhir'
|
|
369
|
+
? 'Export FHIR Questionnaire'
|
|
370
|
+
: 'Use This ID & Export', cancelLabel: "Cancel", showCancel: true, onConfirm: handleConfirmExportId, onClose: () => {
|
|
371
|
+
setExportIdModalOpen(false);
|
|
372
|
+
setExportIdError('');
|
|
373
|
+
} }), _jsx(FeedbackModal, { open: feedback.open, title: feedback.title, message: feedback.message, details: feedback.details, issues: feedback.issues, issuesTitle: feedback.issuesTitle, issuesHint: feedback.issuesHint, variant: feedback.variant, onClose: () => setFeedback((prev) => ({
|
|
374
|
+
...prev,
|
|
375
|
+
open: false,
|
|
376
|
+
})) }), _jsx("div", { className: "ms:hidden ms:lg:block", children: _jsx(FeedbackModal, { open: dryRunFeedback.open, title: dryRunFeedback.title, message: dryRunFeedback.message, details: dryRunFeedback.details, variant: dryRunFeedback.variant, onClose: () => setDryRunFeedback((prev) => ({
|
|
377
|
+
...prev,
|
|
378
|
+
open: false,
|
|
379
|
+
})) }) }), dryRunFeedback.open && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "ms:lg:hidden ms:fixed ms:inset-0 ms:z-40 ms:bg-msoverlay ms:border-0", onClick: () => setDryRunFeedback((prev) => ({
|
|
380
|
+
...prev,
|
|
381
|
+
open: false,
|
|
382
|
+
})), "aria-label": "Close Dry Run result drawer" }), _jsxs("div", { className: "ms:lg:hidden ms:fixed ms:left-0 ms:right-0 ms:bottom-0 ms:z-50 ms:h-[50dvh] ms:bg-mssurface ms:border-t ms:border-msborder ms:rounded-t-2xl ms:shadow-2xl ms:overflow-hidden", children: [_jsxs("div", { className: "ms:flex ms:items-center ms:justify-between ms:px-4 ms:py-2 ms:border-b ms:border-msborder", children: [_jsx("span", { className: "ms:text-sm ms:font-medium ms:text-mstext", children: "Dry Run Result" }), _jsx("button", { type: "button", onClick: () => setDryRunFeedback((prev) => ({
|
|
383
|
+
...prev,
|
|
384
|
+
open: false,
|
|
385
|
+
})), className: "ms:px-2 ms:py-1 ms:bg-transparent ms:text-mstextmuted ms:border-0 ms:outline-none ms:focus:outline-none", "aria-label": "Close Dry Run result drawer", children: "Close" })] }), _jsxs("div", { className: "ms:h-[calc(50dvh-45px)] ms:overflow-y-auto ms:p-4 ms:space-y-3", children: [_jsx("h3", { className: "ms:text-sm ms:font-semibold ms:text-mstext", children: dryRunFeedback.title }), _jsx("p", { className: "ms:text-sm ms:text-mstextmuted", children: dryRunFeedback.message }), dryRunFeedback.details && (_jsx("pre", { className: "ms:rounded-lg ms:border ms:border-msborder ms:bg-msbackground ms:p-3 ms:text-xs ms:overflow-auto ms:whitespace-pre-wrap ms:break-words ms:text-mstext", children: dryRunFeedback.details }))] })] })] })), _jsx("div", { className: "ms:px-4 ms:py-4", children: _jsxs("div", { className: "ms:flex ms:flex-wrap ms:items-center ms:justify-between ms:gap-3", children: [_jsx("div", { className: "mode-toggle ms:flex ms:gap-1 ms:rounded-lg ms:border ms:border-msborder ms:bg-msbackground ms:p-1 ms:w-fit", children: MODES.map(({ value, label, Icon }) => (_jsxs("button", { type: "button", onClick: () => setMode(value), disabled: codeHasError && value !== 'code', className: `mode-btn ms:flex ms:items-center ms:justify-center ms:gap-2 ms:px-2 ms:lg:px-4 ms:py-2 ms:rounded-lg ms:text-xs ms:lg:text-sm ms:font-medium ms:transition-colors ms:border-0 ms:outline-none ms:focus:outline-none ${codeHasError && value !== 'code'
|
|
386
|
+
? 'ms:bg-transparent ms:text-mstextmuted/50 ms:cursor-not-allowed'
|
|
387
|
+
: 'ms:cursor-pointer'} ${mode === value
|
|
388
|
+
? 'ms:bg-msprimary ms:text-mstextsecondary ms:shadow-sm'
|
|
389
|
+
: 'ms:bg-transparent ms:text-mstextmuted ms:hover:text-mstext ms:hover:bg-mssurface'}`, children: [_jsx(Icon, { className: "ms:w-5 ms:h-5" }), _jsx("span", { children: label })] }, value))) }), _jsxs("div", { className: "header-actions ms:flex ms:gap-1 ms:items-center", children: [_jsxs("label", { className: "header-import-label ms:group ms:px-2 ms:py-2 ms:lg:px-3 ms:lg:py-2 ms:rounded-lg ms:border ms:border-msborder ms:bg-mssurface ms:hover:bg-msprimary ms:hover:text-mstextsecondary ms:hover:border-msprimary ms:cursor-pointer ms:text-xs ms:lg:text-sm ms:font-medium ms:transition-colors ms:flex ms:items-center ms:lg:gap-2 ms:gap-0 ms:text-mstext", children: [_jsx(UploadIcon, { className: "ms:w-4 ms:h-4 ms:text-mstext ms:group-hover:text-mstextsecondary ms:transition-colors" }), _jsx("span", { className: "ms:hidden ms:sm:inline", children: "Import" }), _jsx("input", { ref: fileInputRef, type: "file", accept: ".json,.yaml,.yml,application/json,application/x-yaml,text/yaml", onChange: handleImport, "aria-label": "Import form (JSON or YAML)", className: "ms:hidden" })] }), _jsxs("button", { type: "button", onClick: handleExport, className: "export-btn ms:group ms:px-2 ms:py-2 ms:lg:px-3 ms:lg:py-2 ms:rounded-lg ms:border ms:border-msborder ms:bg-mssurface ms:hover:bg-msprimary ms:hover:text-mstextsecondary ms:hover:border-msprimary ms:text-xs ms:lg:text-sm ms:font-medium ms:transition-colors ms:flex ms:items-center ms:lg:gap-2 ms:gap-0 ms:outline-none ms:focus:outline-none ms:text-mstext ms:cursor-pointer", title: "Export", children: [_jsx(DownloadIcon, { className: "ms:w-4 ms:h-4 ms:text-mstext ms:group-hover:text-mstextsecondary ms:transition-colors" }), _jsx("span", { className: "ms:hidden ms:sm:inline", children: "Export" })] }), mode === 'preview' && (_jsxs("button", { type: "button", onClick: handleDryRunSubmit, "aria-label": "Dry run submit", title: "Dry Run Submit", className: "dry-run-submit-btn ms:group ms:px-2 ms:py-2 ms:lg:px-3 ms:lg:py-2 ms:rounded-lg ms:border ms:border-msborder ms:bg-mssurface ms:hover:bg-msprimary ms:hover:text-mstextsecondary ms:hover:border-msprimary ms:text-xs ms:lg:text-sm ms:font-medium ms:transition-colors ms:flex ms:items-center ms:lg:gap-2 ms:gap-0 ms:outline-none ms:focus:outline-none ms:text-mstext ms:cursor-pointer", children: [_jsx("span", { className: "ms:hidden ms:sm:inline", children: "Dry Run Submit" }), _jsx("span", { className: "ms:sm:hidden", children: "Dry Run" })] }))] })] }) })] }));
|
|
390
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FormStore, UIStore } from '@esheet/core';
|
|
3
|
+
export interface CanvasProps {
|
|
4
|
+
/** The form store */
|
|
5
|
+
form: FormStore;
|
|
6
|
+
/** The UI store */
|
|
7
|
+
ui: UIStore;
|
|
8
|
+
/** Whether drag-and-drop reordering is enabled (default: true) */
|
|
9
|
+
dragEnabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const Canvas: React.NamedExoticComponent<CanvasProps>;
|
|
12
|
+
//# sourceMappingURL=Canvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Canvas.d.ts","sourceRoot":"","sources":["../../../src/lib/components/Canvas.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAuB,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAS5E,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,mBAAmB;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAqFD,eAAO,MAAM,MAAM,yCAkZjB,CAAC"}
|