@squaredr/fieldcraft-pro 1.2.0 → 1.4.0
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/chunk-22T5PY6L.mjs +1081 -0
- package/dist/{chunk-4BX7FCXH.mjs → chunk-BK4SMEW4.mjs} +840 -21
- package/dist/{chunk-GNBXIG63.mjs → chunk-BYT2P3I6.mjs} +1264 -88
- package/dist/form-builder/index.d.mts +7 -367
- package/dist/form-builder/index.d.ts +7 -367
- package/dist/form-builder/index.js +1266 -90
- package/dist/form-builder/index.mjs +1 -1
- package/dist/index-nvIvXlmc.d.mts +398 -0
- package/dist/index-nvIvXlmc.d.ts +398 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2926 -335
- package/dist/index.mjs +4 -4
- package/dist/preview-schema-DFvV4y6J.d.mts +66 -0
- package/dist/preview-schema-DFvV4y6J.d.ts +66 -0
- package/dist/response-viewer/index.d.mts +9 -1
- package/dist/response-viewer/index.d.ts +9 -1
- package/dist/response-viewer/index.js +838 -19
- package/dist/response-viewer/index.mjs +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme-editor/index.d.mts +30 -26
- package/dist/theme-editor/index.d.ts +30 -26
- package/dist/theme-editor/index.js +702 -22
- package/dist/theme-editor/index.mjs +1 -1
- package/package.json +8 -6
- package/theme-editor/styles.css +266 -62
- package/dist/chunk-7LQW5QYT.mjs +0 -407
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { cn } from './chunk-QQ4JZGTD.mjs';
|
|
2
2
|
import { requireLicense } from './chunk-VECQKSWS.mjs';
|
|
3
|
-
import { forwardRef, createContext, Component,
|
|
3
|
+
import { forwardRef, lazy, memo, createContext, Component, useState, useRef, useEffect, useCallback, useMemo, Suspense, useContext } from 'react';
|
|
4
4
|
import { DndContext, DragOverlay, useSensors, useSensor, MouseSensor, TouchSensor, useDraggable, useDroppable } from '@dnd-kit/core';
|
|
5
|
-
import { ChevronDown, Undo2, Redo2, Upload, Download, Save, X, PanelLeft, PanelRight, Search, ChevronRight, Plus, Settings, Copy, Trash2, HelpCircle, MoveVertical, Minus, Video, Image,
|
|
6
|
-
import { Button, Separator, Input, Badge, Textarea, Label, Switch } from '@squaredr/fieldcraft-react';
|
|
5
|
+
import { ChevronDown, Undo2, Redo2, PencilRuler, Eye, Code, LayoutTemplate, GitBranch, Upload, Download, Save, X, PanelLeft, PanelRight, Search, ChevronRight, Plus, Settings, FileText, Layers, Copy, Trash2, HelpCircle, MoveVertical, Minus, Video, Image, PartyPopper, Hand, ShieldCheck, SeparatorHorizontal, Info, Heading, Trophy, EyeOff, Calculator, CreditCard, MapPin, Repeat, Grid3X3, Camera, PenTool, Paperclip, CalendarCheck, CalendarRange, Clock, Calendar, ArrowUpDown, Globe, ToggleLeft, CheckSquare, CircleDot, ListOrdered, TrendingUp, BarChart3, Star, SlidersHorizontal, Hash, UserCheck, Link, PhoneCall, Phone, Mail, AlignLeft, Type, GripVertical } from 'lucide-react';
|
|
6
|
+
import { Button, Separator, FormEngineRenderer, Input, Badge, Textarea, Label, Switch } from '@squaredr/fieldcraft-react';
|
|
7
7
|
import { validateSchema, FormEngineSchemaError } from '@squaredr/fieldcraft-core';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { MarkerType, Handle, Position, getBezierPath, BaseEdge, EdgeLabelRenderer, useNodesState, useEdgesState, ReactFlow, Background, Controls, MiniMap } from '@xyflow/react';
|
|
10
|
+
import '@xyflow/react/dist/style.css';
|
|
9
11
|
|
|
10
12
|
var MAX_HISTORY = 50;
|
|
11
13
|
function useUndoRedo(currentSchema, setSchema) {
|
|
@@ -570,7 +572,81 @@ var QUESTION_TYPE_INFO = {
|
|
|
570
572
|
description: "Upload files",
|
|
571
573
|
defaultConfig: { type: "file_upload", maxFiles: 1, maxSizeMb: 10 }
|
|
572
574
|
},
|
|
575
|
+
// ── Compound ──
|
|
576
|
+
legal_name: {
|
|
577
|
+
type: "legal_name",
|
|
578
|
+
label: "Legal Name",
|
|
579
|
+
category: "text",
|
|
580
|
+
icon: "UserCheck",
|
|
581
|
+
description: "First, middle, and last name",
|
|
582
|
+
defaultConfig: { type: "legal_name" }
|
|
583
|
+
},
|
|
584
|
+
address: {
|
|
585
|
+
type: "address",
|
|
586
|
+
label: "Address",
|
|
587
|
+
category: "text",
|
|
588
|
+
icon: "MapPin",
|
|
589
|
+
description: "Street, city, state, ZIP",
|
|
590
|
+
defaultConfig: { type: "address", includeCountry: false }
|
|
591
|
+
},
|
|
592
|
+
signature: {
|
|
593
|
+
type: "signature",
|
|
594
|
+
label: "Signature",
|
|
595
|
+
category: "media",
|
|
596
|
+
icon: "PenTool",
|
|
597
|
+
description: "Canvas signature pad",
|
|
598
|
+
defaultConfig: { type: "signature" }
|
|
599
|
+
},
|
|
600
|
+
date_range: {
|
|
601
|
+
type: "date_range",
|
|
602
|
+
label: "Date Range",
|
|
603
|
+
category: "datetime",
|
|
604
|
+
icon: "CalendarRange",
|
|
605
|
+
description: "Start and end date picker",
|
|
606
|
+
defaultConfig: { type: "date_range" }
|
|
607
|
+
},
|
|
573
608
|
// ── Advanced ──
|
|
609
|
+
repeater: {
|
|
610
|
+
type: "repeater",
|
|
611
|
+
label: "Repeater",
|
|
612
|
+
category: "advanced",
|
|
613
|
+
icon: "ListPlus",
|
|
614
|
+
description: "Repeatable group of fields",
|
|
615
|
+
defaultConfig: { type: "repeater", fields: [], minItems: 1, maxItems: 10 }
|
|
616
|
+
},
|
|
617
|
+
likert: {
|
|
618
|
+
type: "likert",
|
|
619
|
+
label: "Likert Scale",
|
|
620
|
+
category: "advanced",
|
|
621
|
+
icon: "AlignHorizontalSpaceAround",
|
|
622
|
+
description: "Agreement scale (e.g. Strongly Disagree to Strongly Agree)",
|
|
623
|
+
requiresOptions: true,
|
|
624
|
+
defaultConfig: {
|
|
625
|
+
type: "likert",
|
|
626
|
+
scale: [
|
|
627
|
+
{ label: "Strongly Disagree", value: "1" },
|
|
628
|
+
{ label: "Disagree", value: "2" },
|
|
629
|
+
{ label: "Neutral", value: "3" },
|
|
630
|
+
{ label: "Agree", value: "4" },
|
|
631
|
+
{ label: "Strongly Agree", value: "5" }
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
scoring: {
|
|
636
|
+
type: "scoring",
|
|
637
|
+
label: "Scoring",
|
|
638
|
+
category: "advanced",
|
|
639
|
+
icon: "Award",
|
|
640
|
+
description: "Scored question with point values per option",
|
|
641
|
+
requiresOptions: true,
|
|
642
|
+
defaultConfig: {
|
|
643
|
+
type: "scoring",
|
|
644
|
+
options: [
|
|
645
|
+
{ label: "Option 1", value: "1", score: 1 },
|
|
646
|
+
{ label: "Option 2", value: "2", score: 2 }
|
|
647
|
+
]
|
|
648
|
+
}
|
|
649
|
+
},
|
|
574
650
|
matrix: {
|
|
575
651
|
type: "matrix",
|
|
576
652
|
label: "Matrix",
|
|
@@ -704,7 +780,7 @@ var DEFAULT_PALETTE = [
|
|
|
704
780
|
{
|
|
705
781
|
category: "text",
|
|
706
782
|
label: "Text Input",
|
|
707
|
-
types: ["short_text", "long_text", "email", "phone", "url"]
|
|
783
|
+
types: ["short_text", "long_text", "email", "phone", "url", "legal_name", "address"]
|
|
708
784
|
},
|
|
709
785
|
{
|
|
710
786
|
category: "numeric",
|
|
@@ -719,12 +795,12 @@ var DEFAULT_PALETTE = [
|
|
|
719
795
|
{
|
|
720
796
|
category: "datetime",
|
|
721
797
|
label: "Date & Time",
|
|
722
|
-
types: ["date", "time"]
|
|
798
|
+
types: ["date", "time", "date_range"]
|
|
723
799
|
},
|
|
724
800
|
{
|
|
725
801
|
category: "media",
|
|
726
802
|
label: "Media",
|
|
727
|
-
types: ["file_upload"]
|
|
803
|
+
types: ["file_upload", "signature"]
|
|
728
804
|
},
|
|
729
805
|
{
|
|
730
806
|
category: "content",
|
|
@@ -739,7 +815,7 @@ var DEFAULT_PALETTE = [
|
|
|
739
815
|
{
|
|
740
816
|
category: "advanced",
|
|
741
817
|
label: "Advanced",
|
|
742
|
-
types: ["matrix", "calculated", "hidden"]
|
|
818
|
+
types: ["repeater", "likert", "scoring", "matrix", "calculated", "hidden"]
|
|
743
819
|
}
|
|
744
820
|
];
|
|
745
821
|
|
|
@@ -2253,7 +2329,8 @@ function getFieldOptions(schema, excludeId) {
|
|
|
2253
2329
|
return fields;
|
|
2254
2330
|
}
|
|
2255
2331
|
function ConditionEditor({ question, schema, onUpdate }) {
|
|
2256
|
-
const
|
|
2332
|
+
const rawShowIf = question.showIf;
|
|
2333
|
+
const showIf = rawShowIf && rawShowIf.field && !rawShowIf.conditions ? { combine: "AND", conditions: [rawShowIf] } : rawShowIf;
|
|
2257
2334
|
const fieldOptions = getFieldOptions(schema, question.id);
|
|
2258
2335
|
const updateShowIf = (next) => {
|
|
2259
2336
|
onUpdate({ showIf: next });
|
|
@@ -2375,6 +2452,24 @@ function ConditionEditor({ question, schema, onUpdate }) {
|
|
|
2375
2452
|
}
|
|
2376
2453
|
function FormSettingsPanel({ schema, onUpdate }) {
|
|
2377
2454
|
const settings = schema.settings ?? {};
|
|
2455
|
+
const hasConditions = schema.sections.some(
|
|
2456
|
+
(s) => s.showIf || s.questions.some((q) => q.showIf)
|
|
2457
|
+
);
|
|
2458
|
+
const displayModeOptions = hasConditions ? [
|
|
2459
|
+
{ label: "Stepped", value: "stepped" },
|
|
2460
|
+
{ label: "Conversational", value: "conversational" }
|
|
2461
|
+
] : [
|
|
2462
|
+
{ label: "Stepped", value: "stepped" },
|
|
2463
|
+
{ label: "Classic", value: "classic" },
|
|
2464
|
+
{ label: "Conversational", value: "conversational" }
|
|
2465
|
+
];
|
|
2466
|
+
const displayMode = settings.displayMode ?? "stepped";
|
|
2467
|
+
const effectiveDisplayMode = hasConditions && displayMode === "classic" ? "stepped" : displayMode;
|
|
2468
|
+
useEffect(() => {
|
|
2469
|
+
if (effectiveDisplayMode !== displayMode) {
|
|
2470
|
+
onUpdate({ ...schema, settings: { ...settings, displayMode: effectiveDisplayMode } });
|
|
2471
|
+
}
|
|
2472
|
+
}, [effectiveDisplayMode, displayMode]);
|
|
2378
2473
|
const updateSettings = (updates) => {
|
|
2379
2474
|
onUpdate({ ...schema, settings: { ...settings, ...updates } });
|
|
2380
2475
|
};
|
|
@@ -2402,12 +2497,8 @@ function FormSettingsPanel({ schema, onUpdate }) {
|
|
|
2402
2497
|
SettingsSelect,
|
|
2403
2498
|
{
|
|
2404
2499
|
label: "Mode",
|
|
2405
|
-
value:
|
|
2406
|
-
options:
|
|
2407
|
-
{ label: "Classic", value: "classic" },
|
|
2408
|
-
{ label: "Stepped", value: "stepped" },
|
|
2409
|
-
{ label: "Conversational", value: "conversational" }
|
|
2410
|
-
],
|
|
2500
|
+
value: effectiveDisplayMode,
|
|
2501
|
+
options: displayModeOptions,
|
|
2411
2502
|
onChange: (v) => updateSettings({ displayMode: v })
|
|
2412
2503
|
}
|
|
2413
2504
|
),
|
|
@@ -2785,6 +2876,873 @@ function FieldGroup({ label, children }) {
|
|
|
2785
2876
|
children
|
|
2786
2877
|
] });
|
|
2787
2878
|
}
|
|
2879
|
+
var PreviewErrorBoundary = class extends Component {
|
|
2880
|
+
state = { error: null };
|
|
2881
|
+
static getDerivedStateFromError(error) {
|
|
2882
|
+
return { error };
|
|
2883
|
+
}
|
|
2884
|
+
componentDidCatch(error, info) {
|
|
2885
|
+
console.error("[FormBuilder Preview]", error, info);
|
|
2886
|
+
}
|
|
2887
|
+
componentDidUpdate(prevProps) {
|
|
2888
|
+
if (prevProps.resetKey !== this.props.resetKey && this.state.error) {
|
|
2889
|
+
this.setState({ error: null });
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
render() {
|
|
2893
|
+
if (this.state.error) {
|
|
2894
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-4 text-destructive text-sm font-mono", children: [
|
|
2895
|
+
/* @__PURE__ */ jsx("strong", { children: "Render Error:" }),
|
|
2896
|
+
" ",
|
|
2897
|
+
this.state.error.message
|
|
2898
|
+
] });
|
|
2899
|
+
}
|
|
2900
|
+
return this.props.children;
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
function FormPreviewPanel({ schema, preview }) {
|
|
2904
|
+
const hasSections = schema.sections && schema.sections.length > 0;
|
|
2905
|
+
const hasFields = hasSections && schema.sections.some((s) => s.questions.length > 0);
|
|
2906
|
+
if (!hasFields) {
|
|
2907
|
+
return /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
|
|
2908
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "No fields to preview" }),
|
|
2909
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs", children: "Add fields in the Design view to see a live preview here." })
|
|
2910
|
+
] }) });
|
|
2911
|
+
}
|
|
2912
|
+
return /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "max-w-2xl mx-auto py-8 px-4", children: /* @__PURE__ */ jsx(PreviewErrorBoundary, { resetKey: schema.id + (schema.version ?? ""), children: /* @__PURE__ */ jsx(
|
|
2913
|
+
FormEngineRenderer,
|
|
2914
|
+
{
|
|
2915
|
+
schema,
|
|
2916
|
+
theme: preview?.theme,
|
|
2917
|
+
components: preview?.components,
|
|
2918
|
+
onReady: preview?.onReady,
|
|
2919
|
+
onFieldChange: preview?.onFieldChange,
|
|
2920
|
+
onSubmit: preview?.onSubmit ?? (() => {
|
|
2921
|
+
})
|
|
2922
|
+
}
|
|
2923
|
+
) }) }) });
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// src/form-builder/utils/validation-markers.ts
|
|
2927
|
+
var MarkerSeverity = {
|
|
2928
|
+
Error: 8};
|
|
2929
|
+
function errorsToMarkers(errors) {
|
|
2930
|
+
return errors.map((error) => ({
|
|
2931
|
+
severity: MarkerSeverity.Error,
|
|
2932
|
+
message: error.message,
|
|
2933
|
+
startLineNumber: error.line ?? 1,
|
|
2934
|
+
startColumn: error.column ?? 1,
|
|
2935
|
+
endLineNumber: error.endLine ?? error.line ?? 1,
|
|
2936
|
+
endColumn: error.endColumn ?? error.column ?? 1,
|
|
2937
|
+
source: "FieldCraft Schema Validator"
|
|
2938
|
+
}));
|
|
2939
|
+
}
|
|
2940
|
+
var Editor = lazy(() => import('@monaco-editor/react').then((m) => ({ default: m.default })));
|
|
2941
|
+
function MonacoWrapper({ value, onChange, errors }) {
|
|
2942
|
+
const editorRef = useRef(null);
|
|
2943
|
+
const monacoRef = useRef(null);
|
|
2944
|
+
const handleMount = (editor, monaco) => {
|
|
2945
|
+
editorRef.current = editor;
|
|
2946
|
+
monacoRef.current = monaco;
|
|
2947
|
+
};
|
|
2948
|
+
useEffect(() => {
|
|
2949
|
+
const editor = editorRef.current;
|
|
2950
|
+
const monaco = monacoRef.current;
|
|
2951
|
+
if (!editor || !monaco) return;
|
|
2952
|
+
const model = editor.getModel();
|
|
2953
|
+
if (!model) return;
|
|
2954
|
+
const markers = errorsToMarkers(errors);
|
|
2955
|
+
monaco.editor.setModelMarkers(model, "fieldcraft-schema", markers);
|
|
2956
|
+
}, [errors]);
|
|
2957
|
+
return /* @__PURE__ */ jsx(
|
|
2958
|
+
Editor,
|
|
2959
|
+
{
|
|
2960
|
+
height: "100%",
|
|
2961
|
+
language: "json",
|
|
2962
|
+
theme: "vs-dark",
|
|
2963
|
+
value,
|
|
2964
|
+
onChange: (val) => onChange(val ?? ""),
|
|
2965
|
+
onMount: handleMount,
|
|
2966
|
+
options: {
|
|
2967
|
+
minimap: { enabled: false },
|
|
2968
|
+
fontSize: 13,
|
|
2969
|
+
wordWrap: "on",
|
|
2970
|
+
formatOnPaste: true,
|
|
2971
|
+
automaticLayout: true,
|
|
2972
|
+
tabSize: 2,
|
|
2973
|
+
scrollBeyondLastLine: false,
|
|
2974
|
+
padding: { top: 12 }
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
function JsonEditorPanel({ value, onChange, errors, onValidate }) {
|
|
2980
|
+
const handleChange = useCallback(
|
|
2981
|
+
(newValue) => {
|
|
2982
|
+
onChange(newValue);
|
|
2983
|
+
onValidate(newValue);
|
|
2984
|
+
},
|
|
2985
|
+
[onChange, onValidate]
|
|
2986
|
+
);
|
|
2987
|
+
const handleFormat = useCallback(() => {
|
|
2988
|
+
try {
|
|
2989
|
+
const formatted = JSON.stringify(JSON.parse(value), null, 2);
|
|
2990
|
+
onChange(formatted);
|
|
2991
|
+
} catch {
|
|
2992
|
+
}
|
|
2993
|
+
}, [value, onChange]);
|
|
2994
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
|
|
2995
|
+
errors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-3 py-1.5 bg-destructive/10 border-b border-destructive/20 text-destructive text-xs font-mono overflow-x-auto", children: [
|
|
2996
|
+
errors.slice(0, 3).map((e, i) => /* @__PURE__ */ jsxs("div", { children: [
|
|
2997
|
+
e.line ? `Line ${e.line}: ` : "",
|
|
2998
|
+
e.path ? `${e.path}: ` : "",
|
|
2999
|
+
e.message
|
|
3000
|
+
] }, i)),
|
|
3001
|
+
errors.length > 3 && /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
|
|
3002
|
+
"...and ",
|
|
3003
|
+
errors.length - 3,
|
|
3004
|
+
" more"
|
|
3005
|
+
] })
|
|
3006
|
+
] }),
|
|
3007
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between px-3 py-1.5 bg-card border-b border-border text-xs", children: [
|
|
3008
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: errors.length === 0 ? "Valid schema" : `${errors.length} error${errors.length !== 1 ? "s" : ""}` }),
|
|
3009
|
+
/* @__PURE__ */ jsx(
|
|
3010
|
+
"button",
|
|
3011
|
+
{
|
|
3012
|
+
type: "button",
|
|
3013
|
+
onClick: handleFormat,
|
|
3014
|
+
className: "text-primary hover:underline",
|
|
3015
|
+
children: "Format JSON"
|
|
3016
|
+
}
|
|
3017
|
+
)
|
|
3018
|
+
] }),
|
|
3019
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
3020
|
+
Suspense,
|
|
3021
|
+
{
|
|
3022
|
+
fallback: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-muted-foreground", children: "Loading editor..." }),
|
|
3023
|
+
children: /* @__PURE__ */ jsx(MonacoWrapper, { value, onChange: handleChange, errors })
|
|
3024
|
+
}
|
|
3025
|
+
) })
|
|
3026
|
+
] });
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// src/form-builder/utils/logic-graph.ts
|
|
3030
|
+
function extractFieldRefs(expr) {
|
|
3031
|
+
const refs = [];
|
|
3032
|
+
if (expr.field) {
|
|
3033
|
+
refs.push({ field: expr.field, operator: expr.operator, value: expr.value });
|
|
3034
|
+
}
|
|
3035
|
+
if (expr.conditions) {
|
|
3036
|
+
for (const child of expr.conditions) {
|
|
3037
|
+
refs.push(...extractFieldRefs(child));
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
return refs;
|
|
3041
|
+
}
|
|
3042
|
+
function buildLogicGraph(schema) {
|
|
3043
|
+
const nodes = [];
|
|
3044
|
+
const edges = [];
|
|
3045
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
3046
|
+
for (const section of schema.sections) {
|
|
3047
|
+
nodes.push({
|
|
3048
|
+
id: section.id,
|
|
3049
|
+
label: section.title || "Untitled Section",
|
|
3050
|
+
type: "section",
|
|
3051
|
+
sectionId: section.id,
|
|
3052
|
+
sectionTitle: section.title || "Untitled Section",
|
|
3053
|
+
hasCondition: !!section.showIf
|
|
3054
|
+
});
|
|
3055
|
+
nodeIds.add(section.id);
|
|
3056
|
+
if (section.showIf) {
|
|
3057
|
+
const refs = extractFieldRefs(section.showIf);
|
|
3058
|
+
for (const ref of refs) {
|
|
3059
|
+
edges.push({ from: ref.field, to: section.id, edgeType: "showIf", operator: ref.operator, value: ref.value });
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
if (section.onExit) {
|
|
3063
|
+
for (const rule of section.onExit.rules) {
|
|
3064
|
+
if (rule.jumpTo) {
|
|
3065
|
+
edges.push({ from: section.id, to: rule.jumpTo, edgeType: "onExit", jumpTo: rule.jumpTo });
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
if (section.onExit.default) {
|
|
3069
|
+
edges.push({ from: section.id, to: section.onExit.default, edgeType: "onExit", jumpTo: section.onExit.default });
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
for (const question of section.questions) {
|
|
3073
|
+
nodes.push({
|
|
3074
|
+
id: question.id,
|
|
3075
|
+
label: question.label || question.id,
|
|
3076
|
+
type: "field",
|
|
3077
|
+
fieldType: question.type,
|
|
3078
|
+
sectionId: section.id,
|
|
3079
|
+
sectionTitle: section.title || "Untitled Section",
|
|
3080
|
+
hasCondition: !!question.showIf
|
|
3081
|
+
});
|
|
3082
|
+
nodeIds.add(question.id);
|
|
3083
|
+
if (question.showIf) {
|
|
3084
|
+
const refs = extractFieldRefs(question.showIf);
|
|
3085
|
+
for (const ref of refs) {
|
|
3086
|
+
edges.push({ from: ref.field, to: question.id, edgeType: "showIf", operator: ref.operator, value: ref.value });
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
const validEdges = edges.filter((e) => nodeIds.has(e.from) && nodeIds.has(e.to));
|
|
3092
|
+
return { nodes, edges: validEdges };
|
|
3093
|
+
}
|
|
3094
|
+
var SECTION_COLORS = [
|
|
3095
|
+
"#3b82f6",
|
|
3096
|
+
// blue
|
|
3097
|
+
"#8b5cf6",
|
|
3098
|
+
// violet
|
|
3099
|
+
"#06b6d4",
|
|
3100
|
+
// cyan
|
|
3101
|
+
"#f59e0b",
|
|
3102
|
+
// amber
|
|
3103
|
+
"#10b981",
|
|
3104
|
+
// emerald
|
|
3105
|
+
"#ef4444",
|
|
3106
|
+
// red
|
|
3107
|
+
"#ec4899",
|
|
3108
|
+
// pink
|
|
3109
|
+
"#6366f1"
|
|
3110
|
+
// indigo
|
|
3111
|
+
];
|
|
3112
|
+
var FIELD_NODE_WIDTH = 220;
|
|
3113
|
+
var FIELD_NODE_HEIGHT = 52;
|
|
3114
|
+
var SECTION_HEADER_HEIGHT = 40;
|
|
3115
|
+
var SECTION_PADDING_X = 16;
|
|
3116
|
+
var SECTION_PADDING_TOP = 12;
|
|
3117
|
+
var SECTION_PADDING_BOTTOM = 16;
|
|
3118
|
+
var FIELD_GAP = 10;
|
|
3119
|
+
var SECTION_GAP = 80;
|
|
3120
|
+
function formatEdgeLabel(edge) {
|
|
3121
|
+
if (edge.edgeType === "onExit") return "jump";
|
|
3122
|
+
if (!edge.operator) return "";
|
|
3123
|
+
const op = edge.operator;
|
|
3124
|
+
const val = edge.value;
|
|
3125
|
+
if (op === "exists") return "has value";
|
|
3126
|
+
if (op === "notExists") return "is empty";
|
|
3127
|
+
if (val === void 0 || val === null || val === "") return op;
|
|
3128
|
+
const short = String(val).length > 12 ? String(val).slice(0, 12) + "\u2026" : String(val);
|
|
3129
|
+
return `${op} ${short}`;
|
|
3130
|
+
}
|
|
3131
|
+
function buildReactFlowGraph(schema) {
|
|
3132
|
+
const graph = buildLogicGraph(schema);
|
|
3133
|
+
const nodes = [];
|
|
3134
|
+
const edges = [];
|
|
3135
|
+
let sectionX = 0;
|
|
3136
|
+
let maxSectionHeight = 0;
|
|
3137
|
+
for (const section of schema.sections) {
|
|
3138
|
+
const fieldCount = section.questions.length;
|
|
3139
|
+
const contentHeight = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + fieldCount * FIELD_NODE_HEIGHT + Math.max(0, fieldCount - 1) * FIELD_GAP + SECTION_PADDING_BOTTOM;
|
|
3140
|
+
if (contentHeight > maxSectionHeight) maxSectionHeight = contentHeight;
|
|
3141
|
+
}
|
|
3142
|
+
if (maxSectionHeight < SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + SECTION_PADDING_BOTTOM + FIELD_NODE_HEIGHT) {
|
|
3143
|
+
maxSectionHeight = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + SECTION_PADDING_BOTTOM + FIELD_NODE_HEIGHT;
|
|
3144
|
+
}
|
|
3145
|
+
const sectionWidth = FIELD_NODE_WIDTH + SECTION_PADDING_X * 2;
|
|
3146
|
+
for (let sIdx = 0; sIdx < schema.sections.length; sIdx++) {
|
|
3147
|
+
const section = schema.sections[sIdx];
|
|
3148
|
+
const colorIndex = sIdx;
|
|
3149
|
+
nodes.push({
|
|
3150
|
+
id: section.id,
|
|
3151
|
+
type: "sectionNode",
|
|
3152
|
+
position: { x: sectionX, y: 0 },
|
|
3153
|
+
data: {
|
|
3154
|
+
label: section.title || "Untitled Section",
|
|
3155
|
+
sectionId: section.id,
|
|
3156
|
+
fieldCount: section.questions.length,
|
|
3157
|
+
hasCondition: !!section.showIf,
|
|
3158
|
+
hasExitLogic: !!section.onExit,
|
|
3159
|
+
colorIndex
|
|
3160
|
+
},
|
|
3161
|
+
style: { width: sectionWidth, height: maxSectionHeight }
|
|
3162
|
+
});
|
|
3163
|
+
for (let fIdx = 0; fIdx < section.questions.length; fIdx++) {
|
|
3164
|
+
const question = section.questions[fIdx];
|
|
3165
|
+
const fieldY = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + fIdx * (FIELD_NODE_HEIGHT + FIELD_GAP);
|
|
3166
|
+
nodes.push({
|
|
3167
|
+
id: question.id,
|
|
3168
|
+
type: "fieldNode",
|
|
3169
|
+
position: { x: SECTION_PADDING_X, y: fieldY },
|
|
3170
|
+
parentId: section.id,
|
|
3171
|
+
extent: "parent",
|
|
3172
|
+
data: {
|
|
3173
|
+
label: question.label || question.id,
|
|
3174
|
+
fieldType: question.type,
|
|
3175
|
+
sectionId: section.id,
|
|
3176
|
+
hasCondition: !!question.showIf,
|
|
3177
|
+
colorIndex
|
|
3178
|
+
},
|
|
3179
|
+
style: { width: FIELD_NODE_WIDTH }
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
if (sIdx < schema.sections.length - 1) {
|
|
3183
|
+
const nextSection = schema.sections[sIdx + 1];
|
|
3184
|
+
edges.push({
|
|
3185
|
+
id: `pipeline-${section.id}-${nextSection.id}`,
|
|
3186
|
+
source: section.id,
|
|
3187
|
+
target: nextSection.id,
|
|
3188
|
+
type: "pipelineEdge",
|
|
3189
|
+
data: { edgeType: "pipeline" }
|
|
3190
|
+
});
|
|
3191
|
+
}
|
|
3192
|
+
sectionX += sectionWidth + SECTION_GAP;
|
|
3193
|
+
}
|
|
3194
|
+
for (let i = 0; i < graph.edges.length; i++) {
|
|
3195
|
+
const edge = graph.edges[i];
|
|
3196
|
+
edges.push({
|
|
3197
|
+
id: `e-${edge.from}-${edge.to}-${i}`,
|
|
3198
|
+
source: edge.from,
|
|
3199
|
+
target: edge.to,
|
|
3200
|
+
type: "conditionEdge",
|
|
3201
|
+
animated: edge.edgeType === "showIf",
|
|
3202
|
+
style: edge.edgeType === "onExit" ? { strokeDasharray: "6 3", stroke: "#f59e0b" } : void 0,
|
|
3203
|
+
label: formatEdgeLabel(edge),
|
|
3204
|
+
data: {
|
|
3205
|
+
edgeType: edge.edgeType,
|
|
3206
|
+
operator: edge.operator,
|
|
3207
|
+
value: edge.value,
|
|
3208
|
+
sourceId: edge.from,
|
|
3209
|
+
targetId: edge.to
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
return { nodes, edges };
|
|
3214
|
+
}
|
|
3215
|
+
function SectionNodeInner({ data }) {
|
|
3216
|
+
const d = data;
|
|
3217
|
+
const color = SECTION_COLORS[d.colorIndex % SECTION_COLORS.length];
|
|
3218
|
+
return /* @__PURE__ */ jsxs("div", { className: "fc-flow-section-group", style: { borderColor: color }, children: [
|
|
3219
|
+
/* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Left, className: "fc-flow-handle fc-flow-handle--section" }),
|
|
3220
|
+
/* @__PURE__ */ jsxs("div", { className: "fc-flow-section-group__header", style: { background: color }, children: [
|
|
3221
|
+
/* @__PURE__ */ jsx("span", { className: "fc-flow-section-group__label", children: d.label.length > 30 ? d.label.slice(0, 30) + "\u2026" : d.label }),
|
|
3222
|
+
/* @__PURE__ */ jsxs("span", { className: "fc-flow-section-group__meta", children: [
|
|
3223
|
+
d.fieldCount,
|
|
3224
|
+
" field",
|
|
3225
|
+
d.fieldCount !== 1 ? "s" : "",
|
|
3226
|
+
d.hasCondition && /* @__PURE__ */ jsx("span", { className: "fc-flow-section-group__badge", title: "Has showIf condition", children: "?" }),
|
|
3227
|
+
d.hasExitLogic && /* @__PURE__ */ jsx("span", { className: "fc-flow-section-group__badge fc-flow-section-group__badge--jump", title: "Has onExit jump", children: "\u2934" })
|
|
3228
|
+
] })
|
|
3229
|
+
] }),
|
|
3230
|
+
/* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right, className: "fc-flow-handle fc-flow-handle--section" })
|
|
3231
|
+
] });
|
|
3232
|
+
}
|
|
3233
|
+
var SectionNode = memo(SectionNodeInner);
|
|
3234
|
+
function FieldNodeInner({ data }) {
|
|
3235
|
+
const d = data;
|
|
3236
|
+
const color = SECTION_COLORS[d.colorIndex % SECTION_COLORS.length];
|
|
3237
|
+
return /* @__PURE__ */ jsxs(
|
|
3238
|
+
"div",
|
|
3239
|
+
{
|
|
3240
|
+
className: "fc-flow-field",
|
|
3241
|
+
style: {
|
|
3242
|
+
borderColor: d.hasCondition ? color : void 0,
|
|
3243
|
+
borderWidth: d.hasCondition ? 2 : 1
|
|
3244
|
+
},
|
|
3245
|
+
children: [
|
|
3246
|
+
/* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Left, className: "fc-flow-handle" }),
|
|
3247
|
+
/* @__PURE__ */ jsxs("div", { className: "fc-flow-field__content", children: [
|
|
3248
|
+
/* @__PURE__ */ jsx("span", { className: "fc-flow-field__label", children: d.label.length > 26 ? d.label.slice(0, 26) + "\u2026" : d.label }),
|
|
3249
|
+
/* @__PURE__ */ jsx("span", { className: "fc-flow-field__type", children: d.fieldType })
|
|
3250
|
+
] }),
|
|
3251
|
+
d.hasCondition && /* @__PURE__ */ jsx("div", { className: "fc-flow-badge", style: { background: color }, title: "Has showIf condition", children: "?" }),
|
|
3252
|
+
/* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right, className: "fc-flow-handle" })
|
|
3253
|
+
]
|
|
3254
|
+
}
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
var FieldNode = memo(FieldNodeInner);
|
|
3258
|
+
var logicFlowNodeTypes = {
|
|
3259
|
+
sectionNode: SectionNode,
|
|
3260
|
+
fieldNode: FieldNode
|
|
3261
|
+
};
|
|
3262
|
+
function ConditionEdgeInner({
|
|
3263
|
+
id,
|
|
3264
|
+
sourceX,
|
|
3265
|
+
sourceY,
|
|
3266
|
+
targetX,
|
|
3267
|
+
targetY,
|
|
3268
|
+
sourcePosition,
|
|
3269
|
+
targetPosition,
|
|
3270
|
+
label,
|
|
3271
|
+
style,
|
|
3272
|
+
data,
|
|
3273
|
+
markerEnd
|
|
3274
|
+
}) {
|
|
3275
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
3276
|
+
sourceX,
|
|
3277
|
+
sourceY,
|
|
3278
|
+
sourcePosition,
|
|
3279
|
+
targetX,
|
|
3280
|
+
targetY,
|
|
3281
|
+
targetPosition
|
|
3282
|
+
});
|
|
3283
|
+
const isJump = data?.edgeType === "onExit";
|
|
3284
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3285
|
+
/* @__PURE__ */ jsx(
|
|
3286
|
+
BaseEdge,
|
|
3287
|
+
{
|
|
3288
|
+
id,
|
|
3289
|
+
path: edgePath,
|
|
3290
|
+
markerEnd,
|
|
3291
|
+
style: {
|
|
3292
|
+
stroke: isJump ? "#f59e0b" : "var(--primary, #3b82f6)",
|
|
3293
|
+
strokeWidth: 1.5,
|
|
3294
|
+
strokeOpacity: 0.7,
|
|
3295
|
+
...style
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
),
|
|
3299
|
+
label && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
|
|
3300
|
+
"div",
|
|
3301
|
+
{
|
|
3302
|
+
className: "fc-flow-edge-label",
|
|
3303
|
+
style: {
|
|
3304
|
+
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
|
3305
|
+
background: isJump ? "#f59e0b" : "var(--primary, #3b82f6)"
|
|
3306
|
+
},
|
|
3307
|
+
children: label
|
|
3308
|
+
}
|
|
3309
|
+
) })
|
|
3310
|
+
] });
|
|
3311
|
+
}
|
|
3312
|
+
var ConditionEdge = memo(ConditionEdgeInner);
|
|
3313
|
+
function PipelineEdgeInner({
|
|
3314
|
+
id,
|
|
3315
|
+
sourceX,
|
|
3316
|
+
sourceY,
|
|
3317
|
+
targetX,
|
|
3318
|
+
targetY,
|
|
3319
|
+
sourcePosition,
|
|
3320
|
+
targetPosition,
|
|
3321
|
+
markerEnd
|
|
3322
|
+
}) {
|
|
3323
|
+
const [edgePath] = getBezierPath({
|
|
3324
|
+
sourceX,
|
|
3325
|
+
sourceY,
|
|
3326
|
+
sourcePosition,
|
|
3327
|
+
targetX,
|
|
3328
|
+
targetY,
|
|
3329
|
+
targetPosition
|
|
3330
|
+
});
|
|
3331
|
+
return /* @__PURE__ */ jsx(
|
|
3332
|
+
BaseEdge,
|
|
3333
|
+
{
|
|
3334
|
+
id,
|
|
3335
|
+
path: edgePath,
|
|
3336
|
+
markerEnd,
|
|
3337
|
+
style: {
|
|
3338
|
+
stroke: "var(--muted-foreground, #71717a)",
|
|
3339
|
+
strokeWidth: 2,
|
|
3340
|
+
strokeOpacity: 0.4
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
var PipelineEdge = memo(PipelineEdgeInner);
|
|
3346
|
+
var logicFlowEdgeTypes = {
|
|
3347
|
+
conditionEdge: ConditionEdge,
|
|
3348
|
+
pipelineEdge: PipelineEdge
|
|
3349
|
+
};
|
|
3350
|
+
function findQuestion2(schema, fieldId) {
|
|
3351
|
+
for (const section of schema.sections) {
|
|
3352
|
+
for (const q of section.questions) {
|
|
3353
|
+
if (q.id === fieldId) return q;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return void 0;
|
|
3357
|
+
}
|
|
3358
|
+
function updateQuestionInSchema(schema, fieldId, updates) {
|
|
3359
|
+
return {
|
|
3360
|
+
...schema,
|
|
3361
|
+
sections: schema.sections.map((section) => ({
|
|
3362
|
+
...section,
|
|
3363
|
+
questions: section.questions.map(
|
|
3364
|
+
(q) => q.id === fieldId ? { ...q, ...updates } : q
|
|
3365
|
+
)
|
|
3366
|
+
}))
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
function removeConditionForSource(showIf, sourceFieldId) {
|
|
3370
|
+
if (!showIf) return void 0;
|
|
3371
|
+
if (showIf.field === sourceFieldId) return void 0;
|
|
3372
|
+
if (showIf.field && showIf.field !== sourceFieldId) return showIf;
|
|
3373
|
+
if (showIf.conditions) {
|
|
3374
|
+
const filtered = showIf.conditions.map((c) => removeConditionForSource(c, sourceFieldId)).filter((c) => c !== void 0);
|
|
3375
|
+
if (filtered.length === 0) return void 0;
|
|
3376
|
+
if (filtered.length === 1) return filtered[0];
|
|
3377
|
+
return { ...showIf, conditions: filtered };
|
|
3378
|
+
}
|
|
3379
|
+
return showIf;
|
|
3380
|
+
}
|
|
3381
|
+
var defaultEdgeOptions = {
|
|
3382
|
+
markerEnd: {
|
|
3383
|
+
type: MarkerType.ArrowClosed,
|
|
3384
|
+
width: 16,
|
|
3385
|
+
height: 12
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
3389
|
+
const noSections = schema.sections.length === 0;
|
|
3390
|
+
const flowGraph = useMemo(() => buildReactFlowGraph(schema), [schema]);
|
|
3391
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(flowGraph.nodes);
|
|
3392
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(flowGraph.edges);
|
|
3393
|
+
const [selectedFieldId, setSelectedFieldId] = useState(null);
|
|
3394
|
+
const selectedQuestion = selectedFieldId ? findQuestion2(schema, selectedFieldId) : void 0;
|
|
3395
|
+
useEffect(() => {
|
|
3396
|
+
setNodes(flowGraph.nodes);
|
|
3397
|
+
setEdges(flowGraph.edges);
|
|
3398
|
+
}, [flowGraph, setNodes, setEdges]);
|
|
3399
|
+
const sectionIds = useMemo(() => {
|
|
3400
|
+
const ids = [];
|
|
3401
|
+
for (const section of schema.sections) {
|
|
3402
|
+
ids.push(section.id);
|
|
3403
|
+
}
|
|
3404
|
+
return ids;
|
|
3405
|
+
}, [schema]);
|
|
3406
|
+
const onNodeClick = useCallback(
|
|
3407
|
+
(_event, node) => {
|
|
3408
|
+
if (node.type === "fieldNode") {
|
|
3409
|
+
setSelectedFieldId(node.id);
|
|
3410
|
+
}
|
|
3411
|
+
},
|
|
3412
|
+
[]
|
|
3413
|
+
);
|
|
3414
|
+
const onConnect = useCallback(
|
|
3415
|
+
(connection) => {
|
|
3416
|
+
if (!connection.source || !connection.target) return;
|
|
3417
|
+
const targetQuestion = findQuestion2(schema, connection.target);
|
|
3418
|
+
if (!targetQuestion) return;
|
|
3419
|
+
const newCondition = {
|
|
3420
|
+
field: connection.source,
|
|
3421
|
+
operator: "exists"
|
|
3422
|
+
};
|
|
3423
|
+
let updatedShowIf;
|
|
3424
|
+
const existing = targetQuestion.showIf;
|
|
3425
|
+
if (!existing) {
|
|
3426
|
+
updatedShowIf = { combine: "AND", conditions: [newCondition] };
|
|
3427
|
+
} else {
|
|
3428
|
+
updatedShowIf = {
|
|
3429
|
+
...existing,
|
|
3430
|
+
conditions: [...existing.conditions ?? [], newCondition]
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
const updatedSchema = updateQuestionInSchema(schema, connection.target, {
|
|
3434
|
+
showIf: updatedShowIf
|
|
3435
|
+
});
|
|
3436
|
+
onChange(updatedSchema);
|
|
3437
|
+
setSelectedFieldId(connection.target);
|
|
3438
|
+
},
|
|
3439
|
+
[schema, onChange]
|
|
3440
|
+
);
|
|
3441
|
+
const onEdgesDelete = useCallback(
|
|
3442
|
+
(deletedEdges) => {
|
|
3443
|
+
let updatedSchema = schema;
|
|
3444
|
+
for (const edge of deletedEdges) {
|
|
3445
|
+
const edgeData = edge.data;
|
|
3446
|
+
if (!edgeData) continue;
|
|
3447
|
+
const targetId = edge.target;
|
|
3448
|
+
const sourceId = edge.source;
|
|
3449
|
+
const edgeType = edgeData.edgeType;
|
|
3450
|
+
if (edgeType === "showIf") {
|
|
3451
|
+
const question = findQuestion2(updatedSchema, targetId);
|
|
3452
|
+
if (!question) continue;
|
|
3453
|
+
const updatedShowIf = removeConditionForSource(
|
|
3454
|
+
question.showIf,
|
|
3455
|
+
sourceId
|
|
3456
|
+
);
|
|
3457
|
+
updatedSchema = updateQuestionInSchema(updatedSchema, targetId, {
|
|
3458
|
+
showIf: updatedShowIf
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
onChange(updatedSchema);
|
|
3463
|
+
},
|
|
3464
|
+
[schema, onChange]
|
|
3465
|
+
);
|
|
3466
|
+
const handleConditionUpdate = useCallback(
|
|
3467
|
+
(updates) => {
|
|
3468
|
+
if (!selectedFieldId) return;
|
|
3469
|
+
const updatedSchema = updateQuestionInSchema(schema, selectedFieldId, updates);
|
|
3470
|
+
onChange(updatedSchema);
|
|
3471
|
+
},
|
|
3472
|
+
[schema, selectedFieldId, onChange]
|
|
3473
|
+
);
|
|
3474
|
+
const showIfCount = flowGraph.edges.filter(
|
|
3475
|
+
(e) => e.data?.edgeType === "showIf"
|
|
3476
|
+
).length;
|
|
3477
|
+
const jumpCount = flowGraph.edges.filter(
|
|
3478
|
+
(e) => e.data?.edgeType === "onExit"
|
|
3479
|
+
).length;
|
|
3480
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex flex-col bg-background/95 backdrop-blur-sm", children: [
|
|
3481
|
+
/* @__PURE__ */ jsxs("div", { className: "h-12 shrink-0 flex items-center justify-between px-4 border-b border-border bg-card", children: [
|
|
3482
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3483
|
+
/* @__PURE__ */ jsx(GitBranch, { size: 16, className: "text-primary" }),
|
|
3484
|
+
/* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: "Logic Flow" }),
|
|
3485
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
3486
|
+
showIfCount,
|
|
3487
|
+
" condition",
|
|
3488
|
+
showIfCount !== 1 ? "s" : "",
|
|
3489
|
+
jumpCount > 0 && ` \xB7 ${jumpCount} jump${jumpCount !== 1 ? "s" : ""}`,
|
|
3490
|
+
" \xB7 ",
|
|
3491
|
+
sectionIds.length,
|
|
3492
|
+
" section",
|
|
3493
|
+
sectionIds.length !== 1 ? "s" : ""
|
|
3494
|
+
] })
|
|
3495
|
+
] }),
|
|
3496
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3497
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground mr-2", children: "Drag between nodes to create conditions \xB7 Select edges and press Delete to remove" }),
|
|
3498
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onClose, title: "Close logic flow", children: /* @__PURE__ */ jsx(X, { size: 16, strokeWidth: 1.75 }) })
|
|
3499
|
+
] })
|
|
3500
|
+
] }),
|
|
3501
|
+
noSections ? /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-2", children: [
|
|
3502
|
+
/* @__PURE__ */ jsx(GitBranch, { size: 32, className: "mx-auto opacity-40" }),
|
|
3503
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "No sections found" }),
|
|
3504
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs max-w-xs", children: "Add sections and fields to your form to see the logic flow pipeline." })
|
|
3505
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 flex", children: [
|
|
3506
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxs(
|
|
3507
|
+
ReactFlow,
|
|
3508
|
+
{
|
|
3509
|
+
nodes,
|
|
3510
|
+
edges,
|
|
3511
|
+
onNodesChange,
|
|
3512
|
+
onEdgesChange,
|
|
3513
|
+
onNodeClick,
|
|
3514
|
+
onConnect,
|
|
3515
|
+
onEdgesDelete,
|
|
3516
|
+
nodeTypes: logicFlowNodeTypes,
|
|
3517
|
+
edgeTypes: logicFlowEdgeTypes,
|
|
3518
|
+
defaultEdgeOptions,
|
|
3519
|
+
fitView: true,
|
|
3520
|
+
fitViewOptions: { padding: 0.2 },
|
|
3521
|
+
deleteKeyCode: ["Backspace", "Delete"],
|
|
3522
|
+
proOptions: { hideAttribution: true },
|
|
3523
|
+
children: [
|
|
3524
|
+
/* @__PURE__ */ jsx(Background, { gap: 20, size: 1 }),
|
|
3525
|
+
/* @__PURE__ */ jsx(Controls, { showInteractive: false }),
|
|
3526
|
+
/* @__PURE__ */ jsx(
|
|
3527
|
+
MiniMap,
|
|
3528
|
+
{
|
|
3529
|
+
nodeColor: (node) => {
|
|
3530
|
+
const d = node.data;
|
|
3531
|
+
const idx = d.colorIndex ?? 0;
|
|
3532
|
+
return SECTION_COLORS[idx % SECTION_COLORS.length];
|
|
3533
|
+
},
|
|
3534
|
+
maskColor: "rgba(0, 0, 0, 0.3)"
|
|
3535
|
+
}
|
|
3536
|
+
)
|
|
3537
|
+
]
|
|
3538
|
+
}
|
|
3539
|
+
) }),
|
|
3540
|
+
selectedFieldId && selectedQuestion && /* @__PURE__ */ jsxs("div", { className: "w-72 border-l border-border bg-card overflow-auto", children: [
|
|
3541
|
+
/* @__PURE__ */ jsxs("div", { className: "px-3 py-3 border-b border-border flex items-center justify-between", children: [
|
|
3542
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3543
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold truncate", children: selectedQuestion.label || selectedQuestion.id }),
|
|
3544
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: selectedQuestion.type })
|
|
3545
|
+
] }),
|
|
3546
|
+
/* @__PURE__ */ jsx(
|
|
3547
|
+
Button,
|
|
3548
|
+
{
|
|
3549
|
+
variant: "ghost",
|
|
3550
|
+
size: "icon-xs",
|
|
3551
|
+
onClick: () => setSelectedFieldId(null),
|
|
3552
|
+
title: "Close panel",
|
|
3553
|
+
children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 1.75 })
|
|
3554
|
+
}
|
|
3555
|
+
)
|
|
3556
|
+
] }),
|
|
3557
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx(
|
|
3558
|
+
ConditionEditor,
|
|
3559
|
+
{
|
|
3560
|
+
question: selectedQuestion,
|
|
3561
|
+
schema,
|
|
3562
|
+
onUpdate: handleConditionUpdate
|
|
3563
|
+
}
|
|
3564
|
+
) })
|
|
3565
|
+
] })
|
|
3566
|
+
] }),
|
|
3567
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 border-t border-border bg-card flex items-center gap-4 text-xs text-muted-foreground flex-wrap", children: [
|
|
3568
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3569
|
+
/* @__PURE__ */ jsx("div", { className: "w-5 h-0.5 rounded", style: { background: "var(--muted-foreground)", opacity: 0.4 } }),
|
|
3570
|
+
/* @__PURE__ */ jsx("span", { children: "flow" })
|
|
3571
|
+
] }),
|
|
3572
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3573
|
+
/* @__PURE__ */ jsx("div", { className: "w-5 h-0.5 bg-primary rounded" }),
|
|
3574
|
+
/* @__PURE__ */ jsx("span", { children: "showIf" })
|
|
3575
|
+
] }),
|
|
3576
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3577
|
+
/* @__PURE__ */ jsx("div", { className: "w-5 h-0.5 rounded", style: { background: "#f59e0b" } }),
|
|
3578
|
+
/* @__PURE__ */ jsx("span", { children: "jump" })
|
|
3579
|
+
] }),
|
|
3580
|
+
sectionIds.map((id, i) => {
|
|
3581
|
+
const section = schema.sections.find((s) => s.id === id);
|
|
3582
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3583
|
+
/* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-sm", style: { background: SECTION_COLORS[i % SECTION_COLORS.length] } }),
|
|
3584
|
+
/* @__PURE__ */ jsx("span", { children: section?.title || id })
|
|
3585
|
+
] }, id);
|
|
3586
|
+
})
|
|
3587
|
+
] })
|
|
3588
|
+
] });
|
|
3589
|
+
}
|
|
3590
|
+
function TemplateGallery({ templates, onSelect, onClose }) {
|
|
3591
|
+
const [search, setSearch] = useState("");
|
|
3592
|
+
const [activeCategory, setActiveCategory] = useState("all");
|
|
3593
|
+
const [confirmTemplate, setConfirmTemplate] = useState(null);
|
|
3594
|
+
const categories = useMemo(() => {
|
|
3595
|
+
const cats = /* @__PURE__ */ new Set();
|
|
3596
|
+
for (const t of templates) cats.add(t.meta.category);
|
|
3597
|
+
return ["all", ...Array.from(cats)];
|
|
3598
|
+
}, [templates]);
|
|
3599
|
+
const filtered = useMemo(() => {
|
|
3600
|
+
return templates.filter((t) => {
|
|
3601
|
+
const matchesCategory = activeCategory === "all" || t.meta.category === activeCategory;
|
|
3602
|
+
const matchesSearch = !search || t.meta.name.toLowerCase().includes(search.toLowerCase()) || t.meta.description.toLowerCase().includes(search.toLowerCase()) || t.meta.tags?.some((tag) => tag.toLowerCase().includes(search.toLowerCase()));
|
|
3603
|
+
return matchesCategory && matchesSearch;
|
|
3604
|
+
});
|
|
3605
|
+
}, [templates, activeCategory, search]);
|
|
3606
|
+
const handleConfirm = () => {
|
|
3607
|
+
if (confirmTemplate) {
|
|
3608
|
+
onSelect(confirmTemplate);
|
|
3609
|
+
setConfirmTemplate(null);
|
|
3610
|
+
onClose();
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-3xl max-h-[85vh] mx-4 bg-card border border-border rounded-lg shadow-xl flex flex-col overflow-hidden", children: [
|
|
3614
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between px-4 py-3 border-b border-border", children: [
|
|
3615
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3616
|
+
/* @__PURE__ */ jsx(LayoutTemplate, { size: 18, className: "text-primary", strokeWidth: 1.75 }),
|
|
3617
|
+
/* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: "Template Gallery" }),
|
|
3618
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
3619
|
+
"(",
|
|
3620
|
+
templates.length,
|
|
3621
|
+
" templates)"
|
|
3622
|
+
] })
|
|
3623
|
+
] }),
|
|
3624
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsx(X, { size: 16, strokeWidth: 1.75 }) })
|
|
3625
|
+
] }),
|
|
3626
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 space-y-2 border-b border-border", children: [
|
|
3627
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
3628
|
+
/* @__PURE__ */ jsx(Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground", strokeWidth: 1.75 }),
|
|
3629
|
+
/* @__PURE__ */ jsx(
|
|
3630
|
+
"input",
|
|
3631
|
+
{
|
|
3632
|
+
type: "text",
|
|
3633
|
+
placeholder: "Search templates...",
|
|
3634
|
+
value: search,
|
|
3635
|
+
onChange: (e) => setSearch(e.target.value),
|
|
3636
|
+
className: "w-full pl-8 pr-3 py-1.5 text-sm bg-background border border-border rounded-md text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
|
3637
|
+
}
|
|
3638
|
+
)
|
|
3639
|
+
] }),
|
|
3640
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto pb-0.5", children: categories.map((cat) => /* @__PURE__ */ jsx(
|
|
3641
|
+
"button",
|
|
3642
|
+
{
|
|
3643
|
+
type: "button",
|
|
3644
|
+
onClick: () => setActiveCategory(cat),
|
|
3645
|
+
className: `shrink-0 px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${activeCategory === cat ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`,
|
|
3646
|
+
children: cat === "all" ? "All" : cat.charAt(0).toUpperCase() + cat.slice(1)
|
|
3647
|
+
},
|
|
3648
|
+
cat
|
|
3649
|
+
)) })
|
|
3650
|
+
] }),
|
|
3651
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-4", children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center h-40 text-muted-foreground text-sm", children: /* @__PURE__ */ jsx("p", { children: "No templates match your search." }) }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: filtered.map((template) => /* @__PURE__ */ jsxs(
|
|
3652
|
+
"button",
|
|
3653
|
+
{
|
|
3654
|
+
type: "button",
|
|
3655
|
+
onClick: () => setConfirmTemplate(template),
|
|
3656
|
+
className: "text-left p-3 rounded-lg border border-border bg-background hover:border-primary/50 hover:bg-primary/5 transition-colors group",
|
|
3657
|
+
children: [
|
|
3658
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2 mb-1.5", children: [
|
|
3659
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: template.meta.name }),
|
|
3660
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 px-1.5 py-0.5 rounded text-[10px] font-medium bg-muted text-muted-foreground", children: template.meta.category })
|
|
3661
|
+
] }),
|
|
3662
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-2 line-clamp-2", children: template.meta.description }),
|
|
3663
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
|
|
3664
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
3665
|
+
/* @__PURE__ */ jsx(FileText, { size: 11, strokeWidth: 1.75 }),
|
|
3666
|
+
template.meta.fieldCount,
|
|
3667
|
+
" field",
|
|
3668
|
+
template.meta.fieldCount !== 1 ? "s" : ""
|
|
3669
|
+
] }),
|
|
3670
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
3671
|
+
/* @__PURE__ */ jsx(Layers, { size: 11, strokeWidth: 1.75 }),
|
|
3672
|
+
template.meta.sectionCount,
|
|
3673
|
+
" section",
|
|
3674
|
+
template.meta.sectionCount !== 1 ? "s" : ""
|
|
3675
|
+
] })
|
|
3676
|
+
] })
|
|
3677
|
+
]
|
|
3678
|
+
},
|
|
3679
|
+
template.meta.id
|
|
3680
|
+
)) }) }),
|
|
3681
|
+
confirmTemplate && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-3 border-t border-border bg-muted/50 flex items-center justify-between gap-3", children: [
|
|
3682
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
|
|
3683
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Use " }),
|
|
3684
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: confirmTemplate.meta.name }),
|
|
3685
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "? This will replace the current form." })
|
|
3686
|
+
] }),
|
|
3687
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
3688
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirmTemplate(null), children: "Cancel" }),
|
|
3689
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleConfirm, children: "Use Template" })
|
|
3690
|
+
] })
|
|
3691
|
+
] })
|
|
3692
|
+
] }) });
|
|
3693
|
+
}
|
|
3694
|
+
function useDebouncedValidation(delayMs = 500) {
|
|
3695
|
+
const [errors, setErrors] = useState([]);
|
|
3696
|
+
const timerRef = useRef(null);
|
|
3697
|
+
const validate = useCallback(
|
|
3698
|
+
(text) => {
|
|
3699
|
+
if (timerRef.current) {
|
|
3700
|
+
clearTimeout(timerRef.current);
|
|
3701
|
+
}
|
|
3702
|
+
timerRef.current = setTimeout(() => {
|
|
3703
|
+
const result = [];
|
|
3704
|
+
let parsed;
|
|
3705
|
+
try {
|
|
3706
|
+
parsed = JSON.parse(text);
|
|
3707
|
+
} catch (err) {
|
|
3708
|
+
if (err instanceof SyntaxError) {
|
|
3709
|
+
const posMatch = err.message.match(/position\s+(\d+)/i);
|
|
3710
|
+
let line = 1;
|
|
3711
|
+
let column = 1;
|
|
3712
|
+
if (posMatch) {
|
|
3713
|
+
const pos = parseInt(posMatch[1], 10);
|
|
3714
|
+
const upToPos = text.slice(0, pos);
|
|
3715
|
+
line = (upToPos.match(/\n/g) || []).length + 1;
|
|
3716
|
+
column = pos - upToPos.lastIndexOf("\n");
|
|
3717
|
+
}
|
|
3718
|
+
result.push({ message: err.message, line, column, endLine: line, endColumn: column + 1 });
|
|
3719
|
+
} else {
|
|
3720
|
+
result.push({ message: "Invalid JSON" });
|
|
3721
|
+
}
|
|
3722
|
+
setErrors(result);
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
try {
|
|
3726
|
+
validateSchema(parsed);
|
|
3727
|
+
} catch (err) {
|
|
3728
|
+
if (err instanceof FormEngineSchemaError) {
|
|
3729
|
+
for (const issue of err.issues) {
|
|
3730
|
+
result.push({
|
|
3731
|
+
message: issue.message,
|
|
3732
|
+
path: issue.path?.join(".")
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
} else if (err instanceof Error) {
|
|
3736
|
+
result.push({ message: err.message });
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
setErrors(result);
|
|
3740
|
+
}, delayMs);
|
|
3741
|
+
},
|
|
3742
|
+
[delayMs]
|
|
3743
|
+
);
|
|
3744
|
+
return { errors, validate };
|
|
3745
|
+
}
|
|
2788
3746
|
var ThemeCtx = createContext({});
|
|
2789
3747
|
function useBuilderTheme() {
|
|
2790
3748
|
return useContext(ThemeCtx);
|
|
@@ -2857,7 +3815,16 @@ var FormBuilderErrorBoundary = class extends Component {
|
|
|
2857
3815
|
}
|
|
2858
3816
|
};
|
|
2859
3817
|
function FormBuilderCore(props) {
|
|
2860
|
-
const { initialSchema = DEFAULT_SCHEMA, onChange, onSave, height = "100vh", theme, className, toolbarExtra, questionTypes, palette } = props;
|
|
3818
|
+
const { initialSchema = DEFAULT_SCHEMA, onChange, onSave, height = "100vh", theme, className, toolbarExtra, questionTypes, palette, preview, templates, schemaUrl } = props;
|
|
3819
|
+
const [viewMode, setViewMode] = useState("design");
|
|
3820
|
+
const [jsonText, setJsonText] = useState("");
|
|
3821
|
+
const [jsonSwitchError, setJsonSwitchError] = useState(null);
|
|
3822
|
+
const [showLogicMap, setShowLogicMap] = useState(false);
|
|
3823
|
+
const [showTemplateGallery, setShowTemplateGallery] = useState(false);
|
|
3824
|
+
const [saveValidationErrors, setSaveValidationErrors] = useState(null);
|
|
3825
|
+
const [schemaUrlLoading, setSchemaUrlLoading] = useState(!!schemaUrl);
|
|
3826
|
+
const [schemaUrlError, setSchemaUrlError] = useState(null);
|
|
3827
|
+
const { errors: jsonErrors, validate: validateJson } = useDebouncedValidation(400);
|
|
2861
3828
|
const mergedQuestionTypes = questionTypes ? { ...QUESTION_TYPE_INFO, ...questionTypes } : QUESTION_TYPE_INFO;
|
|
2862
3829
|
const builderState = useBuilderState(initialSchema);
|
|
2863
3830
|
const dragDrop = useDragDrop(builderState);
|
|
@@ -2873,15 +3840,54 @@ function FormBuilderCore(props) {
|
|
|
2873
3840
|
setMobilePanel("none");
|
|
2874
3841
|
}
|
|
2875
3842
|
}, [dragDrop.activeDragItem]);
|
|
3843
|
+
useEffect(() => {
|
|
3844
|
+
if (!schemaUrl) return;
|
|
3845
|
+
let cancelled = false;
|
|
3846
|
+
setSchemaUrlLoading(true);
|
|
3847
|
+
setSchemaUrlError(null);
|
|
3848
|
+
fetch(schemaUrl).then((res) => {
|
|
3849
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
3850
|
+
return res.json();
|
|
3851
|
+
}).then((json) => {
|
|
3852
|
+
if (cancelled) return;
|
|
3853
|
+
try {
|
|
3854
|
+
const validated = validateSchema(json);
|
|
3855
|
+
builderState.resetSchema(validated);
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
if (err instanceof FormEngineSchemaError) {
|
|
3858
|
+
setSchemaUrlError(`Invalid schema: ${err.issues[0]?.message ?? "validation failed"}`);
|
|
3859
|
+
} else {
|
|
3860
|
+
setSchemaUrlError(err.message);
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
setSchemaUrlLoading(false);
|
|
3864
|
+
}).catch((err) => {
|
|
3865
|
+
if (cancelled) return;
|
|
3866
|
+
setSchemaUrlError(err.message ?? "Failed to fetch schema");
|
|
3867
|
+
setSchemaUrlLoading(false);
|
|
3868
|
+
});
|
|
3869
|
+
return () => {
|
|
3870
|
+
cancelled = true;
|
|
3871
|
+
};
|
|
3872
|
+
}, [schemaUrl]);
|
|
2876
3873
|
useEffect(() => {
|
|
2877
3874
|
if (onChange && builderState.isDirty) {
|
|
2878
3875
|
onChange(builderState.schema);
|
|
2879
3876
|
}
|
|
2880
3877
|
}, [builderState.schema, builderState.isDirty, onChange]);
|
|
2881
3878
|
const handleSave = useCallback(() => {
|
|
2882
|
-
if (onSave)
|
|
3879
|
+
if (!onSave) return;
|
|
3880
|
+
try {
|
|
3881
|
+
validateSchema(builderState.schema);
|
|
3882
|
+
setSaveValidationErrors(null);
|
|
2883
3883
|
onSave(builderState.schema);
|
|
2884
3884
|
builderState.markClean();
|
|
3885
|
+
} catch (err) {
|
|
3886
|
+
if (err instanceof FormEngineSchemaError) {
|
|
3887
|
+
setSaveValidationErrors(err.issues.map((issue) => issue.message));
|
|
3888
|
+
} else {
|
|
3889
|
+
setSaveValidationErrors([err.message ?? "Unknown validation error"]);
|
|
3890
|
+
}
|
|
2885
3891
|
}
|
|
2886
3892
|
}, [onSave, builderState]);
|
|
2887
3893
|
const handleExport = useCallback(() => {
|
|
@@ -2924,6 +3930,32 @@ ${details}`);
|
|
|
2924
3930
|
},
|
|
2925
3931
|
[builderState]
|
|
2926
3932
|
);
|
|
3933
|
+
const handleViewModeChange = useCallback(
|
|
3934
|
+
(newMode) => {
|
|
3935
|
+
setJsonSwitchError(null);
|
|
3936
|
+
if (viewMode === "json" && newMode !== "json") {
|
|
3937
|
+
try {
|
|
3938
|
+
const parsed = JSON.parse(jsonText);
|
|
3939
|
+
const validated = validateSchema(parsed);
|
|
3940
|
+
builderState.resetSchema(validated);
|
|
3941
|
+
} catch (err) {
|
|
3942
|
+
if (err instanceof SyntaxError) {
|
|
3943
|
+
setJsonSwitchError("Cannot switch: JSON has syntax errors");
|
|
3944
|
+
} else if (err instanceof FormEngineSchemaError) {
|
|
3945
|
+
setJsonSwitchError(`Cannot switch: ${err.issues[0]?.message ?? "Invalid schema"}`);
|
|
3946
|
+
} else {
|
|
3947
|
+
setJsonSwitchError("Cannot switch: Invalid schema");
|
|
3948
|
+
}
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
if (newMode === "json") {
|
|
3953
|
+
setJsonText(JSON.stringify(builderState.schema, null, 2));
|
|
3954
|
+
}
|
|
3955
|
+
setViewMode(newMode);
|
|
3956
|
+
},
|
|
3957
|
+
[viewMode, jsonText, builderState]
|
|
3958
|
+
);
|
|
2927
3959
|
const handleKeyDown = useCallback(
|
|
2928
3960
|
(e) => {
|
|
2929
3961
|
const mod = e.metaKey || e.ctrlKey;
|
|
@@ -3025,8 +4057,74 @@ ${details}`);
|
|
|
3025
4057
|
/* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2 h-5" }),
|
|
3026
4058
|
builderState.isDirty && /* @__PURE__ */ jsx("span", { className: "text-xs text-primary", role: "status", children: "Unsaved changes" })
|
|
3027
4059
|
] }),
|
|
3028
|
-
/* @__PURE__ */
|
|
4060
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
|
|
4061
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 bg-muted rounded-md p-0.5", children: [
|
|
4062
|
+
/* @__PURE__ */ jsxs(
|
|
4063
|
+
Button,
|
|
4064
|
+
{
|
|
4065
|
+
variant: viewMode === "design" ? "secondary" : "ghost",
|
|
4066
|
+
size: "sm",
|
|
4067
|
+
onClick: () => handleViewModeChange("design"),
|
|
4068
|
+
className: "gap-1.5 h-7 px-2.5 text-xs",
|
|
4069
|
+
children: [
|
|
4070
|
+
/* @__PURE__ */ jsx(PencilRuler, { size: 13, strokeWidth: 1.75 }),
|
|
4071
|
+
"Design"
|
|
4072
|
+
]
|
|
4073
|
+
}
|
|
4074
|
+
),
|
|
4075
|
+
/* @__PURE__ */ jsxs(
|
|
4076
|
+
Button,
|
|
4077
|
+
{
|
|
4078
|
+
variant: viewMode === "preview" ? "secondary" : "ghost",
|
|
4079
|
+
size: "sm",
|
|
4080
|
+
onClick: () => handleViewModeChange("preview"),
|
|
4081
|
+
className: "gap-1.5 h-7 px-2.5 text-xs",
|
|
4082
|
+
children: [
|
|
4083
|
+
/* @__PURE__ */ jsx(Eye, { size: 13, strokeWidth: 1.75 }),
|
|
4084
|
+
"Preview"
|
|
4085
|
+
]
|
|
4086
|
+
}
|
|
4087
|
+
),
|
|
4088
|
+
/* @__PURE__ */ jsxs(
|
|
4089
|
+
Button,
|
|
4090
|
+
{
|
|
4091
|
+
variant: viewMode === "json" ? "secondary" : "ghost",
|
|
4092
|
+
size: "sm",
|
|
4093
|
+
onClick: () => handleViewModeChange("json"),
|
|
4094
|
+
className: "gap-1.5 h-7 px-2.5 text-xs",
|
|
4095
|
+
children: [
|
|
4096
|
+
/* @__PURE__ */ jsx(Code, { size: 13, strokeWidth: 1.75 }),
|
|
4097
|
+
"JSON"
|
|
4098
|
+
]
|
|
4099
|
+
}
|
|
4100
|
+
)
|
|
4101
|
+
] }),
|
|
4102
|
+
toolbarExtra
|
|
4103
|
+
] }),
|
|
3029
4104
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
|
|
4105
|
+
templates && templates.length > 0 && /* @__PURE__ */ jsx(
|
|
4106
|
+
Button,
|
|
4107
|
+
{
|
|
4108
|
+
variant: "ghost",
|
|
4109
|
+
size: "icon-sm",
|
|
4110
|
+
onClick: () => setShowTemplateGallery(true),
|
|
4111
|
+
title: "Browse templates",
|
|
4112
|
+
"aria-label": "Template gallery",
|
|
4113
|
+
children: /* @__PURE__ */ jsx(LayoutTemplate, { size: 15, strokeWidth: 1.75 })
|
|
4114
|
+
}
|
|
4115
|
+
),
|
|
4116
|
+
/* @__PURE__ */ jsx(
|
|
4117
|
+
Button,
|
|
4118
|
+
{
|
|
4119
|
+
variant: "ghost",
|
|
4120
|
+
size: "icon-sm",
|
|
4121
|
+
onClick: () => setShowLogicMap(true),
|
|
4122
|
+
title: "View branching logic map",
|
|
4123
|
+
"aria-label": "Logic map",
|
|
4124
|
+
children: /* @__PURE__ */ jsx(GitBranch, { size: 15, strokeWidth: 1.75 })
|
|
4125
|
+
}
|
|
4126
|
+
),
|
|
4127
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-1 h-5" }),
|
|
3030
4128
|
/* @__PURE__ */ jsx(
|
|
3031
4129
|
Button,
|
|
3032
4130
|
{
|
|
@@ -3070,6 +4168,47 @@ ${details}`);
|
|
|
3070
4168
|
}
|
|
3071
4169
|
) }),
|
|
3072
4170
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1", children: [
|
|
4171
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 bg-muted rounded-md p-0.5", children: [
|
|
4172
|
+
/* @__PURE__ */ jsxs(
|
|
4173
|
+
Button,
|
|
4174
|
+
{
|
|
4175
|
+
variant: viewMode === "design" ? "secondary" : "ghost",
|
|
4176
|
+
size: "sm",
|
|
4177
|
+
onClick: () => handleViewModeChange("design"),
|
|
4178
|
+
className: "gap-1 h-7 px-2 text-xs",
|
|
4179
|
+
children: [
|
|
4180
|
+
/* @__PURE__ */ jsx(PencilRuler, { size: 12, strokeWidth: 1.75 }),
|
|
4181
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Design" })
|
|
4182
|
+
]
|
|
4183
|
+
}
|
|
4184
|
+
),
|
|
4185
|
+
/* @__PURE__ */ jsxs(
|
|
4186
|
+
Button,
|
|
4187
|
+
{
|
|
4188
|
+
variant: viewMode === "preview" ? "secondary" : "ghost",
|
|
4189
|
+
size: "sm",
|
|
4190
|
+
onClick: () => handleViewModeChange("preview"),
|
|
4191
|
+
className: "gap-1 h-7 px-2 text-xs",
|
|
4192
|
+
children: [
|
|
4193
|
+
/* @__PURE__ */ jsx(Eye, { size: 12, strokeWidth: 1.75 }),
|
|
4194
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Preview" })
|
|
4195
|
+
]
|
|
4196
|
+
}
|
|
4197
|
+
),
|
|
4198
|
+
/* @__PURE__ */ jsxs(
|
|
4199
|
+
Button,
|
|
4200
|
+
{
|
|
4201
|
+
variant: viewMode === "json" ? "secondary" : "ghost",
|
|
4202
|
+
size: "sm",
|
|
4203
|
+
onClick: () => handleViewModeChange("json"),
|
|
4204
|
+
className: "gap-1 h-7 px-2 text-xs",
|
|
4205
|
+
children: [
|
|
4206
|
+
/* @__PURE__ */ jsx(Code, { size: 12, strokeWidth: 1.75 }),
|
|
4207
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "JSON" })
|
|
4208
|
+
]
|
|
4209
|
+
}
|
|
4210
|
+
)
|
|
4211
|
+
] }),
|
|
3073
4212
|
toolbarExtra,
|
|
3074
4213
|
/* @__PURE__ */ jsxs(Button, { onClick: handleSave, size: "sm", className: "border-0 shadow-sm fcb-glow ml-1", "aria-label": "Save form", children: [
|
|
3075
4214
|
/* @__PURE__ */ jsx(Save, { size: 14, strokeWidth: 2 }),
|
|
@@ -3158,7 +4297,50 @@ ${details}`);
|
|
|
3158
4297
|
"aria-hidden": "true"
|
|
3159
4298
|
}
|
|
3160
4299
|
),
|
|
3161
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
4300
|
+
jsonSwitchError && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-destructive text-xs flex items-center justify-between", children: [
|
|
4301
|
+
/* @__PURE__ */ jsx("span", { children: jsonSwitchError }),
|
|
4302
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setJsonSwitchError(null), className: "ml-2 hover:underline", children: "Dismiss" })
|
|
4303
|
+
] }),
|
|
4304
|
+
saveValidationErrors && saveValidationErrors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-xs", children: [
|
|
4305
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
|
|
4306
|
+
/* @__PURE__ */ jsxs("span", { className: "font-semibold text-destructive", children: [
|
|
4307
|
+
"Schema validation failed (",
|
|
4308
|
+
saveValidationErrors.length,
|
|
4309
|
+
" issue",
|
|
4310
|
+
saveValidationErrors.length !== 1 ? "s" : "",
|
|
4311
|
+
")"
|
|
4312
|
+
] }),
|
|
4313
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setSaveValidationErrors(null), className: "text-destructive hover:underline", children: "Dismiss" })
|
|
4314
|
+
] }),
|
|
4315
|
+
/* @__PURE__ */ jsxs("ul", { className: "list-disc pl-4 text-destructive/80 space-y-0.5", children: [
|
|
4316
|
+
saveValidationErrors.slice(0, 10).map((msg, i) => /* @__PURE__ */ jsx("li", { children: msg }, i)),
|
|
4317
|
+
saveValidationErrors.length > 10 && /* @__PURE__ */ jsxs("li", { children: [
|
|
4318
|
+
"...and ",
|
|
4319
|
+
saveValidationErrors.length - 10,
|
|
4320
|
+
" more"
|
|
4321
|
+
] })
|
|
4322
|
+
] })
|
|
4323
|
+
] }),
|
|
4324
|
+
schemaUrlLoading && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 border-b border-border bg-muted/50 text-xs text-muted-foreground flex items-center gap-2", children: [
|
|
4325
|
+
/* @__PURE__ */ jsx("span", { className: "inline-block w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin" }),
|
|
4326
|
+
"Loading schema from URL..."
|
|
4327
|
+
] }),
|
|
4328
|
+
schemaUrlError && /* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-destructive text-xs flex items-center justify-between", children: [
|
|
4329
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
4330
|
+
"Failed to load schema: ",
|
|
4331
|
+
schemaUrlError
|
|
4332
|
+
] }),
|
|
4333
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setSchemaUrlError(null), className: "ml-2 hover:underline", children: "Dismiss" })
|
|
4334
|
+
] }),
|
|
4335
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 flex overflow-hidden relative", children: viewMode === "json" ? /* @__PURE__ */ jsx(
|
|
4336
|
+
JsonEditorPanel,
|
|
4337
|
+
{
|
|
4338
|
+
value: jsonText,
|
|
4339
|
+
onChange: setJsonText,
|
|
4340
|
+
errors: jsonErrors,
|
|
4341
|
+
onValidate: validateJson
|
|
4342
|
+
}
|
|
4343
|
+
) : viewMode === "preview" ? /* @__PURE__ */ jsx(FormPreviewPanel, { schema: builderState.schema, preview }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3162
4344
|
/* @__PURE__ */ jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }) }),
|
|
3163
4345
|
/* @__PURE__ */ jsx(FormCanvas, { builderState }),
|
|
3164
4346
|
/* @__PURE__ */ jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsx(PropertiesPanel, { builderState }) }),
|
|
@@ -3172,11 +4354,27 @@ ${details}`);
|
|
|
3172
4354
|
"aria-hidden": "true"
|
|
3173
4355
|
}
|
|
3174
4356
|
)
|
|
3175
|
-
] })
|
|
4357
|
+
] }) })
|
|
3176
4358
|
]
|
|
3177
4359
|
}
|
|
3178
4360
|
),
|
|
3179
|
-
/* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: dragDrop.activeDragItem && /* @__PURE__ */ jsx(DragOverlayContent, { item: dragDrop.activeDragItem, schema: builderState.schema, questionTypes: mergedQuestionTypes }) })
|
|
4361
|
+
/* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: dragDrop.activeDragItem && /* @__PURE__ */ jsx(DragOverlayContent, { item: dragDrop.activeDragItem, schema: builderState.schema, questionTypes: mergedQuestionTypes }) }),
|
|
4362
|
+
showLogicMap && /* @__PURE__ */ jsx(
|
|
4363
|
+
LogicFlowEditor,
|
|
4364
|
+
{
|
|
4365
|
+
schema: builderState.schema,
|
|
4366
|
+
onChange: (updated) => builderState.resetSchema(updated),
|
|
4367
|
+
onClose: () => setShowLogicMap(false)
|
|
4368
|
+
}
|
|
4369
|
+
),
|
|
4370
|
+
showTemplateGallery && templates && /* @__PURE__ */ jsx(
|
|
4371
|
+
TemplateGallery,
|
|
4372
|
+
{
|
|
4373
|
+
templates,
|
|
4374
|
+
onSelect: (template) => builderState.resetSchema(template.schema),
|
|
4375
|
+
onClose: () => setShowTemplateGallery(false)
|
|
4376
|
+
}
|
|
4377
|
+
)
|
|
3180
4378
|
]
|
|
3181
4379
|
}
|
|
3182
4380
|
) });
|
|
@@ -3231,76 +4429,54 @@ var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
|
|
|
3231
4429
|
|
|
3232
4430
|
// src/form-builder/theme/presets.ts
|
|
3233
4431
|
var squaredrDarkPreset = {
|
|
3234
|
-
background: "#
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
// sr-error
|
|
3258
|
-
destructiveForeground: "#e8e8ea",
|
|
3259
|
-
// ink-100
|
|
3260
|
-
border: "#1c1c20",
|
|
3261
|
-
// ink-800
|
|
3262
|
-
input: "#1c1c20",
|
|
3263
|
-
// ink-800
|
|
3264
|
-
ring: "oklch(0.82 0.14 210)",
|
|
3265
|
-
// sr-accent-cyan
|
|
3266
|
-
radius: "6px",
|
|
3267
|
-
surface: "#111113",
|
|
3268
|
-
// ink-900
|
|
3269
|
-
surfaceHover: "#17171a",
|
|
3270
|
-
// ink-850
|
|
3271
|
-
canvas: "#0a0a0b",
|
|
3272
|
-
// ink-950
|
|
3273
|
-
panel: "#111113",
|
|
3274
|
-
// ink-900
|
|
3275
|
-
borderStrong: "#26262c",
|
|
3276
|
-
// ink-700
|
|
3277
|
-
textDim: "#5a5a66"
|
|
3278
|
-
// ink-500
|
|
4432
|
+
background: "#0F1A1F",
|
|
4433
|
+
foreground: "#E8EFF1",
|
|
4434
|
+
card: "#16242A",
|
|
4435
|
+
primary: "#63BDB4",
|
|
4436
|
+
primaryForeground: "#0F1A1F",
|
|
4437
|
+
secondary: "#182F31",
|
|
4438
|
+
secondaryForeground: "#E8EFF1",
|
|
4439
|
+
muted: "#182F31",
|
|
4440
|
+
mutedForeground: "#8CA1A9",
|
|
4441
|
+
accent: "#182F31",
|
|
4442
|
+
accentForeground: "#E8EFF1",
|
|
4443
|
+
destructive: "#E08072",
|
|
4444
|
+
destructiveForeground: "#0F1A1F",
|
|
4445
|
+
border: "#2A3B42",
|
|
4446
|
+
input: "#2A3B42",
|
|
4447
|
+
ring: "#63BDB4",
|
|
4448
|
+
radius: "0px",
|
|
4449
|
+
surface: "#16242A",
|
|
4450
|
+
surfaceHover: "#182F31",
|
|
4451
|
+
canvas: "#0F1A1F",
|
|
4452
|
+
panel: "#16242A",
|
|
4453
|
+
borderStrong: "#2F4F4C",
|
|
4454
|
+
textDim: "#5E7680"
|
|
3279
4455
|
};
|
|
3280
4456
|
var cleanPreset = {
|
|
3281
|
-
background: "#
|
|
3282
|
-
foreground: "#
|
|
3283
|
-
card: "#
|
|
3284
|
-
primary: "#
|
|
3285
|
-
primaryForeground: "#
|
|
3286
|
-
secondary: "#
|
|
3287
|
-
secondaryForeground: "#
|
|
3288
|
-
muted: "#
|
|
3289
|
-
mutedForeground: "#
|
|
3290
|
-
accent: "#
|
|
3291
|
-
accentForeground: "#
|
|
3292
|
-
destructive: "#
|
|
3293
|
-
destructiveForeground: "#
|
|
3294
|
-
border: "#
|
|
3295
|
-
input: "#
|
|
3296
|
-
ring: "#
|
|
3297
|
-
radius: "
|
|
3298
|
-
surface: "#
|
|
3299
|
-
surfaceHover: "#
|
|
3300
|
-
canvas: "#
|
|
3301
|
-
panel: "#
|
|
3302
|
-
borderStrong: "#
|
|
3303
|
-
textDim: "#
|
|
4457
|
+
background: "#F4F7F8",
|
|
4458
|
+
foreground: "#12222A",
|
|
4459
|
+
card: "#FFFFFF",
|
|
4460
|
+
primary: "#1F6B6E",
|
|
4461
|
+
primaryForeground: "#FFFFFF",
|
|
4462
|
+
secondary: "#EDF3F2",
|
|
4463
|
+
secondaryForeground: "#12222A",
|
|
4464
|
+
muted: "#EDF3F2",
|
|
4465
|
+
mutedForeground: "#6A7B85",
|
|
4466
|
+
accent: "#EDF3F2",
|
|
4467
|
+
accentForeground: "#12222A",
|
|
4468
|
+
destructive: "#B04A3C",
|
|
4469
|
+
destructiveForeground: "#FFFFFF",
|
|
4470
|
+
border: "#DCE4E8",
|
|
4471
|
+
input: "#DCE4E8",
|
|
4472
|
+
ring: "#1F6B6E",
|
|
4473
|
+
radius: "0px",
|
|
4474
|
+
surface: "#FAFCFC",
|
|
4475
|
+
surfaceHover: "#EDF3F2",
|
|
4476
|
+
canvas: "#F4F7F8",
|
|
4477
|
+
panel: "#FFFFFF",
|
|
4478
|
+
borderStrong: "#B9D1CF",
|
|
4479
|
+
textDim: "#6A7B85"
|
|
3304
4480
|
};
|
|
3305
4481
|
|
|
3306
4482
|
export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, cleanPreset, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, squaredrDarkPreset, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo };
|