@nextlyhq/plugin-form-builder 0.0.2-alpha.33 → 0.0.2-alpha.35

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.
Files changed (38) hide show
  1. package/README.md +4 -5
  2. package/dist/admin/index.d.ts +101 -303
  3. package/dist/admin/index.js +3583 -3245
  4. package/dist/admin/index.js.map +1 -1
  5. package/dist/chunk-I4VFGPNR.js +506 -0
  6. package/dist/chunk-I4VFGPNR.js.map +1 -0
  7. package/dist/index.d.ts +4 -4
  8. package/dist/index.js +422 -573
  9. package/dist/index.js.map +1 -1
  10. package/dist/{index-BRumOEsE.d.ts → types-C-DcuYGF.d.ts} +61 -93
  11. package/package.json +26 -31
  12. package/dist/CheckboxFieldEditor-K4T6QX52.js +0 -121
  13. package/dist/CheckboxFieldEditor-K4T6QX52.js.map +0 -1
  14. package/dist/DateFieldEditor-JFJFAVOH.js +0 -187
  15. package/dist/DateFieldEditor-JFJFAVOH.js.map +0 -1
  16. package/dist/EmailFieldEditor-ZZ4VSKKB.js +0 -138
  17. package/dist/EmailFieldEditor-ZZ4VSKKB.js.map +0 -1
  18. package/dist/NumberFieldEditor-UISAYR6Z.js +0 -175
  19. package/dist/NumberFieldEditor-UISAYR6Z.js.map +0 -1
  20. package/dist/RadioFieldEditor-6UQFH4WT.js +0 -165
  21. package/dist/RadioFieldEditor-6UQFH4WT.js.map +0 -1
  22. package/dist/SelectFieldEditor-PSO4KHV4.js +0 -213
  23. package/dist/SelectFieldEditor-PSO4KHV4.js.map +0 -1
  24. package/dist/TextFieldEditor-5EU64EA5.js +0 -187
  25. package/dist/TextFieldEditor-5EU64EA5.js.map +0 -1
  26. package/dist/TextareaFieldEditor-773G3LNM.js +0 -190
  27. package/dist/TextareaFieldEditor-773G3LNM.js.map +0 -1
  28. package/dist/admin/index.css +0 -934
  29. package/dist/admin/index.css.map +0 -1
  30. package/dist/chunk-ABNZFTQK.js +0 -586
  31. package/dist/chunk-ABNZFTQK.js.map +0 -1
  32. package/dist/chunk-XTCEPJ7B.js +0 -256
  33. package/dist/chunk-XTCEPJ7B.js.map +0 -1
  34. package/dist/components/index.d.ts +0 -3
  35. package/dist/components/index.js +0 -9
  36. package/dist/components/index.js.map +0 -1
  37. package/dist/styles/form-builder.css +0 -1150
  38. package/dist/styles/submissions-filter.css +0 -75
@@ -0,0 +1,506 @@
1
+ // src/utils/evaluate-conditions.ts
2
+ function isEmptyValue(value) {
3
+ if (value === void 0 || value === null) {
4
+ return true;
5
+ }
6
+ if (typeof value === "string" && value.trim() === "") {
7
+ return true;
8
+ }
9
+ if (Array.isArray(value) && value.length === 0) {
10
+ return true;
11
+ }
12
+ return false;
13
+ }
14
+ function toNumber(value) {
15
+ if (typeof value === "number") {
16
+ return value;
17
+ }
18
+ if (typeof value === "string") {
19
+ const trimmed = value.trim();
20
+ if (trimmed === "") {
21
+ return NaN;
22
+ }
23
+ return Number(trimmed);
24
+ }
25
+ return NaN;
26
+ }
27
+ function toString(value) {
28
+ if (value === void 0 || value === null) {
29
+ return "";
30
+ }
31
+ if (typeof value === "string") {
32
+ return value;
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return value.join(", ");
36
+ }
37
+ if (typeof value === "object") {
38
+ return JSON.stringify(value);
39
+ }
40
+ return String(value);
41
+ }
42
+ function compareValues(operator, fieldValue, conditionValue) {
43
+ switch (operator) {
44
+ case "equals": {
45
+ if (Array.isArray(fieldValue)) {
46
+ if (Array.isArray(conditionValue)) {
47
+ if (fieldValue.length !== conditionValue.length) {
48
+ return false;
49
+ }
50
+ return fieldValue.every((v, i) => v === conditionValue[i]);
51
+ }
52
+ return fieldValue.includes(conditionValue);
53
+ }
54
+ return toString(fieldValue) === toString(conditionValue);
55
+ }
56
+ case "notEquals": {
57
+ if (Array.isArray(fieldValue)) {
58
+ if (Array.isArray(conditionValue)) {
59
+ if (fieldValue.length !== conditionValue.length) {
60
+ return true;
61
+ }
62
+ return !fieldValue.every((v, i) => v === conditionValue[i]);
63
+ }
64
+ return !fieldValue.includes(conditionValue);
65
+ }
66
+ return toString(fieldValue) !== toString(conditionValue);
67
+ }
68
+ case "contains": {
69
+ const fieldStr = toString(fieldValue).toLowerCase();
70
+ const conditionStr = toString(conditionValue).toLowerCase();
71
+ if (Array.isArray(fieldValue)) {
72
+ return fieldValue.some(
73
+ (v) => toString(v).toLowerCase().includes(conditionStr)
74
+ );
75
+ }
76
+ return fieldStr.includes(conditionStr);
77
+ }
78
+ case "isEmpty": {
79
+ return isEmptyValue(fieldValue);
80
+ }
81
+ case "isNotEmpty": {
82
+ return !isEmptyValue(fieldValue);
83
+ }
84
+ case "greaterThan": {
85
+ const fieldNum = toNumber(fieldValue);
86
+ const conditionNum = toNumber(conditionValue);
87
+ if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {
88
+ return fieldNum > conditionNum;
89
+ }
90
+ return toString(fieldValue) > toString(conditionValue);
91
+ }
92
+ case "lessThan": {
93
+ const fieldNum = toNumber(fieldValue);
94
+ const conditionNum = toNumber(conditionValue);
95
+ if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {
96
+ return fieldNum < conditionNum;
97
+ }
98
+ return toString(fieldValue) < toString(conditionValue);
99
+ }
100
+ default: {
101
+ console.warn(
102
+ `[evaluateConditions] Unknown comparison operator: ${String(operator)}`
103
+ );
104
+ return false;
105
+ }
106
+ }
107
+ }
108
+ function evaluateSingleCondition(condition, formData) {
109
+ const { field, comparison, value: conditionValue } = condition;
110
+ const fieldValue = getNestedValue(formData, field);
111
+ return compareValues(comparison, fieldValue, conditionValue);
112
+ }
113
+ function getNestedValue(obj, path) {
114
+ if (!path) {
115
+ return void 0;
116
+ }
117
+ if (!path.includes(".")) {
118
+ return obj[path];
119
+ }
120
+ const parts = path.split(".");
121
+ let current = obj;
122
+ for (const part of parts) {
123
+ if (current === void 0 || current === null) {
124
+ return void 0;
125
+ }
126
+ if (typeof current !== "object") {
127
+ return void 0;
128
+ }
129
+ current = current[part];
130
+ }
131
+ return current;
132
+ }
133
+ function evaluateConditions(logic, formData) {
134
+ if (!logic || !logic.enabled) {
135
+ return true;
136
+ }
137
+ const { action, operator, conditions } = logic;
138
+ if (!conditions || conditions.length === 0) {
139
+ return true;
140
+ }
141
+ const results = conditions.map(
142
+ (condition) => evaluateSingleCondition(condition, formData)
143
+ );
144
+ let conditionsMet;
145
+ if (operator === "OR") {
146
+ conditionsMet = results.some((result) => result);
147
+ } else {
148
+ conditionsMet = results.every((result) => result);
149
+ }
150
+ if (action === "hide") {
151
+ return !conditionsMet;
152
+ }
153
+ return conditionsMet;
154
+ }
155
+ function isValidComparisonOperator(operator) {
156
+ const validOperators = [
157
+ "equals",
158
+ "notEquals",
159
+ "contains",
160
+ "isEmpty",
161
+ "isNotEmpty",
162
+ "greaterThan",
163
+ "lessThan"
164
+ ];
165
+ return validOperators.includes(operator);
166
+ }
167
+ function getSupportedComparisonOperators() {
168
+ return [
169
+ "equals",
170
+ "notEquals",
171
+ "contains",
172
+ "isEmpty",
173
+ "isNotEmpty",
174
+ "greaterThan",
175
+ "lessThan"
176
+ ];
177
+ }
178
+
179
+ // src/utils/export-formats.ts
180
+ var UTF8_BOM = "\uFEFF";
181
+ function safeString(value) {
182
+ if (typeof value === "object" && value !== null) {
183
+ return JSON.stringify(value);
184
+ }
185
+ return String(value);
186
+ }
187
+ function formatExportValue(value, field) {
188
+ if (value === null || value === void 0) {
189
+ return "";
190
+ }
191
+ if (field) {
192
+ switch (field.type) {
193
+ case "checkbox":
194
+ return value ? "Yes" : "No";
195
+ case "select":
196
+ if (Array.isArray(value)) {
197
+ return value.join(", ");
198
+ }
199
+ return safeString(value);
200
+ case "file":
201
+ if (Array.isArray(value)) {
202
+ return value.map((v) => safeString(v)).join(", ");
203
+ }
204
+ return safeString(value);
205
+ case "date":
206
+ if (value instanceof Date) {
207
+ return value.toISOString().split("T")[0];
208
+ }
209
+ if (typeof value === "string") {
210
+ const parsed = new Date(value);
211
+ if (!isNaN(parsed.getTime())) {
212
+ return parsed.toISOString().split("T")[0];
213
+ }
214
+ }
215
+ return safeString(value);
216
+ case "time":
217
+ return safeString(value);
218
+ case "hidden":
219
+ return safeString(value);
220
+ default:
221
+ break;
222
+ }
223
+ }
224
+ if (Array.isArray(value)) {
225
+ return value.join(", ");
226
+ }
227
+ if (typeof value === "boolean") {
228
+ return value ? "Yes" : "No";
229
+ }
230
+ if (value instanceof Date) {
231
+ return value.toISOString();
232
+ }
233
+ return safeString(value);
234
+ }
235
+ function formatDate(date, format = "iso") {
236
+ if (!date) return "";
237
+ const d = typeof date === "string" ? new Date(date) : date;
238
+ if (isNaN(d.getTime())) {
239
+ return "";
240
+ }
241
+ return format === "iso" ? d.toISOString() : d.toLocaleString();
242
+ }
243
+ function escapeCSVValue(value, delimiter = ",") {
244
+ const isFormulaLike = /^[=+\-@\t\r]/.test(value) && !/^-?\d+(\.\d+)?$/.test(value);
245
+ const escapedFormula = isFormulaLike ? `'${value}` : value;
246
+ const needsQuoting = escapedFormula.includes(delimiter) || escapedFormula.includes('"') || escapedFormula.includes("\n") || escapedFormula.includes("\r");
247
+ if (!needsQuoting) {
248
+ return escapedFormula;
249
+ }
250
+ const escaped = escapedFormula.replace(/"/g, '""');
251
+ return `"${escaped}"`;
252
+ }
253
+ function exportToCSV(submissions, form, options = {}) {
254
+ const {
255
+ includeMetadata = true,
256
+ delimiter = ",",
257
+ includeBOM = true,
258
+ dateFormat = "iso"
259
+ } = options;
260
+ const exportableFields = form.fields;
261
+ const headers = [];
262
+ for (const field of exportableFields) {
263
+ headers.push(field.label);
264
+ }
265
+ if (includeMetadata) {
266
+ headers.push("ID", "Status", "Submitted At", "IP Address", "User Agent");
267
+ }
268
+ const rows = [];
269
+ for (const submission of submissions) {
270
+ const row = [];
271
+ const data = submission.data;
272
+ for (const field of exportableFields) {
273
+ const value = data[field.name];
274
+ const formatted = formatExportValue(value, field);
275
+ row.push(escapeCSVValue(formatted, delimiter));
276
+ }
277
+ if (includeMetadata) {
278
+ row.push(escapeCSVValue(submission.id, delimiter));
279
+ row.push(escapeCSVValue(submission.status, delimiter));
280
+ row.push(
281
+ escapeCSVValue(
282
+ formatDate(submission.submittedAt, dateFormat),
283
+ delimiter
284
+ )
285
+ );
286
+ row.push(escapeCSVValue(submission.ipAddress || "", delimiter));
287
+ row.push(escapeCSVValue(submission.userAgent || "", delimiter));
288
+ }
289
+ rows.push(row);
290
+ }
291
+ const escapedHeaders = headers.map((h) => escapeCSVValue(h, delimiter));
292
+ const lines = [
293
+ escapedHeaders.join(delimiter),
294
+ ...rows.map((row) => row.join(delimiter))
295
+ ];
296
+ const csvContent = lines.join("\r\n");
297
+ return includeBOM ? UTF8_BOM + csvContent : csvContent;
298
+ }
299
+ function exportToJSON(submissions, form, options = {}) {
300
+ const {
301
+ includeMetadata = true,
302
+ includeFormDefinition = true,
303
+ indent = 2
304
+ } = options;
305
+ const exportableFields = form.fields;
306
+ const formDefinition = includeFormDefinition ? {
307
+ id: form.id,
308
+ name: form.name,
309
+ slug: form.slug,
310
+ fields: exportableFields.map((f) => ({
311
+ name: f.name,
312
+ label: f.label,
313
+ type: f.type
314
+ }))
315
+ } : void 0;
316
+ const exportedSubmissions = submissions.map((submission) => {
317
+ const data = submission.data;
318
+ const fieldData = {};
319
+ for (const field of exportableFields) {
320
+ const value = data[field.name];
321
+ fieldData[field.name] = value;
322
+ }
323
+ if (includeMetadata) {
324
+ return {
325
+ ...fieldData,
326
+ _metadata: {
327
+ id: submission.id,
328
+ status: submission.status,
329
+ submittedAt: submission.submittedAt,
330
+ ipAddress: submission.ipAddress || null,
331
+ userAgent: submission.userAgent || null,
332
+ createdAt: submission.createdAt,
333
+ updatedAt: submission.updatedAt
334
+ }
335
+ };
336
+ }
337
+ return fieldData;
338
+ });
339
+ const exportData = {
340
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
341
+ totalSubmissions: submissions.length,
342
+ submissions: exportedSubmissions
343
+ };
344
+ if (formDefinition) {
345
+ exportData.form = formDefinition;
346
+ }
347
+ return JSON.stringify(exportData, null, indent);
348
+ }
349
+ function downloadFile(content, filename, mimeType) {
350
+ if (typeof window === "undefined" || typeof document === "undefined") {
351
+ throw new Error("downloadFile is only available in browser environments");
352
+ }
353
+ const blob = new Blob([content], { type: mimeType });
354
+ const url = URL.createObjectURL(blob);
355
+ const link = document.createElement("a");
356
+ link.href = url;
357
+ link.download = filename;
358
+ document.body.appendChild(link);
359
+ link.click();
360
+ document.body.removeChild(link);
361
+ URL.revokeObjectURL(url);
362
+ }
363
+ function generateExportFilename(formSlug, format) {
364
+ const safeSlug = formSlug.toLowerCase().replace(/[^a-z0-9_-]+/g, "-") || "form";
365
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
366
+ return `${safeSlug}-submissions-${date}.${format}`;
367
+ }
368
+ function exportAndDownload(submissions, form, format, options) {
369
+ const filename = generateExportFilename(form.slug, format);
370
+ if (format === "csv") {
371
+ const content = exportToCSV(submissions, form, options);
372
+ downloadFile(content, filename, "text/csv;charset=utf-8");
373
+ } else {
374
+ const content = exportToJSON(submissions, form, options);
375
+ downloadFile(content, filename, "application/json");
376
+ }
377
+ }
378
+
379
+ // src/config/defaults.ts
380
+ var DEFAULT_FORM_SETTINGS = {
381
+ submitButtonText: "Submit",
382
+ confirmationType: "message",
383
+ successMessage: "Thank you for your submission!",
384
+ allowMultipleSubmissions: true
385
+ };
386
+ var DEFAULT_NOTIFICATION_SETTINGS = {
387
+ enabled: true,
388
+ replyTo: true
389
+ };
390
+ function toTitleCase(str) {
391
+ return str.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
392
+ }
393
+ function pluralize(singular) {
394
+ if (singular.endsWith("y") && !/[aeiou]y$/i.test(singular)) {
395
+ return singular.slice(0, -1) + "ies";
396
+ }
397
+ if (singular.endsWith("s") || singular.endsWith("x") || singular.endsWith("z") || singular.endsWith("ch") || singular.endsWith("sh")) {
398
+ return singular + "es";
399
+ }
400
+ return singular + "s";
401
+ }
402
+ function applyFormDefaults(config) {
403
+ const singularLabel = config.labels?.singular || toTitleCase(config.slug);
404
+ return {
405
+ ...config,
406
+ // Generate labels from slug if not provided
407
+ labels: {
408
+ singular: singularLabel,
409
+ plural: config.labels?.plural || pluralize(singularLabel)
410
+ },
411
+ // Apply settings defaults
412
+ settings: {
413
+ submitButtonText: config.settings?.submitButtonText || DEFAULT_FORM_SETTINGS.submitButtonText,
414
+ confirmationType: config.settings?.confirmationType || DEFAULT_FORM_SETTINGS.confirmationType,
415
+ successMessage: config.settings?.successMessage || DEFAULT_FORM_SETTINGS.successMessage,
416
+ allowMultipleSubmissions: config.settings?.allowMultipleSubmissions ?? DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,
417
+ // Preserve other settings that don't have defaults
418
+ redirectUrl: config.settings?.redirectUrl,
419
+ redirectRelation: config.settings?.redirectRelation,
420
+ honeypotEnabled: config.settings?.honeypotEnabled,
421
+ captchaEnabled: config.settings?.captchaEnabled,
422
+ captchaSiteKey: config.settings?.captchaSiteKey
423
+ },
424
+ // Apply notification defaults if notifications are configured
425
+ notifications: config.notifications ? {
426
+ enabled: config.notifications.enabled ?? DEFAULT_NOTIFICATION_SETTINGS.enabled,
427
+ recipients: config.notifications.recipients || [],
428
+ subject: config.notifications.subject || `New submission for ${singularLabel}`,
429
+ replyTo: config.notifications.replyTo ?? DEFAULT_NOTIFICATION_SETTINGS.replyTo,
430
+ template: config.notifications.template
431
+ } : void 0,
432
+ // Apply access defaults
433
+ access: {
434
+ // Default: anyone can submit the form
435
+ submit: config.access?.submit || (() => true),
436
+ // Default: only authenticated users can view submissions
437
+ read: config.access?.read || (({ req }) => !!req.user)
438
+ }
439
+ };
440
+ }
441
+ function applyFieldDefaults(field) {
442
+ const label = field.label || (field.name ? toTitleCase(field.name.replace(/([A-Z])/g, " $1").trim()) : "");
443
+ return {
444
+ ...field,
445
+ label,
446
+ required: field.required ?? false
447
+ };
448
+ }
449
+ function createFormConfig(slug, fields, options) {
450
+ return applyFormDefaults({
451
+ slug,
452
+ fields,
453
+ ...options
454
+ });
455
+ }
456
+
457
+ // src/utils/form-settings.ts
458
+ function asOptionalBoolean(value) {
459
+ return typeof value === "boolean" ? value : void 0;
460
+ }
461
+ function asOptionalString(value) {
462
+ return typeof value === "string" && value !== "" ? value : void 0;
463
+ }
464
+ function normalizeFormSettings(raw) {
465
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
466
+ return { ...DEFAULT_FORM_SETTINGS };
467
+ }
468
+ const source = raw;
469
+ const legacyMessage = asOptionalString(source.confirmationMessage);
470
+ const legacyCaptcha = source.captcha && typeof source.captcha === "object" ? source.captcha : void 0;
471
+ const confirmationType = source.confirmationType === "redirect" || source.confirmationType === "relationship" ? source.confirmationType : "message";
472
+ return {
473
+ submitButtonText: asOptionalString(source.submitButtonText) ?? DEFAULT_FORM_SETTINGS.submitButtonText,
474
+ confirmationType,
475
+ successMessage: asOptionalString(source.successMessage) ?? legacyMessage ?? DEFAULT_FORM_SETTINGS.successMessage,
476
+ redirectUrl: asOptionalString(source.redirectUrl),
477
+ redirectPage: source.redirectPage ?? void 0,
478
+ redirectRelation: source.redirectRelation && typeof source.redirectRelation === "object" ? source.redirectRelation : void 0,
479
+ allowMultipleSubmissions: asOptionalBoolean(source.allowMultipleSubmissions) ?? DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,
480
+ honeypotEnabled: asOptionalBoolean(source.honeypotEnabled),
481
+ captchaEnabled: asOptionalBoolean(source.captchaEnabled) ?? asOptionalBoolean(legacyCaptcha?.enabled),
482
+ captchaSiteKey: asOptionalString(source.captchaSiteKey) ?? asOptionalString(legacyCaptcha?.siteKey)
483
+ };
484
+ }
485
+
486
+ export {
487
+ evaluateSingleCondition,
488
+ evaluateConditions,
489
+ isValidComparisonOperator,
490
+ getSupportedComparisonOperators,
491
+ formatExportValue,
492
+ exportToCSV,
493
+ exportToJSON,
494
+ downloadFile,
495
+ generateExportFilename,
496
+ exportAndDownload,
497
+ DEFAULT_FORM_SETTINGS,
498
+ DEFAULT_NOTIFICATION_SETTINGS,
499
+ toTitleCase,
500
+ pluralize,
501
+ applyFormDefaults,
502
+ applyFieldDefaults,
503
+ createFormConfig,
504
+ normalizeFormSettings
505
+ };
506
+ //# sourceMappingURL=chunk-I4VFGPNR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/evaluate-conditions.ts","../src/utils/export-formats.ts","../src/config/defaults.ts","../src/utils/form-settings.ts"],"sourcesContent":["/**\n * Conditional Logic Evaluator\n *\n * Evaluates conditional logic rules to determine field visibility in forms.\n * Used by the FormRenderer component to show/hide fields based on user input.\n *\n * @module utils/evaluate-conditions\n * @since 1.0.0\n *\n * @example\n * ```typescript\n * import { evaluateConditions } from '@nextly/plugin-form-builder';\n *\n * const conditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'AND',\n * conditions: [\n * { field: 'country', comparison: 'equals', value: 'US' },\n * { field: 'age', comparison: 'greaterThan', value: 18 },\n * ],\n * };\n *\n * const formData = { country: 'US', age: 25 };\n * const isVisible = evaluateConditions(conditionalLogic, formData);\n * // isVisible = true (all conditions met, action is 'show')\n * ```\n */\n\nimport type { ConditionalLogic, ConditionalLogicCondition } from \"../types\";\n\n// ============================================================\n// Types\n// ============================================================\n\n/**\n * Comparison operators supported by the conditional logic system.\n */\nexport type ComparisonOperator =\n | \"equals\"\n | \"notEquals\"\n | \"contains\"\n | \"isEmpty\"\n | \"isNotEmpty\"\n | \"greaterThan\"\n | \"lessThan\";\n\n// ============================================================\n// Internal Helper Functions\n// ============================================================\n\n/**\n * Checks if a value is considered \"empty\".\n * Empty values: undefined, null, empty string, empty array.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null) {\n return true;\n }\n if (typeof value === \"string\" && value.trim() === \"\") {\n return true;\n }\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n return false;\n}\n\n/**\n * Converts a value to a number for numeric comparisons.\n * Returns NaN if conversion is not possible.\n */\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed === \"\") {\n return NaN;\n }\n return Number(trimmed);\n }\n return NaN;\n}\n\n/**\n * Converts a value to a string for string comparisons.\n */\nfunction toString(value: unknown): string {\n if (value === undefined || value === null) {\n return \"\";\n }\n if (typeof value === \"string\") {\n return value;\n }\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n // Primitive (number, boolean, bigint, symbol) — safe to coerce.\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to primitive above; rule doesn't follow control flow on unknown\n return String(value);\n}\n\n/**\n * Compares two values using the specified operator.\n *\n * @param operator - The comparison operator to use\n * @param fieldValue - The actual value from the form data\n * @param conditionValue - The expected value from the condition\n * @returns Whether the comparison is true\n */\nfunction compareValues(\n operator: ComparisonOperator,\n fieldValue: unknown,\n conditionValue: unknown\n): boolean {\n switch (operator) {\n case \"equals\": {\n // Strict equality for most cases\n // Handle array values (e.g., checkbox groups, multi-selects)\n if (Array.isArray(fieldValue)) {\n // If conditionValue is also an array, check for deep equality\n if (Array.isArray(conditionValue)) {\n if (fieldValue.length !== conditionValue.length) {\n return false;\n }\n return fieldValue.every((v, i) => v === conditionValue[i]);\n }\n // Check if the array contains the condition value\n return fieldValue.includes(conditionValue);\n }\n // Standard equality - string comparison for flexibility\n return toString(fieldValue) === toString(conditionValue);\n }\n\n case \"notEquals\": {\n // Inverse of equals\n if (Array.isArray(fieldValue)) {\n if (Array.isArray(conditionValue)) {\n if (fieldValue.length !== conditionValue.length) {\n return true;\n }\n return !fieldValue.every((v, i) => v === conditionValue[i]);\n }\n return !fieldValue.includes(conditionValue);\n }\n return toString(fieldValue) !== toString(conditionValue);\n }\n\n case \"contains\": {\n // Case-insensitive substring check\n const fieldStr = toString(fieldValue).toLowerCase();\n const conditionStr = toString(conditionValue).toLowerCase();\n\n // Handle array values - check if any element contains the substring\n if (Array.isArray(fieldValue)) {\n return fieldValue.some(v =>\n toString(v).toLowerCase().includes(conditionStr)\n );\n }\n\n return fieldStr.includes(conditionStr);\n }\n\n case \"isEmpty\": {\n // Check if the field value is empty\n // conditionValue is ignored for this operator\n return isEmptyValue(fieldValue);\n }\n\n case \"isNotEmpty\": {\n // Check if the field value is not empty\n // conditionValue is ignored for this operator\n return !isEmptyValue(fieldValue);\n }\n\n case \"greaterThan\": {\n // Numeric comparison with loose coercion\n const fieldNum = toNumber(fieldValue);\n const conditionNum = toNumber(conditionValue);\n\n // If both can be converted to numbers, use numeric comparison\n if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {\n return fieldNum > conditionNum;\n }\n\n // Fall back to string comparison for non-numeric values\n return toString(fieldValue) > toString(conditionValue);\n }\n\n case \"lessThan\": {\n // Numeric comparison with loose coercion\n const fieldNum = toNumber(fieldValue);\n const conditionNum = toNumber(conditionValue);\n\n // If both can be converted to numbers, use numeric comparison\n if (!Number.isNaN(fieldNum) && !Number.isNaN(conditionNum)) {\n return fieldNum < conditionNum;\n }\n\n // Fall back to string comparison for non-numeric values\n return toString(fieldValue) < toString(conditionValue);\n }\n\n default: {\n // Unknown operator - return false to be safe\n // `operator` is `never` here because the switch is exhaustive over ComparisonOperator;\n // cast to string so the diagnostic survives if a new operator is added without a case.\n console.warn(\n `[evaluateConditions] Unknown comparison operator: ${String(operator)}`\n );\n return false;\n }\n }\n}\n\n/**\n * Evaluates a single condition against the form data.\n *\n * Exported for consumers that gate on one condition rather than a full\n * conditional-logic block — notification send-conditions use exactly this.\n *\n * @param condition - The condition to evaluate\n * @param formData - The current form data\n * @returns Whether the condition is met\n */\nexport function evaluateSingleCondition(\n condition: ConditionalLogicCondition,\n formData: Record<string, unknown>\n): boolean {\n const { field, comparison, value: conditionValue } = condition;\n\n // Get the field value from form data\n // Support nested field access with dot notation (e.g., \"address.city\")\n const fieldValue = getNestedValue(formData, field);\n\n // Evaluate the comparison\n return compareValues(comparison, fieldValue, conditionValue);\n}\n\n/**\n * Gets a nested value from an object using dot notation.\n *\n * @param obj - The object to get the value from\n * @param path - The path to the value (e.g., \"address.city\")\n * @returns The value at the path, or undefined if not found\n */\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n if (!path) {\n return undefined;\n }\n\n // Simple case - no nesting\n if (!path.includes(\".\")) {\n return obj[path];\n }\n\n // Nested case - traverse the path\n const parts = path.split(\".\");\n let current: unknown = obj;\n\n for (const part of parts) {\n if (current === undefined || current === null) {\n return undefined;\n }\n if (typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n}\n\n// ============================================================\n// Main Evaluation Function\n// ============================================================\n\n/**\n * Evaluates conditional logic to determine field visibility.\n *\n * This is the main entry point for conditional logic evaluation.\n * It evaluates all conditions according to the logical operator (AND/OR)\n * and returns whether the field should be visible based on the action\n * (show/hide).\n *\n * @param logic - The conditional logic configuration\n * @param formData - The current form data (field values)\n * @returns Whether the field should be visible\n *\n * @example\n * ```typescript\n * // Show field when country is US AND age is over 18\n * const logic: ConditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'AND',\n * conditions: [\n * { field: 'country', comparison: 'equals', value: 'US' },\n * { field: 'age', comparison: 'greaterThan', value: 18 },\n * ],\n * };\n *\n * evaluateConditions(logic, { country: 'US', age: 25 }); // true\n * evaluateConditions(logic, { country: 'US', age: 15 }); // false\n * evaluateConditions(logic, { country: 'CA', age: 25 }); // false\n *\n * // Hide field when status is \"completed\"\n * const hideLogic: ConditionalLogic = {\n * enabled: true,\n * action: 'hide',\n * operator: 'AND',\n * conditions: [\n * { field: 'status', comparison: 'equals', value: 'completed' },\n * ],\n * };\n *\n * evaluateConditions(hideLogic, { status: 'completed' }); // false (hidden)\n * evaluateConditions(hideLogic, { status: 'pending' }); // true (visible)\n * ```\n *\n * @example\n * ```typescript\n * // Show field when either condition is met (OR)\n * const orLogic: ConditionalLogic = {\n * enabled: true,\n * action: 'show',\n * operator: 'OR',\n * conditions: [\n * { field: 'role', comparison: 'equals', value: 'admin' },\n * { field: 'role', comparison: 'equals', value: 'editor' },\n * ],\n * };\n *\n * evaluateConditions(orLogic, { role: 'admin' }); // true\n * evaluateConditions(orLogic, { role: 'editor' }); // true\n * evaluateConditions(orLogic, { role: 'viewer' }); // false\n * ```\n */\nexport function evaluateConditions(\n logic: ConditionalLogic | undefined,\n formData: Record<string, unknown>\n): boolean {\n // If no logic provided or logic is disabled, field is visible by default\n if (!logic || !logic.enabled) {\n return true;\n }\n\n const { action, operator, conditions } = logic;\n\n // If no conditions, field is visible by default\n if (!conditions || conditions.length === 0) {\n return true;\n }\n\n // Evaluate all conditions\n const results = conditions.map(condition =>\n evaluateSingleCondition(condition, formData)\n );\n\n // Combine results based on logical operator\n let conditionsMet: boolean;\n\n if (operator === \"OR\") {\n // OR: At least one condition must be true\n conditionsMet = results.some(result => result);\n } else {\n // AND (default): All conditions must be true\n conditionsMet = results.every(result => result);\n }\n\n // Apply the action\n // - \"show\": field is visible when conditions are met\n // - \"hide\": field is visible when conditions are NOT met\n if (action === \"hide\") {\n return !conditionsMet;\n }\n\n // Default action is \"show\"\n return conditionsMet;\n}\n\n/**\n * Checks if a comparison operator is valid.\n *\n * @param operator - The operator to check\n * @returns Whether the operator is valid\n */\nexport function isValidComparisonOperator(\n operator: string\n): operator is ComparisonOperator {\n const validOperators: ComparisonOperator[] = [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n ];\n return validOperators.includes(operator as ComparisonOperator);\n}\n\n/**\n * Gets the list of supported comparison operators.\n *\n * @returns Array of supported operator names\n */\nexport function getSupportedComparisonOperators(): ComparisonOperator[] {\n return [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n ];\n}\n","/**\n * Export Formats Utilities\n *\n * Pure utility functions for exporting form submissions to CSV and JSON formats.\n * These functions generate formatted strings that can be used for file downloads\n * or API responses.\n *\n * @module utils/export-formats\n * @since 0.1.0\n */\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Options for CSV export.\n */\nexport interface CSVExportOptions {\n /**\n * Whether to include metadata columns (_id, _status, _submittedAt, etc.).\n * @default true\n */\n includeMetadata?: boolean;\n\n /**\n * Delimiter character for CSV.\n * @default \",\"\n */\n delimiter?: string;\n\n /**\n * Whether to include UTF-8 BOM for Excel compatibility.\n * @default true\n */\n includeBOM?: boolean;\n\n /**\n * Date format for timestamps.\n * @default \"iso\" (ISO 8601)\n */\n dateFormat?: \"iso\" | \"locale\";\n}\n\n/**\n * Options for JSON export.\n */\nexport interface JSONExportOptions {\n /**\n * Whether to include metadata in each submission.\n * @default true\n */\n includeMetadata?: boolean;\n\n /**\n * Whether to include form definition in the export.\n * @default true\n */\n includeFormDefinition?: boolean;\n\n /**\n * Number of spaces for indentation (0 for minified).\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Exported JSON structure.\n */\nexport interface ExportedJSON {\n /** Export metadata */\n exportedAt: string;\n\n /** Form information (if includeFormDefinition is true) */\n form?: {\n id: string;\n name: string;\n slug: string;\n fields: Array<{\n name: string;\n label: string;\n type: string;\n }>;\n };\n\n /** Total number of submissions in export */\n totalSubmissions: number;\n\n /** Array of submission data */\n submissions: Array<Record<string, unknown>>;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** UTF-8 BOM character sequence */\nconst UTF8_BOM = \"\\uFEFF\";\n\n// ============================================================================\n// Value Formatting Helpers\n// ============================================================================\n\n/**\n * Stringify a value safely, falling back to JSON for plain objects so we\n * never emit the default `[object Object]` representation.\n */\nfunction safeString(value: unknown): string {\n if (typeof value === \"object\" && value !== null) {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\n/**\n * Format a field value for export based on field type.\n *\n * @param value - The raw value from submission data\n * @param field - The field definition (optional, for type-specific formatting)\n * @returns Formatted string representation of the value\n *\n * @example\n * ```typescript\n * formatExportValue(true, { type: 'checkbox' }); // \"Yes\"\n * formatExportValue(['a', 'b'], { type: 'select' }); // \"a, b\"\n * formatExportValue(new Date(), { type: 'date' }); // \"2024-01-15\"\n * ```\n */\nexport function formatExportValue(value: unknown, field?: FormField): string {\n // Handle null/undefined\n if (value === null || value === undefined) {\n return \"\";\n }\n\n // Type-specific formatting when field definition is available\n if (field) {\n switch (field.type) {\n case \"checkbox\":\n return value ? \"Yes\" : \"No\";\n\n case \"select\":\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n return safeString(value);\n\n case \"file\":\n if (Array.isArray(value)) {\n // File URLs or IDs\n return value.map(v => safeString(v)).join(\", \");\n }\n return safeString(value);\n\n case \"date\":\n if (value instanceof Date) {\n return value.toISOString().split(\"T\")[0];\n }\n if (typeof value === \"string\") {\n // Already a date string, return as-is or normalize\n const parsed = new Date(value);\n if (!isNaN(parsed.getTime())) {\n return parsed.toISOString().split(\"T\")[0];\n }\n }\n return safeString(value);\n\n case \"time\":\n return safeString(value);\n\n case \"hidden\":\n return safeString(value);\n\n default:\n // text, email, phone, url, textarea, number, radio\n break;\n }\n }\n\n // Generic formatting for values without field definition\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"Yes\" : \"No\";\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n return safeString(value);\n}\n\n/**\n * Format a date for export.\n *\n * @param date - Date value (Date object or string)\n * @param format - Output format (\"iso\" or \"locale\")\n * @returns Formatted date string\n */\nfunction formatDate(\n date: Date | string | undefined,\n format: \"iso\" | \"locale\" = \"iso\"\n): string {\n if (!date) return \"\";\n\n const d = typeof date === \"string\" ? new Date(date) : date;\n\n if (isNaN(d.getTime())) {\n return \"\";\n }\n\n return format === \"iso\" ? d.toISOString() : d.toLocaleString();\n}\n\n// ============================================================================\n// CSV Export\n// ============================================================================\n\n/**\n * Escape a value for CSV format (RFC 4180 compliant).\n *\n * Rules:\n * - Fields containing comma, double-quote, or newline must be quoted\n * - Double-quotes within fields must be escaped with another double-quote\n *\n * @param value - The value to escape\n * @param delimiter - The delimiter being used\n * @returns Properly escaped CSV value\n */\nfunction escapeCSVValue(value: string, delimiter: string = \",\"): string {\n // CSV injection: spreadsheets execute cells starting with a formula\n // trigger character, and syntax quoting does NOT stop that. A leading\n // apostrophe forces text interpretation (the OWASP mitigation). Plain\n // negative numbers are exempt — they are data, not formulas.\n const isFormulaLike =\n /^[=+\\-@\\t\\r]/.test(value) && !/^-?\\d+(\\.\\d+)?$/.test(value);\n const escapedFormula = isFormulaLike ? `'${value}` : value;\n\n // Check if quoting is needed\n const needsQuoting =\n escapedFormula.includes(delimiter) ||\n escapedFormula.includes('\"') ||\n escapedFormula.includes(\"\\n\") ||\n escapedFormula.includes(\"\\r\");\n\n if (!needsQuoting) {\n return escapedFormula;\n }\n\n // Escape double quotes by doubling them\n const escaped = escapedFormula.replace(/\"/g, '\"\"');\n return `\"${escaped}\"`;\n}\n\n/**\n * Export submissions to CSV format.\n *\n * Generates an RFC 4180 compliant CSV string with:\n * - Form field values as columns (in form definition order)\n * - Optional metadata columns at the end (_id, _status, _submittedAt, etc.)\n * - Proper escaping of special characters\n * - Optional UTF-8 BOM for Excel compatibility\n *\n * @param submissions - Array of submission documents to export\n * @param form - Form document containing field definitions\n * @param options - Export options\n * @returns CSV formatted string\n *\n * @example\n * ```typescript\n * const csv = exportToCSV(submissions, form, {\n * includeMetadata: true,\n * includeBOM: true,\n * });\n *\n * // Download in browser\n * downloadFile(csv, 'submissions.csv', 'text/csv;charset=utf-8');\n * ```\n */\nexport function exportToCSV(\n submissions: SubmissionDocument[],\n form: FormDocument,\n options: CSVExportOptions = {}\n): string {\n const {\n includeMetadata = true,\n delimiter = \",\",\n includeBOM = true,\n dateFormat = \"iso\",\n } = options;\n\n const exportableFields = form.fields;\n\n // Build header row\n const headers: string[] = [];\n\n // Form field headers (using labels)\n for (const field of exportableFields) {\n headers.push(field.label);\n }\n\n // Metadata headers\n if (includeMetadata) {\n headers.push(\"ID\", \"Status\", \"Submitted At\", \"IP Address\", \"User Agent\");\n }\n\n // Build data rows\n const rows: string[][] = [];\n\n for (const submission of submissions) {\n const row: string[] = [];\n const data = submission.data;\n\n // Form field values\n for (const field of exportableFields) {\n const value = data[field.name];\n const formatted = formatExportValue(value, field);\n row.push(escapeCSVValue(formatted, delimiter));\n }\n\n // Metadata values\n if (includeMetadata) {\n row.push(escapeCSVValue(submission.id, delimiter));\n row.push(escapeCSVValue(submission.status, delimiter));\n row.push(\n escapeCSVValue(\n formatDate(submission.submittedAt, dateFormat),\n delimiter\n )\n );\n row.push(escapeCSVValue(submission.ipAddress || \"\", delimiter));\n row.push(escapeCSVValue(submission.userAgent || \"\", delimiter));\n }\n\n rows.push(row);\n }\n\n // Escape headers\n const escapedHeaders = headers.map(h => escapeCSVValue(h, delimiter));\n\n // Build CSV string\n const lines = [\n escapedHeaders.join(delimiter),\n ...rows.map(row => row.join(delimiter)),\n ];\n\n // Use CRLF line endings for maximum compatibility\n const csvContent = lines.join(\"\\r\\n\");\n\n // Prepend BOM if requested\n return includeBOM ? UTF8_BOM + csvContent : csvContent;\n}\n\n// ============================================================================\n// JSON Export\n// ============================================================================\n\n/**\n * Export submissions to JSON format.\n *\n * Generates a structured JSON export with:\n * - Export metadata (timestamp)\n * - Optional form definition\n * - Array of submissions with field values\n * - Optional submission metadata (_id, _status, etc.)\n *\n * @param submissions - Array of submission documents to export\n * @param form - Form document containing field definitions\n * @param options - Export options\n * @returns JSON formatted string\n *\n * @example\n * ```typescript\n * const json = exportToJSON(submissions, form, {\n * includeMetadata: true,\n * includeFormDefinition: true,\n * indent: 2,\n * });\n *\n * // Download in browser\n * downloadFile(json, 'submissions.json', 'application/json');\n * ```\n */\nexport function exportToJSON(\n submissions: SubmissionDocument[],\n form: FormDocument,\n options: JSONExportOptions = {}\n): string {\n const {\n includeMetadata = true,\n includeFormDefinition = true,\n indent = 2,\n } = options;\n\n const exportableFields = form.fields;\n\n // Build form definition\n const formDefinition = includeFormDefinition\n ? {\n id: form.id,\n name: form.name,\n slug: form.slug,\n fields: exportableFields.map(f => ({\n name: f.name,\n label: f.label,\n type: f.type,\n })),\n }\n : undefined;\n\n // Build submission data\n const exportedSubmissions = submissions.map(submission => {\n const data = submission.data;\n\n // Build field data object with labels as keys for readability\n const fieldData: Record<string, unknown> = {};\n\n for (const field of exportableFields) {\n const value = data[field.name];\n // Use field name as key (consistent with submission data)\n fieldData[field.name] = value;\n }\n\n // Add metadata if requested\n if (includeMetadata) {\n return {\n ...fieldData,\n _metadata: {\n id: submission.id,\n status: submission.status,\n submittedAt: submission.submittedAt,\n ipAddress: submission.ipAddress || null,\n userAgent: submission.userAgent || null,\n createdAt: submission.createdAt,\n updatedAt: submission.updatedAt,\n },\n };\n }\n\n return fieldData;\n });\n\n // Build export object\n const exportData: ExportedJSON = {\n exportedAt: new Date().toISOString(),\n totalSubmissions: submissions.length,\n submissions: exportedSubmissions,\n };\n\n // Add form definition if requested\n if (formDefinition) {\n exportData.form = formDefinition;\n }\n\n return JSON.stringify(exportData, null, indent);\n}\n\n// ============================================================================\n// Browser Download Helper\n// ============================================================================\n\n/**\n * Trigger a file download in the browser.\n *\n * Creates a Blob from the content and triggers a download using a temporary\n * anchor element. This function is browser-only and will throw in Node.js.\n *\n * @param content - File content as string\n * @param filename - Name for the downloaded file\n * @param mimeType - MIME type of the file\n *\n * @example\n * ```typescript\n * const csv = exportToCSV(submissions, form);\n * downloadFile(csv, 'contact-form-submissions.csv', 'text/csv;charset=utf-8');\n *\n * const json = exportToJSON(submissions, form);\n * downloadFile(json, 'submissions.json', 'application/json');\n * ```\n */\nexport function downloadFile(\n content: string,\n filename: string,\n mimeType: string\n): void {\n // Check for browser environment\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n throw new Error(\"downloadFile is only available in browser environments\");\n }\n\n // Create blob\n const blob = new Blob([content], { type: mimeType });\n\n // Create download URL\n const url = URL.createObjectURL(blob);\n\n // Create temporary anchor element\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = filename;\n\n // Trigger download\n document.body.appendChild(link);\n link.click();\n\n // Cleanup\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Generate a filename for export.\n *\n * Creates a filename with form slug and current timestamp.\n *\n * @param formSlug - The form's slug\n * @param format - Export format ('csv' or 'json')\n * @returns Generated filename\n *\n * @example\n * ```typescript\n * generateExportFilename('contact-form', 'csv');\n * // Returns: \"contact-form-submissions-2024-01-15.csv\"\n * ```\n */\nexport function generateExportFilename(\n formSlug: string,\n format: \"csv\" | \"json\"\n): string {\n // The name ends up inside a Content-Disposition header — restrict it to\n // characters that can never break the header value or the filesystem.\n const safeSlug =\n formSlug.toLowerCase().replace(/[^a-z0-9_-]+/g, \"-\") || \"form\";\n const date = new Date().toISOString().split(\"T\")[0];\n return `${safeSlug}-submissions-${date}.${format}`;\n}\n\n// ============================================================================\n// Convenience Export Functions\n// ============================================================================\n\n/**\n * Export submissions and trigger browser download.\n *\n * Convenience function that combines export and download in one call.\n * Browser-only.\n *\n * @param submissions - Array of submission documents\n * @param form - Form document\n * @param format - Export format ('csv' or 'json')\n * @param options - Format-specific options\n *\n * @example\n * ```typescript\n * // In a React component\n * const handleExport = (format: 'csv' | 'json') => {\n * exportAndDownload(submissions, form, format);\n * };\n * ```\n */\nexport function exportAndDownload(\n submissions: SubmissionDocument[],\n form: FormDocument,\n format: \"csv\" | \"json\",\n options?: CSVExportOptions | JSONExportOptions\n): void {\n const filename = generateExportFilename(form.slug, format);\n\n if (format === \"csv\") {\n const content = exportToCSV(submissions, form, options);\n downloadFile(content, filename, \"text/csv;charset=utf-8\");\n } else {\n const content = exportToJSON(submissions, form, options);\n downloadFile(content, filename, \"application/json\");\n }\n}\n","/**\n * Form Configuration Defaults\n *\n * Applies default values to form configurations, ensuring all required\n * properties have sensible defaults while preserving user-specified values.\n *\n * @module config/defaults\n * @since 1.0.0\n *\n * @example\n * ```typescript\n * import { applyFormDefaults } from '@nextly/plugin-form-builder';\n *\n * const config = {\n * slug: 'contact-form',\n * fields: [\n * { type: 'text', name: 'name', label: 'Name' },\n * { type: 'email', name: 'email', label: 'Email' },\n * ],\n * };\n *\n * const configWithDefaults = applyFormDefaults(config);\n * // configWithDefaults now has labels, settings, etc. with defaults applied\n * ```\n */\n\nimport type { FormConfig, FormSettings, FormNotifications } from \"../types\";\n\n// ============================================================\n// Default Values\n// ============================================================\n\n/**\n * Default form settings.\n */\nexport const DEFAULT_FORM_SETTINGS: Required<\n Pick<\n FormSettings,\n | \"submitButtonText\"\n | \"confirmationType\"\n | \"successMessage\"\n | \"allowMultipleSubmissions\"\n >\n> = {\n submitButtonText: \"Submit\",\n confirmationType: \"message\",\n successMessage: \"Thank you for your submission!\",\n allowMultipleSubmissions: true,\n};\n\n/**\n * Default notification settings.\n */\nexport const DEFAULT_NOTIFICATION_SETTINGS: Pick<\n FormNotifications,\n \"enabled\" | \"replyTo\"\n> = {\n enabled: true,\n replyTo: true,\n};\n\n// ============================================================\n// Helper Functions\n// ============================================================\n\n/**\n * Converts a slug to title case.\n *\n * @param str - The slug string to convert\n * @returns Title case string\n *\n * @example\n * ```typescript\n * toTitleCase('contact-form'); // 'Contact Form'\n * toTitleCase('newsletter_signup'); // 'Newsletter Signup'\n * ```\n */\nexport function toTitleCase(str: string): string {\n return str.replace(/[-_]/g, \" \").replace(/\\b\\w/g, char => char.toUpperCase());\n}\n\n/**\n * Generates a plural form from a singular label.\n * Handles common English pluralization rules.\n *\n * @param singular - The singular form\n * @returns The plural form\n *\n * @example\n * ```typescript\n * pluralize('Form'); // 'Forms'\n * pluralize('Category'); // 'Categories'\n * pluralize('Quiz'); // 'Quizzes'\n * ```\n */\nexport function pluralize(singular: string): string {\n if (singular.endsWith(\"y\") && !/[aeiou]y$/i.test(singular)) {\n return singular.slice(0, -1) + \"ies\";\n }\n if (\n singular.endsWith(\"s\") ||\n singular.endsWith(\"x\") ||\n singular.endsWith(\"z\") ||\n singular.endsWith(\"ch\") ||\n singular.endsWith(\"sh\")\n ) {\n return singular + \"es\";\n }\n return singular + \"s\";\n}\n\n// ============================================================\n// Main Default Application Function\n// ============================================================\n\n/**\n * Apply default values to form configuration.\n *\n * This function takes a partial form configuration and returns a complete\n * configuration with all default values applied. User-specified values\n * are preserved and take precedence over defaults.\n *\n * @param config - Form configuration (may be partial)\n * @returns Configuration with defaults applied\n *\n * @example\n * ```typescript\n * import { applyFormDefaults } from '@nextly/plugin-form-builder';\n *\n * const config = {\n * slug: 'contact-form',\n * fields: [\n * { type: 'text', name: 'name', label: 'Name' },\n * { type: 'email', name: 'email', label: 'Email' },\n * ],\n * };\n *\n * const result = applyFormDefaults(config);\n *\n * // Result:\n * // {\n * // slug: 'contact-form',\n * // labels: { singular: 'Contact Form', plural: 'Contact Forms' },\n * // fields: [...],\n * // settings: {\n * // submitButtonText: 'Submit',\n * // confirmationType: 'message',\n * // successMessage: 'Thank you for your submission!',\n * // allowMultipleSubmissions: true,\n * // },\n * // access: {\n * // submit: () => true,\n * // read: ({ req }) => !!req.user,\n * // },\n * // }\n * ```\n */\nexport function applyFormDefaults(config: FormConfig): FormConfig {\n // Generate singular label from slug if not provided\n const singularLabel = config.labels?.singular || toTitleCase(config.slug);\n\n return {\n ...config,\n\n // Generate labels from slug if not provided\n labels: {\n singular: singularLabel,\n plural: config.labels?.plural || pluralize(singularLabel),\n },\n\n // Apply settings defaults\n settings: {\n submitButtonText:\n config.settings?.submitButtonText ||\n DEFAULT_FORM_SETTINGS.submitButtonText,\n confirmationType:\n config.settings?.confirmationType ||\n DEFAULT_FORM_SETTINGS.confirmationType,\n successMessage:\n config.settings?.successMessage || DEFAULT_FORM_SETTINGS.successMessage,\n allowMultipleSubmissions:\n config.settings?.allowMultipleSubmissions ??\n DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,\n // Preserve other settings that don't have defaults\n redirectUrl: config.settings?.redirectUrl,\n redirectRelation: config.settings?.redirectRelation,\n honeypotEnabled: config.settings?.honeypotEnabled,\n captchaEnabled: config.settings?.captchaEnabled,\n captchaSiteKey: config.settings?.captchaSiteKey,\n },\n\n // Apply notification defaults if notifications are configured\n notifications: config.notifications\n ? {\n enabled:\n config.notifications.enabled ??\n DEFAULT_NOTIFICATION_SETTINGS.enabled,\n recipients: config.notifications.recipients || [],\n subject:\n config.notifications.subject ||\n `New submission for ${singularLabel}`,\n replyTo:\n config.notifications.replyTo ??\n DEFAULT_NOTIFICATION_SETTINGS.replyTo,\n template: config.notifications.template,\n }\n : undefined,\n\n // Apply access defaults\n access: {\n // Default: anyone can submit the form\n submit: config.access?.submit || (() => true),\n // Default: only authenticated users can view submissions\n read: config.access?.read || (({ req }) => !!req.user),\n },\n };\n}\n\n/**\n * Apply default values to individual form field.\n *\n * This is a utility function for applying defaults to a single field,\n * useful when processing fields individually (e.g., in the Schema Builder).\n *\n * @param field - The field to apply defaults to\n * @returns Field with defaults applied\n *\n * @example\n * ```typescript\n * import { applyFieldDefaults } from '@nextly/plugin-form-builder';\n *\n * const field = { type: 'text', name: 'firstName' };\n * const fieldWithDefaults = applyFieldDefaults(field);\n * // { type: 'text', name: 'firstName', label: 'First Name', required: false }\n * ```\n */\nexport function applyFieldDefaults<\n T extends { type: string; name?: string; label?: string; required?: boolean },\n>(field: T): T {\n // Generate label from name if not provided\n const label =\n field.label ||\n (field.name\n ? toTitleCase(field.name.replace(/([A-Z])/g, \" $1\").trim())\n : \"\");\n\n return {\n ...field,\n label,\n required: field.required ?? false,\n };\n}\n\n/**\n * Creates a complete form config with minimal input.\n *\n * This is a convenience function for quickly creating form configs\n * with sensible defaults. Only the slug and fields are required.\n *\n * @param slug - The form slug\n * @param fields - Array of form fields\n * @param options - Optional additional configuration\n * @returns Complete form configuration\n *\n * @example\n * ```typescript\n * import { createFormConfig } from '@nextly/plugin-form-builder';\n *\n * const config = createFormConfig('contact', [\n * { type: 'text', name: 'name', label: 'Name', required: true },\n * { type: 'email', name: 'email', label: 'Email', required: true },\n * { type: 'textarea', name: 'message', label: 'Message' },\n * ]);\n * ```\n */\nexport function createFormConfig(\n slug: string,\n fields: FormConfig[\"fields\"],\n options?: Partial<Omit<FormConfig, \"slug\" | \"fields\">>\n): FormConfig {\n return applyFormDefaults({\n slug,\n fields,\n ...options,\n });\n}\n","/**\n * The ONE reader for stored form settings. Settings were historically\n * written in two shapes (the builder's old context shape and the collection\n * group's declared shape), so every consumer — the builder UI and the\n * submit handler alike — normalizes through here instead of trusting the\n * stored keys.\n */\n\nimport { DEFAULT_FORM_SETTINGS } from \"../config/defaults\";\nimport type { FormSettings } from \"../types\";\n\nexport { DEFAULT_FORM_SETTINGS };\n\nfunction asOptionalBoolean(value: unknown): boolean | undefined {\n return typeof value === \"boolean\" ? value : undefined;\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\n/**\n * Normalize a stored settings value into the canonical {@link FormSettings}.\n *\n * Legacy keys are migrated on read (no data is lost, nothing is rewritten\n * until the form is next saved):\n * - `confirmationMessage` → `successMessage` + `confirmationType: \"message\"`\n * - nested `captcha: { enabled, siteKey }` → flat `captchaEnabled`/`captchaSiteKey`\n *\n * Keys with no consumer (`showResetButton`, `resetButtonText`,\n * `storeSubmissions`, `submissionLimit`) are dropped: a setting that does\n * nothing must not survive into the UI.\n */\nexport function normalizeFormSettings(raw: unknown): FormSettings {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return { ...DEFAULT_FORM_SETTINGS };\n }\n const source = raw as Record<string, unknown>;\n\n const legacyMessage = asOptionalString(source.confirmationMessage);\n const legacyCaptcha =\n source.captcha && typeof source.captcha === \"object\"\n ? (source.captcha as { enabled?: unknown; siteKey?: unknown })\n : undefined;\n\n // \"relationship\" is a real stored value (redirect to a linked document) —\n // coercing it to \"message\" would disable the form's redirect on the next\n // save. Only genuinely unknown values fall back to \"message\".\n const confirmationType =\n source.confirmationType === \"redirect\" ||\n source.confirmationType === \"relationship\"\n ? source.confirmationType\n : \"message\";\n\n return {\n submitButtonText:\n asOptionalString(source.submitButtonText) ??\n DEFAULT_FORM_SETTINGS.submitButtonText,\n confirmationType,\n successMessage:\n asOptionalString(source.successMessage) ??\n legacyMessage ??\n DEFAULT_FORM_SETTINGS.successMessage,\n redirectUrl: asOptionalString(source.redirectUrl),\n redirectPage: source.redirectPage ?? undefined,\n redirectRelation:\n source.redirectRelation && typeof source.redirectRelation === \"object\"\n ? (source.redirectRelation as FormSettings[\"redirectRelation\"])\n : undefined,\n allowMultipleSubmissions:\n asOptionalBoolean(source.allowMultipleSubmissions) ??\n DEFAULT_FORM_SETTINGS.allowMultipleSubmissions,\n honeypotEnabled: asOptionalBoolean(source.honeypotEnabled),\n captchaEnabled:\n asOptionalBoolean(source.captchaEnabled) ??\n asOptionalBoolean(legacyCaptcha?.enabled),\n captchaSiteKey:\n asOptionalString(source.captchaSiteKey) ??\n asOptionalString(legacyCaptcha?.siteKey),\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as nextly from 'nextly';
2
2
  import { CollectionConfig, PluginContext } from 'nextly';
3
3
  import { PluginDefinition } from '@nextlyhq/plugin-sdk';
4
- import { R as ResolvedFormBuilderConfig, F as FormBuilderPluginOptions, a as FormConfig, b as FormSettings, c as FormNotifications, C as CheckboxFormField, D as DateFormField, E as EmailFormField, d as FileFormField, H as HiddenFormField, N as NumberFormField, P as PhoneFormField, e as RadioFormField, S as SelectFormField, T as TextFormField, f as TextareaFormField, g as TimeFormField, U as UrlFormField, h as ConditionalLogic, i as FormField, j as SubmissionDocument, k as FormDocument, W as WebhookEvent } from './index-BRumOEsE.js';
5
- export { B as BaseFormField, l as ConditionalLogicCondition, m as EmailConfig, n as FieldBlockConfig, o as FormAccess, p as FormFieldType, q as FormHookContext, r as FormHooks, s as FormSubmission, t as SubmissionDetailProps, u as SubmissionListProps, V as ValidationRules, v as WebhookConfig, w as isCheckboxFormField, x as isEmailFormField, y as isFileFormField, z as isFormField, A as isFormFieldType, G as isSelectFormField, I as isTextFormField } from './index-BRumOEsE.js';
4
+ import { R as ResolvedFormBuilderConfig, F as FormBuilderPluginOptions, a as FormConfig, b as FormSettings, c as FormNotifications, C as CheckboxFormField, D as DateFormField, E as EmailFormField, d as FileFormField, H as HiddenFormField, N as NumberFormField, P as PhoneFormField, e as RadioFormField, S as SelectFormField, T as TextFormField, f as TextareaFormField, g as TimeFormField, U as UrlFormField, h as ConditionalLogic, i as FormField, j as SubmissionDocument, k as FormDocument, W as WebhookEvent } from './types-C-DcuYGF.js';
5
+ export { B as BaseFormField, l as ConditionalLogicCondition, m as EmailConfig, n as FieldBlockConfig, o as FormAccess, p as FormFieldType, q as FormHookContext, r as FormHooks, s as FormNotification, t as FormNotificationItem, u as FormSubmission, V as ValidationRules, v as WebhookConfig, w as isCheckboxFormField, x as isEmailFormField, y as isFileFormField, z as isFormField, A as isFormFieldType, G as isSelectFormField, I as isTextFormField } from './types-C-DcuYGF.js';
6
6
  import { z } from 'zod';
7
7
  import 'react';
8
8
 
@@ -929,14 +929,14 @@ declare function transformFormData(data: Record<string, unknown>, fields: FormFi
929
929
  * }
930
930
  * ```
931
931
  */
932
- declare function validateFormData(data: Record<string, unknown>, fields: FormField[]): z.SafeParseReturnType<Record<string, unknown>, Record<string, unknown>>;
932
+ declare function validateFormData(data: Record<string, unknown>, fields: FormField[]): z.ZodSafeParseResult<Record<string, unknown>>;
933
933
  /**
934
934
  * Get validation errors as a flat object.
935
935
  *
936
936
  * @param result - Zod safe parse result
937
937
  * @returns Object mapping field names to error messages
938
938
  */
939
- declare function getValidationErrors(result: z.SafeParseReturnType<unknown, unknown>): Record<string, string>;
939
+ declare function getValidationErrors(result: z.ZodSafeParseResult<unknown>): Record<string, string>;
940
940
 
941
941
  /**
942
942
  * Export Formats Utilities