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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +6 -4
  3. package/dist/actions/assertNoBlockCollision.js +18 -0
  4. package/dist/actions/assertNoBlockCollision.js.map +1 -0
  5. package/dist/actions/body/converters.d.ts +20 -0
  6. package/dist/actions/body/converters.js +57 -0
  7. package/dist/actions/body/converters.js.map +1 -0
  8. package/dist/actions/body/escapeHtml.d.ts +6 -0
  9. package/dist/actions/body/escapeHtml.js +14 -0
  10. package/dist/actions/body/escapeHtml.js.map +1 -0
  11. package/dist/actions/body/serializeBody.d.ts +55 -0
  12. package/dist/actions/body/serializeBody.js +78 -0
  13. package/dist/actions/body/serializeBody.js.map +1 -0
  14. package/dist/actions/body/serializeSlate.js +31 -0
  15. package/dist/actions/body/serializeSlate.js.map +1 -0
  16. package/dist/actions/body/textOfBody.d.ts +13 -0
  17. package/dist/actions/body/textOfBody.js +35 -0
  18. package/dist/actions/body/textOfBody.js.map +1 -0
  19. package/dist/actions/body/wildcards.d.ts +10 -0
  20. package/dist/actions/body/wildcards.js +33 -0
  21. package/dist/actions/body/wildcards.js.map +1 -0
  22. package/dist/actions/buildActionBlocks.js +12 -9
  23. package/dist/actions/buildActionBlocks.js.map +1 -1
  24. package/dist/actions/builtin/confirmation.js +47 -43
  25. package/dist/actions/builtin/confirmation.js.map +1 -1
  26. package/dist/actions/builtin/emailAction.d.ts +19 -0
  27. package/dist/actions/builtin/emailAction.js +114 -0
  28. package/dist/actions/builtin/emailAction.js.map +1 -0
  29. package/dist/actions/builtin/emailTeam.js +20 -38
  30. package/dist/actions/builtin/emailTeam.js.map +1 -1
  31. package/dist/actions/builtin/index.d.ts +16 -1
  32. package/dist/actions/builtin/index.js +21 -7
  33. package/dist/actions/builtin/index.js.map +1 -1
  34. package/dist/actions/builtin/signedWebhook.js +6 -2
  35. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  36. package/dist/actions/defineAction.d.ts +8 -5
  37. package/dist/actions/defineAction.js.map +1 -1
  38. package/dist/actions/dispatch.js +3 -2
  39. package/dist/actions/dispatch.js.map +1 -1
  40. package/dist/actions/emailRecipients.d.ts +12 -0
  41. package/dist/actions/emailRecipients.js +143 -0
  42. package/dist/actions/emailRecipients.js.map +1 -0
  43. package/dist/actions/fromAddresses.d.ts +21 -0
  44. package/dist/actions/fromAddresses.js +74 -0
  45. package/dist/actions/fromAddresses.js.map +1 -0
  46. package/dist/actions/recipientSources.d.ts +34 -0
  47. package/dist/actions/recipientSources.js +11 -0
  48. package/dist/actions/recipientSources.js.map +1 -0
  49. package/dist/actions/registry.d.ts +2 -1
  50. package/dist/actions/registry.js +2 -6
  51. package/dist/actions/registry.js.map +1 -1
  52. package/dist/actions/runActions.js +10 -1
  53. package/dist/actions/runActions.js.map +1 -1
  54. package/dist/actions/task.js +40 -15
  55. package/dist/actions/task.js.map +1 -1
  56. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  57. package/dist/aggregation/aggregateResponses.js +8 -14
  58. package/dist/aggregation/aggregateResponses.js.map +1 -1
  59. package/dist/aggregation/resolveResultsRequest.d.ts +35 -6
  60. package/dist/aggregation/resolveResultsRequest.js +68 -11
  61. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  62. package/dist/calc/computeCalcFields.js +2 -1
  63. package/dist/calc/computeCalcFields.js.map +1 -1
  64. package/dist/calc/evaluate.js +1 -2
  65. package/dist/calc/evaluate.js.map +1 -1
  66. package/dist/calc/normalizeCalc.js +6 -19
  67. package/dist/calc/normalizeCalc.js.map +1 -1
  68. package/dist/calc/types.d.ts +8 -2
  69. package/dist/calc/types.js +22 -0
  70. package/dist/calc/types.js.map +1 -0
  71. package/dist/client/ByteSizeField.d.ts +29 -0
  72. package/dist/client/ByteSizeField.js +58 -0
  73. package/dist/client/ByteSizeField.js.map +1 -0
  74. package/dist/client/ClosePollButton.d.ts +15 -0
  75. package/dist/client/ClosePollButton.js +80 -0
  76. package/dist/client/ClosePollButton.js.map +1 -0
  77. package/dist/client/CondensedArray.d.ts +15 -0
  78. package/dist/client/CondensedArray.js +97 -0
  79. package/dist/client/CondensedArray.js.map +1 -0
  80. package/dist/client/ConditionBuilder.js +2 -1
  81. package/dist/client/ConditionBuilder.js.map +1 -1
  82. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  83. package/dist/client/ConsentBlockLabel.js +71 -0
  84. package/dist/client/ConsentBlockLabel.js.map +1 -0
  85. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  86. package/dist/client/ConsentSourceRowLabel.js +21 -0
  87. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  88. package/dist/client/EndpointOptionsSelect.d.ts +46 -0
  89. package/dist/client/EndpointOptionsSelect.js +106 -0
  90. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  91. package/dist/client/FieldBlockLabel.d.ts +18 -0
  92. package/dist/client/FieldBlockLabel.js +34 -0
  93. package/dist/client/FieldBlockLabel.js.map +1 -0
  94. package/dist/client/FieldNameSelect.d.ts +37 -0
  95. package/dist/client/FieldNameSelect.js +72 -0
  96. package/dist/client/FieldNameSelect.js.map +1 -0
  97. package/dist/client/FlowBuilder.d.ts +2 -2
  98. package/dist/client/FlowBuilder.js +137 -88
  99. package/dist/client/FlowBuilder.js.map +1 -1
  100. package/dist/client/FormConditionField.js +1 -4
  101. package/dist/client/FormConditionField.js.map +1 -1
  102. package/dist/client/RecipientsSelect.d.ts +35 -0
  103. package/dist/client/RecipientsSelect.js +193 -0
  104. package/dist/client/RecipientsSelect.js.map +1 -0
  105. package/dist/client/RuleDescription.d.ts +21 -0
  106. package/dist/client/RuleDescription.js +19 -0
  107. package/dist/client/RuleDescription.js.map +1 -0
  108. package/dist/client/admin.css +275 -0
  109. package/dist/client/endpointOptions.js +29 -0
  110. package/dist/client/endpointOptions.js.map +1 -0
  111. package/dist/client/flowAuthoring.js +142 -0
  112. package/dist/client/flowAuthoring.js.map +1 -0
  113. package/dist/client/synthesizeClientField.js +21 -10
  114. package/dist/client/synthesizeClientField.js.map +1 -1
  115. package/dist/client/toStaticLabel.js +13 -0
  116. package/dist/client/toStaticLabel.js.map +1 -0
  117. package/dist/collections/buttonFields.d.ts +34 -0
  118. package/dist/collections/buttonFields.js +35 -0
  119. package/dist/collections/buttonFields.js.map +1 -0
  120. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  121. package/dist/collections/cells/FieldCountCell.js +18 -0
  122. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  123. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  124. package/dist/collections/cells/FlowStepsCell.js +17 -0
  125. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  126. package/dist/collections/formSubmissions.js +133 -47
  127. package/dist/collections/formSubmissions.js.map +1 -1
  128. package/dist/collections/forms.js +416 -114
  129. package/dist/collections/forms.js.map +1 -1
  130. package/dist/collections/formsEndpoints.js +101 -0
  131. package/dist/collections/formsEndpoints.js.map +1 -0
  132. package/dist/collections/redirectFields.d.ts +29 -0
  133. package/dist/conditions/conditionType.js +9 -2
  134. package/dist/conditions/conditionType.js.map +1 -1
  135. package/dist/conditions/evaluate.js +1 -1
  136. package/dist/conditions/evaluate.js.map +1 -1
  137. package/dist/conditions/fieldTypes.d.ts +3 -1
  138. package/dist/conditions/fieldTypes.js +4 -1
  139. package/dist/conditions/fieldTypes.js.map +1 -1
  140. package/dist/conditions/normalizeConditions.js +20 -6
  141. package/dist/conditions/normalizeConditions.js.map +1 -1
  142. package/dist/consent/applyConsentStatements.d.ts +14 -0
  143. package/dist/consent/applyConsentStatements.js +17 -0
  144. package/dist/consent/applyConsentStatements.js.map +1 -0
  145. package/dist/consent/captureConsent.d.ts +38 -11
  146. package/dist/consent/captureConsent.js +60 -13
  147. package/dist/consent/captureConsent.js.map +1 -1
  148. package/dist/consent/consentSourcesField.d.ts +66 -0
  149. package/dist/consent/consentSourcesField.js +90 -0
  150. package/dist/consent/consentSourcesField.js.map +1 -0
  151. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  152. package/dist/consent/resolveConsentEntries.js +40 -0
  153. package/dist/consent/resolveConsentEntries.js.map +1 -0
  154. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  155. package/dist/consent/resolveConsentSourcesRequest.js +54 -0
  156. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  157. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  158. package/dist/consent/resolveConsentStatements.js +44 -0
  159. package/dist/consent/resolveConsentStatements.js.map +1 -0
  160. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  161. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  162. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  163. package/dist/consent/types.d.ts +61 -0
  164. package/dist/context/formContext.d.ts +30 -0
  165. package/dist/context/formContext.js +54 -0
  166. package/dist/context/formContext.js.map +1 -0
  167. package/dist/email/departments.d.ts +59 -0
  168. package/dist/email/departments.js +87 -0
  169. package/dist/email/departments.js.map +1 -0
  170. package/dist/email/departmentsField.d.ts +53 -0
  171. package/dist/email/departmentsField.js +73 -0
  172. package/dist/email/departmentsField.js.map +1 -0
  173. package/dist/exports/client.d.ts +13 -1
  174. package/dist/exports/client.js +13 -1
  175. package/dist/exports/i18n.d.ts +5 -2
  176. package/dist/exports/i18n.js +5 -2
  177. package/dist/exports/react.d.ts +26 -8
  178. package/dist/exports/react.js +19 -3
  179. package/dist/exports/rsc.d.ts +9 -1
  180. package/dist/exports/rsc.js +9 -1
  181. package/dist/exports/types.d.ts +19 -10
  182. package/dist/fields/buildFieldBlocks.js +53 -19
  183. package/dist/fields/buildFieldBlocks.js.map +1 -1
  184. package/dist/fields/builtin/calculation.js +122 -2
  185. package/dist/fields/builtin/calculation.js.map +1 -1
  186. package/dist/fields/builtin/checkbox.js +8 -0
  187. package/dist/fields/builtin/checkbox.js.map +1 -1
  188. package/dist/fields/builtin/consent.js +30 -15
  189. package/dist/fields/builtin/consent.js.map +1 -1
  190. package/dist/fields/builtin/country.d.ts +13 -0
  191. package/dist/fields/builtin/country.js +30 -0
  192. package/dist/fields/builtin/country.js.map +1 -0
  193. package/dist/fields/builtin/date.js +31 -0
  194. package/dist/fields/builtin/date.js.map +1 -0
  195. package/dist/fields/builtin/email.js +1 -1
  196. package/dist/fields/builtin/email.js.map +1 -1
  197. package/dist/fields/builtin/file.d.ts +15 -0
  198. package/dist/fields/builtin/file.js +95 -8
  199. package/dist/fields/builtin/file.js.map +1 -1
  200. package/dist/fields/builtin/index.d.ts +26 -0
  201. package/dist/fields/builtin/index.js +30 -6
  202. package/dist/fields/builtin/index.js.map +1 -1
  203. package/dist/fields/builtin/message.js +30 -0
  204. package/dist/fields/builtin/message.js.map +1 -0
  205. package/dist/fields/builtin/repeater.js +49 -0
  206. package/dist/fields/builtin/repeater.js.map +1 -0
  207. package/dist/fields/builtin/select.js +8 -3
  208. package/dist/fields/builtin/select.js.map +1 -1
  209. package/dist/fields/builtin/state.d.ts +13 -0
  210. package/dist/fields/builtin/state.js +30 -0
  211. package/dist/fields/builtin/state.js.map +1 -0
  212. package/dist/fields/data/regions.d.ts +21 -0
  213. package/dist/fields/data/regions.js +1238 -0
  214. package/dist/fields/data/regions.js.map +1 -0
  215. package/dist/fields/fieldKey.d.ts +15 -0
  216. package/dist/fields/fieldKey.js +16 -0
  217. package/dist/fields/fieldKey.js.map +1 -0
  218. package/dist/fields/fieldNamesOfType.js +30 -0
  219. package/dist/fields/fieldNamesOfType.js.map +1 -0
  220. package/dist/fields/instanceOptions.js +20 -0
  221. package/dist/fields/instanceOptions.js.map +1 -0
  222. package/dist/fields/localizedIf.d.ts +13 -0
  223. package/dist/fields/localizedIf.js +12 -0
  224. package/dist/fields/localizedIf.js.map +1 -0
  225. package/dist/fields/registry.d.ts +3 -1
  226. package/dist/fields/registry.js +23 -8
  227. package/dist/fields/registry.js.map +1 -1
  228. package/dist/fields/sharedConfig.js +120 -58
  229. package/dist/fields/sharedConfig.js.map +1 -1
  230. package/dist/fields/types.d.ts +71 -7
  231. package/dist/flow/engine.d.ts +6 -4
  232. package/dist/flow/engine.js +11 -6
  233. package/dist/flow/engine.js.map +1 -1
  234. package/dist/flow/normalizeFlow.js +28 -9
  235. package/dist/flow/normalizeFlow.js.map +1 -1
  236. package/dist/flow/stepLabel.js +15 -0
  237. package/dist/flow/stepLabel.js.map +1 -0
  238. package/dist/flow/types.d.ts +12 -3
  239. package/dist/flow/types.js +11 -0
  240. package/dist/flow/types.js.map +1 -0
  241. package/dist/form/pollState.d.ts +22 -0
  242. package/dist/form/pollState.js +17 -0
  243. package/dist/form/pollState.js.map +1 -0
  244. package/dist/form/toFormDocument.d.ts +102 -0
  245. package/dist/form/toFormDocument.js +87 -0
  246. package/dist/form/toFormDocument.js.map +1 -0
  247. package/dist/form/types.d.ts +65 -0
  248. package/dist/index.d.ts +55 -39
  249. package/dist/index.js +89 -23
  250. package/dist/index.js.map +1 -1
  251. package/dist/options.d.ts +236 -0
  252. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  253. package/dist/plugin/applyRegistryConfig.js +20 -0
  254. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  255. package/dist/plugin/collectionOverrides.d.ts +22 -0
  256. package/dist/plugin/customState.js +26 -0
  257. package/dist/plugin/customState.js.map +1 -0
  258. package/dist/plugin/registerCollections.js +40 -20
  259. package/dist/plugin/registerCollections.js.map +1 -1
  260. package/dist/plugin/uploadsCollection.d.ts +16 -0
  261. package/dist/plugin/uploadsCollection.js +53 -0
  262. package/dist/plugin/uploadsCollection.js.map +1 -0
  263. package/dist/poll/applyPollOptions.js +18 -0
  264. package/dist/poll/applyPollOptions.js.map +1 -0
  265. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  266. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  267. package/dist/poll/closeJob.d.ts +52 -0
  268. package/dist/poll/closeJob.js +119 -0
  269. package/dist/poll/closeJob.js.map +1 -0
  270. package/dist/poll/definePollOptionSource.d.ts +44 -0
  271. package/dist/poll/definePollOptionSource.js +6 -0
  272. package/dist/poll/definePollOptionSource.js.map +1 -0
  273. package/dist/poll/definePollType.d.ts +34 -0
  274. package/dist/poll/definePollType.js +6 -0
  275. package/dist/poll/definePollType.js.map +1 -0
  276. package/dist/poll/effectivePollOptions.d.ts +38 -0
  277. package/dist/poll/effectivePollOptions.js +64 -0
  278. package/dist/poll/effectivePollOptions.js.map +1 -0
  279. package/dist/poll/mostVoted.d.ts +21 -0
  280. package/dist/poll/mostVoted.js +32 -0
  281. package/dist/poll/mostVoted.js.map +1 -0
  282. package/dist/poll/outcomeBeforeChange.js +91 -0
  283. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  284. package/dist/poll/outcomeFields.d.ts +38 -0
  285. package/dist/poll/outcomeFields.js +52 -0
  286. package/dist/poll/outcomeFields.js.map +1 -0
  287. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  288. package/dist/poll/pollTypeRegistry.js +69 -0
  289. package/dist/poll/pollTypeRegistry.js.map +1 -0
  290. package/dist/poll/registry.d.ts +17 -0
  291. package/dist/poll/registry.js +12 -0
  292. package/dist/poll/registry.js.map +1 -0
  293. package/dist/poll/resolvePollCloseRequest.js +105 -0
  294. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  295. package/dist/poll/resolvePollOptions.d.ts +29 -0
  296. package/dist/poll/resolvePollOptions.js +52 -0
  297. package/dist/poll/resolvePollOptions.js.map +1 -0
  298. package/dist/poll/resolvePollOptionsRequest.js +50 -0
  299. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  300. package/dist/poll/resolvePollOutcome.d.ts +34 -0
  301. package/dist/poll/resolvePollOutcome.js +76 -0
  302. package/dist/poll/resolvePollOutcome.js.map +1 -0
  303. package/dist/poll/resultsField.js +21 -0
  304. package/dist/poll/resultsField.js.map +1 -0
  305. package/dist/prefill/valuesFromSearchParams.js +5 -9
  306. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  307. package/dist/presentations/types.d.ts +3 -2
  308. package/dist/react/Form.d.ts +74 -20
  309. package/dist/react/Form.js +346 -180
  310. package/dist/react/Form.js.map +1 -1
  311. package/dist/react/FormContext.d.ts +44 -1
  312. package/dist/react/FormContext.js.map +1 -1
  313. package/dist/react/FormControls.d.ts +55 -0
  314. package/dist/react/FormControls.js +56 -0
  315. package/dist/react/FormControls.js.map +1 -0
  316. package/dist/react/FormFields.d.ts +18 -0
  317. package/dist/react/FormFields.js +114 -0
  318. package/dist/react/FormFields.js.map +1 -0
  319. package/dist/react/FormLayout.d.ts +4 -2
  320. package/dist/react/FormLayout.js +3 -2
  321. package/dist/react/FormLayout.js.map +1 -1
  322. package/dist/react/FormResults.d.ts +6 -3
  323. package/dist/react/FormResults.js +37 -29
  324. package/dist/react/FormResults.js.map +1 -1
  325. package/dist/react/FormSteps.d.ts +17 -0
  326. package/dist/react/FormSteps.js +35 -0
  327. package/dist/react/FormSteps.js.map +1 -0
  328. package/dist/react/Poll.d.ts +17 -6
  329. package/dist/react/Poll.js +79 -21
  330. package/dist/react/Poll.js.map +1 -1
  331. package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -0
  332. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  333. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  334. package/dist/react/captcha/RecaptchaCaptcha.d.ts +41 -0
  335. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  336. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  337. package/dist/react/captcha/TurnstileCaptcha.d.ts +28 -0
  338. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  339. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  340. package/dist/react/captcha/types.d.ts +8 -0
  341. package/dist/react/captcha/useCaptchaScript.js +45 -0
  342. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  343. package/dist/react/captcha/vendors.js +8 -0
  344. package/dist/react/captcha/vendors.js.map +1 -0
  345. package/dist/react/contract.d.ts +2 -3
  346. package/dist/react/contract.js.map +1 -1
  347. package/dist/react/fetchResults.d.ts +1 -1
  348. package/dist/react/fetchResults.js +1 -1
  349. package/dist/react/fetchResults.js.map +1 -1
  350. package/dist/react/presentation/DialogSurface.js +1 -4
  351. package/dist/react/presentation/DialogSurface.js.map +1 -1
  352. package/dist/react/primitives/Checkbox.d.ts +4 -2
  353. package/dist/react/primitives/Checkbox.js +2 -1
  354. package/dist/react/primitives/Checkbox.js.map +1 -1
  355. package/dist/react/primitives/FieldShell.d.ts +2 -4
  356. package/dist/react/primitives/FieldShell.js +12 -19
  357. package/dist/react/primitives/FieldShell.js.map +1 -1
  358. package/dist/react/primitives/Input.d.ts +1 -1
  359. package/dist/react/primitives/Input.js.map +1 -1
  360. package/dist/react/renderers/calculation.js +1 -4
  361. package/dist/react/renderers/calculation.js.map +1 -1
  362. package/dist/react/renderers/checkbox.js +1 -4
  363. package/dist/react/renderers/checkbox.js.map +1 -1
  364. package/dist/react/renderers/consent.js +78 -35
  365. package/dist/react/renderers/consent.js.map +1 -1
  366. package/dist/react/renderers/date.js +33 -0
  367. package/dist/react/renderers/date.js.map +1 -0
  368. package/dist/react/renderers/email.js +1 -4
  369. package/dist/react/renderers/email.js.map +1 -1
  370. package/dist/react/renderers/file.js +29 -14
  371. package/dist/react/renderers/file.js.map +1 -1
  372. package/dist/react/renderers/index.js +9 -0
  373. package/dist/react/renderers/index.js.map +1 -1
  374. package/dist/react/renderers/message.js +41 -0
  375. package/dist/react/renderers/message.js.map +1 -0
  376. package/dist/react/renderers/number.js +1 -4
  377. package/dist/react/renderers/number.js.map +1 -1
  378. package/dist/react/renderers/region.js +43 -0
  379. package/dist/react/renderers/region.js.map +1 -0
  380. package/dist/react/renderers/repeater.js +131 -0
  381. package/dist/react/renderers/repeater.js.map +1 -0
  382. package/dist/react/renderers/select.js +1 -4
  383. package/dist/react/renderers/select.js.map +1 -1
  384. package/dist/react/renderers/text.js +1 -4
  385. package/dist/react/renderers/text.js.map +1 -1
  386. package/dist/react/renderers/textarea.js +1 -4
  387. package/dist/react/renderers/textarea.js.map +1 -1
  388. package/dist/react/state.d.ts +40 -3
  389. package/dist/react/state.js +64 -9
  390. package/dist/react/state.js.map +1 -1
  391. package/dist/react/uploadFile.d.ts +2 -2
  392. package/dist/react/uploadFile.js +1 -1
  393. package/dist/react/uploadFile.js.map +1 -1
  394. package/dist/react/useField.d.ts +0 -1
  395. package/dist/react/useField.js +6 -6
  396. package/dist/react/useField.js.map +1 -1
  397. package/dist/react/useFormState.d.ts +1 -1
  398. package/dist/react/useFormState.js +1 -1
  399. package/dist/react/useFormState.js.map +1 -1
  400. package/dist/react/validateField.js +3 -6
  401. package/dist/react/validateField.js.map +1 -1
  402. package/dist/recall/interpolate.d.ts +4 -3
  403. package/dist/recall/interpolate.js +5 -4
  404. package/dist/recall/interpolate.js.map +1 -1
  405. package/dist/recall/resolver.d.ts +1 -1
  406. package/dist/recall/resolver.js +14 -2
  407. package/dist/recall/resolver.js.map +1 -1
  408. package/dist/spam/captcha.d.ts +4 -3
  409. package/dist/spam/captcha.js +4 -3
  410. package/dist/spam/captcha.js.map +1 -1
  411. package/dist/spam/clientIp.js +9 -0
  412. package/dist/spam/clientIp.js.map +1 -0
  413. package/dist/spam/constants.js +13 -1
  414. package/dist/spam/constants.js.map +1 -1
  415. package/dist/spam/identify.js +3 -6
  416. package/dist/spam/identify.js.map +1 -1
  417. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  418. package/dist/spam/providers/hcaptcha.js +18 -0
  419. package/dist/spam/providers/hcaptcha.js.map +1 -0
  420. package/dist/spam/providers/recaptcha.d.ts +23 -0
  421. package/dist/spam/providers/recaptcha.js +23 -0
  422. package/dist/spam/providers/recaptcha.js.map +1 -0
  423. package/dist/spam/providers/siteverify.js +34 -0
  424. package/dist/spam/providers/siteverify.js.map +1 -0
  425. package/dist/spam/providers/turnstile.d.ts +17 -0
  426. package/dist/spam/providers/turnstile.js +18 -0
  427. package/dist/spam/providers/turnstile.js.map +1 -0
  428. package/dist/spam/resolveSpam.js +2 -1
  429. package/dist/spam/resolveSpam.js.map +1 -1
  430. package/dist/spam/spamGuard.js +23 -10
  431. package/dist/spam/spamGuard.js.map +1 -1
  432. package/dist/spam/types.d.ts +19 -4
  433. package/dist/submissions/SubmissionAnswers.d.ts +2 -4
  434. package/dist/submissions/SubmissionAnswers.js +114 -34
  435. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  436. package/dist/submissions/SubmissionAnswersClient.js +178 -0
  437. package/dist/submissions/SubmissionAnswersClient.js.map +1 -0
  438. package/dist/submissions/coerceBoolean.js +14 -0
  439. package/dist/submissions/coerceBoolean.js.map +1 -0
  440. package/dist/submissions/createSubmission.d.ts +36 -0
  441. package/dist/submissions/createSubmission.js +26 -0
  442. package/dist/submissions/createSubmission.js.map +1 -0
  443. package/dist/submissions/runSubmission.d.ts +10 -0
  444. package/dist/submissions/runSubmission.js +128 -27
  445. package/dist/submissions/runSubmission.js.map +1 -1
  446. package/dist/submissions/types.d.ts +12 -4
  447. package/dist/submissions/validateSubmission.js +62 -11
  448. package/dist/submissions/validateSubmission.js.map +1 -1
  449. package/dist/submissions/verifyContext.js +35 -0
  450. package/dist/submissions/verifyContext.js.map +1 -0
  451. package/dist/submissions/votedCookie.d.ts +13 -0
  452. package/dist/submissions/votedCookie.js +27 -0
  453. package/dist/submissions/votedCookie.js.map +1 -0
  454. package/dist/translations/clientRuntimeKeys.d.ts +17 -0
  455. package/dist/translations/clientRuntimeKeys.js +46 -0
  456. package/dist/translations/clientRuntimeKeys.js.map +1 -0
  457. package/dist/translations/de.d.ts +12 -0
  458. package/dist/translations/de.js +313 -0
  459. package/dist/translations/de.js.map +1 -0
  460. package/dist/translations/en.js +207 -27
  461. package/dist/translations/en.js.map +1 -1
  462. package/dist/translations/index.d.ts +14 -5
  463. package/dist/translations/index.js +14 -3
  464. package/dist/translations/index.js.map +1 -1
  465. package/dist/translations/keys.d.ts +203 -23
  466. package/dist/translations/keys.js +204 -24
  467. package/dist/translations/keys.js.map +1 -1
  468. package/dist/translations/makeTranslate.d.ts +7 -3
  469. package/dist/translations/makeTranslate.js +12 -3
  470. package/dist/translations/makeTranslate.js.map +1 -1
  471. package/dist/translations/server.js +8 -1
  472. package/dist/translations/server.js.map +1 -1
  473. package/dist/uploads/captureFileRef.d.ts +10 -3
  474. package/dist/uploads/captureFileRef.js +18 -9
  475. package/dist/uploads/captureFileRef.js.map +1 -1
  476. package/dist/uploads/formatBytes.d.ts +9 -0
  477. package/dist/uploads/formatBytes.js +25 -0
  478. package/dist/uploads/formatBytes.js.map +1 -0
  479. package/dist/uploads/resolveFileRef.js +3 -2
  480. package/dist/uploads/resolveFileRef.js.map +1 -1
  481. package/dist/uploads/types.d.ts +1 -2
  482. package/dist/validation/buildRuleBlocks.js +18 -17
  483. package/dist/validation/buildRuleBlocks.js.map +1 -1
  484. package/dist/validation/builtin/email.js +2 -1
  485. package/dist/validation/builtin/email.js.map +1 -1
  486. package/dist/validation/builtin/index.d.ts +14 -0
  487. package/dist/validation/builtin/index.js +12 -1
  488. package/dist/validation/builtin/index.js.map +1 -1
  489. package/dist/validation/builtin/matchesField.js +3 -7
  490. package/dist/validation/builtin/matchesField.js.map +1 -1
  491. package/dist/validation/builtin/max.js +1 -0
  492. package/dist/validation/builtin/max.js.map +1 -1
  493. package/dist/validation/builtin/maxDate.js +24 -0
  494. package/dist/validation/builtin/maxDate.js.map +1 -0
  495. package/dist/validation/builtin/maxLength.js +1 -0
  496. package/dist/validation/builtin/maxLength.js.map +1 -1
  497. package/dist/validation/builtin/min.js +1 -0
  498. package/dist/validation/builtin/min.js.map +1 -1
  499. package/dist/validation/builtin/minDate.js +24 -0
  500. package/dist/validation/builtin/minDate.js.map +1 -0
  501. package/dist/validation/builtin/minLength.js +1 -0
  502. package/dist/validation/builtin/minLength.js.map +1 -1
  503. package/dist/validation/builtin/notAlreadySubmitted.js +2 -0
  504. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  505. package/dist/validation/builtin/oneOf.js +1 -0
  506. package/dist/validation/builtin/oneOf.js.map +1 -1
  507. package/dist/validation/builtin/pattern.js +11 -2
  508. package/dist/validation/builtin/pattern.js.map +1 -1
  509. package/dist/validation/builtin/url.js +1 -0
  510. package/dist/validation/builtin/url.js.map +1 -1
  511. package/dist/validation/builtin/validateDateBound.js +18 -0
  512. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  513. package/dist/validation/builtin/validateRegexParams.js +44 -0
  514. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  515. package/dist/validation/emailPattern.js +10 -0
  516. package/dist/validation/emailPattern.js.map +1 -0
  517. package/dist/validation/fieldTargetParam.d.ts +22 -0
  518. package/dist/validation/fieldTargetParam.js +38 -0
  519. package/dist/validation/fieldTargetParam.js.map +1 -0
  520. package/dist/validation/registry.d.ts +2 -1
  521. package/dist/validation/registry.js +2 -9
  522. package/dist/validation/registry.js.map +1 -1
  523. package/dist/validation/runValidation.js +9 -30
  524. package/dist/validation/runValidation.js.map +1 -1
  525. package/dist/validation/types.d.ts +5 -11
  526. package/dist/validation/validateUrl.js +22 -0
  527. package/dist/validation/validateUrl.js.map +1 -0
  528. package/package.json +7 -5
  529. package/styles/form-builder.css +10 -190
  530. package/dist/collections/uploads.d.ts +0 -29
  531. package/dist/collections/uploads.js +0 -46
  532. package/dist/collections/uploads.js.map +0 -1
  533. package/dist/consent/buildConsentSourceConfig.js +0 -49
  534. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  535. package/dist/consent/builtin/index.d.ts +0 -7
  536. package/dist/consent/builtin/index.js +0 -11
  537. package/dist/consent/builtin/index.js.map +0 -1
  538. package/dist/consent/builtin/pageReference.js +0 -75
  539. package/dist/consent/builtin/pageReference.js.map +0 -1
  540. package/dist/consent/builtin/static.js +0 -44
  541. package/dist/consent/builtin/static.js.map +0 -1
  542. package/dist/consent/defineConsentSource.d.ts +0 -35
  543. package/dist/consent/defineConsentSource.js +0 -6
  544. package/dist/consent/defineConsentSource.js.map +0 -1
  545. package/dist/consent/registry.d.ts +0 -16
  546. package/dist/consent/registry.js +0 -16
  547. package/dist/consent/registry.js.map +0 -1
  548. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  549. package/dist/consent/resolveConsentLinks.js +0 -26
  550. package/dist/consent/resolveConsentLinks.js.map +0 -1
  551. package/dist/presentations/registry.d.ts +0 -15
  552. package/dist/presentations/registry.js +0 -15
  553. package/dist/presentations/registry.js.map +0 -1
  554. package/dist/react/toFormDocument.d.ts +0 -24
  555. package/dist/react/toFormDocument.js +0 -22
  556. package/dist/react/toFormDocument.js.map +0 -1
@@ -4,7 +4,7 @@ import { en } from "../translations/en.js";
4
4
  import { makeTranslate } from "../translations/makeTranslate.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/react/FormResults.tsx
7
- const OneResult = ({ result, t, showCounts }) => {
7
+ const OneResult = ({ result, t, showCounts, winningValues }) => {
8
8
  if (result.total === 0) return /* @__PURE__ */ jsxs("section", {
9
9
  className: "fb-results__field",
10
10
  children: [result.label ? /* @__PURE__ */ jsx("h3", {
@@ -24,31 +24,37 @@ const OneResult = ({ result, t, showCounts }) => {
24
24
  }) : null,
25
25
  /* @__PURE__ */ jsx("ul", {
26
26
  className: "fb-results__list",
27
- children: result.buckets.map((bucket) => /* @__PURE__ */ jsxs("li", {
28
- className: "fb-results__row",
29
- children: [
30
- /* @__PURE__ */ jsx("span", {
31
- className: "fb-results__option",
32
- children: bucket.label
33
- }),
34
- /* @__PURE__ */ jsx("span", {
35
- className: "fb-results__bar",
36
- children: /* @__PURE__ */ jsx("span", {
37
- className: "fb-results__bar-fill",
38
- style: { width: `${bucket.percentage}%` },
39
- "aria-hidden": "true"
40
- })
41
- }),
42
- /* @__PURE__ */ jsxs("span", {
43
- className: "fb-results__pct",
44
- children: [bucket.percentage, "%"]
45
- }),
46
- showCounts ? /* @__PURE__ */ jsx("span", {
47
- className: "fb-results__count",
48
- children: bucket.count
49
- }) : null
50
- ]
51
- }, bucket.value))
27
+ children: result.buckets.map((bucket) => {
28
+ const isWinner = winningValues?.includes(bucket.value) ?? false;
29
+ return /* @__PURE__ */ jsxs("li", {
30
+ className: isWinner ? "fb-results__row fb-results__bucket--winner" : "fb-results__row",
31
+ children: [
32
+ /* @__PURE__ */ jsxs("span", {
33
+ className: "fb-results__option",
34
+ children: [bucket.label, isWinner ? /* @__PURE__ */ jsx("span", {
35
+ className: "fb-results__winner-badge",
36
+ children: t(keys.resultsWinner)
37
+ }) : null]
38
+ }),
39
+ /* @__PURE__ */ jsx("span", {
40
+ className: "fb-results__bar",
41
+ children: /* @__PURE__ */ jsx("span", {
42
+ className: "fb-results__bar-fill",
43
+ style: { width: `${bucket.percentage}%` },
44
+ "aria-hidden": "true"
45
+ })
46
+ }),
47
+ /* @__PURE__ */ jsxs("span", {
48
+ className: "fb-results__pct",
49
+ children: [bucket.percentage, "%"]
50
+ }),
51
+ showCounts ? /* @__PURE__ */ jsx("span", {
52
+ className: "fb-results__count",
53
+ children: bucket.count
54
+ }) : null
55
+ ]
56
+ }, bucket.value);
57
+ })
52
58
  }),
53
59
  /* @__PURE__ */ jsxs("p", {
54
60
  className: "fb-results__total",
@@ -66,16 +72,18 @@ const OneResult = ({ result, t, showCounts }) => {
66
72
  * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,
67
73
  * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never
68
74
  * fetches). The option label, count, and percentage are real text (the accessible content); the bar fill
69
- * is `aria-hidden` visual sugar sized by inline width.
75
+ * is `aria-hidden` visual sugar sized by inline width. With `winningValues`, every matching bucket gets
76
+ * the winner class and a translated badge (real text, so the outcome is accessible); a tie highlights more than one.
70
77
  */
71
- const FormResults = ({ results, t, showCounts = true }) => {
78
+ const FormResults = ({ results, t, showCounts = true, winningValues }) => {
72
79
  const translate = t ?? makeTranslate(en);
73
80
  return /* @__PURE__ */ jsx("div", {
74
81
  className: "fb-results",
75
82
  children: (Array.isArray(results) ? results : [results]).map((result) => /* @__PURE__ */ jsx(OneResult, {
76
83
  result,
77
84
  t: translate,
78
- showCounts
85
+ showCounts,
86
+ winningValues
79
87
  }, result.field))
80
88
  });
81
89
  };
@@ -1 +1 @@
1
- {"version":3,"file":"FormResults.js","names":[],"sources":["../../src/react/FormResults.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport type { RendererTranslate } from './contract'\n\nexport type FormResultsProps = {\n\t/** One field aggregation or several (a survey summary), resolved server-side and passed in. */\n\tresults: FieldAggregation | FieldAggregation[]\n\t/** Localized translator for built-in copy. Defaults to identity (returns the key). */\n\tt?: RendererTranslate\n\tlocale?: string\n\t/** Show the raw count beside each option. Default true. */\n\tshowCounts?: boolean\n}\n\nconst OneResult = ({\n\tresult,\n\tt,\n\tshowCounts,\n}: {\n\tresult: FieldAggregation\n\tt: RendererTranslate\n\tshowCounts: boolean\n}): ReactNode => {\n\tif (result.total === 0) {\n\t\treturn (\n\t\t\t<section className=\"fb-results__field\">\n\t\t\t\t{result.label ? <h3 className=\"fb-results__label\">{result.label}</h3> : null}\n\t\t\t\t<p className=\"fb-results__empty\">{t(keys.resultsNoResponses)}</p>\n\t\t\t</section>\n\t\t)\n\t}\n\treturn (\n\t\t<section className=\"fb-results__field\">\n\t\t\t{result.label ? <h3 className=\"fb-results__label\">{result.label}</h3> : null}\n\t\t\t<ul className=\"fb-results__list\">\n\t\t\t\t{result.buckets.map((bucket) => (\n\t\t\t\t\t<li key={bucket.value} className=\"fb-results__row\">\n\t\t\t\t\t\t<span className=\"fb-results__option\">{bucket.label}</span>\n\t\t\t\t\t\t<span className=\"fb-results__bar\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"fb-results__bar-fill\"\n\t\t\t\t\t\t\t\tstyle={{ width: `${bucket.percentage}%` }}\n\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"fb-results__pct\">{bucket.percentage}%</span>\n\t\t\t\t\t\t{showCounts ? <span className=\"fb-results__count\">{bucket.count}</span> : null}\n\t\t\t\t\t</li>\n\t\t\t\t))}\n\t\t\t</ul>\n\t\t\t<p className=\"fb-results__total\">\n\t\t\t\t{result.total} {t(keys.resultsResponses)}\n\t\t\t\t{result.truncated ? ` (${t(keys.resultsTruncated)})` : ''}\n\t\t\t</p>\n\t\t</section>\n\t)\n}\n\n/**\n * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,\n * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never\n * fetches). The option label, count, and percentage are real text (the accessible content); the bar fill\n * is `aria-hidden` visual sugar sized by inline width.\n */\nexport const FormResults = ({ results, t, showCounts = true }: FormResultsProps): ReactNode => {\n\tconst translate: RendererTranslate = t ?? makeTranslate(en)\n\tconst list = Array.isArray(results) ? results : [results]\n\treturn (\n\t\t<div className=\"fb-results\">\n\t\t\t{list.map((result) => (\n\t\t\t\t<OneResult key={result.field} result={result} t={translate} showCounts={showCounts} />\n\t\t\t))}\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;AAmBA,MAAM,aAAa,EAClB,QACA,GACA,iBAKgB;CAChB,IAAI,OAAO,UAAU,GACpB,OACC,qBAAC,WAAD;EAAS,WAAU;YAAnB,CACE,OAAO,QAAQ,oBAAC,MAAD;GAAI,WAAU;aAAqB,OAAO;EAAU,CAAA,IAAI,MACxE,oBAAC,KAAD;GAAG,WAAU;aAAqB,EAAE,KAAK,kBAAkB;EAAK,CAAA,CACxD;;CAGX,OACC,qBAAC,WAAD;EAAS,WAAU;YAAnB;GACE,OAAO,QAAQ,oBAAC,MAAD;IAAI,WAAU;cAAqB,OAAO;GAAU,CAAA,IAAI;GACxE,oBAAC,MAAD;IAAI,WAAU;cACZ,OAAO,QAAQ,KAAK,WACpB,qBAAC,MAAD;KAAuB,WAAU;eAAjC;MACC,oBAAC,QAAD;OAAM,WAAU;iBAAsB,OAAO;MAAY,CAAA;MACzD,oBAAC,QAAD;OAAM,WAAU;iBACf,oBAAC,QAAD;QACC,WAAU;QACV,OAAO,EAAE,OAAO,GAAG,OAAO,WAAW,GAAG;QACxC,eAAY;OACZ,CAAA;MACI,CAAA;MACN,qBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAmC,OAAO,YAAW,GAAO;;MAC3D,aAAa,oBAAC,QAAD;OAAM,WAAU;iBAAqB,OAAO;MAAY,CAAA,IAAI;KACvE;OAXK,OAAO,KAWZ,CACJ;GACE,CAAA;GACJ,qBAAC,KAAD;IAAG,WAAU;cAAb;KACE,OAAO;KAAM;KAAE,EAAE,KAAK,gBAAgB;KACtC,OAAO,YAAY,KAAK,EAAE,KAAK,gBAAgB,EAAE,KAAK;IACrD;;EACK;;AAEX;;;;;;;AAQA,MAAa,eAAe,EAAE,SAAS,GAAG,aAAa,WAAwC;CAC9F,MAAM,YAA+B,KAAK,cAAc,EAAE;CAE1D,OACC,oBAAC,OAAD;EAAK,WAAU;aAFH,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,GAGhD,KAAK,WACV,oBAAC,WAAD;GAAsC;GAAQ,GAAG;GAAuB;EAAa,GAArE,OAAO,KAA8D,CACrF;CACG,CAAA;AAEP"}
1
+ {"version":3,"file":"FormResults.js","names":[],"sources":["../../src/react/FormResults.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport type { RendererTranslate } from './contract'\n\nexport type FormResultsProps = {\n\t/** One field aggregation or several (a survey summary), resolved server-side and passed in. */\n\tresults: FieldAggregation | FieldAggregation[]\n\t/** Localized translator for built-in copy. Defaults to identity (returns the key). */\n\tt?: RendererTranslate\n\tlocale?: string\n\t/** Show the raw count beside each option. Default true. */\n\tshowCounts?: boolean\n\t/** The recorded outcome's stable option values; every matching bucket is highlighted as a winner (a tie highlights more than one). */\n\twinningValues?: string[]\n}\n\nconst OneResult = ({\n\tresult,\n\tt,\n\tshowCounts,\n\twinningValues,\n}: {\n\tresult: FieldAggregation\n\tt: RendererTranslate\n\tshowCounts: boolean\n\twinningValues?: string[]\n}): ReactNode => {\n\tif (result.total === 0) {\n\t\treturn (\n\t\t\t<section className=\"fb-results__field\">\n\t\t\t\t{result.label ? <h3 className=\"fb-results__label\">{result.label}</h3> : null}\n\t\t\t\t<p className=\"fb-results__empty\">{t(keys.resultsNoResponses)}</p>\n\t\t\t</section>\n\t\t)\n\t}\n\treturn (\n\t\t<section className=\"fb-results__field\">\n\t\t\t{result.label ? <h3 className=\"fb-results__label\">{result.label}</h3> : null}\n\t\t\t<ul className=\"fb-results__list\">\n\t\t\t\t{result.buckets.map((bucket) => {\n\t\t\t\t\tconst isWinner = winningValues?.includes(bucket.value) ?? false\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={bucket.value}\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\tisWinner ? 'fb-results__row fb-results__bucket--winner' : 'fb-results__row'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"fb-results__option\">\n\t\t\t\t\t\t\t\t{bucket.label}\n\t\t\t\t\t\t\t\t{isWinner ? (\n\t\t\t\t\t\t\t\t\t<span className=\"fb-results__winner-badge\">{t(keys.resultsWinner)}</span>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"fb-results__bar\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tclassName=\"fb-results__bar-fill\"\n\t\t\t\t\t\t\t\t\tstyle={{ width: `${bucket.percentage}%` }}\n\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span className=\"fb-results__pct\">{bucket.percentage}%</span>\n\t\t\t\t\t\t\t{showCounts ? <span className=\"fb-results__count\">{bucket.count}</span> : null}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t)\n\t\t\t\t})}\n\t\t\t</ul>\n\t\t\t<p className=\"fb-results__total\">\n\t\t\t\t{result.total} {t(keys.resultsResponses)}\n\t\t\t\t{result.truncated ? ` (${t(keys.resultsTruncated)})` : ''}\n\t\t\t</p>\n\t\t</section>\n\t)\n}\n\n/**\n * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,\n * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never\n * fetches). The option label, count, and percentage are real text (the accessible content); the bar fill\n * is `aria-hidden` visual sugar sized by inline width. With `winningValues`, every matching bucket gets\n * the winner class and a translated badge (real text, so the outcome is accessible); a tie highlights more than one.\n */\nexport const FormResults = ({\n\tresults,\n\tt,\n\tshowCounts = true,\n\twinningValues,\n}: FormResultsProps): ReactNode => {\n\tconst translate: RendererTranslate = t ?? makeTranslate(en)\n\tconst list = Array.isArray(results) ? results : [results]\n\treturn (\n\t\t<div className=\"fb-results\">\n\t\t\t{list.map((result) => (\n\t\t\t\t<OneResult\n\t\t\t\t\tkey={result.field}\n\t\t\t\t\tresult={result}\n\t\t\t\t\tt={translate}\n\t\t\t\t\tshowCounts={showCounts}\n\t\t\t\t\twinningValues={winningValues}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;AAqBA,MAAM,aAAa,EAClB,QACA,GACA,YACA,oBAMgB;CAChB,IAAI,OAAO,UAAU,GACpB,OACC,qBAAC,WAAD;EAAS,WAAU;YAAnB,CACE,OAAO,QAAQ,oBAAC,MAAD;GAAI,WAAU;aAAqB,OAAO;EAAU,CAAA,IAAI,MACxE,oBAAC,KAAD;GAAG,WAAU;aAAqB,EAAE,KAAK,kBAAkB;EAAK,CAAA,CACxD;;CAGX,OACC,qBAAC,WAAD;EAAS,WAAU;YAAnB;GACE,OAAO,QAAQ,oBAAC,MAAD;IAAI,WAAU;cAAqB,OAAO;GAAU,CAAA,IAAI;GACxE,oBAAC,MAAD;IAAI,WAAU;cACZ,OAAO,QAAQ,KAAK,WAAW;KAC/B,MAAM,WAAW,eAAe,SAAS,OAAO,KAAK,KAAK;KAC1D,OACC,qBAAC,MAAD;MAEC,WACC,WAAW,+CAA+C;gBAH5D;OAMC,qBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,OAAO,OACP,WACA,oBAAC,QAAD;SAAM,WAAU;mBAA4B,EAAE,KAAK,aAAa;QAAQ,CAAA,IACrE,IACC;;OACN,oBAAC,QAAD;QAAM,WAAU;kBACf,oBAAC,QAAD;SACC,WAAU;SACV,OAAO,EAAE,OAAO,GAAG,OAAO,WAAW,GAAG;SACxC,eAAY;QACZ,CAAA;OACI,CAAA;OACN,qBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAmC,OAAO,YAAW,GAAO;;OAC3D,aAAa,oBAAC,QAAD;QAAM,WAAU;kBAAqB,OAAO;OAAY,CAAA,IAAI;MACvE;QApBE,OAAO,KAoBT;IAEN,CAAC;GACE,CAAA;GACJ,qBAAC,KAAD;IAAG,WAAU;cAAb;KACE,OAAO;KAAM;KAAE,EAAE,KAAK,gBAAgB;KACtC,OAAO,YAAY,KAAK,EAAE,KAAK,gBAAgB,EAAE,KAAK;IACrD;;EACK;;AAEX;;;;;;;;AASA,MAAa,eAAe,EAC3B,SACA,GACA,aAAa,MACb,oBACkC;CAClC,MAAM,YAA+B,KAAK,cAAc,EAAE;CAE1D,OACC,oBAAC,OAAD;EAAK,WAAU;aAFH,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO,GAGhD,KAAK,WACV,oBAAC,WAAD;GAES;GACR,GAAG;GACS;GACG;EACf,GALK,OAAO,KAKZ,CACD;CACG,CAAA;AAEP"}
@@ -0,0 +1,17 @@
1
+ //#region src/react/FormSteps.d.ts
2
+ type FormStepsProps = {
3
+ /** Additional CSS class on the wrapping `.fb-form-steps` list. */className?: string;
4
+ };
5
+ /**
6
+ * Step-progress indicator for multi-step forms: an ordered list of the flow's steps with the
7
+ * current one marked via `aria-current="step"` and a `--current` class. Step labels use the
8
+ * step's title, falling back to the translated "Step {n}" (same semantics as the admin flow
9
+ * builder). Carries no styling opinions beyond `fb-form-steps` classes. Renders nothing when
10
+ * the form has fewer than two steps. Must render inside `<Form>`.
11
+ */
12
+ declare const FormSteps: ({
13
+ className
14
+ }: FormStepsProps) => import("react/jsx-runtime").JSX.Element | null;
15
+ //#endregion
16
+ export { FormSteps, FormStepsProps };
17
+ //# sourceMappingURL=FormSteps.d.ts.map
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { keys } from "../translations/keys.js";
3
+ import { stepLabel } from "../flow/stepLabel.js";
4
+ import { cn } from "./cn.js";
5
+ import { useFormContext } from "./FormContext.js";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/react/FormSteps.tsx
8
+ /**
9
+ * Step-progress indicator for multi-step forms: an ordered list of the flow's steps with the
10
+ * current one marked via `aria-current="step"` and a `--current` class. Step labels use the
11
+ * step's title, falling back to the translated "Step {n}" (same semantics as the admin flow
12
+ * builder). Carries no styling opinions beyond `fb-form-steps` classes. Renders nothing when
13
+ * the form has fewer than two steps. Must render inside `<Form>`.
14
+ */
15
+ const FormSteps = ({ className }) => {
16
+ const { step, t } = useFormContext();
17
+ const steps = step.flow?.steps;
18
+ if (!steps || steps.length < 2) return null;
19
+ const fallbackTitle = t(keys.flowStepFallbackTitle);
20
+ return /* @__PURE__ */ jsx("ol", {
21
+ className: cn("fb-form-steps", className),
22
+ children: steps.map((flowStep, index) => {
23
+ const current = index === step.stepIndex;
24
+ return /* @__PURE__ */ jsx("li", {
25
+ className: cn("fb-form-steps__step", current && "fb-form-steps__step--current"),
26
+ "aria-current": current ? "step" : void 0,
27
+ children: stepLabel(flowStep, index, fallbackTitle)
28
+ }, flowStep.id);
29
+ })
30
+ });
31
+ };
32
+ //#endregion
33
+ export { FormSteps };
34
+
35
+ //# sourceMappingURL=FormSteps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormSteps.js","names":[],"sources":["../../src/react/FormSteps.tsx"],"sourcesContent":["'use client'\n\nimport { stepLabel } from '../flow/stepLabel'\nimport { keys } from '../translations/keys'\nimport { cn } from './cn'\nimport { useFormContext } from './FormContext'\n\nexport type FormStepsProps = {\n\t/** Additional CSS class on the wrapping `.fb-form-steps` list. */\n\tclassName?: string\n}\n\n/**\n * Step-progress indicator for multi-step forms: an ordered list of the flow's steps with the\n * current one marked via `aria-current=\"step\"` and a `--current` class. Step labels use the\n * step's title, falling back to the translated \"Step {n}\" (same semantics as the admin flow\n * builder). Carries no styling opinions beyond `fb-form-steps` classes. Renders nothing when\n * the form has fewer than two steps. Must render inside `<Form>`.\n */\nexport const FormSteps = ({ className }: FormStepsProps) => {\n\tconst { step, t } = useFormContext()\n\tconst steps = step.flow?.steps\n\tif (!steps || steps.length < 2) {\n\t\treturn null\n\t}\n\tconst fallbackTitle = t(keys.flowStepFallbackTitle)\n\treturn (\n\t\t<ol className={cn('fb-form-steps', className)}>\n\t\t\t{steps.map((flowStep, index) => {\n\t\t\t\tconst current = index === step.stepIndex\n\t\t\t\treturn (\n\t\t\t\t\t<li\n\t\t\t\t\t\tkey={flowStep.id}\n\t\t\t\t\t\tclassName={cn('fb-form-steps__step', current && 'fb-form-steps__step--current')}\n\t\t\t\t\t\taria-current={current ? 'step' : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\t{stepLabel(flowStep, index, fallbackTitle)}\n\t\t\t\t\t</li>\n\t\t\t\t)\n\t\t\t})}\n\t\t</ol>\n\t)\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,MAAa,aAAa,EAAE,gBAAgC;CAC3D,MAAM,EAAE,MAAM,MAAM,eAAe;CACnC,MAAM,QAAQ,KAAK,MAAM;CACzB,IAAI,CAAC,SAAS,MAAM,SAAS,GAC5B,OAAO;CAER,MAAM,gBAAgB,EAAE,KAAK,qBAAqB;CAClD,OACC,oBAAC,MAAD;EAAI,WAAW,GAAG,iBAAiB,SAAS;YAC1C,MAAM,KAAK,UAAU,UAAU;GAC/B,MAAM,UAAU,UAAU,KAAK;GAC/B,OACC,oBAAC,MAAD;IAEC,WAAW,GAAG,uBAAuB,WAAW,8BAA8B;IAC9E,gBAAc,UAAU,SAAS,KAAA;cAEhC,UAAU,UAAU,OAAO,aAAa;GACtC,GALE,SAAS,EAKX;EAEN,CAAC;CACE,CAAA;AAEN"}
@@ -3,8 +3,13 @@ import { fetchFormResults } from "./fetchResults.js";
3
3
 
4
4
  //#region src/react/Poll.d.ts
5
5
  type PollProps = FormProps & {
6
- /** The choice field whose results are shown after voting (should match the form's public `resultsField`). */resultsField: string; /** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */
7
- storageKey?: string; /** Force the voted state (controlled). Omit to use the localStorage guard. */
6
+ /** The choice field whose results are shown after voting (should match the form's public `poll.resultsField`). */resultsField: string; /** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */
7
+ storageKey?: string;
8
+ /**
9
+ * Server-known voted state, ORed with the localStorage guard: `true` marks the visitor as voted;
10
+ * `false`/omitted falls back to localStorage. SSR hosts using the plugin's `poll.votedCookie`
11
+ * option pass `hasVotedCookie(cookieHeader, form.id)` here.
12
+ */
8
13
  hasVoted?: boolean;
9
14
  /**
10
15
  * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module
@@ -13,10 +18,16 @@ type PollProps = FormProps & {
13
18
  fetchResultsImpl?: typeof fetchFormResults;
14
19
  };
15
20
  /**
16
- * A poll: renders `<Form>` until the visitor votes, then fetches the aggregate results and shows
17
- * `<FormResults>`. A per-browser localStorage flag (`storageKey`) skips straight to results on revisit. The
18
- * guard is UX, not integrity (bypassable): server-enforced one-per-identity dedup composes via `req.user`
19
- * (authed forms) or a `notAlreadySubmitted` rule, with cookie/IP identity deferred to the spam phase.
21
+ * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows
22
+ * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated
23
+ * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open
24
+ * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A
25
+ * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final
26
+ * notice plus results with every winning bucket highlighted (a tie highlights more than one). A
27
+ * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`
28
+ * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not
29
+ * integrity (bypassable): server-enforced
30
+ * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.
20
31
  */
21
32
  declare const Poll: ({
22
33
  resultsField,
@@ -1,9 +1,13 @@
1
1
  "use client";
2
+ import { keys } from "../translations/keys.js";
3
+ import { isPollClosed } from "../form/pollState.js";
4
+ import { en } from "../translations/en.js";
5
+ import { makeTranslate } from "../translations/makeTranslate.js";
2
6
  import { Form } from "./Form.js";
3
7
  import { FormResults } from "./FormResults.js";
4
8
  import { fetchFormResults } from "./fetchResults.js";
5
- import { useCallback, useEffect, useState } from "react";
6
- import { jsx } from "react/jsx-runtime";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { useCallback, useEffect, useMemo, useState } from "react";
7
11
  //#region src/react/Poll.tsx
8
12
  const readVoted = (key) => {
9
13
  try {
@@ -18,53 +22,107 @@ const writeVoted = (key) => {
18
22
  } catch {}
19
23
  };
20
24
  /**
21
- * A poll: renders `<Form>` until the visitor votes, then fetches the aggregate results and shows
22
- * `<FormResults>`. A per-browser localStorage flag (`storageKey`) skips straight to results on revisit. The
23
- * guard is UX, not integrity (bypassable): server-enforced one-per-identity dedup composes via `req.user`
24
- * (authed forms) or a `notAlreadySubmitted` rule, with cookie/IP identity deferred to the spam phase.
25
+ * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows
26
+ * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated
27
+ * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open
28
+ * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A
29
+ * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final
30
+ * notice plus results with every winning bucket highlighted (a tie highlights more than one). A
31
+ * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`
32
+ * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not
33
+ * integrity (bypassable): server-enforced
34
+ * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.
25
35
  */
26
36
  const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchFormResults, apiRoute, onSuccess, ...formProps }) => {
27
37
  const key = storageKey ?? `fb-poll-${formProps.form.id}`;
38
+ const poll = formProps.form.poll;
39
+ const closed = isPollClosed(poll);
40
+ const winningValues = poll?.outcome?.winningValues;
41
+ const finalized = Array.isArray(winningValues) && winningValues.length > 0;
42
+ const resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === "afterClose";
28
43
  const [voted, setVoted] = useState(false);
29
44
  const [results, setResults] = useState(null);
45
+ const [loadFailed, setLoadFailed] = useState(false);
46
+ const translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t]);
30
47
  const loadResults = useCallback(async () => {
31
48
  const result = await fetchResultsImpl({
32
49
  formId: formProps.form.id,
33
50
  field: resultsField,
34
51
  apiRoute
35
52
  });
36
- setResults(result.ok ? result.results : []);
53
+ if (result.ok) {
54
+ setResults(result.results);
55
+ setLoadFailed(false);
56
+ } else setLoadFailed(true);
37
57
  }, [
38
58
  fetchResultsImpl,
39
59
  formProps.form.id,
40
60
  resultsField,
41
61
  apiRoute
42
62
  ]);
63
+ const resultsError = /* @__PURE__ */ jsx("p", {
64
+ className: "fb-poll__error",
65
+ role: "alert",
66
+ children: translate(keys.pollResultsError)
67
+ });
43
68
  useEffect(() => {
44
- if (hasVoted ?? readVoted(key)) {
45
- setVoted(true);
46
- loadResults();
47
- }
69
+ const already = hasVoted === true || readVoted(key);
70
+ if (already) setVoted(true);
71
+ if (already && !resultsAwaitClose || closed || finalized) loadResults();
48
72
  }, [
49
73
  hasVoted,
50
74
  key,
51
- loadResults
75
+ loadResults,
76
+ closed,
77
+ resultsAwaitClose,
78
+ finalized
52
79
  ]);
53
- const handleSuccess = useCallback((submissionId) => {
80
+ const handleSuccess = useCallback((submissionId, result) => {
54
81
  writeVoted(key);
55
82
  setVoted(true);
56
- loadResults();
57
- onSuccess?.(submissionId);
83
+ if (!resultsAwaitClose) loadResults();
84
+ onSuccess?.(submissionId, result);
58
85
  }, [
59
86
  key,
60
87
  loadResults,
61
- onSuccess
88
+ onSuccess,
89
+ resultsAwaitClose
62
90
  ]);
63
- if (voted) return results ? /* @__PURE__ */ jsx(FormResults, {
64
- results,
65
- t: formProps.t,
66
- locale: formProps.locale
67
- }) : null;
91
+ if (finalized) return /* @__PURE__ */ jsxs("div", {
92
+ className: "fb-poll fb-poll--final",
93
+ children: [/* @__PURE__ */ jsx("p", {
94
+ className: "fb-poll__final",
95
+ children: translate(keys.pollFinalResult)
96
+ }), loadFailed ? resultsError : results ? /* @__PURE__ */ jsx(FormResults, {
97
+ results,
98
+ winningValues,
99
+ t: formProps.t,
100
+ locale: formProps.locale
101
+ }) : null]
102
+ });
103
+ if (closed) return /* @__PURE__ */ jsxs("div", {
104
+ className: "fb-poll fb-poll--closed",
105
+ children: [/* @__PURE__ */ jsx("p", {
106
+ className: "fb-poll__closed",
107
+ children: translate(keys.pollClosed)
108
+ }), loadFailed ? resultsError : results ? /* @__PURE__ */ jsx(FormResults, {
109
+ results,
110
+ t: formProps.t,
111
+ locale: formProps.locale
112
+ }) : null]
113
+ });
114
+ if (voted) {
115
+ if (resultsAwaitClose) return /* @__PURE__ */ jsx("p", {
116
+ className: "fb-poll__await-close",
117
+ children: translate(keys.pollResultsAfterClose)
118
+ });
119
+ if (loadFailed) return resultsError;
120
+ return results ? /* @__PURE__ */ jsx(FormResults, {
121
+ results,
122
+ t: formProps.t,
123
+ locale: formProps.locale
124
+ }) : null;
125
+ }
68
126
  return /* @__PURE__ */ jsx(Form, {
69
127
  ...formProps,
70
128
  apiRoute,
@@ -1 +1 @@
1
- {"version":3,"file":"Poll.js","names":[],"sources":["../../src/react/Poll.tsx"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { Form, type FormProps } from './Form'\nimport { FormResults } from './FormResults'\nimport { type FetchResultsResult, fetchFormResults } from './fetchResults'\n\nexport type PollProps = FormProps & {\n\t/** The choice field whose results are shown after voting (should match the form's public `resultsField`). */\n\tresultsField: string\n\t/** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */\n\tstorageKey?: string\n\t/** Force the voted state (controlled). Omit to use the localStorage guard. */\n\thasVoted?: boolean\n\t/**\n\t * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module\n\t * scope, `useCallback`, or `useMemo`): an inline function re-runs the load effect and double-fetches.\n\t */\n\tfetchResultsImpl?: typeof fetchFormResults\n}\n\nconst readVoted = (key: string): boolean => {\n\ttry {\n\t\treturn window.localStorage.getItem(key) != null\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst writeVoted = (key: string): void => {\n\ttry {\n\t\twindow.localStorage.setItem(key, '1')\n\t} catch {\n\t\t// Private mode / storage disabled: the guard is best-effort UX, never integrity.\n\t}\n}\n\n/**\n * A poll: renders `<Form>` until the visitor votes, then fetches the aggregate results and shows\n * `<FormResults>`. A per-browser localStorage flag (`storageKey`) skips straight to results on revisit. The\n * guard is UX, not integrity (bypassable): server-enforced one-per-identity dedup composes via `req.user`\n * (authed forms) or a `notAlreadySubmitted` rule, with cookie/IP identity deferred to the spam phase.\n */\nexport const Poll = ({\n\tresultsField,\n\tstorageKey,\n\thasVoted,\n\tfetchResultsImpl = fetchFormResults,\n\tapiRoute,\n\tonSuccess,\n\t...formProps\n}: PollProps) => {\n\tconst key = storageKey ?? `fb-poll-${formProps.form.id}`\n\tconst [voted, setVoted] = useState(false)\n\tconst [results, setResults] = useState<FieldAggregation[] | null>(null)\n\n\tconst loadResults = useCallback(async () => {\n\t\tconst result: FetchResultsResult = await fetchResultsImpl({\n\t\t\tformId: formProps.form.id,\n\t\t\tfield: resultsField,\n\t\t\tapiRoute,\n\t\t})\n\t\tsetResults(result.ok ? result.results : [])\n\t}, [fetchResultsImpl, formProps.form.id, resultsField, apiRoute])\n\n\tuseEffect(() => {\n\t\tconst already = hasVoted ?? readVoted(key)\n\t\tif (already) {\n\t\t\tsetVoted(true)\n\t\t\tvoid loadResults()\n\t\t}\n\t}, [hasVoted, key, loadResults])\n\n\tconst handleSuccess = useCallback(\n\t\t(submissionId?: string) => {\n\t\t\twriteVoted(key)\n\t\t\tsetVoted(true)\n\t\t\tvoid loadResults()\n\t\t\tonSuccess?.(submissionId)\n\t\t},\n\t\t[key, loadResults, onSuccess]\n\t)\n\n\tif (voted) {\n\t\treturn results ? (\n\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t) : null\n\t}\n\n\treturn <Form {...formProps} apiRoute={apiRoute} onSuccess={handleSuccess} />\n}\n"],"mappings":";;;;;;;AAsBA,MAAM,aAAa,QAAyB;CAC3C,IAAI;EACH,OAAO,OAAO,aAAa,QAAQ,GAAG,KAAK;CAC5C,QAAQ;EACP,OAAO;CACR;AACD;AAEA,MAAM,cAAc,QAAsB;CACzC,IAAI;EACH,OAAO,aAAa,QAAQ,KAAK,GAAG;CACrC,QAAQ,CAER;AACD;;;;;;;AAQA,MAAa,QAAQ,EACpB,cACA,YACA,UACA,mBAAmB,kBACnB,UACA,WACA,GAAG,gBACa;CAChB,MAAM,MAAM,cAAc,WAAW,UAAU,KAAK;CACpD,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,CAAC,SAAS,cAAc,SAAoC,IAAI;CAEtE,MAAM,cAAc,YAAY,YAAY;EAC3C,MAAM,SAA6B,MAAM,iBAAiB;GACzD,QAAQ,UAAU,KAAK;GACvB,OAAO;GACP;EACD,CAAC;EACD,WAAW,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;CAC3C,GAAG;EAAC;EAAkB,UAAU,KAAK;EAAI;EAAc;CAAQ,CAAC;CAEhE,gBAAgB;EAEf,IADgB,YAAY,UAAU,GAAG,GAC5B;GACZ,SAAS,IAAI;GACb,YAAiB;EAClB;CACD,GAAG;EAAC;EAAU;EAAK;CAAW,CAAC;CAE/B,MAAM,gBAAgB,aACpB,iBAA0B;EAC1B,WAAW,GAAG;EACd,SAAS,IAAI;EACb,YAAiB;EACjB,YAAY,YAAY;CACzB,GACA;EAAC;EAAK;EAAa;CAAS,CAC7B;CAEA,IAAI,OACH,OAAO,UACN,oBAAC,aAAD;EAAsB;EAAS,GAAG,UAAU;EAAG,QAAQ,UAAU;CAAS,CAAA,IACvE;CAGL,OAAO,oBAAC,MAAD;EAAM,GAAI;EAAqB;EAAU,WAAW;CAAgB,CAAA;AAC5E"}
1
+ {"version":3,"file":"Poll.js","names":[],"sources":["../../src/react/Poll.tsx"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { isPollClosed } from '../form/pollState'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport { Form, type FormProps } from './Form'\nimport { FormResults } from './FormResults'\nimport { type FetchResultsResult, fetchFormResults } from './fetchResults'\n\nexport type PollProps = FormProps & {\n\t/** The choice field whose results are shown after voting (should match the form's public `poll.resultsField`). */\n\tresultsField: string\n\t/** localStorage key for the per-browser voted guard. Default `fb-poll-{form.id}`. */\n\tstorageKey?: string\n\t/**\n\t * Server-known voted state, ORed with the localStorage guard: `true` marks the visitor as voted;\n\t * `false`/omitted falls back to localStorage. SSR hosts using the plugin's `poll.votedCookie`\n\t * option pass `hasVotedCookie(cookieHeader, form.id)` here.\n\t */\n\thasVoted?: boolean\n\t/**\n\t * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module\n\t * scope, `useCallback`, or `useMemo`): an inline function re-runs the load effect and double-fetches.\n\t */\n\tfetchResultsImpl?: typeof fetchFormResults\n}\n\nconst readVoted = (key: string): boolean => {\n\ttry {\n\t\treturn window.localStorage.getItem(key) != null\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst writeVoted = (key: string): void => {\n\ttry {\n\t\twindow.localStorage.setItem(key, '1')\n\t} catch {\n\t\t// Private mode / storage disabled: the guard is best-effort UX, never integrity.\n\t}\n}\n\n/**\n * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows\n * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated\n * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open\n * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A\n * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final\n * notice plus results with every winning bucket highlighted (a tie highlights more than one). A\n * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`\n * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not\n * integrity (bypassable): server-enforced\n * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.\n */\nexport const Poll = ({\n\tresultsField,\n\tstorageKey,\n\thasVoted,\n\tfetchResultsImpl = fetchFormResults,\n\tapiRoute,\n\tonSuccess,\n\t...formProps\n}: PollProps) => {\n\tconst key = storageKey ?? `fb-poll-${formProps.form.id}`\n\tconst poll = formProps.form.poll\n\tconst closed = isPollClosed(poll)\n\tconst winningValues = poll?.outcome?.winningValues\n\tconst finalized = Array.isArray(winningValues) && winningValues.length > 0\n\tconst resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === 'afterClose'\n\tconst [voted, setVoted] = useState(false)\n\tconst [results, setResults] = useState<FieldAggregation[] | null>(null)\n\tconst [loadFailed, setLoadFailed] = useState(false)\n\tconst translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t])\n\n\tconst loadResults = useCallback(async () => {\n\t\tconst result: FetchResultsResult = await fetchResultsImpl({\n\t\t\tformId: formProps.form.id,\n\t\t\tfield: resultsField,\n\t\t\tapiRoute,\n\t\t})\n\t\t// A failed load surfaces as an error, not an empty result set: `[]` would read as \"no votes yet\".\n\t\tif (result.ok) {\n\t\t\tsetResults(result.results)\n\t\t\tsetLoadFailed(false)\n\t\t} else {\n\t\t\tsetLoadFailed(true)\n\t\t}\n\t}, [fetchResultsImpl, formProps.form.id, resultsField, apiRoute])\n\n\tconst resultsError = (\n\t\t<p className=\"fb-poll__error\" role=\"alert\">\n\t\t\t{translate(keys.pollResultsError)}\n\t\t</p>\n\t)\n\n\tuseEffect(() => {\n\t\tconst already = hasVoted === true || readVoted(key)\n\t\tif (already) {\n\t\t\tsetVoted(true)\n\t\t}\n\t\tif ((already && !resultsAwaitClose) || closed || finalized) {\n\t\t\tvoid loadResults()\n\t\t}\n\t}, [hasVoted, key, loadResults, closed, resultsAwaitClose, finalized])\n\n\tconst handleSuccess = useCallback<NonNullable<FormProps['onSuccess']>>(\n\t\t(submissionId, result) => {\n\t\t\twriteVoted(key)\n\t\t\tsetVoted(true)\n\t\t\tif (!resultsAwaitClose) {\n\t\t\t\tvoid loadResults()\n\t\t\t}\n\t\t\t// Forward the resolved success response so a Poll host gets the same onSuccess payload as a Form host.\n\t\t\tonSuccess?.(submissionId, result)\n\t\t},\n\t\t[key, loadResults, onSuccess, resultsAwaitClose]\n\t)\n\n\tif (finalized) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--final\">\n\t\t\t\t<p className=\"fb-poll__final\">{translate(keys.pollFinalResult)}</p>\n\t\t\t\t{loadFailed ? (\n\t\t\t\t\tresultsError\n\t\t\t\t) : results ? (\n\t\t\t\t\t<FormResults\n\t\t\t\t\t\tresults={results}\n\t\t\t\t\t\twinningValues={winningValues}\n\t\t\t\t\t\tt={formProps.t}\n\t\t\t\t\t\tlocale={formProps.locale}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (closed) {\n\t\treturn (\n\t\t\t<div className=\"fb-poll fb-poll--closed\">\n\t\t\t\t<p className=\"fb-poll__closed\">{translate(keys.pollClosed)}</p>\n\t\t\t\t{loadFailed ? (\n\t\t\t\t\tresultsError\n\t\t\t\t) : results ? (\n\t\t\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (voted) {\n\t\tif (resultsAwaitClose) {\n\t\t\treturn <p className=\"fb-poll__await-close\">{translate(keys.pollResultsAfterClose)}</p>\n\t\t}\n\t\tif (loadFailed) {\n\t\t\treturn resultsError\n\t\t}\n\t\treturn results ? (\n\t\t\t<FormResults results={results} t={formProps.t} locale={formProps.locale} />\n\t\t) : null\n\t}\n\n\treturn <Form {...formProps} apiRoute={apiRoute} onSuccess={handleSuccess} />\n}\n"],"mappings":";;;;;;;;;;;AA8BA,MAAM,aAAa,QAAyB;CAC3C,IAAI;EACH,OAAO,OAAO,aAAa,QAAQ,GAAG,KAAK;CAC5C,QAAQ;EACP,OAAO;CACR;AACD;AAEA,MAAM,cAAc,QAAsB;CACzC,IAAI;EACH,OAAO,aAAa,QAAQ,KAAK,GAAG;CACrC,QAAQ,CAER;AACD;;;;;;;;;;;;;AAcA,MAAa,QAAQ,EACpB,cACA,YACA,UACA,mBAAmB,kBACnB,UACA,WACA,GAAG,gBACa;CAChB,MAAM,MAAM,cAAc,WAAW,UAAU,KAAK;CACpD,MAAM,OAAO,UAAU,KAAK;CAC5B,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,gBAAgB,MAAM,SAAS;CACrC,MAAM,YAAY,MAAM,QAAQ,aAAa,KAAK,cAAc,SAAS;CACzE,MAAM,oBAAoB,CAAC,UAAU,CAAC,aAAa,MAAM,sBAAsB;CAC/E,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,MAAM,CAAC,SAAS,cAAc,SAAoC,IAAI;CACtE,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAClD,MAAM,YAAY,cAAc,UAAU,KAAK,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;CAE/E,MAAM,cAAc,YAAY,YAAY;EAC3C,MAAM,SAA6B,MAAM,iBAAiB;GACzD,QAAQ,UAAU,KAAK;GACvB,OAAO;GACP;EACD,CAAC;EAED,IAAI,OAAO,IAAI;GACd,WAAW,OAAO,OAAO;GACzB,cAAc,KAAK;EACpB,OACC,cAAc,IAAI;CAEpB,GAAG;EAAC;EAAkB,UAAU,KAAK;EAAI;EAAc;CAAQ,CAAC;CAEhE,MAAM,eACL,oBAAC,KAAD;EAAG,WAAU;EAAiB,MAAK;YACjC,UAAU,KAAK,gBAAgB;CAC9B,CAAA;CAGJ,gBAAgB;EACf,MAAM,UAAU,aAAa,QAAQ,UAAU,GAAG;EAClD,IAAI,SACH,SAAS,IAAI;EAEd,IAAK,WAAW,CAAC,qBAAsB,UAAU,WAChD,YAAiB;CAEnB,GAAG;EAAC;EAAU;EAAK;EAAa;EAAQ;EAAmB;CAAS,CAAC;CAErE,MAAM,gBAAgB,aACpB,cAAc,WAAW;EACzB,WAAW,GAAG;EACd,SAAS,IAAI;EACb,IAAI,CAAC,mBACJ,YAAiB;EAGlB,YAAY,cAAc,MAAM;CACjC,GACA;EAAC;EAAK;EAAa;EAAW;CAAiB,CAChD;CAEA,IAAI,WACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAkB,UAAU,KAAK,eAAe;EAAK,CAAA,GACjE,aACA,eACG,UACH,oBAAC,aAAD;GACU;GACM;GACf,GAAG,UAAU;GACb,QAAQ,UAAU;EAClB,CAAA,IACE,IACA;;CAIP,IAAI,QACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAmB,UAAU,KAAK,UAAU;EAAK,CAAA,GAC7D,aACA,eACG,UACH,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE,IACA;;CAIP,IAAI,OAAO;EACV,IAAI,mBACH,OAAO,oBAAC,KAAD;GAAG,WAAU;aAAwB,UAAU,KAAK,qBAAqB;EAAK,CAAA;EAEtF,IAAI,YACH,OAAO;EAER,OAAO,UACN,oBAAC,aAAD;GAAsB;GAAS,GAAG,UAAU;GAAG,QAAQ,UAAU;EAAS,CAAA,IACvE;CACL;CAEA,OAAO,oBAAC,MAAD;EAAM,GAAI;EAAqB;EAAU,WAAW;CAAgB,CAAA;AAC5E"}
@@ -0,0 +1,28 @@
1
+ import { CaptchaWidgetHandle } from "./types.js";
2
+ import { Ref } from "react";
3
+
4
+ //#region src/react/captcha/HcaptchaCaptcha.d.ts
5
+ declare const HCAPTCHA_SCRIPT_URL = "https://js.hcaptcha.com/1/api.js?render=explicit";
6
+ type HcaptchaCaptchaProps = {
7
+ /** hCaptcha site key (public). */siteKey: string; /** Receives each fresh token, and null when the token expires or errors. */
8
+ onToken: (token: string | null) => void; /** Extra vendor render params (theme, size, ...). */
9
+ options?: Record<string, unknown>;
10
+ className?: string; /** Override the vendor script URL. */
11
+ scriptSrc?: string;
12
+ ref?: Ref<CaptchaWidgetHandle>;
13
+ };
14
+ /**
15
+ * Headless hCaptcha widget: loads the vendor script once, renders the widget into a bare div,
16
+ * and reports tokens through `onToken`. Pass the latest token to `<Form captchaToken>`.
17
+ */
18
+ declare const HcaptchaCaptcha: ({
19
+ siteKey,
20
+ onToken,
21
+ options,
22
+ className,
23
+ scriptSrc,
24
+ ref
25
+ }: HcaptchaCaptchaProps) => import("react/jsx-runtime").JSX.Element;
26
+ //#endregion
27
+ export { HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, HcaptchaCaptchaProps };
28
+ //# sourceMappingURL=HcaptchaCaptcha.d.ts.map
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { useCaptchaScript } from "./useCaptchaScript.js";
3
+ import { getHcaptcha } from "./vendors.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useEffect, useImperativeHandle, useRef } from "react";
6
+ //#region src/react/captcha/HcaptchaCaptcha.tsx
7
+ const HCAPTCHA_SCRIPT_URL = "https://js.hcaptcha.com/1/api.js?render=explicit";
8
+ /**
9
+ * Headless hCaptcha widget: loads the vendor script once, renders the widget into a bare div,
10
+ * and reports tokens through `onToken`. Pass the latest token to `<Form captchaToken>`.
11
+ */
12
+ const HcaptchaCaptcha = ({ siteKey, onToken, options, className, scriptSrc = HCAPTCHA_SCRIPT_URL, ref }) => {
13
+ const containerRef = useRef(null);
14
+ const widgetIdRef = useRef(null);
15
+ const onTokenRef = useRef(onToken);
16
+ onTokenRef.current = onToken;
17
+ const optionsRef = useRef(options);
18
+ optionsRef.current = options;
19
+ const ready = useCaptchaScript(scriptSrc);
20
+ useImperativeHandle(ref, () => ({ reset: () => {
21
+ const api = getHcaptcha();
22
+ if (api && widgetIdRef.current !== null) api.reset(widgetIdRef.current);
23
+ onTokenRef.current(null);
24
+ } }), []);
25
+ useEffect(() => {
26
+ if (!ready) return;
27
+ const container = containerRef.current;
28
+ const api = getHcaptcha();
29
+ if (!container || !api) return;
30
+ const widgetId = api.render(container, {
31
+ ...optionsRef.current,
32
+ sitekey: siteKey,
33
+ callback: (token) => onTokenRef.current(token),
34
+ "expired-callback": () => onTokenRef.current(null),
35
+ "error-callback": () => onTokenRef.current(null)
36
+ });
37
+ widgetIdRef.current = widgetId;
38
+ return () => {
39
+ widgetIdRef.current = null;
40
+ try {
41
+ api.remove(widgetId);
42
+ } catch {}
43
+ };
44
+ }, [ready, siteKey]);
45
+ return /* @__PURE__ */ jsx("div", {
46
+ ref: containerRef,
47
+ className
48
+ });
49
+ };
50
+ //#endregion
51
+ export { HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha };
52
+
53
+ //# sourceMappingURL=HcaptchaCaptcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HcaptchaCaptcha.js","names":[],"sources":["../../../src/react/captcha/HcaptchaCaptcha.tsx"],"sourcesContent":["'use client'\n\nimport { type Ref, useEffect, useImperativeHandle, useRef } from 'react'\nimport type { CaptchaWidgetHandle } from './types'\nimport { useCaptchaScript } from './useCaptchaScript'\nimport { getHcaptcha } from './vendors'\n\nexport const HCAPTCHA_SCRIPT_URL = 'https://js.hcaptcha.com/1/api.js?render=explicit'\n\nexport type HcaptchaCaptchaProps = {\n\t/** hCaptcha site key (public). */\n\tsiteKey: string\n\t/** Receives each fresh token, and null when the token expires or errors. */\n\tonToken: (token: string | null) => void\n\t/** Extra vendor render params (theme, size, ...). */\n\toptions?: Record<string, unknown>\n\tclassName?: string\n\t/** Override the vendor script URL. */\n\tscriptSrc?: string\n\tref?: Ref<CaptchaWidgetHandle>\n}\n\n/**\n * Headless hCaptcha widget: loads the vendor script once, renders the widget into a bare div,\n * and reports tokens through `onToken`. Pass the latest token to `<Form captchaToken>`.\n */\nexport const HcaptchaCaptcha = ({\n\tsiteKey,\n\tonToken,\n\toptions,\n\tclassName,\n\tscriptSrc = HCAPTCHA_SCRIPT_URL,\n\tref,\n}: HcaptchaCaptchaProps) => {\n\tconst containerRef = useRef<HTMLDivElement>(null)\n\tconst widgetIdRef = useRef<string | null>(null)\n\tconst onTokenRef = useRef(onToken)\n\tonTokenRef.current = onToken\n\tconst optionsRef = useRef(options)\n\toptionsRef.current = options\n\tconst ready = useCaptchaScript(scriptSrc)\n\n\tuseImperativeHandle(\n\t\tref,\n\t\t() => ({\n\t\t\treset: () => {\n\t\t\t\tconst api = getHcaptcha()\n\t\t\t\tif (api && widgetIdRef.current !== null) {\n\t\t\t\t\tapi.reset(widgetIdRef.current)\n\t\t\t\t}\n\t\t\t\tonTokenRef.current(null)\n\t\t\t},\n\t\t}),\n\t\t[]\n\t)\n\n\tuseEffect(() => {\n\t\tif (!ready) {\n\t\t\treturn\n\t\t}\n\t\tconst container = containerRef.current\n\t\tconst api = getHcaptcha()\n\t\tif (!container || !api) {\n\t\t\treturn\n\t\t}\n\t\tconst widgetId = api.render(container, {\n\t\t\t...optionsRef.current,\n\t\t\tsitekey: siteKey,\n\t\t\tcallback: (token: string) => onTokenRef.current(token),\n\t\t\t'expired-callback': () => onTokenRef.current(null),\n\t\t\t'error-callback': () => onTokenRef.current(null),\n\t\t})\n\t\twidgetIdRef.current = widgetId\n\t\treturn () => {\n\t\t\twidgetIdRef.current = null\n\t\t\ttry {\n\t\t\t\tapi.remove(widgetId)\n\t\t\t} catch {}\n\t\t}\n\t}, [ready, siteKey])\n\n\treturn <div ref={containerRef} className={className} />\n}\n"],"mappings":";;;;;;AAOA,MAAa,sBAAsB;;;;;AAmBnC,MAAa,mBAAmB,EAC/B,SACA,SACA,SACA,WACA,YAAY,qBACZ,UAC2B;CAC3B,MAAM,eAAe,OAAuB,IAAI;CAChD,MAAM,cAAc,OAAsB,IAAI;CAC9C,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CACrB,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CACrB,MAAM,QAAQ,iBAAiB,SAAS;CAExC,oBACC,YACO,EACN,aAAa;EACZ,MAAM,MAAM,YAAY;EACxB,IAAI,OAAO,YAAY,YAAY,MAClC,IAAI,MAAM,YAAY,OAAO;EAE9B,WAAW,QAAQ,IAAI;CACxB,EACD,IACA,CAAC,CACF;CAEA,gBAAgB;EACf,IAAI,CAAC,OACJ;EAED,MAAM,YAAY,aAAa;EAC/B,MAAM,MAAM,YAAY;EACxB,IAAI,CAAC,aAAa,CAAC,KAClB;EAED,MAAM,WAAW,IAAI,OAAO,WAAW;GACtC,GAAG,WAAW;GACd,SAAS;GACT,WAAW,UAAkB,WAAW,QAAQ,KAAK;GACrD,0BAA0B,WAAW,QAAQ,IAAI;GACjD,wBAAwB,WAAW,QAAQ,IAAI;EAChD,CAAC;EACD,YAAY,UAAU;EACtB,aAAa;GACZ,YAAY,UAAU;GACtB,IAAI;IACH,IAAI,OAAO,QAAQ;GACpB,QAAQ,CAAC;EACV;CACD,GAAG,CAAC,OAAO,OAAO,CAAC;CAEnB,OAAO,oBAAC,OAAD;EAAK,KAAK;EAAyB;CAAY,CAAA;AACvD"}
@@ -0,0 +1,41 @@
1
+ import { CaptchaWidgetHandle } from "./types.js";
2
+ import { Ref } from "react";
3
+
4
+ //#region src/react/captcha/RecaptchaCaptcha.d.ts
5
+ declare const RECAPTCHA_SCRIPT_URL = "https://www.google.com/recaptcha/api.js";
6
+ type RecaptchaCaptchaHandle = CaptchaWidgetHandle & {
7
+ /**
8
+ * v3 only: fetch a fresh token on demand and report it through `onToken`. v3 tokens expire
9
+ * after about two minutes, so call this right before submit for a long-lived form. Resolves
10
+ * null for v2 or when execution fails.
11
+ */
12
+ execute: (action?: string) => Promise<string | null>;
13
+ };
14
+ type RecaptchaCaptchaProps = {
15
+ /** reCAPTCHA site key (public). */siteKey: string; /** 'v2' renders the checkbox widget; 'v3' runs invisibly and produces score-based tokens. */
16
+ version: 'v2' | 'v3'; /** v3 action name reported to reCAPTCHA. Default 'submit'. */
17
+ action?: string; /** Receives each fresh token, and null when the token expires, errors, or is reset. */
18
+ onToken: (token: string | null) => void; /** Extra vendor render params for v2 (theme, size, ...). */
19
+ options?: Record<string, unknown>;
20
+ className?: string; /** Override the vendor script URL (the version-specific `render` query is not appended). */
21
+ scriptSrc?: string;
22
+ ref?: Ref<RecaptchaCaptchaHandle>;
23
+ };
24
+ /**
25
+ * Headless Google reCAPTCHA widget for v2 (checkbox) and v3 (invisible, score-based). v2 reports
26
+ * tokens as the user solves the challenge; v3 fetches an initial token on mount and exposes
27
+ * `execute` on the ref for on-demand refresh. Pass the latest token to `<Form captchaToken>`.
28
+ */
29
+ declare const RecaptchaCaptcha: ({
30
+ siteKey,
31
+ version,
32
+ action,
33
+ onToken,
34
+ options,
35
+ className,
36
+ scriptSrc,
37
+ ref
38
+ }: RecaptchaCaptchaProps) => import("react/jsx-runtime").JSX.Element;
39
+ //#endregion
40
+ export { RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha, RecaptchaCaptchaHandle, RecaptchaCaptchaProps };
41
+ //# sourceMappingURL=RecaptchaCaptcha.d.ts.map
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { useCaptchaScript } from "./useCaptchaScript.js";
3
+ import { getGrecaptcha } from "./vendors.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useCallback, useEffect, useImperativeHandle, useRef } from "react";
6
+ //#region src/react/captcha/RecaptchaCaptcha.tsx
7
+ const RECAPTCHA_SCRIPT_URL = "https://www.google.com/recaptcha/api.js";
8
+ /**
9
+ * Headless Google reCAPTCHA widget for v2 (checkbox) and v3 (invisible, score-based). v2 reports
10
+ * tokens as the user solves the challenge; v3 fetches an initial token on mount and exposes
11
+ * `execute` on the ref for on-demand refresh. Pass the latest token to `<Form captchaToken>`.
12
+ */
13
+ const RecaptchaCaptcha = ({ siteKey, version, action, onToken, options, className, scriptSrc, ref }) => {
14
+ const src = scriptSrc ?? `https://www.google.com/recaptcha/api.js?render=${version === "v3" ? siteKey : "explicit"}`;
15
+ const containerRef = useRef(null);
16
+ const widgetIdRef = useRef(null);
17
+ const onTokenRef = useRef(onToken);
18
+ onTokenRef.current = onToken;
19
+ const optionsRef = useRef(options);
20
+ optionsRef.current = options;
21
+ const actionRef = useRef(action);
22
+ actionRef.current = action;
23
+ const ready = useCaptchaScript(src);
24
+ const execute = useCallback(async (overrideAction) => {
25
+ const api = getGrecaptcha();
26
+ if (!api || version !== "v3") return null;
27
+ try {
28
+ const token = await api.execute(siteKey, { action: overrideAction ?? actionRef.current ?? "submit" });
29
+ onTokenRef.current(token);
30
+ return token;
31
+ } catch {
32
+ onTokenRef.current(null);
33
+ return null;
34
+ }
35
+ }, [siteKey, version]);
36
+ useImperativeHandle(ref, () => ({
37
+ reset: () => {
38
+ const api = getGrecaptcha();
39
+ if (api && version === "v2" && widgetIdRef.current !== null) api.reset(widgetIdRef.current);
40
+ onTokenRef.current(null);
41
+ },
42
+ execute
43
+ }), [execute, version]);
44
+ useEffect(() => {
45
+ if (!ready) return;
46
+ const api = getGrecaptcha();
47
+ if (!api) return;
48
+ let active = true;
49
+ if (version === "v3") {
50
+ api.ready(() => {
51
+ if (active) execute();
52
+ });
53
+ return () => {
54
+ active = false;
55
+ };
56
+ }
57
+ const container = containerRef.current;
58
+ if (!container) return;
59
+ api.ready(() => {
60
+ if (!active || widgetIdRef.current !== null) return;
61
+ widgetIdRef.current = api.render(container, {
62
+ ...optionsRef.current,
63
+ sitekey: siteKey,
64
+ callback: (token) => onTokenRef.current(token),
65
+ "expired-callback": () => onTokenRef.current(null),
66
+ "error-callback": () => onTokenRef.current(null)
67
+ });
68
+ });
69
+ return () => {
70
+ active = false;
71
+ if (widgetIdRef.current !== null) {
72
+ try {
73
+ api.reset(widgetIdRef.current);
74
+ } catch {}
75
+ widgetIdRef.current = null;
76
+ }
77
+ container.replaceChildren();
78
+ };
79
+ }, [
80
+ ready,
81
+ siteKey,
82
+ version,
83
+ execute
84
+ ]);
85
+ return /* @__PURE__ */ jsx("div", {
86
+ ref: containerRef,
87
+ className
88
+ });
89
+ };
90
+ //#endregion
91
+ export { RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha };
92
+
93
+ //# sourceMappingURL=RecaptchaCaptcha.js.map