@squaredr/fieldcraft-pro 1.3.0 → 1.5.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/index.js CHANGED
@@ -8,6 +8,8 @@ var fieldcraftReact = require('@squaredr/fieldcraft-react');
8
8
  var fieldcraftCore = require('@squaredr/fieldcraft-core');
9
9
  var clsx = require('clsx');
10
10
  var tailwindMerge = require('tailwind-merge');
11
+ var react$1 = require('@xyflow/react');
12
+ require('@xyflow/react/dist/style.css');
11
13
 
12
14
  // ../license/dist/index.mjs
13
15
  var LEGACY_TIER_MAP = {
@@ -555,19 +557,19 @@ function UnlicensedOverlay({ featureName, reason, children }) {
555
557
  )
556
558
  ] });
557
559
  }
558
- function requireLicense(Component2, featureName) {
560
+ function requireLicense(Component3, featureName) {
559
561
  function LicenseGated(props) {
560
562
  const { status, tier } = useLicense();
561
563
  const isProduction = isProductionEnvironment();
562
564
  if (!isProduction) {
563
- return /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props });
565
+ return /* @__PURE__ */ jsxRuntime.jsx(Component3, { ...props });
564
566
  }
565
567
  if (status === "validating") {
566
568
  return null;
567
569
  }
568
570
  const isLicensed = status === "valid" && tier != null && tierHasFeature(tier, featureName);
569
571
  if (isLicensed) {
570
- return /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props });
572
+ return /* @__PURE__ */ jsxRuntime.jsx(Component3, { ...props });
571
573
  }
572
574
  let reason = "A valid license is required for production use.";
573
575
  if (status === "invalid") {
@@ -581,9 +583,9 @@ function requireLicense(Component2, featureName) {
581
583
  } else if (!tier || !tierHasFeature(tier, featureName)) {
582
584
  reason = "Your license does not include this feature. Please upgrade your plan.";
583
585
  }
584
- return /* @__PURE__ */ jsxRuntime.jsx(UnlicensedOverlay, { featureName, reason, children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props }) });
586
+ return /* @__PURE__ */ jsxRuntime.jsx(UnlicensedOverlay, { featureName, reason, children: /* @__PURE__ */ jsxRuntime.jsx(Component3, { ...props }) });
585
587
  }
586
- LicenseGated.displayName = `requireLicense(${Component2.displayName || Component2.name || "Component"})`;
588
+ LicenseGated.displayName = `requireLicense(${Component3.displayName || Component3.name || "Component"})`;
587
589
  return LicenseGated;
588
590
  }
589
591
  var MAX_HISTORY = 50;
@@ -1030,6 +1032,14 @@ var QUESTION_TYPE_INFO = {
1030
1032
  icon: "Phone",
1031
1033
  description: "US phone number input"
1032
1034
  },
1035
+ phone_international: {
1036
+ type: "phone_international",
1037
+ label: "Intl Phone",
1038
+ category: "text",
1039
+ icon: "Globe2",
1040
+ description: "Phone with country code",
1041
+ defaultConfig: { type: "phone_international", defaultCountry: "US" }
1042
+ },
1033
1043
  url: {
1034
1044
  type: "url",
1035
1045
  label: "URL",
@@ -1123,6 +1133,14 @@ var QUESTION_TYPE_INFO = {
1123
1133
  requiresOptions: true,
1124
1134
  defaultConfig: { type: "ranking", items: [] }
1125
1135
  },
1136
+ country_select: {
1137
+ type: "country_select",
1138
+ label: "Country",
1139
+ category: "selection",
1140
+ icon: "Globe",
1141
+ description: "Country dropdown with search",
1142
+ defaultConfig: { type: "country_select" }
1143
+ },
1126
1144
  // ── Date/Time ──
1127
1145
  date: {
1128
1146
  type: "date",
@@ -1149,7 +1167,97 @@ var QUESTION_TYPE_INFO = {
1149
1167
  description: "Upload files",
1150
1168
  defaultConfig: { type: "file_upload", maxFiles: 1, maxSizeMb: 10 }
1151
1169
  },
1170
+ // ── Compound ──
1171
+ legal_name: {
1172
+ type: "legal_name",
1173
+ label: "Legal Name",
1174
+ category: "text",
1175
+ icon: "UserCheck",
1176
+ description: "First, middle, and last name",
1177
+ defaultConfig: { type: "legal_name" }
1178
+ },
1179
+ address: {
1180
+ type: "address",
1181
+ label: "Address",
1182
+ category: "text",
1183
+ icon: "MapPin",
1184
+ description: "Street, city, state, ZIP",
1185
+ defaultConfig: { type: "address", includeCountry: false }
1186
+ },
1187
+ signature: {
1188
+ type: "signature",
1189
+ label: "Signature",
1190
+ category: "media",
1191
+ icon: "PenTool",
1192
+ description: "Canvas signature pad",
1193
+ defaultConfig: { type: "signature" }
1194
+ },
1195
+ image_capture: {
1196
+ type: "image_capture",
1197
+ label: "Camera",
1198
+ category: "media",
1199
+ icon: "Camera",
1200
+ description: "Capture photo from camera",
1201
+ defaultConfig: { type: "image_capture", allowGallery: true }
1202
+ },
1203
+ date_range: {
1204
+ type: "date_range",
1205
+ label: "Date Range",
1206
+ category: "datetime",
1207
+ icon: "CalendarRange",
1208
+ description: "Start and end date picker",
1209
+ defaultConfig: { type: "date_range" }
1210
+ },
1211
+ appointment: {
1212
+ type: "appointment",
1213
+ label: "Appointment",
1214
+ category: "datetime",
1215
+ icon: "CalendarCheck",
1216
+ description: "Date & time slot booking",
1217
+ defaultConfig: { type: "appointment", duration: 30 }
1218
+ },
1152
1219
  // ── Advanced ──
1220
+ repeater: {
1221
+ type: "repeater",
1222
+ label: "Repeater",
1223
+ category: "advanced",
1224
+ icon: "ListPlus",
1225
+ description: "Repeatable group of fields",
1226
+ defaultConfig: { type: "repeater", fields: [], minItems: 1, maxItems: 10 }
1227
+ },
1228
+ likert: {
1229
+ type: "likert",
1230
+ label: "Likert Scale",
1231
+ category: "advanced",
1232
+ icon: "AlignHorizontalSpaceAround",
1233
+ description: "Agreement scale (e.g. Strongly Disagree to Strongly Agree)",
1234
+ requiresOptions: true,
1235
+ defaultConfig: {
1236
+ type: "likert",
1237
+ scale: [
1238
+ { label: "Strongly Disagree", value: "1" },
1239
+ { label: "Disagree", value: "2" },
1240
+ { label: "Neutral", value: "3" },
1241
+ { label: "Agree", value: "4" },
1242
+ { label: "Strongly Agree", value: "5" }
1243
+ ]
1244
+ }
1245
+ },
1246
+ scoring: {
1247
+ type: "scoring",
1248
+ label: "Scoring",
1249
+ category: "advanced",
1250
+ icon: "Award",
1251
+ description: "Scored question with point values per option",
1252
+ requiresOptions: true,
1253
+ defaultConfig: {
1254
+ type: "scoring",
1255
+ options: [
1256
+ { label: "Option 1", value: "1", score: 1 },
1257
+ { label: "Option 2", value: "2", score: 2 }
1258
+ ]
1259
+ }
1260
+ },
1153
1261
  matrix: {
1154
1262
  type: "matrix",
1155
1263
  label: "Matrix",
@@ -1179,6 +1287,14 @@ var QUESTION_TYPE_INFO = {
1179
1287
  description: "Hidden value from URL or static",
1180
1288
  defaultConfig: { type: "hidden", source: "static" }
1181
1289
  },
1290
+ payment: {
1291
+ type: "payment",
1292
+ label: "Payment",
1293
+ category: "advanced",
1294
+ icon: "CreditCard",
1295
+ description: "Collect payment via Stripe",
1296
+ defaultConfig: { type: "payment", provider: "stripe", publicKey: "", currency: "USD" }
1297
+ },
1182
1298
  // ── Structural ──
1183
1299
  section_header: {
1184
1300
  type: "section_header",
@@ -1283,7 +1399,7 @@ var DEFAULT_PALETTE = [
1283
1399
  {
1284
1400
  category: "text",
1285
1401
  label: "Text Input",
1286
- types: ["short_text", "long_text", "email", "phone", "url"]
1402
+ types: ["short_text", "long_text", "email", "phone", "phone_international", "url", "legal_name", "address"]
1287
1403
  },
1288
1404
  {
1289
1405
  category: "numeric",
@@ -1293,17 +1409,17 @@ var DEFAULT_PALETTE = [
1293
1409
  {
1294
1410
  category: "selection",
1295
1411
  label: "Selection",
1296
- types: ["single_select", "multi_select", "dropdown", "boolean", "ranking"]
1412
+ types: ["single_select", "multi_select", "dropdown", "boolean", "ranking", "country_select"]
1297
1413
  },
1298
1414
  {
1299
1415
  category: "datetime",
1300
1416
  label: "Date & Time",
1301
- types: ["date", "time"]
1417
+ types: ["date", "time", "date_range", "appointment"]
1302
1418
  },
1303
1419
  {
1304
1420
  category: "media",
1305
1421
  label: "Media",
1306
- types: ["file_upload"]
1422
+ types: ["file_upload", "signature", "image_capture"]
1307
1423
  },
1308
1424
  {
1309
1425
  category: "content",
@@ -1318,7 +1434,7 @@ var DEFAULT_PALETTE = [
1318
1434
  {
1319
1435
  category: "advanced",
1320
1436
  label: "Advanced",
1321
- types: ["matrix", "calculated", "hidden"]
1437
+ types: ["repeater", "likert", "scoring", "matrix", "calculated", "hidden", "payment"]
1322
1438
  }
1323
1439
  ];
1324
1440
 
@@ -2835,7 +2951,8 @@ function getFieldOptions(schema, excludeId) {
2835
2951
  return fields;
2836
2952
  }
2837
2953
  function ConditionEditor({ question, schema, onUpdate }) {
2838
- const showIf = question.showIf;
2954
+ const rawShowIf = question.showIf;
2955
+ const showIf = rawShowIf && rawShowIf.field && !rawShowIf.conditions ? { combine: "AND", conditions: [rawShowIf] } : rawShowIf;
2839
2956
  const fieldOptions = getFieldOptions(schema, question.id);
2840
2957
  const updateShowIf = (next) => {
2841
2958
  onUpdate({ showIf: next });
@@ -2957,6 +3074,24 @@ function ConditionEditor({ question, schema, onUpdate }) {
2957
3074
  }
2958
3075
  function FormSettingsPanel({ schema, onUpdate }) {
2959
3076
  const settings = schema.settings ?? {};
3077
+ const hasConditions = schema.sections.some(
3078
+ (s) => s.showIf || s.questions.some((q) => q.showIf)
3079
+ );
3080
+ const displayModeOptions = hasConditions ? [
3081
+ { label: "Stepped", value: "stepped" },
3082
+ { label: "Conversational", value: "conversational" }
3083
+ ] : [
3084
+ { label: "Stepped", value: "stepped" },
3085
+ { label: "Classic", value: "classic" },
3086
+ { label: "Conversational", value: "conversational" }
3087
+ ];
3088
+ const displayMode = settings.displayMode ?? "stepped";
3089
+ const effectiveDisplayMode = hasConditions && displayMode === "classic" ? "stepped" : displayMode;
3090
+ react.useEffect(() => {
3091
+ if (effectiveDisplayMode !== displayMode) {
3092
+ onUpdate({ ...schema, settings: { ...settings, displayMode: effectiveDisplayMode } });
3093
+ }
3094
+ }, [effectiveDisplayMode, displayMode]);
2960
3095
  const updateSettings = (updates) => {
2961
3096
  onUpdate({ ...schema, settings: { ...settings, ...updates } });
2962
3097
  };
@@ -2984,12 +3119,8 @@ function FormSettingsPanel({ schema, onUpdate }) {
2984
3119
  SettingsSelect,
2985
3120
  {
2986
3121
  label: "Mode",
2987
- value: settings.displayMode ?? "classic",
2988
- options: [
2989
- { label: "Classic", value: "classic" },
2990
- { label: "Stepped", value: "stepped" },
2991
- { label: "Conversational", value: "conversational" }
2992
- ],
3122
+ value: effectiveDisplayMode,
3123
+ options: displayModeOptions,
2993
3124
  onChange: (v) => updateSettings({ displayMode: v })
2994
3125
  }
2995
3126
  ),
@@ -3367,206 +3498,1147 @@ function FieldGroup({ label, children }) {
3367
3498
  children
3368
3499
  ] });
3369
3500
  }
3370
- var ThemeCtx = react.createContext({});
3371
- function useBuilderTheme() {
3372
- return react.useContext(ThemeCtx);
3373
- }
3374
- function themeToCssVars(theme) {
3375
- const vars = {};
3376
- if (theme.background) vars["--background"] = theme.background;
3377
- if (theme.foreground) vars["--foreground"] = theme.foreground;
3378
- if (theme.card) {
3379
- vars["--card"] = theme.card;
3380
- vars["--card-foreground"] = theme.foreground ?? "";
3381
- vars["--popover"] = theme.card;
3382
- vars["--popover-foreground"] = theme.foreground ?? "";
3383
- }
3384
- if (theme.primary) vars["--primary"] = theme.primary;
3385
- if (theme.primaryForeground) vars["--primary-foreground"] = theme.primaryForeground;
3386
- if (theme.secondary) vars["--secondary"] = theme.secondary;
3387
- if (theme.secondaryForeground) vars["--secondary-foreground"] = theme.secondaryForeground;
3388
- if (theme.muted) vars["--muted"] = theme.muted;
3389
- if (theme.mutedForeground) vars["--muted-foreground"] = theme.mutedForeground;
3390
- if (theme.accent) vars["--accent"] = theme.accent;
3391
- if (theme.accentForeground) vars["--accent-foreground"] = theme.accentForeground;
3392
- if (theme.destructive) vars["--destructive"] = theme.destructive;
3393
- if (theme.destructiveForeground) vars["--destructive-foreground"] = theme.destructiveForeground;
3394
- if (theme.border) vars["--border"] = theme.border;
3395
- if (theme.input) vars["--input"] = theme.input;
3396
- if (theme.ring) vars["--ring"] = theme.ring;
3397
- if (theme.radius) vars["--radius"] = theme.radius;
3398
- if (theme.surface) vars["--fcb-surface"] = theme.surface;
3399
- if (theme.surfaceHover) vars["--fcb-surface-hover"] = theme.surfaceHover;
3400
- if (theme.canvas) vars["--fcb-canvas"] = theme.canvas;
3401
- if (theme.panel) vars["--fcb-panel"] = theme.panel;
3402
- if (theme.borderStrong) vars["--fcb-border-strong"] = theme.borderStrong;
3403
- if (theme.textDim) vars["--fcb-text-dim"] = theme.textDim;
3404
- return vars;
3405
- }
3406
- function FormBuilderThemeProvider({ theme, children }) {
3407
- const resolved = theme ?? {};
3408
- const cssVars = react.useMemo(() => themeToCssVars(resolved), [resolved]);
3409
- return /* @__PURE__ */ jsxRuntime.jsx(ThemeCtx.Provider, { value: resolved, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fcb-root": "", style: cssVars, className: "w-full h-full", children }) });
3410
- }
3411
- var FormBuilderErrorBoundary = class extends react.Component {
3412
- constructor(props) {
3413
- super(props);
3414
- this.state = { hasError: false, error: null };
3415
- }
3501
+ var PreviewErrorBoundary = class extends react.Component {
3502
+ state = { error: null };
3416
3503
  static getDerivedStateFromError(error) {
3417
- return { hasError: true, error };
3504
+ return { error };
3418
3505
  }
3419
3506
  componentDidCatch(error, info) {
3420
- console.error("[FormBuilder] Render error:", error, info.componentStack);
3507
+ console.error("[FormBuilder Preview]", error, info);
3508
+ }
3509
+ componentDidUpdate(prevProps) {
3510
+ if (prevProps.resetKey !== this.props.resetKey && this.state.error) {
3511
+ this.setState({ error: null });
3512
+ }
3421
3513
  }
3422
3514
  render() {
3423
- if (this.state.hasError) {
3424
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center justify-center h-full bg-background text-foreground p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-md text-center space-y-4", children: [
3425
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: "Something went wrong" }),
3426
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "The form builder encountered an unexpected error. Your schema data is preserved." }),
3427
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-destructive bg-destructive/10 rounded-md p-3 text-left overflow-auto max-h-32", children: this.state.error?.message }),
3428
- /* @__PURE__ */ jsxRuntime.jsx(
3429
- fieldcraftReact.Button,
3430
- {
3431
- onClick: () => this.setState({ hasError: false, error: null }),
3432
- variant: "outline",
3433
- children: "Try Again"
3434
- }
3435
- )
3436
- ] }) });
3515
+ if (this.state.error) {
3516
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-destructive text-sm font-mono", children: [
3517
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Render Error:" }),
3518
+ " ",
3519
+ this.state.error.message
3520
+ ] });
3437
3521
  }
3438
3522
  return this.props.children;
3439
3523
  }
3440
3524
  };
3441
- function FormBuilderCore(props) {
3442
- const { initialSchema = DEFAULT_SCHEMA, onChange, onSave, height = "100vh", theme, className, toolbarExtra, questionTypes, palette } = props;
3443
- const mergedQuestionTypes = questionTypes ? { ...QUESTION_TYPE_INFO, ...questionTypes } : QUESTION_TYPE_INFO;
3444
- const builderState = useBuilderState(initialSchema);
3445
- const dragDrop = useDragDrop(builderState);
3446
- const fileInputRef = react.useRef(null);
3447
- const [mobilePanel, setMobilePanel] = react.useState("none");
3448
- react.useEffect(() => {
3449
- if (builderState.selectedItem && window.innerWidth < 768) {
3450
- setMobilePanel("properties");
3451
- }
3452
- }, [builderState.selectedItem]);
3453
- react.useEffect(() => {
3454
- if (!dragDrop.activeDragItem && mobilePanel === "palette") {
3455
- setMobilePanel("none");
3525
+ function FormPreviewPanel({ schema, preview }) {
3526
+ const hasSections = schema.sections && schema.sections.length > 0;
3527
+ const hasFields = hasSections && schema.sections.some((s) => s.questions.length > 0);
3528
+ if (!hasFields) {
3529
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center space-y-2", children: [
3530
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "No fields to preview" }),
3531
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs", children: "Add fields in the Design view to see a live preview here." })
3532
+ ] }) });
3533
+ }
3534
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-2xl mx-auto py-8 px-4", children: /* @__PURE__ */ jsxRuntime.jsx(PreviewErrorBoundary, { resetKey: schema.id + (schema.version ?? ""), children: /* @__PURE__ */ jsxRuntime.jsx(
3535
+ fieldcraftReact.FormEngineRenderer,
3536
+ {
3537
+ schema,
3538
+ theme: preview?.theme,
3539
+ components: preview?.components,
3540
+ onReady: preview?.onReady,
3541
+ onFieldChange: preview?.onFieldChange,
3542
+ onSubmit: preview?.onSubmit ?? (() => {
3543
+ })
3456
3544
  }
3457
- }, [dragDrop.activeDragItem]);
3545
+ ) }) }) });
3546
+ }
3547
+
3548
+ // src/form-builder/utils/validation-markers.ts
3549
+ var MarkerSeverity = {
3550
+ Error: 8};
3551
+ function errorsToMarkers(errors) {
3552
+ return errors.map((error) => ({
3553
+ severity: MarkerSeverity.Error,
3554
+ message: error.message,
3555
+ startLineNumber: error.line ?? 1,
3556
+ startColumn: error.column ?? 1,
3557
+ endLineNumber: error.endLine ?? error.line ?? 1,
3558
+ endColumn: error.endColumn ?? error.column ?? 1,
3559
+ source: "FieldCraft Schema Validator"
3560
+ }));
3561
+ }
3562
+ var Editor = react.lazy(() => import('@monaco-editor/react').then((m) => ({ default: m.default })));
3563
+ function MonacoWrapper({ value, onChange, errors }) {
3564
+ const editorRef = react.useRef(null);
3565
+ const monacoRef = react.useRef(null);
3566
+ const handleMount = (editor, monaco) => {
3567
+ editorRef.current = editor;
3568
+ monacoRef.current = monaco;
3569
+ };
3458
3570
  react.useEffect(() => {
3459
- if (onChange && builderState.isDirty) {
3460
- onChange(builderState.schema);
3461
- }
3462
- }, [builderState.schema, builderState.isDirty, onChange]);
3463
- const handleSave = react.useCallback(() => {
3464
- if (onSave) {
3465
- onSave(builderState.schema);
3466
- builderState.markClean();
3571
+ const editor = editorRef.current;
3572
+ const monaco = monacoRef.current;
3573
+ if (!editor || !monaco) return;
3574
+ const model = editor.getModel();
3575
+ if (!model) return;
3576
+ const markers = errorsToMarkers(errors);
3577
+ monaco.editor.setModelMarkers(model, "fieldcraft-schema", markers);
3578
+ }, [errors]);
3579
+ return /* @__PURE__ */ jsxRuntime.jsx(
3580
+ Editor,
3581
+ {
3582
+ height: "100%",
3583
+ language: "json",
3584
+ theme: "vs-dark",
3585
+ value,
3586
+ onChange: (val) => onChange(val ?? ""),
3587
+ onMount: handleMount,
3588
+ options: {
3589
+ minimap: { enabled: false },
3590
+ fontSize: 13,
3591
+ wordWrap: "on",
3592
+ formatOnPaste: true,
3593
+ automaticLayout: true,
3594
+ tabSize: 2,
3595
+ scrollBeyondLastLine: false,
3596
+ padding: { top: 12 }
3597
+ }
3467
3598
  }
3468
- }, [onSave, builderState]);
3469
- const handleExport = react.useCallback(() => {
3470
- const json = JSON.stringify(builderState.schema, null, 2);
3471
- const blob = new Blob([json], { type: "application/json" });
3472
- const url = URL.createObjectURL(blob);
3473
- const a = document.createElement("a");
3474
- a.href = url;
3475
- a.download = `${builderState.schema.title?.replace(/\s+/g, "-").toLowerCase() || "form"}-schema.json`;
3476
- a.click();
3477
- URL.revokeObjectURL(url);
3478
- }, [builderState.schema]);
3479
- const handleImport = react.useCallback(() => {
3480
- fileInputRef.current?.click();
3481
- }, []);
3482
- const handleFileChange = react.useCallback(
3483
- (e) => {
3484
- const file = e.target.files?.[0];
3485
- if (!file) return;
3486
- const reader = new FileReader();
3487
- reader.onload = (event) => {
3488
- try {
3489
- const parsed = JSON.parse(event.target?.result);
3490
- const validated = fieldcraftCore.validateSchema(parsed);
3491
- builderState.resetSchema(validated);
3492
- } catch (err) {
3493
- if (err instanceof fieldcraftCore.FormEngineSchemaError) {
3494
- const details = err.issues.slice(0, 3).map((i) => `\u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
3495
- alert(`Invalid schema:
3496
- ${details}`);
3497
- } else if (err instanceof SyntaxError) {
3498
- alert("Failed to parse JSON file.");
3499
- } else {
3500
- alert("Invalid schema file.");
3501
- }
3502
- }
3503
- };
3504
- reader.readAsText(file);
3505
- e.target.value = "";
3599
+ );
3600
+ }
3601
+ function JsonEditorPanel({ value, onChange, errors, onValidate }) {
3602
+ const handleChange = react.useCallback(
3603
+ (newValue) => {
3604
+ onChange(newValue);
3605
+ onValidate(newValue);
3506
3606
  },
3507
- [builderState]
3607
+ [onChange, onValidate]
3508
3608
  );
3509
- const handleKeyDown = react.useCallback(
3510
- (e) => {
3511
- const mod = e.metaKey || e.ctrlKey;
3512
- const tag = e.target.tagName;
3513
- const isEditing = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
3514
- if (mod && e.key === "z" && !e.shiftKey) {
3515
- e.preventDefault();
3516
- builderState.undo();
3517
- return;
3518
- }
3519
- if (mod && e.key === "z" && e.shiftKey) {
3520
- e.preventDefault();
3521
- builderState.redo();
3522
- return;
3609
+ const handleFormat = react.useCallback(() => {
3610
+ try {
3611
+ const formatted = JSON.stringify(JSON.parse(value), null, 2);
3612
+ onChange(formatted);
3613
+ } catch {
3614
+ }
3615
+ }, [value, onChange]);
3616
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
3617
+ errors.length > 0 && /* @__PURE__ */ jsxRuntime.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: [
3618
+ errors.slice(0, 3).map((e, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3619
+ e.line ? `Line ${e.line}: ` : "",
3620
+ e.path ? `${e.path}: ` : "",
3621
+ e.message
3622
+ ] }, i)),
3623
+ errors.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-muted-foreground", children: [
3624
+ "...and ",
3625
+ errors.length - 3,
3626
+ " more"
3627
+ ] })
3628
+ ] }),
3629
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 flex items-center justify-between px-3 py-1.5 bg-card border-b border-border text-xs", children: [
3630
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: errors.length === 0 ? "Valid schema" : `${errors.length} error${errors.length !== 1 ? "s" : ""}` }),
3631
+ /* @__PURE__ */ jsxRuntime.jsx(
3632
+ "button",
3633
+ {
3634
+ type: "button",
3635
+ onClick: handleFormat,
3636
+ className: "text-primary hover:underline",
3637
+ children: "Format JSON"
3638
+ }
3639
+ )
3640
+ ] }),
3641
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
3642
+ react.Suspense,
3643
+ {
3644
+ fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-muted-foreground", children: "Loading editor..." }),
3645
+ children: /* @__PURE__ */ jsxRuntime.jsx(MonacoWrapper, { value, onChange: handleChange, errors })
3523
3646
  }
3524
- if (mod && e.key === "s") {
3525
- e.preventDefault();
3526
- handleSave();
3527
- return;
3647
+ ) })
3648
+ ] });
3649
+ }
3650
+
3651
+ // src/form-builder/utils/logic-graph.ts
3652
+ function extractFieldRefs(expr) {
3653
+ const refs = [];
3654
+ if (expr.field) {
3655
+ refs.push({ field: expr.field, operator: expr.operator, value: expr.value });
3656
+ }
3657
+ if (expr.conditions) {
3658
+ for (const child of expr.conditions) {
3659
+ refs.push(...extractFieldRefs(child));
3660
+ }
3661
+ }
3662
+ return refs;
3663
+ }
3664
+ function buildLogicGraph(schema) {
3665
+ const nodes = [];
3666
+ const edges = [];
3667
+ const nodeIds = /* @__PURE__ */ new Set();
3668
+ for (const section of schema.sections) {
3669
+ nodes.push({
3670
+ id: section.id,
3671
+ label: section.title || "Untitled Section",
3672
+ type: "section",
3673
+ sectionId: section.id,
3674
+ sectionTitle: section.title || "Untitled Section",
3675
+ hasCondition: !!section.showIf
3676
+ });
3677
+ nodeIds.add(section.id);
3678
+ if (section.showIf) {
3679
+ const refs = extractFieldRefs(section.showIf);
3680
+ for (const ref of refs) {
3681
+ edges.push({ from: ref.field, to: section.id, edgeType: "showIf", operator: ref.operator, value: ref.value });
3528
3682
  }
3529
- if (isEditing) return;
3530
- if (e.key === "Delete" || e.key === "Backspace") {
3531
- const sel = builderState.selectedItem;
3532
- if (!sel) return;
3533
- e.preventDefault();
3534
- if (sel.type === "question") {
3535
- builderState.removeQuestion(sel.sectionId, sel.questionId);
3536
- } else if (sel.type === "section") {
3537
- builderState.removeSection(sel.sectionId);
3683
+ }
3684
+ if (section.onExit) {
3685
+ for (const rule of section.onExit.rules) {
3686
+ if (rule.jumpTo) {
3687
+ edges.push({ from: section.id, to: rule.jumpTo, edgeType: "onExit", jumpTo: rule.jumpTo });
3538
3688
  }
3539
- return;
3540
3689
  }
3541
- if (e.key === "Escape") {
3542
- if (builderState.selectedItem) {
3543
- e.preventDefault();
3544
- builderState.clearSelection();
3545
- }
3546
- return;
3690
+ if (section.onExit.default) {
3691
+ edges.push({ from: section.id, to: section.onExit.default, edgeType: "onExit", jumpTo: section.onExit.default });
3547
3692
  }
3548
- if (mod && e.key === "d") {
3549
- const sel = builderState.selectedItem;
3550
- if (!sel) return;
3551
- e.preventDefault();
3552
- if (sel.type === "question") {
3553
- builderState.duplicateQuestion(sel.sectionId, sel.questionId);
3554
- } else if (sel.type === "section") {
3555
- builderState.duplicateSection(sel.sectionId);
3693
+ }
3694
+ for (const question of section.questions) {
3695
+ nodes.push({
3696
+ id: question.id,
3697
+ label: question.label || question.id,
3698
+ type: "field",
3699
+ fieldType: question.type,
3700
+ sectionId: section.id,
3701
+ sectionTitle: section.title || "Untitled Section",
3702
+ hasCondition: !!question.showIf
3703
+ });
3704
+ nodeIds.add(question.id);
3705
+ if (question.showIf) {
3706
+ const refs = extractFieldRefs(question.showIf);
3707
+ for (const ref of refs) {
3708
+ edges.push({ from: ref.field, to: question.id, edgeType: "showIf", operator: ref.operator, value: ref.value });
3556
3709
  }
3557
3710
  }
3558
- },
3559
- [builderState, handleSave]
3560
- );
3561
- return /* @__PURE__ */ jsxRuntime.jsx(FormBuilderThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsxs(
3562
- core.DndContext,
3563
- {
3564
- sensors: dragDrop.sensors,
3565
- onDragStart: dragDrop.handleDragStart,
3566
- onDragEnd: dragDrop.handleDragEnd,
3567
- onDragCancel: dragDrop.handleDragCancel,
3568
- children: [
3569
- /* @__PURE__ */ jsxRuntime.jsxs(
3711
+ }
3712
+ }
3713
+ const validEdges = edges.filter((e) => nodeIds.has(e.from) && nodeIds.has(e.to));
3714
+ return { nodes, edges: validEdges };
3715
+ }
3716
+ var SECTION_COLORS = [
3717
+ "#3b82f6",
3718
+ // blue
3719
+ "#8b5cf6",
3720
+ // violet
3721
+ "#06b6d4",
3722
+ // cyan
3723
+ "#f59e0b",
3724
+ // amber
3725
+ "#10b981",
3726
+ // emerald
3727
+ "#ef4444",
3728
+ // red
3729
+ "#ec4899",
3730
+ // pink
3731
+ "#6366f1"
3732
+ // indigo
3733
+ ];
3734
+ var FIELD_NODE_WIDTH = 220;
3735
+ var FIELD_NODE_HEIGHT = 52;
3736
+ var SECTION_HEADER_HEIGHT = 40;
3737
+ var SECTION_PADDING_X = 16;
3738
+ var SECTION_PADDING_TOP = 12;
3739
+ var SECTION_PADDING_BOTTOM = 16;
3740
+ var FIELD_GAP = 10;
3741
+ var SECTION_GAP = 80;
3742
+ function formatEdgeLabel(edge) {
3743
+ if (edge.edgeType === "onExit") return "jump";
3744
+ if (!edge.operator) return "";
3745
+ const op = edge.operator;
3746
+ const val = edge.value;
3747
+ if (op === "exists") return "has value";
3748
+ if (op === "notExists") return "is empty";
3749
+ if (val === void 0 || val === null || val === "") return op;
3750
+ const short = String(val).length > 12 ? String(val).slice(0, 12) + "\u2026" : String(val);
3751
+ return `${op} ${short}`;
3752
+ }
3753
+ function buildReactFlowGraph(schema) {
3754
+ const graph = buildLogicGraph(schema);
3755
+ const nodes = [];
3756
+ const edges = [];
3757
+ let sectionX = 0;
3758
+ let maxSectionHeight = 0;
3759
+ for (const section of schema.sections) {
3760
+ const fieldCount = section.questions.length;
3761
+ const contentHeight = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + fieldCount * FIELD_NODE_HEIGHT + Math.max(0, fieldCount - 1) * FIELD_GAP + SECTION_PADDING_BOTTOM;
3762
+ if (contentHeight > maxSectionHeight) maxSectionHeight = contentHeight;
3763
+ }
3764
+ if (maxSectionHeight < SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + SECTION_PADDING_BOTTOM + FIELD_NODE_HEIGHT) {
3765
+ maxSectionHeight = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + SECTION_PADDING_BOTTOM + FIELD_NODE_HEIGHT;
3766
+ }
3767
+ const sectionWidth = FIELD_NODE_WIDTH + SECTION_PADDING_X * 2;
3768
+ for (let sIdx = 0; sIdx < schema.sections.length; sIdx++) {
3769
+ const section = schema.sections[sIdx];
3770
+ const colorIndex = sIdx;
3771
+ nodes.push({
3772
+ id: section.id,
3773
+ type: "sectionNode",
3774
+ position: { x: sectionX, y: 0 },
3775
+ data: {
3776
+ label: section.title || "Untitled Section",
3777
+ sectionId: section.id,
3778
+ fieldCount: section.questions.length,
3779
+ hasCondition: !!section.showIf,
3780
+ hasExitLogic: !!section.onExit,
3781
+ colorIndex
3782
+ },
3783
+ style: { width: sectionWidth, height: maxSectionHeight }
3784
+ });
3785
+ for (let fIdx = 0; fIdx < section.questions.length; fIdx++) {
3786
+ const question = section.questions[fIdx];
3787
+ const fieldY = SECTION_HEADER_HEIGHT + SECTION_PADDING_TOP + fIdx * (FIELD_NODE_HEIGHT + FIELD_GAP);
3788
+ nodes.push({
3789
+ id: question.id,
3790
+ type: "fieldNode",
3791
+ position: { x: SECTION_PADDING_X, y: fieldY },
3792
+ parentId: section.id,
3793
+ extent: "parent",
3794
+ data: {
3795
+ label: question.label || question.id,
3796
+ fieldType: question.type,
3797
+ sectionId: section.id,
3798
+ hasCondition: !!question.showIf,
3799
+ colorIndex
3800
+ },
3801
+ style: { width: FIELD_NODE_WIDTH }
3802
+ });
3803
+ }
3804
+ if (sIdx < schema.sections.length - 1) {
3805
+ const nextSection = schema.sections[sIdx + 1];
3806
+ edges.push({
3807
+ id: `pipeline-${section.id}-${nextSection.id}`,
3808
+ source: section.id,
3809
+ target: nextSection.id,
3810
+ type: "pipelineEdge",
3811
+ data: { edgeType: "pipeline" }
3812
+ });
3813
+ }
3814
+ sectionX += sectionWidth + SECTION_GAP;
3815
+ }
3816
+ for (let i = 0; i < graph.edges.length; i++) {
3817
+ const edge = graph.edges[i];
3818
+ edges.push({
3819
+ id: `e-${edge.from}-${edge.to}-${i}`,
3820
+ source: edge.from,
3821
+ target: edge.to,
3822
+ type: "conditionEdge",
3823
+ animated: edge.edgeType === "showIf",
3824
+ style: edge.edgeType === "onExit" ? { strokeDasharray: "6 3", stroke: "#f59e0b" } : void 0,
3825
+ label: formatEdgeLabel(edge),
3826
+ data: {
3827
+ edgeType: edge.edgeType,
3828
+ operator: edge.operator,
3829
+ value: edge.value,
3830
+ sourceId: edge.from,
3831
+ targetId: edge.to
3832
+ }
3833
+ });
3834
+ }
3835
+ return { nodes, edges };
3836
+ }
3837
+ function SectionNodeInner({ data }) {
3838
+ const d = data;
3839
+ const color = SECTION_COLORS[d.colorIndex % SECTION_COLORS.length];
3840
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-flow-section-group", style: { borderColor: color }, children: [
3841
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "target", position: react$1.Position.Left, className: "fc-flow-handle fc-flow-handle--section" }),
3842
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-flow-section-group__header", style: { background: color }, children: [
3843
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fc-flow-section-group__label", children: d.label.length > 30 ? d.label.slice(0, 30) + "\u2026" : d.label }),
3844
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fc-flow-section-group__meta", children: [
3845
+ d.fieldCount,
3846
+ " field",
3847
+ d.fieldCount !== 1 ? "s" : "",
3848
+ d.hasCondition && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fc-flow-section-group__badge", title: "Has showIf condition", children: "?" }),
3849
+ d.hasExitLogic && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fc-flow-section-group__badge fc-flow-section-group__badge--jump", title: "Has onExit jump", children: "\u2934" })
3850
+ ] })
3851
+ ] }),
3852
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "source", position: react$1.Position.Right, className: "fc-flow-handle fc-flow-handle--section" })
3853
+ ] });
3854
+ }
3855
+ var SectionNode = react.memo(SectionNodeInner);
3856
+ function FieldNodeInner({ data }) {
3857
+ const d = data;
3858
+ const color = SECTION_COLORS[d.colorIndex % SECTION_COLORS.length];
3859
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3860
+ "div",
3861
+ {
3862
+ className: "fc-flow-field",
3863
+ style: {
3864
+ borderColor: d.hasCondition ? color : void 0,
3865
+ borderWidth: d.hasCondition ? 2 : 1
3866
+ },
3867
+ children: [
3868
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "target", position: react$1.Position.Left, className: "fc-flow-handle" }),
3869
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-flow-field__content", children: [
3870
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fc-flow-field__label", children: d.label.length > 26 ? d.label.slice(0, 26) + "\u2026" : d.label }),
3871
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fc-flow-field__type", children: d.fieldType })
3872
+ ] }),
3873
+ d.hasCondition && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fc-flow-badge", style: { background: color }, title: "Has showIf condition", children: "?" }),
3874
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "source", position: react$1.Position.Right, className: "fc-flow-handle" })
3875
+ ]
3876
+ }
3877
+ );
3878
+ }
3879
+ var FieldNode = react.memo(FieldNodeInner);
3880
+ var logicFlowNodeTypes = {
3881
+ sectionNode: SectionNode,
3882
+ fieldNode: FieldNode
3883
+ };
3884
+ function ConditionEdgeInner({
3885
+ id,
3886
+ sourceX,
3887
+ sourceY,
3888
+ targetX,
3889
+ targetY,
3890
+ sourcePosition,
3891
+ targetPosition,
3892
+ label,
3893
+ style,
3894
+ data,
3895
+ markerEnd
3896
+ }) {
3897
+ const [edgePath, labelX, labelY] = react$1.getBezierPath({
3898
+ sourceX,
3899
+ sourceY,
3900
+ sourcePosition,
3901
+ targetX,
3902
+ targetY,
3903
+ targetPosition
3904
+ });
3905
+ const isJump = data?.edgeType === "onExit";
3906
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3907
+ /* @__PURE__ */ jsxRuntime.jsx(
3908
+ react$1.BaseEdge,
3909
+ {
3910
+ id,
3911
+ path: edgePath,
3912
+ markerEnd,
3913
+ style: {
3914
+ stroke: isJump ? "#f59e0b" : "var(--primary, #3b82f6)",
3915
+ strokeWidth: 1.5,
3916
+ strokeOpacity: 0.7,
3917
+ ...style
3918
+ }
3919
+ }
3920
+ ),
3921
+ label && /* @__PURE__ */ jsxRuntime.jsx(react$1.EdgeLabelRenderer, { children: /* @__PURE__ */ jsxRuntime.jsx(
3922
+ "div",
3923
+ {
3924
+ className: "fc-flow-edge-label",
3925
+ style: {
3926
+ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
3927
+ background: isJump ? "#f59e0b" : "var(--primary, #3b82f6)"
3928
+ },
3929
+ children: label
3930
+ }
3931
+ ) })
3932
+ ] });
3933
+ }
3934
+ var ConditionEdge = react.memo(ConditionEdgeInner);
3935
+ function PipelineEdgeInner({
3936
+ id,
3937
+ sourceX,
3938
+ sourceY,
3939
+ targetX,
3940
+ targetY,
3941
+ sourcePosition,
3942
+ targetPosition,
3943
+ markerEnd
3944
+ }) {
3945
+ const [edgePath] = react$1.getBezierPath({
3946
+ sourceX,
3947
+ sourceY,
3948
+ sourcePosition,
3949
+ targetX,
3950
+ targetY,
3951
+ targetPosition
3952
+ });
3953
+ return /* @__PURE__ */ jsxRuntime.jsx(
3954
+ react$1.BaseEdge,
3955
+ {
3956
+ id,
3957
+ path: edgePath,
3958
+ markerEnd,
3959
+ style: {
3960
+ stroke: "var(--muted-foreground, #71717a)",
3961
+ strokeWidth: 2,
3962
+ strokeOpacity: 0.4
3963
+ }
3964
+ }
3965
+ );
3966
+ }
3967
+ var PipelineEdge = react.memo(PipelineEdgeInner);
3968
+ var logicFlowEdgeTypes = {
3969
+ conditionEdge: ConditionEdge,
3970
+ pipelineEdge: PipelineEdge
3971
+ };
3972
+ function findQuestion2(schema, fieldId) {
3973
+ for (const section of schema.sections) {
3974
+ for (const q of section.questions) {
3975
+ if (q.id === fieldId) return q;
3976
+ }
3977
+ }
3978
+ return void 0;
3979
+ }
3980
+ function updateQuestionInSchema(schema, fieldId, updates) {
3981
+ return {
3982
+ ...schema,
3983
+ sections: schema.sections.map((section) => ({
3984
+ ...section,
3985
+ questions: section.questions.map(
3986
+ (q) => q.id === fieldId ? { ...q, ...updates } : q
3987
+ )
3988
+ }))
3989
+ };
3990
+ }
3991
+ function removeConditionForSource(showIf, sourceFieldId) {
3992
+ if (!showIf) return void 0;
3993
+ if (showIf.field === sourceFieldId) return void 0;
3994
+ if (showIf.field && showIf.field !== sourceFieldId) return showIf;
3995
+ if (showIf.conditions) {
3996
+ const filtered = showIf.conditions.map((c) => removeConditionForSource(c, sourceFieldId)).filter((c) => c !== void 0);
3997
+ if (filtered.length === 0) return void 0;
3998
+ if (filtered.length === 1) return filtered[0];
3999
+ return { ...showIf, conditions: filtered };
4000
+ }
4001
+ return showIf;
4002
+ }
4003
+ var defaultEdgeOptions = {
4004
+ markerEnd: {
4005
+ type: react$1.MarkerType.ArrowClosed,
4006
+ width: 16,
4007
+ height: 12
4008
+ }
4009
+ };
4010
+ function LogicFlowEditor({ schema, onChange, onClose }) {
4011
+ const noSections = schema.sections.length === 0;
4012
+ const flowGraph = react.useMemo(() => buildReactFlowGraph(schema), [schema]);
4013
+ const [nodes, setNodes, onNodesChange] = react$1.useNodesState(flowGraph.nodes);
4014
+ const [edges, setEdges, onEdgesChange] = react$1.useEdgesState(flowGraph.edges);
4015
+ const [selectedFieldId, setSelectedFieldId] = react.useState(null);
4016
+ const selectedQuestion = selectedFieldId ? findQuestion2(schema, selectedFieldId) : void 0;
4017
+ react.useEffect(() => {
4018
+ setNodes(flowGraph.nodes);
4019
+ setEdges(flowGraph.edges);
4020
+ }, [flowGraph, setNodes, setEdges]);
4021
+ const sectionIds = react.useMemo(() => {
4022
+ const ids = [];
4023
+ for (const section of schema.sections) {
4024
+ ids.push(section.id);
4025
+ }
4026
+ return ids;
4027
+ }, [schema]);
4028
+ const onNodeClick = react.useCallback(
4029
+ (_event, node) => {
4030
+ if (node.type === "fieldNode") {
4031
+ setSelectedFieldId(node.id);
4032
+ }
4033
+ },
4034
+ []
4035
+ );
4036
+ const onConnect = react.useCallback(
4037
+ (connection) => {
4038
+ if (!connection.source || !connection.target) return;
4039
+ const targetQuestion = findQuestion2(schema, connection.target);
4040
+ if (!targetQuestion) return;
4041
+ const newCondition = {
4042
+ field: connection.source,
4043
+ operator: "exists"
4044
+ };
4045
+ let updatedShowIf;
4046
+ const existing = targetQuestion.showIf;
4047
+ if (!existing) {
4048
+ updatedShowIf = { combine: "AND", conditions: [newCondition] };
4049
+ } else {
4050
+ updatedShowIf = {
4051
+ ...existing,
4052
+ conditions: [...existing.conditions ?? [], newCondition]
4053
+ };
4054
+ }
4055
+ const updatedSchema = updateQuestionInSchema(schema, connection.target, {
4056
+ showIf: updatedShowIf
4057
+ });
4058
+ onChange(updatedSchema);
4059
+ setSelectedFieldId(connection.target);
4060
+ },
4061
+ [schema, onChange]
4062
+ );
4063
+ const onEdgesDelete = react.useCallback(
4064
+ (deletedEdges) => {
4065
+ let updatedSchema = schema;
4066
+ for (const edge of deletedEdges) {
4067
+ const edgeData = edge.data;
4068
+ if (!edgeData) continue;
4069
+ const targetId = edge.target;
4070
+ const sourceId = edge.source;
4071
+ const edgeType = edgeData.edgeType;
4072
+ if (edgeType === "showIf") {
4073
+ const question = findQuestion2(updatedSchema, targetId);
4074
+ if (!question) continue;
4075
+ const updatedShowIf = removeConditionForSource(
4076
+ question.showIf,
4077
+ sourceId
4078
+ );
4079
+ updatedSchema = updateQuestionInSchema(updatedSchema, targetId, {
4080
+ showIf: updatedShowIf
4081
+ });
4082
+ }
4083
+ }
4084
+ onChange(updatedSchema);
4085
+ },
4086
+ [schema, onChange]
4087
+ );
4088
+ const handleConditionUpdate = react.useCallback(
4089
+ (updates) => {
4090
+ if (!selectedFieldId) return;
4091
+ const updatedSchema = updateQuestionInSchema(schema, selectedFieldId, updates);
4092
+ onChange(updatedSchema);
4093
+ },
4094
+ [schema, selectedFieldId, onChange]
4095
+ );
4096
+ const showIfCount = flowGraph.edges.filter(
4097
+ (e) => e.data?.edgeType === "showIf"
4098
+ ).length;
4099
+ const jumpCount = flowGraph.edges.filter(
4100
+ (e) => e.data?.edgeType === "onExit"
4101
+ ).length;
4102
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex flex-col bg-background/95 backdrop-blur-sm", children: [
4103
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-12 shrink-0 flex items-center justify-between px-4 border-b border-border bg-card", children: [
4104
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
4105
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GitBranch, { size: 16, className: "text-primary" }),
4106
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: "Logic Flow" }),
4107
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
4108
+ showIfCount,
4109
+ " condition",
4110
+ showIfCount !== 1 ? "s" : "",
4111
+ jumpCount > 0 && ` \xB7 ${jumpCount} jump${jumpCount !== 1 ? "s" : ""}`,
4112
+ " \xB7 ",
4113
+ sectionIds.length,
4114
+ " section",
4115
+ sectionIds.length !== 1 ? "s" : ""
4116
+ ] })
4117
+ ] }),
4118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
4119
+ /* @__PURE__ */ jsxRuntime.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" }),
4120
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "icon-sm", onClick: onClose, title: "Close logic flow", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, strokeWidth: 1.75 }) })
4121
+ ] })
4122
+ ] }),
4123
+ noSections ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center space-y-2", children: [
4124
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GitBranch, { size: 32, className: "mx-auto opacity-40" }),
4125
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "No sections found" }),
4126
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs max-w-xs", children: "Add sections and fields to your form to see the logic flow pipeline." })
4127
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex", children: [
4128
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
4129
+ react$1.ReactFlow,
4130
+ {
4131
+ nodes,
4132
+ edges,
4133
+ onNodesChange,
4134
+ onEdgesChange,
4135
+ onNodeClick,
4136
+ onConnect,
4137
+ onEdgesDelete,
4138
+ nodeTypes: logicFlowNodeTypes,
4139
+ edgeTypes: logicFlowEdgeTypes,
4140
+ defaultEdgeOptions,
4141
+ fitView: true,
4142
+ fitViewOptions: { padding: 0.2 },
4143
+ deleteKeyCode: ["Backspace", "Delete"],
4144
+ proOptions: { hideAttribution: true },
4145
+ children: [
4146
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Background, { gap: 20, size: 1 }),
4147
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Controls, { showInteractive: false }),
4148
+ /* @__PURE__ */ jsxRuntime.jsx(
4149
+ react$1.MiniMap,
4150
+ {
4151
+ nodeColor: (node) => {
4152
+ const d = node.data;
4153
+ const idx = d.colorIndex ?? 0;
4154
+ return SECTION_COLORS[idx % SECTION_COLORS.length];
4155
+ },
4156
+ maskColor: "rgba(0, 0, 0, 0.3)"
4157
+ }
4158
+ )
4159
+ ]
4160
+ }
4161
+ ) }),
4162
+ selectedFieldId && selectedQuestion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-72 border-l border-border bg-card overflow-auto", children: [
4163
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-3 border-b border-border flex items-center justify-between", children: [
4164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4165
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold truncate", children: selectedQuestion.label || selectedQuestion.id }),
4166
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground", children: selectedQuestion.type })
4167
+ ] }),
4168
+ /* @__PURE__ */ jsxRuntime.jsx(
4169
+ fieldcraftReact.Button,
4170
+ {
4171
+ variant: "ghost",
4172
+ size: "icon-xs",
4173
+ onClick: () => setSelectedFieldId(null),
4174
+ title: "Close panel",
4175
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 14, strokeWidth: 1.75 })
4176
+ }
4177
+ )
4178
+ ] }),
4179
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(
4180
+ ConditionEditor,
4181
+ {
4182
+ question: selectedQuestion,
4183
+ schema,
4184
+ onUpdate: handleConditionUpdate
4185
+ }
4186
+ ) })
4187
+ ] })
4188
+ ] }),
4189
+ /* @__PURE__ */ jsxRuntime.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: [
4190
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4191
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-5 h-0.5 rounded", style: { background: "var(--muted-foreground)", opacity: 0.4 } }),
4192
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "flow" })
4193
+ ] }),
4194
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4195
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-5 h-0.5 bg-primary rounded" }),
4196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "showIf" })
4197
+ ] }),
4198
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4199
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-5 h-0.5 rounded", style: { background: "#f59e0b" } }),
4200
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "jump" })
4201
+ ] }),
4202
+ sectionIds.map((id, i) => {
4203
+ const section = schema.sections.find((s) => s.id === id);
4204
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
4205
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-3 h-3 rounded-sm", style: { background: SECTION_COLORS[i % SECTION_COLORS.length] } }),
4206
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: section?.title || id })
4207
+ ] }, id);
4208
+ })
4209
+ ] })
4210
+ ] });
4211
+ }
4212
+ function TemplateGallery({ templates, onSelect, onClose }) {
4213
+ const [search, setSearch] = react.useState("");
4214
+ const [activeCategory, setActiveCategory] = react.useState("all");
4215
+ const [confirmTemplate, setConfirmTemplate] = react.useState(null);
4216
+ const categories = react.useMemo(() => {
4217
+ const cats = /* @__PURE__ */ new Set();
4218
+ for (const t of templates) cats.add(t.meta.category);
4219
+ return ["all", ...Array.from(cats)];
4220
+ }, [templates]);
4221
+ const filtered = react.useMemo(() => {
4222
+ return templates.filter((t) => {
4223
+ const matchesCategory = activeCategory === "all" || t.meta.category === activeCategory;
4224
+ 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()));
4225
+ return matchesCategory && matchesSearch;
4226
+ });
4227
+ }, [templates, activeCategory, search]);
4228
+ const handleConfirm = () => {
4229
+ if (confirmTemplate) {
4230
+ onSelect(confirmTemplate);
4231
+ setConfirmTemplate(null);
4232
+ onClose();
4233
+ }
4234
+ };
4235
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm", children: /* @__PURE__ */ jsxRuntime.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: [
4236
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 flex items-center justify-between px-4 py-3 border-b border-border", children: [
4237
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4238
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutTemplate, { size: 18, className: "text-primary", strokeWidth: 1.75 }),
4239
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: "Template Gallery" }),
4240
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
4241
+ "(",
4242
+ templates.length,
4243
+ " templates)"
4244
+ ] })
4245
+ ] }),
4246
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "icon-sm", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, strokeWidth: 1.75 }) })
4247
+ ] }),
4248
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 px-4 py-2 space-y-2 border-b border-border", children: [
4249
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
4250
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { size: 14, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground", strokeWidth: 1.75 }),
4251
+ /* @__PURE__ */ jsxRuntime.jsx(
4252
+ "input",
4253
+ {
4254
+ type: "text",
4255
+ placeholder: "Search templates...",
4256
+ value: search,
4257
+ onChange: (e) => setSearch(e.target.value),
4258
+ 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"
4259
+ }
4260
+ )
4261
+ ] }),
4262
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 overflow-x-auto pb-0.5", children: categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsx(
4263
+ "button",
4264
+ {
4265
+ type: "button",
4266
+ onClick: () => setActiveCategory(cat),
4267
+ 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"}`,
4268
+ children: cat === "all" ? "All" : cat.charAt(0).toUpperCase() + cat.slice(1)
4269
+ },
4270
+ cat
4271
+ )) })
4272
+ ] }),
4273
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto p-4", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center justify-center h-40 text-muted-foreground text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No templates match your search." }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: filtered.map((template) => /* @__PURE__ */ jsxRuntime.jsxs(
4274
+ "button",
4275
+ {
4276
+ type: "button",
4277
+ onClick: () => setConfirmTemplate(template),
4278
+ className: "text-left p-3 rounded-lg border border-border bg-background hover:border-primary/50 hover:bg-primary/5 transition-colors group",
4279
+ children: [
4280
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2 mb-1.5", children: [
4281
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-foreground group-hover:text-primary transition-colors", children: template.meta.name }),
4282
+ /* @__PURE__ */ jsxRuntime.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 })
4283
+ ] }),
4284
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-2 line-clamp-2", children: template.meta.description }),
4285
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
4286
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
4287
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 11, strokeWidth: 1.75 }),
4288
+ template.meta.fieldCount,
4289
+ " field",
4290
+ template.meta.fieldCount !== 1 ? "s" : ""
4291
+ ] }),
4292
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
4293
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Layers, { size: 11, strokeWidth: 1.75 }),
4294
+ template.meta.sectionCount,
4295
+ " section",
4296
+ template.meta.sectionCount !== 1 ? "s" : ""
4297
+ ] })
4298
+ ] })
4299
+ ]
4300
+ },
4301
+ template.meta.id
4302
+ )) }) }),
4303
+ confirmTemplate && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 px-4 py-3 border-t border-border bg-muted/50 flex items-center justify-between gap-3", children: [
4304
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm", children: [
4305
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Use " }),
4306
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: confirmTemplate.meta.name }),
4307
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "? This will replace the current form." })
4308
+ ] }),
4309
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
4310
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "sm", onClick: () => setConfirmTemplate(null), children: "Cancel" }),
4311
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { size: "sm", onClick: handleConfirm, children: "Use Template" })
4312
+ ] })
4313
+ ] })
4314
+ ] }) });
4315
+ }
4316
+ function useDebouncedValidation(delayMs = 500) {
4317
+ const [errors, setErrors] = react.useState([]);
4318
+ const timerRef = react.useRef(null);
4319
+ const validate = react.useCallback(
4320
+ (text) => {
4321
+ if (timerRef.current) {
4322
+ clearTimeout(timerRef.current);
4323
+ }
4324
+ timerRef.current = setTimeout(() => {
4325
+ const result = [];
4326
+ let parsed;
4327
+ try {
4328
+ parsed = JSON.parse(text);
4329
+ } catch (err) {
4330
+ if (err instanceof SyntaxError) {
4331
+ const posMatch = err.message.match(/position\s+(\d+)/i);
4332
+ let line = 1;
4333
+ let column = 1;
4334
+ if (posMatch) {
4335
+ const pos = parseInt(posMatch[1], 10);
4336
+ const upToPos = text.slice(0, pos);
4337
+ line = (upToPos.match(/\n/g) || []).length + 1;
4338
+ column = pos - upToPos.lastIndexOf("\n");
4339
+ }
4340
+ result.push({ message: err.message, line, column, endLine: line, endColumn: column + 1 });
4341
+ } else {
4342
+ result.push({ message: "Invalid JSON" });
4343
+ }
4344
+ setErrors(result);
4345
+ return;
4346
+ }
4347
+ try {
4348
+ fieldcraftCore.validateSchema(parsed);
4349
+ } catch (err) {
4350
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
4351
+ for (const issue of err.issues) {
4352
+ result.push({
4353
+ message: issue.message,
4354
+ path: issue.path?.join(".")
4355
+ });
4356
+ }
4357
+ } else if (err instanceof Error) {
4358
+ result.push({ message: err.message });
4359
+ }
4360
+ }
4361
+ setErrors(result);
4362
+ }, delayMs);
4363
+ },
4364
+ [delayMs]
4365
+ );
4366
+ return { errors, validate };
4367
+ }
4368
+ var ThemeCtx = react.createContext({});
4369
+ function useBuilderTheme() {
4370
+ return react.useContext(ThemeCtx);
4371
+ }
4372
+ function themeToCssVars(theme) {
4373
+ const vars = {};
4374
+ if (theme.background) vars["--background"] = theme.background;
4375
+ if (theme.foreground) vars["--foreground"] = theme.foreground;
4376
+ if (theme.card) {
4377
+ vars["--card"] = theme.card;
4378
+ vars["--card-foreground"] = theme.foreground ?? "";
4379
+ vars["--popover"] = theme.card;
4380
+ vars["--popover-foreground"] = theme.foreground ?? "";
4381
+ }
4382
+ if (theme.primary) vars["--primary"] = theme.primary;
4383
+ if (theme.primaryForeground) vars["--primary-foreground"] = theme.primaryForeground;
4384
+ if (theme.secondary) vars["--secondary"] = theme.secondary;
4385
+ if (theme.secondaryForeground) vars["--secondary-foreground"] = theme.secondaryForeground;
4386
+ if (theme.muted) vars["--muted"] = theme.muted;
4387
+ if (theme.mutedForeground) vars["--muted-foreground"] = theme.mutedForeground;
4388
+ if (theme.accent) vars["--accent"] = theme.accent;
4389
+ if (theme.accentForeground) vars["--accent-foreground"] = theme.accentForeground;
4390
+ if (theme.destructive) vars["--destructive"] = theme.destructive;
4391
+ if (theme.destructiveForeground) vars["--destructive-foreground"] = theme.destructiveForeground;
4392
+ if (theme.border) vars["--border"] = theme.border;
4393
+ if (theme.input) vars["--input"] = theme.input;
4394
+ if (theme.ring) vars["--ring"] = theme.ring;
4395
+ if (theme.radius) vars["--radius"] = theme.radius;
4396
+ if (theme.surface) vars["--fcb-surface"] = theme.surface;
4397
+ if (theme.surfaceHover) vars["--fcb-surface-hover"] = theme.surfaceHover;
4398
+ if (theme.canvas) vars["--fcb-canvas"] = theme.canvas;
4399
+ if (theme.panel) vars["--fcb-panel"] = theme.panel;
4400
+ if (theme.borderStrong) vars["--fcb-border-strong"] = theme.borderStrong;
4401
+ if (theme.textDim) vars["--fcb-text-dim"] = theme.textDim;
4402
+ return vars;
4403
+ }
4404
+ function FormBuilderThemeProvider({ theme, children }) {
4405
+ const resolved = theme ?? {};
4406
+ const cssVars = react.useMemo(() => themeToCssVars(resolved), [resolved]);
4407
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeCtx.Provider, { value: resolved, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fcb-root": "", style: cssVars, className: "w-full h-full", children }) });
4408
+ }
4409
+ var FormBuilderErrorBoundary = class extends react.Component {
4410
+ constructor(props) {
4411
+ super(props);
4412
+ this.state = { hasError: false, error: null };
4413
+ }
4414
+ static getDerivedStateFromError(error) {
4415
+ return { hasError: true, error };
4416
+ }
4417
+ componentDidCatch(error, info) {
4418
+ console.error("[FormBuilder] Render error:", error, info.componentStack);
4419
+ }
4420
+ render() {
4421
+ if (this.state.hasError) {
4422
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center justify-center h-full bg-background text-foreground p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-md text-center space-y-4", children: [
4423
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: "Something went wrong" }),
4424
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "The form builder encountered an unexpected error. Your schema data is preserved." }),
4425
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-destructive bg-destructive/10 rounded-md p-3 text-left overflow-auto max-h-32", children: this.state.error?.message }),
4426
+ /* @__PURE__ */ jsxRuntime.jsx(
4427
+ fieldcraftReact.Button,
4428
+ {
4429
+ onClick: () => this.setState({ hasError: false, error: null }),
4430
+ variant: "outline",
4431
+ children: "Try Again"
4432
+ }
4433
+ )
4434
+ ] }) });
4435
+ }
4436
+ return this.props.children;
4437
+ }
4438
+ };
4439
+ function FormBuilderCore(props) {
4440
+ const { initialSchema = DEFAULT_SCHEMA, onChange, onSave, height = "100vh", theme, className, toolbarExtra, questionTypes, palette, preview, templates, schemaUrl } = props;
4441
+ const [viewMode, setViewMode] = react.useState("design");
4442
+ const [jsonText, setJsonText] = react.useState("");
4443
+ const [jsonSwitchError, setJsonSwitchError] = react.useState(null);
4444
+ const [showLogicMap, setShowLogicMap] = react.useState(false);
4445
+ const [showTemplateGallery, setShowTemplateGallery] = react.useState(false);
4446
+ const [saveValidationErrors, setSaveValidationErrors] = react.useState(null);
4447
+ const [schemaUrlLoading, setSchemaUrlLoading] = react.useState(!!schemaUrl);
4448
+ const [schemaUrlError, setSchemaUrlError] = react.useState(null);
4449
+ const { errors: jsonErrors, validate: validateJson } = useDebouncedValidation(400);
4450
+ const mergedQuestionTypes = questionTypes ? { ...QUESTION_TYPE_INFO, ...questionTypes } : QUESTION_TYPE_INFO;
4451
+ const builderState = useBuilderState(initialSchema);
4452
+ const dragDrop = useDragDrop(builderState);
4453
+ const fileInputRef = react.useRef(null);
4454
+ const [mobilePanel, setMobilePanel] = react.useState("none");
4455
+ react.useEffect(() => {
4456
+ if (builderState.selectedItem && window.innerWidth < 768) {
4457
+ setMobilePanel("properties");
4458
+ }
4459
+ }, [builderState.selectedItem]);
4460
+ react.useEffect(() => {
4461
+ if (!dragDrop.activeDragItem && mobilePanel === "palette") {
4462
+ setMobilePanel("none");
4463
+ }
4464
+ }, [dragDrop.activeDragItem]);
4465
+ react.useEffect(() => {
4466
+ if (!schemaUrl) return;
4467
+ let cancelled = false;
4468
+ setSchemaUrlLoading(true);
4469
+ setSchemaUrlError(null);
4470
+ fetch(schemaUrl).then((res) => {
4471
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
4472
+ return res.json();
4473
+ }).then((json) => {
4474
+ if (cancelled) return;
4475
+ try {
4476
+ const validated = fieldcraftCore.validateSchema(json);
4477
+ builderState.resetSchema(validated);
4478
+ } catch (err) {
4479
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
4480
+ setSchemaUrlError(`Invalid schema: ${err.issues[0]?.message ?? "validation failed"}`);
4481
+ } else {
4482
+ setSchemaUrlError(err.message);
4483
+ }
4484
+ }
4485
+ setSchemaUrlLoading(false);
4486
+ }).catch((err) => {
4487
+ if (cancelled) return;
4488
+ setSchemaUrlError(err.message ?? "Failed to fetch schema");
4489
+ setSchemaUrlLoading(false);
4490
+ });
4491
+ return () => {
4492
+ cancelled = true;
4493
+ };
4494
+ }, [schemaUrl]);
4495
+ react.useEffect(() => {
4496
+ if (onChange && builderState.isDirty) {
4497
+ onChange(builderState.schema);
4498
+ }
4499
+ }, [builderState.schema, builderState.isDirty, onChange]);
4500
+ const handleSave = react.useCallback(() => {
4501
+ if (!onSave) return;
4502
+ try {
4503
+ fieldcraftCore.validateSchema(builderState.schema);
4504
+ setSaveValidationErrors(null);
4505
+ onSave(builderState.schema);
4506
+ builderState.markClean();
4507
+ } catch (err) {
4508
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
4509
+ setSaveValidationErrors(err.issues.map((issue) => issue.message));
4510
+ } else {
4511
+ setSaveValidationErrors([err.message ?? "Unknown validation error"]);
4512
+ }
4513
+ }
4514
+ }, [onSave, builderState]);
4515
+ const handleExport = react.useCallback(() => {
4516
+ const json = JSON.stringify(builderState.schema, null, 2);
4517
+ const blob = new Blob([json], { type: "application/json" });
4518
+ const url = URL.createObjectURL(blob);
4519
+ const a = document.createElement("a");
4520
+ a.href = url;
4521
+ a.download = `${builderState.schema.title?.replace(/\s+/g, "-").toLowerCase() || "form"}-schema.json`;
4522
+ a.click();
4523
+ URL.revokeObjectURL(url);
4524
+ }, [builderState.schema]);
4525
+ const handleImport = react.useCallback(() => {
4526
+ fileInputRef.current?.click();
4527
+ }, []);
4528
+ const handleFileChange = react.useCallback(
4529
+ (e) => {
4530
+ const file = e.target.files?.[0];
4531
+ if (!file) return;
4532
+ const reader = new FileReader();
4533
+ reader.onload = (event) => {
4534
+ try {
4535
+ const parsed = JSON.parse(event.target?.result);
4536
+ const validated = fieldcraftCore.validateSchema(parsed);
4537
+ builderState.resetSchema(validated);
4538
+ } catch (err) {
4539
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
4540
+ const details = err.issues.slice(0, 3).map((i) => `\u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
4541
+ alert(`Invalid schema:
4542
+ ${details}`);
4543
+ } else if (err instanceof SyntaxError) {
4544
+ alert("Failed to parse JSON file.");
4545
+ } else {
4546
+ alert("Invalid schema file.");
4547
+ }
4548
+ }
4549
+ };
4550
+ reader.readAsText(file);
4551
+ e.target.value = "";
4552
+ },
4553
+ [builderState]
4554
+ );
4555
+ const handleViewModeChange = react.useCallback(
4556
+ (newMode) => {
4557
+ setJsonSwitchError(null);
4558
+ if (viewMode === "json" && newMode !== "json") {
4559
+ try {
4560
+ const parsed = JSON.parse(jsonText);
4561
+ const validated = fieldcraftCore.validateSchema(parsed);
4562
+ builderState.resetSchema(validated);
4563
+ } catch (err) {
4564
+ if (err instanceof SyntaxError) {
4565
+ setJsonSwitchError("Cannot switch: JSON has syntax errors");
4566
+ } else if (err instanceof fieldcraftCore.FormEngineSchemaError) {
4567
+ setJsonSwitchError(`Cannot switch: ${err.issues[0]?.message ?? "Invalid schema"}`);
4568
+ } else {
4569
+ setJsonSwitchError("Cannot switch: Invalid schema");
4570
+ }
4571
+ return;
4572
+ }
4573
+ }
4574
+ if (newMode === "json") {
4575
+ setJsonText(JSON.stringify(builderState.schema, null, 2));
4576
+ }
4577
+ setViewMode(newMode);
4578
+ },
4579
+ [viewMode, jsonText, builderState]
4580
+ );
4581
+ const handleKeyDown = react.useCallback(
4582
+ (e) => {
4583
+ const mod = e.metaKey || e.ctrlKey;
4584
+ const tag = e.target.tagName;
4585
+ const isEditing = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
4586
+ if (mod && e.key === "z" && !e.shiftKey) {
4587
+ e.preventDefault();
4588
+ builderState.undo();
4589
+ return;
4590
+ }
4591
+ if (mod && e.key === "z" && e.shiftKey) {
4592
+ e.preventDefault();
4593
+ builderState.redo();
4594
+ return;
4595
+ }
4596
+ if (mod && e.key === "s") {
4597
+ e.preventDefault();
4598
+ handleSave();
4599
+ return;
4600
+ }
4601
+ if (isEditing) return;
4602
+ if (e.key === "Delete" || e.key === "Backspace") {
4603
+ const sel = builderState.selectedItem;
4604
+ if (!sel) return;
4605
+ e.preventDefault();
4606
+ if (sel.type === "question") {
4607
+ builderState.removeQuestion(sel.sectionId, sel.questionId);
4608
+ } else if (sel.type === "section") {
4609
+ builderState.removeSection(sel.sectionId);
4610
+ }
4611
+ return;
4612
+ }
4613
+ if (e.key === "Escape") {
4614
+ if (builderState.selectedItem) {
4615
+ e.preventDefault();
4616
+ builderState.clearSelection();
4617
+ }
4618
+ return;
4619
+ }
4620
+ if (mod && e.key === "d") {
4621
+ const sel = builderState.selectedItem;
4622
+ if (!sel) return;
4623
+ e.preventDefault();
4624
+ if (sel.type === "question") {
4625
+ builderState.duplicateQuestion(sel.sectionId, sel.questionId);
4626
+ } else if (sel.type === "section") {
4627
+ builderState.duplicateSection(sel.sectionId);
4628
+ }
4629
+ }
4630
+ },
4631
+ [builderState, handleSave]
4632
+ );
4633
+ return /* @__PURE__ */ jsxRuntime.jsx(FormBuilderThemeProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsxs(
4634
+ core.DndContext,
4635
+ {
4636
+ sensors: dragDrop.sensors,
4637
+ onDragStart: dragDrop.handleDragStart,
4638
+ onDragEnd: dragDrop.handleDragEnd,
4639
+ onDragCancel: dragDrop.handleDragCancel,
4640
+ children: [
4641
+ /* @__PURE__ */ jsxRuntime.jsxs(
3570
4642
  "div",
3571
4643
  {
3572
4644
  className: cn("flex flex-col bg-background text-foreground", className),
@@ -3607,8 +4679,74 @@ ${details}`);
3607
4679
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "mx-2 h-5" }),
3608
4680
  builderState.isDirty && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-primary", role: "status", children: "Unsaved changes" })
3609
4681
  ] }),
3610
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: toolbarExtra }),
4682
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-2", children: [
4683
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 bg-muted rounded-md p-0.5", children: [
4684
+ /* @__PURE__ */ jsxRuntime.jsxs(
4685
+ fieldcraftReact.Button,
4686
+ {
4687
+ variant: viewMode === "design" ? "secondary" : "ghost",
4688
+ size: "sm",
4689
+ onClick: () => handleViewModeChange("design"),
4690
+ className: "gap-1.5 h-7 px-2.5 text-xs",
4691
+ children: [
4692
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PencilRuler, { size: 13, strokeWidth: 1.75 }),
4693
+ "Design"
4694
+ ]
4695
+ }
4696
+ ),
4697
+ /* @__PURE__ */ jsxRuntime.jsxs(
4698
+ fieldcraftReact.Button,
4699
+ {
4700
+ variant: viewMode === "preview" ? "secondary" : "ghost",
4701
+ size: "sm",
4702
+ onClick: () => handleViewModeChange("preview"),
4703
+ className: "gap-1.5 h-7 px-2.5 text-xs",
4704
+ children: [
4705
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { size: 13, strokeWidth: 1.75 }),
4706
+ "Preview"
4707
+ ]
4708
+ }
4709
+ ),
4710
+ /* @__PURE__ */ jsxRuntime.jsxs(
4711
+ fieldcraftReact.Button,
4712
+ {
4713
+ variant: viewMode === "json" ? "secondary" : "ghost",
4714
+ size: "sm",
4715
+ onClick: () => handleViewModeChange("json"),
4716
+ className: "gap-1.5 h-7 px-2.5 text-xs",
4717
+ children: [
4718
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { size: 13, strokeWidth: 1.75 }),
4719
+ "JSON"
4720
+ ]
4721
+ }
4722
+ )
4723
+ ] }),
4724
+ toolbarExtra
4725
+ ] }),
3611
4726
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
4727
+ templates && templates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4728
+ fieldcraftReact.Button,
4729
+ {
4730
+ variant: "ghost",
4731
+ size: "icon-sm",
4732
+ onClick: () => setShowTemplateGallery(true),
4733
+ title: "Browse templates",
4734
+ "aria-label": "Template gallery",
4735
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutTemplate, { size: 15, strokeWidth: 1.75 })
4736
+ }
4737
+ ),
4738
+ /* @__PURE__ */ jsxRuntime.jsx(
4739
+ fieldcraftReact.Button,
4740
+ {
4741
+ variant: "ghost",
4742
+ size: "icon-sm",
4743
+ onClick: () => setShowLogicMap(true),
4744
+ title: "View branching logic map",
4745
+ "aria-label": "Logic map",
4746
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GitBranch, { size: 15, strokeWidth: 1.75 })
4747
+ }
4748
+ ),
4749
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "mx-1 h-5" }),
3612
4750
  /* @__PURE__ */ jsxRuntime.jsx(
3613
4751
  fieldcraftReact.Button,
3614
4752
  {
@@ -3652,6 +4790,47 @@ ${details}`);
3652
4790
  }
3653
4791
  ) }),
3654
4792
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-1", children: [
4793
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 bg-muted rounded-md p-0.5", children: [
4794
+ /* @__PURE__ */ jsxRuntime.jsxs(
4795
+ fieldcraftReact.Button,
4796
+ {
4797
+ variant: viewMode === "design" ? "secondary" : "ghost",
4798
+ size: "sm",
4799
+ onClick: () => handleViewModeChange("design"),
4800
+ className: "gap-1 h-7 px-2 text-xs",
4801
+ children: [
4802
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PencilRuler, { size: 12, strokeWidth: 1.75 }),
4803
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Design" })
4804
+ ]
4805
+ }
4806
+ ),
4807
+ /* @__PURE__ */ jsxRuntime.jsxs(
4808
+ fieldcraftReact.Button,
4809
+ {
4810
+ variant: viewMode === "preview" ? "secondary" : "ghost",
4811
+ size: "sm",
4812
+ onClick: () => handleViewModeChange("preview"),
4813
+ className: "gap-1 h-7 px-2 text-xs",
4814
+ children: [
4815
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { size: 12, strokeWidth: 1.75 }),
4816
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Preview" })
4817
+ ]
4818
+ }
4819
+ ),
4820
+ /* @__PURE__ */ jsxRuntime.jsxs(
4821
+ fieldcraftReact.Button,
4822
+ {
4823
+ variant: viewMode === "json" ? "secondary" : "ghost",
4824
+ size: "sm",
4825
+ onClick: () => handleViewModeChange("json"),
4826
+ className: "gap-1 h-7 px-2 text-xs",
4827
+ children: [
4828
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { size: 12, strokeWidth: 1.75 }),
4829
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "JSON" })
4830
+ ]
4831
+ }
4832
+ )
4833
+ ] }),
3655
4834
  toolbarExtra,
3656
4835
  /* @__PURE__ */ jsxRuntime.jsxs(fieldcraftReact.Button, { onClick: handleSave, size: "sm", className: "border-0 shadow-sm fcb-glow ml-1", "aria-label": "Save form", children: [
3657
4836
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 14, strokeWidth: 2 }),
@@ -3740,7 +4919,50 @@ ${details}`);
3740
4919
  "aria-hidden": "true"
3741
4920
  }
3742
4921
  ),
3743
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex overflow-hidden relative", children: [
4922
+ jsonSwitchError && /* @__PURE__ */ jsxRuntime.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: [
4923
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: jsonSwitchError }),
4924
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setJsonSwitchError(null), className: "ml-2 hover:underline", children: "Dismiss" })
4925
+ ] }),
4926
+ saveValidationErrors && saveValidationErrors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-xs", children: [
4927
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
4928
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-destructive", children: [
4929
+ "Schema validation failed (",
4930
+ saveValidationErrors.length,
4931
+ " issue",
4932
+ saveValidationErrors.length !== 1 ? "s" : "",
4933
+ ")"
4934
+ ] }),
4935
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setSaveValidationErrors(null), className: "text-destructive hover:underline", children: "Dismiss" })
4936
+ ] }),
4937
+ /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "list-disc pl-4 text-destructive/80 space-y-0.5", children: [
4938
+ saveValidationErrors.slice(0, 10).map((msg, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: msg }, i)),
4939
+ saveValidationErrors.length > 10 && /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
4940
+ "...and ",
4941
+ saveValidationErrors.length - 10,
4942
+ " more"
4943
+ ] })
4944
+ ] })
4945
+ ] }),
4946
+ schemaUrlLoading && /* @__PURE__ */ jsxRuntime.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: [
4947
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin" }),
4948
+ "Loading schema from URL..."
4949
+ ] }),
4950
+ schemaUrlError && /* @__PURE__ */ jsxRuntime.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: [
4951
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4952
+ "Failed to load schema: ",
4953
+ schemaUrlError
4954
+ ] }),
4955
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setSchemaUrlError(null), className: "ml-2 hover:underline", children: "Dismiss" })
4956
+ ] }),
4957
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex overflow-hidden relative", children: viewMode === "json" ? /* @__PURE__ */ jsxRuntime.jsx(
4958
+ JsonEditorPanel,
4959
+ {
4960
+ value: jsonText,
4961
+ onChange: setJsonText,
4962
+ errors: jsonErrors,
4963
+ onValidate: validateJson
4964
+ }
4965
+ ) : viewMode === "preview" ? /* @__PURE__ */ jsxRuntime.jsx(FormPreviewPanel, { schema: builderState.schema, preview }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3744
4966
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsxRuntime.jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }) }),
3745
4967
  /* @__PURE__ */ jsxRuntime.jsx(FormCanvas, { builderState }),
3746
4968
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsxRuntime.jsx(PropertiesPanel, { builderState }) }),
@@ -3754,11 +4976,27 @@ ${details}`);
3754
4976
  "aria-hidden": "true"
3755
4977
  }
3756
4978
  )
3757
- ] })
4979
+ ] }) })
3758
4980
  ]
3759
4981
  }
3760
4982
  ),
3761
- /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { dropAnimation: null, children: dragDrop.activeDragItem && /* @__PURE__ */ jsxRuntime.jsx(DragOverlayContent, { item: dragDrop.activeDragItem, schema: builderState.schema, questionTypes: mergedQuestionTypes }) })
4983
+ /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { dropAnimation: null, children: dragDrop.activeDragItem && /* @__PURE__ */ jsxRuntime.jsx(DragOverlayContent, { item: dragDrop.activeDragItem, schema: builderState.schema, questionTypes: mergedQuestionTypes }) }),
4984
+ showLogicMap && /* @__PURE__ */ jsxRuntime.jsx(
4985
+ LogicFlowEditor,
4986
+ {
4987
+ schema: builderState.schema,
4988
+ onChange: (updated) => builderState.resetSchema(updated),
4989
+ onClose: () => setShowLogicMap(false)
4990
+ }
4991
+ ),
4992
+ showTemplateGallery && templates && /* @__PURE__ */ jsxRuntime.jsx(
4993
+ TemplateGallery,
4994
+ {
4995
+ templates,
4996
+ onSelect: (template) => builderState.resetSchema(template.schema),
4997
+ onClose: () => setShowTemplateGallery(false)
4998
+ }
4999
+ )
3762
5000
  ]
3763
5001
  }
3764
5002
  ) });
@@ -3790,99 +5028,343 @@ function DragOverlayContent({
3790
5028
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium", children: found.question.label })
3791
5029
  ] });
3792
5030
  }
3793
- if (item.type === "section") {
3794
- const section = schema.sections.find((s) => s.id === item.sectionId);
3795
- if (!section) return null;
3796
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 rounded-md border border-primary bg-card text-foreground fcb-shadow-lg cursor-grabbing max-w-sm", children: [
3797
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold", children: section.title }),
3798
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground mt-0.5", children: [
3799
- section.questions.length,
3800
- " field",
3801
- section.questions.length !== 1 ? "s" : ""
5031
+ if (item.type === "section") {
5032
+ const section = schema.sections.find((s) => s.id === item.sectionId);
5033
+ if (!section) return null;
5034
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 rounded-md border border-primary bg-card text-foreground fcb-shadow-lg cursor-grabbing max-w-sm", children: [
5035
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold", children: section.title }),
5036
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground mt-0.5", children: [
5037
+ section.questions.length,
5038
+ " field",
5039
+ section.questions.length !== 1 ? "s" : ""
5040
+ ] })
5041
+ ] });
5042
+ }
5043
+ return null;
5044
+ }
5045
+ function FormBuilderInner(props) {
5046
+ return /* @__PURE__ */ jsxRuntime.jsx(FormBuilderErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(FormBuilderCore, { ...props }) });
5047
+ }
5048
+
5049
+ // src/form-builder/components/FormBuilderGated.tsx
5050
+ var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
5051
+
5052
+ // src/form-builder/theme/presets.ts
5053
+ var squaredrDarkPreset = {
5054
+ background: "#0F1A1F",
5055
+ foreground: "#E8EFF1",
5056
+ card: "#16242A",
5057
+ primary: "#63BDB4",
5058
+ primaryForeground: "#0F1A1F",
5059
+ secondary: "#182F31",
5060
+ secondaryForeground: "#E8EFF1",
5061
+ muted: "#182F31",
5062
+ mutedForeground: "#8CA1A9",
5063
+ accent: "#182F31",
5064
+ accentForeground: "#E8EFF1",
5065
+ destructive: "#E08072",
5066
+ destructiveForeground: "#0F1A1F",
5067
+ border: "#2A3B42",
5068
+ input: "#2A3B42",
5069
+ ring: "#63BDB4",
5070
+ radius: "0px",
5071
+ surface: "#16242A",
5072
+ surfaceHover: "#182F31",
5073
+ canvas: "#0F1A1F",
5074
+ panel: "#16242A",
5075
+ borderStrong: "#2F4F4C",
5076
+ textDim: "#5E7680"
5077
+ };
5078
+ var cleanPreset = {
5079
+ background: "#F4F7F8",
5080
+ foreground: "#12222A",
5081
+ card: "#FFFFFF",
5082
+ primary: "#1F6B6E",
5083
+ primaryForeground: "#FFFFFF",
5084
+ secondary: "#EDF3F2",
5085
+ secondaryForeground: "#12222A",
5086
+ muted: "#EDF3F2",
5087
+ mutedForeground: "#6A7B85",
5088
+ accent: "#EDF3F2",
5089
+ accentForeground: "#12222A",
5090
+ destructive: "#B04A3C",
5091
+ destructiveForeground: "#FFFFFF",
5092
+ border: "#DCE4E8",
5093
+ input: "#DCE4E8",
5094
+ ring: "#1F6B6E",
5095
+ radius: "0px",
5096
+ surface: "#FAFCFC",
5097
+ surfaceHover: "#EDF3F2",
5098
+ canvas: "#F4F7F8",
5099
+ panel: "#FFFFFF",
5100
+ borderStrong: "#B9D1CF",
5101
+ textDim: "#6A7B85"
5102
+ };
5103
+ function formatCurrency(amount, currency) {
5104
+ if (amount == null) return "";
5105
+ try {
5106
+ return new Intl.NumberFormat(void 0, {
5107
+ style: "currency",
5108
+ currency: currency ?? "USD"
5109
+ }).format(amount);
5110
+ } catch {
5111
+ return `${currency ?? "USD"} ${amount.toFixed(2)}`;
5112
+ }
5113
+ }
5114
+ function ProPaymentField(props) {
5115
+ const { field, value, error, touched, disabled, readonly, onChange, onBlur, customProps } = props;
5116
+ const config = field.config;
5117
+ const current = value ?? { status: "pending" };
5118
+ const provider = config?.provider ?? "stripe";
5119
+ const publicKey = config?.publicKey;
5120
+ const amount = customProps?.amount ?? config?.amount;
5121
+ const currency = config?.currency ?? "USD";
5122
+ const directSecret = customProps?.clientSecret;
5123
+ const onCreateIntent = customProps?.onCreatePaymentIntent;
5124
+ const onPaymentComplete = customProps?.onPaymentComplete;
5125
+ const serverUrl = config?.serverUrl;
5126
+ const [clientSecret, setClientSecret] = react.useState(directSecret);
5127
+ const [intentLoading, setIntentLoading] = react.useState(false);
5128
+ const [intentError, setIntentError] = react.useState(null);
5129
+ const mode = directSecret ? "direct" : onCreateIntent ? "callback" : serverUrl ? "url" : "setup";
5130
+ react.useEffect(() => {
5131
+ if (directSecret) {
5132
+ setClientSecret(directSecret);
5133
+ return;
5134
+ }
5135
+ if (mode === "setup" || !amount || !publicKey) return;
5136
+ if (clientSecret) return;
5137
+ const fetchIntent = async () => {
5138
+ setIntentLoading(true);
5139
+ setIntentError(null);
5140
+ try {
5141
+ if (mode === "callback" && onCreateIntent) {
5142
+ const result = await onCreateIntent({ amount, currency, provider, metadata: { fieldId: field.id } });
5143
+ setClientSecret(result.clientSecret);
5144
+ } else if (mode === "url" && serverUrl) {
5145
+ const res = await fetch(serverUrl, {
5146
+ method: "POST",
5147
+ headers: { "Content-Type": "application/json" },
5148
+ body: JSON.stringify({ amount, currency, provider, metadata: { fieldId: field.id } })
5149
+ });
5150
+ if (!res.ok) throw new Error(`Server error (${res.status})`);
5151
+ const data = await res.json();
5152
+ setClientSecret(data.clientSecret);
5153
+ }
5154
+ } catch (err) {
5155
+ setIntentError(err instanceof Error ? err.message : "Failed to create payment intent");
5156
+ } finally {
5157
+ setIntentLoading(false);
5158
+ }
5159
+ };
5160
+ fetchIntent();
5161
+ }, [mode, amount, currency, provider, publicKey, directSecret, clientSecret, onCreateIntent, serverUrl, field.id]);
5162
+ const handleSuccess = (chargeId) => {
5163
+ const result = { status: "succeeded", chargeId };
5164
+ onChange(result);
5165
+ onPaymentComplete?.(result);
5166
+ onBlur();
5167
+ };
5168
+ const handleError = (message) => {
5169
+ const result = { status: "failed", error: message };
5170
+ onChange(result);
5171
+ onPaymentComplete?.(result);
5172
+ onBlur();
5173
+ };
5174
+ if (!publicKey) {
5175
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border-2 border-dashed border-input p-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
5176
+ "Payment field \u2014 configure a ",
5177
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "bg-muted px-1 rounded text-xs", children: "publicKey" }),
5178
+ " in the field properties panel."
5179
+ ] }) }) });
5180
+ }
5181
+ if (provider !== "stripe") {
5182
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-input bg-muted/50 p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
5183
+ 'Provider "',
5184
+ provider,
5185
+ '" \u2014 coming soon.'
5186
+ ] }) }) });
5187
+ }
5188
+ if (current.status === "succeeded") {
5189
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-input p-4", children: [
5190
+ amount != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground mb-2", children: [
5191
+ formatCurrency(amount, currency),
5192
+ config?.description && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5193
+ " \u2014 ",
5194
+ config.description
5195
+ ] })
5196
+ ] }),
5197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm font-medium", style: { color: "var(--success, #22c55e)" }, children: [
5198
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, width: 16, height: 16, children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }),
5199
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5200
+ "Payment successful",
5201
+ current.chargeId ? ` (${current.chargeId})` : ""
5202
+ ] })
5203
+ ] })
5204
+ ] }) });
5205
+ }
5206
+ if (current.status === "failed") {
5207
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-destructive/50 p-4", children: [
5208
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-destructive font-medium", children: [
5209
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, width: 16, height: 16, children: [
5210
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
5211
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
5212
+ ] }),
5213
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5214
+ "Payment failed",
5215
+ current.error ? `: ${current.error}` : ""
5216
+ ] })
5217
+ ] }),
5218
+ /* @__PURE__ */ jsxRuntime.jsx(
5219
+ "button",
5220
+ {
5221
+ type: "button",
5222
+ className: "mt-2 text-xs text-primary hover:underline",
5223
+ onClick: () => {
5224
+ onChange({ status: "pending" });
5225
+ setClientSecret(void 0);
5226
+ },
5227
+ children: "Retry"
5228
+ }
5229
+ )
5230
+ ] }) });
5231
+ }
5232
+ if (intentLoading || mode !== "setup" && mode !== "direct" && !clientSecret) {
5233
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-input p-4", children: [
5234
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
5235
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Payment" }),
5236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
5237
+ ] }),
5238
+ amount != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground mb-3", children: [
5239
+ "Amount: ",
5240
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatCurrency(amount, currency) }),
5241
+ config?.description && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5242
+ " \u2014 ",
5243
+ config.description
5244
+ ] })
5245
+ ] }),
5246
+ intentError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5247
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: intentError }),
5248
+ /* @__PURE__ */ jsxRuntime.jsx(
5249
+ "button",
5250
+ {
5251
+ type: "button",
5252
+ className: "mt-2 text-xs text-primary hover:underline",
5253
+ onClick: () => {
5254
+ setIntentError(null);
5255
+ setClientSecret(void 0);
5256
+ },
5257
+ children: "Retry"
5258
+ }
5259
+ )
5260
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
5261
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
5262
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Preparing payment..." })
5263
+ ] })
5264
+ ] }) });
5265
+ }
5266
+ if (!clientSecret) {
5267
+ const waitingForAmount = config?.amountField && !amount;
5268
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-input p-4", children: [
5269
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
5270
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Payment" }),
5271
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
5272
+ ] }),
5273
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: waitingForAmount ? "Select an option above to proceed with payment." : "Provide a clientSecret via customProps, an onCreatePaymentIntent callback, or a serverUrl." })
5274
+ ] }) });
5275
+ }
5276
+ return /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-input p-4", children: [
5277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
5278
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Payment" }),
5279
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
5280
+ ] }),
5281
+ amount != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground mb-3", children: [
5282
+ "Amount: ",
5283
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatCurrency(amount, currency) }),
5284
+ config?.description && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5285
+ " \u2014 ",
5286
+ config.description
3802
5287
  ] })
5288
+ ] }),
5289
+ /* @__PURE__ */ jsxRuntime.jsx(
5290
+ react.Suspense,
5291
+ {
5292
+ fallback: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground py-4", children: [
5293
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
5294
+ "Loading payment form..."
5295
+ ] }),
5296
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5297
+ PayKitCheckout,
5298
+ {
5299
+ publicKey,
5300
+ clientSecret,
5301
+ amount,
5302
+ currency,
5303
+ disabled: disabled || readonly,
5304
+ onSuccess: handleSuccess,
5305
+ onError: handleError
5306
+ }
5307
+ )
5308
+ }
5309
+ )
5310
+ ] }) });
5311
+ }
5312
+ function PayKitCheckout({
5313
+ publicKey,
5314
+ clientSecret,
5315
+ amount,
5316
+ currency,
5317
+ disabled,
5318
+ onSuccess,
5319
+ onError
5320
+ }) {
5321
+ const [PayKit, setPayKit] = react.useState(null);
5322
+ const [loadError, setLoadError] = react.useState(null);
5323
+ react.useEffect(() => {
5324
+ let cancelled = false;
5325
+ Promise.all([
5326
+ // @ts-expect-error — optional peer dep, resolved at runtime
5327
+ import('@squaredr/paykit-react'),
5328
+ // @ts-expect-error — optional peer dep, resolved at runtime
5329
+ import('@squaredr/paykit-stripe/client')
5330
+ ]).then(([paykit, stripe]) => {
5331
+ if (cancelled) return;
5332
+ setPayKit({
5333
+ Provider: paykit.PayKitProvider,
5334
+ Form: paykit.CheckoutForm,
5335
+ adapter: new stripe.StripeClientAdapter(publicKey)
5336
+ });
5337
+ }).catch((err) => {
5338
+ if (cancelled) return;
5339
+ setLoadError(
5340
+ `Failed to load payment SDK. Ensure @squaredr/paykit-react and @squaredr/paykit-stripe are installed. (${err.message})`
5341
+ );
5342
+ });
5343
+ return () => {
5344
+ cancelled = true;
5345
+ };
5346
+ }, [publicKey]);
5347
+ if (loadError) {
5348
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: loadError });
5349
+ }
5350
+ if (!PayKit) {
5351
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground py-2", children: [
5352
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
5353
+ "Loading Stripe..."
3803
5354
  ] });
3804
5355
  }
3805
- return null;
3806
- }
3807
- function FormBuilderInner(props) {
3808
- return /* @__PURE__ */ jsxRuntime.jsx(FormBuilderErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(FormBuilderCore, { ...props }) });
5356
+ return /* @__PURE__ */ jsxRuntime.jsx(PayKit.Provider, { clientAdapter: PayKit.adapter, children: /* @__PURE__ */ jsxRuntime.jsx(
5357
+ PayKit.Form,
5358
+ {
5359
+ clientSecret,
5360
+ submitLabel: disabled ? "Payment disabled" : `Pay ${formatCurrency(amount, currency)}`,
5361
+ onSuccess: (result) => onSuccess(result.chargeId),
5362
+ onError: (err) => onError(err.message)
5363
+ }
5364
+ ) });
3809
5365
  }
3810
-
3811
- // src/form-builder/components/FormBuilderGated.tsx
3812
- var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
3813
-
3814
- // src/form-builder/theme/presets.ts
3815
- var squaredrDarkPreset = {
3816
- background: "#0a0a0b",
3817
- // ink-950
3818
- foreground: "#e8e8ea",
3819
- // ink-100
3820
- card: "#111113",
3821
- // ink-900
3822
- primary: "oklch(0.82 0.14 210)",
3823
- // sr-accent-cyan
3824
- primaryForeground: "#0a0a0b",
3825
- // ink-950
3826
- secondary: "#17171a",
3827
- // ink-850
3828
- secondaryForeground: "#e8e8ea",
3829
- // ink-100
3830
- muted: "#111113",
3831
- // ink-900
3832
- mutedForeground: "#8a8a95",
3833
- // ink-400
3834
- accent: "#17171a",
3835
- // ink-850
3836
- accentForeground: "#e8e8ea",
3837
- // ink-100
3838
- destructive: "oklch(0.68 0.22 25)",
3839
- // sr-error
3840
- destructiveForeground: "#e8e8ea",
3841
- // ink-100
3842
- border: "#1c1c20",
3843
- // ink-800
3844
- input: "#1c1c20",
3845
- // ink-800
3846
- ring: "oklch(0.82 0.14 210)",
3847
- // sr-accent-cyan
3848
- radius: "6px",
3849
- surface: "#111113",
3850
- // ink-900
3851
- surfaceHover: "#17171a",
3852
- // ink-850
3853
- canvas: "#0a0a0b",
3854
- // ink-950
3855
- panel: "#111113",
3856
- // ink-900
3857
- borderStrong: "#26262c",
3858
- // ink-700
3859
- textDim: "#5a5a66"
3860
- // ink-500
3861
- };
3862
- var cleanPreset = {
3863
- background: "#ffffff",
3864
- foreground: "#111113",
3865
- card: "#f9fafb",
3866
- primary: "#0d9488",
3867
- primaryForeground: "#ffffff",
3868
- secondary: "#f3f4f6",
3869
- secondaryForeground: "#111113",
3870
- muted: "#f3f4f6",
3871
- mutedForeground: "#6b7280",
3872
- accent: "#f3f4f6",
3873
- accentForeground: "#111113",
3874
- destructive: "#ef4444",
3875
- destructiveForeground: "#ffffff",
3876
- border: "#e5e7eb",
3877
- input: "#e5e7eb",
3878
- ring: "#0d9488",
3879
- radius: "6px",
3880
- surface: "#f9fafb",
3881
- surfaceHover: "#f3f4f6",
3882
- canvas: "#ffffff",
3883
- panel: "#ffffff",
3884
- borderStrong: "#d1d5db",
3885
- textDim: "#9ca3af"
5366
+ var PRO_FIELD_OVERRIDES = {
5367
+ payment: ProPaymentField
3886
5368
  };
3887
5369
 
3888
5370
  // src/response-viewer/clinical-display-data.ts
@@ -4021,10 +5503,30 @@ function getScoreSeverity(instrumentKey, score) {
4021
5503
  if (!thresholds) return void 0;
4022
5504
  return thresholds.find((t) => score >= t.min && score <= t.max);
4023
5505
  }
4024
- function ResponseTable({ schema, responses, onRowClick }) {
5506
+ function ResponseTable({
5507
+ schema,
5508
+ responses,
5509
+ onRowClick,
5510
+ selectable,
5511
+ selectedIds,
5512
+ onToggleSelect,
5513
+ onSelectAll
5514
+ }) {
4025
5515
  const questions = getAllQuestions(schema);
5516
+ const allPageSelected = selectable && responses.length > 0 && responses.every(
5517
+ (r) => r.sessionToken && selectedIds?.has(r.sessionToken)
5518
+ );
4026
5519
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-[13px]", children: [
4027
5520
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "bg-muted", children: [
5521
+ selectable && /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-3 py-2 w-8 border-b-2 border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
5522
+ "input",
5523
+ {
5524
+ type: "checkbox",
5525
+ checked: allPageSelected,
5526
+ onChange: () => onSelectAll?.(),
5527
+ className: "accent-primary"
5528
+ }
5529
+ ) }),
4028
5530
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-3 py-2 text-left font-semibold text-foreground border-b-2 border-border whitespace-nowrap", children: "Submitted" }),
4029
5531
  questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx(
4030
5532
  "th",
@@ -4043,6 +5545,19 @@ function ResponseTable({ schema, responses, onRowClick }) {
4043
5545
  onClick: () => onRowClick?.(response),
4044
5546
  className: onRowClick ? "cursor-pointer border-b border-border hover:bg-accent transition-colors" : "border-b border-border",
4045
5547
  children: [
5548
+ selectable && /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2 w-8", children: /* @__PURE__ */ jsxRuntime.jsx(
5549
+ "input",
5550
+ {
5551
+ type: "checkbox",
5552
+ checked: !!(response.sessionToken && selectedIds?.has(response.sessionToken)),
5553
+ onChange: (e) => {
5554
+ e.stopPropagation();
5555
+ if (response.sessionToken) onToggleSelect?.(response.sessionToken);
5556
+ },
5557
+ onClick: (e) => e.stopPropagation(),
5558
+ className: "accent-primary"
5559
+ }
5560
+ ) }),
4046
5561
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-3 py-2 text-foreground max-w-50 overflow-hidden text-ellipsis whitespace-nowrap", children: new Date(response.submittedAt).toLocaleString() }),
4047
5562
  questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx(
4048
5563
  "td",
@@ -4060,7 +5575,7 @@ function ResponseTable({ schema, responses, onRowClick }) {
4060
5575
  responses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
4061
5576
  "td",
4062
5577
  {
4063
- colSpan: questions.length + 2,
5578
+ colSpan: questions.length + 2 + (selectable ? 1 : 0),
4064
5579
  className: "px-3 py-8 text-center text-muted-foreground",
4065
5580
  children: "No responses yet"
4066
5581
  }
@@ -4148,15 +5663,37 @@ function formatCellValue(value, type) {
4148
5663
  if (typeof value === "object") return JSON.stringify(value);
4149
5664
  return String(value);
4150
5665
  }
4151
- function ResponseCard({ response, fields, onClick }) {
5666
+ function ResponseCard({
5667
+ response,
5668
+ fields,
5669
+ onClick,
5670
+ selectable,
5671
+ selected,
5672
+ onToggleSelect
5673
+ }) {
4152
5674
  return /* @__PURE__ */ jsxRuntime.jsxs(
4153
5675
  "div",
4154
5676
  {
4155
5677
  onClick,
4156
- className: onClick ? "border border-border rounded-lg p-4 bg-card cursor-pointer transition-shadow hover:shadow-md" : "border border-border rounded-lg p-4 bg-card",
5678
+ className: (onClick ? "border border-border rounded-lg p-4 bg-card cursor-pointer transition-shadow hover:shadow-md" : "border border-border rounded-lg p-4 bg-card") + (selected ? " ring-2 ring-primary" : ""),
4157
5679
  children: [
4158
5680
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between mb-3 text-xs text-muted-foreground", children: [
4159
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(response.submittedAt).toLocaleString() }),
5681
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
5682
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(
5683
+ "input",
5684
+ {
5685
+ type: "checkbox",
5686
+ checked: !!selected,
5687
+ onChange: (e) => {
5688
+ e.stopPropagation();
5689
+ onToggleSelect?.();
5690
+ },
5691
+ onClick: (e) => e.stopPropagation(),
5692
+ className: "accent-primary"
5693
+ }
5694
+ ),
5695
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(response.submittedAt).toLocaleString() })
5696
+ ] }),
4160
5697
  response.completionTimeMs != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4161
5698
  Math.round(response.completionTimeMs / 1e3),
4162
5699
  "s"
@@ -4247,18 +5784,19 @@ function formatCardValue(value, type) {
4247
5784
  if (typeof value === "object") return JSON.stringify(value);
4248
5785
  return String(value);
4249
5786
  }
4250
- function ResponseDetail({ response, fields, onBack }) {
5787
+ function ResponseDetail({ response, fields, onBack, onDelete }) {
4251
5788
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
4252
5789
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-5 pb-3 border-b border-border", children: [
4253
5790
  onBack && /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "outline", size: "sm", onClick: onBack, children: "Back" }),
4254
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5791
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
4255
5792
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-foreground", children: "Response Detail" }),
4256
5793
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground", children: [
4257
5794
  "Submitted ",
4258
5795
  new Date(response.submittedAt).toLocaleString(),
4259
5796
  response.completionTimeMs != null && ` \u2014 ${Math.round(response.completionTimeMs / 1e3)}s`
4260
5797
  ] })
4261
- ] })
5798
+ ] }),
5799
+ onDelete && /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "destructive", size: "sm", onClick: onDelete, children: "Delete" })
4262
5800
  ] }),
4263
5801
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-4", children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
4264
5802
  "div",
@@ -4611,6 +6149,491 @@ function formatFallbackValue(value) {
4611
6149
  if (typeof value === "object") return JSON.stringify(value, null, 2);
4612
6150
  return String(value);
4613
6151
  }
6152
+ function TimelineView({ responses, questions, onSelect }) {
6153
+ const grouped = react.useMemo(() => groupByDay(responses), [responses]);
6154
+ if (responses.length === 0) {
6155
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground text-sm", children: "No responses to display." });
6156
+ }
6157
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 space-y-6", children: grouped.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6158
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
6159
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-border" }),
6160
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground shrink-0", children: group.dateLabel }),
6161
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground shrink-0", children: [
6162
+ "(",
6163
+ group.responses.length,
6164
+ ")"
6165
+ ] }),
6166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-border" })
6167
+ ] }),
6168
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 ml-4 border-l-2 border-border/50 pl-4", children: group.responses.map((response, idx) => /* @__PURE__ */ jsxRuntime.jsx(
6169
+ TimelineEntry,
6170
+ {
6171
+ response,
6172
+ questions,
6173
+ onView: () => onSelect(response)
6174
+ },
6175
+ response.sessionToken || idx
6176
+ )) })
6177
+ ] }, group.dateLabel)) });
6178
+ }
6179
+ function TimelineEntry({
6180
+ response,
6181
+ questions,
6182
+ onView
6183
+ }) {
6184
+ const time = new Date(response.submittedAt).toLocaleTimeString([], {
6185
+ hour: "2-digit",
6186
+ minute: "2-digit"
6187
+ });
6188
+ const preview = [];
6189
+ for (const q of questions) {
6190
+ if (preview.length >= 3) break;
6191
+ const val = response.values[q.id];
6192
+ if (val != null && val !== "") {
6193
+ preview.push({ questionId: q.id, label: q.label, type: q.type, value: val });
6194
+ }
6195
+ }
6196
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-start gap-3 group", children: [
6197
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute -left-[21px] top-2.5 w-2 h-2 rounded-full bg-border group-hover:bg-primary transition-colors" }),
6198
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 rounded border border-border bg-card p-3 hover:border-primary/40 transition-colors", children: [
6199
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
6200
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: time }),
6201
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6202
+ response.totalScore != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
6203
+ "Score: ",
6204
+ response.totalScore
6205
+ ] }),
6206
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onView, children: "View" })
6207
+ ] })
6208
+ ] }),
6209
+ preview.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-x-4 gap-y-1", children: preview.map((field) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs", children: [
6210
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
6211
+ field.label,
6212
+ ": "
6213
+ ] }),
6214
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: formatPreviewValue(field.value, field.type) })
6215
+ ] }, field.questionId)) })
6216
+ ] })
6217
+ ] });
6218
+ }
6219
+ function groupByDay(responses) {
6220
+ const sorted = [...responses].sort(
6221
+ (a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime()
6222
+ );
6223
+ const groups = /* @__PURE__ */ new Map();
6224
+ for (const r of sorted) {
6225
+ const date = new Date(r.submittedAt).toLocaleDateString(void 0, {
6226
+ weekday: "long",
6227
+ year: "numeric",
6228
+ month: "long",
6229
+ day: "numeric"
6230
+ });
6231
+ const existing = groups.get(date);
6232
+ if (existing) {
6233
+ existing.push(r);
6234
+ } else {
6235
+ groups.set(date, [r]);
6236
+ }
6237
+ }
6238
+ return Array.from(groups.entries()).map(([dateLabel, responses2]) => ({
6239
+ dateLabel,
6240
+ responses: responses2
6241
+ }));
6242
+ }
6243
+ function formatPreviewValue(value, type) {
6244
+ if (value == null) return "\u2014";
6245
+ if (typeof value === "boolean") return value ? "Yes" : "No";
6246
+ if (Array.isArray(value)) return value.length > 0 ? value.slice(0, 2).join(", ") + (value.length > 2 ? "..." : "") : "\u2014";
6247
+ if (typeof value === "object") {
6248
+ if (type === "legal_name") {
6249
+ const n = value;
6250
+ return [n.first, n.last].filter(Boolean).join(" ") || "\u2014";
6251
+ }
6252
+ return "...";
6253
+ }
6254
+ const str = String(value);
6255
+ return str.length > 40 ? str.slice(0, 40) + "..." : str;
6256
+ }
6257
+
6258
+ // src/response-viewer/stats-utils.ts
6259
+ function computeStats(responses, schema) {
6260
+ if (responses.length === 0) {
6261
+ return {
6262
+ totalCount: 0,
6263
+ completionRate: 0,
6264
+ avgCompletionTimeMs: null,
6265
+ dateRange: null,
6266
+ fieldSummaries: []
6267
+ };
6268
+ }
6269
+ const questions = getStatsQuestions(schema);
6270
+ const requiredIds = new Set(questions.filter((q) => q.required).map((q) => q.id));
6271
+ let completedCount = 0;
6272
+ for (const r of responses) {
6273
+ let allFilled = true;
6274
+ for (const id of requiredIds) {
6275
+ if (r.values[id] == null || r.values[id] === "") {
6276
+ allFilled = false;
6277
+ break;
6278
+ }
6279
+ }
6280
+ if (allFilled) completedCount++;
6281
+ }
6282
+ const completionRate = requiredIds.size > 0 ? completedCount / responses.length : 1;
6283
+ const completionTimes = responses.map((r) => r.completionTimeMs).filter((t) => t != null && t > 0);
6284
+ const avgCompletionTimeMs = completionTimes.length > 0 ? completionTimes.reduce((a, b) => a + b, 0) / completionTimes.length : null;
6285
+ const timestamps = responses.map((r) => new Date(r.submittedAt).getTime());
6286
+ const earliest = new Date(Math.min(...timestamps)).toISOString();
6287
+ const latest = new Date(Math.max(...timestamps)).toISOString();
6288
+ const fieldSummaries = questions.map((q) => computeFieldSummary(q, responses));
6289
+ return {
6290
+ totalCount: responses.length,
6291
+ completionRate,
6292
+ avgCompletionTimeMs,
6293
+ dateRange: { earliest, latest },
6294
+ fieldSummaries
6295
+ };
6296
+ }
6297
+ function computeFieldSummary(question, responses) {
6298
+ const values = responses.map((r) => r.values[question.id]).filter((v) => v != null && v !== "");
6299
+ const filledCount = values.length;
6300
+ const filledRate = responses.length > 0 ? filledCount / responses.length : 0;
6301
+ const base = {
6302
+ fieldId: question.id,
6303
+ label: question.label,
6304
+ type: question.type,
6305
+ filledCount,
6306
+ filledRate
6307
+ };
6308
+ if (isNumericType(question.type)) {
6309
+ const nums = values.map((v) => typeof v === "number" ? v : Number(v)).filter((n) => !isNaN(n));
6310
+ if (nums.length > 0) {
6311
+ return {
6312
+ ...base,
6313
+ kind: "numeric",
6314
+ mean: nums.reduce((a, b) => a + b, 0) / nums.length,
6315
+ min: Math.min(...nums),
6316
+ max: Math.max(...nums)
6317
+ };
6318
+ }
6319
+ return { ...base, kind: "other" };
6320
+ }
6321
+ if (isCategoricalType(question.type)) {
6322
+ const counts = /* @__PURE__ */ new Map();
6323
+ for (const v of values) {
6324
+ const key = String(v);
6325
+ counts.set(key, (counts.get(key) ?? 0) + 1);
6326
+ }
6327
+ let mode = "";
6328
+ let modeCount = 0;
6329
+ for (const [key, count] of counts) {
6330
+ if (count > modeCount) {
6331
+ mode = key;
6332
+ modeCount = count;
6333
+ }
6334
+ }
6335
+ return {
6336
+ ...base,
6337
+ kind: "categorical",
6338
+ mode,
6339
+ modeCount,
6340
+ uniqueCount: counts.size
6341
+ };
6342
+ }
6343
+ return { ...base, kind: "other" };
6344
+ }
6345
+ function isNumericType(type) {
6346
+ return [
6347
+ "number",
6348
+ "slider",
6349
+ "rating",
6350
+ "nps",
6351
+ "opinion_scale",
6352
+ "pain_scale"
6353
+ ].includes(type);
6354
+ }
6355
+ function isCategoricalType(type) {
6356
+ return [
6357
+ "single_select",
6358
+ "dropdown",
6359
+ "country_select",
6360
+ "boolean",
6361
+ "consent",
6362
+ "multi_select",
6363
+ "checkbox_group"
6364
+ ].includes(type);
6365
+ }
6366
+ function getStatsQuestions(schema) {
6367
+ const questions = [];
6368
+ for (const section of schema.sections) {
6369
+ for (const q of section.questions) {
6370
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
6371
+ continue;
6372
+ }
6373
+ questions.push(q);
6374
+ }
6375
+ }
6376
+ return questions;
6377
+ }
6378
+ function StatsPanel({ schema, responses, onClose }) {
6379
+ const stats = react.useMemo(() => computeStats(responses, schema), [responses, schema]);
6380
+ if (stats.totalCount === 0) {
6381
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 text-sm text-muted-foreground", children: "No responses to compute statistics." });
6382
+ }
6383
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border bg-muted/30", children: [
6384
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/50", children: [
6385
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: "Statistics" }),
6386
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onClose, children: "Close" })
6387
+ ] }),
6388
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
6389
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3", children: [
6390
+ /* @__PURE__ */ jsxRuntime.jsx(
6391
+ StatCard,
6392
+ {
6393
+ label: "Total Responses",
6394
+ value: String(stats.totalCount)
6395
+ }
6396
+ ),
6397
+ /* @__PURE__ */ jsxRuntime.jsx(
6398
+ StatCard,
6399
+ {
6400
+ label: "Completion Rate",
6401
+ value: `${Math.round(stats.completionRate * 100)}%`
6402
+ }
6403
+ ),
6404
+ /* @__PURE__ */ jsxRuntime.jsx(
6405
+ StatCard,
6406
+ {
6407
+ label: "Avg. Completion Time",
6408
+ value: formatTime(stats.avgCompletionTimeMs)
6409
+ }
6410
+ ),
6411
+ /* @__PURE__ */ jsxRuntime.jsx(
6412
+ StatCard,
6413
+ {
6414
+ label: "Date Range",
6415
+ value: formatDateRange(stats.dateRange)
6416
+ }
6417
+ )
6418
+ ] }),
6419
+ stats.fieldSummaries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 grid grid-cols-2 sm:grid-cols-3 gap-2", children: stats.fieldSummaries.slice(0, 6).map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
6420
+ "div",
6421
+ {
6422
+ className: "px-2.5 py-2 rounded border border-border bg-background text-xs",
6423
+ children: [
6424
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground truncate mb-1", children: f.label }),
6425
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
6426
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-foreground font-medium", children: [
6427
+ Math.round(f.filledRate * 100),
6428
+ "% filled"
6429
+ ] }),
6430
+ f.kind === "numeric" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground", children: [
6431
+ "avg ",
6432
+ f.mean.toFixed(1),
6433
+ " (",
6434
+ f.min,
6435
+ "\u2013",
6436
+ f.max,
6437
+ ")"
6438
+ ] }),
6439
+ f.kind === "categorical" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground truncate", children: [
6440
+ "top: ",
6441
+ f.mode,
6442
+ " (",
6443
+ f.modeCount,
6444
+ ")"
6445
+ ] })
6446
+ ] })
6447
+ ]
6448
+ },
6449
+ f.fieldId
6450
+ )) })
6451
+ ] })
6452
+ ] });
6453
+ }
6454
+ function StatCard({ label, value }) {
6455
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-2 rounded border border-border bg-background", children: [
6456
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[11px] text-muted-foreground mb-0.5", children: label }),
6457
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-foreground", children: value })
6458
+ ] });
6459
+ }
6460
+ function formatTime(ms) {
6461
+ if (ms == null) return "\u2014";
6462
+ const seconds = Math.round(ms / 1e3);
6463
+ if (seconds < 60) return `${seconds}s`;
6464
+ const minutes = Math.floor(seconds / 60);
6465
+ const remainingSeconds = seconds % 60;
6466
+ return `${minutes}m ${remainingSeconds}s`;
6467
+ }
6468
+ function formatDateRange(range) {
6469
+ if (!range) return "\u2014";
6470
+ const fmt = (iso) => new Date(iso).toLocaleDateString();
6471
+ return `${fmt(range.earliest)} \u2013 ${fmt(range.latest)}`;
6472
+ }
6473
+
6474
+ // src/response-viewer/chart-utils.ts
6475
+ function computeFieldDistribution(responses, fieldId) {
6476
+ const counts = /* @__PURE__ */ new Map();
6477
+ let total = 0;
6478
+ for (const r of responses) {
6479
+ const raw = r.values[fieldId];
6480
+ if (raw == null || raw === "") continue;
6481
+ total++;
6482
+ if (typeof raw === "boolean") {
6483
+ const key = raw ? "Yes" : "No";
6484
+ counts.set(key, (counts.get(key) ?? 0) + 1);
6485
+ } else if (Array.isArray(raw)) {
6486
+ for (const item of raw) {
6487
+ const key = String(item);
6488
+ counts.set(key, (counts.get(key) ?? 0) + 1);
6489
+ }
6490
+ } else {
6491
+ const key = String(raw);
6492
+ counts.set(key, (counts.get(key) ?? 0) + 1);
6493
+ }
6494
+ }
6495
+ if (total === 0) return [];
6496
+ const entries = [];
6497
+ for (const [value, count] of counts) {
6498
+ entries.push({
6499
+ value,
6500
+ count,
6501
+ percentage: count / total * 100
6502
+ });
6503
+ }
6504
+ entries.sort((a, b) => b.count - a.count);
6505
+ return entries;
6506
+ }
6507
+ function getChartableFieldIds(schema) {
6508
+ const chartable = [];
6509
+ const chartTypes = /* @__PURE__ */ new Set([
6510
+ "single_select",
6511
+ "dropdown",
6512
+ "country_select",
6513
+ "boolean",
6514
+ "consent",
6515
+ "multi_select",
6516
+ "checkbox_group",
6517
+ "rating",
6518
+ "nps",
6519
+ "opinion_scale",
6520
+ "pain_scale"
6521
+ ]);
6522
+ for (const section of schema.sections) {
6523
+ for (const q of section.questions) {
6524
+ if (chartTypes.has(q.type)) {
6525
+ chartable.push({ id: q.id, label: q.label, type: q.type });
6526
+ }
6527
+ }
6528
+ }
6529
+ return chartable;
6530
+ }
6531
+ function ChartPanel({ schema, responses, onClose }) {
6532
+ const chartableFields = react.useMemo(() => getChartableFieldIds(schema), [schema]);
6533
+ const [selectedFieldId, setSelectedFieldId] = react.useState(chartableFields[0]?.id ?? "");
6534
+ const distribution = react.useMemo(() => {
6535
+ if (!selectedFieldId) return [];
6536
+ return computeFieldDistribution(responses, selectedFieldId);
6537
+ }, [responses, selectedFieldId]);
6538
+ const selectedField = chartableFields.find((f) => f.id === selectedFieldId);
6539
+ const maxCount = distribution.length > 0 ? Math.max(...distribution.map((d) => d.count)) : 1;
6540
+ if (chartableFields.length === 0) {
6541
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-6 border-b border-border bg-muted/30 text-center", children: [
6542
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "No chartable fields found in this schema." }),
6543
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Charts work with select, dropdown, boolean, rating, and similar field types." })
6544
+ ] });
6545
+ }
6546
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border bg-muted/30", children: [
6547
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border/50", children: [
6548
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
6549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: "Field Distribution" }),
6550
+ /* @__PURE__ */ jsxRuntime.jsx(
6551
+ "select",
6552
+ {
6553
+ value: selectedFieldId,
6554
+ onChange: (e) => setSelectedFieldId(e.target.value),
6555
+ className: "text-xs bg-background border border-border rounded px-2 py-1 text-foreground",
6556
+ children: chartableFields.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.id, children: f.label }, f.id))
6557
+ }
6558
+ )
6559
+ ] }),
6560
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "ghost", size: "sm", className: "h-6 text-xs", onClick: onClose, children: "Close" })
6561
+ ] }),
6562
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
6563
+ distribution.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground text-center py-4", children: "No data for this field." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
6564
+ distribution.slice(0, 10).map((entry) => /* @__PURE__ */ jsxRuntime.jsx(BarRow, { entry, maxCount }, entry.value)),
6565
+ distribution.length > 10 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground mt-2", children: [
6566
+ "+",
6567
+ distribution.length - 10,
6568
+ " more values"
6569
+ ] })
6570
+ ] }),
6571
+ selectedField && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 text-[11px] text-muted-foreground", children: [
6572
+ responses.length,
6573
+ " response",
6574
+ responses.length !== 1 ? "s" : "",
6575
+ " \xB7 ",
6576
+ distribution.length,
6577
+ " unique value",
6578
+ distribution.length !== 1 ? "s" : ""
6579
+ ] })
6580
+ ] })
6581
+ ] });
6582
+ }
6583
+ function BarRow({ entry, maxCount }) {
6584
+ const widthPct = maxCount > 0 ? entry.count / maxCount * 100 : 0;
6585
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6586
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-24 shrink-0 text-xs text-foreground truncate", title: entry.value, children: entry.value }),
6587
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-5 bg-background rounded overflow-hidden border border-border/50", children: /* @__PURE__ */ jsxRuntime.jsx(
6588
+ "div",
6589
+ {
6590
+ className: "h-full bg-primary/60 rounded transition-all",
6591
+ style: { width: `${widthPct}%` }
6592
+ }
6593
+ ) }),
6594
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-16 shrink-0 text-xs text-muted-foreground text-right", children: [
6595
+ entry.count,
6596
+ " (",
6597
+ Math.round(entry.percentage),
6598
+ "%)"
6599
+ ] })
6600
+ ] });
6601
+ }
6602
+ function ConfirmDialog2({
6603
+ title,
6604
+ message,
6605
+ confirmLabel = "Confirm",
6606
+ cancelLabel = "Cancel",
6607
+ variant = "default",
6608
+ onConfirm,
6609
+ onCancel
6610
+ }) {
6611
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
6612
+ /* @__PURE__ */ jsxRuntime.jsx(
6613
+ "div",
6614
+ {
6615
+ className: "absolute inset-0 bg-black/50",
6616
+ onClick: onCancel
6617
+ }
6618
+ ),
6619
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative bg-background border border-border rounded-lg shadow-lg p-6 max-w-sm w-full mx-4", children: [
6620
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-foreground mb-2", children: title }),
6621
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mb-4", children: message }),
6622
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
6623
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "outline", size: "sm", onClick: onCancel, children: cancelLabel }),
6624
+ /* @__PURE__ */ jsxRuntime.jsx(
6625
+ fieldcraftReact.Button,
6626
+ {
6627
+ variant: variant === "destructive" ? "destructive" : "default",
6628
+ size: "sm",
6629
+ onClick: onConfirm,
6630
+ children: confirmLabel
6631
+ }
6632
+ )
6633
+ ] })
6634
+ ] })
6635
+ ] });
6636
+ }
4614
6637
 
4615
6638
  // src/response-viewer/export-utils.ts
4616
6639
  function buildCsvContent(schema, responses, options = {}) {
@@ -4745,6 +6768,84 @@ function paginate(items, currentPage, pageSize) {
4745
6768
  };
4746
6769
  }
4747
6770
 
6771
+ // src/response-viewer/search-utils.ts
6772
+ function searchResponses(responses, query, schema) {
6773
+ if (!query.trim()) return responses;
6774
+ const lowerQuery = query.toLowerCase().trim();
6775
+ const questions = getSearchableQuestions(schema);
6776
+ return responses.filter((response) => {
6777
+ if (new Date(response.submittedAt).toLocaleString().toLowerCase().includes(lowerQuery)) {
6778
+ return true;
6779
+ }
6780
+ if (response.sessionToken?.toLowerCase().includes(lowerQuery)) {
6781
+ return true;
6782
+ }
6783
+ for (const q of questions) {
6784
+ const value = response.values[q.id];
6785
+ if (value == null) continue;
6786
+ const stringified = stringifyValue(value, q.type);
6787
+ if (stringified.toLowerCase().includes(lowerQuery)) {
6788
+ return true;
6789
+ }
6790
+ }
6791
+ if (response.totalScore != null && String(response.totalScore).includes(lowerQuery)) {
6792
+ return true;
6793
+ }
6794
+ return false;
6795
+ });
6796
+ }
6797
+ function getSearchableQuestions(schema) {
6798
+ const questions = [];
6799
+ for (const section of schema.sections) {
6800
+ for (const q of section.questions) {
6801
+ if (q.type === "info-block" || q.type === "section-header" || q.type === "page-break") {
6802
+ continue;
6803
+ }
6804
+ questions.push(q);
6805
+ }
6806
+ }
6807
+ return questions;
6808
+ }
6809
+ function stringifyValue(value, type) {
6810
+ if (value == null) return "";
6811
+ switch (type) {
6812
+ case "vitals_entry": {
6813
+ if (typeof value !== "object" || value === null) break;
6814
+ const v = value;
6815
+ const parts = [];
6816
+ if (v.systolicBp && v.diastolicBp) parts.push(`BP ${v.systolicBp}/${v.diastolicBp}`);
6817
+ if (v.heartRate) parts.push(`HR ${v.heartRate}`);
6818
+ if (v.temperature) parts.push(`${v.temperature}`);
6819
+ if (v.oxygenSaturation) parts.push(`SpO2 ${v.oxygenSaturation}`);
6820
+ return parts.join(" ");
6821
+ }
6822
+ case "medication_list":
6823
+ if (Array.isArray(value)) {
6824
+ return value.map((m) => [m.name, m.dosage, m.frequency].filter(Boolean).join(" ")).join(" ");
6825
+ }
6826
+ break;
6827
+ case "allergy_list":
6828
+ if (Array.isArray(value)) {
6829
+ return value.map((a) => [a.allergen, a.severity].filter(Boolean).join(" ")).join(" ");
6830
+ }
6831
+ break;
6832
+ case "legal_name": {
6833
+ if (typeof value !== "object" || value === null) break;
6834
+ const n = value;
6835
+ return [n.first, n.middle, n.last].filter(Boolean).join(" ");
6836
+ }
6837
+ case "address": {
6838
+ if (typeof value !== "object" || value === null) break;
6839
+ const a = value;
6840
+ return [a.street, a.city, a.state, a.zip].filter(Boolean).join(" ");
6841
+ }
6842
+ }
6843
+ if (typeof value === "boolean") return value ? "Yes" : "No";
6844
+ if (Array.isArray(value)) return value.map(String).join(" ");
6845
+ if (typeof value === "object") return JSON.stringify(value);
6846
+ return String(value);
6847
+ }
6848
+
4748
6849
  // src/response-viewer/filter-utils.ts
4749
6850
  function createEmptyFilterState() {
4750
6851
  return { filters: [], dateRange: {} };
@@ -4843,7 +6944,11 @@ function ResponseViewerInner({
4843
6944
  dateFormat,
4844
6945
  columnLabels,
4845
6946
  onExport,
4846
- pageSize = 25
6947
+ pageSize = 25,
6948
+ onDelete,
6949
+ onBulkDelete,
6950
+ onBulkExport,
6951
+ selectable = false
4847
6952
  }) {
4848
6953
  const [viewMode, setViewMode] = react.useState("table");
4849
6954
  const [selectedResponse, setSelectedResponse] = react.useState(null);
@@ -4851,6 +6956,11 @@ function ResponseViewerInner({
4851
6956
  const [effectivePageSize, setEffectivePageSize] = react.useState(pageSize);
4852
6957
  const [filterState, setFilterState] = react.useState(createEmptyFilterState);
4853
6958
  const [showFilterPanel, setShowFilterPanel] = react.useState(false);
6959
+ const [searchQuery, setSearchQuery] = react.useState("");
6960
+ const [showStats, setShowStats] = react.useState(false);
6961
+ const [showCharts, setShowCharts] = react.useState(false);
6962
+ const [selectedIds, setSelectedIds] = react.useState(/* @__PURE__ */ new Set());
6963
+ const [confirmDialog, setConfirmDialog] = react.useState(null);
4854
6964
  const [draftFieldId, setDraftFieldId] = react.useState("");
4855
6965
  const [draftOperator, setDraftOperator] = react.useState("contains");
4856
6966
  const [draftValue, setDraftValue] = react.useState("");
@@ -4861,9 +6971,18 @@ function ResponseViewerInner({
4861
6971
  setEffectivePageSize(pageSize);
4862
6972
  setCurrentPage(1);
4863
6973
  }, [pageSize]);
6974
+ react.useEffect(() => {
6975
+ setSelectedIds(/* @__PURE__ */ new Set());
6976
+ }, [responses]);
6977
+ const searchedResponses = react.useMemo(
6978
+ () => searchResponses(responses, searchQuery, schema),
6979
+ [responses, searchQuery, schema]
6980
+ );
4864
6981
  const isFiltered = hasActiveFilters(filterState);
4865
- const filteredResponses = applyFilters(responses, filterState);
6982
+ const filteredResponses = applyFilters(searchedResponses, filterState);
4866
6983
  const pag = paginate(filteredResponses, currentPage, effectivePageSize);
6984
+ const questions = getAllQuestions2(schema);
6985
+ const isSelectable = selectable && (!!onBulkDelete || !!onBulkExport);
4867
6986
  function handleSelect(response) {
4868
6987
  setSelectedResponse(response);
4869
6988
  onResponseSelect?.(response);
@@ -4910,7 +7029,59 @@ function ResponseViewerInner({
4910
7029
  setFilterState(createEmptyFilterState());
4911
7030
  setCurrentPage(1);
4912
7031
  }
4913
- const questions = getAllQuestions2(schema);
7032
+ function handleToggleSelect(token) {
7033
+ setSelectedIds((prev) => {
7034
+ const next = new Set(prev);
7035
+ if (next.has(token)) {
7036
+ next.delete(token);
7037
+ } else {
7038
+ next.add(token);
7039
+ }
7040
+ return next;
7041
+ });
7042
+ }
7043
+ function handleSelectAll() {
7044
+ const currentTokens = pag.pageItems.map((r) => r.sessionToken).filter(Boolean);
7045
+ const allSelected = currentTokens.every((t) => selectedIds.has(t));
7046
+ setSelectedIds((prev) => {
7047
+ const next = new Set(prev);
7048
+ if (allSelected) {
7049
+ currentTokens.forEach((t) => next.delete(t));
7050
+ } else {
7051
+ currentTokens.forEach((t) => next.add(t));
7052
+ }
7053
+ return next;
7054
+ });
7055
+ }
7056
+ function handleBulkDelete() {
7057
+ if (!onBulkDelete || selectedIds.size === 0) return;
7058
+ setConfirmDialog({
7059
+ title: "Delete selected responses",
7060
+ message: `Are you sure you want to delete ${selectedIds.size} response${selectedIds.size !== 1 ? "s" : ""}? This cannot be undone.`,
7061
+ onConfirm: () => {
7062
+ onBulkDelete(Array.from(selectedIds));
7063
+ setSelectedIds(/* @__PURE__ */ new Set());
7064
+ setConfirmDialog(null);
7065
+ }
7066
+ });
7067
+ }
7068
+ function handleBulkExport() {
7069
+ if (!onBulkExport || selectedIds.size === 0) return;
7070
+ const selected = responses.filter((r) => r.sessionToken && selectedIds.has(r.sessionToken));
7071
+ onBulkExport(selected);
7072
+ }
7073
+ function handleDeleteSingle(token) {
7074
+ if (!onDelete) return;
7075
+ setConfirmDialog({
7076
+ title: "Delete response",
7077
+ message: "Are you sure you want to delete this response? This cannot be undone.",
7078
+ onConfirm: () => {
7079
+ onDelete(token);
7080
+ setSelectedResponse(null);
7081
+ setConfirmDialog(null);
7082
+ }
7083
+ });
7084
+ }
4914
7085
  function getFields(response) {
4915
7086
  return questions.map((q) => ({
4916
7087
  questionId: q.id,
@@ -4921,6 +7092,19 @@ function ResponseViewerInner({
4921
7092
  }
4922
7093
  const heightValue = typeof height === "number" ? `${height}px` : height;
4923
7094
  const widthValue = typeof width === "number" ? `${width}px` : width;
7095
+ const countText = (() => {
7096
+ const hasSearch = searchQuery.trim().length > 0;
7097
+ if (hasSearch && isFiltered) {
7098
+ return `${filteredResponses.length} of ${responses.length} responses (searched + filtered)`;
7099
+ }
7100
+ if (hasSearch) {
7101
+ return `${searchedResponses.length} result${searchedResponses.length !== 1 ? "s" : ""} for "${searchQuery}"`;
7102
+ }
7103
+ if (isFiltered) {
7104
+ return `Showing ${filteredResponses.length} of ${responses.length} responses (filtered)`;
7105
+ }
7106
+ return `${responses.length} response${responses.length !== 1 ? "s" : ""}`;
7107
+ })();
4924
7108
  return /* @__PURE__ */ jsxRuntime.jsxs(
4925
7109
  "div",
4926
7110
  {
@@ -4928,7 +7112,20 @@ function ResponseViewerInner({
4928
7112
  style: { height: heightValue, width: widthValue },
4929
7113
  children: [
4930
7114
  !selectedResponse && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-b border-border", children: [
4931
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground mr-auto", children: isFiltered ? `Showing ${filteredResponses.length} of ${responses.length} responses (filtered)` : `${responses.length} response${responses.length !== 1 ? "s" : ""}` }),
7115
+ /* @__PURE__ */ jsxRuntime.jsx(
7116
+ fieldcraftReact.Input,
7117
+ {
7118
+ type: "text",
7119
+ placeholder: "Search responses...",
7120
+ className: "h-7 text-xs w-40",
7121
+ value: searchQuery,
7122
+ onChange: (e) => {
7123
+ setSearchQuery(e.target.value);
7124
+ setCurrentPage(1);
7125
+ }
7126
+ }
7127
+ ),
7128
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground mr-auto", children: countText }),
4932
7129
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
4933
7130
  /* @__PURE__ */ jsxRuntime.jsx(
4934
7131
  fieldcraftReact.Button,
@@ -4955,6 +7152,48 @@ function ResponseViewerInner({
4955
7152
  onClick: () => setViewMode("card"),
4956
7153
  children: "Cards"
4957
7154
  }
7155
+ ),
7156
+ /* @__PURE__ */ jsxRuntime.jsx(
7157
+ fieldcraftReact.Button,
7158
+ {
7159
+ variant: viewMode === "timeline" ? "secondary" : "ghost",
7160
+ size: "sm",
7161
+ className: cn(
7162
+ "h-7 text-xs",
7163
+ viewMode === "timeline" && "font-semibold border border-ring"
7164
+ ),
7165
+ onClick: () => setViewMode("timeline"),
7166
+ children: "Timeline"
7167
+ }
7168
+ )
7169
+ ] }),
7170
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
7171
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
7172
+ /* @__PURE__ */ jsxRuntime.jsx(
7173
+ fieldcraftReact.Button,
7174
+ {
7175
+ variant: showStats ? "secondary" : "ghost",
7176
+ size: "sm",
7177
+ className: cn(
7178
+ "h-7 text-xs",
7179
+ showStats && "font-semibold border border-ring"
7180
+ ),
7181
+ onClick: () => setShowStats((v) => !v),
7182
+ children: "Stats"
7183
+ }
7184
+ ),
7185
+ /* @__PURE__ */ jsxRuntime.jsx(
7186
+ fieldcraftReact.Button,
7187
+ {
7188
+ variant: showCharts ? "secondary" : "ghost",
7189
+ size: "sm",
7190
+ className: cn(
7191
+ "h-7 text-xs",
7192
+ showCharts && "font-semibold border border-ring"
7193
+ ),
7194
+ onClick: () => setShowCharts((v) => !v),
7195
+ children: "Charts"
7196
+ }
4958
7197
  )
4959
7198
  ] }),
4960
7199
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Separator, { orientation: "vertical", className: "h-5 hidden sm:block" }),
@@ -5020,6 +7259,26 @@ function ResponseViewerInner({
5020
7259
  )
5021
7260
  ] })
5022
7261
  ] }),
7262
+ !selectedResponse && isSelectable && selectedIds.size > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border bg-accent/50", children: [
7263
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium text-foreground", children: [
7264
+ selectedIds.size,
7265
+ " selected"
7266
+ ] }),
7267
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
7268
+ onBulkExport && /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "outline", size: "sm", className: "h-6 text-xs", onClick: handleBulkExport, children: "Export Selected" }),
7269
+ onBulkDelete && /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { variant: "destructive", size: "sm", className: "h-6 text-xs", onClick: handleBulkDelete, children: "Delete Selected" }),
7270
+ /* @__PURE__ */ jsxRuntime.jsx(
7271
+ fieldcraftReact.Button,
7272
+ {
7273
+ variant: "ghost",
7274
+ size: "sm",
7275
+ className: "h-6 text-xs",
7276
+ onClick: () => setSelectedIds(/* @__PURE__ */ new Set()),
7277
+ children: "Clear"
7278
+ }
7279
+ )
7280
+ ] })
7281
+ ] }),
5023
7282
  !selectedResponse && showFilterPanel && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-filter__panel px-3 py-2 border-b border-border bg-muted/50", children: [
5024
7283
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-2", children: [
5025
7284
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground w-16 shrink-0", children: "Date:" }),
@@ -5148,19 +7407,47 @@ function ResponseViewerInner({
5148
7407
  );
5149
7408
  })
5150
7409
  ] }),
5151
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsxRuntime.jsx(
7410
+ !selectedResponse && showStats && /* @__PURE__ */ jsxRuntime.jsx(
7411
+ StatsPanel,
7412
+ {
7413
+ schema,
7414
+ responses: filteredResponses,
7415
+ onClose: () => setShowStats(false)
7416
+ }
7417
+ ),
7418
+ !selectedResponse && showCharts && /* @__PURE__ */ jsxRuntime.jsx(
7419
+ ChartPanel,
7420
+ {
7421
+ schema,
7422
+ responses: filteredResponses,
7423
+ onClose: () => setShowCharts(false)
7424
+ }
7425
+ ),
7426
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex-1 min-w-0 overflow-auto", selectedResponse && "p-4"), children: selectedResponse ? /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
5152
7427
  ResponseDetail,
5153
7428
  {
5154
7429
  response: selectedResponse,
5155
7430
  fields: getFields(selectedResponse),
5156
- onBack: handleBack
7431
+ onBack: handleBack,
7432
+ onDelete: onDelete && selectedResponse.sessionToken ? () => handleDeleteSingle(selectedResponse.sessionToken) : void 0
7433
+ }
7434
+ ) }) : viewMode === "timeline" ? /* @__PURE__ */ jsxRuntime.jsx(
7435
+ TimelineView,
7436
+ {
7437
+ responses: filteredResponses,
7438
+ questions,
7439
+ onSelect: handleSelect
5157
7440
  }
5158
7441
  ) : viewMode === "table" ? /* @__PURE__ */ jsxRuntime.jsx(
5159
7442
  ResponseTable,
5160
7443
  {
5161
7444
  schema,
5162
7445
  responses: pag.pageItems,
5163
- onRowClick: handleSelect
7446
+ onRowClick: handleSelect,
7447
+ selectable: isSelectable,
7448
+ selectedIds,
7449
+ onToggleSelect: handleToggleSelect,
7450
+ onSelectAll: handleSelectAll
5164
7451
  }
5165
7452
  ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 p-3 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]", children: [
5166
7453
  pag.pageItems.map((response, idx) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -5168,13 +7455,16 @@ function ResponseViewerInner({
5168
7455
  {
5169
7456
  response,
5170
7457
  fields: getFields(response),
5171
- onClick: () => handleSelect(response)
7458
+ onClick: () => handleSelect(response),
7459
+ selectable: isSelectable,
7460
+ selected: !!(response.sessionToken && selectedIds.has(response.sessionToken)),
7461
+ onToggleSelect: response.sessionToken ? () => handleToggleSelect(response.sessionToken) : void 0
5172
7462
  },
5173
7463
  response.sessionToken || idx
5174
7464
  )),
5175
- filteredResponses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered ? "No matching responses" : "No responses yet" })
7465
+ filteredResponses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground", children: isFiltered || searchQuery.trim() ? "No matching responses" : "No responses yet" })
5176
7466
  ] }) }),
5177
- !selectedResponse && pag.showPagination && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-t border-border", children: [
7467
+ !selectedResponse && viewMode !== "timeline" && pag.showPagination && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-t border-border", children: [
5178
7468
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fc-rv-pagination__info text-xs text-muted-foreground mr-auto", children: [
5179
7469
  "Showing ",
5180
7470
  pag.startItem,
@@ -5228,7 +7518,18 @@ function ResponseViewerInner({
5228
7518
  }
5229
7519
  )
5230
7520
  ] })
5231
- ] })
7521
+ ] }),
7522
+ confirmDialog && /* @__PURE__ */ jsxRuntime.jsx(
7523
+ ConfirmDialog2,
7524
+ {
7525
+ title: confirmDialog.title,
7526
+ message: confirmDialog.message,
7527
+ confirmLabel: "Delete",
7528
+ variant: "destructive",
7529
+ onConfirm: confirmDialog.onConfirm,
7530
+ onCancel: () => setConfirmDialog(null)
7531
+ }
7532
+ )
5232
7533
  ]
5233
7534
  }
5234
7535
  );
@@ -5301,7 +7602,537 @@ var PREVIEW_SCHEMA = {
5301
7602
  ],
5302
7603
  submitAction: { type: "adapter" }
5303
7604
  };
7605
+
7606
+ // src/theme-editor/css-utils.ts
7607
+ var COLOR_MAP = {
7608
+ primary: "--fc-primary",
7609
+ primaryForeground: "--fc-primary-foreground",
7610
+ secondary: "--fc-secondary",
7611
+ secondaryForeground: "--fc-secondary-foreground",
7612
+ error: "--fc-error",
7613
+ errorForeground: "--fc-error-foreground",
7614
+ warning: "--fc-warning",
7615
+ success: "--fc-success",
7616
+ surface: "--fc-surface",
7617
+ background: "--fc-background",
7618
+ text: "--fc-text",
7619
+ textMuted: "--fc-text-muted",
7620
+ textDisabled: "--fc-text-disabled",
7621
+ border: "--fc-border",
7622
+ borderFocus: "--fc-border-focus",
7623
+ inputBackground: "--fc-input-background"
7624
+ };
7625
+ var TYPOGRAPHY_MAP = {
7626
+ fontFamily: "--fc-font-family",
7627
+ scale: "--fc-scale",
7628
+ questionSize: "--fc-question-size",
7629
+ labelSize: "--fc-label-size",
7630
+ helpTextSize: "--fc-help-text-size",
7631
+ bodySize: "--fc-body-size"
7632
+ };
7633
+ var SHAPE_MAP = {
7634
+ radius: "--fc-radius",
7635
+ inputRadius: "--fc-input-radius",
7636
+ buttonRadius: "--fc-button-radius",
7637
+ cardRadius: "--fc-card-radius"
7638
+ };
7639
+ var SPACING_MAP = {
7640
+ base: "--fc-spacing-base",
7641
+ sectionGap: "--fc-section-gap",
7642
+ fieldGap: "--fc-field-gap",
7643
+ inputPaddingX: "--fc-input-padding-x",
7644
+ inputPaddingY: "--fc-input-padding-y"
7645
+ };
7646
+ var LAYOUT_MAP = {
7647
+ maxWidth: "--fc-max-width",
7648
+ alignment: "--fc-alignment",
7649
+ progressPosition: "--fc-progress-position",
7650
+ sectionLayout: "--fc-section-layout"
7651
+ };
7652
+ function themeToCss(theme) {
7653
+ const lines = [];
7654
+ function addSection2(obj, map, comment) {
7655
+ if (!obj) return;
7656
+ const entries = [];
7657
+ for (const [key, cssVar] of Object.entries(map)) {
7658
+ const val = obj[key];
7659
+ if (val == null) continue;
7660
+ if (typeof val === "number") {
7661
+ entries.push(` ${cssVar}: ${val}px;`);
7662
+ } else {
7663
+ entries.push(` ${cssVar}: ${String(val)};`);
7664
+ }
7665
+ }
7666
+ if (entries.length > 0) {
7667
+ lines.push(` /* ${comment} */`);
7668
+ lines.push(...entries);
7669
+ lines.push("");
7670
+ }
7671
+ }
7672
+ lines.push(":root {");
7673
+ addSection2(theme.colors, COLOR_MAP, "Colors");
7674
+ addSection2(theme.typography, TYPOGRAPHY_MAP, "Typography");
7675
+ addSection2(theme.shape, SHAPE_MAP, "Shape");
7676
+ addSection2(theme.spacing, SPACING_MAP, "Spacing");
7677
+ addSection2(theme.layout, LAYOUT_MAP, "Layout");
7678
+ if (lines.length > 1 && lines[lines.length - 1] === "") {
7679
+ lines.pop();
7680
+ }
7681
+ lines.push("}");
7682
+ return lines.join("\n") + "\n";
7683
+ }
7684
+ var REVERSE_MAP = {};
7685
+ function buildReverseMap() {
7686
+ if (Object.keys(REVERSE_MAP).length > 0) return;
7687
+ const sections = [
7688
+ ["colors", COLOR_MAP],
7689
+ ["typography", TYPOGRAPHY_MAP],
7690
+ ["shape", SHAPE_MAP],
7691
+ ["spacing", SPACING_MAP],
7692
+ ["layout", LAYOUT_MAP]
7693
+ ];
7694
+ for (const [sectionKey, map] of sections) {
7695
+ for (const [propKey, cssVar] of Object.entries(map)) {
7696
+ REVERSE_MAP[cssVar] = [sectionKey, propKey];
7697
+ }
7698
+ }
7699
+ }
7700
+ var NUMERIC_PROPS = /* @__PURE__ */ new Set([
7701
+ "base",
7702
+ "sectionGap",
7703
+ "fieldGap",
7704
+ "inputPaddingX",
7705
+ "inputPaddingY"
7706
+ ]);
7707
+ function cssToTheme(css) {
7708
+ buildReverseMap();
7709
+ const theme = {};
7710
+ const varRegex = /(--fc-[\w-]+)\s*:\s*([^;]+);/g;
7711
+ let match;
7712
+ while ((match = varRegex.exec(css)) !== null) {
7713
+ const cssVar = match[1];
7714
+ let value = match[2].trim();
7715
+ const mapping = REVERSE_MAP[cssVar];
7716
+ if (!mapping) continue;
7717
+ const [sectionKey, propKey] = mapping;
7718
+ if (NUMERIC_PROPS.has(propKey)) {
7719
+ const num = parseFloat(value);
7720
+ if (!isNaN(num)) {
7721
+ value = num;
7722
+ }
7723
+ }
7724
+ if (!theme[sectionKey]) {
7725
+ theme[sectionKey] = {};
7726
+ }
7727
+ theme[sectionKey][propKey] = value;
7728
+ }
7729
+ return theme;
7730
+ }
7731
+ function exportCssFile(theme, filename = "fieldcraft-theme.css") {
7732
+ const css = themeToCss(theme);
7733
+ const blob = new Blob([css], { type: "text/css" });
7734
+ const url = URL.createObjectURL(blob);
7735
+ const a = document.createElement("a");
7736
+ a.href = url;
7737
+ a.download = filename;
7738
+ a.click();
7739
+ URL.revokeObjectURL(url);
7740
+ }
7741
+ function importCssFile(baseTheme, onImport) {
7742
+ const input = document.createElement("input");
7743
+ input.type = "file";
7744
+ input.accept = ".css";
7745
+ input.onchange = () => {
7746
+ const file = input.files?.[0];
7747
+ if (!file) return;
7748
+ const reader = new FileReader();
7749
+ reader.onload = () => {
7750
+ const cssText = reader.result;
7751
+ const partial = cssToTheme(cssText);
7752
+ const merged = deepMerge(baseTheme, partial);
7753
+ onImport(merged);
7754
+ };
7755
+ reader.readAsText(file);
7756
+ };
7757
+ input.click();
7758
+ }
7759
+ function deepMerge(base, partial) {
7760
+ const result = { ...base };
7761
+ for (const key of Object.keys(partial)) {
7762
+ const partialSection = partial[key];
7763
+ if (partialSection && typeof partialSection === "object") {
7764
+ result[key] = {
7765
+ ...base[key],
7766
+ ...partialSection
7767
+ };
7768
+ }
7769
+ }
7770
+ return result;
7771
+ }
7772
+
7773
+ // src/theme-editor/palette-generator.ts
7774
+ function generatePalette(baseHex) {
7775
+ const [h, s, l] = hexToHsl(baseHex);
7776
+ const secH = (h + 180) % 360;
7777
+ const isLightBase = l > 50;
7778
+ return {
7779
+ // Primary
7780
+ primary: hslToHex(h, s, clamp(l, 30, 60)),
7781
+ primaryForeground: isLightBase ? "#ffffff" : "#ffffff",
7782
+ // Secondary (complementary)
7783
+ secondary: hslToHex(secH, Math.max(s - 15, 10), clamp(l, 35, 55)),
7784
+ secondaryForeground: "#ffffff",
7785
+ // Surfaces
7786
+ surface: hslToHex(h, Math.max(s - 35, 3), 97),
7787
+ background: "#F4F7F8",
7788
+ inputBackground: hslToHex(h, Math.max(s - 40, 2), 99),
7789
+ // Text
7790
+ text: hslToHex(h, Math.max(s - 30, 5), 12),
7791
+ textMuted: hslToHex(h, Math.max(s - 30, 5), 45),
7792
+ textDisabled: hslToHex(h, Math.max(s - 35, 3), 65),
7793
+ // Borders
7794
+ border: hslToHex(h, Math.max(s - 30, 5), 85),
7795
+ borderFocus: hslToHex(h, s, clamp(l, 35, 55)),
7796
+ // Semantic — Drafting Teal palette
7797
+ error: "#B04A3C",
7798
+ errorForeground: "#FFFFFF",
7799
+ warning: "#C98A2E",
7800
+ success: "#2E7D5B"
7801
+ };
7802
+ }
7803
+ function applyPaletteToTheme(theme, palette) {
7804
+ return {
7805
+ ...theme,
7806
+ colors: {
7807
+ ...theme.colors,
7808
+ ...palette
7809
+ }
7810
+ };
7811
+ }
7812
+ function hexToHsl(hex) {
7813
+ const rgb = hexToRgb(hex);
7814
+ const r = rgb[0] / 255;
7815
+ const g = rgb[1] / 255;
7816
+ const b = rgb[2] / 255;
7817
+ const max = Math.max(r, g, b);
7818
+ const min = Math.min(r, g, b);
7819
+ const l = (max + min) / 2;
7820
+ if (max === min) {
7821
+ return [0, 0, Math.round(l * 100)];
7822
+ }
7823
+ const d = max - min;
7824
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
7825
+ let h = 0;
7826
+ if (max === r) {
7827
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
7828
+ } else if (max === g) {
7829
+ h = ((b - r) / d + 2) / 6;
7830
+ } else {
7831
+ h = ((r - g) / d + 4) / 6;
7832
+ }
7833
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
7834
+ }
7835
+ function hslToHex(h, s, l) {
7836
+ const sNorm = s / 100;
7837
+ const lNorm = l / 100;
7838
+ const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
7839
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
7840
+ const m = lNorm - c / 2;
7841
+ let r = 0, g = 0, b = 0;
7842
+ if (h < 60) {
7843
+ r = c;
7844
+ g = x;
7845
+ b = 0;
7846
+ } else if (h < 120) {
7847
+ r = x;
7848
+ g = c;
7849
+ b = 0;
7850
+ } else if (h < 180) {
7851
+ r = 0;
7852
+ g = c;
7853
+ b = x;
7854
+ } else if (h < 240) {
7855
+ r = 0;
7856
+ g = x;
7857
+ b = c;
7858
+ } else if (h < 300) {
7859
+ r = x;
7860
+ g = 0;
7861
+ b = c;
7862
+ } else {
7863
+ r = c;
7864
+ g = 0;
7865
+ b = x;
7866
+ }
7867
+ return rgbToHex(
7868
+ Math.round((r + m) * 255),
7869
+ Math.round((g + m) * 255),
7870
+ Math.round((b + m) * 255)
7871
+ );
7872
+ }
7873
+ function hexToRgb(hex) {
7874
+ const cleaned = hex.replace("#", "");
7875
+ const full = cleaned.length === 3 ? cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2] : cleaned;
7876
+ return [
7877
+ parseInt(full.substring(0, 2), 16),
7878
+ parseInt(full.substring(2, 4), 16),
7879
+ parseInt(full.substring(4, 6), 16)
7880
+ ];
7881
+ }
7882
+ function rgbToHex(r, g, b) {
7883
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
7884
+ }
7885
+ function clamp(value, min, max) {
7886
+ return Math.max(min, Math.min(max, value));
7887
+ }
7888
+ var SWATCH_KEYS = [
7889
+ { key: "primary", label: "Primary" },
7890
+ { key: "secondary", label: "Secondary" },
7891
+ { key: "surface", label: "Surface" },
7892
+ { key: "background", label: "Background" },
7893
+ { key: "text", label: "Text" },
7894
+ { key: "textMuted", label: "Muted" },
7895
+ { key: "border", label: "Border" },
7896
+ { key: "error", label: "Error" },
7897
+ { key: "warning", label: "Warning" },
7898
+ { key: "success", label: "Success" }
7899
+ ];
7900
+ function PaletteGenerator({ theme, onApply, onClose }) {
7901
+ const [baseColor, setBaseColor] = react.useState(theme.colors?.primary ?? "#3b82f6");
7902
+ const palette = react.useMemo(() => generatePalette(baseColor), [baseColor]);
7903
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette", children: [
7904
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__header", children: [
7905
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-palette__title", children: "Generate Palette" }),
7906
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
7907
+ ] }),
7908
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__body", children: [
7909
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__picker", children: [
7910
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "fcte-palette__label", children: "Base Color" }),
7911
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__input-row", children: [
7912
+ /* @__PURE__ */ jsxRuntime.jsx(
7913
+ "input",
7914
+ {
7915
+ type: "color",
7916
+ value: baseColor,
7917
+ onChange: (e) => setBaseColor(e.target.value),
7918
+ className: "fcte-field__swatch"
7919
+ }
7920
+ ),
7921
+ /* @__PURE__ */ jsxRuntime.jsx(
7922
+ "input",
7923
+ {
7924
+ type: "text",
7925
+ value: baseColor,
7926
+ onChange: (e) => setBaseColor(e.target.value),
7927
+ className: "fcte-field__text",
7928
+ spellCheck: false
7929
+ }
7930
+ )
7931
+ ] })
7932
+ ] }),
7933
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatches", children: SWATCH_KEYS.map(({ key, label }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__swatch", children: [
7934
+ /* @__PURE__ */ jsxRuntime.jsx(
7935
+ "div",
7936
+ {
7937
+ className: "fcte-palette__swatch-color",
7938
+ style: { backgroundColor: palette[key] }
7939
+ }
7940
+ ),
7941
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatch-label", children: label }),
7942
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatch-hex", children: palette[key] })
7943
+ ] }, key)) }),
7944
+ /* @__PURE__ */ jsxRuntime.jsx(
7945
+ "button",
7946
+ {
7947
+ className: "fcte-btn fcte-btn--primary",
7948
+ onClick: () => onApply(applyPaletteToTheme(theme, palette)),
7949
+ children: "Apply to Theme"
7950
+ }
7951
+ )
7952
+ ] })
7953
+ ] });
7954
+ }
7955
+
7956
+ // src/theme-editor/presets.ts
7957
+ var draftingTealPreset = {
7958
+ colors: {
7959
+ primary: "#1F6B6E",
7960
+ primaryForeground: "#FFFFFF",
7961
+ secondary: "#B9D1CF",
7962
+ secondaryForeground: "#12222A",
7963
+ error: "#B04A3C",
7964
+ errorForeground: "#FFFFFF",
7965
+ warning: "#C98A2E",
7966
+ success: "#2E7D5B",
7967
+ surface: "#FFFFFF",
7968
+ background: "#F4F7F8",
7969
+ text: "#12222A",
7970
+ textMuted: "#6A7B85",
7971
+ textDisabled: "#B9D1CF",
7972
+ border: "#DCE4E8",
7973
+ borderFocus: "#1F6B6E",
7974
+ inputBackground: "#FFFFFF"
7975
+ },
7976
+ typography: {
7977
+ fontFamily: "'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif",
7978
+ scale: "comfortable",
7979
+ questionSize: "1.1875rem",
7980
+ labelSize: "0.8125rem",
7981
+ helpTextSize: "0.8125rem",
7982
+ bodySize: "0.9375rem"
7983
+ },
7984
+ shape: {
7985
+ radius: "none",
7986
+ inputRadius: "0px",
7987
+ buttonRadius: "0px",
7988
+ cardRadius: "0px"
7989
+ },
7990
+ spacing: {
7991
+ base: 16,
7992
+ sectionGap: 32,
7993
+ fieldGap: 24,
7994
+ inputPaddingX: 12,
7995
+ inputPaddingY: 10
7996
+ },
7997
+ layout: {
7998
+ maxWidth: "640px",
7999
+ alignment: "left",
8000
+ progressPosition: "top",
8001
+ sectionLayout: "flat"
8002
+ }
8003
+ };
8004
+ var COMPARISON_PRESETS = {
8005
+ "drafting-teal": { label: "Drafting Teal", theme: draftingTealPreset },
8006
+ clean: { label: "Clean", theme: fieldcraftReact.cleanPreset },
8007
+ dark: { label: "Dark", theme: fieldcraftReact.darkPreset },
8008
+ modern: { label: "Modern", theme: fieldcraftReact.modernPreset },
8009
+ "high-contrast": { label: "High Contrast", theme: fieldcraftReact.highContrastPreset },
8010
+ clinical: { label: "Clinical", theme: fieldcraftReact.clinicalPreset },
8011
+ playful: { label: "Playful", theme: fieldcraftReact.playfulPreset }
8012
+ };
8013
+ function ThemeComparison({ currentTheme, onClose }) {
8014
+ const [compareKey, setCompareKey] = react.useState("clean");
8015
+ const compareTheme = COMPARISON_PRESETS[compareKey]?.theme ?? fieldcraftReact.cleanPreset;
8016
+ const diffs = getColorDiffs(currentTheme, compareTheme);
8017
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare", children: [
8018
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__header", children: [
8019
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__title", children: "Theme Comparison" }),
8020
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__controls", children: [
8021
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "fcte-compare__label", children: "Compare with:" }),
8022
+ /* @__PURE__ */ jsxRuntime.jsx(
8023
+ "select",
8024
+ {
8025
+ value: compareKey,
8026
+ onChange: (e) => setCompareKey(e.target.value),
8027
+ className: "fcte-toolbar__preset",
8028
+ children: Object.entries(COMPARISON_PRESETS).map(([key, { label }]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: key, children: label }, key))
8029
+ }
8030
+ ),
8031
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
8032
+ ] })
8033
+ ] }),
8034
+ diffs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diffs", children: [
8035
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fcte-compare__diff-count", children: [
8036
+ diffs.length,
8037
+ " color",
8038
+ diffs.length !== 1 ? "s" : "",
8039
+ " differ"
8040
+ ] }),
8041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-list", children: [
8042
+ diffs.slice(0, 8).map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-item", children: [
8043
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__diff-label", children: d.key }),
8044
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-swatches", children: [
8045
+ /* @__PURE__ */ jsxRuntime.jsx(
8046
+ "div",
8047
+ {
8048
+ className: "fcte-compare__diff-swatch",
8049
+ style: { backgroundColor: d.current },
8050
+ title: `Current: ${d.current}`
8051
+ }
8052
+ ),
8053
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__diff-arrow", children: "\u2192" }),
8054
+ /* @__PURE__ */ jsxRuntime.jsx(
8055
+ "div",
8056
+ {
8057
+ className: "fcte-compare__diff-swatch",
8058
+ style: { backgroundColor: d.compare },
8059
+ title: `${COMPARISON_PRESETS[compareKey]?.label}: ${d.compare}`
8060
+ }
8061
+ )
8062
+ ] })
8063
+ ] }, d.key)),
8064
+ diffs.length > 8 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fcte-compare__diff-more", children: [
8065
+ "+",
8066
+ diffs.length - 8,
8067
+ " more"
8068
+ ] })
8069
+ ] })
8070
+ ] }),
8071
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panels", children: [
8072
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panel", children: [
8073
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-label", children: "Current Theme" }),
8074
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsxRuntime.jsx(
8075
+ fieldcraftReact.FormEngineRenderer,
8076
+ {
8077
+ schema: PREVIEW_SCHEMA,
8078
+ theme: currentTheme,
8079
+ onSubmit: () => {
8080
+ }
8081
+ }
8082
+ ) })
8083
+ ] }),
8084
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panel", children: [
8085
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-label", children: COMPARISON_PRESETS[compareKey]?.label ?? "Preset" }),
8086
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsxRuntime.jsx(
8087
+ fieldcraftReact.FormEngineRenderer,
8088
+ {
8089
+ schema: PREVIEW_SCHEMA,
8090
+ theme: compareTheme,
8091
+ onSubmit: () => {
8092
+ }
8093
+ }
8094
+ ) })
8095
+ ] })
8096
+ ] })
8097
+ ] });
8098
+ }
8099
+ function getColorDiffs(a, b) {
8100
+ const diffs = [];
8101
+ const aColors = a.colors ?? {};
8102
+ const bColors = b.colors ?? {};
8103
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(aColors), ...Object.keys(bColors)]);
8104
+ for (const key of allKeys) {
8105
+ const aVal = aColors[key] ?? "";
8106
+ const bVal = bColors[key] ?? "";
8107
+ if (aVal.toLowerCase() !== bVal.toLowerCase() && (aVal || bVal)) {
8108
+ diffs.push({ key, current: aVal || "(unset)", compare: bVal || "(unset)" });
8109
+ }
8110
+ }
8111
+ return diffs;
8112
+ }
8113
+ var ThemeCtx2 = react.createContext({});
8114
+ function themeToCssVars2(theme) {
8115
+ const vars = {};
8116
+ if (theme.background) vars["--fcte-bg"] = theme.background;
8117
+ if (theme.surface) vars["--fcte-surface"] = theme.surface;
8118
+ if (theme.surfaceHover) vars["--fcte-surface-hover"] = theme.surfaceHover;
8119
+ if (theme.text) vars["--fcte-text"] = theme.text;
8120
+ if (theme.textMuted) vars["--fcte-text-muted"] = theme.textMuted;
8121
+ if (theme.textDim) vars["--fcte-text-dim"] = theme.textDim;
8122
+ if (theme.border) vars["--fcte-border"] = theme.border;
8123
+ if (theme.borderStrong) vars["--fcte-border-strong"] = theme.borderStrong;
8124
+ if (theme.inputBackground) vars["--fcte-input-bg"] = theme.inputBackground;
8125
+ if (theme.accent) vars["--fcte-accent"] = theme.accent;
8126
+ if (theme.accentForeground) vars["--fcte-accent-text"] = theme.accentForeground;
8127
+ return vars;
8128
+ }
8129
+ function ThemeEditorThemeProvider({ theme, children }) {
8130
+ const resolved = theme ?? {};
8131
+ const cssVars = react.useMemo(() => themeToCssVars2(resolved), [resolved]);
8132
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeCtx2.Provider, { value: resolved, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fcte-root": "", style: cssVars, className: "fcte-provider-root", children }) });
8133
+ }
5304
8134
  var PRESETS = {
8135
+ "drafting-teal": draftingTealPreset,
5305
8136
  clean: fieldcraftReact.cleanPreset,
5306
8137
  dark: fieldcraftReact.darkPreset,
5307
8138
  modern: fieldcraftReact.modernPreset,
@@ -5431,15 +8262,18 @@ function ThemeEditorInner({
5431
8262
  initialTheme,
5432
8263
  onChange,
5433
8264
  onSave,
8265
+ theme: chromeTheme,
5434
8266
  height,
5435
8267
  width,
5436
8268
  className,
5437
8269
  toolbarExtra,
5438
8270
  showPreview = true
5439
8271
  }) {
5440
- const [theme, setTheme] = react.useState(initialTheme ?? fieldcraftReact.cleanPreset);
8272
+ const [theme, setTheme] = react.useState(initialTheme ?? draftingTealPreset);
5441
8273
  const [activeSection, setActiveSection] = react.useState("colors");
5442
- const [presetKey, setPresetKey] = react.useState("custom");
8274
+ const [presetKey, setPresetKey] = react.useState(initialTheme ? "custom" : "drafting-teal");
8275
+ const [showPalette, setShowPalette] = react.useState(false);
8276
+ const [showComparison, setShowComparison] = react.useState(false);
5443
8277
  const themeRef = react.useRef(theme);
5444
8278
  themeRef.current = theme;
5445
8279
  react.useEffect(() => {
@@ -5507,12 +8341,22 @@ function ThemeEditorInner({
5507
8341
  };
5508
8342
  input.click();
5509
8343
  }, [onChange]);
8344
+ const exportCss = react.useCallback(() => {
8345
+ exportCssFile(theme);
8346
+ }, [theme]);
8347
+ const importCss = react.useCallback(() => {
8348
+ importCssFile(theme, (merged) => {
8349
+ setTheme(merged);
8350
+ setPresetKey("custom");
8351
+ onChange?.(merged);
8352
+ });
8353
+ }, [theme, onChange]);
5510
8354
  const currentSection = react.useMemo(
5511
8355
  () => SECTIONS.find((s) => s.id === activeSection),
5512
8356
  [activeSection]
5513
8357
  );
5514
8358
  const sectionValues = theme[currentSection.themeKey] ?? {};
5515
- return /* @__PURE__ */ jsxRuntime.jsxs(
8359
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeEditorThemeProvider, { theme: chromeTheme, children: /* @__PURE__ */ jsxRuntime.jsxs(
5516
8360
  "div",
5517
8361
  {
5518
8362
  className: `fcte-root${className ? ` ${className}` : ""}`,
@@ -5539,22 +8383,65 @@ function ThemeEditorInner({
5539
8383
  ] }),
5540
8384
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-toolbar__right", children: [
5541
8385
  toolbarExtra,
5542
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import" }),
5543
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export" }),
8386
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import JSON" }),
8387
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export JSON" }),
8388
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importCss, className: "fcte-btn fcte-btn--secondary", children: "Import CSS" }),
8389
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportCss, className: "fcte-btn fcte-btn--secondary", children: "Export CSS" }),
8390
+ /* @__PURE__ */ jsxRuntime.jsx(
8391
+ "button",
8392
+ {
8393
+ onClick: () => {
8394
+ setShowPalette((v) => !v);
8395
+ setShowComparison(false);
8396
+ },
8397
+ className: `fcte-btn ${showPalette ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
8398
+ children: "Palette"
8399
+ }
8400
+ ),
8401
+ /* @__PURE__ */ jsxRuntime.jsx(
8402
+ "button",
8403
+ {
8404
+ onClick: () => {
8405
+ setShowComparison((v) => !v);
8406
+ setShowPalette(false);
8407
+ },
8408
+ className: `fcte-btn ${showComparison ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
8409
+ children: "Compare"
8410
+ }
8411
+ ),
5544
8412
  onSave && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onSave(theme), className: "fcte-btn fcte-btn--primary", children: "Save" })
5545
8413
  ] })
5546
8414
  ] }),
5547
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-body", children: [
8415
+ showPalette && /* @__PURE__ */ jsxRuntime.jsx(
8416
+ PaletteGenerator,
8417
+ {
8418
+ theme,
8419
+ onApply: (newTheme) => {
8420
+ setTheme(newTheme);
8421
+ setPresetKey("custom");
8422
+ onChange?.(newTheme);
8423
+ setShowPalette(false);
8424
+ },
8425
+ onClose: () => setShowPalette(false)
8426
+ }
8427
+ ),
8428
+ showComparison ? /* @__PURE__ */ jsxRuntime.jsx(
8429
+ ThemeComparison,
8430
+ {
8431
+ currentTheme: theme,
8432
+ onClose: () => setShowComparison(false)
8433
+ }
8434
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-body", children: [
5548
8435
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-editor", children: [
5549
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-tabs", children: SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
5550
- "button",
8436
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-section-select", children: /* @__PURE__ */ jsxRuntime.jsx(
8437
+ "select",
5551
8438
  {
5552
- className: `fcte-tab${activeSection === s.id ? " fcte-tab--active" : ""}`,
5553
- onClick: () => setActiveSection(s.id),
5554
- children: s.label
5555
- },
5556
- s.id
5557
- )) }),
8439
+ value: activeSection,
8440
+ onChange: (e) => setActiveSection(e.target.value),
8441
+ className: "fcte-field__select",
8442
+ children: SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s.id, children: s.label }, s.id))
8443
+ }
8444
+ ) }),
5558
8445
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-fields", children: currentSection.fields.map((field) => {
5559
8446
  const val = sectionValues[field.key];
5560
8447
  if (field.kind === "color") {
@@ -5632,19 +8519,29 @@ function ThemeEditorInner({
5632
8519
  ] }, field.key);
5633
8520
  }) })
5634
8521
  ] }),
5635
- showPreview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsxRuntime.jsx(
5636
- fieldcraftReact.FormEngineRenderer,
8522
+ showPreview && /* @__PURE__ */ jsxRuntime.jsx(
8523
+ "div",
5637
8524
  {
5638
- schema: PREVIEW_SCHEMA,
5639
- theme,
5640
- onSubmit: () => {
5641
- }
8525
+ className: "fcte-preview",
8526
+ style: {
8527
+ background: theme.colors?.background || void 0,
8528
+ color: theme.colors?.text || void 0
8529
+ },
8530
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsxRuntime.jsx(
8531
+ fieldcraftReact.FormEngineRenderer,
8532
+ {
8533
+ schema: PREVIEW_SCHEMA,
8534
+ theme,
8535
+ onSubmit: () => {
8536
+ }
8537
+ }
8538
+ ) })
5642
8539
  }
5643
- ) }) })
8540
+ )
5644
8541
  ] })
5645
8542
  ]
5646
8543
  }
5647
- );
8544
+ ) });
5648
8545
  }
5649
8546
 
5650
8547
  // src/theme-editor/ThemeEditor.tsx
@@ -5653,7 +8550,7 @@ var ThemeEditor = requireLicense(ThemeEditorInner, "ThemeEditor");
5653
8550
  // src/index.ts
5654
8551
  if (typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
5655
8552
  const _fc_banner = `
5656
- %c FieldCraft Pro %c v1.3.0
8553
+ %c FieldCraft Pro %c v1.5.0
5657
8554
 
5658
8555
  %cForm Builder \xB7 Response Viewer \xB7 Theme Editor
5659
8556
 
@@ -5677,6 +8574,8 @@ exports.FieldCraftProProvider = FieldCraftProProvider;
5677
8574
  exports.FormBuilder = FormBuilder;
5678
8575
  exports.FormBuilderThemeProvider = FormBuilderThemeProvider;
5679
8576
  exports.PREVIEW_SCHEMA = PREVIEW_SCHEMA;
8577
+ exports.PRO_FIELD_OVERRIDES = PRO_FIELD_OVERRIDES;
8578
+ exports.ProPaymentField = ProPaymentField;
5680
8579
  exports.QUESTION_TYPE_INFO = QUESTION_TYPE_INFO;
5681
8580
  exports.ResponseViewer = ResponseViewer;
5682
8581
  exports.ThemeEditor = ThemeEditor;