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,495 @@
1
+ import { ruleGroupField } from './ruleFields.js';
2
+ const DATE_TEXT = /^\d{4}-\d{2}-\d{2}$/;
3
+ function dateLimit(name, label) {
4
+ return {
5
+ name,
6
+ type: 'text',
7
+ admin: {
8
+ description: 'YYYY-MM-DD',
9
+ width: '50%'
10
+ },
11
+ label,
12
+ validate: (value)=>!value || typeof value === 'string' && DATE_TEXT.test(value) ? true : 'Use YYYY-MM-DD.'
13
+ };
14
+ }
15
+ /** Every word one question shows, grouped and localized. */ export function fieldWordsField() {
16
+ return {
17
+ type: 'collapsible',
18
+ admin: {
19
+ initCollapsed: false
20
+ },
21
+ fields: [
22
+ {
23
+ name: 'words',
24
+ type: 'group',
25
+ fields: [
26
+ {
27
+ name: 'label',
28
+ type: 'text',
29
+ localized: true,
30
+ required: true
31
+ },
32
+ {
33
+ name: 'hint',
34
+ type: 'textarea',
35
+ localized: true
36
+ },
37
+ {
38
+ name: 'placeholder',
39
+ type: 'text',
40
+ localized: true
41
+ },
42
+ {
43
+ type: 'row',
44
+ fields: [
45
+ {
46
+ name: 'errorEmpty',
47
+ type: 'text',
48
+ admin: {
49
+ description: 'Shown when a required answer is missing. Empty: the form\'s "required" words.',
50
+ width: '50%'
51
+ },
52
+ localized: true
53
+ },
54
+ {
55
+ name: 'errorInvalid',
56
+ type: 'text',
57
+ admin: {
58
+ description: 'Shown when the answer is not valid. Empty: the form\'s "invalid" words.',
59
+ width: '50%'
60
+ },
61
+ localized: true
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ name: 'rowLabel',
67
+ type: 'text',
68
+ admin: {
69
+ description: 'Label of this answer in messages. Empty: the label.'
70
+ },
71
+ localized: true
72
+ }
73
+ ],
74
+ label: false
75
+ }
76
+ ],
77
+ label: 'Words'
78
+ };
79
+ }
80
+ /** Fields every input block has: key, width, required, words, default, link parameter, rules. */ export function baseInputFields(defaultValue = {
81
+ name: 'defaultValue',
82
+ type: 'text',
83
+ localized: true
84
+ }) {
85
+ return [
86
+ {
87
+ type: 'row',
88
+ fields: [
89
+ {
90
+ name: 'fieldKey',
91
+ type: 'text',
92
+ admin: {
93
+ description: 'Stored as lower-case kebab-case. Used in rules and {{tokens}}.',
94
+ width: '50%'
95
+ },
96
+ label: 'Key',
97
+ required: true
98
+ },
99
+ {
100
+ name: 'width',
101
+ type: 'select',
102
+ admin: {
103
+ width: '25%'
104
+ },
105
+ dbName: 'w',
106
+ defaultValue: 'full',
107
+ options: [
108
+ {
109
+ label: 'Full',
110
+ value: 'full'
111
+ },
112
+ {
113
+ label: 'Half',
114
+ value: 'half'
115
+ }
116
+ ]
117
+ },
118
+ {
119
+ name: 'required',
120
+ type: 'checkbox',
121
+ admin: {
122
+ width: '25%'
123
+ },
124
+ defaultValue: false
125
+ }
126
+ ]
127
+ },
128
+ fieldWordsField(),
129
+ {
130
+ type: 'row',
131
+ fields: [
132
+ {
133
+ ...defaultValue,
134
+ admin: {
135
+ ...defaultValue.admin,
136
+ width: '50%'
137
+ }
138
+ },
139
+ {
140
+ name: 'prefillParam',
141
+ type: 'text',
142
+ admin: {
143
+ description: 'Link parameter that prefills this answer, e.g. need for ?need=…',
144
+ width: '50%'
145
+ },
146
+ label: 'Prefill from link parameter'
147
+ }
148
+ ]
149
+ },
150
+ ruleGroupField()
151
+ ];
152
+ }
153
+ function lengthLimits() {
154
+ return {
155
+ type: 'row',
156
+ fields: [
157
+ {
158
+ name: 'minLength',
159
+ type: 'number',
160
+ admin: {
161
+ width: '50%'
162
+ },
163
+ min: 0
164
+ },
165
+ {
166
+ name: 'maxLength',
167
+ type: 'number',
168
+ admin: {
169
+ width: '50%'
170
+ },
171
+ min: 0
172
+ }
173
+ ]
174
+ };
175
+ }
176
+ function choiceFields(sources) {
177
+ const sourceKeys = Object.keys(sources);
178
+ const typedOptions = {
179
+ name: 'options',
180
+ type: 'array',
181
+ admin: sourceKeys.length ? {
182
+ condition: (_data, sibling)=>!sibling?.optionsFrom || sibling.optionsFrom === 'typed'
183
+ } : {},
184
+ fields: [
185
+ {
186
+ type: 'row',
187
+ fields: [
188
+ {
189
+ name: 'label',
190
+ type: 'text',
191
+ admin: {
192
+ width: '50%'
193
+ },
194
+ localized: true,
195
+ required: true
196
+ },
197
+ {
198
+ name: 'value',
199
+ type: 'text',
200
+ admin: {
201
+ width: '50%'
202
+ },
203
+ required: true
204
+ }
205
+ ]
206
+ }
207
+ ]
208
+ };
209
+ if (!sourceKeys.length) {
210
+ return [
211
+ typedOptions
212
+ ];
213
+ }
214
+ const collections = [
215
+ ...new Set(sourceKeys.map((key)=>String(sources[key].collection)))
216
+ ];
217
+ return [
218
+ {
219
+ name: 'optionsFrom',
220
+ type: 'select',
221
+ dbName: 'of',
222
+ defaultValue: 'typed',
223
+ label: 'Choices from',
224
+ options: [
225
+ {
226
+ label: 'Typed here',
227
+ value: 'typed'
228
+ },
229
+ ...sourceKeys.map((key)=>({
230
+ label: key,
231
+ value: key
232
+ }))
233
+ ]
234
+ },
235
+ typedOptions,
236
+ {
237
+ name: 'sourceItems',
238
+ type: 'relationship',
239
+ admin: {
240
+ condition: (_data, sibling)=>Boolean(sibling?.optionsFrom && sibling.optionsFrom !== 'typed'),
241
+ description: 'Pick and order the items. Empty: every published item, in the collection\'s default order.',
242
+ isSortable: true
243
+ },
244
+ filterOptions: ({ relationTo, siblingData })=>{
245
+ const key = siblingData?.optionsFrom;
246
+ const source = key ? sources[key] : undefined;
247
+ return Boolean(source && String(source.collection) === relationTo);
248
+ },
249
+ hasMany: true,
250
+ relationTo: collections
251
+ }
252
+ ];
253
+ }
254
+ /** The built-in field blocks, in their admin order. */ export function builtInBlocks(ctx) {
255
+ const input = (slug, singular, extra, defaultValue)=>({
256
+ slug,
257
+ fields: [
258
+ ...baseInputFields(defaultValue),
259
+ ...extra
260
+ ],
261
+ labels: {
262
+ plural: `${singular} fields`,
263
+ singular
264
+ }
265
+ });
266
+ return {
267
+ checkbox: input('checkbox', 'Checkbox', [], {
268
+ name: 'defaultValue',
269
+ type: 'checkbox',
270
+ defaultValue: false
271
+ }),
272
+ checkboxGroup: input('checkboxGroup', 'Checkbox group', choiceFields(ctx.sources), {
273
+ name: 'defaultValue',
274
+ type: 'text',
275
+ admin: {
276
+ description: 'Values, separated by commas.'
277
+ }
278
+ }),
279
+ date: input('date', 'Date', [
280
+ {
281
+ type: 'row',
282
+ fields: [
283
+ dateLimit('minDate', 'Earliest'),
284
+ dateLimit('maxDate', 'Latest')
285
+ ]
286
+ }
287
+ ]),
288
+ email: input('email', 'Email', []),
289
+ fileUpload: input('fileUpload', 'File upload', [
290
+ {
291
+ type: 'row',
292
+ fields: [
293
+ {
294
+ name: 'maxFiles',
295
+ type: 'number',
296
+ admin: {
297
+ width: '50%'
298
+ },
299
+ defaultValue: 1,
300
+ max: 10,
301
+ min: 1
302
+ },
303
+ {
304
+ name: 'maxFileSizeMB',
305
+ type: 'number',
306
+ admin: {
307
+ description: 'Empty: the plugin limit.',
308
+ width: '50%'
309
+ },
310
+ min: 0
311
+ }
312
+ ]
313
+ },
314
+ {
315
+ name: 'mimeTypes',
316
+ type: 'select',
317
+ admin: {
318
+ description: 'Empty: every type the plugin allows.'
319
+ },
320
+ dbName: 'mime',
321
+ hasMany: true,
322
+ options: ctx.mimeTypes.map((type)=>({
323
+ label: type,
324
+ value: type
325
+ }))
326
+ }
327
+ ], {
328
+ name: 'defaultValue',
329
+ type: 'text',
330
+ admin: {
331
+ hidden: true
332
+ }
333
+ }),
334
+ heading: {
335
+ slug: 'heading',
336
+ fields: [
337
+ {
338
+ name: 'heading',
339
+ type: 'text',
340
+ localized: true,
341
+ required: true
342
+ },
343
+ {
344
+ name: 'level',
345
+ type: 'select',
346
+ dbName: 'lvl',
347
+ defaultValue: 'h3',
348
+ options: [
349
+ 'h2',
350
+ 'h3',
351
+ 'h4'
352
+ ]
353
+ }
354
+ ],
355
+ labels: {
356
+ plural: 'Headings',
357
+ singular: 'Heading'
358
+ }
359
+ },
360
+ helpText: {
361
+ slug: 'helpText',
362
+ fields: [
363
+ {
364
+ name: 'body',
365
+ type: 'textarea',
366
+ localized: true,
367
+ required: true
368
+ }
369
+ ],
370
+ labels: {
371
+ plural: 'Help texts',
372
+ singular: 'Help text'
373
+ }
374
+ },
375
+ number: input('number', 'Number', [
376
+ {
377
+ type: 'row',
378
+ fields: [
379
+ {
380
+ name: 'min',
381
+ type: 'number',
382
+ admin: {
383
+ width: '50%'
384
+ }
385
+ },
386
+ {
387
+ name: 'max',
388
+ type: 'number',
389
+ admin: {
390
+ width: '50%'
391
+ }
392
+ }
393
+ ]
394
+ }
395
+ ]),
396
+ radio: input('radio', 'Radio', choiceFields(ctx.sources)),
397
+ select: input('select', 'Select', choiceFields(ctx.sources)),
398
+ text: input('text', 'Text', [
399
+ lengthLimits()
400
+ ]),
401
+ textarea: input('textarea', 'Long text', [
402
+ lengthLimits()
403
+ ])
404
+ };
405
+ }
406
+ export const BUILT_IN_TYPES = [
407
+ 'heading',
408
+ 'helpText',
409
+ 'text',
410
+ 'email',
411
+ 'textarea',
412
+ 'number',
413
+ 'date',
414
+ 'select',
415
+ 'radio',
416
+ 'checkbox',
417
+ 'checkboxGroup',
418
+ 'fileUpload'
419
+ ];
420
+ function isPlainObject(value) {
421
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
422
+ }
423
+ /** Deep-merges plain objects; arrays and other values from `patch` replace. */ export function deepMerge(base, patch) {
424
+ if (!isPlainObject(base) || !isPlainObject(patch)) {
425
+ return patch === undefined ? base : patch;
426
+ }
427
+ const out = {
428
+ ...base
429
+ };
430
+ for (const [key, value] of Object.entries(patch)){
431
+ out[key] = isPlainObject(out[key]) && isPlainObject(value) ? deepMerge(out[key], value) : value;
432
+ }
433
+ return out;
434
+ }
435
+ function isDefinition(value) {
436
+ return isPlainObject(value) && isPlainObject(value.block) && typeof value.block.slug === 'string';
437
+ }
438
+ /**
439
+ * Applies the `fields` registry: built-ins on/off (fileUpload off by default), partial blocks
440
+ * merged, new types added with the common input fields. Returns the blocks and the checks for
441
+ * app-defined types.
442
+ */ export function buildFieldBlocks(registry = {}, ctx) {
443
+ const builtIns = builtInBlocks(ctx);
444
+ const blocks = [];
445
+ const fieldTypes = {};
446
+ for (const type of BUILT_IN_TYPES){
447
+ const entry = registry[type];
448
+ const on = entry === undefined ? type !== 'fileUpload' : entry !== false;
449
+ if (!on) {
450
+ continue;
451
+ }
452
+ if (type === 'fileUpload' && !ctx.attachmentsOn) {
453
+ throw new Error('formsPlugin: the fileUpload field type needs attachments (attachments is false).');
454
+ }
455
+ if (isDefinition(entry)) {
456
+ blocks.push(entry.block);
457
+ fieldTypes[type] = {
458
+ normalize: entry.normalize,
459
+ toText: entry.toText,
460
+ validate: entry.validate
461
+ };
462
+ continue;
463
+ }
464
+ blocks.push(isPlainObject(entry) ? deepMerge(builtIns[type], entry) : builtIns[type]);
465
+ }
466
+ for (const [type, entry] of Object.entries(registry)){
467
+ if (BUILT_IN_TYPES.includes(type) || entry === false || entry === true) {
468
+ continue;
469
+ }
470
+ if (!isDefinition(entry)) {
471
+ throw new Error(`formsPlugin: field type "${type}" is not built in and needs { block, validate? }.`);
472
+ }
473
+ const own = new Set(entry.block.fields.map((f)=>'name' in f ? f.name : '').filter(Boolean));
474
+ const base = baseInputFields().filter((f)=>!('name' in f) || !own.has(f.name));
475
+ blocks.push({
476
+ ...entry.block,
477
+ slug: entry.block.slug || type,
478
+ fields: [
479
+ ...base,
480
+ ...entry.block.fields
481
+ ]
482
+ });
483
+ fieldTypes[entry.block.slug || type] = {
484
+ normalize: entry.normalize,
485
+ toText: entry.toText,
486
+ validate: entry.validate
487
+ };
488
+ }
489
+ return {
490
+ blocks,
491
+ fieldTypes
492
+ };
493
+ }
494
+
495
+ //# sourceMappingURL=blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/collections/blocks.ts"],"sourcesContent":["import type { Block, Field } from 'payload'\n\nimport type { FieldTypeChecks } from '../shared/types.js'\nimport type { FieldTypeDefinition, FormsPluginOptions, OptionSource } from '../types.js'\n\nimport { ruleGroupField } from './ruleFields.js'\n\nconst DATE_TEXT = /^\\d{4}-\\d{2}-\\d{2}$/\n\nfunction dateLimit(name: string, label: string): Field {\n return {\n name,\n type: 'text',\n admin: { description: 'YYYY-MM-DD', width: '50%' },\n label,\n validate: (value: unknown) =>\n !value || (typeof value === 'string' && DATE_TEXT.test(value)) ? true : 'Use YYYY-MM-DD.',\n }\n}\n\n/** Every word one question shows, grouped and localized. */\nexport function fieldWordsField(): Field {\n return {\n type: 'collapsible',\n admin: { initCollapsed: false },\n fields: [\n {\n name: 'words',\n type: 'group',\n fields: [\n { name: 'label', type: 'text', localized: true, required: true },\n { name: 'hint', type: 'textarea', localized: true },\n { name: 'placeholder', type: 'text', localized: true },\n {\n type: 'row',\n fields: [\n { name: 'errorEmpty', type: 'text', admin: { description: 'Shown when a required answer is missing. Empty: the form\\'s \"required\" words.', width: '50%' }, localized: true },\n { name: 'errorInvalid', type: 'text', admin: { description: 'Shown when the answer is not valid. Empty: the form\\'s \"invalid\" words.', width: '50%' }, localized: true },\n ],\n },\n { name: 'rowLabel', type: 'text', admin: { description: 'Label of this answer in messages. Empty: the label.' }, localized: true },\n ],\n label: false,\n },\n ],\n label: 'Words',\n }\n}\n\n/** Fields every input block has: key, width, required, words, default, link parameter, rules. */\nexport function baseInputFields(defaultValue: Field = { name: 'defaultValue', type: 'text', localized: true }): Field[] {\n return [\n {\n type: 'row',\n fields: [\n {\n name: 'fieldKey',\n type: 'text',\n admin: { description: 'Stored as lower-case kebab-case. Used in rules and {{tokens}}.', width: '50%' },\n label: 'Key',\n required: true,\n },\n {\n name: 'width',\n type: 'select',\n admin: { width: '25%' },\n dbName: 'w',\n defaultValue: 'full',\n options: [\n { label: 'Full', value: 'full' },\n { label: 'Half', value: 'half' },\n ],\n },\n {\n name: 'required',\n type: 'checkbox',\n admin: { width: '25%' },\n defaultValue: false,\n },\n ],\n },\n fieldWordsField(),\n {\n type: 'row',\n fields: [\n { ...defaultValue, admin: { ...(defaultValue.admin as object), width: '50%' } } as Field,\n {\n name: 'prefillParam',\n type: 'text',\n admin: { description: 'Link parameter that prefills this answer, e.g. need for ?need=…', width: '50%' },\n label: 'Prefill from link parameter',\n },\n ],\n },\n ruleGroupField(),\n ]\n}\n\nfunction lengthLimits(): Field {\n return {\n type: 'row',\n fields: [\n { name: 'minLength', type: 'number', admin: { width: '50%' }, min: 0 },\n { name: 'maxLength', type: 'number', admin: { width: '50%' }, min: 0 },\n ],\n }\n}\n\nfunction choiceFields(sources: Record<string, OptionSource>): Field[] {\n const sourceKeys = Object.keys(sources)\n const typedOptions: Field = {\n name: 'options',\n type: 'array',\n admin: sourceKeys.length\n ? { condition: (_data, sibling) => !sibling?.optionsFrom || sibling.optionsFrom === 'typed' }\n : {},\n fields: [\n {\n type: 'row',\n fields: [\n { name: 'label', type: 'text', admin: { width: '50%' }, localized: true, required: true },\n { name: 'value', type: 'text', admin: { width: '50%' }, required: true },\n ],\n },\n ],\n }\n if (!sourceKeys.length) {\n return [typedOptions]\n }\n const collections = [...new Set(sourceKeys.map((key) => String(sources[key].collection)))]\n return [\n {\n name: 'optionsFrom',\n type: 'select',\n dbName: 'of',\n defaultValue: 'typed',\n label: 'Choices from',\n options: [\n { label: 'Typed here', value: 'typed' },\n ...sourceKeys.map((key) => ({ label: key, value: key })),\n ],\n },\n typedOptions,\n {\n name: 'sourceItems',\n type: 'relationship',\n admin: {\n condition: (_data, sibling) => Boolean(sibling?.optionsFrom && sibling.optionsFrom !== 'typed'),\n description: 'Pick and order the items. Empty: every published item, in the collection\\'s default order.',\n isSortable: true,\n },\n filterOptions: ({ relationTo, siblingData }) => {\n const key = (siblingData as { optionsFrom?: string } | undefined)?.optionsFrom\n const source = key ? sources[key] : undefined\n return Boolean(source && String(source.collection) === relationTo)\n },\n hasMany: true,\n relationTo: collections as never,\n },\n ]\n}\n\nexport type BlockContext = {\n mimeTypes: string[]\n sources: Record<string, OptionSource>\n}\n\n/** The built-in field blocks, in their admin order. */\nexport function builtInBlocks(ctx: BlockContext): Record<string, Block> {\n const input = (slug: string, singular: string, extra: Field[], defaultValue?: Field): Block => ({\n slug,\n fields: [...baseInputFields(defaultValue), ...extra],\n labels: { plural: `${singular} fields`, singular },\n })\n return {\n checkbox: input('checkbox', 'Checkbox', [], { name: 'defaultValue', type: 'checkbox', defaultValue: false }),\n checkboxGroup: input('checkboxGroup', 'Checkbox group', choiceFields(ctx.sources), {\n name: 'defaultValue',\n type: 'text',\n admin: { description: 'Values, separated by commas.' },\n }),\n date: input('date', 'Date', [{ type: 'row', fields: [dateLimit('minDate', 'Earliest'), dateLimit('maxDate', 'Latest')] }]),\n email: input('email', 'Email', []),\n fileUpload: input(\n 'fileUpload',\n 'File upload',\n [\n {\n type: 'row',\n fields: [\n { name: 'maxFiles', type: 'number', admin: { width: '50%' }, defaultValue: 1, max: 10, min: 1 },\n { name: 'maxFileSizeMB', type: 'number', admin: { description: 'Empty: the plugin limit.', width: '50%' }, min: 0 },\n ],\n },\n {\n name: 'mimeTypes',\n type: 'select',\n admin: { description: 'Empty: every type the plugin allows.' },\n dbName: 'mime',\n hasMany: true,\n options: ctx.mimeTypes.map((type) => ({ label: type, value: type })),\n },\n ],\n { name: 'defaultValue', type: 'text', admin: { hidden: true } },\n ),\n heading: {\n slug: 'heading',\n fields: [\n { name: 'heading', type: 'text', localized: true, required: true },\n {\n name: 'level',\n type: 'select',\n dbName: 'lvl',\n defaultValue: 'h3',\n options: ['h2', 'h3', 'h4'],\n },\n ],\n labels: { plural: 'Headings', singular: 'Heading' },\n },\n helpText: {\n slug: 'helpText',\n fields: [{ name: 'body', type: 'textarea', localized: true, required: true }],\n labels: { plural: 'Help texts', singular: 'Help text' },\n },\n number: input('number', 'Number', [\n {\n type: 'row',\n fields: [\n { name: 'min', type: 'number', admin: { width: '50%' } },\n { name: 'max', type: 'number', admin: { width: '50%' } },\n ],\n },\n ]),\n radio: input('radio', 'Radio', choiceFields(ctx.sources)),\n select: input('select', 'Select', choiceFields(ctx.sources)),\n text: input('text', 'Text', [lengthLimits()]),\n textarea: input('textarea', 'Long text', [lengthLimits()]),\n }\n}\n\nexport const BUILT_IN_TYPES = [\n 'heading',\n 'helpText',\n 'text',\n 'email',\n 'textarea',\n 'number',\n 'date',\n 'select',\n 'radio',\n 'checkbox',\n 'checkboxGroup',\n 'fileUpload',\n] as const\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n}\n\n/** Deep-merges plain objects; arrays and other values from `patch` replace. */\nexport function deepMerge<T>(base: T, patch: unknown): T {\n if (!isPlainObject(base) || !isPlainObject(patch)) {\n return (patch === undefined ? base : patch) as T\n }\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n out[key] = isPlainObject(out[key]) && isPlainObject(value) ? deepMerge(out[key], value) : value\n }\n return out as T\n}\n\nfunction isDefinition(value: unknown): value is FieldTypeDefinition {\n return isPlainObject(value) && isPlainObject(value.block) && typeof value.block.slug === 'string'\n}\n\n/**\n * Applies the `fields` registry: built-ins on/off (fileUpload off by default), partial blocks\n * merged, new types added with the common input fields. Returns the blocks and the checks for\n * app-defined types.\n */\nexport function buildFieldBlocks(\n registry: FormsPluginOptions['fields'] = {},\n ctx: { attachmentsOn: boolean } & BlockContext,\n): { blocks: Block[]; fieldTypes: Record<string, FieldTypeChecks> } {\n const builtIns = builtInBlocks(ctx)\n const blocks: Block[] = []\n const fieldTypes: Record<string, FieldTypeChecks> = {}\n\n for (const type of BUILT_IN_TYPES) {\n const entry = registry[type]\n const on = entry === undefined ? type !== 'fileUpload' : entry !== false\n if (!on) {\n continue\n }\n if (type === 'fileUpload' && !ctx.attachmentsOn) {\n throw new Error('formsPlugin: the fileUpload field type needs attachments (attachments is false).')\n }\n if (isDefinition(entry)) {\n blocks.push(entry.block)\n fieldTypes[type] = { normalize: entry.normalize, toText: entry.toText, validate: entry.validate }\n continue\n }\n blocks.push(isPlainObject(entry) ? deepMerge(builtIns[type], entry) : builtIns[type])\n }\n\n for (const [type, entry] of Object.entries(registry)) {\n if ((BUILT_IN_TYPES as readonly string[]).includes(type) || entry === false || entry === true) {\n continue\n }\n if (!isDefinition(entry)) {\n throw new Error(`formsPlugin: field type \"${type}\" is not built in and needs { block, validate? }.`)\n }\n const own = new Set(entry.block.fields.map((f) => ('name' in f ? f.name : '')).filter(Boolean))\n const base = baseInputFields().filter((f) => !('name' in f) || !own.has(f.name))\n blocks.push({ ...entry.block, slug: entry.block.slug || type, fields: [...base, ...entry.block.fields] })\n fieldTypes[entry.block.slug || type] = {\n normalize: entry.normalize,\n toText: entry.toText,\n validate: entry.validate,\n }\n }\n return { blocks, fieldTypes }\n}\n"],"names":["ruleGroupField","DATE_TEXT","dateLimit","name","label","type","admin","description","width","validate","value","test","fieldWordsField","initCollapsed","fields","localized","required","baseInputFields","defaultValue","dbName","options","lengthLimits","min","choiceFields","sources","sourceKeys","Object","keys","typedOptions","length","condition","_data","sibling","optionsFrom","collections","Set","map","key","String","collection","Boolean","isSortable","filterOptions","relationTo","siblingData","source","undefined","hasMany","builtInBlocks","ctx","input","slug","singular","extra","labels","plural","checkbox","checkboxGroup","date","email","fileUpload","max","mimeTypes","hidden","heading","helpText","number","radio","select","text","textarea","BUILT_IN_TYPES","isPlainObject","Array","isArray","getPrototypeOf","prototype","deepMerge","base","patch","out","entries","isDefinition","block","buildFieldBlocks","registry","builtIns","blocks","fieldTypes","entry","on","attachmentsOn","Error","push","normalize","toText","includes","own","f","filter","has"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Block, CollectionConfig, Endpoint } from 'payload';
2
+ import type { ResolvedOptions } from '../types.js';
3
+ export declare function formsCollection(options: ResolvedOptions, blocks: Block[], endpoints: Endpoint[]): CollectionConfig;