@10x-media/form-builder 0.1.0-beta.1 → 0.1.0-beta.2
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 +16 -0
- package/README.md +3 -1
- package/dist/collections/formSubmissions.js +57 -38
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +125 -114
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/uploads.d.ts +2 -1
- package/dist/collections/uploads.js +22 -13
- package/dist/collections/uploads.js.map +1 -1
- package/dist/exports/react.d.ts +2 -2
- package/dist/exports/react.js +1 -1
- package/dist/exports/types.d.ts +2 -1
- package/dist/fields/buildFieldBlocks.js +14 -0
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/index.js +3 -1
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/repeater.js +44 -0
- package/dist/fields/builtin/repeater.js.map +1 -0
- package/dist/fields/types.d.ts +2 -1
- package/dist/index.d.ts +19 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/collectionOverrides.d.ts +22 -0
- package/dist/plugin/registerCollections.js +37 -9
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/react/Form.d.ts +32 -3
- package/dist/react/Form.js +47 -5
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.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/repeater.js +113 -0
- package/dist/react/renderers/repeater.js.map +1 -0
- package/dist/submissions/SubmissionAnswers.d.ts +2 -4
- package/dist/submissions/SubmissionAnswers.js +110 -34
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +142 -0
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -0
- package/dist/submissions/runSubmission.js +54 -2
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +2 -1
- package/dist/translations/en.js +26 -1
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +25 -0
- package/dist/translations/keys.js +26 -1
- package/dist/translations/keys.js.map +1 -1
- package/dist/validation/runValidation.js +1 -1
- package/dist/validation/runValidation.js.map +1 -1
- package/package.json +3 -3
package/dist/exports/react.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { FormPresentation, PresentationWrapperProps } from "../react/presentatio
|
|
|
12
12
|
import { PresentationOption, PresentationRegistry, PresentationsConfig, resolvePresentations } from "../react/presentation/registry.js";
|
|
13
13
|
import { RendererOption, RendererRegistry, RenderersConfig, resolveRenderers } from "../react/registry.js";
|
|
14
14
|
import { SubmitFormResult, SubmitHandler, submitForm } from "../react/submitForm.js";
|
|
15
|
-
import { Form, FormDocument, FormProps } from "../react/Form.js";
|
|
15
|
+
import { BackButtonRenderProps, Form, FormDocument, FormProps, NextButtonRenderProps, SubmitButtonRenderProps } from "../react/Form.js";
|
|
16
16
|
import { FormState } from "../react/state.js";
|
|
17
17
|
import { FormStepInfo } from "../react/FormContext.js";
|
|
18
18
|
import { FieldWidth, FormLayout, FormLayoutProps, widthProps } from "../react/FormLayout.js";
|
|
@@ -42,4 +42,4 @@ import { RecallResolver, buildRecallResolver } from "../recall/resolver.js";
|
|
|
42
42
|
import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
|
|
43
43
|
import { en } from "../translations/en.js";
|
|
44
44
|
import { makeTranslate } from "../translations/makeTranslate.js";
|
|
45
|
-
export { type AggregationBucket, Backdrop, type BackdropProps, CAPTCHA_TOKEN_KEY, type CalcExpression, Checkbox, type CheckboxProps, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormDocument, type FormFieldInstance, FormLayout, type FormLayoutProps, type FormPresentation, type FormProps, FormResults, type FormResultsProps, type FormState, type FormStepInfo, Honeypot, type HoneypotProps, Input, type InputProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, type RecallResolver, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmitFormResult, type SubmitHandler, Textarea, type TextareaProps, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
|
45
|
+
export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, CAPTCHA_TOKEN_KEY, type CalcExpression, Checkbox, type CheckboxProps, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormDocument, type FormFieldInstance, FormLayout, type FormLayoutProps, type FormPresentation, type FormProps, FormResults, type FormResultsProps, type FormState, type FormStepInfo, Honeypot, type HoneypotProps, Input, type InputProps, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, type RecallResolver, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, Textarea, type TextareaProps, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
|
package/dist/exports/react.js
CHANGED
|
@@ -25,11 +25,11 @@ import { FieldShell } from "../react/primitives/FieldShell.js";
|
|
|
25
25
|
import { Checkbox } from "../react/primitives/Checkbox.js";
|
|
26
26
|
import { Input } from "../react/primitives/Input.js";
|
|
27
27
|
import { uploadFile } from "../react/uploadFile.js";
|
|
28
|
+
import { useField } from "../react/useField.js";
|
|
28
29
|
import { Select } from "../react/primitives/Select.js";
|
|
29
30
|
import { Textarea } from "../react/primitives/Textarea.js";
|
|
30
31
|
import { defaultRenderers } from "../react/renderers/index.js";
|
|
31
32
|
import { submitForm } from "../react/submitForm.js";
|
|
32
|
-
import { useField } from "../react/useField.js";
|
|
33
33
|
import { Form } from "../react/Form.js";
|
|
34
34
|
import { FormResults } from "../react/FormResults.js";
|
|
35
35
|
import { fetchFormResults } from "../react/fetchResults.js";
|
package/dist/exports/types.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { ActionOption, ActionRegistry, ActionsConfig } from "../actions/registry
|
|
|
15
15
|
import { ActionResult } from "../actions/runActions.js";
|
|
16
16
|
import { AggregateFieldResponsesArgs, AggregateFormResponsesArgs } from "../aggregation/aggregateResponses.js";
|
|
17
17
|
import { ResolveResultsRequestArgs, ResolveResultsRequestResult } from "../aggregation/resolveResultsRequest.js";
|
|
18
|
+
import { CollectionOverrides, FieldsOverride } from "../plugin/collectionOverrides.js";
|
|
18
19
|
import { UploadsCollectionConfig, UploadsOption } from "../collections/uploads.js";
|
|
19
20
|
import { FieldCondition } from "../conditions/types.js";
|
|
20
21
|
import { AnyConsentSource, ConsentLink, ConsentResolveArgs, ConsentResolved, ConsentSource } from "../consent/defineConsentSource.js";
|
|
@@ -24,4 +25,4 @@ import { PresentationDescriptorOption, PresentationDescriptorRegistry, Presentat
|
|
|
24
25
|
import { CaptchaProvider, CaptchaVerifyArgs, IdentifyFn, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, SpamConfig, SpamMetadataConfig, SpamOption } from "../spam/types.js";
|
|
25
26
|
import { ValidationRuleOption, ValidationRuleRegistry, ValidationRulesConfig } from "../validation/registry.js";
|
|
26
27
|
import { FormBuilderPluginOptions } from "../index.js";
|
|
27
|
-
export type { ActionDefinition, ActionOption, ActionRegistry, ActionResult, ActionRunArgs, ActionsConfig, AggregateFieldResponsesArgs, AggregateFormResponsesArgs, AggregationBucket, AggregationRow, AnyActionDefinition, AnyConsentSource, AnyFormFieldDefinition, AnyValidationRuleDefinition, CalcExpression, CaptchaProvider, CaptchaVerifyArgs, ConsentLink, ConsentProof, ConsentResolveArgs, ConsentResolved, ConsentSource, ConsentSourceOption, ConsentSourceRegistry, ConsentSourcesConfig, FieldAggregation, FieldCondition, FieldMeta, FieldTypeOption, FieldTypeRegistry, FieldTypesConfig, FileFieldConfig, FileRef, FileRefError, FlowStep, FlowTransition, FormBuilderPluginOptions, FormEvent, FormEventSink, FormFieldDefinition, FormFieldFormat, FormFieldInstance, FormFieldValidate, FormFieldValueKind, FormFlow, IdentifyFn, PrefillOptions, PresentationDensity, PresentationDescriptor, PresentationDescriptorOption, PresentationDescriptorRegistry, PresentationSurface, PresentationsDescriptorConfig, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, RecallResolver, ResolveResultsRequestArgs, ResolveResultsRequestResult, SpamConfig, SpamMetadataConfig, SpamOption, SubmissionDescriptor, SubmissionFieldError, SubmissionStatusFilter, SubmissionValue, UploadsCollectionConfig, UploadsOption, ValidationRuleDefinition, ValidationRuleOption, ValidationRuleRegistry, ValidationRuleResult, ValidationRulesConfig, ValidationSeverity };
|
|
28
|
+
export type { ActionDefinition, ActionOption, ActionRegistry, ActionResult, ActionRunArgs, ActionsConfig, AggregateFieldResponsesArgs, AggregateFormResponsesArgs, AggregationBucket, AggregationRow, AnyActionDefinition, AnyConsentSource, AnyFormFieldDefinition, AnyValidationRuleDefinition, CalcExpression, CaptchaProvider, CaptchaVerifyArgs, CollectionOverrides, ConsentLink, ConsentProof, ConsentResolveArgs, ConsentResolved, ConsentSource, ConsentSourceOption, ConsentSourceRegistry, ConsentSourcesConfig, FieldAggregation, FieldCondition, FieldMeta, FieldTypeOption, FieldTypeRegistry, FieldTypesConfig, FieldsOverride, FileFieldConfig, FileRef, FileRefError, FlowStep, FlowTransition, FormBuilderPluginOptions, FormEvent, FormEventSink, FormFieldDefinition, FormFieldFormat, FormFieldInstance, FormFieldValidate, FormFieldValueKind, FormFlow, IdentifyFn, PrefillOptions, PresentationDensity, PresentationDescriptor, PresentationDescriptorOption, PresentationDescriptorRegistry, PresentationSurface, PresentationsDescriptorConfig, RateLimitCheckArgs, RateLimitConfig, RateLimitResult, RateLimiter, RecallResolver, ResolveResultsRequestArgs, ResolveResultsRequestResult, SpamConfig, SpamMetadataConfig, SpamOption, SubmissionDescriptor, SubmissionFieldError, SubmissionStatusFilter, SubmissionValue, UploadsCollectionConfig, UploadsOption, ValidationRuleDefinition, ValidationRuleOption, ValidationRuleRegistry, ValidationRuleResult, ValidationRulesConfig, ValidationSeverity };
|
|
@@ -38,6 +38,20 @@ const buildFieldBlocks = (registry, ruleRegistry, consentRegistry) => {
|
|
|
38
38
|
]
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
+
const repeaterBlock = blocks.find((b) => b.slug === "repeater");
|
|
42
|
+
if (repeaterBlock) {
|
|
43
|
+
const subFieldBlocks = blocks.filter((b) => b.slug !== "repeater");
|
|
44
|
+
const fieldsArr = repeaterBlock.fields;
|
|
45
|
+
const validationsIdx = fieldsArr.findIndex((f) => f.name === "validations");
|
|
46
|
+
const subFieldsField = {
|
|
47
|
+
name: "subFields",
|
|
48
|
+
type: "blocks",
|
|
49
|
+
label: labelFor(keys.configSubFields),
|
|
50
|
+
blocks: subFieldBlocks
|
|
51
|
+
};
|
|
52
|
+
if (validationsIdx >= 0) fieldsArr.splice(validationsIdx, 0, subFieldsField);
|
|
53
|
+
else fieldsArr.push(subFieldsField);
|
|
54
|
+
}
|
|
41
55
|
return blocks;
|
|
42
56
|
};
|
|
43
57
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { buildConsentSourceConfig } from '../consent/buildConsentSourceConfig'\nimport type { ConsentSourceRegistry } from '../consent/registry'\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 { sharedFieldConfig } from './sharedConfig'\n\n/** One add-field block per registered type: shared config, the type's own config, then its validations. */\nexport const buildFieldBlocks = (\n\tregistry: FieldTypeRegistry,\n\truleRegistry: ValidationRuleRegistry,\n\tconsentRegistry?: ConsentSourceRegistry\n): Block[] => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst blocks: Block[] = []\n\tfor (const definition of registry.values()) {\n\t\tlet typeConfig: Field[] = definition.config ?? []\n\n\t\t// Inject dynamic source select + conditional sourceConfig group for the consent field.\n\t\t// consent.ts intentionally omits source/sourceConfig; the live registry drives the\n\t\t// select options and per-source field visibility (admin.condition).\n\t\tif (definition.type === 'consent' && consentRegistry) {\n\t\t\t// statement is first; optional is last; source/sourceConfig go in between.\n\t\t\tconst statement = typeConfig.find((f) => (f as { name?: string }).name === 'statement')\n\t\t\tconst optional = typeConfig.find((f) => (f as { name?: string }).name === 'optional')\n\t\t\ttypeConfig = [\n\t\t\t\t...(statement ? [statement] : []),\n\t\t\t\t...buildConsentSourceConfig(consentRegistry),\n\t\t\t\t...(optional ? [optional] : []),\n\t\t\t]\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\tfields: [\n\t\t\t\t...sharedFieldConfig(conditionTypes),\n\t\t\t\t...typeConfig,\n\t\t\t\t{\n\t\t\t\t\tname: 'validations',\n\t\t\t\t\ttype: 'blocks',\n\t\t\t\t\tlabel: labelFor(keys.validationsLabel),\n\t\t\t\t\tblocks: buildRuleBlocks(ruleRegistry, definition.type),\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t}\n\treturn blocks\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,oBACZ,UACA,cACA,oBACa;CACb,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,SAAkB,CAAC;CACzB,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,aAAsB,WAAW,UAAU,CAAC;EAKhD,IAAI,WAAW,SAAS,aAAa,iBAAiB;GAErD,MAAM,YAAY,WAAW,MAAM,MAAO,EAAwB,SAAS,WAAW;GACtF,MAAM,WAAW,WAAW,MAAM,MAAO,EAAwB,SAAS,UAAU;GACpF,aAAa;IACZ,GAAI,YAAY,CAAC,SAAS,IAAI,CAAC;IAC/B,GAAG,yBAAyB,eAAe;IAC3C,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;GAC9B;EACD;EAEA,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU,SAAS,WAAW,KAAK;IAAG,QAAQ,SAAS,WAAW,KAAK;GAAE;GACnF,QAAQ;IACP,GAAG,kBAAkB,cAAc;IACnC,GAAG;IACH;KACC,MAAM;KACN,MAAM;KACN,OAAO,SAAS,KAAK,gBAAgB;KACrC,QAAQ,gBAAgB,cAAc,WAAW,IAAI;IACtD;GACD;EACD,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { buildConsentSourceConfig } from '../consent/buildConsentSourceConfig'\nimport type { ConsentSourceRegistry } from '../consent/registry'\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 { sharedFieldConfig } from './sharedConfig'\n\n/** One add-field block per registered type: shared config, the type's own config, then its validations. */\nexport const buildFieldBlocks = (\n\tregistry: FieldTypeRegistry,\n\truleRegistry: ValidationRuleRegistry,\n\tconsentRegistry?: ConsentSourceRegistry\n): Block[] => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst blocks: Block[] = []\n\tfor (const definition of registry.values()) {\n\t\tlet typeConfig: Field[] = definition.config ?? []\n\n\t\t// Inject dynamic source select + conditional sourceConfig group for the consent field.\n\t\t// consent.ts intentionally omits source/sourceConfig; the live registry drives the\n\t\t// select options and per-source field visibility (admin.condition).\n\t\tif (definition.type === 'consent' && consentRegistry) {\n\t\t\t// statement is first; optional is last; source/sourceConfig go in between.\n\t\t\tconst statement = typeConfig.find((f) => (f as { name?: string }).name === 'statement')\n\t\t\tconst optional = typeConfig.find((f) => (f as { name?: string }).name === 'optional')\n\t\t\ttypeConfig = [\n\t\t\t\t...(statement ? [statement] : []),\n\t\t\t\t...buildConsentSourceConfig(consentRegistry),\n\t\t\t\t...(optional ? [optional] : []),\n\t\t\t]\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\tfields: [\n\t\t\t\t...sharedFieldConfig(conditionTypes),\n\t\t\t\t...typeConfig,\n\t\t\t\t{\n\t\t\t\t\tname: 'validations',\n\t\t\t\t\ttype: 'blocks',\n\t\t\t\t\tlabel: labelFor(keys.validationsLabel),\n\t\t\t\t\tblocks: buildRuleBlocks(ruleRegistry, definition.type),\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\tconst repeaterBlock = blocks.find((b) => b.slug === 'repeater')\n\tif (repeaterBlock) {\n\t\tconst subFieldBlocks = blocks.filter((b) => b.slug !== 'repeater')\n\t\tconst fieldsArr = repeaterBlock.fields as Field[]\n\t\tconst validationsIdx = fieldsArr.findIndex(\n\t\t\t(f) => (f as { name?: string }).name === 'validations'\n\t\t)\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: subFieldBlocks,\n\t\t}\n\t\tif (validationsIdx >= 0) {\n\t\t\tfieldsArr.splice(validationsIdx, 0, subFieldsField)\n\t\t} else {\n\t\t\tfieldsArr.push(subFieldsField)\n\t\t}\n\t}\n\n\treturn blocks\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,oBACZ,UACA,cACA,oBACa;CACb,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,SAAkB,CAAC;CACzB,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,aAAsB,WAAW,UAAU,CAAC;EAKhD,IAAI,WAAW,SAAS,aAAa,iBAAiB;GAErD,MAAM,YAAY,WAAW,MAAM,MAAO,EAAwB,SAAS,WAAW;GACtF,MAAM,WAAW,WAAW,MAAM,MAAO,EAAwB,SAAS,UAAU;GACpF,aAAa;IACZ,GAAI,YAAY,CAAC,SAAS,IAAI,CAAC;IAC/B,GAAG,yBAAyB,eAAe;IAC3C,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;GAC9B;EACD;EAEA,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU,SAAS,WAAW,KAAK;IAAG,QAAQ,SAAS,WAAW,KAAK;GAAE;GACnF,QAAQ;IACP,GAAG,kBAAkB,cAAc;IACnC,GAAG;IACH;KACC,MAAM;KACN,MAAM;KACN,OAAO,SAAS,KAAK,gBAAgB;KACrC,QAAQ,gBAAgB,cAAc,WAAW,IAAI;IACtD;GACD;EACD,CAAC;CACF;CAKA,MAAM,gBAAgB,OAAO,MAAM,MAAM,EAAE,SAAS,UAAU;CAC9D,IAAI,eAAe;EAClB,MAAM,iBAAiB,OAAO,QAAQ,MAAM,EAAE,SAAS,UAAU;EACjE,MAAM,YAAY,cAAc;EAChC,MAAM,iBAAiB,UAAU,WAC/B,MAAO,EAAwB,SAAS,aAC1C;EACA,MAAM,iBAAwB;GAC7B,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,eAAe;GACpC,QAAQ;EACT;EACA,IAAI,kBAAkB,GACrB,UAAU,OAAO,gBAAgB,GAAG,cAAc;OAElD,UAAU,KAAK,cAAc;CAE/B;CAEA,OAAO;AACR"}
|
|
@@ -4,6 +4,7 @@ import { consentField } from "./consent.js";
|
|
|
4
4
|
import { emailField } from "./email.js";
|
|
5
5
|
import { fileField } from "./file.js";
|
|
6
6
|
import { numberField } from "./number.js";
|
|
7
|
+
import { repeaterField } from "./repeater.js";
|
|
7
8
|
import { selectField } from "./select.js";
|
|
8
9
|
import { textField } from "./text.js";
|
|
9
10
|
import { textareaField } from "./textarea.js";
|
|
@@ -17,7 +18,8 @@ const defaultFieldDefinitions = [
|
|
|
17
18
|
checkboxField,
|
|
18
19
|
calculationField,
|
|
19
20
|
consentField,
|
|
20
|
-
fileField
|
|
21
|
+
fileField,
|
|
22
|
+
repeaterField
|
|
21
23
|
];
|
|
22
24
|
//#endregion
|
|
23
25
|
export { defaultFieldDefinitions };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { emailField } from './email'\nimport { fileField } from './file'\nimport { numberField } from './number'\nimport { selectField } from './select'\nimport { textField } from './text'\nimport { textareaField } from './textarea'\n\n// Field types are authored with precise value/config generics; the registry stores them erased\n// (config is re-narrowed per matched type at execution). One cast per built-in, no `any`.\nexport const defaultFieldDefinitions: AnyFormFieldDefinition[] = [\n\ttextField as AnyFormFieldDefinition,\n\ttextareaField as AnyFormFieldDefinition,\n\temailField as AnyFormFieldDefinition,\n\tnumberField as AnyFormFieldDefinition,\n\tselectField as AnyFormFieldDefinition,\n\tcheckboxField as AnyFormFieldDefinition,\n\tcalculationField as AnyFormFieldDefinition,\n\tconsentField as AnyFormFieldDefinition,\n\tfileField as AnyFormFieldDefinition,\n]\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { emailField } from './email'\nimport { fileField } from './file'\nimport { numberField } from './number'\nimport { repeaterField } from './repeater'\nimport { selectField } from './select'\nimport { textField } from './text'\nimport { textareaField } from './textarea'\n\n// Field types are authored with precise value/config generics; the registry stores them erased\n// (config is re-narrowed per matched type at execution). One cast per built-in, no `any`.\nexport const defaultFieldDefinitions: AnyFormFieldDefinition[] = [\n\ttextField as AnyFormFieldDefinition,\n\ttextareaField as AnyFormFieldDefinition,\n\temailField as AnyFormFieldDefinition,\n\tnumberField as AnyFormFieldDefinition,\n\tselectField as AnyFormFieldDefinition,\n\tcheckboxField as AnyFormFieldDefinition,\n\tcalculationField as AnyFormFieldDefinition,\n\tconsentField as AnyFormFieldDefinition,\n\tfileField as AnyFormFieldDefinition,\n\trepeaterField as AnyFormFieldDefinition,\n]\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,0BAAoD;CAChE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { keys } from "../../translations/keys.js";
|
|
2
|
+
import { labelFor } from "../../translations/server.js";
|
|
3
|
+
import { defineFormField } from "../defineFormField.js";
|
|
4
|
+
//#region src/fields/builtin/repeater.ts
|
|
5
|
+
const repeaterField = defineFormField({
|
|
6
|
+
type: "repeater",
|
|
7
|
+
label: keys.fieldTypeRepeater,
|
|
8
|
+
value: "repeater",
|
|
9
|
+
config: [
|
|
10
|
+
{
|
|
11
|
+
name: "minRows",
|
|
12
|
+
type: "number",
|
|
13
|
+
min: 0,
|
|
14
|
+
label: labelFor(keys.configMinRows)
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "maxRows",
|
|
18
|
+
type: "number",
|
|
19
|
+
min: 1,
|
|
20
|
+
label: labelFor(keys.configMaxRows)
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "addLabel",
|
|
24
|
+
type: "text",
|
|
25
|
+
label: labelFor(keys.configAddLabel)
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
validate: ({ value, config, t }) => {
|
|
29
|
+
const rows = Array.isArray(value) ? value : [];
|
|
30
|
+
const min = typeof config.minRows === "number" ? config.minRows : 0;
|
|
31
|
+
if (rows.length < min) return t(keys.validationRepeaterMin).replace("{min}", String(min));
|
|
32
|
+
const max = typeof config.maxRows === "number" ? config.maxRows : void 0;
|
|
33
|
+
if (max != null && rows.length > max) return t(keys.validationRepeaterMax).replace("{max}", String(max));
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
format: ({ value, t }) => {
|
|
37
|
+
const count = Array.isArray(value) ? value.length : 0;
|
|
38
|
+
return t(keys.repeaterRowCount).replace("{count}", String(count));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
//#endregion
|
|
42
|
+
export { repeaterField };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=repeater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repeater.js","names":[],"sources":["../../../src/fields/builtin/repeater.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { defineFormField } from '../defineFormField'\n\ntype RepeaterConfig = {\n\tminRows?: number\n\tmaxRows?: number\n\taddLabel?: string\n}\n\nexport const repeaterField = defineFormField<'repeater', RepeaterConfig>({\n\ttype: 'repeater',\n\tlabel: keys.fieldTypeRepeater,\n\tvalue: 'repeater',\n\tconfig: [\n\t\t{\n\t\t\tname: 'minRows',\n\t\t\ttype: 'number',\n\t\t\tmin: 0,\n\t\t\tlabel: labelFor(keys.configMinRows),\n\t\t},\n\t\t{\n\t\t\tname: 'maxRows',\n\t\t\ttype: 'number',\n\t\t\tmin: 1,\n\t\t\tlabel: labelFor(keys.configMaxRows),\n\t\t},\n\t\t{\n\t\t\tname: 'addLabel',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelFor(keys.configAddLabel),\n\t\t},\n\t\t// subFields blocks field is injected by buildFieldBlocks after the main loop,\n\t\t// so all non-repeater blocks are available and repeater-in-repeater is excluded.\n\t],\n\tvalidate: ({ value, config, t }) => {\n\t\tconst rows = Array.isArray(value) ? value : []\n\t\tconst min = typeof config.minRows === 'number' ? config.minRows : 0\n\t\tif (rows.length < min) {\n\t\t\treturn t(keys.validationRepeaterMin).replace('{min}', String(min))\n\t\t}\n\t\tconst max = typeof config.maxRows === 'number' ? config.maxRows : undefined\n\t\tif (max != null && rows.length > max) {\n\t\t\treturn t(keys.validationRepeaterMax).replace('{max}', String(max))\n\t\t}\n\t\treturn true\n\t},\n\tformat: ({ value, t }) => {\n\t\tconst count = Array.isArray(value) ? value.length : 0\n\t\treturn t(keys.repeaterRowCount).replace('{count}', String(count))\n\t},\n})\n"],"mappings":";;;;AAUA,MAAa,gBAAgB,gBAA4C;CACxE,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,QAAQ;EACP;GACC,MAAM;GACN,MAAM;GACN,KAAK;GACL,OAAO,SAAS,KAAK,aAAa;EACnC;EACA;GACC,MAAM;GACN,MAAM;GACN,KAAK;GACL,OAAO,SAAS,KAAK,aAAa;EACnC;EACA;GACC,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,cAAc;EACpC;CAGD;CACA,WAAW,EAAE,OAAO,QAAQ,QAAQ;EACnC,MAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;EAC7C,MAAM,MAAM,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;EAClE,IAAI,KAAK,SAAS,KACjB,OAAO,EAAE,KAAK,qBAAqB,EAAE,QAAQ,SAAS,OAAO,GAAG,CAAC;EAElE,MAAM,MAAM,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,KAAA;EAClE,IAAI,OAAO,QAAQ,KAAK,SAAS,KAChC,OAAO,EAAE,KAAK,qBAAqB,EAAE,QAAQ,SAAS,OAAO,GAAG,CAAC;EAElE,OAAO;CACR;CACA,SAAS,EAAE,OAAO,QAAQ;EACzB,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;EACpD,OAAO,EAAE,KAAK,gBAAgB,EAAE,QAAQ,WAAW,OAAO,KAAK,CAAC;CACjE;AACD,CAAC"}
|
package/dist/fields/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
|
5
5
|
|
|
6
6
|
//#region src/fields/types.d.ts
|
|
7
7
|
/** The stored value shapes a field type can declare. Drives `validate`/`format` typing and value coercion. */
|
|
8
|
-
type FormFieldValueKind = 'text' | 'number' | 'boolean' | 'date' | 'text[]' | 'file';
|
|
8
|
+
type FormFieldValueKind = 'text' | 'number' | 'boolean' | 'date' | 'text[]' | 'file' | 'repeater';
|
|
9
9
|
type ValueKindTypeMap = {
|
|
10
10
|
text: string;
|
|
11
11
|
number: number;
|
|
@@ -13,6 +13,7 @@ type ValueKindTypeMap = {
|
|
|
13
13
|
date: string;
|
|
14
14
|
'text[]': string[];
|
|
15
15
|
file: FileRef;
|
|
16
|
+
repeater: Array<Record<string, unknown>>;
|
|
16
17
|
};
|
|
17
18
|
type ValueOfKind<K extends FormFieldValueKind> = ValueKindTypeMap[K];
|
|
18
19
|
/** Resolved per-instance config values for a field type (loose at the DB boundary, narrowed by the author's generic). */
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { ActionOption, ActionRegistry, ActionsConfig, resolveActions } from "./a
|
|
|
19
19
|
import { ActionResult } from "./actions/runActions.js";
|
|
20
20
|
import { AggregateFieldResponsesArgs, AggregateFormResponsesArgs, aggregateFieldResponses, aggregateFormResponses, fieldHasOptions } from "./aggregation/aggregateResponses.js";
|
|
21
21
|
import { ResolveResultsRequestArgs, ResolveResultsRequestResult, resolveFormResultsRequest } from "./aggregation/resolveResultsRequest.js";
|
|
22
|
+
import { CollectionOverrides } from "./plugin/collectionOverrides.js";
|
|
22
23
|
import { FORM_UPLOADS_SLUG, UploadsCollectionConfig, UploadsOption, buildUploadsCollection, resolveUploads } from "./collections/uploads.js";
|
|
23
24
|
import { FieldCondition } from "./conditions/types.js";
|
|
24
25
|
import { AnyConsentSource, ConsentLink, ConsentResolveArgs, ConsentResolved, ConsentSource, defineConsentSource } from "./consent/defineConsentSource.js";
|
|
@@ -62,6 +63,24 @@ type FormBuilderPluginOptions = {
|
|
|
62
63
|
consentSources?: ConsentSourcesConfig; /** The built-in `form-uploads` collection backing file fields. `false` disables it (bring your own); an object overrides slug/upload/access/fields. */
|
|
63
64
|
uploads?: UploadsOption; /** Honeypot + rate-limiting (on by default) + a captcha adapter seam + upload-ownership scoping. `false` disables the whole subsystem. */
|
|
64
65
|
spam?: SpamOption;
|
|
66
|
+
/**
|
|
67
|
+
* When `true`, the raw `values`, `descriptors`, and `consent` JSON fields are visible in the
|
|
68
|
+
* submission admin view. Default `false` — those fields are fully represented by the
|
|
69
|
+
* `SubmissionAnswers` UI component and are noisy when shown alongside it.
|
|
70
|
+
*/
|
|
71
|
+
showSubmissionRawFields?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Override individual plugin-managed collections using explicit spreads. Each key accepts a
|
|
74
|
+
* `CollectionOverrides` object: top-level keys are spread with the plugin's defaults (spread
|
|
75
|
+
* order per key determines who wins), hooks are appended after the plugin's own hooks, and
|
|
76
|
+
* `fields` is a function that receives the default fields and returns the final array so
|
|
77
|
+
* additions/removals are always intentional.
|
|
78
|
+
*/
|
|
79
|
+
overrides?: {
|
|
80
|
+
forms?: CollectionOverrides;
|
|
81
|
+
formSubmissions?: CollectionOverrides;
|
|
82
|
+
uploads?: CollectionOverrides;
|
|
83
|
+
};
|
|
65
84
|
};
|
|
66
85
|
declare module 'payload' {
|
|
67
86
|
interface RegisteredPlugins {
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,9 @@ const formBuilder = definePlugin({
|
|
|
61
61
|
hasJobsPlugin: Boolean(plugins["@10x-media/jobs"]),
|
|
62
62
|
events: options.events,
|
|
63
63
|
uploads,
|
|
64
|
-
spam
|
|
64
|
+
spam,
|
|
65
|
+
showSubmissionRawFields: options.showSubmissionRawFields ?? false,
|
|
66
|
+
overrides: options.overrides
|
|
65
67
|
});
|
|
66
68
|
return config;
|
|
67
69
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { defaultActionDefinitions } from './actions/builtin'\nimport type { ActionsConfig } from './actions/registry'\nimport { resolveActions } from './actions/registry'\nimport { resolveUploads, type UploadsOption } from './collections/uploads'\nimport { defaultConsentSources } from './consent/builtin'\nimport type { ConsentSourcesConfig } from './consent/registry'\nimport { resolveConsentSources } from './consent/registry'\nimport type { FormEventSink } from './events/types'\nimport { defaultFieldDefinitions } from './fields/builtin'\nimport { type FieldTypesConfig, resolveFieldTypes } from './fields/registry'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { defaultPresentationDescriptors } from './presentations/defaults'\nimport type { PresentationsDescriptorConfig } from './presentations/registry'\nimport { resolvePresentationDescriptors } from './presentations/registry'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport type { SpamOption } from './spam/types'\nimport type { TranslationsOption } from './translations'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules, type ValidationRulesConfig } from './validation/registry'\n\nexport type FormBuilderPluginOptions = {\n\tdisabled?: boolean\n\t/**\n\t * Per-locale overrides for this plugin's UI strings, keyed by the typed\n\t * translation keys exported from `@10x-media/form-builder/i18n`. Values win\n\t * over the built-in locales key-by-key; locales the plugin does not ship are\n\t * added whole. App-level `i18n.translations` still wins over both.\n\t */\n\ttranslations?: TranslationsOption\n\t/** Pluggable sink for form lifecycle events. Defaults to a no-op; analytics adapters or a future analytics plugin subscribe here. */\n\tevents?: FormEventSink\n\t/** Add, override, or remove field types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tfields?: FieldTypesConfig\n\t/** Add, override, or remove presentations. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tpresentations?: PresentationsDescriptorConfig\n\t/** Add, override, or remove validation rule types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\trules?: ValidationRulesConfig\n\t/** Add, override, or remove post-submit action types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tactions?: ActionsConfig\n\t/** Add, override, or remove consent source types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tconsentSources?: ConsentSourcesConfig\n\t/** The built-in `form-uploads` collection backing file fields. `false` disables it (bring your own); an object overrides slug/upload/access/fields. */\n\tuploads?: UploadsOption\n\t/** Honeypot + rate-limiting (on by default) + a captcha adapter seam + upload-ownership scoping. `false` disables the whole subsystem. */\n\tspam?: SpamOption\n}\n\ndeclare module 'payload' {\n\tinterface RegisteredPlugins {\n\t\t'@10x-media/form-builder': FormBuilderPluginOptions\n\t}\n}\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst consentRegistry = resolveConsentSources(defaultConsentSources, options.consentSources)\n\t\tconst presentationRegistry = resolvePresentationDescriptors(\n\t\t\tdefaultPresentationDescriptors,\n\t\t\toptions.presentations\n\t\t)\n\t\tconst actionRegistry = resolveActions(defaultActionDefinitions, options.actions)\n\t\tconst uploads = resolveUploads(options.uploads)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tpresentationRegistry,\n\t\t\tactionRegistry,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport { defaultActionDefinitions } from './actions/builtin'\nexport type { ActionDefinition, ActionRunArgs, AnyActionDefinition } from './actions/defineAction'\nexport { defineAction } from './actions/defineAction'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport { evaluateCalc } from './calc/evaluate'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type { CalcExpression } from './calc/types'\nexport type { UploadsCollectionConfig, UploadsOption } from './collections/uploads'\nexport { buildUploadsCollection, FORM_UPLOADS_SLUG, resolveUploads } from './collections/uploads'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { defaultConsentSources } from './consent/builtin'\nexport type { ConsentProof } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type {\n\tAnyConsentSource,\n\tConsentLink,\n\tConsentResolveArgs,\n\tConsentResolved,\n\tConsentSource,\n} from './consent/defineConsentSource'\nexport { defineConsentSource } from './consent/defineConsentSource'\nexport type {\n\tConsentSourceOption,\n\tConsentSourceRegistry,\n\tConsentSourcesConfig,\n} from './consent/registry'\nexport { resolveConsentSources } from './consent/registry'\nexport { resolveConsentLinks } from './consent/resolveConsentLinks'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport { defineFormField } from './fields/defineFormField'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n} from './fields/types'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDescriptorOption,\n\tPresentationDescriptorRegistry,\n\tPresentationsDescriptorConfig,\n} from './presentations/registry'\nexport { resolvePresentationDescriptors } from './presentations/registry'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n\tValidationSeverity,\n} from './validation/types'\nexport type { FormBuilderPluginOptions as PluginOptions }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,WAAW,kBAAkB,yBAAyB,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,kBAAkB,sBAAsB,uBAAuB,QAAQ,cAAc;EAC3F,MAAM,uBAAuB,+BAC5B,gCACA,QAAQ,aACT;EACA,MAAM,iBAAiB,eAAe,0BAA0B,QAAQ,OAAO;EAC/E,MAAM,UAAU,eAAe,QAAQ,OAAO;EAC9C,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,QAAQ,QAAQ;GAChB;GACA;EACD,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { type Config, definePlugin } from 'payload'\nimport { defaultActionDefinitions } from './actions/builtin'\nimport type { ActionsConfig } from './actions/registry'\nimport { resolveActions } from './actions/registry'\nimport { resolveUploads, type UploadsOption } from './collections/uploads'\nimport { defaultConsentSources } from './consent/builtin'\nimport type { ConsentSourcesConfig } from './consent/registry'\nimport { resolveConsentSources } from './consent/registry'\nimport type { FormEventSink } from './events/types'\nimport { defaultFieldDefinitions } from './fields/builtin'\nimport { type FieldTypesConfig, resolveFieldTypes } from './fields/registry'\nimport type { CollectionOverrides } from './plugin/collectionOverrides'\nimport { registerCollections } from './plugin/registerCollections'\nimport { registerTranslations } from './plugin/registerTranslations'\nimport { defaultPresentationDescriptors } from './presentations/defaults'\nimport type { PresentationsDescriptorConfig } from './presentations/registry'\nimport { resolvePresentationDescriptors } from './presentations/registry'\nimport { resolveSpamConfig } from './spam/resolveSpam'\nimport type { SpamOption } from './spam/types'\nimport type { TranslationsOption } from './translations'\nimport { defaultValidationRules } from './validation/builtin'\nimport { resolveValidationRules, type ValidationRulesConfig } from './validation/registry'\n\nexport type FormBuilderPluginOptions = {\n\tdisabled?: boolean\n\t/**\n\t * Per-locale overrides for this plugin's UI strings, keyed by the typed\n\t * translation keys exported from `@10x-media/form-builder/i18n`. Values win\n\t * over the built-in locales key-by-key; locales the plugin does not ship are\n\t * added whole. App-level `i18n.translations` still wins over both.\n\t */\n\ttranslations?: TranslationsOption\n\t/** Pluggable sink for form lifecycle events. Defaults to a no-op; analytics adapters or a future analytics plugin subscribe here. */\n\tevents?: FormEventSink\n\t/** Add, override, or remove field types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tfields?: FieldTypesConfig\n\t/** Add, override, or remove presentations. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tpresentations?: PresentationsDescriptorConfig\n\t/** Add, override, or remove validation rule types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\trules?: ValidationRulesConfig\n\t/** Add, override, or remove post-submit action types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tactions?: ActionsConfig\n\t/** Add, override, or remove consent source types. `false` removes a built-in, `true` keeps it, an object adds or replaces one. */\n\tconsentSources?: ConsentSourcesConfig\n\t/** The built-in `form-uploads` collection backing file fields. `false` disables it (bring your own); an object overrides slug/upload/access/fields. */\n\tuploads?: UploadsOption\n\t/** Honeypot + rate-limiting (on by default) + a captcha adapter seam + upload-ownership scoping. `false` disables the whole subsystem. */\n\tspam?: SpamOption\n\t/**\n\t * When `true`, the raw `values`, `descriptors`, and `consent` JSON fields are visible in the\n\t * submission admin view. Default `false` — those fields are fully represented by the\n\t * `SubmissionAnswers` UI component and are noisy when shown alongside it.\n\t */\n\tshowSubmissionRawFields?: boolean\n\t/**\n\t * Override individual plugin-managed collections using explicit spreads. Each key accepts a\n\t * `CollectionOverrides` object: top-level keys are spread with the plugin's defaults (spread\n\t * order per key determines who wins), hooks are appended after the plugin's own hooks, and\n\t * `fields` is a function that receives the default fields and returns the final array so\n\t * additions/removals are always intentional.\n\t */\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t\tuploads?: CollectionOverrides\n\t}\n}\n\ndeclare module 'payload' {\n\tinterface RegisteredPlugins {\n\t\t'@10x-media/form-builder': FormBuilderPluginOptions\n\t}\n}\n\nexport const formBuilder = definePlugin<FormBuilderPluginOptions>({\n\tslug: '@10x-media/form-builder',\n\torder: 50,\n\tplugin: ({ config, plugins, ...options }): Config => {\n\t\tif (options.disabled === true) {\n\t\t\treturn config\n\t\t}\n\t\tconst registry = resolveFieldTypes(defaultFieldDefinitions, options.fields)\n\t\tconst ruleRegistry = resolveValidationRules(defaultValidationRules, options.rules)\n\t\tconst consentRegistry = resolveConsentSources(defaultConsentSources, options.consentSources)\n\t\tconst presentationRegistry = resolvePresentationDescriptors(\n\t\t\tdefaultPresentationDescriptors,\n\t\t\toptions.presentations\n\t\t)\n\t\tconst actionRegistry = resolveActions(defaultActionDefinitions, options.actions)\n\t\tconst uploads = resolveUploads(options.uploads)\n\t\tconst spam = resolveSpamConfig(options.spam)\n\t\tregisterTranslations(config, options.translations)\n\t\tregisterCollections({\n\t\t\tconfig,\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tpresentationRegistry,\n\t\t\tactionRegistry,\n\t\t\thasJobsPlugin: Boolean(plugins['@10x-media/jobs']),\n\t\t\tevents: options.events,\n\t\t\tuploads,\n\t\t\tspam,\n\t\t\tshowSubmissionRawFields: options.showSubmissionRawFields ?? false,\n\t\t\toverrides: options.overrides,\n\t\t})\n\t\treturn config\n\t},\n})\n\nexport { defaultActionDefinitions } from './actions/builtin'\nexport type { ActionDefinition, ActionRunArgs, AnyActionDefinition } from './actions/defineAction'\nexport { defineAction } from './actions/defineAction'\nexport type { ActionOption, ActionRegistry, ActionsConfig } from './actions/registry'\nexport { resolveActions } from './actions/registry'\nexport type { ActionResult } from './actions/runActions'\nexport { SIGNATURE_HEADER, signPayload } from './actions/sign'\nexport type {\n\tAggregateFieldResponsesArgs,\n\tAggregateFormResponsesArgs,\n} from './aggregation/aggregateResponses'\nexport {\n\taggregateFieldResponses,\n\taggregateFormResponses,\n\tfieldHasOptions,\n} from './aggregation/aggregateResponses'\nexport { aggregateRowForField, aggregateRowsForFields } from './aggregation/aggregateRows'\nexport type {\n\tResolveResultsRequestArgs,\n\tResolveResultsRequestResult,\n} from './aggregation/resolveResultsRequest'\nexport { resolveFormResultsRequest } from './aggregation/resolveResultsRequest'\nexport type {\n\tAggregationBucket,\n\tAggregationRow,\n\tFieldAggregation,\n\tFieldMeta,\n\tSubmissionStatusFilter,\n} from './aggregation/types'\nexport { calcExpressionOf, computeCalcFields } from './calc/computeCalcFields'\nexport { evaluateCalc } from './calc/evaluate'\nexport { normalizeCalc } from './calc/normalizeCalc'\nexport type { CalcExpression } from './calc/types'\nexport type { UploadsCollectionConfig, UploadsOption } from './collections/uploads'\nexport { buildUploadsCollection, FORM_UPLOADS_SLUG, resolveUploads } from './collections/uploads'\nexport { evaluateCondition } from './conditions/evaluate'\nexport type { FieldCondition } from './conditions/types'\nexport { defaultConsentSources } from './consent/builtin'\nexport type { ConsentProof } from './consent/captureConsent'\nexport { captureConsent } from './consent/captureConsent'\nexport type {\n\tAnyConsentSource,\n\tConsentLink,\n\tConsentResolveArgs,\n\tConsentResolved,\n\tConsentSource,\n} from './consent/defineConsentSource'\nexport { defineConsentSource } from './consent/defineConsentSource'\nexport type {\n\tConsentSourceOption,\n\tConsentSourceRegistry,\n\tConsentSourcesConfig,\n} from './consent/registry'\nexport { resolveConsentSources } from './consent/registry'\nexport { resolveConsentLinks } from './consent/resolveConsentLinks'\nexport { resolvePublishedVersionRef } from './consent/resolvePublishedVersionRef'\nexport { defineFormField } from './fields/defineFormField'\nexport type { FieldTypeOption, FieldTypeRegistry, FieldTypesConfig } from './fields/registry'\nexport type {\n\tAnyFormFieldDefinition,\n\tFormFieldDefinition,\n\tFormFieldFormat,\n\tFormFieldValidate,\n\tFormFieldValueKind,\n} from './fields/types'\nexport type { PrefillOptions } from './prefill/valuesFromSearchParams'\nexport { valuesFromSearchParams } from './prefill/valuesFromSearchParams'\nexport { DEFAULT_PRESENTATION_NAME, defaultPresentationDescriptors } from './presentations/defaults'\nexport type {\n\tPresentationDescriptorOption,\n\tPresentationDescriptorRegistry,\n\tPresentationsDescriptorConfig,\n} from './presentations/registry'\nexport { resolvePresentationDescriptors } from './presentations/registry'\nexport type {\n\tPresentationDensity,\n\tPresentationDescriptor,\n\tPresentationSurface,\n} from './presentations/types'\nexport { interpolate } from './recall/interpolate'\nexport type { RecallResolver } from './recall/resolver'\nexport { buildRecallResolver, optionLabelsFor } from './recall/resolver'\nexport { defineCaptchaProvider } from './spam/captcha'\nexport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from './spam/constants'\nexport { defaultIdentify } from './spam/identify'\nexport { createKvRateLimiter } from './spam/rateLimiter'\nexport { resolveSpamConfig } from './spam/resolveSpam'\nexport type {\n\tCaptchaProvider,\n\tCaptchaVerifyArgs,\n\tIdentifyFn,\n\tRateLimitCheckArgs,\n\tRateLimitConfig,\n\tRateLimiter,\n\tRateLimitResult,\n\tSpamConfig,\n\tSpamMetadataConfig,\n\tSpamOption,\n} from './spam/types'\nexport { captureFileRef } from './uploads/captureFileRef'\nexport { resolveFileRef } from './uploads/resolveFileRef'\nexport type { FileFieldConfig, FileRef, FileRefError } from './uploads/types'\nexport { defineValidationRule } from './validation/defineValidationRule'\nexport type {\n\tValidationRuleOption,\n\tValidationRuleRegistry,\n\tValidationRulesConfig,\n} from './validation/registry'\nexport type {\n\tAnyValidationRuleDefinition,\n\tValidationRuleDefinition,\n\tValidationRuleResult,\n\tValidationSeverity,\n} from './validation/types'\nexport type { FormBuilderPluginOptions as PluginOptions }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,MAAa,cAAc,aAAuC;CACjE,MAAM;CACN,OAAO;CACP,SAAS,EAAE,QAAQ,SAAS,GAAG,cAAsB;EACpD,IAAI,QAAQ,aAAa,MACxB,OAAO;EAER,MAAM,WAAW,kBAAkB,yBAAyB,QAAQ,MAAM;EAC1E,MAAM,eAAe,uBAAuB,wBAAwB,QAAQ,KAAK;EACjF,MAAM,kBAAkB,sBAAsB,uBAAuB,QAAQ,cAAc;EAC3F,MAAM,uBAAuB,+BAC5B,gCACA,QAAQ,aACT;EACA,MAAM,iBAAiB,eAAe,0BAA0B,QAAQ,OAAO;EAC/E,MAAM,UAAU,eAAe,QAAQ,OAAO;EAC9C,MAAM,OAAO,kBAAkB,QAAQ,IAAI;EAC3C,qBAAqB,QAAQ,QAAQ,YAAY;EACjD,oBAAoB;GACnB;GACA;GACA;GACA;GACA;GACA;GACA,eAAe,QAAQ,QAAQ,kBAAkB;GACjD,QAAQ,QAAQ;GAChB;GACA;GACA,yBAAyB,QAAQ,2BAA2B;GAC5D,WAAW,QAAQ;EACpB,CAAC;EACD,OAAO;CACR;AACD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CollectionConfig, Field } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/plugin/collectionOverrides.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Instead of accepting a static field array (which deepMerge would silently replace), the consumer
|
|
6
|
+
* receives the plugin's default fields and returns the final array. This makes the intent explicit:
|
|
7
|
+
* append, prepend, filter out, or wrap any field without risk of accidentally wiping the defaults.
|
|
8
|
+
*/
|
|
9
|
+
type FieldsOverride = (args: {
|
|
10
|
+
defaultFields: Field[];
|
|
11
|
+
}) => Field[];
|
|
12
|
+
/**
|
|
13
|
+
* Typed override surface for plugin-managed collections. Top-level keys use explicit spread order
|
|
14
|
+
* (see each collection builder) so the consumer always knows which values they can override and
|
|
15
|
+
* which are locked by the plugin for correctness or security.
|
|
16
|
+
*/
|
|
17
|
+
type CollectionOverrides = {
|
|
18
|
+
fields?: FieldsOverride;
|
|
19
|
+
} & Partial<Omit<CollectionConfig, 'fields'>>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { CollectionOverrides, FieldsOverride };
|
|
22
|
+
//# sourceMappingURL=collectionOverrides.d.ts.map
|
|
@@ -3,24 +3,50 @@ import { buildSubmissionsCollection } from "../collections/formSubmissions.js";
|
|
|
3
3
|
import { registerActionsTask } from "../actions/task.js";
|
|
4
4
|
import { buildUploadOwnerStamp, buildUploadRateLimit } from "../spam/uploadHooks.js";
|
|
5
5
|
//#region src/plugin/registerCollections.ts
|
|
6
|
-
const registerCollections = ({ config, registry, ruleRegistry, consentRegistry, presentationRegistry, actionRegistry, hasJobsPlugin, events, uploads, spam }) => {
|
|
6
|
+
const registerCollections = ({ config, registry, ruleRegistry, consentRegistry, presentationRegistry, actionRegistry, hasJobsPlugin, events, uploads, spam, showSubmissionRawFields, overrides }) => {
|
|
7
7
|
registerActionsTask(config, actionRegistry);
|
|
8
8
|
const hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
let uploadsCollection = null;
|
|
10
|
+
if (uploads.enabled && uploads.collection) {
|
|
11
|
+
const col = uploads.collection;
|
|
12
|
+
if (spam) {
|
|
13
|
+
col.hooks = col.hooks ?? {};
|
|
14
|
+
col.hooks.beforeOperation = [...col.hooks.beforeOperation ?? [], buildUploadRateLimit(spam)];
|
|
15
|
+
col.hooks.beforeValidate = [...col.hooks.beforeValidate ?? [], buildUploadOwnerStamp(spam)];
|
|
16
|
+
}
|
|
17
|
+
const ov = overrides?.uploads;
|
|
18
|
+
if (ov) {
|
|
19
|
+
const defaultFields = col.fields ?? [];
|
|
20
|
+
uploadsCollection = {
|
|
21
|
+
...col,
|
|
22
|
+
...ov ?? {},
|
|
23
|
+
access: {
|
|
24
|
+
...col.access ?? {},
|
|
25
|
+
...ov.access ?? {}
|
|
26
|
+
},
|
|
27
|
+
admin: {
|
|
28
|
+
...col.admin ?? {},
|
|
29
|
+
...ov.admin ?? {}
|
|
30
|
+
},
|
|
31
|
+
hooks: {
|
|
32
|
+
...ov.hooks ?? {},
|
|
33
|
+
beforeOperation: [...col.hooks?.beforeOperation ?? [], ...ov.hooks?.beforeOperation ?? []],
|
|
34
|
+
beforeValidate: [...col.hooks?.beforeValidate ?? [], ...ov.hooks?.beforeValidate ?? []]
|
|
35
|
+
},
|
|
36
|
+
fields: ov.fields ? ov.fields({ defaultFields }) : defaultFields
|
|
37
|
+
};
|
|
38
|
+
} else uploadsCollection = col;
|
|
14
39
|
}
|
|
15
40
|
config.collections = [
|
|
16
41
|
...config.collections ?? [],
|
|
17
|
-
...
|
|
42
|
+
...uploadsCollection ? [uploadsCollection] : [],
|
|
18
43
|
buildFormsCollection({
|
|
19
44
|
registry,
|
|
20
45
|
ruleRegistry,
|
|
21
46
|
consentRegistry,
|
|
22
47
|
presentationRegistry,
|
|
23
|
-
actionRegistry
|
|
48
|
+
actionRegistry,
|
|
49
|
+
overrides: overrides?.forms
|
|
24
50
|
}),
|
|
25
51
|
buildSubmissionsCollection({
|
|
26
52
|
registry,
|
|
@@ -30,7 +56,9 @@ const registerCollections = ({ config, registry, ruleRegistry, consentRegistry,
|
|
|
30
56
|
events,
|
|
31
57
|
hasRunner,
|
|
32
58
|
uploadSlug: uploads.slug,
|
|
33
|
-
spam
|
|
59
|
+
spam,
|
|
60
|
+
showRawFields: showSubmissionRawFields,
|
|
61
|
+
overrides: overrides?.formSubmissions
|
|
34
62
|
})
|
|
35
63
|
];
|
|
36
64
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionConfig, Config } from 'payload'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { buildUploadOwnerStamp, buildUploadRateLimit } from '../spam/uploadHooks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\tpresentationRegistry: PresentationDescriptorRegistry\n\tactionRegistry: ActionRegistry\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: { enabled: boolean; slug: string; collection?: CollectionConfig }\n\tspam: ResolvedSpamConfig | false\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry,\n\tactionRegistry,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\tif (
|
|
1
|
+
{"version":3,"file":"registerCollections.js","names":[],"sources":["../../src/plugin/registerCollections.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field } from 'payload'\nimport type { ActionRegistry } from '../actions/registry'\nimport { registerActionsTask } from '../actions/task'\nimport { buildSubmissionsCollection } from '../collections/formSubmissions'\nimport { buildFormsCollection } from '../collections/forms'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { buildUploadOwnerStamp, buildUploadRateLimit } from '../spam/uploadHooks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { CollectionOverrides } from './collectionOverrides'\n\ntype RegisterCollectionsArgs = {\n\tconfig: Config\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry: ConsentSourceRegistry\n\tpresentationRegistry: PresentationDescriptorRegistry\n\tactionRegistry: ActionRegistry\n\thasJobsPlugin: boolean\n\tevents?: FormEventSink\n\tuploads: { enabled: boolean; slug: string; collection?: CollectionConfig }\n\tspam: ResolvedSpamConfig | false\n\tshowSubmissionRawFields: boolean\n\toverrides?: {\n\t\tforms?: CollectionOverrides\n\t\tformSubmissions?: CollectionOverrides\n\t\tuploads?: CollectionOverrides\n\t}\n}\n\nexport const registerCollections = ({\n\tconfig,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry,\n\tactionRegistry,\n\thasJobsPlugin,\n\tevents,\n\tuploads,\n\tspam,\n\tshowSubmissionRawFields,\n\toverrides,\n}: RegisterCollectionsArgs): void => {\n\tregisterActionsTask(config, actionRegistry)\n\tconst hasRunner = Boolean(config.jobs?.autoRun) || hasJobsPlugin\n\n\tlet uploadsCollection: CollectionConfig | null = null\n\tif (uploads.enabled && uploads.collection) {\n\t\tconst col = uploads.collection\n\n\t\t// Spam hooks are added first (plugin-owned invariant)\n\t\tif (spam) {\n\t\t\tcol.hooks = col.hooks ?? {}\n\t\t\tcol.hooks.beforeOperation = [...(col.hooks.beforeOperation ?? []), buildUploadRateLimit(spam)]\n\t\t\tcol.hooks.beforeValidate = [...(col.hooks.beforeValidate ?? []), buildUploadOwnerStamp(spam)]\n\t\t}\n\n\t\t// Apply CollectionOverrides using explicit spreads after spam hooks are in place.\n\t\t// ...col first so the plugin defaults are the base, ...(ov ?? {}) second so the consumer\n\t\t// wins for any top-level key not explicitly re-set below (consistent with forms/formSubmissions).\n\t\t// The locked keys (hooks array ordering, fields function) are spread last.\n\t\tconst ov = overrides?.uploads\n\t\tif (ov) {\n\t\t\tconst defaultFields = (col.fields ?? []) as Field[]\n\t\t\tuploadsCollection = {\n\t\t\t\t...col,\n\t\t\t\t...(ov ?? {}),\n\t\t\t\taccess: { ...(col.access ?? {}), ...(ov.access ?? {}) },\n\t\t\t\tadmin: { ...(col.admin ?? {}), ...(ov.admin ?? {}) },\n\t\t\t\thooks: {\n\t\t\t\t\t...(ov.hooks ?? {}),\n\t\t\t\t\t// Spam hooks stay first in each array; consumer hooks appended\n\t\t\t\t\tbeforeOperation: [\n\t\t\t\t\t\t...(col.hooks?.beforeOperation ?? []),\n\t\t\t\t\t\t...(ov.hooks?.beforeOperation ?? []),\n\t\t\t\t\t],\n\t\t\t\t\tbeforeValidate: [\n\t\t\t\t\t\t...(col.hooks?.beforeValidate ?? []),\n\t\t\t\t\t\t...(ov.hooks?.beforeValidate ?? []),\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\tfields: ov.fields ? ov.fields({ defaultFields }) : defaultFields,\n\t\t\t}\n\t\t} else {\n\t\t\tuploadsCollection = col\n\t\t}\n\t}\n\n\tconfig.collections = [\n\t\t...(config.collections ?? []),\n\t\t...(uploadsCollection ? [uploadsCollection] : []),\n\t\tbuildFormsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tpresentationRegistry,\n\t\t\tactionRegistry,\n\t\t\toverrides: overrides?.forms,\n\t\t}),\n\t\tbuildSubmissionsCollection({\n\t\t\tregistry,\n\t\t\truleRegistry,\n\t\t\tconsentRegistry,\n\t\t\tactionRegistry,\n\t\t\tevents,\n\t\t\thasRunner,\n\t\t\tuploadSlug: uploads.slug,\n\t\t\tspam,\n\t\t\tshowRawFields: showSubmissionRawFields,\n\t\t\toverrides: overrides?.formSubmissions,\n\t\t}),\n\t]\n}\n"],"mappings":";;;;;AAiCA,MAAa,uBAAuB,EACnC,QACA,UACA,cACA,iBACA,sBACA,gBACA,eACA,QACA,SACA,MACA,yBACA,gBACoC;CACpC,oBAAoB,QAAQ,cAAc;CAC1C,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,KAAK;CAEnD,IAAI,oBAA6C;CACjD,IAAI,QAAQ,WAAW,QAAQ,YAAY;EAC1C,MAAM,MAAM,QAAQ;EAGpB,IAAI,MAAM;GACT,IAAI,QAAQ,IAAI,SAAS,CAAC;GAC1B,IAAI,MAAM,kBAAkB,CAAC,GAAI,IAAI,MAAM,mBAAmB,CAAC,GAAI,qBAAqB,IAAI,CAAC;GAC7F,IAAI,MAAM,iBAAiB,CAAC,GAAI,IAAI,MAAM,kBAAkB,CAAC,GAAI,sBAAsB,IAAI,CAAC;EAC7F;EAMA,MAAM,KAAK,WAAW;EACtB,IAAI,IAAI;GACP,MAAM,gBAAiB,IAAI,UAAU,CAAC;GACtC,oBAAoB;IACnB,GAAG;IACH,GAAI,MAAM,CAAC;IACX,QAAQ;KAAE,GAAI,IAAI,UAAU,CAAC;KAAI,GAAI,GAAG,UAAU,CAAC;IAAG;IACtD,OAAO;KAAE,GAAI,IAAI,SAAS,CAAC;KAAI,GAAI,GAAG,SAAS,CAAC;IAAG;IACnD,OAAO;KACN,GAAI,GAAG,SAAS,CAAC;KAEjB,iBAAiB,CAChB,GAAI,IAAI,OAAO,mBAAmB,CAAC,GACnC,GAAI,GAAG,OAAO,mBAAmB,CAAC,CACnC;KACA,gBAAgB,CACf,GAAI,IAAI,OAAO,kBAAkB,CAAC,GAClC,GAAI,GAAG,OAAO,kBAAkB,CAAC,CAClC;IACD;IACA,QAAQ,GAAG,SAAS,GAAG,OAAO,EAAE,cAAc,CAAC,IAAI;GACpD;EACD,OACC,oBAAoB;CAEtB;CAEA,OAAO,cAAc;EACpB,GAAI,OAAO,eAAe,CAAC;EAC3B,GAAI,oBAAoB,CAAC,iBAAiB,IAAI,CAAC;EAC/C,qBAAqB;GACpB;GACA;GACA;GACA;GACA;GACA,WAAW,WAAW;EACvB,CAAC;EACD,2BAA2B;GAC1B;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,QAAQ;GACpB;GACA,eAAe;GACf,WAAW,WAAW;EACvB,CAAC;CACF;AACD"}
|
package/dist/react/Form.d.ts
CHANGED
|
@@ -17,6 +17,23 @@ type FormDocument = {
|
|
|
17
17
|
flow?: FormFlow; /** Stored presentation name; overridden by the `presentation` prop. */
|
|
18
18
|
defaultPresentation?: string;
|
|
19
19
|
};
|
|
20
|
+
/** Props passed to `renderSubmit`. */
|
|
21
|
+
type SubmitButtonRenderProps = {
|
|
22
|
+
label: string;
|
|
23
|
+
submitting: boolean;
|
|
24
|
+
};
|
|
25
|
+
/** Props passed to `renderNext`. */
|
|
26
|
+
type NextButtonRenderProps = {
|
|
27
|
+
label: string;
|
|
28
|
+
submitting: boolean;
|
|
29
|
+
onClick: () => void;
|
|
30
|
+
};
|
|
31
|
+
/** Props passed to `renderBack`. */
|
|
32
|
+
type BackButtonRenderProps = {
|
|
33
|
+
label: string;
|
|
34
|
+
submitting: boolean;
|
|
35
|
+
onClick: () => void;
|
|
36
|
+
};
|
|
20
37
|
type FormProps = {
|
|
21
38
|
form: FormDocument;
|
|
22
39
|
fieldTypes?: AnyFormFieldDefinition[];
|
|
@@ -45,7 +62,13 @@ type FormProps = {
|
|
|
45
62
|
}; /** A token from your captcha widget; verified server-side when a captcha provider is configured. */
|
|
46
63
|
captchaToken?: string; /** Custom layout: render fields with `useField`/`useFormState` instead of the auto-rendered field loop. */
|
|
47
64
|
children?: ReactNode; /** Additional CSS class names applied to the root `<form>` element (and the success node). */
|
|
48
|
-
className?: string;
|
|
65
|
+
className?: string; /** Replace the default submit button entirely. Receives the resolved label and submitting state. */
|
|
66
|
+
renderSubmit?: (props: SubmitButtonRenderProps) => ReactNode; /** Replace the default "Next" button in multi-step forms. */
|
|
67
|
+
renderNext?: (props: NextButtonRenderProps) => ReactNode; /** Replace the default "Back" button in multi-step forms. */
|
|
68
|
+
renderBack?: (props: BackButtonRenderProps) => ReactNode; /** CSS class forwarded to the default submit `<button>`. Ignored when `renderSubmit` is provided. */
|
|
69
|
+
submitButtonClassName?: string; /** CSS class forwarded to the default "Next" `<button>`. Ignored when `renderNext` is provided. */
|
|
70
|
+
nextButtonClassName?: string; /** CSS class forwarded to the default "Back" `<button>`. Ignored when `renderBack` is provided. */
|
|
71
|
+
backButtonClassName?: string;
|
|
49
72
|
};
|
|
50
73
|
/** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
|
|
51
74
|
declare const Form: ({
|
|
@@ -74,8 +97,14 @@ declare const Form: ({
|
|
|
74
97
|
honeypot,
|
|
75
98
|
captchaToken,
|
|
76
99
|
children,
|
|
77
|
-
className
|
|
100
|
+
className,
|
|
101
|
+
renderSubmit,
|
|
102
|
+
renderNext,
|
|
103
|
+
renderBack,
|
|
104
|
+
submitButtonClassName,
|
|
105
|
+
nextButtonClassName,
|
|
106
|
+
backButtonClassName
|
|
78
107
|
}: FormProps) => import("react/jsx-runtime").JSX.Element;
|
|
79
108
|
//#endregion
|
|
80
|
-
export { Form, FormDocument, FormProps };
|
|
109
|
+
export { BackButtonRenderProps, Form, FormDocument, FormProps, NextButtonRenderProps, SubmitButtonRenderProps };
|
|
81
110
|
//# sourceMappingURL=Form.d.ts.map
|
package/dist/react/Form.js
CHANGED
|
@@ -18,11 +18,11 @@ import { defaultPresentations } from "./presentation/presentations.js";
|
|
|
18
18
|
import { resolvePresentations } from "./presentation/registry.js";
|
|
19
19
|
import { applyRecall } from "./recall.js";
|
|
20
20
|
import { resolveRenderers } from "./registry.js";
|
|
21
|
+
import { useField } from "./useField.js";
|
|
21
22
|
import { defaultRenderers } from "./renderers/index.js";
|
|
22
23
|
import { buildFieldTypeRegistry, buildValidationRuleRegistry, visibleFields } from "./resolveForm.js";
|
|
23
24
|
import { formReducer, initialFormState } from "./state.js";
|
|
24
25
|
import { submitForm } from "./submitForm.js";
|
|
25
|
-
import { useField } from "./useField.js";
|
|
26
26
|
import { validateFieldValue } from "./validateField.js";
|
|
27
27
|
import { createElement, useCallback, useEffect, useId, useMemo, useReducer, useRef, useState } from "react";
|
|
28
28
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -69,7 +69,7 @@ const CalcFieldHost = ({ field, renderer, value, locale, t }) => {
|
|
|
69
69
|
});
|
|
70
70
|
};
|
|
71
71
|
/** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
|
|
72
|
-
const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, events, t, locale = "en", layout, submitLabel = "Submit", nextLabel = "Next", backLabel = "Back", closeLabel = "Close", successMessage = "Thank you.", presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, children, className }) => {
|
|
72
|
+
const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, events, t, locale = "en", layout, submitLabel = "Submit", nextLabel = "Next", backLabel = "Back", closeLabel = "Close", successMessage = "Thank you.", presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, children, className, renderSubmit, renderNext, renderBack, submitButtonClassName, nextButtonClassName, backButtonClassName }) => {
|
|
73
73
|
const honeypotName = honeypot === false ? null : honeypot?.name ?? "website";
|
|
74
74
|
const honeypotRef = useRef(null);
|
|
75
75
|
const registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes]);
|
|
@@ -264,6 +264,29 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
|
|
|
264
264
|
}
|
|
265
265
|
if (result.warnings.length > 0) warnings[result.field.name] = result.warnings;
|
|
266
266
|
}
|
|
267
|
+
for (const field of visible.filter((f) => f.blockType === "repeater")) {
|
|
268
|
+
const rows = Array.isArray(effectiveValues[field.name]) ? effectiveValues[field.name] : [];
|
|
269
|
+
const subFields = Array.isArray(field.subFields) ? field.subFields : [];
|
|
270
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
271
|
+
const row = rows[rowIndex] ?? {};
|
|
272
|
+
for (const subField of subFields) {
|
|
273
|
+
if (!evaluateCondition(subField.visibleWhen, row)) continue;
|
|
274
|
+
if (!evaluateCondition(subField.validateWhen, row)) continue;
|
|
275
|
+
const subResult = await validateFieldValue({
|
|
276
|
+
field: subField,
|
|
277
|
+
value: row[subField.name],
|
|
278
|
+
registry,
|
|
279
|
+
ruleRegistry,
|
|
280
|
+
answers: row,
|
|
281
|
+
locale,
|
|
282
|
+
t: translate
|
|
283
|
+
});
|
|
284
|
+
const compositeKey = `${field.name}[${rowIndex}].${subField.name}`;
|
|
285
|
+
if (subResult.errors.length > 0) errors[compositeKey] = subResult.errors;
|
|
286
|
+
if (subResult.warnings.length > 0) warnings[compositeKey] = subResult.warnings;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
267
290
|
rawDispatch({
|
|
268
291
|
type: "SET_ALL_ISSUES",
|
|
269
292
|
errors,
|
|
@@ -345,7 +368,8 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
|
|
|
345
368
|
dispatch,
|
|
346
369
|
validateField,
|
|
347
370
|
locale,
|
|
348
|
-
step
|
|
371
|
+
step,
|
|
372
|
+
rendererRegistry
|
|
349
373
|
};
|
|
350
374
|
const PresentationWrapper = activePresentation.Wrapper;
|
|
351
375
|
const wrap = (content) => PresentationWrapper ? /* @__PURE__ */ jsx(PresentationWrapper, {
|
|
@@ -425,25 +449,43 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
|
|
|
425
449
|
}) : null,
|
|
426
450
|
flow ? /* @__PURE__ */ jsxs("div", {
|
|
427
451
|
className: "fb-form__controls",
|
|
428
|
-
children: [!step.isFirst ?
|
|
452
|
+
children: [!step.isFirst ? renderBack ? renderBack({
|
|
453
|
+
label: backLabel,
|
|
454
|
+
submitting: state.submitting,
|
|
455
|
+
onClick: goBack
|
|
456
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
429
457
|
type: "button",
|
|
458
|
+
className: backButtonClassName,
|
|
430
459
|
onClick: goBack,
|
|
431
460
|
disabled: state.submitting,
|
|
432
461
|
children: backLabel
|
|
433
|
-
}) : null, step.isTerminal ?
|
|
462
|
+
}) : null, step.isTerminal ? renderSubmit ? renderSubmit({
|
|
463
|
+
label: submitLabel,
|
|
464
|
+
submitting: state.submitting
|
|
465
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
434
466
|
type: "submit",
|
|
467
|
+
className: submitButtonClassName,
|
|
435
468
|
disabled: state.submitting,
|
|
436
469
|
children: submitLabel
|
|
470
|
+
}) : renderNext ? renderNext({
|
|
471
|
+
label: nextLabel,
|
|
472
|
+
submitting: state.submitting,
|
|
473
|
+
onClick: () => void goNext()
|
|
437
474
|
}) : /* @__PURE__ */ jsx("button", {
|
|
438
475
|
type: "button",
|
|
476
|
+
className: nextButtonClassName,
|
|
439
477
|
disabled: state.submitting,
|
|
440
478
|
onClick: () => {
|
|
441
479
|
goNext();
|
|
442
480
|
},
|
|
443
481
|
children: nextLabel
|
|
444
482
|
})]
|
|
483
|
+
}) : renderSubmit ? renderSubmit({
|
|
484
|
+
label: submitLabel,
|
|
485
|
+
submitting: state.submitting
|
|
445
486
|
}) : /* @__PURE__ */ jsx("button", {
|
|
446
487
|
type: "submit",
|
|
488
|
+
className: submitButtonClassName,
|
|
447
489
|
disabled: state.submitting,
|
|
448
490
|
children: submitLabel
|
|
449
491
|
})
|