@squaredr/fieldcraft-pro 1.6.4 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/{chunk-VECQKSWS.mjs → chunk-4MMKB2EW.mjs} +60 -86
- package/dist/{chunk-CFVT2R42.mjs → chunk-4TN6YJDF.mjs} +1 -1
- package/dist/{chunk-5LKGCZAW.mjs → chunk-GZIV2XAD.mjs} +1 -1
- package/dist/{chunk-ANBMT5AW.mjs → chunk-J5V6OVZO.mjs} +127 -55
- package/dist/chunk-VEP3DOCQ.mjs +1111 -0
- package/dist/form-builder/index.d.mts +176 -6
- package/dist/form-builder/index.d.ts +176 -6
- package/dist/form-builder/index.js +128 -79
- package/dist/form-builder/index.mjs +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1310 -147
- package/dist/index.mjs +14 -12
- package/dist/response-viewer/index.js +4 -27
- package/dist/response-viewer/index.mjs +2 -2
- package/dist/styles.css +1 -1
- package/dist/templates/index.d.mts +43 -0
- package/dist/templates/index.d.ts +43 -0
- package/dist/templates/index.js +1119 -0
- package/dist/templates/index.mjs +1 -0
- package/dist/theme-editor/index.js +4 -27
- package/dist/theme-editor/index.mjs +2 -2
- package/dist/types-bFHsZT1n.d.mts +217 -0
- package/dist/types-bFHsZT1n.d.ts +217 -0
- package/package.json +8 -7
- package/dist/index-D19524df.d.mts +0 -387
- package/dist/index-D19524df.d.ts +0 -387
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @squaredr/fieldcraft-pro
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@squaredr/fieldcraft-pro)
|
|
4
|
-
[](https://squaredr.tech/
|
|
4
|
+
[](https://fieldcraft.squaredr.tech/pro#pricing)
|
|
5
5
|
|
|
6
|
-
Pro components for [FieldCraft](https://squaredr.tech
|
|
6
|
+
Pro components for [FieldCraft](https://fieldcraft.squaredr.tech) — a schema-driven form engine for React. Build forms visually, view responses, and customize themes without writing code.
|
|
7
7
|
|
|
8
8
|
## What's Included
|
|
9
9
|
|
|
10
10
|
| Component | Description |
|
|
11
11
|
|-----------|-------------|
|
|
12
|
-
| **FormBuilder** | Drag-and-drop visual form designer with
|
|
12
|
+
| **FormBuilder** | Drag-and-drop visual form designer with 44 field types, undo/redo, import/export, and live preview |
|
|
13
13
|
| **ResponseViewer** | Read-only viewer for submitted form data with table and detail views, CSV/JSON export |
|
|
14
14
|
| **ThemeEditor** | Visual theme editor for form styling with color pickers, typography controls, and live preview |
|
|
15
15
|
|
|
@@ -67,18 +67,18 @@ import { FormBuilder } from "@squaredr/fieldcraft-pro";
|
|
|
67
67
|
/>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
### Field Types (
|
|
70
|
+
### Field Types (42)
|
|
71
71
|
|
|
72
72
|
| Category | Fields |
|
|
73
73
|
|----------|--------|
|
|
74
|
-
| Text | Short Text, Long Text, Email, Phone, URL |
|
|
75
|
-
| Numeric | Number, Slider, Rating, NPS, Opinion Scale |
|
|
76
|
-
| Selection | Single Select, Multi Select, Dropdown, Yes/No, Ranking |
|
|
77
|
-
| Date/Time | Date, Time |
|
|
78
|
-
| Media | File Upload |
|
|
74
|
+
| Text | Short Text, Long Text, Email, Phone, URL, Password |
|
|
75
|
+
| Numeric | Number, Slider, Rating, NPS, Opinion Scale, Currency |
|
|
76
|
+
| Selection | Single Select, Multi Select, Dropdown, Yes/No, Ranking, Checkbox Group |
|
|
77
|
+
| Date/Time | Date, Time, Date Range, Date of Birth |
|
|
78
|
+
| Media | File Upload, Signature, Image Capture |
|
|
79
79
|
| Content | Welcome Screen, Thank You Screen, Rich Text, Image, Video, Divider, Spacer |
|
|
80
80
|
| Structural | Section Header, Info Block, Page Break, Consent |
|
|
81
|
-
| Advanced | Matrix, Calculated, Hidden |
|
|
81
|
+
| Advanced | Matrix, Calculated, Hidden, Repeater, Address, Legal Name, Country Select |
|
|
82
82
|
|
|
83
83
|
### Extending with Custom Fields
|
|
84
84
|
|
|
@@ -125,8 +125,8 @@ Includes presets: Clean, Dark, Modern, High Contrast, Clinical, Playful.
|
|
|
125
125
|
## Requirements
|
|
126
126
|
|
|
127
127
|
- React 18 or 19
|
|
128
|
-
- `@squaredr/fieldcraft-core` >= 1.
|
|
129
|
-
- `@squaredr/fieldcraft-react` >= 1.
|
|
128
|
+
- `@squaredr/fieldcraft-core` >= 1.6.0
|
|
129
|
+
- `@squaredr/fieldcraft-react` >= 1.6.0
|
|
130
130
|
|
|
131
131
|
## License
|
|
132
132
|
|
|
@@ -136,4 +136,4 @@ See [LICENSE.txt](./LICENSE.txt) for details.
|
|
|
136
136
|
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
-
[Get a license](https://squaredr.tech/
|
|
139
|
+
[Get a license](https://fieldcraft.squaredr.tech/pro#pricing) | [Documentation](https://fieldcraft.squaredr.tech/docs) | [GitHub](https://github.com/SquaredR98/fieldcraft-pro) | [Discord](https://discord.gg/squaredr)
|
|
@@ -2,37 +2,16 @@ import { createContext, useState, useEffect, useContext } from 'react';
|
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// ../license/dist/index.mjs
|
|
5
|
+
var PRODUCT_IDS = {
|
|
6
|
+
FIELDCRAFT_PRO: 1
|
|
7
|
+
};
|
|
5
8
|
var LEGACY_TIER_MAP = {
|
|
6
9
|
"01": "starter",
|
|
7
10
|
"02": "pro",
|
|
8
11
|
"03": "business",
|
|
9
12
|
"04": "enterprise"
|
|
10
13
|
};
|
|
11
|
-
var
|
|
12
|
-
1: "starter",
|
|
13
|
-
2: "pro",
|
|
14
|
-
3: "business",
|
|
15
|
-
4: "enterprise"
|
|
16
|
-
};
|
|
17
|
-
var PRO_FEATURES = [
|
|
18
|
-
"SchemaEditor",
|
|
19
|
-
"ResponseViewer",
|
|
20
|
-
"ThemeEditor",
|
|
21
|
-
"FormBuilder",
|
|
22
|
-
"ApiManager",
|
|
23
|
-
"Analytics"
|
|
24
|
-
];
|
|
25
|
-
var ALL_FEATURES = [
|
|
26
|
-
...PRO_FEATURES,
|
|
27
|
-
"Telehealth"
|
|
28
|
-
];
|
|
29
|
-
var TIER_FEATURES = {
|
|
30
|
-
starter: ["SchemaEditor", "ResponseViewer"],
|
|
31
|
-
pro: PRO_FEATURES,
|
|
32
|
-
business: ALL_FEATURES,
|
|
33
|
-
enterprise: ALL_FEATURES
|
|
34
|
-
};
|
|
35
|
-
var HMAC_SECRET = "e50a657813a5ab9563050728a605b9ab58b911d5c1a9f48c6825f379a533ccd7685d11d01937d9f135776a0db3efeefe29b3e5282b27ede8cee93758f94f2d75";
|
|
14
|
+
var HMAC_SECRET = "15bb3af8d82d01de70ab556c5785fd92461e336b941ed65cf99834d5e99924e742db9a9197056b531bf0f429df621be17cb71d8ba5e892075e1b4283969657dc3bbf4d0287cf15804d5482510b8a405bb33a69249e8007a8a3c2af0d499423623dafecb719d985a4ad8e2edf8623936db90387ea3139f8d0eeb2556e408a0d6de69c034e33e1e6a0633af0eeeb63dd13f4711ba0a0aacc3af6b87bdace13a7886013d3a5410817966f0af7230f277b6207ad5de2916f6c0bfab5c2a18ccfe72d513bb726206d8929e8b01d91f6bf38572c0f238302c4fdf0263c7bd0c48cf0e1678f94de727b61388c672c11e82ecb96591d264731177d7588d92cdf70ea46a2";
|
|
36
15
|
var B32_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
|
|
37
16
|
function base32hexDecode(str) {
|
|
38
17
|
const upper = str.toUpperCase();
|
|
@@ -66,14 +45,15 @@ function parseOpaqueKey(key) {
|
|
|
66
45
|
if (!stripped.startsWith("FCPRO")) return null;
|
|
67
46
|
const encoded = stripped.slice(5);
|
|
68
47
|
const decoded = base32hexDecode(encoded);
|
|
69
|
-
if (!decoded || decoded.length <
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
48
|
+
if (!decoded || decoded.length < 25) return null;
|
|
49
|
+
const productId = decoded[0];
|
|
50
|
+
if (productId !== PRODUCT_IDS.FIELDCRAFT_PRO) return null;
|
|
51
|
+
const purchaseTimestamp = (decoded[1] << 24 | decoded[2] << 16 | decoded[3] << 8 | decoded[4]) >>> 0;
|
|
52
|
+
const expiryTimestamp = (decoded[5] << 24 | decoded[6] << 16 | decoded[7] << 8 | decoded[8]) >>> 0;
|
|
53
|
+
const emailHash = decoded.slice(9, 13);
|
|
54
|
+
const nonce = decoded.slice(13, 17);
|
|
55
|
+
const signature = decoded.slice(17, 25);
|
|
56
|
+
return { format: "opaque", productId, purchaseTimestamp, expiryTimestamp, emailHash, nonce, signature };
|
|
77
57
|
}
|
|
78
58
|
function parseKey(key) {
|
|
79
59
|
if (!key || typeof key !== "string") return null;
|
|
@@ -119,54 +99,52 @@ function isExpiredOpaque(expiryTimestamp) {
|
|
|
119
99
|
if (expiryTimestamp === 4294967295) return false;
|
|
120
100
|
return Date.now() / 1e3 > expiryTimestamp;
|
|
121
101
|
}
|
|
102
|
+
function timingSafeEqual(a, b) {
|
|
103
|
+
if (a.length !== b.length) return false;
|
|
104
|
+
let result = 0;
|
|
105
|
+
for (let i = 0; i < a.length; i++) {
|
|
106
|
+
result |= a[i] ^ b[i];
|
|
107
|
+
}
|
|
108
|
+
return result === 0;
|
|
109
|
+
}
|
|
122
110
|
async function validateLicense(key) {
|
|
123
111
|
if (!key || typeof key !== "string") {
|
|
124
|
-
return { status: "invalid"
|
|
112
|
+
return { status: "invalid" };
|
|
125
113
|
}
|
|
126
114
|
const parsed = parseKey(key);
|
|
127
115
|
if (!parsed) {
|
|
128
|
-
return { status: "invalid"
|
|
116
|
+
return { status: "invalid" };
|
|
129
117
|
}
|
|
130
118
|
if (parsed.format === "opaque") {
|
|
131
|
-
const payload = new Uint8Array(
|
|
132
|
-
payload[0] = parsed.
|
|
133
|
-
payload[1] = parsed.
|
|
134
|
-
payload[2] = parsed.
|
|
135
|
-
payload[3] = parsed.
|
|
136
|
-
payload[4] = parsed.
|
|
137
|
-
payload
|
|
119
|
+
const payload = new Uint8Array(17);
|
|
120
|
+
payload[0] = parsed.productId;
|
|
121
|
+
payload[1] = parsed.purchaseTimestamp >>> 24 & 255;
|
|
122
|
+
payload[2] = parsed.purchaseTimestamp >>> 16 & 255;
|
|
123
|
+
payload[3] = parsed.purchaseTimestamp >>> 8 & 255;
|
|
124
|
+
payload[4] = parsed.purchaseTimestamp & 255;
|
|
125
|
+
payload[5] = parsed.expiryTimestamp >>> 24 & 255;
|
|
126
|
+
payload[6] = parsed.expiryTimestamp >>> 16 & 255;
|
|
127
|
+
payload[7] = parsed.expiryTimestamp >>> 8 & 255;
|
|
128
|
+
payload[8] = parsed.expiryTimestamp & 255;
|
|
129
|
+
payload.set(parsed.emailHash, 9);
|
|
130
|
+
payload.set(parsed.nonce, 13);
|
|
138
131
|
const expectedSig = await computeHmacOpaque(payload);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (parsed.signature[i] !== expectedSig[i]) {
|
|
142
|
-
match = false;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (!match) {
|
|
147
|
-
return { status: "invalid", tier: null };
|
|
148
|
-
}
|
|
149
|
-
const tier2 = TIER_MAP_NUMERIC[parsed.tier];
|
|
150
|
-
if (!tier2) {
|
|
151
|
-
return { status: "invalid", tier: null };
|
|
132
|
+
if (!timingSafeEqual(parsed.signature, expectedSig)) {
|
|
133
|
+
return { status: "invalid" };
|
|
152
134
|
}
|
|
153
135
|
if (isExpiredOpaque(parsed.expiryTimestamp)) {
|
|
154
|
-
return { status: "expired"
|
|
136
|
+
return { status: "expired" };
|
|
155
137
|
}
|
|
156
|
-
return { status: "valid"
|
|
138
|
+
return { status: "valid" };
|
|
157
139
|
}
|
|
158
140
|
const expectedHash = await computeHmacLegacy(parsed.tierHex, parsed.expiryHex);
|
|
159
141
|
if (parsed.hash.toUpperCase() !== expectedHash) {
|
|
160
|
-
return { status: "invalid"
|
|
142
|
+
return { status: "invalid" };
|
|
161
143
|
}
|
|
162
|
-
const tier = LEGACY_TIER_MAP[parsed.tierHex];
|
|
163
144
|
if (isExpiredLegacy(parsed.expiryHex)) {
|
|
164
|
-
return { status: "expired"
|
|
145
|
+
return { status: "expired" };
|
|
165
146
|
}
|
|
166
|
-
return { status: "valid"
|
|
167
|
-
}
|
|
168
|
-
function tierHasFeature(tier, featureName) {
|
|
169
|
-
return TIER_FEATURES[tier]?.includes(featureName) ?? false;
|
|
147
|
+
return { status: "valid" };
|
|
170
148
|
}
|
|
171
149
|
var DEV_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
172
150
|
"localhost",
|
|
@@ -239,7 +217,7 @@ function isProductionEnvironment() {
|
|
|
239
217
|
}
|
|
240
218
|
return false;
|
|
241
219
|
}
|
|
242
|
-
var PING_URL = "https://
|
|
220
|
+
var PING_URL = typeof process !== "undefined" && process.env?.NEXT_PUBLIC_FCPRO_PING_URL || "https://fieldcraft.dev/api/license/ping";
|
|
243
221
|
var CACHE_KEY = "fcpro_lp";
|
|
244
222
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
245
223
|
function hashKey(key) {
|
|
@@ -267,10 +245,10 @@ function readCache(key) {
|
|
|
267
245
|
return null;
|
|
268
246
|
}
|
|
269
247
|
}
|
|
270
|
-
function writeCache(key, status,
|
|
248
|
+
function writeCache(key, status, reason = null) {
|
|
271
249
|
if (typeof window === "undefined" || !window.localStorage) return;
|
|
272
250
|
try {
|
|
273
|
-
const entry = { s: status,
|
|
251
|
+
const entry = { s: status, r: reason, ts: Date.now() };
|
|
274
252
|
localStorage.setItem(cacheId(key), JSON.stringify(entry));
|
|
275
253
|
} catch {
|
|
276
254
|
}
|
|
@@ -278,12 +256,12 @@ function writeCache(key, status, tier, reason = null) {
|
|
|
278
256
|
function getCachedPingResult(key) {
|
|
279
257
|
const cached = readCache(key);
|
|
280
258
|
if (!cached) return null;
|
|
281
|
-
return { status: cached.s,
|
|
259
|
+
return { status: cached.s, reason: cached.r || void 0 };
|
|
282
260
|
}
|
|
283
261
|
async function performPing(key) {
|
|
284
262
|
const cached = readCache(key);
|
|
285
263
|
if (cached) {
|
|
286
|
-
return { status: cached.s
|
|
264
|
+
return { status: cached.s };
|
|
287
265
|
}
|
|
288
266
|
try {
|
|
289
267
|
const controller = new AbortController();
|
|
@@ -297,29 +275,28 @@ async function performPing(key) {
|
|
|
297
275
|
clearTimeout(timeout);
|
|
298
276
|
if (!res.ok) return null;
|
|
299
277
|
const data = await res.json();
|
|
300
|
-
const tier = data.tier || null;
|
|
301
278
|
if (data.status === "active") {
|
|
302
|
-
writeCache(key, "valid"
|
|
303
|
-
return { status: "valid"
|
|
279
|
+
writeCache(key, "valid");
|
|
280
|
+
return { status: "valid" };
|
|
304
281
|
}
|
|
305
282
|
if (data.status === "revoked") {
|
|
306
|
-
writeCache(key, "revoked",
|
|
307
|
-
return { status: "revoked",
|
|
283
|
+
writeCache(key, "revoked", data.reason);
|
|
284
|
+
return { status: "revoked", reason: data.reason };
|
|
308
285
|
}
|
|
309
286
|
if (data.status === "expired") {
|
|
310
|
-
writeCache(key, "expired"
|
|
311
|
-
return { status: "expired"
|
|
287
|
+
writeCache(key, "expired");
|
|
288
|
+
return { status: "expired" };
|
|
312
289
|
}
|
|
313
290
|
if (data.status === "domain_mismatch") {
|
|
314
|
-
writeCache(key, "domain_mismatch",
|
|
315
|
-
return { status: "domain_mismatch",
|
|
291
|
+
writeCache(key, "domain_mismatch", data.reason);
|
|
292
|
+
return { status: "domain_mismatch", reason: data.reason };
|
|
316
293
|
}
|
|
317
294
|
return null;
|
|
318
295
|
} catch {
|
|
319
296
|
return null;
|
|
320
297
|
}
|
|
321
298
|
}
|
|
322
|
-
var defaultContext = { status: "validating"
|
|
299
|
+
var defaultContext = { status: "validating" };
|
|
323
300
|
var LicenseCtx = createContext(defaultContext);
|
|
324
301
|
function FieldCraftProProvider({ licenseKey, children }) {
|
|
325
302
|
const [license, setLicense] = useState(defaultContext);
|
|
@@ -333,7 +310,7 @@ function FieldCraftProProvider({ licenseKey, children }) {
|
|
|
333
310
|
const cached = getCachedPingResult(licenseKey);
|
|
334
311
|
if (cached) {
|
|
335
312
|
if (cached.status === "revoked" || cached.status === "domain_mismatch") {
|
|
336
|
-
setLicense({ status: cached.status
|
|
313
|
+
setLicense({ status: cached.status });
|
|
337
314
|
}
|
|
338
315
|
return;
|
|
339
316
|
}
|
|
@@ -341,7 +318,7 @@ function FieldCraftProProvider({ licenseKey, children }) {
|
|
|
341
318
|
if (cancelled) return;
|
|
342
319
|
if (!pingResult) return;
|
|
343
320
|
if (pingResult.status === "revoked" || pingResult.status === "domain_mismatch") {
|
|
344
|
-
setLicense({ status: pingResult.status
|
|
321
|
+
setLicense({ status: pingResult.status });
|
|
345
322
|
}
|
|
346
323
|
});
|
|
347
324
|
});
|
|
@@ -549,7 +526,7 @@ function UnlicensedOverlay({ featureName, reason, children }) {
|
|
|
549
526
|
}
|
|
550
527
|
function requireLicense(Component, featureName) {
|
|
551
528
|
function LicenseGated(props) {
|
|
552
|
-
const { status
|
|
529
|
+
const { status } = useLicense();
|
|
553
530
|
const isProduction = isProductionEnvironment();
|
|
554
531
|
if (!isProduction) {
|
|
555
532
|
return /* @__PURE__ */ jsx(Component, { ...props });
|
|
@@ -557,8 +534,7 @@ function requireLicense(Component, featureName) {
|
|
|
557
534
|
if (status === "validating") {
|
|
558
535
|
return null;
|
|
559
536
|
}
|
|
560
|
-
|
|
561
|
-
if (isLicensed) {
|
|
537
|
+
if (status === "valid") {
|
|
562
538
|
return /* @__PURE__ */ jsx(Component, { ...props });
|
|
563
539
|
}
|
|
564
540
|
let reason = "A valid license is required for production use.";
|
|
@@ -570,8 +546,6 @@ function requireLicense(Component, featureName) {
|
|
|
570
546
|
reason = "This license key has been revoked. Please contact support or purchase a new license.";
|
|
571
547
|
} else if (status === "domain_mismatch") {
|
|
572
548
|
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.";
|
|
573
|
-
} else if (!tier || !tierHasFeature(tier, featureName)) {
|
|
574
|
-
reason = "Your license does not include this feature. Please upgrade your plan.";
|
|
575
549
|
}
|
|
576
550
|
return /* @__PURE__ */ jsx(UnlicensedOverlay, { featureName, reason, children: /* @__PURE__ */ jsx(Component, { ...props }) });
|
|
577
551
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { requireLicense } from './chunk-
|
|
1
|
+
import { requireLicense } from './chunk-4MMKB2EW.mjs';
|
|
2
2
|
import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext } from 'react';
|
|
3
3
|
import { FormEngineRenderer } from '@squaredr/fieldcraft-react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cn } from './chunk-QQ4JZGTD.mjs';
|
|
2
|
-
import { requireLicense } from './chunk-
|
|
2
|
+
import { requireLicense } from './chunk-4MMKB2EW.mjs';
|
|
3
3
|
import { useState, useEffect, useMemo } from 'react';
|
|
4
4
|
import { Input, Button, Separator, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@squaredr/fieldcraft-react';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { cn } from './chunk-QQ4JZGTD.mjs';
|
|
2
|
-
import { requireLicense } from './chunk-
|
|
2
|
+
import { requireLicense } from './chunk-4MMKB2EW.mjs';
|
|
3
3
|
import { forwardRef, lazy, memo, createContext, Component, useState, useRef, useEffect, useCallback, useMemo, Suspense, useContext } from 'react';
|
|
4
4
|
import { DndContext, DragOverlay, useSensors, useSensor, MouseSensor, TouchSensor, useDraggable, useDroppable } from '@dnd-kit/core';
|
|
5
|
-
import { ChevronDown, Undo2, Redo2, PencilRuler, Eye, Code,
|
|
5
|
+
import { ChevronDown, Undo2, Redo2, PencilRuler, Eye, Code, GitBranch, LayoutTemplate, Upload, Download, Save, X, PanelLeft, PanelRight, Search, ChevronRight, Plus, Settings, FileText, Layers, ZoomOut, ZoomIn, Maximize, Copy, Trash2, HelpCircle, MoveVertical, Minus, Video, Image, PartyPopper, Hand, ShieldCheck, SeparatorHorizontal, Info, Heading, Trophy, EyeOff, Calculator, CreditCard, MapPin, Repeat, Grid3X3, Camera, PenTool, Paperclip, CalendarCheck, CalendarRange, Clock, Calendar, ArrowUpDown, Globe, ToggleLeft, CheckSquare, CircleDot, ListOrdered, TrendingUp, BarChart3, Star, SlidersHorizontal, Hash, UserCheck, Link, PhoneCall, Phone, Mail, AlignLeft, Type, GripVertical } from 'lucide-react';
|
|
6
6
|
import { Button, Separator, FormEngineRenderer, Input, Badge, Textarea, Label, Switch } from '@squaredr/fieldcraft-react';
|
|
7
7
|
import { validateSchema, FormEngineSchemaError } from '@squaredr/fieldcraft-core';
|
|
8
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
-
import { MarkerType, Handle, Position, getBezierPath, BaseEdge, EdgeLabelRenderer, useNodesState, useEdgesState, ReactFlow, Background,
|
|
9
|
+
import { MarkerType, Handle, Position, getBezierPath, BaseEdge, EdgeLabelRenderer, useNodesState, useEdgesState, ReactFlowProvider, useReactFlow, ReactFlow, Background, MiniMap } from '@xyflow/react';
|
|
10
10
|
import '@xyflow/react/dist/style.css';
|
|
11
11
|
|
|
12
12
|
var MAX_HISTORY = 50;
|
|
@@ -3425,7 +3425,7 @@ var defaultEdgeOptions = {
|
|
|
3425
3425
|
height: 12
|
|
3426
3426
|
}
|
|
3427
3427
|
};
|
|
3428
|
-
function LogicFlowEditor({ schema, onChange
|
|
3428
|
+
function LogicFlowEditor({ schema, onChange }) {
|
|
3429
3429
|
const noSections = schema.sections.length === 0;
|
|
3430
3430
|
const flowGraph = useMemo(() => buildReactFlowGraph(schema), [schema]);
|
|
3431
3431
|
const [nodes, setNodes, onNodesChange] = useNodesState(flowGraph.nodes);
|
|
@@ -3517,33 +3517,89 @@ function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
|
3517
3517
|
const jumpCount = flowGraph.edges.filter(
|
|
3518
3518
|
(e) => e.data?.edgeType === "onExit"
|
|
3519
3519
|
).length;
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3520
|
+
const totalFields = useMemo(
|
|
3521
|
+
() => schema.sections.reduce((sum, s) => sum + s.questions.length, 0),
|
|
3522
|
+
[schema]
|
|
3523
|
+
);
|
|
3524
|
+
const conditionalFields = useMemo(
|
|
3525
|
+
() => schema.sections.reduce(
|
|
3526
|
+
(sum, s) => sum + s.questions.filter((q) => !!q.showIf).length,
|
|
3527
|
+
0
|
|
3528
|
+
),
|
|
3529
|
+
[schema]
|
|
3530
|
+
);
|
|
3531
|
+
return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(
|
|
3532
|
+
LogicFlowInner,
|
|
3533
|
+
{
|
|
3534
|
+
noSections,
|
|
3535
|
+
nodes,
|
|
3536
|
+
edges,
|
|
3537
|
+
onNodesChange,
|
|
3538
|
+
onEdgesChange,
|
|
3539
|
+
onNodeClick,
|
|
3540
|
+
onConnect,
|
|
3541
|
+
onEdgesDelete,
|
|
3542
|
+
selectedFieldId,
|
|
3543
|
+
selectedQuestion,
|
|
3544
|
+
setSelectedFieldId,
|
|
3545
|
+
handleConditionUpdate,
|
|
3546
|
+
schema,
|
|
3547
|
+
sectionIds,
|
|
3548
|
+
showIfCount,
|
|
3549
|
+
jumpCount,
|
|
3550
|
+
totalFields,
|
|
3551
|
+
conditionalFields
|
|
3552
|
+
}
|
|
3553
|
+
) });
|
|
3554
|
+
}
|
|
3555
|
+
function LogicFlowInner({
|
|
3556
|
+
noSections,
|
|
3557
|
+
nodes,
|
|
3558
|
+
edges,
|
|
3559
|
+
onNodesChange,
|
|
3560
|
+
onEdgesChange,
|
|
3561
|
+
onNodeClick,
|
|
3562
|
+
onConnect,
|
|
3563
|
+
onEdgesDelete,
|
|
3564
|
+
selectedFieldId,
|
|
3565
|
+
selectedQuestion,
|
|
3566
|
+
setSelectedFieldId,
|
|
3567
|
+
handleConditionUpdate,
|
|
3568
|
+
schema,
|
|
3569
|
+
sectionIds,
|
|
3570
|
+
showIfCount,
|
|
3571
|
+
jumpCount,
|
|
3572
|
+
totalFields,
|
|
3573
|
+
conditionalFields
|
|
3574
|
+
}) {
|
|
3575
|
+
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
|
3576
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col bg-background min-h-0", children: [
|
|
3577
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 h-9 flex items-center justify-between px-4 border-b border-border bg-card/50", children: [
|
|
3578
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
3579
|
+
sectionIds.length,
|
|
3580
|
+
" section",
|
|
3581
|
+
sectionIds.length !== 1 ? "s" : "",
|
|
3582
|
+
" \xB7 ",
|
|
3583
|
+
totalFields,
|
|
3584
|
+
" field",
|
|
3585
|
+
totalFields !== 1 ? "s" : "",
|
|
3586
|
+
conditionalFields > 0 && ` \xB7 ${conditionalFields} conditional`,
|
|
3587
|
+
showIfCount > 0 && ` \xB7 ${showIfCount} rule${showIfCount !== 1 ? "s" : ""}`,
|
|
3588
|
+
jumpCount > 0 && ` \xB7 ${jumpCount} jump${jumpCount !== 1 ? "s" : ""}`
|
|
3589
|
+
] }) }),
|
|
3536
3590
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3537
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground mr-2", children: "Drag between nodes to create conditions \xB7 Select edges
|
|
3538
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-
|
|
3591
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground mr-2 hidden md:inline", children: "Drag between nodes to create conditions \xB7 Select edges + Delete to remove" }),
|
|
3592
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-xs", onClick: () => zoomOut(), title: "Zoom out", children: /* @__PURE__ */ jsx(ZoomOut, { size: 14, strokeWidth: 1.75 }) }),
|
|
3593
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-xs", onClick: () => zoomIn(), title: "Zoom in", children: /* @__PURE__ */ jsx(ZoomIn, { size: 14, strokeWidth: 1.75 }) }),
|
|
3594
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-xs", onClick: () => fitView({ padding: 0.2 }), title: "Fit to view", children: /* @__PURE__ */ jsx(Maximize, { size: 14, strokeWidth: 1.75 }) })
|
|
3539
3595
|
] })
|
|
3540
3596
|
] }),
|
|
3541
|
-
noSections ? /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-
|
|
3542
|
-
/* @__PURE__ */ jsx(GitBranch, { size:
|
|
3543
|
-
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "No
|
|
3544
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs max-w-xs", children: "
|
|
3545
|
-
] }) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 flex", children: [
|
|
3546
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxs(
|
|
3597
|
+
noSections ? /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center text-muted-foreground text-sm", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-3", children: [
|
|
3598
|
+
/* @__PURE__ */ jsx(GitBranch, { size: 40, className: "mx-auto opacity-30" }),
|
|
3599
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "No logic to display" }),
|
|
3600
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs max-w-xs mx-auto", children: "Switch to Design mode and add sections with fields. Then come back here to visualize and edit branching logic." })
|
|
3601
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 flex min-h-0", children: [
|
|
3602
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxs(
|
|
3547
3603
|
ReactFlow,
|
|
3548
3604
|
{
|
|
3549
3605
|
nodes,
|
|
@@ -3562,7 +3618,6 @@ function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
|
3562
3618
|
proOptions: { hideAttribution: true },
|
|
3563
3619
|
children: [
|
|
3564
3620
|
/* @__PURE__ */ jsx(Background, { gap: 20, size: 1 }),
|
|
3565
|
-
/* @__PURE__ */ jsx(Controls, { showInteractive: false }),
|
|
3566
3621
|
/* @__PURE__ */ jsx(
|
|
3567
3622
|
MiniMap,
|
|
3568
3623
|
{
|
|
@@ -3577,9 +3632,9 @@ function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
|
3577
3632
|
]
|
|
3578
3633
|
}
|
|
3579
3634
|
) }),
|
|
3580
|
-
selectedFieldId && selectedQuestion && /* @__PURE__ */ jsxs("div", { className: "w-72 border-l border-border bg-card overflow-auto", children: [
|
|
3635
|
+
selectedFieldId && selectedQuestion && /* @__PURE__ */ jsxs("div", { className: "w-72 shrink-0 border-l border-border bg-card overflow-auto", children: [
|
|
3581
3636
|
/* @__PURE__ */ jsxs("div", { className: "px-3 py-3 border-b border-border flex items-center justify-between", children: [
|
|
3582
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3637
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
3583
3638
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-semibold truncate", children: selectedQuestion.label || selectedQuestion.id }),
|
|
3584
3639
|
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: selectedQuestion.type })
|
|
3585
3640
|
] }),
|
|
@@ -3590,7 +3645,7 @@ function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
|
3590
3645
|
size: "icon-xs",
|
|
3591
3646
|
onClick: () => setSelectedFieldId(null),
|
|
3592
3647
|
title: "Close panel",
|
|
3593
|
-
children: /* @__PURE__ */ jsx(
|
|
3648
|
+
children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: "\xD7" })
|
|
3594
3649
|
}
|
|
3595
3650
|
)
|
|
3596
3651
|
] }),
|
|
@@ -3604,7 +3659,7 @@ function LogicFlowEditor({ schema, onChange, onClose }) {
|
|
|
3604
3659
|
) })
|
|
3605
3660
|
] })
|
|
3606
3661
|
] }),
|
|
3607
|
-
/* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-
|
|
3662
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 px-4 py-1.5 border-t border-border bg-card/50 flex items-center gap-4 text-[11px] text-muted-foreground flex-wrap", children: [
|
|
3608
3663
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3609
3664
|
/* @__PURE__ */ jsx("div", { className: "w-5 h-0.5 rounded", style: { background: "var(--muted-foreground)", opacity: 0.4 } }),
|
|
3610
3665
|
/* @__PURE__ */ jsx("span", { children: "flow" })
|
|
@@ -3859,7 +3914,6 @@ function FormBuilderCore(props) {
|
|
|
3859
3914
|
const [viewMode, setViewMode] = useState("design");
|
|
3860
3915
|
const [jsonText, setJsonText] = useState("");
|
|
3861
3916
|
const [jsonSwitchError, setJsonSwitchError] = useState(null);
|
|
3862
|
-
const [showLogicMap, setShowLogicMap] = useState(false);
|
|
3863
3917
|
const [showTemplateGallery, setShowTemplateGallery] = useState(false);
|
|
3864
3918
|
const [saveValidationErrors, setSaveValidationErrors] = useState(null);
|
|
3865
3919
|
const [schemaUrlLoading, setSchemaUrlLoading] = useState(!!schemaUrl);
|
|
@@ -4016,6 +4070,11 @@ ${details}`);
|
|
|
4016
4070
|
handleSave();
|
|
4017
4071
|
return;
|
|
4018
4072
|
}
|
|
4073
|
+
if (mod && e.key === "l") {
|
|
4074
|
+
e.preventDefault();
|
|
4075
|
+
handleViewModeChange(viewMode === "logic" ? "design" : "logic");
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4019
4078
|
if (isEditing) return;
|
|
4020
4079
|
if (e.key === "Delete" || e.key === "Backspace") {
|
|
4021
4080
|
const sel = builderState.selectedItem;
|
|
@@ -4046,7 +4105,7 @@ ${details}`);
|
|
|
4046
4105
|
}
|
|
4047
4106
|
}
|
|
4048
4107
|
},
|
|
4049
|
-
[builderState, handleSave]
|
|
4108
|
+
[builderState, handleSave, handleViewModeChange, viewMode]
|
|
4050
4109
|
);
|
|
4051
4110
|
return /* @__PURE__ */ jsx(FormBuilderThemeProvider, { theme, children: /* @__PURE__ */ jsxs(
|
|
4052
4111
|
DndContext,
|
|
@@ -4137,6 +4196,19 @@ ${details}`);
|
|
|
4137
4196
|
"JSON"
|
|
4138
4197
|
]
|
|
4139
4198
|
}
|
|
4199
|
+
),
|
|
4200
|
+
/* @__PURE__ */ jsxs(
|
|
4201
|
+
Button,
|
|
4202
|
+
{
|
|
4203
|
+
variant: viewMode === "logic" ? "secondary" : "ghost",
|
|
4204
|
+
size: "sm",
|
|
4205
|
+
onClick: () => handleViewModeChange("logic"),
|
|
4206
|
+
className: "gap-1.5 h-7 px-2.5 text-xs",
|
|
4207
|
+
children: [
|
|
4208
|
+
/* @__PURE__ */ jsx(GitBranch, { size: 13, strokeWidth: 1.75 }),
|
|
4209
|
+
"Logic"
|
|
4210
|
+
]
|
|
4211
|
+
}
|
|
4140
4212
|
)
|
|
4141
4213
|
] }),
|
|
4142
4214
|
toolbarExtra
|
|
@@ -4153,17 +4225,6 @@ ${details}`);
|
|
|
4153
4225
|
children: /* @__PURE__ */ jsx(LayoutTemplate, { size: 15, strokeWidth: 1.75 })
|
|
4154
4226
|
}
|
|
4155
4227
|
),
|
|
4156
|
-
/* @__PURE__ */ jsx(
|
|
4157
|
-
Button,
|
|
4158
|
-
{
|
|
4159
|
-
variant: "ghost",
|
|
4160
|
-
size: "icon-sm",
|
|
4161
|
-
onClick: () => setShowLogicMap(true),
|
|
4162
|
-
title: "View branching logic map",
|
|
4163
|
-
"aria-label": "Logic map",
|
|
4164
|
-
children: /* @__PURE__ */ jsx(GitBranch, { size: 15, strokeWidth: 1.75 })
|
|
4165
|
-
}
|
|
4166
|
-
),
|
|
4167
4228
|
/* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-1 h-5" }),
|
|
4168
4229
|
/* @__PURE__ */ jsx(
|
|
4169
4230
|
Button,
|
|
@@ -4247,6 +4308,19 @@ ${details}`);
|
|
|
4247
4308
|
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "JSON" })
|
|
4248
4309
|
]
|
|
4249
4310
|
}
|
|
4311
|
+
),
|
|
4312
|
+
/* @__PURE__ */ jsxs(
|
|
4313
|
+
Button,
|
|
4314
|
+
{
|
|
4315
|
+
variant: viewMode === "logic" ? "secondary" : "ghost",
|
|
4316
|
+
size: "sm",
|
|
4317
|
+
onClick: () => handleViewModeChange("logic"),
|
|
4318
|
+
className: "gap-1 h-7 px-2 text-xs",
|
|
4319
|
+
children: [
|
|
4320
|
+
/* @__PURE__ */ jsx(GitBranch, { size: 12, strokeWidth: 1.75 }),
|
|
4321
|
+
/* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Logic" })
|
|
4322
|
+
]
|
|
4323
|
+
}
|
|
4250
4324
|
)
|
|
4251
4325
|
] }),
|
|
4252
4326
|
toolbarExtra,
|
|
@@ -4380,7 +4454,13 @@ ${details}`);
|
|
|
4380
4454
|
errors: jsonErrors,
|
|
4381
4455
|
onValidate: validateJson
|
|
4382
4456
|
}
|
|
4383
|
-
) : viewMode === "preview" ? /* @__PURE__ */ jsx(FormPreviewPanel, { schema: builderState.schema, preview }) : /* @__PURE__ */
|
|
4457
|
+
) : viewMode === "preview" ? /* @__PURE__ */ jsx(FormPreviewPanel, { schema: builderState.schema, preview }) : viewMode === "logic" ? /* @__PURE__ */ jsx(
|
|
4458
|
+
LogicFlowEditor,
|
|
4459
|
+
{
|
|
4460
|
+
schema: builderState.schema,
|
|
4461
|
+
onChange: (updated) => builderState.resetSchema(updated)
|
|
4462
|
+
}
|
|
4463
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4384
4464
|
/* @__PURE__ */ jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsx(QuestionPalette, { questionTypes: mergedQuestionTypes, palette }) }),
|
|
4385
4465
|
/* @__PURE__ */ jsx(FormCanvas, { builderState }),
|
|
4386
4466
|
/* @__PURE__ */ jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsx(PropertiesPanel, { builderState }) }),
|
|
@@ -4399,14 +4479,6 @@ ${details}`);
|
|
|
4399
4479
|
}
|
|
4400
4480
|
),
|
|
4401
4481
|
/* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: dragDrop.activeDragItem && /* @__PURE__ */ jsx(DragOverlayContent, { item: dragDrop.activeDragItem, schema: builderState.schema, questionTypes: mergedQuestionTypes }) }),
|
|
4402
|
-
showLogicMap && /* @__PURE__ */ jsx(
|
|
4403
|
-
LogicFlowEditor,
|
|
4404
|
-
{
|
|
4405
|
-
schema: builderState.schema,
|
|
4406
|
-
onChange: (updated) => builderState.resetSchema(updated),
|
|
4407
|
-
onClose: () => setShowLogicMap(false)
|
|
4408
|
-
}
|
|
4409
|
-
),
|
|
4410
4482
|
showTemplateGallery && templates && /* @__PURE__ */ jsx(
|
|
4411
4483
|
TemplateGallery,
|
|
4412
4484
|
{
|