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

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 (619) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/README.md +7 -5
  3. package/dist/actions/assertNoBlockCollision.js +18 -0
  4. package/dist/actions/assertNoBlockCollision.js.map +1 -0
  5. package/dist/actions/body/converters.d.ts +20 -0
  6. package/dist/actions/body/converters.js +57 -0
  7. package/dist/actions/body/converters.js.map +1 -0
  8. package/dist/actions/body/escapeHtml.d.ts +6 -0
  9. package/dist/actions/body/escapeHtml.js +14 -0
  10. package/dist/actions/body/escapeHtml.js.map +1 -0
  11. package/dist/actions/body/serializeBody.d.ts +55 -0
  12. package/dist/actions/body/serializeBody.js +78 -0
  13. package/dist/actions/body/serializeBody.js.map +1 -0
  14. package/dist/actions/body/serializeSlate.js +31 -0
  15. package/dist/actions/body/serializeSlate.js.map +1 -0
  16. package/dist/actions/body/textOfBody.d.ts +13 -0
  17. package/dist/actions/body/textOfBody.js +35 -0
  18. package/dist/actions/body/textOfBody.js.map +1 -0
  19. package/dist/actions/body/wildcards.d.ts +10 -0
  20. package/dist/actions/body/wildcards.js +33 -0
  21. package/dist/actions/body/wildcards.js.map +1 -0
  22. package/dist/actions/buildActionBlocks.js +12 -9
  23. package/dist/actions/buildActionBlocks.js.map +1 -1
  24. package/dist/actions/builtin/confirmation.js +47 -43
  25. package/dist/actions/builtin/confirmation.js.map +1 -1
  26. package/dist/actions/builtin/emailAction.d.ts +20 -0
  27. package/dist/actions/builtin/emailAction.js +120 -0
  28. package/dist/actions/builtin/emailAction.js.map +1 -0
  29. package/dist/actions/builtin/emailTeam.js +20 -38
  30. package/dist/actions/builtin/emailTeam.js.map +1 -1
  31. package/dist/actions/builtin/index.d.ts +16 -1
  32. package/dist/actions/builtin/index.js +21 -7
  33. package/dist/actions/builtin/index.js.map +1 -1
  34. package/dist/actions/builtin/signedWebhook.js +6 -2
  35. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  36. package/dist/actions/defineAction.d.ts +17 -5
  37. package/dist/actions/defineAction.js.map +1 -1
  38. package/dist/actions/dispatch.js +51 -6
  39. package/dist/actions/dispatch.js.map +1 -1
  40. package/dist/actions/dispatchContext.js +13 -0
  41. package/dist/actions/dispatchContext.js.map +1 -0
  42. package/dist/actions/emailRecipients.d.ts +12 -0
  43. package/dist/actions/emailRecipients.js +146 -0
  44. package/dist/actions/emailRecipients.js.map +1 -0
  45. package/dist/actions/fromAddresses.d.ts +37 -0
  46. package/dist/actions/fromAddresses.js +131 -0
  47. package/dist/actions/fromAddresses.js.map +1 -0
  48. package/dist/actions/recipientSources.d.ts +34 -0
  49. package/dist/actions/recipientSources.js +11 -0
  50. package/dist/actions/recipientSources.js.map +1 -0
  51. package/dist/actions/registry.d.ts +2 -1
  52. package/dist/actions/registry.js +5 -7
  53. package/dist/actions/registry.js.map +1 -1
  54. package/dist/actions/runActions.js +10 -1
  55. package/dist/actions/runActions.js.map +1 -1
  56. package/dist/actions/task.js +47 -16
  57. package/dist/actions/task.js.map +1 -1
  58. package/dist/aggregation/aggregateResponses.d.ts +9 -0
  59. package/dist/aggregation/aggregateResponses.js +8 -14
  60. package/dist/aggregation/aggregateResponses.js.map +1 -1
  61. package/dist/aggregation/resolveResultsRequest.d.ts +41 -6
  62. package/dist/aggregation/resolveResultsRequest.js +97 -11
  63. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  64. package/dist/calc/computeCalcFields.d.ts +5 -3
  65. package/dist/calc/computeCalcFields.js +6 -4
  66. package/dist/calc/computeCalcFields.js.map +1 -1
  67. package/dist/calc/evaluate.d.ts +26 -3
  68. package/dist/calc/evaluate.js +34 -13
  69. package/dist/calc/evaluate.js.map +1 -1
  70. package/dist/calc/formatCalc.d.ts +11 -0
  71. package/dist/calc/formatCalc.js +35 -0
  72. package/dist/calc/formatCalc.js.map +1 -0
  73. package/dist/calc/formatCalcValue.d.ts +17 -0
  74. package/dist/calc/formatCalcValue.js +17 -0
  75. package/dist/calc/formatCalcValue.js.map +1 -0
  76. package/dist/calc/normalizeCalc.d.ts +17 -3
  77. package/dist/calc/normalizeCalc.js +42 -33
  78. package/dist/calc/normalizeCalc.js.map +1 -1
  79. package/dist/calc/registry.d.ts +43 -0
  80. package/dist/calc/registry.js +27 -0
  81. package/dist/calc/registry.js.map +1 -0
  82. package/dist/calc/resolveCalcContext.d.ts +36 -0
  83. package/dist/calc/resolveCalcContext.js +87 -0
  84. package/dist/calc/resolveCalcContext.js.map +1 -0
  85. package/dist/calc/types.d.ts +25 -4
  86. package/dist/calc/types.js +23 -0
  87. package/dist/calc/types.js.map +1 -0
  88. package/dist/client/ByteSizeField.d.ts +29 -0
  89. package/dist/client/ByteSizeField.js +58 -0
  90. package/dist/client/ByteSizeField.js.map +1 -0
  91. package/dist/client/CalcExpressionBuilder.d.ts +20 -0
  92. package/dist/client/CalcExpressionBuilder.js +890 -0
  93. package/dist/client/CalcExpressionBuilder.js.map +1 -0
  94. package/dist/client/ClosePollButton.d.ts +15 -0
  95. package/dist/client/ClosePollButton.js +80 -0
  96. package/dist/client/ClosePollButton.js.map +1 -0
  97. package/dist/client/CondensedArray.d.ts +15 -0
  98. package/dist/client/CondensedArray.js +97 -0
  99. package/dist/client/CondensedArray.js.map +1 -0
  100. package/dist/client/ConditionBuilder.js +2 -1
  101. package/dist/client/ConditionBuilder.js.map +1 -1
  102. package/dist/client/ConsentBlockLabel.d.ts +15 -0
  103. package/dist/client/ConsentBlockLabel.js +71 -0
  104. package/dist/client/ConsentBlockLabel.js.map +1 -0
  105. package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
  106. package/dist/client/ConsentSourceRowLabel.js +21 -0
  107. package/dist/client/ConsentSourceRowLabel.js.map +1 -0
  108. package/dist/client/EndpointOptionsSelect.d.ts +55 -0
  109. package/dist/client/EndpointOptionsSelect.js +112 -0
  110. package/dist/client/EndpointOptionsSelect.js.map +1 -0
  111. package/dist/client/FieldBlockLabel.d.ts +18 -0
  112. package/dist/client/FieldBlockLabel.js +34 -0
  113. package/dist/client/FieldBlockLabel.js.map +1 -0
  114. package/dist/client/FieldNameSelect.d.ts +37 -0
  115. package/dist/client/FieldNameSelect.js +72 -0
  116. package/dist/client/FieldNameSelect.js.map +1 -0
  117. package/dist/client/FlowBuilder.d.ts +2 -2
  118. package/dist/client/FlowBuilder.js +137 -88
  119. package/dist/client/FlowBuilder.js.map +1 -1
  120. package/dist/client/FormConditionField.js +1 -4
  121. package/dist/client/FormConditionField.js.map +1 -1
  122. package/dist/client/RecipientsSelect.d.ts +37 -0
  123. package/dist/client/RecipientsSelect.js +197 -0
  124. package/dist/client/RecipientsSelect.js.map +1 -0
  125. package/dist/client/RuleDescription.d.ts +21 -0
  126. package/dist/client/RuleDescription.js +19 -0
  127. package/dist/client/RuleDescription.js.map +1 -0
  128. package/dist/client/admin.css +557 -0
  129. package/dist/client/endpointOptions.d.ts +11 -0
  130. package/dist/client/endpointOptions.js +38 -0
  131. package/dist/client/endpointOptions.js.map +1 -0
  132. package/dist/client/flowAuthoring.js +142 -0
  133. package/dist/client/flowAuthoring.js.map +1 -0
  134. package/dist/client/synthesizeClientField.js +21 -10
  135. package/dist/client/synthesizeClientField.js.map +1 -1
  136. package/dist/client/toStaticLabel.js +13 -0
  137. package/dist/client/toStaticLabel.js.map +1 -0
  138. package/dist/collections/buttonFields.d.ts +34 -0
  139. package/dist/collections/buttonFields.js +35 -0
  140. package/dist/collections/buttonFields.js.map +1 -0
  141. package/dist/collections/cells/FieldCountCell.d.ts +15 -0
  142. package/dist/collections/cells/FieldCountCell.js +18 -0
  143. package/dist/collections/cells/FieldCountCell.js.map +1 -0
  144. package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
  145. package/dist/collections/cells/FlowStepsCell.js +17 -0
  146. package/dist/collections/cells/FlowStepsCell.js.map +1 -0
  147. package/dist/collections/formSubmissions.js +114 -28
  148. package/dist/collections/formSubmissions.js.map +1 -1
  149. package/dist/collections/forms.js +449 -112
  150. package/dist/collections/forms.js.map +1 -1
  151. package/dist/collections/formsEndpoints.js +106 -0
  152. package/dist/collections/formsEndpoints.js.map +1 -0
  153. package/dist/collections/redirectFields.d.ts +29 -0
  154. package/dist/collections/settingsFields.d.ts +31 -0
  155. package/dist/collections/settingsFields.js +45 -0
  156. package/dist/collections/settingsFields.js.map +1 -0
  157. package/dist/conditions/conditionType.js +9 -2
  158. package/dist/conditions/conditionType.js.map +1 -1
  159. package/dist/conditions/evaluate.js +1 -1
  160. package/dist/conditions/evaluate.js.map +1 -1
  161. package/dist/conditions/fieldTypes.d.ts +3 -1
  162. package/dist/conditions/fieldTypes.js +4 -1
  163. package/dist/conditions/fieldTypes.js.map +1 -1
  164. package/dist/conditions/normalizeConditions.js +20 -6
  165. package/dist/conditions/normalizeConditions.js.map +1 -1
  166. package/dist/consent/applyConsentStatements.d.ts +14 -0
  167. package/dist/consent/applyConsentStatements.js +17 -0
  168. package/dist/consent/applyConsentStatements.js.map +1 -0
  169. package/dist/consent/captureConsent.d.ts +39 -11
  170. package/dist/consent/captureConsent.js +63 -13
  171. package/dist/consent/captureConsent.js.map +1 -1
  172. package/dist/consent/consentSourcesField.d.ts +66 -0
  173. package/dist/consent/consentSourcesField.js +98 -0
  174. package/dist/consent/consentSourcesField.js.map +1 -0
  175. package/dist/consent/effectiveStatement.js +16 -0
  176. package/dist/consent/effectiveStatement.js.map +1 -0
  177. package/dist/consent/resolveConsentEntries.d.ts +24 -0
  178. package/dist/consent/resolveConsentEntries.js +40 -0
  179. package/dist/consent/resolveConsentEntries.js.map +1 -0
  180. package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
  181. package/dist/consent/resolveConsentSourcesRequest.js +54 -0
  182. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
  183. package/dist/consent/resolveConsentStatements.d.ts +45 -0
  184. package/dist/consent/resolveConsentStatements.js +46 -0
  185. package/dist/consent/resolveConsentStatements.js.map +1 -0
  186. package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
  187. package/dist/consent/resolvePublishedVersionRef.js +13 -10
  188. package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
  189. package/dist/consent/types.d.ts +69 -0
  190. package/dist/context/formContext.d.ts +30 -0
  191. package/dist/context/formContext.js +54 -0
  192. package/dist/context/formContext.js.map +1 -0
  193. package/dist/email/departments.d.ts +59 -0
  194. package/dist/email/departments.js +87 -0
  195. package/dist/email/departments.js.map +1 -0
  196. package/dist/email/departmentsField.d.ts +53 -0
  197. package/dist/email/departmentsField.js +73 -0
  198. package/dist/email/departmentsField.js.map +1 -0
  199. package/dist/exports/client.d.ts +14 -1
  200. package/dist/exports/client.js +14 -1
  201. package/dist/exports/i18n.d.ts +5 -2
  202. package/dist/exports/i18n.js +5 -2
  203. package/dist/exports/react.d.ts +34 -10
  204. package/dist/exports/react.js +24 -4
  205. package/dist/exports/rsc.d.ts +10 -1
  206. package/dist/exports/rsc.js +10 -1
  207. package/dist/exports/types.d.ts +20 -12
  208. package/dist/fields/buildFieldBlocks.js +46 -25
  209. package/dist/fields/buildFieldBlocks.js.map +1 -1
  210. package/dist/fields/builtin/calculation.d.ts +17 -0
  211. package/dist/fields/builtin/calculation.js +193 -18
  212. package/dist/fields/builtin/calculation.js.map +1 -1
  213. package/dist/fields/builtin/checkbox.js +23 -0
  214. package/dist/fields/builtin/checkbox.js.map +1 -1
  215. package/dist/fields/builtin/consent.js +49 -14
  216. package/dist/fields/builtin/consent.js.map +1 -1
  217. package/dist/fields/builtin/country.d.ts +13 -0
  218. package/dist/fields/builtin/country.js +30 -0
  219. package/dist/fields/builtin/country.js.map +1 -0
  220. package/dist/fields/builtin/date.js +31 -0
  221. package/dist/fields/builtin/date.js.map +1 -0
  222. package/dist/fields/builtin/email.js +8 -1
  223. package/dist/fields/builtin/email.js.map +1 -1
  224. package/dist/fields/builtin/file.d.ts +15 -0
  225. package/dist/fields/builtin/file.js +95 -8
  226. package/dist/fields/builtin/file.js.map +1 -1
  227. package/dist/fields/builtin/index.d.ts +30 -0
  228. package/dist/fields/builtin/index.js +37 -10
  229. package/dist/fields/builtin/index.js.map +1 -1
  230. package/dist/fields/builtin/message.js +30 -0
  231. package/dist/fields/builtin/message.js.map +1 -0
  232. package/dist/fields/builtin/repeater.js +8 -3
  233. package/dist/fields/builtin/repeater.js.map +1 -1
  234. package/dist/fields/builtin/select.js +28 -4
  235. package/dist/fields/builtin/select.js.map +1 -1
  236. package/dist/fields/builtin/state.d.ts +13 -0
  237. package/dist/fields/builtin/state.js +30 -0
  238. package/dist/fields/builtin/state.js.map +1 -0
  239. package/dist/fields/builtin/text.js +7 -0
  240. package/dist/fields/builtin/text.js.map +1 -1
  241. package/dist/fields/data/regions.d.ts +21 -0
  242. package/dist/fields/data/regions.js +1238 -0
  243. package/dist/fields/data/regions.js.map +1 -0
  244. package/dist/fields/fieldKey.d.ts +15 -0
  245. package/dist/fields/fieldKey.js +16 -0
  246. package/dist/fields/fieldKey.js.map +1 -0
  247. package/dist/fields/fieldNamesOfType.js +30 -0
  248. package/dist/fields/fieldNamesOfType.js.map +1 -0
  249. package/dist/fields/instanceOptions.js +20 -0
  250. package/dist/fields/instanceOptions.js.map +1 -0
  251. package/dist/fields/localizedIf.d.ts +13 -0
  252. package/dist/fields/localizedIf.js +12 -0
  253. package/dist/fields/localizedIf.js.map +1 -0
  254. package/dist/fields/registry.d.ts +3 -1
  255. package/dist/fields/registry.js +23 -8
  256. package/dist/fields/registry.js.map +1 -1
  257. package/dist/fields/sharedConfig.js +125 -59
  258. package/dist/fields/sharedConfig.js.map +1 -1
  259. package/dist/fields/types.d.ts +78 -9
  260. package/dist/flow/engine.d.ts +6 -4
  261. package/dist/flow/engine.js +11 -6
  262. package/dist/flow/engine.js.map +1 -1
  263. package/dist/flow/normalizeFlow.js +28 -9
  264. package/dist/flow/normalizeFlow.js.map +1 -1
  265. package/dist/flow/stepLabel.js +15 -0
  266. package/dist/flow/stepLabel.js.map +1 -0
  267. package/dist/flow/types.d.ts +12 -3
  268. package/dist/flow/types.js +11 -0
  269. package/dist/flow/types.js.map +1 -0
  270. package/dist/form/pollState.d.ts +23 -0
  271. package/dist/form/pollState.js +17 -0
  272. package/dist/form/pollState.js.map +1 -0
  273. package/dist/form/toFormDocument.d.ts +110 -0
  274. package/dist/form/toFormDocument.js +89 -0
  275. package/dist/form/toFormDocument.js.map +1 -0
  276. package/dist/form/types.d.ts +73 -0
  277. package/dist/index.d.ts +67 -61
  278. package/dist/index.js +120 -25
  279. package/dist/index.js.map +1 -1
  280. package/dist/options.d.ts +281 -0
  281. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  282. package/dist/plugin/applyRegistryConfig.js +20 -0
  283. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  284. package/dist/plugin/customState.js +26 -0
  285. package/dist/plugin/customState.js.map +1 -0
  286. package/dist/plugin/registerCollections.js +43 -40
  287. package/dist/plugin/registerCollections.js.map +1 -1
  288. package/dist/plugin/uploadsCollection.d.ts +16 -0
  289. package/dist/plugin/uploadsCollection.js +53 -0
  290. package/dist/plugin/uploadsCollection.js.map +1 -0
  291. package/dist/poll/applyPollOptions.js +18 -0
  292. package/dist/poll/applyPollOptions.js.map +1 -0
  293. package/dist/poll/buildPollOptionSourceFields.js +50 -0
  294. package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
  295. package/dist/poll/closeJob.d.ts +53 -0
  296. package/dist/poll/closeJob.js +121 -0
  297. package/dist/poll/closeJob.js.map +1 -0
  298. package/dist/poll/definePollOptionSource.d.ts +44 -0
  299. package/dist/poll/definePollOptionSource.js +6 -0
  300. package/dist/poll/definePollOptionSource.js.map +1 -0
  301. package/dist/poll/definePollType.d.ts +34 -0
  302. package/dist/poll/definePollType.js +6 -0
  303. package/dist/poll/definePollType.js.map +1 -0
  304. package/dist/poll/effectivePollOptions.d.ts +38 -0
  305. package/dist/poll/effectivePollOptions.js +64 -0
  306. package/dist/poll/effectivePollOptions.js.map +1 -0
  307. package/dist/poll/mostVoted.d.ts +21 -0
  308. package/dist/poll/mostVoted.js +32 -0
  309. package/dist/poll/mostVoted.js.map +1 -0
  310. package/dist/poll/outcomeBeforeChange.js +91 -0
  311. package/dist/poll/outcomeBeforeChange.js.map +1 -0
  312. package/dist/poll/outcomeFields.d.ts +38 -0
  313. package/dist/poll/outcomeFields.js +52 -0
  314. package/dist/poll/outcomeFields.js.map +1 -0
  315. package/dist/poll/pollTypeRegistry.d.ts +34 -0
  316. package/dist/poll/pollTypeRegistry.js +69 -0
  317. package/dist/poll/pollTypeRegistry.js.map +1 -0
  318. package/dist/poll/registry.d.ts +17 -0
  319. package/dist/poll/registry.js +12 -0
  320. package/dist/poll/registry.js.map +1 -0
  321. package/dist/poll/resolvePollCloseRequest.js +106 -0
  322. package/dist/poll/resolvePollCloseRequest.js.map +1 -0
  323. package/dist/poll/resolvePollOptions.d.ts +29 -0
  324. package/dist/poll/resolvePollOptions.js +52 -0
  325. package/dist/poll/resolvePollOptions.js.map +1 -0
  326. package/dist/poll/resolvePollOptionsRequest.js +50 -0
  327. package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
  328. package/dist/poll/resolvePollOutcome.d.ts +40 -0
  329. package/dist/poll/resolvePollOutcome.js +90 -0
  330. package/dist/poll/resolvePollOutcome.js.map +1 -0
  331. package/dist/poll/resultsField.js +21 -0
  332. package/dist/poll/resultsField.js.map +1 -0
  333. package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
  334. package/dist/poll/votes/aggregateFromVotes.js +54 -0
  335. package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
  336. package/dist/poll/votes/answerValues.js +16 -0
  337. package/dist/poll/votes/answerValues.js.map +1 -0
  338. package/dist/poll/votes/bumpPollVote.js +88 -0
  339. package/dist/poll/votes/bumpPollVote.js.map +1 -0
  340. package/dist/poll/votes/recountPollVotes.d.ts +20 -0
  341. package/dist/poll/votes/recountPollVotes.js +63 -0
  342. package/dist/poll/votes/recountPollVotes.js.map +1 -0
  343. package/dist/poll/votes/voteTallyHook.js +76 -0
  344. package/dist/poll/votes/voteTallyHook.js.map +1 -0
  345. package/dist/poll/votes/votesCollection.d.ts +19 -0
  346. package/dist/poll/votes/votesCollection.js +89 -0
  347. package/dist/poll/votes/votesCollection.js.map +1 -0
  348. package/dist/prefill/valuesFromSearchParams.js +5 -9
  349. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  350. package/dist/presentations/types.d.ts +3 -2
  351. package/dist/react/Form.d.ts +77 -35
  352. package/dist/react/Form.js +368 -222
  353. package/dist/react/Form.js.map +1 -1
  354. package/dist/react/FormContext.d.ts +51 -1
  355. package/dist/react/FormContext.js.map +1 -1
  356. package/dist/react/FormControls.d.ts +55 -0
  357. package/dist/react/FormControls.js +56 -0
  358. package/dist/react/FormControls.js.map +1 -0
  359. package/dist/react/FormFields.d.ts +18 -0
  360. package/dist/react/FormFields.js +114 -0
  361. package/dist/react/FormFields.js.map +1 -0
  362. package/dist/react/FormLayout.d.ts +4 -2
  363. package/dist/react/FormLayout.js +3 -2
  364. package/dist/react/FormLayout.js.map +1 -1
  365. package/dist/react/FormResults.d.ts +8 -3
  366. package/dist/react/FormResults.js +50 -29
  367. package/dist/react/FormResults.js.map +1 -1
  368. package/dist/react/FormSteps.d.ts +17 -0
  369. package/dist/react/FormSteps.js +35 -0
  370. package/dist/react/FormSteps.js.map +1 -0
  371. package/dist/react/Poll.d.ts +40 -6
  372. package/dist/react/Poll.js +137 -33
  373. package/dist/react/Poll.js.map +1 -1
  374. package/dist/react/adapters.d.ts +35 -0
  375. package/dist/react/adapters.js +11 -0
  376. package/dist/react/adapters.js.map +1 -0
  377. package/dist/react/captcha/HcaptchaCaptcha.d.ts +31 -0
  378. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  379. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  380. package/dist/react/captcha/RecaptchaCaptcha.d.ts +44 -0
  381. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  382. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  383. package/dist/react/captcha/TurnstileCaptcha.d.ts +31 -0
  384. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  385. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  386. package/dist/react/captcha/types.d.ts +8 -0
  387. package/dist/react/captcha/useCaptchaScript.js +63 -0
  388. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  389. package/dist/react/captcha/vendors.js +8 -0
  390. package/dist/react/captcha/vendors.js.map +1 -0
  391. package/dist/react/contract.d.ts +2 -3
  392. package/dist/react/contract.js.map +1 -1
  393. package/dist/react/fetchResults.d.ts +1 -1
  394. package/dist/react/fetchResults.js +1 -1
  395. package/dist/react/fetchResults.js.map +1 -1
  396. package/dist/react/presentation/DialogSurface.js +1 -4
  397. package/dist/react/presentation/DialogSurface.js.map +1 -1
  398. package/dist/react/primitives/Checkbox.d.ts +6 -2
  399. package/dist/react/primitives/Checkbox.js +4 -2
  400. package/dist/react/primitives/Checkbox.js.map +1 -1
  401. package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
  402. package/dist/react/primitives/ChoiceGroup.js +33 -0
  403. package/dist/react/primitives/ChoiceGroup.js.map +1 -0
  404. package/dist/react/primitives/FieldShell.d.ts +13 -5
  405. package/dist/react/primitives/FieldShell.js +35 -32
  406. package/dist/react/primitives/FieldShell.js.map +1 -1
  407. package/dist/react/primitives/Input.d.ts +4 -2
  408. package/dist/react/primitives/Input.js +2 -1
  409. package/dist/react/primitives/Input.js.map +1 -1
  410. package/dist/react/renderers/calculation.js +7 -5
  411. package/dist/react/renderers/calculation.js.map +1 -1
  412. package/dist/react/renderers/checkbox.js +3 -5
  413. package/dist/react/renderers/checkbox.js.map +1 -1
  414. package/dist/react/renderers/consent.js +84 -36
  415. package/dist/react/renderers/consent.js.map +1 -1
  416. package/dist/react/renderers/date.js +33 -0
  417. package/dist/react/renderers/date.js.map +1 -0
  418. package/dist/react/renderers/email.js +4 -5
  419. package/dist/react/renderers/email.js.map +1 -1
  420. package/dist/react/renderers/file.js +29 -14
  421. package/dist/react/renderers/file.js.map +1 -1
  422. package/dist/react/renderers/index.js +7 -0
  423. package/dist/react/renderers/index.js.map +1 -1
  424. package/dist/react/renderers/message.js +41 -0
  425. package/dist/react/renderers/message.js.map +1 -0
  426. package/dist/react/renderers/number.js +1 -4
  427. package/dist/react/renderers/number.js.map +1 -1
  428. package/dist/react/renderers/region.js +43 -0
  429. package/dist/react/renderers/region.js.map +1 -0
  430. package/dist/react/renderers/repeater.js +32 -14
  431. package/dist/react/renderers/repeater.js.map +1 -1
  432. package/dist/react/renderers/select.js +21 -6
  433. package/dist/react/renderers/select.js.map +1 -1
  434. package/dist/react/renderers/text.js +4 -5
  435. package/dist/react/renderers/text.js.map +1 -1
  436. package/dist/react/renderers/textarea.js +1 -4
  437. package/dist/react/renderers/textarea.js.map +1 -1
  438. package/dist/react/state.d.ts +46 -3
  439. package/dist/react/state.js +73 -10
  440. package/dist/react/state.js.map +1 -1
  441. package/dist/react/submitForm.js +1 -1
  442. package/dist/react/submitForm.js.map +1 -1
  443. package/dist/react/uploadFile.d.ts +2 -2
  444. package/dist/react/uploadFile.js +1 -1
  445. package/dist/react/uploadFile.js.map +1 -1
  446. package/dist/react/useCalcValues.d.ts +10 -0
  447. package/dist/react/useCalcValues.js +13 -0
  448. package/dist/react/useCalcValues.js.map +1 -0
  449. package/dist/react/useField.d.ts +0 -1
  450. package/dist/react/useField.js +7 -6
  451. package/dist/react/useField.js.map +1 -1
  452. package/dist/react/useFormState.d.ts +1 -1
  453. package/dist/react/useFormState.js +1 -1
  454. package/dist/react/useFormState.js.map +1 -1
  455. package/dist/react/validateField.js +3 -6
  456. package/dist/react/validateField.js.map +1 -1
  457. package/dist/recall/interpolate.d.ts +4 -3
  458. package/dist/recall/interpolate.js +5 -4
  459. package/dist/recall/interpolate.js.map +1 -1
  460. package/dist/recall/resolver.d.ts +1 -1
  461. package/dist/recall/resolver.js +15 -3
  462. package/dist/recall/resolver.js.map +1 -1
  463. package/dist/spam/captcha.d.ts +4 -3
  464. package/dist/spam/captcha.js +4 -3
  465. package/dist/spam/captcha.js.map +1 -1
  466. package/dist/spam/clientIp.js +9 -0
  467. package/dist/spam/clientIp.js.map +1 -0
  468. package/dist/spam/constants.js +13 -1
  469. package/dist/spam/constants.js.map +1 -1
  470. package/dist/spam/identify.js +3 -6
  471. package/dist/spam/identify.js.map +1 -1
  472. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  473. package/dist/spam/providers/hcaptcha.js +18 -0
  474. package/dist/spam/providers/hcaptcha.js.map +1 -0
  475. package/dist/spam/providers/recaptcha.d.ts +23 -0
  476. package/dist/spam/providers/recaptcha.js +23 -0
  477. package/dist/spam/providers/recaptcha.js.map +1 -0
  478. package/dist/spam/providers/siteverify.js +34 -0
  479. package/dist/spam/providers/siteverify.js.map +1 -0
  480. package/dist/spam/providers/turnstile.d.ts +17 -0
  481. package/dist/spam/providers/turnstile.js +18 -0
  482. package/dist/spam/providers/turnstile.js.map +1 -0
  483. package/dist/spam/resolveSpam.js +2 -1
  484. package/dist/spam/resolveSpam.js.map +1 -1
  485. package/dist/spam/spamGuard.js +25 -11
  486. package/dist/spam/spamGuard.js.map +1 -1
  487. package/dist/spam/types.d.ts +19 -4
  488. package/dist/submissions/SubmissionAnswers.js +6 -2
  489. package/dist/submissions/SubmissionAnswers.js.map +1 -1
  490. package/dist/submissions/SubmissionAnswersClient.js +100 -64
  491. package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
  492. package/dist/submissions/coerceBoolean.js +14 -0
  493. package/dist/submissions/coerceBoolean.js.map +1 -0
  494. package/dist/submissions/createSubmission.d.ts +36 -0
  495. package/dist/submissions/createSubmission.js +26 -0
  496. package/dist/submissions/createSubmission.js.map +1 -0
  497. package/dist/submissions/formIdOf.js +13 -0
  498. package/dist/submissions/formIdOf.js.map +1 -0
  499. package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
  500. package/dist/submissions/resolveVotedSubmission.js +51 -0
  501. package/dist/submissions/resolveVotedSubmission.js.map +1 -0
  502. package/dist/submissions/runSubmission.d.ts +10 -0
  503. package/dist/submissions/runSubmission.js +104 -54
  504. package/dist/submissions/runSubmission.js.map +1 -1
  505. package/dist/submissions/types.d.ts +11 -4
  506. package/dist/submissions/validateSubmission.js +99 -13
  507. package/dist/submissions/validateSubmission.js.map +1 -1
  508. package/dist/submissions/verifyContext.js +35 -0
  509. package/dist/submissions/verifyContext.js.map +1 -0
  510. package/dist/submissions/voteChangeEndpoint.js +105 -0
  511. package/dist/submissions/voteChangeEndpoint.js.map +1 -0
  512. package/dist/submissions/votedCookie.d.ts +15 -0
  513. package/dist/submissions/votedCookie.js +74 -0
  514. package/dist/submissions/votedCookie.js.map +1 -0
  515. package/dist/translations/clientRuntimeKeys.d.ts +17 -0
  516. package/dist/translations/clientRuntimeKeys.js +48 -0
  517. package/dist/translations/clientRuntimeKeys.js.map +1 -0
  518. package/dist/translations/de.d.ts +12 -0
  519. package/dist/translations/de.js +380 -0
  520. package/dist/translations/de.js.map +1 -0
  521. package/dist/translations/en.js +250 -28
  522. package/dist/translations/en.js.map +1 -1
  523. package/dist/translations/index.d.ts +14 -5
  524. package/dist/translations/index.js +14 -3
  525. package/dist/translations/index.js.map +1 -1
  526. package/dist/translations/keys.d.ts +245 -23
  527. package/dist/translations/keys.js +246 -24
  528. package/dist/translations/keys.js.map +1 -1
  529. package/dist/translations/makeTranslate.d.ts +7 -3
  530. package/dist/translations/makeTranslate.js +12 -3
  531. package/dist/translations/makeTranslate.js.map +1 -1
  532. package/dist/translations/server.js +8 -1
  533. package/dist/translations/server.js.map +1 -1
  534. package/dist/uploads/captureFileRef.d.ts +10 -3
  535. package/dist/uploads/captureFileRef.js +18 -9
  536. package/dist/uploads/captureFileRef.js.map +1 -1
  537. package/dist/uploads/formatBytes.d.ts +9 -0
  538. package/dist/uploads/formatBytes.js +25 -0
  539. package/dist/uploads/formatBytes.js.map +1 -0
  540. package/dist/uploads/resolveFileRef.js +3 -2
  541. package/dist/uploads/resolveFileRef.js.map +1 -1
  542. package/dist/uploads/types.d.ts +1 -2
  543. package/dist/validation/buildRuleBlocks.js +18 -17
  544. package/dist/validation/buildRuleBlocks.js.map +1 -1
  545. package/dist/validation/builtin/email.js +2 -1
  546. package/dist/validation/builtin/email.js.map +1 -1
  547. package/dist/validation/builtin/index.d.ts +14 -0
  548. package/dist/validation/builtin/index.js +14 -1
  549. package/dist/validation/builtin/index.js.map +1 -1
  550. package/dist/validation/builtin/integer.js +15 -0
  551. package/dist/validation/builtin/integer.js.map +1 -0
  552. package/dist/validation/builtin/matchesField.js +3 -7
  553. package/dist/validation/builtin/matchesField.js.map +1 -1
  554. package/dist/validation/builtin/max.js +1 -0
  555. package/dist/validation/builtin/max.js.map +1 -1
  556. package/dist/validation/builtin/maxDate.js +24 -0
  557. package/dist/validation/builtin/maxDate.js.map +1 -0
  558. package/dist/validation/builtin/maxLength.js +1 -0
  559. package/dist/validation/builtin/maxLength.js.map +1 -1
  560. package/dist/validation/builtin/min.js +1 -0
  561. package/dist/validation/builtin/min.js.map +1 -1
  562. package/dist/validation/builtin/minDate.js +24 -0
  563. package/dist/validation/builtin/minDate.js.map +1 -0
  564. package/dist/validation/builtin/minLength.js +1 -0
  565. package/dist/validation/builtin/minLength.js.map +1 -1
  566. package/dist/validation/builtin/notAlreadySubmitted.js +5 -1
  567. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  568. package/dist/validation/builtin/oneOf.js +1 -0
  569. package/dist/validation/builtin/oneOf.js.map +1 -1
  570. package/dist/validation/builtin/pattern.js +11 -2
  571. package/dist/validation/builtin/pattern.js.map +1 -1
  572. package/dist/validation/builtin/url.js +1 -0
  573. package/dist/validation/builtin/url.js.map +1 -1
  574. package/dist/validation/builtin/validateDateBound.js +18 -0
  575. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  576. package/dist/validation/builtin/validateRegexParams.js +44 -0
  577. package/dist/validation/builtin/validateRegexParams.js.map +1 -0
  578. package/dist/validation/emailPattern.js +10 -0
  579. package/dist/validation/emailPattern.js.map +1 -0
  580. package/dist/validation/fieldTargetParam.d.ts +22 -0
  581. package/dist/validation/fieldTargetParam.js +38 -0
  582. package/dist/validation/fieldTargetParam.js.map +1 -0
  583. package/dist/validation/registry.d.ts +2 -1
  584. package/dist/validation/registry.js +2 -9
  585. package/dist/validation/registry.js.map +1 -1
  586. package/dist/validation/runValidation.js +8 -29
  587. package/dist/validation/runValidation.js.map +1 -1
  588. package/dist/validation/types.d.ts +5 -11
  589. package/dist/validation/validateUrl.js +22 -0
  590. package/dist/validation/validateUrl.js.map +1 -0
  591. package/package.json +14 -6
  592. package/styles/form-builder.css +94 -190
  593. package/dist/collections/uploads.d.ts +0 -30
  594. package/dist/collections/uploads.js +0 -55
  595. package/dist/collections/uploads.js.map +0 -1
  596. package/dist/consent/buildConsentSourceConfig.js +0 -49
  597. package/dist/consent/buildConsentSourceConfig.js.map +0 -1
  598. package/dist/consent/builtin/index.d.ts +0 -7
  599. package/dist/consent/builtin/index.js +0 -11
  600. package/dist/consent/builtin/index.js.map +0 -1
  601. package/dist/consent/builtin/pageReference.js +0 -75
  602. package/dist/consent/builtin/pageReference.js.map +0 -1
  603. package/dist/consent/builtin/static.js +0 -44
  604. package/dist/consent/builtin/static.js.map +0 -1
  605. package/dist/consent/defineConsentSource.d.ts +0 -35
  606. package/dist/consent/defineConsentSource.js +0 -6
  607. package/dist/consent/defineConsentSource.js.map +0 -1
  608. package/dist/consent/registry.d.ts +0 -16
  609. package/dist/consent/registry.js +0 -16
  610. package/dist/consent/registry.js.map +0 -1
  611. package/dist/consent/resolveConsentLinks.d.ts +0 -19
  612. package/dist/consent/resolveConsentLinks.js +0 -26
  613. package/dist/consent/resolveConsentLinks.js.map +0 -1
  614. package/dist/presentations/registry.d.ts +0 -15
  615. package/dist/presentations/registry.js +0 -15
  616. package/dist/presentations/registry.js.map +0 -1
  617. package/dist/react/toFormDocument.d.ts +0 -24
  618. package/dist/react/toFormDocument.js +0 -22
  619. package/dist/react/toFormDocument.js.map +0 -1
@@ -7,18 +7,23 @@ type FormResultsProps = {
7
7
  /** One field aggregation or several (a survey summary), resolved server-side and passed in. */results: FieldAggregation | FieldAggregation[]; /** Localized translator for built-in copy. Defaults to identity (returns the key). */
8
8
  t?: RendererTranslate;
9
9
  locale?: string; /** Show the raw count beside each option. Default true. */
10
- showCounts?: boolean;
10
+ showCounts?: boolean; /** The recorded outcome's stable option values; every matching bucket is highlighted as a winner (a tie highlights more than one). */
11
+ winningValues?: string[]; /** The viewing voter's own pick (e.g. `resolveVotedSubmission().pick`); every matching bucket carries a "your vote" marker. */
12
+ currentValues?: string[];
11
13
  };
12
14
  /**
13
15
  * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,
14
16
  * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never
15
17
  * fetches). The option label, count, and percentage are real text (the accessible content); the bar fill
16
- * is `aria-hidden` visual sugar sized by inline width.
18
+ * is `aria-hidden` visual sugar sized by inline width. With `winningValues`, every matching bucket gets
19
+ * the winner class and a translated badge (real text, so the outcome is accessible); a tie highlights more than one.
17
20
  */
18
21
  declare const FormResults: ({
19
22
  results,
20
23
  t,
21
- showCounts
24
+ showCounts,
25
+ winningValues,
26
+ currentValues
22
27
  }: FormResultsProps) => ReactNode;
23
28
  //#endregion
24
29
  export { FormResults, FormResultsProps };
@@ -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, currentValues }) => {
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,49 @@ 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
+ const isYours = currentValues?.includes(bucket.value) ?? false;
30
+ return /* @__PURE__ */ jsxs("li", {
31
+ className: [
32
+ "fb-results__row",
33
+ ...isWinner ? ["fb-results__bucket--winner"] : [],
34
+ ...isYours ? ["fb-results__bucket--yours"] : []
35
+ ].join(" "),
36
+ children: [
37
+ /* @__PURE__ */ jsxs("span", {
38
+ className: "fb-results__option",
39
+ children: [
40
+ bucket.label,
41
+ isWinner ? /* @__PURE__ */ jsx("span", {
42
+ className: "fb-results__winner-badge",
43
+ children: t(keys.resultsWinner)
44
+ }) : null,
45
+ isYours ? /* @__PURE__ */ jsx("span", {
46
+ className: "fb-results__your-vote-badge",
47
+ children: t(keys.resultsYourVote)
48
+ }) : null
49
+ ]
50
+ }),
51
+ /* @__PURE__ */ jsx("span", {
52
+ className: "fb-results__bar",
53
+ children: /* @__PURE__ */ jsx("span", {
54
+ className: "fb-results__bar-fill",
55
+ style: { width: `${bucket.percentage}%` },
56
+ "aria-hidden": "true"
57
+ })
58
+ }),
59
+ /* @__PURE__ */ jsxs("span", {
60
+ className: "fb-results__pct",
61
+ children: [bucket.percentage, "%"]
62
+ }),
63
+ showCounts ? /* @__PURE__ */ jsx("span", {
64
+ className: "fb-results__count",
65
+ children: bucket.count
66
+ }) : null
67
+ ]
68
+ }, bucket.value);
69
+ })
52
70
  }),
53
71
  /* @__PURE__ */ jsxs("p", {
54
72
  className: "fb-results__total",
@@ -66,16 +84,19 @@ const OneResult = ({ result, t, showCounts }) => {
66
84
  * Presentational results view for polls and survey summaries: per-option bars with percentages. Headless,
67
85
  * data resolved server-side via `aggregateFieldResponses`/`aggregateFormResponses` and passed in (it never
68
86
  * 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.
87
+ * is `aria-hidden` visual sugar sized by inline width. With `winningValues`, every matching bucket gets
88
+ * the winner class and a translated badge (real text, so the outcome is accessible); a tie highlights more than one.
70
89
  */
71
- const FormResults = ({ results, t, showCounts = true }) => {
90
+ const FormResults = ({ results, t, showCounts = true, winningValues, currentValues }) => {
72
91
  const translate = t ?? makeTranslate(en);
73
92
  return /* @__PURE__ */ jsx("div", {
74
93
  className: "fb-results",
75
94
  children: (Array.isArray(results) ? results : [results]).map((result) => /* @__PURE__ */ jsx(OneResult, {
76
95
  result,
77
96
  t: translate,
78
- showCounts
97
+ showCounts,
98
+ winningValues,
99
+ currentValues
79
100
  }, result.field))
80
101
  });
81
102
  };
@@ -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\t/** The viewing voter's own pick (e.g. `resolveVotedSubmission().pick`); every matching bucket carries a \"your vote\" marker. */\n\tcurrentValues?: string[]\n}\n\nconst OneResult = ({\n\tresult,\n\tt,\n\tshowCounts,\n\twinningValues,\n\tcurrentValues,\n}: {\n\tresult: FieldAggregation\n\tt: RendererTranslate\n\tshowCounts: boolean\n\twinningValues?: string[]\n\tcurrentValues?: 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\tconst isYours = currentValues?.includes(bucket.value) ?? false\n\t\t\t\t\tconst rowClass = [\n\t\t\t\t\t\t'fb-results__row',\n\t\t\t\t\t\t...(isWinner ? ['fb-results__bucket--winner'] : []),\n\t\t\t\t\t\t...(isYours ? ['fb-results__bucket--yours'] : []),\n\t\t\t\t\t].join(' ')\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<li key={bucket.value} className={rowClass}>\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\t{isYours ? (\n\t\t\t\t\t\t\t\t\t<span className=\"fb-results__your-vote-badge\">{t(keys.resultsYourVote)}</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\tcurrentValues,\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\tcurrentValues={currentValues}\n\t\t\t\t/>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;AAuBA,MAAM,aAAa,EAClB,QACA,GACA,YACA,eACA,oBAOgB;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,MAAM,UAAU,eAAe,SAAS,OAAO,KAAK,KAAK;KAMzD,OACC,qBAAC,MAAD;MAAuB,WANP;OAChB;OACA,GAAI,WAAW,CAAC,4BAA4B,IAAI,CAAC;OACjD,GAAI,UAAU,CAAC,2BAA2B,IAAI,CAAC;MAChD,EAAE,KAAK,GAEmC;gBAAzC;OACC,qBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,OAAO;SACP,WACA,oBAAC,QAAD;UAAM,WAAU;oBAA4B,EAAE,KAAK,aAAa;SAAQ,CAAA,IACrE;SACH,UACA,oBAAC,QAAD;UAAM,WAAU;oBAA+B,EAAE,KAAK,eAAe;SAAQ,CAAA,IAC1E;QACC;;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;QAnBK,OAAO,KAmBZ;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,eACA,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;GACA;EACf,GANK,OAAO,KAMZ,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"}
@@ -1,22 +1,54 @@
1
1
  import { FormProps } from "./Form.js";
2
+ import { FormResultsProps } from "./FormResults.js";
2
3
  import { fetchFormResults } from "./fetchResults.js";
4
+ import { VotedSubmission } from "../submissions/resolveVotedSubmission.js";
5
+ import { ReactNode } from "react";
3
6
 
4
7
  //#region src/react/Poll.d.ts
5
8
  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. */
9
+ /** The choice field whose results are shown after voting (should match the form's public `poll.resultsField`). */resultsField: string;
10
+ /**
11
+ * Key for the per-browser voted guard (localStorage by default; overridable or disabled via
12
+ * `adapters.voteStorage`). Default `fb-poll-{form.id}`.
13
+ */
14
+ storageKey?: string;
15
+ /**
16
+ * Server-known voted state, ORed with the localStorage guard: `true` marks the visitor as voted;
17
+ * `false`/omitted falls back to localStorage. SSR hosts using the plugin's `poll.votedCookie`
18
+ * option pass `hasVotedCookie(cookieHeader, form.id)` here.
19
+ */
8
20
  hasVoted?: boolean;
9
21
  /**
10
22
  * Injectable results fetch (testing); defaults to `fetchFormResults`. Pass a stable reference (module
11
23
  * scope, `useCallback`, or `useMemo`): an inline function re-runs the load effect and double-fetches.
12
24
  */
13
25
  fetchResultsImpl?: typeof fetchFormResults;
26
+ /**
27
+ * The voter's server-resolved current vote (`resolveVotedSubmission`, read from the httpOnly voted
28
+ * cookie). Presence implies voted; `pick` marks the voter's option in results and `value` prefills
29
+ * the form when they change their vote. Read at mount: after a client-side change the component
30
+ * tracks the new pick itself.
31
+ */
32
+ currentVote?: Pick<VotedSubmission, 'value' | 'pick'> | null;
33
+ /**
34
+ * Replaces the built-in `<FormResults>` wherever the poll shows results (after voting, when
35
+ * closed, and for a recorded outcome). Receives exactly what `<FormResults>` receives at that
36
+ * site, so the default is `(props) => <FormResults {...props} />`; surrounding chrome (the
37
+ * closed/final notices, the change-vote button) stays the poll's.
38
+ */
39
+ renderResults?: (props: FormResultsProps) => ReactNode;
14
40
  };
15
41
  /**
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.
42
+ * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows
43
+ * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated
44
+ * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open
45
+ * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A
46
+ * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final
47
+ * notice plus results with every winning bucket highlighted (a tie highlights more than one). A
48
+ * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`
49
+ * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not
50
+ * integrity (bypassable): server-enforced
51
+ * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.
20
52
  */
21
53
  declare const Poll: ({
22
54
  resultsField,
@@ -25,6 +57,8 @@ declare const Poll: ({
25
57
  fetchResultsImpl,
26
58
  apiRoute,
27
59
  onSuccess,
60
+ currentVote,
61
+ renderResults,
28
62
  ...formProps
29
63
  }: PollProps) => import("react/jsx-runtime").JSX.Element | null;
30
64
  //#endregion
@@ -1,72 +1,176 @@
1
1
  "use client";
2
+ import { keys } from "../translations/keys.js";
3
+ import { isPollClosed, pollConfigOf } from "../form/pollState.js";
4
+ import { answerValues } from "../poll/votes/answerValues.js";
5
+ import { en } from "../translations/en.js";
6
+ import { makeTranslate } from "../translations/makeTranslate.js";
2
7
  import { Form } from "./Form.js";
3
8
  import { FormResults } from "./FormResults.js";
4
9
  import { fetchFormResults } from "./fetchResults.js";
5
- import { useCallback, useEffect, useState } from "react";
6
- import { jsx } from "react/jsx-runtime";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { useCallback, useEffect, useMemo, useState } from "react";
7
12
  //#region src/react/Poll.tsx
8
- const readVoted = (key) => {
9
- try {
10
- return window.localStorage.getItem(key) != null;
11
- } catch {
12
- return false;
13
+ const localStorageVoteStorage = {
14
+ read: (key) => {
15
+ try {
16
+ return window.localStorage.getItem(key) != null;
17
+ } catch {
18
+ return false;
19
+ }
20
+ },
21
+ write: (key) => {
22
+ try {
23
+ window.localStorage.setItem(key, "1");
24
+ } catch {}
13
25
  }
14
26
  };
15
- const writeVoted = (key) => {
16
- try {
17
- window.localStorage.setItem(key, "1");
18
- } catch {}
27
+ /** `false` disables client persistence entirely (`hasVoted` still marks voted). */
28
+ const noopVoteStorage = {
29
+ read: () => false,
30
+ write: () => {}
31
+ };
32
+ const resolveVoteStorage = (configured) => {
33
+ if (configured === false) return noopVoteStorage;
34
+ return configured ?? localStorageVoteStorage;
19
35
  };
20
36
  /**
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.
37
+ * A poll: renders `<Form>` while open and not yet voted, then fetches the aggregate results and shows
38
+ * `<FormResults>`. Lifecycle comes from `form.poll`: past `closesAt` the poll is closed (a translated
39
+ * notice plus results, which the endpoint serves for any visibility once closed); a voted-but-open
40
+ * `afterClose` poll shows a translated wait notice instead of fetching (the endpoint would refuse). A
41
+ * recorded `outcome.winningValues` (via `resolvePollOutcome`) supersedes everything: a translated final
42
+ * notice plus results with every winning bucket highlighted (a tie highlights more than one). A
43
+ * per-browser localStorage flag (`storageKey`) skips straight to results on revisit; `hasVoted: true`
44
+ * (e.g. from the server-set voted cookie) marks voted regardless of localStorage. The guard is UX, not
45
+ * integrity (bypassable): server-enforced
46
+ * one-per-identity dedup composes via `req.user` (authed forms) or a `notAlreadySubmitted` rule.
25
47
  */
26
- const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchFormResults, apiRoute, onSuccess, ...formProps }) => {
48
+ const Poll = ({ resultsField, storageKey, hasVoted, fetchResultsImpl = fetchFormResults, apiRoute, onSuccess, currentVote, renderResults, ...formProps }) => {
49
+ const renderResultsView = renderResults ?? ((props) => /* @__PURE__ */ jsx(FormResults, { ...props }));
27
50
  const key = storageKey ?? `fb-poll-${formProps.form.id}`;
28
- const [voted, setVoted] = useState(false);
51
+ const poll = formProps.form.poll;
52
+ const closed = isPollClosed(poll);
53
+ const allowChange = pollConfigOf(poll)?.allowChange === true;
54
+ const winningValues = poll?.outcome?.winningValues;
55
+ const finalized = Array.isArray(winningValues) && winningValues.length > 0;
56
+ const resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === "afterClose";
57
+ const [voted, setVoted] = useState(hasVoted === true || currentVote != null);
58
+ const [changing, setChanging] = useState(false);
59
+ const [pick, setPick] = useState(currentVote?.pick);
60
+ const [prefill, setPrefill] = useState(currentVote?.value);
61
+ const [changeReady, setChangeReady] = useState(currentVote != null);
29
62
  const [results, setResults] = useState(null);
63
+ const [loadFailed, setLoadFailed] = useState(false);
64
+ const translate = useMemo(() => formProps.t ?? makeTranslate(en), [formProps.t]);
65
+ const configuredVoteStorage = formProps.adapters?.voteStorage;
66
+ const voteStorage = useMemo(() => resolveVoteStorage(configuredVoteStorage), [configuredVoteStorage]);
30
67
  const loadResults = useCallback(async () => {
31
68
  const result = await fetchResultsImpl({
32
69
  formId: formProps.form.id,
33
70
  field: resultsField,
34
71
  apiRoute
35
72
  });
36
- setResults(result.ok ? result.results : []);
73
+ if (result.ok) {
74
+ setResults(result.results);
75
+ setLoadFailed(false);
76
+ } else setLoadFailed(true);
37
77
  }, [
38
78
  fetchResultsImpl,
39
79
  formProps.form.id,
40
80
  resultsField,
41
81
  apiRoute
42
82
  ]);
83
+ const resultsError = /* @__PURE__ */ jsx("p", {
84
+ className: "fb-poll__error",
85
+ role: "alert",
86
+ children: translate(keys.pollResultsError)
87
+ });
43
88
  useEffect(() => {
44
- if (hasVoted ?? readVoted(key)) {
45
- setVoted(true);
46
- loadResults();
47
- }
89
+ const already = hasVoted === true || currentVote != null || voteStorage.read(key);
90
+ if (already) setVoted(true);
91
+ if (already && !resultsAwaitClose || closed || finalized) loadResults();
48
92
  }, [
49
93
  hasVoted,
94
+ currentVote,
50
95
  key,
51
- loadResults
96
+ loadResults,
97
+ closed,
98
+ resultsAwaitClose,
99
+ finalized,
100
+ voteStorage
52
101
  ]);
53
- const handleSuccess = useCallback((submissionId) => {
54
- writeVoted(key);
102
+ const handleSuccess = useCallback((submissionId, result) => {
103
+ voteStorage.write(key);
55
104
  setVoted(true);
56
- loadResults();
57
- onSuccess?.(submissionId);
105
+ setChanging(false);
106
+ setChangeReady(true);
107
+ if (result?.values) {
108
+ setPrefill(result.values.find((row) => row.field === resultsField)?.value);
109
+ setPick(answerValues(result.values, resultsField));
110
+ }
111
+ if (!resultsAwaitClose) loadResults();
112
+ onSuccess?.(submissionId, result);
58
113
  }, [
59
114
  key,
60
115
  loadResults,
61
- onSuccess
116
+ onSuccess,
117
+ resultsAwaitClose,
118
+ voteStorage,
119
+ resultsField
62
120
  ]);
63
- if (voted) return results ? /* @__PURE__ */ jsx(FormResults, {
64
- results,
65
- t: formProps.t,
66
- locale: formProps.locale
67
- }) : null;
121
+ if (finalized) return /* @__PURE__ */ jsxs("div", {
122
+ className: "fb-poll fb-poll--final",
123
+ children: [/* @__PURE__ */ jsx("p", {
124
+ className: "fb-poll__final",
125
+ children: translate(keys.pollFinalResult)
126
+ }), loadFailed ? resultsError : results ? renderResultsView({
127
+ results,
128
+ winningValues,
129
+ currentValues: pick,
130
+ t: formProps.t,
131
+ locale: formProps.locale
132
+ }) : null]
133
+ });
134
+ if (closed) return /* @__PURE__ */ jsxs("div", {
135
+ className: "fb-poll fb-poll--closed",
136
+ children: [/* @__PURE__ */ jsx("p", {
137
+ className: "fb-poll__closed",
138
+ children: translate(keys.pollClosed)
139
+ }), loadFailed ? resultsError : results ? renderResultsView({
140
+ results,
141
+ currentValues: pick,
142
+ t: formProps.t,
143
+ locale: formProps.locale
144
+ }) : null]
145
+ });
146
+ if (voted && !changing) {
147
+ if (resultsAwaitClose) return /* @__PURE__ */ jsx("p", {
148
+ className: "fb-poll__await-close",
149
+ children: translate(keys.pollResultsAfterClose)
150
+ });
151
+ if (loadFailed) return resultsError;
152
+ return results ? /* @__PURE__ */ jsxs("div", {
153
+ className: "fb-poll fb-poll--voted",
154
+ children: [renderResultsView({
155
+ results,
156
+ currentValues: pick,
157
+ t: formProps.t,
158
+ locale: formProps.locale
159
+ }), allowChange && changeReady ? /* @__PURE__ */ jsx("button", {
160
+ className: "fb-poll__change",
161
+ type: "button",
162
+ onClick: () => setChanging(true),
163
+ children: translate(keys.pollChangeVote)
164
+ }) : null]
165
+ }) : null;
166
+ }
167
+ const initialValues = changing && prefill !== void 0 ? {
168
+ ...formProps.initialValues ?? {},
169
+ [resultsField]: prefill
170
+ } : formProps.initialValues;
68
171
  return /* @__PURE__ */ jsx(Form, {
69
172
  ...formProps,
173
+ initialValues,
70
174
  apiRoute,
71
175
  onSuccess: handleSuccess
72
176
  });
@@ -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 type { ReactNode } from 'react'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { FieldAggregation } from '../aggregation/types'\nimport { isPollClosed, pollConfigOf } from '../form/pollState'\nimport { answerValues } from '../poll/votes/answerValues'\nimport type { VotedSubmission } from '../submissions/resolveVotedSubmission'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport type { VoteStorage } from './adapters'\nimport { Form, type FormProps } from './Form'\nimport { FormResults, type FormResultsProps } 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/**\n\t * Key for the per-browser voted guard (localStorage by default; overridable or disabled via\n\t * `adapters.voteStorage`). Default `fb-poll-{form.id}`.\n\t */\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\t/**\n\t * The voter's server-resolved current vote (`resolveVotedSubmission`, read from the httpOnly voted\n\t * cookie). Presence implies voted; `pick` marks the voter's option in results and `value` prefills\n\t * the form when they change their vote. Read at mount: after a client-side change the component\n\t * tracks the new pick itself.\n\t */\n\tcurrentVote?: Pick<VotedSubmission, 'value' | 'pick'> | null\n\t/**\n\t * Replaces the built-in `<FormResults>` wherever the poll shows results (after voting, when\n\t * closed, and for a recorded outcome). Receives exactly what `<FormResults>` receives at that\n\t * site, so the default is `(props) => <FormResults {...props} />`; surrounding chrome (the\n\t * closed/final notices, the change-vote button) stays the poll's.\n\t */\n\trenderResults?: (props: FormResultsProps) => ReactNode\n}\n\nconst localStorageVoteStorage: VoteStorage = {\n\tread: (key) => {\n\t\ttry {\n\t\t\treturn window.localStorage.getItem(key) != null\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t},\n\twrite: (key) => {\n\t\ttry {\n\t\t\twindow.localStorage.setItem(key, '1')\n\t\t} catch {\n\t\t\t// Private mode / storage disabled: the guard is best-effort UX, never integrity.\n\t\t}\n\t},\n}\n\n/** `false` disables client persistence entirely (`hasVoted` still marks voted). */\nconst noopVoteStorage: VoteStorage = { read: () => false, write: () => {} }\n\nconst resolveVoteStorage = (configured: VoteStorage | false | undefined): VoteStorage => {\n\tif (configured === false) {\n\t\treturn noopVoteStorage\n\t}\n\treturn configured ?? localStorageVoteStorage\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\tcurrentVote,\n\trenderResults,\n\t...formProps\n}: PollProps) => {\n\tconst renderResultsView =\n\t\trenderResults ?? ((props: FormResultsProps): ReactNode => <FormResults {...props} />)\n\tconst key = storageKey ?? `fb-poll-${formProps.form.id}`\n\tconst poll = formProps.form.poll\n\tconst closed = isPollClosed(poll)\n\tconst allowChange = pollConfigOf(poll)?.allowChange === true\n\tconst winningValues = poll?.outcome?.winningValues\n\tconst finalized = Array.isArray(winningValues) && winningValues.length > 0\n\tconst resultsAwaitClose = !closed && !finalized && poll?.resultsVisibility === 'afterClose'\n\t// Server-known voted state renders the results view on the very first paint; the effect below\n\t// only adds the localStorage read.\n\tconst [voted, setVoted] = useState(hasVoted === true || currentVote != null)\n\tconst [changing, setChanging] = useState(false)\n\tconst [pick, setPick] = useState<string[] | undefined>(currentVote?.pick)\n\tconst [prefill, setPrefill] = useState<unknown>(currentVote?.value)\n\t// Changing is offered only when the signed cookie verifiably identifies a submission: at mount\n\t// via a resolved `currentVote`, or after an in-session vote (the server just issued the cookie).\n\t// A localStorage-only voted flag proves nothing about the cookie, and a \"change\" without one\n\t// would create a second submission instead of updating.\n\tconst [changeReady, setChangeReady] = useState(currentVote != null)\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\tconst configuredVoteStorage = formProps.adapters?.voteStorage\n\tconst voteStorage = useMemo(\n\t\t() => resolveVoteStorage(configuredVoteStorage),\n\t\t[configuredVoteStorage]\n\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 || currentVote != null || voteStorage.read(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, currentVote, key, loadResults, closed, resultsAwaitClose, finalized, voteStorage])\n\n\tconst handleSuccess = useCallback<NonNullable<FormProps['onSuccess']>>(\n\t\t(submissionId, result) => {\n\t\t\tvoteStorage.write(key)\n\t\t\tsetVoted(true)\n\t\t\tsetChanging(false)\n\t\t\tsetChangeReady(true)\n\t\t\t// Track the pick client-side so the \"your vote\" marker is fresh right after a (re-)vote,\n\t\t\t// without waiting for a server-resolved currentVote on the next page load.\n\t\t\tif (result?.values) {\n\t\t\t\tconst entry = result.values.find((row) => row.field === resultsField)\n\t\t\t\tsetPrefill(entry?.value)\n\t\t\t\tsetPick(answerValues(result.values, resultsField))\n\t\t\t}\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, voteStorage, resultsField]\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\t? resultsError\n\t\t\t\t\t: results\n\t\t\t\t\t\t? renderResultsView({\n\t\t\t\t\t\t\t\tresults,\n\t\t\t\t\t\t\t\twinningValues,\n\t\t\t\t\t\t\t\tcurrentValues: pick,\n\t\t\t\t\t\t\t\tt: formProps.t,\n\t\t\t\t\t\t\t\tlocale: formProps.locale,\n\t\t\t\t\t\t\t})\n\t\t\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\t? resultsError\n\t\t\t\t\t: results\n\t\t\t\t\t\t? renderResultsView({\n\t\t\t\t\t\t\t\tresults,\n\t\t\t\t\t\t\t\tcurrentValues: pick,\n\t\t\t\t\t\t\t\tt: formProps.t,\n\t\t\t\t\t\t\t\tlocale: formProps.locale,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: null}\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (voted && !changing) {\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<div className=\"fb-poll fb-poll--voted\">\n\t\t\t\t{renderResultsView({\n\t\t\t\t\tresults,\n\t\t\t\t\tcurrentValues: pick,\n\t\t\t\t\tt: formProps.t,\n\t\t\t\t\tlocale: formProps.locale,\n\t\t\t\t})}\n\t\t\t\t{allowChange && changeReady ? (\n\t\t\t\t\t<button className=\"fb-poll__change\" type=\"button\" onClick={() => setChanging(true)}>\n\t\t\t\t\t\t{translate(keys.pollChangeVote)}\n\t\t\t\t\t</button>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t) : null\n\t}\n\n\t// While changing, the current pick prefills the results field; the server identifies the\n\t// submission to update from the httpOnly voted cookie, so the client sends a normal submit.\n\tconst initialValues =\n\t\tchanging && prefill !== undefined\n\t\t\t? { ...(formProps.initialValues ?? {}), [resultsField]: prefill }\n\t\t\t: formProps.initialValues\n\treturn (\n\t\t<Form\n\t\t\t{...formProps}\n\t\t\tinitialValues={initialValues}\n\t\t\tapiRoute={apiRoute}\n\t\t\tonSuccess={handleSuccess}\n\t\t/>\n\t)\n}\n"],"mappings":";;;;;;;;;;;;AAmDA,MAAM,0BAAuC;CAC5C,OAAO,QAAQ;EACd,IAAI;GACH,OAAO,OAAO,aAAa,QAAQ,GAAG,KAAK;EAC5C,QAAQ;GACP,OAAO;EACR;CACD;CACA,QAAQ,QAAQ;EACf,IAAI;GACH,OAAO,aAAa,QAAQ,KAAK,GAAG;EACrC,QAAQ,CAER;CACD;AACD;;AAGA,MAAM,kBAA+B;CAAE,YAAY;CAAO,aAAa,CAAC;AAAE;AAE1E,MAAM,sBAAsB,eAA6D;CACxF,IAAI,eAAe,OAClB,OAAO;CAER,OAAO,cAAc;AACtB;;;;;;;;;;;;;AAcA,MAAa,QAAQ,EACpB,cACA,YACA,UACA,mBAAmB,kBACnB,UACA,WACA,aACA,eACA,GAAG,gBACa;CAChB,MAAM,oBACL,mBAAmB,UAAuC,oBAAC,aAAD,EAAa,GAAI,MAAQ,CAAA;CACpF,MAAM,MAAM,cAAc,WAAW,UAAU,KAAK;CACpD,MAAM,OAAO,UAAU,KAAK;CAC5B,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,cAAc,aAAa,IAAI,GAAG,gBAAgB;CACxD,MAAM,gBAAgB,MAAM,SAAS;CACrC,MAAM,YAAY,MAAM,QAAQ,aAAa,KAAK,cAAc,SAAS;CACzE,MAAM,oBAAoB,CAAC,UAAU,CAAC,aAAa,MAAM,sBAAsB;CAG/E,MAAM,CAAC,OAAO,YAAY,SAAS,aAAa,QAAQ,eAAe,IAAI;CAC3E,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,CAAC,MAAM,WAAW,SAA+B,aAAa,IAAI;CACxE,MAAM,CAAC,SAAS,cAAc,SAAkB,aAAa,KAAK;CAKlE,MAAM,CAAC,aAAa,kBAAkB,SAAS,eAAe,IAAI;CAClE,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;CAC/E,MAAM,wBAAwB,UAAU,UAAU;CAClD,MAAM,cAAc,cACb,mBAAmB,qBAAqB,GAC9C,CAAC,qBAAqB,CACvB;CAEA,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,eAAe,QAAQ,YAAY,KAAK,GAAG;EAChF,IAAI,SACH,SAAS,IAAI;EAEd,IAAK,WAAW,CAAC,qBAAsB,UAAU,WAChD,YAAiB;CAEnB,GAAG;EAAC;EAAU;EAAa;EAAK;EAAa;EAAQ;EAAmB;EAAW;CAAW,CAAC;CAE/F,MAAM,gBAAgB,aACpB,cAAc,WAAW;EACzB,YAAY,MAAM,GAAG;EACrB,SAAS,IAAI;EACb,YAAY,KAAK;EACjB,eAAe,IAAI;EAGnB,IAAI,QAAQ,QAAQ;GAEnB,WADc,OAAO,OAAO,MAAM,QAAQ,IAAI,UAAU,YACzC,GAAG,KAAK;GACvB,QAAQ,aAAa,OAAO,QAAQ,YAAY,CAAC;EAClD;EACA,IAAI,CAAC,mBACJ,YAAiB;EAGlB,YAAY,cAAc,MAAM;CACjC,GACA;EAAC;EAAK;EAAa;EAAW;EAAmB;EAAa;CAAY,CAC3E;CAEA,IAAI,WACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAkB,UAAU,KAAK,eAAe;EAAK,CAAA,GACjE,aACE,eACA,UACC,kBAAkB;GAClB;GACA;GACA,eAAe;GACf,GAAG,UAAU;GACb,QAAQ,UAAU;EACnB,CAAC,IACA,IACA;;CAIP,IAAI,QACH,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACC,oBAAC,KAAD;GAAG,WAAU;aAAmB,UAAU,KAAK,UAAU;EAAK,CAAA,GAC7D,aACE,eACA,UACC,kBAAkB;GAClB;GACA,eAAe;GACf,GAAG,UAAU;GACb,QAAQ,UAAU;EACnB,CAAC,IACA,IACA;;CAIP,IAAI,SAAS,CAAC,UAAU;EACvB,IAAI,mBACH,OAAO,oBAAC,KAAD;GAAG,WAAU;aAAwB,UAAU,KAAK,qBAAqB;EAAK,CAAA;EAEtF,IAAI,YACH,OAAO;EAER,OAAO,UACN,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAkB;IAClB;IACA,eAAe;IACf,GAAG,UAAU;IACb,QAAQ,UAAU;GACnB,CAAC,GACA,eAAe,cACf,oBAAC,UAAD;IAAQ,WAAU;IAAkB,MAAK;IAAS,eAAe,YAAY,IAAI;cAC/E,UAAU,KAAK,cAAc;GACvB,CAAA,IACL,IACA;OACF;CACL;CAIA,MAAM,gBACL,YAAY,YAAY,KAAA,IACrB;EAAE,GAAI,UAAU,iBAAiB,CAAC;GAAK,eAAe;CAAQ,IAC9D,UAAU;CACd,OACC,oBAAC,MAAD;EACC,GAAI;EACW;EACL;EACV,WAAW;CACX,CAAA;AAEH"}
@@ -0,0 +1,35 @@
1
+ //#region src/react/adapters.d.ts
2
+ /** The plugin's navigation intent for a post-submit redirect, passed through to the adapter. */
3
+ type NavigateOptions = {
4
+ replace: boolean;
5
+ };
6
+ /**
7
+ * Client persistence for a poll's per-browser voted flag. `read` reports whether the flag is set;
8
+ * `write` sets it after a successful vote.
9
+ */
10
+ type VoteStorage = {
11
+ read: (key: string) => boolean;
12
+ write: (key: string) => void;
13
+ };
14
+ /** Loads a third-party script and resolves once it is executable (e.g. a consent-gated loader). */
15
+ type LoadScript = (src: string) => Promise<void>;
16
+ /**
17
+ * Host-owned effects. Every member is optional and defaults to the current DOM behavior, so
18
+ * omitting the prop entirely preserves the default semantics exactly.
19
+ *
20
+ * - `navigate`: replaces `window.location.assign`/`.replace` for the post-submit redirect. When
21
+ * set, the plugin never touches `window.location` for that form; a throwing adapter propagates
22
+ * and never falls back to a hard navigation. Not awaited.
23
+ * - `voteStorage`: replaces the `<Poll>` localStorage voted-flag read/write; `false` disables
24
+ * client persistence entirely (e.g. when the server-side `poll.votedCookie` is the source of truth).
25
+ * - `loadScript`: replaces the captcha widgets' `document.head` script injection (consent
26
+ * managers, CSP nonces). The per-src cache and evict-on-failure retry still apply.
27
+ */
28
+ type FormAdapters = {
29
+ navigate?: (url: string, options: NavigateOptions) => void;
30
+ voteStorage?: VoteStorage | false;
31
+ loadScript?: LoadScript;
32
+ };
33
+ //#endregion
34
+ export { FormAdapters, LoadScript, NavigateOptions, VoteStorage };
35
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1,11 @@
1
+ //#region src/react/adapters.ts
2
+ /** The default navigation: a full document load, push semantics unless `replace` is requested. */
3
+ const defaultNavigate = (url, options) => {
4
+ if (typeof window === "undefined") return;
5
+ if (options.replace) window.location.replace(url);
6
+ else window.location.assign(url);
7
+ };
8
+ //#endregion
9
+ export { defaultNavigate };
10
+
11
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","names":[],"sources":["../../src/react/adapters.ts"],"sourcesContent":["/** The plugin's navigation intent for a post-submit redirect, passed through to the adapter. */\nexport type NavigateOptions = { replace: boolean }\n\n/**\n * Client persistence for a poll's per-browser voted flag. `read` reports whether the flag is set;\n * `write` sets it after a successful vote.\n */\nexport type VoteStorage = {\n\tread: (key: string) => boolean\n\twrite: (key: string) => void\n}\n\n/** Loads a third-party script and resolves once it is executable (e.g. a consent-gated loader). */\nexport type LoadScript = (src: string) => Promise<void>\n\n/**\n * Host-owned effects. Every member is optional and defaults to the current DOM behavior, so\n * omitting the prop entirely preserves the default semantics exactly.\n *\n * - `navigate`: replaces `window.location.assign`/`.replace` for the post-submit redirect. When\n * set, the plugin never touches `window.location` for that form; a throwing adapter propagates\n * and never falls back to a hard navigation. Not awaited.\n * - `voteStorage`: replaces the `<Poll>` localStorage voted-flag read/write; `false` disables\n * client persistence entirely (e.g. when the server-side `poll.votedCookie` is the source of truth).\n * - `loadScript`: replaces the captcha widgets' `document.head` script injection (consent\n * managers, CSP nonces). The per-src cache and evict-on-failure retry still apply.\n */\nexport type FormAdapters = {\n\tnavigate?: (url: string, options: NavigateOptions) => void\n\tvoteStorage?: VoteStorage | false\n\tloadScript?: LoadScript\n}\n\n/** The default navigation: a full document load, push semantics unless `replace` is requested. */\nexport const defaultNavigate = (url: string, options: NavigateOptions): void => {\n\tif (typeof window === 'undefined') {\n\t\treturn\n\t}\n\tif (options.replace) {\n\t\twindow.location.replace(url)\n\t} else {\n\t\twindow.location.assign(url)\n\t}\n}\n"],"mappings":";;AAkCA,MAAa,mBAAmB,KAAa,YAAmC;CAC/E,IAAI,OAAO,WAAW,aACrB;CAED,IAAI,QAAQ,SACX,OAAO,SAAS,QAAQ,GAAG;MAE3B,OAAO,SAAS,OAAO,GAAG;AAE5B"}