@10x-media/form-builder 0.1.0-beta.5 → 0.1.0-beta.7
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.
- package/CHANGELOG.md +102 -0
- package/dist/actions/buildActionBlocks.js +12 -9
- package/dist/actions/buildActionBlocks.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +27 -79
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +17 -0
- package/dist/actions/builtin/emailAction.js +83 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +14 -84
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +9 -11
- package/dist/actions/builtin/index.js +11 -11
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +2 -2
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +116 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/registry.d.ts +2 -1
- package/dist/actions/registry.js +2 -6
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/task.js +5 -3
- package/dist/actions/task.js.map +1 -1
- package/dist/calc/evaluate.js +1 -2
- package/dist/calc/evaluate.js.map +1 -1
- package/dist/calc/normalizeCalc.js +1 -2
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/types.js.map +1 -1
- package/dist/client/ConsentBlockLabel.d.ts +15 -0
- package/dist/client/ConsentBlockLabel.js +71 -0
- package/dist/client/ConsentBlockLabel.js.map +1 -0
- package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
- package/dist/client/ConsentSourceRowLabel.js +21 -0
- package/dist/client/ConsentSourceRowLabel.js.map +1 -0
- package/dist/client/EndpointOptionsSelect.d.ts +1 -0
- package/dist/client/EndpointOptionsSelect.js +4 -1
- package/dist/client/EndpointOptionsSelect.js.map +1 -1
- package/dist/client/FieldNameSelect.d.ts +1 -0
- package/dist/client/FieldNameSelect.js +4 -1
- package/dist/client/FieldNameSelect.js.map +1 -1
- package/dist/client/RecipientsSelect.d.ts +31 -0
- package/dist/client/RecipientsSelect.js +174 -0
- package/dist/client/RecipientsSelect.js.map +1 -0
- package/dist/client/admin.css +5 -0
- package/dist/collections/buttonFields.js +1 -1
- package/dist/collections/formSubmissions.js +4 -2
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +46 -99
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/formsEndpoints.js +101 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/conditions/evaluate.js +1 -1
- package/dist/conditions/evaluate.js.map +1 -1
- package/dist/conditions/fieldTypes.d.ts +3 -1
- package/dist/conditions/fieldTypes.js +4 -1
- package/dist/conditions/fieldTypes.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +18 -5
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/captureConsent.d.ts +21 -5
- package/dist/consent/captureConsent.js +31 -7
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +8 -0
- package/dist/consent/consentSourcesField.js +13 -2
- package/dist/consent/consentSourcesField.js.map +1 -1
- package/dist/consent/resolveConsentEntries.d.ts +1 -1
- package/dist/consent/resolveConsentEntries.js +1 -4
- package/dist/consent/resolveConsentEntries.js.map +1 -1
- package/dist/consent/resolveConsentSourcesRequest.js +0 -1
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
- package/dist/consent/resolveConsentStatements.d.ts +1 -1
- package/dist/consent/resolveConsentStatements.js.map +1 -1
- package/dist/consent/types.d.ts +13 -1
- package/dist/email/departments.d.ts +13 -9
- package/dist/email/departments.js +7 -50
- package/dist/email/departments.js.map +1 -1
- package/dist/email/departmentsField.d.ts +4 -0
- package/dist/email/departmentsField.js +5 -1
- package/dist/email/departmentsField.js.map +1 -1
- package/dist/exports/client.d.ts +4 -1
- package/dist/exports/client.js +4 -1
- package/dist/exports/react.d.ts +3 -2
- package/dist/exports/react.js +4 -3
- package/dist/exports/rsc.js +1 -1
- package/dist/exports/types.d.ts +1 -1
- package/dist/fields/buildFieldBlocks.js +4 -4
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/consent.js +6 -1
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/email.js +1 -1
- package/dist/fields/builtin/email.js.map +1 -1
- package/dist/fields/builtin/file.js +9 -3
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +1 -1
- package/dist/fields/builtin/index.js +3 -3
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +1 -1
- package/dist/fields/builtin/repeater.js +1 -1
- package/dist/fields/builtin/select.js +1 -1
- package/dist/fields/registry.d.ts +2 -1
- package/dist/fields/registry.js +2 -6
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +1 -1
- package/dist/fields/types.d.ts +9 -2
- package/dist/flow/normalizeFlow.js +7 -3
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/form/toFormDocument.d.ts +20 -3
- package/dist/form/toFormDocument.js +16 -4
- package/dist/form/toFormDocument.js.map +1 -1
- package/dist/form/types.d.ts +5 -5
- package/dist/index.d.ts +5 -193
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +228 -0
- package/dist/plugin/applyRegistryConfig.d.ts +11 -0
- package/dist/plugin/applyRegistryConfig.js +20 -0
- package/dist/plugin/applyRegistryConfig.js.map +1 -0
- package/dist/plugin/registerCollections.js +3 -1
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +3 -0
- package/dist/plugin/uploadsCollection.js +10 -1
- package/dist/plugin/uploadsCollection.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -2
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- package/dist/poll/definePollOptionSource.d.ts +2 -2
- package/dist/poll/definePollOptionSource.js.map +1 -1
- package/dist/poll/pollTypeRegistry.js +2 -4
- package/dist/poll/pollTypeRegistry.js.map +1 -1
- package/dist/poll/registry.d.ts +2 -1
- package/dist/poll/registry.js +2 -6
- package/dist/poll/registry.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +0 -1
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
- package/dist/prefill/valuesFromSearchParams.js +2 -7
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +3 -1
- package/dist/react/Form.js +93 -155
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +5 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormFields.d.ts +18 -0
- package/dist/react/FormFields.js +114 -0
- package/dist/react/FormFields.js.map +1 -0
- package/dist/react/FormLayout.d.ts +4 -2
- package/dist/react/FormLayout.js +3 -2
- package/dist/react/FormLayout.js.map +1 -1
- package/dist/react/Poll.js +13 -3
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/presentation/DialogSurface.js +1 -4
- package/dist/react/presentation/DialogSurface.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -3
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -3
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +2 -3
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +1 -3
- package/dist/react/renderers/date.js.map +1 -1
- package/dist/react/renderers/email.js +1 -3
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +6 -7
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/number.js +1 -3
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +1 -3
- package/dist/react/renderers/region.js.map +1 -1
- package/dist/react/renderers/repeater.js +20 -4
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -3
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -3
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -3
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/spam/constants.js +7 -1
- package/dist/spam/constants.js.map +1 -1
- package/dist/spam/resolveSpam.js +2 -1
- package/dist/spam/resolveSpam.js.map +1 -1
- package/dist/spam/spamGuard.js +22 -6
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +17 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +33 -26
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- package/dist/submissions/coerceBoolean.js +14 -0
- package/dist/submissions/coerceBoolean.js.map +1 -0
- package/dist/submissions/runSubmission.js +9 -8
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/validateSubmission.js +4 -2
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/translations/de.js +14 -2
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +14 -2
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +14 -2
- package/dist/translations/keys.js +14 -2
- package/dist/translations/keys.js.map +1 -1
- package/dist/translations/server.js +8 -1
- package/dist/translations/server.js.map +1 -1
- package/dist/uploads/captureFileRef.d.ts +10 -3
- package/dist/uploads/captureFileRef.js +18 -9
- package/dist/uploads/captureFileRef.js.map +1 -1
- package/dist/uploads/resolveFileRef.js +2 -1
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -1
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/emailPattern.js +10 -0
- package/dist/validation/emailPattern.js.map +1 -0
- package/dist/validation/registry.d.ts +2 -1
- package/dist/validation/registry.js +2 -9
- package/dist/validation/registry.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,52 +1,9 @@
|
|
|
1
|
-
import { localizedIf } from "../fields/localizedIf.js";
|
|
2
|
-
import { keys } from "../translations/keys.js";
|
|
3
|
-
import { asTranslate, labelFor } from "../translations/server.js";
|
|
4
1
|
//#region src/email/departments.ts
|
|
5
|
-
const TO_FIELD_REF = "@10x-media/form-builder/client#EndpointOptionsSelect";
|
|
6
2
|
/**
|
|
7
|
-
*
|
|
8
|
-
* fine, otherwise the value must be one of the resolver's options for this request. A throwing
|
|
9
|
-
* resolver fails closed with a translated message rather than surfacing a raw error on save.
|
|
10
|
-
*
|
|
11
|
-
* Payload runs this on every save, not only when `to` changed, so while the resolver is down no form
|
|
12
|
-
* carrying an email-team action with a `to` set can be saved at all, including edits that never touch
|
|
13
|
-
* the address. That is the deliberate trade: failing open would persist a routing target the host can
|
|
14
|
-
* no longer vouch for. A resolver reaching a flaky upstream should cache or fall back internally
|
|
15
|
-
* rather than throw.
|
|
16
|
-
*/
|
|
17
|
-
const validateToField = (resolver) => async (value, { req }) => {
|
|
18
|
-
if (typeof value !== "string" || value.length === 0) return true;
|
|
19
|
-
let options;
|
|
20
|
-
try {
|
|
21
|
-
options = await resolver({ req });
|
|
22
|
-
} catch {
|
|
23
|
-
return asTranslate(req.t)(keys.validationToUnavailable);
|
|
24
|
-
}
|
|
25
|
-
return options.some((option) => option.value === value) ? true : asTranslate(req.t)(keys.validationToUnknown);
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* The `emailTeam` `to` select when `email.departments` is set: an `EndpointOptionsSelect` backed by
|
|
29
|
-
* the forms collection's `/:id/departments` endpoint (registered only then). Localized (following
|
|
30
|
-
* `localizeContent`), so each admin locale stores its own resolved department address and the
|
|
31
|
-
* submission's locale selects the stored `to` at send. Mirrors `buildFromField`; the route's document
|
|
32
|
-
* id goes unused server-side (the option set is request-scoped, not per-form).
|
|
33
|
-
*/
|
|
34
|
-
const buildToField = (resolver, localizeContent) => ({
|
|
35
|
-
name: "to",
|
|
36
|
-
type: "text",
|
|
37
|
-
label: labelFor(keys.actionConfigTo),
|
|
38
|
-
validate: validateToField(resolver),
|
|
39
|
-
...localizedIf(localizeContent),
|
|
40
|
-
admin: { components: { Field: {
|
|
41
|
-
path: TO_FIELD_REF,
|
|
42
|
-
clientProps: { endpoint: "departments" }
|
|
43
|
-
} } }
|
|
44
|
-
});
|
|
45
|
-
/**
|
|
46
|
-
* Authorize and resolve the `GET /:id/departments` request backing the `to` select: authenticated
|
|
3
|
+
* Authorize and resolve the `GET /:id/departments` request backing the recipient selects: authenticated
|
|
47
4
|
* callers get the host resolver's current options for this request; anonymous callers are always
|
|
48
|
-
* refused. The route id is unused (
|
|
49
|
-
* 403 unauthenticated, 503 when the resolver throws (fail closed).
|
|
5
|
+
* refused. The route id is unused (the option set is request-scoped, not per-form). Statuses mirror
|
|
6
|
+
* `resolveFromAddressesRequest`: 403 unauthenticated, 503 when the resolver throws (fail closed).
|
|
50
7
|
*/
|
|
51
8
|
const resolveDepartmentsRequest = async (args) => {
|
|
52
9
|
const { isAuthed, req, resolver } = args;
|
|
@@ -93,9 +50,9 @@ const pickLocaleValue = (value, reqLocale, defaultLocale, localeCodes) => {
|
|
|
93
50
|
}
|
|
94
51
|
};
|
|
95
52
|
/**
|
|
96
|
-
* Build the `emailTeam`
|
|
97
|
-
* a tenants row, wherever the host placed it), fetched at `locale: 'all'`. Each row's `label`
|
|
98
|
-
* `email` are resolved for `req.locale` via the current -> default -> next-available chain (see
|
|
53
|
+
* Build the `emailTeam` recipient options from a `departmentsField()`-bearing document (a settings
|
|
54
|
+
* global, a tenants row, wherever the host placed it), fetched at `locale: 'all'`. Each row's `label`
|
|
55
|
+
* and `email` are resolved for `req.locale` via the current -> default -> next-available chain (see
|
|
99
56
|
* {@link pickLocaleValue}), reading `defaultLocale`/`localeCodes` from `payload.config.localization`
|
|
100
57
|
* (a bare string is used as-is when localization is disabled). Rows with no resolvable email are
|
|
101
58
|
* dropped; a row's label is resolved independently of its email and falls back to the email when
|
|
@@ -125,6 +82,6 @@ const resolveDepartmentOptions = (args) => {
|
|
|
125
82
|
return options;
|
|
126
83
|
};
|
|
127
84
|
//#endregion
|
|
128
|
-
export {
|
|
85
|
+
export { resolveDepartmentOptions, resolveDepartmentsRequest };
|
|
129
86
|
|
|
130
87
|
//# sourceMappingURL=departments.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"departments.js","names":[],"sources":["../../src/email/departments.ts"],"sourcesContent":["import type { Payload, PayloadRequest, TextField } from 'payload'\nimport { localizedIf } from '../fields/localizedIf'\nimport { keys } from '../translations/keys'\nimport { asTranslate, labelFor } from '../translations/server'\n\n/** One selectable department for the `emailTeam` action; `value` is the email address routed to. */\nexport type DepartmentOption = { label: string; value: string }\n\n/**\n * Host seam resolving the selectable departments for `emailTeam`'s `to` (plugin option\n * `email.departments`). Req-scoped, so multi-tenant hosts derive tenant scoping from `req` (host\n * header, cookie, or auth context) and return only that tenant's departments. `value` is the literal\n * string `payload.sendEmail` accepts as `to` (an email address); `label` is how the department is\n * named in the admin select. Mirrors {@link FromAddressesResolver}. {@link resolveDepartmentOptions}\n * builds this from a `departmentsField()`-bearing document with the reusable locale fallback, so a\n * host does not reimplement it.\n */\nexport type DepartmentEmailsResolver = (args: {\n\treq: PayloadRequest\n}) => Promise<DepartmentOption[]> | DepartmentOption[]\n\nconst TO_FIELD_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/**\n * Validate for the `to` field, closed over the host resolver (mirrors `validateFromField`): unset is\n * fine, otherwise the value must be one of the resolver's options for this request. A throwing\n * resolver fails closed with a translated message rather than surfacing a raw error on save.\n *\n * Payload runs this on every save, not only when `to` changed, so while the resolver is down no form\n * carrying an email-team action with a `to` set can be saved at all, including edits that never touch\n * the address. That is the deliberate trade: failing open would persist a routing target the host can\n * no longer vouch for. A resolver reaching a flaky upstream should cache or fall back internally\n * rather than throw.\n */\nexport const validateToField =\n\t(resolver: DepartmentEmailsResolver) =>\n\tasync (value: unknown, { req }: { req: PayloadRequest }): Promise<string | true> => {\n\t\tif (typeof value !== 'string' || value.length === 0) {\n\t\t\treturn true\n\t\t}\n\t\tlet options: DepartmentOption[]\n\t\ttry {\n\t\t\toptions = await resolver({ req })\n\t\t} catch {\n\t\t\treturn asTranslate(req.t)(keys.validationToUnavailable)\n\t\t}\n\t\treturn options.some((option) => option.value === value)\n\t\t\t? true\n\t\t\t: asTranslate(req.t)(keys.validationToUnknown)\n\t}\n\n/**\n * The `emailTeam` `to` select when `email.departments` is set: an `EndpointOptionsSelect` backed by\n * the forms collection's `/:id/departments` endpoint (registered only then). Localized (following\n * `localizeContent`), so each admin locale stores its own resolved department address and the\n * submission's locale selects the stored `to` at send. Mirrors `buildFromField`; the route's document\n * id goes unused server-side (the option set is request-scoped, not per-form).\n */\nexport const buildToField = (\n\tresolver: DepartmentEmailsResolver,\n\tlocalizeContent: boolean\n): TextField => ({\n\tname: 'to',\n\ttype: 'text',\n\tlabel: labelFor(keys.actionConfigTo),\n\tvalidate: validateToField(resolver),\n\t...localizedIf(localizeContent),\n\tadmin: {\n\t\tcomponents: {\n\t\t\tField: {\n\t\t\t\tpath: TO_FIELD_REF,\n\t\t\t\tclientProps: {\n\t\t\t\t\tendpoint: 'departments',\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n})\n\nexport type ResolveDepartmentsRequestArgs = {\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: DepartmentEmailsResolver\n}\n\nexport type ResolveDepartmentsRequestResult = {\n\tstatus: number\n\tbody: { options: DepartmentOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/departments` request backing the `to` select: authenticated\n * callers get the host resolver's current options for this request; anonymous callers are always\n * refused. The route id is unused (see `buildToField`). Statuses mirror `resolveFromAddressesRequest`:\n * 403 unauthenticated, 503 when the resolver throws (fail closed).\n */\nexport const resolveDepartmentsRequest = async (\n\targs: ResolveDepartmentsRequestArgs\n): Promise<ResolveDepartmentsRequestResult> => {\n\tconst { isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolver({ req })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Departments unavailable' }] } }\n\t}\n}\n\n/**\n * Resolve one localized subfield value for `reqLocale` with the current -> default -> next-available\n * chain. A non-localized subfield (fetched at `locale: 'all'` on a host without localization) is a\n * bare string, used as-is; a localized one is a `{ [locale]: value }` map, from which `reqLocale`,\n * then `defaultLocale`, then the first non-empty configured locale wins. Empty strings are skipped\n * (they fall through the chain), and undefined is returned when nothing resolves.\n */\n// biome-ignore lint/complexity/useMaxParams: the locale-fallback inputs are the minimal surface (value, reqLocale, defaultLocale, localeCodes)\nexport const pickLocaleValue = (\n\tvalue: unknown,\n\treqLocale: string | undefined,\n\tdefaultLocale: string | undefined,\n\tlocaleCodes: string[]\n): string | undefined => {\n\tif (typeof value === 'string') {\n\t\treturn value.length > 0 ? value : undefined\n\t}\n\tif (value == null || typeof value !== 'object') {\n\t\treturn undefined\n\t}\n\tconst map = value as Record<string, unknown>\n\tconst nonEmpty = (entry: unknown): string | undefined =>\n\t\ttypeof entry === 'string' && entry.length > 0 ? entry : undefined\n\tconst ordered = [reqLocale, defaultLocale, ...localeCodes].filter(\n\t\t(code): code is string => typeof code === 'string' && code.length > 0\n\t)\n\tfor (const code of ordered) {\n\t\tconst hit = nonEmpty(map[code])\n\t\tif (hit !== undefined) {\n\t\t\treturn hit\n\t\t}\n\t}\n\t// Defensive: a `locale: 'all'` fetch keys by configured locales, but a key outside them still\n\t// counts toward \"next available\" so a value is never silently dropped.\n\tfor (const key of Object.keys(map)) {\n\t\tconst hit = nonEmpty(map[key])\n\t\tif (hit !== undefined) {\n\t\t\treturn hit\n\t\t}\n\t}\n\treturn undefined\n}\n\nexport type ResolveDepartmentOptionsArgs = {\n\tpayload: Payload\n\treq: PayloadRequest\n\t/** A document fetched at `locale: 'all'`, so each localized subfield is a `{ locale: value }` map. */\n\tdoc: Record<string, unknown>\n\t/** The array field name the departments live under; defaults to `departmentEmails`. */\n\tfield?: string\n}\n\n/**\n * Build the `emailTeam` `to` options from a `departmentsField()`-bearing document (a settings global,\n * a tenants row, wherever the host placed it), fetched at `locale: 'all'`. Each row's `label` and\n * `email` are resolved for `req.locale` via the current -> default -> next-available chain (see\n * {@link pickLocaleValue}), reading `defaultLocale`/`localeCodes` from `payload.config.localization`\n * (a bare string is used as-is when localization is disabled). Rows with no resolvable email are\n * dropped; a row's label is resolved independently of its email and falls back to the email when\n * absent. This is the reusable seam a host's `email.departments` resolver returns, so tenant scoping\n * (which document is read) stays the host's call while the locale fallback stays consistent.\n */\nexport const resolveDepartmentOptions = (\n\targs: ResolveDepartmentOptionsArgs\n): DepartmentOption[] => {\n\tconst { payload, req, doc, field } = args\n\tconst rows = doc[field ?? 'departmentEmails']\n\tif (!Array.isArray(rows)) {\n\t\treturn []\n\t}\n\tconst localization = payload.config.localization\n\tconst localeCodes = localization ? localization.localeCodes : []\n\tconst defaultLocale = localization ? localization.defaultLocale : undefined\n\tconst reqLocale = typeof req.locale === 'string' ? req.locale : undefined\n\tconst options: DepartmentOption[] = []\n\tfor (const row of rows) {\n\t\tif (row == null || typeof row !== 'object') {\n\t\t\tcontinue\n\t\t}\n\t\tconst entry = row as Record<string, unknown>\n\t\tconst email = pickLocaleValue(entry.email, reqLocale, defaultLocale, localeCodes)\n\t\tif (email === undefined) {\n\t\t\tcontinue\n\t\t}\n\t\tconst label = pickLocaleValue(entry.label, reqLocale, defaultLocale, localeCodes) ?? email\n\t\toptions.push({ label, value: email })\n\t}\n\treturn options\n}\n"],"mappings":";;;;AAqBA,MAAM,eAAe;;;;;;;;;;;;AAarB,MAAa,mBACX,aACD,OAAO,OAAgB,EAAE,UAA2D;CACnF,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GACjD,OAAO;CAER,IAAI;CACJ,IAAI;EACH,UAAU,MAAM,SAAS,EAAE,IAAI,CAAC;CACjC,QAAQ;EACP,OAAO,YAAY,IAAI,CAAC,EAAE,KAAK,uBAAuB;CACvD;CACA,OAAO,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,IACnD,OACA,YAAY,IAAI,CAAC,EAAE,KAAK,mBAAmB;AAC/C;;;;;;;;AASD,MAAa,gBACZ,UACA,qBACgB;CAChB,MAAM;CACN,MAAM;CACN,OAAO,SAAS,KAAK,cAAc;CACnC,UAAU,gBAAgB,QAAQ;CAClC,GAAG,YAAY,eAAe;CAC9B,OAAO,EACN,YAAY,EACX,OAAO;EACN,MAAM;EACN,aAAa,EACZ,UAAU,cACX;CACD,EACD,EACD;AACD;;;;;;;AAoBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,UAAU,KAAK,aAAa;CACpC,IAAI,CAAC,UACJ,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,SAAS,EAAE,IAAI,CAAC,EACA;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC,EAAE;EAAE;CAClF;AACD;;;;;;;;AAUA,MAAa,mBACZ,OACA,WACA,eACA,gBACwB;CACxB,IAAI,OAAO,UAAU,UACpB,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;CAEnC,IAAI,SAAS,QAAQ,OAAO,UAAU,UACrC;CAED,MAAM,MAAM;CACZ,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;CACzD,MAAM,UAAU;EAAC;EAAW;EAAe,GAAG;CAAW,EAAE,QACzD,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CACrE;CACA,KAAK,MAAM,QAAQ,SAAS;EAC3B,MAAM,MAAM,SAAS,IAAI,KAAK;EAC9B,IAAI,QAAQ,KAAA,GACX,OAAO;CAET;CAGA,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAAG;EACnC,MAAM,MAAM,SAAS,IAAI,IAAI;EAC7B,IAAI,QAAQ,KAAA,GACX,OAAO;CAET;AAED;;;;;;;;;;;AAqBA,MAAa,4BACZ,SACwB;CACxB,MAAM,EAAE,SAAS,KAAK,KAAK,UAAU;CACrC,MAAM,OAAO,IAAI,SAAS;CAC1B,IAAI,CAAC,MAAM,QAAQ,IAAI,GACtB,OAAO,CAAC;CAET,MAAM,eAAe,QAAQ,OAAO;CACpC,MAAM,cAAc,eAAe,aAAa,cAAc,CAAC;CAC/D,MAAM,gBAAgB,eAAe,aAAa,gBAAgB,KAAA;CAClE,MAAM,YAAY,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,KAAA;CAChE,MAAM,UAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,MAAM;EACvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,UACjC;EAED,MAAM,QAAQ;EACd,MAAM,QAAQ,gBAAgB,MAAM,OAAO,WAAW,eAAe,WAAW;EAChF,IAAI,UAAU,KAAA,GACb;EAED,MAAM,QAAQ,gBAAgB,MAAM,OAAO,WAAW,eAAe,WAAW,KAAK;EACrF,QAAQ,KAAK;GAAE;GAAO,OAAO;EAAM,CAAC;CACrC;CACA,OAAO;AACR"}
|
|
1
|
+
{"version":3,"file":"departments.js","names":[],"sources":["../../src/email/departments.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\n/** One selectable department for the `emailTeam` recipients; `value` is the email address routed to. */\nexport type DepartmentOption = { label: string; value: string }\n\n/**\n * Host seam resolving the selectable departments for `emailTeam`'s recipient fields (plugin option\n * `email.departments`). Req-scoped, so multi-tenant hosts derive tenant scoping from `req` (host\n * header, cookie, or auth context) and return only that tenant's departments. `value` is the literal\n * string `payload.sendEmail` accepts (an email address); `label` is how the department is named in the\n * admin select. Mirrors {@link FromAddressesResolver}. {@link resolveDepartmentOptions} builds this\n * from a `departmentsField()`-bearing document with the reusable locale fallback, so a host does not\n * reimplement it.\n *\n * Do not thread the save's `req` into a `find({ locale: 'all' })` from within a form's save\n * validation: it flips `req.locale` to `all` and corrupts the localized write of the document being\n * saved. Read the settings document without the save's `req` (already-committed data, no transaction).\n */\nexport type DepartmentEmailsResolver = (args: {\n\treq: PayloadRequest\n}) => Promise<DepartmentOption[]> | DepartmentOption[]\n\nexport type ResolveDepartmentsRequestArgs = {\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq: PayloadRequest\n\tresolver: DepartmentEmailsResolver\n}\n\nexport type ResolveDepartmentsRequestResult = {\n\tstatus: number\n\tbody: { options: DepartmentOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/departments` request backing the recipient selects: authenticated\n * callers get the host resolver's current options for this request; anonymous callers are always\n * refused. The route id is unused (the option set is request-scoped, not per-form). Statuses mirror\n * `resolveFromAddressesRequest`: 403 unauthenticated, 503 when the resolver throws (fail closed).\n */\nexport const resolveDepartmentsRequest = async (\n\targs: ResolveDepartmentsRequestArgs\n): Promise<ResolveDepartmentsRequestResult> => {\n\tconst { isAuthed, req, resolver } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolver({ req })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Departments unavailable' }] } }\n\t}\n}\n\n/**\n * Resolve one localized subfield value for `reqLocale` with the current -> default -> next-available\n * chain. A non-localized subfield (fetched at `locale: 'all'` on a host without localization) is a\n * bare string, used as-is; a localized one is a `{ [locale]: value }` map, from which `reqLocale`,\n * then `defaultLocale`, then the first non-empty configured locale wins. Empty strings are skipped\n * (they fall through the chain), and undefined is returned when nothing resolves.\n */\n// biome-ignore lint/complexity/useMaxParams: the locale-fallback inputs are the minimal surface (value, reqLocale, defaultLocale, localeCodes)\nexport const pickLocaleValue = (\n\tvalue: unknown,\n\treqLocale: string | undefined,\n\tdefaultLocale: string | undefined,\n\tlocaleCodes: string[]\n): string | undefined => {\n\tif (typeof value === 'string') {\n\t\treturn value.length > 0 ? value : undefined\n\t}\n\tif (value == null || typeof value !== 'object') {\n\t\treturn undefined\n\t}\n\tconst map = value as Record<string, unknown>\n\tconst nonEmpty = (entry: unknown): string | undefined =>\n\t\ttypeof entry === 'string' && entry.length > 0 ? entry : undefined\n\tconst ordered = [reqLocale, defaultLocale, ...localeCodes].filter(\n\t\t(code): code is string => typeof code === 'string' && code.length > 0\n\t)\n\tfor (const code of ordered) {\n\t\tconst hit = nonEmpty(map[code])\n\t\tif (hit !== undefined) {\n\t\t\treturn hit\n\t\t}\n\t}\n\t// Defensive: a `locale: 'all'` fetch keys by configured locales, but a key outside them still\n\t// counts toward \"next available\" so a value is never silently dropped.\n\tfor (const key of Object.keys(map)) {\n\t\tconst hit = nonEmpty(map[key])\n\t\tif (hit !== undefined) {\n\t\t\treturn hit\n\t\t}\n\t}\n\treturn undefined\n}\n\nexport type ResolveDepartmentOptionsArgs = {\n\tpayload: Payload\n\treq: PayloadRequest\n\t/** A document fetched at `locale: 'all'`, so each localized subfield is a `{ locale: value }` map. */\n\tdoc: Record<string, unknown>\n\t/** The array field name the departments live under; defaults to `departmentEmails`. */\n\tfield?: string\n}\n\n/**\n * Build the `emailTeam` recipient options from a `departmentsField()`-bearing document (a settings\n * global, a tenants row, wherever the host placed it), fetched at `locale: 'all'`. Each row's `label`\n * and `email` are resolved for `req.locale` via the current -> default -> next-available chain (see\n * {@link pickLocaleValue}), reading `defaultLocale`/`localeCodes` from `payload.config.localization`\n * (a bare string is used as-is when localization is disabled). Rows with no resolvable email are\n * dropped; a row's label is resolved independently of its email and falls back to the email when\n * absent. This is the reusable seam a host's `email.departments` resolver returns, so tenant scoping\n * (which document is read) stays the host's call while the locale fallback stays consistent.\n */\nexport const resolveDepartmentOptions = (\n\targs: ResolveDepartmentOptionsArgs\n): DepartmentOption[] => {\n\tconst { payload, req, doc, field } = args\n\tconst rows = doc[field ?? 'departmentEmails']\n\tif (!Array.isArray(rows)) {\n\t\treturn []\n\t}\n\tconst localization = payload.config.localization\n\tconst localeCodes = localization ? localization.localeCodes : []\n\tconst defaultLocale = localization ? localization.defaultLocale : undefined\n\tconst reqLocale = typeof req.locale === 'string' ? req.locale : undefined\n\tconst options: DepartmentOption[] = []\n\tfor (const row of rows) {\n\t\tif (row == null || typeof row !== 'object') {\n\t\t\tcontinue\n\t\t}\n\t\tconst entry = row as Record<string, unknown>\n\t\tconst email = pickLocaleValue(entry.email, reqLocale, defaultLocale, localeCodes)\n\t\tif (email === undefined) {\n\t\t\tcontinue\n\t\t}\n\t\tconst label = pickLocaleValue(entry.label, reqLocale, defaultLocale, localeCodes) ?? email\n\t\toptions.push({ label, value: email })\n\t}\n\treturn options\n}\n"],"mappings":";;;;;;;AAwCA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,UAAU,KAAK,aAAa;CACpC,IAAI,CAAC,UACJ,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,SAAS,EAAE,IAAI,CAAC,EACA;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC,EAAE;EAAE;CAClF;AACD;;;;;;;;AAUA,MAAa,mBACZ,OACA,WACA,eACA,gBACwB;CACxB,IAAI,OAAO,UAAU,UACpB,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;CAEnC,IAAI,SAAS,QAAQ,OAAO,UAAU,UACrC;CAED,MAAM,MAAM;CACZ,MAAM,YAAY,UACjB,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;CACzD,MAAM,UAAU;EAAC;EAAW;EAAe,GAAG;CAAW,EAAE,QACzD,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,CACrE;CACA,KAAK,MAAM,QAAQ,SAAS;EAC3B,MAAM,MAAM,SAAS,IAAI,KAAK;EAC9B,IAAI,QAAQ,KAAA,GACX,OAAO;CAET;CAGA,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,GAAG;EACnC,MAAM,MAAM,SAAS,IAAI,IAAI;EAC7B,IAAI,QAAQ,KAAA,GACX,OAAO;CAET;AAED;;;;;;;;;;;AAqBA,MAAa,4BACZ,SACwB;CACxB,MAAM,EAAE,SAAS,KAAK,KAAK,UAAU;CACrC,MAAM,OAAO,IAAI,SAAS;CAC1B,IAAI,CAAC,MAAM,QAAQ,IAAI,GACtB,OAAO,CAAC;CAET,MAAM,eAAe,QAAQ,OAAO;CACpC,MAAM,cAAc,eAAe,aAAa,cAAc,CAAC;CAC/D,MAAM,gBAAgB,eAAe,aAAa,gBAAgB,KAAA;CAClE,MAAM,YAAY,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,KAAA;CAChE,MAAM,UAA8B,CAAC;CACrC,KAAK,MAAM,OAAO,MAAM;EACvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,UACjC;EAED,MAAM,QAAQ;EACd,MAAM,QAAQ,gBAAgB,MAAM,OAAO,WAAW,eAAe,WAAW;EAChF,IAAI,UAAU,KAAA,GACb;EAED,MAAM,QAAQ,gBAAgB,MAAM,OAAO,WAAW,eAAe,WAAW,KAAK;EACrF,QAAQ,KAAK;GAAE;GAAO,OAAO;EAAM,CAAC;CACrC;CACA,OAAO;AACR"}
|
|
@@ -42,6 +42,10 @@ type DepartmentsFieldOptions = {
|
|
|
42
42
|
* },
|
|
43
43
|
* })
|
|
44
44
|
* ```
|
|
45
|
+
*
|
|
46
|
+
* The read above omits the save's `req` on purpose: threading it into a `locale: 'all'` read from
|
|
47
|
+
* within a form's save validation flips `req.locale` to `all` and corrupts the localized write of the
|
|
48
|
+
* document being saved. It reads already-committed data, so no transaction (`req`) is needed.
|
|
45
49
|
*/
|
|
46
50
|
declare const departmentsField: (options?: DepartmentsFieldOptions) => ArrayField;
|
|
47
51
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { localizedIf } from "../fields/localizedIf.js";
|
|
2
1
|
import { keys } from "../translations/keys.js";
|
|
3
2
|
import { labelForKey } from "../translations/server.js";
|
|
3
|
+
import { localizedIf } from "../fields/localizedIf.js";
|
|
4
4
|
//#region src/email/departmentsField.ts
|
|
5
5
|
/**
|
|
6
6
|
* The department-emails array, for the host to place on any collection or global they own: a
|
|
@@ -33,6 +33,10 @@ import { labelForKey } from "../translations/server.js";
|
|
|
33
33
|
* },
|
|
34
34
|
* })
|
|
35
35
|
* ```
|
|
36
|
+
*
|
|
37
|
+
* The read above omits the save's `req` on purpose: threading it into a `locale: 'all'` read from
|
|
38
|
+
* within a form's save validation flips `req.locale` to `all` and corrupts the localized write of the
|
|
39
|
+
* document being saved. It reads already-committed data, so no transaction (`req`) is needed.
|
|
36
40
|
*/
|
|
37
41
|
const departmentsField = (options = {}) => {
|
|
38
42
|
const localize = options.localized !== false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"departmentsField.js","names":[],"sources":["../../src/email/departmentsField.ts"],"sourcesContent":["import type { ArrayField } from 'payload'\nimport { localizedIf } from '../fields/localizedIf'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nexport type DepartmentsFieldOptions = {\n\t/** Field name, i.e. what the row array is stored under. Defaults to `departmentEmails`. */\n\tname?: string\n\t/** Field label. Defaults to the plugin's translated one. */\n\tlabel?: ArrayField['label']\n\t/**\n\t * Whether each row's `email` (and its `label`) carry `localized: true`, so the same field can\n\t * hold a different address per locale. Default `true`; Payload strips the flag on hosts without\n\t * `localization`, so it is safe either way.\n\t */\n\tlocalized?: boolean\n}\n\n/**\n * The department-emails array, for the host to place on any collection or global they own: a\n * settings global, a tenants collection, a departments parent, wherever the addresses belong. The\n * plugin never registers it and never guesses where it lives; the `email.departments` resolver reads\n * it back, which is also where multi-tenant scoping goes: place this on the tenant-scoped document\n * and have the resolver return only that tenant's addresses, derived from `req`.\n *\n * A row is a `label` (how the address is named when routing a submission to it) and the `email` it\n * resolves to. Because `email` is `localized` by default, a host can hold a different address per\n * locale without changing the stored shape. The array itself is not localized, so per-locale edits\n * must address existing rows by their own auto-assigned `id` rather than re-sending id-less rows.\n *\n * It renders condensed: `admin.components.Field` is {@link CondensedArray}, which lays each row out\n * flat as `[label, email]` at 50% each with no per-row collapsible, while keeping Payload's native\n * per-locale storage and row ids.\n *\n * ```ts\n * // The host's own collection or global:\n * fields: [departmentsField()]\n *\n * // The plugin, reading it back to route a submission. `resolveDepartmentOptions` reads the array\n * // (fetched at `locale: 'all'`) and resolves each row's address for the requesting locale:\n * formBuilder({\n * email: {\n * departments: async ({ req }) => {\n * const settings = await req.payload.findGlobal({ slug: 'settings', depth: 0, locale: 'all' })\n * return resolveDepartmentOptions({ payload: req.payload, req, doc: settings })\n * },\n * },\n * })\n * ```\n */\nexport const departmentsField = (options: DepartmentsFieldOptions = {}): ArrayField => {\n\tconst localize = options.localized !== false\n\n\treturn {\n\t\tname: options.name ?? 'departmentEmails',\n\t\ttype: 'array',\n\t\tlabel: options.label ?? labelForKey(keys.departmentsField),\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.departmentSingular),\n\t\t\tplural: labelForKey(keys.departmentPlural),\n\t\t},\n\t\tadmin: {\n\t\t\tdescription: labelForKey(keys.departmentsFieldDescription),\n\t\t\tcomponents: { Field: '@10x-media/form-builder/client#CondensedArray' },\n\t\t},\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'label',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.departmentLabel),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'email',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.departmentEmail),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t],\n\t}\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"departmentsField.js","names":[],"sources":["../../src/email/departmentsField.ts"],"sourcesContent":["import type { ArrayField } from 'payload'\nimport { localizedIf } from '../fields/localizedIf'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nexport type DepartmentsFieldOptions = {\n\t/** Field name, i.e. what the row array is stored under. Defaults to `departmentEmails`. */\n\tname?: string\n\t/** Field label. Defaults to the plugin's translated one. */\n\tlabel?: ArrayField['label']\n\t/**\n\t * Whether each row's `email` (and its `label`) carry `localized: true`, so the same field can\n\t * hold a different address per locale. Default `true`; Payload strips the flag on hosts without\n\t * `localization`, so it is safe either way.\n\t */\n\tlocalized?: boolean\n}\n\n/**\n * The department-emails array, for the host to place on any collection or global they own: a\n * settings global, a tenants collection, a departments parent, wherever the addresses belong. The\n * plugin never registers it and never guesses where it lives; the `email.departments` resolver reads\n * it back, which is also where multi-tenant scoping goes: place this on the tenant-scoped document\n * and have the resolver return only that tenant's addresses, derived from `req`.\n *\n * A row is a `label` (how the address is named when routing a submission to it) and the `email` it\n * resolves to. Because `email` is `localized` by default, a host can hold a different address per\n * locale without changing the stored shape. The array itself is not localized, so per-locale edits\n * must address existing rows by their own auto-assigned `id` rather than re-sending id-less rows.\n *\n * It renders condensed: `admin.components.Field` is {@link CondensedArray}, which lays each row out\n * flat as `[label, email]` at 50% each with no per-row collapsible, while keeping Payload's native\n * per-locale storage and row ids.\n *\n * ```ts\n * // The host's own collection or global:\n * fields: [departmentsField()]\n *\n * // The plugin, reading it back to route a submission. `resolveDepartmentOptions` reads the array\n * // (fetched at `locale: 'all'`) and resolves each row's address for the requesting locale:\n * formBuilder({\n * email: {\n * departments: async ({ req }) => {\n * const settings = await req.payload.findGlobal({ slug: 'settings', depth: 0, locale: 'all' })\n * return resolveDepartmentOptions({ payload: req.payload, req, doc: settings })\n * },\n * },\n * })\n * ```\n *\n * The read above omits the save's `req` on purpose: threading it into a `locale: 'all'` read from\n * within a form's save validation flips `req.locale` to `all` and corrupts the localized write of the\n * document being saved. It reads already-committed data, so no transaction (`req`) is needed.\n */\nexport const departmentsField = (options: DepartmentsFieldOptions = {}): ArrayField => {\n\tconst localize = options.localized !== false\n\n\treturn {\n\t\tname: options.name ?? 'departmentEmails',\n\t\ttype: 'array',\n\t\tlabel: options.label ?? labelForKey(keys.departmentsField),\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.departmentSingular),\n\t\t\tplural: labelForKey(keys.departmentPlural),\n\t\t},\n\t\tadmin: {\n\t\t\tdescription: labelForKey(keys.departmentsFieldDescription),\n\t\t\tcomponents: { Field: '@10x-media/form-builder/client#CondensedArray' },\n\t\t},\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'label',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.departmentLabel),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'email',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelForKey(keys.departmentEmail),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t],\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,oBAAoB,UAAmC,CAAC,MAAkB;CACtF,MAAM,WAAW,QAAQ,cAAc;CAEvC,OAAO;EACN,MAAM,QAAQ,QAAQ;EACtB,MAAM;EACN,OAAO,QAAQ,SAAS,YAAY,KAAK,gBAAgB;EACzD,QAAQ;GACP,UAAU,YAAY,KAAK,kBAAkB;GAC7C,QAAQ,YAAY,KAAK,gBAAgB;EAC1C;EACA,OAAO;GACN,aAAa,YAAY,KAAK,2BAA2B;GACzD,YAAY,EAAE,OAAO,gDAAgD;EACtE;EACA,QAAQ,CACP;GACC,MAAM;GACN,MAAM;GACN,OAAO,YAAY,KAAK,eAAe;GACvC,OAAO,EAAE,OAAO,MAAM;GACtB,GAAG,YAAY,QAAQ;EACxB,GACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,YAAY,KAAK,eAAe;GACvC,OAAO,EAAE,OAAO,MAAM;GACtB,GAAG,YAAY,QAAQ;EACxB,CACD;CACD;AACD"}
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { ByteSizeField } from "../client/ByteSizeField.js";
|
|
2
2
|
import { ClosePollButton } from "../client/ClosePollButton.js";
|
|
3
3
|
import { CondensedArray } from "../client/CondensedArray.js";
|
|
4
|
+
import { ConsentBlockLabel } from "../client/ConsentBlockLabel.js";
|
|
5
|
+
import { ConsentSourceRowLabel } from "../client/ConsentSourceRowLabel.js";
|
|
4
6
|
import { EndpointOptionsSelect } from "../client/EndpointOptionsSelect.js";
|
|
5
7
|
import { FieldBlockLabel } from "../client/FieldBlockLabel.js";
|
|
6
8
|
import { FieldNameSelect } from "../client/FieldNameSelect.js";
|
|
7
9
|
import { FlowBuilder } from "../client/FlowBuilder.js";
|
|
8
10
|
import { FormConditionField } from "../client/FormConditionField.js";
|
|
11
|
+
import { RecipientsSelect } from "../client/RecipientsSelect.js";
|
|
9
12
|
import { RuleDescription } from "../client/RuleDescription.js";
|
|
10
13
|
import { FieldCountCell } from "../collections/cells/FieldCountCell.js";
|
|
11
14
|
import { FlowStepsCell } from "../collections/cells/FlowStepsCell.js";
|
|
12
|
-
export { ByteSizeField, ClosePollButton, CondensedArray, EndpointOptionsSelect, FieldBlockLabel, FieldCountCell, FieldNameSelect, FlowBuilder, FlowStepsCell, FormConditionField, RuleDescription };
|
|
15
|
+
export { ByteSizeField, ClosePollButton, CondensedArray, ConsentBlockLabel, ConsentSourceRowLabel, EndpointOptionsSelect, FieldBlockLabel, FieldCountCell, FieldNameSelect, FlowBuilder, FlowStepsCell, FormConditionField, RecipientsSelect, RuleDescription };
|
package/dist/exports/client.js
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import { ByteSizeField } from "../client/ByteSizeField.js";
|
|
3
3
|
import { ClosePollButton } from "../client/ClosePollButton.js";
|
|
4
4
|
import { CondensedArray } from "../client/CondensedArray.js";
|
|
5
|
+
import { ConsentBlockLabel } from "../client/ConsentBlockLabel.js";
|
|
6
|
+
import { ConsentSourceRowLabel } from "../client/ConsentSourceRowLabel.js";
|
|
5
7
|
import { EndpointOptionsSelect } from "../client/EndpointOptionsSelect.js";
|
|
6
8
|
import { FieldBlockLabel } from "../client/FieldBlockLabel.js";
|
|
7
9
|
import { FieldNameSelect } from "../client/FieldNameSelect.js";
|
|
8
10
|
import { FlowBuilder } from "../client/FlowBuilder.js";
|
|
9
11
|
import { FormConditionField } from "../client/FormConditionField.js";
|
|
12
|
+
import { RecipientsSelect } from "../client/RecipientsSelect.js";
|
|
10
13
|
import { RuleDescription } from "../client/RuleDescription.js";
|
|
11
14
|
import { FieldCountCell } from "../collections/cells/FieldCountCell.js";
|
|
12
15
|
import { FlowStepsCell } from "../collections/cells/FlowStepsCell.js";
|
|
13
|
-
export { ByteSizeField, ClosePollButton, CondensedArray, EndpointOptionsSelect, FieldBlockLabel, FieldCountCell, FieldNameSelect, FlowBuilder, FlowStepsCell, FormConditionField, RuleDescription };
|
|
16
|
+
export { ByteSizeField, ClosePollButton, CondensedArray, ConsentBlockLabel, ConsentSourceRowLabel, EndpointOptionsSelect, FieldBlockLabel, FieldCountCell, FieldNameSelect, FlowBuilder, FlowStepsCell, FormConditionField, RecipientsSelect, RuleDescription };
|
package/dist/exports/react.d.ts
CHANGED
|
@@ -26,8 +26,10 @@ import { PresentationOption, PresentationRegistry, PresentationsConfig, resolveP
|
|
|
26
26
|
import { RendererOption, RendererRegistry, RenderersConfig, resolveRenderers } from "../react/registry.js";
|
|
27
27
|
import { SubmitFormResult, SubmitHandler, submitForm } from "../react/submitForm.js";
|
|
28
28
|
import { Form, FormProps } from "../react/Form.js";
|
|
29
|
+
import { RecallResolver, buildRecallResolver } from "../recall/resolver.js";
|
|
29
30
|
import { FieldErrors, FormAction, FormState } from "../react/state.js";
|
|
30
31
|
import { FormContextValue, FormControlLabels, FormStepInfo, useFormContext } from "../react/FormContext.js";
|
|
32
|
+
import { FormFields, FormFieldsProps } from "../react/FormFields.js";
|
|
31
33
|
import { FieldWidth, FormLayout, FormLayoutProps, widthProps } from "../react/FormLayout.js";
|
|
32
34
|
import { FormResults, FormResultsProps } from "../react/FormResults.js";
|
|
33
35
|
import { FormSteps, FormStepsProps } from "../react/FormSteps.js";
|
|
@@ -51,9 +53,8 @@ import { UseFieldResult, useField } from "../react/useField.js";
|
|
|
51
53
|
import { useFormState } from "../react/useFormState.js";
|
|
52
54
|
import { useFormStep } from "../react/useFormStep.js";
|
|
53
55
|
import { interpolate } from "../recall/interpolate.js";
|
|
54
|
-
import { RecallResolver, buildRecallResolver } from "../recall/resolver.js";
|
|
55
56
|
import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
|
|
56
57
|
import { en } from "../translations/en.js";
|
|
57
58
|
import { makeTranslate } from "../translations/makeTranslate.js";
|
|
58
59
|
import { formatBytes } from "../uploads/formatBytes.js";
|
|
59
|
-
export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, type BodyContext, CAPTCHA_TOKEN_KEY, COUNTRIES, type CalcExpression, type CaptchaWidgetHandle, Checkbox, type CheckboxProps, type ConsentStatement, type ConsentStatements, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldErrors, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormAction, type FormButtonSettings, type FormContextValue, type FormControlLabels, FormControls, type FormControlsProps, type FormDocument, type FormFieldInstance, FormLayout, type FormLayoutProps, type FormPollSettings, type FormPresentation, type FormProps, type FormResponseSettings, FormResults, type FormResultsProps, type FormState, type FormStepInfo, FormSteps, type FormStepsProps, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, type HcaptchaCaptchaProps, Honeypot, type HoneypotProps, Input, type InputProps, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, RECAPTCHA_SCRIPT_URL, type RecallResolver, RecaptchaCaptcha, type RecaptchaCaptchaHandle, type RecaptchaCaptchaProps, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmissionValue, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, TURNSTILE_SCRIPT_URL, Textarea, type TextareaProps, type ToFormDocumentOptions, TurnstileCaptcha, type TurnstileCaptchaProps, US_STATES, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
|
60
|
+
export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, type BodyContext, CAPTCHA_TOKEN_KEY, COUNTRIES, type CalcExpression, type CaptchaWidgetHandle, Checkbox, type CheckboxProps, type ConsentStatement, type ConsentStatements, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldErrors, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormAction, type FormButtonSettings, type FormContextValue, type FormControlLabels, FormControls, type FormControlsProps, type FormDocument, type FormFieldInstance, FormFields, type FormFieldsProps, FormLayout, type FormLayoutProps, type FormPollSettings, type FormPresentation, type FormProps, type FormResponseSettings, FormResults, type FormResultsProps, type FormState, type FormStepInfo, FormSteps, type FormStepsProps, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, type HcaptchaCaptchaProps, Honeypot, type HoneypotProps, Input, type InputProps, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, RECAPTCHA_SCRIPT_URL, type RecallResolver, RecaptchaCaptcha, type RecaptchaCaptchaHandle, type RecaptchaCaptchaProps, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmissionValue, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, TURNSTILE_SCRIPT_URL, Textarea, type TextareaProps, type ToFormDocumentOptions, TurnstileCaptcha, type TurnstileCaptchaProps, US_STATES, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
package/dist/exports/react.js
CHANGED
|
@@ -7,8 +7,8 @@ import { fieldKey } from "../fields/fieldKey.js";
|
|
|
7
7
|
import { evaluateCalc } from "../calc/evaluate.js";
|
|
8
8
|
import { computeCalcFields } from "../calc/computeCalcFields.js";
|
|
9
9
|
import { evaluateCondition } from "../conditions/evaluate.js";
|
|
10
|
-
import { en } from "../translations/en.js";
|
|
11
10
|
import { textOfBody } from "../actions/body/textOfBody.js";
|
|
11
|
+
import { en } from "../translations/en.js";
|
|
12
12
|
import { COUNTRIES, US_STATES } from "../fields/data/regions.js";
|
|
13
13
|
import { toFormDocument } from "../form/toFormDocument.js";
|
|
14
14
|
import { valuesFromSearchParams } from "../prefill/valuesFromSearchParams.js";
|
|
@@ -24,6 +24,8 @@ import { makeTranslate } from "../translations/makeTranslate.js";
|
|
|
24
24
|
import { useFormContext } from "../react/FormContext.js";
|
|
25
25
|
import { FormControls } from "../react/FormControls.js";
|
|
26
26
|
import { FormLayout, widthProps } from "../react/FormLayout.js";
|
|
27
|
+
import { useField } from "../react/useField.js";
|
|
28
|
+
import { FormFields } from "../react/FormFields.js";
|
|
27
29
|
import { Honeypot } from "../react/Honeypot.js";
|
|
28
30
|
import { Backdrop } from "../react/presentation/Backdrop.js";
|
|
29
31
|
import { useDismiss } from "../react/presentation/useDismiss.js";
|
|
@@ -38,7 +40,6 @@ import { Checkbox } from "../react/primitives/Checkbox.js";
|
|
|
38
40
|
import { Input } from "../react/primitives/Input.js";
|
|
39
41
|
import { uploadFile } from "../react/uploadFile.js";
|
|
40
42
|
import { Select } from "../react/primitives/Select.js";
|
|
41
|
-
import { useField } from "../react/useField.js";
|
|
42
43
|
import { Textarea } from "../react/primitives/Textarea.js";
|
|
43
44
|
import { defaultRenderers } from "../react/renderers/index.js";
|
|
44
45
|
import { submitForm } from "../react/submitForm.js";
|
|
@@ -49,4 +50,4 @@ import { fetchFormResults } from "../react/fetchResults.js";
|
|
|
49
50
|
import { Poll } from "../react/Poll.js";
|
|
50
51
|
import { useFormState } from "../react/useFormState.js";
|
|
51
52
|
import { useFormStep } from "../react/useFormStep.js";
|
|
52
|
-
export { Backdrop, CAPTCHA_TOKEN_KEY, COUNTRIES, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormControls, FormLayout, FormResults, FormSteps, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, Honeypot, Input, Poll, RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha, Select, TURNSTILE_SCRIPT_URL, Textarea, TurnstileCaptcha, US_STATES, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
|
53
|
+
export { Backdrop, CAPTCHA_TOKEN_KEY, COUNTRIES, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormControls, FormFields, FormLayout, FormResults, FormSteps, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, Honeypot, Input, Poll, RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha, Select, TURNSTILE_SCRIPT_URL, Textarea, TurnstileCaptcha, US_STATES, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, fieldKey, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, sanitizeUrl, serializeBody, stepFieldNames, submitForm, textOfBody, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormContext, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
package/dist/exports/rsc.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { resolveConsentStatements } from "../consent/resolveConsentStatements.js";
|
|
1
2
|
import { isPollClosed } from "../form/pollState.js";
|
|
2
3
|
import { resolvePollOptions } from "../poll/resolvePollOptions.js";
|
|
3
4
|
import { resolvePollOutcome } from "../poll/resolvePollOutcome.js";
|
|
4
5
|
import { resolveEffectivePollOptions } from "../poll/effectivePollOptions.js";
|
|
5
6
|
import { hasVotedCookie, votedCookieName } from "../submissions/votedCookie.js";
|
|
6
|
-
import { resolveConsentStatements } from "../consent/resolveConsentStatements.js";
|
|
7
7
|
import { toFormDocument } from "../form/toFormDocument.js";
|
|
8
8
|
import { SubmissionAnswers } from "../submissions/SubmissionAnswers.js";
|
|
9
9
|
export { SubmissionAnswers, hasVotedCookie, isPollClosed, resolveConsentStatements, resolveEffectivePollOptions, resolvePollOptions, resolvePollOutcome, toFormDocument, votedCookieName };
|
package/dist/exports/types.d.ts
CHANGED
|
@@ -32,5 +32,5 @@ import { CollectionOverrides, FieldsOverride } from "../plugin/collectionOverrid
|
|
|
32
32
|
import { CaptchaProvider, CaptchaVerifyArgs, IdentifyFn, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, SpamConfig, SpamMetadataConfig, SpamOption } from "../spam/types.js";
|
|
33
33
|
import { UploadsOption } from "../plugin/uploadsCollection.js";
|
|
34
34
|
import { ValidationRuleOption, ValidationRuleRegistry, ValidationRulesConfig } from "../validation/registry.js";
|
|
35
|
-
import { FormBuilderPluginOptions } from "../
|
|
35
|
+
import { FormBuilderPluginOptions } from "../options.js";
|
|
36
36
|
export type { ActionDefinition, ActionOption, ActionRegistry, ActionResult, ActionRunArgs, ActionsConfig, AggregateFieldResponsesArgs, AggregateFormResponsesArgs, AggregationBucket, AggregationRow, AnyActionDefinition, AnyFormFieldDefinition, AnyValidationRuleDefinition, ButtonFieldsOverride, ButtonsOption, CalcExpression, CaptchaProvider, CaptchaVerifyArgs, CollectionOverrides, ConsentProof, ConsentSourceEntry, ConsentSourceOption, ConsentSourcePage, ConsentSourcesFieldOptions, ConsentSourcesResolver, ConsentStatement, ConsentStatements, DefaultButtonFields, FieldAggregation, FieldCondition, FieldMeta, FieldTypeOption, FieldTypeRegistry, FieldTypesConfig, FieldsOverride, FileFieldConfig, FileRef, FileRefError, FlowStep, FlowTransition, FormBuilderPluginOptions, FormButtonSettings, FormDocument, FormEvent, FormEventSink, FormFieldDefinition, FormFieldFormat, FormFieldInstance, FormFieldValidate, FormFieldValueKind, FormFlow, FormPollSettings, FormResponseSettings, FormResultsAccess, FormResultsAccessArgs, FromAddressOption, FromAddressesResolver, IdentifyFn, OmittableSharedField, PollOptionSourceOption, PollOptionSourceRegistry, PollOptionSourcesConfig, PrefillOptions, PresentationDensity, PresentationDescriptor, PresentationSurface, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, RecallResolver, ResolveConsentEntriesArgs, ResolveConsentSourcesRequestArgs, ResolveConsentSourcesRequestResult, ResolveConsentStatementsArgs, ResolvePublishedVersionRefArgs, ResolveResultsRequestArgs, ResolveResultsRequestResult, RichTextBodyOption, SpamConfig, SpamMetadataConfig, SpamOption, SubmissionDescriptor, SubmissionFieldError, SubmissionStatusFilter, SubmissionValue, SubmissionWidth, UploadsOption, ValidationRuleDefinition, ValidationRuleOption, ValidationRuleRegistry, ValidationRuleResult, ValidationRulesConfig };
|
|
@@ -9,8 +9,8 @@ const FIELD_BLOCK_LABEL_REF = "@10x-media/form-builder/client#FieldBlockLabel";
|
|
|
9
9
|
* Replace Payload's default `blockName` row header (which reads "Untitled" until filled) with the
|
|
10
10
|
* field's own `label`, keyed to the type label as fallback. See `FieldBlockLabel`.
|
|
11
11
|
*/
|
|
12
|
-
const blockLabelAdmin = (typeLabelKey) => ({ components: { Label: {
|
|
13
|
-
path:
|
|
12
|
+
const blockLabelAdmin = (typeLabelKey, ref = FIELD_BLOCK_LABEL_REF) => ({ components: { Label: {
|
|
13
|
+
path: ref,
|
|
14
14
|
clientProps: { typeLabelKey }
|
|
15
15
|
} } });
|
|
16
16
|
/** Whether `fields` contains a field named `name`, descending into presentational rows. */
|
|
@@ -36,7 +36,7 @@ const buildFieldBlocks = ({ registry, ruleRegistry, localize = true }) => {
|
|
|
36
36
|
singular: labelFor(definition.label),
|
|
37
37
|
plural: labelFor(definition.label)
|
|
38
38
|
},
|
|
39
|
-
admin: blockLabelAdmin(definition.label),
|
|
39
|
+
admin: blockLabelAdmin(definition.label, definition.blockLabel),
|
|
40
40
|
fields: typeConfig
|
|
41
41
|
});
|
|
42
42
|
continue;
|
|
@@ -47,7 +47,7 @@ const buildFieldBlocks = ({ registry, ruleRegistry, localize = true }) => {
|
|
|
47
47
|
singular: labelFor(definition.label),
|
|
48
48
|
plural: labelFor(definition.label)
|
|
49
49
|
},
|
|
50
|
-
admin: blockLabelAdmin(definition.label),
|
|
50
|
+
admin: blockLabelAdmin(definition.label, definition.blockLabel),
|
|
51
51
|
fields: [fieldBlockTabs({
|
|
52
52
|
conditionTypes,
|
|
53
53
|
typeConfig,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field, TabsField } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\nimport { buildRuleBlocks } from '../validation/buildRuleBlocks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { FieldTypeRegistry } from './registry'\nimport { fieldBlockTabs } from './sharedConfig'\n\nconst FIELD_BLOCK_LABEL_REF = '@10x-media/form-builder/client#FieldBlockLabel'\n\n/**\n * Replace Payload's default `blockName` row header (which reads \"Untitled\" until filled) with the\n * field's own `label`, keyed to the type label as fallback. See `FieldBlockLabel`.\n */\nconst blockLabelAdmin = (
|
|
1
|
+
{"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field, TabsField } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\nimport { buildRuleBlocks } from '../validation/buildRuleBlocks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { FieldTypeRegistry } from './registry'\nimport { fieldBlockTabs } from './sharedConfig'\n\nconst FIELD_BLOCK_LABEL_REF = '@10x-media/form-builder/client#FieldBlockLabel'\n\n/**\n * Replace Payload's default `blockName` row header (which reads \"Untitled\" until filled) with the\n * field's own `label`, keyed to the type label as fallback. See `FieldBlockLabel`.\n */\nconst blockLabelAdmin = (\n\ttypeLabelKey: string,\n\tref: string = FIELD_BLOCK_LABEL_REF\n): NonNullable<Block['admin']> => ({\n\tcomponents: { Label: { path: ref, clientProps: { typeLabelKey } } },\n})\n\n/** Whether `fields` contains a field named `name`, descending into presentational rows. */\nconst hasNameField = (fields: Field[]): boolean =>\n\tfields.some((f) => {\n\t\tif ('name' in f && f.name === 'name') {\n\t\t\treturn true\n\t\t}\n\t\treturn f.type === 'row' && hasNameField(f.fields)\n\t})\n\n/** The tab whose fields hold the shared basics and type config, found by the `name` field it carries. */\nconst fieldTabOf = (block: Block): Field[] | undefined => {\n\tconst tabsField = block.fields.find((f): f is TabsField => f.type === 'tabs')\n\treturn tabsField?.tabs.find((tab) => hasNameField(tab.fields))?.fields\n}\n\n/**\n * One add-field block per registered type: tabs holding shared config, type config, and\n * validations. Bare definitions (e.g. `message`) skip all of that: their block is the\n * definition's `config` alone, no name/shared basics/tabs. `localize` controls whether the\n * shared content fields carry `localized: true`; per-type config fields carry their own flag\n * from the registry definitions.\n */\ntype BuildFieldBlocksArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tlocalize?: boolean\n}\n\nexport const buildFieldBlocks = ({\n\tregistry,\n\truleRegistry,\n\tlocalize = true,\n}: BuildFieldBlocksArgs): Block[] => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst blocks: Block[] = []\n\tconst bareSlugs = new Set<string>()\n\tfor (const definition of registry.values()) {\n\t\tconst typeConfig: Field[] = definition.config ?? []\n\n\t\tif (definition.bare === true) {\n\t\t\tbareSlugs.add(definition.type)\n\t\t\tblocks.push({\n\t\t\t\tslug: definition.type,\n\t\t\t\tlabels: { singular: labelFor(definition.label), plural: labelFor(definition.label) },\n\t\t\t\tadmin: blockLabelAdmin(definition.label, definition.blockLabel),\n\t\t\t\tfields: typeConfig,\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tblocks.push({\n\t\t\tslug: definition.type,\n\t\t\tlabels: { singular: labelFor(definition.label), plural: labelFor(definition.label) },\n\t\t\tadmin: blockLabelAdmin(definition.label, definition.blockLabel),\n\t\t\tfields: [\n\t\t\t\tfieldBlockTabs({\n\t\t\t\t\tconditionTypes,\n\t\t\t\t\ttypeConfig,\n\t\t\t\t\tvalidations: {\n\t\t\t\t\t\tname: 'validations',\n\t\t\t\t\t\ttype: 'blocks',\n\t\t\t\t\t\tlabel: labelFor(keys.validationsLabel),\n\t\t\t\t\t\tblocks: buildRuleBlocks(ruleRegistry, definition.type),\n\t\t\t\t\t},\n\t\t\t\t\tlocalize,\n\t\t\t\t\tomitShared: definition.omitShared,\n\t\t\t\t}),\n\t\t\t],\n\t\t})\n\t}\n\n\t// Second pass: inject subFields into the repeater block using all non-repeater blocks.\n\t// Done after the main loop so every sibling block is already built.\n\t// Repeater-in-repeater is not supported in v1; the repeater block is excluded from subFields.\n\t// Bare blocks are excluded too: repeater row plumbing (values, composite error paths) is name-keyed.\n\tconst repeaterBlock = blocks.find((b) => b.slug === 'repeater')\n\tif (repeaterBlock) {\n\t\tconst subFieldsField: Field = {\n\t\t\tname: 'subFields',\n\t\t\ttype: 'blocks',\n\t\t\tlabel: labelFor(keys.configSubFields),\n\t\t\tblocks: blocks.filter((b) => b.slug !== 'repeater' && !bareSlugs.has(b.slug)),\n\t\t}\n\t\tconst fieldTabFields = fieldTabOf(repeaterBlock)\n\t\t;(fieldTabFields ?? (repeaterBlock.fields as Field[])).push(subFieldsField)\n\t}\n\n\treturn blocks\n}\n"],"mappings":";;;;;;AASA,MAAM,wBAAwB;;;;;AAM9B,MAAM,mBACL,cACA,MAAc,2BACoB,EAClC,YAAY,EAAE,OAAO;CAAE,MAAM;CAAK,aAAa,EAAE,aAAa;AAAE,EAAE,EACnE;;AAGA,MAAM,gBAAgB,WACrB,OAAO,MAAM,MAAM;CAClB,IAAI,UAAU,KAAK,EAAE,SAAS,QAC7B,OAAO;CAER,OAAO,EAAE,SAAS,SAAS,aAAa,EAAE,MAAM;AACjD,CAAC;;AAGF,MAAM,cAAc,UAAsC;CAEzD,OADkB,MAAM,OAAO,MAAM,MAAsB,EAAE,SAAS,MACvD,GAAG,KAAK,MAAM,QAAQ,aAAa,IAAI,MAAM,CAAC,GAAG;AACjE;AAeA,MAAa,oBAAoB,EAChC,UACA,cACA,WAAW,WACyB;CACpC,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,SAAkB,CAAC;CACzB,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,MAAM,aAAsB,WAAW,UAAU,CAAC;EAElD,IAAI,WAAW,SAAS,MAAM;GAC7B,UAAU,IAAI,WAAW,IAAI;GAC7B,OAAO,KAAK;IACX,MAAM,WAAW;IACjB,QAAQ;KAAE,UAAU,SAAS,WAAW,KAAK;KAAG,QAAQ,SAAS,WAAW,KAAK;IAAE;IACnF,OAAO,gBAAgB,WAAW,OAAO,WAAW,UAAU;IAC9D,QAAQ;GACT,CAAC;GACD;EACD;EAEA,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU,SAAS,WAAW,KAAK;IAAG,QAAQ,SAAS,WAAW,KAAK;GAAE;GACnF,OAAO,gBAAgB,WAAW,OAAO,WAAW,UAAU;GAC9D,QAAQ,CACP,eAAe;IACd;IACA;IACA,aAAa;KACZ,MAAM;KACN,MAAM;KACN,OAAO,SAAS,KAAK,gBAAgB;KACrC,QAAQ,gBAAgB,cAAc,WAAW,IAAI;IACtD;IACA;IACA,YAAY,WAAW;GACxB,CAAC,CACF;EACD,CAAC;CACF;CAMA,MAAM,gBAAgB,OAAO,MAAM,MAAM,EAAE,SAAS,UAAU;CAC9D,IAAI,eAAe;EAClB,MAAM,iBAAwB;GAC7B,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,eAAe;GACpC,QAAQ,OAAO,QAAQ,MAAM,EAAE,SAAS,cAAc,CAAC,UAAU,IAAI,EAAE,IAAI,CAAC;EAC7E;EAEC,CADsB,WAAW,aACnB,KAAM,cAAc,QAAoB,KAAK,cAAc;CAC3E;CAEA,OAAO;AACR"}
|
|
@@ -19,7 +19,12 @@ const consentField = defineFormField({
|
|
|
19
19
|
type: "consent",
|
|
20
20
|
label: keys.fieldTypeConsent,
|
|
21
21
|
value: "boolean",
|
|
22
|
-
omitShared: [
|
|
22
|
+
omitShared: [
|
|
23
|
+
"label",
|
|
24
|
+
"placeholder",
|
|
25
|
+
"description"
|
|
26
|
+
],
|
|
27
|
+
blockLabel: "@10x-media/form-builder/client#ConsentBlockLabel",
|
|
23
28
|
config: [{
|
|
24
29
|
name: "source",
|
|
25
30
|
type: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consent.js","names":[],"sources":["../../../src/fields/builtin/consent.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelForKey } from '../../translations/server'\nimport { defineFormField } from '../defineFormField'\n\ntype ConsentConfig = { source?: string; required?: boolean }\n\nconst SOURCE_FIELD_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/**\n * Consent is a reference, so the field is a reference: the author picks a source and nothing else.\n * The statement, the policy page, and its version all live with the source (see\n * `consentSourcesField`), so there is no wording to copy per form, no URL to paste, and no version\n * to type; correcting a statement corrects every form pointing at it. `label` and `placeholder` are\n * dropped for the same reason: the statement is the visible text and the checkbox's accessible name.\n *\n * Registered only when the plugin's `consent.sources` resolver is set, since a consent field with\n * no source to reference has nothing to say and nothing to prove.\n *\n * The intrinsic validator owns `required`: the engine's shared required guard only fires on an\n * empty value, and `false` (the coerced state of both an unchecked box and an absent answer) is a\n * present one, so without this a required consent would accept an explicit refusal.\n */\nexport const consentField = defineFormField<'boolean', ConsentConfig>({\n\ttype: 'consent',\n\tlabel: keys.fieldTypeConsent,\n\tvalue: 'boolean',\n\tomitShared: ['label', 'placeholder'],\n\tconfig: [\n\t\t{\n\t\t\tname: 'source',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.consentConfigSource),\n\t\t\tadmin: {\n\t\t\t\tcomponents: {\n\t\t\t\t\tField: {\n\t\t\t\t\t\tpath: SOURCE_FIELD_REF,\n\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\tendpoint: 'consent-sources',\n\t\t\t\t\t\t\tdescriptionKey: keys.consentConfigSourceDescription,\n\t\t\t\t\t\t\tisClearable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t],\n\tvalidate: ({ value, config, t }) =>\n\t\tconfig.required === true && value !== true ? t(keys.validationRequired) : true,\n\tformat: ({ value, t }) => t(value === true ? keys.formatYes : keys.formatNo),\n})\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,MAAa,eAAe,gBAA0C;CACrE,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,YAAY,
|
|
1
|
+
{"version":3,"file":"consent.js","names":[],"sources":["../../../src/fields/builtin/consent.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelForKey } from '../../translations/server'\nimport { defineFormField } from '../defineFormField'\n\ntype ConsentConfig = { source?: string; required?: boolean }\n\nconst SOURCE_FIELD_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/**\n * Consent is a reference, so the field is a reference: the author picks a source and nothing else.\n * The statement, the policy page, and its version all live with the source (see\n * `consentSourcesField`), so there is no wording to copy per form, no URL to paste, and no version\n * to type; correcting a statement corrects every form pointing at it. `label` and `placeholder` are\n * dropped for the same reason: the statement is the visible text and the checkbox's accessible name.\n *\n * Registered only when the plugin's `consent.sources` resolver is set, since a consent field with\n * no source to reference has nothing to say and nothing to prove.\n *\n * The intrinsic validator owns `required`: the engine's shared required guard only fires on an\n * empty value, and `false` (the coerced state of both an unchecked box and an absent answer) is a\n * present one, so without this a required consent would accept an explicit refusal.\n */\nexport const consentField = defineFormField<'boolean', ConsentConfig>({\n\ttype: 'consent',\n\tlabel: keys.fieldTypeConsent,\n\tvalue: 'boolean',\n\tomitShared: ['label', 'placeholder', 'description'],\n\tblockLabel: '@10x-media/form-builder/client#ConsentBlockLabel',\n\tconfig: [\n\t\t{\n\t\t\tname: 'source',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.consentConfigSource),\n\t\t\tadmin: {\n\t\t\t\tcomponents: {\n\t\t\t\t\tField: {\n\t\t\t\t\t\tpath: SOURCE_FIELD_REF,\n\t\t\t\t\t\tclientProps: {\n\t\t\t\t\t\t\tendpoint: 'consent-sources',\n\t\t\t\t\t\t\tdescriptionKey: keys.consentConfigSourceDescription,\n\t\t\t\t\t\t\tisClearable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t],\n\tvalidate: ({ value, config, t }) =>\n\t\tconfig.required === true && value !== true ? t(keys.validationRequired) : true,\n\tformat: ({ value, t }) => t(value === true ? keys.formatYes : keys.formatNo),\n})\n"],"mappings":";;;;;;;;;;;;;;;;;AAsBA,MAAa,eAAe,gBAA0C;CACrE,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,YAAY;EAAC;EAAS;EAAe;CAAa;CAClD,YAAY;CACZ,QAAQ,CACP;EACC,MAAM;EACN,MAAM;EACN,OAAO,YAAY,KAAK,mBAAmB;EAC3C,OAAO,EACN,YAAY,EACX,OAAO;GACN,MAAM;GACN,aAAa;IACZ,UAAU;IACV,gBAAgB,KAAK;IACrB,aAAa;GACd;EACD,EACD,EACD;CACD,CACD;CACA,WAAW,EAAE,OAAO,QAAQ,QAC3B,OAAO,aAAa,QAAQ,UAAU,OAAO,EAAE,KAAK,kBAAkB,IAAI;CAC3E,SAAS,EAAE,OAAO,QAAQ,EAAE,UAAU,OAAO,KAAK,YAAY,KAAK,QAAQ;AAC5E,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "../../translations/keys.js";
|
|
2
2
|
import { defineFormField } from "../defineFormField.js";
|
|
3
|
+
import { EMAIL_PATTERN } from "../../validation/emailPattern.js";
|
|
3
4
|
//#region src/fields/builtin/email.ts
|
|
4
|
-
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5
5
|
const emailField = defineFormField({
|
|
6
6
|
type: "email",
|
|
7
7
|
label: keys.fieldTypeEmail,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.js","names":[],"sources":["../../../src/fields/builtin/email.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineFormField } from '../defineFormField'\n\
|
|
1
|
+
{"version":3,"file":"email.js","names":[],"sources":["../../../src/fields/builtin/email.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { EMAIL_PATTERN } from '../../validation/emailPattern'\nimport { defineFormField } from '../defineFormField'\n\nexport const emailField = defineFormField<'text'>({\n\ttype: 'email',\n\tlabel: keys.fieldTypeEmail,\n\tvalue: 'text',\n\tvalidate: ({ value, t }) => {\n\t\tif (value == null || value === '') {\n\t\t\treturn true\n\t\t}\n\t\treturn EMAIL_PATTERN.test(value) ? true : t(keys.validationEmail)\n\t},\n\tformat: ({ value }) => (value == null ? '' : String(value)),\n})\n"],"mappings":";;;;AAIA,MAAa,aAAa,gBAAwB;CACjD,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,WAAW,EAAE,OAAO,QAAQ;EAC3B,IAAI,SAAS,QAAQ,UAAU,IAC9B,OAAO;EAER,OAAO,cAAc,KAAK,KAAK,IAAI,OAAO,EAAE,KAAK,eAAe;CACjE;CACA,SAAS,EAAE,YAAa,SAAS,OAAO,KAAK,OAAO,KAAK;AAC1D,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { keys } from "../../translations/keys.js";
|
|
2
2
|
import { labelFor } from "../../translations/server.js";
|
|
3
3
|
import { defineFormField } from "../defineFormField.js";
|
|
4
|
+
import { mimeAllowed } from "../../uploads/resolveFileRef.js";
|
|
4
5
|
//#region src/fields/builtin/file.ts
|
|
5
6
|
const isFileRef = (value) => typeof value === "object" && value !== null && "filename" in value;
|
|
6
7
|
const MAX_SIZE_REF = "@10x-media/form-builder/client#ByteSizeField";
|
|
@@ -83,8 +84,12 @@ const fileMimeTypeOptions = [
|
|
|
83
84
|
* so the client renderer knows where to POST; the server never trusts it at submit time (plugin config
|
|
84
85
|
* stays the source of truth). Named `uploadsCollection` because `collection` is a mongoose-reserved
|
|
85
86
|
* schema pathname.
|
|
87
|
+
*
|
|
88
|
+
* `allowedMimeTypes` (the host upload collection's `upload.mimeTypes`, threaded from the plugin
|
|
89
|
+
* factory) constrains the picker's MIME options to what the collection actually accepts; omitted
|
|
90
|
+
* keeps the full list. Admin-time only: the collection and `captureFileRef` remain authoritative.
|
|
86
91
|
*/
|
|
87
|
-
const
|
|
92
|
+
const buildFileField = (allowedMimeTypes) => defineFormField({
|
|
88
93
|
type: "file",
|
|
89
94
|
label: keys.fieldTypeFile,
|
|
90
95
|
value: "file",
|
|
@@ -99,7 +104,7 @@ const fileField = defineFormField({
|
|
|
99
104
|
name: "mimeTypes",
|
|
100
105
|
type: "select",
|
|
101
106
|
hasMany: true,
|
|
102
|
-
options: fileMimeTypeOptions,
|
|
107
|
+
options: fileMimeTypeOptions.filter((option) => mimeAllowed(option.value, allowedMimeTypes)),
|
|
103
108
|
label: labelFor(keys.fileConfigMimeTypes)
|
|
104
109
|
},
|
|
105
110
|
{
|
|
@@ -115,7 +120,8 @@ const fileField = defineFormField({
|
|
|
115
120
|
],
|
|
116
121
|
format: ({ value }) => isFileRef(value) ? value.filename : ""
|
|
117
122
|
});
|
|
123
|
+
buildFileField();
|
|
118
124
|
//#endregion
|
|
119
|
-
export {
|
|
125
|
+
export { buildFileField, fileMimeTypeOptions };
|
|
120
126
|
|
|
121
127
|
//# sourceMappingURL=file.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.js","names":[],"sources":["../../../src/fields/builtin/file.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport type { FileRef } from '../../uploads/types'\nimport { defineFormField } from '../defineFormField'\n\ntype FileConfig = {\n\tuploadsCollection?: string\n\tmimeTypes?: string[]\n\tmaxSize?: number\n}\n\nconst isFileRef = (value: unknown): value is FileRef =>\n\ttypeof value === 'object' && value !== null && 'filename' in value\n\nconst MAX_SIZE_REF = '@10x-media/form-builder/client#ByteSizeField'\n\n/**\n * Curated MIME type choices for the file field's `mimeTypes` select. Values are raw MIME strings or\n * trailing-`*` prefix wildcards, exactly the patterns `resolveFileRef` matches server-side; `type/*`\n * values double as the file input's `accept` attribute (browsers ignore other prefix patterns, which\n * is fine since the server stays authoritative). Every value must be 63 bytes or less: Payload's\n * db-postgres adapter stores select options as a pg enum, and pg enum labels cap at 63 bytes.\n */\nexport const fileMimeTypeOptions = [\n\t{ label: 'Any image', value: 'image/*' },\n\t{ label: 'PNG image', value: 'image/png' },\n\t{ label: 'JPEG image', value: 'image/jpeg' },\n\t{ label: 'WebP image', value: 'image/webp' },\n\t{ label: 'GIF image', value: 'image/gif' },\n\t{ label: 'SVG image', value: 'image/svg+xml' },\n\t{ label: 'PDF', value: 'application/pdf' },\n\t{ label: 'Plain text', value: 'text/plain' },\n\t{ label: 'CSV', value: 'text/csv' },\n\t{ label: 'ZIP archive', value: 'application/zip' },\n\t{ label: 'Word document (.doc)', value: 'application/msword' },\n\t{ label: 'Excel spreadsheet (.xls)', value: 'application/vnd.ms-excel' },\n\t{\n\t\tlabel: 'Office documents (docx, xlsx, pptx)',\n\t\tvalue: 'application/vnd.openxmlformats-officedocument.*',\n\t},\n\t{ label: 'Any audio', value: 'audio/*' },\n\t{ label: 'Any video', value: 'video/*' },\n]\n\n/**\n * The `file` field type. Its stored value is a server-captured {@link FileRef}; the client only ever submits\n * the upload id, and `runSubmission` re-reads filename/mimeType/filesize from the upload doc at the trust\n * boundary (see `captureFileRef`). The intrinsic validator is intentionally a no-op: MIME/size/existence are\n * enforced authoritatively server-side against the stored doc, never against client metadata. `mimeTypes`\n * and `maxSize` persist on the block so the capture can enforce them. `uploadsCollection` is not\n * author-editable: a forms hook stamps the plugin-configured uploads collection slug onto it on every save\n * so the client renderer knows where to POST; the server never trusts it at submit time (plugin config\n * stays the source of truth). Named `uploadsCollection` because `collection` is a mongoose-reserved\n * schema pathname.\n */\nexport const
|
|
1
|
+
{"version":3,"file":"file.js","names":[],"sources":["../../../src/fields/builtin/file.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { mimeAllowed } from '../../uploads/resolveFileRef'\nimport type { FileRef } from '../../uploads/types'\nimport { defineFormField } from '../defineFormField'\n\ntype FileConfig = {\n\tuploadsCollection?: string\n\tmimeTypes?: string[]\n\tmaxSize?: number\n}\n\nconst isFileRef = (value: unknown): value is FileRef =>\n\ttypeof value === 'object' && value !== null && 'filename' in value\n\nconst MAX_SIZE_REF = '@10x-media/form-builder/client#ByteSizeField'\n\n/**\n * Curated MIME type choices for the file field's `mimeTypes` select. Values are raw MIME strings or\n * trailing-`*` prefix wildcards, exactly the patterns `resolveFileRef` matches server-side; `type/*`\n * values double as the file input's `accept` attribute (browsers ignore other prefix patterns, which\n * is fine since the server stays authoritative). Every value must be 63 bytes or less: Payload's\n * db-postgres adapter stores select options as a pg enum, and pg enum labels cap at 63 bytes.\n */\nexport const fileMimeTypeOptions = [\n\t{ label: 'Any image', value: 'image/*' },\n\t{ label: 'PNG image', value: 'image/png' },\n\t{ label: 'JPEG image', value: 'image/jpeg' },\n\t{ label: 'WebP image', value: 'image/webp' },\n\t{ label: 'GIF image', value: 'image/gif' },\n\t{ label: 'SVG image', value: 'image/svg+xml' },\n\t{ label: 'PDF', value: 'application/pdf' },\n\t{ label: 'Plain text', value: 'text/plain' },\n\t{ label: 'CSV', value: 'text/csv' },\n\t{ label: 'ZIP archive', value: 'application/zip' },\n\t{ label: 'Word document (.doc)', value: 'application/msword' },\n\t{ label: 'Excel spreadsheet (.xls)', value: 'application/vnd.ms-excel' },\n\t{\n\t\tlabel: 'Office documents (docx, xlsx, pptx)',\n\t\tvalue: 'application/vnd.openxmlformats-officedocument.*',\n\t},\n\t{ label: 'Any audio', value: 'audio/*' },\n\t{ label: 'Any video', value: 'video/*' },\n]\n\n/**\n * The `file` field type. Its stored value is a server-captured {@link FileRef}; the client only ever submits\n * the upload id, and `runSubmission` re-reads filename/mimeType/filesize from the upload doc at the trust\n * boundary (see `captureFileRef`). The intrinsic validator is intentionally a no-op: MIME/size/existence are\n * enforced authoritatively server-side against the stored doc, never against client metadata. `mimeTypes`\n * and `maxSize` persist on the block so the capture can enforce them. `uploadsCollection` is not\n * author-editable: a forms hook stamps the plugin-configured uploads collection slug onto it on every save\n * so the client renderer knows where to POST; the server never trusts it at submit time (plugin config\n * stays the source of truth). Named `uploadsCollection` because `collection` is a mongoose-reserved\n * schema pathname.\n *\n * `allowedMimeTypes` (the host upload collection's `upload.mimeTypes`, threaded from the plugin\n * factory) constrains the picker's MIME options to what the collection actually accepts; omitted\n * keeps the full list. Admin-time only: the collection and `captureFileRef` remain authoritative.\n */\nexport const buildFileField = (allowedMimeTypes?: string[]) =>\n\tdefineFormField<'file', FileConfig>({\n\t\ttype: 'file',\n\t\tlabel: keys.fieldTypeFile,\n\t\tvalue: 'file',\n\t\tomitShared: ['placeholder'],\n\t\tconfig: [\n\t\t\t{ name: 'uploadsCollection', type: 'text', admin: { hidden: true } },\n\t\t\t{\n\t\t\t\tname: 'mimeTypes',\n\t\t\t\ttype: 'select',\n\t\t\t\thasMany: true,\n\t\t\t\toptions: fileMimeTypeOptions.filter((option) =>\n\t\t\t\t\tmimeAllowed(option.value, allowedMimeTypes)\n\t\t\t\t),\n\t\t\t\tlabel: labelFor(keys.fileConfigMimeTypes),\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'maxSize',\n\t\t\t\ttype: 'number',\n\t\t\t\tmin: 0,\n\t\t\t\tlabel: labelFor(keys.fileConfigMaxSize),\n\t\t\t\tadmin: {\n\t\t\t\t\tcomponents: {\n\t\t\t\t\t\tField: {\n\t\t\t\t\t\t\tpath: MAX_SIZE_REF,\n\t\t\t\t\t\t\tclientProps: { descriptionKey: keys.fileConfigMaxSizeDescription },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t\tformat: ({ value }) => (isFileRef(value) ? value.filename : ''),\n\t})\n\n/** The file field with the full MIME option list, for consumers spreading a default definition. */\nexport const fileField = buildFileField()\n"],"mappings":";;;;;AAYA,MAAM,aAAa,UAClB,OAAO,UAAU,YAAY,UAAU,QAAQ,cAAc;AAE9D,MAAM,eAAe;;;;;;;;AASrB,MAAa,sBAAsB;CAClC;EAAE,OAAO;EAAa,OAAO;CAAU;CACvC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAgB;CAC7C;EAAE,OAAO;EAAO,OAAO;CAAkB;CACzC;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAO,OAAO;CAAW;CAClC;EAAE,OAAO;EAAe,OAAO;CAAkB;CACjD;EAAE,OAAO;EAAwB,OAAO;CAAqB;CAC7D;EAAE,OAAO;EAA4B,OAAO;CAA2B;CACvE;EACC,OAAO;EACP,OAAO;CACR;CACA;EAAE,OAAO;EAAa,OAAO;CAAU;CACvC;EAAE,OAAO;EAAa,OAAO;CAAU;AACxC;;;;;;;;;;;;;;;;AAiBA,MAAa,kBAAkB,qBAC9B,gBAAoC;CACnC,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,YAAY,CAAC,aAAa;CAC1B,QAAQ;EACP;GAAE,MAAM;GAAqB,MAAM;GAAQ,OAAO,EAAE,QAAQ,KAAK;EAAE;EACnE;GACC,MAAM;GACN,MAAM;GACN,SAAS;GACT,SAAS,oBAAoB,QAAQ,WACpC,YAAY,OAAO,OAAO,gBAAgB,CAC3C;GACA,OAAO,SAAS,KAAK,mBAAmB;EACzC;EACA;GACC,MAAM;GACN,MAAM;GACN,KAAK;GACL,OAAO,SAAS,KAAK,iBAAiB;GACtC,OAAO,EACN,YAAY,EACX,OAAO;IACN,MAAM;IACN,aAAa,EAAE,gBAAgB,KAAK,6BAA6B;GAClE,EACD,EACD;EACD;CACD;CACA,SAAS,EAAE,YAAa,UAAU,KAAK,IAAI,MAAM,WAAW;AAC7D,CAAC;AAGuB,eAAe"}
|
|
@@ -10,7 +10,7 @@ import { RichTextField } from "payload";
|
|
|
10
10
|
* value/config generics; the registry stores them erased (config is re-narrowed per matched type at
|
|
11
11
|
* execution), hence one cast per built-in, no `any`.
|
|
12
12
|
*/
|
|
13
|
-
declare const buildDefaultFieldDefinitions: (localize: boolean, editor?: RichTextField["editor"]) => AnyFormFieldDefinition[];
|
|
13
|
+
declare const buildDefaultFieldDefinitions: (localize: boolean, editor?: RichTextField["editor"], uploadMimeTypes?: string[]) => AnyFormFieldDefinition[];
|
|
14
14
|
declare const defaultFieldDefinitions: AnyFormFieldDefinition[];
|
|
15
15
|
/**
|
|
16
16
|
* `defaultFieldDefinitions` indexed by `type`, so a single built-in can be spread and tweaked
|
|
@@ -3,7 +3,7 @@ import { checkboxField } from "./checkbox.js";
|
|
|
3
3
|
import { consentField } from "./consent.js";
|
|
4
4
|
import { dateField } from "./date.js";
|
|
5
5
|
import { emailField } from "./email.js";
|
|
6
|
-
import {
|
|
6
|
+
import { buildFileField } from "./file.js";
|
|
7
7
|
import { buildMessageField } from "./message.js";
|
|
8
8
|
import { numberField } from "./number.js";
|
|
9
9
|
import { buildRepeaterField } from "./repeater.js";
|
|
@@ -19,7 +19,7 @@ import { textareaField } from "./textarea.js";
|
|
|
19
19
|
* value/config generics; the registry stores them erased (config is re-narrowed per matched type at
|
|
20
20
|
* execution), hence one cast per built-in, no `any`.
|
|
21
21
|
*/
|
|
22
|
-
const buildDefaultFieldDefinitions = (localize, editor) => [
|
|
22
|
+
const buildDefaultFieldDefinitions = (localize, editor, uploadMimeTypes) => [
|
|
23
23
|
textField,
|
|
24
24
|
textareaField,
|
|
25
25
|
emailField,
|
|
@@ -28,7 +28,7 @@ const buildDefaultFieldDefinitions = (localize, editor) => [
|
|
|
28
28
|
checkboxField,
|
|
29
29
|
calculationField,
|
|
30
30
|
consentField,
|
|
31
|
-
|
|
31
|
+
buildFileField(uploadMimeTypes),
|
|
32
32
|
buildRepeaterField(localize),
|
|
33
33
|
dateField,
|
|
34
34
|
buildMessageField(localize, editor)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { RichTextField } from 'payload'\nimport type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { dateField } from './date'\nimport { emailField } from './email'\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { RichTextField } from 'payload'\nimport type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { dateField } from './date'\nimport { emailField } from './email'\nimport { buildFileField } from './file'\nimport { buildMessageField } from './message'\nimport { numberField } from './number'\nimport { buildRepeaterField } from './repeater'\nimport { buildSelectField } from './select'\nimport { textField } from './text'\nimport { textareaField } from './textarea'\n\n/**\n * Built-in field definitions with content-bearing config fields (select option labels, repeater add\n * label) carrying `localized: true` when `localize` is true. `editor`, when given, overrides the\n * richText editor on the message content field (from the plugin's `richText.editor` option). Field\n * types without content config are shared static definitions. Definitions are authored with precise\n * value/config generics; the registry stores them erased (config is re-narrowed per matched type at\n * execution), hence one cast per built-in, no `any`.\n */\nexport const buildDefaultFieldDefinitions = (\n\tlocalize: boolean,\n\teditor?: RichTextField['editor'],\n\tuploadMimeTypes?: string[]\n): AnyFormFieldDefinition[] => [\n\ttextField as AnyFormFieldDefinition,\n\ttextareaField as AnyFormFieldDefinition,\n\temailField as AnyFormFieldDefinition,\n\tnumberField as AnyFormFieldDefinition,\n\tbuildSelectField(localize) as AnyFormFieldDefinition,\n\tcheckboxField as AnyFormFieldDefinition,\n\tcalculationField as AnyFormFieldDefinition,\n\tconsentField as AnyFormFieldDefinition,\n\tbuildFileField(uploadMimeTypes) as AnyFormFieldDefinition,\n\tbuildRepeaterField(localize) as AnyFormFieldDefinition,\n\tdateField as AnyFormFieldDefinition,\n\tbuildMessageField(localize, editor) as AnyFormFieldDefinition,\n]\n\nexport const defaultFieldDefinitions: AnyFormFieldDefinition[] = buildDefaultFieldDefinitions(true)\n\n/**\n * `defaultFieldDefinitions` indexed by `type`, so a single built-in can be spread and tweaked\n * without hand-rolling a `.find()`, e.g.\n * `fields: { text: { ...defaultFieldDefinitionsByType.text, validate: myValidate } }`. The\n * `fields` plugin option replaces a built-in of the same `type`; `false` removes it, `true` keeps it.\n * Prebuilt with content localization on; with `localizeContent: false`, derive overrides from\n * `buildDefaultFieldDefinitions(false)` instead so the spread does not reintroduce `localized`.\n */\nexport const defaultFieldDefinitionsByType: Record<string, AnyFormFieldDefinition> =\n\tObject.fromEntries(defaultFieldDefinitions.map((definition) => [definition.type, definition]))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,gCACZ,UACA,QACA,oBAC8B;CAC9B;CACA;CACA;CACA;CACA,iBAAiB,QAAQ;CACzB;CACA;CACA;CACA,eAAe,eAAe;CAC9B,mBAAmB,QAAQ;CAC3B;CACA,kBAAkB,UAAU,MAAM;AACnC;AAEA,MAAa,0BAAoD,6BAA6B,IAAI;;;;;;;;;AAUlG,MAAa,gCACZ,OAAO,YAAY,wBAAwB,KAAK,eAAe,CAAC,WAAW,MAAM,UAAU,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { localizedIf } from "../localizedIf.js";
|
|
2
1
|
import { keys } from "../../translations/keys.js";
|
|
3
2
|
import { labelFor } from "../../translations/server.js";
|
|
3
|
+
import { localizedIf } from "../localizedIf.js";
|
|
4
4
|
import { defineFormField } from "../defineFormField.js";
|
|
5
5
|
//#region src/fields/builtin/message.ts
|
|
6
6
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { localizedIf } from "../localizedIf.js";
|
|
2
1
|
import { keys } from "../../translations/keys.js";
|
|
3
2
|
import { labelFor } from "../../translations/server.js";
|
|
3
|
+
import { localizedIf } from "../localizedIf.js";
|
|
4
4
|
import { defineFormField } from "../defineFormField.js";
|
|
5
5
|
//#region src/fields/builtin/repeater.ts
|
|
6
6
|
/** `addLabel` is the visitor-facing add-row button text and follows `localize`; row bounds never do. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { localizedIf } from "../localizedIf.js";
|
|
2
1
|
import { keys } from "../../translations/keys.js";
|
|
3
2
|
import { labelFor } from "../../translations/server.js";
|
|
3
|
+
import { localizedIf } from "../localizedIf.js";
|
|
4
4
|
import { defineFormField } from "../defineFormField.js";
|
|
5
5
|
//#region src/fields/builtin/select.ts
|
|
6
6
|
/** Option `label` is author-facing content and follows `localize`; `value` is an identifier and never does. */
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AnyFormFieldDefinition } from "./types.js";
|
|
2
|
+
import { RegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
2
3
|
import { Payload } from "payload";
|
|
3
4
|
|
|
4
5
|
//#region src/fields/registry.d.ts
|
|
5
6
|
type FieldTypeRegistry = Map<string, AnyFormFieldDefinition>;
|
|
6
7
|
/** Per-type opt-in: `false` removes a built-in, `true` keeps it, an object adds a new type or replaces one. */
|
|
7
8
|
type FieldTypeOption = boolean | AnyFormFieldDefinition;
|
|
8
|
-
type FieldTypesConfig =
|
|
9
|
+
type FieldTypesConfig = RegistryConfig<AnyFormFieldDefinition>;
|
|
9
10
|
//#endregion
|
|
10
11
|
export { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig };
|
|
11
12
|
//# sourceMappingURL=registry.d.ts.map
|