payload-dynamic-forms 0.0.0-stage → 0.1.0

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +404 -2
  3. package/dist/access.d.ts +7 -0
  4. package/dist/access.js +10 -0
  5. package/dist/access.js.map +1 -0
  6. package/dist/collections/attachments.d.ts +6 -0
  7. package/dist/collections/attachments.js +67 -0
  8. package/dist/collections/attachments.js.map +1 -0
  9. package/dist/collections/blocks.d.ts +27 -0
  10. package/dist/collections/blocks.js +495 -0
  11. package/dist/collections/blocks.js.map +1 -0
  12. package/dist/collections/forms.d.ts +3 -0
  13. package/dist/collections/forms.js +553 -0
  14. package/dist/collections/forms.js.map +1 -0
  15. package/dist/collections/overrides.d.ts +7 -0
  16. package/dist/collections/overrides.js +49 -0
  17. package/dist/collections/overrides.js.map +1 -0
  18. package/dist/collections/ruleFields.d.ts +6 -0
  19. package/dist/collections/ruleFields.js +97 -0
  20. package/dist/collections/ruleFields.js.map +1 -0
  21. package/dist/collections/submissions.d.ts +7 -0
  22. package/dist/collections/submissions.js +225 -0
  23. package/dist/collections/submissions.js.map +1 -0
  24. package/dist/delivery/run.d.ts +36 -0
  25. package/dist/delivery/run.js +187 -0
  26. package/dist/delivery/run.js.map +1 -0
  27. package/dist/delivery/webhook.d.ts +26 -0
  28. package/dist/delivery/webhook.js +71 -0
  29. package/dist/delivery/webhook.js.map +1 -0
  30. package/dist/endpoints/public.d.ts +4 -0
  31. package/dist/endpoints/public.js +27 -0
  32. package/dist/endpoints/public.js.map +1 -0
  33. package/dist/endpoints/resend.d.ts +4 -0
  34. package/dist/endpoints/resend.js +58 -0
  35. package/dist/endpoints/resend.js.map +1 -0
  36. package/dist/endpoints/submit.d.ts +4 -0
  37. package/dist/endpoints/submit.js +320 -0
  38. package/dist/endpoints/submit.js.map +1 -0
  39. package/dist/exports/shared.d.ts +13 -0
  40. package/dist/exports/shared.js +13 -0
  41. package/dist/exports/shared.js.map +1 -0
  42. package/dist/formBlock.d.ts +13 -0
  43. package/dist/formBlock.js +25 -0
  44. package/dist/formBlock.js.map +1 -0
  45. package/dist/hooks/resendTick.d.ts +8 -0
  46. package/dist/hooks/resendTick.js +55 -0
  47. package/dist/hooks/resendTick.js.map +1 -0
  48. package/dist/hooks/validateForm.d.ts +8 -0
  49. package/dist/hooks/validateForm.js +251 -0
  50. package/dist/hooks/validateForm.js.map +1 -0
  51. package/dist/index.d.ts +18 -0
  52. package/dist/index.js +124 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/security/crypto.d.ts +2 -0
  55. package/dist/security/crypto.js +17 -0
  56. package/dist/security/crypto.js.map +1 -0
  57. package/dist/security/rateLimit.d.ts +13 -0
  58. package/dist/security/rateLimit.js +28 -0
  59. package/dist/security/rateLimit.js.map +1 -0
  60. package/dist/security/request.d.ts +3 -0
  61. package/dist/security/request.js +13 -0
  62. package/dist/security/request.js.map +1 -0
  63. package/dist/security/turnstile.d.ts +28 -0
  64. package/dist/security/turnstile.js +56 -0
  65. package/dist/security/turnstile.js.map +1 -0
  66. package/dist/server/forms.d.ts +73 -0
  67. package/dist/server/forms.js +215 -0
  68. package/dist/server/forms.js.map +1 -0
  69. package/dist/server/resend.d.ts +27 -0
  70. package/dist/server/resend.js +84 -0
  71. package/dist/server/resend.js.map +1 -0
  72. package/dist/shared/answers.d.ts +47 -0
  73. package/dist/shared/answers.js +282 -0
  74. package/dist/shared/answers.js.map +1 -0
  75. package/dist/shared/operators.d.ts +41 -0
  76. package/dist/shared/operators.js +54 -0
  77. package/dist/shared/operators.js.map +1 -0
  78. package/dist/shared/rules.d.ts +58 -0
  79. package/dist/shared/rules.js +324 -0
  80. package/dist/shared/rules.js.map +1 -0
  81. package/dist/shared/steps.d.ts +28 -0
  82. package/dist/shared/steps.js +109 -0
  83. package/dist/shared/steps.js.map +1 -0
  84. package/dist/shared/tokens.d.ts +25 -0
  85. package/dist/shared/tokens.js +96 -0
  86. package/dist/shared/tokens.js.map +1 -0
  87. package/dist/shared/types.d.ts +181 -0
  88. package/dist/shared/types.js +5 -0
  89. package/dist/shared/types.js.map +1 -0
  90. package/dist/shared/whatsapp.d.ts +20 -0
  91. package/dist/shared/whatsapp.js +26 -0
  92. package/dist/shared/whatsapp.js.map +1 -0
  93. package/dist/shared/words.d.ts +9 -0
  94. package/dist/shared/words.js +41 -0
  95. package/dist/shared/words.js.map +1 -0
  96. package/dist/types.d.ts +133 -0
  97. package/dist/types.js +3 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +113 -4
@@ -0,0 +1,282 @@
1
+ import { getFieldLabel, getInputFields, getOptions, getSectionKey, hasAnswer, isChoiceBlock, toStringList } from './rules.js';
2
+ import { getVisibility } from './steps.js';
3
+ import { interpolate } from './tokens.js';
4
+ import { fillWords } from './words.js';
5
+ const TRUE_WORDS = [
6
+ '1',
7
+ 'on',
8
+ 'true',
9
+ 'yes'
10
+ ];
11
+ /** Default limits for file-upload fields. */ export const DEFAULT_FILE_LIMITS = {
12
+ maxFileSize: 10 * 1024 * 1024,
13
+ maxTotalSize: 25 * 1024 * 1024,
14
+ mimeTypes: [
15
+ 'application/pdf',
16
+ 'image/jpeg',
17
+ 'image/png',
18
+ 'image/webp',
19
+ 'text/plain',
20
+ 'text/csv',
21
+ 'application/msword',
22
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
23
+ ]
24
+ };
25
+ /** One valid address: no spaces, commas or lists, a dot in the domain. */ export function isValidEmail(value) {
26
+ if (typeof value !== 'string') {
27
+ return false;
28
+ }
29
+ return /^[^\s@,;<>()"'[\]\\]+@[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?(?:\.[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?)+$/i.test(value.trim());
30
+ }
31
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
32
+ function isValidDate(value) {
33
+ if (!DATE.test(value)) {
34
+ return false;
35
+ }
36
+ const time = Date.parse(`${value}T00:00:00Z`);
37
+ return !Number.isNaN(time) && new Date(time).toISOString().slice(0, 10) === value;
38
+ }
39
+ function first(raw) {
40
+ return Array.isArray(raw) ? raw[0] : raw;
41
+ }
42
+ /** Turns a raw answer (form data or JSON) into the value the field type keeps. */ export function normalizeAnswer(field, raw, fieldTypes) {
43
+ const custom = fieldTypes?.[field.blockType]?.normalize;
44
+ if (custom) {
45
+ return custom(raw, field);
46
+ }
47
+ switch(field.blockType){
48
+ case 'checkbox':
49
+ {
50
+ const v = first(raw);
51
+ if (typeof v === 'boolean') {
52
+ return v;
53
+ }
54
+ return TRUE_WORDS.includes(String(v ?? '').trim().toLowerCase());
55
+ }
56
+ case 'checkboxGroup':
57
+ return toStringList(raw);
58
+ case 'number':
59
+ {
60
+ const v = first(raw);
61
+ if (typeof v === 'number') {
62
+ return Number.isFinite(v) ? v : String(v);
63
+ }
64
+ const text = String(v ?? '').trim();
65
+ if (!text) {
66
+ return '';
67
+ }
68
+ const n = Number(text);
69
+ return Number.isFinite(n) ? n : text;
70
+ }
71
+ default:
72
+ {
73
+ const v = first(raw);
74
+ if (v === null || v === undefined) {
75
+ return '';
76
+ }
77
+ return typeof v === 'object' ? '' : String(v).trim();
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * The text of an answer in messages and snapshots: option labels, file names, and the form's
83
+ * `yes` / `no` words for a checkbox (pass `fillWords(form.wording)` or the public form's wording).
84
+ */ export function answerToText(field, value, words, fieldTypes) {
85
+ const custom = fieldTypes?.[field.blockType]?.toText;
86
+ if (custom) {
87
+ return custom(value, field, words);
88
+ }
89
+ if (value === null || value === undefined) {
90
+ return '';
91
+ }
92
+ if (typeof value === 'boolean') {
93
+ return value ? words.yes : words.no;
94
+ }
95
+ if (typeof value === 'object' && !Array.isArray(value)) {
96
+ return Array.isArray(value.filenames) ? value.filenames.join(', ') : '';
97
+ }
98
+ if (isChoiceBlock(field)) {
99
+ const options = getOptions(field);
100
+ return toStringList(value).map((v)=>options.find((o)=>String(o.value) === v)?.label || v).join(', ');
101
+ }
102
+ if (Array.isArray(value)) {
103
+ return value.join(', ');
104
+ }
105
+ return String(value);
106
+ }
107
+ function checkValue(field, value) {
108
+ switch(field.blockType){
109
+ case 'checkboxGroup':
110
+ {
111
+ const listed = new Set(getOptions(field).map((o)=>String(o.value)));
112
+ return toStringList(value).every((v)=>listed.has(v)) ? null : 'invalid';
113
+ }
114
+ case 'date':
115
+ {
116
+ const text = String(value);
117
+ if (!isValidDate(text)) {
118
+ return 'invalid';
119
+ }
120
+ const min = field.minDate?.slice(0, 10);
121
+ const max = field.maxDate?.slice(0, 10);
122
+ if (min && text < min || max && text > max) {
123
+ return 'invalid';
124
+ }
125
+ return null;
126
+ }
127
+ case 'email':
128
+ return isValidEmail(value) ? null : 'invalid';
129
+ case 'number':
130
+ {
131
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
132
+ return 'invalid';
133
+ }
134
+ if (typeof field.min === 'number' && value < field.min) {
135
+ return 'invalid';
136
+ }
137
+ if (typeof field.max === 'number' && value > field.max) {
138
+ return 'invalid';
139
+ }
140
+ return null;
141
+ }
142
+ case 'radio':
143
+ case 'select':
144
+ {
145
+ const listed = new Set(getOptions(field).map((o)=>String(o.value)));
146
+ return listed.has(String(value)) ? null : 'invalid';
147
+ }
148
+ case 'text':
149
+ case 'textarea':
150
+ {
151
+ const length = [
152
+ ...String(value)
153
+ ].length;
154
+ if (typeof field.minLength === 'number' && field.minLength > 0 && length < field.minLength) {
155
+ return 'invalid';
156
+ }
157
+ if (typeof field.maxLength === 'number' && field.maxLength > 0 && length > field.maxLength) {
158
+ return 'invalid';
159
+ }
160
+ return null;
161
+ }
162
+ default:
163
+ return null;
164
+ }
165
+ }
166
+ /** Checks the files given to one upload field: count, MIME type and size. */ export function checkFiles(field, files, limits = {}) {
167
+ const maxFiles = Math.max(1, Number(field.maxFiles) || 1);
168
+ if (files.length > maxFiles) {
169
+ return false;
170
+ }
171
+ const pluginTypes = limits.mimeTypes || DEFAULT_FILE_LIMITS.mimeTypes;
172
+ const fieldTypes = (field.mimeTypes || []).filter((t)=>pluginTypes.includes(t));
173
+ const allowed = fieldTypes.length ? fieldTypes : pluginTypes;
174
+ const pluginMax = limits.maxFileSize ?? DEFAULT_FILE_LIMITS.maxFileSize;
175
+ const fieldMax = Number(field.maxFileSizeMB) > 0 ? Number(field.maxFileSizeMB) * 1024 * 1024 : pluginMax;
176
+ const maxSize = Math.min(fieldMax, pluginMax);
177
+ return files.every((file)=>allowed.includes(file.type || 'application/octet-stream') && file.size <= maxSize && file.size > 0);
178
+ }
179
+ function errorWords(field, key, problem, words) {
180
+ const label = getFieldLabel(field, key);
181
+ if (problem === 'empty') {
182
+ return field.words?.errorEmpty?.trim() || interpolate(words.required, {
183
+ label
184
+ });
185
+ }
186
+ if (field.words?.errorInvalid?.trim()) {
187
+ return field.words.errorInvalid.trim();
188
+ }
189
+ return interpolate(problem === 'file' ? words.fileRejected : words.invalid, {
190
+ label
191
+ });
192
+ }
193
+ /**
194
+ * Normalises raw answers, drops hidden ones (and, in steps, those of sections off the path), and
195
+ * reports a field error for every shown field that is required and empty or has a bad value.
196
+ */ export function checkAnswers(form, raw, options = {}) {
197
+ const words = options.words || fillWords(form.wording);
198
+ const files = options.files || {};
199
+ const normalized = {};
200
+ for (const { field, key } of getInputFields(form)){
201
+ if (field.blockType === 'fileUpload') {
202
+ normalized[key] = (files[key] || []).map((f)=>f.name || 'upload');
203
+ continue;
204
+ }
205
+ normalized[key] = normalizeAnswer(field, raw[key], options.fieldTypes);
206
+ }
207
+ const visibility = getVisibility(form, normalized);
208
+ const answers = {};
209
+ const fieldErrors = {};
210
+ const fileKeys = [];
211
+ let totalSize = 0;
212
+ const maxTotal = options.limits?.maxTotalSize ?? DEFAULT_FILE_LIMITS.maxTotalSize;
213
+ for (const { field, key } of getInputFields(form)){
214
+ if (!visibility.fieldKeys.has(key) || fieldErrors[key]) {
215
+ continue;
216
+ }
217
+ if (field.blockType === 'fileUpload') {
218
+ const given = files[key] || [];
219
+ if (!given.length) {
220
+ if (field.required) {
221
+ fieldErrors[key] = errorWords(field, key, 'empty', words);
222
+ }
223
+ continue;
224
+ }
225
+ fileKeys.push(key);
226
+ totalSize += given.reduce((sum, f)=>sum + f.size, 0);
227
+ if (!checkFiles(field, given, options.limits) || totalSize > maxTotal) {
228
+ fieldErrors[key] = errorWords(field, key, 'file', words);
229
+ }
230
+ continue;
231
+ }
232
+ const value = normalized[key];
233
+ answers[key] = value;
234
+ const given = hasAnswer(value);
235
+ if (!given) {
236
+ if (field.required) {
237
+ fieldErrors[key] = errorWords(field, key, 'empty', words);
238
+ }
239
+ continue;
240
+ }
241
+ const custom = options.fieldTypes?.[field.blockType]?.validate;
242
+ if (custom) {
243
+ const result = custom(value, {
244
+ answers: normalized,
245
+ field
246
+ });
247
+ if (result !== true) {
248
+ fieldErrors[key] = typeof result === 'string' && result ? result : errorWords(field, key, 'invalid', words);
249
+ continue;
250
+ }
251
+ }
252
+ const problem = checkValue(field, value);
253
+ if (problem) {
254
+ fieldErrors[key] = errorWords(field, key, problem, words);
255
+ }
256
+ }
257
+ return {
258
+ answers,
259
+ fieldErrors,
260
+ fileKeys,
261
+ ok: Object.keys(fieldErrors).length === 0
262
+ };
263
+ }
264
+ /** Section title, label and value per answered, shown field, as worded now. */ export function buildSnapshot(form, answers, fieldTypes) {
265
+ const words = fillWords(form.wording);
266
+ const rows = [];
267
+ for (const { field, key, section } of getInputFields(form)){
268
+ if (!(key in answers) || !hasAnswer(answers[key])) {
269
+ continue;
270
+ }
271
+ rows.push({
272
+ fieldKey: key,
273
+ label: getFieldLabel(field, key),
274
+ sectionKey: getSectionKey(section),
275
+ sectionTitle: section.title?.trim() || getSectionKey(section),
276
+ value: answerToText(field, answers[key], words, fieldTypes)
277
+ });
278
+ }
279
+ return rows;
280
+ }
281
+
282
+ //# sourceMappingURL=answers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared/answers.ts"],"sourcesContent":["import type {\n Answers,\n AnswerValue,\n FieldTypeChecks,\n FileLike,\n FormLike,\n FormWords,\n InputBlock,\n SnapshotRow,\n YesNoWords,\n} from './types.js'\n\nimport {\n getFieldLabel,\n getInputFields,\n getOptions,\n getSectionKey,\n hasAnswer,\n isChoiceBlock,\n toStringList,\n} from './rules.js'\nimport { getVisibility } from './steps.js'\nimport { interpolate } from './tokens.js'\nimport { fillWords } from './words.js'\n\nconst TRUE_WORDS = ['1', 'on', 'true', 'yes']\n\n/** Default limits for file-upload fields. */\nexport const DEFAULT_FILE_LIMITS = {\n maxFileSize: 10 * 1024 * 1024,\n maxTotalSize: 25 * 1024 * 1024,\n mimeTypes: [\n 'application/pdf',\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'text/plain',\n 'text/csv',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n ],\n} as const\n\nexport type FileLimits = {\n maxFileSize?: number\n maxTotalSize?: number\n mimeTypes?: readonly string[]\n}\n\n/** One valid address: no spaces, commas or lists, a dot in the domain. */\nexport function isValidEmail(value: unknown): boolean {\n if (typeof value !== 'string') {\n return false\n }\n return /^[^\\s@,;<>()\"'[\\]\\\\]+@[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?(?:\\.[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?)+$/i.test(\n value.trim(),\n )\n}\n\nconst DATE = /^\\d{4}-\\d{2}-\\d{2}$/\n\nfunction isValidDate(value: string): boolean {\n if (!DATE.test(value)) {\n return false\n }\n const time = Date.parse(`${value}T00:00:00Z`)\n return !Number.isNaN(time) && new Date(time).toISOString().slice(0, 10) === value\n}\n\nfunction first(raw: unknown): unknown {\n return Array.isArray(raw) ? raw[0] : raw\n}\n\n/** Turns a raw answer (form data or JSON) into the value the field type keeps. */\nexport function normalizeAnswer(\n field: InputBlock,\n raw: unknown,\n fieldTypes?: Record<string, FieldTypeChecks>,\n): AnswerValue {\n const custom = fieldTypes?.[field.blockType]?.normalize\n if (custom) {\n return custom(raw, field)\n }\n switch (field.blockType) {\n case 'checkbox': {\n const v = first(raw)\n if (typeof v === 'boolean') {\n return v\n }\n return TRUE_WORDS.includes(String(v ?? '').trim().toLowerCase())\n }\n case 'checkboxGroup':\n return toStringList(raw)\n case 'number': {\n const v = first(raw)\n if (typeof v === 'number') {\n return Number.isFinite(v) ? v : String(v)\n }\n const text = String(v ?? '').trim()\n if (!text) {\n return ''\n }\n const n = Number(text)\n return Number.isFinite(n) ? n : text\n }\n default: {\n const v = first(raw)\n if (v === null || v === undefined) {\n return ''\n }\n return typeof v === 'object' ? '' : String(v).trim()\n }\n }\n}\n\n/**\n * The text of an answer in messages and snapshots: option labels, file names, and the form's\n * `yes` / `no` words for a checkbox (pass `fillWords(form.wording)` or the public form's wording).\n */\nexport function answerToText(\n field: InputBlock,\n value: AnswerValue,\n words: YesNoWords,\n fieldTypes?: Record<string, FieldTypeChecks>,\n): string {\n const custom = fieldTypes?.[field.blockType]?.toText\n if (custom) {\n return custom(value, field, words)\n }\n if (value === null || value === undefined) {\n return ''\n }\n if (typeof value === 'boolean') {\n return value ? words.yes : words.no\n }\n if (typeof value === 'object' && !Array.isArray(value)) {\n return Array.isArray(value.filenames) ? value.filenames.join(', ') : ''\n }\n if (isChoiceBlock(field)) {\n const options = getOptions(field)\n return toStringList(value)\n .map((v) => options.find((o) => String(o.value) === v)?.label || v)\n .join(', ')\n }\n if (Array.isArray(value)) {\n return value.join(', ')\n }\n return String(value)\n}\n\ntype Problem = 'empty' | 'file' | 'invalid' | null\n\nfunction checkValue(field: InputBlock, value: AnswerValue): Problem {\n switch (field.blockType) {\n case 'checkboxGroup': {\n const listed = new Set(getOptions(field).map((o) => String(o.value)))\n return toStringList(value).every((v) => listed.has(v)) ? null : 'invalid'\n }\n case 'date': {\n const text = String(value)\n if (!isValidDate(text)) {\n return 'invalid'\n }\n const min = field.minDate?.slice(0, 10)\n const max = field.maxDate?.slice(0, 10)\n if ((min && text < min) || (max && text > max)) {\n return 'invalid'\n }\n return null\n }\n case 'email':\n return isValidEmail(value) ? null : 'invalid'\n case 'number': {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return 'invalid'\n }\n if (typeof field.min === 'number' && value < field.min) {\n return 'invalid'\n }\n if (typeof field.max === 'number' && value > field.max) {\n return 'invalid'\n }\n return null\n }\n case 'radio':\n case 'select': {\n const listed = new Set(getOptions(field).map((o) => String(o.value)))\n return listed.has(String(value)) ? null : 'invalid'\n }\n case 'text':\n case 'textarea': {\n const length = [...String(value)].length\n if (typeof field.minLength === 'number' && field.minLength > 0 && length < field.minLength) {\n return 'invalid'\n }\n if (typeof field.maxLength === 'number' && field.maxLength > 0 && length > field.maxLength) {\n return 'invalid'\n }\n return null\n }\n default:\n return null\n }\n}\n\n/** Checks the files given to one upload field: count, MIME type and size. */\nexport function checkFiles(field: InputBlock, files: FileLike[], limits: FileLimits = {}): boolean {\n const maxFiles = Math.max(1, Number(field.maxFiles) || 1)\n if (files.length > maxFiles) {\n return false\n }\n const pluginTypes = limits.mimeTypes || DEFAULT_FILE_LIMITS.mimeTypes\n const fieldTypes = (field.mimeTypes || []).filter((t) => pluginTypes.includes(t))\n const allowed = fieldTypes.length ? fieldTypes : pluginTypes\n const pluginMax = limits.maxFileSize ?? DEFAULT_FILE_LIMITS.maxFileSize\n const fieldMax = Number(field.maxFileSizeMB) > 0 ? Number(field.maxFileSizeMB) * 1024 * 1024 : pluginMax\n const maxSize = Math.min(fieldMax, pluginMax)\n return files.every(\n (file) => allowed.includes(file.type || 'application/octet-stream') && file.size <= maxSize && file.size > 0,\n )\n}\n\nexport type CheckOptions = {\n /** Checks for app-registered field types. */\n fieldTypes?: Record<string, FieldTypeChecks>\n /** Files per upload field key. */\n files?: Record<string, FileLike[]>\n limits?: FileLimits\n /** Form words; default: the form's wording filled from the defaults. */\n words?: FormWords\n}\n\nexport type CheckResult = {\n /** Shown answers only, normalised. Upload fields are left out (the server adds them). */\n answers: Answers\n fieldErrors: Record<string, string>\n /** Keys of shown upload fields that got files. */\n fileKeys: string[]\n ok: boolean\n}\n\nfunction errorWords(field: InputBlock, key: string, problem: Exclude<Problem, null>, words: FormWords): string {\n const label = getFieldLabel(field, key)\n if (problem === 'empty') {\n return field.words?.errorEmpty?.trim() || interpolate(words.required, { label })\n }\n if (field.words?.errorInvalid?.trim()) {\n return field.words.errorInvalid.trim()\n }\n return interpolate(problem === 'file' ? words.fileRejected : words.invalid, { label })\n}\n\n/**\n * Normalises raw answers, drops hidden ones (and, in steps, those of sections off the path), and\n * reports a field error for every shown field that is required and empty or has a bad value.\n */\nexport function checkAnswers(form: FormLike, raw: Record<string, unknown>, options: CheckOptions = {}): CheckResult {\n const words = options.words || fillWords(form.wording)\n const files = options.files || {}\n const normalized: Answers = {}\n for (const { field, key } of getInputFields(form)) {\n if (field.blockType === 'fileUpload') {\n normalized[key] = (files[key] || []).map((f) => f.name || 'upload')\n continue\n }\n normalized[key] = normalizeAnswer(field, raw[key], options.fieldTypes)\n }\n const visibility = getVisibility(form, normalized)\n const answers: Answers = {}\n const fieldErrors: Record<string, string> = {}\n const fileKeys: string[] = []\n let totalSize = 0\n const maxTotal = options.limits?.maxTotalSize ?? DEFAULT_FILE_LIMITS.maxTotalSize\n\n for (const { field, key } of getInputFields(form)) {\n if (!visibility.fieldKeys.has(key) || fieldErrors[key]) {\n continue\n }\n if (field.blockType === 'fileUpload') {\n const given = files[key] || []\n if (!given.length) {\n if (field.required) {\n fieldErrors[key] = errorWords(field, key, 'empty', words)\n }\n continue\n }\n fileKeys.push(key)\n totalSize += given.reduce((sum, f) => sum + f.size, 0)\n if (!checkFiles(field, given, options.limits) || totalSize > maxTotal) {\n fieldErrors[key] = errorWords(field, key, 'file', words)\n }\n continue\n }\n const value = normalized[key]!\n answers[key] = value\n const given = hasAnswer(value)\n if (!given) {\n if (field.required) {\n fieldErrors[key] = errorWords(field, key, 'empty', words)\n }\n continue\n }\n const custom = options.fieldTypes?.[field.blockType]?.validate\n if (custom) {\n const result = custom(value, { answers: normalized, field })\n if (result !== true) {\n fieldErrors[key] = typeof result === 'string' && result ? result : errorWords(field, key, 'invalid', words)\n continue\n }\n }\n const problem = checkValue(field, value)\n if (problem) {\n fieldErrors[key] = errorWords(field, key, problem, words)\n }\n }\n return { answers, fieldErrors, fileKeys, ok: Object.keys(fieldErrors).length === 0 }\n}\n\n/** Section title, label and value per answered, shown field, as worded now. */\nexport function buildSnapshot(\n form: FormLike,\n answers: Answers,\n fieldTypes?: Record<string, FieldTypeChecks>,\n): SnapshotRow[] {\n const words = fillWords(form.wording)\n const rows: SnapshotRow[] = []\n for (const { field, key, section } of getInputFields(form)) {\n if (!(key in answers) || !hasAnswer(answers[key])) {\n continue\n }\n rows.push({\n fieldKey: key,\n label: getFieldLabel(field, key),\n sectionKey: getSectionKey(section),\n sectionTitle: section.title?.trim() || getSectionKey(section),\n value: answerToText(field, answers[key], words, fieldTypes),\n })\n }\n return rows\n}\n"],"names":["getFieldLabel","getInputFields","getOptions","getSectionKey","hasAnswer","isChoiceBlock","toStringList","getVisibility","interpolate","fillWords","TRUE_WORDS","DEFAULT_FILE_LIMITS","maxFileSize","maxTotalSize","mimeTypes","isValidEmail","value","test","trim","DATE","isValidDate","time","Date","parse","Number","isNaN","toISOString","slice","first","raw","Array","isArray","normalizeAnswer","field","fieldTypes","custom","blockType","normalize","v","includes","String","toLowerCase","isFinite","text","n","undefined","answerToText","words","toText","yes","no","filenames","join","options","map","find","o","label","checkValue","listed","Set","every","has","min","minDate","max","maxDate","length","minLength","maxLength","checkFiles","files","limits","maxFiles","Math","pluginTypes","filter","t","allowed","pluginMax","fieldMax","maxFileSizeMB","maxSize","file","type","size","errorWords","key","problem","errorEmpty","required","errorInvalid","fileRejected","invalid","checkAnswers","form","wording","normalized","f","name","visibility","answers","fieldErrors","fileKeys","totalSize","maxTotal","fieldKeys","given","push","reduce","sum","validate","result","ok","Object","keys","buildSnapshot","rows","section","fieldKey","sectionKey","sectionTitle","title"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The one list of rule operators. Every rule field in the forms collection uses this array,
3
+ * and the rule engine handles exactly these values.
4
+ */
5
+ export declare const OPERATORS: readonly [{
6
+ readonly label: "Equals";
7
+ readonly value: "equals";
8
+ }, {
9
+ readonly label: "Does not equal";
10
+ readonly value: "notEquals";
11
+ }, {
12
+ readonly label: "Contains";
13
+ readonly value: "contains";
14
+ }, {
15
+ readonly label: "Does not contain";
16
+ readonly value: "notContains";
17
+ }, {
18
+ readonly label: "Has a value";
19
+ readonly value: "hasValue";
20
+ }, {
21
+ readonly label: "Has no value";
22
+ readonly value: "notHasValue";
23
+ }, {
24
+ readonly label: "Greater than";
25
+ readonly value: "greaterThan";
26
+ }, {
27
+ readonly label: "Less than";
28
+ readonly value: "lessThan";
29
+ }, {
30
+ readonly label: "Includes option";
31
+ readonly value: "includesOption";
32
+ }];
33
+ export type OperatorValue = (typeof OPERATORS)[number]['value'];
34
+ export declare const OPERATOR_VALUES: readonly OperatorValue[];
35
+ export declare const MATCH_MODES: readonly [{
36
+ readonly label: "All rules must match";
37
+ readonly value: "all";
38
+ }, {
39
+ readonly label: "Any rule can match";
40
+ readonly value: "any";
41
+ }];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The one list of rule operators. Every rule field in the forms collection uses this array,
3
+ * and the rule engine handles exactly these values.
4
+ */ export const OPERATORS = [
5
+ {
6
+ label: 'Equals',
7
+ value: 'equals'
8
+ },
9
+ {
10
+ label: 'Does not equal',
11
+ value: 'notEquals'
12
+ },
13
+ {
14
+ label: 'Contains',
15
+ value: 'contains'
16
+ },
17
+ {
18
+ label: 'Does not contain',
19
+ value: 'notContains'
20
+ },
21
+ {
22
+ label: 'Has a value',
23
+ value: 'hasValue'
24
+ },
25
+ {
26
+ label: 'Has no value',
27
+ value: 'notHasValue'
28
+ },
29
+ {
30
+ label: 'Greater than',
31
+ value: 'greaterThan'
32
+ },
33
+ {
34
+ label: 'Less than',
35
+ value: 'lessThan'
36
+ },
37
+ {
38
+ label: 'Includes option',
39
+ value: 'includesOption'
40
+ }
41
+ ];
42
+ export const OPERATOR_VALUES = OPERATORS.map((o)=>o.value);
43
+ export const MATCH_MODES = [
44
+ {
45
+ label: 'All rules must match',
46
+ value: 'all'
47
+ },
48
+ {
49
+ label: 'Any rule can match',
50
+ value: 'any'
51
+ }
52
+ ];
53
+
54
+ //# sourceMappingURL=operators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared/operators.ts"],"sourcesContent":["/**\n * The one list of rule operators. Every rule field in the forms collection uses this array,\n * and the rule engine handles exactly these values.\n */\nexport const OPERATORS = [\n { label: 'Equals', value: 'equals' },\n { label: 'Does not equal', value: 'notEquals' },\n { label: 'Contains', value: 'contains' },\n { label: 'Does not contain', value: 'notContains' },\n { label: 'Has a value', value: 'hasValue' },\n { label: 'Has no value', value: 'notHasValue' },\n { label: 'Greater than', value: 'greaterThan' },\n { label: 'Less than', value: 'lessThan' },\n { label: 'Includes option', value: 'includesOption' },\n] as const satisfies ReadonlyArray<{ label: string; value: string }>\n\nexport type OperatorValue = (typeof OPERATORS)[number]['value']\n\nexport const OPERATOR_VALUES: readonly OperatorValue[] = OPERATORS.map((o) => o.value)\n\nexport const MATCH_MODES = [\n { label: 'All rules must match', value: 'all' },\n { label: 'Any rule can match', value: 'any' },\n] as const\n"],"names":["OPERATORS","label","value","OPERATOR_VALUES","map","o","MATCH_MODES"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,YAAY;IACvB;QAAEC,OAAO;QAAUC,OAAO;IAAS;IACnC;QAAED,OAAO;QAAkBC,OAAO;IAAY;IAC9C;QAAED,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAoBC,OAAO;IAAc;IAClD;QAAED,OAAO;QAAeC,OAAO;IAAW;IAC1C;QAAED,OAAO;QAAgBC,OAAO;IAAc;IAC9C;QAAED,OAAO;QAAgBC,OAAO;IAAc;IAC9C;QAAED,OAAO;QAAaC,OAAO;IAAW;IACxC;QAAED,OAAO;QAAmBC,OAAO;IAAiB;CACrD,CAAmE;AAIpE,OAAO,MAAMC,kBAA4CH,UAAUI,GAAG,CAAC,CAACC,IAAMA,EAAEH,KAAK,EAAC;AAEtF,OAAO,MAAMI,cAAc;IACzB;QAAEL,OAAO;QAAwBC,OAAO;IAAM;IAC9C;QAAED,OAAO;QAAsBC,OAAO;IAAM;CAC7C,CAAS"}
@@ -0,0 +1,58 @@
1
+ import type { Answers, AnswerValue, ChoiceOption, FieldBlock, FormLike, FormSection, InputBlock, RuleGroup, RuleRow } from './types.js';
2
+ /** Lower-case kebab-case: ` Project Type ` → `project-type`. */
3
+ export declare function normalizeKey(value: unknown): string;
4
+ /**
5
+ * Splits a rule key into the field key and an optional option attribute:
6
+ * `Need.fieldSet` → `{ key: 'need', attribute: 'fieldSet' }`. The attribute keeps its case.
7
+ */
8
+ export declare function splitRuleKey(value: unknown): {
9
+ attribute?: string;
10
+ key: string;
11
+ };
12
+ export declare const DISPLAY_BLOCK_TYPES: readonly string[];
13
+ export declare const CHOICE_BLOCK_TYPES: readonly string[];
14
+ export declare function isInputBlock(block: FieldBlock | null | undefined): block is InputBlock;
15
+ export declare function isChoiceBlock(block: FieldBlock | null | undefined): boolean;
16
+ export declare function getFieldKey(block: FieldBlock | null | undefined): string;
17
+ export declare function getSectionKey(section: FormSection | null | undefined): string;
18
+ export declare function getSections(form: FormLike): FormSection[];
19
+ export type InputEntry = {
20
+ field: InputBlock;
21
+ key: string;
22
+ section: FormSection;
23
+ };
24
+ /** Every input of the form in order, with its normalised key. */
25
+ export declare function getInputFields(form: FormLike): InputEntry[];
26
+ export declare function getInputFieldMap(form: FormLike): Map<string, InputEntry>;
27
+ export declare function getFieldLabel(field: InputBlock, key?: string): string;
28
+ export declare function getRowLabel(field: InputBlock, key?: string): string;
29
+ /** True when an answer counts as given: `false` and empty strings, lists and objects don't. */
30
+ export declare function hasAnswer(value: unknown): boolean;
31
+ export declare function toStringList(value: unknown): string[];
32
+ /** The options a choice field offers (typed, or resolved from a source by the server). */
33
+ export declare function getOptions(field: InputBlock | undefined): ChoiceOption[];
34
+ /**
35
+ * The value a rule row reads: the answer, or for `<key>.<attribute>` the attribute of the
36
+ * chosen option(s) of that field.
37
+ */
38
+ export declare function readRuleValue(ruleKey: unknown, answers: Answers, form?: FormLike): unknown;
39
+ export declare function evaluateRow(row: null | RuleRow | undefined, answers: Answers, form?: FormLike): boolean;
40
+ /** A group matches when all rows match (`all`, default) or any row does (`any`); empty matches. */
41
+ export declare function evaluateRules(group: null | RuleGroup | undefined, answers: Answers, form?: FormLike): boolean;
42
+ export declare function isSectionVisible(section: FormSection, answers: Answers, form?: FormLike): boolean;
43
+ /** A field is shown when its section is shown and its own rules match. */
44
+ export declare function isFieldVisible(section: FormSection, field: FieldBlock, answers: Answers, form?: FormLike): boolean;
45
+ export declare function getVisibleSections(form: FormLike, answers: Answers): FormSection[];
46
+ type SearchParamsLike = {
47
+ get(name: string): null | string;
48
+ getAll?(name: string): string[];
49
+ } | Record<string, string | string[] | undefined> | string | URLSearchParams;
50
+ /** The starting answer of a field from its default value. */
51
+ export declare function defaultAnswer(field: InputBlock): AnswerValue;
52
+ /**
53
+ * Starting answers for a renderer: each input's default, or the value of its link parameter
54
+ * when the app passes the page's search parameters. Choice fields take the parameter only when
55
+ * it is one of their options.
56
+ */
57
+ export declare function initialAnswers(form: FormLike, searchParams?: SearchParamsLike): Answers;
58
+ export {};