@frigear-nu/forms 0.0.6 → 0.0.7

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 (185) hide show
  1. package/README.md +16 -1
  2. package/dist/module.d.mts +148 -8
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +64 -35
  5. package/dist/runtime/app/components/FormBuilder.d.vue.ts +26 -0
  6. package/dist/runtime/app/components/FormBuilder.vue +220 -0
  7. package/dist/runtime/app/components/FormBuilder.vue.d.ts +26 -0
  8. package/dist/runtime/app/components/FormBuilderAccess.d.vue.ts +16 -0
  9. package/dist/runtime/app/components/FormBuilderAccess.vue +404 -0
  10. package/dist/runtime/app/components/FormBuilderAccess.vue.d.ts +16 -0
  11. package/dist/runtime/app/components/FormBuilderCaptchaSettings.d.vue.ts +15 -0
  12. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue +126 -0
  13. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue.d.ts +15 -0
  14. package/dist/runtime/app/components/FormBuilderCondition.d.vue.ts +16 -0
  15. package/dist/runtime/app/components/FormBuilderCondition.vue +62 -0
  16. package/dist/runtime/app/components/FormBuilderCondition.vue.d.ts +16 -0
  17. package/dist/runtime/app/components/FormBuilderFieldRow.d.vue.ts +27 -0
  18. package/dist/runtime/app/components/FormBuilderFieldRow.vue +87 -0
  19. package/dist/runtime/app/components/FormBuilderFieldRow.vue.d.ts +27 -0
  20. package/dist/runtime/app/components/FormBuilderFieldSettings.d.vue.ts +25 -0
  21. package/dist/runtime/app/components/FormBuilderFieldSettings.vue +170 -0
  22. package/dist/runtime/app/components/FormBuilderFieldSettings.vue.d.ts +25 -0
  23. package/dist/runtime/app/components/FormBuilderInsertRow.d.vue.ts +20 -0
  24. package/dist/runtime/app/components/FormBuilderInsertRow.vue +51 -0
  25. package/dist/runtime/app/components/FormBuilderInsertRow.vue.d.ts +20 -0
  26. package/dist/runtime/app/components/FormBuilderOptions.d.vue.ts +11 -0
  27. package/dist/runtime/app/components/FormBuilderOptions.vue +81 -0
  28. package/dist/runtime/app/components/FormBuilderOptions.vue.d.ts +11 -0
  29. package/dist/runtime/app/components/FormBuilderPhoneSettings.d.vue.ts +11 -0
  30. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue +63 -0
  31. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue.d.ts +11 -0
  32. package/dist/runtime/app/components/FormBuilderSettings.d.vue.ts +11 -0
  33. package/dist/runtime/app/components/FormBuilderSettings.vue +233 -0
  34. package/dist/runtime/app/components/FormBuilderSettings.vue.d.ts +11 -0
  35. package/dist/runtime/app/components/FormBuilderStepHeader.d.vue.ts +27 -0
  36. package/dist/runtime/app/components/FormBuilderStepHeader.vue +71 -0
  37. package/dist/runtime/app/components/FormBuilderStepHeader.vue.d.ts +27 -0
  38. package/dist/runtime/app/components/FormBuilderStepSettings.d.vue.ts +13 -0
  39. package/dist/runtime/app/components/FormBuilderStepSettings.vue +33 -0
  40. package/dist/runtime/app/components/FormBuilderStepSettings.vue.d.ts +13 -0
  41. package/dist/runtime/app/components/FormBuilderText.d.vue.ts +19 -0
  42. package/dist/runtime/app/components/FormBuilderText.vue +127 -0
  43. package/dist/runtime/app/components/FormBuilderText.vue.d.ts +19 -0
  44. package/dist/runtime/app/components/FormCaptcha.d.vue.ts +19 -0
  45. package/dist/runtime/app/components/FormCaptcha.vue +95 -0
  46. package/dist/runtime/app/components/FormCaptcha.vue.d.ts +19 -0
  47. package/dist/runtime/app/components/FormEmbed.d.vue.ts +17 -0
  48. package/dist/runtime/app/components/FormEmbed.vue +113 -0
  49. package/dist/runtime/app/components/FormEmbed.vue.d.ts +17 -0
  50. package/dist/runtime/app/components/FormField.d.vue.ts +4 -0
  51. package/dist/runtime/app/components/FormField.vue +35 -3
  52. package/dist/runtime/app/components/FormField.vue.d.ts +4 -0
  53. package/dist/runtime/app/components/FormPhoneInput.d.vue.ts +20 -0
  54. package/dist/runtime/app/components/FormPhoneInput.vue +56 -0
  55. package/dist/runtime/app/components/FormPhoneInput.vue.d.ts +20 -0
  56. package/dist/runtime/app/components/FormPreview.d.vue.ts +12 -0
  57. package/dist/runtime/app/components/FormPreview.vue +244 -0
  58. package/dist/runtime/app/components/FormPreview.vue.d.ts +12 -0
  59. package/dist/runtime/app/components/FormRenderer.d.vue.ts +6 -0
  60. package/dist/runtime/app/components/FormRenderer.vue +16 -7
  61. package/dist/runtime/app/components/FormRenderer.vue.d.ts +6 -0
  62. package/dist/runtime/app/components/FormSubmissionView.vue +2 -2
  63. package/dist/runtime/app/composables/index.d.ts +4 -1
  64. package/dist/runtime/app/composables/index.js +4 -1
  65. package/dist/runtime/app/composables/useCaptcha.d.ts +22 -0
  66. package/dist/runtime/app/composables/useCaptcha.js +39 -0
  67. package/dist/runtime/app/composables/useCaptchaSecrets.d.ts +7 -0
  68. package/dist/runtime/app/composables/useCaptchaSecrets.js +69 -0
  69. package/dist/runtime/app/composables/useFormBuilderEditor.d.ts +49 -0
  70. package/dist/runtime/app/composables/useFormBuilderEditor.js +210 -0
  71. package/dist/runtime/app/composables/useFormBuilderLabels.d.ts +8 -0
  72. package/dist/runtime/app/composables/useFormBuilderLabels.js +41 -0
  73. package/dist/runtime/app/composables/useForms.d.ts +18 -3
  74. package/dist/runtime/app/composables/useForms.js +38 -5
  75. package/dist/runtime/app/pages/admin-form.vue +112 -69
  76. package/dist/runtime/app/pages/admin-submission.vue +7 -16
  77. package/dist/runtime/app/pages/admin-submissions.vue +41 -46
  78. package/dist/runtime/app/pages/admin.vue +128 -44
  79. package/dist/runtime/app/pages/form.vue +71 -5
  80. package/dist/runtime/i18n/lang/da.json +231 -3
  81. package/dist/runtime/i18n/lang/en.json +231 -3
  82. package/dist/runtime/menus.js +10 -0
  83. package/dist/runtime/server/db/schema.sqlite.d.ts +283 -0
  84. package/dist/runtime/server/db/schema.sqlite.js +33 -1
  85. package/dist/runtime/server/mcp/schemas.d.ts +9 -0
  86. package/dist/runtime/server/mcp/schemas.js +8 -0
  87. package/dist/runtime/server/mcp/tools/admin/create-form.d.ts +2 -0
  88. package/dist/runtime/server/mcp/tools/admin/create-form.js +21 -0
  89. package/dist/runtime/server/mcp/tools/admin/get-form-definition.d.ts +2 -0
  90. package/dist/runtime/server/mcp/tools/admin/get-form-definition.js +14 -0
  91. package/dist/runtime/server/mcp/tools/admin/get-submission.d.ts +2 -0
  92. package/dist/runtime/server/mcp/tools/admin/get-submission.js +26 -0
  93. package/dist/runtime/server/mcp/tools/admin/list-all-forms.d.ts +2 -0
  94. package/dist/runtime/server/mcp/tools/admin/list-all-forms.js +21 -0
  95. package/dist/runtime/server/mcp/tools/admin/list-submissions.d.ts +2 -0
  96. package/dist/runtime/server/mcp/tools/admin/list-submissions.js +22 -0
  97. package/dist/runtime/server/mcp/tools/admin/update-form.d.ts +2 -0
  98. package/dist/runtime/server/mcp/tools/admin/update-form.js +24 -0
  99. package/dist/runtime/server/mcp/tools/get-form.d.ts +2 -0
  100. package/dist/runtime/server/mcp/tools/get-form.js +12 -0
  101. package/dist/runtime/server/mcp/tools/list-forms.d.ts +2 -0
  102. package/dist/runtime/server/mcp/tools/list-forms.js +11 -0
  103. package/dist/runtime/server/mcp/tools/submit-form.d.ts +2 -0
  104. package/dist/runtime/server/mcp/tools/submit-form.js +17 -0
  105. package/dist/runtime/server/plugins/email-delivery.js +16 -11
  106. package/dist/runtime/server/plugins/encryption.d.ts +2 -0
  107. package/dist/runtime/server/plugins/encryption.js +4 -0
  108. package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
  109. package/dist/runtime/server/plugins/staff-scopes.js +1 -0
  110. package/dist/runtime/server/routes/admin/form-clone.post.d.ts +2 -0
  111. package/dist/runtime/server/routes/admin/form-clone.post.js +17 -0
  112. package/dist/runtime/server/routes/admin/form-password.delete.d.ts +2 -0
  113. package/dist/runtime/server/routes/admin/form-password.delete.js +8 -0
  114. package/dist/runtime/server/routes/admin/form-password.get.d.ts +2 -0
  115. package/dist/runtime/server/routes/admin/form-password.get.js +8 -0
  116. package/dist/runtime/server/routes/admin/form-password.put.d.ts +2 -0
  117. package/dist/runtime/server/routes/admin/form-password.put.js +14 -0
  118. package/dist/runtime/server/routes/admin/form-secret.delete.d.ts +2 -0
  119. package/dist/runtime/server/routes/admin/form-secret.delete.js +8 -0
  120. package/dist/runtime/server/routes/admin/form-secrets.get.d.ts +2 -0
  121. package/dist/runtime/server/routes/admin/form-secrets.get.js +5 -0
  122. package/dist/runtime/server/routes/admin/form-secrets.put.d.ts +2 -0
  123. package/dist/runtime/server/routes/admin/form-secrets.put.js +12 -0
  124. package/dist/runtime/server/routes/admin/form-token.delete.d.ts +2 -0
  125. package/dist/runtime/server/routes/admin/form-token.delete.js +12 -0
  126. package/dist/runtime/server/routes/admin/form-tokens.get.d.ts +2 -0
  127. package/dist/runtime/server/routes/admin/form-tokens.get.js +8 -0
  128. package/dist/runtime/server/routes/admin/form-tokens.post.d.ts +2 -0
  129. package/dist/runtime/server/routes/admin/form-tokens.post.js +12 -0
  130. package/dist/runtime/server/routes/admin/form.get.js +5 -1
  131. package/dist/runtime/server/routes/admin/form.patch.js +3 -12
  132. package/dist/runtime/server/routes/admin/forms.post.js +1 -6
  133. package/dist/runtime/server/routes/public/access.get.d.ts +2 -0
  134. package/dist/runtime/server/routes/public/access.get.js +3 -0
  135. package/dist/runtime/server/routes/public/form.get.js +3 -1
  136. package/dist/runtime/server/routes/public/submit.post.js +2 -2
  137. package/dist/runtime/server/utils/access.d.ts +27 -0
  138. package/dist/runtime/server/utils/access.js +63 -0
  139. package/dist/runtime/server/utils/admin-schemas.d.ts +219 -15
  140. package/dist/runtime/server/utils/auth.d.ts +7 -7
  141. package/dist/runtime/server/utils/auth.js +4 -4
  142. package/dist/runtime/server/utils/captcha.d.ts +10 -0
  143. package/dist/runtime/server/utils/captcha.js +25 -0
  144. package/dist/runtime/server/utils/crypto.d.ts +18 -0
  145. package/dist/runtime/server/utils/crypto.js +48 -0
  146. package/dist/runtime/server/utils/forms.d.ts +6 -1
  147. package/dist/runtime/server/utils/forms.js +28 -0
  148. package/dist/runtime/server/utils/mcp-access.d.ts +9 -0
  149. package/dist/runtime/server/utils/mcp-access.js +8 -0
  150. package/dist/runtime/server/utils/rate-limit.d.ts +3 -0
  151. package/dist/runtime/server/utils/rate-limit.js +19 -0
  152. package/dist/runtime/server/utils/render.d.ts +2 -11
  153. package/dist/runtime/server/utils/render.js +1 -26
  154. package/dist/runtime/server/utils/secrets.d.ts +28 -0
  155. package/dist/runtime/server/utils/secrets.js +60 -0
  156. package/dist/runtime/server/utils/submit.d.ts +12 -1
  157. package/dist/runtime/server/utils/submit.js +18 -4
  158. package/dist/runtime/utils/builder.d.ts +73 -0
  159. package/dist/runtime/utils/builder.js +220 -0
  160. package/dist/runtime/utils/captcha.d.ts +9 -0
  161. package/dist/runtime/utils/captcha.js +7 -0
  162. package/dist/runtime/utils/conditions.d.ts +3 -1
  163. package/dist/runtime/utils/conditions.js +4 -0
  164. package/dist/runtime/utils/definition.d.ts +116 -6
  165. package/dist/runtime/utils/definition.js +53 -4
  166. package/dist/runtime/utils/embed.d.ts +8 -0
  167. package/dist/runtime/utils/embed.js +7 -0
  168. package/dist/runtime/utils/index.d.ts +16 -3
  169. package/dist/runtime/utils/index.js +66 -3
  170. package/dist/runtime/utils/markdown.d.ts +1 -5
  171. package/dist/runtime/utils/markdown.js +1 -5
  172. package/dist/runtime/utils/phone.d.ts +29 -0
  173. package/dist/runtime/utils/phone.js +294 -0
  174. package/dist/runtime/utils/preview.d.ts +12 -0
  175. package/dist/runtime/utils/preview.js +17 -0
  176. package/dist/runtime/utils/schedule.d.ts +1 -0
  177. package/dist/runtime/utils/schedule.js +1 -0
  178. package/dist/runtime/utils/schema.d.ts +2 -0
  179. package/dist/runtime/utils/schema.js +12 -4
  180. package/dist/runtime/utils/template.d.ts +57 -0
  181. package/dist/runtime/utils/template.js +147 -0
  182. package/dist/runtime/utils/text.d.ts +0 -3
  183. package/dist/runtime/utils/text.js +0 -5
  184. package/dist/types.d.mts +5 -1
  185. package/package.json +13 -5
@@ -1,5 +1,37 @@
1
1
  import { z } from 'zod';
2
- import type { FormDefinition } from '../../types.js';
2
+ import type { FormAccess, FormDefinition } from '../../types.js';
3
+ export declare const accessSchema: z.ZodObject<{
4
+ availability: z.ZodDefault<z.ZodEnum<{
5
+ web: "web";
6
+ api: "api";
7
+ }>>;
8
+ auth: z.ZodDefault<z.ZodEnum<{
9
+ none: "none";
10
+ user: "user";
11
+ token: "token";
12
+ password: "password";
13
+ }>>;
14
+ schedule: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
15
+ type: z.ZodLiteral<"always">;
16
+ }, z.core.$strip>, z.ZodObject<{
17
+ type: z.ZodLiteral<"range">;
18
+ from: z.ZodOptional<z.ZodString>;
19
+ to: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>, z.ZodObject<{
21
+ type: z.ZodLiteral<"yearly">;
22
+ from: z.ZodObject<{
23
+ month: z.ZodNumber;
24
+ day: z.ZodNumber;
25
+ time: z.ZodString;
26
+ }, z.core.$strip>;
27
+ to: z.ZodObject<{
28
+ month: z.ZodNumber;
29
+ day: z.ZodNumber;
30
+ time: z.ZodString;
31
+ }, z.core.$strip>;
32
+ timezone: z.ZodString;
33
+ }, z.core.$strip>], "type">>;
34
+ }, z.core.$strip>;
3
35
  export declare const formDefinitionSchema: z.ZodObject<{
4
36
  mode: z.ZodEnum<{
5
37
  single: "single";
@@ -16,9 +48,11 @@ export declare const formDefinitionSchema: z.ZodObject<{
16
48
  showIf: z.ZodOptional<z.ZodObject<{
17
49
  field: z.ZodString;
18
50
  op: z.ZodEnum<{
51
+ in: "in";
19
52
  eq: "eq";
20
53
  neq: "neq";
21
- in: "in";
54
+ empty: "empty";
55
+ notEmpty: "notEmpty";
22
56
  truthy: "truthy";
23
57
  }>;
24
58
  value: z.ZodOptional<z.ZodUnknown>;
@@ -27,16 +61,17 @@ export declare const formDefinitionSchema: z.ZodObject<{
27
61
  key: z.ZodString;
28
62
  type: z.ZodEnum<{
29
63
  number: "number";
30
- text: "text";
64
+ date: "date";
65
+ file: "file";
31
66
  email: "email";
67
+ text: "text";
32
68
  tel: "tel";
33
69
  textarea: "textarea";
34
70
  select: "select";
35
71
  radio: "radio";
36
72
  checkbox: "checkbox";
37
- date: "date";
38
- file: "file";
39
73
  markdown: "markdown";
74
+ captcha: "captcha";
40
75
  }>;
41
76
  content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
42
77
  label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
@@ -60,12 +95,39 @@ export declare const formDefinitionSchema: z.ZodObject<{
60
95
  accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
61
96
  message: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
62
97
  }, z.core.$strip>>;
98
+ phone: z.ZodOptional<z.ZodObject<{
99
+ mode: z.ZodOptional<z.ZodEnum<{
100
+ simple: "simple";
101
+ country: "country";
102
+ }>>;
103
+ countries: z.ZodOptional<z.ZodArray<z.ZodString>>;
104
+ defaultCountry: z.ZodOptional<z.ZodString>;
105
+ }, z.core.$strip>>;
106
+ captcha: z.ZodOptional<z.ZodObject<{
107
+ provider: z.ZodOptional<z.ZodEnum<{
108
+ turnstile: "turnstile";
109
+ recaptcha: "recaptcha";
110
+ hcaptcha: "hcaptcha";
111
+ }>>;
112
+ siteKey: z.ZodOptional<z.ZodString>;
113
+ theme: z.ZodOptional<z.ZodEnum<{
114
+ auto: "auto";
115
+ light: "light";
116
+ dark: "dark";
117
+ }>>;
118
+ size: z.ZodOptional<z.ZodEnum<{
119
+ normal: "normal";
120
+ compact: "compact";
121
+ }>>;
122
+ }, z.core.$strip>>;
63
123
  showIf: z.ZodOptional<z.ZodObject<{
64
124
  field: z.ZodString;
65
125
  op: z.ZodEnum<{
126
+ in: "in";
66
127
  eq: "eq";
67
128
  neq: "neq";
68
- in: "in";
129
+ empty: "empty";
130
+ notEmpty: "notEmpty";
69
131
  truthy: "truthy";
70
132
  }>;
71
133
  value: z.ZodOptional<z.ZodUnknown>;
@@ -80,12 +142,60 @@ export declare const formDefinitionSchema: z.ZodObject<{
80
142
  keep: z.ZodArray<z.ZodString>;
81
143
  }, z.core.$strip>>;
82
144
  }, z.core.$strip>>;
145
+ access: z.ZodOptional<z.ZodObject<{
146
+ availability: z.ZodDefault<z.ZodEnum<{
147
+ web: "web";
148
+ api: "api";
149
+ }>>;
150
+ auth: z.ZodDefault<z.ZodEnum<{
151
+ none: "none";
152
+ user: "user";
153
+ token: "token";
154
+ password: "password";
155
+ }>>;
156
+ schedule: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
157
+ type: z.ZodLiteral<"always">;
158
+ }, z.core.$strip>, z.ZodObject<{
159
+ type: z.ZodLiteral<"range">;
160
+ from: z.ZodOptional<z.ZodString>;
161
+ to: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ type: z.ZodLiteral<"yearly">;
164
+ from: z.ZodObject<{
165
+ month: z.ZodNumber;
166
+ day: z.ZodNumber;
167
+ time: z.ZodString;
168
+ }, z.core.$strip>;
169
+ to: z.ZodObject<{
170
+ month: z.ZodNumber;
171
+ day: z.ZodNumber;
172
+ time: z.ZodString;
173
+ }, z.core.$strip>;
174
+ timezone: z.ZodString;
175
+ }, z.core.$strip>], "type">>;
176
+ }, z.core.$strip>>;
83
177
  delivery: z.ZodOptional<z.ZodObject<{
84
178
  email: z.ZodOptional<z.ZodObject<{
85
179
  to: z.ZodArray<z.ZodString>;
86
180
  subject: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
181
+ body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
182
+ routes: z.ZodOptional<z.ZodObject<{
183
+ field: z.ZodString;
184
+ to: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
185
+ }, z.core.$strip>>;
186
+ replyToField: z.ZodOptional<z.ZodString>;
87
187
  }, z.core.$strip>>;
88
188
  }, z.core.$strip>>;
89
189
  }, z.core.$strip>;
90
190
  export type FormDefinitionInput = z.input<typeof formDefinitionSchema>;
91
191
  export declare function parseFormDefinition(input: unknown): FormDefinition;
192
+ /** Recipients and reply-to of an email delivery for the given answers (a matching route wins over `to`). */
193
+ export declare function resolveEmailDelivery(email: NonNullable<NonNullable<FormDefinition['delivery']>['email']>, answers: Record<string, unknown>): {
194
+ to: string[];
195
+ replyTo: string | undefined;
196
+ };
197
+ export declare const defaultAccess: FormAccess;
198
+ /** The effective access of a definition (older definitions have none and stay open). */
199
+ export declare function resolveAccess(definition: {
200
+ access?: Partial<FormAccess>;
201
+ }): FormAccess;
@@ -1,15 +1,22 @@
1
1
  import { z } from "zod";
2
+ import { scheduleSchema } from "@frigear-nu/module-kit/schedule";
3
+ import { isPhoneCountry } from "./phone.js";
2
4
  const translated = z.union([z.string(), z.record(z.string(), z.string())]);
5
+ const MAX_BODY = 1e4;
6
+ const bodyText = z.union([
7
+ z.string().max(MAX_BODY),
8
+ z.record(z.string(), z.string().max(MAX_BODY))
9
+ ]);
3
10
  const KEY = /^[a-z]\w*$/i;
4
11
  const ID = /^[a-z0-9][\w-]*$/i;
5
12
  const condition = z.object({
6
13
  field: z.string().regex(KEY),
7
- op: z.enum(["eq", "neq", "in", "truthy"]),
14
+ op: z.enum(["eq", "neq", "in", "empty", "notEmpty", "truthy"]),
8
15
  value: z.unknown().optional()
9
16
  });
10
17
  const field = z.object({
11
18
  key: z.string().regex(KEY, "Field keys must start with a letter and contain only letters, digits and underscores."),
12
- type: z.enum(["text", "email", "tel", "number", "textarea", "select", "radio", "checkbox", "date", "file", "markdown"]),
19
+ type: z.enum(["text", "email", "tel", "number", "textarea", "select", "radio", "checkbox", "date", "file", "markdown", "captcha"]),
13
20
  content: translated.optional(),
14
21
  label: translated.optional(),
15
22
  description: translated.optional(),
@@ -29,6 +36,17 @@ const field = z.object({
29
36
  accept: z.array(z.string()).optional(),
30
37
  message: translated.optional()
31
38
  }).optional(),
39
+ phone: z.object({
40
+ mode: z.enum(["simple", "country"]).optional(),
41
+ countries: z.array(z.string().toUpperCase().refine(isPhoneCountry, "Unknown country code.")).max(300).optional(),
42
+ defaultCountry: z.string().toUpperCase().refine(isPhoneCountry, "Unknown country code.").optional()
43
+ }).optional(),
44
+ captcha: z.object({
45
+ provider: z.enum(["turnstile", "recaptcha", "hcaptcha"]).optional(),
46
+ siteKey: z.string().max(200).regex(/^[\w-]*$/, "Site keys contain only letters, digits, dashes and underscores.").optional(),
47
+ theme: z.enum(["auto", "light", "dark"]).optional(),
48
+ size: z.enum(["normal", "compact"]).optional()
49
+ }).optional(),
32
50
  showIf: condition.optional()
33
51
  });
34
52
  const step = z.object({
@@ -40,6 +58,11 @@ const step = z.object({
40
58
  });
41
59
  const sameOriginPath = (value) => value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/\\");
42
60
  const emailAddress = z.string().regex(/^[^\s@${}]+@[^\s$@{}][^\s$.@{}]*\.[^\s$@{}]+$/, "Recipients must be plain email addresses.");
61
+ export const accessSchema = z.object({
62
+ availability: z.enum(["web", "api"]).default("web"),
63
+ auth: z.enum(["none", "user", "token", "password"]).default("none"),
64
+ schedule: scheduleSchema.optional()
65
+ });
43
66
  export const formDefinitionSchema = z.object({
44
67
  mode: z.enum(["single", "stepped"]),
45
68
  storage: z.enum(["db", "none"]).default("db"),
@@ -50,14 +73,24 @@ export const formDefinitionSchema = z.object({
50
73
  redirect: z.string().refine(sameOriginPath, "Redirect must be a path on this site.").optional(),
51
74
  allowResubmit: z.object({ keep: z.array(z.string()) }).optional()
52
75
  }).optional(),
76
+ access: accessSchema.optional(),
53
77
  delivery: z.object({
54
- email: z.object({ to: z.array(emailAddress).min(1), subject: translated.optional() }).optional()
78
+ email: z.object({
79
+ to: z.array(emailAddress).min(1),
80
+ subject: translated.optional(),
81
+ body: bodyText.optional(),
82
+ routes: z.object({ field: z.string(), to: z.record(z.string(), z.array(emailAddress).min(1)) }).optional(),
83
+ replyToField: z.string().optional()
84
+ }).optional()
55
85
  }).optional()
56
86
  }).superRefine((definition, ctx) => {
57
87
  const fail = (message, path) => ctx.addIssue({ code: "custom", message, path });
58
88
  if (definition.mode === "single" && definition.steps.length !== 1) {
59
89
  fail("A single page form has exactly one step.", ["steps"]);
60
90
  }
91
+ if (definition.access?.auth === "token" && definition.access.availability !== "api") {
92
+ fail("Token access needs the form to be API only.", ["access", "availability"]);
93
+ }
61
94
  const stepIds = /* @__PURE__ */ new Set();
62
95
  const keys = /* @__PURE__ */ new Map();
63
96
  definition.steps.forEach((s, stepIndex) => {
@@ -91,9 +124,16 @@ export const formDefinitionSchema = z.object({
91
124
  check(s.showIf, ["steps", stepIndex, "showIf", "field"]);
92
125
  s.fields.forEach((f, fieldIndex) => {
93
126
  check(f.showIf, ["steps", stepIndex, "fields", fieldIndex, "showIf", "field"]);
94
- if (f.type !== "markdown") seen.add(f.key);
127
+ if (f.type !== "markdown" && f.type !== "captcha") seen.add(f.key);
95
128
  });
96
129
  });
130
+ const emailDelivery = definition.delivery?.email;
131
+ if (emailDelivery?.routes && !seen.has(emailDelivery.routes.field)) {
132
+ fail(`Unknown field "${emailDelivery.routes.field}".`, ["delivery", "email", "routes", "field"]);
133
+ }
134
+ if (emailDelivery?.replyToField && keys.get(emailDelivery.replyToField) !== "email") {
135
+ fail("Reply-to must be an email field.", ["delivery", "email", "replyToField"]);
136
+ }
97
137
  definition.submit?.allowResubmit?.keep.forEach((key, index) => {
98
138
  if (!keys.has(key)) fail(`Unknown field "${key}".`, ["submit", "allowResubmit", "keep", index]);
99
139
  });
@@ -101,3 +141,12 @@ export const formDefinitionSchema = z.object({
101
141
  export function parseFormDefinition(input) {
102
142
  return formDefinitionSchema.parse(input);
103
143
  }
144
+ export function resolveEmailDelivery(email, answers) {
145
+ const routed = email.routes?.to[String(answers[email.routes.field] ?? "")];
146
+ const replyTo = email.replyToField ? String(answers[email.replyToField] ?? "").trim() : "";
147
+ return { to: routed?.length ? routed : email.to, replyTo: replyTo || void 0 };
148
+ }
149
+ export const defaultAccess = { availability: "web", auth: "none" };
150
+ export function resolveAccess(definition) {
151
+ return { ...defaultAccess, ...definition.access };
152
+ }
@@ -0,0 +1,8 @@
1
+ import type { FormAccessInfoDto } from '../../types.js';
2
+ /** What `<FormEmbed>` renders for a form. */
3
+ export type EmbedMode = 'form' | 'password' | 'sign-in' | 'hidden' | 'not-found';
4
+ /**
5
+ * Picks what an embedded form shows from its access info. API-only forms and token forms have no
6
+ * public UI (a bearer token cannot be typed into a page), so they render nothing; a missing form is `not-found`.
7
+ */
8
+ export declare function embedMode(access: FormAccessInfoDto | null | undefined): EmbedMode;
@@ -0,0 +1,7 @@
1
+ export function embedMode(access) {
2
+ if (!access) return "not-found";
3
+ if (access.access.availability === "api" || access.access.auth === "token") return "hidden";
4
+ if (access.access.auth === "user" && !access.signedIn) return "sign-in";
5
+ if (access.access.auth === "password") return "password";
6
+ return "form";
7
+ }
@@ -1,13 +1,26 @@
1
1
  import type { FormDefinition } from '../../types.js';
2
2
  export type * from '../../types';
3
- export { translate, interpolate, defaultMessages, newId } from './text.js';
3
+ export { interpolate, defaultMessages, newId } from './text.js';
4
+ export { translate, readTranslated, writeTranslated, expandTranslated, collapseTranslated } from '@frigear-nu/ui/translated';
4
5
  export type { MessageTranslator, TranslateParams } from './text.js';
5
- export { evaluateCondition, resolveVisibility, isFieldVisible, isInputField } from './conditions.js';
6
+ export { evaluateCondition, resolveVisibility, isFieldVisible, isInputField, isAnswerField } from './conditions.js';
6
7
  export type { Answers, VisibleStep } from './conditions.js';
7
8
  export { buildFormSchema, buildFieldSchema, validatedFields } from './schema.js';
8
9
  export type { SchemaOptions } from './schema.js';
9
- export { formDefinitionSchema, parseFormDefinition } from './definition.js';
10
+ export { fieldTypes, arrayMove, moveField, moveStep, dropIndex, insertField, insertStep, removeField, updateField, updateStep, duplicateField, createField, createStep, removeStep, changeMode, pruneReferences, renameFieldKey, allFieldKeys, uniqueFieldKey, uniqueStepId, setEmailRecipients, patchEmailDelivery, setRouteField, setRoute, setStorage, setAccess, storageWarnings, emailFieldKeys, routableFields, } from './builder.js';
11
+ export type { FieldPosition } from './builder.js';
12
+ export { formDefinitionSchema, accessSchema, defaultAccess, resolveAccess, parseFormDefinition, resolveEmailDelivery } from './definition.js';
10
13
  export type { FormDefinitionInput } from './definition.js';
11
14
  /** Typed identity helper for authoring definitions (seed files, tests). */
12
15
  export declare function defineForm<const T extends FormDefinition>(definition: T): T;
13
16
  export { renderMarkdown } from './markdown.js';
17
+ export { captchaProviders, normalizeCaptchaProvider, resolveCaptcha } from './captcha.js';
18
+ export { previewPayload, previewScale, PREVIEW_WIDTHS } from './preview.js';
19
+ export type { PreviewDevice } from './preview.js';
20
+ export { renderAnswers, formatValue, escapeHtml, answersToText, answersToHtml, composeEmail, renderTemplateText, renderTemplateHtml, renderTemplateLine, templateVariables, availableVariables, unknownBodyVariables, sampleAnswers, staticVariables, } from './template.js';
21
+ export type { RenderedAnswer, EmailContext } from './template.js';
22
+ export { allPhoneCountries, phoneCountries, defaultPhoneCountry, countryName, composePhone, parsePhone, phoneIssue, isPhoneCountry, isCountryPhone, } from './phone.js';
23
+ export type { PhoneCountry } from './phone.js';
24
+ export { embedMode } from './embed.js';
25
+ export type { EmbedMode } from './embed.js';
26
+ export { isFormOpen, nextFormOpening } from './schedule.js';
@@ -1,8 +1,71 @@
1
- export { translate, interpolate, defaultMessages, newId } from "./text.js";
2
- export { evaluateCondition, resolveVisibility, isFieldVisible, isInputField } from "./conditions.js";
1
+ export { interpolate, defaultMessages, newId } from "./text.js";
2
+ export { translate, readTranslated, writeTranslated, expandTranslated, collapseTranslated } from "@frigear-nu/ui/translated";
3
+ export { evaluateCondition, resolveVisibility, isFieldVisible, isInputField, isAnswerField } from "./conditions.js";
3
4
  export { buildFormSchema, buildFieldSchema, validatedFields } from "./schema.js";
4
- export { formDefinitionSchema, parseFormDefinition } from "./definition.js";
5
+ export {
6
+ fieldTypes,
7
+ arrayMove,
8
+ moveField,
9
+ moveStep,
10
+ dropIndex,
11
+ insertField,
12
+ insertStep,
13
+ removeField,
14
+ updateField,
15
+ updateStep,
16
+ duplicateField,
17
+ createField,
18
+ createStep,
19
+ removeStep,
20
+ changeMode,
21
+ pruneReferences,
22
+ renameFieldKey,
23
+ allFieldKeys,
24
+ uniqueFieldKey,
25
+ uniqueStepId,
26
+ setEmailRecipients,
27
+ patchEmailDelivery,
28
+ setRouteField,
29
+ setRoute,
30
+ setStorage,
31
+ setAccess,
32
+ storageWarnings,
33
+ emailFieldKeys,
34
+ routableFields
35
+ } from "./builder.js";
36
+ export { formDefinitionSchema, accessSchema, defaultAccess, resolveAccess, parseFormDefinition, resolveEmailDelivery } from "./definition.js";
5
37
  export function defineForm(definition) {
6
38
  return definition;
7
39
  }
8
40
  export { renderMarkdown } from "./markdown.js";
41
+ export { captchaProviders, normalizeCaptchaProvider, resolveCaptcha } from "./captcha.js";
42
+ export { previewPayload, previewScale, PREVIEW_WIDTHS } from "./preview.js";
43
+ export {
44
+ renderAnswers,
45
+ formatValue,
46
+ escapeHtml,
47
+ answersToText,
48
+ answersToHtml,
49
+ composeEmail,
50
+ renderTemplateText,
51
+ renderTemplateHtml,
52
+ renderTemplateLine,
53
+ templateVariables,
54
+ availableVariables,
55
+ unknownBodyVariables,
56
+ sampleAnswers,
57
+ staticVariables
58
+ } from "./template.js";
59
+ export {
60
+ allPhoneCountries,
61
+ phoneCountries,
62
+ defaultPhoneCountry,
63
+ countryName,
64
+ composePhone,
65
+ parsePhone,
66
+ phoneIssue,
67
+ isPhoneCountry,
68
+ isCountryPhone
69
+ } from "./phone.js";
70
+ export { embedMode } from "./embed.js";
71
+ export { isFormOpen, nextFormOpening } from "./schedule.js";
@@ -1,5 +1 @@
1
- /**
2
- * Tiny, safe renderer for `markdown` fields: escapes everything, then supports `**bold**`,
3
- * `_italic_`, `[text](https://…)` links and blank-line separated paragraphs.
4
- */
5
- export declare function renderMarkdown(source: string): string;
1
+ export { renderMarkdown } from '@frigear-nu/ui/markdown';
@@ -1,5 +1 @@
1
- const escape = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2
- const safeHref = (href) => /^(?:https?:\/\/|\/(?![/\\])|mailto:)/.test(href);
3
- export function renderMarkdown(source) {
4
- return source.split(/\n{2,}/).map((paragraph) => escape(paragraph.trim()).replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\b_(.+?)_\b/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (match, label, href) => safeHref(href.replace(/&amp;/g, "&")) ? `<a href="${href}" target="_blank" rel="noopener" class="underline">${label}</a>` : match).replace(/\n/g, "<br>")).filter(Boolean).map((paragraph) => `<p>${paragraph}</p>`).join("");
5
- }
1
+ export { renderMarkdown } from "@frigear-nu/ui/markdown";
@@ -0,0 +1,29 @@
1
+ import type { FormField, PhoneConfig } from '../../types.js';
2
+ export declare const DEFAULT_PHONE_COUNTRY = "DK";
3
+ export type PhoneCountry = {
4
+ code: string;
5
+ dial: string;
6
+ flag: string;
7
+ };
8
+ export declare const allPhoneCountries: PhoneCountry[];
9
+ export declare const isPhoneCountry: (code: string) => boolean;
10
+ /** Whether the field collects its phone number with a separate country selector. */
11
+ export declare const isCountryPhone: (field: Pick<FormField, "type" | "phone">) => boolean;
12
+ /** The countries a phone field offers, in the order of its `countries` allow-list (unknown codes dropped), or every country. */
13
+ export declare function phoneCountries(config?: PhoneConfig): PhoneCountry[];
14
+ /** The preselected country: `defaultCountry` when offered, else Denmark when offered, else the first one offered. */
15
+ export declare function defaultPhoneCountry(config?: PhoneConfig): string;
16
+ /** Localised country name, falling back to the ISO code. */
17
+ export declare function countryName(code: string, locale: string): string;
18
+ /** Merges a country and the national number into the single stored value, e.g. `+4512345678`. Empty without digits. */
19
+ export declare function composePhone(country: string, national: string): string;
20
+ /**
21
+ * Splits a stored value back into country and national digits. Among the offered countries the longest
22
+ * matching dialing code wins; `preferred` wins between countries sharing that code (`+1`, `+44`, ...).
23
+ */
24
+ export declare function parsePhone(value: string, config?: PhoneConfig, preferred?: string): {
25
+ country: string;
26
+ national: string;
27
+ };
28
+ /** Why a value is not an acceptable phone number for the field, or `undefined` when it is. */
29
+ export declare function phoneIssue(field: Pick<FormField, 'phone'>, value: string): 'format' | 'country' | undefined;