@10x-media/form-builder 0.1.0-beta.1 → 0.1.0-beta.11

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 (556) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +6 -4
  3. package/dist/actions/assertNoBlockCollision.js +18 -0
  4. package/dist/actions/assertNoBlockCollision.js.map +1 -0
  5. package/dist/actions/body/converters.d.ts +20 -0
  6. package/dist/actions/body/converters.js +57 -0
  7. package/dist/actions/body/converters.js.map +1 -0
  8. package/dist/actions/body/escapeHtml.d.ts +6 -0
  9. package/dist/actions/body/escapeHtml.js +14 -0
  10. package/dist/actions/body/escapeHtml.js.map +1 -0
  11. package/dist/actions/body/serializeBody.d.ts +55 -0
  12. package/dist/actions/body/serializeBody.js +78 -0
  13. package/dist/actions/body/serializeBody.js.map +1 -0
  14. package/dist/actions/body/serializeSlate.js +31 -0
  15. package/dist/actions/body/serializeSlate.js.map +1 -0
  16. package/dist/actions/body/textOfBody.d.ts +13 -0
  17. package/dist/actions/body/textOfBody.js +35 -0
  18. package/dist/actions/body/textOfBody.js.map +1 -0
  19. package/dist/actions/body/wildcards.d.ts +10 -0
  20. package/dist/actions/body/wildcards.js +33 -0
  21. package/dist/actions/body/wildcards.js.map +1 -0
  22. package/dist/actions/buildActionBlocks.js +12 -9
  23. package/dist/actions/buildActionBlocks.js.map +1 -1
  24. package/dist/actions/builtin/confirmation.js +47 -43
  25. package/dist/actions/builtin/confirmation.js.map +1 -1
  26. package/dist/actions/builtin/emailAction.d.ts +19 -0
  27. package/dist/actions/builtin/emailAction.js +114 -0
  28. package/dist/actions/builtin/emailAction.js.map +1 -0
  29. package/dist/actions/builtin/emailTeam.js +20 -38
  30. package/dist/actions/builtin/emailTeam.js.map +1 -1
  31. package/dist/actions/builtin/index.d.ts +16 -1
  32. package/dist/actions/builtin/index.js +21 -7
  33. package/dist/actions/builtin/index.js.map +1 -1
  34. package/dist/actions/builtin/signedWebhook.js +6 -2
  35. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  36. package/dist/actions/defineAction.d.ts +8 -5
  37. package/dist/actions/defineAction.js.map +1 -1
  38. package/dist/actions/dispatch.js +3 -2
  39. package/dist/actions/dispatch.js.map +1 -1
  40. package/dist/actions/emailRecipients.d.ts +12 -0
  41. package/dist/actions/emailRecipients.js +143 -0
  42. package/dist/actions/emailRecipients.js.map +1 -0
  43. package/dist/actions/fromAddresses.d.ts +21 -0
  44. package/dist/actions/fromAddresses.js +74 -0
  45. package/dist/actions/fromAddresses.js.map +1 -0
  46. package/dist/actions/recipientSources.d.ts +34 -0
  47. package/dist/actions/recipientSources.js +11 -0
  48. package/dist/actions/recipientSources.js.map +1 -0
  49. package/dist/actions/registry.d.ts +2 -1
  50. package/dist/actions/registry.js +2 -6
  51. package/dist/actions/registry.js.map +1 -1
  52. package/dist/actions/runActions.js +10 -1
  53. package/dist/actions/runActions.js.map +1 -1
  54. package/dist/actions/task.js +40 -15
  55. package/dist/actions/task.js.map +1 -1
  56. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  57. package/dist/aggregation/aggregateResponses.js +8 -14
  58. package/dist/aggregation/aggregateResponses.js.map +1 -1
  59. package/dist/aggregation/resolveResultsRequest.d.ts +35 -6
  60. package/dist/aggregation/resolveResultsRequest.js +68 -11
  61. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  62. package/dist/calc/computeCalcFields.js +2 -1
  63. package/dist/calc/computeCalcFields.js.map +1 -1
  64. package/dist/calc/evaluate.js +1 -2
  65. package/dist/calc/evaluate.js.map +1 -1
  66. package/dist/calc/normalizeCalc.js +6 -19
  67. package/dist/calc/normalizeCalc.js.map +1 -1
  68. package/dist/calc/types.d.ts +8 -2
  69. package/dist/calc/types.js +22 -0
  70. package/dist/calc/types.js.map +1 -0
  71. package/dist/client/ByteSizeField.d.ts +29 -0
  72. package/dist/client/ByteSizeField.js +58 -0
  73. package/dist/client/ByteSizeField.js.map +1 -0
  74. package/dist/client/ClosePollButton.d.ts +15 -0
  75. package/dist/client/ClosePollButton.js +80 -0
  76. package/dist/client/ClosePollButton.js.map +1 -0
  77. package/dist/client/CondensedArray.d.ts +15 -0
  78. package/dist/client/CondensedArray.js +97 -0
  79. package/dist/client/CondensedArray.js.map +1 -0
  80. package/dist/client/ConditionBuilder.js +2 -1
  81. package/dist/client/ConditionBuilder.js.map +1 -1
  82. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  83. package/dist/client/ConsentBlockLabel.js +71 -0
  84. package/dist/client/ConsentBlockLabel.js.map +1 -0
  85. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  86. package/dist/client/ConsentSourceRowLabel.js +21 -0
  87. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  88. package/dist/client/EndpointOptionsSelect.d.ts +46 -0
  89. package/dist/client/EndpointOptionsSelect.js +106 -0
  90. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  91. package/dist/client/FieldBlockLabel.d.ts +18 -0
  92. package/dist/client/FieldBlockLabel.js +34 -0
  93. package/dist/client/FieldBlockLabel.js.map +1 -0
  94. package/dist/client/FieldNameSelect.d.ts +37 -0
  95. package/dist/client/FieldNameSelect.js +72 -0
  96. package/dist/client/FieldNameSelect.js.map +1 -0
  97. package/dist/client/FlowBuilder.d.ts +2 -2
  98. package/dist/client/FlowBuilder.js +137 -88
  99. package/dist/client/FlowBuilder.js.map +1 -1
  100. package/dist/client/FormConditionField.js +1 -4
  101. package/dist/client/FormConditionField.js.map +1 -1
  102. package/dist/client/RecipientsSelect.d.ts +35 -0
  103. package/dist/client/RecipientsSelect.js +193 -0
  104. package/dist/client/RecipientsSelect.js.map +1 -0
  105. package/dist/client/RuleDescription.d.ts +21 -0
  106. package/dist/client/RuleDescription.js +19 -0
  107. package/dist/client/RuleDescription.js.map +1 -0
  108. package/dist/client/admin.css +275 -0
  109. package/dist/client/endpointOptions.js +29 -0
  110. package/dist/client/endpointOptions.js.map +1 -0
  111. package/dist/client/flowAuthoring.js +142 -0
  112. package/dist/client/flowAuthoring.js.map +1 -0
  113. package/dist/client/synthesizeClientField.js +21 -10
  114. package/dist/client/synthesizeClientField.js.map +1 -1
  115. package/dist/client/toStaticLabel.js +13 -0
  116. package/dist/client/toStaticLabel.js.map +1 -0
  117. package/dist/collections/buttonFields.d.ts +34 -0
  118. package/dist/collections/buttonFields.js +35 -0
  119. package/dist/collections/buttonFields.js.map +1 -0
  120. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  121. package/dist/collections/cells/FieldCountCell.js +18 -0
  122. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  123. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  124. package/dist/collections/cells/FlowStepsCell.js +17 -0
  125. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  126. package/dist/collections/formSubmissions.js +133 -47
  127. package/dist/collections/formSubmissions.js.map +1 -1
  128. package/dist/collections/forms.js +416 -114
  129. package/dist/collections/forms.js.map +1 -1
  130. package/dist/collections/formsEndpoints.js +101 -0
  131. package/dist/collections/formsEndpoints.js.map +1 -0
  132. package/dist/collections/redirectFields.d.ts +29 -0
  133. package/dist/conditions/conditionType.js +9 -2
  134. package/dist/conditions/conditionType.js.map +1 -1
  135. package/dist/conditions/evaluate.js +1 -1
  136. package/dist/conditions/evaluate.js.map +1 -1
  137. package/dist/conditions/fieldTypes.d.ts +3 -1
  138. package/dist/conditions/fieldTypes.js +4 -1
  139. package/dist/conditions/fieldTypes.js.map +1 -1
  140. package/dist/conditions/normalizeConditions.js +20 -6
  141. package/dist/conditions/normalizeConditions.js.map +1 -1
  142. package/dist/consent/applyConsentStatements.d.ts +14 -0
  143. package/dist/consent/applyConsentStatements.js +17 -0
  144. package/dist/consent/applyConsentStatements.js.map +1 -0
  145. package/dist/consent/captureConsent.d.ts +38 -11
  146. package/dist/consent/captureConsent.js +60 -13
  147. package/dist/consent/captureConsent.js.map +1 -1
  148. package/dist/consent/consentSourcesField.d.ts +66 -0
  149. package/dist/consent/consentSourcesField.js +90 -0
  150. package/dist/consent/consentSourcesField.js.map +1 -0
  151. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  152. package/dist/consent/resolveConsentEntries.js +40 -0
  153. package/dist/consent/resolveConsentEntries.js.map +1 -0
  154. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  155. package/dist/consent/resolveConsentSourcesRequest.js +54 -0
  156. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  157. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  158. package/dist/consent/resolveConsentStatements.js +44 -0
  159. package/dist/consent/resolveConsentStatements.js.map +1 -0
  160. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  161. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  162. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  163. package/dist/consent/types.d.ts +61 -0
  164. package/dist/context/formContext.d.ts +30 -0
  165. package/dist/context/formContext.js +54 -0
  166. package/dist/context/formContext.js.map +1 -0
  167. package/dist/email/departments.d.ts +59 -0
  168. package/dist/email/departments.js +87 -0
  169. package/dist/email/departments.js.map +1 -0
  170. package/dist/email/departmentsField.d.ts +53 -0
  171. package/dist/email/departmentsField.js +73 -0
  172. package/dist/email/departmentsField.js.map +1 -0
  173. package/dist/exports/client.d.ts +13 -1
  174. package/dist/exports/client.js +13 -1
  175. package/dist/exports/i18n.d.ts +5 -2
  176. package/dist/exports/i18n.js +5 -2
  177. package/dist/exports/react.d.ts +26 -8
  178. package/dist/exports/react.js +19 -3
  179. package/dist/exports/rsc.d.ts +9 -1
  180. package/dist/exports/rsc.js +9 -1
  181. package/dist/exports/types.d.ts +19 -10
  182. package/dist/fields/buildFieldBlocks.js +53 -19
  183. package/dist/fields/buildFieldBlocks.js.map +1 -1
  184. package/dist/fields/builtin/calculation.js +122 -2
  185. package/dist/fields/builtin/calculation.js.map +1 -1
  186. package/dist/fields/builtin/checkbox.js +8 -0
  187. package/dist/fields/builtin/checkbox.js.map +1 -1
  188. package/dist/fields/builtin/consent.js +30 -15
  189. package/dist/fields/builtin/consent.js.map +1 -1
  190. package/dist/fields/builtin/country.d.ts +13 -0
  191. package/dist/fields/builtin/country.js +30 -0
  192. package/dist/fields/builtin/country.js.map +1 -0
  193. package/dist/fields/builtin/date.js +31 -0
  194. package/dist/fields/builtin/date.js.map +1 -0
  195. package/dist/fields/builtin/email.js +1 -1
  196. package/dist/fields/builtin/email.js.map +1 -1
  197. package/dist/fields/builtin/file.d.ts +15 -0
  198. package/dist/fields/builtin/file.js +95 -8
  199. package/dist/fields/builtin/file.js.map +1 -1
  200. package/dist/fields/builtin/index.d.ts +26 -0
  201. package/dist/fields/builtin/index.js +30 -6
  202. package/dist/fields/builtin/index.js.map +1 -1
  203. package/dist/fields/builtin/message.js +30 -0
  204. package/dist/fields/builtin/message.js.map +1 -0
  205. package/dist/fields/builtin/repeater.js +49 -0
  206. package/dist/fields/builtin/repeater.js.map +1 -0
  207. package/dist/fields/builtin/select.js +8 -3
  208. package/dist/fields/builtin/select.js.map +1 -1
  209. package/dist/fields/builtin/state.d.ts +13 -0
  210. package/dist/fields/builtin/state.js +30 -0
  211. package/dist/fields/builtin/state.js.map +1 -0
  212. package/dist/fields/data/regions.d.ts +21 -0
  213. package/dist/fields/data/regions.js +1238 -0
  214. package/dist/fields/data/regions.js.map +1 -0
  215. package/dist/fields/fieldKey.d.ts +15 -0
  216. package/dist/fields/fieldKey.js +16 -0
  217. package/dist/fields/fieldKey.js.map +1 -0
  218. package/dist/fields/fieldNamesOfType.js +30 -0
  219. package/dist/fields/fieldNamesOfType.js.map +1 -0
  220. package/dist/fields/instanceOptions.js +20 -0
  221. package/dist/fields/instanceOptions.js.map +1 -0
  222. package/dist/fields/localizedIf.d.ts +13 -0
  223. package/dist/fields/localizedIf.js +12 -0
  224. package/dist/fields/localizedIf.js.map +1 -0
  225. package/dist/fields/registry.d.ts +3 -1
  226. package/dist/fields/registry.js +23 -8
  227. package/dist/fields/registry.js.map +1 -1
  228. package/dist/fields/sharedConfig.js +120 -58
  229. package/dist/fields/sharedConfig.js.map +1 -1
  230. package/dist/fields/types.d.ts +71 -7
  231. package/dist/flow/engine.d.ts +6 -4
  232. package/dist/flow/engine.js +11 -6
  233. package/dist/flow/engine.js.map +1 -1
  234. package/dist/flow/normalizeFlow.js +28 -9
  235. package/dist/flow/normalizeFlow.js.map +1 -1
  236. package/dist/flow/stepLabel.js +15 -0
  237. package/dist/flow/stepLabel.js.map +1 -0
  238. package/dist/flow/types.d.ts +12 -3
  239. package/dist/flow/types.js +11 -0
  240. package/dist/flow/types.js.map +1 -0
  241. package/dist/form/pollState.d.ts +22 -0
  242. package/dist/form/pollState.js +17 -0
  243. package/dist/form/pollState.js.map +1 -0
  244. package/dist/form/toFormDocument.d.ts +102 -0
  245. package/dist/form/toFormDocument.js +87 -0
  246. package/dist/form/toFormDocument.js.map +1 -0
  247. package/dist/form/types.d.ts +65 -0
  248. package/dist/index.d.ts +55 -39
  249. package/dist/index.js +89 -23
  250. package/dist/index.js.map +1 -1
  251. package/dist/options.d.ts +236 -0
  252. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  253. package/dist/plugin/applyRegistryConfig.js +20 -0
  254. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  255. package/dist/plugin/collectionOverrides.d.ts +22 -0
  256. package/dist/plugin/customState.js +26 -0
  257. package/dist/plugin/customState.js.map +1 -0
  258. package/dist/plugin/registerCollections.js +40 -20
  259. package/dist/plugin/registerCollections.js.map +1 -1
  260. package/dist/plugin/uploadsCollection.d.ts +16 -0
  261. package/dist/plugin/uploadsCollection.js +53 -0
  262. package/dist/plugin/uploadsCollection.js.map +1 -0
  263. package/dist/poll/applyPollOptions.js +18 -0
  264. package/dist/poll/applyPollOptions.js.map +1 -0
  265. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  266. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  267. package/dist/poll/closeJob.d.ts +52 -0
  268. package/dist/poll/closeJob.js +119 -0
  269. package/dist/poll/closeJob.js.map +1 -0
  270. package/dist/poll/definePollOptionSource.d.ts +44 -0
  271. package/dist/poll/definePollOptionSource.js +6 -0
  272. package/dist/poll/definePollOptionSource.js.map +1 -0
  273. package/dist/poll/definePollType.d.ts +34 -0
  274. package/dist/poll/definePollType.js +6 -0
  275. package/dist/poll/definePollType.js.map +1 -0
  276. package/dist/poll/effectivePollOptions.d.ts +38 -0
  277. package/dist/poll/effectivePollOptions.js +64 -0
  278. package/dist/poll/effectivePollOptions.js.map +1 -0
  279. package/dist/poll/mostVoted.d.ts +21 -0
  280. package/dist/poll/mostVoted.js +32 -0
  281. package/dist/poll/mostVoted.js.map +1 -0
  282. package/dist/poll/outcomeBeforeChange.js +91 -0
  283. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  284. package/dist/poll/outcomeFields.d.ts +38 -0
  285. package/dist/poll/outcomeFields.js +52 -0
  286. package/dist/poll/outcomeFields.js.map +1 -0
  287. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  288. package/dist/poll/pollTypeRegistry.js +69 -0
  289. package/dist/poll/pollTypeRegistry.js.map +1 -0
  290. package/dist/poll/registry.d.ts +17 -0
  291. package/dist/poll/registry.js +12 -0
  292. package/dist/poll/registry.js.map +1 -0
  293. package/dist/poll/resolvePollCloseRequest.js +105 -0
  294. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  295. package/dist/poll/resolvePollOptions.d.ts +29 -0
  296. package/dist/poll/resolvePollOptions.js +52 -0
  297. package/dist/poll/resolvePollOptions.js.map +1 -0
  298. package/dist/poll/resolvePollOptionsRequest.js +50 -0
  299. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  300. package/dist/poll/resolvePollOutcome.d.ts +34 -0
  301. package/dist/poll/resolvePollOutcome.js +76 -0
  302. package/dist/poll/resolvePollOutcome.js.map +1 -0
  303. package/dist/poll/resultsField.js +21 -0
  304. package/dist/poll/resultsField.js.map +1 -0
  305. package/dist/prefill/valuesFromSearchParams.js +5 -9
  306. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  307. package/dist/presentations/types.d.ts +3 -2
  308. package/dist/react/Form.d.ts +74 -20
  309. package/dist/react/Form.js +346 -180
  310. package/dist/react/Form.js.map +1 -1
  311. package/dist/react/FormContext.d.ts +44 -1
  312. package/dist/react/FormContext.js.map +1 -1
  313. package/dist/react/FormControls.d.ts +55 -0
  314. package/dist/react/FormControls.js +56 -0
  315. package/dist/react/FormControls.js.map +1 -0
  316. package/dist/react/FormFields.d.ts +18 -0
  317. package/dist/react/FormFields.js +114 -0
  318. package/dist/react/FormFields.js.map +1 -0
  319. package/dist/react/FormLayout.d.ts +4 -2
  320. package/dist/react/FormLayout.js +3 -2
  321. package/dist/react/FormLayout.js.map +1 -1
  322. package/dist/react/FormResults.d.ts +6 -3
  323. package/dist/react/FormResults.js +37 -29
  324. package/dist/react/FormResults.js.map +1 -1
  325. package/dist/react/FormSteps.d.ts +17 -0
  326. package/dist/react/FormSteps.js +35 -0
  327. package/dist/react/FormSteps.js.map +1 -0
  328. package/dist/react/Poll.d.ts +17 -6
  329. package/dist/react/Poll.js +79 -21
  330. package/dist/react/Poll.js.map +1 -1
  331. package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -0
  332. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  333. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  334. package/dist/react/captcha/RecaptchaCaptcha.d.ts +41 -0
  335. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  336. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  337. package/dist/react/captcha/TurnstileCaptcha.d.ts +28 -0
  338. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  339. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  340. package/dist/react/captcha/types.d.ts +8 -0
  341. package/dist/react/captcha/useCaptchaScript.js +45 -0
  342. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  343. package/dist/react/captcha/vendors.js +8 -0
  344. package/dist/react/captcha/vendors.js.map +1 -0
  345. package/dist/react/contract.d.ts +2 -3
  346. package/dist/react/contract.js.map +1 -1
  347. package/dist/react/fetchResults.d.ts +1 -1
  348. package/dist/react/fetchResults.js +1 -1
  349. package/dist/react/fetchResults.js.map +1 -1
  350. package/dist/react/presentation/DialogSurface.js +1 -4
  351. package/dist/react/presentation/DialogSurface.js.map +1 -1
  352. package/dist/react/primitives/Checkbox.d.ts +4 -2
  353. package/dist/react/primitives/Checkbox.js +2 -1
  354. package/dist/react/primitives/Checkbox.js.map +1 -1
  355. package/dist/react/primitives/FieldShell.d.ts +2 -4
  356. package/dist/react/primitives/FieldShell.js +12 -19
  357. package/dist/react/primitives/FieldShell.js.map +1 -1
  358. package/dist/react/primitives/Input.d.ts +1 -1
  359. package/dist/react/primitives/Input.js.map +1 -1
  360. package/dist/react/renderers/calculation.js +1 -4
  361. package/dist/react/renderers/calculation.js.map +1 -1
  362. package/dist/react/renderers/checkbox.js +1 -4
  363. package/dist/react/renderers/checkbox.js.map +1 -1
  364. package/dist/react/renderers/consent.js +78 -35
  365. package/dist/react/renderers/consent.js.map +1 -1
  366. package/dist/react/renderers/date.js +33 -0
  367. package/dist/react/renderers/date.js.map +1 -0
  368. package/dist/react/renderers/email.js +1 -4
  369. package/dist/react/renderers/email.js.map +1 -1
  370. package/dist/react/renderers/file.js +29 -14
  371. package/dist/react/renderers/file.js.map +1 -1
  372. package/dist/react/renderers/index.js +9 -0
  373. package/dist/react/renderers/index.js.map +1 -1
  374. package/dist/react/renderers/message.js +41 -0
  375. package/dist/react/renderers/message.js.map +1 -0
  376. package/dist/react/renderers/number.js +1 -4
  377. package/dist/react/renderers/number.js.map +1 -1
  378. package/dist/react/renderers/region.js +43 -0
  379. package/dist/react/renderers/region.js.map +1 -0
  380. package/dist/react/renderers/repeater.js +131 -0
  381. package/dist/react/renderers/repeater.js.map +1 -0
  382. package/dist/react/renderers/select.js +1 -4
  383. package/dist/react/renderers/select.js.map +1 -1
  384. package/dist/react/renderers/text.js +1 -4
  385. package/dist/react/renderers/text.js.map +1 -1
  386. package/dist/react/renderers/textarea.js +1 -4
  387. package/dist/react/renderers/textarea.js.map +1 -1
  388. package/dist/react/state.d.ts +40 -3
  389. package/dist/react/state.js +64 -9
  390. package/dist/react/state.js.map +1 -1
  391. package/dist/react/uploadFile.d.ts +2 -2
  392. package/dist/react/uploadFile.js +1 -1
  393. package/dist/react/uploadFile.js.map +1 -1
  394. package/dist/react/useField.d.ts +0 -1
  395. package/dist/react/useField.js +6 -6
  396. package/dist/react/useField.js.map +1 -1
  397. package/dist/react/useFormState.d.ts +1 -1
  398. package/dist/react/useFormState.js +1 -1
  399. package/dist/react/useFormState.js.map +1 -1
  400. package/dist/react/validateField.js +3 -6
  401. package/dist/react/validateField.js.map +1 -1
  402. package/dist/recall/interpolate.d.ts +4 -3
  403. package/dist/recall/interpolate.js +5 -4
  404. package/dist/recall/interpolate.js.map +1 -1
  405. package/dist/recall/resolver.d.ts +1 -1
  406. package/dist/recall/resolver.js +14 -2
  407. package/dist/recall/resolver.js.map +1 -1
  408. package/dist/spam/captcha.d.ts +4 -3
  409. package/dist/spam/captcha.js +4 -3
  410. package/dist/spam/captcha.js.map +1 -1
  411. package/dist/spam/clientIp.js +9 -0
  412. package/dist/spam/clientIp.js.map +1 -0
  413. package/dist/spam/constants.js +13 -1
  414. package/dist/spam/constants.js.map +1 -1
  415. package/dist/spam/identify.js +3 -6
  416. package/dist/spam/identify.js.map +1 -1
  417. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  418. package/dist/spam/providers/hcaptcha.js +18 -0
  419. package/dist/spam/providers/hcaptcha.js.map +1 -0
  420. package/dist/spam/providers/recaptcha.d.ts +23 -0
  421. package/dist/spam/providers/recaptcha.js +23 -0
  422. package/dist/spam/providers/recaptcha.js.map +1 -0
  423. package/dist/spam/providers/siteverify.js +34 -0
  424. package/dist/spam/providers/siteverify.js.map +1 -0
  425. package/dist/spam/providers/turnstile.d.ts +17 -0
  426. package/dist/spam/providers/turnstile.js +18 -0
  427. package/dist/spam/providers/turnstile.js.map +1 -0
  428. package/dist/spam/resolveSpam.js +2 -1
  429. package/dist/spam/resolveSpam.js.map +1 -1
  430. package/dist/spam/spamGuard.js +23 -10
  431. package/dist/spam/spamGuard.js.map +1 -1
  432. package/dist/spam/types.d.ts +19 -4
  433. package/dist/submissions/SubmissionAnswers.d.ts +2 -4
  434. package/dist/submissions/SubmissionAnswers.js +114 -34
  435. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  436. package/dist/submissions/SubmissionAnswersClient.js +178 -0
  437. package/dist/submissions/SubmissionAnswersClient.js.map +1 -0
  438. package/dist/submissions/coerceBoolean.js +14 -0
  439. package/dist/submissions/coerceBoolean.js.map +1 -0
  440. package/dist/submissions/createSubmission.d.ts +36 -0
  441. package/dist/submissions/createSubmission.js +26 -0
  442. package/dist/submissions/createSubmission.js.map +1 -0
  443. package/dist/submissions/runSubmission.d.ts +10 -0
  444. package/dist/submissions/runSubmission.js +128 -27
  445. package/dist/submissions/runSubmission.js.map +1 -1
  446. package/dist/submissions/types.d.ts +12 -4
  447. package/dist/submissions/validateSubmission.js +62 -11
  448. package/dist/submissions/validateSubmission.js.map +1 -1
  449. package/dist/submissions/verifyContext.js +35 -0
  450. package/dist/submissions/verifyContext.js.map +1 -0
  451. package/dist/submissions/votedCookie.d.ts +13 -0
  452. package/dist/submissions/votedCookie.js +27 -0
  453. package/dist/submissions/votedCookie.js.map +1 -0
  454. package/dist/translations/clientRuntimeKeys.d.ts +17 -0
  455. package/dist/translations/clientRuntimeKeys.js +46 -0
  456. package/dist/translations/clientRuntimeKeys.js.map +1 -0
  457. package/dist/translations/de.d.ts +12 -0
  458. package/dist/translations/de.js +313 -0
  459. package/dist/translations/de.js.map +1 -0
  460. package/dist/translations/en.js +207 -27
  461. package/dist/translations/en.js.map +1 -1
  462. package/dist/translations/index.d.ts +14 -5
  463. package/dist/translations/index.js +14 -3
  464. package/dist/translations/index.js.map +1 -1
  465. package/dist/translations/keys.d.ts +203 -23
  466. package/dist/translations/keys.js +204 -24
  467. package/dist/translations/keys.js.map +1 -1
  468. package/dist/translations/makeTranslate.d.ts +7 -3
  469. package/dist/translations/makeTranslate.js +12 -3
  470. package/dist/translations/makeTranslate.js.map +1 -1
  471. package/dist/translations/server.js +8 -1
  472. package/dist/translations/server.js.map +1 -1
  473. package/dist/uploads/captureFileRef.d.ts +10 -3
  474. package/dist/uploads/captureFileRef.js +18 -9
  475. package/dist/uploads/captureFileRef.js.map +1 -1
  476. package/dist/uploads/formatBytes.d.ts +9 -0
  477. package/dist/uploads/formatBytes.js +25 -0
  478. package/dist/uploads/formatBytes.js.map +1 -0
  479. package/dist/uploads/resolveFileRef.js +3 -2
  480. package/dist/uploads/resolveFileRef.js.map +1 -1
  481. package/dist/uploads/types.d.ts +1 -2
  482. package/dist/validation/buildRuleBlocks.js +18 -17
  483. package/dist/validation/buildRuleBlocks.js.map +1 -1
  484. package/dist/validation/builtin/email.js +2 -1
  485. package/dist/validation/builtin/email.js.map +1 -1
  486. package/dist/validation/builtin/index.d.ts +14 -0
  487. package/dist/validation/builtin/index.js +12 -1
  488. package/dist/validation/builtin/index.js.map +1 -1
  489. package/dist/validation/builtin/matchesField.js +3 -7
  490. package/dist/validation/builtin/matchesField.js.map +1 -1
  491. package/dist/validation/builtin/max.js +1 -0
  492. package/dist/validation/builtin/max.js.map +1 -1
  493. package/dist/validation/builtin/maxDate.js +24 -0
  494. package/dist/validation/builtin/maxDate.js.map +1 -0
  495. package/dist/validation/builtin/maxLength.js +1 -0
  496. package/dist/validation/builtin/maxLength.js.map +1 -1
  497. package/dist/validation/builtin/min.js +1 -0
  498. package/dist/validation/builtin/min.js.map +1 -1
  499. package/dist/validation/builtin/minDate.js +24 -0
  500. package/dist/validation/builtin/minDate.js.map +1 -0
  501. package/dist/validation/builtin/minLength.js +1 -0
  502. package/dist/validation/builtin/minLength.js.map +1 -1
  503. package/dist/validation/builtin/notAlreadySubmitted.js +2 -0
  504. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  505. package/dist/validation/builtin/oneOf.js +1 -0
  506. package/dist/validation/builtin/oneOf.js.map +1 -1
  507. package/dist/validation/builtin/pattern.js +11 -2
  508. package/dist/validation/builtin/pattern.js.map +1 -1
  509. package/dist/validation/builtin/url.js +1 -0
  510. package/dist/validation/builtin/url.js.map +1 -1
  511. package/dist/validation/builtin/validateDateBound.js +18 -0
  512. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  513. package/dist/validation/builtin/validateRegexParams.js +44 -0
  514. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  515. package/dist/validation/emailPattern.js +10 -0
  516. package/dist/validation/emailPattern.js.map +1 -0
  517. package/dist/validation/fieldTargetParam.d.ts +22 -0
  518. package/dist/validation/fieldTargetParam.js +38 -0
  519. package/dist/validation/fieldTargetParam.js.map +1 -0
  520. package/dist/validation/registry.d.ts +2 -1
  521. package/dist/validation/registry.js +2 -9
  522. package/dist/validation/registry.js.map +1 -1
  523. package/dist/validation/runValidation.js +9 -30
  524. package/dist/validation/runValidation.js.map +1 -1
  525. package/dist/validation/types.d.ts +5 -11
  526. package/dist/validation/validateUrl.js +22 -0
  527. package/dist/validation/validateUrl.js.map +1 -0
  528. package/package.json +7 -5
  529. package/styles/form-builder.css +10 -190
  530. package/dist/collections/uploads.d.ts +0 -29
  531. package/dist/collections/uploads.js +0 -46
  532. package/dist/collections/uploads.js.map +0 -1
  533. package/dist/consent/buildConsentSourceConfig.js +0 -49
  534. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  535. package/dist/consent/builtin/index.d.ts +0 -7
  536. package/dist/consent/builtin/index.js +0 -11
  537. package/dist/consent/builtin/index.js.map +0 -1
  538. package/dist/consent/builtin/pageReference.js +0 -75
  539. package/dist/consent/builtin/pageReference.js.map +0 -1
  540. package/dist/consent/builtin/static.js +0 -44
  541. package/dist/consent/builtin/static.js.map +0 -1
  542. package/dist/consent/defineConsentSource.d.ts +0 -35
  543. package/dist/consent/defineConsentSource.js +0 -6
  544. package/dist/consent/defineConsentSource.js.map +0 -1
  545. package/dist/consent/registry.d.ts +0 -16
  546. package/dist/consent/registry.js +0 -16
  547. package/dist/consent/registry.js.map +0 -1
  548. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  549. package/dist/consent/resolveConsentLinks.js +0 -26
  550. package/dist/consent/resolveConsentLinks.js.map +0 -1
  551. package/dist/presentations/registry.d.ts +0 -15
  552. package/dist/presentations/registry.js +0 -15
  553. package/dist/presentations/registry.js.map +0 -1
  554. package/dist/react/toFormDocument.d.ts +0 -24
  555. package/dist/react/toFormDocument.js +0 -22
  556. package/dist/react/toFormDocument.js.map +0 -1
@@ -1,28 +1,55 @@
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
+ /** What a submission snapshots of the agreed wording. `'hash'` and `'text'` combine as `'both'`. */
7
+ type ConsentSnapshotMode = 'hash' | 'text' | 'both' | false;
6
8
  type ConsentProof = {
7
- agreed: boolean;
8
- ref?: string;
9
- versionRef?: string;
9
+ agreed: boolean; /** The source `id` the field referenced; empty when the author never picked one. */
10
+ source: string;
11
+ page?: ConsentSourcePage;
12
+ versionRef?: string; /** The source label at submit time, so an audit names the statement even after a rename or delete. */
13
+ name?: string; /** sha256 of the statement plain text agreed to: tamper-evident and PII-free. */
14
+ statementHash?: string; /** Plain-text snapshot of the statement agreed to, for a human-readable audit. */
15
+ statementText?: string;
10
16
  at: string;
11
17
  };
12
18
  /**
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.
19
+ * The authoritative consent proof, built at submit time from the server's own view of the source
20
+ * (the field carries an id, never a statement, so there is nothing here the client could forge).
21
+ *
22
+ * Proof is id-based on purpose: it records which document was agreed to, so renaming, re-slugging,
23
+ * or re-routing the policy leaves every past proof intact and resolvable. `snapshot` additionally
24
+ * captures the agreed wording (a `statementHash`, the plain `statementText`, or both) plus the source
25
+ * `name`, so an audit shows exactly what was agreed to independent of later edits; `false` keeps the
26
+ * lean id-only proof. The snapshot is captured in every branch, including a refusal and a page-less
27
+ * source.
28
+ *
29
+ * Time-of-check/time-of-use: a visitor reads the wording resolved when the form loaded, but the
30
+ * snapshot here is re-resolved from the source at submit. If the source text changed in that window,
31
+ * the proof records the submit-time wording, not the exact bytes rendered earlier. This is
32
+ * deliberate, the server's live view is authoritative and the window is one visitor session; a form
33
+ * that must freeze the exact wording agreed to should version the source (drafts give a `versionRef`
34
+ * pinning the published version, below).
35
+ *
36
+ * `versionRef` is recorded only when the page's collection has drafts enabled, and then it is the
37
+ * published version document's own id. Versions without drafts get no `versionRef`, because there
38
+ * is no published/draft distinction to pin to: `_status` only exists under drafts, so a
39
+ * published-version lookup there matches nothing, and recording that as a version reference would
40
+ * be inventing one. Absent means absent, in both cases.
41
+ *
15
42
  * `now` is injected by the caller (`new Date().toISOString()`) for testability.
16
43
  */
17
44
  declare const captureConsent: (args: {
18
45
  field: FormFieldInstance;
19
- agreed: boolean;
20
- registry: ConsentSourceRegistry;
46
+ agreed: boolean; /** The host's resolved sources for this request (see `resolveConsentEntries`). */
47
+ entries: ConsentSourceEntry[];
21
48
  payload: Payload;
22
49
  req?: PayloadRequest;
23
- locale: string;
24
- now: string;
50
+ now: string; /** What to snapshot of the agreed wording (plugin option `consent.snapshot`); defaults to `'both'`. */
51
+ snapshot?: ConsentSnapshotMode;
25
52
  }) => Promise<ConsentProof>;
26
53
  //#endregion
27
- export { ConsentProof, captureConsent };
54
+ export { ConsentProof, ConsentSnapshotMode, captureConsent };
28
55
  //# sourceMappingURL=captureConsent.d.ts.map
@@ -1,24 +1,71 @@
1
- import { resolveConsentLinks } from "./resolveConsentLinks.js";
1
+ import { textOfBody } from "../actions/body/textOfBody.js";
2
+ import { resolvePublishedVersionRef } from "./resolvePublishedVersionRef.js";
3
+ import { createHash } from "node:crypto";
4
+ import { hasDraftsEnabled } from "payload/shared";
2
5
  //#region src/consent/captureConsent.ts
3
6
  /**
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.
7
+ * The authoritative consent proof, built at submit time from the server's own view of the source
8
+ * (the field carries an id, never a statement, so there is nothing here the client could forge).
9
+ *
10
+ * Proof is id-based on purpose: it records which document was agreed to, so renaming, re-slugging,
11
+ * or re-routing the policy leaves every past proof intact and resolvable. `snapshot` additionally
12
+ * captures the agreed wording (a `statementHash`, the plain `statementText`, or both) plus the source
13
+ * `name`, so an audit shows exactly what was agreed to independent of later edits; `false` keeps the
14
+ * lean id-only proof. The snapshot is captured in every branch, including a refusal and a page-less
15
+ * source.
16
+ *
17
+ * Time-of-check/time-of-use: a visitor reads the wording resolved when the form loaded, but the
18
+ * snapshot here is re-resolved from the source at submit. If the source text changed in that window,
19
+ * the proof records the submit-time wording, not the exact bytes rendered earlier. This is
20
+ * deliberate, the server's live view is authoritative and the window is one visitor session; a form
21
+ * that must freeze the exact wording agreed to should version the source (drafts give a `versionRef`
22
+ * pinning the published version, below).
23
+ *
24
+ * `versionRef` is recorded only when the page's collection has drafts enabled, and then it is the
25
+ * published version document's own id. Versions without drafts get no `versionRef`, because there
26
+ * is no published/draft distinction to pin to: `_status` only exists under drafts, so a
27
+ * published-version lookup there matches nothing, and recording that as a version reference would
28
+ * be inventing one. Absent means absent, in both cases.
29
+ *
6
30
  * `now` is injected by the caller (`new Date().toISOString()`) for testability.
7
31
  */
8
32
  const captureConsent = async (args) => {
9
- const resolved = await resolveConsentLinks(args.field, {
10
- registry: args.registry,
11
- payload: args.payload,
12
- req: args.req,
13
- locale: args.locale
33
+ const source = typeof args.field.source === "string" ? args.field.source : "";
34
+ const entry = source ? args.entries.find((candidate) => candidate.id === source) : void 0;
35
+ const snapshot = args.snapshot ?? "both";
36
+ const withSnapshot = (proof) => {
37
+ if (!entry || snapshot === false) return proof;
38
+ if (typeof entry.name === "string" && entry.name.trim().length > 0) proof.name = entry.name.trim();
39
+ const text = textOfBody(entry.statement);
40
+ if (text.length > 0) {
41
+ if (snapshot === "hash" || snapshot === "both") proof.statementHash = createHash("sha256").update(text).digest("hex");
42
+ if (snapshot === "text" || snapshot === "both") proof.statementText = text;
43
+ }
44
+ return proof;
45
+ };
46
+ const page = entry?.page;
47
+ if (!page) return withSnapshot({
48
+ agreed: args.agreed,
49
+ source,
50
+ at: args.now
14
51
  });
15
- const ref = resolved.links[0]?.url || void 0;
16
- return {
52
+ const collection = args.payload.collections[page.relationTo];
53
+ const versionRef = collection && hasDraftsEnabled(collection.config) ? await resolvePublishedVersionRef({
54
+ payload: args.payload,
55
+ collection: page.relationTo,
56
+ id: page.id,
57
+ req: args.req
58
+ }) : null;
59
+ return withSnapshot({
17
60
  agreed: args.agreed,
18
- ...ref ? { ref } : {},
19
- ...resolved.versionRef ? { versionRef: resolved.versionRef } : {},
61
+ source,
62
+ page: {
63
+ relationTo: page.relationTo,
64
+ id: page.id
65
+ },
66
+ ...versionRef ? { versionRef } : {},
20
67
  at: args.now
21
- };
68
+ });
22
69
  };
23
70
  //#endregion
24
71
  export { captureConsent };
@@ -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 { createHash } from 'node:crypto'\nimport type { Payload, PayloadRequest } from 'payload'\nimport { hasDraftsEnabled } from 'payload/shared'\nimport { textOfBody } from '../actions/body/textOfBody'\nimport type { FormFieldInstance } from '../submissions/types'\nimport { resolvePublishedVersionRef } from './resolvePublishedVersionRef'\nimport type { ConsentSourceEntry, ConsentSourcePage } from './types'\n\n/** What a submission snapshots of the agreed wording. `'hash'` and `'text'` combine as `'both'`. */\nexport type ConsentSnapshotMode = 'hash' | 'text' | 'both' | false\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\t/** The source label at submit time, so an audit names the statement even after a rename or delete. */\n\tname?: string\n\t/** sha256 of the statement plain text agreed to: tamper-evident and PII-free. */\n\tstatementHash?: string\n\t/** Plain-text snapshot of the statement agreed to, for a human-readable audit. */\n\tstatementText?: 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. `snapshot` additionally\n * captures the agreed wording (a `statementHash`, the plain `statementText`, or both) plus the source\n * `name`, so an audit shows exactly what was agreed to independent of later edits; `false` keeps the\n * lean id-only proof. The snapshot is captured in every branch, including a refusal and a page-less\n * source.\n *\n * Time-of-check/time-of-use: a visitor reads the wording resolved when the form loaded, but the\n * snapshot here is re-resolved from the source at submit. If the source text changed in that window,\n * the proof records the submit-time wording, not the exact bytes rendered earlier. This is\n * deliberate, the server's live view is authoritative and the window is one visitor session; a form\n * that must freeze the exact wording agreed to should version the source (drafts give a `versionRef`\n * pinning the published version, below).\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\t/** What to snapshot of the agreed wording (plugin option `consent.snapshot`); defaults to `'both'`. */\n\tsnapshot?: ConsentSnapshotMode\n}): Promise<ConsentProof> => {\n\tconst source = typeof args.field.source === 'string' ? args.field.source : ''\n\tconst entry = source ? args.entries.find((candidate) => candidate.id === source) : undefined\n\tconst snapshot = args.snapshot ?? 'both'\n\n\t// Capture the agreed wording (and the source label) onto every proof branch. Kept out of the two\n\t// returns so a refusal and a page-less source are snapshotted the same as the happy path.\n\tconst withSnapshot = (proof: ConsentProof): ConsentProof => {\n\t\tif (!entry || snapshot === false) {\n\t\t\treturn proof\n\t\t}\n\t\tif (typeof entry.name === 'string' && entry.name.trim().length > 0) {\n\t\t\tproof.name = entry.name.trim()\n\t\t}\n\t\tconst text = textOfBody(entry.statement)\n\t\tif (text.length > 0) {\n\t\t\tif (snapshot === 'hash' || snapshot === 'both') {\n\t\t\t\tproof.statementHash = createHash('sha256').update(text).digest('hex')\n\t\t\t}\n\t\t\tif (snapshot === 'text' || snapshot === 'both') {\n\t\t\t\tproof.statementText = text\n\t\t\t}\n\t\t}\n\t\treturn proof\n\t}\n\n\tconst page = entry?.page\n\tif (!page) {\n\t\treturn withSnapshot({ 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 withSnapshot({\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,MAAa,iBAAiB,OAAO,SAUR;CAC5B,MAAM,SAAS,OAAO,KAAK,MAAM,WAAW,WAAW,KAAK,MAAM,SAAS;CAC3E,MAAM,QAAQ,SAAS,KAAK,QAAQ,MAAM,cAAc,UAAU,OAAO,MAAM,IAAI,KAAA;CACnF,MAAM,WAAW,KAAK,YAAY;CAIlC,MAAM,gBAAgB,UAAsC;EAC3D,IAAI,CAAC,SAAS,aAAa,OAC1B,OAAO;EAER,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,KAAK,EAAE,SAAS,GAChE,MAAM,OAAO,MAAM,KAAK,KAAK;EAE9B,MAAM,OAAO,WAAW,MAAM,SAAS;EACvC,IAAI,KAAK,SAAS,GAAG;GACpB,IAAI,aAAa,UAAU,aAAa,QACvC,MAAM,gBAAgB,WAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;GAErE,IAAI,aAAa,UAAU,aAAa,QACvC,MAAM,gBAAgB;EAExB;EACA,OAAO;CACR;CAEA,MAAM,OAAO,OAAO;CACpB,IAAI,CAAC,MACJ,OAAO,aAAa;EAAE,QAAQ,KAAK;EAAQ;EAAQ,IAAI,KAAK;CAAI,CAAC;CAKlE,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,aAAa;EACnB,QAAQ,KAAK;EACb;EACA,MAAM;GAAE,YAAY,KAAK;GAAY,IAAI,KAAK;EAAG;EACjD,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACnC,IAAI,KAAK;CACV,CAAC;AACF"}
@@ -0,0 +1,66 @@
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
+ * The resolver receives the whole form document, so derive tenant scoping from a field on it rather
56
+ * than reading the form back: reading the `forms` collection from the resolver re-enters this form's
57
+ * afterRead hook (the plugin guards against the resulting recursion, but the read is still wasted).
58
+ *
59
+ * A resolver reading its settings document must not thread the save's `req` into a `locale: 'all'`
60
+ * read from within a form's save validation: that flips `req.locale` to `all` and corrupts the
61
+ * localized write of the document being saved. It reads already-committed data, so no `req` is needed.
62
+ */
63
+ declare const consentSourcesField: (options?: ConsentSourcesFieldOptions) => ArrayField;
64
+ //#endregion
65
+ export { ConsentSourcesFieldOptions, consentSourcesField };
66
+ //# sourceMappingURL=consentSourcesField.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { keys } from "../translations/keys.js";
2
+ import { labelForKey } from "../translations/server.js";
3
+ import { localizedIf } from "../fields/localizedIf.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
+ * The resolver receives the whole form document, so derive tenant scoping from a field on it rather
40
+ * than reading the form back: reading the `forms` collection from the resolver re-enters this form's
41
+ * afterRead hook (the plugin guards against the resulting recursion, but the read is still wasted).
42
+ *
43
+ * A resolver reading its settings document must not thread the save's `req` into a `locale: 'all'`
44
+ * read from within a form's save validation: that flips `req.locale` to `all` and corrupts the
45
+ * localized write of the document being saved. It reads already-committed data, so no `req` is needed.
46
+ */
47
+ const consentSourcesField = (options = {}) => {
48
+ const localize = options.localized !== false;
49
+ const relationTo = options.relationTo && (!Array.isArray(options.relationTo) || options.relationTo.length > 0) ? options.relationTo : void 0;
50
+ const pageField = relationTo ? [{
51
+ name: "page",
52
+ type: "relationship",
53
+ relationTo: Array.isArray(relationTo) ? relationTo : [relationTo],
54
+ label: labelForKey(keys.consentSourcePage),
55
+ admin: { description: labelForKey(keys.consentSourcePageDescription) }
56
+ }] : [];
57
+ return {
58
+ name: options.name ?? "consentSources",
59
+ type: "array",
60
+ label: options.label ?? labelForKey(keys.consentSourcesField),
61
+ labels: {
62
+ singular: labelForKey(keys.consentSourceSingular),
63
+ plural: labelForKey(keys.consentSourcePlural)
64
+ },
65
+ admin: {
66
+ description: labelForKey(keys.consentSourcesFieldDescription),
67
+ components: { RowLabel: "@10x-media/form-builder/client#ConsentSourceRowLabel" }
68
+ },
69
+ fields: [
70
+ {
71
+ name: "name",
72
+ type: "text",
73
+ label: labelForKey(keys.consentSourceLabel),
74
+ ...localizedIf(localize)
75
+ },
76
+ {
77
+ name: "statement",
78
+ type: "richText",
79
+ label: labelForKey(keys.consentSourceStatement),
80
+ ...options.editor ? { editor: options.editor } : {},
81
+ ...localizedIf(localize)
82
+ },
83
+ ...pageField
84
+ ]
85
+ };
86
+ };
87
+ //#endregion
88
+ export { consentSourcesField };
89
+
90
+ //# 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 *\n * The resolver receives the whole form document, so derive tenant scoping from a field on it rather\n * than reading the form back: reading the `forms` collection from the resolver re-enters this form's\n * afterRead hook (the plugin guards against the resulting recursion, but the read is still wasted).\n *\n * A resolver reading its settings document must not thread the save's `req` into a `locale: 'all'`\n * read from within a form's save validation: that flips `req.locale` to `all` and corrupts the\n * localized write of the document being saved. It reads already-committed data, so no `req` is needed.\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: {\n\t\t\tdescription: labelForKey(keys.consentSourcesFieldDescription),\n\t\t\tcomponents: { RowLabel: '@10x-media/form-builder/client#ConsentSourceRowLabel' },\n\t\t},\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,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;GACN,aAAa,YAAY,KAAK,8BAA8B;GAC5D,YAAY,EAAE,UAAU,uDAAuD;EAChF;EACA,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 whole form being resolved; forwarded to the resolver, which casts for non-standard fields (e.g. tenant). */
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
@@ -0,0 +1,40 @@
1
+ import { customStateOf, stashCustomState } from "../plugin/customState.js";
2
+ //#region src/consent/resolveConsentEntries.ts
3
+ /** Park the host's resolver on the config so root-level helpers can find it via `payload.config`. */
4
+ const stashConsentSources = (custom, sources) => stashCustomState(custom, { consentSources: sources });
5
+ const consentSourcesOf = (payload) => customStateOf(payload).consentSources;
6
+ const CACHE_KEY = "formBuilderConsentSourcesCache";
7
+ const cacheOf = (req) => {
8
+ if (!req.context) return;
9
+ const existing = req.context[CACHE_KEY];
10
+ if (existing instanceof Map) return existing;
11
+ const created = /* @__PURE__ */ new Map();
12
+ req.context[CACHE_KEY] = created;
13
+ return created;
14
+ };
15
+ /**
16
+ * The host's consent sources for this request, or an empty list when no resolver is configured
17
+ * (in which case the `consent` field type is not registered either, so nothing can reference one).
18
+ * Throws whatever the resolver throws: every caller decides how to fail, and none of them fail open.
19
+ * Cached per request and form id, so one submission resolves the host's sources once no matter how
20
+ * many consent fields the form carries.
21
+ */
22
+ const resolveConsentEntries = async (args) => {
23
+ const { payload, req, form } = args;
24
+ const resolver = args.sources ?? consentSourcesOf(payload);
25
+ if (!resolver) return [];
26
+ const cache = cacheOf(req);
27
+ const cacheKey = String(form.id);
28
+ const cached = cache?.get(cacheKey);
29
+ if (cached) return cached;
30
+ const entries = await resolver({
31
+ req,
32
+ form
33
+ });
34
+ cache?.set(cacheKey, entries);
35
+ return entries;
36
+ };
37
+ //#endregion
38
+ export { resolveConsentEntries, stashConsentSources };
39
+
40
+ //# sourceMappingURL=resolveConsentEntries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveConsentEntries.js","names":[],"sources":["../../src/consent/resolveConsentEntries.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\nimport type { ConsentSourceEntry, ConsentSourcesResolver } from './types'\n\ntype ConsentCustomState = { consentSources: ConsentSourcesResolver }\n\n/** Park the host's resolver on the config so root-level helpers can find it via `payload.config`. */\nexport const stashConsentSources = (\n\tcustom: Record<string, unknown> | undefined,\n\tsources: ConsentSourcesResolver\n): Record<string, unknown> =>\n\tstashCustomState<ConsentCustomState>(custom, { consentSources: sources })\n\nexport const consentSourcesOf = (payload: Payload): ConsentSourcesResolver | undefined =>\n\tcustomStateOf<ConsentCustomState>(payload).consentSources\n\nconst CACHE_KEY = 'formBuilderConsentSourcesCache'\n\ntype EntriesCache = Map<string, ConsentSourceEntry[]>\n\nconst cacheOf = (req: PayloadRequest): EntriesCache | undefined => {\n\tif (!req.context) {\n\t\treturn undefined\n\t}\n\tconst existing = req.context[CACHE_KEY]\n\tif (existing instanceof Map) {\n\t\treturn existing as EntriesCache\n\t}\n\tconst created: EntriesCache = new Map()\n\treq.context[CACHE_KEY] = created\n\treturn created\n}\n\nexport type ResolveConsentEntriesArgs = {\n\tpayload: Payload\n\treq: PayloadRequest\n\t/** The whole form being resolved; forwarded to the resolver, which casts for non-standard fields (e.g. tenant). */\n\tform: { id: number | string; title?: string | null }\n\t/** Resolver override for plugin-internal callers; defaults to the one the plugin stashed on the config. */\n\tsources?: ConsentSourcesResolver\n}\n\n/**\n * The host's consent sources for this request, or an empty list when no resolver is configured\n * (in which case the `consent` field type is not registered either, so nothing can reference one).\n * Throws whatever the resolver throws: every caller decides how to fail, and none of them fail open.\n * Cached per request and form id, so one submission resolves the host's sources once no matter how\n * many consent fields the form carries.\n */\nexport const resolveConsentEntries = async (\n\targs: ResolveConsentEntriesArgs\n): Promise<ConsentSourceEntry[]> => {\n\tconst { payload, req, form } = args\n\tconst resolver = args.sources ?? consentSourcesOf(payload)\n\tif (!resolver) {\n\t\treturn []\n\t}\n\tconst cache = cacheOf(req)\n\tconst cacheKey = String(form.id)\n\tconst cached = cache?.get(cacheKey)\n\tif (cached) {\n\t\treturn cached\n\t}\n\tconst entries = await resolver({ req, form })\n\tcache?.set(cacheKey, entries)\n\treturn entries\n}\n"],"mappings":";;;AAOA,MAAa,uBACZ,QACA,YAEA,iBAAqC,QAAQ,EAAE,gBAAgB,QAAQ,CAAC;AAEzE,MAAa,oBAAoB,YAChC,cAAkC,OAAO,EAAE;AAE5C,MAAM,YAAY;AAIlB,MAAM,WAAW,QAAkD;CAClE,IAAI,CAAC,IAAI,SACR;CAED,MAAM,WAAW,IAAI,QAAQ;CAC7B,IAAI,oBAAoB,KACvB,OAAO;CAER,MAAM,0BAAwB,IAAI,IAAI;CACtC,IAAI,QAAQ,aAAa;CACzB,OAAO;AACR;;;;;;;;AAkBA,MAAa,wBAAwB,OACpC,SACmC;CACnC,MAAM,EAAE,SAAS,KAAK,SAAS;CAC/B,MAAM,WAAW,KAAK,WAAW,iBAAiB,OAAO;CACzD,IAAI,CAAC,UACJ,OAAO,CAAC;CAET,MAAM,QAAQ,QAAQ,GAAG;CACzB,MAAM,WAAW,OAAO,KAAK,EAAE;CAC/B,MAAM,SAAS,OAAO,IAAI,QAAQ;CAClC,IAAI,QACH,OAAO;CAER,MAAM,UAAU,MAAM,SAAS;EAAE;EAAK;CAAK,CAAC;CAC5C,OAAO,IAAI,UAAU,OAAO;CAC5B,OAAO;AACR"}
@@ -0,0 +1,37 @@
1
+ import { ConsentSourcesResolver } from "./types.js";
2
+ import { Payload, PayloadRequest } from "payload";
3
+
4
+ //#region src/consent/resolveConsentSourcesRequest.d.ts
5
+ /** One selectable consent source for the consent field's `source` select. */
6
+ type ConsentSourceOption = {
7
+ label: string;
8
+ value: string;
9
+ };
10
+ type ResolveConsentSourcesRequestArgs = {
11
+ payload: Payload;
12
+ formId: number | string | undefined; /** Whether the caller is authenticated (an admin/user). */
13
+ isAuthed: boolean;
14
+ req: PayloadRequest;
15
+ resolver: ConsentSourcesResolver;
16
+ };
17
+ type ResolveConsentSourcesRequestResult = {
18
+ status: number;
19
+ body: {
20
+ options: ConsentSourceOption[];
21
+ } | {
22
+ errors: {
23
+ message: string;
24
+ }[];
25
+ };
26
+ };
27
+ /**
28
+ * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:
29
+ * authenticated callers get the host's sources for this request (a multi-tenant resolver scoping by
30
+ * `req` therefore offers an author only their own tenant's), anonymous callers are always refused.
31
+ * Statuses mirror the poll-options endpoint: 400 missing id, 403 unauthenticated, 404 unknown form,
32
+ * 503 when the resolver throws (fail closed).
33
+ */
34
+ declare const resolveConsentSourcesRequest: (args: ResolveConsentSourcesRequestArgs) => Promise<ResolveConsentSourcesRequestResult>;
35
+ //#endregion
36
+ export { ConsentSourceOption, ResolveConsentSourcesRequestArgs, ResolveConsentSourcesRequestResult, resolveConsentSourcesRequest };
37
+ //# sourceMappingURL=resolveConsentSourcesRequest.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { resolveConsentEntries } from "./resolveConsentEntries.js";
2
+ import { FORMS_SLUG } from "../collections/forms.js";
3
+ //#region src/consent/resolveConsentSourcesRequest.ts
4
+ /**
5
+ * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:
6
+ * authenticated callers get the host's sources for this request (a multi-tenant resolver scoping by
7
+ * `req` therefore offers an author only their own tenant's), anonymous callers are always refused.
8
+ * Statuses mirror the poll-options endpoint: 400 missing id, 403 unauthenticated, 404 unknown form,
9
+ * 503 when the resolver throws (fail closed).
10
+ */
11
+ const resolveConsentSourcesRequest = async (args) => {
12
+ const { payload, formId, isAuthed, req, resolver } = args;
13
+ if (!isAuthed) return {
14
+ status: 403,
15
+ body: { errors: [{ message: "Forbidden" }] }
16
+ };
17
+ if (formId == null) return {
18
+ status: 400,
19
+ body: { errors: [{ message: "Missing form id" }] }
20
+ };
21
+ const form = await payload.findByID({
22
+ collection: FORMS_SLUG,
23
+ id: formId,
24
+ depth: 0,
25
+ req
26
+ }).catch(() => null);
27
+ if (!form) return {
28
+ status: 404,
29
+ body: { errors: [{ message: "Not found" }] }
30
+ };
31
+ try {
32
+ return {
33
+ status: 200,
34
+ body: { options: (await resolveConsentEntries({
35
+ payload,
36
+ req,
37
+ form,
38
+ sources: resolver
39
+ })).map((entry) => ({
40
+ label: entry.name ?? entry.id,
41
+ value: entry.id
42
+ })) }
43
+ };
44
+ } catch {
45
+ return {
46
+ status: 503,
47
+ body: { errors: [{ message: "Consent sources unavailable" }] }
48
+ };
49
+ }
50
+ };
51
+ //#endregion
52
+ export { resolveConsentSourcesRequest };
53
+
54
+ //# sourceMappingURL=resolveConsentSourcesRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveConsentSourcesRequest.js","names":[],"sources":["../../src/consent/resolveConsentSourcesRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { resolveConsentEntries } from './resolveConsentEntries'\nimport type { ConsentSourcesResolver } from './types'\n\n/** One selectable consent source for the consent field's `source` select. */\nexport type ConsentSourceOption = { label: string; value: string }\n\nexport type ResolveConsentSourcesRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: ConsentSourcesResolver\n}\n\nexport type ResolveConsentSourcesRequestResult = {\n\tstatus: number\n\tbody: { options: ConsentSourceOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/consent-sources` request backing the consent `source` select:\n * authenticated callers get the host's sources for this request (a multi-tenant resolver scoping by\n * `req` therefore offers an author only their own tenant's), anonymous callers are always refused.\n * Statuses mirror the poll-options endpoint: 400 missing id, 403 unauthenticated, 404 unknown form,\n * 503 when the resolver throws (fail closed).\n */\nexport const resolveConsentSourcesRequest = async (\n\targs: ResolveConsentSourcesRequestArgs\n): Promise<ResolveConsentSourcesRequestResult> => {\n\tconst { payload, formId, isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\t// Load under the caller's own read access (no overrideAccess): a tenant-scoped `forms.access.read`\n\t// then refuses a cross-tenant form id (null -> 404), so an authed user can never enumerate another\n\t// tenant's consent source labels/ids.\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst entries = await resolveConsentEntries({ payload, req, form, sources: resolver })\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\tbody: {\n\t\t\t\toptions: entries.map((entry) => ({ label: entry.name ?? entry.id, value: entry.id })),\n\t\t\t},\n\t\t}\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Consent sources unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AA6BA,MAAa,+BAA+B,OAC3C,SACiD;CACjD,MAAM,EAAE,SAAS,QAAQ,UAAU,KAAK,aAAa;CACrD,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAK1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG;CAAI,CAAC,EAC9D,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GACN,QAAQ;GACR,MAAM,EACL,UAAS,MAJW,sBAAsB;IAAE;IAAS;IAAK;IAAM,SAAS;GAAS,CAAC,GAIlE,KAAK,WAAW;IAAE,OAAO,MAAM,QAAQ,MAAM;IAAI,OAAO,MAAM;GAAG,EAAE,EACrF;EACD;CACD,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,8BAA8B,CAAC,EAAE;EAAE;CACtF;AACD"}
@@ -0,0 +1,45 @@
1
+ import { ConsentSourcesResolver } from "./types.js";
2
+ import { Payload, PayloadRequest } from "payload";
3
+
4
+ //#region src/consent/resolveConsentStatements.d.ts
5
+ /** What a consent field shows the visitor, resolved from its source at request time. */
6
+ type ConsentStatement = {
7
+ /** Rich text state (or whatever the host's resolver returned), for the request's locale. */statement?: unknown; /** The policy link beside the statement, present only when the source carries both a `url` and a `label`. */
8
+ link?: {
9
+ label: string;
10
+ url: string;
11
+ };
12
+ };
13
+ /** Resolved statements keyed by consent field name, as `toFormDocument`'s `consentStatements` takes them. */
14
+ type ConsentStatements = Record<string, ConsentStatement>;
15
+ type ResolveConsentStatementsArgs = {
16
+ payload: Payload;
17
+ req: PayloadRequest; /** A loaded forms document; its consent fields' `source` ids drive resolution. The whole doc is forwarded to the resolver. */
18
+ form: {
19
+ id: number | string;
20
+ title?: string | null;
21
+ fields?: {
22
+ blockType: string;
23
+ name?: string;
24
+ [key: string]: unknown;
25
+ }[] | null;
26
+ }; /** Resolver override for plugin-internal callers; defaults to the one the plugin stashed on the config. */
27
+ sources?: ConsentSourcesResolver;
28
+ };
29
+ /**
30
+ * Resolve every consent field's statement for this request, ready to hand to
31
+ * `toFormDocument(doc, { consentStatements })`.
32
+ *
33
+ * The statement is a live reference, not a copy: a form stores only its source id, and the
34
+ * sentence the visitor reads is resolved here, in their locale, from whatever the source says
35
+ * right now. So correcting a policy sentence corrects every form referencing it, with nothing to
36
+ * re-save, and a form rendered without this step shows no statement at all rather than a stale one.
37
+ *
38
+ * Call it server-side (a Server Component, a route handler) before rendering `<Form>`. Throws
39
+ * whatever the host's resolver throws, so a page can decide between failing and rendering without
40
+ * consent; a submit resolves the source again regardless, and the proof is built from that.
41
+ */
42
+ declare const resolveConsentStatements: (args: ResolveConsentStatementsArgs) => Promise<ConsentStatements>;
43
+ //#endregion
44
+ export { ConsentStatement, ConsentStatements, ResolveConsentStatementsArgs, resolveConsentStatements };
45
+ //# sourceMappingURL=resolveConsentStatements.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { resolveConsentEntries } from "./resolveConsentEntries.js";
2
+ //#region src/consent/resolveConsentStatements.ts
3
+ /**
4
+ * Resolve every consent field's statement for this request, ready to hand to
5
+ * `toFormDocument(doc, { consentStatements })`.
6
+ *
7
+ * The statement is a live reference, not a copy: a form stores only its source id, and the
8
+ * sentence the visitor reads is resolved here, in their locale, from whatever the source says
9
+ * right now. So correcting a policy sentence corrects every form referencing it, with nothing to
10
+ * re-save, and a form rendered without this step shows no statement at all rather than a stale one.
11
+ *
12
+ * Call it server-side (a Server Component, a route handler) before rendering `<Form>`. Throws
13
+ * whatever the host's resolver throws, so a page can decide between failing and rendering without
14
+ * consent; a submit resolves the source again regardless, and the proof is built from that.
15
+ */
16
+ const resolveConsentStatements = async (args) => {
17
+ const { payload, req, form, sources } = args;
18
+ const consentFields = (form.fields ?? []).filter((field) => field.blockType === "consent" && typeof field.name === "string" && field.name.length > 0);
19
+ if (consentFields.length === 0) return {};
20
+ const entries = await resolveConsentEntries({
21
+ payload,
22
+ req,
23
+ form,
24
+ sources
25
+ });
26
+ const statements = {};
27
+ for (const field of consentFields) {
28
+ const entry = entries.find((candidate) => candidate.id === field.source);
29
+ if (!entry) continue;
30
+ const label = typeof entry.name === "string" && entry.name.trim() !== "" ? entry.name : "";
31
+ statements[field.name] = {
32
+ ...entry.statement != null ? { statement: entry.statement } : {},
33
+ ...label && typeof entry.url === "string" && entry.url.length > 0 ? { link: {
34
+ label,
35
+ url: entry.url
36
+ } } : {}
37
+ };
38
+ }
39
+ return statements;
40
+ };
41
+ //#endregion
42
+ export { resolveConsentStatements };
43
+
44
+ //# sourceMappingURL=resolveConsentStatements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveConsentStatements.js","names":[],"sources":["../../src/consent/resolveConsentStatements.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { resolveConsentEntries } from './resolveConsentEntries'\nimport type { ConsentSourcesResolver } from './types'\n\n/** What a consent field shows the visitor, resolved from its source at request time. */\nexport type ConsentStatement = {\n\t/** Rich text state (or whatever the host's resolver returned), for the request's locale. */\n\tstatement?: unknown\n\t/** The policy link beside the statement, present only when the source carries both a `url` and a `label`. */\n\tlink?: { label: string; url: string }\n}\n\n/** Resolved statements keyed by consent field name, as `toFormDocument`'s `consentStatements` takes them. */\nexport type ConsentStatements = Record<string, ConsentStatement>\n\nexport type ResolveConsentStatementsArgs = {\n\tpayload: Payload\n\treq: PayloadRequest\n\t/** A loaded forms document; its consent fields' `source` ids drive resolution. The whole doc is forwarded to the resolver. */\n\tform: {\n\t\tid: number | string\n\t\ttitle?: string | null\n\t\tfields?: { blockType: string; name?: string; [key: string]: unknown }[] | null\n\t}\n\t/** Resolver override for plugin-internal callers; defaults to the one the plugin stashed on the config. */\n\tsources?: ConsentSourcesResolver\n}\n\n/**\n * Resolve every consent field's statement for this request, ready to hand to\n * `toFormDocument(doc, { consentStatements })`.\n *\n * The statement is a live reference, not a copy: a form stores only its source id, and the\n * sentence the visitor reads is resolved here, in their locale, from whatever the source says\n * right now. So correcting a policy sentence corrects every form referencing it, with nothing to\n * re-save, and a form rendered without this step shows no statement at all rather than a stale one.\n *\n * Call it server-side (a Server Component, a route handler) before rendering `<Form>`. Throws\n * whatever the host's resolver throws, so a page can decide between failing and rendering without\n * consent; a submit resolves the source again regardless, and the proof is built from that.\n */\nexport const resolveConsentStatements = async (\n\targs: ResolveConsentStatementsArgs\n): Promise<ConsentStatements> => {\n\tconst { payload, req, form, sources } = args\n\tconst consentFields = (form.fields ?? []).filter(\n\t\t(field): field is { blockType: string; name: string; source?: unknown } =>\n\t\t\tfield.blockType === 'consent' && typeof field.name === 'string' && field.name.length > 0\n\t)\n\tif (consentFields.length === 0) {\n\t\treturn {}\n\t}\n\tconst entries = await resolveConsentEntries({ payload, req, form, sources })\n\tconst statements: ConsentStatements = {}\n\tfor (const field of consentFields) {\n\t\tconst entry = entries.find((candidate) => candidate.id === field.source)\n\t\tif (!entry) {\n\t\t\tcontinue\n\t\t}\n\t\t// The link needs a name of its own, so it takes the source's `name` and never falls back to\n\t\t// the `id`: the id is a machine identifier, and showing it as the words a visitor clicks is\n\t\t// worse than no link at all. A source with a url and no name simply has no link.\n\t\tconst label = typeof entry.name === 'string' && entry.name.trim() !== '' ? entry.name : ''\n\t\tstatements[field.name] = {\n\t\t\t...(entry.statement != null ? { statement: entry.statement } : {}),\n\t\t\t...(label && typeof entry.url === 'string' && entry.url.length > 0\n\t\t\t\t? { link: { label, url: entry.url } }\n\t\t\t\t: {}),\n\t\t}\n\t}\n\treturn statements\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyCA,MAAa,2BAA2B,OACvC,SACgC;CAChC,MAAM,EAAE,SAAS,KAAK,MAAM,YAAY;CACxC,MAAM,iBAAiB,KAAK,UAAU,CAAC,GAAG,QACxC,UACA,MAAM,cAAc,aAAa,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,CACzF;CACA,IAAI,cAAc,WAAW,GAC5B,OAAO,CAAC;CAET,MAAM,UAAU,MAAM,sBAAsB;EAAE;EAAS;EAAK;EAAM;CAAQ,CAAC;CAC3E,MAAM,aAAgC,CAAC;CACvC,KAAK,MAAM,SAAS,eAAe;EAClC,MAAM,QAAQ,QAAQ,MAAM,cAAc,UAAU,OAAO,MAAM,MAAM;EACvE,IAAI,CAAC,OACJ;EAKD,MAAM,QAAQ,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,KAAK,MAAM,KAAK,MAAM,OAAO;EACxF,WAAW,MAAM,QAAQ;GACxB,GAAI,MAAM,aAAa,OAAO,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;GAChE,GAAI,SAAS,OAAO,MAAM,QAAQ,YAAY,MAAM,IAAI,SAAS,IAC9D,EAAE,MAAM;IAAE;IAAO,KAAK,MAAM;GAAI,EAAE,IAClC,CAAC;EACL;CACD;CACA,OAAO;AACR"}