@squaredr/fieldcraft-pro 1.8.0 → 1.9.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.
@@ -336,21 +336,20 @@ var MAX_HISTORY = 50;
336
336
  function useUndoRedo(currentSchema, setSchema) {
337
337
  const historyRef = React.useRef([currentSchema]);
338
338
  const [currentIndex, setCurrentIndex] = React.useState(0);
339
- const currentIndexRef = React.useRef(currentIndex);
340
- currentIndexRef.current = currentIndex;
341
339
  const canUndo = currentIndex > 0;
342
340
  const canRedo = currentIndex < historyRef.current.length - 1;
343
341
  const push = React.useCallback(
344
342
  (schema) => {
345
- const idx = currentIndexRef.current;
346
- historyRef.current = historyRef.current.slice(0, idx + 1);
347
- historyRef.current.push(schema);
348
- if (historyRef.current.length > MAX_HISTORY) {
349
- historyRef.current.shift();
350
- setCurrentIndex(historyRef.current.length - 1);
351
- } else {
352
- setCurrentIndex(idx + 1);
353
- }
343
+ setCurrentIndex((prevIndex) => {
344
+ historyRef.current = historyRef.current.slice(0, prevIndex + 1);
345
+ historyRef.current.push(schema);
346
+ let newIndex = prevIndex + 1;
347
+ if (historyRef.current.length > MAX_HISTORY) {
348
+ historyRef.current.shift();
349
+ newIndex = Math.max(0, newIndex - 1);
350
+ }
351
+ return newIndex;
352
+ });
354
353
  },
355
354
  []
356
355
  );
@@ -383,13 +382,9 @@ function useUndoRedo(currentSchema, setSchema) {
383
382
  }
384
383
 
385
384
  // src/form-builder/utils/id-generator.ts
386
- var counter = 0;
387
385
  function generateId(prefix) {
388
- const timestamp = Date.now().toString(36);
389
- const random = Math.random().toString(36).substring(2, 7);
390
- counter = (counter + 1) % 1e4;
391
- const count = counter.toString(36);
392
- return `${prefix}_${timestamp}${count}${random}`;
386
+ const uuid = crypto.randomUUID().replace(/-/g, "").substring(0, 12);
387
+ return `${prefix}_${uuid}`;
393
388
  }
394
389
  function generateSectionId() {
395
390
  return generateId("section");
@@ -476,18 +471,39 @@ function duplicateSection(schema, sectionId) {
476
471
  const sectionIndex = newSchema.sections.findIndex((s) => s.id === sectionId);
477
472
  if (sectionIndex === -1) return schema;
478
473
  const original = newSchema.sections[sectionIndex];
474
+ const idMap = /* @__PURE__ */ new Map();
475
+ for (const q of original.questions) {
476
+ idMap.set(q.id, generateQuestionId());
477
+ }
479
478
  const duplicate = {
480
479
  ...original,
481
480
  id: generateSectionId(),
482
481
  title: `${original.title} (Copy)`,
482
+ showIf: original.showIf ? remapConditionFieldIds(original.showIf, idMap) : void 0,
483
483
  questions: original.questions.map((q) => ({
484
484
  ...q,
485
- id: generateQuestionId()
485
+ id: idMap.get(q.id),
486
+ showIf: q.showIf ? remapConditionFieldIds(q.showIf, idMap) : void 0
486
487
  }))
487
488
  };
488
489
  newSchema.sections.splice(sectionIndex + 1, 0, duplicate);
489
490
  return newSchema;
490
491
  }
492
+ function remapConditionFieldIds(expr, idMap) {
493
+ if (expr.field) {
494
+ return {
495
+ ...expr,
496
+ field: idMap.get(expr.field) ?? expr.field
497
+ };
498
+ }
499
+ if (expr.conditions) {
500
+ return {
501
+ ...expr,
502
+ conditions: expr.conditions.map((c) => remapConditionFieldIds(c, idMap))
503
+ };
504
+ }
505
+ return expr;
506
+ }
491
507
  function addQuestion(schema, sectionId, question, index) {
492
508
  const newSchema = deepClone(schema);
493
509
  const section = newSchema.sections.find((s) => s.id === sectionId);
@@ -631,7 +647,7 @@ function useBuilderState(initialSchema) {
631
647
  (sectionId) => {
632
648
  applyMutation((s) => removeSection(s, sectionId));
633
649
  setSelectedItem((prev) => {
634
- if (prev?.type === "section" && prev.sectionId === sectionId) return null;
650
+ if (prev?.sectionId === sectionId) return null;
635
651
  return prev;
636
652
  });
637
653
  },
@@ -1764,6 +1780,14 @@ function SectionBlock({ section, builderState }) {
1764
1780
  }
1765
1781
  function FormCanvas({ builderState }) {
1766
1782
  const { schema } = builderState;
1783
+ const [localTitle, setLocalTitle] = React.useState(schema.title);
1784
+ const [localDesc, setLocalDesc] = React.useState(schema.description ?? "");
1785
+ React.useEffect(() => {
1786
+ setLocalTitle(schema.title);
1787
+ }, [schema.title]);
1788
+ React.useEffect(() => {
1789
+ setLocalDesc(schema.description ?? "");
1790
+ }, [schema.description]);
1767
1791
  const handleAddSection = () => {
1768
1792
  const newSection = {
1769
1793
  id: generateSectionId(),
@@ -1780,9 +1804,12 @@ function FormCanvas({ builderState }) {
1780
1804
  "input",
1781
1805
  {
1782
1806
  type: "text",
1783
- value: schema.title,
1784
- onChange: (e) => {
1785
- builderState.updateSchema({ ...schema, title: e.target.value });
1807
+ value: localTitle,
1808
+ onChange: (e) => setLocalTitle(e.target.value),
1809
+ onBlur: () => {
1810
+ if (localTitle !== schema.title) {
1811
+ builderState.updateSchema({ ...schema, title: localTitle });
1812
+ }
1786
1813
  },
1787
1814
  className: "w-full text-xl font-bold bg-transparent text-foreground border-0 outline-none p-1.5 rounded-md focus:bg-card transition-colors",
1788
1815
  placeholder: "Form Title"
@@ -1791,9 +1818,12 @@ function FormCanvas({ builderState }) {
1791
1818
  /* @__PURE__ */ jsxRuntime.jsx(
1792
1819
  "textarea",
1793
1820
  {
1794
- value: schema.description ?? "",
1795
- onChange: (e) => {
1796
- builderState.updateSchema({ ...schema, description: e.target.value });
1821
+ value: localDesc,
1822
+ onChange: (e) => setLocalDesc(e.target.value),
1823
+ onBlur: () => {
1824
+ if (localDesc !== (schema.description ?? "")) {
1825
+ builderState.updateSchema({ ...schema, description: localDesc || void 0 });
1826
+ }
1797
1827
  },
1798
1828
  className: "w-full text-sm text-muted-foreground bg-transparent border-0 outline-none p-1.5 resize-y min-h-12 rounded-md leading-relaxed focus:bg-card transition-colors",
1799
1829
  placeholder: "Form description (optional)"
@@ -1896,12 +1926,26 @@ SelectItem.displayName = "SelectItem";
1896
1926
  function useConfigUpdater(question, onUpdate) {
1897
1927
  return (updates) => {
1898
1928
  const current = question.config ?? {};
1899
- onUpdate({ config: { ...current, type: question.type, ...updates } });
1929
+ const merged = { ...current, type: question.type, ...updates };
1930
+ for (const key of Object.keys(merged)) {
1931
+ if (merged[key] === void 0) delete merged[key];
1932
+ }
1933
+ onUpdate({ config: merged });
1900
1934
  };
1901
1935
  }
1902
- function QuestionConfigEditor({ question, onUpdate }) {
1936
+ function QuestionConfigEditor({ question, schema, onUpdate }) {
1903
1937
  const updateConfig = useConfigUpdater(question, onUpdate);
1904
1938
  const config = question.config ?? {};
1939
+ const allQuestions = React.useMemo(() => {
1940
+ if (!schema?.sections) return [];
1941
+ return schema.sections.flatMap((s) => s.questions || []);
1942
+ }, [schema]);
1943
+ const candidateAmountFields = React.useMemo(() => {
1944
+ return allQuestions.filter((q) => q.id !== question.id && ["calculated", "number", "slider", "rating", "single_select"].includes(q.type)).map((q) => ({ label: `${q.label || q.id} (${q.id})`, value: q.id }));
1945
+ }, [allQuestions, question.id]);
1946
+ const candidateTimezoneFields = React.useMemo(() => {
1947
+ return allQuestions.filter((q) => q.id !== question.id && ["dropdown", "single_select", "short_text"].includes(q.type)).map((q) => ({ label: `${q.label || q.id} (${q.id})`, value: q.id }));
1948
+ }, [allQuestions, question.id]);
1905
1949
  switch (question.type) {
1906
1950
  // ── Text ──
1907
1951
  case "short_text":
@@ -1925,32 +1969,32 @@ function QuestionConfigEditor({ question, onUpdate }) {
1925
1969
  // ── Numeric ──
1926
1970
  case "number":
1927
1971
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Number Settings", children: [
1928
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Min", value: config.min, onChange: (v) => updateConfig({ min: v }) }),
1929
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max", value: config.max, onChange: (v) => updateConfig({ max: v }) }),
1972
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Min Value", value: config.min, onChange: (v) => updateConfig({ min: v }) }),
1973
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Value", value: config.max, onChange: (v) => updateConfig({ max: v }) }),
1930
1974
  /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v }) }),
1931
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Decimal Places", value: config.decimalPlaces, placeholder: "0", onChange: (v) => updateConfig({ decimalPlaces: v }) }),
1932
1975
  /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Prefix", value: config.prefix, placeholder: "e.g. $", onChange: (v) => updateConfig({ prefix: v }) }),
1933
1976
  /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Suffix", value: config.suffix, placeholder: "e.g. kg", onChange: (v) => updateConfig({ suffix: v }) })
1934
1977
  ] });
1935
1978
  case "slider":
1936
1979
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Slider Settings", children: [
1937
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Min", value: config.min, placeholder: "0", onChange: (v) => updateConfig({ min: v }) }),
1938
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max", value: config.max, placeholder: "100", onChange: (v) => updateConfig({ max: v }) }),
1939
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v }) }),
1940
- /* @__PURE__ */ jsxRuntime.jsx(ToggleField, { label: "Show Value", checked: !!config.showValue, onChange: (v) => updateConfig({ showValue: v }) }),
1941
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Min Label", value: config.minLabel, onChange: (v) => updateConfig({ minLabel: v }) }),
1942
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Max Label", value: config.maxLabel, onChange: (v) => updateConfig({ maxLabel: v }) })
1980
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Min Value", value: config.min, placeholder: "0", onChange: (v) => updateConfig({ min: v ?? 0 }) }),
1981
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Value", value: config.max, placeholder: "100", onChange: (v) => updateConfig({ max: v ?? 100 }) }),
1982
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v ?? 1 }) }),
1983
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Min Label", value: config.minLabel, placeholder: "Low", onChange: (v) => updateConfig({ minLabel: v }) }),
1984
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Max Label", value: config.maxLabel, placeholder: "High", onChange: (v) => updateConfig({ maxLabel: v }) }),
1985
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleField, { label: "Show Value", checked: config.showValue !== false, onChange: (v) => updateConfig({ showValue: v }) }),
1986
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Unit", value: config.unit, placeholder: "e.g. %", onChange: (v) => updateConfig({ unit: v }) })
1943
1987
  ] });
1944
1988
  case "rating":
1945
1989
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Rating Settings", children: [
1946
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Stars", value: config.max, placeholder: "5", onChange: (v) => updateConfig({ max: v }) }),
1947
- /* @__PURE__ */ jsxRuntime.jsx(SelectField, { label: "Icon", value: config.icon ?? "star", options: [{ label: "Star", value: "star" }, { label: "Heart", value: "heart" }, { label: "Circle", value: "circle" }], onChange: (v) => updateConfig({ icon: v }) }),
1948
- /* @__PURE__ */ jsxRuntime.jsx(ToggleField, { label: "Show Labels", checked: !!config.showLabels, onChange: (v) => updateConfig({ showLabels: v }) })
1990
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Rating", value: config.maxRating, placeholder: "5", onChange: (v) => updateConfig({ maxRating: v ?? 5 }) }),
1991
+ /* @__PURE__ */ jsxRuntime.jsx(SelectField, { label: "Icon", value: config.icon ?? "star", options: [{ label: "Star", value: "star" }, { label: "Heart", value: "heart" }, { label: "Thumb", value: "thumb" }], onChange: (v) => updateConfig({ icon: v }) }),
1992
+ /* @__PURE__ */ jsxRuntime.jsx(ToggleField, { label: "Allow Half Ratings", checked: !!config.allowHalf, onChange: (v) => updateConfig({ allowHalf: v }) })
1949
1993
  ] });
1950
1994
  case "nps":
1951
1995
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "NPS Settings", children: [
1952
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Low Label", value: config.lowLabel ?? "Not likely", onChange: (v) => updateConfig({ lowLabel: v }) }),
1953
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "High Label", value: config.highLabel ?? "Very likely", onChange: (v) => updateConfig({ highLabel: v }) })
1996
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Left Label", value: config.leftLabel ?? "Not likely", onChange: (v) => updateConfig({ leftLabel: v }) }),
1997
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Right Label", value: config.rightLabel ?? "Extremely likely", onChange: (v) => updateConfig({ rightLabel: v }) })
1954
1998
  ] });
1955
1999
  case "opinion_scale":
1956
2000
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Scale Settings", children: [
@@ -2016,50 +2060,126 @@ function QuestionConfigEditor({ question, onUpdate }) {
2016
2060
  /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Range (days)", value: config.maxRangeDays, onChange: (v) => updateConfig({ maxRangeDays: v }) })
2017
2061
  ] });
2018
2062
  case "appointment": {
2019
- const appointmentMode = typeof config.embedUrl === "string" ? "embed" : typeof config.slotsUrl === "string" ? "url" : "static";
2063
+ const appointmentMode = typeof config.slotsUrl === "string" ? "url" : typeof config.embedUrl === "string" ? "embed" : "static";
2064
+ const timezoneFieldOptions = [
2065
+ { label: "None (Static Timezone)", value: "" },
2066
+ ...candidateTimezoneFields,
2067
+ { label: "Custom Field ID...", value: "__custom__" }
2068
+ ];
2069
+ const isCustomTzField = Boolean(config.timezoneField) && !candidateTimezoneFields.some((opt) => opt.value === config.timezoneField);
2020
2070
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Appointment Settings", children: [
2021
2071
  /* @__PURE__ */ jsxRuntime.jsx(
2022
2072
  SelectField,
2023
2073
  {
2024
- label: "Mode",
2074
+ label: "Scheduling Mode",
2025
2075
  value: appointmentMode,
2026
2076
  options: [
2027
- { label: "Static Slots", value: "static" },
2028
- { label: "API Endpoint", value: "url" },
2029
- { label: "Embed (Calendly / Cal.com)", value: "embed" }
2077
+ { label: "Manual Static Slots", value: "static" },
2078
+ { label: "Fetch from URL", value: "url" },
2079
+ { label: "Third-Party Embed (Calendly / Cal.com)", value: "embed" }
2030
2080
  ],
2031
2081
  onChange: (v) => {
2032
- if (v === "static") {
2033
- updateConfig({ slotsUrl: void 0, embedUrl: void 0, embedProvider: void 0 });
2034
- } else if (v === "url") {
2035
- updateConfig({ slotsUrl: "", embedUrl: void 0, embedProvider: void 0, slots: void 0 });
2082
+ if (v === "url") {
2083
+ updateConfig({
2084
+ slotsUrl: "",
2085
+ embedUrl: void 0,
2086
+ embedProvider: void 0,
2087
+ slots: void 0
2088
+ });
2036
2089
  } else if (v === "embed") {
2037
- updateConfig({ embedUrl: "", slotsUrl: void 0, slots: void 0 });
2090
+ updateConfig({
2091
+ embedUrl: "",
2092
+ embedProvider: config.embedProvider || "cal_com",
2093
+ slotsUrl: void 0,
2094
+ slots: void 0
2095
+ });
2096
+ } else {
2097
+ updateConfig({
2098
+ slots: config.slots ?? [],
2099
+ slotsUrl: void 0,
2100
+ embedUrl: void 0,
2101
+ embedProvider: void 0
2102
+ });
2038
2103
  }
2039
2104
  }
2040
2105
  }
2041
2106
  ),
2042
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Duration (min)", value: config.duration ?? 30, onChange: (v) => updateConfig({ duration: v }) }),
2043
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Timezone", value: config.timezone, placeholder: "e.g. America/New_York", onChange: (v) => updateConfig({ timezone: v }) }),
2044
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Timezone Field", value: config.timezoneField, placeholder: "Field ID for dynamic timezone", onChange: (v) => updateConfig({ timezoneField: v }) }),
2045
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Date Format", value: config.dateFormat, placeholder: "Locale-aware (default)", onChange: (v) => updateConfig({ dateFormat: v }) }),
2046
- appointmentMode === "url" && /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Slots URL", value: config.slotsUrl, placeholder: "https://api.example.com/slots", onChange: (v) => updateConfig({ slotsUrl: v }) }),
2107
+ appointmentMode === "url" && /* @__PURE__ */ jsxRuntime.jsx(
2108
+ TextField,
2109
+ {
2110
+ label: "Slots URL",
2111
+ value: config.slotsUrl,
2112
+ placeholder: "https://example.com/slots",
2113
+ onChange: (v) => updateConfig({ slotsUrl: v ?? "" })
2114
+ }
2115
+ ),
2047
2116
  appointmentMode === "embed" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2048
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Embed URL", value: config.embedUrl, placeholder: "https://calendly.com/your-name/30min", onChange: (v) => updateConfig({ embedUrl: v }) }),
2117
+ /* @__PURE__ */ jsxRuntime.jsx(
2118
+ TextField,
2119
+ {
2120
+ label: "Embed URL",
2121
+ value: config.embedUrl,
2122
+ placeholder: "https://calendly.com/... or https://cal.com/...",
2123
+ onChange: (v) => updateConfig({ embedUrl: v ?? "" })
2124
+ }
2125
+ ),
2049
2126
  /* @__PURE__ */ jsxRuntime.jsx(
2050
2127
  SelectField,
2051
2128
  {
2052
2129
  label: "Embed Provider",
2053
- value: config.embedProvider ?? "custom",
2130
+ value: config.embedProvider ?? "cal_com",
2054
2131
  options: [
2055
- { label: "Calendly", value: "calendly" },
2056
2132
  { label: "Cal.com", value: "cal_com" },
2133
+ { label: "Calendly", value: "calendly" },
2057
2134
  { label: "Custom", value: "custom" }
2058
2135
  ],
2059
2136
  onChange: (v) => updateConfig({ embedProvider: v })
2060
2137
  }
2061
2138
  )
2062
2139
  ] }),
2140
+ /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Duration (min)", value: config.duration ?? 30, onChange: (v) => updateConfig({ duration: v }) }),
2141
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Timezone", value: config.timezone, placeholder: "e.g. America/New_York", onChange: (v) => updateConfig({ timezone: v }) }),
2142
+ candidateTimezoneFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2143
+ /* @__PURE__ */ jsxRuntime.jsx(
2144
+ SelectField,
2145
+ {
2146
+ label: "Dynamic Timezone Field",
2147
+ value: isCustomTzField ? "__custom__" : config.timezoneField ?? "",
2148
+ options: timezoneFieldOptions,
2149
+ onChange: (v) => {
2150
+ if (v !== "__custom__") {
2151
+ updateConfig({ timezoneField: v || void 0 });
2152
+ }
2153
+ }
2154
+ }
2155
+ ),
2156
+ isCustomTzField && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
2157
+ TextField,
2158
+ {
2159
+ label: "Custom Timezone Field ID",
2160
+ value: config.timezoneField,
2161
+ placeholder: "e.g. timezone",
2162
+ onChange: (v) => updateConfig({ timezoneField: v || void 0 })
2163
+ }
2164
+ ) })
2165
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
2166
+ TextField,
2167
+ {
2168
+ label: "Timezone Field",
2169
+ value: config.timezoneField,
2170
+ placeholder: "Field ID for dynamic timezone",
2171
+ onChange: (v) => updateConfig({ timezoneField: v || void 0 })
2172
+ }
2173
+ ),
2174
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Date Format", value: config.dateFormat, placeholder: "Locale-aware (default)", onChange: (v) => updateConfig({ dateFormat: v }) }),
2175
+ /* @__PURE__ */ jsxRuntime.jsx(
2176
+ ToggleField,
2177
+ {
2178
+ label: "Auto-Advance on Booking Complete",
2179
+ checked: config.autoAdvance !== false,
2180
+ onChange: (v) => updateConfig({ autoAdvance: v })
2181
+ }
2182
+ ),
2063
2183
  appointmentMode === "static" && /* @__PURE__ */ jsxRuntime.jsx(
2064
2184
  AppointmentSlotsEditor,
2065
2185
  {
@@ -2072,10 +2192,41 @@ function QuestionConfigEditor({ question, onUpdate }) {
2072
2192
  // ── Media ──
2073
2193
  case "file_upload":
2074
2194
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Upload Settings", children: [
2195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2.5 bg-muted/40 border border-border rounded-md text-xs text-muted-foreground flex items-center gap-1.5", children: [
2196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u{1F4E6}" }),
2197
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Uploads stream to your cloud bucket using Form / Account Storage settings." })
2198
+ ] }),
2075
2199
  /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Files", value: config.maxFiles ?? 1, onChange: (v) => updateConfig({ maxFiles: v }) }),
2076
2200
  /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Max Size (MB)", value: config.maxSizeMb ?? 10, onChange: (v) => updateConfig({ maxSizeMb: v }) }),
2077
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Accepted Types", value: config.accept?.join(", "), placeholder: "e.g. .pdf, .jpg, .png", onChange: (v) => updateConfig({ accept: v ? v.split(",").map((s) => s.trim()) : void 0 }) }),
2078
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Upload URL", value: config.uploadUrl, placeholder: "Server upload endpoint", onChange: (v) => updateConfig({ uploadUrl: v }) })
2201
+ /* @__PURE__ */ jsxRuntime.jsx(
2202
+ TextField,
2203
+ {
2204
+ label: "Accepted Types (comma-separated)",
2205
+ value: config.accept?.join(", "),
2206
+ placeholder: "e.g. image/*, .pdf, .docx, .zip",
2207
+ onChange: (v) => updateConfig({
2208
+ accept: v ? v.split(",").map((s) => s.trim()).filter(Boolean) : void 0
2209
+ })
2210
+ }
2211
+ ),
2212
+ /* @__PURE__ */ jsxRuntime.jsx(
2213
+ TextField,
2214
+ {
2215
+ label: "Upload URL",
2216
+ value: config.uploadUrl,
2217
+ placeholder: "https://api.example.com/s3/presign",
2218
+ onChange: (v) => updateConfig({ uploadUrl: v || void 0 })
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ jsxRuntime.jsx(
2222
+ TextField,
2223
+ {
2224
+ label: "Public Key",
2225
+ value: config.publicKey,
2226
+ placeholder: "pk_test_...",
2227
+ onChange: (v) => updateConfig({ publicKey: v || void 0 })
2228
+ }
2229
+ )
2079
2230
  ] });
2080
2231
  case "signature":
2081
2232
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Signature Settings", children: [
@@ -2192,32 +2343,120 @@ function QuestionConfigEditor({ question, onUpdate }) {
2192
2343
  /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Default Country", value: config.defaultCountry, placeholder: "e.g. US", onChange: (v) => updateConfig({ defaultCountry: v }) }),
2193
2344
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2194
2345
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground mb-2 block", children: "Fields" }),
2195
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: ADDRESS_FIELDS.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
2196
- ToggleField,
2197
- {
2198
- label: f.label,
2199
- checked: activeFields.includes(f.value),
2200
- onChange: (checked) => {
2201
- const next = checked ? [...activeFields, f.value] : activeFields.filter((v) => v !== f.value);
2202
- updateConfig({ fields: next.length > 0 ? next : void 0 });
2203
- }
2204
- },
2205
- f.value
2206
- )) })
2346
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: ADDRESS_FIELDS.map((f) => {
2347
+ const isChecked = activeFields.includes(f.value);
2348
+ const isLastChecked = isChecked && activeFields.length === 1;
2349
+ return /* @__PURE__ */ jsxRuntime.jsx(
2350
+ ToggleField,
2351
+ {
2352
+ label: f.label,
2353
+ checked: isChecked,
2354
+ disabled: isLastChecked,
2355
+ onChange: (checked) => {
2356
+ const next = checked ? [...activeFields, f.value] : activeFields.filter((v) => v !== f.value);
2357
+ updateConfig({ fields: next });
2358
+ }
2359
+ },
2360
+ f.value
2361
+ );
2362
+ }) })
2207
2363
  ] })
2208
2364
  ] });
2209
2365
  }
2210
- case "payment":
2366
+ case "payment": {
2367
+ const amountFieldOptions = [
2368
+ { label: "None (Fixed Amount)", value: "" },
2369
+ ...candidateAmountFields,
2370
+ { label: "Custom Field ID...", value: "__custom__" }
2371
+ ];
2372
+ const isCustomAmountField = Boolean(config.amountField) && !candidateAmountFields.some((opt) => opt.value === config.amountField);
2211
2373
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Payment Settings", children: [
2212
- /* @__PURE__ */ jsxRuntime.jsx(SelectField, { label: "Provider", value: config.provider ?? "stripe", options: [{ label: "Stripe", value: "stripe" }, { label: "PayPal", value: "paypal" }], onChange: (v) => updateConfig({ provider: v }) }),
2213
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Public Key", value: config.publicKey ?? "", placeholder: "Provider public/client key", onChange: (v) => updateConfig({ publicKey: v }) }),
2214
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Server URL", value: config.serverUrl ?? "", placeholder: "https://api.example.com/create-intent", onChange: (v) => updateConfig({ serverUrl: v || void 0 }) }),
2215
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Client Secret Path", value: config.responseMapping?.clientSecretPath ?? "", placeholder: "clientSecret", onChange: (v) => updateConfig({ responseMapping: v ? { clientSecretPath: v } : void 0 }) }),
2216
- /* @__PURE__ */ jsxRuntime.jsx(NumberField, { label: "Amount", value: config.amount, onChange: (v) => updateConfig({ amount: v }) }),
2217
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Amount Field", value: config.amountField, placeholder: "Field ID for dynamic amount", onChange: (v) => updateConfig({ amountField: v }) }),
2218
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Currency", value: config.currency, placeholder: "USD", onChange: (v) => updateConfig({ currency: v }) }),
2219
- /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Description", value: config.description, onChange: (v) => updateConfig({ description: v }) })
2374
+ /* @__PURE__ */ jsxRuntime.jsx(
2375
+ SelectField,
2376
+ {
2377
+ label: "Provider",
2378
+ value: config.provider ?? "stripe",
2379
+ options: [
2380
+ { label: "Stripe", value: "stripe" },
2381
+ { label: "PayPal", value: "paypal" },
2382
+ { label: "Razorpay", value: "razorpay" }
2383
+ ],
2384
+ onChange: (v) => updateConfig({ provider: v })
2385
+ }
2386
+ ),
2387
+ /* @__PURE__ */ jsxRuntime.jsx(
2388
+ TextField,
2389
+ {
2390
+ label: "Public Key",
2391
+ value: config.publicKey,
2392
+ placeholder: "pk_test_...",
2393
+ onChange: (v) => updateConfig({ publicKey: v || void 0 })
2394
+ }
2395
+ ),
2396
+ /* @__PURE__ */ jsxRuntime.jsx(
2397
+ TextField,
2398
+ {
2399
+ label: "Server URL",
2400
+ value: config.serverUrl,
2401
+ placeholder: "https://api.example.com/create-intent",
2402
+ onChange: (v) => updateConfig({ serverUrl: v || void 0 })
2403
+ }
2404
+ ),
2405
+ candidateAmountFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2406
+ /* @__PURE__ */ jsxRuntime.jsx(
2407
+ SelectField,
2408
+ {
2409
+ label: "Dynamic Amount Field",
2410
+ value: isCustomAmountField ? "__custom__" : config.amountField ?? "",
2411
+ options: amountFieldOptions,
2412
+ onChange: (v) => {
2413
+ if (v !== "__custom__") {
2414
+ updateConfig({ amountField: v || void 0 });
2415
+ }
2416
+ }
2417
+ }
2418
+ ),
2419
+ isCustomAmountField && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(
2420
+ TextField,
2421
+ {
2422
+ label: "Custom Field ID",
2423
+ value: config.amountField,
2424
+ placeholder: "Field ID for dynamic amount",
2425
+ onChange: (v) => updateConfig({ amountField: v || void 0 })
2426
+ }
2427
+ ) })
2428
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
2429
+ TextField,
2430
+ {
2431
+ label: "Amount Field",
2432
+ value: config.amountField,
2433
+ placeholder: "Field ID for dynamic amount (e.g. total_cost)",
2434
+ onChange: (v) => updateConfig({ amountField: v || void 0 })
2435
+ }
2436
+ ),
2437
+ !config.amountField && /* @__PURE__ */ jsxRuntime.jsx(
2438
+ NumberField,
2439
+ {
2440
+ label: "Fixed Amount",
2441
+ value: config.amount,
2442
+ onChange: (v) => updateConfig({ amount: v })
2443
+ }
2444
+ ),
2445
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Currency", value: config.currency ?? "USD", placeholder: "USD", onChange: (v) => updateConfig({ currency: v || "USD" }) }),
2446
+ /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Description", value: config.description, onChange: (v) => updateConfig({ description: v }) }),
2447
+ /* @__PURE__ */ jsxRuntime.jsx(
2448
+ TextField,
2449
+ {
2450
+ label: "Client Secret Response Path",
2451
+ value: typeof config.responseMapping?.clientSecretPath === "string" ? config.responseMapping.clientSecretPath : config.responseMapping ?? "",
2452
+ placeholder: "clientSecret",
2453
+ onChange: (v) => updateConfig({
2454
+ responseMapping: v ? { clientSecretPath: v } : void 0
2455
+ })
2456
+ }
2457
+ )
2220
2458
  ] });
2459
+ }
2221
2460
  case "calculated":
2222
2461
  return /* @__PURE__ */ jsxRuntime.jsxs(ConfigSection, { title: "Calculated Field", children: [
2223
2462
  /* @__PURE__ */ jsxRuntime.jsx(TextField, { label: "Expression", value: config.expression ?? "", placeholder: "e.g. {q1} + {q2}", onChange: (v) => updateConfig({ expression: v }) }),
@@ -2326,11 +2565,12 @@ function NumberField({
2326
2565
  function ToggleField({
2327
2566
  label,
2328
2567
  checked,
2329
- onChange
2568
+ onChange,
2569
+ disabled
2330
2570
  }) {
2331
2571
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2332
2572
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground", children: label }),
2333
- /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Switch, { checked, onCheckedChange: onChange })
2573
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Switch, { checked, onCheckedChange: onChange, disabled })
2334
2574
  ] });
2335
2575
  }
2336
2576
  function SelectField({
@@ -3302,7 +3542,14 @@ function QuestionProperties({ question, sectionId, builderState, onClose, onOpen
3302
3542
  }
3303
3543
  )
3304
3544
  ] }),
3305
- /* @__PURE__ */ jsxRuntime.jsx(QuestionConfigEditor, { question, onUpdate: updateQuestion2 }),
3545
+ /* @__PURE__ */ jsxRuntime.jsx(
3546
+ QuestionConfigEditor,
3547
+ {
3548
+ question,
3549
+ schema: builderState.schema,
3550
+ onUpdate: updateQuestion2
3551
+ }
3552
+ ),
3306
3553
  hasOptions && /* @__PURE__ */ jsxRuntime.jsx(
3307
3554
  OptionsEditor,
3308
3555
  {
@@ -3479,6 +3726,11 @@ function MonacoWrapper({ value, onChange, errors }) {
3479
3726
  editorRef.current = editor;
3480
3727
  monacoRef.current = monaco;
3481
3728
  };
3729
+ React.useEffect(() => {
3730
+ return () => {
3731
+ editorRef.current?.dispose();
3732
+ };
3733
+ }, []);
3482
3734
  React.useEffect(() => {
3483
3735
  const editor = editorRef.current;
3484
3736
  const monaco = monacoRef.current;
@@ -4283,6 +4535,11 @@ function TemplateGallery({ templates, onSelect, onClose }) {
4283
4535
  function useDebouncedValidation(delayMs = 500) {
4284
4536
  const [errors, setErrors] = React.useState([]);
4285
4537
  const timerRef = React.useRef(null);
4538
+ React.useEffect(() => {
4539
+ return () => {
4540
+ if (timerRef.current) clearTimeout(timerRef.current);
4541
+ };
4542
+ }, []);
4286
4543
  const validate = React.useCallback(
4287
4544
  (text) => {
4288
4545
  if (timerRef.current) {
@@ -5033,6 +5290,58 @@ function FormBuilderInner(props) {
5033
5290
  // src/form-builder/components/FormBuilderGated.tsx
5034
5291
  var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
5035
5292
 
5293
+ // src/form-builder/theme/presets.ts
5294
+ var squaredrDarkPreset = {
5295
+ background: "#0F1A1F",
5296
+ foreground: "#E8EFF1",
5297
+ card: "#16242A",
5298
+ primary: "#63BDB4",
5299
+ primaryForeground: "#0F1A1F",
5300
+ secondary: "#182F31",
5301
+ secondaryForeground: "#E8EFF1",
5302
+ muted: "#182F31",
5303
+ mutedForeground: "#8CA1A9",
5304
+ accent: "#182F31",
5305
+ accentForeground: "#E8EFF1",
5306
+ destructive: "#E08072",
5307
+ destructiveForeground: "#0F1A1F",
5308
+ border: "#2A3B42",
5309
+ input: "#2A3B42",
5310
+ ring: "#63BDB4",
5311
+ radius: "0px",
5312
+ surface: "#16242A",
5313
+ surfaceHover: "#182F31",
5314
+ canvas: "#0F1A1F",
5315
+ panel: "#16242A",
5316
+ borderStrong: "#2F4F4C",
5317
+ textDim: "#5E7680"
5318
+ };
5319
+ var cleanPreset = {
5320
+ background: "#F4F7F8",
5321
+ foreground: "#12222A",
5322
+ card: "#FFFFFF",
5323
+ primary: "#1F6B6E",
5324
+ primaryForeground: "#FFFFFF",
5325
+ secondary: "#EDF3F2",
5326
+ secondaryForeground: "#12222A",
5327
+ muted: "#EDF3F2",
5328
+ mutedForeground: "#6A7B85",
5329
+ accent: "#EDF3F2",
5330
+ accentForeground: "#12222A",
5331
+ destructive: "#B04A3C",
5332
+ destructiveForeground: "#FFFFFF",
5333
+ border: "#DCE4E8",
5334
+ input: "#DCE4E8",
5335
+ ring: "#1F6B6E",
5336
+ radius: "0px",
5337
+ surface: "#FAFCFC",
5338
+ surfaceHover: "#EDF3F2",
5339
+ canvas: "#F4F7F8",
5340
+ panel: "#FFFFFF",
5341
+ borderStrong: "#B9D1CF",
5342
+ textDim: "#6A7B85"
5343
+ };
5344
+
5036
5345
  exports.DEFAULT_PALETTE = DEFAULT_PALETTE;
5037
5346
  exports.DEFAULT_SCHEMA = DEFAULT_SCHEMA;
5038
5347
  exports.FormBuilder = FormBuilder;
@@ -5041,6 +5350,7 @@ exports.QUESTION_TYPE_INFO = QUESTION_TYPE_INFO;
5041
5350
  exports.addOption = addOption;
5042
5351
  exports.addQuestion = addQuestion;
5043
5352
  exports.addSection = addSection;
5353
+ exports.cleanPreset = cleanPreset;
5044
5354
  exports.cn = cn;
5045
5355
  exports.duplicateQuestion = duplicateQuestion;
5046
5356
  exports.duplicateSection = duplicateSection;
@@ -5056,6 +5366,7 @@ exports.moveSection = moveSection;
5056
5366
  exports.removeOption = removeOption;
5057
5367
  exports.removeQuestion = removeQuestion;
5058
5368
  exports.removeSection = removeSection;
5369
+ exports.squaredrDarkPreset = squaredrDarkPreset;
5059
5370
  exports.updateOption = updateOption;
5060
5371
  exports.updateQuestion = updateQuestion;
5061
5372
  exports.updateSection = updateSection;