@10x-media/form-builder 0.1.0-beta.3 → 0.1.0-beta.5

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 (406) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +4 -4
  3. package/dist/actions/body/serializeBody.d.ts +22 -4
  4. package/dist/actions/body/serializeBody.js +3 -1
  5. package/dist/actions/body/serializeBody.js.map +1 -1
  6. package/dist/actions/body/textOfBody.d.ts +13 -0
  7. package/dist/actions/body/textOfBody.js +35 -0
  8. package/dist/actions/body/textOfBody.js.map +1 -0
  9. package/dist/actions/builtin/confirmation.js +51 -12
  10. package/dist/actions/builtin/confirmation.js.map +1 -1
  11. package/dist/actions/builtin/emailTeam.js +82 -34
  12. package/dist/actions/builtin/emailTeam.js.map +1 -1
  13. package/dist/actions/builtin/index.d.ts +18 -1
  14. package/dist/actions/builtin/index.js +21 -7
  15. package/dist/actions/builtin/index.js.map +1 -1
  16. package/dist/actions/builtin/signedWebhook.js +2 -16
  17. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  18. package/dist/actions/defineAction.d.ts +1 -1
  19. package/dist/actions/fromAddresses.d.ts +21 -0
  20. package/dist/actions/fromAddresses.js +74 -0
  21. package/dist/actions/fromAddresses.js.map +1 -0
  22. package/dist/actions/runActions.js +2 -0
  23. package/dist/actions/runActions.js.map +1 -1
  24. package/dist/actions/task.js +9 -8
  25. package/dist/actions/task.js.map +1 -1
  26. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  27. package/dist/aggregation/aggregateResponses.js +8 -14
  28. package/dist/aggregation/aggregateResponses.js.map +1 -1
  29. package/dist/aggregation/resolveResultsRequest.d.ts +35 -6
  30. package/dist/aggregation/resolveResultsRequest.js +68 -11
  31. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  32. package/dist/calc/computeCalcFields.js +2 -1
  33. package/dist/calc/computeCalcFields.js.map +1 -1
  34. package/dist/client/ByteSizeField.d.ts +8 -0
  35. package/dist/client/ByteSizeField.js +3 -1
  36. package/dist/client/ByteSizeField.js.map +1 -1
  37. package/dist/client/ClosePollButton.d.ts +15 -0
  38. package/dist/client/ClosePollButton.js +80 -0
  39. package/dist/client/ClosePollButton.js.map +1 -0
  40. package/dist/client/CondensedArray.d.ts +15 -0
  41. package/dist/client/CondensedArray.js +97 -0
  42. package/dist/client/CondensedArray.js.map +1 -0
  43. package/dist/client/EndpointOptionsSelect.d.ts +45 -0
  44. package/dist/client/EndpointOptionsSelect.js +103 -0
  45. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  46. package/dist/client/FieldBlockLabel.d.ts +18 -0
  47. package/dist/client/FieldBlockLabel.js +34 -0
  48. package/dist/client/FieldBlockLabel.js.map +1 -0
  49. package/dist/client/FieldNameSelect.d.ts +20 -4
  50. package/dist/client/FieldNameSelect.js +25 -14
  51. package/dist/client/FieldNameSelect.js.map +1 -1
  52. package/dist/client/FlowBuilder.d.ts +2 -1
  53. package/dist/client/FlowBuilder.js +134 -83
  54. package/dist/client/FlowBuilder.js.map +1 -1
  55. package/dist/client/RuleDescription.d.ts +21 -0
  56. package/dist/client/RuleDescription.js +19 -0
  57. package/dist/client/RuleDescription.js.map +1 -0
  58. package/dist/client/admin.css +83 -5
  59. package/dist/client/endpointOptions.js +29 -0
  60. package/dist/client/endpointOptions.js.map +1 -0
  61. package/dist/client/flowAuthoring.js +142 -0
  62. package/dist/client/flowAuthoring.js.map +1 -0
  63. package/dist/client/synthesizeClientField.js +21 -10
  64. package/dist/client/synthesizeClientField.js.map +1 -1
  65. package/dist/collections/buttonFields.d.ts +34 -0
  66. package/dist/collections/buttonFields.js +35 -0
  67. package/dist/collections/buttonFields.js.map +1 -0
  68. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  69. package/dist/collections/cells/FieldCountCell.js +18 -0
  70. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  71. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  72. package/dist/collections/cells/FlowStepsCell.js +17 -0
  73. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  74. package/dist/collections/formSubmissions.js +55 -12
  75. package/dist/collections/formSubmissions.js.map +1 -1
  76. package/dist/collections/forms.js +376 -57
  77. package/dist/collections/forms.js.map +1 -1
  78. package/dist/collections/redirectFields.d.ts +29 -0
  79. package/dist/conditions/conditionType.js +9 -2
  80. package/dist/conditions/conditionType.js.map +1 -1
  81. package/dist/conditions/normalizeConditions.js +2 -1
  82. package/dist/conditions/normalizeConditions.js.map +1 -1
  83. package/dist/consent/applyConsentStatements.d.ts +14 -0
  84. package/dist/consent/applyConsentStatements.js +17 -0
  85. package/dist/consent/applyConsentStatements.js.map +1 -0
  86. package/dist/consent/captureConsent.d.ts +19 -8
  87. package/dist/consent/captureConsent.js +34 -11
  88. package/dist/consent/captureConsent.js.map +1 -1
  89. package/dist/consent/consentSourcesField.d.ts +58 -0
  90. package/dist/consent/consentSourcesField.js +79 -0
  91. package/dist/consent/consentSourcesField.js.map +1 -0
  92. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  93. package/dist/consent/resolveConsentEntries.js +43 -0
  94. package/dist/consent/resolveConsentEntries.js.map +1 -0
  95. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  96. package/dist/consent/resolveConsentSourcesRequest.js +55 -0
  97. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  98. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  99. package/dist/consent/resolveConsentStatements.js +44 -0
  100. package/dist/consent/resolveConsentStatements.js.map +1 -0
  101. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  102. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  103. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  104. package/dist/consent/types.d.ts +49 -0
  105. package/dist/email/departments.d.ts +55 -0
  106. package/dist/email/departments.js +130 -0
  107. package/dist/email/departments.js.map +1 -0
  108. package/dist/email/departmentsField.d.ts +49 -0
  109. package/dist/email/departmentsField.js +69 -0
  110. package/dist/email/departmentsField.js.map +1 -0
  111. package/dist/exports/client.d.ts +8 -1
  112. package/dist/exports/client.js +8 -1
  113. package/dist/exports/react.d.ts +15 -6
  114. package/dist/exports/react.js +11 -3
  115. package/dist/exports/rsc.d.ts +8 -1
  116. package/dist/exports/rsc.js +8 -1
  117. package/dist/exports/types.d.ts +18 -10
  118. package/dist/fields/buildFieldBlocks.js +43 -19
  119. package/dist/fields/buildFieldBlocks.js.map +1 -1
  120. package/dist/fields/builtin/calculation.js +7 -0
  121. package/dist/fields/builtin/calculation.js.map +1 -1
  122. package/dist/fields/builtin/checkbox.js +8 -0
  123. package/dist/fields/builtin/checkbox.js.map +1 -1
  124. package/dist/fields/builtin/consent.js +25 -15
  125. package/dist/fields/builtin/consent.js.map +1 -1
  126. package/dist/fields/builtin/country.d.ts +13 -0
  127. package/dist/fields/builtin/country.js +30 -0
  128. package/dist/fields/builtin/country.js.map +1 -0
  129. package/dist/fields/builtin/date.js +2 -0
  130. package/dist/fields/builtin/date.js.map +1 -1
  131. package/dist/fields/builtin/file.js +13 -8
  132. package/dist/fields/builtin/file.js.map +1 -1
  133. package/dist/fields/builtin/index.d.ts +13 -1
  134. package/dist/fields/builtin/index.js +20 -7
  135. package/dist/fields/builtin/index.js.map +1 -1
  136. package/dist/fields/builtin/message.js +30 -0
  137. package/dist/fields/builtin/message.js.map +1 -0
  138. package/dist/fields/builtin/repeater.js +8 -3
  139. package/dist/fields/builtin/repeater.js.map +1 -1
  140. package/dist/fields/builtin/select.js +8 -3
  141. package/dist/fields/builtin/select.js.map +1 -1
  142. package/dist/fields/builtin/state.d.ts +13 -0
  143. package/dist/fields/builtin/state.js +30 -0
  144. package/dist/fields/builtin/state.js.map +1 -0
  145. package/dist/fields/data/regions.d.ts +21 -0
  146. package/dist/fields/data/regions.js +1238 -0
  147. package/dist/fields/data/regions.js.map +1 -0
  148. package/dist/fields/fieldKey.d.ts +15 -0
  149. package/dist/fields/fieldKey.js +16 -0
  150. package/dist/fields/fieldKey.js.map +1 -0
  151. package/dist/fields/fieldNamesOfType.js +13 -8
  152. package/dist/fields/fieldNamesOfType.js.map +1 -1
  153. package/dist/fields/instanceOptions.js +20 -0
  154. package/dist/fields/instanceOptions.js.map +1 -0
  155. package/dist/fields/localizedIf.d.ts +13 -0
  156. package/dist/fields/localizedIf.js +12 -0
  157. package/dist/fields/localizedIf.js.map +1 -0
  158. package/dist/fields/registry.d.ts +1 -0
  159. package/dist/fields/registry.js +21 -2
  160. package/dist/fields/registry.js.map +1 -1
  161. package/dist/fields/sharedConfig.js +101 -51
  162. package/dist/fields/sharedConfig.js.map +1 -1
  163. package/dist/fields/types.d.ts +61 -5
  164. package/dist/flow/engine.d.ts +6 -4
  165. package/dist/flow/engine.js +11 -6
  166. package/dist/flow/engine.js.map +1 -1
  167. package/dist/flow/normalizeFlow.js +22 -7
  168. package/dist/flow/normalizeFlow.js.map +1 -1
  169. package/dist/flow/stepLabel.js +15 -0
  170. package/dist/flow/stepLabel.js.map +1 -0
  171. package/dist/flow/types.d.ts +12 -3
  172. package/dist/flow/types.js +11 -0
  173. package/dist/flow/types.js.map +1 -0
  174. package/dist/form/pollState.d.ts +22 -0
  175. package/dist/form/pollState.js +17 -0
  176. package/dist/form/pollState.js.map +1 -0
  177. package/dist/form/toFormDocument.d.ts +85 -0
  178. package/dist/form/toFormDocument.js +75 -0
  179. package/dist/form/toFormDocument.js.map +1 -0
  180. package/dist/form/types.d.ts +65 -0
  181. package/dist/index.d.ts +193 -27
  182. package/dist/index.js +59 -19
  183. package/dist/index.js.map +1 -1
  184. package/dist/plugin/customState.js +26 -0
  185. package/dist/plugin/customState.js.map +1 -0
  186. package/dist/plugin/registerCollections.js +33 -44
  187. package/dist/plugin/registerCollections.js.map +1 -1
  188. package/dist/plugin/uploadsCollection.d.ts +13 -0
  189. package/dist/plugin/uploadsCollection.js +44 -0
  190. package/dist/plugin/uploadsCollection.js.map +1 -0
  191. package/dist/poll/applyPollOptions.js +18 -0
  192. package/dist/poll/applyPollOptions.js.map +1 -0
  193. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  194. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  195. package/dist/poll/closeJob.d.ts +52 -0
  196. package/dist/poll/closeJob.js +119 -0
  197. package/dist/poll/closeJob.js.map +1 -0
  198. package/dist/poll/definePollOptionSource.d.ts +44 -0
  199. package/dist/poll/definePollOptionSource.js +6 -0
  200. package/dist/poll/definePollOptionSource.js.map +1 -0
  201. package/dist/poll/definePollType.d.ts +34 -0
  202. package/dist/poll/definePollType.js +6 -0
  203. package/dist/poll/definePollType.js.map +1 -0
  204. package/dist/poll/effectivePollOptions.d.ts +38 -0
  205. package/dist/poll/effectivePollOptions.js +64 -0
  206. package/dist/poll/effectivePollOptions.js.map +1 -0
  207. package/dist/poll/mostVoted.d.ts +21 -0
  208. package/dist/poll/mostVoted.js +32 -0
  209. package/dist/poll/mostVoted.js.map +1 -0
  210. package/dist/poll/outcomeBeforeChange.js +91 -0
  211. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  212. package/dist/poll/outcomeFields.d.ts +38 -0
  213. package/dist/poll/outcomeFields.js +52 -0
  214. package/dist/poll/outcomeFields.js.map +1 -0
  215. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  216. package/dist/poll/pollTypeRegistry.js +71 -0
  217. package/dist/poll/pollTypeRegistry.js.map +1 -0
  218. package/dist/poll/registry.d.ts +16 -0
  219. package/dist/poll/registry.js +16 -0
  220. package/dist/poll/registry.js.map +1 -0
  221. package/dist/poll/resolvePollCloseRequest.js +105 -0
  222. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  223. package/dist/poll/resolvePollOptions.d.ts +29 -0
  224. package/dist/poll/resolvePollOptions.js +52 -0
  225. package/dist/poll/resolvePollOptions.js.map +1 -0
  226. package/dist/poll/resolvePollOptionsRequest.js +51 -0
  227. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  228. package/dist/poll/resolvePollOutcome.d.ts +34 -0
  229. package/dist/poll/resolvePollOutcome.js +76 -0
  230. package/dist/poll/resolvePollOutcome.js.map +1 -0
  231. package/dist/poll/resultsField.js +21 -0
  232. package/dist/poll/resultsField.js.map +1 -0
  233. package/dist/prefill/valuesFromSearchParams.js +3 -2
  234. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  235. package/dist/presentations/types.d.ts +3 -2
  236. package/dist/react/Form.d.ts +15 -33
  237. package/dist/react/Form.js +144 -119
  238. package/dist/react/Form.js.map +1 -1
  239. package/dist/react/FormContext.d.ts +37 -1
  240. package/dist/react/FormContext.js.map +1 -1
  241. package/dist/react/FormControls.d.ts +55 -0
  242. package/dist/react/FormControls.js +56 -0
  243. package/dist/react/FormControls.js.map +1 -0
  244. package/dist/react/FormResults.d.ts +6 -3
  245. package/dist/react/FormResults.js +37 -29
  246. package/dist/react/FormResults.js.map +1 -1
  247. package/dist/react/FormSteps.d.ts +17 -0
  248. package/dist/react/FormSteps.js +35 -0
  249. package/dist/react/FormSteps.js.map +1 -0
  250. package/dist/react/Poll.d.ts +17 -6
  251. package/dist/react/Poll.js +66 -18
  252. package/dist/react/Poll.js.map +1 -1
  253. package/dist/react/contract.d.ts +2 -3
  254. package/dist/react/contract.js.map +1 -1
  255. package/dist/react/fetchResults.d.ts +1 -1
  256. package/dist/react/fetchResults.js +1 -1
  257. package/dist/react/fetchResults.js.map +1 -1
  258. package/dist/react/primitives/Checkbox.d.ts +4 -2
  259. package/dist/react/primitives/Checkbox.js +2 -1
  260. package/dist/react/primitives/Checkbox.js.map +1 -1
  261. package/dist/react/primitives/FieldShell.d.ts +2 -4
  262. package/dist/react/primitives/FieldShell.js +12 -19
  263. package/dist/react/primitives/FieldShell.js.map +1 -1
  264. package/dist/react/renderers/calculation.js +1 -2
  265. package/dist/react/renderers/calculation.js.map +1 -1
  266. package/dist/react/renderers/checkbox.js +1 -2
  267. package/dist/react/renderers/checkbox.js.map +1 -1
  268. package/dist/react/renderers/consent.js +78 -34
  269. package/dist/react/renderers/consent.js.map +1 -1
  270. package/dist/react/renderers/date.js +1 -3
  271. package/dist/react/renderers/date.js.map +1 -1
  272. package/dist/react/renderers/email.js +1 -2
  273. package/dist/react/renderers/email.js.map +1 -1
  274. package/dist/react/renderers/file.js +6 -3
  275. package/dist/react/renderers/file.js.map +1 -1
  276. package/dist/react/renderers/index.js +5 -0
  277. package/dist/react/renderers/index.js.map +1 -1
  278. package/dist/react/renderers/message.js +35 -0
  279. package/dist/react/renderers/message.js.map +1 -0
  280. package/dist/react/renderers/number.js +1 -2
  281. package/dist/react/renderers/number.js.map +1 -1
  282. package/dist/react/renderers/region.js +45 -0
  283. package/dist/react/renderers/region.js.map +1 -0
  284. package/dist/react/renderers/repeater.js +7 -10
  285. package/dist/react/renderers/repeater.js.map +1 -1
  286. package/dist/react/renderers/select.js +1 -2
  287. package/dist/react/renderers/select.js.map +1 -1
  288. package/dist/react/renderers/text.js +1 -2
  289. package/dist/react/renderers/text.js.map +1 -1
  290. package/dist/react/renderers/textarea.js +1 -2
  291. package/dist/react/renderers/textarea.js.map +1 -1
  292. package/dist/react/state.d.ts +23 -2
  293. package/dist/react/state.js +16 -7
  294. package/dist/react/state.js.map +1 -1
  295. package/dist/react/uploadFile.d.ts +2 -2
  296. package/dist/react/uploadFile.js +1 -1
  297. package/dist/react/uploadFile.js.map +1 -1
  298. package/dist/react/useField.d.ts +0 -1
  299. package/dist/react/useField.js +0 -1
  300. package/dist/react/useField.js.map +1 -1
  301. package/dist/react/useFormState.d.ts +1 -1
  302. package/dist/react/useFormState.js +1 -1
  303. package/dist/react/useFormState.js.map +1 -1
  304. package/dist/react/validateField.js +3 -6
  305. package/dist/react/validateField.js.map +1 -1
  306. package/dist/recall/interpolate.d.ts +4 -3
  307. package/dist/recall/interpolate.js +5 -4
  308. package/dist/recall/interpolate.js.map +1 -1
  309. package/dist/recall/resolver.d.ts +1 -1
  310. package/dist/recall/resolver.js +14 -2
  311. package/dist/recall/resolver.js.map +1 -1
  312. package/dist/spam/captcha.d.ts +1 -1
  313. package/dist/spam/captcha.js +1 -1
  314. package/dist/spam/captcha.js.map +1 -1
  315. package/dist/submissions/SubmissionAnswers.js +6 -2
  316. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  317. package/dist/submissions/SubmissionAnswersClient.js +91 -62
  318. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  319. package/dist/submissions/createSubmission.d.ts +36 -0
  320. package/dist/submissions/createSubmission.js +26 -0
  321. package/dist/submissions/createSubmission.js.map +1 -0
  322. package/dist/submissions/runSubmission.d.ts +10 -0
  323. package/dist/submissions/runSubmission.js +92 -44
  324. package/dist/submissions/runSubmission.js.map +1 -1
  325. package/dist/submissions/types.d.ts +11 -4
  326. package/dist/submissions/validateSubmission.js +59 -10
  327. package/dist/submissions/validateSubmission.js.map +1 -1
  328. package/dist/submissions/votedCookie.d.ts +13 -0
  329. package/dist/submissions/votedCookie.js +27 -0
  330. package/dist/submissions/votedCookie.js.map +1 -0
  331. package/dist/translations/de.js +295 -0
  332. package/dist/translations/de.js.map +1 -0
  333. package/dist/translations/en.js +138 -23
  334. package/dist/translations/en.js.map +1 -1
  335. package/dist/translations/index.d.ts +2 -1
  336. package/dist/translations/index.js +6 -2
  337. package/dist/translations/index.js.map +1 -1
  338. package/dist/translations/keys.d.ts +134 -19
  339. package/dist/translations/keys.js +135 -20
  340. package/dist/translations/keys.js.map +1 -1
  341. package/dist/uploads/types.d.ts +1 -2
  342. package/dist/validation/buildRuleBlocks.js +18 -17
  343. package/dist/validation/buildRuleBlocks.js.map +1 -1
  344. package/dist/validation/builtin/email.js +1 -0
  345. package/dist/validation/builtin/email.js.map +1 -1
  346. package/dist/validation/builtin/matchesField.js +3 -7
  347. package/dist/validation/builtin/matchesField.js.map +1 -1
  348. package/dist/validation/builtin/max.js +1 -0
  349. package/dist/validation/builtin/max.js.map +1 -1
  350. package/dist/validation/builtin/maxDate.js +1 -0
  351. package/dist/validation/builtin/maxDate.js.map +1 -1
  352. package/dist/validation/builtin/maxLength.js +1 -0
  353. package/dist/validation/builtin/maxLength.js.map +1 -1
  354. package/dist/validation/builtin/min.js +1 -0
  355. package/dist/validation/builtin/min.js.map +1 -1
  356. package/dist/validation/builtin/minDate.js +1 -0
  357. package/dist/validation/builtin/minDate.js.map +1 -1
  358. package/dist/validation/builtin/minLength.js +1 -0
  359. package/dist/validation/builtin/minLength.js.map +1 -1
  360. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  361. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  362. package/dist/validation/builtin/oneOf.js +1 -0
  363. package/dist/validation/builtin/oneOf.js.map +1 -1
  364. package/dist/validation/builtin/pattern.js +11 -2
  365. package/dist/validation/builtin/pattern.js.map +1 -1
  366. package/dist/validation/builtin/url.js +1 -0
  367. package/dist/validation/builtin/url.js.map +1 -1
  368. package/dist/validation/builtin/validateRegexParams.js +44 -0
  369. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  370. package/dist/validation/fieldTargetParam.d.ts +22 -0
  371. package/dist/validation/fieldTargetParam.js +38 -0
  372. package/dist/validation/fieldTargetParam.js.map +1 -0
  373. package/dist/validation/runValidation.js +8 -29
  374. package/dist/validation/runValidation.js.map +1 -1
  375. package/dist/validation/types.d.ts +5 -11
  376. package/dist/validation/validateUrl.js +22 -0
  377. package/dist/validation/validateUrl.js.map +1 -0
  378. package/package.json +7 -6
  379. package/styles/form-builder.css +10 -0
  380. package/dist/collections/uploads.d.ts +0 -30
  381. package/dist/collections/uploads.js +0 -55
  382. package/dist/collections/uploads.js.map +0 -1
  383. package/dist/consent/buildConsentSourceConfig.js +0 -49
  384. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  385. package/dist/consent/builtin/index.d.ts +0 -7
  386. package/dist/consent/builtin/index.js +0 -11
  387. package/dist/consent/builtin/index.js.map +0 -1
  388. package/dist/consent/builtin/pageReference.js +0 -75
  389. package/dist/consent/builtin/pageReference.js.map +0 -1
  390. package/dist/consent/builtin/static.js +0 -44
  391. package/dist/consent/builtin/static.js.map +0 -1
  392. package/dist/consent/defineConsentSource.d.ts +0 -35
  393. package/dist/consent/defineConsentSource.js +0 -6
  394. package/dist/consent/defineConsentSource.js.map +0 -1
  395. package/dist/consent/registry.d.ts +0 -16
  396. package/dist/consent/registry.js +0 -16
  397. package/dist/consent/registry.js.map +0 -1
  398. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  399. package/dist/consent/resolveConsentLinks.js +0 -26
  400. package/dist/consent/resolveConsentLinks.js.map +0 -1
  401. package/dist/presentations/registry.d.ts +0 -15
  402. package/dist/presentations/registry.js +0 -15
  403. package/dist/presentations/registry.js.map +0 -1
  404. package/dist/react/toFormDocument.d.ts +0 -24
  405. package/dist/react/toFormDocument.js +0 -22
  406. package/dist/react/toFormDocument.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype Field,\n\ttype PayloadRequest,\n\tValidationError,\n} from 'payload'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { ActionRegistry } from '../actions/registry'\nimport { resolveFormResultsRequest } from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\n\nexport const FORMS_SLUG = 'forms'\n\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry?: ConsentSourceRegistry\n\tpresentationRegistry?: PresentationDescriptorRegistry\n\tactionRegistry?: ActionRegistry\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry = new Map(Object.entries(defaultPresentationDescriptors)),\n\tactionRegistry = new Map(),\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (\n\t\t\tdata &&\n\t\t\ttypeof data.defaultPresentation === 'string' &&\n\t\t\t!presentationRegistry.has(data.defaultPresentation)\n\t\t) {\n\t\t\tdata.defaultPresentation = DEFAULT_PRESENTATION_NAME\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\tconst fieldNames = normalized\n\t\t\t\t.map((field: FieldRow) => (typeof field.name === 'string' ? field.name : undefined))\n\t\t\t\t.filter((name): name is string => name !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldNames)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t'A flow needs at least two steps with unique, non-empty IDs. Add another step or remove the flow.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t}\n\t\treturn data\n\t}\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks(registry, ruleRegistry, consentRegistry),\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'title', type: 'text', required: true, label: labelForKey(keys.fieldTitle) },\n\t\t// Unnamed tabs are presentational only: fields/flow/actions stay at the document root.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField] },\n\t\t\t\t{ label: labelForKey(keys.tabFlow), fields: [flowField] },\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: 'defaultPresentation',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: DEFAULT_PRESENTATION_NAME,\n\t\t\toptions: [...presentationRegistry.values()].map((descriptor) => ({\n\t\t\t\tlabel: labelFor(descriptor.label),\n\t\t\t\tvalue: descriptor.name,\n\t\t\t})),\n\t\t\tlabel: labelForKey(keys.configDefaultPresentation),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'showResults',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: false,\n\t\t\tlabel: labelForKey(keys.configShowResults),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'resultsField',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.configResultsField),\n\t\t\tadmin: {\n\t\t\t\tposition: 'sidebar',\n\t\t\t\tdescription:\n\t\t\t\t\t'Field whose aggregate results are public when \"Show results publicly\" is on. Use a choice field, never a free-text or PII field.',\n\t\t\t\tcondition: (data) => Boolean(data?.showResults),\n\t\t\t},\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: { group: 'Forms', useAsTitle: 'title', ...(overrides?.admin ?? {}) },\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionAfterChangeHook,\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype CollectionSlug,\n\ttype Field,\n\ttype PayloadRequest,\n\ttype TextFieldSingleValidation,\n\tValidationError,\n} from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { FromAddressesResolver } from '../actions/fromAddresses'\nimport { resolveFromAddressesRequest } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport {\n\ttype FormResultsAccess,\n\tresolveFormResultsRequest,\n} from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport { resolveConsentSourcesRequest } from '../consent/resolveConsentSourcesRequest'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport { type DepartmentEmailsResolver, resolveDepartmentsRequest } from '../email/departments'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport { fieldNamesOfType } from '../fields/fieldNamesOfType'\nimport { localizedIf } from '../fields/localizedIf'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { END_OF_FORM } from '../flow/types'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport { buildPollOptionSourceFields } from '../poll/buildPollOptionSourceFields'\nimport { enqueuePollClose } from '../poll/closeJob'\nimport { pollOutcomeBeforeChange } from '../poll/outcomeBeforeChange'\nimport { buildDefaultOutcomeFields, type OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport { type PollTypeRegistry, resolvePollTypes } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { resolvePollCloseRequest } from '../poll/resolvePollCloseRequest'\nimport { resolvePollOptionsRequest } from '../poll/resolvePollOptionsRequest'\nimport { buildValidateResultsField, pollEligibleTypes } from '../poll/resultsField'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelFor, labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { validateUrl } from '../validation/validateUrl'\nimport { type ButtonsOption, buildDefaultButtonFields } from './buttonFields'\nimport type { ResponseOption } from './redirectFields'\n\nexport const FORMS_SLUG = 'forms'\n\n/**\n * Require the title in the default locale (and on hosts without localization), but let it be empty in\n * other locales so a localized form falls back to the default-locale title rather than forcing a\n * translation for every locale. Payload's field-level `required` cannot express this: it enforces per\n * write-locale, which would make a title mandatory in every locale and break the documented fallback.\n */\nconst validateFormTitle: TextFieldSingleValidation = (value, { req }) => {\n\tconst localization = req.payload.config.localization\n\tconst defaultLocale = localization ? localization.defaultLocale : undefined\n\tconst enforced =\n\t\tdefaultLocale === undefined || req.locale === undefined || req.locale === defaultLocale\n\tif (enforced && (typeof value !== 'string' || value.trim().length === 0)) {\n\t\treturn req.t('validation:required')\n\t}\n\treturn true\n}\n\n/**\n * Field-level validation for the raw flow JSON: step ids must be non-empty, unique, and not the\n * reserved `END_OF_FORM` sentinel, and every explicit step-id reference (a string `next`, a\n * transition `to`) must resolve to a known step. `next: null` (explicit end of form) and an\n * absent `next` (fall through to the next step in array order) are always valid. On full-document\n * saves the beforeValidate pass has already run `normalizeFlow` and laundered dangling\n * references, so the unknown-target checks mainly protect partial API updates that send `flow`\n * without `fields`.\n */\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tif (steps.some((s) => s.id === END_OF_FORM)) {\n\t\treturn `Flow: step ID \"${END_OF_FORM}\" is reserved`\n\t}\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\n/**\n * Stamp the configured uploads collection slug onto every `file` block (including repeater\n * sub-fields) on each save. The block carries a hidden `uploadsCollection` field so the slug\n * reaches the client renderer through the form document; the server always overwrites it from\n * plugin config, so the stored value is never author- or client-controlled.\n */\nconst stampFileCollections = (rows: FieldRow[], slug: string): void => {\n\tfor (const row of rows) {\n\t\tif (row.blockType === 'file') {\n\t\t\t;(row as { uploadsCollection?: string }).uploadsCollection = slug\n\t\t}\n\t\tconst subFields = (row as { subFields?: unknown }).subFields\n\t\tif (Array.isArray(subFields)) {\n\t\t\tstampFileCollections(subFields as FieldRow[], slug)\n\t\t}\n\t}\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/**\n\t * The plugin `consent.sources` option. Present: the `/:id/consent-sources` endpoint backing the\n\t * consent field's source select is registered. Absent: neither it nor the consent field type exists.\n\t */\n\tconsentSources?: ConsentSourcesResolver\n\tactionRegistry?: ActionRegistry\n\tlocalizeContent?: boolean\n\t/** The plugin `richText` option; `editor` overrides the response message's richText editor. */\n\trichText?: RichTextBodyOption\n\t/** The host-owned uploads collection slug from plugin config; absent when uploads are disabled. */\n\tuploadsCollectionSlug?: string\n\t/** Host seam gating anonymous results reads (plugin option `results.access`). */\n\tresultsAccess?: FormResultsAccess\n\t/** Registered poll option sources (plugin option `poll.sources`); empty registry means no source fields. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Registered poll outcome strategies (`poll.types`); drives the poll `type` select options. Defaults to the built-ins. */\n\tpollTypeRegistry?: PollTypeRegistry\n\t/** The plugin `poll.outcomeFields` seam; composes the outcome group from the two default fields. */\n\toutcomeFields?: OutcomeFieldsOverride\n\t/** The plugin `buttons` option; `fields` composes the `{ submit, prev, next }` labels from the localized defaults. */\n\tbuttons?: ButtonsOption\n\t/** The plugin `response` option; `redirect.fields` composes the `response.redirect` group from its default fields. */\n\tresponse?: ResponseOption\n\t/**\n\t * The plugin `email.fromAddresses` option. Present: both email actions gain a `from` select and\n\t * the `/:id/from-addresses` endpoint is registered. Absent: neither exists.\n\t */\n\tfromAddresses?: FromAddressesResolver\n\t/**\n\t * The plugin `email.departments` option. Present: the `emailTeam` `to` becomes a department select\n\t * and the `/:id/departments` endpoint backing it is registered. Absent: `to` stays a plain field.\n\t */\n\tdepartments?: DepartmentEmailsResolver\n\t/**\n\t * The plugin `redirectRelationships` option. Non-empty: `response.redirect` gains a polymorphic\n\t * `reference` relationship field so an author can redirect to an internal document instead of\n\t * (or alongside) a URL. Absent or empty: no `reference` field exists, matching today's URL-only\n\t * redirect.\n\t */\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tactionRegistry = new Map(),\n\tlocalizeContent = true,\n\trichText,\n\tuploadsCollectionSlug,\n\tresultsAccess,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tresponse,\n\tfromAddresses,\n\tdepartments,\n\tredirectRelationships,\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst pollTypes = pollTypeRegistry ?? resolvePollTypes()\n\tconst pollResultsTypes = pollEligibleTypes(registry)\n\tconst bareTypes = new Set(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => d.type)\n\t)\n\t// Handed to the FlowBuilder so it can list bare (nameless) blocks in the step picker: slug ->\n\t// label (an i18n key or literal, resolved client-side).\n\tconst bareTypeLabels = Object.fromEntries(\n\t\t[...registry.values()].filter((d) => d.bare === true).map((d) => [d.type, d.label])\n\t)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\tconst FIELD_NAME_SELECT_REF = '@10x-media/form-builder/client#FieldNameSelect'\n\tconst FLOW_STEPS_CELL_REF = '@10x-media/form-builder/client#FlowStepsCell'\n\tconst FIELD_COUNT_CELL_REF = '@10x-media/form-builder/client#FieldCountCell'\n\tconst CLOSE_POLL_BUTTON_REF = '@10x-media/form-builder/client#ClosePollButton'\n\t// The `source` outcome strategy delegates to the poll's option source, so it is useless without one:\n\t// offer it in the author-facing select only when option sources are registered. The strategy stays in\n\t// the registry regardless, so `resolvePollOutcome` and host code can still address it.\n\tconst hasOptionSources = (pollSourceRegistry?.size ?? 0) > 0\n\t// `mostVoted` leads the author-facing select because it is the default: an enabled poll works\n\t// immediately, with no winner to hand-pick. Only the select order changes; the registry keeps its\n\t// own order (host strategies stay after the built-ins).\n\tconst orderedPollTypes = [\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type === 'mostVoted'),\n\t\t...[...pollTypes.values()].filter((strategy) => strategy.type !== 'mostVoted'),\n\t]\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (uploadsCollectionSlug) {\n\t\t\t\tstampFileCollections(normalized, uploadsCollectionSlug)\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\t// Flow step assignments store field keys: machine names for named fields, block row ids\n\t\t\t// for bare (nameless) blocks. Mirrors `fieldKey` over the raw rows.\n\t\t\tconst fieldKeys = normalized\n\t\t\t\t.map((field: FieldRow) => {\n\t\t\t\t\tif (typeof field.name === 'string' && field.name.length > 0) {\n\t\t\t\t\t\treturn field.name\n\t\t\t\t\t}\n\t\t\t\t\treturn bareTypes.has(field.blockType) && field.id != null ? String(field.id) : undefined\n\t\t\t\t})\n\t\t\t\t.filter((key): key is string => key !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldKeys)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage: 'A flow needs at least two steps. Add another step or remove the flow.',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t\t// A poll needs a field whose answers are the votes. With the poll enabled and no vote field\n\t\t\t// chosen (and the outcome not coming from an option source), bind one: auto-fill the sole\n\t\t\t// eligible field for a one-choice form, otherwise surface a clear error rather than storing a\n\t\t\t// poll that can never aggregate. The optionSource/`source` exemption preserves domain-driven\n\t\t\t// polls that have no on-form vote field. `buildValidateResultsField` still checks a set value;\n\t\t\t// this is the enforcer for enabled polls.\n\t\t\tif (data.pollEnabled === true) {\n\t\t\t\tconst poll =\n\t\t\t\t\tdata.poll != null && typeof data.poll === 'object'\n\t\t\t\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t\t\t\t: {}\n\t\t\t\tconst resultsField = typeof poll.resultsField === 'string' ? poll.resultsField.trim() : ''\n\t\t\t\tconst optionSource = typeof poll.optionSource === 'string' ? poll.optionSource.trim() : ''\n\t\t\t\tconst sourceDriven = optionSource.length > 0 || poll.type === 'source'\n\t\t\t\tif (resultsField.length === 0 && !sourceDriven) {\n\t\t\t\t\tconst eligible = fieldNamesOfType(data.fields, pollResultsTypes)\n\t\t\t\t\tif (eligible.length === 1) {\n\t\t\t\t\t\tdata.poll = { ...poll, resultsField: eligible[0] }\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst messageKey =\n\t\t\t\t\t\t\teligible.length > 1 ? keys.pollVoteFieldChoose : keys.pollVoteFieldMissing\n\t\t\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\t\t\terrors: [{ path: 'poll.resultsField', message: asTranslate(req.t)(messageKey) }],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\treq.t\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn data\n\t}\n\n\t// After every save, (re)schedule the poll's auto-close job when it applies (enabled + closesAt +\n\t// unresolved + non-manual strategy + a job runner present). Best-effort and non-throwing, so it\n\t// never affects the save; without a runner the resolve-on-read fallback in the results endpoint\n\t// heals the outcome instead.\n\tconst pollCloseAfterChange: CollectionAfterChangeHook = async ({ doc, req }) => {\n\t\tawait enqueuePollClose({ payload: req.payload, form: doc, req })\n\t\treturn doc\n\t}\n\n\t// Select-option label from a strategy's `label`: a string is resolved like a field label (i18n key\n\t// or literal), a per-locale record passes through unchanged.\n\tconst pollTypeOptionLabel = (label: string | Record<string, string>) =>\n\t\ttypeof label === 'string' ? labelFor(label) : label\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks({ registry, ruleRegistry, localize: localizeContent }),\n\t\tadmin: { components: { Cell: FIELD_COUNT_CELL_REF } },\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tCell: FLOW_STEPS_CELL_REF,\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes, bareTypeLabels } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: ['string', 'null'] as ('string' | 'null')[] },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\t// Optional polymorphic reference to an internal document, for `response.redirect` to point at\n\t// instead of (or alongside) a URL. Absent unless `redirectRelationships` is a non-empty array:\n\t// always polymorphic even for one slug (the consent `page` field's precedent), so a host adding\n\t// a second collection later never changes the stored shape. The plugin never resolves this to a\n\t// URL itself; `toFormDocument` passes the raw `{ relationTo, value }` through for the host to\n\t// resolve, since only the host knows its own routing.\n\tconst redirectReferenceField: Field[] =\n\t\tredirectRelationships && redirectRelationships.length > 0\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: 'reference',\n\t\t\t\t\t\ttype: 'relationship',\n\t\t\t\t\t\trelationTo: redirectRelationships,\n\t\t\t\t\t\tlabel: labelForKey(keys.responseRedirectReference),\n\t\t\t\t\t\tadmin: { description: labelForKey(keys.responseRedirectReferenceDescription) },\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\n\t// The redirect group's fields: the `url` text field plus the optional polymorphic `reference`.\n\t// The `response.redirect.fields` seam composes them (mirroring `buttons.fields`), so a host can\n\t// prepend a custom link field, swap `url` for their own picker, or filter; the default (no\n\t// override) is the same `[url, ...reference]` as before. Built-in redirect handling still reads\n\t// `redirect.url`/`redirect.reference`, so a host replacing `url` owns resolving their field to a\n\t// destination in their frontend.\n\tconst urlField: Field = {\n\t\tname: 'url',\n\t\ttype: 'text',\n\t\tlabel: labelForKey(keys.responseUrl),\n\t\tvalidate: validateUrl,\n\t}\n\tconst defaultRedirectFields: Field[] = [urlField, ...redirectReferenceField]\n\tconst redirectFields = response?.redirect?.fields\n\t\t? response.redirect.fields({ defaultFields: defaultRedirectFields })\n\t\t: defaultRedirectFields\n\n\t// What the visitor sees after a successful submit. Publicly readable (unlike actions): the\n\t// client renderer needs message/redirect. `type`/`url` are behavior, never localized;\n\t// `message` is visitor-facing content and follows `localizeContent`.\n\t// `type` is defaulted and not clearable rather than `required`: a required member would make\n\t// the whole group required in generated types, breaking typed `payload.create` calls that\n\t// omit `response`. Consumers treat a missing type as 'message'.\n\tconst responseField: Field = {\n\t\tname: 'response',\n\t\ttype: 'group',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'message',\n\t\t\t\tlabel: labelForKey(keys.responseType),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeMessage), value: 'message' },\n\t\t\t\t\t{ label: labelForKey(keys.responseTypeRedirect), value: 'redirect' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'message',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.responseMessage),\n\t\t\t\t// Unset type (docs predating this field) means 'message', matching the client fallback.\n\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.type !== 'redirect' },\n\t\t\t\t...(richText?.editor ? { editor: richText.editor } : {}),\n\t\t\t\t...localizedIf(localizeContent),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'redirect',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.responseRedirect),\n\t\t\t\tadmin: {\n\t\t\t\t\tcondition: (_data, siblingData) => siblingData?.type === 'redirect',\n\t\t\t\t\thideGutter: true,\n\t\t\t\t},\n\t\t\t\tfields: redirectFields,\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultOutcomeFields = buildDefaultOutcomeFields()\n\tconst defaultButtonFields = buildDefaultButtonFields(localizeContent)\n\t// Half-width copy of a default field for the prev/next row; merges width into whatever admin the\n\t// field already carries (a host's `buttons.fields` override may add its own) rather than replacing\n\t// it. The cast is safe: `width` is a valid admin option on every field variant, but spreading a\n\t// `Field`-typed value's `admin` back into a union-typed object literal loses the discriminant TS\n\t// needs to check it structurally.\n\tconst halfWidth = (field: Field): Field =>\n\t\t({ ...field, admin: { ...field.admin, width: '50%' } }) as Field\n\t// The three button-label fields sit at the document root now (no `buttons` group): `submit` at\n\t// the bottom of the Fields tab, `prev`/`next` in a row on the Flow tab. The `buttons.fields` seam\n\t// composes each slot from the already-localized defaults, so a host can wrap a default in a row\n\t// with its own field (e.g. an icon select) or replace it; `toFormDocument` reassembles the three\n\t// labels into `FormDocument.buttons` for the client.\n\tconst composedButtons = buttons?.fields\n\t\t? buttons.fields({ defaultFields: defaultButtonFields })\n\t\t: defaultButtonFields\n\tconst submitField = composedButtons.submit\n\tconst prevNextRow: Field = {\n\t\ttype: 'row',\n\t\t// The prev/next labels only matter once the flow has a step; the Flow tab itself is gated on\n\t\t// `multistep`. `data` (1st arg) is the whole document, so `flow.steps` reads the stored flow.\n\t\tadmin: {\n\t\t\tcondition: (data) => {\n\t\t\t\tconst steps = (data as { flow?: { steps?: unknown } })?.flow?.steps\n\t\t\t\treturn Array.isArray(steps) && steps.length >= 1\n\t\t\t},\n\t\t},\n\t\tfields: [halfWidth(composedButtons.prev), halfWidth(composedButtons.next)],\n\t}\n\n\t// Poll lifecycle config; identifiers and behavior, never localized. Lives inside the conditional\n\t// Poll tab (gated on the top-level `pollEnabled` flag), so the per-field enabled conditions are\n\t// gone; `label: false` suppresses the group header the tab label already provides.\n\t// `resultsVisibility` is defaulted and not clearable rather than `required` for the same\n\t// generated-types reason as `response.type`.\n\tconst pollGroupField: Field = {\n\t\tname: 'poll',\n\t\ttype: 'group',\n\t\tlabel: false,\n\t\tfields: [\n\t\t\t// Authored by picking from the form's poll-eligible fields; the stored value stays a plain\n\t\t\t// text field name. Select options and server validate share `pollResultsTypes`, so they\n\t\t\t// cannot drift.\n\t\t\t{\n\t\t\t\tname: 'resultsField',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.pollResultsField),\n\t\t\t\tvalidate: buildValidateResultsField(pollResultsTypes),\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: {\n\t\t\t\t\t\tField: {\n\t\t\t\t\t\t\tpath: FIELD_NAME_SELECT_REF,\n\t\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\t\ttypes: pollResultsTypes,\n\t\t\t\t\t\t\t\tdescriptionKey: keys.pollResultsFieldDescription,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// How the winning value(s) get decided. `mostVoted` (default) and `source` auto-resolve on close\n\t\t\t// (via the scheduled job or the results-read fallback); `manual` leaves it to an admin. Options\n\t\t\t// come from the registered `poll.types` strategies, so a host strategy appears here too, with\n\t\t\t// `mostVoted` first so an enabled poll works with no winner to hand-pick.\n\t\t\t{\n\t\t\t\tname: 'type',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'mostVoted',\n\t\t\t\tlabel: labelForKey(keys.pollType),\n\t\t\t\tadmin: { isClearable: false, description: labelForKey(keys.pollTypeDescription) },\n\t\t\t\toptions: orderedPollTypes\n\t\t\t\t\t.filter((strategy) => strategy.type !== 'source' || hasOptionSources)\n\t\t\t\t\t.map((strategy) => ({\n\t\t\t\t\t\tlabel: pollTypeOptionLabel(strategy.label),\n\t\t\t\t\t\tvalue: strategy.type,\n\t\t\t\t\t})),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'resultsVisibility',\n\t\t\t\ttype: 'select',\n\t\t\t\tdefaultValue: 'afterVote',\n\t\t\t\tlabel: labelForKey(keys.pollResultsVisibility),\n\t\t\t\tadmin: { isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterVote), value: 'afterVote' },\n\t\t\t\t\t{ label: labelForKey(keys.pollVisibilityAfterClose), value: 'afterClose' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'closesAt',\n\t\t\t\ttype: 'date',\n\t\t\t\tlabel: labelForKey(keys.pollClosesAt),\n\t\t\t\tadmin: { date: { pickerAppearance: 'dayAndTime' } },\n\t\t\t},\n\t\t\t// Close / reopen the poll from the admin. The button toggles on the live `closesAt` and saves the\n\t\t\t// whole document (persisting an unsaved winner alongside `closesAt`) rather than calling the close\n\t\t\t// endpoint, so there is no DB-vs-form-state mismatch. Always mounted inside the (pollEnabled-gated)\n\t\t\t// Poll tab now; the button owns both states, so it carries no `admin.condition` of its own.\n\t\t\t{\n\t\t\t\tname: 'closePoll',\n\t\t\t\ttype: 'ui',\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: { Field: CLOSE_POLL_BUTTON_REF },\n\t\t\t\t},\n\t\t\t},\n\t\t\t...buildPollOptionSourceFields(pollSourceRegistry ?? new Map()),\n\t\t\t// `winningValues` is recorded either by an admin picking from the poll's effective options\n\t\t\t// (served by `/:id/poll-options`) or by `resolvePollOutcome` (host domain logic); more than\n\t\t\t// one value records a tie. The `pollOutcomeBeforeChange` hook validates both paths and owns\n\t\t\t// the `resolvedAt` stamp. `resolvedAt` itself stays fully locked: field-level create/update\n\t\t\t// access blocks every non-override caller write (Payload silently drops the denied value\n\t\t\t// rather than erroring) while the hook's stamp, applied after access filtering, still\n\t\t\t// persists. The `poll.outcomeFields` seam receives both defaults and its return becomes the\n\t\t\t// group's fields verbatim, so a host can swap `winningValues` for its own component; the hook\n\t\t\t// still validates every stored value against the effective options, so no swap bypasses it.\n\t\t\t// `hideGutter` stays: outcome is still a group nested inside the poll group.\n\t\t\t{\n\t\t\t\tname: 'outcome',\n\t\t\t\ttype: 'group',\n\t\t\t\tlabel: labelForKey(keys.pollOutcome),\n\t\t\t\tadmin: { hideGutter: true },\n\t\t\t\tfields: outcomeFields\n\t\t\t\t\t? outcomeFields({ defaultFields: defaultOutcomeFields })\n\t\t\t\t\t: [defaultOutcomeFields.winningValues, defaultOutcomeFields.resolvedAt],\n\t\t\t},\n\t\t],\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t// The document title is the only visitor-facing text the collection renders itself: it is\n\t\t// always required (drives `useAsTitle` in the admin list/relationship views) and is passed\n\t\t// through `toFormDocument` as `FormDocument.title`. Whether and how a host renders it above\n\t\t// the fields is entirely the host's call; the plugin does not gate or duplicate it.\n\t\t{\n\t\t\tname: 'title',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.fieldTitle),\n\t\t\t// Visitor-facing content, so it follows `localizeContent` like the response message and\n\t\t\t// consent statement: hosts with localization get a per-locale title (and a locale-aware\n\t\t\t// `useAsTitle`), hosts without it are unaffected since Payload strips the flag. `validate`\n\t\t\t// (not `required`) keeps it mandatory only in the default locale so other locales fall back.\n\t\t\t...localizedIf(localizeContent),\n\t\t\tvalidate: validateFormTitle,\n\t\t},\n\t\t// The two form-type flags: behavior, never localized. `multistep` gates the Flow tab and the\n\t\t// client's step navigation; `pollEnabled` gates the Poll tab and marks the form a poll.\n\t\t{\n\t\t\ttype: 'row',\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tname: 'multistep',\n\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: labelForKey(keys.formMultistep),\n\t\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'pollEnabled',\n\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: labelForKey(keys.formPollEnabled),\n\t\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t// Unnamed tabs are presentational: their fields stay at the document root. An unnamed tab's\n\t\t// `admin.condition` receives the whole document as its 2nd arg, so the Flow and Poll tabs gate\n\t\t// on the root-level flags. The poll group nests its config under `form.poll` as before.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField, submitField] },\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.tabFlow),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.multistep === true },\n\t\t\t\t\tfields: [flowField, prevNextRow],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: labelForKey(keys.pollGroup),\n\t\t\t\t\tadmin: { condition: (_data, siblingData) => siblingData?.pollEnabled === true },\n\t\t\t\t\tfields: [pollGroupField],\n\t\t\t\t},\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t\t{ label: labelForKey(keys.tabResponse), fields: [responseField] },\n\t\t\t],\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t\taccess: resultsAccess,\n\t\t\t\t\teligibleTypes: pollResultsTypes,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tpath: '/:id/poll-options',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst { status, body } = await resolvePollOptionsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t\t// Trusted admin action (authenticated only): close a poll now and resolve its outcome. A POST\n\t\t// because it mutates; auth is `Boolean(req.user)` so an anonymous caller gets 403 from the helper.\n\t\t{\n\t\t\tpath: '/:id/close',\n\t\t\tmethod: 'post',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst { status, body } = await resolvePollCloseRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t\t...(consentSources\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: '/:id/consent-sources',\n\t\t\t\t\t\tmethod: 'get' as const,\n\t\t\t\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\t\t\t\tconst { status, body } = await resolveConsentSourcesRequest({\n\t\t\t\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\t\tresolver: consentSources,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treturn Response.json(body, { status })\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t// The route id is unused: the from-addresses set is request-scoped (e.g. per tenant), not\n\t\t// per-form. Registered as a doc-scoped route only so the admin field can reuse\n\t\t// EndpointOptionsSelect unmodified (see buildFromField).\n\t\t...(fromAddresses\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: '/:id/from-addresses',\n\t\t\t\t\t\tmethod: 'get' as const,\n\t\t\t\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\t\t\t\tconst { status, body } = await resolveFromAddressesRequest({\n\t\t\t\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\t\tresolver: fromAddresses,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treturn Response.json(body, { status })\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t// Same request-scoped, id-unused shape as from-addresses: registered doc-scoped only so the\n\t\t// admin `to` select can reuse EndpointOptionsSelect unmodified (see buildToField).\n\t\t...(departments\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: '/:id/departments',\n\t\t\t\t\t\tmethod: 'get' as const,\n\t\t\t\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\t\t\t\tconst { status, body } = await resolveDepartmentsRequest({\n\t\t\t\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\t\t\t\treq,\n\t\t\t\t\t\t\t\tresolver: departments,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treturn Response.json(body, { status })\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tuseAsTitle: 'title',\n\t\t\tdefaultColumns: ['title', 'fields', 'flow', 'pollEnabled', 'updatedAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t\tbeforeChange: [pollOutcomeBeforeChange, ...(overrides?.hooks?.beforeChange ?? [])],\n\t\t\tafterChange: [pollCloseAfterChange, ...(overrides?.hooks?.afterChange ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { Field } from "payload";
2
+
3
+ //#region src/collections/redirectFields.d.ts
4
+ /**
5
+ * Composes the fields inside the forms `response.redirect` group. Receives the default fields (the
6
+ * `url` text field, plus the polymorphic `reference` relationship when `redirectRelationships` is
7
+ * set) and returns the group's final field array verbatim, so a host can prepend a custom link
8
+ * field, swap `url` for their own picker, reorder, or filter. Mirrors `buttons.fields` and
9
+ * `poll.outcomeFields`.
10
+ *
11
+ * The plugin's built-in redirect handling reads `redirect.url` (and `redirect.reference` when
12
+ * configured); `toFormDocument` passes the whole `response` group through, so a host that replaces
13
+ * `url` with their own link field owns resolving it to a destination in their frontend, the same as
14
+ * the internal-reference case.
15
+ */
16
+ type RedirectFieldsOverride = (args: {
17
+ defaultFields: Field[];
18
+ }) => Field[];
19
+ /** The forms `response.redirect` group options: its fields composition seam. */
20
+ type RedirectOption = {
21
+ fields?: RedirectFieldsOverride;
22
+ };
23
+ /** The plugin `response` option: currently the `redirect` group's composition seam. */
24
+ type ResponseOption = {
25
+ redirect?: RedirectOption;
26
+ };
27
+ //#endregion
28
+ export { RedirectFieldsOverride, RedirectOption, ResponseOption };
29
+ //# sourceMappingURL=redirectFields.d.ts.map
@@ -2,10 +2,17 @@ import { defaultConditionType } from "./fieldTypes.js";
2
2
  //#region src/conditions/conditionType.ts
3
3
  /** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */
4
4
  const conditionTypeForDefinition = (definition) => definition.conditionType ?? defaultConditionType(definition.value);
5
- /** A serializable slug -> condition type map for every registered field type, handed to the client builder. */
5
+ /**
6
+ * A serializable slug -> condition type map for every conditionable field type, handed to the client
7
+ * builder. Valueless ('none' kind, display-only) types are left out so they never appear as operands;
8
+ * consumers treat a missing entry as "not conditionable". An explicit `conditionType` opts back in.
9
+ */
6
10
  const buildConditionTypeMap = (registry) => {
7
11
  const map = {};
8
- for (const definition of registry.values()) map[definition.type] = conditionTypeForDefinition(definition);
12
+ for (const definition of registry.values()) {
13
+ if (definition.value === "none" && definition.conditionType == null) continue;
14
+ map[definition.type] = conditionTypeForDefinition(definition);
15
+ }
9
16
  return map;
10
17
  };
11
18
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"conditionType.js","names":[],"sources":["../../src/conditions/conditionType.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../fields/types'\nimport { type ConditionFieldType, defaultConditionType } from './fieldTypes'\n\n/** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */\nexport const conditionTypeForDefinition = (\n\tdefinition: Pick<AnyFormFieldDefinition, 'value' | 'conditionType'>\n): ConditionFieldType => definition.conditionType ?? defaultConditionType(definition.value)\n\n/** A serializable slug -> condition type map for every registered field type, handed to the client builder. */\nexport const buildConditionTypeMap = (\n\tregistry: Map<string, AnyFormFieldDefinition>\n): Record<string, ConditionFieldType> => {\n\tconst map: Record<string, ConditionFieldType> = {}\n\tfor (const definition of registry.values()) {\n\t\tmap[definition.type] = conditionTypeForDefinition(definition)\n\t}\n\treturn map\n}\n"],"mappings":";;;AAIA,MAAa,8BACZ,eACwB,WAAW,iBAAiB,qBAAqB,WAAW,KAAK;;AAG1F,MAAa,yBACZ,aACwC;CACxC,MAAM,MAA0C,CAAC;CACjD,KAAK,MAAM,cAAc,SAAS,OAAO,GACxC,IAAI,WAAW,QAAQ,2BAA2B,UAAU;CAE7D,OAAO;AACR"}
1
+ {"version":3,"file":"conditionType.js","names":[],"sources":["../../src/conditions/conditionType.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../fields/types'\nimport { type ConditionFieldType, defaultConditionType } from './fieldTypes'\n\n/** The condition input a field type uses in the builder: its declared `conditionType` or the value-kind default. */\nexport const conditionTypeForDefinition = (\n\tdefinition: Pick<AnyFormFieldDefinition, 'value' | 'conditionType'>\n): ConditionFieldType => definition.conditionType ?? defaultConditionType(definition.value)\n\n/**\n * A serializable slug -> condition type map for every conditionable field type, handed to the client\n * builder. Valueless ('none' kind, display-only) types are left out so they never appear as operands;\n * consumers treat a missing entry as \"not conditionable\". An explicit `conditionType` opts back in.\n */\nexport const buildConditionTypeMap = (\n\tregistry: Map<string, AnyFormFieldDefinition>\n): Record<string, ConditionFieldType> => {\n\tconst map: Record<string, ConditionFieldType> = {}\n\tfor (const definition of registry.values()) {\n\t\tif (definition.value === 'none' && definition.conditionType == null) {\n\t\t\tcontinue\n\t\t}\n\t\tmap[definition.type] = conditionTypeForDefinition(definition)\n\t}\n\treturn map\n}\n"],"mappings":";;;AAIA,MAAa,8BACZ,eACwB,WAAW,iBAAiB,qBAAqB,WAAW,KAAK;;;;;;AAO1F,MAAa,yBACZ,aACwC;CACxC,MAAM,MAA0C,CAAC;CACjD,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,WAAW,UAAU,UAAU,WAAW,iBAAiB,MAC9D;EAED,IAAI,WAAW,QAAQ,2BAA2B,UAAU;CAC7D;CACA,OAAO;AACR"}
@@ -29,7 +29,8 @@ const normalizeFormConditions = (fields, conditionTypes) => {
29
29
  const operandTypes = /* @__PURE__ */ new Map();
30
30
  for (const row of fields) {
31
31
  const name = typeof row.name === "string" ? row.name.trim() : "";
32
- if (name.length > 0) operandTypes.set(name, conditionTypes[row.blockType] ?? "text");
32
+ const conditionType = conditionTypes[row.blockType];
33
+ if (name.length > 0 && conditionType) operandTypes.set(name, conditionType);
33
34
  }
34
35
  return fields.map((row) => {
35
36
  const visibleWhen = normalizeOne(row.visibleWhen, operandTypes);
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeConditions.js","names":[],"sources":["../../src/conditions/normalizeConditions.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport { transformWhereQuery } from 'payload/shared'\nimport type { ConditionFieldType } from './fieldTypes'\nimport { conditionOperators } from './fieldTypes'\n\nexport type FieldRow = {\n\tblockType: string\n\tname?: string\n\tvisibleWhen?: unknown\n\tvalidateWhen?: unknown\n\t[key: string]: unknown\n}\n\nconst isValidConstraint = (\n\tconstraint: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): boolean => {\n\tif (constraint == null || typeof constraint !== 'object') {\n\t\treturn false\n\t}\n\tconst field = Object.keys(constraint as object)[0]\n\tif (!field) {\n\t\treturn false\n\t}\n\tconst type = operandTypes.get(field)\n\tif (!type) {\n\t\treturn false\n\t}\n\tconst ops = (constraint as Record<string, unknown>)[field]\n\tif (ops == null || typeof ops !== 'object') {\n\t\treturn false\n\t}\n\tconst operator = Object.keys(ops as object)[0]\n\treturn Boolean(operator && (conditionOperators[type] as readonly string[]).includes(operator))\n}\n\nconst normalizeOne = (\n\traw: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): Where | undefined => {\n\tif (raw == null || typeof raw !== 'object' || Object.keys(raw as object).length === 0) {\n\t\treturn undefined\n\t}\n\tconst canonical = transformWhereQuery(raw as Where)\n\tconst or = Array.isArray(canonical.or)\n\t\t? canonical.or\n\t\t: Array.isArray(canonical.and)\n\t\t\t? [{ and: canonical.and }]\n\t\t\t: []\n\tconst groups = or\n\t\t.map((group) => {\n\t\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as unknown[]) : []\n\t\t\treturn and.filter((constraint) => isValidConstraint(constraint, operandTypes))\n\t\t})\n\t\t.filter((group) => group.length > 0)\n\treturn groups.length > 0 ? ({ or: groups.map((and) => ({ and })) } as Where) : undefined\n}\n\n/**\n * Normalize every field's `visibleWhen`/`validateWhen` against the form's own field list. Canonicalizes\n * to OR-of-ANDs and strips constraints whose operand field is missing or whose operator is invalid for\n * that field's condition type, so stored conditions always match what `evaluateCondition` can run.\n */\nexport const normalizeFormConditions = (\n\tfields: FieldRow[],\n\tconditionTypes: Record<string, ConditionFieldType>\n): FieldRow[] => {\n\tconst operandTypes = new Map<string, ConditionFieldType>()\n\tfor (const row of fields) {\n\t\tconst name = typeof row.name === 'string' ? row.name.trim() : ''\n\t\tif (name.length > 0) {\n\t\t\toperandTypes.set(name, conditionTypes[row.blockType] ?? 'text')\n\t\t}\n\t}\n\treturn fields.map((row) => {\n\t\tconst visibleWhen = normalizeOne(row.visibleWhen, operandTypes)\n\t\tconst validateWhen = normalizeOne(row.validateWhen, operandTypes)\n\t\treturn { ...row, visibleWhen, validateWhen }\n\t})\n}\n"],"mappings":";;;AAaA,MAAM,qBACL,YACA,iBACa;CACb,IAAI,cAAc,QAAQ,OAAO,eAAe,UAC/C,OAAO;CAER,MAAM,QAAQ,OAAO,KAAK,UAAoB,EAAE;CAChD,IAAI,CAAC,OACJ,OAAO;CAER,MAAM,OAAO,aAAa,IAAI,KAAK;CACnC,IAAI,CAAC,MACJ,OAAO;CAER,MAAM,MAAO,WAAuC;CACpD,IAAI,OAAO,QAAQ,OAAO,QAAQ,UACjC,OAAO;CAER,MAAM,WAAW,OAAO,KAAK,GAAa,EAAE;CAC5C,OAAO,QAAQ,YAAa,mBAAmB,MAA4B,SAAS,QAAQ,CAAC;AAC9F;AAEA,MAAM,gBACL,KACA,iBACuB;CACvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAa,EAAE,WAAW,GACnF;CAED,MAAM,YAAY,oBAAoB,GAAY;CAMlD,MAAM,UALK,MAAM,QAAQ,UAAU,EAAE,IAClC,UAAU,KACV,MAAM,QAAQ,UAAU,GAAG,IAC1B,CAAC,EAAE,KAAK,UAAU,IAAI,CAAC,IACvB,CAAC,GAEH,KAAK,UAAU;EAEf,QADY,MAAM,QAAS,MAAgB,GAAG,IAAM,MAAgB,MAAoB,CAAC,GAC9E,QAAQ,eAAe,kBAAkB,YAAY,YAAY,CAAC;CAC9E,CAAC,EACA,QAAQ,UAAU,MAAM,SAAS,CAAC;CACpC,OAAO,OAAO,SAAS,IAAK,EAAE,IAAI,OAAO,KAAK,SAAS,EAAE,IAAI,EAAE,EAAE,IAAc,KAAA;AAChF;;;;;;AAOA,MAAa,2BACZ,QACA,mBACgB;CAChB,MAAM,+BAAe,IAAI,IAAgC;CACzD,KAAK,MAAM,OAAO,QAAQ;EACzB,MAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,IAAI;EAC9D,IAAI,KAAK,SAAS,GACjB,aAAa,IAAI,MAAM,eAAe,IAAI,cAAc,MAAM;CAEhE;CACA,OAAO,OAAO,KAAK,QAAQ;EAC1B,MAAM,cAAc,aAAa,IAAI,aAAa,YAAY;EAC9D,MAAM,eAAe,aAAa,IAAI,cAAc,YAAY;EAChE,OAAO;GAAE,GAAG;GAAK;GAAa;EAAa;CAC5C,CAAC;AACF"}
1
+ {"version":3,"file":"normalizeConditions.js","names":[],"sources":["../../src/conditions/normalizeConditions.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport { transformWhereQuery } from 'payload/shared'\nimport type { ConditionFieldType } from './fieldTypes'\nimport { conditionOperators } from './fieldTypes'\n\nexport type FieldRow = {\n\tblockType: string\n\tname?: string\n\tvisibleWhen?: unknown\n\tvalidateWhen?: unknown\n\t[key: string]: unknown\n}\n\nconst isValidConstraint = (\n\tconstraint: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): boolean => {\n\tif (constraint == null || typeof constraint !== 'object') {\n\t\treturn false\n\t}\n\tconst field = Object.keys(constraint as object)[0]\n\tif (!field) {\n\t\treturn false\n\t}\n\tconst type = operandTypes.get(field)\n\tif (!type) {\n\t\treturn false\n\t}\n\tconst ops = (constraint as Record<string, unknown>)[field]\n\tif (ops == null || typeof ops !== 'object') {\n\t\treturn false\n\t}\n\tconst operator = Object.keys(ops as object)[0]\n\treturn Boolean(operator && (conditionOperators[type] as readonly string[]).includes(operator))\n}\n\nconst normalizeOne = (\n\traw: unknown,\n\toperandTypes: Map<string, ConditionFieldType>\n): Where | undefined => {\n\tif (raw == null || typeof raw !== 'object' || Object.keys(raw as object).length === 0) {\n\t\treturn undefined\n\t}\n\tconst canonical = transformWhereQuery(raw as Where)\n\tconst or = Array.isArray(canonical.or)\n\t\t? canonical.or\n\t\t: Array.isArray(canonical.and)\n\t\t\t? [{ and: canonical.and }]\n\t\t\t: []\n\tconst groups = or\n\t\t.map((group) => {\n\t\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as unknown[]) : []\n\t\t\treturn and.filter((constraint) => isValidConstraint(constraint, operandTypes))\n\t\t})\n\t\t.filter((group) => group.length > 0)\n\treturn groups.length > 0 ? ({ or: groups.map((and) => ({ and })) } as Where) : undefined\n}\n\n/**\n * Normalize every field's `visibleWhen`/`validateWhen` against the form's own field list. Canonicalizes\n * to OR-of-ANDs and strips constraints whose operand field is missing or whose operator is invalid for\n * that field's condition type, so stored conditions always match what `evaluateCondition` can run.\n */\nexport const normalizeFormConditions = (\n\tfields: FieldRow[],\n\tconditionTypes: Record<string, ConditionFieldType>\n): FieldRow[] => {\n\tconst operandTypes = new Map<string, ConditionFieldType>()\n\tfor (const row of fields) {\n\t\tconst name = typeof row.name === 'string' ? row.name.trim() : ''\n\t\t// A type absent from the map is not conditionable (display-only, e.g. message): constraints\n\t\t// referencing it are stripped, matching the client operand list.\n\t\tconst conditionType = conditionTypes[row.blockType]\n\t\tif (name.length > 0 && conditionType) {\n\t\t\toperandTypes.set(name, conditionType)\n\t\t}\n\t}\n\treturn fields.map((row) => {\n\t\tconst visibleWhen = normalizeOne(row.visibleWhen, operandTypes)\n\t\tconst validateWhen = normalizeOne(row.validateWhen, operandTypes)\n\t\treturn { ...row, visibleWhen, validateWhen }\n\t})\n}\n"],"mappings":";;;AAaA,MAAM,qBACL,YACA,iBACa;CACb,IAAI,cAAc,QAAQ,OAAO,eAAe,UAC/C,OAAO;CAER,MAAM,QAAQ,OAAO,KAAK,UAAoB,EAAE;CAChD,IAAI,CAAC,OACJ,OAAO;CAER,MAAM,OAAO,aAAa,IAAI,KAAK;CACnC,IAAI,CAAC,MACJ,OAAO;CAER,MAAM,MAAO,WAAuC;CACpD,IAAI,OAAO,QAAQ,OAAO,QAAQ,UACjC,OAAO;CAER,MAAM,WAAW,OAAO,KAAK,GAAa,EAAE;CAC5C,OAAO,QAAQ,YAAa,mBAAmB,MAA4B,SAAS,QAAQ,CAAC;AAC9F;AAEA,MAAM,gBACL,KACA,iBACuB;CACvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,OAAO,KAAK,GAAa,EAAE,WAAW,GACnF;CAED,MAAM,YAAY,oBAAoB,GAAY;CAMlD,MAAM,UALK,MAAM,QAAQ,UAAU,EAAE,IAClC,UAAU,KACV,MAAM,QAAQ,UAAU,GAAG,IAC1B,CAAC,EAAE,KAAK,UAAU,IAAI,CAAC,IACvB,CAAC,GAEH,KAAK,UAAU;EAEf,QADY,MAAM,QAAS,MAAgB,GAAG,IAAM,MAAgB,MAAoB,CAAC,GAC9E,QAAQ,eAAe,kBAAkB,YAAY,YAAY,CAAC;CAC9E,CAAC,EACA,QAAQ,UAAU,MAAM,SAAS,CAAC;CACpC,OAAO,OAAO,SAAS,IAAK,EAAE,IAAI,OAAO,KAAK,SAAS,EAAE,IAAI,EAAE,EAAE,IAAc,KAAA;AAChF;;;;;;AAOA,MAAa,2BACZ,QACA,mBACgB;CAChB,MAAM,+BAAe,IAAI,IAAgC;CACzD,KAAK,MAAM,OAAO,QAAQ;EACzB,MAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,IAAI;EAG9D,MAAM,gBAAgB,eAAe,IAAI;EACzC,IAAI,KAAK,SAAS,KAAK,eACtB,aAAa,IAAI,MAAM,aAAa;CAEtC;CACA,OAAO,OAAO,KAAK,QAAQ;EAC1B,MAAM,cAAc,aAAa,IAAI,aAAa,YAAY;EAC9D,MAAM,eAAe,aAAa,IAAI,cAAc,YAAY;EAChE,OAAO;GAAE,GAAG;GAAK;GAAa;EAAa;CAC5C,CAAC;AACF"}
@@ -0,0 +1,14 @@
1
+ import { ConsentStatements } from "./resolveConsentStatements.js";
2
+
3
+ //#region src/consent/applyConsentStatements.d.ts
4
+ /**
5
+ * Merge server-resolved statements onto the consent field instances they belong to, keyed by field
6
+ * name, without mutating the input. A field with no resolved statement (no source picked, or a key
7
+ * the host's sources no longer carry) is left untouched and renders without one.
8
+ */
9
+ declare const applyConsentStatements: <T extends {
10
+ name?: string;
11
+ }>(fields: T[], statements: ConsentStatements) => T[];
12
+ //#endregion
13
+ export { applyConsentStatements };
14
+ //# sourceMappingURL=applyConsentStatements.d.ts.map
@@ -0,0 +1,17 @@
1
+ //#region src/consent/applyConsentStatements.ts
2
+ /**
3
+ * Merge server-resolved statements onto the consent field instances they belong to, keyed by field
4
+ * name, without mutating the input. A field with no resolved statement (no source picked, or a key
5
+ * the host's sources no longer carry) is left untouched and renders without one.
6
+ */
7
+ const applyConsentStatements = (fields, statements) => fields.map((instance) => {
8
+ const resolved = instance.name ? statements[instance.name] : void 0;
9
+ return resolved ? {
10
+ ...instance,
11
+ ...resolved
12
+ } : instance;
13
+ });
14
+ //#endregion
15
+ export { applyConsentStatements };
16
+
17
+ //# sourceMappingURL=applyConsentStatements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyConsentStatements.js","names":[],"sources":["../../src/consent/applyConsentStatements.ts"],"sourcesContent":["import type { ConsentStatements } from './resolveConsentStatements'\n\n/**\n * Merge server-resolved statements onto the consent field instances they belong to, keyed by field\n * name, without mutating the input. A field with no resolved statement (no source picked, or a key\n * the host's sources no longer carry) is left untouched and renders without one.\n */\nexport const applyConsentStatements = <T extends { name?: string }>(\n\tfields: T[],\n\tstatements: ConsentStatements\n): T[] =>\n\tfields.map((instance) => {\n\t\tconst resolved = instance.name ? statements[instance.name] : undefined\n\t\treturn resolved ? { ...instance, ...resolved } : instance\n\t})\n"],"mappings":";;;;;;AAOA,MAAa,0BACZ,QACA,eAEA,OAAO,KAAK,aAAa;CACxB,MAAM,WAAW,SAAS,OAAO,WAAW,SAAS,QAAQ,KAAA;CAC7D,OAAO,WAAW;EAAE,GAAG;EAAU,GAAG;CAAS,IAAI;AAClD,CAAC"}
@@ -1,26 +1,37 @@
1
1
  import { FormFieldInstance } from "../submissions/types.js";
2
- import { ConsentSourceRegistry } from "./registry.js";
2
+ import { ConsentSourceEntry, ConsentSourcePage } from "./types.js";
3
3
  import { Payload, PayloadRequest } from "payload";
4
4
 
5
5
  //#region src/consent/captureConsent.d.ts
6
6
  type ConsentProof = {
7
- agreed: boolean;
8
- ref?: string;
7
+ agreed: boolean; /** The source `id` the field referenced; empty when the author never picked one. */
8
+ source: string;
9
+ page?: ConsentSourcePage;
9
10
  versionRef?: string;
10
11
  at: string;
11
12
  };
12
13
  /**
13
- * Build the authoritative consent proof at submit time: re-resolve the source server-side
14
- * (ignores any client ref), capturing a reference url -- never the policy text.
14
+ * The authoritative consent proof, built at submit time from the server's own view of the source
15
+ * (the field carries an id, never a statement, so there is nothing here the client could forge).
16
+ *
17
+ * Proof is id-based on purpose: it records which document was agreed to, so renaming, re-slugging,
18
+ * or re-routing the policy leaves every past proof intact and resolvable. The statement text is
19
+ * never stored, so editing a policy cannot rewrite history either.
20
+ *
21
+ * `versionRef` is recorded only when the page's collection has drafts enabled, and then it is the
22
+ * published version document's own id. Versions without drafts get no `versionRef`, because there
23
+ * is no published/draft distinction to pin to: `_status` only exists under drafts, so a
24
+ * published-version lookup there matches nothing, and recording that as a version reference would
25
+ * be inventing one. Absent means absent, in both cases.
26
+ *
15
27
  * `now` is injected by the caller (`new Date().toISOString()`) for testability.
16
28
  */
17
29
  declare const captureConsent: (args: {
18
30
  field: FormFieldInstance;
19
- agreed: boolean;
20
- registry: ConsentSourceRegistry;
31
+ agreed: boolean; /** The host's resolved sources for this request (see `resolveConsentEntries`). */
32
+ entries: ConsentSourceEntry[];
21
33
  payload: Payload;
22
34
  req?: PayloadRequest;
23
- locale: string;
24
35
  now: string;
25
36
  }) => Promise<ConsentProof>;
26
37
  //#endregion
@@ -1,22 +1,45 @@
1
- import { resolveConsentLinks } from "./resolveConsentLinks.js";
1
+ import { resolvePublishedVersionRef } from "./resolvePublishedVersionRef.js";
2
+ import { hasDraftsEnabled } from "payload/shared";
2
3
  //#region src/consent/captureConsent.ts
3
4
  /**
4
- * Build the authoritative consent proof at submit time: re-resolve the source server-side
5
- * (ignores any client ref), capturing a reference url -- never the policy text.
5
+ * The authoritative consent proof, built at submit time from the server's own view of the source
6
+ * (the field carries an id, never a statement, so there is nothing here the client could forge).
7
+ *
8
+ * Proof is id-based on purpose: it records which document was agreed to, so renaming, re-slugging,
9
+ * or re-routing the policy leaves every past proof intact and resolvable. The statement text is
10
+ * never stored, so editing a policy cannot rewrite history either.
11
+ *
12
+ * `versionRef` is recorded only when the page's collection has drafts enabled, and then it is the
13
+ * published version document's own id. Versions without drafts get no `versionRef`, because there
14
+ * is no published/draft distinction to pin to: `_status` only exists under drafts, so a
15
+ * published-version lookup there matches nothing, and recording that as a version reference would
16
+ * be inventing one. Absent means absent, in both cases.
17
+ *
6
18
  * `now` is injected by the caller (`new Date().toISOString()`) for testability.
7
19
  */
8
20
  const captureConsent = async (args) => {
9
- const resolved = await resolveConsentLinks(args.field, {
10
- registry: args.registry,
21
+ const source = typeof args.field.source === "string" ? args.field.source : "";
22
+ const page = source ? args.entries.find((entry) => entry.id === source)?.page : void 0;
23
+ if (!page) return {
24
+ agreed: args.agreed,
25
+ source,
26
+ at: args.now
27
+ };
28
+ const collection = args.payload.collections[page.relationTo];
29
+ const versionRef = collection && hasDraftsEnabled(collection.config) ? await resolvePublishedVersionRef({
11
30
  payload: args.payload,
12
- req: args.req,
13
- locale: args.locale
14
- });
15
- const ref = resolved.links[0]?.url || void 0;
31
+ collection: page.relationTo,
32
+ id: page.id,
33
+ req: args.req
34
+ }) : null;
16
35
  return {
17
36
  agreed: args.agreed,
18
- ...ref ? { ref } : {},
19
- ...resolved.versionRef ? { versionRef: resolved.versionRef } : {},
37
+ source,
38
+ page: {
39
+ relationTo: page.relationTo,
40
+ id: page.id
41
+ },
42
+ ...versionRef ? { versionRef } : {},
20
43
  at: args.now
21
44
  };
22
45
  };
@@ -1 +1 @@
1
- {"version":3,"file":"captureConsent.js","names":[],"sources":["../../src/consent/captureConsent.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { ConsentSourceRegistry } from './registry'\nimport { resolveConsentLinks } from './resolveConsentLinks'\n\nexport type ConsentProof = {\n\tagreed: boolean\n\tref?: string\n\tversionRef?: string\n\tat: string\n}\n\n/**\n * Build the authoritative consent proof at submit time: re-resolve the source server-side\n * (ignores any client ref), capturing a reference url -- never the policy text.\n * `now` is injected by the caller (`new Date().toISOString()`) for testability.\n */\nexport const captureConsent = async (args: {\n\tfield: FormFieldInstance\n\tagreed: boolean\n\tregistry: ConsentSourceRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n\tlocale: string\n\tnow: string\n}): Promise<ConsentProof> => {\n\tconst resolved = await resolveConsentLinks(args.field, {\n\t\tregistry: args.registry,\n\t\tpayload: args.payload,\n\t\treq: args.req,\n\t\tlocale: args.locale,\n\t})\n\tconst ref = resolved.links[0]?.url || undefined\n\treturn {\n\t\tagreed: args.agreed,\n\t\t...(ref ? { ref } : {}),\n\t\t...(resolved.versionRef ? { versionRef: resolved.versionRef } : {}),\n\t\tat: args.now,\n\t}\n}\n"],"mappings":";;;;;;;AAiBA,MAAa,iBAAiB,OAAO,SAQR;CAC5B,MAAM,WAAW,MAAM,oBAAoB,KAAK,OAAO;EACtD,UAAU,KAAK;EACf,SAAS,KAAK;EACd,KAAK,KAAK;EACV,QAAQ,KAAK;CACd,CAAC;CACD,MAAM,MAAM,SAAS,MAAM,IAAI,OAAO,KAAA;CACtC,OAAO;EACN,QAAQ,KAAK;EACb,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;EACrB,GAAI,SAAS,aAAa,EAAE,YAAY,SAAS,WAAW,IAAI,CAAC;EACjE,IAAI,KAAK;CACV;AACD"}
1
+ {"version":3,"file":"captureConsent.js","names":[],"sources":["../../src/consent/captureConsent.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { hasDraftsEnabled } from 'payload/shared'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { resolvePublishedVersionRef } from './resolvePublishedVersionRef'\nimport type { ConsentSourceEntry, ConsentSourcePage } from './types'\n\nexport type ConsentProof = {\n\tagreed: boolean\n\t/** The source `id` the field referenced; empty when the author never picked one. */\n\tsource: string\n\tpage?: ConsentSourcePage\n\tversionRef?: string\n\tat: string\n}\n\n/**\n * The authoritative consent proof, built at submit time from the server's own view of the source\n * (the field carries an id, never a statement, so there is nothing here the client could forge).\n *\n * Proof is id-based on purpose: it records which document was agreed to, so renaming, re-slugging,\n * or re-routing the policy leaves every past proof intact and resolvable. The statement text is\n * never stored, so editing a policy cannot rewrite history either.\n *\n * `versionRef` is recorded only when the page's collection has drafts enabled, and then it is the\n * published version document's own id. Versions without drafts get no `versionRef`, because there\n * is no published/draft distinction to pin to: `_status` only exists under drafts, so a\n * published-version lookup there matches nothing, and recording that as a version reference would\n * be inventing one. Absent means absent, in both cases.\n *\n * `now` is injected by the caller (`new Date().toISOString()`) for testability.\n */\nexport const captureConsent = async (args: {\n\tfield: FormFieldInstance\n\tagreed: boolean\n\t/** The host's resolved sources for this request (see `resolveConsentEntries`). */\n\tentries: ConsentSourceEntry[]\n\tpayload: Payload\n\treq?: PayloadRequest\n\tnow: string\n}): Promise<ConsentProof> => {\n\tconst source = typeof args.field.source === 'string' ? args.field.source : ''\n\tconst page = source ? args.entries.find((entry) => entry.id === source)?.page : undefined\n\tif (!page) {\n\t\treturn { agreed: args.agreed, source, at: args.now }\n\t}\n\t// `page.relationTo` is a runtime string; cast the key so the lookup type-checks against a host's\n\t// narrowed collection map (a consumer's generated `CollectionSlug`) as well as the plugin's own\n\t// broad one. The value is still guarded below, since an unregistered slug resolves to undefined.\n\tconst collection = args.payload.collections[page.relationTo as keyof Payload['collections']]\n\tconst versionRef =\n\t\tcollection && hasDraftsEnabled(collection.config)\n\t\t\t? await resolvePublishedVersionRef({\n\t\t\t\t\tpayload: args.payload,\n\t\t\t\t\tcollection: page.relationTo,\n\t\t\t\t\tid: page.id,\n\t\t\t\t\treq: args.req,\n\t\t\t\t})\n\t\t\t: null\n\treturn {\n\t\tagreed: args.agreed,\n\t\tsource,\n\t\tpage: { relationTo: page.relationTo, id: page.id },\n\t\t...(versionRef ? { versionRef } : {}),\n\t\tat: args.now,\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+BA,MAAa,iBAAiB,OAAO,SAQR;CAC5B,MAAM,SAAS,OAAO,KAAK,MAAM,WAAW,WAAW,KAAK,MAAM,SAAS;CAC3E,MAAM,OAAO,SAAS,KAAK,QAAQ,MAAM,UAAU,MAAM,OAAO,MAAM,GAAG,OAAO,KAAA;CAChF,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ,KAAK;EAAQ;EAAQ,IAAI,KAAK;CAAI;CAKpD,MAAM,aAAa,KAAK,QAAQ,YAAY,KAAK;CACjD,MAAM,aACL,cAAc,iBAAiB,WAAW,MAAM,IAC7C,MAAM,2BAA2B;EACjC,SAAS,KAAK;EACd,YAAY,KAAK;EACjB,IAAI,KAAK;EACT,KAAK,KAAK;CACX,CAAC,IACA;CACJ,OAAO;EACN,QAAQ,KAAK;EACb;EACA,MAAM;GAAE,YAAY,KAAK;GAAY,IAAI,KAAK;EAAG;EACjD,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACnC,IAAI,KAAK;CACV;AACD"}
@@ -0,0 +1,58 @@
1
+ import { ArrayField, CollectionSlug, RichTextField } from "payload";
2
+
3
+ //#region src/consent/consentSourcesField.d.ts
4
+ type ConsentSourcesFieldOptions = {
5
+ /** Field name, i.e. what the row array is stored under. Defaults to `consentSources`. */name?: string; /** Field label. Defaults to the plugin's translated one. */
6
+ label?: ArrayField['label'];
7
+ /**
8
+ * Collections whose documents can be picked as a source's policy page. Omitted (the default):
9
+ * no page picker at all, so sources are statement-only and their proofs carry no page or
10
+ * version reference. Always stored polymorphically, even for a single slug (see below).
11
+ */
12
+ relationTo?: CollectionSlug | CollectionSlug[];
13
+ /**
14
+ * Whether the visitor-facing `statement` and `name` carry `localized: true`. Default `true`;
15
+ * Payload strips the flag on hosts without `localization`, so it is safe either way. Mirrors
16
+ * the plugin's `localizeContent` option, which this host-called factory cannot see.
17
+ */
18
+ localized?: boolean; /** Overrides the project's default richText editor for the `statement`, as `richText.editor` does plugin-side. */
19
+ editor?: RichTextField['editor'];
20
+ };
21
+ /**
22
+ * The consent sources array, for the host to place on any collection or global they own: a
23
+ * settings global, a tenants collection, a legal-pages parent, wherever the sources belong. The
24
+ * plugin never registers it and never guesses where it lives; a matching `consent.sources` resolver
25
+ * reads it back (see {@link ConsentSourcesResolver}), which is also where multi-tenant scoping goes:
26
+ * place this on the tenant-scoped document and have the resolver return only the sources of the
27
+ * tenant it derives from `req`.
28
+ *
29
+ * A row is a `name` (shown when picking the source on a form, and used as the policy link text
30
+ * beside the statement), the `statement` the visitor agrees to, and optionally the `page` that
31
+ * statement belongs to. The stable reference a consent field stores, and the only part that must
32
+ * outlive edits, is the row's own auto-assigned `id`: it survives a `name` edit or reordering,
33
+ * unlike a hand-authored key would. Authors fill in no version, no URL, and no document id by
34
+ * hand: the version is detected and recorded at submit time, and the page is a picker.
35
+ *
36
+ * `page` is always polymorphic, even when `relationTo` names a single collection, because a
37
+ * monomorphic relationship stores a bare id: the proof needs the collection alongside it to stay
38
+ * resolvable, and a host adding a second collection later would otherwise change the stored shape.
39
+ *
40
+ * ```ts
41
+ * // The host's own collection or global:
42
+ * fields: [consentSourcesField({ relationTo: ['pages', 'legal-notices'] })]
43
+ *
44
+ * // The plugin, reading it back:
45
+ * formBuilder({
46
+ * consent: {
47
+ * sources: async ({ req }) => {
48
+ * const settings = await req.payload.findGlobal({ slug: 'settings', depth: 0, locale: req.locale, req })
49
+ * return (settings.consentSources ?? []).map((row) => ({ ... }))
50
+ * },
51
+ * },
52
+ * })
53
+ * ```
54
+ */
55
+ declare const consentSourcesField: (options?: ConsentSourcesFieldOptions) => ArrayField;
56
+ //#endregion
57
+ export { ConsentSourcesFieldOptions, consentSourcesField };
58
+ //# sourceMappingURL=consentSourcesField.d.ts.map
@@ -0,0 +1,79 @@
1
+ import { localizedIf } from "../fields/localizedIf.js";
2
+ import { keys } from "../translations/keys.js";
3
+ import { labelForKey } from "../translations/server.js";
4
+ //#region src/consent/consentSourcesField.ts
5
+ /**
6
+ * The consent sources array, for the host to place on any collection or global they own: a
7
+ * settings global, a tenants collection, a legal-pages parent, wherever the sources belong. The
8
+ * plugin never registers it and never guesses where it lives; a matching `consent.sources` resolver
9
+ * reads it back (see {@link ConsentSourcesResolver}), which is also where multi-tenant scoping goes:
10
+ * place this on the tenant-scoped document and have the resolver return only the sources of the
11
+ * tenant it derives from `req`.
12
+ *
13
+ * A row is a `name` (shown when picking the source on a form, and used as the policy link text
14
+ * beside the statement), the `statement` the visitor agrees to, and optionally the `page` that
15
+ * statement belongs to. The stable reference a consent field stores, and the only part that must
16
+ * outlive edits, is the row's own auto-assigned `id`: it survives a `name` edit or reordering,
17
+ * unlike a hand-authored key would. Authors fill in no version, no URL, and no document id by
18
+ * hand: the version is detected and recorded at submit time, and the page is a picker.
19
+ *
20
+ * `page` is always polymorphic, even when `relationTo` names a single collection, because a
21
+ * monomorphic relationship stores a bare id: the proof needs the collection alongside it to stay
22
+ * resolvable, and a host adding a second collection later would otherwise change the stored shape.
23
+ *
24
+ * ```ts
25
+ * // The host's own collection or global:
26
+ * fields: [consentSourcesField({ relationTo: ['pages', 'legal-notices'] })]
27
+ *
28
+ * // The plugin, reading it back:
29
+ * formBuilder({
30
+ * consent: {
31
+ * sources: async ({ req }) => {
32
+ * const settings = await req.payload.findGlobal({ slug: 'settings', depth: 0, locale: req.locale, req })
33
+ * return (settings.consentSources ?? []).map((row) => ({ ... }))
34
+ * },
35
+ * },
36
+ * })
37
+ * ```
38
+ */
39
+ const consentSourcesField = (options = {}) => {
40
+ const localize = options.localized !== false;
41
+ const relationTo = options.relationTo && (!Array.isArray(options.relationTo) || options.relationTo.length > 0) ? options.relationTo : void 0;
42
+ const pageField = relationTo ? [{
43
+ name: "page",
44
+ type: "relationship",
45
+ relationTo: Array.isArray(relationTo) ? relationTo : [relationTo],
46
+ label: labelForKey(keys.consentSourcePage),
47
+ admin: { description: labelForKey(keys.consentSourcePageDescription) }
48
+ }] : [];
49
+ return {
50
+ name: options.name ?? "consentSources",
51
+ type: "array",
52
+ label: options.label ?? labelForKey(keys.consentSourcesField),
53
+ labels: {
54
+ singular: labelForKey(keys.consentSourceSingular),
55
+ plural: labelForKey(keys.consentSourcePlural)
56
+ },
57
+ admin: { description: labelForKey(keys.consentSourcesFieldDescription) },
58
+ fields: [
59
+ {
60
+ name: "name",
61
+ type: "text",
62
+ label: labelForKey(keys.consentSourceLabel),
63
+ ...localizedIf(localize)
64
+ },
65
+ {
66
+ name: "statement",
67
+ type: "richText",
68
+ label: labelForKey(keys.consentSourceStatement),
69
+ ...options.editor ? { editor: options.editor } : {},
70
+ ...localizedIf(localize)
71
+ },
72
+ ...pageField
73
+ ]
74
+ };
75
+ };
76
+ //#endregion
77
+ export { consentSourcesField };
78
+
79
+ //# sourceMappingURL=consentSourcesField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consentSourcesField.js","names":[],"sources":["../../src/consent/consentSourcesField.ts"],"sourcesContent":["import type { ArrayField, CollectionSlug, Field, RichTextField } from 'payload'\nimport { localizedIf } from '../fields/localizedIf'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nexport type ConsentSourcesFieldOptions = {\n\t/** Field name, i.e. what the row array is stored under. Defaults to `consentSources`. */\n\tname?: string\n\t/** Field label. Defaults to the plugin's translated one. */\n\tlabel?: ArrayField['label']\n\t/**\n\t * Collections whose documents can be picked as a source's policy page. Omitted (the default):\n\t * no page picker at all, so sources are statement-only and their proofs carry no page or\n\t * version reference. Always stored polymorphically, even for a single slug (see below).\n\t */\n\trelationTo?: CollectionSlug | CollectionSlug[]\n\t/**\n\t * Whether the visitor-facing `statement` and `name` carry `localized: true`. Default `true`;\n\t * Payload strips the flag on hosts without `localization`, so it is safe either way. Mirrors\n\t * the plugin's `localizeContent` option, which this host-called factory cannot see.\n\t */\n\tlocalized?: boolean\n\t/** Overrides the project's default richText editor for the `statement`, as `richText.editor` does plugin-side. */\n\teditor?: RichTextField['editor']\n}\n\n/**\n * The consent sources array, for the host to place on any collection or global they own: a\n * settings global, a tenants collection, a legal-pages parent, wherever the sources belong. The\n * plugin never registers it and never guesses where it lives; a matching `consent.sources` resolver\n * reads it back (see {@link ConsentSourcesResolver}), which is also where multi-tenant scoping goes:\n * place this on the tenant-scoped document and have the resolver return only the sources of the\n * tenant it derives from `req`.\n *\n * A row is a `name` (shown when picking the source on a form, and used as the policy link text\n * beside the statement), the `statement` the visitor agrees to, and optionally the `page` that\n * statement belongs to. The stable reference a consent field stores, and the only part that must\n * outlive edits, is the row's own auto-assigned `id`: it survives a `name` edit or reordering,\n * unlike a hand-authored key would. Authors fill in no version, no URL, and no document id by\n * hand: the version is detected and recorded at submit time, and the page is a picker.\n *\n * `page` is always polymorphic, even when `relationTo` names a single collection, because a\n * monomorphic relationship stores a bare id: the proof needs the collection alongside it to stay\n * resolvable, and a host adding a second collection later would otherwise change the stored shape.\n *\n * ```ts\n * // The host's own collection or global:\n * fields: [consentSourcesField({ relationTo: ['pages', 'legal-notices'] })]\n *\n * // The plugin, reading it back:\n * formBuilder({\n * consent: {\n * sources: async ({ req }) => {\n * const settings = await req.payload.findGlobal({ slug: 'settings', depth: 0, locale: req.locale, req })\n * return (settings.consentSources ?? []).map((row) => ({ ... }))\n * },\n * },\n * })\n * ```\n */\nexport const consentSourcesField = (options: ConsentSourcesFieldOptions = {}): ArrayField => {\n\tconst localize = options.localized !== false\n\t// An empty `relationTo` array is treated as \"no page picker\", not a relationship field pointing\n\t// at nothing (which Payload rejects at boot).\n\tconst relationTo =\n\t\toptions.relationTo && (!Array.isArray(options.relationTo) || options.relationTo.length > 0)\n\t\t\t? options.relationTo\n\t\t\t: undefined\n\tconst pageField: Field[] = relationTo\n\t\t? [\n\t\t\t\t{\n\t\t\t\t\tname: 'page',\n\t\t\t\t\ttype: 'relationship',\n\t\t\t\t\trelationTo: Array.isArray(relationTo) ? relationTo : [relationTo],\n\t\t\t\t\tlabel: labelForKey(keys.consentSourcePage),\n\t\t\t\t\tadmin: { description: labelForKey(keys.consentSourcePageDescription) },\n\t\t\t\t},\n\t\t\t]\n\t\t: []\n\n\treturn {\n\t\tname: options.name ?? 'consentSources',\n\t\ttype: 'array',\n\t\tlabel: options.label ?? labelForKey(keys.consentSourcesField),\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.consentSourceSingular),\n\t\t\tplural: labelForKey(keys.consentSourcePlural),\n\t\t},\n\t\tadmin: { description: labelForKey(keys.consentSourcesFieldDescription) },\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'name',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.consentSourceLabel),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'statement',\n\t\t\t\ttype: 'richText',\n\t\t\t\tlabel: labelForKey(keys.consentSourceStatement),\n\t\t\t\t...(options.editor ? { editor: options.editor } : {}),\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t...pageField,\n\t\t],\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,MAAa,uBAAuB,UAAsC,CAAC,MAAkB;CAC5F,MAAM,WAAW,QAAQ,cAAc;CAGvC,MAAM,aACL,QAAQ,eAAe,CAAC,MAAM,QAAQ,QAAQ,UAAU,KAAK,QAAQ,WAAW,SAAS,KACtF,QAAQ,aACR,KAAA;CACJ,MAAM,YAAqB,aACxB,CACA;EACC,MAAM;EACN,MAAM;EACN,YAAY,MAAM,QAAQ,UAAU,IAAI,aAAa,CAAC,UAAU;EAChE,OAAO,YAAY,KAAK,iBAAiB;EACzC,OAAO,EAAE,aAAa,YAAY,KAAK,4BAA4B,EAAE;CACtE,CACD,IACC,CAAC;CAEJ,OAAO;EACN,MAAM,QAAQ,QAAQ;EACtB,MAAM;EACN,OAAO,QAAQ,SAAS,YAAY,KAAK,mBAAmB;EAC5D,QAAQ;GACP,UAAU,YAAY,KAAK,qBAAqB;GAChD,QAAQ,YAAY,KAAK,mBAAmB;EAC7C;EACA,OAAO,EAAE,aAAa,YAAY,KAAK,8BAA8B,EAAE;EACvE,QAAQ;GACP;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,kBAAkB;IAC1C,GAAG,YAAY,QAAQ;GACxB;GACA;IACC,MAAM;IACN,MAAM;IACN,OAAO,YAAY,KAAK,sBAAsB;IAC9C,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;IACnD,GAAG,YAAY,QAAQ;GACxB;GACA,GAAG;EACJ;CACD;AACD"}
@@ -0,0 +1,24 @@
1
+ import { ConsentSourceEntry, ConsentSourcesResolver } from "./types.js";
2
+ import { Payload, PayloadRequest } from "payload";
3
+
4
+ //#region src/consent/resolveConsentEntries.d.ts
5
+ type ResolveConsentEntriesArgs = {
6
+ payload: Payload;
7
+ req: PayloadRequest; /** The form whose consent fields are being resolved; handed to the resolver for per-form scoping. */
8
+ form: {
9
+ id: number | string;
10
+ title?: string | null;
11
+ }; /** Resolver override for plugin-internal callers; defaults to the one the plugin stashed on the config. */
12
+ sources?: ConsentSourcesResolver;
13
+ };
14
+ /**
15
+ * The host's consent sources for this request, or an empty list when no resolver is configured
16
+ * (in which case the `consent` field type is not registered either, so nothing can reference one).
17
+ * Throws whatever the resolver throws: every caller decides how to fail, and none of them fail open.
18
+ * Cached per request and form id, so one submission resolves the host's sources once no matter how
19
+ * many consent fields the form carries.
20
+ */
21
+ declare const resolveConsentEntries: (args: ResolveConsentEntriesArgs) => Promise<ConsentSourceEntry[]>;
22
+ //#endregion
23
+ export { ResolveConsentEntriesArgs, resolveConsentEntries };
24
+ //# sourceMappingURL=resolveConsentEntries.d.ts.map