@10x-media/form-builder 0.1.0-beta.6 → 0.1.0-beta.8

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 (174) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/actions/body/serializeBody.d.ts +3 -1
  3. package/dist/actions/body/serializeBody.js.map +1 -1
  4. package/dist/actions/buildActionBlocks.js +12 -9
  5. package/dist/actions/buildActionBlocks.js.map +1 -1
  6. package/dist/actions/builtin/confirmation.js +13 -68
  7. package/dist/actions/builtin/confirmation.js.map +1 -1
  8. package/dist/actions/builtin/emailAction.d.ts +17 -0
  9. package/dist/actions/builtin/emailAction.js +83 -0
  10. package/dist/actions/builtin/emailAction.js.map +1 -0
  11. package/dist/actions/builtin/emailTeam.js +14 -74
  12. package/dist/actions/builtin/emailTeam.js.map +1 -1
  13. package/dist/actions/builtin/index.d.ts +9 -12
  14. package/dist/actions/builtin/index.js +11 -11
  15. package/dist/actions/builtin/index.js.map +1 -1
  16. package/dist/actions/defineAction.d.ts +2 -2
  17. package/dist/actions/defineAction.js.map +1 -1
  18. package/dist/actions/emailRecipients.js +75 -31
  19. package/dist/actions/emailRecipients.js.map +1 -1
  20. package/dist/actions/registry.d.ts +2 -1
  21. package/dist/actions/registry.js +2 -6
  22. package/dist/actions/registry.js.map +1 -1
  23. package/dist/actions/task.js +5 -3
  24. package/dist/actions/task.js.map +1 -1
  25. package/dist/calc/evaluate.js +1 -2
  26. package/dist/calc/evaluate.js.map +1 -1
  27. package/dist/calc/normalizeCalc.js +1 -2
  28. package/dist/calc/normalizeCalc.js.map +1 -1
  29. package/dist/calc/types.js.map +1 -1
  30. package/dist/collections/buttonFields.js +1 -1
  31. package/dist/collections/formSubmissions.js +2 -1
  32. package/dist/collections/formSubmissions.js.map +1 -1
  33. package/dist/collections/forms.js +32 -100
  34. package/dist/collections/forms.js.map +1 -1
  35. package/dist/collections/formsEndpoints.js +101 -0
  36. package/dist/collections/formsEndpoints.js.map +1 -0
  37. package/dist/conditions/evaluate.js +1 -1
  38. package/dist/conditions/evaluate.js.map +1 -1
  39. package/dist/conditions/fieldTypes.d.ts +3 -1
  40. package/dist/conditions/fieldTypes.js +4 -1
  41. package/dist/conditions/fieldTypes.js.map +1 -1
  42. package/dist/conditions/normalizeConditions.js +18 -5
  43. package/dist/conditions/normalizeConditions.js.map +1 -1
  44. package/dist/consent/captureConsent.d.ts +7 -0
  45. package/dist/consent/captureConsent.js +7 -0
  46. package/dist/consent/captureConsent.js.map +1 -1
  47. package/dist/consent/consentSourcesField.d.ts +4 -0
  48. package/dist/consent/consentSourcesField.js +5 -1
  49. package/dist/consent/consentSourcesField.js.map +1 -1
  50. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  51. package/dist/consent/resolveConsentEntries.js +1 -4
  52. package/dist/consent/resolveConsentEntries.js.map +1 -1
  53. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  54. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  55. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  56. package/dist/consent/resolveConsentStatements.js.map +1 -1
  57. package/dist/consent/types.d.ts +9 -1
  58. package/dist/email/departmentsField.js +1 -1
  59. package/dist/exports/react.d.ts +5 -4
  60. package/dist/exports/react.js +3 -2
  61. package/dist/exports/rsc.js +1 -1
  62. package/dist/exports/types.d.ts +1 -1
  63. package/dist/fields/builtin/email.js +1 -1
  64. package/dist/fields/builtin/email.js.map +1 -1
  65. package/dist/fields/builtin/message.js +1 -1
  66. package/dist/fields/builtin/repeater.js +1 -1
  67. package/dist/fields/builtin/select.js +1 -1
  68. package/dist/fields/registry.d.ts +2 -1
  69. package/dist/fields/registry.js +2 -6
  70. package/dist/fields/registry.js.map +1 -1
  71. package/dist/fields/sharedConfig.js +1 -1
  72. package/dist/flow/normalizeFlow.js +7 -3
  73. package/dist/flow/normalizeFlow.js.map +1 -1
  74. package/dist/index.d.ts +4 -201
  75. package/dist/index.js +11 -4
  76. package/dist/index.js.map +1 -1
  77. package/dist/options.d.ts +229 -0
  78. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  79. package/dist/plugin/applyRegistryConfig.js +20 -0
  80. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  81. package/dist/plugin/registerCollections.js +2 -1
  82. package/dist/plugin/registerCollections.js.map +1 -1
  83. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  84. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  85. package/dist/poll/definePollOptionSource.d.ts +2 -2
  86. package/dist/poll/definePollOptionSource.js.map +1 -1
  87. package/dist/poll/pollTypeRegistry.js +2 -4
  88. package/dist/poll/pollTypeRegistry.js.map +1 -1
  89. package/dist/poll/registry.d.ts +2 -1
  90. package/dist/poll/registry.js +2 -6
  91. package/dist/poll/registry.js.map +1 -1
  92. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  93. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  94. package/dist/prefill/valuesFromSearchParams.js +2 -7
  95. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  96. package/dist/react/Form.d.ts +39 -3
  97. package/dist/react/Form.js +148 -164
  98. package/dist/react/Form.js.map +1 -1
  99. package/dist/react/FormContext.d.ts +7 -1
  100. package/dist/react/FormContext.js.map +1 -1
  101. package/dist/react/FormFields.d.ts +18 -0
  102. package/dist/react/FormFields.js +114 -0
  103. package/dist/react/FormFields.js.map +1 -0
  104. package/dist/react/FormLayout.d.ts +4 -2
  105. package/dist/react/FormLayout.js +3 -2
  106. package/dist/react/FormLayout.js.map +1 -1
  107. package/dist/react/Poll.js +15 -5
  108. package/dist/react/Poll.js.map +1 -1
  109. package/dist/react/presentation/DialogSurface.js +1 -4
  110. package/dist/react/presentation/DialogSurface.js.map +1 -1
  111. package/dist/react/renderers/calculation.js +1 -3
  112. package/dist/react/renderers/calculation.js.map +1 -1
  113. package/dist/react/renderers/checkbox.js +1 -3
  114. package/dist/react/renderers/checkbox.js.map +1 -1
  115. package/dist/react/renderers/consent.js +2 -3
  116. package/dist/react/renderers/consent.js.map +1 -1
  117. package/dist/react/renderers/date.js +1 -3
  118. package/dist/react/renderers/date.js.map +1 -1
  119. package/dist/react/renderers/email.js +1 -3
  120. package/dist/react/renderers/email.js.map +1 -1
  121. package/dist/react/renderers/file.js +6 -7
  122. package/dist/react/renderers/file.js.map +1 -1
  123. package/dist/react/renderers/message.js +8 -2
  124. package/dist/react/renderers/message.js.map +1 -1
  125. package/dist/react/renderers/number.js +1 -3
  126. package/dist/react/renderers/number.js.map +1 -1
  127. package/dist/react/renderers/region.js +1 -3
  128. package/dist/react/renderers/region.js.map +1 -1
  129. package/dist/react/renderers/repeater.js +20 -4
  130. package/dist/react/renderers/repeater.js.map +1 -1
  131. package/dist/react/renderers/select.js +1 -3
  132. package/dist/react/renderers/select.js.map +1 -1
  133. package/dist/react/renderers/text.js +1 -3
  134. package/dist/react/renderers/text.js.map +1 -1
  135. package/dist/react/renderers/textarea.js +1 -3
  136. package/dist/react/renderers/textarea.js.map +1 -1
  137. package/dist/react/state.d.ts +3 -0
  138. package/dist/react/state.js +1 -0
  139. package/dist/react/state.js.map +1 -1
  140. package/dist/spam/constants.js +7 -1
  141. package/dist/spam/constants.js.map +1 -1
  142. package/dist/spam/resolveSpam.js +2 -1
  143. package/dist/spam/resolveSpam.js.map +1 -1
  144. package/dist/spam/spamGuard.js +22 -6
  145. package/dist/spam/spamGuard.js.map +1 -1
  146. package/dist/spam/types.d.ts +17 -2
  147. package/dist/submissions/coerceBoolean.js +14 -0
  148. package/dist/submissions/coerceBoolean.js.map +1 -0
  149. package/dist/submissions/runSubmission.js +7 -7
  150. package/dist/submissions/runSubmission.js.map +1 -1
  151. package/dist/submissions/validateSubmission.js +3 -2
  152. package/dist/submissions/validateSubmission.js.map +1 -1
  153. package/dist/translations/de.js +10 -0
  154. package/dist/translations/de.js.map +1 -1
  155. package/dist/translations/en.js +10 -0
  156. package/dist/translations/en.js.map +1 -1
  157. package/dist/translations/keys.d.ts +10 -0
  158. package/dist/translations/keys.js +10 -0
  159. package/dist/translations/keys.js.map +1 -1
  160. package/dist/translations/server.js +8 -1
  161. package/dist/translations/server.js.map +1 -1
  162. package/dist/uploads/captureFileRef.d.ts +10 -3
  163. package/dist/uploads/captureFileRef.js +18 -9
  164. package/dist/uploads/captureFileRef.js.map +1 -1
  165. package/dist/validation/builtin/email.js +1 -1
  166. package/dist/validation/builtin/email.js.map +1 -1
  167. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  168. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  169. package/dist/validation/emailPattern.js +10 -0
  170. package/dist/validation/emailPattern.js.map +1 -0
  171. package/dist/validation/registry.d.ts +2 -1
  172. package/dist/validation/registry.js +2 -9
  173. package/dist/validation/registry.js.map +1 -1
  174. package/package.json +4 -4
@@ -0,0 +1,229 @@
1
+ import { TranslationsOption } from "./translations/index.js";
2
+ import { RichTextBodyOption } from "./actions/body/serializeBody.js";
3
+ import { ConsentSourcesResolver } from "./consent/types.js";
4
+ import { FieldTypesConfig } from "./fields/registry.js";
5
+ import { FormEventSink } from "./events/types.js";
6
+ import { PollOptionSourcesConfig } from "./poll/registry.js";
7
+ import { FromAddressesResolver } from "./actions/fromAddresses.js";
8
+ import { ActionsConfig } from "./actions/registry.js";
9
+ import { FormResultsAccess } from "./aggregation/resolveResultsRequest.js";
10
+ import { ButtonsOption } from "./collections/buttonFields.js";
11
+ import { ConsentSnapshotMode } from "./consent/captureConsent.js";
12
+ import { DepartmentEmailsResolver } from "./email/departments.js";
13
+ import { RecipientsConfig } from "./actions/emailRecipients.js";
14
+ import { ResponseOption } from "./collections/redirectFields.js";
15
+ import { CollectionOverrides } from "./plugin/collectionOverrides.js";
16
+ import { SpamOption } from "./spam/types.js";
17
+ import { UploadsOption } from "./plugin/uploadsCollection.js";
18
+ import { OutcomeFieldsOverride } from "./poll/outcomeFields.js";
19
+ import { PollTypesConfig } from "./poll/pollTypeRegistry.js";
20
+ import { ValidationRulesConfig } from "./validation/registry.js";
21
+ import { CollectionSlug } from "payload";
22
+
23
+ //#region src/options.d.ts
24
+ type FormBuilderPluginOptions = {
25
+ disabled?: boolean;
26
+ /**
27
+ * Per-locale overrides for this plugin's UI strings, keyed by the typed
28
+ * translation keys exported from `@10x-media/form-builder/i18n`. Values win
29
+ * over the built-in locales key-by-key; locales the plugin does not ship are
30
+ * added whole. App-level `i18n.translations` still wins over both.
31
+ */
32
+ translations?: TranslationsOption; /** Pluggable sink for form lifecycle events. Defaults to a no-op; analytics adapters or a future analytics plugin subscribe here. */
33
+ events?: FormEventSink;
34
+ /**
35
+ * Content-bearing author fields (labels, placeholders, option labels, action subjects and
36
+ * bodies) are localized by default. Payload strips the `localized` flag on hosts without
37
+ * `localization` configured, so the default is safe everywhere. Set `false` to keep form
38
+ * content single-locale even on localized hosts. Spread-overrides of the prebuilt default
39
+ * exports (`defaultFieldDefinitionsByType`, `defaultActionDefinitions`) carry `localized`
40
+ * flags from the default-true set; when opting out, derive overrides from
41
+ * `buildDefaultFieldDefinitions(false)` / `buildDefaultActionDefinitions({ localize: false })` instead.
42
+ * Consent statements are unaffected either way: they live on the host's own
43
+ * `consentSourcesField()`, which carries its own `localized` option.
44
+ */
45
+ localizeContent?: boolean; /** Add, override, or remove field types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */
46
+ fields?: FieldTypesConfig; /** Add, override, or remove validation rule types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */
47
+ rules?: ValidationRulesConfig; /** Add, override, or remove post-submit action types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */
48
+ actions?: ActionsConfig;
49
+ /**
50
+ * Customize how the plugin's rich text is authored and rendered. `editor` is the default
51
+ * Lexical/richText editor for every plugin richText field (message content, consent
52
+ * statement, response message, action bodies); `bodyEditor` overrides the action body
53
+ * fields and `responseEditor` overrides the success response message field, each falling
54
+ * back to `editor`. `converters` spread over the default
55
+ * Lexical node converters; `serialize` replaces the whole action-body pipeline (e.g. to
56
+ * target chat or plain-text channels instead of email HTML). A custom `serialize` receives
57
+ * the submitted `form` (id/title) and `req`, enabling per-tenant lookups or handing the raw
58
+ * body off to a renderer like react-email.
59
+ */
60
+ richText?: RichTextBodyOption;
61
+ /**
62
+ * Email routing for the `emailTeam` and `confirmation` actions. Both sub-options are opt-in by the
63
+ * presence of a resolver (not a `false`/`true`/object flag) and share a shape: a `req`-scoped
64
+ * resolver, evaluated per request via an endpoint, whose choice is validated at save time only and
65
+ * never re-checked when the action sends (the config is admin-authored, not visitor-controlled).
66
+ *
67
+ * `fromAddresses` gives both actions a `from` select whose options come from the resolver; absent,
68
+ * neither action has a `from` field and every send uses the email adapter's default sender. The
69
+ * intended use is multi-tenant hosts where each tenant may only send from particular addresses
70
+ * (derive the tenant from `req`, return its allowed senders). Values are the literal string
71
+ * `payload.sendEmail` accepts as `from` (e.g. `'Name <addr@x.com>'` or a plain address).
72
+ *
73
+ * `departments` turns the `emailTeam` `to` into a select whose options come from the resolver
74
+ * (a `/:id/departments` endpoint); the intended use is to place `departmentsField()` on a document
75
+ * you own and read it back with `resolveDepartmentOptions`, which resolves each department's address
76
+ * for the requesting locale. Because `to` is localized, each admin locale stores its own resolved
77
+ * address and a submission's locale selects the address it routes to (Payload-native, no per-send
78
+ * lookup). Storing the resolved address, rather than a department id resolved live at send (consent's
79
+ * model), is deliberate: the routing target a form was saved with stays audit-stable even if the
80
+ * resolver's data later changes, so do not "fix" it into a live lookup. Multi-tenant hosts scope
81
+ * which document they read by the tenant derived from `req`; absent, the recipient fields simply
82
+ * offer no preset department options (still free-typed emails and field tokens).
83
+ */
84
+ email?: {
85
+ fromAddresses?: FromAddressesResolver;
86
+ departments?: DepartmentEmailsResolver; /** Narrows the recipient fields' behavior (free-typed emails, field tokens). See {@link RecipientsConfig}. */
87
+ recipients?: RecipientsConfig;
88
+ };
89
+ /**
90
+ * Where the consent statements a form can reference come from. Absent (the default): no sources,
91
+ * so the built-in `consent` field type is not registered at all and authors cannot add a consent
92
+ * field with nothing to reference (a developer-registered custom `consent` type via `fields`
93
+ * still wins).
94
+ *
95
+ * `sources` is an async `req`-scoped resolver returning the sources available to this request.
96
+ * The intended shape: place `consentSourcesField()` on a collection or global you own and read it
97
+ * back here. Multi-tenant hosts scope that read by the tenant derived from `req`, so a tenant's
98
+ * authors only ever see, and their visitors only ever agree to, their own statements. A form
99
+ * stores only a source's row `id`; the statement is resolved live per request (see
100
+ * `resolveConsentStatements`) and the proof is rebuilt from the source at submit, so neither is
101
+ * ever a copy the client could stale or forge.
102
+ */
103
+ consent?: {
104
+ sources: ConsentSourcesResolver;
105
+ /**
106
+ * What each submission snapshots of the agreed consent wording, for a versioned audit trail.
107
+ * `'both'` (default) stores a `statementHash`, the plain `statementText`, and the source name;
108
+ * `'hash'` keeps only the tamper-evident hash; `'text'` only the readable text; `false` keeps the
109
+ * lean id-only proof. The proof stays id-based regardless; this only adds the wording snapshot.
110
+ */
111
+ snapshot?: ConsentSnapshotMode;
112
+ /**
113
+ * Whether to resolve each form's consent statements on every read via an afterRead hook (the
114
+ * default, `true`). It attaches `consentStatements` to the form doc so a host can render the
115
+ * wording without a second call, at the cost of one source lookup per form read (N on a list).
116
+ * Set `false` to skip that hook and resolve statements yourself with the exported
117
+ * `resolveConsentStatements` when you render a form; submit-time proof capture is unaffected either
118
+ * way (it always re-resolves from the source).
119
+ */
120
+ resolveOnRead?: boolean;
121
+ };
122
+ /**
123
+ * Form-level button labels: `submitLabel` at the bottom of the Fields tab, `prevLabel` and
124
+ * `nextLabel` in a row at the bottom of the Flow tab (shown once the flow has a step). The
125
+ * rendered chrome resolves each label as `<Form>` prop, then the stored value, then the
126
+ * translated default. `fields` composes them: it receives the three default fields as a
127
+ * `{ submit, prev, next }` map with the content localization flag already applied and returns
128
+ * the map, so wrapping a default in a row with a host field (e.g. an icon select) or replacing
129
+ * one is explicit. `FormDocument.buttons` reassembles only those three known labels, so a
130
+ * host-added sibling field is still stored but is read off the raw document, not `doc.buttons`.
131
+ */
132
+ buttons?: ButtonsOption;
133
+ /**
134
+ * The success-response group. `redirect.fields` composes the fields inside the `response.redirect`
135
+ * group, mirroring `buttons.fields`: it receives the default fields (the `url` text field, plus the
136
+ * polymorphic `reference` relationship when `redirectRelationships` is set) and returns the group's
137
+ * final field array, so a host can prepend a custom link field (the pattern most projects already
138
+ * have), swap `url` for their own picker, reorder, or filter. Omit it and the group stays the
139
+ * built-in `url` (+ optional `reference`), unchanged.
140
+ *
141
+ * The plugin's built-in redirect handling reads `redirect.url` (and `redirect.reference` when
142
+ * configured); `toFormDocument` passes the whole `response` group through, so a host that replaces
143
+ * `url` with their own link field owns resolving it to a destination in their frontend, the same as
144
+ * the internal-reference case.
145
+ */
146
+ response?: ResponseOption;
147
+ /**
148
+ * Collections whose documents `response.redirect` can reference (`response.redirect.reference`),
149
+ * letting an author redirect a visitor to an internal document instead of a URL after a
150
+ * successful submit. Absent (the default): no `reference` field exists at all, matching today's
151
+ * URL-only redirect. Always polymorphic, even for a single slug (a `CollectionSlug[]` array, not
152
+ * a bare `CollectionSlug`), so a host adding a second collection later never changes the stored
153
+ * shape, the same precedent as `consentSourcesField()`'s `page` picker. The plugin never resolves
154
+ * the reference to a URL itself (it has no notion of the host's routing); `toFormDocument` passes
155
+ * the raw `{ relationTo, value }` pair through on `FormDocument.response.redirect.reference` for
156
+ * the host to resolve. Mirrors the `redirectRelationships` option of Payload's native
157
+ * `plugin-form-builder`.
158
+ */
159
+ redirectRelationships?: CollectionSlug[];
160
+ /**
161
+ * File uploads are bring-your-own. Default `false`: no upload collection is involved and the
162
+ * built-in `file` field type is removed from the registry, so form authors cannot add a field
163
+ * with nowhere to land (a developer-registered custom `file` type via `fields` still wins).
164
+ * `{ collection: 'slug' }` points at a host-owned upload collection (created by the app with
165
+ * its storage adapter); the plugin validates it at boot, appends its hidden `owner` field when
166
+ * absent, and prepends the spam upload hooks.
167
+ */
168
+ uploads?: UploadsOption; /** Honeypot + rate-limiting (on by default) + a captcha adapter seam + upload-ownership scoping. `false` disables the whole subsystem. */
169
+ spam?: SpamOption;
170
+ /**
171
+ * Aggregate-results endpoint options. `access` gates anonymous reads after the form is loaded
172
+ * and before anything is served; absent keeps the plugin-default gating (poll opt-in +
173
+ * visibility + enumerable-field guard). Multi-tenant hosts should compare `form.tenant` against
174
+ * the tenant derived from `req` so one tenant's poll counts are never readable under another
175
+ * tenant's id. Authenticated callers bypass this seam.
176
+ */
177
+ results?: {
178
+ access?: FormResultsAccess;
179
+ };
180
+ /**
181
+ * Poll behavior. `votedCookie: true` sets an httpOnly `fb-voted-{formId}=1` cookie on each
182
+ * successful submission to a poll-enabled form, letting SSR hosts read the voted state via
183
+ * `hasVotedCookie` and pass it to `<Poll hasVoted>`. Default `false`.
184
+ * `sources` registers poll option sources (`definePollOptionSource`), letting authors populate
185
+ * a poll's choices from host domain data with stable values; there are no built-ins. With at
186
+ * least one source registered, the forms poll group gains an `optionSource` select plus its
187
+ * per-source `sourceConfig`, and submissions to a sourced poll only accept resolved values.
188
+ * `outcomeFields` composes the poll `outcome` group: it receives the two default fields
189
+ * (`winningValues`, `resolvedAt`) and returns the group's final field array verbatim, so a host
190
+ * can swap `winningValues` for its own component (e.g. a relationship picker over the voteable
191
+ * records). Membership validation runs server-side regardless, so a swap cannot bypass it.
192
+ * `types` registers outcome strategies (`definePollType`) shown in the poll `type` select. The
193
+ * built-ins `manual` (default, hand-picked), `mostVoted` (auto-resolves to the top choice(s) on
194
+ * close), and `source` (delegates to the option source) are always registered; a host entry keyed
195
+ * by (or carrying) a built-in slug replaces it. A closed poll whose strategy is not `manual`
196
+ * auto-resolves via a scheduled job when a runner is present, or on the next results read otherwise.
197
+ */
198
+ poll?: {
199
+ votedCookie?: boolean;
200
+ sources?: PollOptionSourcesConfig;
201
+ types?: PollTypesConfig;
202
+ outcomeFields?: OutcomeFieldsOverride;
203
+ };
204
+ /**
205
+ * When `true`, the raw `values`, `descriptors`, and `consent` JSON fields are visible in the
206
+ * submission admin view. Default `false`, because the `SubmissionAnswers` UI component already
207
+ * represents those fields fully and they are noisy shown alongside it.
208
+ */
209
+ showSubmissionRawFields?: boolean;
210
+ /**
211
+ * Override individual plugin-managed collections using explicit spreads. Each key accepts a
212
+ * `CollectionOverrides` object: top-level keys are spread with the plugin's defaults (spread
213
+ * order per key determines who wins), hooks are appended after the plugin's own hooks, and
214
+ * `fields` is a function that receives the default fields and returns the final array so
215
+ * additions/removals are always intentional.
216
+ */
217
+ overrides?: {
218
+ forms?: CollectionOverrides;
219
+ formSubmissions?: CollectionOverrides;
220
+ };
221
+ };
222
+ declare module 'payload' {
223
+ interface RegisteredPlugins {
224
+ '@10x-media/form-builder': FormBuilderPluginOptions;
225
+ }
226
+ }
227
+ //#endregion
228
+ export { FormBuilderPluginOptions };
229
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1,11 @@
1
+ //#region src/plugin/applyRegistryConfig.d.ts
2
+ /** Any registry entry: an object carrying a string `type` discriminator, keyed by that type. */
3
+ type TypedDefinition = {
4
+ type: string;
5
+ };
6
+ /** Per-type opt-in: `false` removes a type, `true` keeps the seeded default, a definition adds or replaces one. */
7
+ type RegistryOption<T extends TypedDefinition> = boolean | T;
8
+ type RegistryConfig<T extends TypedDefinition> = Record<string, RegistryOption<T>>;
9
+ //#endregion
10
+ export { RegistryConfig };
11
+ //# sourceMappingURL=applyRegistryConfig.d.ts.map
@@ -0,0 +1,20 @@
1
+ //#region src/plugin/applyRegistryConfig.ts
2
+ /**
3
+ * Apply a per-type opt-in override map onto a seeded registry, in place. `false` removes the type,
4
+ * `true` keeps whatever default is already seeded (a no-op when none exists), and a definition adds
5
+ * or replaces one with its `type` forced to the config key, so the authored slug (derived from
6
+ * `type`) and the lookup key can never drift apart and silently orphan the entry. Shared by the
7
+ * field-type, validation-rule, action, poll-source, and poll-type registries.
8
+ */
9
+ const applyRegistryConfig = (registry, config) => {
10
+ for (const [type, option] of Object.entries(config)) if (option === false) registry.delete(type);
11
+ else if (option !== true) registry.set(type, {
12
+ ...option,
13
+ type
14
+ });
15
+ return registry;
16
+ };
17
+ //#endregion
18
+ export { applyRegistryConfig };
19
+
20
+ //# sourceMappingURL=applyRegistryConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyRegistryConfig.js","names":[],"sources":["../../src/plugin/applyRegistryConfig.ts"],"sourcesContent":["/** Any registry entry: an object carrying a string `type` discriminator, keyed by that type. */\nexport type TypedDefinition = { type: string }\n\n/** Per-type opt-in: `false` removes a type, `true` keeps the seeded default, a definition adds or replaces one. */\nexport type RegistryOption<T extends TypedDefinition> = boolean | T\n\nexport type RegistryConfig<T extends TypedDefinition> = Record<string, RegistryOption<T>>\n\n/**\n * Apply a per-type opt-in override map onto a seeded registry, in place. `false` removes the type,\n * `true` keeps whatever default is already seeded (a no-op when none exists), and a definition adds\n * or replaces one with its `type` forced to the config key, so the authored slug (derived from\n * `type`) and the lookup key can never drift apart and silently orphan the entry. Shared by the\n * field-type, validation-rule, action, poll-source, and poll-type registries.\n */\nexport const applyRegistryConfig = <T extends TypedDefinition>(\n\tregistry: Map<string, T>,\n\tconfig: RegistryConfig<T>\n): Map<string, T> => {\n\tfor (const [type, option] of Object.entries(config)) {\n\t\tif (option === false) {\n\t\t\tregistry.delete(type)\n\t\t} else if (option !== true) {\n\t\t\tregistry.set(type, { ...option, type })\n\t\t}\n\t}\n\treturn registry\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,uBACZ,UACA,WACoB;CACpB,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,MAAM,GACjD,IAAI,WAAW,OACd,SAAS,OAAO,IAAI;MACd,IAAI,WAAW,MACrB,SAAS,IAAI,MAAM;EAAE,GAAG;EAAQ;CAAK,CAAC;CAGxC,OAAO;AACR"}
@@ -4,7 +4,7 @@ import { buildSubmissionsCollection } from "../collections/formSubmissions.js";
4
4
  import { registerActionsTask } from "../actions/task.js";
5
5
  import { attachUploadsCollection } from "./uploadsCollection.js";
6
6
  //#region src/plugin/registerCollections.ts
7
- const registerCollections = ({ config, registry, ruleRegistry, consentSources, consentSnapshot, actionRegistry, richText, hasJobsPlugin, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, buttons, response, fromAddresses, departments, redirectRelationships, overrides }) => {
7
+ const registerCollections = ({ config, registry, ruleRegistry, consentSources, consentSnapshot, consentResolveOnRead, actionRegistry, richText, hasJobsPlugin, events, uploads, spam, showSubmissionRawFields, localizeContent, resultsAccess, votedCookie, pollSourceRegistry, pollTypeRegistry, outcomeFields, buttons, response, fromAddresses, departments, redirectRelationships, overrides }) => {
8
8
  registerActionsTask(config, actionRegistry, richText);
9
9
  registerPollCloseTask(config);
10
10
  const hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin;
@@ -36,6 +36,7 @@ const registerCollections = ({ config, registry, ruleRegistry, consentSources, c
36
36
  registry,
37
37
  ruleRegistry,
38
38
  consentSources,
39
+ consentResolveOnRead,
39
40
  actionRegistry,
40
41
  localizeContent,
41
42
  richText,
@@ -1 +1 @@
1
- {"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentSources?: ConsentSourcesResolver\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\tbuttons?: ButtonsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tresponse,\n\tfromAddresses,\n\tdepartments,\n\tredirectRelationships,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload has no nav-sort option, so it lists collections in registration order. Register\n\t// `form-submissions` before `forms` so the \"Forms\" nav group reads alphabetically\n\t// (`form-submissions` < `forms`); ordering plugin nav groups against a host's own collections\n\t// stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t]\n}\n"],"mappings":";;;;;;AAsDA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,gBACA,iBACA,gBACA,UACA,eACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,SACA,UACA,eACA,aACA,uBACA,gBACoC;CACpC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,MAAM;CAC5B,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAOnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;EACD,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;CACF;AACD"}
1
+ {"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionSlug, Config } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport type { FromAddressesResolver } from '../actions/fromAddresses'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport type { FormResultsAccess } from '../aggregation/resolveResultsRequest'\nimport type { ButtonsOption } from '../collections/buttonFields'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ResponseOption } from '../collections/redirectFields'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport type { DepartmentEmailsResolver } from '../email/departments'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { registerPollCloseTask } from '../poll/closeJob'\nimport type { OutcomeFieldsOverride } from '../poll/outcomeFields'\nimport type { PollTypeRegistry } from '../poll/pollTypeRegistry'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\nimport { attachUploadsCollection, type UploadsOption } from './uploadsCollection'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentSources?: ConsentSourcesResolver\n\tconsentSnapshot?: ConsentSnapshotMode\n\t/** Plugin `consent.resolveOnRead` (default true); false skips the per-read consent afterRead hook. */\n\tconsentResolveOnRead?: boolean\n\tactionRegistry: ActionRegistry\n\trichText?: RichTextBodyOption\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: UploadsOption\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\tlocalizeContent: boolean\n\tresultsAccess?: FormResultsAccess\n\tvotedCookie: boolean\n\tpollSourceRegistry: PollOptionSourceRegistry\n\tpollTypeRegistry: PollTypeRegistry\n\toutcomeFields?: OutcomeFieldsOverride\n\tbuttons?: ButtonsOption\n\tresponse?: ResponseOption\n\tfromAddresses?: FromAddressesResolver\n\tdepartments?: DepartmentEmailsResolver\n\tredirectRelationships?: CollectionSlug[]\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tconsentSnapshot,\n\tconsentResolveOnRead,\n\tactionRegistry,\n\trichText,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\tlocalizeContent,\n\tresultsAccess,\n\tvotedCookie,\n\tpollSourceRegistry,\n\tpollTypeRegistry,\n\toutcomeFields,\n\tbuttons,\n\tresponse,\n\tfromAddresses,\n\tdepartments,\n\tredirectRelationships,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry, richText)\n\tregisterPollCloseTask(config)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tconst uploadSlug = uploads === false ? undefined : uploads.collection\n\tif (uploads !== false) {\n\t\tattachUploadsCollection({ config, slug: uploads.collection, spam })\n\t}\n\n\t// Payload has no nav-sort option, so it lists collections in registration order. Register\n\t// `form-submissions` before `forms` so the \"Forms\" nav group reads alphabetically\n\t// (`form-submissions` < `forms`); ordering plugin nav groups against a host's own collections\n\t// stays a host concern.\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tconsentSnapshot,\n\t\t\tactionRegistry,\n\t\t\trichText,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tuploadSlug,\n\t\t\tspam,\n\t\t\tvotedCookie,\n\t\t\tpollSourceRegistry,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentSources,\n\t\t\tconsentResolveOnRead,\n\t\t\tactionRegistry,\n\t\t\tlocalizeContent,\n\t\t\trichText,\n\t\t\tuploadsCollectionSlug: uploadSlug,\n\t\t\tresultsAccess,\n\t\t\tpollSourceRegistry,\n\t\t\tpollTypeRegistry,\n\t\t\toutcomeFields,\n\t\t\tbuttons,\n\t\t\tresponse,\n\t\t\tfromAddresses,\n\t\t\tdepartments,\n\t\t\tredirectRelationships,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t]\n}\n"],"mappings":";;;;;;AAwDA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,gBACA,iBACA,sBACA,gBACA,UACA,eACA,QACA,SACA,MACA,yBACA,iBACA,eACA,aACA,oBACA,kBACA,eACA,SACA,UACA,eACA,aACA,uBACA,gBACoC;CACpC,oBAAoB,QAAQ,gBAAgB,QAAQ;CACpD,sBAAsB,MAAM;CAC5B,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,MAAM,aAAa,YAAY,QAAQ,KAAA,IAAY,QAAQ;CAC3D,IAAI,YAAY,OACf,wBAAwB;EAAE;EAAQ,MAAM,QAAQ;EAAY;CAAK,CAAC;CAOnE,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;EACD,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA;GACA;GACA,uBAAuB;GACvB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;CACF;AACD"}
@@ -1,5 +1,5 @@
1
1
  import { keys } from "../translations/keys.js";
2
- import { labelFor, labelForKey } from "../translations/server.js";
2
+ import { labelForKey, resolveDefinitionLabel } from "../translations/server.js";
3
3
  //#region src/poll/buildPollOptionSourceFields.ts
4
4
  /**
5
5
  * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns
@@ -29,7 +29,7 @@ const buildPollOptionSourceFields = (registry) => {
29
29
  label: labelForKey(keys.pollOptionSource),
30
30
  admin: { description: labelForKey(keys.pollOptionSourceDescription) },
31
31
  options: sources.map((source) => ({
32
- label: labelFor(source.label),
32
+ label: resolveDefinitionLabel(source.label),
33
33
  value: source.type
34
34
  }))
35
35
  }, {
@@ -1 +1 @@
1
- {"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t},\n\t\t\toptions: sources.map((source) => ({ label: labelFor(source.label), value: source.type })),\n\t\t},\n\t\t{\n\t\t\tname: 'sourceConfig',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollSourceConfig),\n\t\t\tadmin: {\n\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.optionSource),\n\t\t\t},\n\t\t\tfields:\n\t\t\t\tsourceConfigFields.length > 0\n\t\t\t\t\t? sourceConfigFields\n\t\t\t\t\t: [{ name: '_placeholder', type: 'text', admin: { hidden: true } }],\n\t\t},\n\t]\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,+BAA+B,aAAgD;CAC3F,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;CACrC,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAGT,MAAM,qBAA8B,QAAQ,SAAS,YACnD,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;EACpC,MAAM,gBACL,OAAO,MAAM,UAAU,YAAY,MAAM,UAAU,OAAO,MAAM,QAAQ,CAAC;EAC1E,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG;IACH,YAAY,SACV,MAA+C,MAAM,iBAAiB,OAAO;GAChF;EACD;CACD,CAAC,CACF;CAEA,OAAO,CACN;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,aAAa,YAAY,KAAK,2BAA2B,EAC1D;EACA,SAAS,QAAQ,KAAK,YAAY;GAAE,OAAO,SAAS,OAAO,KAAK;GAAG,OAAO,OAAO;EAAK,EAAE;CACzF,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,YAAY,OAAO,gBAAgB,QAAQ,aAAa,YAAY,EACrE;EACA,QACC,mBAAmB,SAAS,IACzB,qBACA,CAAC;GAAE,MAAM;GAAgB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE,CAAC;CACrE,CACD;AACD"}
1
+ {"version":3,"file":"buildPollOptionSourceFields.js","names":[],"sources":["../../src/poll/buildPollOptionSourceFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelForKey, resolveDefinitionLabel } from '../translations/server'\nimport type { PollOptionSourceRegistry } from './registry'\n\n/**\n * Builds the `optionSource` select and `sourceConfig` group for the forms poll group; returns\n * nothing when no sources are registered, so the fields only exist for hosts that opted in.\n * Fields inside `sourceConfig` are annotated with `admin.condition` so only the fields of the\n * currently selected source type are visible; like consent's `sourceConfig`, field names within\n * the group must be unique across all registered sources. Unlike consent's `source` select there\n * is no default: hand-authored options remain the baseline and a source is an explicit opt-in per\n * form (the select stays clearable).\n */\nexport const buildPollOptionSourceFields = (registry: PollOptionSourceRegistry): Field[] => {\n\tconst sources = [...registry.values()]\n\tif (sources.length === 0) {\n\t\treturn []\n\t}\n\n\tconst sourceConfigFields: Field[] = sources.flatMap((source) =>\n\t\t(source.config ?? []).map((field) => {\n\t\t\tconst existingAdmin =\n\t\t\t\ttypeof field.admin === 'object' && field.admin !== null ? field.admin : {}\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tadmin: {\n\t\t\t\t\t...existingAdmin,\n\t\t\t\t\tcondition: (data: unknown) =>\n\t\t\t\t\t\t(data as { poll?: { optionSource?: string } })?.poll?.optionSource === source.type,\n\t\t\t\t},\n\t\t\t} as Field\n\t\t})\n\t)\n\n\treturn [\n\t\t{\n\t\t\tname: 'optionSource',\n\t\t\ttype: 'select',\n\t\t\tlabel: labelForKey(keys.pollOptionSource),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelForKey(keys.pollOptionSourceDescription),\n\t\t\t},\n\t\t\toptions: sources.map((source) => ({\n\t\t\t\tlabel: resolveDefinitionLabel(source.label),\n\t\t\t\tvalue: source.type,\n\t\t\t})),\n\t\t},\n\t\t{\n\t\t\tname: 'sourceConfig',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.pollSourceConfig),\n\t\t\tadmin: {\n\t\t\t\tcondition: (_data, siblingData) => Boolean(siblingData?.optionSource),\n\t\t\t},\n\t\t\tfields:\n\t\t\t\tsourceConfigFields.length > 0\n\t\t\t\t\t? sourceConfigFields\n\t\t\t\t\t: [{ name: '_placeholder', type: 'text', admin: { hidden: true } }],\n\t\t},\n\t]\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,+BAA+B,aAAgD;CAC3F,MAAM,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;CACrC,IAAI,QAAQ,WAAW,GACtB,OAAO,CAAC;CAGT,MAAM,qBAA8B,QAAQ,SAAS,YACnD,OAAO,UAAU,CAAC,GAAG,KAAK,UAAU;EACpC,MAAM,gBACL,OAAO,MAAM,UAAU,YAAY,MAAM,UAAU,OAAO,MAAM,QAAQ,CAAC;EAC1E,OAAO;GACN,GAAG;GACH,OAAO;IACN,GAAG;IACH,YAAY,SACV,MAA+C,MAAM,iBAAiB,OAAO;GAChF;EACD;CACD,CAAC,CACF;CAEA,OAAO,CACN;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,aAAa,YAAY,KAAK,2BAA2B,EAC1D;EACA,SAAS,QAAQ,KAAK,YAAY;GACjC,OAAO,uBAAuB,OAAO,KAAK;GAC1C,OAAO,OAAO;EACf,EAAE;CACH,GACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,gBAAgB;EACxC,OAAO,EACN,YAAY,OAAO,gBAAgB,QAAQ,aAAa,YAAY,EACrE;EACA,QACC,mBAAmB,SAAS,IACzB,qBACA,CAAC;GAAE,MAAM;GAAgB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE,CAAC;CACrE,CACD;AACD"}
@@ -22,8 +22,8 @@ type PollOptionResolveArgs<TConfig extends Record<string, unknown> = Record<stri
22
22
  * the resolved values are the only accepted answers (fail closed).
23
23
  */
24
24
  type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {
25
- type: string;
26
- label: string;
25
+ type: string; /** i18n-key or literal (resolved like a field label), or a per-locale record. */
26
+ label: string | Record<string, string>;
27
27
  config?: Field[];
28
28
  resolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[];
29
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */\nexport type PollOption = { label: string; value: string }\n\nexport type PollOptionResolveArgs<\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n> = {\n\tconfig: TConfig\n\tform: { id: number | string; title?: string }\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * A poll option source type, authored once by the host: `config` is the admin `Field[]` an author\n * fills per form (e.g. an event id); `resolve` maps that config to the current options from host\n * domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where\n * the resolved values are the only accepted answers (fail closed).\n */\nexport type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {\n\ttype: string\n\tlabel: string\n\tconfig?: Field[]\n\tresolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[]\n\t/**\n\t * Optional outcome resolver: return the winning option value(s) from domain data (e.g. the\n\t * recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome\n\t * is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is\n\t * `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit\n\t * `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's\n\t * resolved options before it is recorded, so each must be one of the values `resolve` yields.\n\t */\n\tresolveOutcome?: (\n\t\targs: PollOptionResolveArgs<TConfig>\n\t) => Promise<string | string[] | undefined> | string | string[] | undefined\n}\n\n/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */\nexport type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>\n\nexport const definePollOptionSource = <\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n>(\n\tsource: PollOptionSource<TConfig>\n): PollOptionSource<TConfig> => source\n"],"mappings":";AAyCA,MAAa,0BAGZ,WAC+B"}
1
+ {"version":3,"file":"definePollOptionSource.js","names":[],"sources":["../../src/poll/definePollOptionSource.ts"],"sourcesContent":["import type { Field, Payload, PayloadRequest } from 'payload'\n\n/** One selectable poll choice. `value` is the host's stable identifier contract: it is stored in submissions and later matched against `outcome.winningValues`, so it must never change once votes exist. */\nexport type PollOption = { label: string; value: string }\n\nexport type PollOptionResolveArgs<\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n> = {\n\tconfig: TConfig\n\tform: { id: number | string; title?: string }\n\tpayload: Payload\n\treq?: PayloadRequest\n}\n\n/**\n * A poll option source type, authored once by the host: `config` is the admin `Field[]` an author\n * fills per form (e.g. an event id); `resolve` maps that config to the current options from host\n * domain data. Called at render time (via `resolvePollOptions`) and again at submission time, where\n * the resolved values are the only accepted answers (fail closed).\n */\nexport type PollOptionSource<TConfig extends Record<string, unknown> = Record<string, unknown>> = {\n\ttype: string\n\t/** i18n-key or literal (resolved like a field label), or a per-locale record. */\n\tlabel: string | Record<string, string>\n\tconfig?: Field[]\n\tresolve: (args: PollOptionResolveArgs<TConfig>) => Promise<PollOption[]> | PollOption[]\n\t/**\n\t * Optional outcome resolver: return the winning option value(s) from domain data (e.g. the\n\t * recorded race result), a `string[]` when the result is a tie, or `undefined` while the outcome\n\t * is not yet decidable. Backs the built-in `source` poll type, so a form whose `poll.type` is\n\t * `source` auto-resolves through it (via the scheduled close job, a results-read, or an explicit\n\t * `resolvePollOutcome({ payload, formId })`); every returned value is validated against the poll's\n\t * resolved options before it is recorded, so each must be one of the values `resolve` yields.\n\t */\n\tresolveOutcome?: (\n\t\targs: PollOptionResolveArgs<TConfig>\n\t) => Promise<string | string[] | undefined> | string | string[] | undefined\n}\n\n/** Erased shape stored in the registry; config re-narrows per matched type at resolution. */\nexport type AnyPollOptionSource = PollOptionSource<Record<string, unknown>>\n\nexport const definePollOptionSource = <\n\tTConfig extends Record<string, unknown> = Record<string, unknown>,\n>(\n\tsource: PollOptionSource<TConfig>\n): PollOptionSource<TConfig> => source\n"],"mappings":";AA0CA,MAAa,0BAGZ,WAC+B"}
@@ -1,4 +1,5 @@
1
1
  import { keys } from "../translations/keys.js";
2
+ import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
2
3
  import { customStateOf, stashCustomState } from "../plugin/customState.js";
3
4
  import { definePollType } from "./definePollType.js";
4
5
  import { mostVotedStrategy } from "./mostVoted.js";
@@ -56,10 +57,7 @@ const resolvePollTypes = (config) => {
56
57
  [sourceStrategy.type, sourceStrategy]
57
58
  ]);
58
59
  if (Array.isArray(config)) for (const strategy of config) registry.set(strategy.type, strategy);
59
- else if (config) for (const [type, strategy] of Object.entries(config)) registry.set(type, {
60
- ...strategy,
61
- type
62
- });
60
+ else if (config) applyRegistryConfig(registry, config);
63
61
  return registry;
64
62
  };
65
63
  /** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */
@@ -1 +1 @@
1
- {"version":3,"file":"pollTypeRegistry.js","names":[],"sources":["../../src/poll/pollTypeRegistry.ts"],"sourcesContent":["import type { Payload } from 'payload'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\nimport { keys } from '../translations/keys'\nimport { definePollType, type PollOutcomeStrategy } from './definePollType'\nimport { mostVotedStrategy } from './mostVoted'\nimport { pollOptionSourcesOf } from './resolvePollOptions'\n\nexport type PollTypeRegistry = Map<string, PollOutcomeStrategy>\n\n/**\n * Host-supplied outcome strategies: a keyed record (the key forces `type`, like the field-type\n * registry) or a plain array. There are no boolean toggles because the three built-ins are always\n * registered; a host entry with a built-in's `type` replaces it.\n */\nexport type PollTypesConfig = Record<string, PollOutcomeStrategy> | PollOutcomeStrategy[]\n\n/** The default: an admin records `winningValues` by hand, so there is nothing to auto-resolve. */\nexport const manualStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'manual',\n\tlabel: keys.pollTypeManual,\n\tresolveOutcome: () => undefined,\n})\n\n/** One winner set as a deduped array of non-empty strings, or `undefined` when the source yields none. */\nconst normalizeSourceOutcome = (value: string | string[] | undefined): string[] | undefined => {\n\tif (value === undefined) {\n\t\treturn undefined\n\t}\n\tconst cleaned = [\n\t\t...new Set(\n\t\t\t(Array.isArray(value) ? value : [value]).filter(\n\t\t\t\t(entry): entry is string => typeof entry === 'string' && entry.length > 0\n\t\t\t)\n\t\t),\n\t]\n\treturn cleaned.length > 0 ? cleaned : undefined\n}\n\n/**\n * Delegate to the poll's configured option source `resolveOutcome` (the same domain resolver that\n * feeds explicit `resolvePollOutcome` auto mode before this task shipped). Returns `undefined` when\n * the poll has no source, the source is unregistered, or it does not implement `resolveOutcome`, so a\n * misconfigured poll never throws from the close job or the read fallback; a source that resolves to\n * no decision (its `resolveOutcome` returns `undefined`) also yields `undefined`.\n */\nexport const sourceStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'source',\n\tlabel: keys.pollTypeSource,\n\tresolveOutcome: async ({ payload, req, form, poll }) => {\n\t\tconst optionSource =\n\t\t\ttypeof poll.optionSource === 'string' && poll.optionSource.length > 0\n\t\t\t\t? poll.optionSource\n\t\t\t\t: undefined\n\t\tif (!optionSource) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst source = pollOptionSourcesOf(payload).get(optionSource)\n\t\tif (!source?.resolveOutcome) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst config =\n\t\t\tpoll.sourceConfig != null && typeof poll.sourceConfig === 'object'\n\t\t\t\t? (poll.sourceConfig as Record<string, unknown>)\n\t\t\t\t: {}\n\t\treturn normalizeSourceOutcome(\n\t\t\tawait source.resolveOutcome({\n\t\t\t\tconfig,\n\t\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t)\n\t},\n})\n\n/**\n * The active poll-type registry: the three always-registered built-ins (`manual`, `mostVoted`,\n * `source`) plus any host strategies from the plugin `poll.types` option. A host entry keyed by (or\n * carrying a `type` of) a built-in slug replaces that built-in. Mirrors the field-type registry: a\n * record key forces the strategy's `type` so a keyed override cannot rename the slot.\n */\nexport const resolvePollTypes = (config?: PollTypesConfig): PollTypeRegistry => {\n\tconst registry: PollTypeRegistry = new Map([\n\t\t[manualStrategy.type, manualStrategy],\n\t\t[mostVotedStrategy.type, mostVotedStrategy],\n\t\t[sourceStrategy.type, sourceStrategy],\n\t])\n\tif (Array.isArray(config)) {\n\t\tfor (const strategy of config) {\n\t\t\tregistry.set(strategy.type, strategy)\n\t\t}\n\t} else if (config) {\n\t\tfor (const [type, strategy] of Object.entries(config)) {\n\t\t\tregistry.set(type, { ...strategy, type })\n\t\t}\n\t}\n\treturn registry\n}\n\ntype PollTypesCustomState = { pollTypes: PollTypeRegistry }\n\n/** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */\nexport const stashPollTypes = (\n\tcustom: Record<string, unknown> | undefined,\n\tregistry: PollTypeRegistry\n): Record<string, unknown> =>\n\tstashCustomState<PollTypesCustomState>(custom, { pollTypes: registry })\n\nexport const pollTypesOf = (payload: Payload): PollTypeRegistry =>\n\tcustomStateOf<PollTypesCustomState>(payload).pollTypes ?? new Map()\n"],"mappings":";;;;;;;AAiBA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,sBAAsB,KAAA;AACvB,CAAC;;AAGD,MAAM,0BAA0B,UAA+D;CAC9F,IAAI,UAAU,KAAA,GACb;CAED,MAAM,UAAU,CACf,GAAG,IAAI,KACL,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAAG,QACvC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CACzE,CACD,CACD;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACvC;;;;;;;;AASA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,gBAAgB,OAAO,EAAE,SAAS,KAAK,MAAM,WAAW;EACvD,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;EACJ,IAAI,CAAC,cACJ;EAED,MAAM,SAAS,oBAAoB,OAAO,EAAE,IAAI,YAAY;EAC5D,IAAI,CAAC,QAAQ,gBACZ;EAED,MAAM,SACL,KAAK,gBAAgB,QAAQ,OAAO,KAAK,iBAAiB,WACtD,KAAK,eACN,CAAC;EACL,OAAO,uBACN,MAAM,OAAO,eAAe;GAC3B;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC,CACF;CACD;AACD,CAAC;;;;;;;AAQD,MAAa,oBAAoB,WAA+C;CAC/E,MAAM,WAA6B,IAAI,IAAI;EAC1C,CAAC,eAAe,MAAM,cAAc;EACpC,CAAC,kBAAkB,MAAM,iBAAiB;EAC1C,CAAC,eAAe,MAAM,cAAc;CACrC,CAAC;CACD,IAAI,MAAM,QAAQ,MAAM,GACvB,KAAK,MAAM,YAAY,QACtB,SAAS,IAAI,SAAS,MAAM,QAAQ;MAE/B,IAAI,QACV,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,MAAM,GACnD,SAAS,IAAI,MAAM;EAAE,GAAG;EAAU;CAAK,CAAC;CAG1C,OAAO;AACR;;AAKA,MAAa,kBACZ,QACA,aAEA,iBAAuC,QAAQ,EAAE,WAAW,SAAS,CAAC;AAEvE,MAAa,eAAe,YAC3B,cAAoC,OAAO,EAAE,6BAAa,IAAI,IAAI"}
1
+ {"version":3,"file":"pollTypeRegistry.js","names":[],"sources":["../../src/poll/pollTypeRegistry.ts"],"sourcesContent":["import type { Payload } from 'payload'\nimport { applyRegistryConfig } from '../plugin/applyRegistryConfig'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\nimport { keys } from '../translations/keys'\nimport { definePollType, type PollOutcomeStrategy } from './definePollType'\nimport { mostVotedStrategy } from './mostVoted'\nimport { pollOptionSourcesOf } from './resolvePollOptions'\n\nexport type PollTypeRegistry = Map<string, PollOutcomeStrategy>\n\n/**\n * Host-supplied outcome strategies: a keyed record (the key forces `type`, like the field-type\n * registry) or a plain array. There are no boolean toggles because the three built-ins are always\n * registered; a host entry with a built-in's `type` replaces it.\n */\nexport type PollTypesConfig = Record<string, PollOutcomeStrategy> | PollOutcomeStrategy[]\n\n/** The default: an admin records `winningValues` by hand, so there is nothing to auto-resolve. */\nexport const manualStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'manual',\n\tlabel: keys.pollTypeManual,\n\tresolveOutcome: () => undefined,\n})\n\n/** One winner set as a deduped array of non-empty strings, or `undefined` when the source yields none. */\nconst normalizeSourceOutcome = (value: string | string[] | undefined): string[] | undefined => {\n\tif (value === undefined) {\n\t\treturn undefined\n\t}\n\tconst cleaned = [\n\t\t...new Set(\n\t\t\t(Array.isArray(value) ? value : [value]).filter(\n\t\t\t\t(entry): entry is string => typeof entry === 'string' && entry.length > 0\n\t\t\t)\n\t\t),\n\t]\n\treturn cleaned.length > 0 ? cleaned : undefined\n}\n\n/**\n * Delegate to the poll's configured option source `resolveOutcome` (the same domain resolver that\n * feeds explicit `resolvePollOutcome` auto mode before this task shipped). Returns `undefined` when\n * the poll has no source, the source is unregistered, or it does not implement `resolveOutcome`, so a\n * misconfigured poll never throws from the close job or the read fallback; a source that resolves to\n * no decision (its `resolveOutcome` returns `undefined`) also yields `undefined`.\n */\nexport const sourceStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'source',\n\tlabel: keys.pollTypeSource,\n\tresolveOutcome: async ({ payload, req, form, poll }) => {\n\t\tconst optionSource =\n\t\t\ttypeof poll.optionSource === 'string' && poll.optionSource.length > 0\n\t\t\t\t? poll.optionSource\n\t\t\t\t: undefined\n\t\tif (!optionSource) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst source = pollOptionSourcesOf(payload).get(optionSource)\n\t\tif (!source?.resolveOutcome) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst config =\n\t\t\tpoll.sourceConfig != null && typeof poll.sourceConfig === 'object'\n\t\t\t\t? (poll.sourceConfig as Record<string, unknown>)\n\t\t\t\t: {}\n\t\treturn normalizeSourceOutcome(\n\t\t\tawait source.resolveOutcome({\n\t\t\t\tconfig,\n\t\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t)\n\t},\n})\n\n/**\n * The active poll-type registry: the three always-registered built-ins (`manual`, `mostVoted`,\n * `source`) plus any host strategies from the plugin `poll.types` option. A host entry keyed by (or\n * carrying a `type` of) a built-in slug replaces that built-in. Mirrors the field-type registry: a\n * record key forces the strategy's `type` so a keyed override cannot rename the slot.\n */\nexport const resolvePollTypes = (config?: PollTypesConfig): PollTypeRegistry => {\n\tconst registry: PollTypeRegistry = new Map([\n\t\t[manualStrategy.type, manualStrategy],\n\t\t[mostVotedStrategy.type, mostVotedStrategy],\n\t\t[sourceStrategy.type, sourceStrategy],\n\t])\n\tif (Array.isArray(config)) {\n\t\tfor (const strategy of config) {\n\t\t\tregistry.set(strategy.type, strategy)\n\t\t}\n\t} else if (config) {\n\t\t// A keyed record shares the sibling registries' merge (the key forces `type`). There are no\n\t\t// boolean toggles here because the three built-ins are always registered, so those branches\n\t\t// are inert for this always-strategy config.\n\t\tapplyRegistryConfig(registry, config)\n\t}\n\treturn registry\n}\n\ntype PollTypesCustomState = { pollTypes: PollTypeRegistry }\n\n/** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */\nexport const stashPollTypes = (\n\tcustom: Record<string, unknown> | undefined,\n\tregistry: PollTypeRegistry\n): Record<string, unknown> =>\n\tstashCustomState<PollTypesCustomState>(custom, { pollTypes: registry })\n\nexport const pollTypesOf = (payload: Payload): PollTypeRegistry =>\n\tcustomStateOf<PollTypesCustomState>(payload).pollTypes ?? new Map()\n"],"mappings":";;;;;;;;AAkBA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,sBAAsB,KAAA;AACvB,CAAC;;AAGD,MAAM,0BAA0B,UAA+D;CAC9F,IAAI,UAAU,KAAA,GACb;CAED,MAAM,UAAU,CACf,GAAG,IAAI,KACL,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAAG,QACvC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CACzE,CACD,CACD;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACvC;;;;;;;;AASA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,gBAAgB,OAAO,EAAE,SAAS,KAAK,MAAM,WAAW;EACvD,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;EACJ,IAAI,CAAC,cACJ;EAED,MAAM,SAAS,oBAAoB,OAAO,EAAE,IAAI,YAAY;EAC5D,IAAI,CAAC,QAAQ,gBACZ;EAED,MAAM,SACL,KAAK,gBAAgB,QAAQ,OAAO,KAAK,iBAAiB,WACtD,KAAK,eACN,CAAC;EACL,OAAO,uBACN,MAAM,OAAO,eAAe;GAC3B;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC,CACF;CACD;AACD,CAAC;;;;;;;AAQD,MAAa,oBAAoB,WAA+C;CAC/E,MAAM,WAA6B,IAAI,IAAI;EAC1C,CAAC,eAAe,MAAM,cAAc;EACpC,CAAC,kBAAkB,MAAM,iBAAiB;EAC1C,CAAC,eAAe,MAAM,cAAc;CACrC,CAAC;CACD,IAAI,MAAM,QAAQ,MAAM,GACvB,KAAK,MAAM,YAAY,QACtB,SAAS,IAAI,SAAS,MAAM,QAAQ;MAE/B,IAAI,QAIV,oBAAoB,UAAU,MAAM;CAErC,OAAO;AACR;;AAKA,MAAa,kBACZ,QACA,aAEA,iBAAuC,QAAQ,EAAE,WAAW,SAAS,CAAC;AAEvE,MAAa,eAAe,YAC3B,cAAoC,OAAO,EAAE,6BAAa,IAAI,IAAI"}
@@ -1,10 +1,11 @@
1
1
  import { AnyPollOptionSource } from "./definePollOptionSource.js";
2
+ import { RegistryConfig } from "../plugin/applyRegistryConfig.js";
2
3
 
3
4
  //#region src/poll/registry.d.ts
4
5
  type PollOptionSourceRegistry = Map<string, AnyPollOptionSource>;
5
6
  /** `false` removes a source, `true` keeps it (no-op: there are no built-ins), a definition adds or replaces one. */
6
7
  type PollOptionSourceOption = boolean | AnyPollOptionSource;
7
- type PollOptionSourcesConfig = Record<string, PollOptionSourceOption>;
8
+ type PollOptionSourcesConfig = RegistryConfig<AnyPollOptionSource>;
8
9
  /**
9
10
  * Resolve the active poll option-source registry from a consumer map. Unlike the consent and
10
11
  * action registries there are no built-in defaults: the registry starts empty and only host
@@ -1,15 +1,11 @@
1
+ import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
1
2
  //#region src/poll/registry.ts
2
3
  /**
3
4
  * Resolve the active poll option-source registry from a consumer map. Unlike the consent and
4
5
  * action registries there are no built-in defaults: the registry starts empty and only host
5
6
  * definitions populate it, so `true`/`false` entries are accepted but change nothing.
6
7
  */
7
- const resolvePollOptionSources = (config = {}) => {
8
- const registry = /* @__PURE__ */ new Map();
9
- for (const [type, option] of Object.entries(config)) if (option === false) registry.delete(type);
10
- else if (option === true) {} else registry.set(type, option);
11
- return registry;
12
- };
8
+ const resolvePollOptionSources = (config = {}) => applyRegistryConfig(/* @__PURE__ */ new Map(), config);
13
9
  //#endregion
14
10
  export { resolvePollOptionSources };
15
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../src/poll/registry.ts"],"sourcesContent":["import type { AnyPollOptionSource } from './definePollOptionSource'\n\nexport type PollOptionSourceRegistry = Map<string, AnyPollOptionSource>\n\n/** `false` removes a source, `true` keeps it (no-op: there are no built-ins), a definition adds or replaces one. */\nexport type PollOptionSourceOption = boolean | AnyPollOptionSource\n\nexport type PollOptionSourcesConfig = Record<string, PollOptionSourceOption>\n\n/**\n * Resolve the active poll option-source registry from a consumer map. Unlike the consent and\n * action registries there are no built-in defaults: the registry starts empty and only host\n * definitions populate it, so `true`/`false` entries are accepted but change nothing.\n */\nexport const resolvePollOptionSources = (\n\tconfig: PollOptionSourcesConfig = {}\n): PollOptionSourceRegistry => {\n\tconst registry: PollOptionSourceRegistry = new Map()\n\tfor (const [type, option] of Object.entries(config)) {\n\t\tif (option === false) {\n\t\t\tregistry.delete(type)\n\t\t} else if (option === true) {\n\t\t\t// keep the default; no-op because no built-in poll option sources exist\n\t\t} else {\n\t\t\tregistry.set(type, option)\n\t\t}\n\t}\n\treturn registry\n}\n"],"mappings":";;;;;;AAcA,MAAa,4BACZ,SAAkC,CAAC,MACL;CAC9B,MAAM,2BAAqC,IAAI,IAAI;CACnD,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,MAAM,GACjD,IAAI,WAAW,OACd,SAAS,OAAO,IAAI;MACd,IAAI,WAAW,MAAM,CAE5B,OACC,SAAS,IAAI,MAAM,MAAM;CAG3B,OAAO;AACR"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../src/poll/registry.ts"],"sourcesContent":["import { applyRegistryConfig, type RegistryConfig } from '../plugin/applyRegistryConfig'\nimport type { AnyPollOptionSource } from './definePollOptionSource'\n\nexport type PollOptionSourceRegistry = Map<string, AnyPollOptionSource>\n\n/** `false` removes a source, `true` keeps it (no-op: there are no built-ins), a definition adds or replaces one. */\nexport type PollOptionSourceOption = boolean | AnyPollOptionSource\n\nexport type PollOptionSourcesConfig = RegistryConfig<AnyPollOptionSource>\n\n/**\n * Resolve the active poll option-source registry from a consumer map. Unlike the consent and\n * action registries there are no built-in defaults: the registry starts empty and only host\n * definitions populate it, so `true`/`false` entries are accepted but change nothing.\n */\nexport const resolvePollOptionSources = (\n\tconfig: PollOptionSourcesConfig = {}\n): PollOptionSourceRegistry => applyRegistryConfig(new Map<string, AnyPollOptionSource>(), config)\n"],"mappings":";;;;;;;AAeA,MAAa,4BACZ,SAAkC,CAAC,MACL,oCAAoB,IAAI,IAAiC,GAAG,MAAM"}
@@ -22,7 +22,6 @@ const resolvePollOptionsRequest = async (args) => {
22
22
  collection: FORMS_SLUG,
23
23
  id: formId,
24
24
  depth: 0,
25
- overrideAccess: true,
26
25
  req
27
26
  }).catch(() => null);
28
27
  if (!form) return {
@@ -1 +1 @@
1
- {"version":3,"file":"resolvePollOptionsRequest.js","names":[],"sources":["../../src/poll/resolvePollOptionsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nexport type ResolvePollOptionsRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq?: PayloadRequest\n}\n\nexport type ResolvePollOptionsRequestResult = {\n\tstatus: number\n\tbody: { options: PollOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:\n * authenticated callers get the poll's effective options (source-resolved or authored, exactly the\n * set outcome validation accepts), anonymous callers are always refused. Statuses mirror the\n * results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option\n * source fails (fail closed).\n */\nexport const resolvePollOptionsRequest = async (\n\targs: ResolvePollOptionsRequestArgs\n): Promise<ResolvePollOptionsRequestResult> => {\n\tconst { payload, formId, isAuthed, req } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolveEffectivePollOptions({ payload, req, form })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Poll options unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,SAAS,QAAQ,UAAU,QAAQ;CAC3C,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAE1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC,EAClC;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;EAAE;CACnF;AACD"}
1
+ {"version":3,"file":"resolvePollOptionsRequest.js","names":[],"sources":["../../src/poll/resolvePollOptionsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nexport type ResolvePollOptionsRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq?: PayloadRequest\n}\n\nexport type ResolvePollOptionsRequestResult = {\n\tstatus: number\n\tbody: { options: PollOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:\n * authenticated callers get the poll's effective options (source-resolved or authored, exactly the\n * set outcome validation accepts), anonymous callers are always refused. Statuses mirror the\n * results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option\n * source fails (fail closed).\n */\nexport const resolvePollOptionsRequest = async (\n\targs: ResolvePollOptionsRequestArgs\n): Promise<ResolvePollOptionsRequestResult> => {\n\tconst { payload, formId, isAuthed, req } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\t// Load under the caller's own read access (no overrideAccess): a tenant-scoped `forms.access.read`\n\t// then refuses a cross-tenant form id (null -> 404), so an authed user cannot read another tenant's\n\t// poll options.\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolveEffectivePollOptions({ payload, req, form })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Poll options unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,SAAS,QAAQ,UAAU,QAAQ;CAC3C,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAK1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG;CAAI,CAAC,EAC9D,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC,EAClC;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;EAAE;CACnF;AACD"}
@@ -1,18 +1,13 @@
1
1
  import { isNamedField } from "../fields/fieldKey.js";
2
+ import { isTruthyString } from "../submissions/coerceBoolean.js";
2
3
  //#region src/prefill/valuesFromSearchParams.ts
3
- const TRUTHY = new Set([
4
- "true",
5
- "1",
6
- "on",
7
- "yes"
8
- ]);
9
4
  const coerce = (kind, raw, all) => {
10
5
  switch (kind) {
11
6
  case "number": {
12
7
  const n = Number(raw);
13
8
  return Number.isNaN(n) ? void 0 : n;
14
9
  }
15
- case "boolean": return TRUTHY.has(raw.toLowerCase());
10
+ case "boolean": return isTruthyString(raw);
16
11
  case "text[]": return all;
17
12
  default: return raw;
18
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"valuesFromSearchParams.js","names":[],"sources":["../../src/prefill/valuesFromSearchParams.ts"],"sourcesContent":["import { isNamedField } from '../fields/fieldKey'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { FormFieldValueKind } from '../fields/types'\nimport type { FormFieldInstance } from '../submissions/types'\n\nexport type PrefillOptions = {\n\t/** Map a query-param name to a field name. Unmapped params use their own name. */\n\tmap?: Record<string, string>\n\t/** If set, only these field names may be prefilled. */\n\tallow?: string[]\n\t/** These field names may never be prefilled. */\n\tdeny?: string[]\n}\n\nconst TRUTHY = new Set(['true', '1', 'on', 'yes'])\n\nconst coerce = (kind: FormFieldValueKind, raw: string, all: string[]): unknown => {\n\tswitch (kind) {\n\t\tcase 'number': {\n\t\t\tconst n = Number(raw)\n\t\t\treturn Number.isNaN(n) ? undefined : n\n\t\t}\n\t\tcase 'boolean':\n\t\t\treturn TRUTHY.has(raw.toLowerCase())\n\t\tcase 'text[]':\n\t\t\treturn all\n\t\tdefault:\n\t\t\treturn raw\n\t}\n}\n\n/**\n * Map URL/query params to typed initial values for KNOWN fields only, coerced by each field's value\n * kind. Unknown, denied, or un-allowed params are ignored; invalid numbers are dropped. Prefilled\n * values are never trusted -- they still validate on submit. Pass the result to `<Form initialValues>`.\n */\n// biome-ignore lint/complexity/useMaxParams: four positional args are the minimal surface (params, fields, registry, options)\nexport const valuesFromSearchParams = (\n\tparams: URLSearchParams | Record<string, string>,\n\tfields: FormFieldInstance[],\n\tregistry: FieldTypeRegistry,\n\toptions: PrefillOptions = {}\n): Record<string, unknown> => {\n\tconst isSearch = params instanceof URLSearchParams\n\tconst paramKeys = isSearch ? [...new Set(params.keys())] : Object.keys(params)\n\tconst byName = new Map(fields.filter(isNamedField).map((field) => [field.name, field]))\n\tconst result: Record<string, unknown> = {}\n\n\tfor (const param of paramKeys) {\n\t\tconst fieldName = options.map?.[param] ?? param\n\n\t\tif (options.allow && !options.allow.includes(fieldName)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (options.deny?.includes(fieldName)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst field = byName.get(fieldName)\n\t\tconst definition = field && registry.get(field.blockType)\n\t\t// 'none'-kind (display-only) fields carry no value, so a param under their name is ignored.\n\t\tif (!field || !definition || definition.value === 'none') {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst all = isSearch ? params.getAll(param) : [params[param] as string]\n\t\tconst value = coerce(definition.value, all[0] ?? '', all)\n\t\tif (value !== undefined) {\n\t\t\tresult[fieldName] = value\n\t\t}\n\t}\n\n\treturn result\n}\n"],"mappings":";;AAcA,MAAM,SAAS,IAAI,IAAI;CAAC;CAAQ;CAAK;CAAM;AAAK,CAAC;AAEjD,MAAM,UAAU,MAA0B,KAAa,QAA2B;CACjF,QAAQ,MAAR;EACC,KAAK,UAAU;GACd,MAAM,IAAI,OAAO,GAAG;GACpB,OAAO,OAAO,MAAM,CAAC,IAAI,KAAA,IAAY;EACtC;EACA,KAAK,WACJ,OAAO,OAAO,IAAI,IAAI,YAAY,CAAC;EACpC,KAAK,UACJ,OAAO;EACR,SACC,OAAO;CACT;AACD;;;;;;AAQA,MAAa,0BACZ,QACA,QACA,UACA,UAA0B,CAAC,MACE;CAC7B,MAAM,WAAW,kBAAkB;CACnC,MAAM,YAAY,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,MAAM;CAC7E,MAAM,SAAS,IAAI,IAAI,OAAO,OAAO,YAAY,EAAE,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CACtF,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,SAAS,WAAW;EAC9B,MAAM,YAAY,QAAQ,MAAM,UAAU;EAE1C,IAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,SAAS,SAAS,GACrD;EAED,IAAI,QAAQ,MAAM,SAAS,SAAS,GACnC;EAGD,MAAM,QAAQ,OAAO,IAAI,SAAS;EAClC,MAAM,aAAa,SAAS,SAAS,IAAI,MAAM,SAAS;EAExD,IAAI,CAAC,SAAS,CAAC,cAAc,WAAW,UAAU,QACjD;EAGD,MAAM,MAAM,WAAW,OAAO,OAAO,KAAK,IAAI,CAAC,OAAO,MAAgB;EACtE,MAAM,QAAQ,OAAO,WAAW,OAAO,IAAI,MAAM,IAAI,GAAG;EACxD,IAAI,UAAU,KAAA,GACb,OAAO,aAAa;CAEtB;CAEA,OAAO;AACR"}
1
+ {"version":3,"file":"valuesFromSearchParams.js","names":[],"sources":["../../src/prefill/valuesFromSearchParams.ts"],"sourcesContent":["import { isNamedField } from '../fields/fieldKey'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { FormFieldValueKind } from '../fields/types'\nimport { isTruthyString } from '../submissions/coerceBoolean'\nimport type { FormFieldInstance } from '../submissions/types'\n\nexport type PrefillOptions = {\n\t/** Map a query-param name to a field name. Unmapped params use their own name. */\n\tmap?: Record<string, string>\n\t/** If set, only these field names may be prefilled. */\n\tallow?: string[]\n\t/** These field names may never be prefilled. */\n\tdeny?: string[]\n}\n\nconst coerce = (kind: FormFieldValueKind, raw: string, all: string[]): unknown => {\n\tswitch (kind) {\n\t\tcase 'number': {\n\t\t\tconst n = Number(raw)\n\t\t\treturn Number.isNaN(n) ? undefined : n\n\t\t}\n\t\tcase 'boolean':\n\t\t\treturn isTruthyString(raw)\n\t\tcase 'text[]':\n\t\t\treturn all\n\t\tdefault:\n\t\t\treturn raw\n\t}\n}\n\n/**\n * Map URL/query params to typed initial values for KNOWN fields only, coerced by each field's value\n * kind. Unknown, denied, or un-allowed params are ignored; invalid numbers are dropped. Prefilled\n * values are never trusted -- they still validate on submit. Pass the result to `<Form initialValues>`.\n */\n// biome-ignore lint/complexity/useMaxParams: four positional args are the minimal surface (params, fields, registry, options)\nexport const valuesFromSearchParams = (\n\tparams: URLSearchParams | Record<string, string>,\n\tfields: FormFieldInstance[],\n\tregistry: FieldTypeRegistry,\n\toptions: PrefillOptions = {}\n): Record<string, unknown> => {\n\tconst isSearch = params instanceof URLSearchParams\n\tconst paramKeys = isSearch ? [...new Set(params.keys())] : Object.keys(params)\n\tconst byName = new Map(fields.filter(isNamedField).map((field) => [field.name, field]))\n\tconst result: Record<string, unknown> = {}\n\n\tfor (const param of paramKeys) {\n\t\tconst fieldName = options.map?.[param] ?? param\n\n\t\tif (options.allow && !options.allow.includes(fieldName)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (options.deny?.includes(fieldName)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst field = byName.get(fieldName)\n\t\tconst definition = field && registry.get(field.blockType)\n\t\t// 'none'-kind (display-only) fields carry no value, so a param under their name is ignored.\n\t\tif (!field || !definition || definition.value === 'none') {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst all = isSearch ? params.getAll(param) : [params[param] as string]\n\t\tconst value = coerce(definition.value, all[0] ?? '', all)\n\t\tif (value !== undefined) {\n\t\t\tresult[fieldName] = value\n\t\t}\n\t}\n\n\treturn result\n}\n"],"mappings":";;;AAeA,MAAM,UAAU,MAA0B,KAAa,QAA2B;CACjF,QAAQ,MAAR;EACC,KAAK,UAAU;GACd,MAAM,IAAI,OAAO,GAAG;GACpB,OAAO,OAAO,MAAM,CAAC,IAAI,KAAA,IAAY;EACtC;EACA,KAAK,WACJ,OAAO,eAAe,GAAG;EAC1B,KAAK,UACJ,OAAO;EACR,SACC,OAAO;CACT;AACD;;;;;;AAQA,MAAa,0BACZ,QACA,QACA,UACA,UAA0B,CAAC,MACE;CAC7B,MAAM,WAAW,kBAAkB;CACnC,MAAM,YAAY,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,MAAM;CAC7E,MAAM,SAAS,IAAI,IAAI,OAAO,OAAO,YAAY,EAAE,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CACtF,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,SAAS,WAAW;EAC9B,MAAM,YAAY,QAAQ,MAAM,UAAU;EAE1C,IAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,SAAS,SAAS,GACrD;EAED,IAAI,QAAQ,MAAM,SAAS,SAAS,GACnC;EAGD,MAAM,QAAQ,OAAO,IAAI,SAAS;EAClC,MAAM,aAAa,SAAS,SAAS,IAAI,MAAM,SAAS;EAExD,IAAI,CAAC,SAAS,CAAC,cAAc,WAAW,UAAU,QACjD;EAGD,MAAM,MAAM,WAAW,OAAO,OAAO,KAAK,IAAI,CAAC,OAAO,MAAgB;EACtE,MAAM,QAAQ,OAAO,WAAW,OAAO,IAAI,MAAM,IAAI,GAAG;EACxD,IAAI,UAAU,KAAA,GACb,OAAO,aAAa;CAEtB;CAEA,OAAO;AACR"}