@squaredr/fieldcraft-pro 0.0.5 → 1.0.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.
@@ -0,0 +1,9 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ // src/form-builder/utils/cn.ts
5
+ function cn(...inputs) {
6
+ return twMerge(clsx(inputs));
7
+ }
8
+
9
+ export { cn };
@@ -212,12 +212,12 @@ declare const cleanPreset: FormBuilderTheme;
212
212
  * Metadata for all question types available in the Pro palette.
213
213
  * Icons are lucide-react component names (imported dynamically via ICON_MAP).
214
214
  *
215
- * 28 field types — general-purpose form building.
215
+ * 32 field types — general-purpose form building.
216
216
  * Telehealth-specific fields (15) are in @squaredr/fieldcraft-pro-telehealth.
217
217
  */
218
218
  declare const QUESTION_TYPE_INFO: Record<string, QuestionTypeInfo>;
219
219
  /**
220
- * Default palette layout with categories (28 Pro field types).
220
+ * Default palette layout with categories (32 Pro field types).
221
221
  * Telehealth fields can be appended via FormBuilder's `palette` prop.
222
222
  */
223
223
  declare const DEFAULT_PALETTE: ({
@@ -212,12 +212,12 @@ declare const cleanPreset: FormBuilderTheme;
212
212
  * Metadata for all question types available in the Pro palette.
213
213
  * Icons are lucide-react component names (imported dynamically via ICON_MAP).
214
214
  *
215
- * 28 field types — general-purpose form building.
215
+ * 32 field types — general-purpose form building.
216
216
  * Telehealth-specific fields (15) are in @squaredr/fieldcraft-pro-telehealth.
217
217
  */
218
218
  declare const QUESTION_TYPE_INFO: Record<string, QuestionTypeInfo>;
219
219
  /**
220
- * Default palette layout with categories (28 Pro field types).
220
+ * Default palette layout with categories (32 Pro field types).
221
221
  * Telehealth fields can be appended via FormBuilder's `palette` prop.
222
222
  */
223
223
  declare const DEFAULT_PALETTE: ({
@@ -5,6 +5,7 @@ var react = require('react');
5
5
  var core = require('@dnd-kit/core');
6
6
  var lucideReact = require('lucide-react');
7
7
  var fieldcraftReact = require('@squaredr/fieldcraft-react');
8
+ var fieldcraftCore = require('@squaredr/fieldcraft-core');
8
9
  var clsx = require('clsx');
9
10
  var tailwindMerge = require('tailwind-merge');
10
11
  var jsxRuntime = require('react/jsx-runtime');
@@ -80,6 +81,39 @@ function generateOptionId() {
80
81
  function deepClone(obj) {
81
82
  return structuredClone(obj);
82
83
  }
84
+ function cleanCondition(expr, deletedFieldId) {
85
+ if (expr.field === deletedFieldId) return void 0;
86
+ if (expr.field && expr.field !== deletedFieldId) return expr;
87
+ if (expr.conditions) {
88
+ const cleaned = expr.conditions.map((c) => cleanCondition(c, deletedFieldId)).filter((c) => c !== void 0);
89
+ if (cleaned.length === 0) return void 0;
90
+ if (cleaned.length === 1) return cleaned[0];
91
+ return { ...expr, conditions: cleaned };
92
+ }
93
+ return expr;
94
+ }
95
+ function cleanupOrphanConditions(schema, deletedFieldId) {
96
+ for (const section of schema.sections) {
97
+ if (section.showIf) {
98
+ const cleaned = cleanCondition(section.showIf, deletedFieldId);
99
+ if (cleaned) {
100
+ section.showIf = cleaned;
101
+ } else {
102
+ delete section.showIf;
103
+ }
104
+ }
105
+ for (const question of section.questions) {
106
+ if (question.showIf) {
107
+ const cleaned = cleanCondition(question.showIf, deletedFieldId);
108
+ if (cleaned) {
109
+ question.showIf = cleaned;
110
+ } else {
111
+ delete question.showIf;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
83
117
  function addSection(schema, section, index) {
84
118
  const newSchema = deepClone(schema);
85
119
  newSchema.sections.splice(index, 0, section);
@@ -87,7 +121,12 @@ function addSection(schema, section, index) {
87
121
  }
88
122
  function removeSection(schema, sectionId) {
89
123
  const newSchema = deepClone(schema);
124
+ const removedSection = newSchema.sections.find((s) => s.id === sectionId);
125
+ const removedFieldIds = removedSection ? removedSection.questions.map((q) => q.id) : [];
90
126
  newSchema.sections = newSchema.sections.filter((s) => s.id !== sectionId);
127
+ for (const fieldId of removedFieldIds) {
128
+ cleanupOrphanConditions(newSchema, fieldId);
129
+ }
91
130
  return newSchema;
92
131
  }
93
132
  function updateSection(schema, sectionId, updates) {
@@ -137,6 +176,7 @@ function removeQuestion(schema, sectionId, questionId) {
137
176
  const section = newSchema.sections.find((s) => s.id === sectionId);
138
177
  if (!section) return schema;
139
178
  section.questions = section.questions.filter((q) => q.id !== questionId);
179
+ cleanupOrphanConditions(newSchema, questionId);
140
180
  return newSchema;
141
181
  }
142
182
  function updateQuestion(schema, sectionId, questionId, updates) {
@@ -975,11 +1015,13 @@ function QuestionPalette({ questionTypes, palette }) {
975
1015
  const isCollapsed = collapsed[category.category] && !search;
976
1016
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
977
1017
  /* @__PURE__ */ jsxRuntime.jsxs(
978
- "button",
1018
+ fieldcraftReact.Button,
979
1019
  {
980
1020
  type: "button",
1021
+ variant: "ghost",
1022
+ size: "xs",
981
1023
  onClick: () => toggleCategory(category.category),
982
- className: "flex items-center gap-1.5 w-full px-1 py-1 text-left text-[11px] font-semibold uppercase tracking-widest text-muted-foreground bg-transparent border-0 cursor-pointer",
1024
+ className: "w-full justify-start px-1 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground",
983
1025
  "aria-expanded": !isCollapsed,
984
1026
  children: [
985
1027
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1343,11 +1385,12 @@ function FormCanvas({ builderState }) {
1343
1385
  section.id
1344
1386
  )),
1345
1387
  /* @__PURE__ */ jsxRuntime.jsxs(
1346
- "button",
1388
+ fieldcraftReact.Button,
1347
1389
  {
1348
1390
  type: "button",
1391
+ variant: "outline",
1349
1392
  onClick: handleAddSection,
1350
- className: "w-full py-4 text-sm font-medium rounded-lg border border-dashed border-fcb-border-strong text-muted-foreground bg-transparent flex items-center justify-center gap-2 cursor-pointer transition-colors hover:border-primary hover:text-primary hover:bg-primary/5",
1393
+ className: "w-full py-4 h-auto text-sm font-medium border-dashed border-fcb-border-strong text-muted-foreground hover:border-primary hover:text-primary hover:bg-primary/5",
1351
1394
  children: [
1352
1395
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 14, strokeWidth: 2 }),
1353
1396
  "Add Section"
@@ -1357,6 +1400,26 @@ function FormCanvas({ builderState }) {
1357
1400
  ] })
1358
1401
  ] }) });
1359
1402
  }
1403
+ var NativeSelect = react.forwardRef(
1404
+ ({ className, wrapperClassName, children, ...props }, ref) => {
1405
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative", wrapperClassName), children: [
1406
+ /* @__PURE__ */ jsxRuntime.jsx(
1407
+ "select",
1408
+ {
1409
+ ref,
1410
+ className: cn(
1411
+ "flex h-9 w-full appearance-none rounded-md border border-input bg-card px-3 py-1 pr-8 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-foreground cursor-pointer",
1412
+ className
1413
+ ),
1414
+ ...props,
1415
+ children
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" })
1419
+ ] });
1420
+ }
1421
+ );
1422
+ NativeSelect.displayName = "NativeSelect";
1360
1423
  function useConfigUpdater(question, onUpdate) {
1361
1424
  return (field, value) => {
1362
1425
  const current = question.config ?? {};
@@ -1751,18 +1814,7 @@ function SelectField({
1751
1814
  }) {
1752
1815
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1753
1816
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground mb-1", children: label }),
1754
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
1755
- /* @__PURE__ */ jsxRuntime.jsx(
1756
- "select",
1757
- {
1758
- value,
1759
- onChange: (e) => onChange(e.target.value),
1760
- className: "flex h-9 w-full appearance-none rounded-md border border-input bg-card px-3 py-1 pr-8 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-foreground cursor-pointer",
1761
- children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
1762
- }
1763
- ),
1764
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) })
1765
- ] })
1817
+ /* @__PURE__ */ jsxRuntime.jsx(NativeSelect, { value, onChange: (e) => onChange(e.target.value), children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value)) })
1766
1818
  ] });
1767
1819
  }
1768
1820
  function LikertLabelsEditor({
@@ -1783,15 +1835,7 @@ function LikertLabelsEditor({
1783
1835
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1784
1836
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1785
1837
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground", children: "Scale Labels" }),
1786
- /* @__PURE__ */ jsxRuntime.jsx(
1787
- "button",
1788
- {
1789
- type: "button",
1790
- onClick: handleAdd,
1791
- className: "text-xs text-primary hover:underline",
1792
- children: "+ Add"
1793
- }
1794
- )
1838
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1795
1839
  ] }),
1796
1840
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: labels.map((label, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 group", children: [
1797
1841
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground w-4 text-right shrink-0", children: index + 1 }),
@@ -1804,11 +1848,13 @@ function LikertLabelsEditor({
1804
1848
  }
1805
1849
  ),
1806
1850
  labels.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
1807
- "button",
1851
+ fieldcraftReact.Button,
1808
1852
  {
1809
1853
  type: "button",
1854
+ variant: "ghost",
1855
+ size: "icon-xs",
1810
1856
  onClick: () => handleRemove(index),
1811
- className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1",
1857
+ className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
1812
1858
  children: "x"
1813
1859
  }
1814
1860
  )
@@ -1837,15 +1883,7 @@ function MatrixItemsEditor({
1837
1883
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1838
1884
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1839
1885
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground", children: label }),
1840
- /* @__PURE__ */ jsxRuntime.jsx(
1841
- "button",
1842
- {
1843
- type: "button",
1844
- onClick: handleAdd,
1845
- className: "text-xs text-primary hover:underline",
1846
- children: "+ Add"
1847
- }
1848
- )
1886
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1849
1887
  ] }),
1850
1888
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 group", children: [
1851
1889
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1857,11 +1895,13 @@ function MatrixItemsEditor({
1857
1895
  }
1858
1896
  ),
1859
1897
  items.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
1860
- "button",
1898
+ fieldcraftReact.Button,
1861
1899
  {
1862
1900
  type: "button",
1901
+ variant: "ghost",
1902
+ size: "icon-xs",
1863
1903
  onClick: () => handleRemove(index),
1864
- className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1",
1904
+ className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
1865
1905
  children: "x"
1866
1906
  }
1867
1907
  )
@@ -1894,7 +1934,7 @@ function ScoringOptionsEditor({
1894
1934
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1895
1935
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1896
1936
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground", children: "Score Options" }),
1897
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: handleAdd, className: "text-xs text-primary hover:underline", children: "+ Add" })
1937
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1898
1938
  ] }),
1899
1939
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: options.map((opt, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 group", children: [
1900
1940
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1916,7 +1956,7 @@ function ScoringOptionsEditor({
1916
1956
  placeholder: "Score"
1917
1957
  }
1918
1958
  ),
1919
- options.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => handleRemove(index), className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1", children: "x" })
1959
+ options.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "ghost", size: "icon-xs", onClick: () => handleRemove(index), className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity", children: "x" })
1920
1960
  ] }, index)) })
1921
1961
  ] });
1922
1962
  }
@@ -1938,7 +1978,7 @@ function ScoreRangesEditor({
1938
1978
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1939
1979
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1940
1980
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground", children: "Score Ranges" }),
1941
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: handleAdd, className: "text-xs text-primary hover:underline", children: "+ Add" })
1981
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1942
1982
  ] }),
1943
1983
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: ranges.map((range, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 rounded-md border border-border space-y-1.5 group", children: [
1944
1984
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -1946,7 +1986,7 @@ function ScoreRangesEditor({
1946
1986
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "to" }),
1947
1987
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Input, { type: "number", value: range.max, onChange: (e) => handleUpdate(index, { max: Number(e.target.value) }), className: "h-7 text-xs w-16", placeholder: "Max" }),
1948
1988
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Input, { value: range.label, onChange: (e) => handleUpdate(index, { label: e.target.value }), className: "h-7 text-xs flex-1", placeholder: "Label" }),
1949
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => handleRemove(index), className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1", children: "x" })
1989
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "ghost", size: "icon-xs", onClick: () => handleRemove(index), className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity", children: "x" })
1950
1990
  ] }),
1951
1991
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Input, { value: range.description ?? "", onChange: (e) => handleUpdate(index, { description: e.target.value || void 0 }), className: "h-7 text-xs", placeholder: "Description (optional)" })
1952
1992
  ] }, index)) })
@@ -1996,26 +2036,20 @@ function ValidationRulesEditor({ question, onUpdate }) {
1996
2036
  },
1997
2037
  `${rule.type}-${index}`
1998
2038
  )) }),
1999
- availableTypes.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2000
- /* @__PURE__ */ jsxRuntime.jsxs(
2001
- "select",
2002
- {
2003
- value: "",
2004
- onChange: (e) => {
2005
- if (e.target.value) addRule(e.target.value);
2006
- },
2007
- className: "flex h-8 w-full appearance-none rounded-md border border-input bg-card px-3 py-1 pr-8 text-xs shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-muted-foreground cursor-pointer",
2008
- children: [
2009
- /* @__PURE__ */ jsxRuntime.jsxs("option", { value: "", children: [
2010
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 12 }),
2011
- " Add validation rule..."
2012
- ] }),
2013
- availableTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t.value, children: t.label }, t.value))
2014
- ]
2015
- }
2016
- ),
2017
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) })
2018
- ] }) })
2039
+ availableTypes.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
2040
+ NativeSelect,
2041
+ {
2042
+ value: "",
2043
+ onChange: (e) => {
2044
+ if (e.target.value) addRule(e.target.value);
2045
+ },
2046
+ className: "h-8 text-xs text-muted-foreground",
2047
+ children: [
2048
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Add validation rule..." }),
2049
+ availableTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t.value, children: t.label }, t.value))
2050
+ ]
2051
+ }
2052
+ ) })
2019
2053
  ] });
2020
2054
  }
2021
2055
  function RuleRow({
@@ -2029,13 +2063,15 @@ function RuleRow({
2029
2063
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2030
2064
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: label }),
2031
2065
  /* @__PURE__ */ jsxRuntime.jsx(
2032
- "button",
2066
+ fieldcraftReact.Button,
2033
2067
  {
2034
2068
  type: "button",
2069
+ variant: "ghost",
2070
+ size: "icon-xs",
2035
2071
  onClick: onRemove,
2036
- className: "opacity-0 group-hover:opacity-100 transition-opacity",
2072
+ className: "opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive/10 hover:text-destructive",
2037
2073
  title: "Remove rule",
2038
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, className: "text-destructive", strokeWidth: 1.75 })
2074
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, strokeWidth: 1.75 })
2039
2075
  }
2040
2076
  )
2041
2077
  ] }),
@@ -2270,11 +2306,13 @@ function ConditionEditor({ question, schema, onUpdate }) {
2270
2306
  !hasConditions && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-3", children: "Always visible. Add a rule to show this field conditionally." }),
2271
2307
  hasConditions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 mb-3", children: showIf.conditions.map((cond, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2272
2308
  index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2273
- "button",
2309
+ fieldcraftReact.Button,
2274
2310
  {
2275
2311
  type: "button",
2312
+ variant: "link",
2313
+ size: "xs",
2276
2314
  onClick: toggleCombine,
2277
- className: "text-[10px] font-semibold uppercase tracking-wider text-primary mb-1.5 block cursor-pointer hover:underline",
2315
+ className: "text-[10px] font-semibold uppercase tracking-wider px-0 mb-1.5",
2278
2316
  children: showIf.combine ?? "AND"
2279
2317
  }
2280
2318
  ),
@@ -2282,43 +2320,39 @@ function ConditionEditor({ question, schema, onUpdate }) {
2282
2320
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2283
2321
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: "When" }),
2284
2322
  /* @__PURE__ */ jsxRuntime.jsx(
2285
- "button",
2323
+ fieldcraftReact.Button,
2286
2324
  {
2287
2325
  type: "button",
2326
+ variant: "ghost",
2327
+ size: "icon-xs",
2288
2328
  onClick: () => removeCondition(index),
2289
- className: "opacity-0 group-hover:opacity-100 transition-opacity",
2329
+ className: "opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive/10 hover:text-destructive",
2290
2330
  title: "Remove condition",
2291
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, className: "text-destructive", strokeWidth: 1.75 })
2331
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, strokeWidth: 1.75 })
2292
2332
  }
2293
2333
  )
2294
2334
  ] }),
2295
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2296
- /* @__PURE__ */ jsxRuntime.jsxs(
2297
- "select",
2298
- {
2299
- value: cond.field ?? "",
2300
- onChange: (e) => updateCondition(index, { field: e.target.value }),
2301
- className: "flex h-7 w-full appearance-none rounded-md border border-input bg-card px-2 py-1 pr-7 text-xs shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-foreground cursor-pointer",
2302
- children: [
2303
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select field..." }),
2304
- fieldOptions.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.id, children: f.label }, f.id))
2305
- ]
2306
- }
2307
- ),
2308
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 size-3 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) })
2309
- ] }),
2310
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2311
- /* @__PURE__ */ jsxRuntime.jsx(
2312
- "select",
2313
- {
2314
- value: cond.operator ?? "eq",
2315
- onChange: (e) => updateCondition(index, { operator: e.target.value }),
2316
- className: "flex h-7 w-full appearance-none rounded-md border border-input bg-card px-2 py-1 pr-7 text-xs shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-foreground cursor-pointer",
2317
- children: OPERATORS.map((op) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: op.value, children: op.label }, op.value))
2318
- }
2319
- ),
2320
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 size-3 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) })
2321
- ] }),
2335
+ /* @__PURE__ */ jsxRuntime.jsxs(
2336
+ NativeSelect,
2337
+ {
2338
+ value: cond.field ?? "",
2339
+ onChange: (e) => updateCondition(index, { field: e.target.value }),
2340
+ className: "h-7 text-xs",
2341
+ children: [
2342
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select field..." }),
2343
+ fieldOptions.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.id, children: f.label }, f.id))
2344
+ ]
2345
+ }
2346
+ ),
2347
+ /* @__PURE__ */ jsxRuntime.jsx(
2348
+ NativeSelect,
2349
+ {
2350
+ value: cond.operator ?? "eq",
2351
+ onChange: (e) => updateCondition(index, { operator: e.target.value }),
2352
+ className: "h-7 text-xs",
2353
+ children: OPERATORS.map((op) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: op.value, children: op.label }, op.value))
2354
+ }
2355
+ ),
2322
2356
  cond.operator !== "exists" && cond.operator !== "notExists" && /* @__PURE__ */ jsxRuntime.jsx(
2323
2357
  fieldcraftReact.Input,
2324
2358
  {
@@ -2331,11 +2365,13 @@ function ConditionEditor({ question, schema, onUpdate }) {
2331
2365
  ] })
2332
2366
  ] }, index)) }),
2333
2367
  fieldOptions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
2334
- "button",
2368
+ fieldcraftReact.Button,
2335
2369
  {
2336
2370
  type: "button",
2371
+ variant: "link",
2372
+ size: "xs",
2337
2373
  onClick: addCondition,
2338
- className: "flex items-center gap-1.5 text-xs text-primary hover:underline",
2374
+ className: "px-0 gap-1.5",
2339
2375
  children: [
2340
2376
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 12, strokeWidth: 2 }),
2341
2377
  "Add condition"
@@ -2497,18 +2533,7 @@ function SettingsSelect({
2497
2533
  }) {
2498
2534
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2499
2535
  /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Label, { className: "text-xs text-muted-foreground mb-1", children: label }),
2500
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
2501
- /* @__PURE__ */ jsxRuntime.jsx(
2502
- "select",
2503
- {
2504
- value,
2505
- onChange: (e) => onChange(e.target.value),
2506
- className: "flex h-9 w-full appearance-none rounded-md border border-input bg-card px-3 py-1 pr-8 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring text-foreground cursor-pointer",
2507
- children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value))
2508
- }
2509
- ),
2510
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) })
2511
- ] })
2536
+ /* @__PURE__ */ jsxRuntime.jsx(NativeSelect, { value, onChange: (e) => onChange(e.target.value), children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, opt.value)) })
2512
2537
  ] });
2513
2538
  }
2514
2539
  function PropertiesPanel({ builderState }) {
@@ -2610,13 +2635,14 @@ function QuestionProperties({ question, sectionId, builderState, onClose, onOpen
2610
2635
  typeInfo?.label
2611
2636
  ] }) }),
2612
2637
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 px-4 pt-3 flex gap-0.5", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
2613
- "button",
2638
+ fieldcraftReact.Button,
2614
2639
  {
2615
2640
  type: "button",
2641
+ variant: activeTab === tab.key ? "default" : "ghost",
2642
+ size: "xs",
2616
2643
  onClick: () => setActiveTab(tab.key),
2617
2644
  className: cn(
2618
- "px-3 py-1.5 text-xs font-medium rounded-md border-0 cursor-pointer transition-colors",
2619
- activeTab === tab.key ? "bg-primary text-primary-foreground" : "bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground"
2645
+ activeTab !== tab.key && "text-muted-foreground"
2620
2646
  ),
2621
2647
  children: tab.label
2622
2648
  },
@@ -2875,13 +2901,18 @@ function FormBuilderCore(props) {
2875
2901
  reader.onload = (event) => {
2876
2902
  try {
2877
2903
  const parsed = JSON.parse(event.target?.result);
2878
- if (parsed && parsed.sections && Array.isArray(parsed.sections)) {
2879
- builderState.resetSchema(parsed);
2904
+ const validated = fieldcraftCore.validateSchema(parsed);
2905
+ builderState.resetSchema(validated);
2906
+ } catch (err) {
2907
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
2908
+ const details = err.issues.slice(0, 3).map((i) => `\u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
2909
+ alert(`Invalid schema:
2910
+ ${details}`);
2911
+ } else if (err instanceof SyntaxError) {
2912
+ alert("Failed to parse JSON file.");
2880
2913
  } else {
2881
- alert("Invalid schema: must contain a 'sections' array.");
2914
+ alert("Invalid schema file.");
2882
2915
  }
2883
- } catch {
2884
- alert("Failed to parse JSON file.");
2885
2916
  }
2886
2917
  };
2887
2918
  reader.readAsText(file);
@@ -1 +1,2 @@
1
- export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, cleanPreset, cn, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, squaredrDarkPreset, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from '../chunk-APHGEYG4.mjs';
1
+ 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 } from '../chunk-DDQDYTMB.mjs';
2
+ export { cn } from '../chunk-QQ4JZGTD.mjs';