@squaredr/fieldcraft-pro 0.0.5 → 1.1.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.
@@ -1,15 +1,336 @@
1
1
  'use strict';
2
2
 
3
- var fieldcraftProLicense = require('@squaredr/fieldcraft-pro-license');
4
3
  var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
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
- var jsxRuntime = require('react/jsx-runtime');
11
11
 
12
- // src/form-builder/components/FormBuilderGated.tsx
12
+ // ../license/dist/index.mjs
13
+ var PRO_FEATURES = [
14
+ "SchemaEditor",
15
+ "ResponseViewer",
16
+ "ThemeEditor",
17
+ "FormBuilder",
18
+ "ApiManager",
19
+ "Analytics"
20
+ ];
21
+ var ALL_FEATURES = [
22
+ ...PRO_FEATURES,
23
+ "Telehealth"
24
+ ];
25
+ var TIER_FEATURES = {
26
+ starter: ["SchemaEditor", "ResponseViewer"],
27
+ pro: PRO_FEATURES,
28
+ business: ALL_FEATURES,
29
+ enterprise: ALL_FEATURES
30
+ };
31
+ function tierHasFeature(tier, featureName) {
32
+ return TIER_FEATURES[tier]?.includes(featureName) ?? false;
33
+ }
34
+ var DEV_HOSTNAMES = /* @__PURE__ */ new Set([
35
+ "localhost",
36
+ "127.0.0.1",
37
+ "0.0.0.0",
38
+ "[::1]",
39
+ "::1"
40
+ ]);
41
+ var DEV_HOSTNAME_PATTERNS = [
42
+ /^192\.168\.\d+\.\d+$/,
43
+ // local network
44
+ /^10\.\d+\.\d+\.\d+$/,
45
+ // local network
46
+ /^172\.(1[6-9]|2\d|3[01])\.\d+\.\d+$/
47
+ // local network
48
+ ];
49
+ var PRODUCTION_HOST_SUFFIXES = [
50
+ ".vercel.app",
51
+ ".netlify.app",
52
+ ".netlify.com",
53
+ ".herokuapp.com",
54
+ ".fly.dev",
55
+ ".railway.app",
56
+ ".render.com",
57
+ ".onrender.com",
58
+ ".pages.dev",
59
+ // Cloudflare Pages
60
+ ".workers.dev",
61
+ // Cloudflare Workers
62
+ ".azurewebsites.net",
63
+ ".web.app",
64
+ // Firebase
65
+ ".firebaseapp.com",
66
+ ".amplifyapp.com",
67
+ ".surge.sh",
68
+ ".github.io",
69
+ ".gitlab.io"
70
+ ];
71
+ function isDevHostname(hostname) {
72
+ if (DEV_HOSTNAMES.has(hostname)) return true;
73
+ for (const pattern of DEV_HOSTNAME_PATTERNS) {
74
+ if (pattern.test(hostname)) return true;
75
+ }
76
+ return false;
77
+ }
78
+ function isKnownProductionHost(hostname) {
79
+ for (const suffix of PRODUCTION_HOST_SUFFIXES) {
80
+ if (hostname.endsWith(suffix)) return true;
81
+ }
82
+ return false;
83
+ }
84
+ function hasRealTLD(hostname) {
85
+ return /\.[a-z]{2,}$/i.test(hostname) && !isDevHostname(hostname);
86
+ }
87
+ function isProductionEnvironment() {
88
+ const _proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
89
+ if (_proc?.env) {
90
+ const nodeEnv = _proc.env.NODE_ENV;
91
+ if (nodeEnv === "production") return true;
92
+ if (nodeEnv === "development" || nodeEnv === "test") return false;
93
+ }
94
+ if (typeof window !== "undefined" && window.location) {
95
+ const { hostname, protocol, port } = window.location;
96
+ if (protocol === "file:") return false;
97
+ if (isDevHostname(hostname)) return false;
98
+ if (port && port !== "443" && port !== "80") return false;
99
+ if (isKnownProductionHost(hostname)) return true;
100
+ if (protocol === "https:" && hasRealTLD(hostname)) return true;
101
+ if (hasRealTLD(hostname)) return true;
102
+ }
103
+ return false;
104
+ }
105
+ var defaultContext = { status: "validating", tier: null };
106
+ var LicenseCtx = react.createContext(defaultContext);
107
+ function useLicense() {
108
+ return react.useContext(LicenseCtx);
109
+ }
110
+ function UnlicensedOverlay({ featureName, reason, children }) {
111
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", minHeight: "200px" }, children: [
112
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { pointerEvents: "none", userSelect: "none", filter: "blur(2px)" }, children }),
113
+ /* @__PURE__ */ jsxRuntime.jsxs(
114
+ "div",
115
+ {
116
+ style: {
117
+ position: "absolute",
118
+ inset: 0,
119
+ zIndex: 2147483647,
120
+ display: "flex",
121
+ flexDirection: "column",
122
+ alignItems: "center",
123
+ justifyContent: "center",
124
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
125
+ backdropFilter: "blur(4px)",
126
+ fontFamily: "system-ui, -apple-system, sans-serif",
127
+ padding: "32px"
128
+ },
129
+ children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx(
131
+ "div",
132
+ {
133
+ style: {
134
+ position: "absolute",
135
+ inset: 0,
136
+ overflow: "hidden",
137
+ pointerEvents: "none",
138
+ zIndex: 0
139
+ },
140
+ children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
141
+ "div",
142
+ {
143
+ style: {
144
+ position: "absolute",
145
+ top: `${i * 80}px`,
146
+ left: "-50%",
147
+ right: "-50%",
148
+ textAlign: "center",
149
+ transform: "rotate(-30deg)",
150
+ fontSize: "18px",
151
+ fontWeight: 700,
152
+ color: "rgba(255, 255, 255, 0.06)",
153
+ letterSpacing: "8px",
154
+ textTransform: "uppercase",
155
+ whiteSpace: "nowrap",
156
+ userSelect: "none"
157
+ },
158
+ children: "UNLICENSED \u2022 FIELDCRAFT PRO \u2022 UNLICENSED \u2022 FIELDCRAFT PRO \u2022 UNLICENSED \u2022 FIELDCRAFT PRO"
159
+ },
160
+ i
161
+ ))
162
+ }
163
+ ),
164
+ /* @__PURE__ */ jsxRuntime.jsxs(
165
+ "div",
166
+ {
167
+ style: {
168
+ position: "relative",
169
+ zIndex: 1,
170
+ textAlign: "center",
171
+ maxWidth: "420px"
172
+ },
173
+ children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx(
175
+ "div",
176
+ {
177
+ style: {
178
+ width: "64px",
179
+ height: "64px",
180
+ margin: "0 auto 16px",
181
+ borderRadius: "50%",
182
+ backgroundColor: "rgba(239, 68, 68, 0.2)",
183
+ border: "2px solid rgba(239, 68, 68, 0.5)",
184
+ display: "flex",
185
+ alignItems: "center",
186
+ justifyContent: "center",
187
+ fontSize: "28px"
188
+ },
189
+ children: "\u{1F6E1}"
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsxRuntime.jsx(
193
+ "div",
194
+ {
195
+ style: {
196
+ fontSize: "22px",
197
+ fontWeight: 700,
198
+ color: "#ffffff",
199
+ marginBottom: "8px",
200
+ letterSpacing: "-0.02em"
201
+ },
202
+ children: "License Required"
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsxRuntime.jsx(
206
+ "div",
207
+ {
208
+ style: {
209
+ fontSize: "15px",
210
+ fontWeight: 600,
211
+ color: "#f87171",
212
+ marginBottom: "6px"
213
+ },
214
+ children: featureName
215
+ }
216
+ ),
217
+ /* @__PURE__ */ jsxRuntime.jsx(
218
+ "div",
219
+ {
220
+ style: {
221
+ fontSize: "14px",
222
+ color: "#d1d5db",
223
+ marginBottom: "24px",
224
+ lineHeight: "1.5"
225
+ },
226
+ children: reason
227
+ }
228
+ ),
229
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "10px", justifyContent: "center", flexWrap: "wrap" }, children: [
230
+ /* @__PURE__ */ jsxRuntime.jsx(
231
+ "a",
232
+ {
233
+ href: "https://squaredr.tech/products/fieldcraft/admin-pro#pricing",
234
+ target: "_blank",
235
+ rel: "noopener noreferrer",
236
+ style: {
237
+ display: "inline-block",
238
+ padding: "12px 24px",
239
+ fontSize: "14px",
240
+ fontWeight: 600,
241
+ color: "#ffffff",
242
+ backgroundColor: "#2563eb",
243
+ borderRadius: "8px",
244
+ textDecoration: "none",
245
+ boxShadow: "0 4px 14px rgba(37, 99, 235, 0.4)"
246
+ },
247
+ children: "Get a License"
248
+ }
249
+ ),
250
+ /* @__PURE__ */ jsxRuntime.jsx(
251
+ "a",
252
+ {
253
+ href: "https://discord.gg/zMxdu5UVW",
254
+ target: "_blank",
255
+ rel: "noopener noreferrer",
256
+ style: {
257
+ display: "inline-block",
258
+ padding: "12px 24px",
259
+ fontSize: "14px",
260
+ fontWeight: 600,
261
+ color: "#93c5fd",
262
+ backgroundColor: "rgba(37, 99, 235, 0.15)",
263
+ border: "1px solid rgba(37, 99, 235, 0.4)",
264
+ borderRadius: "8px",
265
+ textDecoration: "none"
266
+ },
267
+ children: "Join Discord"
268
+ }
269
+ )
270
+ ] }),
271
+ /* @__PURE__ */ jsxRuntime.jsxs(
272
+ "div",
273
+ {
274
+ style: {
275
+ fontSize: "12px",
276
+ color: "#6b7280",
277
+ marginTop: "16px"
278
+ },
279
+ children: [
280
+ "This component works freely in development.",
281
+ " ",
282
+ /* @__PURE__ */ jsxRuntime.jsx(
283
+ "a",
284
+ {
285
+ href: "https://squaredr.tech/products/fieldcraft/docs/pro/license",
286
+ target: "_blank",
287
+ rel: "noopener noreferrer",
288
+ style: { color: "#9ca3af", textDecoration: "underline" },
289
+ children: "Learn more"
290
+ }
291
+ )
292
+ ]
293
+ }
294
+ )
295
+ ]
296
+ }
297
+ )
298
+ ]
299
+ }
300
+ )
301
+ ] });
302
+ }
303
+ function requireLicense(Component2, featureName) {
304
+ function LicenseGated(props) {
305
+ const { status, tier } = useLicense();
306
+ const isProduction = isProductionEnvironment();
307
+ if (!isProduction) {
308
+ return /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props });
309
+ }
310
+ if (status === "validating") {
311
+ return null;
312
+ }
313
+ const isLicensed = status === "valid" && tier != null && tierHasFeature(tier, featureName);
314
+ if (isLicensed) {
315
+ return /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props });
316
+ }
317
+ let reason = "A valid license is required for production use.";
318
+ if (status === "invalid") {
319
+ reason = "Invalid license key. Please check your license key and try again.";
320
+ } else if (status === "expired") {
321
+ reason = "Your license has expired. Please renew to continue using this component in production.";
322
+ } else if (status === "revoked") {
323
+ reason = "This license key has been revoked. Please contact support or purchase a new license.";
324
+ } else if (status === "domain_mismatch") {
325
+ reason = "This license key is registered to a different domain. Each key can only be used on one production domain. Please purchase a new license for this domain.";
326
+ } else if (!tier || !tierHasFeature(tier, featureName)) {
327
+ reason = "Your license does not include this feature. Please upgrade your plan.";
328
+ }
329
+ return /* @__PURE__ */ jsxRuntime.jsx(UnlicensedOverlay, { featureName, reason, children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props }) });
330
+ }
331
+ LicenseGated.displayName = `requireLicense(${Component2.displayName || Component2.name || "Component"})`;
332
+ return LicenseGated;
333
+ }
13
334
  var MAX_HISTORY = 50;
14
335
  function useUndoRedo(currentSchema, setSchema) {
15
336
  const historyRef = react.useRef([currentSchema]);
@@ -80,6 +401,39 @@ function generateOptionId() {
80
401
  function deepClone(obj) {
81
402
  return structuredClone(obj);
82
403
  }
404
+ function cleanCondition(expr, deletedFieldId) {
405
+ if (expr.field === deletedFieldId) return void 0;
406
+ if (expr.field && expr.field !== deletedFieldId) return expr;
407
+ if (expr.conditions) {
408
+ const cleaned = expr.conditions.map((c) => cleanCondition(c, deletedFieldId)).filter((c) => c !== void 0);
409
+ if (cleaned.length === 0) return void 0;
410
+ if (cleaned.length === 1) return cleaned[0];
411
+ return { ...expr, conditions: cleaned };
412
+ }
413
+ return expr;
414
+ }
415
+ function cleanupOrphanConditions(schema, deletedFieldId) {
416
+ for (const section of schema.sections) {
417
+ if (section.showIf) {
418
+ const cleaned = cleanCondition(section.showIf, deletedFieldId);
419
+ if (cleaned) {
420
+ section.showIf = cleaned;
421
+ } else {
422
+ delete section.showIf;
423
+ }
424
+ }
425
+ for (const question of section.questions) {
426
+ if (question.showIf) {
427
+ const cleaned = cleanCondition(question.showIf, deletedFieldId);
428
+ if (cleaned) {
429
+ question.showIf = cleaned;
430
+ } else {
431
+ delete question.showIf;
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
83
437
  function addSection(schema, section, index) {
84
438
  const newSchema = deepClone(schema);
85
439
  newSchema.sections.splice(index, 0, section);
@@ -87,7 +441,12 @@ function addSection(schema, section, index) {
87
441
  }
88
442
  function removeSection(schema, sectionId) {
89
443
  const newSchema = deepClone(schema);
444
+ const removedSection = newSchema.sections.find((s) => s.id === sectionId);
445
+ const removedFieldIds = removedSection ? removedSection.questions.map((q) => q.id) : [];
90
446
  newSchema.sections = newSchema.sections.filter((s) => s.id !== sectionId);
447
+ for (const fieldId of removedFieldIds) {
448
+ cleanupOrphanConditions(newSchema, fieldId);
449
+ }
91
450
  return newSchema;
92
451
  }
93
452
  function updateSection(schema, sectionId, updates) {
@@ -137,6 +496,7 @@ function removeQuestion(schema, sectionId, questionId) {
137
496
  const section = newSchema.sections.find((s) => s.id === sectionId);
138
497
  if (!section) return schema;
139
498
  section.questions = section.questions.filter((q) => q.id !== questionId);
499
+ cleanupOrphanConditions(newSchema, questionId);
140
500
  return newSchema;
141
501
  }
142
502
  function updateQuestion(schema, sectionId, questionId, updates) {
@@ -975,11 +1335,13 @@ function QuestionPalette({ questionTypes, palette }) {
975
1335
  const isCollapsed = collapsed[category.category] && !search;
976
1336
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3", children: [
977
1337
  /* @__PURE__ */ jsxRuntime.jsxs(
978
- "button",
1338
+ fieldcraftReact.Button,
979
1339
  {
980
1340
  type: "button",
1341
+ variant: "ghost",
1342
+ size: "xs",
981
1343
  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",
1344
+ className: "w-full justify-start px-1 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground",
983
1345
  "aria-expanded": !isCollapsed,
984
1346
  children: [
985
1347
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1343,11 +1705,12 @@ function FormCanvas({ builderState }) {
1343
1705
  section.id
1344
1706
  )),
1345
1707
  /* @__PURE__ */ jsxRuntime.jsxs(
1346
- "button",
1708
+ fieldcraftReact.Button,
1347
1709
  {
1348
1710
  type: "button",
1711
+ variant: "outline",
1349
1712
  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",
1713
+ 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
1714
  children: [
1352
1715
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 14, strokeWidth: 2 }),
1353
1716
  "Add Section"
@@ -1357,6 +1720,26 @@ function FormCanvas({ builderState }) {
1357
1720
  ] })
1358
1721
  ] }) });
1359
1722
  }
1723
+ var NativeSelect = react.forwardRef(
1724
+ ({ className, wrapperClassName, children, ...props }, ref) => {
1725
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative", wrapperClassName), children: [
1726
+ /* @__PURE__ */ jsxRuntime.jsx(
1727
+ "select",
1728
+ {
1729
+ ref,
1730
+ className: cn(
1731
+ "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",
1732
+ className
1733
+ ),
1734
+ ...props,
1735
+ children
1736
+ }
1737
+ ),
1738
+ /* @__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" })
1739
+ ] });
1740
+ }
1741
+ );
1742
+ NativeSelect.displayName = "NativeSelect";
1360
1743
  function useConfigUpdater(question, onUpdate) {
1361
1744
  return (field, value) => {
1362
1745
  const current = question.config ?? {};
@@ -1751,18 +2134,7 @@ function SelectField({
1751
2134
  }) {
1752
2135
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1753
2136
  /* @__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
- ] })
2137
+ /* @__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
2138
  ] });
1767
2139
  }
1768
2140
  function LikertLabelsEditor({
@@ -1783,15 +2155,7 @@ function LikertLabelsEditor({
1783
2155
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1784
2156
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1785
2157
  /* @__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
- )
2158
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1795
2159
  ] }),
1796
2160
  /* @__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
2161
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground w-4 text-right shrink-0", children: index + 1 }),
@@ -1804,11 +2168,13 @@ function LikertLabelsEditor({
1804
2168
  }
1805
2169
  ),
1806
2170
  labels.length > 2 && /* @__PURE__ */ jsxRuntime.jsx(
1807
- "button",
2171
+ fieldcraftReact.Button,
1808
2172
  {
1809
2173
  type: "button",
2174
+ variant: "ghost",
2175
+ size: "icon-xs",
1810
2176
  onClick: () => handleRemove(index),
1811
- className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1",
2177
+ className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
1812
2178
  children: "x"
1813
2179
  }
1814
2180
  )
@@ -1837,15 +2203,7 @@ function MatrixItemsEditor({
1837
2203
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1838
2204
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1839
2205
  /* @__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
- )
2206
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1849
2207
  ] }),
1850
2208
  /* @__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
2209
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1857,11 +2215,13 @@ function MatrixItemsEditor({
1857
2215
  }
1858
2216
  ),
1859
2217
  items.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(
1860
- "button",
2218
+ fieldcraftReact.Button,
1861
2219
  {
1862
2220
  type: "button",
2221
+ variant: "ghost",
2222
+ size: "icon-xs",
1863
2223
  onClick: () => handleRemove(index),
1864
- className: "text-xs text-destructive opacity-0 group-hover:opacity-100 transition-opacity px-1",
2224
+ className: "text-destructive opacity-0 group-hover:opacity-100 transition-opacity",
1865
2225
  children: "x"
1866
2226
  }
1867
2227
  )
@@ -1894,7 +2254,7 @@ function ScoringOptionsEditor({
1894
2254
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1895
2255
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1896
2256
  /* @__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" })
2257
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1898
2258
  ] }),
1899
2259
  /* @__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
2260
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1916,7 +2276,7 @@ function ScoringOptionsEditor({
1916
2276
  placeholder: "Score"
1917
2277
  }
1918
2278
  ),
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" })
2279
+ 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
2280
  ] }, index)) })
1921
2281
  ] });
1922
2282
  }
@@ -1938,7 +2298,7 @@ function ScoreRangesEditor({
1938
2298
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1939
2299
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1940
2300
  /* @__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" })
2301
+ /* @__PURE__ */ jsxRuntime.jsx(fieldcraftReact.Button, { type: "button", variant: "link", size: "xs", onClick: handleAdd, className: "px-0", children: "+ Add" })
1942
2302
  ] }),
1943
2303
  /* @__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
2304
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
@@ -1946,7 +2306,7 @@ function ScoreRangesEditor({
1946
2306
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "to" }),
1947
2307
  /* @__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
2308
  /* @__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" })
2309
+ /* @__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
2310
  ] }),
1951
2311
  /* @__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
2312
  ] }, index)) })
@@ -1996,26 +2356,20 @@ function ValidationRulesEditor({ question, onUpdate }) {
1996
2356
  },
1997
2357
  `${rule.type}-${index}`
1998
2358
  )) }),
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
- ] }) })
2359
+ availableTypes.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
2360
+ NativeSelect,
2361
+ {
2362
+ value: "",
2363
+ onChange: (e) => {
2364
+ if (e.target.value) addRule(e.target.value);
2365
+ },
2366
+ className: "h-8 text-xs text-muted-foreground",
2367
+ children: [
2368
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Add validation rule..." }),
2369
+ availableTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t.value, children: t.label }, t.value))
2370
+ ]
2371
+ }
2372
+ ) })
2019
2373
  ] });
2020
2374
  }
2021
2375
  function RuleRow({
@@ -2029,13 +2383,15 @@ function RuleRow({
2029
2383
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2030
2384
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: label }),
2031
2385
  /* @__PURE__ */ jsxRuntime.jsx(
2032
- "button",
2386
+ fieldcraftReact.Button,
2033
2387
  {
2034
2388
  type: "button",
2389
+ variant: "ghost",
2390
+ size: "icon-xs",
2035
2391
  onClick: onRemove,
2036
- className: "opacity-0 group-hover:opacity-100 transition-opacity",
2392
+ className: "opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive/10 hover:text-destructive",
2037
2393
  title: "Remove rule",
2038
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, className: "text-destructive", strokeWidth: 1.75 })
2394
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, strokeWidth: 1.75 })
2039
2395
  }
2040
2396
  )
2041
2397
  ] }),
@@ -2270,11 +2626,13 @@ function ConditionEditor({ question, schema, onUpdate }) {
2270
2626
  !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
2627
  hasConditions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 mb-3", children: showIf.conditions.map((cond, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2272
2628
  index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2273
- "button",
2629
+ fieldcraftReact.Button,
2274
2630
  {
2275
2631
  type: "button",
2632
+ variant: "link",
2633
+ size: "xs",
2276
2634
  onClick: toggleCombine,
2277
- className: "text-[10px] font-semibold uppercase tracking-wider text-primary mb-1.5 block cursor-pointer hover:underline",
2635
+ className: "text-[10px] font-semibold uppercase tracking-wider px-0 mb-1.5",
2278
2636
  children: showIf.combine ?? "AND"
2279
2637
  }
2280
2638
  ),
@@ -2282,43 +2640,39 @@ function ConditionEditor({ question, schema, onUpdate }) {
2282
2640
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2283
2641
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wider", children: "When" }),
2284
2642
  /* @__PURE__ */ jsxRuntime.jsx(
2285
- "button",
2643
+ fieldcraftReact.Button,
2286
2644
  {
2287
2645
  type: "button",
2646
+ variant: "ghost",
2647
+ size: "icon-xs",
2288
2648
  onClick: () => removeCondition(index),
2289
- className: "opacity-0 group-hover:opacity-100 transition-opacity",
2649
+ className: "opacity-0 group-hover:opacity-100 transition-opacity hover:bg-destructive/10 hover:text-destructive",
2290
2650
  title: "Remove condition",
2291
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, className: "text-destructive", strokeWidth: 1.75 })
2651
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { size: 12, strokeWidth: 1.75 })
2292
2652
  }
2293
2653
  )
2294
2654
  ] }),
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
- ] }),
2655
+ /* @__PURE__ */ jsxRuntime.jsxs(
2656
+ NativeSelect,
2657
+ {
2658
+ value: cond.field ?? "",
2659
+ onChange: (e) => updateCondition(index, { field: e.target.value }),
2660
+ className: "h-7 text-xs",
2661
+ children: [
2662
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select field..." }),
2663
+ fieldOptions.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f.id, children: f.label }, f.id))
2664
+ ]
2665
+ }
2666
+ ),
2667
+ /* @__PURE__ */ jsxRuntime.jsx(
2668
+ NativeSelect,
2669
+ {
2670
+ value: cond.operator ?? "eq",
2671
+ onChange: (e) => updateCondition(index, { operator: e.target.value }),
2672
+ className: "h-7 text-xs",
2673
+ children: OPERATORS.map((op) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: op.value, children: op.label }, op.value))
2674
+ }
2675
+ ),
2322
2676
  cond.operator !== "exists" && cond.operator !== "notExists" && /* @__PURE__ */ jsxRuntime.jsx(
2323
2677
  fieldcraftReact.Input,
2324
2678
  {
@@ -2331,11 +2685,13 @@ function ConditionEditor({ question, schema, onUpdate }) {
2331
2685
  ] })
2332
2686
  ] }, index)) }),
2333
2687
  fieldOptions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
2334
- "button",
2688
+ fieldcraftReact.Button,
2335
2689
  {
2336
2690
  type: "button",
2691
+ variant: "link",
2692
+ size: "xs",
2337
2693
  onClick: addCondition,
2338
- className: "flex items-center gap-1.5 text-xs text-primary hover:underline",
2694
+ className: "px-0 gap-1.5",
2339
2695
  children: [
2340
2696
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 12, strokeWidth: 2 }),
2341
2697
  "Add condition"
@@ -2497,18 +2853,7 @@ function SettingsSelect({
2497
2853
  }) {
2498
2854
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2499
2855
  /* @__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
- ] })
2856
+ /* @__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
2857
  ] });
2513
2858
  }
2514
2859
  function PropertiesPanel({ builderState }) {
@@ -2610,13 +2955,14 @@ function QuestionProperties({ question, sectionId, builderState, onClose, onOpen
2610
2955
  typeInfo?.label
2611
2956
  ] }) }),
2612
2957
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 px-4 pt-3 flex gap-0.5", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
2613
- "button",
2958
+ fieldcraftReact.Button,
2614
2959
  {
2615
2960
  type: "button",
2961
+ variant: activeTab === tab.key ? "default" : "ghost",
2962
+ size: "xs",
2616
2963
  onClick: () => setActiveTab(tab.key),
2617
2964
  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"
2965
+ activeTab !== tab.key && "text-muted-foreground"
2620
2966
  ),
2621
2967
  children: tab.label
2622
2968
  },
@@ -2875,13 +3221,18 @@ function FormBuilderCore(props) {
2875
3221
  reader.onload = (event) => {
2876
3222
  try {
2877
3223
  const parsed = JSON.parse(event.target?.result);
2878
- if (parsed && parsed.sections && Array.isArray(parsed.sections)) {
2879
- builderState.resetSchema(parsed);
3224
+ const validated = fieldcraftCore.validateSchema(parsed);
3225
+ builderState.resetSchema(validated);
3226
+ } catch (err) {
3227
+ if (err instanceof fieldcraftCore.FormEngineSchemaError) {
3228
+ const details = err.issues.slice(0, 3).map((i) => `\u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
3229
+ alert(`Invalid schema:
3230
+ ${details}`);
3231
+ } else if (err instanceof SyntaxError) {
3232
+ alert("Failed to parse JSON file.");
2880
3233
  } else {
2881
- alert("Invalid schema: must contain a 'sections' array.");
3234
+ alert("Invalid schema file.");
2882
3235
  }
2883
- } catch {
2884
- alert("Failed to parse JSON file.");
2885
3236
  }
2886
3237
  };
2887
3238
  reader.readAsText(file);
@@ -3091,7 +3442,7 @@ function FormBuilderInner(props) {
3091
3442
  }
3092
3443
 
3093
3444
  // src/form-builder/components/FormBuilderGated.tsx
3094
- var FormBuilder = fieldcraftProLicense.requireLicense(FormBuilderInner, "FormBuilder");
3445
+ var FormBuilder = requireLicense(FormBuilderInner, "FormBuilder");
3095
3446
 
3096
3447
  // src/form-builder/theme/presets.ts
3097
3448
  var squaredrDarkPreset = {