@nextlyhq/plugin-form-builder 0.0.2-alpha.74 → 0.0.2-alpha.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,37 @@
1
1
  import "@nextlyhq/plugin-sdk";
2
2
  import { CollectionConfig, FieldDefinition, RequestContext } from "nextly";
3
3
  import { ComponentType } from "react";
4
+ /** The widget's id, the one core's default names. */
5
+ declare const NEW_SUBMISSIONS_WIDGET_ID = "form-builder/new-submissions";
6
+ /** One new submission, as the card draws it. */
7
+ interface NewSubmissionRow {
8
+ id: string;
9
+ /** The submitter's name or email, or "Submission" where the form has neither answered. */
10
+ title: string;
11
+ /** The form's name, where the reader may read it. */
12
+ form?: string;
13
+ /** The first line of the first text answer not already the title. */
14
+ excerpt?: string;
15
+ /** When it was submitted, as an ISO string. */
16
+ at?: string;
17
+ /** The submission, in the admin. */
18
+ href: string;
19
+ }
20
+ /** The card's data: its "Number of entries" of rows, how many new submissions there are, and their list. */
21
+ interface NewSubmissionsData {
22
+ items: NewSubmissionRow[];
23
+ /** How many new submissions the reader may read, in all. */
24
+ total: number;
25
+ /** `total` is a floor: there are at least this many. */
26
+ atLeast?: true;
27
+ /** Every submission, in the admin. */
28
+ allHref: string;
29
+ /**
30
+ * The submissions collection's slug as the site installed it, the one a
31
+ * row is marked read in when it is opened.
32
+ */
33
+ collection: string;
34
+ }
4
35
  /**
5
36
  * Reading the document a form's redirect setting points at.
6
37
  *
@@ -53,8 +84,11 @@ interface EmailConfig {
53
84
  * the `form-builder.beforeEmail` D63 filter seam.
54
85
  */
55
86
  interface FormEmailNotification {
56
- /** Resolved recipient address. */
57
- to: string;
87
+ /**
88
+ * Resolved recipient address, or the addresses of a team notification
89
+ * that has several. A notification with one recipient keeps a string.
90
+ */
91
+ to: string | string[];
58
92
  /** Email template slug to render. */
59
93
  templateSlug: string;
60
94
  /** Template variables (submitted data + form metadata). */
@@ -74,6 +108,14 @@ interface FormEmailNotification {
74
108
  bcc?: string[];
75
109
  /** Source notification id; undefined for filter-injected emails. */
76
110
  notificationId?: string;
111
+ /**
112
+ * The files that go with this email, by media id: the notification's own
113
+ * choice of file fields. Absent on a filter-injected email, which takes the
114
+ * files of the fields marked to attach to notification emails.
115
+ */
116
+ attachments?: Array<{
117
+ mediaId: string;
118
+ }>;
77
119
  }
78
120
  /**
79
121
  * A single email notification rule stored on a form.
@@ -96,10 +138,25 @@ interface FormNotification {
96
138
  * the template/provider default.
97
139
  */
98
140
  senderEmail?: string;
141
+ /**
142
+ * Which preset the notification started from: one to the team, or a
143
+ * confirmation to the person who submitted. Absent on rules saved before
144
+ * presets, which read by their recipient.
145
+ */
146
+ kind?: "team" | "visitor";
99
147
  /** How the recipient address is determined */
100
148
  recipientType: "static" | "field";
101
- /** Recipient: static email address or a {{fieldName}} reference */
149
+ /**
150
+ * Recipient: static email address or a {{fieldName}} reference. A team
151
+ * notification with several addresses keeps them in `recipients`; this
152
+ * then holds the first, for readers that know only one.
153
+ */
102
154
  to: string;
155
+ /**
156
+ * A team notification's addresses. Absent on rules saved before several
157
+ * were allowed, whose single `to` reads as a list of one.
158
+ */
159
+ recipients?: string[];
103
160
  /** CC email addresses */
104
161
  cc: string[];
105
162
  /** BCC email addresses */
@@ -116,6 +173,20 @@ interface FormNotification {
116
173
  * multi-condition tree without a migration.
117
174
  */
118
175
  condition?: ConditionalLogicCondition;
176
+ /**
177
+ * Several send conditions: all must hold, or any one. Takes the place of
178
+ * `condition`, which reads as a list of one under `all` when this is absent.
179
+ */
180
+ conditions?: {
181
+ match: "all" | "any";
182
+ list: ConditionalLogicCondition[];
183
+ };
184
+ /**
185
+ * The names of the form's file fields whose files go with this email.
186
+ * Absent on rules saved before it, which attach exactly the file fields
187
+ * marked to attach to notification emails.
188
+ */
189
+ attachFields?: string[];
119
190
  /** Slug of the email template to use */
120
191
  templateSlug?: string;
121
192
  }
@@ -168,6 +239,12 @@ interface SubmissionDocument {
168
239
  userAgent?: string;
169
240
  /** Which spam check flagged this, when one did. */
170
241
  spamReason?: string | null;
242
+ /**
243
+ * What it was asked, as the write seam stored it (`utils/asked-fields`):
244
+ * its fields' labels and option labels at the time. Absent on a
245
+ * submission stored before submissions kept it.
246
+ */
247
+ asked?: unknown;
171
248
  submittedAt: Date;
172
249
  createdAt: Date;
173
250
  updatedAt: Date;
@@ -197,6 +274,7 @@ interface FormFieldToggles {
197
274
  date: boolean | Partial<FieldBlockConfig>;
198
275
  time: boolean | Partial<FieldBlockConfig>;
199
276
  hidden: boolean | Partial<FieldBlockConfig>;
277
+ consent: boolean | Partial<FieldBlockConfig>;
200
278
  }
201
279
  /** What a host may override on a collection this plugin contributes. */
202
280
  type CollectionOverrides = Partial<CollectionConfig> & {
@@ -407,6 +485,21 @@ interface FormBuilderPluginOptions {
407
485
  /**
408
486
  * Spam protection configuration.
409
487
  */
488
+ /**
489
+ * One response per person.
490
+ */
491
+ oneResponse?: {
492
+ /**
493
+ * How many earlier submissions a form may have for a change of its
494
+ * one-response setting to give them their keys inside the save itself,
495
+ * so the save answers with the form as it then stands. A form with more
496
+ * is brought up to date by the plugin's background sweep instead, and
497
+ * until it is, each new submission is compared with the earlier answers.
498
+ * Lower it to keep saves short on a slow database.
499
+ * @default 200
500
+ */
501
+ keysInSave?: number;
502
+ };
410
503
  spamProtection?: {
411
504
  /**
412
505
  * Enable honeypot field for spam detection.
@@ -414,15 +507,29 @@ interface FormBuilderPluginOptions {
414
507
  */
415
508
  honeypot?: boolean;
416
509
  /**
417
- * Enable reCAPTCHA v3.
418
- * @default false
510
+ * Google reCAPTCHA v3: a score for each visitor, in the background.
511
+ * `enabled` makes it the site default for forms whose bot check is "Site
512
+ * default". The token is verified on the server with `secretKey`; the
513
+ * visitor's form renders the widget with `siteKey`.
514
+ * @default { enabled: false }
419
515
  */
420
516
  recaptcha?: {
421
517
  enabled: boolean;
422
518
  siteKey?: string;
423
519
  secretKey?: string;
520
+ /** Below this score (0.0 to 1.0) a visitor is a bot. @default 0.5 */
424
521
  scoreThreshold?: number;
425
522
  };
523
+ /**
524
+ * Cloudflare Turnstile: a check in the background that rarely shows a
525
+ * puzzle. `enabled` makes it the site default when reCAPTCHA's is not.
526
+ * @default { enabled: false }
527
+ */
528
+ turnstile?: {
529
+ enabled: boolean;
530
+ siteKey?: string;
531
+ secretKey?: string;
532
+ };
426
533
  /**
427
534
  * Rate limiting configuration.
428
535
  */
@@ -525,7 +632,7 @@ interface FormConfig {
525
632
  * Form field type (discriminated union).
526
633
  * Uses the `type` property as the discriminant.
527
634
  */
528
- type FormField = TextFormField | EmailFormField | NumberFormField | PhoneFormField | UrlFormField | TextareaFormField | SelectFormField | CheckboxFormField | RadioFormField | FileFormField | DateFormField | TimeFormField | HiddenFormField;
635
+ type FormField = TextFormField | EmailFormField | NumberFormField | PhoneFormField | UrlFormField | TextareaFormField | SelectFormField | CheckboxFormField | RadioFormField | FileFormField | DateFormField | TimeFormField | HiddenFormField | ConsentFormField;
529
636
  /**
530
637
  * A form field of a built-in type OR a plugin-contributed type. Stored form
531
638
  * data and the builder work with this; `isKnownFormField` narrows it back to
@@ -536,7 +643,7 @@ type AnyFormField = FormField | CustomFormField;
536
643
  /**
537
644
  * All available built-in form field type identifiers.
538
645
  */
539
- type FormFieldType = "text" | "email" | "number" | "phone" | "url" | "textarea" | "select" | "checkbox" | "radio" | "file" | "date" | "time" | "hidden";
646
+ type FormFieldType = "text" | "email" | "number" | "phone" | "url" | "textarea" | "select" | "checkbox" | "radio" | "file" | "date" | "time" | "hidden" | "consent";
540
647
  /**
541
648
  * A form field's type on the wire: a built-in or a plugin-contributed type id
542
649
  * that opted into the forms surface. The `(string & {})` arm admits the plugin
@@ -727,6 +834,25 @@ interface HiddenFormField extends Omit<BaseFormField, "type"> {
727
834
  type: "hidden";
728
835
  defaultValue?: string;
729
836
  }
837
+ /**
838
+ * A box the visitor ticks to agree to a text, such as a privacy policy. It is
839
+ * always required, and its answer keeps the text the visitor was shown, so a
840
+ * later change to the text does not change what an earlier visitor agreed to.
841
+ */
842
+ interface ConsentFormField extends Omit<BaseFormField, "type"> {
843
+ type: "consent";
844
+ /** What the visitor agrees to, shown beside the box and stored with it. */
845
+ agreementText: string;
846
+ }
847
+ /**
848
+ * A consent field's stored answer: whether the visitor agreed, and the text
849
+ * the form showed them. Made by the server from the form, never taken from
850
+ * the visitor, and never changed by an edit of the submission.
851
+ */
852
+ interface ConsentAnswer {
853
+ agreed: boolean;
854
+ text: string;
855
+ }
730
856
  interface ValidationRules {
731
857
  /** Custom error message */
732
858
  errorMessage?: string;
@@ -757,6 +883,10 @@ interface ConditionalLogicCondition {
757
883
  /** Value to compare against (not used for isEmpty/isNotEmpty) */
758
884
  value?: unknown;
759
885
  }
886
+ /** How a form tells one person's response from another's. */
887
+ type OneResponseMode = "off" | "email" | "browser" | "ip";
888
+ /** Which bot check a form asks for. */
889
+ type BotCheckMode = "default" | "off" | "recaptcha" | "turnstile";
760
890
  /**
761
891
  * Form settings and behavior.
762
892
  */
@@ -791,8 +921,57 @@ interface FormSettings {
791
921
  relationTo: string;
792
922
  value: string;
793
923
  };
794
- /** Allow multiple submissions from same IP (default true) */
924
+ /**
925
+ * Allow multiple submissions from same IP (default true).
926
+ * @deprecated Read as `oneResponse` (true is `"off"`, false is `"ip"`);
927
+ * no longer written.
928
+ */
795
929
  allowMultipleSubmissions?: boolean;
930
+ /**
931
+ * One response per person, and how a person is told apart: `"off"` allows
932
+ * several; `"email"` by the answer to `oneResponseField`, `"browser"` by a
933
+ * token the browser keeps, `"ip"` by network address.
934
+ *
935
+ * @default "off"
936
+ */
937
+ oneResponse?: OneResponseMode;
938
+ /** The email field `oneResponse: "email"` reads, by name. */
939
+ oneResponseField?: string;
940
+ /**
941
+ * Which run of one-response checks the form is on: a whole number from 0,
942
+ * moved on when the mode changes so earlier responses count afresh.
943
+ *
944
+ * @default 0
945
+ */
946
+ oneResponseVersion?: number;
947
+ /**
948
+ * The one-response version whose earlier submissions all carry their keys.
949
+ * Behind `oneResponseVersion` while the plugin is still making them, after
950
+ * a change of mode or Email field; unset reads as up to date. Written by
951
+ * the plugin only.
952
+ */
953
+ oneResponseKeyedVersion?: number;
954
+ /**
955
+ * The bot check: `"default"` follows the plugin's option, `"off"` none, or
956
+ * a named provider.
957
+ *
958
+ * @default "default"
959
+ */
960
+ botCheck?: BotCheckMode;
961
+ /** When the form stops taking submissions, as an ISO date and time. */
962
+ closesAt?: string;
963
+ /** How many submissions the form takes before it closes; none for no limit. */
964
+ submissionLimit?: number;
965
+ /**
966
+ * Whether a submission keeps the visitor's IP address and browser.
967
+ *
968
+ * @default true
969
+ */
970
+ storeIpAndBrowser?: boolean;
971
+ /** How many days submissions are kept; none to keep them. */
972
+ keepDays?: number;
973
+ /** A note about the form, for people in the admin only. */
974
+ internalNote?: string;
796
975
  /**
797
976
  * Per-form honeypot override. Unset inherits the plugin's
798
977
  * `spamProtection.honeypot`; the form wins where set.
@@ -801,6 +980,8 @@ interface FormSettings {
801
980
  /**
802
981
  * Per-form reCAPTCHA override. Unset inherits the plugin's
803
982
  * `spamProtection.recaptcha.enabled`; the form wins where set.
983
+ * @deprecated Read as `botCheck` (unset is `"default"`, true
984
+ * `"recaptcha"`, false `"off"`); no longer written.
804
985
  */
805
986
  captchaEnabled?: boolean;
806
987
  /** reCAPTCHA site key (client-facing) when captcha is enabled per-form */
@@ -913,9 +1094,10 @@ interface ResolvedFormBuilderConfig {
913
1094
  /**
914
1095
  * Whether each redirect target collection is localized, keyed by slug.
915
1096
  *
916
- * Resolved at `init` from the collection's own registered metadata rather
917
- * than asked for in options, because the collection already declares it and
918
- * a second declaration drifts.
1097
+ * Resolved at `init` from the collection's own declaration rather than
1098
+ * asked for in options, because the collection already declares it and a
1099
+ * second declaration drifts: the configuration's for a collection code
1100
+ * declares, the registry row's for one made in the Schema Builder.
919
1101
  *
920
1102
  * It decides whether a target's main-row `status` answers for the document:
921
1103
  * a localized collection publishes on a locale companion, so the row reads
@@ -931,6 +1113,8 @@ interface ResolvedFormBuilderConfig {
931
1113
  defaultToEmail?: string;
932
1114
  enabled: boolean;
933
1115
  };
1116
+ /** `FormBuilderPluginOptions["oneResponse"]`, every option filled. */
1117
+ oneResponse: Required<NonNullable<FormBuilderPluginOptions["oneResponse"]>>;
934
1118
  spamProtection: {
935
1119
  honeypot: boolean;
936
1120
  recaptcha?: {
@@ -939,6 +1123,11 @@ interface ResolvedFormBuilderConfig {
939
1123
  secretKey?: string;
940
1124
  scoreThreshold?: number;
941
1125
  };
1126
+ turnstile?: {
1127
+ enabled: boolean;
1128
+ siteKey?: string;
1129
+ secretKey?: string;
1130
+ };
942
1131
  rateLimit?: {
943
1132
  maxSubmissions: number;
944
1133
  windowMs: number;
@@ -983,36 +1172,5 @@ declare function isCheckboxFormField(field: FormField): field is CheckboxFormFie
983
1172
  * Type guard for file field.
984
1173
  */
985
1174
  declare function isFileFormField(field: FormField): field is FileFormField;
986
- /** The widget's id, the one core's default names. */
987
- declare const NEW_SUBMISSIONS_WIDGET_ID = "form-builder/new-submissions";
988
- /** One new submission, as the card draws it. */
989
- interface NewSubmissionRow {
990
- id: string;
991
- /** The submitter's name or email, or "Submission" where the form has neither answered. */
992
- title: string;
993
- /** The form's name, where the reader may read it. */
994
- form?: string;
995
- /** The first line of the first text answer not already the title. */
996
- excerpt?: string;
997
- /** When it was submitted, as an ISO string. */
998
- at?: string;
999
- /** The submission, in the admin. */
1000
- href: string;
1001
- }
1002
- /** The card's data: the rows its size holds, how many new submissions there are, and their list. */
1003
- interface NewSubmissionsData {
1004
- items: NewSubmissionRow[];
1005
- /** How many new submissions the reader may read, in all. */
1006
- total: number;
1007
- /** `total` is a floor: there are at least this many. */
1008
- atLeast?: true;
1009
- /** Every submission, in the admin. */
1010
- allHref: string;
1011
- /**
1012
- * The submissions collection's slug as the site installed it, the one a
1013
- * row is marked read in when it is opened.
1014
- */
1015
- collection: string;
1016
- }
1017
- export { HiddenFormField as A, UrlFormField as B, FormHookContext as C, FormNotifications as D, FormNotificationItem as E, SelectFormField as F, isFormField as G, isCheckboxFormField as H, SubmissionDocument as I, isSelectFormField as J, isFormFieldType as K, TextFormField as L, PhoneFormField as M, RadioFormField as N, FormSettings as O, ResolvedFormBuilderConfig as P, TextareaFormField as R, FormFieldTypeId as S, FormNotification as T, isEmailFormField as U, ValidationRules as V, isFileFormField as W, isTextFormField as Y, FormBuilderPluginOptions as _, BUILT_IN_FORM_FIELD_TYPES as a, FormField as b, ConditionalLogic as c, DateFormField as d, EmailConfig as f, FormAccess as g, FileFormField as h, AnyFormField as i, NumberFormField as j, FormSubmission as k, ConditionalLogicCondition as l, FieldBlockConfig as m, NewSubmissionRow as n, BaseFormField as o, EmailFormField as p, isKnownFormField as q, NewSubmissionsData as r, CheckboxFormField as s, NEW_SUBMISSIONS_WIDGET_ID as t, CustomFormField as u, FormConfig as v, FormHooks as w, FormFieldType as x, FormDocument as y, TimeFormField as z };
1018
- //# sourceMappingURL=new-submissions-CzlhVGhH.d.ts.map
1175
+ export { NumberFormField as A, ValidationRules as B, FormHooks as C, FormSettings as D, FormNotifications as E, SubmissionDocument as F, isFormFieldType as G, isEmailFormField as H, TextFormField as I, isTextFormField as J, isKnownFormField as K, TextareaFormField as L, RadioFormField as M, ResolvedFormBuilderConfig as N, FormSubmission as O, SelectFormField as P, TimeFormField as R, FormHookContext as S, FormNotificationItem as T, isFileFormField as U, isCheckboxFormField as V, isFormField as W, NewSubmissionRow as X, NEW_SUBMISSIONS_WIDGET_ID as Y, NewSubmissionsData as Z, FormConfig as _, ConditionalLogic as a, FormFieldType as b, ConsentFormField as c, EmailConfig as d, EmailFormField as f, FormBuilderPluginOptions as g, FormAccess as h, CheckboxFormField as i, PhoneFormField as j, HiddenFormField as k, CustomFormField as l, FileFormField as m, BUILT_IN_FORM_FIELD_TYPES as n, ConditionalLogicCondition as o, FieldBlockConfig as p, isSelectFormField as q, BaseFormField as r, ConsentAnswer as s, AnyFormField as t, DateFormField as u, FormDocument as v, FormNotification as w, FormFieldTypeId as x, FormField as y, UrlFormField as z };
1176
+ //# sourceMappingURL=types-XdBrUfi1.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextlyhq/plugin-form-builder",
3
- "version": "0.0.2-alpha.74",
3
+ "version": "0.0.2-alpha.76",
4
4
  "description": "Visual form builder plugin for Nextly with Code-First and Schema Builder support",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,20 +30,20 @@
30
30
  "zod": "^4.6.7"
31
31
  },
32
32
  "peerDependencies": {
33
- "@nextlyhq/admin": "0.0.2-alpha.74",
34
- "@nextlyhq/plugin-sdk": "0.0.2-alpha.74",
35
- "@nextlyhq/ui": "^0.0.2-alpha.74",
33
+ "@nextlyhq/admin": "0.0.2-alpha.76",
34
+ "@nextlyhq/plugin-sdk": "0.0.2-alpha.76",
35
+ "@nextlyhq/ui": "^0.0.2-alpha.76",
36
36
  "@tanstack/react-query": ">=5.0.0",
37
37
  "lucide-react": ">=0.400.0",
38
38
  "next": "^16.4.1",
39
- "nextly": "0.0.2-alpha.74",
39
+ "nextly": "0.0.2-alpha.76",
40
40
  "react": "^19.3.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@nextlyhq/admin": "0.0.2-alpha.74",
44
- "@nextlyhq/plugin-sdk": "0.0.2-alpha.74",
45
- "@nextlyhq/tsconfig": "0.0.2-alpha.74",
46
- "@nextlyhq/ui": "0.0.2-alpha.74",
43
+ "@nextlyhq/admin": "0.0.2-alpha.76",
44
+ "@nextlyhq/plugin-sdk": "0.0.2-alpha.76",
45
+ "@nextlyhq/tsconfig": "0.0.2-alpha.76",
46
+ "@nextlyhq/ui": "0.0.2-alpha.76",
47
47
  "@tanstack/react-query": "^5.62.0",
48
48
  "@testing-library/jest-dom": "^6.9.1",
49
49
  "@testing-library/react": "^16.3.0",
@@ -52,7 +52,7 @@
52
52
  "@types/react": "19.3.0",
53
53
  "jsdom": "^27.1.0",
54
54
  "lucide-react": "^0.544.0",
55
- "nextly": "0.0.2-alpha.74",
55
+ "nextly": "0.0.2-alpha.76",
56
56
  "react": "19.3.1",
57
57
  "react-dom": "19.3.1",
58
58
  "tsdown": "^0.23.1",