@nextlyhq/plugin-form-builder 0.0.2-alpha.35 → 0.0.2-alpha.39
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/dist/admin/index.d.ts +31 -22
- package/dist/admin/index.js +178 -120
- package/dist/admin/index.js.map +1 -1
- package/dist/{chunk-I4VFGPNR.js → chunk-WVI7N3QO.js} +54 -1
- package/dist/chunk-WVI7N3QO.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +24 -41
- package/dist/index.js.map +1 -1
- package/dist/{types-C-DcuYGF.d.ts → types-r8uBeB0u.d.ts} +37 -4
- package/package.json +11 -11
- package/dist/chunk-I4VFGPNR.js.map +0 -1
|
@@ -117,7 +117,7 @@ interface FormDocument {
|
|
|
117
117
|
id: string;
|
|
118
118
|
slug: string;
|
|
119
119
|
name: string;
|
|
120
|
-
fields:
|
|
120
|
+
fields: AnyFormField[];
|
|
121
121
|
settings: FormSettings;
|
|
122
122
|
notifications: FormNotification[];
|
|
123
123
|
webhooks?: WebhookConfig[];
|
|
@@ -443,7 +443,7 @@ interface FormConfig {
|
|
|
443
443
|
/**
|
|
444
444
|
* Form fields definition.
|
|
445
445
|
*/
|
|
446
|
-
fields:
|
|
446
|
+
fields: AnyFormField[];
|
|
447
447
|
/**
|
|
448
448
|
* Form settings and behavior.
|
|
449
449
|
*/
|
|
@@ -471,9 +471,32 @@ interface FormConfig {
|
|
|
471
471
|
*/
|
|
472
472
|
type FormField = TextFormField | EmailFormField | NumberFormField | PhoneFormField | UrlFormField | TextareaFormField | SelectFormField | CheckboxFormField | RadioFormField | FileFormField | DateFormField | TimeFormField | HiddenFormField;
|
|
473
473
|
/**
|
|
474
|
-
*
|
|
474
|
+
* A form field of a built-in type OR a plugin-contributed type. Stored form
|
|
475
|
+
* data and the builder work with this; `isKnownFormField` narrows it back to
|
|
476
|
+
* the closed `FormField` union so `switch (field.type)` keeps discriminating
|
|
477
|
+
* the built-ins (a `CustomFormField` in the union itself would collapse that).
|
|
478
|
+
*/
|
|
479
|
+
type AnyFormField = FormField | CustomFormField;
|
|
480
|
+
/**
|
|
481
|
+
* All available built-in form field type identifiers.
|
|
475
482
|
*/
|
|
476
483
|
type FormFieldType = "text" | "email" | "number" | "phone" | "url" | "textarea" | "select" | "checkbox" | "radio" | "file" | "date" | "time" | "hidden";
|
|
484
|
+
/**
|
|
485
|
+
* A form field's type on the wire: a built-in or a plugin-contributed type id
|
|
486
|
+
* that opted into the forms surface. The `(string & {})` arm admits the plugin
|
|
487
|
+
* id while keeping built-in autocomplete. Built-in `case` narrowing still works
|
|
488
|
+
* because every field-type switch keeps a `default` for the custom arm.
|
|
489
|
+
*/
|
|
490
|
+
type FormFieldTypeId = FormFieldType | (string & {});
|
|
491
|
+
/** Every built-in form field type, as a value list (the single source shared
|
|
492
|
+
* by the field-type guard and the config validator). */
|
|
493
|
+
declare const BUILT_IN_FORM_FIELD_TYPES: readonly FormFieldType[];
|
|
494
|
+
/**
|
|
495
|
+
* Narrow an `AnyFormField` to the closed built-in `FormField` union. Returns
|
|
496
|
+
* `false` for a plugin-contributed field, so callers handle it generically
|
|
497
|
+
* before switching on the built-in discriminant.
|
|
498
|
+
*/
|
|
499
|
+
declare function isKnownFormField(field: AnyFormField): field is FormField;
|
|
477
500
|
/**
|
|
478
501
|
* Base form field properties.
|
|
479
502
|
* All form field types extend this interface.
|
|
@@ -505,6 +528,16 @@ interface BaseFormField {
|
|
|
505
528
|
width?: "25%" | "33%" | "50%" | "66%" | "75%" | "100%";
|
|
506
529
|
};
|
|
507
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* A plugin-contributed form field type (outside the built-in union). Carries
|
|
533
|
+
* only the base properties — the plugin owns its editor component and value
|
|
534
|
+
* semantics — and its `type` is the plugin's registered field-type id. Kept a
|
|
535
|
+
* distinct union member so built-in `case` narrowing is unaffected while a
|
|
536
|
+
* plugin field still type-checks as a `FormField`.
|
|
537
|
+
*/
|
|
538
|
+
interface CustomFormField extends Omit<BaseFormField, "type"> {
|
|
539
|
+
type: string & {};
|
|
540
|
+
}
|
|
508
541
|
/**
|
|
509
542
|
* Text input field.
|
|
510
543
|
*/
|
|
@@ -977,4 +1010,4 @@ declare function isCheckboxFormField(field: FormField): field is CheckboxFormFie
|
|
|
977
1010
|
*/
|
|
978
1011
|
declare function isFileFormField(field: FormField): field is FileFormField;
|
|
979
1012
|
|
|
980
|
-
export {
|
|
1013
|
+
export { type AnyFormField as A, BUILT_IN_FORM_FIELD_TYPES as B, type CheckboxFormField as C, type DateFormField as D, type EmailFormField as E, type FormBuilderPluginOptions as F, isEmailFormField as G, type HiddenFormField as H, isFileFormField as I, isFormField as J, isFormFieldType as K, isKnownFormField as L, isSelectFormField as M, type NumberFormField as N, isTextFormField as O, type PhoneFormField as P, type ResolvedFormBuilderConfig as R, type SelectFormField as S, type TextFormField as T, type UrlFormField as U, type ValidationRules as V, type WebhookEvent as W, type FormConfig as a, type FormSettings as b, type FormNotifications as c, type FileFormField as d, type RadioFormField as e, type TextareaFormField as f, type TimeFormField as g, type ConditionalLogic as h, type SubmissionDocument as i, type FormDocument as j, type BaseFormField as k, type ConditionalLogicCondition as l, type CustomFormField as m, type EmailConfig as n, type FieldBlockConfig as o, type FormAccess as p, type FormField as q, type FormFieldType as r, type FormFieldTypeId as s, type FormHookContext as t, type FormHooks as u, type FormNotification as v, type FormNotificationItem as w, type FormSubmission as x, type WebhookConfig as y, isCheckboxFormField as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextlyhq/plugin-form-builder",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.39",
|
|
4
4
|
"description": "Visual form builder plugin for Nextly with Code-First and Schema Builder support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"zod": "^4.1.12"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@nextlyhq/admin": "0.0.2-alpha.
|
|
35
|
-
"@nextlyhq/ui": "0.0.2-alpha.35",
|
|
34
|
+
"@nextlyhq/admin": "0.0.2-alpha.39",
|
|
36
35
|
"@tanstack/react-query": ">=5.0.0",
|
|
37
36
|
"lucide-react": ">=0.400.0",
|
|
38
37
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
39
|
-
"nextly": "0.0.2-alpha.
|
|
38
|
+
"nextly": "0.0.2-alpha.39",
|
|
40
39
|
"react": "^18.0.0 || ^19.0.0",
|
|
41
|
-
"@nextlyhq/plugin-sdk": "0.0.2-alpha.
|
|
40
|
+
"@nextlyhq/plugin-sdk": "0.0.2-alpha.39",
|
|
41
|
+
"@nextlyhq/ui": "^0.0.2-alpha.39"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@tanstack/react-query": "^5.62.0",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"lucide-react": "^0.544.0",
|
|
48
48
|
"tsup": "^8.5.0",
|
|
49
49
|
"typescript": "^5.9.3",
|
|
50
|
-
"vitest": "^4.0
|
|
51
|
-
"@nextlyhq/admin": "0.0.2-alpha.
|
|
52
|
-
"@nextlyhq/plugin-sdk": "0.0.2-alpha.
|
|
53
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.
|
|
54
|
-
"@nextlyhq/ui": "0.0.2-alpha.
|
|
55
|
-
"nextly": "0.0.2-alpha.
|
|
50
|
+
"vitest": "^4.1.0",
|
|
51
|
+
"@nextlyhq/admin": "0.0.2-alpha.39",
|
|
52
|
+
"@nextlyhq/plugin-sdk": "0.0.2-alpha.39",
|
|
53
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.39",
|
|
54
|
+
"@nextlyhq/ui": "0.0.2-alpha.39",
|
|
55
|
+
"nextly": "0.0.2-alpha.39"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=20.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|