@10x-media/form-builder 0.1.0-beta.13 → 0.1.0-beta.15

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 (71) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/collections/formSubmissions.js +40 -20
  3. package/dist/collections/formSubmissions.js.map +1 -1
  4. package/dist/collections/forms.js +20 -0
  5. package/dist/collections/forms.js.map +1 -1
  6. package/dist/exports/react.d.ts +3 -1
  7. package/dist/exports/rsc.d.ts +2 -1
  8. package/dist/exports/rsc.js +4 -3
  9. package/dist/form/pollState.d.ts +2 -1
  10. package/dist/form/pollState.js.map +1 -1
  11. package/dist/form/toFormDocument.d.ts +1 -0
  12. package/dist/form/toFormDocument.js +1 -0
  13. package/dist/form/toFormDocument.js.map +1 -1
  14. package/dist/form/types.d.ts +2 -1
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +5 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/poll/votes/aggregateFromVotes.js +2 -1
  19. package/dist/poll/votes/aggregateFromVotes.js.map +1 -1
  20. package/dist/poll/votes/answerValues.js +16 -0
  21. package/dist/poll/votes/answerValues.js.map +1 -0
  22. package/dist/poll/votes/voteTallyHook.js +31 -13
  23. package/dist/poll/votes/voteTallyHook.js.map +1 -1
  24. package/dist/react/Form.d.ts +12 -2
  25. package/dist/react/Form.js +8 -3
  26. package/dist/react/Form.js.map +1 -1
  27. package/dist/react/FormResults.d.ts +4 -2
  28. package/dist/react/FormResults.js +21 -8
  29. package/dist/react/FormResults.js.map +1 -1
  30. package/dist/react/Poll.d.ts +14 -1
  31. package/dist/react/Poll.js +66 -21
  32. package/dist/react/Poll.js.map +1 -1
  33. package/dist/react/adapters.d.ts +35 -0
  34. package/dist/react/adapters.js +11 -0
  35. package/dist/react/adapters.js.map +1 -0
  36. package/dist/react/captcha/HcaptchaCaptcha.d.ts +4 -1
  37. package/dist/react/captcha/HcaptchaCaptcha.js +2 -2
  38. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -1
  39. package/dist/react/captcha/RecaptchaCaptcha.d.ts +4 -1
  40. package/dist/react/captcha/RecaptchaCaptcha.js +2 -2
  41. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -1
  42. package/dist/react/captcha/TurnstileCaptcha.d.ts +4 -1
  43. package/dist/react/captcha/TurnstileCaptcha.js +2 -2
  44. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -1
  45. package/dist/react/captcha/useCaptchaScript.js +23 -5
  46. package/dist/react/captcha/useCaptchaScript.js.map +1 -1
  47. package/dist/spam/spamGuard.js +2 -1
  48. package/dist/spam/spamGuard.js.map +1 -1
  49. package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
  50. package/dist/submissions/resolveVotedSubmission.js +51 -0
  51. package/dist/submissions/resolveVotedSubmission.js.map +1 -0
  52. package/dist/submissions/validateSubmission.js +17 -6
  53. package/dist/submissions/validateSubmission.js.map +1 -1
  54. package/dist/submissions/verifyContext.js +1 -1
  55. package/dist/submissions/voteChangeEndpoint.js +94 -0
  56. package/dist/submissions/voteChangeEndpoint.js.map +1 -0
  57. package/dist/submissions/votedCookie.d.ts +2 -0
  58. package/dist/submissions/votedCookie.js +53 -6
  59. package/dist/submissions/votedCookie.js.map +1 -1
  60. package/dist/translations/clientRuntimeKeys.js +3 -1
  61. package/dist/translations/clientRuntimeKeys.js.map +1 -1
  62. package/dist/translations/de.js +5 -0
  63. package/dist/translations/de.js.map +1 -1
  64. package/dist/translations/en.js +5 -0
  65. package/dist/translations/en.js.map +1 -1
  66. package/dist/translations/keys.d.ts +5 -0
  67. package/dist/translations/keys.js +5 -0
  68. package/dist/translations/keys.js.map +1 -1
  69. package/dist/validation/builtin/notAlreadySubmitted.js +3 -1
  70. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  71. package/package.json +3 -3
@@ -52,6 +52,7 @@ function toFormDocument(form, options) {
52
52
  const poll = form.poll ? {
53
53
  resultsVisibility: form.poll.resultsVisibility ?? void 0,
54
54
  closesAt: form.poll.closesAt ?? void 0,
55
+ ...form.poll.allowChange === true ? { allowChange: true } : {},
55
56
  ...winningValues.length > 0 ? { outcome: { winningValues } } : {}
56
57
  } : void 0;
57
58
  let fields = form.fields ?? [];
@@ -1 +1 @@
1
- {"version":3,"file":"toFormDocument.js","names":[],"sources":["../../src/form/toFormDocument.ts"],"sourcesContent":["import type { CalcResolved } from '../calc/evaluate'\nimport { applyConsentStatements } from '../consent/applyConsentStatements'\nimport type { ConsentStatements } from '../consent/resolveConsentStatements'\nimport type { FormFlow } from '../flow/types'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { FormButtonSettings, FormDocument, FormResponseSettings } from './types'\n\nexport type ToFormDocumentOptions = {\n\t/**\n\t * Source-resolved poll options (from `resolvePollOptions`), injected as the options of the\n\t * field instance named by the form's `poll.resultsField` so the client renders the host's\n\t * current choices instead of hand-authored ones.\n\t */\n\tpollOptions?: PollOption[]\n\t/**\n\t * Source-resolved consent statements (from `resolveConsentStatements`), injected into the\n\t * consent field instances they name. A form stores only a source id, so without these its\n\t * consent fields render with no statement at all.\n\t */\n\tconsentStatements?: ConsentStatements\n\t/**\n\t * Resolve an internal redirect `reference` to a same-site URL. Called only when\n\t * `response.type === 'redirect'`, a `reference` is present, and no `redirect.url` is set. Keep it\n\t * sync: operate on the already-loaded (populated) reference, since resolving a Doc to a URL is host\n\t * routing knowledge the plugin cannot supply. The result is written to `redirect.url`, so `<Form>`\n\t * navigates with no client change.\n\t */\n\tresolveRedirect?: (reference: {\n\t\trelationTo: string\n\t\tvalue: number | string | object\n\t}) => string | null | undefined\n}\n\n/**\n * Reassemble the client button labels from the document's top-level `submitLabel`/`prevLabel`/\n * `nextLabel` fields, keeping only non-empty strings (mirroring the client's own `storedLabel`\n * fallback). Returns `undefined` when the author set no label, so the client uses its defaults.\n */\nconst buttonSettingsOf = (form: {\n\tsubmitLabel?: string | null\n\tprevLabel?: string | null\n\tnextLabel?: string | null\n}): FormButtonSettings | undefined => {\n\tconst label = (value: unknown): string | undefined =>\n\t\ttypeof value === 'string' && value.length > 0 ? value : undefined\n\tconst settings: FormButtonSettings = {}\n\tconst submitLabel = label(form.submitLabel)\n\tconst prevLabel = label(form.prevLabel)\n\tconst nextLabel = label(form.nextLabel)\n\tif (submitLabel !== undefined) settings.submitLabel = submitLabel\n\tif (prevLabel !== undefined) settings.prevLabel = prevLabel\n\tif (nextLabel !== undefined) settings.nextLabel = nextLabel\n\treturn Object.keys(settings).length > 0 ? settings : undefined\n}\n\n/**\n * Narrows a Payload-generated form document (from `getPayload().findByID()` or `fetch`) to\n * `FormDocument` without an unsafe `as` cast. Handles the small structural mismatches between\n * the auto-generated collection types and what `<Form>` expects:\n * - `fields` may be a typed blocks-union array or null; normalized to `FormFieldInstance[]`\n * (nameless bare rows, e.g. message blocks, pass through with their row `id` intact)\n * - `flow` is stored as opaque JSON; typed as `FormFlow | undefined`\n * - `response` may be null; coerced to `undefined`, otherwise passed through wholesale, which\n * includes `response.redirect.reference` (present only when the plugin's `redirectRelationships`\n * option is set): `value` may be a bare id (`depth: 0`) or a populated document, and the host\n * resolves it to a URL either by writing `redirect.url` or by passing `options.resolveRedirect`\n * - the button labels (`submitLabel`/`prevLabel`/`nextLabel`) live at the document root now (there\n * is no `buttons` group); the non-empty ones are reassembled into `FormDocument.buttons`\n * - `title` may be null; coerced to `undefined`\n * - `multistep`/`pollEnabled` are the two top-level form-type flags, coerced to strict booleans\n * - `poll` may be null; coerced to `undefined` (`resultsField`, `optionSource`, and\n * `sourceConfig` are dropped: server-side only; `outcome` passes through `winningValues` only)\n *\n * Unknown keys survive on `response` but not on `poll`, which is deliberate rather than incidental.\n * `response` is a visitor-facing group a host is meant to extend (`overrides.forms.fields`) and read\n * back off `FormDocument` in custom chrome, so it is cast wholesale. `poll` is plugin-owned config\n * that is split between\n * client-safe lifecycle state and server-only members, so it is rebuilt from an allowlist: a\n * passthrough would ship whatever a host put in `sourceConfig` (domain config, ids, credentials)\n * to every anonymous visitor. Adding a client-visible poll member means naming it here.\n *\n * Pure and framework-agnostic (no 'use client'): safe to call from a Server Component or any\n * other server-side code before handing the result to the client `<Form>`.\n */\nexport function toFormDocument(\n\tform: {\n\t\tid: number | string\n\t\tfields?: { blockType: string; name?: string; [key: string]: unknown }[] | null\n\t\tflow?: unknown\n\t\tresponse?: {\n\t\t\ttype?: string | null\n\t\t\tmessage?: unknown\n\t\t\tredirect?: {\n\t\t\t\turl?: string | null\n\t\t\t\treference?: { relationTo?: string | null; value?: number | string | object | null } | null\n\t\t\t} | null\n\t\t} | null\n\t\tsubmitLabel?: string | null\n\t\tprevLabel?: string | null\n\t\tnextLabel?: string | null\n\t\ttitle?: string | null\n\t\tmultistep?: boolean | null\n\t\tpollEnabled?: boolean | null\n\t\tpoll?: {\n\t\t\tresultsField?: string | null\n\t\t\tresultsVisibility?: string | null\n\t\t\tclosesAt?: string | null\n\t\t\toutcome?: { winningValues?: (string | null)[] | null } | null\n\t\t} | null\n\t\t/**\n\t\t * Consent statements resolved onto the doc by the plugin's forms `afterRead` hook (when\n\t\t * `consent.sources` is set), so consent renders on any fetch path (modal/client, not just RSC).\n\t\t * An explicit `options.consentStatements` takes precedence when both are present.\n\t\t */\n\t\tconsentStatements?: ConsentStatements\n\t\t/**\n\t\t * Calc source values resolved onto the doc by the plugin's forms `afterRead` hook (when calc\n\t\t * sources are registered and used); passed through so the client's live preview computes with\n\t\t * real values. `functions` can never ride a document, hence the Pick.\n\t\t */\n\t\tcalcResolved?: Pick<CalcResolved, 'sources' | 'weights'>\n\t},\n\toptions?: ToFormDocumentOptions\n): FormDocument {\n\tconst rawWinningValues = form.poll?.outcome?.winningValues\n\tconst winningValues = Array.isArray(rawWinningValues)\n\t\t? rawWinningValues.filter(\n\t\t\t\t(value): value is string => typeof value === 'string' && value.length > 0\n\t\t\t)\n\t\t: []\n\tconst poll = form.poll\n\t\t? {\n\t\t\t\tresultsVisibility: (form.poll.resultsVisibility ?? undefined) as\n\t\t\t\t\t| 'afterVote'\n\t\t\t\t\t| 'afterClose'\n\t\t\t\t\t| undefined,\n\t\t\t\tclosesAt: form.poll.closesAt ?? undefined,\n\t\t\t\t...(winningValues.length > 0 ? { outcome: { winningValues } } : {}),\n\t\t\t}\n\t\t: undefined\n\tlet fields = (form.fields ?? []) as FormFieldInstance[]\n\tif (options?.pollOptions) {\n\t\tfields = applyPollOptions(fields, form.poll?.resultsField, options.pollOptions)\n\t}\n\tconst consentStatements = options?.consentStatements ?? form.consentStatements\n\tif (consentStatements) {\n\t\tfields = applyConsentStatements(fields, consentStatements)\n\t}\n\tlet response = (form.response as FormResponseSettings | null | undefined) ?? undefined\n\t// A host that passes `resolveRedirect` gets internal references turned into `redirect.url` (all\n\t// `<Form>` navigates by), but only when the author set no explicit url. Non-mutating: clone the group.\n\tif (\n\t\toptions?.resolveRedirect &&\n\t\tresponse?.type === 'redirect' &&\n\t\tresponse.redirect?.reference &&\n\t\t!response.redirect.url\n\t) {\n\t\tconst url = options.resolveRedirect(response.redirect.reference)\n\t\tif (url) {\n\t\t\tresponse = { ...response, redirect: { ...response.redirect, url } }\n\t\t}\n\t}\n\treturn {\n\t\tid: form.id,\n\t\tfields,\n\t\tflow: form.flow as FormFlow | undefined,\n\t\tresponse,\n\t\tbuttons: buttonSettingsOf(form),\n\t\ttitle: form.title ?? undefined,\n\t\tmultistep: form.multistep === true,\n\t\tpollEnabled: form.pollEnabled === true,\n\t\tpoll,\n\t\tcalcResolved: form.calcResolved,\n\t}\n}\n"],"mappings":";;;;;;;;AAwCA,MAAM,oBAAoB,SAIY;CACrC,MAAM,SAAS,UACd,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;CACzD,MAAM,WAA+B,CAAC;CACtC,MAAM,cAAc,MAAM,KAAK,WAAW;CAC1C,MAAM,YAAY,MAAM,KAAK,SAAS;CACtC,MAAM,YAAY,MAAM,KAAK,SAAS;CACtC,IAAI,gBAAgB,KAAA,GAAW,SAAS,cAAc;CACtD,IAAI,cAAc,KAAA,GAAW,SAAS,YAAY;CAClD,IAAI,cAAc,KAAA,GAAW,SAAS,YAAY;CAClD,OAAO,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW,KAAA;AACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,eACf,MAqCA,SACe;CACf,MAAM,mBAAmB,KAAK,MAAM,SAAS;CAC7C,MAAM,gBAAgB,MAAM,QAAQ,gBAAgB,IACjD,iBAAiB,QAChB,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CACzE,IACC,CAAC;CACJ,MAAM,OAAO,KAAK,OACf;EACA,mBAAoB,KAAK,KAAK,qBAAqB,KAAA;EAInD,UAAU,KAAK,KAAK,YAAY,KAAA;EAChC,GAAI,cAAc,SAAS,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC;CAClE,IACC,KAAA;CACH,IAAI,SAAU,KAAK,UAAU,CAAC;CAC9B,IAAI,SAAS,aACZ,SAAS,iBAAiB,QAAQ,KAAK,MAAM,cAAc,QAAQ,WAAW;CAE/E,MAAM,oBAAoB,SAAS,qBAAqB,KAAK;CAC7D,IAAI,mBACH,SAAS,uBAAuB,QAAQ,iBAAiB;CAE1D,IAAI,WAAY,KAAK,YAAwD,KAAA;CAG7E,IACC,SAAS,mBACT,UAAU,SAAS,cACnB,SAAS,UAAU,aACnB,CAAC,SAAS,SAAS,KAClB;EACD,MAAM,MAAM,QAAQ,gBAAgB,SAAS,SAAS,SAAS;EAC/D,IAAI,KACH,WAAW;GAAE,GAAG;GAAU,UAAU;IAAE,GAAG,SAAS;IAAU;GAAI;EAAE;CAEpE;CACA,OAAO;EACN,IAAI,KAAK;EACT;EACA,MAAM,KAAK;EACX;EACA,SAAS,iBAAiB,IAAI;EAC9B,OAAO,KAAK,SAAS,KAAA;EACrB,WAAW,KAAK,cAAc;EAC9B,aAAa,KAAK,gBAAgB;EAClC;EACA,cAAc,KAAK;CACpB;AACD"}
1
+ {"version":3,"file":"toFormDocument.js","names":[],"sources":["../../src/form/toFormDocument.ts"],"sourcesContent":["import type { CalcResolved } from '../calc/evaluate'\nimport { applyConsentStatements } from '../consent/applyConsentStatements'\nimport type { ConsentStatements } from '../consent/resolveConsentStatements'\nimport type { FormFlow } from '../flow/types'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { FormButtonSettings, FormDocument, FormResponseSettings } from './types'\n\nexport type ToFormDocumentOptions = {\n\t/**\n\t * Source-resolved poll options (from `resolvePollOptions`), injected as the options of the\n\t * field instance named by the form's `poll.resultsField` so the client renders the host's\n\t * current choices instead of hand-authored ones.\n\t */\n\tpollOptions?: PollOption[]\n\t/**\n\t * Source-resolved consent statements (from `resolveConsentStatements`), injected into the\n\t * consent field instances they name. A form stores only a source id, so without these its\n\t * consent fields render with no statement at all.\n\t */\n\tconsentStatements?: ConsentStatements\n\t/**\n\t * Resolve an internal redirect `reference` to a same-site URL. Called only when\n\t * `response.type === 'redirect'`, a `reference` is present, and no `redirect.url` is set. Keep it\n\t * sync: operate on the already-loaded (populated) reference, since resolving a Doc to a URL is host\n\t * routing knowledge the plugin cannot supply. The result is written to `redirect.url`, so `<Form>`\n\t * navigates with no client change.\n\t */\n\tresolveRedirect?: (reference: {\n\t\trelationTo: string\n\t\tvalue: number | string | object\n\t}) => string | null | undefined\n}\n\n/**\n * Reassemble the client button labels from the document's top-level `submitLabel`/`prevLabel`/\n * `nextLabel` fields, keeping only non-empty strings (mirroring the client's own `storedLabel`\n * fallback). Returns `undefined` when the author set no label, so the client uses its defaults.\n */\nconst buttonSettingsOf = (form: {\n\tsubmitLabel?: string | null\n\tprevLabel?: string | null\n\tnextLabel?: string | null\n}): FormButtonSettings | undefined => {\n\tconst label = (value: unknown): string | undefined =>\n\t\ttypeof value === 'string' && value.length > 0 ? value : undefined\n\tconst settings: FormButtonSettings = {}\n\tconst submitLabel = label(form.submitLabel)\n\tconst prevLabel = label(form.prevLabel)\n\tconst nextLabel = label(form.nextLabel)\n\tif (submitLabel !== undefined) settings.submitLabel = submitLabel\n\tif (prevLabel !== undefined) settings.prevLabel = prevLabel\n\tif (nextLabel !== undefined) settings.nextLabel = nextLabel\n\treturn Object.keys(settings).length > 0 ? settings : undefined\n}\n\n/**\n * Narrows a Payload-generated form document (from `getPayload().findByID()` or `fetch`) to\n * `FormDocument` without an unsafe `as` cast. Handles the small structural mismatches between\n * the auto-generated collection types and what `<Form>` expects:\n * - `fields` may be a typed blocks-union array or null; normalized to `FormFieldInstance[]`\n * (nameless bare rows, e.g. message blocks, pass through with their row `id` intact)\n * - `flow` is stored as opaque JSON; typed as `FormFlow | undefined`\n * - `response` may be null; coerced to `undefined`, otherwise passed through wholesale, which\n * includes `response.redirect.reference` (present only when the plugin's `redirectRelationships`\n * option is set): `value` may be a bare id (`depth: 0`) or a populated document, and the host\n * resolves it to a URL either by writing `redirect.url` or by passing `options.resolveRedirect`\n * - the button labels (`submitLabel`/`prevLabel`/`nextLabel`) live at the document root now (there\n * is no `buttons` group); the non-empty ones are reassembled into `FormDocument.buttons`\n * - `title` may be null; coerced to `undefined`\n * - `multistep`/`pollEnabled` are the two top-level form-type flags, coerced to strict booleans\n * - `poll` may be null; coerced to `undefined` (`resultsField`, `optionSource`, and\n * `sourceConfig` are dropped: server-side only; `outcome` passes through `winningValues` only)\n *\n * Unknown keys survive on `response` but not on `poll`, which is deliberate rather than incidental.\n * `response` is a visitor-facing group a host is meant to extend (`overrides.forms.fields`) and read\n * back off `FormDocument` in custom chrome, so it is cast wholesale. `poll` is plugin-owned config\n * that is split between\n * client-safe lifecycle state and server-only members, so it is rebuilt from an allowlist: a\n * passthrough would ship whatever a host put in `sourceConfig` (domain config, ids, credentials)\n * to every anonymous visitor. Adding a client-visible poll member means naming it here.\n *\n * Pure and framework-agnostic (no 'use client'): safe to call from a Server Component or any\n * other server-side code before handing the result to the client `<Form>`.\n */\nexport function toFormDocument(\n\tform: {\n\t\tid: number | string\n\t\tfields?: { blockType: string; name?: string; [key: string]: unknown }[] | null\n\t\tflow?: unknown\n\t\tresponse?: {\n\t\t\ttype?: string | null\n\t\t\tmessage?: unknown\n\t\t\tredirect?: {\n\t\t\t\turl?: string | null\n\t\t\t\treference?: { relationTo?: string | null; value?: number | string | object | null } | null\n\t\t\t} | null\n\t\t} | null\n\t\tsubmitLabel?: string | null\n\t\tprevLabel?: string | null\n\t\tnextLabel?: string | null\n\t\ttitle?: string | null\n\t\tmultistep?: boolean | null\n\t\tpollEnabled?: boolean | null\n\t\tpoll?: {\n\t\t\tresultsField?: string | null\n\t\t\tresultsVisibility?: string | null\n\t\t\tclosesAt?: string | null\n\t\t\tallowChange?: boolean | null\n\t\t\toutcome?: { winningValues?: (string | null)[] | null } | null\n\t\t} | null\n\t\t/**\n\t\t * Consent statements resolved onto the doc by the plugin's forms `afterRead` hook (when\n\t\t * `consent.sources` is set), so consent renders on any fetch path (modal/client, not just RSC).\n\t\t * An explicit `options.consentStatements` takes precedence when both are present.\n\t\t */\n\t\tconsentStatements?: ConsentStatements\n\t\t/**\n\t\t * Calc source values resolved onto the doc by the plugin's forms `afterRead` hook (when calc\n\t\t * sources are registered and used); passed through so the client's live preview computes with\n\t\t * real values. `functions` can never ride a document, hence the Pick.\n\t\t */\n\t\tcalcResolved?: Pick<CalcResolved, 'sources' | 'weights'>\n\t},\n\toptions?: ToFormDocumentOptions\n): FormDocument {\n\tconst rawWinningValues = form.poll?.outcome?.winningValues\n\tconst winningValues = Array.isArray(rawWinningValues)\n\t\t? rawWinningValues.filter(\n\t\t\t\t(value): value is string => typeof value === 'string' && value.length > 0\n\t\t\t)\n\t\t: []\n\tconst poll = form.poll\n\t\t? {\n\t\t\t\tresultsVisibility: (form.poll.resultsVisibility ?? undefined) as\n\t\t\t\t\t| 'afterVote'\n\t\t\t\t\t| 'afterClose'\n\t\t\t\t\t| undefined,\n\t\t\t\tclosesAt: form.poll.closesAt ?? undefined,\n\t\t\t\t...(form.poll.allowChange === true ? { allowChange: true } : {}),\n\t\t\t\t...(winningValues.length > 0 ? { outcome: { winningValues } } : {}),\n\t\t\t}\n\t\t: undefined\n\tlet fields = (form.fields ?? []) as FormFieldInstance[]\n\tif (options?.pollOptions) {\n\t\tfields = applyPollOptions(fields, form.poll?.resultsField, options.pollOptions)\n\t}\n\tconst consentStatements = options?.consentStatements ?? form.consentStatements\n\tif (consentStatements) {\n\t\tfields = applyConsentStatements(fields, consentStatements)\n\t}\n\tlet response = (form.response as FormResponseSettings | null | undefined) ?? undefined\n\t// A host that passes `resolveRedirect` gets internal references turned into `redirect.url` (all\n\t// `<Form>` navigates by), but only when the author set no explicit url. Non-mutating: clone the group.\n\tif (\n\t\toptions?.resolveRedirect &&\n\t\tresponse?.type === 'redirect' &&\n\t\tresponse.redirect?.reference &&\n\t\t!response.redirect.url\n\t) {\n\t\tconst url = options.resolveRedirect(response.redirect.reference)\n\t\tif (url) {\n\t\t\tresponse = { ...response, redirect: { ...response.redirect, url } }\n\t\t}\n\t}\n\treturn {\n\t\tid: form.id,\n\t\tfields,\n\t\tflow: form.flow as FormFlow | undefined,\n\t\tresponse,\n\t\tbuttons: buttonSettingsOf(form),\n\t\ttitle: form.title ?? undefined,\n\t\tmultistep: form.multistep === true,\n\t\tpollEnabled: form.pollEnabled === true,\n\t\tpoll,\n\t\tcalcResolved: form.calcResolved,\n\t}\n}\n"],"mappings":";;;;;;;;AAwCA,MAAM,oBAAoB,SAIY;CACrC,MAAM,SAAS,UACd,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;CACzD,MAAM,WAA+B,CAAC;CACtC,MAAM,cAAc,MAAM,KAAK,WAAW;CAC1C,MAAM,YAAY,MAAM,KAAK,SAAS;CACtC,MAAM,YAAY,MAAM,KAAK,SAAS;CACtC,IAAI,gBAAgB,KAAA,GAAW,SAAS,cAAc;CACtD,IAAI,cAAc,KAAA,GAAW,SAAS,YAAY;CAClD,IAAI,cAAc,KAAA,GAAW,SAAS,YAAY;CAClD,OAAO,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW,KAAA;AACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,eACf,MAsCA,SACe;CACf,MAAM,mBAAmB,KAAK,MAAM,SAAS;CAC7C,MAAM,gBAAgB,MAAM,QAAQ,gBAAgB,IACjD,iBAAiB,QAChB,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CACzE,IACC,CAAC;CACJ,MAAM,OAAO,KAAK,OACf;EACA,mBAAoB,KAAK,KAAK,qBAAqB,KAAA;EAInD,UAAU,KAAK,KAAK,YAAY,KAAA;EAChC,GAAI,KAAK,KAAK,gBAAgB,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;EAC9D,GAAI,cAAc,SAAS,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC;CAClE,IACC,KAAA;CACH,IAAI,SAAU,KAAK,UAAU,CAAC;CAC9B,IAAI,SAAS,aACZ,SAAS,iBAAiB,QAAQ,KAAK,MAAM,cAAc,QAAQ,WAAW;CAE/E,MAAM,oBAAoB,SAAS,qBAAqB,KAAK;CAC7D,IAAI,mBACH,SAAS,uBAAuB,QAAQ,iBAAiB;CAE1D,IAAI,WAAY,KAAK,YAAwD,KAAA;CAG7E,IACC,SAAS,mBACT,UAAU,SAAS,cACnB,SAAS,UAAU,aACnB,CAAC,SAAS,SAAS,KAClB;EACD,MAAM,MAAM,QAAQ,gBAAgB,SAAS,SAAS,SAAS;EAC/D,IAAI,KACH,WAAW;GAAE,GAAG;GAAU,UAAU;IAAE,GAAG,SAAS;IAAU;GAAI;EAAE;CAEpE;CACA,OAAO;EACN,IAAI,KAAK;EACT;EACA,MAAM,KAAK;EACX;EACA,SAAS,iBAAiB,IAAI;EAC9B,OAAO,KAAK,SAAS,KAAA;EACrB,WAAW,KAAK,cAAc;EAC9B,aAAa,KAAK,gBAAgB;EAClC;EACA,cAAc,KAAK;CACpB;AACD"}
@@ -40,7 +40,8 @@ type FormButtonSettings = {
40
40
  */
41
41
  type FormPollSettings = {
42
42
  /** When anonymous callers may read aggregate results. Default 'afterVote'. */resultsVisibility?: 'afterVote' | 'afterClose' | null; /** ISO date after which submissions are rejected and the poll renders as closed. */
43
- closesAt?: string | null; /** Present once the host recorded a final outcome via `resolvePollOutcome`; `<Poll>` renders the final state. A tie carries more than one value. */
43
+ closesAt?: string | null; /** Whether a voter may change their vote while the poll is open (`poll.allowChange`). Present only when enabled. */
44
+ allowChange?: boolean; /** Present once the host recorded a final outcome via `resolvePollOutcome`; `<Poll>` renders the final state. A tie carries more than one value. */
44
45
  outcome?: {
45
46
  winningValues?: string[];
46
47
  };
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ import { PrefillOptions, valuesFromSearchParams } from "./prefill/valuesFromSear
22
22
  import { AnyValidationRuleDefinition, ValidationRuleDefinition, ValidationRuleResult } from "./validation/types.js";
23
23
  import { PresentationDensity, PresentationDescriptor, PresentationSurface } from "./presentations/types.js";
24
24
  import { RecallResolver, buildRecallResolver, optionLabelsFor } from "./recall/resolver.js";
25
+ import { VotedSubmission, resolveVotedSubmission } from "./submissions/resolveVotedSubmission.js";
25
26
  import { interpolate } from "./recall/interpolate.js";
26
27
  import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "./spam/constants.js";
27
28
  import { formatBytes } from "./uploads/formatBytes.js";
@@ -94,5 +95,5 @@ import { FieldTargetParamOptions, fieldTargetParam } from "./validation/fieldTar
94
95
  //#region src/index.d.ts
95
96
  declare const formBuilder: (options: FormBuilderPluginOptions) => import("payload").Plugin;
96
97
  //#endregion
97
- export { type ActionDefinition, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressesResolver, type HcaptchaProviderOptions, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
98
+ export { type ActionDefinition, type ActionOption, type ActionRegistry, type ActionResult, type ActionRunArgs, type ActionsConfig, type AggregateFieldResponsesArgs, type AggregateFormResponsesArgs, type AggregationBucket, type AggregationRow, type AnyActionDefinition, type AnyFormFieldDefinition, type AnyPollOptionSource, type AnyValidationRuleDefinition, type BodyContext, type BodyConverter, type BodyConverterArgs, type BodyRender, type ButtonFieldsOverride, type ButtonsOption, CAPTCHA_TOKEN_KEY, type CalcAllowed, type CalcDisplayConfig, type CalcExpression, type CalcFunction, type CalcResolved, type CalcSource, type CalcSourceResolveArgs, type CalcWeightResolveArgs, type CaptchaProvider, type CaptchaVerifyArgs, type ConsentProof, type ConsentSnapshotMode, type ConsentSourceEntry, type ConsentSourceOption, type ConsentSourcePage, type ConsentSourcesFieldOptions, type ConsentSourcesResolver, type ConsentStatement, type ConsentStatements, type CreateSubmissionArgs, type CreatedSubmission, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, type DefaultButtonFields, type DefaultOutcomeFields, type DefaultSettingsFields, type DepartmentEmailsResolver, type DepartmentOption, type DepartmentsFieldOptions, type FieldAggregation, type FieldCondition, type FieldMeta, type FieldTargetParamOptions, type FieldTypeOption, type FieldTypeRegistry, type FieldTypesConfig, type FileFieldConfig, type FileRef, type FileRefError, type FormBuilderPluginOptions, type FormButtonSettings, type FormContextReference, type FormDocument, type FormFieldDefinition, type FormFieldFormat, type FormFieldValidate, type FormFieldValueKind, type FormPollSettings, type FormResponseSettings, type FormResultsAccess, type FormResultsAccessArgs, type FromAddressOption, type FromAddressesResolver, type HcaptchaProviderOptions, type IdentifyFn, type OmittableSharedField, type OutcomeFieldsOverride, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, type FormBuilderPluginOptions as PluginOptions, type PollCloseTaskInput, type PollOption, type PollOptionResolveArgs, type PollOptionSource, type PollOptionSourceOption, type PollOptionSourceRegistry, type PollOptionSourcesConfig, type PollOutcomeStrategy, type PollOutcomeStrategyArgs, type PollTypeRegistry, type PollTypesConfig, type PrefillOptions, type PresentationDensity, type PresentationDescriptor, type PresentationSurface, RESPONDENTS_VALUE, type RateLimitCheckArgs, type RateLimitConfig, type RateLimitResult, type RateLimiter, type RecallResolver, type RecaptchaProviderOptions, type RecipientResolveArgs, type RecipientSource, type RecipientSourceRegistry, type RedirectFieldsOverride, type RedirectOption, type ResolveCalcContextArgs, type ResolveConsentEntriesArgs, type ResolveConsentSourcesRequestArgs, type ResolveConsentSourcesRequestResult, type ResolveConsentStatementsArgs, type ResolveDepartmentOptionsArgs, type ResolveDepartmentsRequestArgs, type ResolveDepartmentsRequestResult, type ResolveEffectivePollOptionsArgs, type ResolveFieldOptionsArgs, type ResolvePollOptionsArgs, type ResolvePollOutcomeArgs, type ResolvePublishedVersionRefArgs, type ResolveResultsRequestArgs, type ResolveResultsRequestResult, type ResponseOption, type RichTextBodyOption, SIGNATURE_HEADER, type SerializeBodyArgs, type SettingsFieldsOverride, type SettingsOption, type SpamConfig, type SpamMetadataConfig, type SpamOption, type SubmissionStatusFilter, type ToFormDocumentOptions, type TurnstileProviderOptions, type UploadsOption, VOTE_SHARDS, type ValidationRuleDefinition, type ValidationRuleOption, type ValidationRuleRegistry, type ValidationRuleResult, type ValidationRulesConfig, type VotedSubmission, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
98
99
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -18,12 +18,12 @@ import { resolveFileRef } from "./uploads/resolveFileRef.js";
18
18
  import { fileMimeTypeOptions } from "./fields/builtin/file.js";
19
19
  import { buildDefaultFieldDefinitions, defaultFieldDefinitions, defaultFieldDefinitionsByType } from "./fields/builtin/index.js";
20
20
  import { resolveFieldTypes, stashFieldTypes } from "./fields/registry.js";
21
+ import { isPollClosed } from "./form/pollState.js";
21
22
  import { fieldKey } from "./fields/fieldKey.js";
22
23
  import { calcWeightKey, evaluateCalc } from "./calc/evaluate.js";
23
24
  import { calcExpressionOf, computeCalcFields } from "./calc/computeCalcFields.js";
24
25
  import { calcUsesSources, resolveCalcContext } from "./calc/resolveCalcContext.js";
25
26
  import { resolveConsentStatements } from "./consent/resolveConsentStatements.js";
26
- import { isPollClosed } from "./form/pollState.js";
27
27
  import { aggregateRowForField, aggregateRowsForFields } from "./aggregation/aggregateRows.js";
28
28
  import { aggregateFieldResponses, aggregateFormResponses, fieldHasOptions } from "./aggregation/aggregateResponses.js";
29
29
  import { resolvePollOptions, stashPollOptionSources } from "./poll/resolvePollOptions.js";
@@ -41,14 +41,14 @@ import { resolveFormResultsRequest } from "./aggregation/resolveResultsRequest.j
41
41
  import { resolveConsentSourcesRequest } from "./consent/resolveConsentSourcesRequest.js";
42
42
  import { resolveDepartmentOptions } from "./email/departments.js";
43
43
  import { buildDefaultSettingsFields } from "./collections/settingsFields.js";
44
+ import { signFormContext, verifyFormContext } from "./context/formContext.js";
45
+ import { hasVotedCookie, votedCookieName } from "./submissions/votedCookie.js";
44
46
  import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "./spam/constants.js";
45
47
  import { evaluateCondition } from "./conditions/evaluate.js";
46
48
  import { textOfBody } from "./actions/body/textOfBody.js";
47
49
  import { resolvePublishedVersionRef } from "./consent/resolvePublishedVersionRef.js";
48
50
  import { captureConsent } from "./consent/captureConsent.js";
49
51
  import { captureFileRef } from "./uploads/captureFileRef.js";
50
- import { hasVotedCookie, votedCookieName } from "./submissions/votedCookie.js";
51
- import { signFormContext, verifyFormContext } from "./context/formContext.js";
52
52
  import { readUploadCollectionMimeTypes } from "./plugin/uploadsCollection.js";
53
53
  import { registerCollections } from "./plugin/registerCollections.js";
54
54
  import { registerTranslations } from "./plugin/registerTranslations.js";
@@ -77,6 +77,7 @@ import { hcaptchaProvider } from "./spam/providers/hcaptcha.js";
77
77
  import { recaptchaProvider } from "./spam/providers/recaptcha.js";
78
78
  import { turnstileProvider } from "./spam/providers/turnstile.js";
79
79
  import { createSubmission } from "./submissions/createSubmission.js";
80
+ import { resolveVotedSubmission } from "./submissions/resolveVotedSubmission.js";
80
81
  import { formatBytes } from "./uploads/formatBytes.js";
81
82
  import { definePlugin } from "payload";
82
83
  //#region src/index.ts
@@ -160,6 +161,6 @@ const formBuilder = definePlugin({
160
161
  }
161
162
  });
162
163
  //#endregion
163
- export { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, RESPONDENTS_VALUE, SIGNATURE_HEADER, VOTE_SHARDS, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
164
+ export { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, DEFAULT_PRESENTATION_NAME, POLL_CLOSE_TASK_SLUG, POLL_VOTES_SLUG, RESPONDENTS_VALUE, SIGNATURE_HEADER, VOTE_SHARDS, aggregateFieldResponses, aggregateFormResponses, aggregateFromVotes, aggregateRowForField, aggregateRowsForFields, applyConsentStatements, buildDefaultActionDefinitions, buildDefaultButtonFields, buildDefaultFieldDefinitions, buildDefaultOutcomeFields, buildDefaultSettingsFields, buildNextLabelField, buildPollCloseTask, buildPrevLabelField, buildRecallResolver, buildResolvedAtField, buildSubmitLabelField, buildWinningValuesField, calcExpressionOf, calcUsesSources, calcWeightKey, captureConsent, captureFileRef, computeCalcFields, consentSourcesField, countryField, createKvRateLimiter, createSubmission, defaultActionDefinitions, defaultBodyConverters, defaultFieldDefinitions, defaultFieldDefinitionsByType, defaultIdentify, defaultPresentationDescriptors, defaultValidationRules, defaultValidationRulesByType, defineAction, defineCalcFunction, defineCalcSource, defineCaptchaProvider, defineFormField, definePollOptionSource, definePollType, defineValidationRule, departmentsField, enqueuePollClose, escapeHtml, evaluateCalc, evaluateCondition, fieldHasOptions, fieldKey, fieldTargetParam, fileMimeTypeOptions, formBuilder, formatBytes, formatCalc, formatCalcValue, hasVotedCookie, hcaptchaProvider, interpolate, isPollClosed, localizedIf, manualStrategy, mostVotedStrategy, normalizeCalc, optionLabelsFor, pollTypesOf, recaptchaProvider, recountPollVotes, registerPollCloseTask, renderAllValues, renderAllValuesTable, resolveActions, resolveCalcContext, resolveConsentEntries, resolveConsentSourcesRequest, resolveConsentStatements, resolveDepartmentOptions, resolveEffectivePollOptions, resolveFileRef, resolveFormResultsRequest, resolvePollOptionSources, resolvePollOptions, resolvePollOutcome, resolvePollTypes, resolvePublishedVersionRef, resolveSpamConfig, resolveVotedSubmission, runPollClose, sanitizeUrl, serializeBody, shouldAutoResolvePoll, signFormContext, signPayload, sourceStrategy, stashPollTypes, stateField, textOfBody, toFormDocument, topBucketValues, turnstileProvider, valuesFromSearchParams, verifyFormContext, votedCookieName };
164
165
 
165
166
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport type { FormBuilderPluginOptions } from './options'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tdepartments,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type { ActionDefinition, ActionRunArgs, AnyActionDefinition } from './actions/defineAction'\nexport { defineAction } from './actions/defineAction'\nexport type { FromAddressesResolver, FromAddressOption } from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,kBAAkB,QAAQ,oBAAoB;EACpD,MAAM,UAAU,QAAQ,WAAW;EACnC,MAAM,cAAc,QAAQ,MAAM,WAAW,CAAC;EAC9C,MAAM,gBAAgB,QAAQ,MAAM,aAAa,CAAC;EAGlD,8BAA8B,aAAa;EAC3C,0BAA0B,WAAW;EAIrC,MAAM,cAAc;GACnB,SAAS,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;GACzC,WAAW,IAAI,IAAI,OAAO,KAAK,aAAa,CAAC;EAC9C;EAGA,MAAM,iBAAiB,OAAO,QAAQ,WAAW,EAAE,KAAK,CAAC,KAAK,aAAa;GAC1E;GACA,OAAO,OAAO;GACd,QAAQ,OAAO,OAAO,YAAY;GAClC,SAAS,OAAO,OAAO,mBAAmB;EAC3C,EAAE;EAIF,MAAM,kBACL,YAAY,QACT,KAAA,IACC,QAAQ,aAAa,8BAA8B,QAAQ,QAAQ,UAAU;EAClF,MAAM,iBAAiB,QAAQ,SAAS;EAgBxC,MAAM,WAAW,kBAXe,6BAC/B,iBACA,QAAQ,UAAU,QAClB,iBACA,aACA,cACD,EAAE,QACA,gBACC,YAAY,SAAS,WAAW,SAAS,YACzC,mBAAmB,KAAA,KAAa,WAAW,SAAS,UAEE,GAAG,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,gBAAgB,QAAQ,OAAO;EACrC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,iBAAiB,eACtB,8BAA8B;GAC7B,UAAU;GACV,QAAQ,QAAQ,UAAU,cAAc,QAAQ,UAAU;GAC1D;GACA;GACA,YAAY,QAAQ,OAAO;GAC3B,kBAAkB,QAAQ,OAAO;EAClC,CAAC,GACD,QAAQ,OACT;EAGA,6BAA6B,QAAQ,cAAc;EACnD,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,MAAM,qBAAqB,yBAAyB,QAAQ,MAAM,OAAO;EACzE,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,KAAK;EAM7D,OAAO,SAAS,uBAAuB,OAAO,QAAQ,kBAAkB;EACxE,OAAO,SAAS,eAAe,OAAO,QAAQ,gBAAgB;EAC9D,OAAO,SAAS,gBAAgB,OAAO,QAAQ,QAAQ;EACvD,IAAI,gBACH,OAAO,SAAS,oBAAoB,OAAO,QAAQ,cAAc;EAElE,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,iBAAiB,QAAQ,SAAS,YAAY;GAC9C,sBAAsB,QAAQ,SAAS;GACvC;GACA,UAAU,QAAQ;GAClB,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,QAAQ,QAAQ;GAChB;GACA;GACA,yBAAyB,QAAQ,2BAA2B;GAC5D;GACA,eAAe,QAAQ,SAAS;GAChC,aAAa,QAAQ,MAAM,gBAAgB;GAC3C;GACA;GACA,eAAe,QAAQ,MAAM;GAC7B,WAAW,QAAQ,MAAM,UAAU,QAAQ,QAAS,QAAQ,MAAM,SAAS,CAAC;GAC5E,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB;GACA;GACA,uBAAuB,QAAQ;GAC/B,WAAW,QAAQ;EACpB,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { assertNoActionBlockCollision } from './actions/assertNoBlockCollision'\nimport { buildDefaultActionDefinitions } from './actions/builtin'\nimport { resolveActions } from './actions/registry'\nimport { assertNoCalcFunctionCollision, assertValidCalcSourceKeys } from './calc/registry'\nimport { stashConsentSources } from './consent/resolveConsentEntries'\nimport { buildDefaultFieldDefinitions } from './fields/builtin'\nimport { resolveFieldTypes, stashFieldTypes } from './fields/registry'\nimport type { FormBuilderPluginOptions } from './options'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { readUploadCollectionMimeTypes } from './plugin/uploadsCollection'\nimport { resolvePollTypes, stashPollTypes } from './poll/pollTypeRegistry'\nimport { resolvePollOptionSources } from './poll/registry'\nimport { stashPollOptionSources } from './poll/resolvePollOptions'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules } from './validation/registry'\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst localizeContent = options.localizeContent !== false\n\t\tconst uploads = options.uploads ?? false\n\t\tconst calcSources = options.calc?.sources ?? {}\n\t\tconst calcFunctions = options.calc?.functions ?? {}\n\t\t// Fail fast: the evaluator resolves built-ins first, so a colliding custom function could never\n\t\t// run; a source key with a space would make the weight-map key (`calcWeightKey`) ambiguous.\n\t\tassertNoCalcFunctionCollision(calcFunctions)\n\t\tassertValidCalcSourceKeys(calcSources)\n\t\t// The allowed extension names, threaded into every normalizeCalc gate (the calculation field's\n\t\t// validate and the forms beforeValidate) so a stored expression can only ever reference a\n\t\t// registered source or function.\n\t\tconst calcAllowed = {\n\t\t\tsources: new Set(Object.keys(calcSources)),\n\t\t\tfunctions: new Set(Object.keys(calcFunctions)),\n\t\t}\n\t\t// Serializable source metadata for the builder UI: key, label, and implemented modes. The\n\t\t// resolver functions themselves never leave the server.\n\t\tconst calcSourceMeta = Object.entries(calcSources).map(([key, source]) => ({\n\t\t\tkey,\n\t\t\tlabel: source.label,\n\t\t\tscalar: typeof source.resolve === 'function',\n\t\t\tweights: typeof source.resolveWeights === 'function',\n\t\t}))\n\t\t// The file field's MIME picker is constrained to what the host upload collection accepts: the\n\t\t// explicit `uploads.mimeTypes` override, else the collection's own `upload.mimeTypes`. Read here,\n\t\t// before the field registry freezes below (attachUploadsCollection runs too late).\n\t\tconst uploadMimeTypes =\n\t\t\tuploads === false\n\t\t\t\t? undefined\n\t\t\t\t: (uploads.mimeTypes ?? readUploadCollectionMimeTypes(config, uploads.collection))\n\t\tconst consentSources = options.consent?.sources\n\t\t// A built-in with nowhere to point never enters the registry, so an author is never offered a\n\t\t// field that cannot work: file without an uploads collection has nowhere to store anything,\n\t\t// consent without sources has no statement to reference. An explicit `fields.file` /\n\t\t// `fields.consent` definition remains a developer choice.\n\t\tconst defaultFieldDefinitions = buildDefaultFieldDefinitions(\n\t\t\tlocalizeContent,\n\t\t\toptions.richText?.editor,\n\t\t\tuploadMimeTypes,\n\t\t\tcalcAllowed,\n\t\t\tcalcSourceMeta\n\t\t).filter(\n\t\t\t(definition) =>\n\t\t\t\t(uploads !== false || definition.type !== 'file') &&\n\t\t\t\t(consentSources !== undefined || definition.type !== 'consent')\n\t\t)\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst fromAddresses = options.email?.fromAddresses\n\t\tconst departments = options.email?.departments\n\t\tconst actionRegistry = resolveActions(\n\t\t\tbuildDefaultActionDefinitions({\n\t\t\t\tlocalize: localizeContent,\n\t\t\t\teditor: options.richText?.bodyEditor ?? options.richText?.editor,\n\t\t\t\tfromAddresses,\n\t\t\t\tdepartments,\n\t\t\t\trecipients: options.email?.recipients,\n\t\t\t\trecipientSources: options.email?.recipientSources,\n\t\t\t}),\n\t\t\toptions.actions\n\t\t)\n\t\t// Fail fast if a custom action type collides with a host block slug (Payload resolves blocks\n\t\t// globally by slug, which would silently merge the content block's fields into saved actions).\n\t\tassertNoActionBlockCollision(config, actionRegistry)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tconst pollSourceRegistry = resolvePollOptionSources(options.poll?.sources)\n\t\tconst pollTypeRegistry = resolvePollTypes(options.poll?.types)\n\t\t// Stashed on config.custom so the root-level resolvePollOptions, resolveEffectivePollOptions,\n\t\t// resolvePollOutcome, and resolveConsentStatements helpers can reach these through\n\t\t// `payload.config` at request time, without threading plugin state through the host's own\n\t\t// server code. The field-type registry rides along so resolveEffectivePollOptions can look up a\n\t\t// results field's definition (and its `resolveOptions`) from a bare `payload` instance.\n\t\tconfig.custom = stashPollOptionSources(config.custom, pollSourceRegistry)\n\t\tconfig.custom = stashPollTypes(config.custom, pollTypeRegistry)\n\t\tconfig.custom = stashFieldTypes(config.custom, registry)\n\t\tif (consentSources) {\n\t\t\tconfig.custom = stashConsentSources(config.custom, consentSources)\n\t\t}\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tcalcAllowed,\n\t\t\tcalcSources,\n\t\t\tcalcFunctions,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot: options.consent?.snapshot ?? 'both',\n\t\t\tconsentResolveOnRead: options.consent?.resolveOnRead,\n\t\t\tactionRegistry,\n\t\t\trichText: options.richText,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\tlocalizeContent,\n\t\t\tresultsAccess: options.results?.access,\n\t\t\tvotedCookie: options.poll?.votedCookie === true,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields: options.poll?.outcomeFields,\n\t\t\tpollVotes: options.poll?.votes === false ? false : (options.poll?.votes ?? {}),\n\t\t\tbuttons: options.buttons,\n\t\t\tsettings: options.settings,\n\t\t\tresponse: options.response,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships: options.redirectRelationships,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport type {\n\tBodyConverter,\n\tBodyConverterArgs,\n\tBodyRender,\n} from './actions/body/converters'\nexport { defaultBodyConverters, sanitizeUrl } from './actions/body/converters'\nexport { escapeHtml } from './actions/body/escapeHtml'\nexport type {\n\tBodyContext,\n\tRichTextBodyOption,\n\tSerializeBodyArgs,\n} from './actions/body/serializeBody'\nexport { serializeBody } from './actions/body/serializeBody'\nexport { textOfBody } from './actions/body/textOfBody'\nexport { renderAllValues, renderAllValuesTable } from './actions/body/wildcards'\nexport { buildDefaultActionDefinitions, defaultActionDefinitions } from './actions/builtin'\nexport type { ActionDefinition, ActionRunArgs, AnyActionDefinition } from './actions/defineAction'\nexport { defineAction } from './actions/defineAction'\nexport type { FromAddressesResolver, FromAddressOption } from './actions/fromAddresses'\nexport type {\n\tRecipientResolveArgs,\n\tRecipientSource,\n\tRecipientSourceRegistry,\n} from './actions/recipientSources'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tFormResultsAccess,\n\tFormResultsAccessArgs,\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport type { CalcResolved } from './calc/evaluate'\nexport { calcWeightKey, evaluateCalc } from './calc/evaluate'\nexport { formatCalc } from './calc/formatCalc'\nexport type { CalcDisplayConfig } from './calc/formatCalcValue'\nexport { formatCalcValue } from './calc/formatCalcValue'\nexport type { CalcAllowed } from './calc/normalizeCalc'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type {\n\tCalcFunction,\n\tCalcSource,\n\tCalcSourceResolveArgs,\n\tCalcWeightResolveArgs,\n} from './calc/registry'\nexport { defineCalcFunction, defineCalcSource } from './calc/registry'\nexport type { ResolveCalcContextArgs } from './calc/resolveCalcContext'\nexport { calcUsesSources, resolveCalcContext } from './calc/resolveCalcContext'\nexport type { CalcExpression } from './calc/types'\nexport type {\n\tButtonFieldsOverride,\n\tButtonsOption,\n\tDefaultButtonFields,\n} from './collections/buttonFields'\nexport {\n\tbuildDefaultButtonFields,\n\tbuildNextLabelField,\n\tbuildPrevLabelField,\n\tbuildSubmitLabelField,\n} from './collections/buttonFields'\nexport type {\n\tRedirectFieldsOverride,\n\tRedirectOption,\n\tResponseOption,\n} from './collections/redirectFields'\nexport type {\n\tDefaultSettingsFields,\n\tSettingsFieldsOverride,\n\tSettingsOption,\n} from './collections/settingsFields'\nexport { buildDefaultSettingsFields } from './collections/settingsFields'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { applyConsentStatements } from './consent/applyConsentStatements'\nexport type { ConsentProof, ConsentSnapshotMode } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type { ConsentSourcesFieldOptions } from './consent/consentSourcesField'\nexport { consentSourcesField } from './consent/consentSourcesField'\nexport type { ResolveConsentEntriesArgs } from './consent/resolveConsentEntries'\nexport { resolveConsentEntries } from './consent/resolveConsentEntries'\nexport type {\n\tConsentSourceOption,\n\tResolveConsentSourcesRequestArgs,\n\tResolveConsentSourcesRequestResult,\n} from './consent/resolveConsentSourcesRequest'\nexport { resolveConsentSourcesRequest } from './consent/resolveConsentSourcesRequest'\nexport type {\n\tConsentStatement,\n\tConsentStatements,\n\tResolveConsentStatementsArgs,\n} from './consent/resolveConsentStatements'\nexport { resolveConsentStatements } from './consent/resolveConsentStatements'\nexport type { ResolvePublishedVersionRefArgs } from './consent/resolvePublishedVersionRef'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport type {\n\tConsentSourceEntry,\n\tConsentSourcePage,\n\tConsentSourcesResolver,\n} from './consent/types'\nexport type { FormContextReference } from './context/formContext'\nexport { signFormContext, verifyFormContext } from './context/formContext'\nexport type {\n\tDepartmentEmailsResolver,\n\tDepartmentOption,\n\tResolveDepartmentOptionsArgs,\n\tResolveDepartmentsRequestArgs,\n\tResolveDepartmentsRequestResult,\n} from './email/departments'\nexport { resolveDepartmentOptions } from './email/departments'\nexport type { DepartmentsFieldOptions } from './email/departmentsField'\nexport { departmentsField } from './email/departmentsField'\nexport {\n\tbuildDefaultFieldDefinitions,\n\tdefaultFieldDefinitions,\n\tdefaultFieldDefinitionsByType,\n} from './fields/builtin'\nexport { countryField } from './fields/builtin/country'\nexport { fileMimeTypeOptions } from './fields/builtin/file'\nexport { stateField } from './fields/builtin/state'\nexport { defineFormField } from './fields/defineFormField'\nexport { fieldKey } from './fields/fieldKey'\nexport { localizedIf } from './fields/localizedIf'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n\tOmittableSharedField,\n\tResolveFieldOptionsArgs,\n} from './fields/types'\nexport { isPollClosed } from './form/pollState'\nexport type { ToFormDocumentOptions } from './form/toFormDocument'\nexport { toFormDocument } from './form/toFormDocument'\nexport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from './form/types'\nexport type {\n\tFormBuilderPluginOptions,\n\tFormBuilderPluginOptions as PluginOptions,\n} from './options'\nexport type { UploadsOption } from './plugin/uploadsCollection'\nexport type { PollCloseTaskInput } from './poll/closeJob'\nexport {\n\tbuildPollCloseTask,\n\tenqueuePollClose,\n\tPOLL_CLOSE_TASK_SLUG,\n\tregisterPollCloseTask,\n\trunPollClose,\n\tshouldAutoResolvePoll,\n} from './poll/closeJob'\nexport type {\n\tAnyPollOptionSource,\n\tPollOption,\n\tPollOptionResolveArgs,\n\tPollOptionSource,\n} from './poll/definePollOptionSource'\nexport { definePollOptionSource } from './poll/definePollOptionSource'\nexport type { PollOutcomeStrategy, PollOutcomeStrategyArgs } from './poll/definePollType'\nexport { definePollType } from './poll/definePollType'\nexport type { ResolveEffectivePollOptionsArgs } from './poll/effectivePollOptions'\nexport { resolveEffectivePollOptions } from './poll/effectivePollOptions'\nexport { mostVotedStrategy, topBucketValues } from './poll/mostVoted'\nexport type { DefaultOutcomeFields, OutcomeFieldsOverride } from './poll/outcomeFields'\nexport {\n\tbuildDefaultOutcomeFields,\n\tbuildResolvedAtField,\n\tbuildWinningValuesField,\n} from './poll/outcomeFields'\nexport type { PollTypeRegistry, PollTypesConfig } from './poll/pollTypeRegistry'\nexport {\n\tmanualStrategy,\n\tpollTypesOf,\n\tresolvePollTypes,\n\tsourceStrategy,\n\tstashPollTypes,\n} from './poll/pollTypeRegistry'\nexport type {\n\tPollOptionSourceOption,\n\tPollOptionSourceRegistry,\n\tPollOptionSourcesConfig,\n} from './poll/registry'\nexport { resolvePollOptionSources } from './poll/registry'\nexport type { ResolvePollOptionsArgs } from './poll/resolvePollOptions'\nexport { resolvePollOptions } from './poll/resolvePollOptions'\nexport type { ResolvePollOutcomeArgs } from './poll/resolvePollOutcome'\nexport { resolvePollOutcome } from './poll/resolvePollOutcome'\nexport { aggregateFromVotes } from './poll/votes/aggregateFromVotes'\nexport { recountPollVotes } from './poll/votes/recountPollVotes'\nexport { POLL_VOTES_SLUG, RESPONDENTS_VALUE, VOTE_SHARDS } from './poll/votes/votesCollection'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport type { HcaptchaProviderOptions } from './spam/providers/hcaptcha'\nexport { hcaptchaProvider } from './spam/providers/hcaptcha'\nexport type { RecaptchaProviderOptions } from './spam/providers/recaptcha'\nexport { recaptchaProvider } from './spam/providers/recaptcha'\nexport type { TurnstileProviderOptions } from './spam/providers/turnstile'\nexport { turnstileProvider } from './spam/providers/turnstile'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport type { CreatedSubmission, CreateSubmissionArgs } from './submissions/createSubmission'\nexport { createSubmission } from './submissions/createSubmission'\nexport type { VotedSubmission } from './submissions/resolveVotedSubmission'\nexport { resolveVotedSubmission } from './submissions/resolveVotedSubmission'\nexport { hasVotedCookie, votedCookieName } from './submissions/votedCookie'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { formatBytes } from './uploads/formatBytes'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defaultValidationRules, defaultValidationRulesByType } from './validation/builtin'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type { FieldTargetParamOptions } from './validation/fieldTargetParam'\nexport { fieldTargetParam } from './validation/fieldTargetParam'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n} from './validation/types'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,kBAAkB,QAAQ,oBAAoB;EACpD,MAAM,UAAU,QAAQ,WAAW;EACnC,MAAM,cAAc,QAAQ,MAAM,WAAW,CAAC;EAC9C,MAAM,gBAAgB,QAAQ,MAAM,aAAa,CAAC;EAGlD,8BAA8B,aAAa;EAC3C,0BAA0B,WAAW;EAIrC,MAAM,cAAc;GACnB,SAAS,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;GACzC,WAAW,IAAI,IAAI,OAAO,KAAK,aAAa,CAAC;EAC9C;EAGA,MAAM,iBAAiB,OAAO,QAAQ,WAAW,EAAE,KAAK,CAAC,KAAK,aAAa;GAC1E;GACA,OAAO,OAAO;GACd,QAAQ,OAAO,OAAO,YAAY;GAClC,SAAS,OAAO,OAAO,mBAAmB;EAC3C,EAAE;EAIF,MAAM,kBACL,YAAY,QACT,KAAA,IACC,QAAQ,aAAa,8BAA8B,QAAQ,QAAQ,UAAU;EAClF,MAAM,iBAAiB,QAAQ,SAAS;EAgBxC,MAAM,WAAW,kBAXe,6BAC/B,iBACA,QAAQ,UAAU,QAClB,iBACA,aACA,cACD,EAAE,QACA,gBACC,YAAY,SAAS,WAAW,SAAS,YACzC,mBAAmB,KAAA,KAAa,WAAW,SAAS,UAEE,GAAG,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,gBAAgB,QAAQ,OAAO;EACrC,MAAM,cAAc,QAAQ,OAAO;EACnC,MAAM,iBAAiB,eACtB,8BAA8B;GAC7B,UAAU;GACV,QAAQ,QAAQ,UAAU,cAAc,QAAQ,UAAU;GAC1D;GACA;GACA,YAAY,QAAQ,OAAO;GAC3B,kBAAkB,QAAQ,OAAO;EAClC,CAAC,GACD,QAAQ,OACT;EAGA,6BAA6B,QAAQ,cAAc;EACnD,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,MAAM,qBAAqB,yBAAyB,QAAQ,MAAM,OAAO;EACzE,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,KAAK;EAM7D,OAAO,SAAS,uBAAuB,OAAO,QAAQ,kBAAkB;EACxE,OAAO,SAAS,eAAe,OAAO,QAAQ,gBAAgB;EAC9D,OAAO,SAAS,gBAAgB,OAAO,QAAQ,QAAQ;EACvD,IAAI,gBACH,OAAO,SAAS,oBAAoB,OAAO,QAAQ,cAAc;EAElE,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,iBAAiB,QAAQ,SAAS,YAAY;GAC9C,sBAAsB,QAAQ,SAAS;GACvC;GACA,UAAU,QAAQ;GAClB,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,QAAQ,QAAQ;GAChB;GACA;GACA,yBAAyB,QAAQ,2BAA2B;GAC5D;GACA,eAAe,QAAQ,SAAS;GAChC,aAAa,QAAQ,MAAM,gBAAgB;GAC3C;GACA;GACA,eAAe,QAAQ,MAAM;GAC7B,WAAW,QAAQ,MAAM,UAAU,QAAQ,QAAS,QAAQ,MAAM,SAAS,CAAC;GAC5E,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,UAAU,QAAQ;GAClB;GACA;GACA,uBAAuB,QAAQ;GAC/B,WAAW,QAAQ;EACpB,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
@@ -26,11 +26,12 @@ const aggregateFromVotes = async (args) => {
26
26
  if (value === "") total += count;
27
27
  else counts.set(value, (counts.get(value) ?? 0) + count);
28
28
  }
29
+ total = Math.max(0, total);
29
30
  const optionValues = new Set(options.map((option) => option.value));
30
31
  const leftovers = [...counts.keys()].filter((value) => !optionValues.has(value)).sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0));
31
32
  const labelByValue = new Map(options.map((option) => [option.value, option.label]));
32
33
  const buckets = [...options.map((option) => option.value), ...leftovers].map((value) => {
33
- const count = counts.get(value) ?? 0;
34
+ const count = Math.max(0, counts.get(value) ?? 0);
34
35
  return {
35
36
  value,
36
37
  label: labelByValue.get(value) ?? value,
@@ -1 +1 @@
1
- {"version":3,"file":"aggregateFromVotes.js","names":[],"sources":["../../../src/poll/votes/aggregateFromVotes.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { FieldAggregation } from '../../aggregation/types'\nimport { POLL_VOTES_SLUG, RESPONDENTS_VALUE } from './votesCollection'\n\ntype VoteRow = { field?: unknown; value?: unknown; count?: unknown }\n\nconst round1 = (n: number): number => Math.round(n * 10) / 10\n\n/**\n * Poll results from the tally store: one find over the form's rows for the field (O(options),\n * no scan, no truncation), buckets zero-seeded from the effective options so order and labels\n * match the scan-based shape. Counts sum across shard rows (see VOTE_SHARDS). `total` comes\n * from the respondents rows, keeping the percentage denominator exact for multi-value answers.\n */\nexport const aggregateFromVotes = async (args: {\n\tpayload: Payload\n\tformId: number | string\n\tfield: string\n\tmeta?: { label?: string; fieldType?: string }\n\toptions: { value: string; label: string }[]\n\treq?: PayloadRequest\n}): Promise<FieldAggregation> => {\n\tconst { payload, formId, field, meta, options, req } = args\n\tconst { docs } = await payload.find({\n\t\tcollection: POLL_VOTES_SLUG,\n\t\twhere: { and: [{ form: { equals: String(formId) } }, { field: { equals: field } }] },\n\t\tlimit: 100_000,\n\t\tpagination: false,\n\t\tdepth: 0,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\n\tconst counts = new Map<string, number>()\n\tlet total = 0\n\tfor (const doc of docs as VoteRow[]) {\n\t\tconst value = doc.value == null ? RESPONDENTS_VALUE : String(doc.value)\n\t\tconst count = typeof doc.count === 'number' ? doc.count : 0\n\t\tif (value === RESPONDENTS_VALUE) {\n\t\t\ttotal += count\n\t\t} else {\n\t\t\tcounts.set(value, (counts.get(value) ?? 0) + count)\n\t\t}\n\t}\n\n\tconst optionValues = new Set(options.map((option) => option.value))\n\tconst leftovers = [...counts.keys()]\n\t\t.filter((value) => !optionValues.has(value))\n\t\t.sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0))\n\tconst labelByValue = new Map(options.map((option) => [option.value, option.label]))\n\n\tconst buckets = [...options.map((option) => option.value), ...leftovers].map((value) => {\n\t\tconst count = counts.get(value) ?? 0\n\t\treturn {\n\t\t\tvalue,\n\t\t\tlabel: labelByValue.get(value) ?? value,\n\t\t\tcount,\n\t\t\tpercentage: total > 0 ? round1((count / total) * 100) : 0,\n\t\t}\n\t})\n\n\treturn {\n\t\tfield,\n\t\tlabel: meta?.label,\n\t\tfieldType: meta?.fieldType,\n\t\ttotal,\n\t\tbuckets,\n\t\ttruncated: false,\n\t}\n}\n"],"mappings":";;AAMA,MAAM,UAAU,MAAsB,KAAK,MAAM,IAAI,EAAE,IAAI;;;;;;;AAQ3D,MAAa,qBAAqB,OAAO,SAOR;CAChC,MAAM,EAAE,SAAS,QAAQ,OAAO,MAAM,SAAS,QAAQ;CACvD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;EACnC,YAAY;EACZ,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,MAAM,EAAE,CAAC,EAAE;EACnF,OAAO;EACP,YAAY;EACZ,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC;CAED,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,MAAmB;EACpC,MAAM,QAAQ,IAAI,SAAS,OAAA,KAA2B,OAAO,IAAI,KAAK;EACtE,MAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;EAC1D,IAAI,UAAA,IACH,SAAS;OAET,OAAO,IAAI,QAAQ,OAAO,IAAI,KAAK,KAAK,KAAK,KAAK;CAEpD;CAEA,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAClE,MAAM,YAAY,CAAC,GAAG,OAAO,KAAK,CAAC,EACjC,QAAQ,UAAU,CAAC,aAAa,IAAI,KAAK,CAAC,EAC1C,MAAM,GAAG,OAAO,OAAO,IAAI,CAAC,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,EAAE;CAC5D,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC;CAElF,MAAM,UAAU,CAAC,GAAG,QAAQ,KAAK,WAAW,OAAO,KAAK,GAAG,GAAG,SAAS,EAAE,KAAK,UAAU;EACvF,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK;EACnC,OAAO;GACN;GACA,OAAO,aAAa,IAAI,KAAK,KAAK;GAClC;GACA,YAAY,QAAQ,IAAI,OAAQ,QAAQ,QAAS,GAAG,IAAI;EACzD;CACD,CAAC;CAED,OAAO;EACN;EACA,OAAO,MAAM;EACb,WAAW,MAAM;EACjB;EACA;EACA,WAAW;CACZ;AACD"}
1
+ {"version":3,"file":"aggregateFromVotes.js","names":[],"sources":["../../../src/poll/votes/aggregateFromVotes.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { FieldAggregation } from '../../aggregation/types'\nimport { POLL_VOTES_SLUG, RESPONDENTS_VALUE } from './votesCollection'\n\ntype VoteRow = { field?: unknown; value?: unknown; count?: unknown }\n\nconst round1 = (n: number): number => Math.round(n * 10) / 10\n\n/**\n * Poll results from the tally store: one find over the form's rows for the field (O(options),\n * no scan, no truncation), buckets zero-seeded from the effective options so order and labels\n * match the scan-based shape. Counts sum across shard rows (see VOTE_SHARDS). `total` comes\n * from the respondents rows, keeping the percentage denominator exact for multi-value answers.\n */\nexport const aggregateFromVotes = async (args: {\n\tpayload: Payload\n\tformId: number | string\n\tfield: string\n\tmeta?: { label?: string; fieldType?: string }\n\toptions: { value: string; label: string }[]\n\treq?: PayloadRequest\n}): Promise<FieldAggregation> => {\n\tconst { payload, formId, field, meta, options, req } = args\n\tconst { docs } = await payload.find({\n\t\tcollection: POLL_VOTES_SLUG,\n\t\twhere: { and: [{ form: { equals: String(formId) } }, { field: { equals: field } }] },\n\t\tlimit: 100_000,\n\t\tpagination: false,\n\t\tdepth: 0,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\n\tconst counts = new Map<string, number>()\n\tlet total = 0\n\tfor (const doc of docs as VoteRow[]) {\n\t\tconst value = doc.value == null ? RESPONDENTS_VALUE : String(doc.value)\n\t\tconst count = typeof doc.count === 'number' ? doc.count : 0\n\t\tif (value === RESPONDENTS_VALUE) {\n\t\t\ttotal += count\n\t\t} else {\n\t\t\tcounts.set(value, (counts.get(value) ?? 0) + count)\n\t\t}\n\t}\n\n\t// Clamp at read: a concurrent double-change can transiently double-decrement one value (see\n\t// the tally hook's drift note), and a negative bucket count must never surface to a reader.\n\ttotal = Math.max(0, total)\n\n\tconst optionValues = new Set(options.map((option) => option.value))\n\tconst leftovers = [...counts.keys()]\n\t\t.filter((value) => !optionValues.has(value))\n\t\t.sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0))\n\tconst labelByValue = new Map(options.map((option) => [option.value, option.label]))\n\n\tconst buckets = [...options.map((option) => option.value), ...leftovers].map((value) => {\n\t\tconst count = Math.max(0, counts.get(value) ?? 0)\n\t\treturn {\n\t\t\tvalue,\n\t\t\tlabel: labelByValue.get(value) ?? value,\n\t\t\tcount,\n\t\t\tpercentage: total > 0 ? round1((count / total) * 100) : 0,\n\t\t}\n\t})\n\n\treturn {\n\t\tfield,\n\t\tlabel: meta?.label,\n\t\tfieldType: meta?.fieldType,\n\t\ttotal,\n\t\tbuckets,\n\t\ttruncated: false,\n\t}\n}\n"],"mappings":";;AAMA,MAAM,UAAU,MAAsB,KAAK,MAAM,IAAI,EAAE,IAAI;;;;;;;AAQ3D,MAAa,qBAAqB,OAAO,SAOR;CAChC,MAAM,EAAE,SAAS,QAAQ,OAAO,MAAM,SAAS,QAAQ;CACvD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;EACnC,YAAY;EACZ,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,MAAM,EAAE,CAAC,EAAE;EACnF,OAAO;EACP,YAAY;EACZ,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC;CAED,MAAM,yBAAS,IAAI,IAAoB;CACvC,IAAI,QAAQ;CACZ,KAAK,MAAM,OAAO,MAAmB;EACpC,MAAM,QAAQ,IAAI,SAAS,OAAA,KAA2B,OAAO,IAAI,KAAK;EACtE,MAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;EAC1D,IAAI,UAAA,IACH,SAAS;OAET,OAAO,IAAI,QAAQ,OAAO,IAAI,KAAK,KAAK,KAAK,KAAK;CAEpD;CAIA,QAAQ,KAAK,IAAI,GAAG,KAAK;CAEzB,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAClE,MAAM,YAAY,CAAC,GAAG,OAAO,KAAK,CAAC,EACjC,QAAQ,UAAU,CAAC,aAAa,IAAI,KAAK,CAAC,EAC1C,MAAM,GAAG,OAAO,OAAO,IAAI,CAAC,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,EAAE;CAC5D,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,WAAW,CAAC,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC;CAElF,MAAM,UAAU,CAAC,GAAG,QAAQ,KAAK,WAAW,OAAO,KAAK,GAAG,GAAG,SAAS,EAAE,KAAK,UAAU;EACvF,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,IAAI,KAAK,KAAK,CAAC;EAChD,OAAO;GACN;GACA,OAAO,aAAa,IAAI,KAAK,KAAK;GAClC;GACA,YAAY,QAAQ,IAAI,OAAQ,QAAQ,QAAS,GAAG,IAAI;EACzD;CACD,CAAC;CAED,OAAO;EACN;EACA,OAAO,MAAM;EACb,WAAW,MAAM;EACjB;EACA;EACA,WAAW;CACZ;AACD"}
@@ -0,0 +1,16 @@
1
+ //#region src/poll/votes/answerValues.ts
2
+ /**
3
+ * A submission's answer(s) for one field as normalized non-empty strings: the shape both the vote
4
+ * tally and the voted-pick resolver count by (a multi-select answer contributes one entry per
5
+ * selected value).
6
+ */
7
+ const answerValues = (values, field) => {
8
+ if (!Array.isArray(values)) return [];
9
+ const entry = values.find((row) => row.field === field);
10
+ if (entry == null) return [];
11
+ return (Array.isArray(entry.value) ? entry.value : [entry.value]).filter((value) => value != null && value !== "").map((value) => String(value));
12
+ };
13
+ //#endregion
14
+ export { answerValues };
15
+
16
+ //# sourceMappingURL=answerValues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answerValues.js","names":[],"sources":["../../../src/poll/votes/answerValues.ts"],"sourcesContent":["import type { SubmissionValue } from '../../submissions/types'\n\n/**\n * A submission's answer(s) for one field as normalized non-empty strings: the shape both the vote\n * tally and the voted-pick resolver count by (a multi-select answer contributes one entry per\n * selected value).\n */\nexport const answerValues = (values: unknown, field: string): string[] => {\n\tif (!Array.isArray(values)) return []\n\tconst entry = (values as SubmissionValue[]).find((row) => row.field === field)\n\tif (entry == null) return []\n\tconst raw = Array.isArray(entry.value) ? entry.value : [entry.value]\n\treturn raw.filter((value) => value != null && value !== '').map((value) => String(value))\n}\n"],"mappings":";;;;;;AAOA,MAAa,gBAAgB,QAAiB,UAA4B;CACzE,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;CACpC,MAAM,QAAS,OAA6B,MAAM,QAAQ,IAAI,UAAU,KAAK;CAC7E,IAAI,SAAS,MAAM,OAAO,CAAC;CAE3B,QADY,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,MAAM,KAAK,GACxD,QAAQ,UAAU,SAAS,QAAQ,UAAU,EAAE,EAAE,KAAK,UAAU,OAAO,KAAK,CAAC;AACzF"}
@@ -2,27 +2,29 @@ import { pollConfigOf } from "../../form/pollState.js";
2
2
  import "./votesCollection.js";
3
3
  import { FORMS_SLUG } from "../../collections/forms.js";
4
4
  import { formIdOf } from "../../submissions/formIdOf.js";
5
+ import { answerValues } from "./answerValues.js";
5
6
  import { bumpPollVote } from "./bumpPollVote.js";
6
7
  //#region src/poll/votes/voteTallyHook.ts
7
8
  const isComplete = (doc) => doc.status == null || doc.status === "complete";
8
- const answerValues = (values, field) => {
9
- if (!Array.isArray(values)) return [];
10
- const entry = values.find((row) => row.field === field);
11
- if (entry == null) return [];
12
- return (Array.isArray(entry.value) ? entry.value : [entry.value]).filter((value) => value != null && value !== "").map((value) => String(value));
13
- };
14
9
  /** Narrow a request's loosely typed `transactionID` to the value `bumpPollVote` accepts. */
15
10
  const transactionIDOf = (transactionID) => typeof transactionID === "number" || typeof transactionID === "string" ? transactionID : void 0;
16
11
  /**
17
- * Append-only vote counting: a submission entering `complete` bumps one tally per answer value
18
- * for the poll results field, plus the shared respondents row. Votes are permanent by design:
19
- * no decrement on delete, edit, or prune, which is what lets a pruned submission's vote
20
- * survive. Bump failures propagate so the enclosing transaction rolls the submission back
12
+ * Vote counting: a submission entering `complete` bumps one tally per answer value for the poll
13
+ * results field, plus the shared respondents row. Votes are permanent by default: no decrement on
14
+ * delete, edit, or prune, which is what lets a pruned submission's vote survive. The one
15
+ * exception is an `allowChange` poll, where an update of an already-complete submission moves the
16
+ * tally with the changed answer (decrement removed values, increment added ones, respondents
17
+ * untouched); delete and prune stay permanent even then. Two concurrent changes of the same
18
+ * submission can each read the same previous answer and double-adjust; the client's double-submit
19
+ * guard makes that rare and `recountPollVotes` is the healer, matching `bumpPollVote`'s residual
20
+ * drift note. Bump failures propagate so the enclosing transaction rolls the submission back
21
21
  * rather than silently undercounting; registered before the swallow-all dispatch hook.
22
22
  */
23
23
  const makeVoteTallyHook = () => async ({ doc, previousDoc, operation, req }) => {
24
24
  if (operation !== "create" && operation !== "update") return doc;
25
- if (!(operation === "create" && isComplete(doc) || operation === "update" && isComplete(doc) && !isComplete(previousDoc ?? {}))) return doc;
25
+ const entering = operation === "create" && isComplete(doc) || operation === "update" && isComplete(doc) && !isComplete(previousDoc ?? {});
26
+ const changing = operation === "update" && isComplete(doc) && isComplete(previousDoc ?? {}) && !entering;
27
+ if (!entering && !changing) return doc;
26
28
  const formId = formIdOf(doc.form);
27
29
  if (formId == null) return doc;
28
30
  const form = await req.payload.findByID({
@@ -35,13 +37,29 @@ const makeVoteTallyHook = () => async ({ doc, previousDoc, operation, req }) =>
35
37
  const poll = form?.pollEnabled === true ? pollConfigOf(form.poll) : void 0;
36
38
  const resultsField = typeof poll?.resultsField === "string" && poll.resultsField.length > 0 ? poll.resultsField : void 0;
37
39
  if (!resultsField) return doc;
38
- const votes = answerValues(doc.values, resultsField);
39
- if (votes.length === 0) return doc;
40
40
  const transactionID = transactionIDOf(req.transactionID);
41
41
  const base = {
42
42
  form: String(formId),
43
43
  field: resultsField
44
44
  };
45
+ if (changing) {
46
+ if (poll?.allowChange !== true) return doc;
47
+ const previous = answerValues(previousDoc?.values, resultsField);
48
+ const next = answerValues(doc.values, resultsField);
49
+ const removed = previous.filter((value) => !next.includes(value));
50
+ const added = next.filter((value) => !previous.includes(value));
51
+ for (const value of removed) await bumpPollVote(req.payload, {
52
+ ...base,
53
+ value
54
+ }, -1, transactionID);
55
+ for (const value of added) await bumpPollVote(req.payload, {
56
+ ...base,
57
+ value
58
+ }, 1, transactionID);
59
+ return doc;
60
+ }
61
+ const votes = answerValues(doc.values, resultsField);
62
+ if (votes.length === 0) return doc;
45
63
  for (const value of votes) await bumpPollVote(req.payload, {
46
64
  ...base,
47
65
  value
@@ -1 +1 @@
1
- {"version":3,"file":"voteTallyHook.js","names":[],"sources":["../../../src/poll/votes/voteTallyHook.ts"],"sourcesContent":["import type { CollectionAfterChangeHook } from 'payload'\nimport { FORMS_SLUG } from '../../collections/forms'\nimport { pollConfigOf } from '../../form/pollState'\nimport { formIdOf } from '../../submissions/formIdOf'\nimport type { SubmissionValue } from '../../submissions/types'\nimport { bumpPollVote } from './bumpPollVote'\nimport { RESPONDENTS_VALUE } from './votesCollection'\n\nconst isComplete = (doc: { status?: unknown }): boolean =>\n\tdoc.status == null || doc.status === 'complete'\n\nconst answerValues = (values: unknown, field: string): string[] => {\n\tif (!Array.isArray(values)) return []\n\tconst entry = (values as SubmissionValue[]).find((row) => row.field === field)\n\tif (entry == null) return []\n\tconst raw = Array.isArray(entry.value) ? entry.value : [entry.value]\n\treturn raw.filter((value) => value != null && value !== '').map((value) => String(value))\n}\n\n/** Narrow a request's loosely typed `transactionID` to the value `bumpPollVote` accepts. */\nexport const transactionIDOf = (transactionID: unknown): number | string | undefined =>\n\ttypeof transactionID === 'number' || typeof transactionID === 'string' ? transactionID : undefined\n\n/**\n * Append-only vote counting: a submission entering `complete` bumps one tally per answer value\n * for the poll results field, plus the shared respondents row. Votes are permanent by design:\n * no decrement on delete, edit, or prune, which is what lets a pruned submission's vote\n * survive. Bump failures propagate so the enclosing transaction rolls the submission back\n * rather than silently undercounting; registered before the swallow-all dispatch hook.\n */\nexport const makeVoteTallyHook =\n\t(): CollectionAfterChangeHook =>\n\tasync ({ doc, previousDoc, operation, req }) => {\n\t\tif (operation !== 'create' && operation !== 'update') return doc\n\t\tconst entering =\n\t\t\t(operation === 'create' && isComplete(doc)) ||\n\t\t\t(operation === 'update' && isComplete(doc) && !isComplete(previousDoc ?? {}))\n\t\tif (!entering) return doc\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) return doc\n\t\tconst form = await req.payload\n\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t.catch(() => null)\n\t\tconst poll = form?.pollEnabled === true ? pollConfigOf(form.poll) : undefined\n\t\tconst resultsField =\n\t\t\ttypeof poll?.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tif (!resultsField) return doc\n\t\tconst votes = answerValues(doc.values, resultsField)\n\t\tif (votes.length === 0) return doc\n\t\tconst transactionID = transactionIDOf(req.transactionID)\n\t\tconst base = { form: String(formId), field: resultsField }\n\t\tfor (const value of votes) {\n\t\t\tawait bumpPollVote(req.payload, { ...base, value }, 1, transactionID)\n\t\t}\n\t\tawait bumpPollVote(req.payload, { ...base, value: RESPONDENTS_VALUE }, 1, transactionID)\n\t\treturn doc\n\t}\n"],"mappings":";;;;;;AAQA,MAAM,cAAc,QACnB,IAAI,UAAU,QAAQ,IAAI,WAAW;AAEtC,MAAM,gBAAgB,QAAiB,UAA4B;CAClE,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC;CACpC,MAAM,QAAS,OAA6B,MAAM,QAAQ,IAAI,UAAU,KAAK;CAC7E,IAAI,SAAS,MAAM,OAAO,CAAC;CAE3B,QADY,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,MAAM,KAAK,GACxD,QAAQ,UAAU,SAAS,QAAQ,UAAU,EAAE,EAAE,KAAK,UAAU,OAAO,KAAK,CAAC;AACzF;;AAGA,MAAa,mBAAmB,kBAC/B,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,WAAW,gBAAgB,KAAA;;;;;;;;AAS1F,MAAa,0BAEZ,OAAO,EAAE,KAAK,aAAa,WAAW,UAAU;CAC/C,IAAI,cAAc,YAAY,cAAc,UAAU,OAAO;CAI7D,IAAI,EAFF,cAAc,YAAY,WAAW,GAAG,KACxC,cAAc,YAAY,WAAW,GAAG,KAAK,CAAC,WAAW,eAAe,CAAC,CAAC,IAC7D,OAAO;CACtB,MAAM,SAAS,SAAS,IAAI,IAAI;CAChC,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,OAAO,MAAM,IAAI,QACrB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,MAAM,OAAO,MAAM,gBAAgB,OAAO,aAAa,KAAK,IAAI,IAAI,KAAA;CACpE,MAAM,eACL,OAAO,MAAM,iBAAiB,YAAY,KAAK,aAAa,SAAS,IAClE,KAAK,eACL,KAAA;CACJ,IAAI,CAAC,cAAc,OAAO;CAC1B,MAAM,QAAQ,aAAa,IAAI,QAAQ,YAAY;CACnD,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,gBAAgB,gBAAgB,IAAI,aAAa;CACvD,MAAM,OAAO;EAAE,MAAM,OAAO,MAAM;EAAG,OAAO;CAAa;CACzD,KAAK,MAAM,SAAS,OACnB,MAAM,aAAa,IAAI,SAAS;EAAE,GAAG;EAAM;CAAM,GAAG,GAAG,aAAa;CAErE,MAAM,aAAa,IAAI,SAAS;EAAE,GAAG;EAAM,OAAA;CAAyB,GAAG,GAAG,aAAa;CACvF,OAAO;AACR"}
1
+ {"version":3,"file":"voteTallyHook.js","names":[],"sources":["../../../src/poll/votes/voteTallyHook.ts"],"sourcesContent":["import type { CollectionAfterChangeHook } from 'payload'\nimport { FORMS_SLUG } from '../../collections/forms'\nimport { pollConfigOf } from '../../form/pollState'\nimport { formIdOf } from '../../submissions/formIdOf'\nimport { answerValues } from './answerValues'\nimport { bumpPollVote } from './bumpPollVote'\nimport { RESPONDENTS_VALUE } from './votesCollection'\n\nconst isComplete = (doc: { status?: unknown }): boolean =>\n\tdoc.status == null || doc.status === 'complete'\n\n/** Narrow a request's loosely typed `transactionID` to the value `bumpPollVote` accepts. */\nexport const transactionIDOf = (transactionID: unknown): number | string | undefined =>\n\ttypeof transactionID === 'number' || typeof transactionID === 'string' ? transactionID : undefined\n\n/**\n * Vote counting: a submission entering `complete` bumps one tally per answer value for the poll\n * results field, plus the shared respondents row. Votes are permanent by default: no decrement on\n * delete, edit, or prune, which is what lets a pruned submission's vote survive. The one\n * exception is an `allowChange` poll, where an update of an already-complete submission moves the\n * tally with the changed answer (decrement removed values, increment added ones, respondents\n * untouched); delete and prune stay permanent even then. Two concurrent changes of the same\n * submission can each read the same previous answer and double-adjust; the client's double-submit\n * guard makes that rare and `recountPollVotes` is the healer, matching `bumpPollVote`'s residual\n * drift note. Bump failures propagate so the enclosing transaction rolls the submission back\n * rather than silently undercounting; registered before the swallow-all dispatch hook.\n */\nexport const makeVoteTallyHook =\n\t(): CollectionAfterChangeHook =>\n\tasync ({ doc, previousDoc, operation, req }) => {\n\t\tif (operation !== 'create' && operation !== 'update') return doc\n\t\tconst entering =\n\t\t\t(operation === 'create' && isComplete(doc)) ||\n\t\t\t(operation === 'update' && isComplete(doc) && !isComplete(previousDoc ?? {}))\n\t\tconst changing =\n\t\t\toperation === 'update' && isComplete(doc) && isComplete(previousDoc ?? {}) && !entering\n\t\tif (!entering && !changing) return doc\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) return doc\n\t\tconst form = await req.payload\n\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t.catch(() => null)\n\t\tconst poll = form?.pollEnabled === true ? pollConfigOf(form.poll) : undefined\n\t\tconst resultsField =\n\t\t\ttypeof poll?.resultsField === 'string' && poll.resultsField.length > 0\n\t\t\t\t? poll.resultsField\n\t\t\t\t: undefined\n\t\tif (!resultsField) return doc\n\t\tconst transactionID = transactionIDOf(req.transactionID)\n\t\tconst base = { form: String(formId), field: resultsField }\n\t\tif (changing) {\n\t\t\tif (poll?.allowChange !== true) return doc\n\t\t\tconst previous = answerValues(previousDoc?.values, resultsField)\n\t\t\tconst next = answerValues(doc.values, resultsField)\n\t\t\tconst removed = previous.filter((value) => !next.includes(value))\n\t\t\tconst added = next.filter((value) => !previous.includes(value))\n\t\t\tfor (const value of removed) {\n\t\t\t\tawait bumpPollVote(req.payload, { ...base, value }, -1, transactionID)\n\t\t\t}\n\t\t\tfor (const value of added) {\n\t\t\t\tawait bumpPollVote(req.payload, { ...base, value }, 1, transactionID)\n\t\t\t}\n\t\t\treturn doc\n\t\t}\n\t\tconst votes = answerValues(doc.values, resultsField)\n\t\tif (votes.length === 0) return doc\n\t\tfor (const value of votes) {\n\t\t\tawait bumpPollVote(req.payload, { ...base, value }, 1, transactionID)\n\t\t}\n\t\tawait bumpPollVote(req.payload, { ...base, value: RESPONDENTS_VALUE }, 1, transactionID)\n\t\treturn doc\n\t}\n"],"mappings":";;;;;;;AAQA,MAAM,cAAc,QACnB,IAAI,UAAU,QAAQ,IAAI,WAAW;;AAGtC,MAAa,mBAAmB,kBAC/B,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,WAAW,gBAAgB,KAAA;;;;;;;;;;;;;AAc1F,MAAa,0BAEZ,OAAO,EAAE,KAAK,aAAa,WAAW,UAAU;CAC/C,IAAI,cAAc,YAAY,cAAc,UAAU,OAAO;CAC7D,MAAM,WACJ,cAAc,YAAY,WAAW,GAAG,KACxC,cAAc,YAAY,WAAW,GAAG,KAAK,CAAC,WAAW,eAAe,CAAC,CAAC;CAC5E,MAAM,WACL,cAAc,YAAY,WAAW,GAAG,KAAK,WAAW,eAAe,CAAC,CAAC,KAAK,CAAC;CAChF,IAAI,CAAC,YAAY,CAAC,UAAU,OAAO;CACnC,MAAM,SAAS,SAAS,IAAI,IAAI;CAChC,IAAI,UAAU,MAAM,OAAO;CAC3B,MAAM,OAAO,MAAM,IAAI,QACrB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,MAAM,OAAO,MAAM,gBAAgB,OAAO,aAAa,KAAK,IAAI,IAAI,KAAA;CACpE,MAAM,eACL,OAAO,MAAM,iBAAiB,YAAY,KAAK,aAAa,SAAS,IAClE,KAAK,eACL,KAAA;CACJ,IAAI,CAAC,cAAc,OAAO;CAC1B,MAAM,gBAAgB,gBAAgB,IAAI,aAAa;CACvD,MAAM,OAAO;EAAE,MAAM,OAAO,MAAM;EAAG,OAAO;CAAa;CACzD,IAAI,UAAU;EACb,IAAI,MAAM,gBAAgB,MAAM,OAAO;EACvC,MAAM,WAAW,aAAa,aAAa,QAAQ,YAAY;EAC/D,MAAM,OAAO,aAAa,IAAI,QAAQ,YAAY;EAClD,MAAM,UAAU,SAAS,QAAQ,UAAU,CAAC,KAAK,SAAS,KAAK,CAAC;EAChE,MAAM,QAAQ,KAAK,QAAQ,UAAU,CAAC,SAAS,SAAS,KAAK,CAAC;EAC9D,KAAK,MAAM,SAAS,SACnB,MAAM,aAAa,IAAI,SAAS;GAAE,GAAG;GAAM;EAAM,GAAG,IAAI,aAAa;EAEtE,KAAK,MAAM,SAAS,OACnB,MAAM,aAAa,IAAI,SAAS;GAAE,GAAG;GAAM;EAAM,GAAG,GAAG,aAAa;EAErE,OAAO;CACR;CACA,MAAM,QAAQ,aAAa,IAAI,QAAQ,YAAY;CACnD,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,KAAK,MAAM,SAAS,OACnB,MAAM,aAAa,IAAI,SAAS;EAAE,GAAG;EAAM;CAAM,GAAG,GAAG,aAAa;CAErE,MAAM,aAAa,IAAI,SAAS;EAAE,GAAG;EAAM,OAAA;CAAyB,GAAG,GAAG,aAAa;CACvF,OAAO;AACR"}
@@ -1,6 +1,8 @@
1
+ import { SubmissionValue } from "../submissions/types.js";
1
2
  import { AnyFormFieldDefinition } from "../fields/types.js";
2
3
  import { BodyConverter } from "../actions/body/converters.js";
3
4
  import { FormButtonSettings, FormDocument, FormPollSettings, FormResponseSettings } from "../form/types.js";
5
+ import { FormAdapters } from "./adapters.js";
4
6
  import { RendererTranslate } from "./contract.js";
5
7
  import { FormEventSink } from "../events/types.js";
6
8
  import { AnyValidationRuleDefinition } from "../validation/types.js";
@@ -25,7 +27,8 @@ type FormSuccessResponse = {
25
27
  };
26
28
  /** The second argument to `onSuccess`: the resolved success response (an object, so it can grow). */
27
29
  type FormSuccessResult = {
28
- response?: FormSuccessResponse;
30
+ response?: FormSuccessResponse; /** The submitted answers (reserved keys like the honeypot and captcha token excluded), e.g. for `<Poll>` to track the voter's pick. */
31
+ values?: SubmissionValue[];
29
32
  };
30
33
  type FormProps = {
31
34
  form: FormDocument;
@@ -96,6 +99,12 @@ type FormProps = {
96
99
  submitButtonClassName?: string; /** CSS class forwarded to the default "Next" `<button>`. Ignored when `renderNext` is provided. */
97
100
  nextButtonClassName?: string; /** CSS class forwarded to the default "Back" `<button>`. Ignored when `renderBack` is provided. */
98
101
  backButtonClassName?: string;
102
+ /**
103
+ * Host-owned effects (navigation, vote persistence, script loading). Each member defaults to
104
+ * the built-in DOM behavior; see {@link FormAdapters}. Pass a stable reference (module scope or
105
+ * `useMemo`), matching the other injectable props.
106
+ */
107
+ adapters?: FormAdapters;
99
108
  };
100
109
  /** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
101
110
  declare const Form: ({
@@ -135,7 +144,8 @@ declare const Form: ({
135
144
  renderBack,
136
145
  submitButtonClassName,
137
146
  nextButtonClassName,
138
- backButtonClassName
147
+ backButtonClassName,
148
+ adapters
139
149
  }: FormProps) => import("react/jsx-runtime").JSX.Element;
140
150
  //#endregion
141
151
  export { Form, FormProps, FormSuccessResponse, FormSuccessResult };
@@ -14,6 +14,7 @@ import { buildRecallResolver, descriptorsFor } from "../recall/resolver.js";
14
14
  import { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from "../flow/engine.js";
15
15
  import { cn } from "./cn.js";
16
16
  import { makeTranslate } from "../translations/makeTranslate.js";
17
+ import { defaultNavigate } from "./adapters.js";
17
18
  import { emitFormEvent } from "./events.js";
18
19
  import { FormContext } from "./FormContext.js";
19
20
  import { FormControls } from "./FormControls.js";
@@ -60,7 +61,7 @@ const focusFirstIn = (root, selector) => {
60
61
  /** A stored button label counts only when it is a non-empty string; anything else falls through. */
61
62
  const storedLabel = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
62
63
  /** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
63
- const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, converters, successBehavior = "replace", calcFunctions, events, t, locale = "en", layout, submitLabel, nextLabel, prevLabel, closeLabel, successMessage, presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, context, children, header, className, renderSubmit, renderNext, renderBack, submitButtonClassName, nextButtonClassName, backButtonClassName }) => {
64
+ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, converters, successBehavior = "replace", calcFunctions, events, t, locale = "en", layout, submitLabel, nextLabel, prevLabel, closeLabel, successMessage, presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, context, children, header, className, renderSubmit, renderNext, renderBack, submitButtonClassName, nextButtonClassName, backButtonClassName, adapters }) => {
64
65
  const honeypotName = honeypot === false ? null : honeypot?.name ?? "website";
65
66
  const honeypotRef = useRef(null);
66
67
  const registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes]);
@@ -421,6 +422,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
421
422
  }
422
423
  rawDispatch({ type: "SUBMIT_START" });
423
424
  const values = answeredValues();
425
+ const answers = [...values];
424
426
  if (honeypotName) {
425
427
  const decoy = honeypotRef.current?.value ?? "";
426
428
  if (decoy !== "") values.push({
@@ -451,7 +453,10 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
451
453
  type: "submission.created",
452
454
  submissionId: result.submissionId
453
455
  });
454
- onSuccess?.(result.submissionId, { response: resolveSuccessResponse() });
456
+ onSuccess?.(result.submissionId, {
457
+ response: resolveSuccessResponse(),
458
+ values: answers
459
+ });
455
460
  if (successBehavior === "reset") {
456
461
  rawDispatch({
457
462
  type: "RESET",
@@ -470,7 +475,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
470
475
  if (activePresentation.dismissOnSuccess) handleClose();
471
476
  }
472
477
  const redirectUrl = form.response?.type === "redirect" ? form.response.redirect?.url : void 0;
473
- if (typeof redirectUrl === "string" && redirectUrl.length > 0 && typeof window !== "undefined") window.location.assign(redirectUrl);
478
+ if (typeof redirectUrl === "string" && redirectUrl.length > 0) (adapters?.navigate ?? defaultNavigate)(redirectUrl, { replace: false });
474
479
  } else {
475
480
  if (result.fieldErrors) rawDispatch({
476
481
  type: "SET_ALL_ISSUES",