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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +45 -1402
  3. package/dist/actions/buildActionBlocks.js +19 -0
  4. package/dist/actions/buildActionBlocks.js.map +1 -0
  5. package/dist/actions/builtin/confirmation.js +49 -0
  6. package/dist/actions/builtin/confirmation.js.map +1 -0
  7. package/dist/actions/builtin/emailTeam.js +46 -0
  8. package/dist/actions/builtin/emailTeam.js.map +1 -0
  9. package/dist/actions/builtin/index.d.ts +7 -0
  10. package/dist/actions/builtin/index.js +13 -0
  11. package/dist/actions/builtin/index.js.map +1 -0
  12. package/dist/actions/builtin/signedWebhook.js +49 -0
  13. package/dist/actions/builtin/signedWebhook.js.map +1 -0
  14. package/dist/actions/defineAction.d.ts +36 -0
  15. package/dist/actions/defineAction.js +6 -0
  16. package/dist/actions/defineAction.js.map +1 -0
  17. package/dist/actions/dispatch.js +48 -0
  18. package/dist/actions/dispatch.js.map +1 -0
  19. package/dist/actions/registry.d.ts +16 -0
  20. package/dist/actions/registry.js +16 -0
  21. package/dist/actions/registry.js.map +1 -0
  22. package/dist/actions/runActions.d.ts +9 -0
  23. package/dist/actions/runActions.js +34 -0
  24. package/dist/actions/runActions.js.map +1 -0
  25. package/dist/actions/sign.d.ts +7 -0
  26. package/dist/actions/sign.js +9 -0
  27. package/dist/actions/sign.js.map +1 -0
  28. package/dist/actions/task.js +82 -0
  29. package/dist/actions/task.js.map +1 -0
  30. package/dist/aggregation/aggregateResponses.d.ts +31 -0
  31. package/dist/aggregation/aggregateResponses.js +86 -0
  32. package/dist/aggregation/aggregateResponses.js.map +1 -0
  33. package/dist/aggregation/aggregateRows.d.ts +10 -0
  34. package/dist/aggregation/aggregateRows.js +64 -0
  35. package/dist/aggregation/aggregateRows.js.map +1 -0
  36. package/dist/aggregation/resolveResultsRequest.d.ts +32 -0
  37. package/dist/aggregation/resolveResultsRequest.js +56 -0
  38. package/dist/aggregation/resolveResultsRequest.js.map +1 -0
  39. package/dist/aggregation/types.d.ts +39 -0
  40. package/dist/calc/computeCalcFields.d.ts +15 -0
  41. package/dist/calc/computeCalcFields.js +27 -0
  42. package/dist/calc/computeCalcFields.js.map +1 -0
  43. package/dist/calc/evaluate.d.ts +8 -0
  44. package/dist/calc/evaluate.js +59 -0
  45. package/dist/calc/evaluate.js.map +1 -0
  46. package/dist/calc/normalizeCalc.d.ts +8 -0
  47. package/dist/calc/normalizeCalc.js +100 -0
  48. package/dist/calc/normalizeCalc.js.map +1 -0
  49. package/dist/calc/types.d.ts +28 -0
  50. package/dist/client/ConditionBuilder.js +121 -0
  51. package/dist/client/ConditionBuilder.js.map +1 -0
  52. package/dist/client/ConditionRow.js +150 -0
  53. package/dist/client/ConditionRow.js.map +1 -0
  54. package/dist/client/FlowBuilder.d.ts +15 -0
  55. package/dist/client/FlowBuilder.js +408 -0
  56. package/dist/client/FlowBuilder.js.map +1 -0
  57. package/dist/client/FormConditionField.d.ts +21 -0
  58. package/dist/client/FormConditionField.js +33 -0
  59. package/dist/client/FormConditionField.js.map +1 -0
  60. package/dist/client/synthesizeClientField.js +46 -0
  61. package/dist/client/synthesizeClientField.js.map +1 -0
  62. package/dist/collections/formSubmissions.js +157 -0
  63. package/dist/collections/formSubmissions.js.map +1 -0
  64. package/dist/collections/forms.js +198 -0
  65. package/dist/collections/forms.js.map +1 -0
  66. package/dist/collections/uploads.d.ts +30 -0
  67. package/dist/collections/uploads.js +55 -0
  68. package/dist/collections/uploads.js.map +1 -0
  69. package/dist/conditions/conditionType.js +14 -0
  70. package/dist/conditions/conditionType.js.map +1 -0
  71. package/dist/conditions/evaluate.d.ts +17 -0
  72. package/dist/conditions/evaluate.js +94 -0
  73. package/dist/conditions/evaluate.js.map +1 -0
  74. package/dist/conditions/fieldTypes.d.ts +9 -0
  75. package/dist/{fieldTypes-CzhhJXjg.js → conditions/fieldTypes.js} +2 -2
  76. package/dist/conditions/fieldTypes.js.map +1 -0
  77. package/dist/conditions/normalizeConditions.js +47 -0
  78. package/dist/conditions/normalizeConditions.js.map +1 -0
  79. package/dist/conditions/types.d.ts +8 -0
  80. package/dist/consent/buildConsentSourceConfig.js +49 -0
  81. package/dist/consent/buildConsentSourceConfig.js.map +1 -0
  82. package/dist/consent/builtin/index.d.ts +7 -0
  83. package/dist/consent/builtin/index.js +11 -0
  84. package/dist/consent/builtin/index.js.map +1 -0
  85. package/dist/consent/builtin/pageReference.js +75 -0
  86. package/dist/consent/builtin/pageReference.js.map +1 -0
  87. package/dist/consent/builtin/static.js +44 -0
  88. package/dist/consent/builtin/static.js.map +1 -0
  89. package/dist/consent/captureConsent.d.ts +28 -0
  90. package/dist/consent/captureConsent.js +26 -0
  91. package/dist/consent/captureConsent.js.map +1 -0
  92. package/dist/consent/defineConsentSource.d.ts +35 -0
  93. package/dist/consent/defineConsentSource.js +6 -0
  94. package/dist/consent/defineConsentSource.js.map +1 -0
  95. package/dist/consent/registry.d.ts +16 -0
  96. package/dist/consent/registry.js +16 -0
  97. package/dist/consent/registry.js.map +1 -0
  98. package/dist/consent/resolveConsentLinks.d.ts +19 -0
  99. package/dist/consent/resolveConsentLinks.js +26 -0
  100. package/dist/consent/resolveConsentLinks.js.map +1 -0
  101. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -0
  102. package/dist/consent/resolvePublishedVersionRef.js +27 -0
  103. package/dist/consent/resolvePublishedVersionRef.js.map +1 -0
  104. package/dist/events/noopSink.js +6 -0
  105. package/dist/events/noopSink.js.map +1 -0
  106. package/dist/events/resolveEventSink.js +8 -0
  107. package/dist/events/resolveEventSink.js.map +1 -0
  108. package/dist/events/types.d.ts +31 -0
  109. package/dist/exports/client.d.ts +3 -21
  110. package/dist/exports/client.js +3 -319
  111. package/dist/exports/i18n.d.ts +3 -138
  112. package/dist/exports/i18n.js +2 -2
  113. package/dist/exports/react.d.ts +45 -550
  114. package/dist/exports/react.js +39 -1563
  115. package/dist/exports/rsc.d.ts +2 -16
  116. package/dist/exports/rsc.js +1 -54
  117. package/dist/exports/types.d.ts +28 -5
  118. package/dist/fields/buildFieldBlocks.js +60 -0
  119. package/dist/fields/buildFieldBlocks.js.map +1 -0
  120. package/dist/fields/builtin/calculation.js +24 -0
  121. package/dist/fields/builtin/calculation.js.map +1 -0
  122. package/dist/fields/builtin/checkbox.js +13 -0
  123. package/dist/fields/builtin/checkbox.js.map +1 -0
  124. package/dist/fields/builtin/consent.js +32 -0
  125. package/dist/fields/builtin/consent.js.map +1 -0
  126. package/dist/fields/builtin/email.js +18 -0
  127. package/dist/fields/builtin/email.js.map +1 -0
  128. package/dist/fields/builtin/file.js +40 -0
  129. package/dist/fields/builtin/file.js.map +1 -0
  130. package/dist/fields/builtin/index.js +27 -0
  131. package/dist/fields/builtin/index.js.map +1 -0
  132. package/dist/fields/builtin/number.js +17 -0
  133. package/dist/fields/builtin/number.js.map +1 -0
  134. package/dist/fields/builtin/repeater.js +44 -0
  135. package/dist/fields/builtin/repeater.js.map +1 -0
  136. package/dist/fields/builtin/select.js +44 -0
  137. package/dist/fields/builtin/select.js.map +1 -0
  138. package/dist/fields/builtin/text.js +13 -0
  139. package/dist/fields/builtin/text.js.map +1 -0
  140. package/dist/fields/builtin/textarea.js +13 -0
  141. package/dist/fields/builtin/textarea.js.map +1 -0
  142. package/dist/fields/defineFormField.d.ts +13 -0
  143. package/dist/fields/defineFormField.js +12 -0
  144. package/dist/fields/defineFormField.js.map +1 -0
  145. package/dist/fields/registry.d.ts +10 -0
  146. package/dist/fields/registry.js +24 -0
  147. package/dist/fields/registry.js.map +1 -0
  148. package/dist/fields/sharedConfig.js +89 -0
  149. package/dist/fields/sharedConfig.js.map +1 -0
  150. package/dist/fields/types.d.ts +89 -0
  151. package/dist/flow/engine.d.ts +17 -0
  152. package/dist/flow/engine.js +24 -0
  153. package/dist/flow/engine.js.map +1 -0
  154. package/dist/flow/normalizeFlow.js +35 -0
  155. package/dist/flow/normalizeFlow.js.map +1 -0
  156. package/dist/{types-DsJ_6kGJ.d.ts → flow/types.d.ts} +2 -2
  157. package/dist/index.d.ts +93 -4
  158. package/dist/index.js +43 -1890
  159. package/dist/index.js.map +1 -1
  160. package/dist/plugin/access.js +12 -0
  161. package/dist/plugin/access.js.map +1 -0
  162. package/dist/plugin/collectionOverrides.d.ts +22 -0
  163. package/dist/plugin/registerCollections.js +68 -0
  164. package/dist/plugin/registerCollections.js.map +1 -0
  165. package/dist/plugin/registerTranslations.js +19 -0
  166. package/dist/plugin/registerTranslations.js.map +1 -0
  167. package/dist/prefill/valuesFromSearchParams.d.ts +18 -0
  168. package/dist/prefill/valuesFromSearchParams.js +45 -0
  169. package/dist/prefill/valuesFromSearchParams.js.map +1 -0
  170. package/dist/presentations/defaults.d.ts +33 -0
  171. package/dist/presentations/defaults.js +35 -0
  172. package/dist/presentations/defaults.js.map +1 -0
  173. package/dist/presentations/registry.d.ts +15 -0
  174. package/dist/presentations/registry.js +15 -0
  175. package/dist/presentations/registry.js.map +1 -0
  176. package/dist/presentations/types.d.ts +18 -0
  177. package/dist/react/Form.d.ts +110 -0
  178. package/dist/react/Form.js +499 -0
  179. package/dist/react/Form.js.map +1 -0
  180. package/dist/react/FormContext.d.ts +16 -0
  181. package/dist/react/FormContext.js +14 -0
  182. package/dist/react/FormContext.js.map +1 -0
  183. package/dist/react/FormLayout.d.ts +20 -0
  184. package/dist/react/FormLayout.js +14 -0
  185. package/dist/react/FormLayout.js.map +1 -0
  186. package/dist/react/FormResults.d.ts +25 -0
  187. package/dist/react/FormResults.js +85 -0
  188. package/dist/react/FormResults.js.map +1 -0
  189. package/dist/react/Honeypot.d.ts +22 -0
  190. package/dist/react/Honeypot.js +38 -0
  191. package/dist/react/Honeypot.js.map +1 -0
  192. package/dist/react/Poll.d.ts +32 -0
  193. package/dist/react/Poll.js +77 -0
  194. package/dist/react/Poll.js.map +1 -0
  195. package/dist/react/cn.d.ts +6 -0
  196. package/dist/react/cn.js +8 -0
  197. package/dist/react/cn.js.map +1 -0
  198. package/dist/react/contract.d.ts +32 -0
  199. package/dist/react/contract.js +7 -0
  200. package/dist/react/contract.js.map +1 -0
  201. package/dist/react/events.js +14 -0
  202. package/dist/react/events.js.map +1 -0
  203. package/dist/react/fetchResults.d.ts +25 -0
  204. package/dist/react/fetchResults.js +31 -0
  205. package/dist/react/fetchResults.js.map +1 -0
  206. package/dist/react/presentation/Backdrop.d.ts +18 -0
  207. package/dist/react/presentation/Backdrop.js +14 -0
  208. package/dist/react/presentation/Backdrop.js.map +1 -0
  209. package/dist/react/presentation/DialogSurface.d.ts +35 -0
  210. package/dist/react/presentation/DialogSurface.js +56 -0
  211. package/dist/react/presentation/DialogSurface.js.map +1 -0
  212. package/dist/react/presentation/presentations.d.ts +7 -0
  213. package/dist/react/presentation/presentations.js +32 -0
  214. package/dist/react/presentation/presentations.js.map +1 -0
  215. package/dist/react/presentation/registry.d.ts +11 -0
  216. package/dist/react/presentation/registry.js +12 -0
  217. package/dist/react/presentation/registry.js.map +1 -0
  218. package/dist/react/presentation/types.d.ts +19 -0
  219. package/dist/react/presentation/useDismiss.d.ts +19 -0
  220. package/dist/react/presentation/useDismiss.js +33 -0
  221. package/dist/react/presentation/useDismiss.js.map +1 -0
  222. package/dist/react/presentation/useFocusTrap.d.ts +8 -0
  223. package/dist/react/presentation/useFocusTrap.js +35 -0
  224. package/dist/react/presentation/useFocusTrap.js.map +1 -0
  225. package/dist/react/presentation/useScrollLock.d.ts +6 -0
  226. package/dist/react/presentation/useScrollLock.js +18 -0
  227. package/dist/react/presentation/useScrollLock.js.map +1 -0
  228. package/dist/react/primitives/Checkbox.d.ts +26 -0
  229. package/dist/react/primitives/Checkbox.js +20 -0
  230. package/dist/react/primitives/Checkbox.js.map +1 -0
  231. package/dist/react/primitives/FieldShell.d.ts +31 -0
  232. package/dist/react/primitives/FieldShell.js +51 -0
  233. package/dist/react/primitives/FieldShell.js.map +1 -0
  234. package/dist/react/primitives/Input.d.ts +30 -0
  235. package/dist/react/primitives/Input.js +21 -0
  236. package/dist/react/primitives/Input.js.map +1 -0
  237. package/dist/react/primitives/Select.d.ts +34 -0
  238. package/dist/react/primitives/Select.js +26 -0
  239. package/dist/react/primitives/Select.js.map +1 -0
  240. package/dist/react/primitives/Textarea.d.ts +28 -0
  241. package/dist/react/primitives/Textarea.js +20 -0
  242. package/dist/react/primitives/Textarea.js.map +1 -0
  243. package/dist/react/recall.js +20 -0
  244. package/dist/react/recall.js.map +1 -0
  245. package/dist/react/registry.d.ts +16 -0
  246. package/dist/react/registry.js +16 -0
  247. package/dist/react/registry.js.map +1 -0
  248. package/dist/react/renderers/calculation.js +30 -0
  249. package/dist/react/renderers/calculation.js.map +1 -0
  250. package/dist/react/renderers/checkbox.js +35 -0
  251. package/dist/react/renderers/checkbox.js.map +1 -0
  252. package/dist/react/renderers/consent.js +54 -0
  253. package/dist/react/renderers/consent.js.map +1 -0
  254. package/dist/react/renderers/email.js +37 -0
  255. package/dist/react/renderers/email.js.map +1 -0
  256. package/dist/react/renderers/file.js +92 -0
  257. package/dist/react/renderers/file.js.map +1 -0
  258. package/dist/react/renderers/index.d.ts +8 -0
  259. package/dist/react/renderers/index.js +28 -0
  260. package/dist/react/renderers/index.js.map +1 -0
  261. package/dist/react/renderers/number.js +40 -0
  262. package/dist/react/renderers/number.js.map +1 -0
  263. package/dist/react/renderers/repeater.js +113 -0
  264. package/dist/react/renderers/repeater.js.map +1 -0
  265. package/dist/react/renderers/select.js +38 -0
  266. package/dist/react/renderers/select.js.map +1 -0
  267. package/dist/react/renderers/text.js +37 -0
  268. package/dist/react/renderers/text.js.map +1 -0
  269. package/dist/react/renderers/textarea.js +36 -0
  270. package/dist/react/renderers/textarea.js.map +1 -0
  271. package/dist/react/resolveForm.js +16 -0
  272. package/dist/react/resolveForm.js.map +1 -0
  273. package/dist/react/state.d.ts +15 -0
  274. package/dist/react/state.js +70 -0
  275. package/dist/react/state.js.map +1 -0
  276. package/dist/react/submitForm.d.ts +31 -0
  277. package/dist/react/submitForm.js +58 -0
  278. package/dist/react/submitForm.js.map +1 -0
  279. package/dist/react/toFormDocument.d.ts +24 -0
  280. package/dist/react/toFormDocument.js +22 -0
  281. package/dist/react/toFormDocument.js.map +1 -0
  282. package/dist/react/uploadFile.d.ts +23 -0
  283. package/dist/react/uploadFile.js +39 -0
  284. package/dist/react/uploadFile.js.map +1 -0
  285. package/dist/react/useField.d.ts +14 -0
  286. package/dist/react/useField.js +49 -0
  287. package/dist/react/useField.js.map +1 -0
  288. package/dist/react/useFormState.d.ts +8 -0
  289. package/dist/react/useFormState.js +9 -0
  290. package/dist/react/useFormState.js.map +1 -0
  291. package/dist/react/useFormStep.d.ts +8 -0
  292. package/dist/react/useFormStep.js +9 -0
  293. package/dist/react/useFormStep.js.map +1 -0
  294. package/dist/react/validateField.js +30 -0
  295. package/dist/react/validateField.js.map +1 -0
  296. package/dist/recall/interpolate.d.ts +10 -0
  297. package/dist/recall/interpolate.js +16 -0
  298. package/dist/recall/interpolate.js.map +1 -0
  299. package/dist/recall/resolver.d.ts +22 -0
  300. package/dist/recall/resolver.js +36 -0
  301. package/dist/recall/resolver.js.map +1 -0
  302. package/dist/spam/captcha.d.ts +12 -0
  303. package/dist/spam/captcha.js +11 -0
  304. package/dist/spam/captcha.js.map +1 -0
  305. package/dist/spam/constants.d.ts +12 -0
  306. package/dist/spam/constants.js +15 -0
  307. package/dist/spam/constants.js.map +1 -0
  308. package/dist/spam/identify.d.ts +12 -0
  309. package/dist/spam/identify.js +20 -0
  310. package/dist/spam/identify.js.map +1 -0
  311. package/dist/spam/rateLimiter.d.ts +18 -0
  312. package/dist/spam/rateLimiter.js +41 -0
  313. package/dist/spam/rateLimiter.js.map +1 -0
  314. package/dist/spam/reserved.js +30 -0
  315. package/dist/spam/reserved.js.map +1 -0
  316. package/dist/spam/resolveSpam.d.ts +12 -0
  317. package/dist/spam/resolveSpam.js +42 -0
  318. package/dist/spam/resolveSpam.js.map +1 -0
  319. package/dist/spam/spamGuard.js +68 -0
  320. package/dist/spam/spamGuard.js.map +1 -0
  321. package/dist/spam/types.d.ts +73 -0
  322. package/dist/spam/uploadHooks.js +33 -0
  323. package/dist/spam/uploadHooks.js.map +1 -0
  324. package/dist/submissions/SubmissionAnswers.d.ts +14 -0
  325. package/dist/submissions/SubmissionAnswers.js +133 -0
  326. package/dist/submissions/SubmissionAnswers.js.map +1 -0
  327. package/dist/submissions/SubmissionAnswersClient.js +142 -0
  328. package/dist/submissions/SubmissionAnswersClient.js.map +1 -0
  329. package/dist/submissions/runSubmission.js +257 -0
  330. package/dist/submissions/runSubmission.js.map +1 -0
  331. package/dist/submissions/types.d.ts +34 -0
  332. package/dist/submissions/validateSubmission.js +59 -0
  333. package/dist/submissions/validateSubmission.js.map +1 -0
  334. package/dist/translations/en.d.ts +12 -0
  335. package/dist/{translations-edMqq_2e.js → translations/en.js} +29 -23
  336. package/dist/translations/en.js.map +1 -0
  337. package/dist/translations/index.d.ts +14 -0
  338. package/dist/translations/index.js +26 -0
  339. package/dist/translations/index.js.map +1 -0
  340. package/dist/translations/keys.d.ts +157 -0
  341. package/dist/{keys-N5xGiUsh.js → translations/keys.js} +28 -3
  342. package/dist/translations/keys.js.map +1 -0
  343. package/dist/translations/makeTranslate.d.ts +11 -0
  344. package/dist/translations/makeTranslate.js +11 -0
  345. package/dist/translations/makeTranslate.js.map +1 -0
  346. package/dist/translations/server.js +25 -0
  347. package/dist/translations/server.js.map +1 -0
  348. package/dist/translations/useTranslation.js +12 -0
  349. package/dist/translations/useTranslation.js.map +1 -0
  350. package/dist/uploads/captureFileRef.d.ts +27 -0
  351. package/dist/uploads/captureFileRef.js +34 -0
  352. package/dist/uploads/captureFileRef.js.map +1 -0
  353. package/dist/uploads/resolveFileRef.d.ts +26 -0
  354. package/dist/uploads/resolveFileRef.js +44 -0
  355. package/dist/uploads/resolveFileRef.js.map +1 -0
  356. package/dist/uploads/types.d.ts +20 -0
  357. package/dist/validation/buildRuleBlocks.js +46 -0
  358. package/dist/validation/buildRuleBlocks.js.map +1 -0
  359. package/dist/validation/builtin/email.js +19 -0
  360. package/dist/validation/builtin/email.js.map +1 -0
  361. package/dist/validation/builtin/index.js +27 -0
  362. package/dist/validation/builtin/index.js.map +1 -0
  363. package/dist/validation/builtin/matchesField.js +20 -0
  364. package/dist/validation/builtin/matchesField.js.map +1 -0
  365. package/dist/validation/builtin/max.js +21 -0
  366. package/dist/validation/builtin/max.js.map +1 -0
  367. package/dist/validation/builtin/maxLength.js +26 -0
  368. package/dist/validation/builtin/maxLength.js.map +1 -0
  369. package/dist/validation/builtin/min.js +21 -0
  370. package/dist/validation/builtin/min.js.map +1 -0
  371. package/dist/validation/builtin/minLength.js +26 -0
  372. package/dist/validation/builtin/minLength.js.map +1 -0
  373. package/dist/validation/builtin/notAlreadySubmitted.js +32 -0
  374. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -0
  375. package/dist/validation/builtin/oneOf.js +34 -0
  376. package/dist/validation/builtin/oneOf.js.map +1 -0
  377. package/dist/validation/builtin/pattern.js +41 -0
  378. package/dist/validation/builtin/pattern.js.map +1 -0
  379. package/dist/validation/builtin/url.js +22 -0
  380. package/dist/validation/builtin/url.js.map +1 -0
  381. package/dist/validation/defineValidationRule.d.ts +12 -0
  382. package/dist/validation/defineValidationRule.js +11 -0
  383. package/dist/validation/defineValidationRule.js.map +1 -0
  384. package/dist/validation/message.js +14 -0
  385. package/dist/validation/message.js.map +1 -0
  386. package/dist/validation/registry.d.ts +10 -0
  387. package/dist/validation/registry.js +20 -0
  388. package/dist/validation/registry.js.map +1 -0
  389. package/dist/validation/runValidation.js +111 -0
  390. package/dist/validation/runValidation.js.map +1 -0
  391. package/dist/validation/types.d.ts +76 -0
  392. package/package.json +8 -5
  393. package/styles/form-builder.css +201 -11
  394. package/dist/constants-B-BUfetP.d.ts +0 -288
  395. package/dist/exports/client.js.map +0 -1
  396. package/dist/exports/react.js.map +0 -1
  397. package/dist/exports/rsc.js.map +0 -1
  398. package/dist/fieldTypes-B8jkNRob.d.ts +0 -188
  399. package/dist/fieldTypes-CzhhJXjg.js.map +0 -1
  400. package/dist/index-DfFYGbVF.d.ts +0 -481
  401. package/dist/keys-N5xGiUsh.js.map +0 -1
  402. package/dist/registry-CoCyhtvB.js +0 -282
  403. package/dist/registry-CoCyhtvB.js.map +0 -1
  404. package/dist/resolver-OeQyVH2N.js +0 -665
  405. package/dist/resolver-OeQyVH2N.js.map +0 -1
  406. package/dist/translations-edMqq_2e.js.map +0 -1
@@ -1,1564 +1,40 @@
1
1
  "use client";
2
- import { c as runValidation, d as computeCalcFields, f as evaluateCalc, g as DEFAULT_HONEYPOT_FIELD, h as CAPTCHA_TOKEN_KEY, i as buildRuleRegistry, l as evaluateCondition, m as defaultPresentationDescriptors, o as defaultValidationRules, r as valuesFromSearchParams, t as buildRecallResolver, u as calcExpressionOf, v as noopEventSink, y as interpolate } from "../resolver-OeQyVH2N.js";
3
- import { t as keys } from "../keys-N5xGiUsh.js";
4
- import { r as defaultFieldDefinitions, t as buildRegistry } from "../registry-CoCyhtvB.js";
5
- import { createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useReducer, useRef, useState } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- //#region src/flow/engine.ts
8
- /** The entry step id (the first step), or `undefined` for an empty flow. */
9
- const firstStepId = (flow) => flow.steps[0]?.id;
10
- /** The step with the given id, or `undefined`. */
11
- const getStep = (flow, id) => flow.steps.find((step) => step.id === id);
12
- /** The field machine names a step renders (empty for an unknown step). */
13
- const stepFieldNames = (flow, id) => getStep(flow, id)?.fields ?? [];
14
- /**
15
- * Resolve the next step id from the current step + answers: the first transition whose `when` matches
16
- * (via `evaluateCondition`), else the default `next`, else `undefined` (terminal). Pure + isomorphic.
17
- */
18
- const resolveNextStepId = (flow, currentId, answers) => {
19
- const step = getStep(flow, currentId);
20
- if (!step) return;
21
- for (const transition of step.transitions ?? []) if (evaluateCondition(transition.when, answers)) return transition.to;
22
- return step.next;
23
- };
24
- /** Whether the current step is terminal (no matching transition + no default next). */
25
- const isTerminalStepId = (flow, currentId, answers) => resolveNextStepId(flow, currentId, answers) === void 0;
26
- //#endregion
27
- //#region src/react/contract.ts
28
- /** Identity helper that pins a renderer to the `FieldRenderer` type so authors get prop inference. */
29
- const defineFieldRenderer = (renderer) => renderer;
30
- //#endregion
31
- //#region src/react/events.ts
32
- /** Emit a lifecycle event through the sink, stamping `formId` + an ISO `at`. */
33
- const emitFormEvent = (sink, formId, event) => {
34
- sink.emit({
35
- ...event,
36
- formId,
37
- at: (/* @__PURE__ */ new Date()).toISOString()
38
- });
39
- };
40
- //#endregion
41
- //#region src/react/FormContext.ts
42
- const FormContext = createContext(null);
43
- /** Read the form controller context. Throws if used outside `<Form>`. */
44
- const useFormContext = () => {
45
- const context = useContext(FormContext);
46
- if (!context) throw new Error("useFormContext must be used within a <Form>");
47
- return context;
48
- };
49
- //#endregion
50
- //#region src/react/FormLayout.tsx
51
- /** Establishes the layout container. With the stylesheet imported, children with `data-width` flow into a grid. */
52
- const FormLayout = ({ children, enabled = true }) => /* @__PURE__ */ jsx("div", {
53
- className: enabled ? "fb-form fb-form--grid" : "fb-form",
54
- children
55
- });
56
- /** Props to spread onto a field wrapper to declare its grid width. */
57
- const widthProps = (width) => ({ "data-width": width ?? "full" });
58
- //#endregion
59
- //#region src/react/Honeypot.tsx
60
- const HIDDEN = {
61
- position: "absolute",
62
- width: 1,
63
- height: 1,
64
- padding: 0,
65
- margin: -1,
66
- overflow: "hidden",
67
- clip: "rect(0, 0, 0, 0)",
68
- whiteSpace: "nowrap",
69
- border: 0
70
- };
71
- /**
72
- * A visually hidden honeypot decoy. Real users never see or tab to it; bots that fill every input trip it,
73
- * and the server rejects the submission. Off-screen + aria-hidden + tabIndex -1 + autoComplete off.
74
- */
75
- const Honeypot = ({ name, inputRef }) => /* @__PURE__ */ jsx("div", {
76
- "aria-hidden": "true",
77
- style: HIDDEN,
78
- children: /* @__PURE__ */ jsxs("label", { children: ["Leave this field empty", /* @__PURE__ */ jsx("input", {
79
- ref: inputRef,
80
- type: "text",
81
- name,
82
- tabIndex: -1,
83
- autoComplete: "off",
84
- defaultValue: ""
85
- })] })
86
- });
87
- //#endregion
88
- //#region src/react/presentation/Backdrop.tsx
89
- /** A full-viewport backdrop behind an overlay surface. Styling comes from the `data-fb-backdrop` hook. */
90
- const Backdrop = ({ onClick, className }) => createElement("div", {
91
- "data-fb-backdrop": "",
92
- className,
93
- onClick,
94
- "aria-hidden": true
95
- });
96
- //#endregion
97
- //#region src/react/presentation/useDismiss.ts
98
- /** Dismiss-on-Escape and dismiss-on-outside-pointerdown for an overlay surface. */
99
- const useDismiss = ({ active, onDismiss, closeOnEscape = true, closeOnOutsideClick = true }, ref) => {
100
- useEffect(() => {
101
- if (!active) return;
102
- const onKeyDown = (event) => {
103
- if (closeOnEscape && event.key === "Escape") onDismiss();
104
- };
105
- const onPointerDown = (event) => {
106
- if (!closeOnOutsideClick) return;
107
- const node = ref.current;
108
- if (node && event.target instanceof Node && !node.contains(event.target)) onDismiss();
109
- };
110
- document.addEventListener("keydown", onKeyDown);
111
- document.addEventListener("pointerdown", onPointerDown);
112
- return () => {
113
- document.removeEventListener("keydown", onKeyDown);
114
- document.removeEventListener("pointerdown", onPointerDown);
115
- };
116
- }, [
117
- active,
118
- onDismiss,
119
- closeOnEscape,
120
- closeOnOutsideClick,
121
- ref
122
- ]);
123
- };
124
- //#endregion
125
- //#region src/react/presentation/useFocusTrap.ts
126
- const FOCUSABLE = "a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
127
- /** Trap Tab focus within the container while active. */
128
- const useFocusTrap = (ref, active) => {
129
- useEffect(() => {
130
- if (!active) return;
131
- const node = ref.current;
132
- if (!node) return;
133
- const onKeyDown = (event) => {
134
- if (event.key !== "Tab") return;
135
- const focusable = Array.from(node.querySelectorAll(FOCUSABLE));
136
- if (focusable.length === 0) return;
137
- const first = focusable[0];
138
- const last = focusable[focusable.length - 1];
139
- const activeEl = document.activeElement;
140
- if (event.shiftKey && activeEl === first) {
141
- event.preventDefault();
142
- last.focus();
143
- } else if (!event.shiftKey && activeEl === last) {
144
- event.preventDefault();
145
- first.focus();
146
- }
147
- };
148
- node.addEventListener("keydown", onKeyDown);
149
- return () => {
150
- node.removeEventListener("keydown", onKeyDown);
151
- };
152
- }, [ref, active]);
153
- };
154
- //#endregion
155
- //#region src/react/presentation/useScrollLock.ts
156
- /** Lock body scroll while active; restores the prior value on cleanup. */
157
- const useScrollLock = (active) => {
158
- useEffect(() => {
159
- if (!active) return;
160
- const previous = document.body.style.overflow;
161
- document.body.style.overflow = "hidden";
162
- return () => {
163
- document.body.style.overflow = previous;
164
- };
165
- }, [active]);
166
- };
167
- //#endregion
168
- //#region src/react/presentation/DialogSurface.tsx
169
- /**
170
- * Accessible, dependency-free overlay surface: backdrop + role=dialog/aria-modal, focus-trap,
171
- * scroll-lock, Escape/outside-click dismiss, initial focus, and focus restore on close. Composes the
172
- * exported primitives so a consumer can rebuild it from the same parts.
173
- */
174
- const DialogSurface = ({ open, onClose, label, labelledBy, surface, closeLabel = "Close", closeOnEscape, closeOnOutsideClick, children }) => {
175
- const ref = useRef(null);
176
- const restoreRef = useRef(null);
177
- useScrollLock(open);
178
- useFocusTrap(ref, open);
179
- useDismiss({
180
- active: open,
181
- onDismiss: onClose,
182
- closeOnEscape,
183
- closeOnOutsideClick
184
- }, ref);
185
- useEffect(() => {
186
- if (!open) return;
187
- restoreRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
188
- ref.current?.focus();
189
- return () => {
190
- restoreRef.current?.focus();
191
- };
192
- }, [open]);
193
- if (!open) return null;
194
- return createElement("div", { "data-fb-overlay": surface ?? "" }, [createElement(Backdrop, {
195
- key: "backdrop",
196
- onClick: onClose
197
- }), createElement("div", {
198
- key: "surface",
199
- ref,
200
- role: "dialog",
201
- "aria-modal": true,
202
- "aria-label": labelledBy ? void 0 : label,
203
- "aria-labelledby": labelledBy,
204
- "data-fb-dialog": surface ?? "",
205
- tabIndex: -1
206
- }, [createElement("button", {
207
- key: "close",
208
- type: "button",
209
- "data-fb-dialog-close": "",
210
- "aria-label": closeLabel,
211
- onClick: onClose
212
- }, "×"), createElement("div", { key: "body" }, children)])]);
213
- };
214
- //#endregion
215
- //#region src/react/presentation/presentations.tsx
216
- const overlayWrapper = (surface) => {
217
- const Wrapper = ({ open, onClose, title, closeLabel, children }) => createElement(DialogSurface, {
218
- open,
219
- onClose,
220
- label: title,
221
- surface,
222
- closeLabel
223
- }, children);
224
- Wrapper.displayName = `${surface}Wrapper`;
225
- return Wrapper;
226
- };
227
- const defaultPresentations = {
228
- page: defaultPresentationDescriptors.page,
229
- inline: defaultPresentationDescriptors.inline,
230
- modal: {
231
- ...defaultPresentationDescriptors.modal,
232
- Wrapper: overlayWrapper("modal")
233
- },
234
- drawer: {
235
- ...defaultPresentationDescriptors.drawer,
236
- Wrapper: overlayWrapper("drawer")
237
- }
238
- };
239
- //#endregion
240
- //#region src/react/presentation/registry.ts
241
- const resolvePresentations = (defaults = defaultPresentations, config = {}) => {
242
- const registry = new Map(Object.entries(defaults));
243
- for (const [name, option] of Object.entries(config)) if (option === false) registry.delete(name);
244
- else if (option === true) {} else registry.set(name, option);
245
- return registry;
246
- };
247
- //#endregion
248
- //#region src/react/recall.ts
249
- /**
250
- * Return a copy of the field with recall tokens interpolated in its label, description, and option
251
- * labels. Never alters `name` (identity for React keys and state lookups must stay stable).
252
- */
253
- const applyRecall = (field, resolve) => {
254
- const next = { ...field };
255
- if (typeof field.label === "string") next.label = interpolate(field.label, resolve);
256
- if (typeof field.description === "string") next.description = interpolate(field.description, resolve);
257
- if (Array.isArray(field.options)) next.options = field.options.map((option) => option && typeof option.label === "string" ? {
258
- ...option,
259
- label: interpolate(option.label, resolve)
260
- } : option);
261
- return next;
262
- };
263
- //#endregion
264
- //#region src/react/registry.ts
265
- /**
266
- * Resolve the active renderer registry from the defaults and a consumer override map. `false` removes a
267
- * type, `true` keeps the default (no-op when none exists), a renderer adds a new type or replaces one.
268
- * Mirrors the field-type and validation-rule registry convention.
269
- */
270
- const resolveRenderers = (defaults, config = {}) => {
271
- const registry = new Map(Object.entries(defaults));
272
- for (const [type, option] of Object.entries(config)) if (option === false) registry.delete(type);
273
- else if (option === true) {} else registry.set(type, option);
274
- return registry;
275
- };
276
- //#endregion
277
- //#region src/react/primitives/FieldShell.tsx
278
- /**
279
- * Accessible wrapper for a single field: a `<label>` bound to the control, the control slot, an optional
280
- * description, and error/warning messages. The control inside must set `aria-describedby={describedById}`
281
- * and `aria-invalid` when errors exist; this shell renders the matching `id={describedById}` region.
282
- */
283
- const FieldShell = ({ id, label, description, required, errors = [], warnings = [], describedById, children }) => /* @__PURE__ */ jsxs("div", {
284
- className: "fb-field",
285
- "data-invalid": errors.length > 0 ? "" : void 0,
286
- children: [
287
- label ? /* @__PURE__ */ jsxs("label", {
288
- className: "fb-field__label",
289
- htmlFor: id,
290
- children: [label, required ? /* @__PURE__ */ jsx("span", {
291
- className: "fb-field__required",
292
- "aria-hidden": "true",
293
- children: " *"
294
- }) : null]
295
- }) : null,
296
- children,
297
- /* @__PURE__ */ jsxs("div", {
298
- id: describedById,
299
- className: "fb-field__messages",
300
- children: [
301
- description ? /* @__PURE__ */ jsx("p", {
302
- className: "fb-field__description",
303
- children: description
304
- }) : null,
305
- errors.length > 0 ? /* @__PURE__ */ jsx("div", {
306
- role: "alert",
307
- "aria-atomic": "true",
308
- className: "fb-field__errors",
309
- children: errors.map((message) => /* @__PURE__ */ jsx("p", {
310
- className: "fb-field__error",
311
- children: message
312
- }, message))
313
- }) : null,
314
- warnings.map((message) => /* @__PURE__ */ jsx("p", {
315
- className: "fb-field__warning",
316
- children: message
317
- }, message))
318
- ]
319
- })
320
- ]
321
- });
322
- //#endregion
323
- //#region src/react/renderers/calculation.tsx
324
- /** Read-only renderer for a calculation field: the value is derived, never user-editable, so it shows in an `<output>` rather than an input. */
325
- const calculationRenderer = defineFieldRenderer(({ field, value, errors, warnings, required }) => {
326
- const id = useId();
327
- const describedById = `${id}-desc`;
328
- return /* @__PURE__ */ jsx(FieldShell, {
329
- id,
330
- label: field.label,
331
- description: typeof field.description === "string" ? field.description : void 0,
332
- required,
333
- errors,
334
- warnings,
335
- describedById,
336
- children: /* @__PURE__ */ jsx("output", {
337
- id,
338
- className: "fb-field__calc",
339
- "aria-describedby": describedById,
340
- children: value == null ? "" : String(value)
341
- })
342
- });
343
- });
344
- //#endregion
345
- //#region src/react/primitives/Checkbox.tsx
346
- const Checkbox = ({ id, name, checked, onChange, onBlur, required, disabled, invalid, describedById }) => /* @__PURE__ */ jsx("input", {
347
- type: "checkbox",
348
- className: "fb-checkbox",
349
- id,
350
- name,
351
- checked,
352
- required,
353
- disabled,
354
- "aria-invalid": invalid || void 0,
355
- "aria-describedby": describedById,
356
- onChange: (e) => onChange(e.target.checked),
357
- onBlur
358
- });
359
- //#endregion
360
- //#region src/react/renderers/checkbox.tsx
361
- const checkboxRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
362
- const id = useId();
363
- const describedById = `${id}-desc`;
364
- return /* @__PURE__ */ jsx(FieldShell, {
365
- id,
366
- label: field.label,
367
- description: typeof field.description === "string" ? field.description : void 0,
368
- required,
369
- errors,
370
- warnings,
371
- describedById,
372
- children: /* @__PURE__ */ jsx(Checkbox, {
373
- id,
374
- name,
375
- checked: value ?? false,
376
- onChange,
377
- onBlur,
378
- required,
379
- disabled,
380
- invalid: errors.length > 0,
381
- describedById
382
- })
383
- });
384
- });
385
- //#endregion
386
- //#region src/react/renderers/consent.tsx
387
- const consentRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
388
- const id = useId();
389
- const describedById = `${id}-desc`;
390
- const text = (raw) => typeof raw === "string" && raw.trim() !== "" ? raw : void 0;
391
- const statement = text(field.statement) ?? text(field.label);
392
- const links = Array.isArray(field.consentLinks) ? field.consentLinks : (() => {
393
- const sc = field.sourceConfig;
394
- if (sc && typeof sc.url === "string" && sc.url) return [{
395
- label: typeof sc.label === "string" ? sc.label : "Policy",
396
- url: sc.url
397
- }];
398
- return [];
399
- })();
400
- return /* @__PURE__ */ jsxs(FieldShell, {
401
- id,
402
- label: statement,
403
- description: typeof field.description === "string" ? field.description : void 0,
404
- required,
405
- errors,
406
- warnings,
407
- describedById,
408
- children: [/* @__PURE__ */ jsx(Checkbox, {
409
- id,
410
- name,
411
- checked: value ?? false,
412
- onChange,
413
- onBlur,
414
- required,
415
- disabled,
416
- invalid: errors.length > 0,
417
- describedById
418
- }), links.length > 0 ? /* @__PURE__ */ jsx("span", {
419
- className: "fb-consent__links",
420
- children: links.map((link) => /* @__PURE__ */ jsx("a", {
421
- href: link.url,
422
- target: "_blank",
423
- rel: "noopener noreferrer",
424
- className: "fb-consent__link",
425
- children: link.label
426
- }, link.url))
427
- }) : null]
428
- });
429
- });
430
- //#endregion
431
- //#region src/react/primitives/Input.tsx
432
- const Input = ({ id, name, type = "text", value, onChange, onBlur, placeholder, required, disabled, invalid, describedById }) => /* @__PURE__ */ jsx("input", {
433
- id,
434
- name,
435
- type,
436
- className: "fb-input",
437
- value,
438
- placeholder,
439
- required,
440
- disabled,
441
- "aria-invalid": invalid || void 0,
442
- "aria-describedby": describedById,
443
- onChange: (event) => onChange(event.target.value),
444
- onBlur
445
- });
446
- //#endregion
447
- //#region src/react/renderers/email.tsx
448
- const emailRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
449
- const id = useId();
450
- const describedById = `${id}-desc`;
451
- return /* @__PURE__ */ jsx(FieldShell, {
452
- id,
453
- label: field.label,
454
- description: typeof field.description === "string" ? field.description : void 0,
455
- required,
456
- errors,
457
- warnings,
458
- describedById,
459
- children: /* @__PURE__ */ jsx(Input, {
460
- id,
461
- name,
462
- type: "email",
463
- value: value ?? "",
464
- onChange,
465
- onBlur,
466
- placeholder: typeof field.placeholder === "string" ? field.placeholder : void 0,
467
- required,
468
- disabled,
469
- invalid: errors.length > 0,
470
- describedById
471
- })
472
- });
473
- });
474
- //#endregion
475
- //#region src/react/uploadFile.ts
476
- /**
477
- * Upload one file to a Payload upload collection (`POST {apiRoute}/{collection}`, multipart) and return its
478
- * id, which the file field stores as its value. The server re-derives the file metadata from the stored doc
479
- * at submit, so the client is never trusted for it. Pure: inject `fetchImpl` in tests.
480
- */
481
- const uploadFile = async (input) => {
482
- const { file, collection = "form-uploads", apiRoute = "/api", fetchImpl = fetch } = input;
483
- const body = new FormData();
484
- body.append("file", file);
485
- let response;
486
- try {
487
- response = await fetchImpl(`${apiRoute}/${collection}`, {
488
- method: "POST",
489
- body
490
- });
491
- } catch (error) {
492
- return {
493
- ok: false,
494
- message: error instanceof Error ? error.message : "Network error"
495
- };
496
- }
497
- if (!response.ok) return {
498
- ok: false,
499
- message: `Upload failed (${response.status})`
500
- };
501
- const id = (await response.json().catch(() => ({}))).doc?.id;
502
- return id == null ? {
503
- ok: false,
504
- message: "No id returned"
505
- } : {
506
- ok: true,
507
- id
508
- };
509
- };
510
- //#endregion
511
- //#region src/react/renderers/file.tsx
512
- const acceptOf = (mimeTypes) => {
513
- if (!Array.isArray(mimeTypes)) return;
514
- const types = mimeTypes.filter((entry) => typeof entry === "string");
515
- return types.length > 0 ? types.join(",") : void 0;
516
- };
517
- /**
518
- * Headless file renderer: a native file input that uploads the chosen file to the upload collection and
519
- * stores the returned id as the field value. The server re-derives filename/mimeType/filesize from the
520
- * stored doc at submit, so the client value is only the id. Shows the uploaded filename with a clear
521
- * control and surfaces upload errors. Single file (v1); the upload posts to the default `/api` route.
522
- */
523
- const fileRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
524
- const id = useId();
525
- const describedById = `${id}-desc`;
526
- const [uploading, setUploading] = useState(false);
527
- const [filename, setFilename] = useState(void 0);
528
- const [localError, setLocalError] = useState(void 0);
529
- const collection = typeof field.relationTo === "string" ? field.relationTo : "form-uploads";
530
- const accept = acceptOf(field.mimeTypes);
531
- const allErrors = localError ? [...errors, localError] : errors;
532
- const displayName = filename ?? (value !== void 0 && value !== null && value !== "" ? "Uploaded file" : void 0);
533
- const handleChange = async (event) => {
534
- const selected = event.target.files?.[0];
535
- if (!selected) return;
536
- setUploading(true);
537
- setLocalError(void 0);
538
- const result = await uploadFile({
539
- file: selected,
540
- collection
541
- });
542
- setUploading(false);
543
- if (result.ok) {
544
- setFilename(selected.name);
545
- onChange(result.id);
546
- } else setLocalError(result.message ?? "Upload failed");
547
- };
548
- const clear = () => {
549
- setFilename(void 0);
550
- setLocalError(void 0);
551
- onChange("");
552
- };
553
- return /* @__PURE__ */ jsxs(FieldShell, {
554
- id,
555
- label: field.label,
556
- description: typeof field.description === "string" ? field.description : void 0,
557
- required,
558
- errors: allErrors,
559
- warnings,
560
- describedById,
561
- children: [
562
- /* @__PURE__ */ jsx("input", {
563
- id,
564
- name,
565
- type: "file",
566
- accept,
567
- disabled: disabled || uploading,
568
- onChange: (event) => {
569
- handleChange(event);
570
- },
571
- onBlur,
572
- "aria-invalid": allErrors.length > 0,
573
- "aria-describedby": describedById
574
- }),
575
- uploading ? /* @__PURE__ */ jsx("p", {
576
- className: "fb-field__file-status",
577
- "aria-live": "polite",
578
- children: "Uploading"
579
- }) : null,
580
- displayName ? /* @__PURE__ */ jsxs("p", {
581
- className: "fb-field__file-name",
582
- children: [/* @__PURE__ */ jsx("span", { children: displayName }), /* @__PURE__ */ jsx("button", {
583
- type: "button",
584
- className: "fb-field__file-clear",
585
- onClick: clear,
586
- disabled,
587
- children: "Remove"
588
- })]
589
- }) : null
590
- ]
591
- });
592
- });
593
- //#endregion
594
- //#region src/react/renderers/number.tsx
595
- const numberRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
596
- const id = useId();
597
- const describedById = `${id}-desc`;
598
- return /* @__PURE__ */ jsx(FieldShell, {
599
- id,
600
- label: field.label,
601
- description: typeof field.description === "string" ? field.description : void 0,
602
- required,
603
- errors,
604
- warnings,
605
- describedById,
606
- children: /* @__PURE__ */ jsx(Input, {
607
- id,
608
- name,
609
- type: "number",
610
- value: value == null ? "" : String(value),
611
- onChange: (raw) => {
612
- const next = Number(raw);
613
- onChange(raw === "" || Number.isNaN(next) ? void 0 : next);
614
- },
615
- onBlur,
616
- placeholder: typeof field.placeholder === "string" ? field.placeholder : void 0,
617
- required,
618
- disabled,
619
- invalid: errors.length > 0,
620
- describedById
621
- })
622
- });
623
- });
624
- //#endregion
625
- //#region src/react/primitives/Select.tsx
626
- const Select = ({ id, name, value, options, onChange, onBlur, required, disabled, invalid, describedById, placeholder }) => /* @__PURE__ */ jsxs("select", {
627
- id,
628
- name,
629
- className: "fb-select",
630
- value,
631
- required,
632
- disabled,
633
- "aria-invalid": invalid || void 0,
634
- "aria-describedby": describedById,
635
- onChange: (event) => onChange(event.target.value),
636
- onBlur,
637
- children: [placeholder !== void 0 ? /* @__PURE__ */ jsx("option", {
638
- value: "",
639
- children: placeholder
640
- }) : null, options.map((option) => /* @__PURE__ */ jsx("option", {
641
- value: option.value,
642
- children: option.label
643
- }, option.value))]
644
- });
645
- //#endregion
646
- //#region src/react/renderers/select.tsx
647
- const selectRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
648
- const id = useId();
649
- const describedById = `${id}-desc`;
650
- const options = Array.isArray(field.options) ? field.options : [];
651
- return /* @__PURE__ */ jsx(FieldShell, {
652
- id,
653
- label: field.label,
654
- description: typeof field.description === "string" ? field.description : void 0,
655
- required,
656
- errors,
657
- warnings,
658
- describedById,
659
- children: /* @__PURE__ */ jsx(Select, {
660
- id,
661
- name,
662
- value: value ?? "",
663
- options,
664
- onChange,
665
- onBlur,
666
- placeholder: typeof field.placeholder === "string" ? field.placeholder : void 0,
667
- required,
668
- disabled,
669
- invalid: errors.length > 0,
670
- describedById
671
- })
672
- });
673
- });
674
- //#endregion
675
- //#region src/react/renderers/text.tsx
676
- const textRenderer = defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
677
- const id = useId();
678
- const describedById = `${id}-desc`;
679
- return /* @__PURE__ */ jsx(FieldShell, {
680
- id,
681
- label: field.label,
682
- description: typeof field.description === "string" ? field.description : void 0,
683
- required,
684
- errors,
685
- warnings,
686
- describedById,
687
- children: /* @__PURE__ */ jsx(Input, {
688
- id,
689
- name,
690
- type: "text",
691
- value: value ?? "",
692
- onChange,
693
- onBlur,
694
- placeholder: typeof field.placeholder === "string" ? field.placeholder : void 0,
695
- required,
696
- disabled,
697
- invalid: errors.length > 0,
698
- describedById
699
- })
700
- });
701
- });
702
- //#endregion
703
- //#region src/react/primitives/Textarea.tsx
704
- const Textarea = ({ id, name, value, onChange, onBlur, placeholder, required, disabled, invalid, describedById }) => /* @__PURE__ */ jsx("textarea", {
705
- id,
706
- name,
707
- className: "fb-textarea",
708
- value,
709
- placeholder,
710
- required,
711
- disabled,
712
- "aria-invalid": invalid || void 0,
713
- "aria-describedby": describedById,
714
- onChange: (e) => onChange(e.target.value),
715
- onBlur
716
- });
717
- //#endregion
718
- //#region src/react/renderers/index.ts
719
- /** The built-in field renderers, keyed by field-type slug. Override via the renderer registry. */
720
- const defaultRenderers = {
721
- calculation: calculationRenderer,
722
- checkbox: checkboxRenderer,
723
- consent: consentRenderer,
724
- email: emailRenderer,
725
- file: fileRenderer,
726
- number: numberRenderer,
727
- select: selectRenderer,
728
- text: textRenderer,
729
- textarea: defineFieldRenderer(({ field, name, value, onChange, onBlur, errors, warnings, required, disabled }) => {
730
- const id = useId();
731
- const describedById = `${id}-desc`;
732
- return /* @__PURE__ */ jsx(FieldShell, {
733
- id,
734
- label: field.label,
735
- description: typeof field.description === "string" ? field.description : void 0,
736
- required,
737
- errors,
738
- warnings,
739
- describedById,
740
- children: /* @__PURE__ */ jsx(Textarea, {
741
- id,
742
- name,
743
- value: value ?? "",
744
- onChange,
745
- onBlur,
746
- placeholder: typeof field.placeholder === "string" ? field.placeholder : void 0,
747
- required,
748
- disabled,
749
- invalid: errors.length > 0,
750
- describedById
751
- })
752
- });
753
- })
754
- };
755
- //#endregion
756
- //#region src/react/resolveForm.ts
757
- /** Build the field-type registry from the bundled defaults plus any consumer-supplied extra definitions. */
758
- const buildFieldTypeRegistry = (extra = []) => buildRegistry([...defaultFieldDefinitions, ...extra]);
759
- /** Build the validation-rule registry from the bundled defaults plus consumer extras. */
760
- const buildValidationRuleRegistry = (extra = []) => buildRuleRegistry([...defaultValidationRules, ...extra]);
761
- /** The subset of fields currently visible, evaluating each field's `visibleWhen` against the answers. */
762
- const visibleFields = (fields, values) => fields.filter((field) => evaluateCondition(field.visibleWhen, values));
763
- //#endregion
764
- //#region src/react/state.ts
765
- const initialFormState = (values) => ({
766
- values,
767
- errors: {},
768
- warnings: {},
769
- touched: {},
770
- submitting: false,
771
- submitted: false,
772
- submitAttempted: false
773
- });
774
- /** Changing a value clears that field's prior errors (re-validated by the caller). */
775
- const formReducer = (state, action) => {
776
- switch (action.type) {
777
- case "SET_VALUE": {
778
- const { [action.name]: _removed, ...restErrors } = state.errors;
779
- return {
780
- ...state,
781
- values: {
782
- ...state.values,
783
- [action.name]: action.value
784
- },
785
- errors: restErrors
786
- };
787
- }
788
- case "TOUCH": return state.touched[action.name] ? state : {
789
- ...state,
790
- touched: {
791
- ...state.touched,
792
- [action.name]: true
793
- }
794
- };
795
- case "SET_FIELD_ISSUES": return {
796
- ...state,
797
- errors: {
798
- ...state.errors,
799
- [action.name]: action.errors
800
- },
801
- warnings: {
802
- ...state.warnings,
803
- [action.name]: action.warnings
804
- }
805
- };
806
- case "SET_ALL_ISSUES": return {
807
- ...state,
808
- errors: action.errors,
809
- warnings: action.warnings,
810
- submitAttempted: true
811
- };
812
- case "SUBMIT_START": return {
813
- ...state,
814
- submitting: true,
815
- submitError: void 0
816
- };
817
- case "SUBMIT_SUCCESS": return {
818
- ...state,
819
- submitting: false,
820
- submitted: true
821
- };
822
- case "SUBMIT_ERROR": return {
823
- ...state,
824
- submitting: false,
825
- submitError: action.message
826
- };
827
- default: return state;
828
- }
829
- };
830
- //#endregion
831
- //#region src/react/submitForm.ts
832
- const toFieldErrors = (body) => {
833
- const nested = body.errors?.[0]?.data?.errors ?? [];
834
- const map = {};
835
- for (const entry of nested) if (typeof entry.path === "string" && typeof entry.message === "string") map[entry.path] = [...map[entry.path] ?? [], entry.message];
836
- return map;
837
- };
838
- /**
839
- * The default submission transport: POST `{apiRoute}/form-submissions` with `{ form, values }`. On 201
840
- * returns the created submission id; on a 400 Payload `ValidationError` maps `data.errors[].path` to
841
- * per-field messages; otherwise returns a generic message. Pure: inject `fetchImpl` in tests.
842
- */
843
- const submitForm = async (input) => {
844
- const { formId, values, apiRoute = "/api", fetchImpl = fetch } = input;
845
- let response;
846
- try {
847
- response = await fetchImpl(`${apiRoute}/form-submissions`, {
848
- method: "POST",
849
- headers: { "Content-Type": "application/json" },
850
- body: JSON.stringify({
851
- form: formId,
852
- values
853
- })
854
- });
855
- } catch (error) {
856
- return {
857
- ok: false,
858
- message: error instanceof Error ? error.message : "Network error"
859
- };
860
- }
861
- if (response.ok) {
862
- const id = (await response.json().catch(() => ({}))).doc?.id;
863
- return {
864
- ok: true,
865
- submissionId: id === void 0 ? void 0 : String(id)
866
- };
867
- }
868
- if (response.status === 400) {
869
- const body = await response.json().catch(() => ({}));
870
- const fieldErrors = toFieldErrors(body);
871
- if (Object.keys(fieldErrors).length > 0) return {
872
- ok: false,
873
- fieldErrors
874
- };
875
- return {
876
- ok: false,
877
- message: body.errors?.[0]?.message ?? "Validation failed"
878
- };
879
- }
880
- return {
881
- ok: false,
882
- message: `Request failed (${response.status})`
883
- };
884
- };
885
- //#endregion
886
- //#region src/react/useField.ts
887
- /** Bind one field by name to the form controller: its value, issues, and change/blur handlers. */
888
- const useField = (name) => {
889
- const { state, dispatch, validateField } = useFormContext();
890
- const touched = state.touched[name] ?? false;
891
- const showIssues = touched || state.submitAttempted;
892
- const value = state.values[name];
893
- const setValue = useCallback((next) => {
894
- dispatch({
895
- type: "SET_VALUE",
896
- name,
897
- value: next
898
- });
899
- if (touched || state.submitAttempted) validateField(name, next);
900
- }, [
901
- dispatch,
902
- name,
903
- touched,
904
- state.submitAttempted,
905
- validateField
906
- ]);
907
- const onBlur = useCallback(() => {
908
- dispatch({
909
- type: "TOUCH",
910
- name
911
- });
912
- validateField(name, value);
913
- }, [
914
- dispatch,
915
- name,
916
- validateField,
917
- value
918
- ]);
919
- return {
920
- value,
921
- errors: showIssues ? state.errors[name] ?? [] : [],
922
- warnings: showIssues ? state.warnings[name] ?? [] : [],
923
- touched,
924
- setValue,
925
- onBlur
926
- };
927
- };
928
- //#endregion
929
- //#region src/react/validateField.ts
930
- /**
931
- * Run client-side validation for one field, reusing the shared engine in `mode: 'client'` (server-only
932
- * rules are skipped, no `req`/`payload`). Splits issues into blocking errors and advisory warnings.
933
- */
934
- const validateFieldValue = async (input) => {
935
- const { field, value, registry, ruleRegistry, answers, locale, t } = input;
936
- const { errors: issues } = await runValidation({
937
- field,
938
- fieldDefinition: registry.get(field.blockType),
939
- value,
940
- fieldType: field.blockType,
941
- ruleRegistry,
942
- answers,
943
- locale,
944
- t,
945
- operation: "create",
946
- event: "onChange",
947
- mode: "client"
948
- });
949
- return {
950
- errors: issues.filter((issue) => issue.severity === "error").map((issue) => issue.message),
951
- warnings: issues.filter((issue) => issue.severity === "warning").map((issue) => issue.message)
952
- };
953
- };
954
- //#endregion
955
- //#region src/react/Form.tsx
956
- const isEmpty = (value) => value == null || value === "" || Array.isArray(value) && value.length === 0;
957
- const FIELD_WIDTHS = new Set([
958
- "full",
959
- "half",
960
- "third",
961
- "twoThirds"
962
- ]);
963
- const FieldHost = ({ field, renderer, locale, t }) => {
964
- const id = useId();
965
- const { value, errors, warnings, setValue, onBlur } = useField(field.name);
966
- return createElement(renderer, {
967
- field,
968
- id,
969
- name: field.name,
970
- value,
971
- onChange: setValue,
972
- onBlur,
973
- errors,
974
- warnings,
975
- required: Boolean(field.required),
976
- locale,
977
- t
978
- });
979
- };
980
- /** Hosts a derived (calc) field: read-only, value supplied from `effectiveValues`, never bound via `useField`. */
981
- const CalcFieldHost = ({ field, renderer, value, locale, t }) => {
982
- return createElement(renderer, {
983
- field,
984
- id: useId(),
985
- name: field.name,
986
- value,
987
- onChange: () => {},
988
- onBlur: () => {},
989
- errors: [],
990
- warnings: [],
991
- required: false,
992
- disabled: true,
993
- locale,
994
- t
995
- });
996
- };
997
- /** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
998
- const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, events, t, locale = "en", layout, submitLabel = "Submit", nextLabel = "Next", backLabel = "Back", closeLabel = "Close", successMessage = "Thank you.", presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, children }) => {
999
- const honeypotName = honeypot === false ? null : honeypot?.name ?? "confirm_email";
1000
- const honeypotRef = useRef(null);
1001
- const registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes]);
1002
- const ruleRegistry = useMemo(() => buildValidationRuleRegistry(rules), [rules]);
1003
- const rendererRegistry = useMemo(() => resolveRenderers(defaultRenderers, renderers), [renderers]);
1004
- const presentationRegistry = useMemo(() => resolvePresentations(defaultPresentations, presentations), [presentations]);
1005
- const activePresentation = typeof presentation === "object" ? presentation : presentationRegistry.get(presentation ?? form.defaultPresentation ?? "page") ?? presentationRegistry.get("page") ?? defaultPresentationDescriptors.page;
1006
- const fieldsByName = useMemo(() => new Map(form.fields.map((field) => [field.name, field])), [form.fields]);
1007
- const translate = useMemo(() => t ?? ((key) => key), [t]);
1008
- const sinkRef = useRef(noopEventSink);
1009
- sinkRef.current = events ?? noopEventSink;
1010
- const formIdRef = useRef("");
1011
- formIdRef.current = String(form.id);
1012
- const [state, rawDispatch] = useReducer(formReducer, form.fields, (fields) => initialFormState({
1013
- ...Object.fromEntries(fields.map((field) => [field.name, void 0])),
1014
- ...initialValues ?? {}
1015
- }));
1016
- const effectiveValues = useMemo(() => computeCalcFields(form.fields, state.values), [form.fields, state.values]);
1017
- const recall = useMemo(() => buildRecallResolver({
1018
- fields: form.fields,
1019
- values: effectiveValues,
1020
- registry,
1021
- locale,
1022
- t: translate
1023
- }), [
1024
- form.fields,
1025
- effectiveValues,
1026
- registry,
1027
- locale,
1028
- translate
1029
- ]);
1030
- const flow = form.flow && form.flow.steps.length >= 2 ? form.flow : void 0;
1031
- const [currentStepId, setCurrentStepId] = useState(() => flow ? firstStepId(flow) : void 0);
1032
- const [history, setHistory] = useState([]);
1033
- const startedRef = useRef(false);
1034
- const submittedRef = useRef(false);
1035
- const submittingRef = useRef(false);
1036
- const flowRef = useRef(flow);
1037
- flowRef.current = flow;
1038
- const dispatch = useCallback((action) => {
1039
- if (action.type === "SET_VALUE" && !startedRef.current) {
1040
- startedRef.current = true;
1041
- emitFormEvent(sinkRef.current, formIdRef.current, { type: "form.started" });
1042
- }
1043
- rawDispatch(action);
1044
- }, []);
1045
- const validateField = useCallback((name, value) => {
1046
- const field = fieldsByName.get(name);
1047
- if (!field) return;
1048
- const answers = {
1049
- ...effectiveValues,
1050
- [name]: value
1051
- };
1052
- if (!evaluateCondition(field.validateWhen, answers)) {
1053
- rawDispatch({
1054
- type: "SET_FIELD_ISSUES",
1055
- name,
1056
- errors: [],
1057
- warnings: []
1058
- });
1059
- return;
1060
- }
1061
- validateFieldValue({
1062
- field,
1063
- value,
1064
- registry,
1065
- ruleRegistry,
1066
- answers,
1067
- locale,
1068
- t: translate
1069
- }).then(({ errors, warnings }) => {
1070
- rawDispatch({
1071
- type: "SET_FIELD_ISSUES",
1072
- name,
1073
- errors,
1074
- warnings
1075
- });
1076
- const [firstError] = errors;
1077
- if (firstError !== void 0) emitFormEvent(sinkRef.current, formIdRef.current, {
1078
- type: "field.errored",
1079
- field: name,
1080
- message: firstError
1081
- });
1082
- });
1083
- }, [
1084
- fieldsByName,
1085
- effectiveValues,
1086
- registry,
1087
- ruleRegistry,
1088
- locale,
1089
- translate
1090
- ]);
1091
- const visible = visibleFields(form.fields, effectiveValues);
1092
- const stepVisible = (flow && currentStepId ? stepFieldNames(flow, currentStepId) : []).map((name) => visible.find((field) => field.name === name)).filter((field) => Boolean(field));
1093
- const goNext = async () => {
1094
- if (!flow || !currentStepId) return;
1095
- const results = await Promise.all(stepVisible.filter((field) => !calcExpressionOf(field)).filter((field) => evaluateCondition(field.validateWhen, effectiveValues)).map(async (field) => ({
1096
- field,
1097
- ...await validateFieldValue({
1098
- field,
1099
- value: effectiveValues[field.name],
1100
- registry,
1101
- ruleRegistry,
1102
- answers: effectiveValues,
1103
- locale,
1104
- t: translate
1105
- })
1106
- })));
1107
- let hasError = false;
1108
- for (const result of results) {
1109
- rawDispatch({
1110
- type: "TOUCH",
1111
- name: result.field.name
1112
- });
1113
- rawDispatch({
1114
- type: "SET_FIELD_ISSUES",
1115
- name: result.field.name,
1116
- errors: result.errors,
1117
- warnings: result.warnings
1118
- });
1119
- if (result.errors.length > 0) hasError = true;
1120
- }
1121
- if (hasError) return;
1122
- const next = resolveNextStepId(flow, currentStepId, state.values);
1123
- if (!next) return;
1124
- emitFormEvent(sinkRef.current, formIdRef.current, {
1125
- type: "step.completed",
1126
- stepId: currentStepId
1127
- });
1128
- setHistory((prev) => [...prev, currentStepId]);
1129
- setCurrentStepId(next);
1130
- emitFormEvent(sinkRef.current, formIdRef.current, {
1131
- type: "step.viewed",
1132
- stepId: next
1133
- });
1134
- };
1135
- const goBack = () => {
1136
- const prev = history[history.length - 1];
1137
- if (prev === void 0) return;
1138
- setHistory((entries) => entries.slice(0, -1));
1139
- setCurrentStepId(prev);
1140
- emitFormEvent(sinkRef.current, formIdRef.current, {
1141
- type: "step.viewed",
1142
- stepId: prev
1143
- });
1144
- };
1145
- useEffect(() => {
1146
- emitFormEvent(sinkRef.current, formIdRef.current, { type: "form.viewed" });
1147
- const mountFlow = flowRef.current;
1148
- if (mountFlow) {
1149
- const first = firstStepId(mountFlow);
1150
- if (first) emitFormEvent(sinkRef.current, formIdRef.current, {
1151
- type: "step.viewed",
1152
- stepId: first
1153
- });
1154
- }
1155
- return () => {
1156
- if (!submittedRef.current && !submittingRef.current) emitFormEvent(sinkRef.current, formIdRef.current, { type: "form.abandoned" });
1157
- };
1158
- }, []);
1159
- const handleClose = useCallback(() => {
1160
- onClose?.();
1161
- }, [onClose]);
1162
- const handleSubmit = async (event) => {
1163
- event.preventDefault();
1164
- if (submittingRef.current) return;
1165
- submittingRef.current = true;
1166
- const visible = visibleFields(form.fields, effectiveValues);
1167
- const results = await Promise.all(visible.filter((field) => !calcExpressionOf(field)).filter((field) => evaluateCondition(field.validateWhen, effectiveValues)).map(async (field) => ({
1168
- field,
1169
- ...await validateFieldValue({
1170
- field,
1171
- value: effectiveValues[field.name],
1172
- registry,
1173
- ruleRegistry,
1174
- answers: effectiveValues,
1175
- locale,
1176
- t: translate
1177
- })
1178
- })));
1179
- const errors = {};
1180
- const warnings = {};
1181
- for (const result of results) {
1182
- if (result.errors.length > 0) {
1183
- errors[result.field.name] = result.errors;
1184
- const [firstError] = result.errors;
1185
- if (firstError !== void 0) emitFormEvent(sinkRef.current, formIdRef.current, {
1186
- type: "field.errored",
1187
- field: result.field.name,
1188
- message: firstError
1189
- });
1190
- }
1191
- if (result.warnings.length > 0) warnings[result.field.name] = result.warnings;
1192
- }
1193
- rawDispatch({
1194
- type: "SET_ALL_ISSUES",
1195
- errors,
1196
- warnings
1197
- });
1198
- if (Object.keys(errors).length > 0) {
1199
- submittingRef.current = false;
1200
- return;
1201
- }
1202
- rawDispatch({ type: "SUBMIT_START" });
1203
- const values = visible.filter((field) => !isEmpty(effectiveValues[field.name])).map((field) => ({
1204
- field: field.name,
1205
- value: effectiveValues[field.name]
1206
- }));
1207
- if (honeypotName) {
1208
- const decoy = honeypotRef.current?.value ?? "";
1209
- if (decoy !== "") values.push({
1210
- field: honeypotName,
1211
- value: decoy
1212
- });
1213
- }
1214
- if (captchaToken) values.push({
1215
- field: CAPTCHA_TOKEN_KEY,
1216
- value: captchaToken
1217
- });
1218
- const result = onSubmit ? await onSubmit({
1219
- formId: form.id,
1220
- values
1221
- }) : await submitForm({
1222
- formId: form.id,
1223
- values,
1224
- apiRoute
1225
- });
1226
- submittingRef.current = false;
1227
- if (result.ok) {
1228
- submittedRef.current = true;
1229
- rawDispatch({ type: "SUBMIT_SUCCESS" });
1230
- emitFormEvent(sinkRef.current, formIdRef.current, {
1231
- type: "submission.created",
1232
- submissionId: result.submissionId
1233
- });
1234
- onSuccess?.(result.submissionId);
1235
- if (activePresentation.dismissOnSuccess) handleClose();
1236
- } else {
1237
- if (result.fieldErrors) rawDispatch({
1238
- type: "SET_ALL_ISSUES",
1239
- errors: result.fieldErrors,
1240
- warnings: {}
1241
- });
1242
- const message = result.message ?? "Submission failed";
1243
- rawDispatch({
1244
- type: "SUBMIT_ERROR",
1245
- message
1246
- });
1247
- onError?.(message);
1248
- }
1249
- };
1250
- const step = flow ? {
1251
- flow,
1252
- currentStepId,
1253
- stepIndex: flow.steps.findIndex((s) => s.id === currentStepId),
1254
- stepCount: flow.steps.length,
1255
- isFirst: history.length === 0,
1256
- isTerminal: currentStepId ? isTerminalStepId(flow, currentStepId, state.values) : true,
1257
- goNext: () => {
1258
- goNext();
1259
- },
1260
- goBack
1261
- } : {
1262
- stepIndex: 0,
1263
- stepCount: 1,
1264
- isFirst: true,
1265
- isTerminal: true,
1266
- goNext: () => {},
1267
- goBack: () => {}
1268
- };
1269
- const contextValue = {
1270
- state,
1271
- dispatch,
1272
- validateField,
1273
- locale,
1274
- step
1275
- };
1276
- const PresentationWrapper = activePresentation.Wrapper;
1277
- const wrap = (content) => PresentationWrapper ? /* @__PURE__ */ jsx(PresentationWrapper, {
1278
- presentation: activePresentation,
1279
- open: true,
1280
- onClose: handleClose,
1281
- title,
1282
- closeLabel,
1283
- children: content
1284
- }) : content;
1285
- if (children !== void 0) return /* @__PURE__ */ jsx(FormContext.Provider, {
1286
- value: contextValue,
1287
- children: wrap(/* @__PURE__ */ jsxs("form", {
1288
- className: "fb-form-root",
1289
- noValidate: true,
1290
- onSubmit: handleSubmit,
1291
- "data-fb-presentation": activePresentation.name,
1292
- "data-fb-density": activePresentation.density,
1293
- children: [honeypotName ? /* @__PURE__ */ jsx(Honeypot, {
1294
- name: honeypotName,
1295
- inputRef: honeypotRef
1296
- }) : null, children]
1297
- }))
1298
- });
1299
- if (state.submitted) return /* @__PURE__ */ jsx(FormContext.Provider, {
1300
- value: contextValue,
1301
- children: wrap(/* @__PURE__ */ jsx("p", {
1302
- role: "status",
1303
- className: "fb-form__success",
1304
- "data-fb-presentation": activePresentation.name,
1305
- "data-fb-density": activePresentation.density,
1306
- children: interpolate(successMessage, recall)
1307
- }))
1308
- });
1309
- const rendered = (flow ? stepVisible : visible).filter((field) => field.hidden !== true && field.calcDisplay !== false);
1310
- return /* @__PURE__ */ jsx(FormContext.Provider, {
1311
- value: contextValue,
1312
- children: wrap(/* @__PURE__ */ jsxs("form", {
1313
- className: "fb-form-root",
1314
- noValidate: true,
1315
- onSubmit: handleSubmit,
1316
- "data-fb-presentation": activePresentation.name,
1317
- "data-fb-density": activePresentation.density,
1318
- children: [
1319
- honeypotName ? /* @__PURE__ */ jsx(Honeypot, {
1320
- name: honeypotName,
1321
- inputRef: honeypotRef
1322
- }) : null,
1323
- /* @__PURE__ */ jsx(FormLayout, {
1324
- enabled: layout !== false,
1325
- children: rendered.map((field) => {
1326
- const renderer = rendererRegistry.get(field.blockType);
1327
- if (!renderer) return null;
1328
- const width = typeof field.width === "string" && FIELD_WIDTHS.has(field.width) ? field.width : void 0;
1329
- const recalledField = applyRecall(field, recall);
1330
- return /* @__PURE__ */ jsx("div", {
1331
- ...widthProps(width),
1332
- children: calcExpressionOf(field) ? /* @__PURE__ */ jsx(CalcFieldHost, {
1333
- field: recalledField,
1334
- renderer,
1335
- value: effectiveValues[field.name],
1336
- locale,
1337
- t: translate
1338
- }) : /* @__PURE__ */ jsx(FieldHost, {
1339
- field: recalledField,
1340
- renderer,
1341
- locale,
1342
- t: translate
1343
- })
1344
- }, field.name);
1345
- })
1346
- }),
1347
- state.submitError ? /* @__PURE__ */ jsx("p", {
1348
- role: "alert",
1349
- className: "fb-form__submit-error",
1350
- children: state.submitError
1351
- }) : null,
1352
- flow ? /* @__PURE__ */ jsxs("div", {
1353
- className: "fb-form__controls",
1354
- children: [!step.isFirst ? /* @__PURE__ */ jsx("button", {
1355
- type: "button",
1356
- onClick: goBack,
1357
- disabled: state.submitting,
1358
- children: backLabel
1359
- }) : null, step.isTerminal ? /* @__PURE__ */ jsx("button", {
1360
- type: "submit",
1361
- disabled: state.submitting,
1362
- children: submitLabel
1363
- }) : /* @__PURE__ */ jsx("button", {
1364
- type: "button",
1365
- disabled: state.submitting,
1366
- onClick: () => {
1367
- goNext();
1368
- },
1369
- children: nextLabel
1370
- })]
1371
- }) : /* @__PURE__ */ jsx("button", {
1372
- type: "submit",
1373
- disabled: state.submitting,
1374
- children: submitLabel
1375
- })
1376
- ]
1377
- }))
1378
- });
1379
- };
1380
- //#endregion
1381
- //#region src/react/FormResults.tsx
1382
- const OneResult = ({ result, t, showCounts }) => {
1383
- if (result.total === 0) return /* @__PURE__ */ jsxs("section", {
1384
- className: "fb-results__field",
1385
- children: [result.label ? /* @__PURE__ */ jsx("h3", {
1386
- className: "fb-results__label",
1387
- children: result.label
1388
- }) : null, /* @__PURE__ */ jsx("p", {
1389
- className: "fb-results__empty",
1390
- children: t(keys.resultsNoResponses)
1391
- })]
1392
- });
1393
- return /* @__PURE__ */ jsxs("section", {
1394
- className: "fb-results__field",
1395
- children: [
1396
- result.label ? /* @__PURE__ */ jsx("h3", {
1397
- className: "fb-results__label",
1398
- children: result.label
1399
- }) : null,
1400
- /* @__PURE__ */ jsx("ul", {
1401
- className: "fb-results__list",
1402
- children: result.buckets.map((bucket) => /* @__PURE__ */ jsxs("li", {
1403
- className: "fb-results__row",
1404
- children: [
1405
- /* @__PURE__ */ jsx("span", {
1406
- className: "fb-results__option",
1407
- children: bucket.label
1408
- }),
1409
- /* @__PURE__ */ jsx("span", {
1410
- className: "fb-results__bar",
1411
- children: /* @__PURE__ */ jsx("span", {
1412
- className: "fb-results__bar-fill",
1413
- style: { width: `${bucket.percentage}%` },
1414
- "aria-hidden": "true"
1415
- })
1416
- }),
1417
- /* @__PURE__ */ jsxs("span", {
1418
- className: "fb-results__pct",
1419
- children: [bucket.percentage, "%"]
1420
- }),
1421
- showCounts ? /* @__PURE__ */ jsx("span", {
1422
- className: "fb-results__count",
1423
- children: bucket.count
1424
- }) : null
1425
- ]
1426
- }, bucket.value))
1427
- }),
1428
- /* @__PURE__ */ jsxs("p", {
1429
- className: "fb-results__total",
1430
- children: [
1431
- result.total,
1432
- " ",
1433
- t(keys.resultsResponses),
1434
- result.truncated ? ` (${t(keys.resultsTruncated)})` : ""
1435
- ]
1436
- })
1437
- ]
1438
- });
1439
- };
1440
- /**
1441
- * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,
1442
- * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never
1443
- * fetches). The option label, count, and percentage are real text (the accessible content); the bar fill
1444
- * is `aria-hidden` visual sugar sized by inline width.
1445
- */
1446
- const FormResults = ({ results, t, showCounts = true }) => {
1447
- const translate = t ?? ((key) => key);
1448
- return /* @__PURE__ */ jsx("div", {
1449
- className: "fb-results",
1450
- children: (Array.isArray(results) ? results : [results]).map((result) => /* @__PURE__ */ jsx(OneResult, {
1451
- result,
1452
- t: translate,
1453
- showCounts
1454
- }, result.field))
1455
- });
1456
- };
1457
- //#endregion
1458
- //#region src/react/fetchResults.ts
1459
- /**
1460
- * Fetch aggregate poll/survey results from the form-builder results endpoint
1461
- * (`GET {apiRoute}/forms/:id/results`). Returns the server-resolved aggregations; the endpoint gates public
1462
- * access by the form's `showResults` opt-in. Pure: inject `fetchImpl` in tests.
1463
- */
1464
- const fetchFormResults = async (input) => {
1465
- const { formId, field, apiRoute = "/api", fetchImpl = fetch } = input;
1466
- const query = field ? `?field=${encodeURIComponent(field)}` : "";
1467
- let response;
1468
- try {
1469
- response = await fetchImpl(`${apiRoute}/forms/${formId}/results${query}`, { method: "GET" });
1470
- } catch (error) {
1471
- return {
1472
- ok: false,
1473
- message: error instanceof Error ? error.message : "Network error"
1474
- };
1475
- }
1476
- if (!response.ok) return {
1477
- ok: false,
1478
- message: `Request failed (${response.status})`
1479
- };
1480
- return {
1481
- ok: true,
1482
- results: (await response.json().catch(() => ({}))).results ?? []
1483
- };
1484
- };
1485
- //#endregion
1486
- //#region src/react/Poll.tsx
1487
- const readVoted = (key) => {
1488
- try {
1489
- return window.localStorage.getItem(key) != null;
1490
- } catch {
1491
- return false;
1492
- }
1493
- };
1494
- const writeVoted = (key) => {
1495
- try {
1496
- window.localStorage.setItem(key, "1");
1497
- } catch {}
1498
- };
1499
- /**
1500
- * A poll: renders `<Form>` until the visitor votes, then fetches the aggregate results and shows
1501
- * `<FormResults>`. A per-browser localStorage flag (`storageKey`) skips straight to results on revisit. The
1502
- * guard is UX, not integrity (bypassable): server-enforced one-per-identity dedup composes via `req.user`
1503
- * (authed forms) or a `notAlreadySubmitted` rule, with cookie/IP identity deferred to the spam phase.
1504
- */
1505
- const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchFormResults, apiRoute, onSuccess, ...formProps }) => {
1506
- const key = storageKey ?? `fb-poll-${formProps.form.id}`;
1507
- const [voted, setVoted] = useState(false);
1508
- const [results, setResults] = useState(null);
1509
- const loadResults = useCallback(async () => {
1510
- const result = await fetchResultsImpl({
1511
- formId: formProps.form.id,
1512
- field: resultsField,
1513
- apiRoute
1514
- });
1515
- setResults(result.ok ? result.results : []);
1516
- }, [
1517
- fetchResultsImpl,
1518
- formProps.form.id,
1519
- resultsField,
1520
- apiRoute
1521
- ]);
1522
- useEffect(() => {
1523
- if (hasVoted ?? readVoted(key)) {
1524
- setVoted(true);
1525
- loadResults();
1526
- }
1527
- }, [
1528
- hasVoted,
1529
- key,
1530
- loadResults
1531
- ]);
1532
- const handleSuccess = useCallback((submissionId) => {
1533
- writeVoted(key);
1534
- setVoted(true);
1535
- loadResults();
1536
- onSuccess?.(submissionId);
1537
- }, [
1538
- key,
1539
- loadResults,
1540
- onSuccess
1541
- ]);
1542
- if (voted) return results ? /* @__PURE__ */ jsx(FormResults, {
1543
- results,
1544
- t: formProps.t,
1545
- locale: formProps.locale
1546
- }) : null;
1547
- return /* @__PURE__ */ jsx(Form, {
1548
- ...formProps,
1549
- apiRoute,
1550
- onSuccess: handleSuccess
1551
- });
1552
- };
1553
- //#endregion
1554
- //#region src/react/useFormState.ts
1555
- /** The whole form state (values, errors, warnings, touched, submitting, submitted, submitError). */
1556
- const useFormState = () => useFormContext().state;
1557
- //#endregion
1558
- //#region src/react/useFormStep.ts
1559
- /** Multi-step navigation state for the current form (single-step default when the form has no flow). */
1560
- const useFormStep = () => useFormContext().step;
1561
- //#endregion
1562
- export { Backdrop, CAPTCHA_TOKEN_KEY, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormLayout, FormResults, Honeypot, Input, Poll, Select, Textarea, buildRecallResolver, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
1563
-
1564
- //# sourceMappingURL=react.js.map
2
+ import { interpolate } from "../recall/interpolate.js";
3
+ import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
4
+ import { evaluateCalc } from "../calc/evaluate.js";
5
+ import { computeCalcFields } from "../calc/computeCalcFields.js";
6
+ import { evaluateCondition } from "../conditions/evaluate.js";
7
+ import { en } from "../translations/en.js";
8
+ import { valuesFromSearchParams } from "../prefill/valuesFromSearchParams.js";
9
+ import { buildRecallResolver } from "../recall/resolver.js";
10
+ import { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from "../flow/engine.js";
11
+ import { cn } from "../react/cn.js";
12
+ import { defineFieldRenderer } from "../react/contract.js";
13
+ import { makeTranslate } from "../translations/makeTranslate.js";
14
+ import { FormLayout, widthProps } from "../react/FormLayout.js";
15
+ import { Honeypot } from "../react/Honeypot.js";
16
+ import { Backdrop } from "../react/presentation/Backdrop.js";
17
+ import { useDismiss } from "../react/presentation/useDismiss.js";
18
+ import { useFocusTrap } from "../react/presentation/useFocusTrap.js";
19
+ import { useScrollLock } from "../react/presentation/useScrollLock.js";
20
+ import { DialogSurface } from "../react/presentation/DialogSurface.js";
21
+ import { defaultPresentations } from "../react/presentation/presentations.js";
22
+ import { resolvePresentations } from "../react/presentation/registry.js";
23
+ import { resolveRenderers } from "../react/registry.js";
24
+ import { FieldShell } from "../react/primitives/FieldShell.js";
25
+ import { Checkbox } from "../react/primitives/Checkbox.js";
26
+ import { Input } from "../react/primitives/Input.js";
27
+ import { uploadFile } from "../react/uploadFile.js";
28
+ import { useField } from "../react/useField.js";
29
+ import { Select } from "../react/primitives/Select.js";
30
+ import { Textarea } from "../react/primitives/Textarea.js";
31
+ import { defaultRenderers } from "../react/renderers/index.js";
32
+ import { submitForm } from "../react/submitForm.js";
33
+ import { Form } from "../react/Form.js";
34
+ import { FormResults } from "../react/FormResults.js";
35
+ import { fetchFormResults } from "../react/fetchResults.js";
36
+ import { Poll } from "../react/Poll.js";
37
+ import { toFormDocument } from "../react/toFormDocument.js";
38
+ import { useFormState } from "../react/useFormState.js";
39
+ import { useFormStep } from "../react/useFormStep.js";
40
+ export { Backdrop, CAPTCHA_TOKEN_KEY, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormLayout, FormResults, Honeypot, Input, Poll, Select, Textarea, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };