@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.
@@ -15,21 +15,20 @@ var MAX_HISTORY = 50;
15
15
  function useUndoRedo(currentSchema, setSchema) {
16
16
  const historyRef = useRef([currentSchema]);
17
17
  const [currentIndex, setCurrentIndex] = useState(0);
18
- const currentIndexRef = useRef(currentIndex);
19
- currentIndexRef.current = currentIndex;
20
18
  const canUndo = currentIndex > 0;
21
19
  const canRedo = currentIndex < historyRef.current.length - 1;
22
20
  const push = useCallback(
23
21
  (schema) => {
24
- const idx = currentIndexRef.current;
25
- historyRef.current = historyRef.current.slice(0, idx + 1);
26
- historyRef.current.push(schema);
27
- if (historyRef.current.length > MAX_HISTORY) {
28
- historyRef.current.shift();
29
- setCurrentIndex(historyRef.current.length - 1);
30
- } else {
31
- setCurrentIndex(idx + 1);
32
- }
22
+ setCurrentIndex((prevIndex) => {
23
+ historyRef.current = historyRef.current.slice(0, prevIndex + 1);
24
+ historyRef.current.push(schema);
25
+ let newIndex = prevIndex + 1;
26
+ if (historyRef.current.length > MAX_HISTORY) {
27
+ historyRef.current.shift();
28
+ newIndex = Math.max(0, newIndex - 1);
29
+ }
30
+ return newIndex;
31
+ });
33
32
  },
34
33
  []
35
34
  );
@@ -62,13 +61,9 @@ function useUndoRedo(currentSchema, setSchema) {
62
61
  }
63
62
 
64
63
  // src/form-builder/utils/id-generator.ts
65
- var counter = 0;
66
64
  function generateId(prefix) {
67
- const timestamp = Date.now().toString(36);
68
- const random = Math.random().toString(36).substring(2, 7);
69
- counter = (counter + 1) % 1e4;
70
- const count = counter.toString(36);
71
- return `${prefix}_${timestamp}${count}${random}`;
65
+ const uuid = crypto.randomUUID().replace(/-/g, "").substring(0, 12);
66
+ return `${prefix}_${uuid}`;
72
67
  }
73
68
  function generateSectionId() {
74
69
  return generateId("section");
@@ -155,18 +150,39 @@ function duplicateSection(schema, sectionId) {
155
150
  const sectionIndex = newSchema.sections.findIndex((s) => s.id === sectionId);
156
151
  if (sectionIndex === -1) return schema;
157
152
  const original = newSchema.sections[sectionIndex];
153
+ const idMap = /* @__PURE__ */ new Map();
154
+ for (const q of original.questions) {
155
+ idMap.set(q.id, generateQuestionId());
156
+ }
158
157
  const duplicate = {
159
158
  ...original,
160
159
  id: generateSectionId(),
161
160
  title: `${original.title} (Copy)`,
161
+ showIf: original.showIf ? remapConditionFieldIds(original.showIf, idMap) : void 0,
162
162
  questions: original.questions.map((q) => ({
163
163
  ...q,
164
- id: generateQuestionId()
164
+ id: idMap.get(q.id),
165
+ showIf: q.showIf ? remapConditionFieldIds(q.showIf, idMap) : void 0
165
166
  }))
166
167
  };
167
168
  newSchema.sections.splice(sectionIndex + 1, 0, duplicate);
168
169
  return newSchema;
169
170
  }
171
+ function remapConditionFieldIds(expr, idMap) {
172
+ if (expr.field) {
173
+ return {
174
+ ...expr,
175
+ field: idMap.get(expr.field) ?? expr.field
176
+ };
177
+ }
178
+ if (expr.conditions) {
179
+ return {
180
+ ...expr,
181
+ conditions: expr.conditions.map((c) => remapConditionFieldIds(c, idMap))
182
+ };
183
+ }
184
+ return expr;
185
+ }
170
186
  function addQuestion(schema, sectionId, question, index) {
171
187
  const newSchema = deepClone(schema);
172
188
  const section = newSchema.sections.find((s) => s.id === sectionId);
@@ -310,7 +326,7 @@ function useBuilderState(initialSchema) {
310
326
  (sectionId) => {
311
327
  applyMutation((s) => removeSection(s, sectionId));
312
328
  setSelectedItem((prev) => {
313
- if (prev?.type === "section" && prev.sectionId === sectionId) return null;
329
+ if (prev?.sectionId === sectionId) return null;
314
330
  return prev;
315
331
  });
316
332
  },
@@ -1440,6 +1456,14 @@ function SectionBlock({ section, builderState }) {
1440
1456
  }
1441
1457
  function FormCanvas({ builderState }) {
1442
1458
  const { schema } = builderState;
1459
+ const [localTitle, setLocalTitle] = useState(schema.title);
1460
+ const [localDesc, setLocalDesc] = useState(schema.description ?? "");
1461
+ useEffect(() => {
1462
+ setLocalTitle(schema.title);
1463
+ }, [schema.title]);
1464
+ useEffect(() => {
1465
+ setLocalDesc(schema.description ?? "");
1466
+ }, [schema.description]);
1443
1467
  const handleAddSection = () => {
1444
1468
  const newSection = {
1445
1469
  id: generateSectionId(),
@@ -1456,9 +1480,12 @@ function FormCanvas({ builderState }) {
1456
1480
  "input",
1457
1481
  {
1458
1482
  type: "text",
1459
- value: schema.title,
1460
- onChange: (e) => {
1461
- builderState.updateSchema({ ...schema, title: e.target.value });
1483
+ value: localTitle,
1484
+ onChange: (e) => setLocalTitle(e.target.value),
1485
+ onBlur: () => {
1486
+ if (localTitle !== schema.title) {
1487
+ builderState.updateSchema({ ...schema, title: localTitle });
1488
+ }
1462
1489
  },
1463
1490
  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",
1464
1491
  placeholder: "Form Title"
@@ -1467,9 +1494,12 @@ function FormCanvas({ builderState }) {
1467
1494
  /* @__PURE__ */ jsx(
1468
1495
  "textarea",
1469
1496
  {
1470
- value: schema.description ?? "",
1471
- onChange: (e) => {
1472
- builderState.updateSchema({ ...schema, description: e.target.value });
1497
+ value: localDesc,
1498
+ onChange: (e) => setLocalDesc(e.target.value),
1499
+ onBlur: () => {
1500
+ if (localDesc !== (schema.description ?? "")) {
1501
+ builderState.updateSchema({ ...schema, description: localDesc || void 0 });
1502
+ }
1473
1503
  },
1474
1504
  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",
1475
1505
  placeholder: "Form description (optional)"
@@ -1572,12 +1602,26 @@ SelectItem.displayName = "SelectItem";
1572
1602
  function useConfigUpdater(question, onUpdate) {
1573
1603
  return (updates) => {
1574
1604
  const current = question.config ?? {};
1575
- onUpdate({ config: { ...current, type: question.type, ...updates } });
1605
+ const merged = { ...current, type: question.type, ...updates };
1606
+ for (const key of Object.keys(merged)) {
1607
+ if (merged[key] === void 0) delete merged[key];
1608
+ }
1609
+ onUpdate({ config: merged });
1576
1610
  };
1577
1611
  }
1578
- function QuestionConfigEditor({ question, onUpdate }) {
1612
+ function QuestionConfigEditor({ question, schema, onUpdate }) {
1579
1613
  const updateConfig = useConfigUpdater(question, onUpdate);
1580
1614
  const config = question.config ?? {};
1615
+ const allQuestions = useMemo(() => {
1616
+ if (!schema?.sections) return [];
1617
+ return schema.sections.flatMap((s) => s.questions || []);
1618
+ }, [schema]);
1619
+ const candidateAmountFields = useMemo(() => {
1620
+ 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 }));
1621
+ }, [allQuestions, question.id]);
1622
+ const candidateTimezoneFields = useMemo(() => {
1623
+ 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 }));
1624
+ }, [allQuestions, question.id]);
1581
1625
  switch (question.type) {
1582
1626
  // ── Text ──
1583
1627
  case "short_text":
@@ -1601,32 +1645,32 @@ function QuestionConfigEditor({ question, onUpdate }) {
1601
1645
  // ── Numeric ──
1602
1646
  case "number":
1603
1647
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Number Settings", children: [
1604
- /* @__PURE__ */ jsx(NumberField, { label: "Min", value: config.min, onChange: (v) => updateConfig({ min: v }) }),
1605
- /* @__PURE__ */ jsx(NumberField, { label: "Max", value: config.max, onChange: (v) => updateConfig({ max: v }) }),
1648
+ /* @__PURE__ */ jsx(NumberField, { label: "Min Value", value: config.min, onChange: (v) => updateConfig({ min: v }) }),
1649
+ /* @__PURE__ */ jsx(NumberField, { label: "Max Value", value: config.max, onChange: (v) => updateConfig({ max: v }) }),
1606
1650
  /* @__PURE__ */ jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v }) }),
1607
- /* @__PURE__ */ jsx(NumberField, { label: "Decimal Places", value: config.decimalPlaces, placeholder: "0", onChange: (v) => updateConfig({ decimalPlaces: v }) }),
1608
1651
  /* @__PURE__ */ jsx(TextField, { label: "Prefix", value: config.prefix, placeholder: "e.g. $", onChange: (v) => updateConfig({ prefix: v }) }),
1609
1652
  /* @__PURE__ */ jsx(TextField, { label: "Suffix", value: config.suffix, placeholder: "e.g. kg", onChange: (v) => updateConfig({ suffix: v }) })
1610
1653
  ] });
1611
1654
  case "slider":
1612
1655
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Slider Settings", children: [
1613
- /* @__PURE__ */ jsx(NumberField, { label: "Min", value: config.min, placeholder: "0", onChange: (v) => updateConfig({ min: v }) }),
1614
- /* @__PURE__ */ jsx(NumberField, { label: "Max", value: config.max, placeholder: "100", onChange: (v) => updateConfig({ max: v }) }),
1615
- /* @__PURE__ */ jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v }) }),
1616
- /* @__PURE__ */ jsx(ToggleField, { label: "Show Value", checked: !!config.showValue, onChange: (v) => updateConfig({ showValue: v }) }),
1617
- /* @__PURE__ */ jsx(TextField, { label: "Min Label", value: config.minLabel, onChange: (v) => updateConfig({ minLabel: v }) }),
1618
- /* @__PURE__ */ jsx(TextField, { label: "Max Label", value: config.maxLabel, onChange: (v) => updateConfig({ maxLabel: v }) })
1656
+ /* @__PURE__ */ jsx(NumberField, { label: "Min Value", value: config.min, placeholder: "0", onChange: (v) => updateConfig({ min: v ?? 0 }) }),
1657
+ /* @__PURE__ */ jsx(NumberField, { label: "Max Value", value: config.max, placeholder: "100", onChange: (v) => updateConfig({ max: v ?? 100 }) }),
1658
+ /* @__PURE__ */ jsx(NumberField, { label: "Step", value: config.step, placeholder: "1", onChange: (v) => updateConfig({ step: v ?? 1 }) }),
1659
+ /* @__PURE__ */ jsx(TextField, { label: "Min Label", value: config.minLabel, placeholder: "Low", onChange: (v) => updateConfig({ minLabel: v }) }),
1660
+ /* @__PURE__ */ jsx(TextField, { label: "Max Label", value: config.maxLabel, placeholder: "High", onChange: (v) => updateConfig({ maxLabel: v }) }),
1661
+ /* @__PURE__ */ jsx(ToggleField, { label: "Show Value", checked: config.showValue !== false, onChange: (v) => updateConfig({ showValue: v }) }),
1662
+ /* @__PURE__ */ jsx(TextField, { label: "Unit", value: config.unit, placeholder: "e.g. %", onChange: (v) => updateConfig({ unit: v }) })
1619
1663
  ] });
1620
1664
  case "rating":
1621
1665
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Rating Settings", children: [
1622
- /* @__PURE__ */ jsx(NumberField, { label: "Max Stars", value: config.max, placeholder: "5", onChange: (v) => updateConfig({ max: v }) }),
1623
- /* @__PURE__ */ 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 }) }),
1624
- /* @__PURE__ */ jsx(ToggleField, { label: "Show Labels", checked: !!config.showLabels, onChange: (v) => updateConfig({ showLabels: v }) })
1666
+ /* @__PURE__ */ jsx(NumberField, { label: "Max Rating", value: config.maxRating, placeholder: "5", onChange: (v) => updateConfig({ maxRating: v ?? 5 }) }),
1667
+ /* @__PURE__ */ 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 }) }),
1668
+ /* @__PURE__ */ jsx(ToggleField, { label: "Allow Half Ratings", checked: !!config.allowHalf, onChange: (v) => updateConfig({ allowHalf: v }) })
1625
1669
  ] });
1626
1670
  case "nps":
1627
1671
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "NPS Settings", children: [
1628
- /* @__PURE__ */ jsx(TextField, { label: "Low Label", value: config.lowLabel ?? "Not likely", onChange: (v) => updateConfig({ lowLabel: v }) }),
1629
- /* @__PURE__ */ jsx(TextField, { label: "High Label", value: config.highLabel ?? "Very likely", onChange: (v) => updateConfig({ highLabel: v }) })
1672
+ /* @__PURE__ */ jsx(TextField, { label: "Left Label", value: config.leftLabel ?? "Not likely", onChange: (v) => updateConfig({ leftLabel: v }) }),
1673
+ /* @__PURE__ */ jsx(TextField, { label: "Right Label", value: config.rightLabel ?? "Extremely likely", onChange: (v) => updateConfig({ rightLabel: v }) })
1630
1674
  ] });
1631
1675
  case "opinion_scale":
1632
1676
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Scale Settings", children: [
@@ -1692,50 +1736,126 @@ function QuestionConfigEditor({ question, onUpdate }) {
1692
1736
  /* @__PURE__ */ jsx(NumberField, { label: "Max Range (days)", value: config.maxRangeDays, onChange: (v) => updateConfig({ maxRangeDays: v }) })
1693
1737
  ] });
1694
1738
  case "appointment": {
1695
- const appointmentMode = typeof config.embedUrl === "string" ? "embed" : typeof config.slotsUrl === "string" ? "url" : "static";
1739
+ const appointmentMode = typeof config.slotsUrl === "string" ? "url" : typeof config.embedUrl === "string" ? "embed" : "static";
1740
+ const timezoneFieldOptions = [
1741
+ { label: "None (Static Timezone)", value: "" },
1742
+ ...candidateTimezoneFields,
1743
+ { label: "Custom Field ID...", value: "__custom__" }
1744
+ ];
1745
+ const isCustomTzField = Boolean(config.timezoneField) && !candidateTimezoneFields.some((opt) => opt.value === config.timezoneField);
1696
1746
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Appointment Settings", children: [
1697
1747
  /* @__PURE__ */ jsx(
1698
1748
  SelectField,
1699
1749
  {
1700
- label: "Mode",
1750
+ label: "Scheduling Mode",
1701
1751
  value: appointmentMode,
1702
1752
  options: [
1703
- { label: "Static Slots", value: "static" },
1704
- { label: "API Endpoint", value: "url" },
1705
- { label: "Embed (Calendly / Cal.com)", value: "embed" }
1753
+ { label: "Manual Static Slots", value: "static" },
1754
+ { label: "Fetch from URL", value: "url" },
1755
+ { label: "Third-Party Embed (Calendly / Cal.com)", value: "embed" }
1706
1756
  ],
1707
1757
  onChange: (v) => {
1708
- if (v === "static") {
1709
- updateConfig({ slotsUrl: void 0, embedUrl: void 0, embedProvider: void 0 });
1710
- } else if (v === "url") {
1711
- updateConfig({ slotsUrl: "", embedUrl: void 0, embedProvider: void 0, slots: void 0 });
1758
+ if (v === "url") {
1759
+ updateConfig({
1760
+ slotsUrl: "",
1761
+ embedUrl: void 0,
1762
+ embedProvider: void 0,
1763
+ slots: void 0
1764
+ });
1712
1765
  } else if (v === "embed") {
1713
- updateConfig({ embedUrl: "", slotsUrl: void 0, slots: void 0 });
1766
+ updateConfig({
1767
+ embedUrl: "",
1768
+ embedProvider: config.embedProvider || "cal_com",
1769
+ slotsUrl: void 0,
1770
+ slots: void 0
1771
+ });
1772
+ } else {
1773
+ updateConfig({
1774
+ slots: config.slots ?? [],
1775
+ slotsUrl: void 0,
1776
+ embedUrl: void 0,
1777
+ embedProvider: void 0
1778
+ });
1714
1779
  }
1715
1780
  }
1716
1781
  }
1717
1782
  ),
1718
- /* @__PURE__ */ jsx(NumberField, { label: "Duration (min)", value: config.duration ?? 30, onChange: (v) => updateConfig({ duration: v }) }),
1719
- /* @__PURE__ */ jsx(TextField, { label: "Timezone", value: config.timezone, placeholder: "e.g. America/New_York", onChange: (v) => updateConfig({ timezone: v }) }),
1720
- /* @__PURE__ */ jsx(TextField, { label: "Timezone Field", value: config.timezoneField, placeholder: "Field ID for dynamic timezone", onChange: (v) => updateConfig({ timezoneField: v }) }),
1721
- /* @__PURE__ */ jsx(TextField, { label: "Date Format", value: config.dateFormat, placeholder: "Locale-aware (default)", onChange: (v) => updateConfig({ dateFormat: v }) }),
1722
- appointmentMode === "url" && /* @__PURE__ */ jsx(TextField, { label: "Slots URL", value: config.slotsUrl, placeholder: "https://api.example.com/slots", onChange: (v) => updateConfig({ slotsUrl: v }) }),
1783
+ appointmentMode === "url" && /* @__PURE__ */ jsx(
1784
+ TextField,
1785
+ {
1786
+ label: "Slots URL",
1787
+ value: config.slotsUrl,
1788
+ placeholder: "https://example.com/slots",
1789
+ onChange: (v) => updateConfig({ slotsUrl: v ?? "" })
1790
+ }
1791
+ ),
1723
1792
  appointmentMode === "embed" && /* @__PURE__ */ jsxs(Fragment, { children: [
1724
- /* @__PURE__ */ jsx(TextField, { label: "Embed URL", value: config.embedUrl, placeholder: "https://calendly.com/your-name/30min", onChange: (v) => updateConfig({ embedUrl: v }) }),
1793
+ /* @__PURE__ */ jsx(
1794
+ TextField,
1795
+ {
1796
+ label: "Embed URL",
1797
+ value: config.embedUrl,
1798
+ placeholder: "https://calendly.com/... or https://cal.com/...",
1799
+ onChange: (v) => updateConfig({ embedUrl: v ?? "" })
1800
+ }
1801
+ ),
1725
1802
  /* @__PURE__ */ jsx(
1726
1803
  SelectField,
1727
1804
  {
1728
1805
  label: "Embed Provider",
1729
- value: config.embedProvider ?? "custom",
1806
+ value: config.embedProvider ?? "cal_com",
1730
1807
  options: [
1731
- { label: "Calendly", value: "calendly" },
1732
1808
  { label: "Cal.com", value: "cal_com" },
1809
+ { label: "Calendly", value: "calendly" },
1733
1810
  { label: "Custom", value: "custom" }
1734
1811
  ],
1735
1812
  onChange: (v) => updateConfig({ embedProvider: v })
1736
1813
  }
1737
1814
  )
1738
1815
  ] }),
1816
+ /* @__PURE__ */ jsx(NumberField, { label: "Duration (min)", value: config.duration ?? 30, onChange: (v) => updateConfig({ duration: v }) }),
1817
+ /* @__PURE__ */ jsx(TextField, { label: "Timezone", value: config.timezone, placeholder: "e.g. America/New_York", onChange: (v) => updateConfig({ timezone: v }) }),
1818
+ candidateTimezoneFields.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
1819
+ /* @__PURE__ */ jsx(
1820
+ SelectField,
1821
+ {
1822
+ label: "Dynamic Timezone Field",
1823
+ value: isCustomTzField ? "__custom__" : config.timezoneField ?? "",
1824
+ options: timezoneFieldOptions,
1825
+ onChange: (v) => {
1826
+ if (v !== "__custom__") {
1827
+ updateConfig({ timezoneField: v || void 0 });
1828
+ }
1829
+ }
1830
+ }
1831
+ ),
1832
+ isCustomTzField && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
1833
+ TextField,
1834
+ {
1835
+ label: "Custom Timezone Field ID",
1836
+ value: config.timezoneField,
1837
+ placeholder: "e.g. timezone",
1838
+ onChange: (v) => updateConfig({ timezoneField: v || void 0 })
1839
+ }
1840
+ ) })
1841
+ ] }) : /* @__PURE__ */ jsx(
1842
+ TextField,
1843
+ {
1844
+ label: "Timezone Field",
1845
+ value: config.timezoneField,
1846
+ placeholder: "Field ID for dynamic timezone",
1847
+ onChange: (v) => updateConfig({ timezoneField: v || void 0 })
1848
+ }
1849
+ ),
1850
+ /* @__PURE__ */ jsx(TextField, { label: "Date Format", value: config.dateFormat, placeholder: "Locale-aware (default)", onChange: (v) => updateConfig({ dateFormat: v }) }),
1851
+ /* @__PURE__ */ jsx(
1852
+ ToggleField,
1853
+ {
1854
+ label: "Auto-Advance on Booking Complete",
1855
+ checked: config.autoAdvance !== false,
1856
+ onChange: (v) => updateConfig({ autoAdvance: v })
1857
+ }
1858
+ ),
1739
1859
  appointmentMode === "static" && /* @__PURE__ */ jsx(
1740
1860
  AppointmentSlotsEditor,
1741
1861
  {
@@ -1748,10 +1868,41 @@ function QuestionConfigEditor({ question, onUpdate }) {
1748
1868
  // ── Media ──
1749
1869
  case "file_upload":
1750
1870
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Upload Settings", children: [
1871
+ /* @__PURE__ */ 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: [
1872
+ /* @__PURE__ */ jsx("span", { children: "\u{1F4E6}" }),
1873
+ /* @__PURE__ */ jsx("span", { children: "Uploads stream to your cloud bucket using Form / Account Storage settings." })
1874
+ ] }),
1751
1875
  /* @__PURE__ */ jsx(NumberField, { label: "Max Files", value: config.maxFiles ?? 1, onChange: (v) => updateConfig({ maxFiles: v }) }),
1752
1876
  /* @__PURE__ */ jsx(NumberField, { label: "Max Size (MB)", value: config.maxSizeMb ?? 10, onChange: (v) => updateConfig({ maxSizeMb: v }) }),
1753
- /* @__PURE__ */ 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 }) }),
1754
- /* @__PURE__ */ jsx(TextField, { label: "Upload URL", value: config.uploadUrl, placeholder: "Server upload endpoint", onChange: (v) => updateConfig({ uploadUrl: v }) })
1877
+ /* @__PURE__ */ jsx(
1878
+ TextField,
1879
+ {
1880
+ label: "Accepted Types (comma-separated)",
1881
+ value: config.accept?.join(", "),
1882
+ placeholder: "e.g. image/*, .pdf, .docx, .zip",
1883
+ onChange: (v) => updateConfig({
1884
+ accept: v ? v.split(",").map((s) => s.trim()).filter(Boolean) : void 0
1885
+ })
1886
+ }
1887
+ ),
1888
+ /* @__PURE__ */ jsx(
1889
+ TextField,
1890
+ {
1891
+ label: "Upload URL",
1892
+ value: config.uploadUrl,
1893
+ placeholder: "https://api.example.com/s3/presign",
1894
+ onChange: (v) => updateConfig({ uploadUrl: v || void 0 })
1895
+ }
1896
+ ),
1897
+ /* @__PURE__ */ jsx(
1898
+ TextField,
1899
+ {
1900
+ label: "Public Key",
1901
+ value: config.publicKey,
1902
+ placeholder: "pk_test_...",
1903
+ onChange: (v) => updateConfig({ publicKey: v || void 0 })
1904
+ }
1905
+ )
1755
1906
  ] });
1756
1907
  case "signature":
1757
1908
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Signature Settings", children: [
@@ -1868,32 +2019,120 @@ function QuestionConfigEditor({ question, onUpdate }) {
1868
2019
  /* @__PURE__ */ jsx(TextField, { label: "Default Country", value: config.defaultCountry, placeholder: "e.g. US", onChange: (v) => updateConfig({ defaultCountry: v }) }),
1869
2020
  /* @__PURE__ */ jsxs("div", { children: [
1870
2021
  /* @__PURE__ */ jsx(Label, { className: "text-xs text-muted-foreground mb-2 block", children: "Fields" }),
1871
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: ADDRESS_FIELDS.map((f) => /* @__PURE__ */ jsx(
1872
- ToggleField,
1873
- {
1874
- label: f.label,
1875
- checked: activeFields.includes(f.value),
1876
- onChange: (checked) => {
1877
- const next = checked ? [...activeFields, f.value] : activeFields.filter((v) => v !== f.value);
1878
- updateConfig({ fields: next.length > 0 ? next : void 0 });
1879
- }
1880
- },
1881
- f.value
1882
- )) })
2022
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: ADDRESS_FIELDS.map((f) => {
2023
+ const isChecked = activeFields.includes(f.value);
2024
+ const isLastChecked = isChecked && activeFields.length === 1;
2025
+ return /* @__PURE__ */ jsx(
2026
+ ToggleField,
2027
+ {
2028
+ label: f.label,
2029
+ checked: isChecked,
2030
+ disabled: isLastChecked,
2031
+ onChange: (checked) => {
2032
+ const next = checked ? [...activeFields, f.value] : activeFields.filter((v) => v !== f.value);
2033
+ updateConfig({ fields: next });
2034
+ }
2035
+ },
2036
+ f.value
2037
+ );
2038
+ }) })
1883
2039
  ] })
1884
2040
  ] });
1885
2041
  }
1886
- case "payment":
2042
+ case "payment": {
2043
+ const amountFieldOptions = [
2044
+ { label: "None (Fixed Amount)", value: "" },
2045
+ ...candidateAmountFields,
2046
+ { label: "Custom Field ID...", value: "__custom__" }
2047
+ ];
2048
+ const isCustomAmountField = Boolean(config.amountField) && !candidateAmountFields.some((opt) => opt.value === config.amountField);
1887
2049
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Payment Settings", children: [
1888
- /* @__PURE__ */ jsx(SelectField, { label: "Provider", value: config.provider ?? "stripe", options: [{ label: "Stripe", value: "stripe" }, { label: "PayPal", value: "paypal" }], onChange: (v) => updateConfig({ provider: v }) }),
1889
- /* @__PURE__ */ jsx(TextField, { label: "Public Key", value: config.publicKey ?? "", placeholder: "Provider public/client key", onChange: (v) => updateConfig({ publicKey: v }) }),
1890
- /* @__PURE__ */ jsx(TextField, { label: "Server URL", value: config.serverUrl ?? "", placeholder: "https://api.example.com/create-intent", onChange: (v) => updateConfig({ serverUrl: v || void 0 }) }),
1891
- /* @__PURE__ */ jsx(TextField, { label: "Client Secret Path", value: config.responseMapping?.clientSecretPath ?? "", placeholder: "clientSecret", onChange: (v) => updateConfig({ responseMapping: v ? { clientSecretPath: v } : void 0 }) }),
1892
- /* @__PURE__ */ jsx(NumberField, { label: "Amount", value: config.amount, onChange: (v) => updateConfig({ amount: v }) }),
1893
- /* @__PURE__ */ jsx(TextField, { label: "Amount Field", value: config.amountField, placeholder: "Field ID for dynamic amount", onChange: (v) => updateConfig({ amountField: v }) }),
1894
- /* @__PURE__ */ jsx(TextField, { label: "Currency", value: config.currency, placeholder: "USD", onChange: (v) => updateConfig({ currency: v }) }),
1895
- /* @__PURE__ */ jsx(TextField, { label: "Description", value: config.description, onChange: (v) => updateConfig({ description: v }) })
2050
+ /* @__PURE__ */ jsx(
2051
+ SelectField,
2052
+ {
2053
+ label: "Provider",
2054
+ value: config.provider ?? "stripe",
2055
+ options: [
2056
+ { label: "Stripe", value: "stripe" },
2057
+ { label: "PayPal", value: "paypal" },
2058
+ { label: "Razorpay", value: "razorpay" }
2059
+ ],
2060
+ onChange: (v) => updateConfig({ provider: v })
2061
+ }
2062
+ ),
2063
+ /* @__PURE__ */ jsx(
2064
+ TextField,
2065
+ {
2066
+ label: "Public Key",
2067
+ value: config.publicKey,
2068
+ placeholder: "pk_test_...",
2069
+ onChange: (v) => updateConfig({ publicKey: v || void 0 })
2070
+ }
2071
+ ),
2072
+ /* @__PURE__ */ jsx(
2073
+ TextField,
2074
+ {
2075
+ label: "Server URL",
2076
+ value: config.serverUrl,
2077
+ placeholder: "https://api.example.com/create-intent",
2078
+ onChange: (v) => updateConfig({ serverUrl: v || void 0 })
2079
+ }
2080
+ ),
2081
+ candidateAmountFields.length > 0 ? /* @__PURE__ */ jsxs("div", { children: [
2082
+ /* @__PURE__ */ jsx(
2083
+ SelectField,
2084
+ {
2085
+ label: "Dynamic Amount Field",
2086
+ value: isCustomAmountField ? "__custom__" : config.amountField ?? "",
2087
+ options: amountFieldOptions,
2088
+ onChange: (v) => {
2089
+ if (v !== "__custom__") {
2090
+ updateConfig({ amountField: v || void 0 });
2091
+ }
2092
+ }
2093
+ }
2094
+ ),
2095
+ isCustomAmountField && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2096
+ TextField,
2097
+ {
2098
+ label: "Custom Field ID",
2099
+ value: config.amountField,
2100
+ placeholder: "Field ID for dynamic amount",
2101
+ onChange: (v) => updateConfig({ amountField: v || void 0 })
2102
+ }
2103
+ ) })
2104
+ ] }) : /* @__PURE__ */ jsx(
2105
+ TextField,
2106
+ {
2107
+ label: "Amount Field",
2108
+ value: config.amountField,
2109
+ placeholder: "Field ID for dynamic amount (e.g. total_cost)",
2110
+ onChange: (v) => updateConfig({ amountField: v || void 0 })
2111
+ }
2112
+ ),
2113
+ !config.amountField && /* @__PURE__ */ jsx(
2114
+ NumberField,
2115
+ {
2116
+ label: "Fixed Amount",
2117
+ value: config.amount,
2118
+ onChange: (v) => updateConfig({ amount: v })
2119
+ }
2120
+ ),
2121
+ /* @__PURE__ */ jsx(TextField, { label: "Currency", value: config.currency ?? "USD", placeholder: "USD", onChange: (v) => updateConfig({ currency: v || "USD" }) }),
2122
+ /* @__PURE__ */ jsx(TextField, { label: "Description", value: config.description, onChange: (v) => updateConfig({ description: v }) }),
2123
+ /* @__PURE__ */ jsx(
2124
+ TextField,
2125
+ {
2126
+ label: "Client Secret Response Path",
2127
+ value: typeof config.responseMapping?.clientSecretPath === "string" ? config.responseMapping.clientSecretPath : config.responseMapping ?? "",
2128
+ placeholder: "clientSecret",
2129
+ onChange: (v) => updateConfig({
2130
+ responseMapping: v ? { clientSecretPath: v } : void 0
2131
+ })
2132
+ }
2133
+ )
1896
2134
  ] });
2135
+ }
1897
2136
  case "calculated":
1898
2137
  return /* @__PURE__ */ jsxs(ConfigSection, { title: "Calculated Field", children: [
1899
2138
  /* @__PURE__ */ jsx(TextField, { label: "Expression", value: config.expression ?? "", placeholder: "e.g. {q1} + {q2}", onChange: (v) => updateConfig({ expression: v }) }),
@@ -2002,11 +2241,12 @@ function NumberField({
2002
2241
  function ToggleField({
2003
2242
  label,
2004
2243
  checked,
2005
- onChange
2244
+ onChange,
2245
+ disabled
2006
2246
  }) {
2007
2247
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2008
2248
  /* @__PURE__ */ jsx(Label, { className: "text-xs text-muted-foreground", children: label }),
2009
- /* @__PURE__ */ jsx(Switch, { checked, onCheckedChange: onChange })
2249
+ /* @__PURE__ */ jsx(Switch, { checked, onCheckedChange: onChange, disabled })
2010
2250
  ] });
2011
2251
  }
2012
2252
  function SelectField({
@@ -2978,7 +3218,14 @@ function QuestionProperties({ question, sectionId, builderState, onClose, onOpen
2978
3218
  }
2979
3219
  )
2980
3220
  ] }),
2981
- /* @__PURE__ */ jsx(QuestionConfigEditor, { question, onUpdate: updateQuestion2 }),
3221
+ /* @__PURE__ */ jsx(
3222
+ QuestionConfigEditor,
3223
+ {
3224
+ question,
3225
+ schema: builderState.schema,
3226
+ onUpdate: updateQuestion2
3227
+ }
3228
+ ),
2982
3229
  hasOptions && /* @__PURE__ */ jsx(
2983
3230
  OptionsEditor,
2984
3231
  {
@@ -3155,6 +3402,11 @@ function MonacoWrapper({ value, onChange, errors }) {
3155
3402
  editorRef.current = editor;
3156
3403
  monacoRef.current = monaco;
3157
3404
  };
3405
+ useEffect(() => {
3406
+ return () => {
3407
+ editorRef.current?.dispose();
3408
+ };
3409
+ }, []);
3158
3410
  useEffect(() => {
3159
3411
  const editor = editorRef.current;
3160
3412
  const monaco = monacoRef.current;
@@ -3959,6 +4211,11 @@ function TemplateGallery({ templates, onSelect, onClose }) {
3959
4211
  function useDebouncedValidation(delayMs = 500) {
3960
4212
  const [errors, setErrors] = useState([]);
3961
4213
  const timerRef = useRef(null);
4214
+ useEffect(() => {
4215
+ return () => {
4216
+ if (timerRef.current) clearTimeout(timerRef.current);
4217
+ };
4218
+ }, []);
3962
4219
  const validate = useCallback(
3963
4220
  (text) => {
3964
4221
  if (timerRef.current) {
@@ -4709,4 +4966,56 @@ function FormBuilderInner(props) {
4709
4966
  // src/form-builder/components/FormBuilderGated.tsx
4710
4967
  var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
4711
4968
 
4712
- export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo };
4969
+ // src/form-builder/theme/presets.ts
4970
+ var squaredrDarkPreset = {
4971
+ background: "#0F1A1F",
4972
+ foreground: "#E8EFF1",
4973
+ card: "#16242A",
4974
+ primary: "#63BDB4",
4975
+ primaryForeground: "#0F1A1F",
4976
+ secondary: "#182F31",
4977
+ secondaryForeground: "#E8EFF1",
4978
+ muted: "#182F31",
4979
+ mutedForeground: "#8CA1A9",
4980
+ accent: "#182F31",
4981
+ accentForeground: "#E8EFF1",
4982
+ destructive: "#E08072",
4983
+ destructiveForeground: "#0F1A1F",
4984
+ border: "#2A3B42",
4985
+ input: "#2A3B42",
4986
+ ring: "#63BDB4",
4987
+ radius: "0px",
4988
+ surface: "#16242A",
4989
+ surfaceHover: "#182F31",
4990
+ canvas: "#0F1A1F",
4991
+ panel: "#16242A",
4992
+ borderStrong: "#2F4F4C",
4993
+ textDim: "#5E7680"
4994
+ };
4995
+ var cleanPreset = {
4996
+ background: "#F4F7F8",
4997
+ foreground: "#12222A",
4998
+ card: "#FFFFFF",
4999
+ primary: "#1F6B6E",
5000
+ primaryForeground: "#FFFFFF",
5001
+ secondary: "#EDF3F2",
5002
+ secondaryForeground: "#12222A",
5003
+ muted: "#EDF3F2",
5004
+ mutedForeground: "#6A7B85",
5005
+ accent: "#EDF3F2",
5006
+ accentForeground: "#12222A",
5007
+ destructive: "#B04A3C",
5008
+ destructiveForeground: "#FFFFFF",
5009
+ border: "#DCE4E8",
5010
+ input: "#DCE4E8",
5011
+ ring: "#1F6B6E",
5012
+ radius: "0px",
5013
+ surface: "#FAFCFC",
5014
+ surfaceHover: "#EDF3F2",
5015
+ canvas: "#F4F7F8",
5016
+ panel: "#FFFFFF",
5017
+ borderStrong: "#B9D1CF",
5018
+ textDim: "#6A7B85"
5019
+ };
5020
+
5021
+ export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, cleanPreset, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, squaredrDarkPreset, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo };