@10x-media/form-builder 0.1.0-beta.3 → 0.1.0-beta.4
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 +42 -0
- package/README.md +2 -2
- package/dist/actions/body/serializeBody.d.ts +19 -4
- package/dist/actions/body/serializeBody.js +3 -1
- package/dist/actions/body/serializeBody.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +13 -4
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +13 -4
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +13 -1
- package/dist/actions/builtin/index.js +18 -7
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/builtin/signedWebhook.js +2 -16
- package/dist/actions/builtin/signedWebhook.js.map +1 -1
- package/dist/actions/runActions.js +2 -0
- package/dist/actions/runActions.js.map +1 -1
- package/dist/aggregation/resolveResultsRequest.d.ts +22 -6
- package/dist/aggregation/resolveResultsRequest.js +18 -7
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/client/FlowBuilder.js +98 -53
- package/dist/client/FlowBuilder.js.map +1 -1
- package/dist/client/admin.css +47 -5
- package/dist/client/flowAuthoring.js +35 -0
- package/dist/client/flowAuthoring.js.map +1 -0
- package/dist/client/synthesizeClientField.js +6 -2
- package/dist/client/synthesizeClientField.js.map +1 -1
- package/dist/collections/formSubmissions.js +47 -9
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +180 -34
- package/dist/collections/forms.js.map +1 -1
- package/dist/conditions/conditionType.js +9 -2
- package/dist/conditions/conditionType.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +2 -1
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/builtin/index.d.ts +11 -1
- package/dist/consent/builtin/index.js +15 -5
- package/dist/consent/builtin/index.js.map +1 -1
- package/dist/consent/builtin/static.js +7 -3
- package/dist/consent/builtin/static.js.map +1 -1
- package/dist/exports/react.d.ts +10 -6
- package/dist/exports/react.js +6 -2
- package/dist/exports/rsc.d.ts +6 -1
- package/dist/exports/rsc.js +6 -1
- package/dist/exports/types.d.ts +4 -4
- package/dist/fields/buildFieldBlocks.js +17 -8
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/consent.js +8 -4
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/file.js +8 -4
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +11 -1
- package/dist/fields/builtin/index.js +21 -9
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +26 -0
- package/dist/fields/builtin/message.js.map +1 -0
- package/dist/fields/builtin/repeater.js +7 -3
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +7 -3
- package/dist/fields/builtin/select.js.map +1 -1
- package/dist/fields/localizedIf.d.ts +13 -0
- package/dist/fields/localizedIf.js +12 -0
- package/dist/fields/localizedIf.js.map +1 -0
- package/dist/fields/sharedConfig.js +64 -51
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +7 -2
- package/dist/flow/normalizeFlow.js +7 -1
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/flow/stepLabel.js +15 -0
- package/dist/flow/stepLabel.js.map +1 -0
- package/dist/form/pollState.d.ts +22 -0
- package/dist/form/pollState.js +17 -0
- package/dist/form/pollState.js.map +1 -0
- package/dist/form/toFormDocument.d.ts +60 -0
- package/dist/form/toFormDocument.js +39 -0
- package/dist/form/toFormDocument.js.map +1 -0
- package/dist/form/types.d.ts +48 -0
- package/dist/index.d.ts +69 -18
- package/dist/index.js +24 -13
- package/dist/index.js.map +1 -1
- package/dist/plugin/registerCollections.js +15 -36
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +11 -0
- package/dist/plugin/uploadsCollection.js +44 -0
- package/dist/plugin/uploadsCollection.js.map +1 -0
- package/dist/poll/applyPollOptions.js +17 -0
- package/dist/poll/applyPollOptions.js.map +1 -0
- package/dist/poll/buildPollOptionSourceFields.js +53 -0
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +35 -0
- package/dist/poll/definePollOptionSource.js +6 -0
- package/dist/poll/definePollOptionSource.js.map +1 -0
- package/dist/poll/registry.d.ts +16 -0
- package/dist/poll/registry.js +16 -0
- package/dist/poll/registry.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +28 -0
- package/dist/poll/resolvePollOptions.js +64 -0
- package/dist/poll/resolvePollOptions.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +21 -0
- package/dist/poll/resolvePollOutcome.js +36 -0
- package/dist/poll/resolvePollOutcome.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +1 -1
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/presentations/types.d.ts +3 -2
- package/dist/react/Form.d.ts +13 -31
- package/dist/react/Form.js +96 -83
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +35 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +55 -0
- package/dist/react/FormControls.js +56 -0
- package/dist/react/FormControls.js.map +1 -0
- package/dist/react/FormResults.d.ts +6 -3
- package/dist/react/FormResults.js +37 -29
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/FormSteps.d.ts +17 -0
- package/dist/react/FormSteps.js +35 -0
- package/dist/react/FormSteps.js.map +1 -0
- package/dist/react/Poll.d.ts +17 -6
- package/dist/react/Poll.js +66 -18
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/fetchResults.d.ts +1 -1
- package/dist/react/fetchResults.js +1 -1
- package/dist/react/fetchResults.js.map +1 -1
- package/dist/react/renderers/file.js +5 -1
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +2 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/message.js +35 -0
- package/dist/react/renderers/message.js.map +1 -0
- package/dist/react/state.d.ts +25 -1
- package/dist/react/state.js +14 -1
- package/dist/react/state.js.map +1 -1
- package/dist/react/uploadFile.d.ts +2 -2
- package/dist/react/uploadFile.js +1 -1
- package/dist/react/uploadFile.js.map +1 -1
- package/dist/submissions/runSubmission.js +70 -33
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/validateSubmission.js +41 -7
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/votedCookie.d.ts +13 -0
- package/dist/submissions/votedCookie.js +27 -0
- package/dist/submissions/votedCookie.js.map +1 -0
- package/dist/translations/de.js +219 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +45 -6
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/index.d.ts +2 -1
- package/dist/translations/index.js +6 -2
- package/dist/translations/index.js.map +1 -1
- package/dist/translations/keys.d.ts +43 -4
- package/dist/translations/keys.js +44 -5
- package/dist/translations/keys.js.map +1 -1
- package/dist/uploads/types.d.ts +1 -2
- package/dist/validation/validateUrl.js +22 -0
- package/dist/validation/validateUrl.js.map +1 -0
- package/package.json +2 -1
- package/styles/form-builder.css +10 -0
- package/dist/collections/uploads.d.ts +0 -30
- package/dist/collections/uploads.js +0 -55
- package/dist/collections/uploads.js.map +0 -1
- package/dist/presentations/registry.d.ts +0 -15
- package/dist/presentations/registry.js +0 -15
- package/dist/presentations/registry.js.map +0 -1
- package/dist/react/toFormDocument.d.ts +0 -24
- package/dist/react/toFormDocument.js +0 -22
- package/dist/react/toFormDocument.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/fields/localizedIf.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Spreadable localization flag: `{ localized: true }` when `localize` is true, an empty object
|
|
4
|
+
* otherwise, so `...localizedIf(flag)` adds the key only when wanted. Payload itself strips
|
|
5
|
+
* `localized` from fields when the host config has no `localization`, so spreading `true` is
|
|
6
|
+
* always safe.
|
|
7
|
+
*/
|
|
8
|
+
declare const localizedIf: (localize: boolean) => {
|
|
9
|
+
localized: true;
|
|
10
|
+
} | Record<string, never>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { localizedIf };
|
|
13
|
+
//# sourceMappingURL=localizedIf.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/fields/localizedIf.ts
|
|
2
|
+
/**
|
|
3
|
+
* Spreadable localization flag: `{ localized: true }` when `localize` is true, an empty object
|
|
4
|
+
* otherwise, so `...localizedIf(flag)` adds the key only when wanted. Payload itself strips
|
|
5
|
+
* `localized` from fields when the host config has no `localization`, so spreading `true` is
|
|
6
|
+
* always safe.
|
|
7
|
+
*/
|
|
8
|
+
const localizedIf = (localize) => localize ? { localized: true } : {};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { localizedIf };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=localizedIf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localizedIf.js","names":[],"sources":["../../src/fields/localizedIf.ts"],"sourcesContent":["/**\n * Spreadable localization flag: `{ localized: true }` when `localize` is true, an empty object\n * otherwise, so `...localizedIf(flag)` adds the key only when wanted. Payload itself strips\n * `localized` from fields when the host config has no `localization`, so spreading `true` is\n * always safe.\n */\nexport const localizedIf = (localize: boolean): { localized: true } | Record<string, never> =>\n\tlocalize ? { localized: true } : {}\n"],"mappings":";;;;;;;AAMA,MAAa,eAAe,aAC3B,WAAW,EAAE,WAAW,KAAK,IAAI,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localizedIf } from "./localizedIf.js";
|
|
1
2
|
import { keys } from "../translations/keys.js";
|
|
2
3
|
import { labelFor } from "../translations/server.js";
|
|
3
4
|
//#region src/fields/sharedConfig.ts
|
|
@@ -14,72 +15,84 @@ const conditionField = (name, labelKey, conditionTypes) => ({
|
|
|
14
15
|
/**
|
|
15
16
|
* Config every field instance carries regardless of type. `name` is the machine key written into
|
|
16
17
|
* submissions; `width` is stored for the layout grid. Field types add their own `config` after
|
|
17
|
-
* these inside the Field tab (see `fieldBlockTabs`).
|
|
18
|
+
* these inside the Field tab (see `fieldBlockTabs`). Content-bearing fields (`label`,
|
|
19
|
+
* `placeholder`, `description`) are localized unless `localize` is false; identifiers and
|
|
20
|
+
* behavior flags never are. The rows below are presentational only (unnamed, data paths stay
|
|
21
|
+
* flat); `required` sits last, under `description`, per the admin layout.
|
|
18
22
|
*/
|
|
19
|
-
const sharedFieldConfig = () => [
|
|
23
|
+
const sharedFieldConfig = (localize = true) => [
|
|
20
24
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
type: "row",
|
|
26
|
+
fields: [{
|
|
27
|
+
name: "name",
|
|
28
|
+
type: "text",
|
|
29
|
+
required: true,
|
|
30
|
+
label: labelFor(keys.configName),
|
|
31
|
+
admin: { width: "50%" }
|
|
32
|
+
}, {
|
|
33
|
+
name: "label",
|
|
34
|
+
type: "text",
|
|
35
|
+
label: labelFor(keys.configLabel),
|
|
36
|
+
admin: { width: "50%" },
|
|
37
|
+
...localizedIf(localize)
|
|
38
|
+
}]
|
|
25
39
|
},
|
|
26
40
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
name: "width",
|
|
38
|
-
type: "select",
|
|
39
|
-
defaultValue: "full",
|
|
40
|
-
label: labelFor(keys.configWidth),
|
|
41
|
-
options: [
|
|
42
|
-
{
|
|
43
|
-
label: "Full",
|
|
44
|
-
value: "full"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
label: "Half",
|
|
48
|
-
value: "half"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
label: "Third",
|
|
52
|
-
value: "third"
|
|
41
|
+
type: "row",
|
|
42
|
+
fields: [{
|
|
43
|
+
name: "width",
|
|
44
|
+
type: "select",
|
|
45
|
+
required: true,
|
|
46
|
+
defaultValue: "full",
|
|
47
|
+
label: labelFor(keys.configWidth),
|
|
48
|
+
admin: {
|
|
49
|
+
width: "50%",
|
|
50
|
+
isClearable: false
|
|
53
51
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
options: [
|
|
53
|
+
{
|
|
54
|
+
label: "Full",
|
|
55
|
+
value: "full"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: "Half",
|
|
59
|
+
value: "half"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: "Third",
|
|
63
|
+
value: "third"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: "Two thirds",
|
|
67
|
+
value: "twoThirds"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}, {
|
|
71
|
+
name: "placeholder",
|
|
72
|
+
type: "text",
|
|
73
|
+
label: labelFor(keys.configPlaceholder),
|
|
74
|
+
admin: { width: "50%" },
|
|
75
|
+
...localizedIf(localize)
|
|
76
|
+
}]
|
|
64
77
|
},
|
|
65
78
|
{
|
|
66
79
|
name: "description",
|
|
67
80
|
type: "textarea",
|
|
68
|
-
label: labelFor(keys.configDescription)
|
|
81
|
+
label: labelFor(keys.configDescription),
|
|
82
|
+
...localizedIf(localize)
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "required",
|
|
86
|
+
type: "checkbox",
|
|
87
|
+
label: labelFor(keys.configRequired)
|
|
69
88
|
}
|
|
70
89
|
];
|
|
71
|
-
|
|
72
|
-
* The single field of every field block: unnamed tabs (presentational only, data paths stay flat).
|
|
73
|
-
* Field tab holds the shared basics plus the type's own config; Validation holds the rule blocks and
|
|
74
|
-
* `validateWhen`; Advanced holds `visibleWhen` and `hidden`. `visibleWhen`/`validateWhen` store a
|
|
75
|
-
* canonical Payload `Where`, edited by the native condition builder.
|
|
76
|
-
*/
|
|
77
|
-
const fieldBlockTabs = (conditionTypes, typeConfig, validations) => ({
|
|
90
|
+
const fieldBlockTabs = ({ conditionTypes, typeConfig, validations, localize = true }) => ({
|
|
78
91
|
type: "tabs",
|
|
79
92
|
tabs: [
|
|
80
93
|
{
|
|
81
94
|
label: labelFor(keys.tabField),
|
|
82
|
-
fields: [...sharedFieldConfig(), ...typeConfig]
|
|
95
|
+
fields: [...sharedFieldConfig(localize), ...typeConfig]
|
|
83
96
|
},
|
|
84
97
|
{
|
|
85
98
|
label: labelFor(keys.tabValidation),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedConfig.js","names":[],"sources":["../../src/fields/sharedConfig.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport type { ConditionFieldType } from '../conditions/fieldTypes'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\n\nconst CONDITION_FIELD_REF = '@10x-media/form-builder/client#FormConditionField'\n\nconst conditionField = (\n\tname: 'visibleWhen' | 'validateWhen',\n\tlabelKey: string,\n\tconditionTypes: Record<string, ConditionFieldType>\n): Field => ({\n\tname,\n\ttype: 'json',\n\tlabel: labelFor(labelKey),\n\tadmin: {\n\t\tcomponents: {\n\t\t\tField: { path: CONDITION_FIELD_REF, clientProps: { conditionTypes } },\n\t\t},\n\t},\n})\n\n/**\n * Config every field instance carries regardless of type. `name` is the machine key written into\n * submissions; `width` is stored for the layout grid. Field types add their own `config` after\n * these inside the Field tab (see `fieldBlockTabs`).\n */\nexport const sharedFieldConfig = (): Field[] => [\n\t{
|
|
1
|
+
{"version":3,"file":"sharedConfig.js","names":[],"sources":["../../src/fields/sharedConfig.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport type { ConditionFieldType } from '../conditions/fieldTypes'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\nimport { localizedIf } from './localizedIf'\n\nconst CONDITION_FIELD_REF = '@10x-media/form-builder/client#FormConditionField'\n\nconst conditionField = (\n\tname: 'visibleWhen' | 'validateWhen',\n\tlabelKey: string,\n\tconditionTypes: Record<string, ConditionFieldType>\n): Field => ({\n\tname,\n\ttype: 'json',\n\tlabel: labelFor(labelKey),\n\tadmin: {\n\t\tcomponents: {\n\t\t\tField: { path: CONDITION_FIELD_REF, clientProps: { conditionTypes } },\n\t\t},\n\t},\n})\n\n/**\n * Config every field instance carries regardless of type. `name` is the machine key written into\n * submissions; `width` is stored for the layout grid. Field types add their own `config` after\n * these inside the Field tab (see `fieldBlockTabs`). Content-bearing fields (`label`,\n * `placeholder`, `description`) are localized unless `localize` is false; identifiers and\n * behavior flags never are. The rows below are presentational only (unnamed, data paths stay\n * flat); `required` sits last, under `description`, per the admin layout.\n */\nexport const sharedFieldConfig = (localize = true): Field[] => [\n\t{\n\t\ttype: 'row',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'name',\n\t\t\t\ttype: 'text',\n\t\t\t\trequired: true,\n\t\t\t\tlabel: labelFor(keys.configName),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'label',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.configLabel),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\ttype: 'row',\n\t\tfields: [\n\t\t\t{\n\t\t\t\tname: 'width',\n\t\t\t\ttype: 'select',\n\t\t\t\trequired: true,\n\t\t\t\tdefaultValue: 'full',\n\t\t\t\tlabel: labelFor(keys.configWidth),\n\t\t\t\tadmin: { width: '50%', isClearable: false },\n\t\t\t\toptions: [\n\t\t\t\t\t{ label: 'Full', value: 'full' },\n\t\t\t\t\t{ label: 'Half', value: 'half' },\n\t\t\t\t\t{ label: 'Third', value: 'third' },\n\t\t\t\t\t{ label: 'Two thirds', value: 'twoThirds' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'placeholder',\n\t\t\t\ttype: 'text',\n\t\t\t\tlabel: labelFor(keys.configPlaceholder),\n\t\t\t\tadmin: { width: '50%' },\n\t\t\t\t...localizedIf(localize),\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\tname: 'description',\n\t\ttype: 'textarea',\n\t\tlabel: labelFor(keys.configDescription),\n\t\t...localizedIf(localize),\n\t},\n\t{ name: 'required', type: 'checkbox', label: labelFor(keys.configRequired) },\n]\n\n/**\n * The single field of every field block: unnamed tabs (presentational only, data paths stay flat).\n * Field tab holds the shared basics plus the type's own config; Validation holds the rule blocks and\n * `validateWhen`; Advanced holds `visibleWhen` and `hidden`. `visibleWhen`/`validateWhen` store a\n * canonical Payload `Where`, edited by the native condition builder.\n */\ntype FieldBlockTabsArgs = {\n\tconditionTypes: Record<string, ConditionFieldType>\n\ttypeConfig: Field[]\n\tvalidations: Field\n\tlocalize?: boolean\n}\n\nexport const fieldBlockTabs = ({\n\tconditionTypes,\n\ttypeConfig,\n\tvalidations,\n\tlocalize = true,\n}: FieldBlockTabsArgs): Field => ({\n\ttype: 'tabs',\n\ttabs: [\n\t\t{\n\t\t\tlabel: labelFor(keys.tabField),\n\t\t\tfields: [...sharedFieldConfig(localize), ...typeConfig],\n\t\t},\n\t\t{\n\t\t\tlabel: labelFor(keys.tabValidation),\n\t\t\tfields: [\n\t\t\t\tvalidations,\n\t\t\t\tconditionField('validateWhen', keys.configValidateWhen, conditionTypes),\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tlabel: labelFor(keys.tabAdvanced),\n\t\t\tfields: [\n\t\t\t\tconditionField('visibleWhen', keys.configVisibleWhen, conditionTypes),\n\t\t\t\t{ name: 'hidden', type: 'checkbox', label: labelFor(keys.configHidden) },\n\t\t\t],\n\t\t},\n\t],\n})\n"],"mappings":";;;;AAMA,MAAM,sBAAsB;AAE5B,MAAM,kBACL,MACA,UACA,oBACY;CACZ;CACA,MAAM;CACN,OAAO,SAAS,QAAQ;CACxB,OAAO,EACN,YAAY,EACX,OAAO;EAAE,MAAM;EAAqB,aAAa,EAAE,eAAe;CAAE,EACrE,EACD;AACD;;;;;;;;;AAUA,MAAa,qBAAqB,WAAW,SAAkB;CAC9D;EACC,MAAM;EACN,QAAQ,CACP;GACC,MAAM;GACN,MAAM;GACN,UAAU;GACV,OAAO,SAAS,KAAK,UAAU;GAC/B,OAAO,EAAE,OAAO,MAAM;EACvB,GACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,WAAW;GAChC,OAAO,EAAE,OAAO,MAAM;GACtB,GAAG,YAAY,QAAQ;EACxB,CACD;CACD;CACA;EACC,MAAM;EACN,QAAQ,CACP;GACC,MAAM;GACN,MAAM;GACN,UAAU;GACV,cAAc;GACd,OAAO,SAAS,KAAK,WAAW;GAChC,OAAO;IAAE,OAAO;IAAO,aAAa;GAAM;GAC1C,SAAS;IACR;KAAE,OAAO;KAAQ,OAAO;IAAO;IAC/B;KAAE,OAAO;KAAQ,OAAO;IAAO;IAC/B;KAAE,OAAO;KAAS,OAAO;IAAQ;IACjC;KAAE,OAAO;KAAc,OAAO;IAAY;GAC3C;EACD,GACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,iBAAiB;GACtC,OAAO,EAAE,OAAO,MAAM;GACtB,GAAG,YAAY,QAAQ;EACxB,CACD;CACD;CACA;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,iBAAiB;EACtC,GAAG,YAAY,QAAQ;CACxB;CACA;EAAE,MAAM;EAAY,MAAM;EAAY,OAAO,SAAS,KAAK,cAAc;CAAE;AAC5E;AAeA,MAAa,kBAAkB,EAC9B,gBACA,YACA,aACA,WAAW,YACsB;CACjC,MAAM;CACN,MAAM;EACL;GACC,OAAO,SAAS,KAAK,QAAQ;GAC7B,QAAQ,CAAC,GAAG,kBAAkB,QAAQ,GAAG,GAAG,UAAU;EACvD;EACA;GACC,OAAO,SAAS,KAAK,aAAa;GAClC,QAAQ,CACP,aACA,eAAe,gBAAgB,KAAK,oBAAoB,cAAc,CACvE;EACD;EACA;GACC,OAAO,SAAS,KAAK,WAAW;GAChC,QAAQ,CACP,eAAe,eAAe,KAAK,mBAAmB,cAAc,GACpE;IAAE,MAAM;IAAU,MAAM;IAAY,OAAO,SAAS,KAAK,YAAY;GAAE,CACxE;EACD;CACD;AACD"}
|
package/dist/fields/types.d.ts
CHANGED
|
@@ -4,8 +4,12 @@ import { Field } from "payload";
|
|
|
4
4
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
5
|
|
|
6
6
|
//#region src/fields/types.d.ts
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The stored value shapes a field type can declare. Drives `validate`/`format` typing and value
|
|
9
|
+
* coercion. `'none'` marks a display-only type (e.g. `message`): the engine never seeds, validates,
|
|
10
|
+
* or stores a value for it, and a client-sent value under its name is dropped.
|
|
11
|
+
*/
|
|
12
|
+
type FormFieldValueKind = 'text' | 'number' | 'boolean' | 'date' | 'text[]' | 'file' | 'repeater' | 'none';
|
|
9
13
|
type ValueKindTypeMap = {
|
|
10
14
|
text: string;
|
|
11
15
|
number: number;
|
|
@@ -14,6 +18,7 @@ type ValueKindTypeMap = {
|
|
|
14
18
|
'text[]': string[];
|
|
15
19
|
file: FileRef;
|
|
16
20
|
repeater: Array<Record<string, unknown>>;
|
|
21
|
+
none: never;
|
|
17
22
|
};
|
|
18
23
|
type ValueOfKind<K extends FormFieldValueKind> = ValueKindTypeMap[K];
|
|
19
24
|
/** Resolved per-instance config values for a field type (loose at the DB boundary, narrowed by the author's generic). */
|
|
@@ -4,6 +4,7 @@ const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v)
|
|
|
4
4
|
* Pure guard that normalizes a raw (possibly untrusted) flow value against the
|
|
5
5
|
* form's current field list. Returns `undefined` when the flow is absent, empty,
|
|
6
6
|
* or resolves to fewer than two steps (a single step is an ordinary form).
|
|
7
|
+
* A field assigned to multiple steps keeps only its first occurrence in step order.
|
|
7
8
|
*/
|
|
8
9
|
const normalizeFlow = (raw, fieldNames) => {
|
|
9
10
|
if (!isRecord(raw) || !Array.isArray(raw.steps)) return;
|
|
@@ -11,9 +12,14 @@ const normalizeFlow = (raw, fieldNames) => {
|
|
|
11
12
|
const rawSteps = raw.steps.filter((s) => isRecord(s) && typeof s.id === "string");
|
|
12
13
|
const knownIds = new Set(rawSteps.map((s) => s.id));
|
|
13
14
|
if (knownIds.size < 2) return;
|
|
15
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
14
16
|
return { steps: rawSteps.map((s) => {
|
|
15
17
|
const id = s.id;
|
|
16
|
-
const fields = Array.isArray(s.fields) ? s.fields.filter((f) =>
|
|
18
|
+
const fields = Array.isArray(s.fields) ? s.fields.filter((f) => {
|
|
19
|
+
if (typeof f !== "string" || !knownFields.has(f) || seenFields.has(f)) return false;
|
|
20
|
+
seenFields.add(f);
|
|
21
|
+
return true;
|
|
22
|
+
}) : [];
|
|
17
23
|
const transitions = (Array.isArray(s.transitions) ? s.transitions : []).filter((t) => isRecord(t) && typeof t.to === "string" && knownIds.has(t.to) && isRecord(t.when)).map((t) => ({
|
|
18
24
|
when: t.when,
|
|
19
25
|
to: t.to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalizeFlow.js","names":[],"sources":["../../src/flow/normalizeFlow.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport type { FlowStep, FormFlow } from './types'\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n\tv !== null && typeof v === 'object' && !Array.isArray(v)\n\n/**\n * Pure guard that normalizes a raw (possibly untrusted) flow value against the\n * form's current field list. Returns `undefined` when the flow is absent, empty,\n * or resolves to fewer than two steps (a single step is an ordinary form).\n */\nexport const normalizeFlow = (raw: unknown, fieldNames: string[]): FormFlow | undefined => {\n\tif (!isRecord(raw) || !Array.isArray(raw.steps)) {\n\t\treturn undefined\n\t}\n\n\tconst knownFields = new Set(fieldNames)\n\n\tconst rawSteps = raw.steps.filter(\n\t\t(s): s is Record<string, unknown> => isRecord(s) && typeof s.id === 'string'\n\t)\n\n\tconst knownIds = new Set(rawSteps.map((s) => s.id as string))\n\n\tif (knownIds.size < 2) {\n\t\treturn undefined\n\t}\n\n\tconst steps: FlowStep[] = rawSteps.map((s) => {\n\t\tconst id = s.id as string\n\n\t\tconst fields = Array.isArray(s.fields)\n\t\t\t? s.fields.filter((f): f is string => typeof f
|
|
1
|
+
{"version":3,"file":"normalizeFlow.js","names":[],"sources":["../../src/flow/normalizeFlow.ts"],"sourcesContent":["import type { Where } from 'payload'\nimport type { FlowStep, FormFlow } from './types'\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n\tv !== null && typeof v === 'object' && !Array.isArray(v)\n\n/**\n * Pure guard that normalizes a raw (possibly untrusted) flow value against the\n * form's current field list. Returns `undefined` when the flow is absent, empty,\n * or resolves to fewer than two steps (a single step is an ordinary form).\n * A field assigned to multiple steps keeps only its first occurrence in step order.\n */\nexport const normalizeFlow = (raw: unknown, fieldNames: string[]): FormFlow | undefined => {\n\tif (!isRecord(raw) || !Array.isArray(raw.steps)) {\n\t\treturn undefined\n\t}\n\n\tconst knownFields = new Set(fieldNames)\n\n\tconst rawSteps = raw.steps.filter(\n\t\t(s): s is Record<string, unknown> => isRecord(s) && typeof s.id === 'string'\n\t)\n\n\tconst knownIds = new Set(rawSteps.map((s) => s.id as string))\n\n\tif (knownIds.size < 2) {\n\t\treturn undefined\n\t}\n\n\tconst seenFields = new Set<string>()\n\n\tconst steps: FlowStep[] = rawSteps.map((s) => {\n\t\tconst id = s.id as string\n\n\t\tconst fields = Array.isArray(s.fields)\n\t\t\t? s.fields.filter((f): f is string => {\n\t\t\t\t\tif (typeof f !== 'string' || !knownFields.has(f) || seenFields.has(f)) return false\n\t\t\t\t\tseenFields.add(f)\n\t\t\t\t\treturn true\n\t\t\t\t})\n\t\t\t: []\n\n\t\tconst rawTransitions = Array.isArray(s.transitions) ? s.transitions : []\n\t\tconst transitions = rawTransitions\n\t\t\t.filter(\n\t\t\t\t(t): t is Record<string, unknown> =>\n\t\t\t\t\tisRecord(t) &&\n\t\t\t\t\ttypeof t.to === 'string' &&\n\t\t\t\t\tknownIds.has(t.to as string) &&\n\t\t\t\t\tisRecord(t.when)\n\t\t\t)\n\t\t\t.map((t) => ({ when: t.when as Where, to: t.to as string }))\n\n\t\tconst next = typeof s.next === 'string' && knownIds.has(s.next) ? (s.next as string) : undefined\n\n\t\tconst step: FlowStep = { id, fields }\n\t\tif (typeof s.title === 'string') step.title = s.title\n\t\tif (transitions.length > 0) step.transitions = transitions\n\t\tif (next !== undefined) step.next = next\n\n\t\treturn step\n\t})\n\n\treturn { steps }\n}\n"],"mappings":";AAGA,MAAM,YAAY,MACjB,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;;;;;;;AAQxD,MAAa,iBAAiB,KAAc,eAA+C;CAC1F,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,QAAQ,IAAI,KAAK,GAC7C;CAGD,MAAM,cAAc,IAAI,IAAI,UAAU;CAEtC,MAAM,WAAW,IAAI,MAAM,QACzB,MAAoC,SAAS,CAAC,KAAK,OAAO,EAAE,OAAO,QACrE;CAEA,MAAM,WAAW,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,EAAY,CAAC;CAE5D,IAAI,SAAS,OAAO,GACnB;CAGD,MAAM,6BAAa,IAAI,IAAY;CAkCnC,OAAO,EAAE,OAhCiB,SAAS,KAAK,MAAM;EAC7C,MAAM,KAAK,EAAE;EAEb,MAAM,SAAS,MAAM,QAAQ,EAAE,MAAM,IAClC,EAAE,OAAO,QAAQ,MAAmB;GACpC,IAAI,OAAO,MAAM,YAAY,CAAC,YAAY,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,GAAG,OAAO;GAC9E,WAAW,IAAI,CAAC;GAChB,OAAO;EACR,CAAC,IACA,CAAC;EAGJ,MAAM,eADiB,MAAM,QAAQ,EAAE,WAAW,IAAI,EAAE,cAAc,CAAC,GAErE,QACC,MACA,SAAS,CAAC,KACV,OAAO,EAAE,OAAO,YAChB,SAAS,IAAI,EAAE,EAAY,KAC3B,SAAS,EAAE,IAAI,CACjB,EACC,KAAK,OAAO;GAAE,MAAM,EAAE;GAAe,IAAI,EAAE;EAAa,EAAE;EAE5D,MAAM,OAAO,OAAO,EAAE,SAAS,YAAY,SAAS,IAAI,EAAE,IAAI,IAAK,EAAE,OAAkB,KAAA;EAEvF,MAAM,OAAiB;GAAE;GAAI;EAAO;EACpC,IAAI,OAAO,EAAE,UAAU,UAAU,KAAK,QAAQ,EAAE;EAChD,IAAI,YAAY,SAAS,GAAG,KAAK,cAAc;EAC/C,IAAI,SAAS,KAAA,GAAW,KAAK,OAAO;EAEpC,OAAO;CACR,CAEa,EAAE;AAChB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/flow/stepLabel.ts
|
|
2
|
+
/**
|
|
3
|
+
* Display label for a step: its trimmed title, else the fallback template with
|
|
4
|
+
* `{n}` replaced by the 1-based index. Step ids never surface in the UI.
|
|
5
|
+
* Lives with the flow engine (not the admin client module) because the frontend
|
|
6
|
+
* `<FormSteps>` indicator resolves labels with the same semantics.
|
|
7
|
+
*/
|
|
8
|
+
const stepLabel = (step, index, fallbackTemplate) => {
|
|
9
|
+
const title = step.title?.trim();
|
|
10
|
+
return title !== void 0 && title.length > 0 ? title : fallbackTemplate.replace("{n}", String(index + 1));
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { stepLabel };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=stepLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stepLabel.js","names":[],"sources":["../../src/flow/stepLabel.ts"],"sourcesContent":["import type { FlowStep } from './types'\n\n/**\n * Display label for a step: its trimmed title, else the fallback template with\n * `{n}` replaced by the 1-based index. Step ids never surface in the UI.\n * Lives with the flow engine (not the admin client module) because the frontend\n * `<FormSteps>` indicator resolves labels with the same semantics.\n */\nexport const stepLabel = (\n\tstep: Pick<FlowStep, 'title'>,\n\tindex: number,\n\tfallbackTemplate: string\n): string => {\n\tconst title = step.title?.trim()\n\treturn title !== undefined && title.length > 0\n\t\t? title\n\t\t: fallbackTemplate.replace('{n}', String(index + 1))\n}\n"],"mappings":";;;;;;;AAQA,MAAa,aACZ,MACA,OACA,qBACY;CACZ,MAAM,QAAQ,KAAK,OAAO,KAAK;CAC/B,OAAO,UAAU,KAAA,KAAa,MAAM,SAAS,IAC1C,QACA,iBAAiB,QAAQ,OAAO,OAAO,QAAQ,CAAC,CAAC;AACrD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/form/pollState.d.ts
|
|
2
|
+
/** Poll settings as loaded from a forms document (server) or a `FormDocument` (client). */
|
|
3
|
+
type PollConfigLike = {
|
|
4
|
+
enabled?: boolean | null;
|
|
5
|
+
resultsField?: string | null;
|
|
6
|
+
resultsVisibility?: string | null;
|
|
7
|
+
closesAt?: string | null;
|
|
8
|
+
optionSource?: string | null;
|
|
9
|
+
sourceConfig?: Record<string, unknown> | null;
|
|
10
|
+
outcome?: {
|
|
11
|
+
winningValue?: string | null;
|
|
12
|
+
resolvedAt?: string | null;
|
|
13
|
+
} | null;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Whether the poll's `closesAt` has passed. An unset or unparseable `closesAt` never closes;
|
|
17
|
+
* framework-agnostic so the server guard, the results gate, and the client `<Poll>` agree.
|
|
18
|
+
*/
|
|
19
|
+
declare const isPollClosed: (poll: PollConfigLike | null | undefined, now?: number) => boolean;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { isPollClosed };
|
|
22
|
+
//# sourceMappingURL=pollState.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/form/pollState.ts
|
|
2
|
+
/** Narrow a forms document's untyped `poll` member. */
|
|
3
|
+
const pollConfigOf = (poll) => poll != null && typeof poll === "object" ? poll : void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the poll's `closesAt` has passed. An unset or unparseable `closesAt` never closes;
|
|
6
|
+
* framework-agnostic so the server guard, the results gate, and the client `<Poll>` agree.
|
|
7
|
+
*/
|
|
8
|
+
const isPollClosed = (poll, now = Date.now()) => {
|
|
9
|
+
const closesAt = poll?.closesAt;
|
|
10
|
+
if (typeof closesAt !== "string" || closesAt.length === 0) return false;
|
|
11
|
+
const ts = Date.parse(closesAt);
|
|
12
|
+
return Number.isFinite(ts) && ts <= now;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { isPollClosed, pollConfigOf };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=pollState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pollState.js","names":[],"sources":["../../src/form/pollState.ts"],"sourcesContent":["/** Poll settings as loaded from a forms document (server) or a `FormDocument` (client). */\nexport type PollConfigLike = {\n\tenabled?: boolean | null\n\tresultsField?: string | null\n\tresultsVisibility?: string | null\n\tclosesAt?: string | null\n\toptionSource?: string | null\n\tsourceConfig?: Record<string, unknown> | null\n\toutcome?: { winningValue?: string | null; resolvedAt?: string | null } | null\n}\n\n/** Narrow a forms document's untyped `poll` member. */\nexport const pollConfigOf = (poll: unknown): PollConfigLike | undefined =>\n\tpoll != null && typeof poll === 'object' ? (poll as PollConfigLike) : undefined\n\n/**\n * Whether the poll's `closesAt` has passed. An unset or unparseable `closesAt` never closes;\n * framework-agnostic so the server guard, the results gate, and the client `<Poll>` agree.\n */\nexport const isPollClosed = (\n\tpoll: PollConfigLike | null | undefined,\n\tnow: number = Date.now()\n): boolean => {\n\tconst closesAt = poll?.closesAt\n\tif (typeof closesAt !== 'string' || closesAt.length === 0) {\n\t\treturn false\n\t}\n\tconst ts = Date.parse(closesAt)\n\treturn Number.isFinite(ts) && ts <= now\n}\n"],"mappings":";;AAYA,MAAa,gBAAgB,SAC5B,QAAQ,QAAQ,OAAO,SAAS,WAAY,OAA0B,KAAA;;;;;AAMvE,MAAa,gBACZ,MACA,MAAc,KAAK,IAAI,MACV;CACb,MAAM,WAAW,MAAM;CACvB,IAAI,OAAO,aAAa,YAAY,SAAS,WAAW,GACvD,OAAO;CAER,MAAM,KAAK,KAAK,MAAM,QAAQ;CAC9B,OAAO,OAAO,SAAS,EAAE,KAAK,MAAM;AACrC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PollOption } from "../poll/definePollOptionSource.js";
|
|
2
|
+
import { FormDocument } from "./types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/form/toFormDocument.d.ts
|
|
5
|
+
type ToFormDocumentOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Source-resolved poll options (from `resolvePollOptions`), injected as the options of the
|
|
8
|
+
* field instance named by the form's `poll.resultsField` so the client renders the host's
|
|
9
|
+
* current choices instead of hand-authored ones.
|
|
10
|
+
*/
|
|
11
|
+
pollOptions?: PollOption[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Narrows a Payload-generated form document (from `getPayload().findByID()` or `fetch`) to
|
|
15
|
+
* `FormDocument` without an unsafe `as` cast. Handles the small structural mismatches between
|
|
16
|
+
* the auto-generated collection types and what `<Form>` expects:
|
|
17
|
+
* - `fields` may be a typed blocks-union array or null; normalized to `FormFieldInstance[]`
|
|
18
|
+
* - `flow` is stored as opaque JSON; typed as `FormFlow | undefined`
|
|
19
|
+
* - `response` may be null; coerced to `undefined`
|
|
20
|
+
* - `display` may be null; coerced to `undefined`
|
|
21
|
+
* - `poll` may be null; coerced to `undefined` (`resultsField`, `optionSource`, and
|
|
22
|
+
* `sourceConfig` are dropped: server-side only; `outcome` passes through `winningValue` only)
|
|
23
|
+
*
|
|
24
|
+
* Pure and framework-agnostic (no 'use client'): safe to call from a Server Component or any
|
|
25
|
+
* other server-side code before handing the result to the client `<Form>`.
|
|
26
|
+
*/
|
|
27
|
+
declare function toFormDocument(form: {
|
|
28
|
+
id: number | string;
|
|
29
|
+
fields?: {
|
|
30
|
+
blockType: string;
|
|
31
|
+
name: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}[] | null;
|
|
34
|
+
flow?: unknown;
|
|
35
|
+
response?: {
|
|
36
|
+
type?: string | null;
|
|
37
|
+
message?: unknown;
|
|
38
|
+
redirect?: {
|
|
39
|
+
url?: string | null;
|
|
40
|
+
} | null;
|
|
41
|
+
submitLabel?: string | null;
|
|
42
|
+
} | null;
|
|
43
|
+
display?: {
|
|
44
|
+
showTitle?: boolean | null;
|
|
45
|
+
title?: string | null;
|
|
46
|
+
intro?: unknown;
|
|
47
|
+
} | null;
|
|
48
|
+
poll?: {
|
|
49
|
+
enabled?: boolean | null;
|
|
50
|
+
resultsField?: string | null;
|
|
51
|
+
resultsVisibility?: string | null;
|
|
52
|
+
closesAt?: string | null;
|
|
53
|
+
outcome?: {
|
|
54
|
+
winningValue?: string | null;
|
|
55
|
+
} | null;
|
|
56
|
+
} | null;
|
|
57
|
+
}, options?: ToFormDocumentOptions): FormDocument;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ToFormDocumentOptions, toFormDocument };
|
|
60
|
+
//# sourceMappingURL=toFormDocument.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { applyPollOptions } from "../poll/applyPollOptions.js";
|
|
2
|
+
//#region src/form/toFormDocument.ts
|
|
3
|
+
/**
|
|
4
|
+
* Narrows a Payload-generated form document (from `getPayload().findByID()` or `fetch`) to
|
|
5
|
+
* `FormDocument` without an unsafe `as` cast. Handles the small structural mismatches between
|
|
6
|
+
* the auto-generated collection types and what `<Form>` expects:
|
|
7
|
+
* - `fields` may be a typed blocks-union array or null; normalized to `FormFieldInstance[]`
|
|
8
|
+
* - `flow` is stored as opaque JSON; typed as `FormFlow | undefined`
|
|
9
|
+
* - `response` may be null; coerced to `undefined`
|
|
10
|
+
* - `display` may be null; coerced to `undefined`
|
|
11
|
+
* - `poll` may be null; coerced to `undefined` (`resultsField`, `optionSource`, and
|
|
12
|
+
* `sourceConfig` are dropped: server-side only; `outcome` passes through `winningValue` only)
|
|
13
|
+
*
|
|
14
|
+
* Pure and framework-agnostic (no 'use client'): safe to call from a Server Component or any
|
|
15
|
+
* other server-side code before handing the result to the client `<Form>`.
|
|
16
|
+
*/
|
|
17
|
+
function toFormDocument(form, options) {
|
|
18
|
+
const winningValue = form.poll?.outcome?.winningValue;
|
|
19
|
+
const poll = form.poll ? {
|
|
20
|
+
enabled: form.poll.enabled ?? void 0,
|
|
21
|
+
resultsVisibility: form.poll.resultsVisibility ?? void 0,
|
|
22
|
+
closesAt: form.poll.closesAt ?? void 0,
|
|
23
|
+
...typeof winningValue === "string" && winningValue.length > 0 ? { outcome: { winningValue } } : {}
|
|
24
|
+
} : void 0;
|
|
25
|
+
let fields = form.fields ?? [];
|
|
26
|
+
if (options?.pollOptions) fields = applyPollOptions(fields, form.poll?.resultsField, options.pollOptions);
|
|
27
|
+
return {
|
|
28
|
+
id: form.id,
|
|
29
|
+
fields,
|
|
30
|
+
flow: form.flow,
|
|
31
|
+
response: form.response ?? void 0,
|
|
32
|
+
display: form.display ?? void 0,
|
|
33
|
+
poll
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { toFormDocument };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=toFormDocument.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toFormDocument.js","names":[],"sources":["../../src/form/toFormDocument.ts"],"sourcesContent":["import type { FormFlow } from '../flow/types'\nimport { applyPollOptions } from '../poll/applyPollOptions'\nimport type { PollOption } from '../poll/definePollOptionSource'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { FormDisplaySettings, FormDocument, FormResponseSettings } from './types'\n\nexport type ToFormDocumentOptions = {\n\t/**\n\t * Source-resolved poll options (from `resolvePollOptions`), injected as the options of the\n\t * field instance named by the form's `poll.resultsField` so the client renders the host's\n\t * current choices instead of hand-authored ones.\n\t */\n\tpollOptions?: PollOption[]\n}\n\n/**\n * Narrows a Payload-generated form document (from `getPayload().findByID()` or `fetch`) to\n * `FormDocument` without an unsafe `as` cast. Handles the small structural mismatches between\n * the auto-generated collection types and what `<Form>` expects:\n * - `fields` may be a typed blocks-union array or null; normalized to `FormFieldInstance[]`\n * - `flow` is stored as opaque JSON; typed as `FormFlow | undefined`\n * - `response` may be null; coerced to `undefined`\n * - `display` may be null; coerced to `undefined`\n * - `poll` may be null; coerced to `undefined` (`resultsField`, `optionSource`, and\n * `sourceConfig` are dropped: server-side only; `outcome` passes through `winningValue` only)\n *\n * Pure and framework-agnostic (no 'use client'): safe to call from a Server Component or any\n * other server-side code before handing the result to the client `<Form>`.\n */\nexport function toFormDocument(\n\tform: {\n\t\tid: number | string\n\t\tfields?: { blockType: string; name: string; [key: string]: unknown }[] | null\n\t\tflow?: unknown\n\t\tresponse?: {\n\t\t\ttype?: string | null\n\t\t\tmessage?: unknown\n\t\t\tredirect?: { url?: string | null } | null\n\t\t\tsubmitLabel?: string | null\n\t\t} | null\n\t\tdisplay?: {\n\t\t\tshowTitle?: boolean | null\n\t\t\ttitle?: string | null\n\t\t\tintro?: unknown\n\t\t} | null\n\t\tpoll?: {\n\t\t\tenabled?: boolean | null\n\t\t\tresultsField?: string | null\n\t\t\tresultsVisibility?: string | null\n\t\t\tclosesAt?: string | null\n\t\t\toutcome?: { winningValue?: string | null } | null\n\t\t} | null\n\t},\n\toptions?: ToFormDocumentOptions\n): FormDocument {\n\tconst winningValue = form.poll?.outcome?.winningValue\n\tconst poll = form.poll\n\t\t? {\n\t\t\t\tenabled: form.poll.enabled ?? undefined,\n\t\t\t\tresultsVisibility: (form.poll.resultsVisibility ?? undefined) as\n\t\t\t\t\t| 'afterVote'\n\t\t\t\t\t| 'afterClose'\n\t\t\t\t\t| undefined,\n\t\t\t\tclosesAt: form.poll.closesAt ?? undefined,\n\t\t\t\t...(typeof winningValue === 'string' && winningValue.length > 0\n\t\t\t\t\t? { outcome: { winningValue } }\n\t\t\t\t\t: {}),\n\t\t\t}\n\t\t: undefined\n\tlet fields = (form.fields ?? []) as FormFieldInstance[]\n\tif (options?.pollOptions) {\n\t\tfields = applyPollOptions(fields, form.poll?.resultsField, options.pollOptions)\n\t}\n\treturn {\n\t\tid: form.id,\n\t\tfields,\n\t\tflow: form.flow as FormFlow | undefined,\n\t\tresponse: (form.response as FormResponseSettings | null | undefined) ?? undefined,\n\t\tdisplay: (form.display as FormDisplaySettings | null | undefined) ?? undefined,\n\t\tpoll,\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6BA,SAAgB,eACf,MAuBA,SACe;CACf,MAAM,eAAe,KAAK,MAAM,SAAS;CACzC,MAAM,OAAO,KAAK,OACf;EACA,SAAS,KAAK,KAAK,WAAW,KAAA;EAC9B,mBAAoB,KAAK,KAAK,qBAAqB,KAAA;EAInD,UAAU,KAAK,KAAK,YAAY,KAAA;EAChC,GAAI,OAAO,iBAAiB,YAAY,aAAa,SAAS,IAC3D,EAAE,SAAS,EAAE,aAAa,EAAE,IAC5B,CAAC;CACL,IACC,KAAA;CACH,IAAI,SAAU,KAAK,UAAU,CAAC;CAC9B,IAAI,SAAS,aACZ,SAAS,iBAAiB,QAAQ,KAAK,MAAM,cAAc,QAAQ,WAAW;CAE/E,OAAO;EACN,IAAI,KAAK;EACT;EACA,MAAM,KAAK;EACX,UAAW,KAAK,YAAwD,KAAA;EACxE,SAAU,KAAK,WAAsD,KAAA;EACrE;CACD;AACD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { FormFieldInstance } from "../submissions/types.js";
|
|
2
|
+
import { FormFlow } from "../flow/types.js";
|
|
3
|
+
|
|
4
|
+
//#region src/form/types.d.ts
|
|
5
|
+
/** Serializable per-form response settings: what the visitor gets after a successful submit. */
|
|
6
|
+
type FormResponseSettings = {
|
|
7
|
+
type?: 'message' | 'redirect' | null; /** Rich text state serialized via `serializeBody`; shown instead of the plain `successMessage`. */
|
|
8
|
+
message?: unknown; /** Applies on the custom-`children` path too (part of submit handling); `message`/`submitLabel` only affect default rendering. */
|
|
9
|
+
redirect?: {
|
|
10
|
+
url?: string | null;
|
|
11
|
+
} | null;
|
|
12
|
+
submitLabel?: string | null;
|
|
13
|
+
};
|
|
14
|
+
/** Serializable per-form display settings: what the visitor sees above the fields, before submit. */
|
|
15
|
+
type FormDisplaySettings = {
|
|
16
|
+
showTitle?: boolean;
|
|
17
|
+
title?: string; /** Rich text state serialized via `serializeBody`; rendered above the fields when non-empty. */
|
|
18
|
+
intro?: unknown;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Serializable per-form poll settings the client needs for lifecycle rendering (open/voted/closed).
|
|
22
|
+
* `resultsField` intentionally stays server-side: the results endpoint resolves the public field
|
|
23
|
+
* itself, so the client never needs it from the document.
|
|
24
|
+
*/
|
|
25
|
+
type FormPollSettings = {
|
|
26
|
+
enabled?: boolean; /** When anonymous callers may read aggregate results. Default 'afterVote'. */
|
|
27
|
+
resultsVisibility?: 'afterVote' | 'afterClose' | null; /** ISO date after which submissions are rejected and the poll renders as closed. */
|
|
28
|
+
closesAt?: string | null; /** Present once the host recorded a final outcome via `resolvePollOutcome`; `<Poll>` renders the final state. */
|
|
29
|
+
outcome?: {
|
|
30
|
+
winningValue?: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Serializable shape `<Form>` expects, and what `toFormDocument` narrows a Payload-generated
|
|
35
|
+
* document to. Framework-agnostic (no React, no 'use client') so it can be constructed and
|
|
36
|
+
* consumed on the server, e.g. in a Server Component before handing it to the client `<Form>`.
|
|
37
|
+
*/
|
|
38
|
+
type FormDocument = {
|
|
39
|
+
id: number | string;
|
|
40
|
+
fields: FormFieldInstance[];
|
|
41
|
+
flow?: FormFlow;
|
|
42
|
+
response?: FormResponseSettings;
|
|
43
|
+
display?: FormDisplaySettings;
|
|
44
|
+
poll?: FormPollSettings;
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { FormDisplaySettings, FormDocument, FormPollSettings, FormResponseSettings };
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|