@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.20

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 (615) hide show
  1. package/CHANGELOG.md +354 -0
  2. package/README.md +7 -5
  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 +20 -0
  27. package/dist/actions/builtin/emailAction.js +120 -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 +146 -0
  42. package/dist/actions/emailRecipients.js.map +1 -0
  43. package/dist/actions/fromAddresses.d.ts +37 -0
  44. package/dist/actions/fromAddresses.js +131 -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 +41 -6
  60. package/dist/aggregation/resolveResultsRequest.js +97 -11
  61. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  62. package/dist/calc/computeCalcFields.d.ts +5 -3
  63. package/dist/calc/computeCalcFields.js +6 -4
  64. package/dist/calc/computeCalcFields.js.map +1 -1
  65. package/dist/calc/evaluate.d.ts +26 -3
  66. package/dist/calc/evaluate.js +34 -13
  67. package/dist/calc/evaluate.js.map +1 -1
  68. package/dist/calc/formatCalc.d.ts +11 -0
  69. package/dist/calc/formatCalc.js +35 -0
  70. package/dist/calc/formatCalc.js.map +1 -0
  71. package/dist/calc/formatCalcValue.d.ts +17 -0
  72. package/dist/calc/formatCalcValue.js +17 -0
  73. package/dist/calc/formatCalcValue.js.map +1 -0
  74. package/dist/calc/normalizeCalc.d.ts +17 -3
  75. package/dist/calc/normalizeCalc.js +42 -33
  76. package/dist/calc/normalizeCalc.js.map +1 -1
  77. package/dist/calc/registry.d.ts +43 -0
  78. package/dist/calc/registry.js +27 -0
  79. package/dist/calc/registry.js.map +1 -0
  80. package/dist/calc/resolveCalcContext.d.ts +36 -0
  81. package/dist/calc/resolveCalcContext.js +87 -0
  82. package/dist/calc/resolveCalcContext.js.map +1 -0
  83. package/dist/calc/types.d.ts +25 -4
  84. package/dist/calc/types.js +23 -0
  85. package/dist/calc/types.js.map +1 -0
  86. package/dist/client/ByteSizeField.d.ts +29 -0
  87. package/dist/client/ByteSizeField.js +58 -0
  88. package/dist/client/ByteSizeField.js.map +1 -0
  89. package/dist/client/CalcExpressionBuilder.d.ts +20 -0
  90. package/dist/client/CalcExpressionBuilder.js +890 -0
  91. package/dist/client/CalcExpressionBuilder.js.map +1 -0
  92. package/dist/client/ClosePollButton.d.ts +15 -0
  93. package/dist/client/ClosePollButton.js +80 -0
  94. package/dist/client/ClosePollButton.js.map +1 -0
  95. package/dist/client/CondensedArray.d.ts +15 -0
  96. package/dist/client/CondensedArray.js +97 -0
  97. package/dist/client/CondensedArray.js.map +1 -0
  98. package/dist/client/ConditionBuilder.js +2 -1
  99. package/dist/client/ConditionBuilder.js.map +1 -1
  100. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  101. package/dist/client/ConsentBlockLabel.js +71 -0
  102. package/dist/client/ConsentBlockLabel.js.map +1 -0
  103. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  104. package/dist/client/ConsentSourceRowLabel.js +21 -0
  105. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  106. package/dist/client/EndpointOptionsSelect.d.ts +55 -0
  107. package/dist/client/EndpointOptionsSelect.js +112 -0
  108. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  109. package/dist/client/FieldBlockLabel.d.ts +18 -0
  110. package/dist/client/FieldBlockLabel.js +34 -0
  111. package/dist/client/FieldBlockLabel.js.map +1 -0
  112. package/dist/client/FieldNameSelect.d.ts +37 -0
  113. package/dist/client/FieldNameSelect.js +72 -0
  114. package/dist/client/FieldNameSelect.js.map +1 -0
  115. package/dist/client/FlowBuilder.d.ts +2 -2
  116. package/dist/client/FlowBuilder.js +137 -88
  117. package/dist/client/FlowBuilder.js.map +1 -1
  118. package/dist/client/FormConditionField.js +1 -4
  119. package/dist/client/FormConditionField.js.map +1 -1
  120. package/dist/client/RecipientsSelect.d.ts +37 -0
  121. package/dist/client/RecipientsSelect.js +197 -0
  122. package/dist/client/RecipientsSelect.js.map +1 -0
  123. package/dist/client/RuleDescription.d.ts +21 -0
  124. package/dist/client/RuleDescription.js +19 -0
  125. package/dist/client/RuleDescription.js.map +1 -0
  126. package/dist/client/admin.css +557 -0
  127. package/dist/client/endpointOptions.d.ts +11 -0
  128. package/dist/client/endpointOptions.js +38 -0
  129. package/dist/client/endpointOptions.js.map +1 -0
  130. package/dist/client/flowAuthoring.js +142 -0
  131. package/dist/client/flowAuthoring.js.map +1 -0
  132. package/dist/client/synthesizeClientField.js +21 -10
  133. package/dist/client/synthesizeClientField.js.map +1 -1
  134. package/dist/client/toStaticLabel.js +13 -0
  135. package/dist/client/toStaticLabel.js.map +1 -0
  136. package/dist/collections/buttonFields.d.ts +34 -0
  137. package/dist/collections/buttonFields.js +35 -0
  138. package/dist/collections/buttonFields.js.map +1 -0
  139. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  140. package/dist/collections/cells/FieldCountCell.js +18 -0
  141. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  142. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  143. package/dist/collections/cells/FlowStepsCell.js +17 -0
  144. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  145. package/dist/collections/formSubmissions.js +112 -27
  146. package/dist/collections/formSubmissions.js.map +1 -1
  147. package/dist/collections/forms.js +449 -112
  148. package/dist/collections/forms.js.map +1 -1
  149. package/dist/collections/formsEndpoints.js +106 -0
  150. package/dist/collections/formsEndpoints.js.map +1 -0
  151. package/dist/collections/redirectFields.d.ts +29 -0
  152. package/dist/collections/settingsFields.d.ts +31 -0
  153. package/dist/collections/settingsFields.js +45 -0
  154. package/dist/collections/settingsFields.js.map +1 -0
  155. package/dist/conditions/conditionType.js +9 -2
  156. package/dist/conditions/conditionType.js.map +1 -1
  157. package/dist/conditions/evaluate.js +1 -1
  158. package/dist/conditions/evaluate.js.map +1 -1
  159. package/dist/conditions/fieldTypes.d.ts +3 -1
  160. package/dist/conditions/fieldTypes.js +4 -1
  161. package/dist/conditions/fieldTypes.js.map +1 -1
  162. package/dist/conditions/normalizeConditions.js +20 -6
  163. package/dist/conditions/normalizeConditions.js.map +1 -1
  164. package/dist/consent/applyConsentStatements.d.ts +14 -0
  165. package/dist/consent/applyConsentStatements.js +17 -0
  166. package/dist/consent/applyConsentStatements.js.map +1 -0
  167. package/dist/consent/captureConsent.d.ts +39 -11
  168. package/dist/consent/captureConsent.js +63 -13
  169. package/dist/consent/captureConsent.js.map +1 -1
  170. package/dist/consent/consentSourcesField.d.ts +66 -0
  171. package/dist/consent/consentSourcesField.js +98 -0
  172. package/dist/consent/consentSourcesField.js.map +1 -0
  173. package/dist/consent/effectiveStatement.js +16 -0
  174. package/dist/consent/effectiveStatement.js.map +1 -0
  175. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  176. package/dist/consent/resolveConsentEntries.js +40 -0
  177. package/dist/consent/resolveConsentEntries.js.map +1 -0
  178. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  179. package/dist/consent/resolveConsentSourcesRequest.js +54 -0
  180. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  181. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  182. package/dist/consent/resolveConsentStatements.js +46 -0
  183. package/dist/consent/resolveConsentStatements.js.map +1 -0
  184. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  185. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  186. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  187. package/dist/consent/types.d.ts +69 -0
  188. package/dist/context/formContext.d.ts +30 -0
  189. package/dist/context/formContext.js +54 -0
  190. package/dist/context/formContext.js.map +1 -0
  191. package/dist/email/departments.d.ts +59 -0
  192. package/dist/email/departments.js +87 -0
  193. package/dist/email/departments.js.map +1 -0
  194. package/dist/email/departmentsField.d.ts +53 -0
  195. package/dist/email/departmentsField.js +73 -0
  196. package/dist/email/departmentsField.js.map +1 -0
  197. package/dist/exports/client.d.ts +14 -1
  198. package/dist/exports/client.js +14 -1
  199. package/dist/exports/i18n.d.ts +5 -2
  200. package/dist/exports/i18n.js +5 -2
  201. package/dist/exports/react.d.ts +34 -10
  202. package/dist/exports/react.js +24 -4
  203. package/dist/exports/rsc.d.ts +10 -1
  204. package/dist/exports/rsc.js +10 -1
  205. package/dist/exports/types.d.ts +20 -12
  206. package/dist/fields/buildFieldBlocks.js +46 -25
  207. package/dist/fields/buildFieldBlocks.js.map +1 -1
  208. package/dist/fields/builtin/calculation.d.ts +17 -0
  209. package/dist/fields/builtin/calculation.js +193 -18
  210. package/dist/fields/builtin/calculation.js.map +1 -1
  211. package/dist/fields/builtin/checkbox.js +23 -0
  212. package/dist/fields/builtin/checkbox.js.map +1 -1
  213. package/dist/fields/builtin/consent.js +49 -14
  214. package/dist/fields/builtin/consent.js.map +1 -1
  215. package/dist/fields/builtin/country.d.ts +13 -0
  216. package/dist/fields/builtin/country.js +30 -0
  217. package/dist/fields/builtin/country.js.map +1 -0
  218. package/dist/fields/builtin/date.js +31 -0
  219. package/dist/fields/builtin/date.js.map +1 -0
  220. package/dist/fields/builtin/email.js +8 -1
  221. package/dist/fields/builtin/email.js.map +1 -1
  222. package/dist/fields/builtin/file.d.ts +15 -0
  223. package/dist/fields/builtin/file.js +95 -8
  224. package/dist/fields/builtin/file.js.map +1 -1
  225. package/dist/fields/builtin/index.d.ts +30 -0
  226. package/dist/fields/builtin/index.js +37 -10
  227. package/dist/fields/builtin/index.js.map +1 -1
  228. package/dist/fields/builtin/message.js +30 -0
  229. package/dist/fields/builtin/message.js.map +1 -0
  230. package/dist/fields/builtin/repeater.js +8 -3
  231. package/dist/fields/builtin/repeater.js.map +1 -1
  232. package/dist/fields/builtin/select.js +28 -4
  233. package/dist/fields/builtin/select.js.map +1 -1
  234. package/dist/fields/builtin/state.d.ts +13 -0
  235. package/dist/fields/builtin/state.js +30 -0
  236. package/dist/fields/builtin/state.js.map +1 -0
  237. package/dist/fields/builtin/text.js +7 -0
  238. package/dist/fields/builtin/text.js.map +1 -1
  239. package/dist/fields/data/regions.d.ts +21 -0
  240. package/dist/fields/data/regions.js +1238 -0
  241. package/dist/fields/data/regions.js.map +1 -0
  242. package/dist/fields/fieldKey.d.ts +15 -0
  243. package/dist/fields/fieldKey.js +16 -0
  244. package/dist/fields/fieldKey.js.map +1 -0
  245. package/dist/fields/fieldNamesOfType.js +30 -0
  246. package/dist/fields/fieldNamesOfType.js.map +1 -0
  247. package/dist/fields/instanceOptions.js +20 -0
  248. package/dist/fields/instanceOptions.js.map +1 -0
  249. package/dist/fields/localizedIf.d.ts +13 -0
  250. package/dist/fields/localizedIf.js +12 -0
  251. package/dist/fields/localizedIf.js.map +1 -0
  252. package/dist/fields/registry.d.ts +3 -1
  253. package/dist/fields/registry.js +23 -8
  254. package/dist/fields/registry.js.map +1 -1
  255. package/dist/fields/sharedConfig.js +125 -59
  256. package/dist/fields/sharedConfig.js.map +1 -1
  257. package/dist/fields/types.d.ts +78 -9
  258. package/dist/flow/engine.d.ts +6 -4
  259. package/dist/flow/engine.js +11 -6
  260. package/dist/flow/engine.js.map +1 -1
  261. package/dist/flow/normalizeFlow.js +28 -9
  262. package/dist/flow/normalizeFlow.js.map +1 -1
  263. package/dist/flow/stepLabel.js +15 -0
  264. package/dist/flow/stepLabel.js.map +1 -0
  265. package/dist/flow/types.d.ts +12 -3
  266. package/dist/flow/types.js +11 -0
  267. package/dist/flow/types.js.map +1 -0
  268. package/dist/form/pollState.d.ts +23 -0
  269. package/dist/form/pollState.js +17 -0
  270. package/dist/form/pollState.js.map +1 -0
  271. package/dist/form/toFormDocument.d.ts +110 -0
  272. package/dist/form/toFormDocument.js +89 -0
  273. package/dist/form/toFormDocument.js.map +1 -0
  274. package/dist/form/types.d.ts +73 -0
  275. package/dist/index.d.ts +67 -61
  276. package/dist/index.js +120 -25
  277. package/dist/index.js.map +1 -1
  278. package/dist/options.d.ts +281 -0
  279. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  280. package/dist/plugin/applyRegistryConfig.js +20 -0
  281. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  282. package/dist/plugin/customState.js +26 -0
  283. package/dist/plugin/customState.js.map +1 -0
  284. package/dist/plugin/registerCollections.js +43 -40
  285. package/dist/plugin/registerCollections.js.map +1 -1
  286. package/dist/plugin/uploadsCollection.d.ts +16 -0
  287. package/dist/plugin/uploadsCollection.js +53 -0
  288. package/dist/plugin/uploadsCollection.js.map +1 -0
  289. package/dist/poll/applyPollOptions.js +18 -0
  290. package/dist/poll/applyPollOptions.js.map +1 -0
  291. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  292. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  293. package/dist/poll/closeJob.d.ts +53 -0
  294. package/dist/poll/closeJob.js +121 -0
  295. package/dist/poll/closeJob.js.map +1 -0
  296. package/dist/poll/definePollOptionSource.d.ts +44 -0
  297. package/dist/poll/definePollOptionSource.js +6 -0
  298. package/dist/poll/definePollOptionSource.js.map +1 -0
  299. package/dist/poll/definePollType.d.ts +34 -0
  300. package/dist/poll/definePollType.js +6 -0
  301. package/dist/poll/definePollType.js.map +1 -0
  302. package/dist/poll/effectivePollOptions.d.ts +38 -0
  303. package/dist/poll/effectivePollOptions.js +64 -0
  304. package/dist/poll/effectivePollOptions.js.map +1 -0
  305. package/dist/poll/mostVoted.d.ts +21 -0
  306. package/dist/poll/mostVoted.js +32 -0
  307. package/dist/poll/mostVoted.js.map +1 -0
  308. package/dist/poll/outcomeBeforeChange.js +91 -0
  309. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  310. package/dist/poll/outcomeFields.d.ts +38 -0
  311. package/dist/poll/outcomeFields.js +52 -0
  312. package/dist/poll/outcomeFields.js.map +1 -0
  313. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  314. package/dist/poll/pollTypeRegistry.js +69 -0
  315. package/dist/poll/pollTypeRegistry.js.map +1 -0
  316. package/dist/poll/registry.d.ts +17 -0
  317. package/dist/poll/registry.js +12 -0
  318. package/dist/poll/registry.js.map +1 -0
  319. package/dist/poll/resolvePollCloseRequest.js +106 -0
  320. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  321. package/dist/poll/resolvePollOptions.d.ts +29 -0
  322. package/dist/poll/resolvePollOptions.js +52 -0
  323. package/dist/poll/resolvePollOptions.js.map +1 -0
  324. package/dist/poll/resolvePollOptionsRequest.js +50 -0
  325. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  326. package/dist/poll/resolvePollOutcome.d.ts +40 -0
  327. package/dist/poll/resolvePollOutcome.js +90 -0
  328. package/dist/poll/resolvePollOutcome.js.map +1 -0
  329. package/dist/poll/resultsField.js +21 -0
  330. package/dist/poll/resultsField.js.map +1 -0
  331. package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
  332. package/dist/poll/votes/aggregateFromVotes.js +54 -0
  333. package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
  334. package/dist/poll/votes/answerValues.js +16 -0
  335. package/dist/poll/votes/answerValues.js.map +1 -0
  336. package/dist/poll/votes/bumpPollVote.js +88 -0
  337. package/dist/poll/votes/bumpPollVote.js.map +1 -0
  338. package/dist/poll/votes/recountPollVotes.d.ts +20 -0
  339. package/dist/poll/votes/recountPollVotes.js +63 -0
  340. package/dist/poll/votes/recountPollVotes.js.map +1 -0
  341. package/dist/poll/votes/voteTallyHook.js +76 -0
  342. package/dist/poll/votes/voteTallyHook.js.map +1 -0
  343. package/dist/poll/votes/votesCollection.d.ts +19 -0
  344. package/dist/poll/votes/votesCollection.js +89 -0
  345. package/dist/poll/votes/votesCollection.js.map +1 -0
  346. package/dist/prefill/valuesFromSearchParams.js +5 -9
  347. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  348. package/dist/presentations/types.d.ts +3 -2
  349. package/dist/react/Form.d.ts +77 -35
  350. package/dist/react/Form.js +368 -222
  351. package/dist/react/Form.js.map +1 -1
  352. package/dist/react/FormContext.d.ts +51 -1
  353. package/dist/react/FormContext.js.map +1 -1
  354. package/dist/react/FormControls.d.ts +55 -0
  355. package/dist/react/FormControls.js +56 -0
  356. package/dist/react/FormControls.js.map +1 -0
  357. package/dist/react/FormFields.d.ts +18 -0
  358. package/dist/react/FormFields.js +114 -0
  359. package/dist/react/FormFields.js.map +1 -0
  360. package/dist/react/FormLayout.d.ts +4 -2
  361. package/dist/react/FormLayout.js +3 -2
  362. package/dist/react/FormLayout.js.map +1 -1
  363. package/dist/react/FormResults.d.ts +8 -3
  364. package/dist/react/FormResults.js +50 -29
  365. package/dist/react/FormResults.js.map +1 -1
  366. package/dist/react/FormSteps.d.ts +17 -0
  367. package/dist/react/FormSteps.js +35 -0
  368. package/dist/react/FormSteps.js.map +1 -0
  369. package/dist/react/Poll.d.ts +40 -6
  370. package/dist/react/Poll.js +137 -33
  371. package/dist/react/Poll.js.map +1 -1
  372. package/dist/react/adapters.d.ts +35 -0
  373. package/dist/react/adapters.js +11 -0
  374. package/dist/react/adapters.js.map +1 -0
  375. package/dist/react/captcha/HcaptchaCaptcha.d.ts +31 -0
  376. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  377. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  378. package/dist/react/captcha/RecaptchaCaptcha.d.ts +44 -0
  379. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  380. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  381. package/dist/react/captcha/TurnstileCaptcha.d.ts +31 -0
  382. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  383. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  384. package/dist/react/captcha/types.d.ts +8 -0
  385. package/dist/react/captcha/useCaptchaScript.js +63 -0
  386. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  387. package/dist/react/captcha/vendors.js +8 -0
  388. package/dist/react/captcha/vendors.js.map +1 -0
  389. package/dist/react/contract.d.ts +2 -3
  390. package/dist/react/contract.js.map +1 -1
  391. package/dist/react/fetchResults.d.ts +1 -1
  392. package/dist/react/fetchResults.js +1 -1
  393. package/dist/react/fetchResults.js.map +1 -1
  394. package/dist/react/presentation/DialogSurface.js +1 -4
  395. package/dist/react/presentation/DialogSurface.js.map +1 -1
  396. package/dist/react/primitives/Checkbox.d.ts +6 -2
  397. package/dist/react/primitives/Checkbox.js +4 -2
  398. package/dist/react/primitives/Checkbox.js.map +1 -1
  399. package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
  400. package/dist/react/primitives/ChoiceGroup.js +33 -0
  401. package/dist/react/primitives/ChoiceGroup.js.map +1 -0
  402. package/dist/react/primitives/FieldShell.d.ts +13 -5
  403. package/dist/react/primitives/FieldShell.js +35 -32
  404. package/dist/react/primitives/FieldShell.js.map +1 -1
  405. package/dist/react/primitives/Input.d.ts +4 -2
  406. package/dist/react/primitives/Input.js +2 -1
  407. package/dist/react/primitives/Input.js.map +1 -1
  408. package/dist/react/renderers/calculation.js +7 -5
  409. package/dist/react/renderers/calculation.js.map +1 -1
  410. package/dist/react/renderers/checkbox.js +3 -5
  411. package/dist/react/renderers/checkbox.js.map +1 -1
  412. package/dist/react/renderers/consent.js +84 -36
  413. package/dist/react/renderers/consent.js.map +1 -1
  414. package/dist/react/renderers/date.js +33 -0
  415. package/dist/react/renderers/date.js.map +1 -0
  416. package/dist/react/renderers/email.js +4 -5
  417. package/dist/react/renderers/email.js.map +1 -1
  418. package/dist/react/renderers/file.js +29 -14
  419. package/dist/react/renderers/file.js.map +1 -1
  420. package/dist/react/renderers/index.js +7 -0
  421. package/dist/react/renderers/index.js.map +1 -1
  422. package/dist/react/renderers/message.js +41 -0
  423. package/dist/react/renderers/message.js.map +1 -0
  424. package/dist/react/renderers/number.js +1 -4
  425. package/dist/react/renderers/number.js.map +1 -1
  426. package/dist/react/renderers/region.js +43 -0
  427. package/dist/react/renderers/region.js.map +1 -0
  428. package/dist/react/renderers/repeater.js +32 -14
  429. package/dist/react/renderers/repeater.js.map +1 -1
  430. package/dist/react/renderers/select.js +21 -6
  431. package/dist/react/renderers/select.js.map +1 -1
  432. package/dist/react/renderers/text.js +4 -5
  433. package/dist/react/renderers/text.js.map +1 -1
  434. package/dist/react/renderers/textarea.js +1 -4
  435. package/dist/react/renderers/textarea.js.map +1 -1
  436. package/dist/react/state.d.ts +40 -3
  437. package/dist/react/state.js +66 -9
  438. package/dist/react/state.js.map +1 -1
  439. package/dist/react/uploadFile.d.ts +2 -2
  440. package/dist/react/uploadFile.js +1 -1
  441. package/dist/react/uploadFile.js.map +1 -1
  442. package/dist/react/useCalcValues.d.ts +10 -0
  443. package/dist/react/useCalcValues.js +13 -0
  444. package/dist/react/useCalcValues.js.map +1 -0
  445. package/dist/react/useField.d.ts +0 -1
  446. package/dist/react/useField.js +6 -6
  447. package/dist/react/useField.js.map +1 -1
  448. package/dist/react/useFormState.d.ts +1 -1
  449. package/dist/react/useFormState.js +1 -1
  450. package/dist/react/useFormState.js.map +1 -1
  451. package/dist/react/validateField.js +3 -6
  452. package/dist/react/validateField.js.map +1 -1
  453. package/dist/recall/interpolate.d.ts +4 -3
  454. package/dist/recall/interpolate.js +5 -4
  455. package/dist/recall/interpolate.js.map +1 -1
  456. package/dist/recall/resolver.d.ts +1 -1
  457. package/dist/recall/resolver.js +15 -3
  458. package/dist/recall/resolver.js.map +1 -1
  459. package/dist/spam/captcha.d.ts +4 -3
  460. package/dist/spam/captcha.js +4 -3
  461. package/dist/spam/captcha.js.map +1 -1
  462. package/dist/spam/clientIp.js +9 -0
  463. package/dist/spam/clientIp.js.map +1 -0
  464. package/dist/spam/constants.js +13 -1
  465. package/dist/spam/constants.js.map +1 -1
  466. package/dist/spam/identify.js +3 -6
  467. package/dist/spam/identify.js.map +1 -1
  468. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  469. package/dist/spam/providers/hcaptcha.js +18 -0
  470. package/dist/spam/providers/hcaptcha.js.map +1 -0
  471. package/dist/spam/providers/recaptcha.d.ts +23 -0
  472. package/dist/spam/providers/recaptcha.js +23 -0
  473. package/dist/spam/providers/recaptcha.js.map +1 -0
  474. package/dist/spam/providers/siteverify.js +34 -0
  475. package/dist/spam/providers/siteverify.js.map +1 -0
  476. package/dist/spam/providers/turnstile.d.ts +17 -0
  477. package/dist/spam/providers/turnstile.js +18 -0
  478. package/dist/spam/providers/turnstile.js.map +1 -0
  479. package/dist/spam/resolveSpam.js +2 -1
  480. package/dist/spam/resolveSpam.js.map +1 -1
  481. package/dist/spam/spamGuard.js +25 -11
  482. package/dist/spam/spamGuard.js.map +1 -1
  483. package/dist/spam/types.d.ts +19 -4
  484. package/dist/submissions/SubmissionAnswers.js +6 -2
  485. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  486. package/dist/submissions/SubmissionAnswersClient.js +100 -64
  487. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  488. package/dist/submissions/coerceBoolean.js +14 -0
  489. package/dist/submissions/coerceBoolean.js.map +1 -0
  490. package/dist/submissions/createSubmission.d.ts +36 -0
  491. package/dist/submissions/createSubmission.js +26 -0
  492. package/dist/submissions/createSubmission.js.map +1 -0
  493. package/dist/submissions/formIdOf.js +13 -0
  494. package/dist/submissions/formIdOf.js.map +1 -0
  495. package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
  496. package/dist/submissions/resolveVotedSubmission.js +51 -0
  497. package/dist/submissions/resolveVotedSubmission.js.map +1 -0
  498. package/dist/submissions/runSubmission.d.ts +10 -0
  499. package/dist/submissions/runSubmission.js +104 -54
  500. package/dist/submissions/runSubmission.js.map +1 -1
  501. package/dist/submissions/types.d.ts +11 -4
  502. package/dist/submissions/validateSubmission.js +99 -13
  503. package/dist/submissions/validateSubmission.js.map +1 -1
  504. package/dist/submissions/verifyContext.js +35 -0
  505. package/dist/submissions/verifyContext.js.map +1 -0
  506. package/dist/submissions/voteChangeEndpoint.js +100 -0
  507. package/dist/submissions/voteChangeEndpoint.js.map +1 -0
  508. package/dist/submissions/votedCookie.d.ts +15 -0
  509. package/dist/submissions/votedCookie.js +74 -0
  510. package/dist/submissions/votedCookie.js.map +1 -0
  511. package/dist/translations/clientRuntimeKeys.d.ts +17 -0
  512. package/dist/translations/clientRuntimeKeys.js +48 -0
  513. package/dist/translations/clientRuntimeKeys.js.map +1 -0
  514. package/dist/translations/de.d.ts +12 -0
  515. package/dist/translations/de.js +379 -0
  516. package/dist/translations/de.js.map +1 -0
  517. package/dist/translations/en.js +249 -28
  518. package/dist/translations/en.js.map +1 -1
  519. package/dist/translations/index.d.ts +14 -5
  520. package/dist/translations/index.js +14 -3
  521. package/dist/translations/index.js.map +1 -1
  522. package/dist/translations/keys.d.ts +244 -23
  523. package/dist/translations/keys.js +245 -24
  524. package/dist/translations/keys.js.map +1 -1
  525. package/dist/translations/makeTranslate.d.ts +7 -3
  526. package/dist/translations/makeTranslate.js +12 -3
  527. package/dist/translations/makeTranslate.js.map +1 -1
  528. package/dist/translations/server.js +8 -1
  529. package/dist/translations/server.js.map +1 -1
  530. package/dist/uploads/captureFileRef.d.ts +10 -3
  531. package/dist/uploads/captureFileRef.js +18 -9
  532. package/dist/uploads/captureFileRef.js.map +1 -1
  533. package/dist/uploads/formatBytes.d.ts +9 -0
  534. package/dist/uploads/formatBytes.js +25 -0
  535. package/dist/uploads/formatBytes.js.map +1 -0
  536. package/dist/uploads/resolveFileRef.js +3 -2
  537. package/dist/uploads/resolveFileRef.js.map +1 -1
  538. package/dist/uploads/types.d.ts +1 -2
  539. package/dist/validation/buildRuleBlocks.js +18 -17
  540. package/dist/validation/buildRuleBlocks.js.map +1 -1
  541. package/dist/validation/builtin/email.js +2 -1
  542. package/dist/validation/builtin/email.js.map +1 -1
  543. package/dist/validation/builtin/index.d.ts +14 -0
  544. package/dist/validation/builtin/index.js +14 -1
  545. package/dist/validation/builtin/index.js.map +1 -1
  546. package/dist/validation/builtin/integer.js +15 -0
  547. package/dist/validation/builtin/integer.js.map +1 -0
  548. package/dist/validation/builtin/matchesField.js +3 -7
  549. package/dist/validation/builtin/matchesField.js.map +1 -1
  550. package/dist/validation/builtin/max.js +1 -0
  551. package/dist/validation/builtin/max.js.map +1 -1
  552. package/dist/validation/builtin/maxDate.js +24 -0
  553. package/dist/validation/builtin/maxDate.js.map +1 -0
  554. package/dist/validation/builtin/maxLength.js +1 -0
  555. package/dist/validation/builtin/maxLength.js.map +1 -1
  556. package/dist/validation/builtin/min.js +1 -0
  557. package/dist/validation/builtin/min.js.map +1 -1
  558. package/dist/validation/builtin/minDate.js +24 -0
  559. package/dist/validation/builtin/minDate.js.map +1 -0
  560. package/dist/validation/builtin/minLength.js +1 -0
  561. package/dist/validation/builtin/minLength.js.map +1 -1
  562. package/dist/validation/builtin/notAlreadySubmitted.js +5 -1
  563. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  564. package/dist/validation/builtin/oneOf.js +1 -0
  565. package/dist/validation/builtin/oneOf.js.map +1 -1
  566. package/dist/validation/builtin/pattern.js +11 -2
  567. package/dist/validation/builtin/pattern.js.map +1 -1
  568. package/dist/validation/builtin/url.js +1 -0
  569. package/dist/validation/builtin/url.js.map +1 -1
  570. package/dist/validation/builtin/validateDateBound.js +18 -0
  571. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  572. package/dist/validation/builtin/validateRegexParams.js +44 -0
  573. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  574. package/dist/validation/emailPattern.js +10 -0
  575. package/dist/validation/emailPattern.js.map +1 -0
  576. package/dist/validation/fieldTargetParam.d.ts +22 -0
  577. package/dist/validation/fieldTargetParam.js +38 -0
  578. package/dist/validation/fieldTargetParam.js.map +1 -0
  579. package/dist/validation/registry.d.ts +2 -1
  580. package/dist/validation/registry.js +2 -9
  581. package/dist/validation/registry.js.map +1 -1
  582. package/dist/validation/runValidation.js +8 -29
  583. package/dist/validation/runValidation.js.map +1 -1
  584. package/dist/validation/types.d.ts +5 -11
  585. package/dist/validation/validateUrl.js +22 -0
  586. package/dist/validation/validateUrl.js.map +1 -0
  587. package/package.json +14 -6
  588. package/styles/form-builder.css +94 -190
  589. package/dist/collections/uploads.d.ts +0 -30
  590. package/dist/collections/uploads.js +0 -55
  591. package/dist/collections/uploads.js.map +0 -1
  592. package/dist/consent/buildConsentSourceConfig.js +0 -49
  593. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  594. package/dist/consent/builtin/index.d.ts +0 -7
  595. package/dist/consent/builtin/index.js +0 -11
  596. package/dist/consent/builtin/index.js.map +0 -1
  597. package/dist/consent/builtin/pageReference.js +0 -75
  598. package/dist/consent/builtin/pageReference.js.map +0 -1
  599. package/dist/consent/builtin/static.js +0 -44
  600. package/dist/consent/builtin/static.js.map +0 -1
  601. package/dist/consent/defineConsentSource.d.ts +0 -35
  602. package/dist/consent/defineConsentSource.js +0 -6
  603. package/dist/consent/defineConsentSource.js.map +0 -1
  604. package/dist/consent/registry.d.ts +0 -16
  605. package/dist/consent/registry.js +0 -16
  606. package/dist/consent/registry.js.map +0 -1
  607. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  608. package/dist/consent/resolveConsentLinks.js +0 -26
  609. package/dist/consent/resolveConsentLinks.js.map +0 -1
  610. package/dist/presentations/registry.d.ts +0 -15
  611. package/dist/presentations/registry.js +0 -15
  612. package/dist/presentations/registry.js.map +0 -1
  613. package/dist/react/toFormDocument.d.ts +0 -24
  614. package/dist/react/toFormDocument.js +0 -22
  615. package/dist/react/toFormDocument.js.map +0 -1
@@ -3,13 +3,50 @@ type FieldErrors = Record<string, string[]>;
3
3
  type FormState = {
4
4
  values: Record<string, unknown>;
5
5
  errors: FieldErrors;
6
- warnings: FieldErrors;
7
6
  touched: Record<string, boolean>;
8
7
  submitting: boolean;
9
8
  submitted: boolean;
10
- submitAttempted: boolean;
9
+ /**
10
+ * The step ids whose validation the user has attempted (a blocked advance, or a submit). A field
11
+ * reveals its error when it is touched or its own step is in this set, never via a single global flag,
12
+ * so a submit attempt cannot pre-reveal errors on a step the visitor has not reached.
13
+ */
14
+ attemptedSteps: Set<string>;
11
15
  submitError?: string;
12
16
  };
17
+ type FormAction = {
18
+ type: 'SET_VALUE';
19
+ name: string;
20
+ value: unknown;
21
+ } | {
22
+ type: 'TOUCH';
23
+ name: string;
24
+ } | {
25
+ type: 'SET_FIELD_ISSUES';
26
+ name: string;
27
+ errors: string[];
28
+ } | {
29
+ type: 'SET_ALL_ISSUES';
30
+ errors: FieldErrors;
31
+ steps: string[];
32
+ } | {
33
+ type: 'MARK_STEP_ATTEMPTED';
34
+ stepId: string;
35
+ } | {
36
+ type: 'REMOVE_REPEATER_ROW';
37
+ name: string;
38
+ index: number;
39
+ } | {
40
+ type: 'SUBMIT_START';
41
+ } | {
42
+ type: 'SUBMIT_SUCCESS';
43
+ } | {
44
+ type: 'SUBMIT_ERROR';
45
+ message: string;
46
+ } | {
47
+ type: 'RESET';
48
+ values: Record<string, unknown>;
49
+ };
13
50
  //#endregion
14
- export { FormState };
51
+ export { FieldErrors, FormAction, FormState };
15
52
  //# sourceMappingURL=state.d.ts.map
@@ -1,13 +1,65 @@
1
+ import { isNamedField } from "../fields/fieldKey.js";
2
+ import { consentDisplayOf } from "../consent/effectiveStatement.js";
1
3
  //#region src/react/state.ts
4
+ /**
5
+ * The step a field's error reveal is keyed to when the form has no flow, or the field belongs to no
6
+ * step. A single-step form has exactly this one step, so its reveal collapses to the old global one.
7
+ */
8
+ const DEFAULT_STEP_ID = "__form__";
9
+ /**
10
+ * Per-field defaults for the reducer's initial state. Nameless (bare) blocks carry no value and
11
+ * are skipped. A repeater with a positive `minRows` starts pre-seeded with that many empty rows,
12
+ * matching the schema's own floor. Computed once, ahead of the reducer, so seeding is never an
13
+ * action: it can't touch a field, trigger validation, or (via `Form`'s dispatch wrapper) be
14
+ * mistaken for the user's first edit and fire `form.started`.
15
+ */
16
+ const seedFieldValues = (fields) => Object.fromEntries(fields.filter(isNamedField).map((field) => {
17
+ if (field.blockType === "repeater") {
18
+ const minRows = typeof field.minRows === "number" ? field.minRows : 0;
19
+ if (minRows > 0) return [field.name, Array.from({ length: minRows }, () => ({}))];
20
+ }
21
+ if (field.blockType === "consent" && consentDisplayOf(field) === "notice") return [field.name, true];
22
+ return [field.name, void 0];
23
+ }));
2
24
  const initialFormState = (values) => ({
3
25
  values,
4
26
  errors: {},
5
- warnings: {},
6
27
  touched: {},
7
28
  submitting: false,
8
29
  submitted: false,
9
- submitAttempted: false
30
+ attemptedSteps: /* @__PURE__ */ new Set()
10
31
  });
32
+ /**
33
+ * Re-key composite entries (`name[i].sub`) after repeater row `removed` is deleted: drop the removed
34
+ * index and shift every higher index down by one, so surviving rows keep their own errors/touched
35
+ * flags instead of inheriting a deleted or shifted neighbour's. Matches on the `name[<int>]` prefix,
36
+ * so it is agnostic to the sub-key shape after `]` and needs no sub-field list. Returns the same
37
+ * reference when nothing changed, so an unrelated dispatch does not churn state identity.
38
+ */
39
+ const reindexRepeaterKeys = (map, name, removed) => {
40
+ const prefix = `${name}[`;
41
+ let changed = false;
42
+ const next = {};
43
+ for (const [key, value] of Object.entries(map)) {
44
+ if (!key.startsWith(prefix)) {
45
+ next[key] = value;
46
+ continue;
47
+ }
48
+ const close = key.indexOf("]", prefix.length);
49
+ const idx = close === -1 ? NaN : Number(key.slice(prefix.length, close));
50
+ if (!Number.isInteger(idx) || idx < removed) {
51
+ next[key] = value;
52
+ continue;
53
+ }
54
+ if (idx === removed) {
55
+ changed = true;
56
+ continue;
57
+ }
58
+ next[`${name}[${idx - 1}]${key.slice(close + 1)}`] = value;
59
+ changed = true;
60
+ }
61
+ return changed ? next : map;
62
+ };
11
63
  /** Changing a value clears that field's prior errors (re-validated by the caller). */
12
64
  const formReducer = (state, action) => {
13
65
  switch (action.type) {
@@ -34,17 +86,21 @@ const formReducer = (state, action) => {
34
86
  errors: {
35
87
  ...state.errors,
36
88
  [action.name]: action.errors
37
- },
38
- warnings: {
39
- ...state.warnings,
40
- [action.name]: action.warnings
41
89
  }
42
90
  };
43
91
  case "SET_ALL_ISSUES": return {
44
92
  ...state,
45
93
  errors: action.errors,
46
- warnings: action.warnings,
47
- submitAttempted: true
94
+ attemptedSteps: new Set([...state.attemptedSteps, ...action.steps])
95
+ };
96
+ case "MARK_STEP_ATTEMPTED": return state.attemptedSteps.has(action.stepId) ? state : {
97
+ ...state,
98
+ attemptedSteps: new Set([...state.attemptedSteps, action.stepId])
99
+ };
100
+ case "REMOVE_REPEATER_ROW": return {
101
+ ...state,
102
+ errors: reindexRepeaterKeys(state.errors, action.name, action.index),
103
+ touched: reindexRepeaterKeys(state.touched, action.name, action.index)
48
104
  };
49
105
  case "SUBMIT_START": return {
50
106
  ...state,
@@ -61,10 +117,11 @@ const formReducer = (state, action) => {
61
117
  submitting: false,
62
118
  submitError: action.message
63
119
  };
120
+ case "RESET": return initialFormState(action.values);
64
121
  default: return state;
65
122
  }
66
123
  };
67
124
  //#endregion
68
- export { formReducer, initialFormState };
125
+ export { DEFAULT_STEP_ID, formReducer, initialFormState, seedFieldValues };
69
126
 
70
127
  //# sourceMappingURL=state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","names":[],"sources":["../../src/react/state.ts"],"sourcesContent":["export type FieldErrors = Record<string, string[]>\n\nexport type FormState = {\n\tvalues: Record<string, unknown>\n\terrors: FieldErrors\n\twarnings: FieldErrors\n\ttouched: Record<string, boolean>\n\tsubmitting: boolean\n\tsubmitted: boolean\n\tsubmitAttempted: boolean\n\tsubmitError?: string\n}\n\nexport type FormAction =\n\t| { type: 'SET_VALUE'; name: string; value: unknown }\n\t| { type: 'TOUCH'; name: string }\n\t| { type: 'SET_FIELD_ISSUES'; name: string; errors: string[]; warnings: string[] }\n\t| { type: 'SET_ALL_ISSUES'; errors: FieldErrors; warnings: FieldErrors }\n\t| { type: 'SUBMIT_START' }\n\t| { type: 'SUBMIT_SUCCESS' }\n\t| { type: 'SUBMIT_ERROR'; message: string }\n\nexport const initialFormState = (values: Record<string, unknown>): FormState => ({\n\tvalues,\n\terrors: {},\n\twarnings: {},\n\ttouched: {},\n\tsubmitting: false,\n\tsubmitted: false,\n\tsubmitAttempted: false,\n})\n\n/** Changing a value clears that field's prior errors (re-validated by the caller). */\nexport const formReducer = (state: FormState, action: FormAction): FormState => {\n\tswitch (action.type) {\n\t\tcase 'SET_VALUE': {\n\t\t\tconst { [action.name]: _removed, ...restErrors } = state.errors\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tvalues: { ...state.values, [action.name]: action.value },\n\t\t\t\terrors: restErrors,\n\t\t\t}\n\t\t}\n\t\tcase 'TOUCH':\n\t\t\treturn state.touched[action.name]\n\t\t\t\t? state\n\t\t\t\t: { ...state, touched: { ...state.touched, [action.name]: true } }\n\t\tcase 'SET_FIELD_ISSUES':\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\terrors: { ...state.errors, [action.name]: action.errors },\n\t\t\t\twarnings: { ...state.warnings, [action.name]: action.warnings },\n\t\t\t}\n\t\tcase 'SET_ALL_ISSUES':\n\t\t\treturn { ...state, errors: action.errors, warnings: action.warnings, submitAttempted: true }\n\t\tcase 'SUBMIT_START':\n\t\t\treturn { ...state, submitting: true, submitError: undefined }\n\t\tcase 'SUBMIT_SUCCESS':\n\t\t\treturn { ...state, submitting: false, submitted: true }\n\t\tcase 'SUBMIT_ERROR':\n\t\t\treturn { ...state, submitting: false, submitError: action.message }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n"],"mappings":";AAsBA,MAAa,oBAAoB,YAAgD;CAChF;CACA,QAAQ,CAAC;CACT,UAAU,CAAC;CACX,SAAS,CAAC;CACV,YAAY;CACZ,WAAW;CACX,iBAAiB;AAClB;;AAGA,MAAa,eAAe,OAAkB,WAAkC;CAC/E,QAAQ,OAAO,MAAf;EACC,KAAK,aAAa;GACjB,MAAM,GAAG,OAAO,OAAO,UAAU,GAAG,eAAe,MAAM;GACzD,OAAO;IACN,GAAG;IACH,QAAQ;KAAE,GAAG,MAAM;MAAS,OAAO,OAAO,OAAO;IAAM;IACvD,QAAQ;GACT;EACD;EACA,KAAK,SACJ,OAAO,MAAM,QAAQ,OAAO,QACzB,QACA;GAAE,GAAG;GAAO,SAAS;IAAE,GAAG,MAAM;KAAU,OAAO,OAAO;GAAK;EAAE;EACnE,KAAK,oBACJ,OAAO;GACN,GAAG;GACH,QAAQ;IAAE,GAAG,MAAM;KAAS,OAAO,OAAO,OAAO;GAAO;GACxD,UAAU;IAAE,GAAG,MAAM;KAAW,OAAO,OAAO,OAAO;GAAS;EAC/D;EACD,KAAK,kBACJ,OAAO;GAAE,GAAG;GAAO,QAAQ,OAAO;GAAQ,UAAU,OAAO;GAAU,iBAAiB;EAAK;EAC5F,KAAK,gBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAM,aAAa,KAAA;EAAU;EAC7D,KAAK,kBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAO,WAAW;EAAK;EACvD,KAAK,gBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAO,aAAa,OAAO;EAAQ;EACnE,SACC,OAAO;CACT;AACD"}
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../src/react/state.ts"],"sourcesContent":["import { consentDisplayOf } from '../consent/effectiveStatement'\nimport { isNamedField } from '../fields/fieldKey'\nimport type { FormFieldInstance } from '../submissions/types'\n\nexport type FieldErrors = Record<string, string[]>\n\n/**\n * The step a field's error reveal is keyed to when the form has no flow, or the field belongs to no\n * step. A single-step form has exactly this one step, so its reveal collapses to the old global one.\n */\nexport const DEFAULT_STEP_ID = '__form__'\n\nexport type FormState = {\n\tvalues: Record<string, unknown>\n\terrors: FieldErrors\n\ttouched: Record<string, boolean>\n\tsubmitting: boolean\n\tsubmitted: boolean\n\t/**\n\t * The step ids whose validation the user has attempted (a blocked advance, or a submit). A field\n\t * reveals its error when it is touched or its own step is in this set, never via a single global flag,\n\t * so a submit attempt cannot pre-reveal errors on a step the visitor has not reached.\n\t */\n\tattemptedSteps: Set<string>\n\tsubmitError?: string\n}\n\nexport type FormAction =\n\t| { type: 'SET_VALUE'; name: string; value: unknown }\n\t| { type: 'TOUCH'; name: string }\n\t| { type: 'SET_FIELD_ISSUES'; name: string; errors: string[] }\n\t| { type: 'SET_ALL_ISSUES'; errors: FieldErrors; steps: string[] }\n\t| { type: 'MARK_STEP_ATTEMPTED'; stepId: string }\n\t| { type: 'REMOVE_REPEATER_ROW'; name: string; index: number }\n\t| { type: 'SUBMIT_START' }\n\t| { type: 'SUBMIT_SUCCESS' }\n\t| { type: 'SUBMIT_ERROR'; message: string }\n\t| { type: 'RESET'; values: Record<string, unknown> }\n\n/**\n * Per-field defaults for the reducer's initial state. Nameless (bare) blocks carry no value and\n * are skipped. A repeater with a positive `minRows` starts pre-seeded with that many empty rows,\n * matching the schema's own floor. Computed once, ahead of the reducer, so seeding is never an\n * action: it can't touch a field, trigger validation, or (via `Form`'s dispatch wrapper) be\n * mistaken for the user's first edit and fire `form.started`.\n */\nexport const seedFieldValues = (fields: FormFieldInstance[]): Record<string, unknown> =>\n\tObject.fromEntries(\n\t\tfields.filter(isNamedField).map((field) => {\n\t\t\tif (field.blockType === 'repeater') {\n\t\t\t\tconst minRows = typeof field.minRows === 'number' ? field.minRows : 0\n\t\t\t\tif (minRows > 0) {\n\t\t\t\t\treturn [field.name, Array.from({ length: minRows }, () => ({}))]\n\t\t\t\t}\n\t\t\t}\n\t\t\t// A notice-display consent has no control and the submit is the agreement, so its value\n\t\t\t// is true from the start; the server coerces the same, keeping dependent conditions and\n\t\t\t// calc in agreement across both engines.\n\t\t\tif (field.blockType === 'consent' && consentDisplayOf(field) === 'notice') {\n\t\t\t\treturn [field.name, true]\n\t\t\t}\n\t\t\treturn [field.name, undefined]\n\t\t})\n\t)\n\nexport const initialFormState = (values: Record<string, unknown>): FormState => ({\n\tvalues,\n\terrors: {},\n\ttouched: {},\n\tsubmitting: false,\n\tsubmitted: false,\n\tattemptedSteps: new Set(),\n})\n\n/**\n * Re-key composite entries (`name[i].sub`) after repeater row `removed` is deleted: drop the removed\n * index and shift every higher index down by one, so surviving rows keep their own errors/touched\n * flags instead of inheriting a deleted or shifted neighbour's. Matches on the `name[<int>]` prefix,\n * so it is agnostic to the sub-key shape after `]` and needs no sub-field list. Returns the same\n * reference when nothing changed, so an unrelated dispatch does not churn state identity.\n */\nconst reindexRepeaterKeys = <T>(\n\tmap: Record<string, T>,\n\tname: string,\n\tremoved: number\n): Record<string, T> => {\n\tconst prefix = `${name}[`\n\tlet changed = false\n\tconst next: Record<string, T> = {}\n\tfor (const [key, value] of Object.entries(map)) {\n\t\tif (!key.startsWith(prefix)) {\n\t\t\tnext[key] = value\n\t\t\tcontinue\n\t\t}\n\t\tconst close = key.indexOf(']', prefix.length)\n\t\tconst idx = close === -1 ? Number.NaN : Number(key.slice(prefix.length, close))\n\t\tif (!Number.isInteger(idx) || idx < removed) {\n\t\t\tnext[key] = value\n\t\t\tcontinue\n\t\t}\n\t\tif (idx === removed) {\n\t\t\tchanged = true\n\t\t\tcontinue\n\t\t}\n\t\tnext[`${name}[${idx - 1}]${key.slice(close + 1)}`] = value\n\t\tchanged = true\n\t}\n\treturn changed ? next : map\n}\n\n/** Changing a value clears that field's prior errors (re-validated by the caller). */\nexport const formReducer = (state: FormState, action: FormAction): FormState => {\n\tswitch (action.type) {\n\t\tcase 'SET_VALUE': {\n\t\t\tconst { [action.name]: _removed, ...restErrors } = state.errors\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tvalues: { ...state.values, [action.name]: action.value },\n\t\t\t\terrors: restErrors,\n\t\t\t}\n\t\t}\n\t\tcase 'TOUCH':\n\t\t\treturn state.touched[action.name]\n\t\t\t\t? state\n\t\t\t\t: { ...state, touched: { ...state.touched, [action.name]: true } }\n\t\tcase 'SET_FIELD_ISSUES':\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\terrors: { ...state.errors, [action.name]: action.errors },\n\t\t\t}\n\t\tcase 'SET_ALL_ISSUES':\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\terrors: action.errors,\n\t\t\t\tattemptedSteps: new Set([...state.attemptedSteps, ...action.steps]),\n\t\t\t}\n\t\tcase 'MARK_STEP_ATTEMPTED':\n\t\t\treturn state.attemptedSteps.has(action.stepId)\n\t\t\t\t? state\n\t\t\t\t: { ...state, attemptedSteps: new Set([...state.attemptedSteps, action.stepId]) }\n\t\tcase 'REMOVE_REPEATER_ROW':\n\t\t\t// The row value is removed by the field's own SET_VALUE; this shifts the composite issue keys\n\t\t\t// (`name[i].sub`) that a plain value array cannot carry, so a deleted row's errors never strand\n\t\t\t// on a survivor or linger unreachably.\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\terrors: reindexRepeaterKeys(state.errors, action.name, action.index),\n\t\t\t\ttouched: reindexRepeaterKeys(state.touched, action.name, action.index),\n\t\t\t}\n\t\tcase 'SUBMIT_START':\n\t\t\treturn { ...state, submitting: true, submitError: undefined }\n\t\tcase 'SUBMIT_SUCCESS':\n\t\t\treturn { ...state, submitting: false, submitted: true }\n\t\tcase 'SUBMIT_ERROR':\n\t\t\treturn { ...state, submitting: false, submitError: action.message }\n\t\tcase 'RESET':\n\t\t\treturn initialFormState(action.values)\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n"],"mappings":";;;;;;;AAUA,MAAa,kBAAkB;;;;;;;;AAoC/B,MAAa,mBAAmB,WAC/B,OAAO,YACN,OAAO,OAAO,YAAY,EAAE,KAAK,UAAU;CAC1C,IAAI,MAAM,cAAc,YAAY;EACnC,MAAM,UAAU,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU;EACpE,IAAI,UAAU,GACb,OAAO,CAAC,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,UAAU,CAAC,EAAE,CAAC;CAEjE;CAIA,IAAI,MAAM,cAAc,aAAa,iBAAiB,KAAK,MAAM,UAChE,OAAO,CAAC,MAAM,MAAM,IAAI;CAEzB,OAAO,CAAC,MAAM,MAAM,KAAA,CAAS;AAC9B,CAAC,CACF;AAED,MAAa,oBAAoB,YAAgD;CAChF;CACA,QAAQ,CAAC;CACT,SAAS,CAAC;CACV,YAAY;CACZ,WAAW;CACX,gCAAgB,IAAI,IAAI;AACzB;;;;;;;;AASA,MAAM,uBACL,KACA,MACA,YACuB;CACvB,MAAM,SAAS,GAAG,KAAK;CACvB,IAAI,UAAU;CACd,MAAM,OAA0B,CAAC;CACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC/C,IAAI,CAAC,IAAI,WAAW,MAAM,GAAG;GAC5B,KAAK,OAAO;GACZ;EACD;EACA,MAAM,QAAQ,IAAI,QAAQ,KAAK,OAAO,MAAM;EAC5C,MAAM,MAAM,UAAU,KAAK,MAAa,OAAO,IAAI,MAAM,OAAO,QAAQ,KAAK,CAAC;EAC9E,IAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,SAAS;GAC5C,KAAK,OAAO;GACZ;EACD;EACA,IAAI,QAAQ,SAAS;GACpB,UAAU;GACV;EACD;EACA,KAAK,GAAG,KAAK,GAAG,MAAM,EAAE,GAAG,IAAI,MAAM,QAAQ,CAAC,OAAO;EACrD,UAAU;CACX;CACA,OAAO,UAAU,OAAO;AACzB;;AAGA,MAAa,eAAe,OAAkB,WAAkC;CAC/E,QAAQ,OAAO,MAAf;EACC,KAAK,aAAa;GACjB,MAAM,GAAG,OAAO,OAAO,UAAU,GAAG,eAAe,MAAM;GACzD,OAAO;IACN,GAAG;IACH,QAAQ;KAAE,GAAG,MAAM;MAAS,OAAO,OAAO,OAAO;IAAM;IACvD,QAAQ;GACT;EACD;EACA,KAAK,SACJ,OAAO,MAAM,QAAQ,OAAO,QACzB,QACA;GAAE,GAAG;GAAO,SAAS;IAAE,GAAG,MAAM;KAAU,OAAO,OAAO;GAAK;EAAE;EACnE,KAAK,oBACJ,OAAO;GACN,GAAG;GACH,QAAQ;IAAE,GAAG,MAAM;KAAS,OAAO,OAAO,OAAO;GAAO;EACzD;EACD,KAAK,kBACJ,OAAO;GACN,GAAG;GACH,QAAQ,OAAO;GACf,gBAAgB,IAAI,IAAI,CAAC,GAAG,MAAM,gBAAgB,GAAG,OAAO,KAAK,CAAC;EACnE;EACD,KAAK,uBACJ,OAAO,MAAM,eAAe,IAAI,OAAO,MAAM,IAC1C,QACA;GAAE,GAAG;GAAO,gBAAgB,IAAI,IAAI,CAAC,GAAG,MAAM,gBAAgB,OAAO,MAAM,CAAC;EAAE;EAClF,KAAK,uBAIJ,OAAO;GACN,GAAG;GACH,QAAQ,oBAAoB,MAAM,QAAQ,OAAO,MAAM,OAAO,KAAK;GACnE,SAAS,oBAAoB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK;EACtE;EACD,KAAK,gBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAM,aAAa,KAAA;EAAU;EAC7D,KAAK,kBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAO,WAAW;EAAK;EACvD,KAAK,gBACJ,OAAO;GAAE,GAAG;GAAO,YAAY;GAAO,aAAa,OAAO;EAAQ;EACnE,KAAK,SACJ,OAAO,iBAAiB,OAAO,MAAM;EACtC,SACC,OAAO;CACT;AACD"}
@@ -1,7 +1,7 @@
1
1
  //#region src/react/uploadFile.d.ts
2
2
  type UploadFileInput = {
3
- file: File; /** Upload collection slug. Defaults to `form-uploads`. */
4
- collection?: string;
3
+ file: File; /** Upload collection slug. File fields carry it as the server-stamped `collection` block config. */
4
+ collection: string;
5
5
  apiRoute?: string;
6
6
  fetchImpl?: typeof fetch;
7
7
  };
@@ -5,7 +5,7 @@
5
5
  * at submit, so the client is never trusted for it. Pure: inject `fetchImpl` in tests.
6
6
  */
7
7
  const uploadFile = async (input) => {
8
- const { file, collection = "form-uploads", apiRoute = "/api", fetchImpl = fetch } = input;
8
+ const { file, collection, apiRoute = "/api", fetchImpl = fetch } = input;
9
9
  const body = new FormData();
10
10
  body.append("file", file);
11
11
  let response;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadFile.js","names":[],"sources":["../../src/react/uploadFile.ts"],"sourcesContent":["export type UploadFileInput = {\n\tfile: File\n\t/** Upload collection slug. Defaults to `form-uploads`. */\n\tcollection?: string\n\tapiRoute?: string\n\tfetchImpl?: typeof fetch\n}\n\nexport type UploadFileResult = { ok: true; id: string | number } | { ok: false; message?: string }\n\n/**\n * Upload one file to a Payload upload collection (`POST {apiRoute}/{collection}`, multipart) and return its\n * id, which the file field stores as its value. The server re-derives the file metadata from the stored doc\n * at submit, so the client is never trusted for it. Pure: inject `fetchImpl` in tests.\n */\nexport const uploadFile = async (input: UploadFileInput): Promise<UploadFileResult> => {\n\tconst { file, collection = 'form-uploads', apiRoute = '/api', fetchImpl = fetch } = input\n\tconst body = new FormData()\n\tbody.append('file', file)\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetchImpl(`${apiRoute}/${collection}`, { method: 'POST', body })\n\t} catch (error) {\n\t\treturn { ok: false, message: error instanceof Error ? error.message : 'Network error' }\n\t}\n\tif (!response.ok) {\n\t\treturn { ok: false, message: `Upload failed (${response.status})` }\n\t}\n\tconst data = (await response.json().catch(() => ({}))) as { doc?: { id?: string | number } }\n\tconst id = data.doc?.id\n\treturn id == null ? { ok: false, message: 'No id returned' } : { ok: true, id }\n}\n"],"mappings":";;;;;;AAeA,MAAa,aAAa,OAAO,UAAsD;CACtF,MAAM,EAAE,MAAM,aAAa,gBAAgB,WAAW,QAAQ,YAAY,UAAU;CACpF,MAAM,OAAO,IAAI,SAAS;CAC1B,KAAK,OAAO,QAAQ,IAAI;CACxB,IAAI;CACJ,IAAI;EACH,WAAW,MAAM,UAAU,GAAG,SAAS,GAAG,cAAc;GAAE,QAAQ;GAAQ;EAAK,CAAC;CACjF,SAAS,OAAO;EACf,OAAO;GAAE,IAAI;GAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU;EAAgB;CACvF;CACA,IAAI,CAAC,SAAS,IACb,OAAO;EAAE,IAAI;EAAO,SAAS,kBAAkB,SAAS,OAAO;CAAG;CAGnE,MAAM,MAAK,MADS,SAAS,KAAK,EAAE,aAAa,CAAC,EAAE,GACpC,KAAK;CACrB,OAAO,MAAM,OAAO;EAAE,IAAI;EAAO,SAAS;CAAiB,IAAI;EAAE,IAAI;EAAM;CAAG;AAC/E"}
1
+ {"version":3,"file":"uploadFile.js","names":[],"sources":["../../src/react/uploadFile.ts"],"sourcesContent":["export type UploadFileInput = {\n\tfile: File\n\t/** Upload collection slug. File fields carry it as the server-stamped `collection` block config. */\n\tcollection: string\n\tapiRoute?: string\n\tfetchImpl?: typeof fetch\n}\n\nexport type UploadFileResult = { ok: true; id: string | number } | { ok: false; message?: string }\n\n/**\n * Upload one file to a Payload upload collection (`POST {apiRoute}/{collection}`, multipart) and return its\n * id, which the file field stores as its value. The server re-derives the file metadata from the stored doc\n * at submit, so the client is never trusted for it. Pure: inject `fetchImpl` in tests.\n */\nexport const uploadFile = async (input: UploadFileInput): Promise<UploadFileResult> => {\n\tconst { file, collection, apiRoute = '/api', fetchImpl = fetch } = input\n\tconst body = new FormData()\n\tbody.append('file', file)\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetchImpl(`${apiRoute}/${collection}`, { method: 'POST', body })\n\t} catch (error) {\n\t\treturn { ok: false, message: error instanceof Error ? error.message : 'Network error' }\n\t}\n\tif (!response.ok) {\n\t\treturn { ok: false, message: `Upload failed (${response.status})` }\n\t}\n\tconst data = (await response.json().catch(() => ({}))) as { doc?: { id?: string | number } }\n\tconst id = data.doc?.id\n\treturn id == null ? { ok: false, message: 'No id returned' } : { ok: true, id }\n}\n"],"mappings":";;;;;;AAeA,MAAa,aAAa,OAAO,UAAsD;CACtF,MAAM,EAAE,MAAM,YAAY,WAAW,QAAQ,YAAY,UAAU;CACnE,MAAM,OAAO,IAAI,SAAS;CAC1B,KAAK,OAAO,QAAQ,IAAI;CACxB,IAAI;CACJ,IAAI;EACH,WAAW,MAAM,UAAU,GAAG,SAAS,GAAG,cAAc;GAAE,QAAQ;GAAQ;EAAK,CAAC;CACjF,SAAS,OAAO;EACf,OAAO;GAAE,IAAI;GAAO,SAAS,iBAAiB,QAAQ,MAAM,UAAU;EAAgB;CACvF;CACA,IAAI,CAAC,SAAS,IACb,OAAO;EAAE,IAAI;EAAO,SAAS,kBAAkB,SAAS,OAAO;CAAG;CAGnE,MAAM,MAAK,MADS,SAAS,KAAK,EAAE,aAAa,CAAC,EAAE,GACpC,KAAK;CACrB,OAAO,MAAM,OAAO;EAAE,IAAI;EAAO,SAAS;CAAiB,IAAI;EAAE,IAAI;EAAM;CAAG;AAC/E"}
@@ -0,0 +1,10 @@
1
+ //#region src/react/useCalcValues.d.ts
2
+ /**
3
+ * Live computed calculation values (field name -> number), recomputed as answers change. For
4
+ * composed frontends (custom layouts over `useFormContext`/`FormFields`) that render running
5
+ * totals without re-evaluating expressions themselves.
6
+ */
7
+ declare const useCalcValues: () => Record<string, number>;
8
+ //#endregion
9
+ export { useCalcValues };
10
+ //# sourceMappingURL=useCalcValues.d.ts.map
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { useFormContext } from "./FormContext.js";
3
+ //#region src/react/useCalcValues.ts
4
+ /**
5
+ * Live computed calculation values (field name -> number), recomputed as answers change. For
6
+ * composed frontends (custom layouts over `useFormContext`/`FormFields`) that render running
7
+ * totals without re-evaluating expressions themselves.
8
+ */
9
+ const useCalcValues = () => useFormContext().calcValues ?? {};
10
+ //#endregion
11
+ export { useCalcValues };
12
+
13
+ //# sourceMappingURL=useCalcValues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCalcValues.js","names":[],"sources":["../../src/react/useCalcValues.ts"],"sourcesContent":["'use client'\n\nimport { useFormContext } from './FormContext'\n\n/**\n * Live computed calculation values (field name -> number), recomputed as answers change. For\n * composed frontends (custom layouts over `useFormContext`/`FormFields`) that render running\n * totals without re-evaluating expressions themselves.\n */\nexport const useCalcValues = (): Record<string, number> => useFormContext().calcValues ?? {}\n"],"mappings":";;;;;;;;AASA,MAAa,sBAA8C,eAAe,EAAE,cAAc,CAAC"}
@@ -2,7 +2,6 @@
2
2
  type UseFieldResult<TValue = unknown> = {
3
3
  value: TValue | undefined;
4
4
  errors: string[];
5
- warnings: string[];
6
5
  touched: boolean;
7
6
  setValue: (value: TValue) => void; /** Mark touched and validate now (call on blur). */
8
7
  onBlur: () => void;
@@ -1,12 +1,14 @@
1
1
  "use client";
2
2
  import { useFormContext } from "./FormContext.js";
3
+ import "./state.js";
3
4
  import { useCallback } from "react";
4
5
  //#region src/react/useField.ts
5
6
  /** Bind one field by name to the form controller: its value, issues, and change/blur handlers. */
6
7
  const useField = (name) => {
7
- const { state, dispatch, validateField } = useFormContext();
8
+ const { state, dispatch, validateField, stepIdOfField } = useFormContext();
8
9
  const touched = state.touched[name] ?? false;
9
- const showIssues = touched || state.submitAttempted;
10
+ const stepAttempted = state.attemptedSteps.has(stepIdOfField?.(name) ?? "__form__");
11
+ const showIssues = touched || stepAttempted;
10
12
  const value = state.values[name];
11
13
  const setValue = useCallback((next) => {
12
14
  dispatch({
@@ -14,12 +16,11 @@ const useField = (name) => {
14
16
  name,
15
17
  value: next
16
18
  });
17
- if (touched || state.submitAttempted) validateField(name, next);
19
+ if (showIssues) validateField(name, next);
18
20
  }, [
19
21
  dispatch,
20
22
  name,
21
- touched,
22
- state.submitAttempted,
23
+ showIssues,
23
24
  validateField
24
25
  ]);
25
26
  const onBlur = useCallback(() => {
@@ -37,7 +38,6 @@ const useField = (name) => {
37
38
  return {
38
39
  value,
39
40
  errors: showIssues ? state.errors[name] ?? [] : [],
40
- warnings: showIssues ? state.warnings[name] ?? [] : [],
41
41
  touched,
42
42
  setValue,
43
43
  onBlur
@@ -1 +1 @@
1
- {"version":3,"file":"useField.js","names":[],"sources":["../../src/react/useField.ts"],"sourcesContent":["'use client'\n\nimport { useCallback } from 'react'\nimport { useFormContext } from './FormContext'\n\nexport type UseFieldResult<TValue = unknown> = {\n\tvalue: TValue | undefined\n\terrors: string[]\n\twarnings: string[]\n\ttouched: boolean\n\tsetValue: (value: TValue) => void\n\t/** Mark touched and validate now (call on blur). */\n\tonBlur: () => void\n}\n\n/** Bind one field by name to the form controller: its value, issues, and change/blur handlers. */\nexport const useField = <TValue = unknown>(name: string): UseFieldResult<TValue> => {\n\tconst { state, dispatch, validateField } = useFormContext()\n\tconst touched = state.touched[name] ?? false\n\tconst showIssues = touched || state.submitAttempted\n\tconst value = state.values[name] as TValue | undefined\n\n\tconst setValue = useCallback(\n\t\t(next: TValue) => {\n\t\t\tdispatch({ type: 'SET_VALUE', name, value: next })\n\t\t\tif (touched || state.submitAttempted) {\n\t\t\t\tvalidateField(name, next)\n\t\t\t}\n\t\t},\n\t\t[dispatch, name, touched, state.submitAttempted, validateField]\n\t)\n\n\tconst onBlur = useCallback(() => {\n\t\tdispatch({ type: 'TOUCH', name })\n\t\tvalidateField(name, value)\n\t}, [dispatch, name, validateField, value])\n\n\treturn {\n\t\tvalue,\n\t\terrors: showIssues ? (state.errors[name] ?? []) : [],\n\t\twarnings: showIssues ? (state.warnings[name] ?? []) : [],\n\t\ttouched,\n\t\tsetValue,\n\t\tonBlur,\n\t}\n}\n"],"mappings":";;;;;AAgBA,MAAa,YAA8B,SAAyC;CACnF,MAAM,EAAE,OAAO,UAAU,kBAAkB,eAAe;CAC1D,MAAM,UAAU,MAAM,QAAQ,SAAS;CACvC,MAAM,aAAa,WAAW,MAAM;CACpC,MAAM,QAAQ,MAAM,OAAO;CAE3B,MAAM,WAAW,aACf,SAAiB;EACjB,SAAS;GAAE,MAAM;GAAa;GAAM,OAAO;EAAK,CAAC;EACjD,IAAI,WAAW,MAAM,iBACpB,cAAc,MAAM,IAAI;CAE1B,GACA;EAAC;EAAU;EAAM;EAAS,MAAM;EAAiB;CAAa,CAC/D;CAEA,MAAM,SAAS,kBAAkB;EAChC,SAAS;GAAE,MAAM;GAAS;EAAK,CAAC;EAChC,cAAc,MAAM,KAAK;CAC1B,GAAG;EAAC;EAAU;EAAM;EAAe;CAAK,CAAC;CAEzC,OAAO;EACN;EACA,QAAQ,aAAc,MAAM,OAAO,SAAS,CAAC,IAAK,CAAC;EACnD,UAAU,aAAc,MAAM,SAAS,SAAS,CAAC,IAAK,CAAC;EACvD;EACA;EACA;CACD;AACD"}
1
+ {"version":3,"file":"useField.js","names":[],"sources":["../../src/react/useField.ts"],"sourcesContent":["'use client'\n\nimport { useCallback } from 'react'\nimport { useFormContext } from './FormContext'\nimport { DEFAULT_STEP_ID } from './state'\n\nexport type UseFieldResult<TValue = unknown> = {\n\tvalue: TValue | undefined\n\terrors: string[]\n\ttouched: boolean\n\tsetValue: (value: TValue) => void\n\t/** Mark touched and validate now (call on blur). */\n\tonBlur: () => void\n}\n\n/** Bind one field by name to the form controller: its value, issues, and change/blur handlers. */\nexport const useField = <TValue = unknown>(name: string): UseFieldResult<TValue> => {\n\tconst { state, dispatch, validateField, stepIdOfField } = useFormContext()\n\tconst touched = state.touched[name] ?? false\n\t// Reveal is a function of this field's own step, never a single global flag: the field's error shows\n\t// once it is touched, or once the step it belongs to has been attempted (a blocked advance or submit).\n\tconst stepAttempted = state.attemptedSteps.has(stepIdOfField?.(name) ?? DEFAULT_STEP_ID)\n\tconst showIssues = touched || stepAttempted\n\tconst value = state.values[name] as TValue | undefined\n\n\tconst setValue = useCallback(\n\t\t(next: TValue) => {\n\t\t\tdispatch({ type: 'SET_VALUE', name, value: next })\n\t\t\t// Re-validate on change only once the error is already revealed; never reveal an untouched field mid-typing.\n\t\t\tif (showIssues) {\n\t\t\t\tvalidateField(name, next)\n\t\t\t}\n\t\t},\n\t\t[dispatch, name, showIssues, validateField]\n\t)\n\n\tconst onBlur = useCallback(() => {\n\t\tdispatch({ type: 'TOUCH', name })\n\t\tvalidateField(name, value)\n\t}, [dispatch, name, validateField, value])\n\n\treturn {\n\t\tvalue,\n\t\terrors: showIssues ? (state.errors[name] ?? []) : [],\n\t\ttouched,\n\t\tsetValue,\n\t\tonBlur,\n\t}\n}\n"],"mappings":";;;;;;AAgBA,MAAa,YAA8B,SAAyC;CACnF,MAAM,EAAE,OAAO,UAAU,eAAe,kBAAkB,eAAe;CACzE,MAAM,UAAU,MAAM,QAAQ,SAAS;CAGvC,MAAM,gBAAgB,MAAM,eAAe,IAAI,gBAAgB,IAAI,KAAA,UAAoB;CACvF,MAAM,aAAa,WAAW;CAC9B,MAAM,QAAQ,MAAM,OAAO;CAE3B,MAAM,WAAW,aACf,SAAiB;EACjB,SAAS;GAAE,MAAM;GAAa;GAAM,OAAO;EAAK,CAAC;EAEjD,IAAI,YACH,cAAc,MAAM,IAAI;CAE1B,GACA;EAAC;EAAU;EAAM;EAAY;CAAa,CAC3C;CAEA,MAAM,SAAS,kBAAkB;EAChC,SAAS;GAAE,MAAM;GAAS;EAAK,CAAC;EAChC,cAAc,MAAM,KAAK;CAC1B,GAAG;EAAC;EAAU;EAAM;EAAe;CAAK,CAAC;CAEzC,OAAO;EACN;EACA,QAAQ,aAAc,MAAM,OAAO,SAAS,CAAC,IAAK,CAAC;EACnD;EACA;EACA;CACD;AACD"}
@@ -1,7 +1,7 @@
1
1
  import { FormState } from "./state.js";
2
2
 
3
3
  //#region src/react/useFormState.d.ts
4
- /** The whole form state (values, errors, warnings, touched, submitting, submitted, submitError). */
4
+ /** The whole form state (values, errors, touched, submitting, submitted, submitError). */
5
5
  declare const useFormState: () => FormState;
6
6
  //#endregion
7
7
  export { useFormState };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useFormContext } from "./FormContext.js";
3
3
  //#region src/react/useFormState.ts
4
- /** The whole form state (values, errors, warnings, touched, submitting, submitted, submitError). */
4
+ /** The whole form state (values, errors, touched, submitting, submitted, submitError). */
5
5
  const useFormState = () => useFormContext().state;
6
6
  //#endregion
7
7
  export { useFormState };
@@ -1 +1 @@
1
- {"version":3,"file":"useFormState.js","names":[],"sources":["../../src/react/useFormState.ts"],"sourcesContent":["'use client'\n\nimport { useFormContext } from './FormContext'\nimport type { FormState } from './state'\n\n/** The whole form state (values, errors, warnings, touched, submitting, submitted, submitError). */\nexport const useFormState = (): FormState => useFormContext().state\n"],"mappings":";;;;AAMA,MAAa,qBAAgC,eAAe,EAAE"}
1
+ {"version":3,"file":"useFormState.js","names":[],"sources":["../../src/react/useFormState.ts"],"sourcesContent":["'use client'\n\nimport { useFormContext } from './FormContext'\nimport type { FormState } from './state'\n\n/** The whole form state (values, errors, touched, submitting, submitted, submitError). */\nexport const useFormState = (): FormState => useFormContext().state\n"],"mappings":";;;;AAMA,MAAa,qBAAgC,eAAe,EAAE"}
@@ -2,11 +2,11 @@ import { runValidation } from "../validation/runValidation.js";
2
2
  //#region src/react/validateField.ts
3
3
  /**
4
4
  * Run client-side validation for one field, reusing the shared engine in `mode: 'client'` (server-only
5
- * rules are skipped, no `req`/`payload`). Splits issues into blocking errors and advisory warnings.
5
+ * rules are skipped, no `req`/`payload`). Every returned message is a blocking error.
6
6
  */
7
7
  const validateFieldValue = async (input) => {
8
8
  const { field, value, registry, ruleRegistry, answers, locale, t } = input;
9
- const { errors: issues } = await runValidation({
9
+ const { errors } = await runValidation({
10
10
  field,
11
11
  fieldDefinition: registry.get(field.blockType),
12
12
  value,
@@ -19,10 +19,7 @@ const validateFieldValue = async (input) => {
19
19
  event: "onChange",
20
20
  mode: "client"
21
21
  });
22
- return {
23
- errors: issues.filter((issue) => issue.severity === "error").map((issue) => issue.message),
24
- warnings: issues.filter((issue) => issue.severity === "warning").map((issue) => issue.message)
25
- };
22
+ return { errors };
26
23
  };
27
24
  //#endregion
28
25
  export { validateFieldValue };
@@ -1 +1 @@
1
- {"version":3,"file":"validateField.js","names":[],"sources":["../../src/react/validateField.ts"],"sourcesContent":["import type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\n\nexport type ValidateFieldInput = {\n\tfield: FormFieldInstance\n\tvalue: unknown\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tanswers: Record<string, unknown>\n\tlocale: string\n\tt: Translate\n}\n\nexport type ValidateFieldResult = { errors: string[]; warnings: string[] }\n\n/**\n * Run client-side validation for one field, reusing the shared engine in `mode: 'client'` (server-only\n * rules are skipped, no `req`/`payload`). Splits issues into blocking errors and advisory warnings.\n */\nexport const validateFieldValue = async (\n\tinput: ValidateFieldInput\n): Promise<ValidateFieldResult> => {\n\tconst { field, value, registry, ruleRegistry, answers, locale, t } = input\n\tconst { errors: issues } = await runValidation({\n\t\tfield,\n\t\tfieldDefinition: registry.get(field.blockType),\n\t\tvalue,\n\t\tfieldType: field.blockType,\n\t\truleRegistry,\n\t\tanswers,\n\t\tlocale,\n\t\tt,\n\t\toperation: 'create',\n\t\tevent: 'onChange',\n\t\tmode: 'client',\n\t})\n\treturn {\n\t\terrors: issues.filter((issue) => issue.severity === 'error').map((issue) => issue.message),\n\t\twarnings: issues.filter((issue) => issue.severity === 'warning').map((issue) => issue.message),\n\t}\n}\n"],"mappings":";;;;;;AAsBA,MAAa,qBAAqB,OACjC,UACkC;CAClC,MAAM,EAAE,OAAO,OAAO,UAAU,cAAc,SAAS,QAAQ,MAAM;CACrE,MAAM,EAAE,QAAQ,WAAW,MAAM,cAAc;EAC9C;EACA,iBAAiB,SAAS,IAAI,MAAM,SAAS;EAC7C;EACA,WAAW,MAAM;EACjB;EACA;EACA;EACA;EACA,WAAW;EACX,OAAO;EACP,MAAM;CACP,CAAC;CACD,OAAO;EACN,QAAQ,OAAO,QAAQ,UAAU,MAAM,aAAa,OAAO,EAAE,KAAK,UAAU,MAAM,OAAO;EACzF,UAAU,OAAO,QAAQ,UAAU,MAAM,aAAa,SAAS,EAAE,KAAK,UAAU,MAAM,OAAO;CAC9F;AACD"}
1
+ {"version":3,"file":"validateField.js","names":[],"sources":["../../src/react/validateField.ts"],"sourcesContent":["import type { FieldTypeRegistry } from '../fields/registry'\nimport type { Translate } from '../fields/types'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { runValidation } from '../validation/runValidation'\n\nexport type ValidateFieldInput = {\n\tfield: FormFieldInstance\n\tvalue: unknown\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tanswers: Record<string, unknown>\n\tlocale: string\n\tt: Translate\n}\n\nexport type ValidateFieldResult = { errors: string[] }\n\n/**\n * Run client-side validation for one field, reusing the shared engine in `mode: 'client'` (server-only\n * rules are skipped, no `req`/`payload`). Every returned message is a blocking error.\n */\nexport const validateFieldValue = async (\n\tinput: ValidateFieldInput\n): Promise<ValidateFieldResult> => {\n\tconst { field, value, registry, ruleRegistry, answers, locale, t } = input\n\tconst { errors } = await runValidation({\n\t\tfield,\n\t\tfieldDefinition: registry.get(field.blockType),\n\t\tvalue,\n\t\tfieldType: field.blockType,\n\t\truleRegistry,\n\t\tanswers,\n\t\tlocale,\n\t\tt,\n\t\toperation: 'create',\n\t\tevent: 'onChange',\n\t\tmode: 'client',\n\t})\n\treturn { errors }\n}\n"],"mappings":";;;;;;AAsBA,MAAa,qBAAqB,OACjC,UACkC;CAClC,MAAM,EAAE,OAAO,OAAO,UAAU,cAAc,SAAS,QAAQ,MAAM;CACrE,MAAM,EAAE,WAAW,MAAM,cAAc;EACtC;EACA,iBAAiB,SAAS,IAAI,MAAM,SAAS;EAC7C;EACA,WAAW,MAAM;EACjB;EACA;EACA;EACA;EACA,WAAW;EACX,OAAO;EACP,MAAM;CACP,CAAC;CACD,OAAO,EAAE,OAAO;AACjB"}
@@ -1,8 +1,9 @@
1
1
  //#region src/recall/interpolate.d.ts
2
2
  /**
3
- * Replace `{{ name }}` and `{{ name|fallback }}` tokens. `resolve` returns the recalled value (''
4
- * when absent); the fallback (or '') is substituted when `resolve` yields ''. Token-free input is
5
- * returned unchanged, so this is a no-op for ordinary text.
3
+ * Replace `{{ name }}`, `{{ name|fallback }}`, and `{{ name||fallback }}` tokens (single and
4
+ * double pipe are equivalent; the second pipe is just consumed, not part of the fallback text).
5
+ * `resolve` returns the recalled value ('' when absent); the fallback (or '') is substituted when
6
+ * `resolve` yields ''. Token-free input is returned unchanged, so this is a no-op for ordinary text.
6
7
  */
7
8
  declare const interpolate: (template: string, resolve: (name: string) => string) => string;
8
9
  //#endregion
@@ -1,9 +1,10 @@
1
1
  //#region src/recall/interpolate.ts
2
- const TOKEN = /\{\{\s*([^{}|]+?)\s*(?:\|([^{}]*?))?\}\}/g;
2
+ const TOKEN = /\{\{\s*([^{}|]+?)\s*(?:\|\|?([^{}]*?))?\}\}/g;
3
3
  /**
4
- * Replace `{{ name }}` and `{{ name|fallback }}` tokens. `resolve` returns the recalled value (''
5
- * when absent); the fallback (or '') is substituted when `resolve` yields ''. Token-free input is
6
- * returned unchanged, so this is a no-op for ordinary text.
4
+ * Replace `{{ name }}`, `{{ name|fallback }}`, and `{{ name||fallback }}` tokens (single and
5
+ * double pipe are equivalent; the second pipe is just consumed, not part of the fallback text).
6
+ * `resolve` returns the recalled value ('' when absent); the fallback (or '') is substituted when
7
+ * `resolve` yields ''. Token-free input is returned unchanged, so this is a no-op for ordinary text.
7
8
  */
8
9
  const interpolate = (template, resolve) => template.replace(TOKEN, (_match, rawName, rawFallback) => {
9
10
  const value = resolve(rawName.trim());
@@ -1 +1 @@
1
- {"version":3,"file":"interpolate.js","names":[],"sources":["../../src/recall/interpolate.ts"],"sourcesContent":["const TOKEN = /\\{\\{\\s*([^{}|]+?)\\s*(?:\\|([^{}]*?))?\\}\\}/g\n\n/**\n * Replace `{{ name }}` and `{{ name|fallback }}` tokens. `resolve` returns the recalled value (''\n * when absent); the fallback (or '') is substituted when `resolve` yields ''. Token-free input is\n * returned unchanged, so this is a no-op for ordinary text.\n */\nexport const interpolate = (template: string, resolve: (name: string) => string): string =>\n\ttemplate.replace(TOKEN, (_match, rawName: string, rawFallback?: string) => {\n\t\tconst value = resolve(rawName.trim())\n\t\tif (value !== '') {\n\t\t\treturn value\n\t\t}\n\t\treturn rawFallback === undefined ? '' : rawFallback.trim()\n\t})\n"],"mappings":";AAAA,MAAM,QAAQ;;;;;;AAOd,MAAa,eAAe,UAAkB,YAC7C,SAAS,QAAQ,QAAQ,QAAQ,SAAiB,gBAAyB;CAC1E,MAAM,QAAQ,QAAQ,QAAQ,KAAK,CAAC;CACpC,IAAI,UAAU,IACb,OAAO;CAER,OAAO,gBAAgB,KAAA,IAAY,KAAK,YAAY,KAAK;AAC1D,CAAC"}
1
+ {"version":3,"file":"interpolate.js","names":[],"sources":["../../src/recall/interpolate.ts"],"sourcesContent":["const TOKEN = /\\{\\{\\s*([^{}|]+?)\\s*(?:\\|\\|?([^{}]*?))?\\}\\}/g\n\n/**\n * Replace `{{ name }}`, `{{ name|fallback }}`, and `{{ name||fallback }}` tokens (single and\n * double pipe are equivalent; the second pipe is just consumed, not part of the fallback text).\n * `resolve` returns the recalled value ('' when absent); the fallback (or '') is substituted when\n * `resolve` yields ''. Token-free input is returned unchanged, so this is a no-op for ordinary text.\n */\nexport const interpolate = (template: string, resolve: (name: string) => string): string =>\n\ttemplate.replace(TOKEN, (_match, rawName: string, rawFallback?: string) => {\n\t\tconst value = resolve(rawName.trim())\n\t\tif (value !== '') {\n\t\t\treturn value\n\t\t}\n\t\treturn rawFallback === undefined ? '' : rawFallback.trim()\n\t})\n"],"mappings":";AAAA,MAAM,QAAQ;;;;;;;AAQd,MAAa,eAAe,UAAkB,YAC7C,SAAS,QAAQ,QAAQ,QAAQ,SAAiB,gBAAyB;CAC1E,MAAM,QAAQ,QAAQ,QAAQ,KAAK,CAAC;CACpC,IAAI,UAAU,IACb,OAAO;CAER,OAAO,gBAAgB,KAAA,IAAY,KAAK,YAAY,KAAK;AAC1D,CAAC"}
@@ -1,5 +1,5 @@
1
- import { AnyFormFieldDefinition } from "../fields/types.js";
2
1
  import { FormFieldInstance } from "../submissions/types.js";
2
+ import { AnyFormFieldDefinition } from "../fields/types.js";
3
3
 
4
4
  //#region src/recall/resolver.d.ts
5
5
  type RecallResolver = (name: string) => string;
@@ -1,10 +1,11 @@
1
+ import { isNamedField } from "../fields/fieldKey.js";
1
2
  //#region src/recall/resolver.ts
2
3
  /** Derive a value->label map from a select-like field instance's `options`. */
3
4
  const optionLabelsFor = (field) => {
4
5
  const options = field.options;
5
6
  if (!Array.isArray(options)) return;
6
7
  const map = {};
7
- for (const option of options) if (option && typeof option.value === "string") map[option.value] = option.label ?? option.value;
8
+ for (const option of options) if (option && typeof option.value === "string") map[option.value] = option.label?.trim() ? option.label : option.value;
8
9
  return Object.keys(map).length > 0 ? map : void 0;
9
10
  };
10
11
  /**
@@ -13,7 +14,7 @@ const optionLabelsFor = (field) => {
13
14
  * value -> ''. Pure and isomorphic; the renderer and server templates share it.
14
15
  */
15
16
  const buildRecallResolver = (args) => {
16
- const byName = new Map(args.fields.map((field) => [field.name, field]));
17
+ const byName = new Map(args.fields.filter(isNamedField).map((field) => [field.name, field]));
17
18
  return (name) => {
18
19
  const field = byName.get(name);
19
20
  if (!field) return "";
@@ -30,7 +31,18 @@ const buildRecallResolver = (args) => {
30
31
  return Array.isArray(value) ? value.join(", ") : String(value);
31
32
  };
32
33
  };
34
+ /**
35
+ * Build a submission-shaped descriptor per named field: the client-side counterpart to the
36
+ * per-field descriptors `runSubmission` snapshots server-side. Lets client-rendered templates
37
+ * (the response message) label rows the same way server-rendered ones (email-team) do. `optionLabels`
38
+ * is omitted: pair with values already formatted via `buildRecallResolver`, not raw stored values.
39
+ */
40
+ const descriptorsFor = (fields) => fields.filter(isNamedField).map((field) => ({
41
+ field: field.name,
42
+ label: field.label ?? field.name,
43
+ fieldType: field.blockType
44
+ }));
33
45
  //#endregion
34
- export { buildRecallResolver, optionLabelsFor };
46
+ export { buildRecallResolver, descriptorsFor, optionLabelsFor };
35
47
 
36
48
  //# sourceMappingURL=resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.js","names":[],"sources":["../../src/recall/resolver.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../fields/types'\nimport type { FormFieldInstance } from '../submissions/types'\n\nexport type RecallResolver = (name: string) => string\n\ntype Option = { label?: string; value?: string }\n\n/** Derive a value->label map from a select-like field instance's `options`. */\nexport const optionLabelsFor = (field: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = field.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Option[]) {\n\t\tif (option && typeof option.value === 'string') {\n\t\t\tmap[option.value] = option.label ?? option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\n/**\n * Build a recall resolver: `{{name}}` -> the field's current value, formatted via its type's `format`\n * (so a select shows its option label, a checkbox Yes/No, a date localized). Missing field or empty\n * value -> ''. Pure and isomorphic; the renderer and server templates share it.\n */\nexport const buildRecallResolver = (args: {\n\tfields: FormFieldInstance[]\n\tvalues: Record<string, unknown>\n\tregistry: Map<string, AnyFormFieldDefinition>\n\tlocale: string\n\tt: (key: string) => string\n}): RecallResolver => {\n\tconst byName = new Map(args.fields.map((field) => [field.name, field]))\n\treturn (name) => {\n\t\tconst field = byName.get(name)\n\t\tif (!field) {\n\t\t\treturn ''\n\t\t}\n\t\tconst value = args.values[name]\n\t\tif (value == null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\treturn ''\n\t\t}\n\t\tconst definition = args.registry.get(field.blockType)\n\t\tif (definition?.format) {\n\t\t\treturn definition.format({\n\t\t\t\tvalue,\n\t\t\t\tconfig: {},\n\t\t\t\toptionLabels: optionLabelsFor(field),\n\t\t\t\tlocale: args.locale,\n\t\t\t\tt: args.t,\n\t\t\t})\n\t\t}\n\t\treturn Array.isArray(value) ? value.join(', ') : String(value)\n\t}\n}\n"],"mappings":";;AAQA,MAAa,mBAAmB,UAAiE;CAChG,MAAM,UAAU,MAAM;CACtB,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB;CAED,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,UAAU,SACpB,IAAI,UAAU,OAAO,OAAO,UAAU,UACrC,IAAI,OAAO,SAAS,OAAO,SAAS,OAAO;CAG7C,OAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM,KAAA;AAC5C;;;;;;AAOA,MAAa,uBAAuB,SAMd;CACrB,MAAM,SAAS,IAAI,IAAI,KAAK,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CACtE,QAAQ,SAAS;EAChB,MAAM,QAAQ,OAAO,IAAI,IAAI;EAC7B,IAAI,CAAC,OACJ,OAAO;EAER,MAAM,QAAQ,KAAK,OAAO;EAC1B,IAAI,SAAS,QAAQ,UAAU,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAC9E,OAAO;EAER,MAAM,aAAa,KAAK,SAAS,IAAI,MAAM,SAAS;EACpD,IAAI,YAAY,QACf,OAAO,WAAW,OAAO;GACxB;GACA,QAAQ,CAAC;GACT,cAAc,gBAAgB,KAAK;GACnC,QAAQ,KAAK;GACb,GAAG,KAAK;EACT,CAAC;EAEF,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK;CAC9D;AACD"}
1
+ {"version":3,"file":"resolver.js","names":[],"sources":["../../src/recall/resolver.ts"],"sourcesContent":["import { isNamedField } from '../fields/fieldKey'\nimport type { AnyFormFieldDefinition } from '../fields/types'\nimport type { FormFieldInstance, SubmissionDescriptor } from '../submissions/types'\n\nexport type RecallResolver = (name: string) => string\n\ntype Option = { label?: string; value?: string }\n\n/** Derive a value->label map from a select-like field instance's `options`. */\nexport const optionLabelsFor = (field: FormFieldInstance): Record<string, string> | undefined => {\n\tconst options = field.options\n\tif (!Array.isArray(options)) {\n\t\treturn undefined\n\t}\n\tconst map: Record<string, string> = {}\n\tfor (const option of options as Option[]) {\n\t\tif (option && typeof option.value === 'string') {\n\t\t\tmap[option.value] = option.label?.trim() ? option.label : option.value\n\t\t}\n\t}\n\treturn Object.keys(map).length > 0 ? map : undefined\n}\n\n/**\n * Build a recall resolver: `{{name}}` -> the field's current value, formatted via its type's `format`\n * (so a select shows its option label, a checkbox Yes/No, a date localized). Missing field or empty\n * value -> ''. Pure and isomorphic; the renderer and server templates share it.\n */\nexport const buildRecallResolver = (args: {\n\tfields: FormFieldInstance[]\n\tvalues: Record<string, unknown>\n\tregistry: Map<string, AnyFormFieldDefinition>\n\tlocale: string\n\tt: (key: string) => string\n}): RecallResolver => {\n\tconst byName = new Map(args.fields.filter(isNamedField).map((field) => [field.name, field]))\n\treturn (name) => {\n\t\tconst field = byName.get(name)\n\t\tif (!field) {\n\t\t\treturn ''\n\t\t}\n\t\tconst value = args.values[name]\n\t\tif (value == null || value === '' || (Array.isArray(value) && value.length === 0)) {\n\t\t\treturn ''\n\t\t}\n\t\tconst definition = args.registry.get(field.blockType)\n\t\tif (definition?.format) {\n\t\t\treturn definition.format({\n\t\t\t\tvalue,\n\t\t\t\tconfig: {},\n\t\t\t\toptionLabels: optionLabelsFor(field),\n\t\t\t\tlocale: args.locale,\n\t\t\t\tt: args.t,\n\t\t\t})\n\t\t}\n\t\treturn Array.isArray(value) ? value.join(', ') : String(value)\n\t}\n}\n\n/**\n * Build a submission-shaped descriptor per named field: the client-side counterpart to the\n * per-field descriptors `runSubmission` snapshots server-side. Lets client-rendered templates\n * (the response message) label rows the same way server-rendered ones (email-team) do. `optionLabels`\n * is omitted: pair with values already formatted via `buildRecallResolver`, not raw stored values.\n */\nexport const descriptorsFor = (fields: FormFieldInstance[]): SubmissionDescriptor[] =>\n\tfields.filter(isNamedField).map((field) => ({\n\t\tfield: field.name,\n\t\tlabel: field.label ?? field.name,\n\t\tfieldType: field.blockType,\n\t}))\n"],"mappings":";;;AASA,MAAa,mBAAmB,UAAiE;CAChG,MAAM,UAAU,MAAM;CACtB,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB;CAED,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,UAAU,SACpB,IAAI,UAAU,OAAO,OAAO,UAAU,UACrC,IAAI,OAAO,SAAS,OAAO,OAAO,KAAK,IAAI,OAAO,QAAQ,OAAO;CAGnE,OAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM,KAAA;AAC5C;;;;;;AAOA,MAAa,uBAAuB,SAMd;CACrB,MAAM,SAAS,IAAI,IAAI,KAAK,OAAO,OAAO,YAAY,EAAE,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CAC3F,QAAQ,SAAS;EAChB,MAAM,QAAQ,OAAO,IAAI,IAAI;EAC7B,IAAI,CAAC,OACJ,OAAO;EAER,MAAM,QAAQ,KAAK,OAAO;EAC1B,IAAI,SAAS,QAAQ,UAAU,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAC9E,OAAO;EAER,MAAM,aAAa,KAAK,SAAS,IAAI,MAAM,SAAS;EACpD,IAAI,YAAY,QACf,OAAO,WAAW,OAAO;GACxB;GACA,QAAQ,CAAC;GACT,cAAc,gBAAgB,KAAK;GACnC,QAAQ,KAAK;GACb,GAAG,KAAK;EACT,CAAC;EAEF,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK;CAC9D;AACD;;;;;;;AAQA,MAAa,kBAAkB,WAC9B,OAAO,OAAO,YAAY,EAAE,KAAK,WAAW;CAC3C,OAAO,MAAM;CACb,OAAO,MAAM,SAAS,MAAM;CAC5B,WAAW,MAAM;AAClB,EAAE"}
@@ -2,9 +2,10 @@ import { CaptchaProvider } from "./types.js";
2
2
 
3
3
  //#region src/spam/captcha.d.ts
4
4
  /**
5
- * Identity helper for authoring a captcha provider (mirrors `defineAction`/`defineConsentSource`).
6
- * v1 ships no built-in provider; a project supplies one (Turnstile/reCAPTCHA/hCaptcha) and the submit
7
- * path verifies a token carried on the submission. Prebuilt providers are a v1.x addition.
5
+ * Identity helper for authoring a captcha provider (mirrors `defineAction`/`definePollOptionSource`).
6
+ * Bundled adapters (`turnstileProvider`, `recaptchaProvider`, `hcaptchaProvider`) are built with it;
7
+ * use it directly for any other verification service. The submit path verifies a token carried on
8
+ * the submission.
8
9
  */
9
10
  declare const defineCaptchaProvider: (provider: CaptchaProvider) => CaptchaProvider;
10
11
  //#endregion
@@ -1,8 +1,9 @@
1
1
  //#region src/spam/captcha.ts
2
2
  /**
3
- * Identity helper for authoring a captcha provider (mirrors `defineAction`/`defineConsentSource`).
4
- * v1 ships no built-in provider; a project supplies one (Turnstile/reCAPTCHA/hCaptcha) and the submit
5
- * path verifies a token carried on the submission. Prebuilt providers are a v1.x addition.
3
+ * Identity helper for authoring a captcha provider (mirrors `defineAction`/`definePollOptionSource`).
4
+ * Bundled adapters (`turnstileProvider`, `recaptchaProvider`, `hcaptchaProvider`) are built with it;
5
+ * use it directly for any other verification service. The submit path verifies a token carried on
6
+ * the submission.
6
7
  */
7
8
  const defineCaptchaProvider = (provider) => provider;
8
9
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"captcha.js","names":[],"sources":["../../src/spam/captcha.ts"],"sourcesContent":["import type { CaptchaProvider } from './types'\n\n/**\n * Identity helper for authoring a captcha provider (mirrors `defineAction`/`defineConsentSource`).\n * v1 ships no built-in provider; a project supplies one (Turnstile/reCAPTCHA/hCaptcha) and the submit\n * path verifies a token carried on the submission. Prebuilt providers are a v1.x addition.\n */\nexport const defineCaptchaProvider = (provider: CaptchaProvider): CaptchaProvider => provider\n"],"mappings":";;;;;;AAOA,MAAa,yBAAyB,aAA+C"}
1
+ {"version":3,"file":"captcha.js","names":[],"sources":["../../src/spam/captcha.ts"],"sourcesContent":["import type { CaptchaProvider } from './types'\n\n/**\n * Identity helper for authoring a captcha provider (mirrors `defineAction`/`definePollOptionSource`).\n * Bundled adapters (`turnstileProvider`, `recaptchaProvider`, `hcaptchaProvider`) are built with it;\n * use it directly for any other verification service. The submit path verifies a token carried on\n * the submission.\n */\nexport const defineCaptchaProvider = (provider: CaptchaProvider): CaptchaProvider => provider\n"],"mappings":";;;;;;;AAQA,MAAa,yBAAyB,aAA+C"}
@@ -0,0 +1,9 @@
1
+ //#region src/spam/clientIp.ts
2
+ /** First hop of a comma-separated proxy header (the client, per X-Forwarded-For convention), or undefined. */
3
+ const firstHop = (headers, header) => {
4
+ return headers?.get(header)?.split(",")[0]?.trim() || void 0;
5
+ };
6
+ //#endregion
7
+ export { firstHop };
8
+
9
+ //# sourceMappingURL=clientIp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientIp.js","names":[],"sources":["../../src/spam/clientIp.ts"],"sourcesContent":["/** First hop of a comma-separated proxy header (the client, per X-Forwarded-For convention), or undefined. */\nexport const firstHop = (headers: Headers | undefined, header: string): string | undefined => {\n\tconst first = headers?.get(header)?.split(',')[0]?.trim()\n\treturn first || undefined\n}\n"],"mappings":";;AACA,MAAa,YAAY,SAA8B,WAAuC;CAE7F,OADc,SAAS,IAAI,MAAM,GAAG,MAAM,GAAG,EAAE,IAAI,KAAK,KACxC,KAAA;AACjB"}
@@ -7,9 +7,21 @@
7
7
  const DEFAULT_HONEYPOT_FIELD = "website";
8
8
  /** Reserved `values` entry carrying a captcha token from the client. */
9
9
  const CAPTCHA_TOKEN_KEY = "__fb_captcha";
10
+ /**
11
+ * Reserved `values` entry carrying a signed form-context token (see `context/formContext.ts`). Rides
12
+ * alongside the answers like the captcha token, but is verified and stored separately: it is never an
13
+ * answer and must not appear in `values`, the answers view, or `{{*}}` body output.
14
+ */
15
+ const CONTEXT_KEY = "__fb_ctx";
16
+ /**
17
+ * Reserved `values` entry the honeypot decoy is submitted under. The decoy's DOM input keeps a
18
+ * plausible cosmetic name (e.g. `website`), but the submitted value rides this fixed key so a real
19
+ * field sharing that name is never stripped or mistaken for the decoy.
20
+ */
21
+ const HONEYPOT_VALUE_KEY = "__fb_hp";
10
22
  /** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */
11
23
  const IDENTITY_CONTEXT_KEY = "formBuilderSpamIdentity";
12
24
  //#endregion
13
- export { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, IDENTITY_CONTEXT_KEY };
25
+ export { CAPTCHA_TOKEN_KEY, CONTEXT_KEY, DEFAULT_HONEYPOT_FIELD, HONEYPOT_VALUE_KEY, IDENTITY_CONTEXT_KEY };
14
26
 
15
27
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../src/spam/constants.ts"],"sourcesContent":["/**\n * Default honeypot decoy field name. \"website\" is plausible to bots but doesn't trigger Chrome's\n * email-address autofill (unlike names containing \"email\"). Bots fill every input; real users\n * never see the hidden field.\n */\nexport const DEFAULT_HONEYPOT_FIELD = 'website'\n\n/** Reserved `values` entry carrying a captcha token from the client. */\nexport const CAPTCHA_TOKEN_KEY = '__fb_captcha'\n\n/** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */\nexport const IDENTITY_CONTEXT_KEY = 'formBuilderSpamIdentity'\n"],"mappings":";;;;;;AAKA,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;AAGjC,MAAa,uBAAuB"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../src/spam/constants.ts"],"sourcesContent":["/**\n * Default honeypot decoy field name. \"website\" is plausible to bots but doesn't trigger Chrome's\n * email-address autofill (unlike names containing \"email\"). Bots fill every input; real users\n * never see the hidden field.\n */\nexport const DEFAULT_HONEYPOT_FIELD = 'website'\n\n/** Reserved `values` entry carrying a captcha token from the client. */\nexport const CAPTCHA_TOKEN_KEY = '__fb_captcha'\n\n/**\n * Reserved `values` entry carrying a signed form-context token (see `context/formContext.ts`). Rides\n * alongside the answers like the captcha token, but is verified and stored separately: it is never an\n * answer and must not appear in `values`, the answers view, or `{{*}}` body output.\n */\nexport const CONTEXT_KEY = '__fb_ctx'\n\n/**\n * Reserved `values` entry the honeypot decoy is submitted under. The decoy's DOM input keeps a\n * plausible cosmetic name (e.g. `website`), but the submitted value rides this fixed key so a real\n * field sharing that name is never stripped or mistaken for the decoy.\n */\nexport const HONEYPOT_VALUE_KEY = '__fb_hp'\n\n/** `req.context` key under which the spam guard stashes the resolved identity for upload-ownership verification. */\nexport const IDENTITY_CONTEXT_KEY = 'formBuilderSpamIdentity'\n"],"mappings":";;;;;;AAKA,MAAa,yBAAyB;;AAGtC,MAAa,oBAAoB;;;;;;AAOjC,MAAa,cAAc;;;;;;AAO3B,MAAa,qBAAqB;;AAGlC,MAAa,uBAAuB"}
@@ -1,3 +1,4 @@
1
+ import { firstHop } from "./clientIp.js";
1
2
  //#region src/spam/identify.ts
2
3
  /**
3
4
  * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id
@@ -7,12 +8,8 @@
7
8
  const defaultIdentify = (ipHeader) => (req) => {
8
9
  const userId = req.user?.id;
9
10
  if (userId != null) return `user:${String(userId)}`;
10
- const header = req.headers?.get(ipHeader);
11
- if (header) {
12
- const first = header.split(",")[0]?.trim();
13
- if (first) return `ip:${first}`;
14
- }
15
- return null;
11
+ const ip = firstHop(req.headers, ipHeader);
12
+ return ip ? `ip:${ip}` : null;
16
13
  };
17
14
  //#endregion
18
15
  export { defaultIdentify };
@@ -1 +1 @@
1
- {"version":3,"file":"identify.js","names":[],"sources":["../../src/spam/identify.ts"],"sourcesContent":["import type { IdentifyFn } from './types'\n\n/**\n * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id\n * (trustworthy); else the first hop of the configured trusted IP header (best-effort, proxy-dependent);\n * else null, in which case the caller fails open (cannot fairly rate-limit an unidentifiable client).\n */\nexport const defaultIdentify =\n\t(ipHeader: string): IdentifyFn =>\n\t(req) => {\n\t\tconst userId = req.user?.id\n\t\tif (userId != null) {\n\t\t\treturn `user:${String(userId)}`\n\t\t}\n\t\tconst header = req.headers?.get(ipHeader)\n\t\tif (header) {\n\t\t\tconst first = header.split(',')[0]?.trim()\n\t\t\tif (first) {\n\t\t\t\treturn `ip:${first}`\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n"],"mappings":";;;;;;AAOA,MAAa,mBACX,cACA,QAAQ;CACR,MAAM,SAAS,IAAI,MAAM;CACzB,IAAI,UAAU,MACb,OAAO,QAAQ,OAAO,MAAM;CAE7B,MAAM,SAAS,IAAI,SAAS,IAAI,QAAQ;CACxC,IAAI,QAAQ;EACX,MAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,IAAI,KAAK;EACzC,IAAI,OACH,OAAO,MAAM;CAEf;CACA,OAAO;AACR"}
1
+ {"version":3,"file":"identify.js","names":[],"sources":["../../src/spam/identify.ts"],"sourcesContent":["import { firstHop } from './clientIp'\nimport type { IdentifyFn } from './types'\n\n/**\n * Default identity resolution for rate-limiting + upload ownership. Prefers an authenticated user id\n * (trustworthy); else the first hop of the configured trusted IP header (best-effort, proxy-dependent);\n * else null, in which case the caller fails open (cannot fairly rate-limit an unidentifiable client).\n */\nexport const defaultIdentify =\n\t(ipHeader: string): IdentifyFn =>\n\t(req) => {\n\t\tconst userId = req.user?.id\n\t\tif (userId != null) {\n\t\t\treturn `user:${String(userId)}`\n\t\t}\n\t\tconst ip = firstHop(req.headers, ipHeader)\n\t\treturn ip ? `ip:${ip}` : null\n\t}\n"],"mappings":";;;;;;;AAQA,MAAa,mBACX,cACA,QAAQ;CACR,MAAM,SAAS,IAAI,MAAM;CACzB,IAAI,UAAU,MACb,OAAO,QAAQ,OAAO,MAAM;CAE7B,MAAM,KAAK,SAAS,IAAI,SAAS,QAAQ;CACzC,OAAO,KAAK,MAAM,OAAO;AAC1B"}