@nextlyhq/plugin-form-builder 0.0.2-alpha.7 → 0.0.2-alpha.71

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 (39) hide show
  1. package/README.md +29 -17
  2. package/dist/admin/index.d.ts +251 -454
  3. package/dist/admin/index.js +4782 -3985
  4. package/dist/admin/index.js.map +1 -1
  5. package/dist/field-references-BshlgMcn.js +1048 -0
  6. package/dist/field-references-BshlgMcn.js.map +1 -0
  7. package/dist/index.d.ts +341 -615
  8. package/dist/index.js +3893 -2406
  9. package/dist/index.js.map +1 -1
  10. package/dist/types-BHReIA0j.d.ts +986 -0
  11. package/package.json +40 -39
  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-MMOXXL7P.js +0 -138
  17. package/dist/EmailFieldEditor-MMOXXL7P.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/index-C0kBvYjm.d.ts +0 -1012
  38. package/dist/styles/form-builder.css +0 -1150
  39. package/dist/styles/submissions-filter.css +0 -75
@@ -0,0 +1,986 @@
1
+ import { CollectionConfig, FieldDefinition, RequestContext } from "nextly";
2
+ import { ComponentType } from "react";
3
+ /**
4
+ * Reading the document a form's redirect setting points at.
5
+ *
6
+ * Separate from `redirect-target` because this half has no dependencies and
7
+ * the other half imports `nextly/config`: the admin picker needs to read a
8
+ * stored reference to show what is selected, and pulling the config surface
9
+ * into the browser bundle to do it would be a real cost for no gain.
10
+ */
11
+ /** A document being turned into a URL. `id` is always present; the rest is the row. */
12
+ interface RedirectTargetDocument {
13
+ id: string;
14
+ [field: string]: unknown;
15
+ }
16
+ /**
17
+ * How documents in one collection become URLs.
18
+ *
19
+ * A string is a path with `{field}` placeholders filled from the document.
20
+ * A function receives the document and returns the path itself.
21
+ */
22
+ type RedirectUrlPattern = string | ((document: RedirectTargetDocument) => string | undefined);
23
+ /** The collection-to-pattern map, after the array shorthand is normalised. */
24
+ type RedirectRelationships = Record<string, RedirectUrlPattern>;
25
+ /**
26
+ * Configuration for customizing field block behavior.
27
+ */
28
+ interface FieldBlockConfig {
29
+ /** Default label for this field type */
30
+ label?: string;
31
+ /** Default validation settings */
32
+ validation?: Record<string, unknown>;
33
+ /** Custom component to use for this field type */
34
+ component?: ComponentType<unknown>;
35
+ }
36
+ /**
37
+ * Email configuration for notifications.
38
+ */
39
+ interface EmailConfig {
40
+ to: string | string[];
41
+ from?: string;
42
+ subject: string;
43
+ html?: string;
44
+ text?: string;
45
+ replyTo?: string;
46
+ cc?: string | string[];
47
+ bcc?: string | string[];
48
+ }
49
+ /**
50
+ * @experimental A resolved outgoing form notification (template-based) as built
51
+ * by the submission handler, before it is sent. This is the value transformed by
52
+ * the `form-builder.beforeEmail` D63 filter seam.
53
+ */
54
+ interface FormEmailNotification {
55
+ /** Resolved recipient address. */
56
+ to: string;
57
+ /** Email template slug to render. */
58
+ templateSlug: string;
59
+ /** Template variables (submitted data + form metadata). */
60
+ variables: Record<string, unknown>;
61
+ /** Email provider id; undefined = system default. */
62
+ providerId?: string;
63
+ /**
64
+ * Resolved sender address (rule's senderEmail, else the plugin's
65
+ * defaultFrom); undefined = template/provider default.
66
+ */
67
+ from?: string;
68
+ /** Resolved Reply-To address, if the rule configured one. */
69
+ replyTo?: string;
70
+ /** CC addresses, if any. */
71
+ cc?: string[];
72
+ /** BCC addresses, if any. */
73
+ bcc?: string[];
74
+ /** Source notification id; undefined for filter-injected emails. */
75
+ notificationId?: string;
76
+ }
77
+ /**
78
+ * A single email notification rule stored on a form.
79
+ * Matches the shape saved by the form builder admin UI. This is the ONE
80
+ * notification rule type — the admin context re-exports it rather than
81
+ * keeping a parallel copy.
82
+ */
83
+ interface FormNotification {
84
+ /** Unique ID for this notification */
85
+ id: string;
86
+ /** Display name for this notification rule */
87
+ name: string;
88
+ /** Whether this notification is active */
89
+ enabled: boolean;
90
+ /** ID of the email provider to use; undefined = system default */
91
+ providerId?: string;
92
+ /**
93
+ * Sender email override. Resolution order at send time:
94
+ * this value, else the plugin's `notifications.defaultFrom` option, else
95
+ * the template/provider default.
96
+ */
97
+ senderEmail?: string;
98
+ /** How the recipient address is determined */
99
+ recipientType: "static" | "field";
100
+ /** Recipient: static email address or a {{fieldName}} reference */
101
+ to: string;
102
+ /** CC email addresses */
103
+ cc: string[];
104
+ /** BCC email addresses */
105
+ bcc: string[];
106
+ /**
107
+ * Reply-To address: a `{{fieldName}}` reference to one of the form's
108
+ * email fields (resolved against the submission) or a literal address.
109
+ */
110
+ replyTo?: string;
111
+ /**
112
+ * Optional send condition evaluated against the submitted data. When set
113
+ * and unmet, the rule is skipped for that submission. A single condition
114
+ * covers the common case; the JSON storage leaves room to grow into a
115
+ * multi-condition tree without a migration.
116
+ */
117
+ condition?: ConditionalLogicCondition;
118
+ /** Slug of the email template to use */
119
+ templateSlug?: string;
120
+ }
121
+ /**
122
+ * @deprecated Use {@link FormNotification}. Alias kept so existing imports
123
+ * keep compiling during the alpha; will be removed before a stable release.
124
+ */
125
+ type FormNotificationItem = FormNotification;
126
+ /**
127
+ * Form document structure (stored in database).
128
+ */
129
+ interface FormDocument {
130
+ id: string;
131
+ slug: string;
132
+ name: string;
133
+ fields: AnyFormField[];
134
+ settings: FormSettings;
135
+ notifications: FormNotification[];
136
+ status: "draft" | "published" | "closed";
137
+ /**
138
+ * What a closed form tells a visitor who reaches it.
139
+ *
140
+ * Optional because the field is only written once an author has a reason to
141
+ * give, and a row created before it existed has none. A closed form with
142
+ * nothing here falls back to the generic refusal, which is also what a draft
143
+ * gets — a draft has never been public, so there is no author intent to
144
+ * relay.
145
+ */
146
+ closedMessage?: string;
147
+ createdAt: Date;
148
+ updatedAt: Date;
149
+ }
150
+ /**
151
+ * Submission document structure (stored in database).
152
+ */
153
+ interface SubmissionDocument {
154
+ id: string;
155
+ form: string | FormDocument;
156
+ data: Record<string, unknown>;
157
+ /**
158
+ * Where the submission sits in review.
159
+ *
160
+ * `spam` is one of them, not a separate flag: the stored field offers it, the
161
+ * admin has a Spam tab and filters the other views with `not_equals: "spam"`,
162
+ * the notification hook skips it, and marking something "Not spam" moves it
163
+ * back to `new`. `spamReason` records WHICH check flagged it.
164
+ */
165
+ status: "new" | "read" | "archived" | "spam";
166
+ ipAddress?: string;
167
+ userAgent?: string;
168
+ /** Which spam check flagged this, when one did. */
169
+ spamReason?: string | null;
170
+ submittedAt: Date;
171
+ createdAt: Date;
172
+ updatedAt: Date;
173
+ }
174
+ /**
175
+ * Configuration options for the form builder plugin.
176
+ */
177
+ /**
178
+ * Which field types the builder offers, and how each one is customised.
179
+ *
180
+ * Declared ONCE and viewed twice: the options surface takes
181
+ * `Partial<FormFieldToggles>` and the resolved config takes it whole, because
182
+ * "resolved" means exactly "every toggle now has a value". Written out twice
183
+ * the two lists agree until someone adds a field type to one of them.
184
+ */
185
+ interface FormFieldToggles {
186
+ text: boolean | Partial<FieldBlockConfig>;
187
+ email: boolean | Partial<FieldBlockConfig>;
188
+ number: boolean | Partial<FieldBlockConfig>;
189
+ phone: boolean | Partial<FieldBlockConfig>;
190
+ url: boolean | Partial<FieldBlockConfig>;
191
+ textarea: boolean | Partial<FieldBlockConfig>;
192
+ select: boolean | Partial<FieldBlockConfig>;
193
+ checkbox: boolean | Partial<FieldBlockConfig>;
194
+ radio: boolean | Partial<FieldBlockConfig>;
195
+ file: boolean | Partial<FieldBlockConfig>;
196
+ date: boolean | Partial<FieldBlockConfig>;
197
+ time: boolean | Partial<FieldBlockConfig>;
198
+ hidden: boolean | Partial<FieldBlockConfig>;
199
+ }
200
+ /** What a host may override on a collection this plugin contributes. */
201
+ type CollectionOverrides = Partial<CollectionConfig> & {
202
+ slug?: string;
203
+ labels?: {
204
+ singular?: string;
205
+ plural?: string;
206
+ };
207
+ /** Fields can be an array or a function receiving defaultFields */
208
+ fields?: FieldDefinition[] | ((args: {
209
+ defaultFields: FieldDefinition[];
210
+ }) => FieldDefinition[]);
211
+ };
212
+ /** The same, once the plugin's defaults have filled in what a host omitted. */
213
+ type ResolvedCollectionOverrides = {
214
+ slug: string;
215
+ labels: {
216
+ singular: string;
217
+ plural: string;
218
+ };
219
+ } & Partial<CollectionConfig>;
220
+ interface FormBuilderPluginOptions {
221
+ /**
222
+ * Override the default Forms collection configuration.
223
+ *
224
+ * Allows customizing the slug, labels, fields, access control, and other
225
+ * collection options.
226
+ *
227
+ * The `fields` option can be:
228
+ * - An array of fields (merged with defaults)
229
+ * - A function receiving `{ defaultFields }` for full control
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * // Option 1: Array (fields merged with defaults)
234
+ * formOverrides: {
235
+ * slug: 'my-forms',
236
+ * labels: { singular: 'My Form', plural: 'My Forms' },
237
+ * access: {
238
+ * read: () => true,
239
+ * // Narrow the permission, never replace it: the role screen still
240
+ * // decides who holds `create-my-forms`.
241
+ * create: ({ permissions }) =>
242
+ * permissions.includes('create-my-forms') && process.env.CONTENT_FREEZE !== 'on',
243
+ * },
244
+ * fields: [
245
+ * text({ name: 'internalNotes', label: 'Internal Notes' }),
246
+ * ],
247
+ * }
248
+ *
249
+ * // Option 2: Function (full control over fields)
250
+ * formOverrides: {
251
+ * slug: 'contact-forms',
252
+ * access: {
253
+ * read: ({ permissions }) => permissions.includes('read-contact-forms'),
254
+ * update: () => false,
255
+ * },
256
+ * fields: ({ defaultFields }) => {
257
+ * return [
258
+ * ...defaultFields,
259
+ * { name: 'custom', type: 'text' },
260
+ * ];
261
+ * },
262
+ * }
263
+ * ```
264
+ */
265
+ formOverrides?: CollectionOverrides;
266
+ /**
267
+ * Override the default Form Submissions collection configuration.
268
+ *
269
+ * Allows customizing the slug, labels, fields, access control, and other
270
+ * collection options.
271
+ *
272
+ * The `fields` option can be:
273
+ * - An array of fields (merged with defaults)
274
+ * - A function receiving `{ defaultFields }` for full control
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * // Option 1: Array (fields merged with defaults)
279
+ * formSubmissionOverrides: {
280
+ * slug: 'my-submissions',
281
+ * labels: { singular: 'Response', plural: 'Responses' },
282
+ * access: {
283
+ * read: ({ permissions }) => permissions.includes('read-my-submissions'),
284
+ * },
285
+ * fields: [
286
+ * select({
287
+ * name: 'priority',
288
+ * label: 'Priority',
289
+ * options: [
290
+ * { label: 'Low', value: 'low' },
291
+ * { label: 'High', value: 'high' },
292
+ * ],
293
+ * }),
294
+ * ],
295
+ * }
296
+ *
297
+ * // Option 2: Function (full control over fields)
298
+ * formSubmissionOverrides: {
299
+ * fields: ({ defaultFields }) => {
300
+ * // Add field after 'form' field
301
+ * const formIndex = defaultFields.findIndex(f => f.name === 'form');
302
+ * return [
303
+ * ...defaultFields.slice(0, formIndex + 1),
304
+ * { name: 'source', type: 'text' },
305
+ * ...defaultFields.slice(formIndex + 1),
306
+ * ];
307
+ * },
308
+ * }
309
+ * ```
310
+ */
311
+ formSubmissionOverrides?: CollectionOverrides;
312
+ /**
313
+ * Configure which field types are available in the form builder.
314
+ *
315
+ * Set to `false` to disable a field type, or provide partial configuration
316
+ * to customize default behavior.
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * fields: {
321
+ * // Disable payment field
322
+ * payment: false,
323
+ *
324
+ * // Customize text field defaults
325
+ * text: {
326
+ * maxLength: 500,
327
+ * },
328
+ *
329
+ * // Disable file uploads
330
+ * file: false,
331
+ * }
332
+ * ```
333
+ */
334
+ fields?: Partial<FormFieldToggles>;
335
+ /**
336
+ * Collections whose documents a form may redirect to after submission, and
337
+ * how each one's documents become URLs.
338
+ *
339
+ * The mapping is configuration because it cannot be derived: a site's URL
340
+ * structure belongs to the site. `{field}` placeholders are filled from the
341
+ * document; a function covers a path this package cannot express.
342
+ *
343
+ * The array shorthand names the collections and takes `/{slug}` for all of
344
+ * them, which is the routing the page builder ships.
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * redirectRelationships: ['pages']
349
+ *
350
+ * redirectRelationships: {
351
+ * pages: '/{slug}',
352
+ * posts: '/blog/{slug}',
353
+ * docs: page => `/docs/${String(page.section)}/${String(page.slug)}`,
354
+ * }
355
+ * ```
356
+ */
357
+ redirectRelationships?: string[] | RedirectRelationships;
358
+ /**
359
+ * Hook called before sending email notifications.
360
+ *
361
+ * Runs as the `form-builder.beforeEmail` D63 filter: the resolved outgoing
362
+ * notifications (template + recipient + variables) are threaded through this
363
+ * callback before they are sent. Allows modifying, adding, or filtering the
364
+ * outgoing notifications. Return the modified array.
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * beforeEmail: async ({ emails, form, submission }) => {
369
+ * // Add BCC to all outgoing notifications
370
+ * return emails.map((e) => ({ ...e, bcc: ['archive@example.com'] }));
371
+ * }
372
+ * ```
373
+ */
374
+ beforeEmail?: (args: {
375
+ emails: FormEmailNotification[];
376
+ form: FormDocument;
377
+ submission: SubmissionDocument;
378
+ }) => Promise<FormEmailNotification[]> | FormEmailNotification[];
379
+ /**
380
+ * Email notification settings.
381
+ */
382
+ notifications?: {
383
+ /**
384
+ * Default "from" email address.
385
+ */
386
+ defaultFrom?: string;
387
+ /**
388
+ * Default "to" email address for all form submissions.
389
+ *
390
+ * If specified, this email will receive all form submissions
391
+ * regardless of form-specific notification settings.
392
+ * @example
393
+ * ```typescript
394
+ * notifications: {
395
+ * defaultToEmail: 'submissions@example.com',
396
+ * }
397
+ * ```
398
+ */
399
+ defaultToEmail?: string;
400
+ /**
401
+ * Enable/disable email notifications globally.
402
+ * @default true
403
+ */
404
+ enabled?: boolean;
405
+ };
406
+ /**
407
+ * Spam protection configuration.
408
+ */
409
+ spamProtection?: {
410
+ /**
411
+ * Enable honeypot field for spam detection.
412
+ * @default true
413
+ */
414
+ honeypot?: boolean;
415
+ /**
416
+ * Enable reCAPTCHA v3.
417
+ * @default false
418
+ */
419
+ recaptcha?: {
420
+ enabled: boolean;
421
+ siteKey?: string;
422
+ secretKey?: string;
423
+ scoreThreshold?: number;
424
+ };
425
+ /**
426
+ * Rate limiting configuration.
427
+ */
428
+ rateLimit?: {
429
+ /**
430
+ * Maximum submissions per window.
431
+ * @default 10
432
+ */
433
+ maxSubmissions?: number;
434
+ /**
435
+ * Time window in milliseconds.
436
+ * @default 60000 (1 minute)
437
+ */
438
+ windowMs?: number;
439
+ };
440
+ };
441
+ /**
442
+ * File upload settings.
443
+ */
444
+ uploads?: {
445
+ /**
446
+ * Maximum file size in bytes.
447
+ * @default 10485760 (10MB)
448
+ */
449
+ maxFileSize?: number;
450
+ /**
451
+ * Allowed file types (MIME types).
452
+ * @default ['image/*', 'application/pdf', 'text/*']
453
+ */
454
+ allowedMimeTypes?: string[];
455
+ /**
456
+ * Collection to store uploaded files.
457
+ * @default 'media'
458
+ */
459
+ uploadCollection?: string;
460
+ };
461
+ /**
462
+ * Enable/disable features.
463
+ */
464
+ features?: {
465
+ /**
466
+ * Enable visual form builder UI.
467
+ * @default true
468
+ */
469
+ builder?: boolean;
470
+ /**
471
+ * Enable conditional logic.
472
+ * @default true
473
+ */
474
+ conditionalLogic?: boolean;
475
+ /**
476
+ * Enable file uploads.
477
+ * @default true
478
+ */
479
+ fileUploads?: boolean;
480
+ };
481
+ }
482
+ /**
483
+ * Form configuration interface.
484
+ * Similar to CollectionConfig but tailored for forms.
485
+ */
486
+ interface FormConfig {
487
+ /**
488
+ * Unique identifier for the form.
489
+ */
490
+ slug: string;
491
+ /**
492
+ * Display labels for the form.
493
+ */
494
+ labels?: {
495
+ singular?: string;
496
+ plural?: string;
497
+ };
498
+ /**
499
+ * Form fields definition.
500
+ */
501
+ fields: AnyFormField[];
502
+ /**
503
+ * Form settings and behavior.
504
+ */
505
+ settings?: FormSettings;
506
+ /**
507
+ * Email notification configuration.
508
+ */
509
+ notifications?: FormNotifications;
510
+ /**
511
+ * Access control for form submission and viewing.
512
+ */
513
+ access?: FormAccess;
514
+ /**
515
+ * Lifecycle hooks for form submissions.
516
+ */
517
+ hooks?: FormHooks;
518
+ /**
519
+ * Custom metadata.
520
+ */
521
+ custom?: Record<string, unknown>;
522
+ }
523
+ /**
524
+ * Form field type (discriminated union).
525
+ * Uses the `type` property as the discriminant.
526
+ */
527
+ type FormField = TextFormField | EmailFormField | NumberFormField | PhoneFormField | UrlFormField | TextareaFormField | SelectFormField | CheckboxFormField | RadioFormField | FileFormField | DateFormField | TimeFormField | HiddenFormField;
528
+ /**
529
+ * A form field of a built-in type OR a plugin-contributed type. Stored form
530
+ * data and the builder work with this; `isKnownFormField` narrows it back to
531
+ * the closed `FormField` union so `switch (field.type)` keeps discriminating
532
+ * the built-ins (a `CustomFormField` in the union itself would collapse that).
533
+ */
534
+ type AnyFormField = FormField | CustomFormField;
535
+ /**
536
+ * All available built-in form field type identifiers.
537
+ */
538
+ type FormFieldType = "text" | "email" | "number" | "phone" | "url" | "textarea" | "select" | "checkbox" | "radio" | "file" | "date" | "time" | "hidden";
539
+ /**
540
+ * A form field's type on the wire: a built-in or a plugin-contributed type id
541
+ * that opted into the forms surface. The `(string & {})` arm admits the plugin
542
+ * id while keeping built-in autocomplete. Built-in `case` narrowing still works
543
+ * because every field-type switch keeps a `default` for the custom arm.
544
+ */
545
+ type FormFieldTypeId = FormFieldType | (string & {});
546
+ /** Every built-in form field type, as a value list (the single source shared
547
+ * by the field-type guard and the config validator). */
548
+ declare const BUILT_IN_FORM_FIELD_TYPES: readonly FormFieldType[];
549
+ /**
550
+ * Narrow an `AnyFormField` to the closed built-in `FormField` union. Returns
551
+ * `false` for a plugin-contributed field, so callers handle it generically
552
+ * before switching on the built-in discriminant.
553
+ */
554
+ declare function isKnownFormField(field: AnyFormField): field is FormField;
555
+ /**
556
+ * Base form field properties.
557
+ * All form field types extend this interface.
558
+ */
559
+ interface BaseFormField {
560
+ /** Field type identifier (discriminant) */
561
+ type: FormFieldType;
562
+ /** Field name (used as key in submission data) */
563
+ name: string;
564
+ /** Display label */
565
+ label: string;
566
+ /** Placeholder text */
567
+ placeholder?: string;
568
+ /** Help text shown below field */
569
+ helpText?: string;
570
+ /** Default value */
571
+ defaultValue?: unknown;
572
+ /** Whether field is required */
573
+ required?: boolean;
574
+ /** Validation rules */
575
+ validation?: ValidationRules;
576
+ /** Conditional logic configuration */
577
+ conditionalLogic?: ConditionalLogic;
578
+ /** Admin UI configuration */
579
+ admin?: {
580
+ /** CSS classes */
581
+ className?: string;
582
+ /** Field width */
583
+ width?: "25%" | "33%" | "50%" | "66%" | "75%" | "100%";
584
+ };
585
+ }
586
+ /**
587
+ * A plugin-contributed form field type (outside the built-in union). Carries
588
+ * only the base properties — the plugin owns its editor component and value
589
+ * semantics — and its `type` is the plugin's registered field-type id. Kept a
590
+ * distinct union member so built-in `case` narrowing is unaffected while a
591
+ * plugin field still type-checks as a `FormField`.
592
+ */
593
+ interface CustomFormField extends Omit<BaseFormField, "type"> {
594
+ type: string & {};
595
+ }
596
+ /**
597
+ * Text input field.
598
+ */
599
+ interface TextFormField extends Omit<BaseFormField, "type"> {
600
+ type: "text";
601
+ defaultValue?: string;
602
+ validation?: ValidationRules & {
603
+ minLength?: number;
604
+ maxLength?: number;
605
+ pattern?: string;
606
+ };
607
+ }
608
+ /**
609
+ * Email input field.
610
+ */
611
+ interface EmailFormField extends Omit<BaseFormField, "type"> {
612
+ type: "email";
613
+ defaultValue?: string;
614
+ validation?: ValidationRules & {
615
+ pattern?: string;
616
+ };
617
+ }
618
+ /**
619
+ * Number input field.
620
+ */
621
+ interface NumberFormField extends Omit<BaseFormField, "type"> {
622
+ type: "number";
623
+ defaultValue?: number;
624
+ validation?: ValidationRules & {
625
+ min?: number;
626
+ max?: number;
627
+ step?: number;
628
+ };
629
+ }
630
+ /**
631
+ * Phone input field.
632
+ */
633
+ interface PhoneFormField extends Omit<BaseFormField, "type"> {
634
+ type: "phone";
635
+ defaultValue?: string;
636
+ validation?: ValidationRules & {
637
+ pattern?: string;
638
+ };
639
+ }
640
+ /**
641
+ * URL input field.
642
+ */
643
+ interface UrlFormField extends Omit<BaseFormField, "type"> {
644
+ type: "url";
645
+ defaultValue?: string;
646
+ validation?: ValidationRules & {
647
+ pattern?: string;
648
+ };
649
+ }
650
+ /**
651
+ * Textarea field.
652
+ */
653
+ interface TextareaFormField extends Omit<BaseFormField, "type"> {
654
+ type: "textarea";
655
+ defaultValue?: string;
656
+ rows?: number;
657
+ validation?: ValidationRules & {
658
+ minLength?: number;
659
+ maxLength?: number;
660
+ };
661
+ }
662
+ /**
663
+ * Select dropdown field.
664
+ */
665
+ interface SelectFormField extends Omit<BaseFormField, "type"> {
666
+ type: "select";
667
+ options: Array<{
668
+ label: string;
669
+ value: string;
670
+ }>;
671
+ defaultValue?: string | string[];
672
+ allowMultiple?: boolean;
673
+ }
674
+ /**
675
+ * Single checkbox field.
676
+ */
677
+ interface CheckboxFormField extends Omit<BaseFormField, "type"> {
678
+ type: "checkbox";
679
+ defaultValue?: boolean;
680
+ }
681
+ /**
682
+ * Radio group field.
683
+ */
684
+ interface RadioFormField extends Omit<BaseFormField, "type"> {
685
+ type: "radio";
686
+ options: Array<{
687
+ label: string;
688
+ value: string;
689
+ }>;
690
+ defaultValue?: string;
691
+ }
692
+ /**
693
+ * File upload field.
694
+ */
695
+ interface FileFormField extends Omit<BaseFormField, "type"> {
696
+ type: "file";
697
+ accept?: string;
698
+ multiple?: boolean;
699
+ maxFileSize?: number;
700
+ /**
701
+ * When true, files uploaded through this field are attached to all
702
+ * notification emails for this form. Default false.
703
+ */
704
+ attachToEmail?: boolean;
705
+ }
706
+ /**
707
+ * Date picker field.
708
+ */
709
+ interface DateFormField extends Omit<BaseFormField, "type"> {
710
+ type: "date";
711
+ defaultValue?: string;
712
+ min?: string;
713
+ max?: string;
714
+ }
715
+ /**
716
+ * Time picker field.
717
+ */
718
+ interface TimeFormField extends Omit<BaseFormField, "type"> {
719
+ type: "time";
720
+ defaultValue?: string;
721
+ }
722
+ /**
723
+ * Hidden field.
724
+ */
725
+ interface HiddenFormField extends Omit<BaseFormField, "type"> {
726
+ type: "hidden";
727
+ defaultValue?: string;
728
+ }
729
+ interface ValidationRules {
730
+ /** Custom error message */
731
+ errorMessage?: string;
732
+ /** Custom validation function */
733
+ validate?: (value: unknown, formData: Record<string, unknown>) => boolean | string | Promise<boolean | string>;
734
+ }
735
+ /**
736
+ * Conditional logic configuration.
737
+ */
738
+ interface ConditionalLogic {
739
+ /** Whether conditional logic is enabled */
740
+ enabled: boolean;
741
+ /** Action to perform when conditions are met */
742
+ action: "show" | "hide";
743
+ /** Logical operator for multiple conditions */
744
+ operator: "AND" | "OR";
745
+ /** Conditions to evaluate */
746
+ conditions: ConditionalLogicCondition[];
747
+ }
748
+ /**
749
+ * A single conditional logic condition.
750
+ */
751
+ interface ConditionalLogicCondition {
752
+ /** Field name to check */
753
+ field: string;
754
+ /** Comparison operator */
755
+ comparison: "equals" | "notEquals" | "contains" | "isEmpty" | "isNotEmpty" | "greaterThan" | "lessThan";
756
+ /** Value to compare against (not used for isEmpty/isNotEmpty) */
757
+ value?: unknown;
758
+ }
759
+ /**
760
+ * Form settings and behavior.
761
+ */
762
+ interface FormSettings {
763
+ /** Submit button text */
764
+ submitButtonText?: string;
765
+ /**
766
+ * Confirmation type after successful submission.
767
+ * - "message": Display a success message
768
+ * - "redirect": Redirect to a URL
769
+ * - "relationship": Redirect to a linked document (requires
770
+ * `redirectRelationships` in the plugin options)
771
+ *
772
+ * @default "message"
773
+ */
774
+ confirmationType?: "message" | "redirect" | "relationship";
775
+ /**
776
+ * Linked document for relationship-based redirects (the collection's
777
+ * `redirectPage` relationship value). Preserved verbatim — resolution
778
+ * happens where the relationship is rendered.
779
+ */
780
+ redirectPage?: unknown;
781
+ /** Success message (supports HTML) - used when confirmationType is "message" */
782
+ successMessage?: string;
783
+ /** Redirect URL after successful submission - used when confirmationType is "redirect" */
784
+ redirectUrl?: string;
785
+ /**
786
+ * Relationship to redirect document - used when confirmationType is "redirect"
787
+ * and redirectRelationships are configured in plugin options.
788
+ */
789
+ redirectRelation?: {
790
+ relationTo: string;
791
+ value: string;
792
+ };
793
+ /** Allow multiple submissions from same IP (default true) */
794
+ allowMultipleSubmissions?: boolean;
795
+ /**
796
+ * Per-form honeypot override. Unset inherits the plugin's
797
+ * `spamProtection.honeypot`; the form wins where set.
798
+ */
799
+ honeypotEnabled?: boolean;
800
+ /**
801
+ * Per-form reCAPTCHA override. Unset inherits the plugin's
802
+ * `spamProtection.recaptcha.enabled`; the form wins where set.
803
+ */
804
+ captchaEnabled?: boolean;
805
+ /** reCAPTCHA site key (client-facing) when captcha is enabled per-form */
806
+ captchaSiteKey?: string;
807
+ }
808
+ /**
809
+ * Email notification configuration for a form.
810
+ */
811
+ interface FormNotifications {
812
+ /** Enable email notifications */
813
+ enabled: boolean;
814
+ /** Recipient email addresses */
815
+ recipients: Array<{
816
+ email: string;
817
+ name?: string;
818
+ }>;
819
+ /** Email subject line */
820
+ subject?: string;
821
+ /** Use submitter's email as reply-to */
822
+ replyTo?: boolean;
823
+ /** Custom email template */
824
+ template?: string;
825
+ }
826
+ /**
827
+ * Form access control.
828
+ */
829
+ interface FormAccess {
830
+ /** Who can submit the form */
831
+ submit?: (context: {
832
+ req: RequestContext;
833
+ }) => boolean | Promise<boolean>;
834
+ /** Who can view submissions */
835
+ read?: (context: {
836
+ req: RequestContext;
837
+ }) => boolean | Promise<boolean>;
838
+ }
839
+ /**
840
+ * Form lifecycle hooks.
841
+ */
842
+ interface FormHooks {
843
+ /** Before submission validation */
844
+ beforeValidate?: Array<(context: FormHookContext) => void | Promise<void>>;
845
+ /** Before submission is saved */
846
+ beforeSubmit?: Array<(context: FormHookContext) => void | Promise<void>>;
847
+ /** After submission is saved */
848
+ afterSubmit?: Array<(context: FormHookContext) => void | Promise<void>>;
849
+ }
850
+ /**
851
+ * Hook context for form hooks.
852
+ */
853
+ interface FormHookContext {
854
+ /** Form configuration */
855
+ form: FormConfig;
856
+ /** Submission data */
857
+ data: Record<string, unknown>;
858
+ /** Request context */
859
+ req: RequestContext;
860
+ /** Submission metadata */
861
+ metadata?: {
862
+ ipAddress?: string;
863
+ userAgent?: string;
864
+ };
865
+ }
866
+ /**
867
+ * Form submission data structure.
868
+ */
869
+ interface FormSubmission {
870
+ /** Submission ID */
871
+ id: string;
872
+ /** Form slug */
873
+ formSlug: string;
874
+ /** Submission data (JSON) */
875
+ data: Record<string, unknown>;
876
+ /**
877
+ * Submission status.
878
+ *
879
+ * `spam` is one of them, not a separate flag: the stored field offers it, the
880
+ * admin has a Spam tab and filters its other views with `not_equals: "spam"`,
881
+ * and marking something "Not spam" moves it back to `new`.
882
+ */
883
+ status: "new" | "read" | "archived" | "spam";
884
+ /** Which spam check flagged this, when one did. */
885
+ spamReason?: string | null;
886
+ /** Internal notes (admin only) */
887
+ notes?: string;
888
+ /** Metadata */
889
+ ipAddress?: string;
890
+ userAgent?: string;
891
+ submittedAt: Date;
892
+ /** Created/updated timestamps */
893
+ createdAt: Date;
894
+ updatedAt: Date;
895
+ }
896
+ /**
897
+ * Resolved form builder configuration after applying defaults.
898
+ * Used internally by the plugin.
899
+ */
900
+ interface ResolvedFormBuilderConfig {
901
+ formOverrides: ResolvedCollectionOverrides;
902
+ formSubmissionOverrides: ResolvedCollectionOverrides;
903
+ fields: FormFieldToggles;
904
+ /**
905
+ * The collection-to-URL map, with the array shorthand already expanded.
906
+ *
907
+ * ONE shape, so nothing downstream branches on which spelling was written.
908
+ * The list of redirect collections is `Object.keys` of this rather than a
909
+ * second field beside it, which would agree with it today and drift later.
910
+ */
911
+ redirectRelationships: RedirectRelationships;
912
+ /**
913
+ * Whether each redirect target collection is localized, keyed by slug.
914
+ *
915
+ * Resolved at `init` from the collection's own registered metadata rather
916
+ * than asked for in options, because the collection already declares it and
917
+ * a second declaration drifts.
918
+ *
919
+ * It decides whether a target's main-row `status` answers for the document:
920
+ * a localized collection publishes on a locale companion, so the row reads
921
+ * `draft` while a translation is public. A slug missing here is a collection
922
+ * whose metadata could not be read — undefined, not false, because reading
923
+ * an unknown as "not localized" is what marks a reachable translation as a
924
+ * draft.
925
+ */
926
+ redirectTargetLocalization: Record<string, boolean>;
927
+ beforeEmail?: FormBuilderPluginOptions["beforeEmail"];
928
+ notifications: {
929
+ defaultFrom?: string;
930
+ defaultToEmail?: string;
931
+ enabled: boolean;
932
+ };
933
+ spamProtection: {
934
+ honeypot: boolean;
935
+ recaptcha?: {
936
+ enabled: boolean;
937
+ siteKey?: string;
938
+ secretKey?: string;
939
+ scoreThreshold?: number;
940
+ };
941
+ rateLimit?: {
942
+ maxSubmissions: number;
943
+ windowMs: number;
944
+ };
945
+ };
946
+ uploads: {
947
+ maxFileSize: number;
948
+ allowedMimeTypes: string[];
949
+ uploadCollection: string;
950
+ };
951
+ features: {
952
+ builder: boolean;
953
+ conditionalLogic: boolean;
954
+ fileUploads: boolean;
955
+ };
956
+ }
957
+ /**
958
+ * Check if a value is a FormField.
959
+ */
960
+ declare function isFormField(value: unknown): value is FormField;
961
+ /**
962
+ * Check if a FormField is a specific type.
963
+ */
964
+ declare function isFormFieldType<T extends FormField>(field: FormField, type: T["type"]): field is T;
965
+ /**
966
+ * Type guard for text field.
967
+ */
968
+ declare function isTextFormField(field: FormField): field is TextFormField;
969
+ /**
970
+ * Type guard for email field.
971
+ */
972
+ declare function isEmailFormField(field: FormField): field is EmailFormField;
973
+ /**
974
+ * Type guard for select field.
975
+ */
976
+ declare function isSelectFormField(field: FormField): field is SelectFormField;
977
+ /**
978
+ * Type guard for checkbox field.
979
+ */
980
+ declare function isCheckboxFormField(field: FormField): field is CheckboxFormField;
981
+ /**
982
+ * Type guard for file field.
983
+ */
984
+ declare function isFileFormField(field: FormField): field is FileFormField;
985
+ export { RadioFormField as A, isEmailFormField as B, FormNotificationItem as C, HiddenFormField as D, FormSubmission as E, TextareaFormField as F, isSelectFormField as G, isFormField as H, TimeFormField as I, isTextFormField as K, UrlFormField as L, SelectFormField as M, SubmissionDocument as N, NumberFormField as O, TextFormField as P, ValidationRules as R, FormNotification as S, FormSettings as T, isFormFieldType as U, isFileFormField as V, isKnownFormField as W, FormField as _, ConditionalLogic as a, FormHookContext as b, DateFormField as c, FieldBlockConfig as d, FileFormField as f, FormDocument as g, FormConfig as h, CheckboxFormField as i, ResolvedFormBuilderConfig as j, PhoneFormField as k, EmailConfig as l, FormBuilderPluginOptions as m, BUILT_IN_FORM_FIELD_TYPES as n, ConditionalLogicCondition as o, FormAccess as p, BaseFormField as r, CustomFormField as s, AnyFormField as t, EmailFormField as u, FormFieldType as v, FormNotifications as w, FormHooks as x, FormFieldTypeId as y, isCheckboxFormField as z };
986
+ //# sourceMappingURL=types-BHReIA0j.d.ts.map