@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.21
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 +360 -0
- package/README.md +7 -5
- package/dist/actions/assertNoBlockCollision.js +18 -0
- package/dist/actions/assertNoBlockCollision.js.map +1 -0
- package/dist/actions/body/converters.d.ts +20 -0
- package/dist/actions/body/converters.js +57 -0
- package/dist/actions/body/converters.js.map +1 -0
- package/dist/actions/body/escapeHtml.d.ts +6 -0
- package/dist/actions/body/escapeHtml.js +14 -0
- package/dist/actions/body/escapeHtml.js.map +1 -0
- package/dist/actions/body/serializeBody.d.ts +55 -0
- package/dist/actions/body/serializeBody.js +78 -0
- package/dist/actions/body/serializeBody.js.map +1 -0
- package/dist/actions/body/serializeSlate.js +31 -0
- package/dist/actions/body/serializeSlate.js.map +1 -0
- package/dist/actions/body/textOfBody.d.ts +13 -0
- package/dist/actions/body/textOfBody.js +35 -0
- package/dist/actions/body/textOfBody.js.map +1 -0
- package/dist/actions/body/wildcards.d.ts +10 -0
- package/dist/actions/body/wildcards.js +33 -0
- package/dist/actions/body/wildcards.js.map +1 -0
- package/dist/actions/buildActionBlocks.js +12 -9
- package/dist/actions/buildActionBlocks.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +47 -43
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +20 -0
- package/dist/actions/builtin/emailAction.js +120 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +20 -38
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +16 -1
- package/dist/actions/builtin/index.js +21 -7
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/builtin/signedWebhook.js +6 -2
- package/dist/actions/builtin/signedWebhook.js.map +1 -1
- package/dist/actions/defineAction.d.ts +17 -5
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/dispatch.js +51 -6
- package/dist/actions/dispatch.js.map +1 -1
- package/dist/actions/dispatchContext.js +13 -0
- package/dist/actions/dispatchContext.js.map +1 -0
- package/dist/actions/emailRecipients.d.ts +12 -0
- package/dist/actions/emailRecipients.js +146 -0
- package/dist/actions/emailRecipients.js.map +1 -0
- package/dist/actions/fromAddresses.d.ts +37 -0
- package/dist/actions/fromAddresses.js +131 -0
- package/dist/actions/fromAddresses.js.map +1 -0
- package/dist/actions/recipientSources.d.ts +34 -0
- package/dist/actions/recipientSources.js +11 -0
- package/dist/actions/recipientSources.js.map +1 -0
- package/dist/actions/registry.d.ts +2 -1
- package/dist/actions/registry.js +5 -7
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/runActions.js +10 -1
- package/dist/actions/runActions.js.map +1 -1
- package/dist/actions/task.js +47 -16
- package/dist/actions/task.js.map +1 -1
- package/dist/aggregation/aggregateResponses.d.ts +9 -0
- package/dist/aggregation/aggregateResponses.js +8 -14
- package/dist/aggregation/aggregateResponses.js.map +1 -1
- package/dist/aggregation/resolveResultsRequest.d.ts +41 -6
- package/dist/aggregation/resolveResultsRequest.js +97 -11
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/computeCalcFields.d.ts +5 -3
- package/dist/calc/computeCalcFields.js +6 -4
- package/dist/calc/computeCalcFields.js.map +1 -1
- package/dist/calc/evaluate.d.ts +26 -3
- package/dist/calc/evaluate.js +34 -13
- package/dist/calc/evaluate.js.map +1 -1
- package/dist/calc/formatCalc.d.ts +11 -0
- package/dist/calc/formatCalc.js +35 -0
- package/dist/calc/formatCalc.js.map +1 -0
- package/dist/calc/formatCalcValue.d.ts +17 -0
- package/dist/calc/formatCalcValue.js +17 -0
- package/dist/calc/formatCalcValue.js.map +1 -0
- package/dist/calc/normalizeCalc.d.ts +17 -3
- package/dist/calc/normalizeCalc.js +42 -33
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/registry.d.ts +43 -0
- package/dist/calc/registry.js +27 -0
- package/dist/calc/registry.js.map +1 -0
- package/dist/calc/resolveCalcContext.d.ts +36 -0
- package/dist/calc/resolveCalcContext.js +87 -0
- package/dist/calc/resolveCalcContext.js.map +1 -0
- package/dist/calc/types.d.ts +25 -4
- package/dist/calc/types.js +23 -0
- package/dist/calc/types.js.map +1 -0
- package/dist/client/ByteSizeField.d.ts +29 -0
- package/dist/client/ByteSizeField.js +58 -0
- package/dist/client/ByteSizeField.js.map +1 -0
- package/dist/client/CalcExpressionBuilder.d.ts +20 -0
- package/dist/client/CalcExpressionBuilder.js +890 -0
- package/dist/client/CalcExpressionBuilder.js.map +1 -0
- package/dist/client/ClosePollButton.d.ts +15 -0
- package/dist/client/ClosePollButton.js +80 -0
- package/dist/client/ClosePollButton.js.map +1 -0
- package/dist/client/CondensedArray.d.ts +15 -0
- package/dist/client/CondensedArray.js +97 -0
- package/dist/client/CondensedArray.js.map +1 -0
- package/dist/client/ConditionBuilder.js +2 -1
- package/dist/client/ConditionBuilder.js.map +1 -1
- package/dist/client/ConsentBlockLabel.d.ts +15 -0
- package/dist/client/ConsentBlockLabel.js +71 -0
- package/dist/client/ConsentBlockLabel.js.map +1 -0
- package/dist/client/ConsentSourceRowLabel.d.ts +9 -0
- package/dist/client/ConsentSourceRowLabel.js +21 -0
- package/dist/client/ConsentSourceRowLabel.js.map +1 -0
- package/dist/client/EndpointOptionsSelect.d.ts +55 -0
- package/dist/client/EndpointOptionsSelect.js +112 -0
- package/dist/client/EndpointOptionsSelect.js.map +1 -0
- package/dist/client/FieldBlockLabel.d.ts +18 -0
- package/dist/client/FieldBlockLabel.js +34 -0
- package/dist/client/FieldBlockLabel.js.map +1 -0
- package/dist/client/FieldNameSelect.d.ts +37 -0
- package/dist/client/FieldNameSelect.js +72 -0
- package/dist/client/FieldNameSelect.js.map +1 -0
- package/dist/client/FlowBuilder.d.ts +2 -2
- package/dist/client/FlowBuilder.js +137 -88
- package/dist/client/FlowBuilder.js.map +1 -1
- package/dist/client/FormConditionField.js +1 -4
- package/dist/client/FormConditionField.js.map +1 -1
- package/dist/client/RecipientsSelect.d.ts +37 -0
- package/dist/client/RecipientsSelect.js +197 -0
- package/dist/client/RecipientsSelect.js.map +1 -0
- package/dist/client/RuleDescription.d.ts +21 -0
- package/dist/client/RuleDescription.js +19 -0
- package/dist/client/RuleDescription.js.map +1 -0
- package/dist/client/admin.css +557 -0
- package/dist/client/endpointOptions.d.ts +11 -0
- package/dist/client/endpointOptions.js +38 -0
- package/dist/client/endpointOptions.js.map +1 -0
- package/dist/client/flowAuthoring.js +142 -0
- package/dist/client/flowAuthoring.js.map +1 -0
- package/dist/client/synthesizeClientField.js +21 -10
- package/dist/client/synthesizeClientField.js.map +1 -1
- package/dist/client/toStaticLabel.js +13 -0
- package/dist/client/toStaticLabel.js.map +1 -0
- package/dist/collections/buttonFields.d.ts +34 -0
- package/dist/collections/buttonFields.js +35 -0
- package/dist/collections/buttonFields.js.map +1 -0
- package/dist/collections/cells/FieldCountCell.d.ts +15 -0
- package/dist/collections/cells/FieldCountCell.js +18 -0
- package/dist/collections/cells/FieldCountCell.js.map +1 -0
- package/dist/collections/cells/FlowStepsCell.d.ts +14 -0
- package/dist/collections/cells/FlowStepsCell.js +17 -0
- package/dist/collections/cells/FlowStepsCell.js.map +1 -0
- package/dist/collections/formSubmissions.js +114 -28
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +449 -112
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/formsEndpoints.js +106 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/collections/redirectFields.d.ts +29 -0
- package/dist/collections/settingsFields.d.ts +31 -0
- package/dist/collections/settingsFields.js +45 -0
- package/dist/collections/settingsFields.js.map +1 -0
- package/dist/conditions/conditionType.js +9 -2
- package/dist/conditions/conditionType.js.map +1 -1
- package/dist/conditions/evaluate.js +1 -1
- package/dist/conditions/evaluate.js.map +1 -1
- package/dist/conditions/fieldTypes.d.ts +3 -1
- package/dist/conditions/fieldTypes.js +4 -1
- package/dist/conditions/fieldTypes.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +20 -6
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/applyConsentStatements.d.ts +14 -0
- package/dist/consent/applyConsentStatements.js +17 -0
- package/dist/consent/applyConsentStatements.js.map +1 -0
- package/dist/consent/captureConsent.d.ts +39 -11
- package/dist/consent/captureConsent.js +63 -13
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +66 -0
- package/dist/consent/consentSourcesField.js +98 -0
- package/dist/consent/consentSourcesField.js.map +1 -0
- package/dist/consent/effectiveStatement.js +16 -0
- package/dist/consent/effectiveStatement.js.map +1 -0
- package/dist/consent/resolveConsentEntries.d.ts +24 -0
- package/dist/consent/resolveConsentEntries.js +40 -0
- package/dist/consent/resolveConsentEntries.js.map +1 -0
- package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
- package/dist/consent/resolveConsentSourcesRequest.js +54 -0
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
- package/dist/consent/resolveConsentStatements.d.ts +45 -0
- package/dist/consent/resolveConsentStatements.js +46 -0
- package/dist/consent/resolveConsentStatements.js.map +1 -0
- package/dist/consent/resolvePublishedVersionRef.d.ts +17 -11
- package/dist/consent/resolvePublishedVersionRef.js +13 -10
- package/dist/consent/resolvePublishedVersionRef.js.map +1 -1
- package/dist/consent/types.d.ts +69 -0
- package/dist/context/formContext.d.ts +30 -0
- package/dist/context/formContext.js +54 -0
- package/dist/context/formContext.js.map +1 -0
- package/dist/email/departments.d.ts +59 -0
- package/dist/email/departments.js +87 -0
- package/dist/email/departments.js.map +1 -0
- package/dist/email/departmentsField.d.ts +53 -0
- package/dist/email/departmentsField.js +73 -0
- package/dist/email/departmentsField.js.map +1 -0
- package/dist/exports/client.d.ts +14 -1
- package/dist/exports/client.js +14 -1
- package/dist/exports/i18n.d.ts +5 -2
- package/dist/exports/i18n.js +5 -2
- package/dist/exports/react.d.ts +34 -10
- package/dist/exports/react.js +24 -4
- package/dist/exports/rsc.d.ts +10 -1
- package/dist/exports/rsc.js +10 -1
- package/dist/exports/types.d.ts +20 -12
- package/dist/fields/buildFieldBlocks.js +46 -25
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.d.ts +17 -0
- package/dist/fields/builtin/calculation.js +193 -18
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/checkbox.js +23 -0
- package/dist/fields/builtin/checkbox.js.map +1 -1
- package/dist/fields/builtin/consent.js +49 -14
- package/dist/fields/builtin/consent.js.map +1 -1
- package/dist/fields/builtin/country.d.ts +13 -0
- package/dist/fields/builtin/country.js +30 -0
- package/dist/fields/builtin/country.js.map +1 -0
- package/dist/fields/builtin/date.js +31 -0
- package/dist/fields/builtin/date.js.map +1 -0
- package/dist/fields/builtin/email.js +8 -1
- package/dist/fields/builtin/email.js.map +1 -1
- package/dist/fields/builtin/file.d.ts +15 -0
- package/dist/fields/builtin/file.js +95 -8
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +30 -0
- package/dist/fields/builtin/index.js +37 -10
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +30 -0
- package/dist/fields/builtin/message.js.map +1 -0
- package/dist/fields/builtin/repeater.js +8 -3
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +28 -4
- package/dist/fields/builtin/select.js.map +1 -1
- package/dist/fields/builtin/state.d.ts +13 -0
- package/dist/fields/builtin/state.js +30 -0
- package/dist/fields/builtin/state.js.map +1 -0
- package/dist/fields/builtin/text.js +7 -0
- package/dist/fields/builtin/text.js.map +1 -1
- package/dist/fields/data/regions.d.ts +21 -0
- package/dist/fields/data/regions.js +1238 -0
- package/dist/fields/data/regions.js.map +1 -0
- package/dist/fields/fieldKey.d.ts +15 -0
- package/dist/fields/fieldKey.js +16 -0
- package/dist/fields/fieldKey.js.map +1 -0
- package/dist/fields/fieldNamesOfType.js +30 -0
- package/dist/fields/fieldNamesOfType.js.map +1 -0
- package/dist/fields/instanceOptions.js +20 -0
- package/dist/fields/instanceOptions.js.map +1 -0
- 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/registry.d.ts +3 -1
- package/dist/fields/registry.js +23 -8
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +125 -59
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +78 -9
- package/dist/flow/engine.d.ts +6 -4
- package/dist/flow/engine.js +11 -6
- package/dist/flow/engine.js.map +1 -1
- package/dist/flow/normalizeFlow.js +28 -9
- 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/flow/types.d.ts +12 -3
- package/dist/flow/types.js +11 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/form/pollState.d.ts +23 -0
- package/dist/form/pollState.js +17 -0
- package/dist/form/pollState.js.map +1 -0
- package/dist/form/toFormDocument.d.ts +110 -0
- package/dist/form/toFormDocument.js +89 -0
- package/dist/form/toFormDocument.js.map +1 -0
- package/dist/form/types.d.ts +73 -0
- package/dist/index.d.ts +67 -61
- package/dist/index.js +120 -25
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +281 -0
- package/dist/plugin/applyRegistryConfig.d.ts +11 -0
- package/dist/plugin/applyRegistryConfig.js +20 -0
- package/dist/plugin/applyRegistryConfig.js.map +1 -0
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +43 -40
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +16 -0
- package/dist/plugin/uploadsCollection.js +53 -0
- package/dist/plugin/uploadsCollection.js.map +1 -0
- package/dist/poll/applyPollOptions.js +18 -0
- package/dist/poll/applyPollOptions.js.map +1 -0
- package/dist/poll/buildPollOptionSourceFields.js +50 -0
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -0
- package/dist/poll/closeJob.d.ts +53 -0
- package/dist/poll/closeJob.js +121 -0
- package/dist/poll/closeJob.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +44 -0
- package/dist/poll/definePollOptionSource.js +6 -0
- package/dist/poll/definePollOptionSource.js.map +1 -0
- package/dist/poll/definePollType.d.ts +34 -0
- package/dist/poll/definePollType.js +6 -0
- package/dist/poll/definePollType.js.map +1 -0
- package/dist/poll/effectivePollOptions.d.ts +38 -0
- package/dist/poll/effectivePollOptions.js +64 -0
- package/dist/poll/effectivePollOptions.js.map +1 -0
- package/dist/poll/mostVoted.d.ts +21 -0
- package/dist/poll/mostVoted.js +32 -0
- package/dist/poll/mostVoted.js.map +1 -0
- package/dist/poll/outcomeBeforeChange.js +91 -0
- package/dist/poll/outcomeBeforeChange.js.map +1 -0
- package/dist/poll/outcomeFields.d.ts +38 -0
- package/dist/poll/outcomeFields.js +52 -0
- package/dist/poll/outcomeFields.js.map +1 -0
- package/dist/poll/pollTypeRegistry.d.ts +34 -0
- package/dist/poll/pollTypeRegistry.js +69 -0
- package/dist/poll/pollTypeRegistry.js.map +1 -0
- package/dist/poll/registry.d.ts +17 -0
- package/dist/poll/registry.js +12 -0
- package/dist/poll/registry.js.map +1 -0
- package/dist/poll/resolvePollCloseRequest.js +106 -0
- package/dist/poll/resolvePollCloseRequest.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +29 -0
- package/dist/poll/resolvePollOptions.js +52 -0
- package/dist/poll/resolvePollOptions.js.map +1 -0
- package/dist/poll/resolvePollOptionsRequest.js +50 -0
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +40 -0
- package/dist/poll/resolvePollOutcome.js +90 -0
- package/dist/poll/resolvePollOutcome.js.map +1 -0
- package/dist/poll/resultsField.js +21 -0
- package/dist/poll/resultsField.js.map +1 -0
- package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
- package/dist/poll/votes/aggregateFromVotes.js +54 -0
- package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
- package/dist/poll/votes/answerValues.js +16 -0
- package/dist/poll/votes/answerValues.js.map +1 -0
- package/dist/poll/votes/bumpPollVote.js +88 -0
- package/dist/poll/votes/bumpPollVote.js.map +1 -0
- package/dist/poll/votes/recountPollVotes.d.ts +20 -0
- package/dist/poll/votes/recountPollVotes.js +63 -0
- package/dist/poll/votes/recountPollVotes.js.map +1 -0
- package/dist/poll/votes/voteTallyHook.js +76 -0
- package/dist/poll/votes/voteTallyHook.js.map +1 -0
- package/dist/poll/votes/votesCollection.d.ts +19 -0
- package/dist/poll/votes/votesCollection.js +89 -0
- package/dist/poll/votes/votesCollection.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +5 -9
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/presentations/types.d.ts +3 -2
- package/dist/react/Form.d.ts +77 -35
- package/dist/react/Form.js +368 -222
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +51 -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/FormFields.d.ts +18 -0
- package/dist/react/FormFields.js +114 -0
- package/dist/react/FormFields.js.map +1 -0
- package/dist/react/FormLayout.d.ts +4 -2
- package/dist/react/FormLayout.js +3 -2
- package/dist/react/FormLayout.js.map +1 -1
- package/dist/react/FormResults.d.ts +8 -3
- package/dist/react/FormResults.js +50 -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 +40 -6
- package/dist/react/Poll.js +137 -33
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/adapters.d.ts +35 -0
- package/dist/react/adapters.js +11 -0
- package/dist/react/adapters.js.map +1 -0
- package/dist/react/captcha/HcaptchaCaptcha.d.ts +31 -0
- package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
- package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/RecaptchaCaptcha.d.ts +44 -0
- package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
- package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
- package/dist/react/captcha/TurnstileCaptcha.d.ts +31 -0
- package/dist/react/captcha/TurnstileCaptcha.js +53 -0
- package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
- package/dist/react/captcha/types.d.ts +8 -0
- package/dist/react/captcha/useCaptchaScript.js +63 -0
- package/dist/react/captcha/useCaptchaScript.js.map +1 -0
- package/dist/react/captcha/vendors.js +8 -0
- package/dist/react/captcha/vendors.js.map +1 -0
- package/dist/react/contract.d.ts +2 -3
- package/dist/react/contract.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/presentation/DialogSurface.js +1 -4
- package/dist/react/presentation/DialogSurface.js.map +1 -1
- package/dist/react/primitives/Checkbox.d.ts +6 -2
- package/dist/react/primitives/Checkbox.js +4 -2
- package/dist/react/primitives/Checkbox.js.map +1 -1
- package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
- package/dist/react/primitives/ChoiceGroup.js +33 -0
- package/dist/react/primitives/ChoiceGroup.js.map +1 -0
- package/dist/react/primitives/FieldShell.d.ts +13 -5
- package/dist/react/primitives/FieldShell.js +35 -32
- package/dist/react/primitives/FieldShell.js.map +1 -1
- package/dist/react/primitives/Input.d.ts +4 -2
- package/dist/react/primitives/Input.js +2 -1
- package/dist/react/primitives/Input.js.map +1 -1
- package/dist/react/renderers/calculation.js +7 -5
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +3 -5
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +84 -36
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +33 -0
- package/dist/react/renderers/date.js.map +1 -0
- package/dist/react/renderers/email.js +4 -5
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +29 -14
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +7 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/message.js +41 -0
- package/dist/react/renderers/message.js.map +1 -0
- package/dist/react/renderers/number.js +1 -4
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +43 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +32 -14
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +21 -6
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +4 -5
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -4
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/react/state.d.ts +46 -3
- package/dist/react/state.js +73 -10
- package/dist/react/state.js.map +1 -1
- package/dist/react/submitForm.js +1 -1
- package/dist/react/submitForm.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/react/useCalcValues.d.ts +10 -0
- package/dist/react/useCalcValues.js +13 -0
- package/dist/react/useCalcValues.js.map +1 -0
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +7 -6
- package/dist/react/useField.js.map +1 -1
- package/dist/react/useFormState.d.ts +1 -1
- package/dist/react/useFormState.js +1 -1
- package/dist/react/useFormState.js.map +1 -1
- package/dist/react/validateField.js +3 -6
- package/dist/react/validateField.js.map +1 -1
- package/dist/recall/interpolate.d.ts +4 -3
- package/dist/recall/interpolate.js +5 -4
- package/dist/recall/interpolate.js.map +1 -1
- package/dist/recall/resolver.d.ts +1 -1
- package/dist/recall/resolver.js +15 -3
- package/dist/recall/resolver.js.map +1 -1
- package/dist/spam/captcha.d.ts +4 -3
- package/dist/spam/captcha.js +4 -3
- package/dist/spam/captcha.js.map +1 -1
- package/dist/spam/clientIp.js +9 -0
- package/dist/spam/clientIp.js.map +1 -0
- package/dist/spam/constants.js +13 -1
- package/dist/spam/constants.js.map +1 -1
- package/dist/spam/identify.js +3 -6
- package/dist/spam/identify.js.map +1 -1
- package/dist/spam/providers/hcaptcha.d.ts +17 -0
- package/dist/spam/providers/hcaptcha.js +18 -0
- package/dist/spam/providers/hcaptcha.js.map +1 -0
- package/dist/spam/providers/recaptcha.d.ts +23 -0
- package/dist/spam/providers/recaptcha.js +23 -0
- package/dist/spam/providers/recaptcha.js.map +1 -0
- package/dist/spam/providers/siteverify.js +34 -0
- package/dist/spam/providers/siteverify.js.map +1 -0
- package/dist/spam/providers/turnstile.d.ts +17 -0
- package/dist/spam/providers/turnstile.js +18 -0
- package/dist/spam/providers/turnstile.js.map +1 -0
- package/dist/spam/resolveSpam.js +2 -1
- package/dist/spam/resolveSpam.js.map +1 -1
- package/dist/spam/spamGuard.js +25 -11
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +19 -4
- package/dist/submissions/SubmissionAnswers.js +6 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +100 -64
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- package/dist/submissions/coerceBoolean.js +14 -0
- package/dist/submissions/coerceBoolean.js.map +1 -0
- package/dist/submissions/createSubmission.d.ts +36 -0
- package/dist/submissions/createSubmission.js +26 -0
- package/dist/submissions/createSubmission.js.map +1 -0
- package/dist/submissions/formIdOf.js +13 -0
- package/dist/submissions/formIdOf.js.map +1 -0
- package/dist/submissions/resolveVotedSubmission.d.ts +27 -0
- package/dist/submissions/resolveVotedSubmission.js +51 -0
- package/dist/submissions/resolveVotedSubmission.js.map +1 -0
- package/dist/submissions/runSubmission.d.ts +10 -0
- package/dist/submissions/runSubmission.js +104 -54
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +11 -4
- package/dist/submissions/validateSubmission.js +99 -13
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/verifyContext.js +35 -0
- package/dist/submissions/verifyContext.js.map +1 -0
- package/dist/submissions/voteChangeEndpoint.js +105 -0
- package/dist/submissions/voteChangeEndpoint.js.map +1 -0
- package/dist/submissions/votedCookie.d.ts +15 -0
- package/dist/submissions/votedCookie.js +74 -0
- package/dist/submissions/votedCookie.js.map +1 -0
- package/dist/translations/clientRuntimeKeys.d.ts +17 -0
- package/dist/translations/clientRuntimeKeys.js +48 -0
- package/dist/translations/clientRuntimeKeys.js.map +1 -0
- package/dist/translations/de.d.ts +12 -0
- package/dist/translations/de.js +380 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +250 -28
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/index.d.ts +14 -5
- package/dist/translations/index.js +14 -3
- package/dist/translations/index.js.map +1 -1
- package/dist/translations/keys.d.ts +245 -23
- package/dist/translations/keys.js +246 -24
- package/dist/translations/keys.js.map +1 -1
- package/dist/translations/makeTranslate.d.ts +7 -3
- package/dist/translations/makeTranslate.js +12 -3
- package/dist/translations/makeTranslate.js.map +1 -1
- package/dist/translations/server.js +8 -1
- package/dist/translations/server.js.map +1 -1
- package/dist/uploads/captureFileRef.d.ts +10 -3
- package/dist/uploads/captureFileRef.js +18 -9
- package/dist/uploads/captureFileRef.js.map +1 -1
- package/dist/uploads/formatBytes.d.ts +9 -0
- package/dist/uploads/formatBytes.js +25 -0
- package/dist/uploads/formatBytes.js.map +1 -0
- package/dist/uploads/resolveFileRef.js +3 -2
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/uploads/types.d.ts +1 -2
- package/dist/validation/buildRuleBlocks.js +18 -17
- package/dist/validation/buildRuleBlocks.js.map +1 -1
- package/dist/validation/builtin/email.js +2 -1
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/index.d.ts +14 -0
- package/dist/validation/builtin/index.js +14 -1
- package/dist/validation/builtin/index.js.map +1 -1
- package/dist/validation/builtin/integer.js +15 -0
- package/dist/validation/builtin/integer.js.map +1 -0
- package/dist/validation/builtin/matchesField.js +3 -7
- package/dist/validation/builtin/matchesField.js.map +1 -1
- package/dist/validation/builtin/max.js +1 -0
- package/dist/validation/builtin/max.js.map +1 -1
- package/dist/validation/builtin/maxDate.js +24 -0
- package/dist/validation/builtin/maxDate.js.map +1 -0
- package/dist/validation/builtin/maxLength.js +1 -0
- package/dist/validation/builtin/maxLength.js.map +1 -1
- package/dist/validation/builtin/min.js +1 -0
- package/dist/validation/builtin/min.js.map +1 -1
- package/dist/validation/builtin/minDate.js +24 -0
- package/dist/validation/builtin/minDate.js.map +1 -0
- package/dist/validation/builtin/minLength.js +1 -0
- package/dist/validation/builtin/minLength.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +5 -1
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/builtin/oneOf.js +1 -0
- package/dist/validation/builtin/oneOf.js.map +1 -1
- package/dist/validation/builtin/pattern.js +11 -2
- package/dist/validation/builtin/pattern.js.map +1 -1
- package/dist/validation/builtin/url.js +1 -0
- package/dist/validation/builtin/url.js.map +1 -1
- package/dist/validation/builtin/validateDateBound.js +18 -0
- package/dist/validation/builtin/validateDateBound.js.map +1 -0
- package/dist/validation/builtin/validateRegexParams.js +44 -0
- package/dist/validation/builtin/validateRegexParams.js.map +1 -0
- package/dist/validation/emailPattern.js +10 -0
- package/dist/validation/emailPattern.js.map +1 -0
- package/dist/validation/fieldTargetParam.d.ts +22 -0
- package/dist/validation/fieldTargetParam.js +38 -0
- package/dist/validation/fieldTargetParam.js.map +1 -0
- package/dist/validation/registry.d.ts +2 -1
- package/dist/validation/registry.js +2 -9
- package/dist/validation/registry.js.map +1 -1
- package/dist/validation/runValidation.js +8 -29
- package/dist/validation/runValidation.js.map +1 -1
- package/dist/validation/types.d.ts +5 -11
- package/dist/validation/validateUrl.js +22 -0
- package/dist/validation/validateUrl.js.map +1 -0
- package/package.json +14 -6
- package/styles/form-builder.css +94 -190
- 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/consent/buildConsentSourceConfig.js +0 -49
- package/dist/consent/buildConsentSourceConfig.js.map +0 -1
- package/dist/consent/builtin/index.d.ts +0 -7
- package/dist/consent/builtin/index.js +0 -11
- package/dist/consent/builtin/index.js.map +0 -1
- package/dist/consent/builtin/pageReference.js +0 -75
- package/dist/consent/builtin/pageReference.js.map +0 -1
- package/dist/consent/builtin/static.js +0 -44
- package/dist/consent/builtin/static.js.map +0 -1
- package/dist/consent/defineConsentSource.d.ts +0 -35
- package/dist/consent/defineConsentSource.js +0 -6
- package/dist/consent/defineConsentSource.js.map +0 -1
- package/dist/consent/registry.d.ts +0 -16
- package/dist/consent/registry.js +0 -16
- package/dist/consent/registry.js.map +0 -1
- package/dist/consent/resolveConsentLinks.d.ts +0 -19
- package/dist/consent/resolveConsentLinks.js +0 -26
- package/dist/consent/resolveConsentLinks.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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,365 @@
|
|
|
1
1
|
# @10x-media/form-builder
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.21
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Two submit/validate fixes. Actions can declare `essential: true` on `defineAction`: an essential action runs inline before the response (never queued, bounded by the dispatch deadline), its failure or timeout turns the submit into an error the visitor sees with a translated plugin message, the remaining actions are skipped, and the submission is kept even on a `persistSubmissions: false` form so a failed provider handoff never loses what the visitor sent. Fire-and-forget actions are unchanged. And blurring a pristine field no longer reveals its required error: reveal now needs the field to be dirty (changed since mount or the last reset) or its step submitted, which also stops a freshly reset form from showing errors when clicked.
|
|
8
|
+
|
|
9
|
+
## 0.1.0-beta.20
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Consent fields gain a Display setting: `checkbox` (the default, unchanged) or `notice`, which renders the statement as passive prose with no control, for flows where submitting is the opt-in ("By subscribing, you agree to our privacy policy"). The server records `agreed: true` on a notice proof regardless of the client payload, `required` is ignored for notices, and the proof carries `display: 'notice'` so audits distinguish the two. `consentSourcesField` rows gain a `noticeStatement` rich text beside `statement`, so one source phrases each presentation naturally while keeping one policy, one version, and one id in every proof; a notice field falls back to `statement` when it is empty. Whichever wording renders is exactly what the proof snapshots, selected through one shared function on both paths.
|
|
14
|
+
|
|
15
|
+
## 0.1.0-beta.19
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- The from-address and department selects now load their options while a form is still being created, instead of sitting empty until the first save. Both option sets are request-scoped (they depend on who is asking, not on the form document), so their endpoints now live at id-less paths (`GET /api/forms/from-addresses`, `GET /api/forms/departments`); the old `/:id/`-prefixed routes still answer on the same handlers for anything that hardcoded them. `EndpointOptionsSelect` and `RecipientsSelect` gain a `scope: 'document' | 'request'` clientProp (default `'document'`, unchanged behaviour) so a host field backed by its own request-scoped endpoint can opt into the same create-mode loading. Document-scoped selects (poll options, consent sources) are unaffected.
|
|
20
|
+
|
|
21
|
+
## 0.1.0-beta.18
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- Add `email.fromSources`: senders resolved at send time, for hosts where the from-address is tenant identity rather than per-form configuration. A source stores a stable namespaced value (e.g. `tenant:default`) on the action and re-resolves the actual address on every send with the same run-time arguments a recipient source gets, so a tenant that changes its from-address changes it for every saved form at once. Sources appear in the existing From select ahead of the `fromAddresses` literals; literals keep today's behaviour exactly, including never touching a source at send.
|
|
26
|
+
|
|
27
|
+
## 0.1.0-beta.17
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- Add `renderResults` to `<Poll>`: a render prop that replaces the built-in `<FormResults>` wherever the poll shows results (after voting, when closed, and for a recorded outcome). It receives the exact `FormResultsProps` the default rendering gets, so hosts can draw result rows from their own data, keyed by each bucket's option value, or wrap `<FormResults>` in their own layout. Omitting the prop changes nothing.
|
|
32
|
+
|
|
33
|
+
## 0.1.0-beta.16
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Fix every REST submission returning 500 on beta.15. The vote-change endpoint delegates ordinary creates to Payload's stock create handler, which re-parses the request body the endpoint had already consumed; a consumed fetch body stays non-null, so the second read threw "Body is unusable". The endpoint now hides the spent stream before delegating (the parsed `data`/`file` are already on the request), and finds the stock handler by its own endpoint tag instead of handler identity, so a host-wrapped handler can no longer recurse. Local API submissions were never affected.
|
|
38
|
+
|
|
39
|
+
## 0.1.0-beta.15
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- Changeable votes for polls via a new `poll.allowChange` checkbox (default off; existing polls are unaffected). With it on, the voted cookie carries the voter's signed submission id, a repeat submit from the same browser updates that submission in place instead of creating another, and the vote tally moves with the changed answer (old value decrements, new one increments, respondents unchanged). Changes run the full create pipeline (validation, spam controls, consent recapture) and are rejected once the poll closes. New `resolveVotedSubmission` (root and `/rsc`) resolves the voter's current pick server-side from the httpOnly cookie; `<Poll currentVote>` highlights it in results ("Your vote" badge, also via `<FormResults currentValues>`) and offers a "Change vote" button that reopens the form prefilled. `onSuccess` now also receives the submitted answer values on its result object.
|
|
44
|
+
|
|
45
|
+
## 0.1.0-beta.14
|
|
46
|
+
|
|
47
|
+
### Minor Changes
|
|
48
|
+
|
|
49
|
+
- Add an `adapters` prop for host-owned effects on `<Form>`, `<Poll>`, and the captcha widgets. `adapters.navigate` replaces the post-submit `window.location` redirect so router-based hosts (Next.js App Router, TanStack Router, React Router) can transition in place; the plugin passes its intent (`{ replace: false }`) and never falls back to a hard navigation when the adapter is set. `adapters.voteStorage` overrides the poll voted-flag persistence (or disables it with `false` for hosts on the server-side `poll.votedCookie`). `adapters.loadScript` replaces the captcha vendor script injection for consent-gated or CSP-nonced loading, keeping the per-src cache and evict-on-failure retry. Every member is optional and defaults to the current behavior, so existing consumers are unaffected.
|
|
50
|
+
|
|
51
|
+
## 0.1.0-beta.13
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- Make the poll tally writer's Postgres import opaque to bundlers.
|
|
56
|
+
|
|
57
|
+
The optional `@payloadcms/db-postgres` peer was loaded via a literal dynamic import, which bundlers resolve at build time: a Mongo host on Turbopack failed `next build` with "Module not found" even though the Postgres branch never runs there (and `serverExternalPackages` does not help, since it governs bundling, not resolution). The specifier is now built at runtime, so no bundler resolves it and the package is only touched when the adapter is actually Postgres. Mongo hosts that worked around this with a resolve alias or stub can remove it.
|
|
58
|
+
|
|
59
|
+
Two upgrade notes from the same integrator round: hosts that had extended the select field block with their own `display` field must remove it on the beta.12 bump (the plugin ships one with identical values, and Payload refuses to boot with two same-name fields); and custom renderers replace the built-ins wholesale, so per-instance settings added in minor releases (`display` variants, `autocomplete`, calculation formatting) must be read by your renderer or an author's choice is silently ignored, now documented in the rendering guide.
|
|
60
|
+
|
|
61
|
+
## 0.1.0-beta.12
|
|
62
|
+
|
|
63
|
+
### Minor Changes
|
|
64
|
+
|
|
65
|
+
- Authoring UX round: a visual calculation builder, display variants, an optional-by-default field config surface, and a poll vote tally store.
|
|
66
|
+
|
|
67
|
+
- **Visual calculation builder.** The calculation field's expression is authored in a native left-to-right chain editor: start with a value (Field, Number, Weighted field, Function, or a registered source), add operation steps, optionally finish with a function, with a live readable preview and honest remove affordances throughout. The stored expression tree is unchanged and any API-written expression loads as the equivalent chain; read-only and error states are wired, and field pickers only offer numeric fields (calculations restricted to earlier ones, matching evaluation order).
|
|
68
|
+
- **Calculation extensibility.** A new `calc` option opens the expression engine to host data: `calc.sources` registers named value resolvers (`resolve` returns one number per render/submission, e.g. a tax rate or member discount; `resolveWeights` returns per-option numbers for a chosen field, e.g. each selectable product's live price) that resolve server-side, ride the form document for the client preview, and re-resolve authoritatively at submit; `calc.functions` registers custom calculation functions (client previews additionally accept the same map via `<Form calcFunctions>`). The calculation field also gains display formatting (`decimals`, localized `prefix`/`suffix`) applied in the renderer, admin answers, and email output. New utilities: `defineCalcSource`, `defineCalcFunction`, `resolveCalcContext`, `formatCalc`, `formatCalcValue`, and a `useCalcValues()` hook for composed frontends reading live computed values.
|
|
69
|
+
- **Display variants.** Select fields gain an optional `display` setting (`dropdown` default, `radio`, `buttons`) rendered as an accessible radio group with the invalid-state focus routing intact; checkbox fields gain `checkbox` or `switch`. The `ChoiceGroup` primitive is exported for custom renderers.
|
|
70
|
+
- **Optional field settings.** Field width and select option labels are no longer required: width defaults to full (the renderer already falls back), and a missing or blank option label falls back to the value everywhere (renderer, admin answers, aggregation, recall). Width option labels are localized.
|
|
71
|
+
- **Sidebar form flags with a compose seam.** `multistep`, `pollEnabled`, and `persistSubmissions` are stacked sidebar checkboxes, composed through the new `settings.fields` option so a host can relocate, wrap, extend, or drop them. Forms now registers before form-submissions in the admin nav.
|
|
72
|
+
- **Advanced tab seam.** Field types can append settings to the Advanced tab via `advancedConfig`; text and email use it for an `autocomplete` autofill hint. The `hidden` flag documents that hidden fields are still validated. New `integer` validation rule for number fields; flow validation messages are localized.
|
|
73
|
+
- **Poll vote store (default on).** Votes are counted into a hidden append-only tally collection (`form-poll-votes`) when a submission completes: results reads and `mostVoted` resolution become one indexed find (no scan, no 10k truncation), tallies survive `persistSubmissions: false` and admin deletions (votes are permanent), rows are pure aggregate so a persist-off poll retains zero per-visitor data, and writes are atomic, transaction-joined, and sharded against concurrent write conflicts. `poll.votes: false` restores scan-based results and rejects persist-off polls at save. Exported: `recountPollVotes`, `aggregateFromVotes`, `POLL_VOTES_SLUG`, `RESPONDENTS_VALUE`, `VOTE_SHARDS`. Postgres hosts create the new `form_poll_votes` table with their next migration; `@10x-media/form-builder` declares `@payloadcms/db-postgres` as an optional peer for the tally write path.
|
|
74
|
+
|
|
75
|
+
## 0.1.0-beta.11
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
78
|
+
|
|
79
|
+
- Server-resolved recipients with a signed form context, opt-out submission storage, and an action-block collision guard.
|
|
80
|
+
|
|
81
|
+
- **Recipient sources.** A new `email.recipientSources` option registers recipients the plugin resolves server-side at send time, offered in every recipient field (**To**/**Cc**/**Bcc**/**Reply-To**) as their own option group. A source's namespaced `value` cannot collide with an address, its `resolve` returns zero or more addresses (each sanitized to one, so a source cannot inject headers), an empty resolve is dropped, and a send with every recipient empty is skipped rather than sent with an empty **To**. This lets one reusable form email "the person this page is about" without an address ever reaching the browser.
|
|
82
|
+
- **Signed form context.** `signFormContext({ payload, relationTo, value })` (exported from `/rsc` and root) mints a signed, expiring reference to the document a form was rendered for; `<Form context={token} />` carries it, the plugin verifies it on submit (rejecting a tampered or expired one rather than dropping it), stores the verified `{ relationTo, value }` on the submission, and hands it to each source's `resolve`. The reference is signed, not encrypted (the id is already public); it never appears in the answers, the admin answers view, or `{{*}}` body output.
|
|
83
|
+
- **`persistSubmissions`.** A per-form "Store submissions" flag (default on). Unchecked, the plugin deletes the submission after its actions run, for a pure signup form that only POSTs to a provider. Deletion is once, after the whole action pass, regardless of individual action success; pruning does not cascade to uploaded files (documented).
|
|
84
|
+
- **Action-block collision guard.** The plugin now throws a clear error at boot if a registered action `type` collides with a host Payload block slug (which Payload would otherwise silently merge), instead of corrupting the stored action shape. Action block slugs are unchanged, so existing actions keep working.
|
|
85
|
+
|
|
86
|
+
## 0.1.0-beta.10
|
|
87
|
+
|
|
88
|
+
### Minor Changes
|
|
89
|
+
|
|
90
|
+
- Locale-correct i18n bundles for host fallbacks, and repeater error re-indexing on row removal.
|
|
91
|
+
|
|
92
|
+
- **Locale bundles are exported for host translators.** `@10x-media/form-builder/react` and `/i18n` now export `de`, `bundles` (`Record<string, TranslationBundle>`), the `TranslationBundle` type, and `clientRuntimeKeys`. `makeTranslate` also accepts a locale code (`makeTranslate('de')`) in addition to a map, so a host bridging its own translator can fall back with `makeTranslate(locale)` (or `makeTranslate(bundles[locale] ?? en)`) and a visitor's locale no longer silently resolves to English for a key the host did not mirror. `clientRuntimeKeys` lists the keys the visitor runtime resolves, for asserting mirror coverage in a test.
|
|
93
|
+
- **Removing a repeater row re-indexes its sub-field errors.** Deleting a row now clears that row's composite validation errors and shifts the surviving rows' errors down, so a server-side error (for example `crew[2].paddlerName`) no longer mis-attributes to a shifted row or lingers unreachably after a delete.
|
|
94
|
+
|
|
95
|
+
## 0.1.0-beta.9
|
|
96
|
+
|
|
97
|
+
### Minor Changes
|
|
98
|
+
|
|
99
|
+
- Multi-step keyboard, per-step validation reveal, and focus/accessibility fixes for the `<Form>` runtime.
|
|
100
|
+
|
|
101
|
+
- **Enter advances a multi-step form.** On a non-terminal step, Enter in a single-line field now validates and advances the step (exactly like the Next control), or keeps the visitor on the step and reveals its errors when a field is invalid, instead of doing nothing. On the terminal step Enter submits once (native submit, still behind the existing re-entrancy guard). Textareas (newline), selects (confirm), and buttons stay exempt, and single-step forms keep native Enter-to-submit.
|
|
102
|
+
- **Validation errors reveal per step, not globally.** A field's error now shows only once it is touched or a submit/advance attempt was made for the step it belongs to. Previously any submit attempt flipped one global flag, so navigating forward to a later step surfaced errors on fields the visitor had never reached. Internally `FormState.submitAttempted` becomes `attemptedSteps`, and reveal is keyed to each field's step.
|
|
103
|
+
- **A terminal Submit routes to the first invalid step.** When an earlier step is invalid at submit time (for example a field that becomes required only after a later answer), the form navigates back to the first step that owns an invalid field and focuses it, rather than failing in place on the terminal step.
|
|
104
|
+
- **Focus moves with the step.** Every step change (forward or back) moves focus into the new step, and a blocked advance or submit moves focus to the first invalid field, so keyboard and screen-reader users travel with the form.
|
|
105
|
+
- **Step changes and validation failures are announced.** A polite `aria-live` region announces "Step X of Y" on each change, and a `role="alert"` summary appears when an advance or submit is blocked. New translation keys `form.stepStatus` and `form.stepInvalid` (English and German), overridable through the `translations` option.
|
|
106
|
+
|
|
107
|
+
## 0.1.0-beta.8
|
|
108
|
+
|
|
109
|
+
### Minor Changes
|
|
110
|
+
|
|
111
|
+
- Second feedback round: submit control, step field order, response editor, and host-rendered success.
|
|
112
|
+
|
|
113
|
+
- **Implicit Enter-submit is suppressed on multi-step forms.** A form-level key handler prevents a lone text input from submitting the whole form on Enter; only the explicit Submit control submits. Textareas (newline), selects (confirm), and buttons are exempt, and single-step forms keep native Enter-to-submit. The `<form>` is the plugin's even in `children` mode, so this is the only place a host could apply the guard.
|
|
114
|
+
- **Step fields render in `form.fields` order.** A step's `fields` list is treated as membership only; the render order follows the form's field order rather than the flow-builder entry order.
|
|
115
|
+
- **`richText.responseEditor`.** The success response message field's editor can now be set independently via `richText.responseEditor` (falling back to `richText.editor`), mirroring `bodyEditor` for action bodies. The message block stays on the plugin-wide `editor`.
|
|
116
|
+
- **Host-rendered success.** `onSuccess` gains a second argument, `{ response }`, carrying the recall-resolved success response (a redirect url, or the message serialized with the form's converters), so a host can toast or render it; `<Poll>` forwards the same payload to its host. A new `converters` prop on `<Form>` threads custom block converters (e.g. host `icon`/`badge` blocks) into the client serializer, so those blocks survive in the success message, the resolved response, and inline `message` blocks. `successBehavior: 'reset'` clears the form in place after a successful submit instead of swapping in the success screen (returning a multi-step form to its first step), so a host can keep the form usable and give feedback via `onSuccess`. Defaults (`onSuccess`'s first argument, `successBehavior: 'replace'`) are unchanged. New exported types: `FormSuccessResponse`, `FormSuccessResult`.
|
|
117
|
+
|
|
118
|
+
## 0.1.0-beta.7
|
|
119
|
+
|
|
120
|
+
### Minor Changes
|
|
121
|
+
|
|
122
|
+
- Composition seam, consent re-entrance safety, and server-side recipient enforcement.
|
|
123
|
+
|
|
124
|
+
- **`<FormFields>` + `<Form header>`.** The standard field loop is now an exported `<FormFields>`
|
|
125
|
+
component, so a custom `children` layout can compose `<Form><FormSteps /><FormFields />
|
|
126
|
+
<FormControls /></Form>` without reimplementing visibility, calc, recall, and step filtering. For
|
|
127
|
+
the common case, `<Form header={<FormSteps />} />` places chrome above the fields while keeping all
|
|
128
|
+
default behavior. `recall` is now available on the form context.
|
|
129
|
+
- **Consent resolver receives the whole form document.** A multi-tenant `consent.sources` resolver
|
|
130
|
+
derives its tenant from a field on the provided doc instead of reading the form back. The consent
|
|
131
|
+
`afterRead` hook now guards against resolver re-entrance (a per-id `Set` on `req.context`), so a
|
|
132
|
+
resolver that does read the form back can no longer recurse.
|
|
133
|
+
- **`email.recipients.allowCustom: false` is enforced on the server.** When set, a non-token
|
|
134
|
+
recipient must be one of the resolved department options, validated at save and fail-closed
|
|
135
|
+
(mirroring the `from` field). The default (`allowCustom` true) is unchanged.
|
|
136
|
+
|
|
137
|
+
- Two new controls for consent reads and upload ownership.
|
|
138
|
+
|
|
139
|
+
- **`consent.resolveOnRead`** (default `true`) gates the afterRead hook that resolves each form's consent statements on every read. Set it `false` to skip that per-read source lookup (N queries on a list view) and resolve statements yourself with the exported `resolveConsentStatements` when you render a form. Submit-time proof capture is unaffected either way, since it always re-resolves from the source.
|
|
140
|
+
- **`spam.uploadOwnership`** (default `'lenient'`) controls how strictly a submitted file's upload ownership is enforced. `'strict'` additionally rejects a stamped upload whenever the submitter cannot be identified, so a deployment that always identifies requests (a trusted proxy header, or a custom `identify`) never lets an unverifiable claim capture an owned upload. `'lenient'` keeps the current fail-open behavior for unidentifiable submitters.
|
|
141
|
+
|
|
142
|
+
### Patch Changes
|
|
143
|
+
|
|
144
|
+
- Consolidate the built-in email actions. `emailTeam` and `confirmation` were ~80% identical (the same recipient fields, subject/body config, from-field insertion, and send logic) duplicated across two files, each built through a five-positional-argument builder that needed a `useMaxParams` lint suppression. Both now share one `buildEmailAction` skeleton and one `EmailActionOptions` object; each action supplies only what differs (its `to` target and whether a missing recipient throws or is skipped). Behavior is unchanged, verified by both actions' full existing test suites passing against the shared implementation.
|
|
145
|
+
|
|
146
|
+
- Condition and flow correctness.
|
|
147
|
+
|
|
148
|
+
- `file` and `repeater` fields now offer a presence-only condition (exists / does not exist) in the builder, instead of the text/select operators they cannot actually satisfy (a `FileRef` object or a row array only stringifies). A presence check also treats an empty repeater as absent.
|
|
149
|
+
- Flow transition `when` clauses are laundered the same way field conditions are: a transition referencing a field that was later deleted is dropped, rather than persisting as an always-true route that force-navigates the visitor. Valid transition conditions are canonicalized to the same OR-of-ANDs shape as field conditions.
|
|
150
|
+
|
|
151
|
+
- Correctness and security hardening from an internal audit.
|
|
152
|
+
|
|
153
|
+
- **Registry key forced** on custom `actions` and `poll.sources`: a definition registered under a key that differs from its `type` no longer silently never runs.
|
|
154
|
+
- **Recipient injection closed**: a resolved `{{field}}` recipient is sanitized to a single address, dropping CR/LF header injection and any extra addresses a crafted submission value could smuggle in.
|
|
155
|
+
- **`email.recipients.fieldTokens: false`** is now enforced on the server, not just the client.
|
|
156
|
+
- **Consent integrity**: prefill and submit-time boolean coercion share one truthy allow-list, so a stray string value (e.g. from a non-React client) is never read as consent.
|
|
157
|
+
- **Cross-tenant leak closed**: the consent-sources and poll-options endpoints load the form under the caller's own read access (no `overrideAccess`), so an authenticated user cannot enumerate another tenant's form.
|
|
158
|
+
- **Rate-limit skip surfaced**: when the request identity is null the limiter still fails open, but the skip is recorded on `meta.spam.rateLimit` and warned once instead of being silent.
|
|
159
|
+
- **Honeypot collision fixed**: the decoy is submitted under a fixed reserved key, so a real field sharing the decoy's DOM input name (e.g. `website`) is never stripped or mistaken for the honeypot.
|
|
160
|
+
- **Action failures logged**: a failed post-submit email or webhook is logged per action instead of being swallowed while the submission looks fully successful.
|
|
161
|
+
|
|
162
|
+
- DX and robustness cleanup from the package audit.
|
|
163
|
+
|
|
164
|
+
- **Renderers honor the passed `id` prop.** Eleven built-in field renderers minted their own `useId()` and ignored the `id` the render host already generates and passes, so each field ran two id hooks and the documented contract prop was dead. They now use the passed `id` (the host stays the single source; a custom host can control it). `repeater` and `message` already conformed.
|
|
165
|
+
- **The overlay backdrop no longer fires `onClose` twice.** A backdrop click was handled both by the backdrop's own `onClick` and by the surface's outside-pointerdown dismiss, so it closed twice; the backdrop's handler also ignored `closeOnOutsideClick: false`. The backdrop is now purely presentational and dismissal is owned solely by the dismiss hook, which respects the flag.
|
|
166
|
+
- **Polls surface a results-load failure.** A failed results fetch was stored as an empty array and rendered as a zero-count "no votes yet" table. It now shows a localized error instead of masking the failure.
|
|
167
|
+
- **`captureFileRef` gains unit coverage** for its ownership match/mismatch, unidentified-submitter fail-open, unstamped-upload passthrough, and failed-load cases.
|
|
168
|
+
- **The consent proof documents its time-of-check/time-of-use window** (wording is read at form load but re-resolved at submit) so the intended behavior is not mistaken for a bug.
|
|
169
|
+
|
|
170
|
+
- Split the two largest source files for maintainability, with no behavior change.
|
|
171
|
+
|
|
172
|
+
- The `FormBuilderPluginOptions` type (nearly 200 lines of the plugin's public option surface, with its doc comments) moves out of `index.ts` into a dedicated `src/options.ts`, leaving `index.ts` focused on the plugin factory and the public barrel. The type is still exported under the same name (and its `PluginOptions` alias).
|
|
173
|
+
- The forms collection's built-in endpoints (poll results/options/close, plus the optional consent-sources, from-addresses, and departments endpoints) move out of `buildFormsCollection` into `buildFormsEndpoints`, so the collection builder stays focused on field and hook composition.
|
|
174
|
+
|
|
175
|
+
`index.ts` drops from ~558 to ~346 lines and `forms.ts` from ~845 to ~743; the moved code is unchanged and every existing unit, integration, and cross-DB test passes against it.
|
|
176
|
+
|
|
177
|
+
- Localize the last hard-coded English strings in the public form renderer. The file field's attached-file indicator, in-flight "Uploading" status, upload-failure fallback, and remove control, plus the form's close-control label, success message, and submit-failure fallback, now resolve through the same `t` the rest of the renderer already uses (bundled `en`/`de`, host-overridable per locale) instead of baked-in English. A German (or any) `t` now localizes them; behavior under the default English `t` is unchanged.
|
|
178
|
+
|
|
179
|
+
- Consolidate the registry and constant plumbing.
|
|
180
|
+
|
|
181
|
+
- The field-type, validation-rule, action, poll-source, and poll-type registries all resolved their opt-in override map (`false` removes, `true` keeps, a definition adds/replaces with `type` forced to the key) with their own copy of the same loop. That loop now lives in one shared `applyRegistryConfig` helper, so the merge semantics are defined once instead of five times.
|
|
182
|
+
- Definition `label` contracts are aligned: actions and poll option sources now accept a per-locale `Record<string, string>` label in addition to a string key/literal, matching poll types. All three resolve through one shared `resolveDefinitionLabel` helper. Widening only, so existing string labels are unaffected.
|
|
183
|
+
- The email-format regex (duplicated between the `email` field type and the `email` validation rule) and the calc recursion-depth guard (duplicated between the calc parser and evaluator) are each a single shared constant now, so the pair can no longer drift.
|
|
184
|
+
|
|
185
|
+
- Repeater correctness fixes.
|
|
186
|
+
|
|
187
|
+
- Multi-step forms now validate a step's repeater sub-fields on **Next**, so an invalid required sub-field can no longer be skipped past; it surfaces inline, mirroring submit and the server. `goNext` also gains a re-entrancy guard so a double-click cannot push the same step onto history twice.
|
|
188
|
+
- Editing a repeater sub-field clears its stale server-side error immediately, instead of leaving it shown until the next submit.
|
|
189
|
+
- Repeater rows carry a stable React key, so removing a middle row no longer strands a stateful sub-renderer's local state (e.g. the file renderer's filename) onto the wrong row.
|
|
190
|
+
|
|
191
|
+
## 0.1.0-beta.6
|
|
192
|
+
|
|
193
|
+
### Minor Changes
|
|
194
|
+
|
|
195
|
+
- Email actions and consent polish, plus paddle-worldwide handoff asks.
|
|
196
|
+
|
|
197
|
+
- **New creatable multi-recipient email field.** To, Reply-to, CC, and BCC on the Email Team and
|
|
198
|
+
Confirmation actions accept multiple recipients as native badges, mixing picked department
|
|
199
|
+
addresses, free-typed emails, and `{{field}}` recipient tokens (drag-reorderable, `admin.width`
|
|
200
|
+
aware). Configurable via `email.recipients` (`allowCustom`, `fieldTokens`, `tokenFieldTypes`).
|
|
201
|
+
- **Half-width pairing** of the email address fields (To/Reply-to, CC/BCC).
|
|
202
|
+
- **Consent field polish:** no more redundant description (the source statement is the content); the
|
|
203
|
+
field block title and the source array rows now show the consent source's name; the condensed
|
|
204
|
+
department-emails array centers its remove control on the input.
|
|
205
|
+
- **Consent statements on every read.** An `afterRead` hook resolves statements onto the form doc, so
|
|
206
|
+
consent renders wherever the form is fetched (modal/client, not only RSC); `toFormDocument` falls
|
|
207
|
+
back to the doc-carried statements.
|
|
208
|
+
- **`toFormDocument`** accepts a populated redirect `reference` (`value: string | number | object |
|
|
209
|
+
null`) and an optional `resolveRedirect` seam that fills `redirect.url`.
|
|
210
|
+
- **Versioned consent proof.** Submissions snapshot the agreed wording via `consent.snapshot`
|
|
211
|
+
(default `both`: a statement hash, the plain text, and the source name), for an audit trail
|
|
212
|
+
independent of later source edits.
|
|
213
|
+
- **File MIME picker** constrained to the host upload collection's `mimeTypes` (with an optional
|
|
214
|
+
`uploads.mimeTypes` override).
|
|
215
|
+
|
|
216
|
+
**BREAKING:** `emailTeam`/`confirmation` `to`, `cc`, `bcc`, and `replyTo` are now `string[]` (`text`
|
|
217
|
+
`hasMany`). A legacy single-string value reads as one recipient; Postgres consumers regenerate a
|
|
218
|
+
migration.
|
|
219
|
+
|
|
220
|
+
## 0.1.0-beta.5
|
|
221
|
+
|
|
222
|
+
### Minor Changes
|
|
223
|
+
|
|
224
|
+
- Finish localizing the flow builder: the remaining transition, hint, empty-state, and fallback-title strings now go through the translation system (en and de).
|
|
225
|
+
|
|
226
|
+
Ship shadcn `country` and `state` field renderers so the shadcn registry covers every built-in field type, and export `COUNTRIES` and `US_STATES` from `@10x-media/form-builder/react` for them. A parity test now asserts the registry renderer set matches the package's `defaultRenderers`, so a new field type cannot ship without its shadcn renderer. The country and state option labels are intentionally left unlocalized (the stored value is the stable code).
|
|
227
|
+
|
|
228
|
+
- Feedback round: host-owned consent sources with live statements, bare message blocks, form-level button labels, sequential step flow, poll field eligibility and outcome selection, and per-request sender addresses, plus a field-config cleanup pass (drop dead shared fields, source poll options from a field, admin polish). Also: consent sources identified by their own row id, a two-checkbox form header driving conditional Flow/Poll tabs, poll outcomes that support ties with extensible auto-close strategies, fully localized and department-routable email, concise list-view columns, and response messages with the same recall fidelity as action emails. This pass also redesigns the submission answers view around native Payload fields, adds a manual poll-close action with self-managing `mostVoted` outcomes, clarifies field labels in the flow builder, and adds a server-side `createSubmission` helper alongside a documented submit contract. Validation is simplified to a binary pass-or-fail (severities and warnings are gone), every rule now opens with a plain-language description, `matchesField` and custom rules target another field through a validated picker, `country` and `state` become opt-in field types, and the redirect response gains a field-composition seam.
|
|
229
|
+
|
|
230
|
+
**Breaking:**
|
|
231
|
+
|
|
232
|
+
- **Display settings are gone; the form's title is exposed instead.** The `display` group (`showTitle`/`title`/`intro`), its Display tab, `FormDocument.display`, and `FormDisplaySettings` are all removed, and `<Form>` no longer renders a heading or intro above the fields. Whether a form's title appears is the host's call: `FormDocument.title` carries the document title (the collection's existing required `useAsTitle` field) for a page to render, or not. Authored `display` data is orphaned on next save.
|
|
233
|
+
- **`message` is a bare, nameless block.** The block is its `content` rich text alone: no name, label, width, required, placeholder, description, validation, or `visibleWhen`. `FormFieldDefinition` gains `bare`, `FormFieldInstance.name` becomes optional, and instances are addressed by key (a name, else Payload's hidden block row id), so flow step assignment, render keys, and the builder's picker all keep working. Stored named message rows keep rendering, but resaving a form through the admin drops the name, so a flow referencing a legacy message by name needs that message re-assigned to its step. Bare blocks are excluded from repeater `subFields`, whose row plumbing is name-keyed. A bare definition must declare `value: 'none'`; the plugin throws at boot otherwise.
|
|
234
|
+
- **`response.submitLabel` is removed in favour of a `buttons` group.** Every form now carries `buttons` at the bottom of its Fields tab: `submitLabel` at full width, then `prevLabel` and `nextLabel` side by side in a row (previous first). Store the label at `buttons.submitLabel`; `FormResponseSettings` no longer carries `submitLabel` and `<Form>` ignores legacy stored values. The `<Form>`/`<FormControls>` label prop and the `buttons.fields` seam key are `prevLabel`/`defaultFields.prev` (the `renderBack` prop, `backButtonClassName`, `BackButtonRenderProps`, and the `fb-form__back` class keep the `back` name; only the label concept moved to `prev`).
|
|
235
|
+
- **A flow step's unset `next` falls through instead of ending the form.** `next` now has three states: absent falls through to the next step in array order (the last step ends the form), `null` is an explicit end of form regardless of position, and a step id is a jump. Matching transitions still win over all three. Flows relying on an unset `next` to end the form mid-array must set that step's next to "End of form" (`next: null`). Newly added steps previously carried no `next` and were silently terminal, so a fresh two-step flow showed Submit on step one and never reached step two. `__end__` is reserved as the builder's sentinel and rejected as a step id.
|
|
236
|
+
- **Consent references sources you own, not fields on the form.** A consent field now stores one thing, a source `key`. The statement, its policy page, and its link live on a `consentSourcesField()` you place on any collection or global you own, read back through a `consent.sources` resolver. The old machinery is removed wholesale: `defineConsentSource`, the consent-source registry, the `static` and `pageReference` built-ins, `resolveConsentLinks`, `buildConsentSourceConfig`, the per-source `sourceConfig` group, the `consentSources` plugin option, and the consent block's own `statement` and `optional` fields. Without a `consent.sources` resolver the `consent` field type is not registered at all, mirroring `uploads: false` removing `file`. The statement is a live reference resolved per request in the visitor's locale via `resolveConsentStatements()` (root + `/rsc`) and injected by `toFormDocument(doc, { consentStatements })`, so correcting a sentence corrects every form pointing at it, with nothing to re-save. Proof is id-based: `ConsentProof` is now `{ agreed, source, page?: { relationTo, id }, versionRef?, at }` (the old `ref` slug is gone); `versionRef` is the published version document's own id, captured only when the page's collection has drafts enabled (a collection with versions but no drafts, or none at all, records no `versionRef` rather than a misleading null). `required` now actually enforces (the field gained an intrinsic validator, since a missing consent coerces to `false`, a present value the shared required guard skipped), and `label`/`placeholder` drop off the block via `omitShared` because the statement is the visible text and the accessible name. Authored statements and source config from the previous shape are orphaned. New exports: `consentSourcesField`, `resolveConsentStatements`, the `ConsentSourceEntry`/`ConsentSourcePage`/`ConsentSourcesResolver` types, and `sanitizeUrl` (now also from `./react`).
|
|
237
|
+
- **`poll.resultsField` is a select over eligible fields.** It was free text. It now lists the form's named instances of field types declaring `pollEligible` (built-in: `select`), and server validation enforces the same rule on save, so a free-text or PII field can never be stored while the poll is enabled.
|
|
238
|
+
- **Some field types no longer store shared basics they never read.** `FormFieldDefinition.omitShared` lets a type leave chosen shared config fields (`label`, `placeholder`, `description`, `width`, `required`) unauthored while keeping the rest of the chrome; `name` is never omittable. Applied where the renderer proves the field dead: `checkbox`, `file`, `repeater`, and `date` no longer store a `placeholder` (browsers ignore one on a date input), and `calculation`, being server-derived and read-only, stores neither `placeholder` nor `required`. Those columns are dropped, orphaning any authored values. `OmittableSharedField` is exported.
|
|
239
|
+
- **`richText.editor` is now plugin-wide.** It previously overrode the action body fields only; it is now the default editor for every plugin rich text field (message content, response message, action bodies). The new `richText.bodyEditor` is the action-body-specific override and falls back to `editor`. A host that set `editor` to give email authors a different toolbar should rename it to `bodyEditor` to keep that scope. Consent statements are unaffected: they live on your `consentSourcesField()`, which carries its own `editor` option.
|
|
240
|
+
- **A consent source is identified by its own row id, not a hand-authored `key`.** `consentSourcesField()`'s rows no longer have a `key`; the array row's own auto-assigned id is the stable reference a consent field stores and a proof records, so renaming or reordering a row can never orphan either. The row's name, shown when picking a source and used as the policy-link text, is now called `name` (was `label`). Row-level descriptions were trimmed to the array itself and the page field, relabeled "Statement source" and described as: set it if you want a submission to save a reference to your policy. A `consent.sources` resolver returning the previous `{ key, label, ... }` shape must return `{ id, name, ... }` instead; existing consent field values referencing an old `key` no longer resolve to a source.
|
|
241
|
+
- **The form header gained two independent checkboxes, and the `buttons` group is gone.** `multistep` and `pollEnabled` are new top-level checkboxes: `multistep` reveals a **Flow** tab (previously always visible) and drives the client's step navigation, `pollEnabled` reveals a new **Poll** tab and replaces the previous nested `poll.enabled` (the poll group's own fields are otherwise unchanged). The **Buttons** group is gone: `submitLabel` now sits directly at the bottom of the Fields tab, and `nextLabel`/`prevLabel` sit in a row at the bottom of the Flow tab (shown once the flow has a step); all three are still reassembled into `FormDocument.buttons` the same way for `<Form>`. The `buttons.fields` composition seam now receives and returns a `{ submit, prev, next }` map keyed by slot instead of a single field array. A field a host adds alongside a default label no longer rides along automatically into `FormDocument.buttons`: `buttons`, like `poll`, is now a fixed allowlist on `toFormDocument` (previously it, like `response` still is, passed the whole group through), so surfacing a custom button field to the client now needs merging it into `toFormDocument`'s output by hand. `FormDocument` gains `multistep: boolean` and `pollEnabled: boolean`.
|
|
242
|
+
- **A poll outcome records a set of winners, not one.** `poll.outcome.winningValue` is `poll.outcome.winningValues`, an array: the admin winner picker is a multi-select, and a tie records every tied value instead of forcing a single pick. `resolvePollOutcome`'s `winningValue` argument is `winningValues`, also an array. `definePollOptionSource.resolveOutcome` may now return a single value, an array (a tie), or `undefined`.
|
|
243
|
+
- **The form title is localized.** `title` (the collection's `useAsTitle` field) now follows `localizeContent` like the rest of a form's visitor-facing text, required only in the default locale so other locales fall back to it instead of forcing a translation everywhere.
|
|
244
|
+
- **A poll's outcome type defaults to `mostVoted`, not `manual`, and the winner picker is hidden for every auto-resolving type, not just `mostVoted`.** A freshly enabled poll now resolves itself at close with no admin action required; picking `manual` still opts back into a hand-set winner. `poll.outcome.winningValues` shows only for `manual` now; it used to show for anything except `mostVoted`, so a `source` poll (or a host `definePollType` strategy) that previously let an editor hand-override its winner no longer offers that picker at all. A poll that already has an explicit `type` stored is unaffected; only one whose `type` was never saved picks up the new default.
|
|
245
|
+
- **Enabling a poll now requires a vote field.** With `pollEnabled` on and no `poll.resultsField` set, saving auto-picks the form's one eligible field (a named `select`, an opted-in `country`/`state`, or a custom `pollEligible` instance), asks you to choose when there are several, and refuses the save when there are none, unless the poll is driven by an option source or the `source` outcome type, which need no on-form field. Previously an enabled poll with no vote field saved without complaint and simply had nothing to aggregate.
|
|
246
|
+
- **Validation is binary; `severity` and `warnings` are gone.** A rule either blocks or passes, so the per-rule `severity` select, the `{ message, severity }` result form, `ValidationSeverity`, and the entire non-blocking `warnings` pathway are removed: the `FieldRendererProps.warnings` prop, the form state's `warnings`, `FieldShell`'s warning region, and `ValidateFieldResult.warnings`. A rule's `validate` now returns `true` (pass) or a `string` (a blocking error); every failure blocks submission. A custom renderer that read `warnings` should drop it, and a rule returning `{ message, severity: 'warning' }` now blocks like any other error.
|
|
247
|
+
- **`matchesField`'s target is a picker validated on save.** Its `field` param is authored by choosing from the form's own fields instead of typing a name (the value is still a stored field name, so no data migration), and it is checked on save: a target that no longer resolves to a field is a save error rather than a rule that silently never fires. The same picker and check are available to custom rules through the exported `fieldTargetParam`.
|
|
248
|
+
|
|
249
|
+
**Features:**
|
|
250
|
+
|
|
251
|
+
- Host-owned consent sources: `consentSourcesField()` is an array (`name`, rich text `statement`, optional polymorphic `page`) you place wherever your policies live, and `consent.sources` is the async `req`-scoped resolver that reads it back. The statement is authored rich text serialized through the same escaping and URL-sanitizing pipeline as action bodies; the policy link (a source's `name` plus a resolver-provided `url`) renders beside it, and the checkbox takes its accessible name from the statement flattened to its visible words. That flattener, `textOfBody`, is exported (root and `./react`) and handles rich text, legacy Slate, or a plain string. Multi-tenancy needs no plugin support: place the field on a tenant-scoped document and scope the resolver by `req`.
|
|
252
|
+
- Form-level button labels: `submitLabel`/`nextLabel`/`prevLabel`, localized following `localizeContent`, each resolved as `<Form>` prop, then the stored value, then the translated default. `buildSubmitLabelField`, `buildNextLabelField`, `buildPrevLabelField`, and `buildDefaultButtonFields` (keyed `submit`/`next`/`prev`) are exported, and `useFormContext()` now exposes the `form` document so custom chrome can read them.
|
|
253
|
+
- Sequential step fall-through: the flow builder's Default next select gains "Next step in order" as the default for an unset `next`, "End of form" maps to an explicit `null`, `normalizeFlow` preserves `null` as distinct from absent, and deleting a step cascades, clearing every `next` and transition aimed at it.
|
|
254
|
+
- Poll field eligibility: `pollEligible` on a field definition marks a type choosable as a poll's results field. The results endpoint gates anonymous reads on the same rule, serves option-source polls anonymously (buckets seeded in resolved order with the source's labels), fails closed with a 503 when a source throws, and refuses with a 403 while resolution is empty.
|
|
255
|
+
- A field type can source its own poll options: `FormFieldDefinition.resolveOptions` lets a `pollEligible` type produce its poll choices from the configured instance itself (for example a `hasMany` relationship to the voteable records) rather than from authored options or a shared source. The one resolution backs submission validation, the admin winner select, and results labeling alike; it is consulted after an `optionSource` and before the instance's authored `options`. `resolveEffectivePollOptions` (root and `/rsc`) and `ResolveFieldOptionsArgs` are exported.
|
|
256
|
+
- Poll outcome selection: `poll.outcome.winningValues` is admin-editable through a multi-select backed by an authenticated `GET /api/forms/:id/poll-options`, with `resolvedAt` server-stamped and access-locked (set, changed, or cleared by a hook; direct writes dropped). The group is composable through the `poll.outcomeFields` option, which receives the two default fields (`winningValues`, `resolvedAt`) and returns the group's fields verbatim, so a host can swap the winner picker for its own component; membership validation runs server-side regardless, so a swap cannot bypass it. `definePollOptionSource` gains a `resolveOutcome` seam, so `resolvePollOutcome` runs in auto mode with `winningValues` omitted and returns the recorded set either way. `resolveEffectivePollOptions` is the single option set backing the admin select, the endpoint, outcome validation, and a field type's `resolveOptions`, so what the admin offers and what the server accepts cannot drift. `buildWinningValuesField`, `buildResolvedAtField`, `buildDefaultOutcomeFields`, and `OutcomeFieldsOverride` are exported.
|
|
257
|
+
- Per-request sender addresses: `email.fromAddresses`, an async `req`-scoped resolver, gives both email actions a From select backed by an authenticated `GET /api/forms/:id/from-addresses`, validated for membership on save and passed through to `payload.sendEmail`. Intended for multi-tenant hosts scoping senders to the tenant derived from `req`. Absent (the default) adds no field and keeps the adapter's default sender.
|
|
258
|
+
- Admin polish: select option rows label their two fields `Label`/`Value` (were `Option label`/`Option value`) beneath an array already titled Option/Options; the flow step title's hardcoded, untranslatable placeholder is gone and the remaining English strings in the flow builder are translated; and `poll.outcome` and `response.redirect`, the two groups nested inside another group, render flush via `admin.hideGutter`.
|
|
259
|
+
- Extensible poll outcome types: `definePollType` registers a named outcome strategy (`resolveOutcome`), chosen per form via the poll's `type` select and configured plugin-wide via `poll.types`. Three ship and are always available: `manual` (default; an admin always sets the winner), `mostVoted` (auto-resolves to the top-voted value(s) once there are responses, a tie recording every co-winner, declining to decide on a truncated sample), and `source` (delegates to the poll's option source's own `resolveOutcome`). A poll with a `closesAt` and a non-`manual` type schedules its own resolution as a native Payload job (`waitUntil: closesAt`, re-enqueued on every save so the newest close date always wins); this needs a wired job runner (`config.jobs.autoRun`, a worker, or a serverless cron) to actually run. As a safety net with no runner, a closed, unresolved, non-manual poll also resolves the next time its results are read, by an admin or through the public endpoint alike.
|
|
260
|
+
- Department-routed email: the new `email.departments` plugin option turns `emailTeam`'s **To** into a select over host-managed addresses (an authenticated endpoint, validated on save, fails closed like `email.fromAddresses`). `departmentsField()` is the array a host places anywhere (a settings global, a tenant document) to hold the addresses, rendered as condensed flat rows with no per-row collapsible, and `resolveDepartmentOptions` builds the resolver's return value from it with a reusable current-locale -> default-locale -> next-available fallback. Independent of departments, **To** is now always localized like the subject and body, so a submission routes to the address stored for its own locale; the queued (job) dispatch path loads the form at the submission's own stored locale for the same reason.
|
|
261
|
+
- Concise, localized list columns: the `forms` list shows a field count ("6 Fields") and a step count ("2 steps", or none with no flow) instead of Payload's raw block/JSON summaries, plus a curated default column set (title, fields, flow, poll, updated at); `form-submissions` gets a curated default set too (form, status, locale, created at).
|
|
262
|
+
- Response messages interpolate recall tokens: a form's success message (`response.message`) now supports the same `{{field}}` / `{{field|fallback}}` / `{{field||fallback}}` tokens as an action body, resolved against the submission's own formatted answers (option labels, Yes/No, localized dates) and field descriptors, matching what `emailTeam`'s body already gave an admin.
|
|
263
|
+
- The submission answers view now renders with native Payload field components (the same `TextInput`/`FieldLabel`/`Pill` building blocks Payload's own fields use) instead of bespoke markup, and honors each field's authored width so the admin layout matches the widths editors set on the form. Consent renders as an Agreed/Declined `Pill` beside its source link and capture timestamp; `locale` and the submission `meta` (received-at time, IP/user agent when captured, spam signal) are folded into a single Submission-details section instead of separate top-level fields. `showSubmissionRawFields` now also gates `locale` and `meta`, alongside the existing `values`/`descriptors`/`consent`. `SubmissionDescriptor` gains `width`, and `SubmissionWidth` (`full`/`half`/`third`/`twoThirds`) is exported.
|
|
264
|
+
- A poll with no scheduled `closesAt` gets a manual **Close poll now** button in the admin, backed by an authenticated `POST /api/forms/:id/close`: it stamps `closesAt` to now and, for the `mostVoted`/`source` outcome types, resolves the winner the same way a scheduled auto-close would. A `manual` poll must already have a winner recorded, closing refuses (400) otherwise, and closing an already-closed poll is refused the same way. A `mostVoted` poll now manages its own outcome end to end: its `winningValues` field is hidden from the outcome group entirely (the strategy decides the winner, so there is nothing to hand-pick), whether it resolves on schedule or via this new button. The outcome `type` select reads more plainly, now with a description of what it does ("How the winning option is decided when the poll closes."): its options are `Set the winner manually`, `Most-voted option wins`, and `Winner from the option source` (the last hidden from the select until an option source is registered).
|
|
265
|
+
- The flow builder's field picker and unassigned-fields list now label each field with its authored `label` (falling back to the machine name) instead of the raw name, and label a bare message block with a short snippet of its authored content (falling back to its translated type label, numbered when the form has more than one) instead of a generic placeholder.
|
|
266
|
+
- A server-side `createSubmission(payload, { form, values, req? })` (root export) wraps `payload.create` for the `form-submissions` collection, giving server code (a script, a queued job, another collection's hook) a typed, documented entry point instead of hand-rolling the collection slug and body shape. It runs the exact pipeline the browser and REST paths run: server-authoritative validation, spam metadata when `req` is threaded through, descriptor snapshotting, consent capture, and post-submit action dispatch are all `form-submissions` collection hooks, not a route handler. Access is unchanged (`create: () => true`; `read` logged-in only; `update` disabled), so a raw REST `POST` and `createSubmission` reach the identical pipeline `submitForm` does.
|
|
267
|
+
- Admin and docs polish: a new Options API conventions section documents the plugin's few configuration shapes (opt-in flags, resolver-gated features, composition seams, registry seams), so which pattern a given option follows is no longer a guess. A field block's collapsed row header now shows the field's own label once set, falling back to its translated type name, instead of Payload's default block-name placeholder that reads "Untitled" until filled in (`FieldBlockLabel`, exported from `./client`). Two field descriptions duplicating their own field's obvious purpose are gone: the previous/next button-label fields' "Shown on multi-step forms only" and the department-routed **To** field's "The department this submission is routed to. Each locale keeps its own address."
|
|
268
|
+
- Save-through poll close and reopen: the Poll tab's button now saves the whole document through the admin's own submit instead of posting to a dedicated endpoint, so a `manual` poll's winner, even one picked in the same sitting, persists together with the close in one request rather than needing a prior save. The same button now also reopens: once closed, clicking it clears `closesAt` and the recorded `winningValues` together, for every outcome type. The `POST /:id/close` endpoint keeps its previous behavior unchanged for programmatic callers; only the admin button stopped using it.
|
|
269
|
+
- Email actions gain **Cc**, **Bcc**, and **Reply-to**: `emailTeam` and `confirmation` both carry all three, localized like `subject` and interpolated with the same recall tokens, each accepting a comma-separated address list forwarded to `payload.sendEmail` as-is.
|
|
270
|
+
- Internal-reference redirects: the new `redirectRelationships` plugin option (a `CollectionSlug[]`, named after `@payloadcms/plugin-form-builder`'s option of the same purpose) adds a polymorphic `reference` field to `response.redirect`, so an author can point a successful submit at an internal document instead of, or alongside, a URL. `toFormDocument` passes the raw `{ relationTo, value }` pair through unresolved; resolving it to a URL is the host's job, since the plugin has no notion of your routing.
|
|
271
|
+
- Two opt-in field types, `country` and `state`: fixed-option pickers (every ISO 3166-1 country; the 50 US states plus DC) that store the code and `format` it to the English name, poll-eligible like `select`. They are not registered by default; a host enables them with `fields: { country: countryField, state: stateField }` (both exported). Their renderers are always registered, so opting in needs no frontend wiring.
|
|
272
|
+
- Every validation rule opens with a plain-language description. Each rule's config block leads with a short, localized sentence explaining what makes it fail, so an editor never has to infer a rule's mechanics from its name (`matchesField`, for instance, states that it fails when the two answers differ). `ValidationRuleDefinition.description` carries it as a localizable key; the built-ins ship English and German.
|
|
273
|
+
- `fieldTargetParam(name, opts?)` is a reusable rule/action param that targets another field on the same form: a text value authored by the field-name picker, with a save-time existence check so a removed target is caught. `matchesField` uses it, and it is exported for custom rules that need to point at another field.
|
|
274
|
+
- A composition seam for the redirect response, `response.redirect.fields`: it hands you the group's default fields (the `url` text field, plus the `reference` relationship when `redirectRelationships` is set) and takes your array back, so you can drop in a custom link field, swap `url` for your own picker, reorder, or filter, mirroring `buttons.fields` and `poll.outcomeFields`. `RedirectFieldsOverride`, `ResponseOption`, and `RedirectOption` are exported. The plugin's built-in redirect still reads `redirect.url` / `redirect.reference`, so a host that replaces `url` owns resolving its own field to a destination.
|
|
275
|
+
|
|
276
|
+
**Fixes:**
|
|
277
|
+
|
|
278
|
+
- **A required checkbox accepted an explicit "unchecked".** The engine's required guard fires on `isEmpty(value)`, and `false` is a present value, so an untouched box (never sent) was rejected while a checked-then-unchecked one (sent as `false`) passed. `checkbox` (and `consent`) gained the intrinsic validator they lacked rather than widening the global `isEmpty`, which would ripple to every boolean. The one `runValidation` engine backs both client and server, so the fix binds them alike.
|
|
279
|
+
- **Admins saw raw stored values for sourced polls.** The authenticated results endpoint now injects the results field's effective options (source-resolved, `resolveOptions`-backed, or authored) too, so a sourced or resolver-backed poll renders labels in the admin instead of raw values, best-effort: a resolve failure there degrades to raw values rather than blocking the trusted read (the anonymous path still fails closed).
|
|
280
|
+
- The `pattern` rule's params are validated at authoring time, so an uncompilable expression or flag set is a save error instead of a rule that silently never fires. `pattern` compiles against its sibling `flags`, since flags decide validity, and an invalid flag is left to the `flags` field so one typo reports once. The submit-time fail-open stays as the safety net for rules stored before the guard existed.
|
|
281
|
+
- The unused-translation-key guard could never fail. It listed key-enumerating files by name and so missed `de.ts`, which then entered the usage corpus and, keying every entry as `keys.<name>`, matched all keys on its own, leaving `unused` permanently empty and the guard passing unconditionally. The corpus now excludes `src/translations/` by directory rather than by naming the files inside it, so no future locale can rejoin it and re-blind the scan however it happens to be written; nothing in that directory consumes a `keys.X` reference, so a key used only from there was never a used key anyway. Unreferenced keys are now reported, and no orphans exist once the scan works.
|
|
282
|
+
- A defaulted admin select was clearable with a silent consequence: a cleared submission `status` (which defaults to `complete`, and aggregation counts `complete`) dropped the submission out of every poll result with nothing in the UI saying so. It is now `isClearable: false`, matching the precedent on `width`, `response.type`, and `poll.resultsVisibility`.
|
|
283
|
+
- The shadcn registry's consent field rendered no required marker, making a required consent the only labelled field in the registry giving the visitor no sign it was mandatory until submit failed.
|
|
284
|
+
- Three fields with a custom select component silently rendered no description. A custom `Field` replaces Payload's whole default render, and a function `admin.description` is dropped from the client field before it ever gets there, so the text was configured but unreachable: the confirmation action's recipient field ("The email field on this form the confirmation is sent to.") and the file field's maximum size both lost theirs in earlier releases, and the poll's results field lost the sentence naming the rule it exists to enforce ("Use a choice field, never a free-text or PII field."). All three now pass a translated description key to their component, which renders it.
|
|
285
|
+
- The condition builder's value input for a `select` or `date` operand threw and broke the builder. Payload's native `Select`/`Date` leaf field components read `field.admin.placeholder` unconditionally, and the client field the builder synthesizes for a leaf condition carried no `admin` object at all; every synthesized leaf (`text` and `number` too, for consistency) now carries an empty `admin: {}`.
|
|
286
|
+
- Closing a `manual` poll with no winner recorded used to round-trip to the server and fail with a toast error after the click. The Close button now disables itself first, with an inline hint ("Select a winning value first."), so the invalid click is prevented rather than attempted and rejected.
|
|
287
|
+
- The poll's vote-field select was labelled "Results field," easy to misread as a display setting rather than the question being voted on. It is now "Vote field," with its description explaining the new auto-fill behavior.
|
|
288
|
+
- The email actions' **Cc** and **Bcc** labels are now cased **CC** and **BCC** (both locales), matching how the headers are conventionally written.
|
|
289
|
+
- The shadcn `form-results` registry snapshot was rebuilt. It still shipped the single-winner `winningValue?: string` component after the source moved to `winningValues?: string[]` for tie support, so a project pulling the registry got a component that could not highlight a tie; `registry:build` now matches the source.
|
|
290
|
+
|
|
291
|
+
**Notes:**
|
|
292
|
+
|
|
293
|
+
- No migrations, as ever pre-1.0: the removed `display` group, `response.submitLabel`, the consent `statement`/`optional` fields, the per-source `sourceConfig`, the dropped `placeholder`/`required` columns, and the form title's move to localized storage are orphaned rather than dropped. Write your own if you carry data.
|
|
294
|
+
- The From select and the poll winner select are both document-scoped, so each stays empty until the form's first save: their endpoints need a document id to resolve against. Save the form, then pick. The consent-sources select is the same.
|
|
295
|
+
- `email.fromAddresses` fails closed, and Payload runs the validate on every save rather than only when `from` changed, so while the resolver is throwing, no form whose email action has a From set can be saved at all, including edits that never touch the address. Failing open would persist a sender the host can no longer vouch for; a resolver reaching a flaky upstream should cache or fall back internally instead of throwing.
|
|
296
|
+
- Internal, no changeset of its own: the shared test harness gained `warmVersionCollections`, initializing drafts-enabled collections so consent version-capture is exercisable in integration tests. It is tooling (`@10x-media/test-harness`), not published.
|
|
297
|
+
- The plugin now registers `form-submissions` before `forms` (Payload lists collections in registration order within a nav group), so the shared "Forms" sidebar group reads alphabetically. Ordering the rest of a host's own sidebar against it stays the host's config concern; Payload has no global nav-sort option.
|
|
298
|
+
|
|
299
|
+
### Patch Changes
|
|
300
|
+
|
|
301
|
+
- Correct `payload` and `@payloadcms/ui` peer ranges to `^3.83.0`. The plugin uses `definePlugin`, which shipped in Payload 3.83.0, so 3.82.x installs satisfied the old range but failed at import.
|
|
302
|
+
|
|
303
|
+
## 0.1.0-beta.4
|
|
304
|
+
|
|
305
|
+
### Minor Changes
|
|
306
|
+
|
|
307
|
+
- Feedback round: flow authoring, localized content, response/display settings, bring-your-own uploads, poll lifecycle, composable form chrome, and a German locale.
|
|
308
|
+
|
|
309
|
+
**Breaking:**
|
|
310
|
+
|
|
311
|
+
- **Uploads are bring-your-own and off by default.** The plugin no longer registers a `form-uploads` collection. `uploads` is now `false` (default; the `file` field type is removed from the registry entirely) or `{ collection: 'slug' }` pointing at an upload-enabled collection the app owns; the plugin validates it at boot, appends a hidden `owner` field, and prepends the spam upload hooks. The file field's `relationTo` config is gone (the server stamps the configured slug onto each `file` block as a hidden `uploadsCollection`), and `overrides.uploads` no longer exists.
|
|
312
|
+
- **Stored presentation is gone.** The `defaultPresentation` field on `forms` and the plugin-level `presentations` option are removed. Presentation is decided at render time via the `presentation`/`presentations` props on `<Form>`; the React presentation system (page/inline/modal/drawer, custom `FormPresentation`) is unchanged.
|
|
313
|
+
- **Poll config moved into a `poll` group.** The top-level `showResults`/`resultsField` fields on `forms` are replaced by `poll.enabled`, `poll.resultsField`, plus the new `poll.resultsVisibility` and `poll.closesAt`. The results endpoint now gates on `poll.enabled` and visibility.
|
|
314
|
+
- **Content fields are localized by default.** Author-facing content (field labels, placeholders, descriptions, option labels, consent statements, action subjects/bodies, response/display content) carries `localized: true`; on hosts with Payload `localization` configured the stored data shape changes to per-locale objects. Hosts without localization are unaffected (Payload strips the flag). Opt out with `localizeContent: false`; when opting out, derive registry overrides from `buildDefaultFieldDefinitions(false)` / `buildDefaultActionDefinitions(false)` / `buildDefaultConsentSources(false)` instead of spreading the prebuilt defaults, which carry the localized flags.
|
|
315
|
+
- **`<Poll hasVoted>` ORs with the localStorage guard.** `hasVoted: true` marks the visitor as voted regardless of localStorage; it previously could suppress the localStorage read. `false`/omitted falls back to localStorage as before.
|
|
316
|
+
- **Form chrome markup changed.** The default buttons now carry stable classes (`fb-form__back`, `fb-form__next`, `fb-form__submit`) and always render inside a `.fb-form__controls` wrapper (previously classless buttons, wrapped only on multi-step forms). CSS or tests targeting the old bare buttons need the new hooks.
|
|
317
|
+
|
|
318
|
+
**Features:**
|
|
319
|
+
|
|
320
|
+
- Flow authoring without ids: steps are named by title (ids auto-generated, never shown), field assignment is exclusive with an unassigned-fields list, and save-time normalization dedupes fields across steps.
|
|
321
|
+
- `message` field type: display-only rich text between fields, never validated or stored, condition-targetable, recall-token aware.
|
|
322
|
+
- Response settings (`response` group + tab): success `message` rich text, `redirect` URL (fires on the custom-`children` path too), and `submitLabel` with prop-over-document-over-default precedence.
|
|
323
|
+
- Display settings (`display` group + tab): opt-in form title (`showTitle`/`title`) and rich text `intro` rendered above the fields, recall-token aware.
|
|
324
|
+
- Field config layout: the Field tab lays basics out in rows (name/label, width/placeholder), `width` is required with a `full` default, and `required` sits last.
|
|
325
|
+
- Poll lifecycle: `closesAt` close date enforced at submission time, `resultsVisibility` (`afterVote`/`afterClose`) gating for anonymous results, a `results.access` seam for multi-tenant scoping, and `poll: { votedCookie: true }` for an httpOnly SSR voted marker read via `hasVotedCookie`.
|
|
326
|
+
- Poll option sources: `definePollOptionSource` + the `poll.sources` registry resolve a poll's choices from host domain data at render (`resolvePollOptions` + `toFormDocument(doc, { pollOptions })`) and at submission (resolved values are the only accepted answers, fail closed).
|
|
327
|
+
- Poll outcome: `resolvePollOutcome` records a final `winningValue` (admin read-only, server-only write path); `<Poll>` renders the final state and `<FormResults winningValue>` highlights the winner.
|
|
328
|
+
- `richText.editor` overrides the editor on both action body fields (e.g. a minimal toolbar for email authors); a custom `richText.serialize` now receives the submitted `form` and `req` for per-tenant lookups or handing off to react-email.
|
|
329
|
+
- Composable chrome: `<FormControls>` (the default buttons with all `render*`/className overrides) and `<FormSteps>` (step progress with `aria-current`) are exported for custom `children` layouts, and `useFormContext` exposes the controller context including resolved `labels`, the active `t`, and `effectiveValues`.
|
|
330
|
+
- Server-safe `toFormDocument`: no `'use client'` directive, exported from the root and the `/rsc` subpath (which also gains `resolvePollOptions`, `resolvePollOutcome`, `hasVotedCookie`, `votedCookieName`, and `isPollClosed`), so Server Components narrow the document without pulling in the React client layer.
|
|
331
|
+
- German (`de`) admin and renderer strings ship built in alongside English.
|
|
332
|
+
- The shadcn registry now covers every built-in field type (adds date, consent, calculation, and repeater renderers); the default `textarea` type label is now "Textarea".
|
|
333
|
+
- Repeaters with `minRows` start pre-seeded with that many empty rows instead of appearing blank until validation; an explicit `initialValues` entry for the field (including `[]`) still wins over the seed.
|
|
334
|
+
|
|
335
|
+
**Fixes:**
|
|
336
|
+
|
|
337
|
+
- File fields nested inside a repeater row now get the same server-side upload enforcement as top-level file fields: each captured value is re-derived into a `FileRef` from the stored upload doc (owner, MIME type, and size checked against the sub-field config) instead of storing the raw client-submitted id, and the submission fails closed on a forged id or a missing uploads collection.
|
|
338
|
+
- Upload enforcement is now driven by a field's value kind rather than its block type, so a custom field type registered with `value: 'file'` is captured and enforced (owner/MIME/size) at both the top level and inside repeaters, not just the built-in `file` type.
|
|
339
|
+
|
|
340
|
+
**Notes:**
|
|
341
|
+
|
|
342
|
+
- The voted cookie is deliberately not `Secure`: it is a UX-only marker (skip re-showing an answered form), never an integrity or auth signal, and staying non-Secure keeps plain-HTTP local development working.
|
|
343
|
+
- No migrations are shipped for the removed/moved `forms` columns (`defaultPresentation`, `showResults`, top-level `resultsField`); pre-1.0 with no known production consumers, write your own if you carry data.
|
|
344
|
+
|
|
345
|
+
## 0.1.0-beta.3
|
|
346
|
+
|
|
347
|
+
### Minor Changes
|
|
348
|
+
|
|
349
|
+
- Bundled captcha adapters and widget components. `turnstileProvider`, `recaptchaProvider` (v2 + v3 with `minScore`), and `hcaptchaProvider` verify tokens server-side with fail-closed semantics (network errors, timeouts, and non-2xx responses reject the submission). Matching headless `TurnstileCaptcha`, `RecaptchaCaptcha`, and `HcaptchaCaptcha` components on the `/react` export load each vendor script once, report tokens through `onToken` for `<Form captchaToken>`, clear on expiry or error, and expose `reset` (plus on-demand `execute` refresh for reCAPTCHA v3) via a ref handle.
|
|
350
|
+
|
|
351
|
+
- Feedback round: `date` field type (native date input, real-calendar validation) with `minDate`/`maxDate` rules; rich text notification bodies for `emailTeam`/`confirmation` using the project's configured editor, serialized with escaping, sanitized links, `{{ name|fallback }}`/`{{*}}`/`{{*:table}}` tokens, a plugin `richText { converters, serialize }` option for custom nodes and non-HTML channels, and `renderBody` on action run args; the confirmation recipient is a select over the form's email fields, validated server-side on save; `signedWebhook` validates its URL as absolute http(s) and describes its config fields; calculation expressions get an admin description, Monaco JSON-schema autocomplete, and save-time validation of the expression tree; the file field's `mimeTypes` is a curated select and `maxSize` live-previews as a human-readable size, with a client-side size pre-check and hint line on the renderer; flow transitions resolve against effective values so calculation fields can drive branching; the forms document (Fields/Flow/Actions) and every field block (Field/Validation/Advanced) use tabbed admin layouts, and admin components ship their own CSS (no `styles.css` import needed for the admin). New exports: `serializeBody`, `defaultBodyConverters`, `sanitizeUrl`, `escapeHtml`, `renderAllValues`, `renderAllValuesTable`, `fileMimeTypeOptions`, `formatBytes`, `defaultFieldDefinitionsByType`, `defaultValidationRulesByType`, and the `ByteSizeField`/`FieldNameSelect` client components.
|
|
352
|
+
|
|
353
|
+
**Soft compat:** existing plain-string action bodies keep working through the send path (interpolated exactly as before), though the admin editor shows them as empty rich text until re-entered. File fields keep enforcing stored free-text MIME values, but values outside the curated list need re-selecting the next time that field is edited; submitted data is untouched.
|
|
354
|
+
|
|
355
|
+
**Postgres migration note:** on `@payloadcms/db-postgres`, two `forms` column types change: action `body` goes `varchar` to `jsonb` (pre-existing string bodies are not valid JSON, so cast with `USING to_jsonb(body)` when writing the migration), and `mimeTypes` moves from a text array to an enum-backed select. Mongo needs no migration. The project config must also set an `editor` (rich text bodies inherit it; Payload throws `MissingEditorProp` without one).
|
|
356
|
+
|
|
357
|
+
### Patch Changes
|
|
358
|
+
|
|
359
|
+
- The registry (shadcn-style) file field now matches the built-in file renderer: a client-side max-size pre-check rejects oversized files before uploading, and an accepted-types/max-size hint line renders under the input.
|
|
360
|
+
|
|
361
|
+
`minDate`/`maxDate` rule bounds are now validated as real `YYYY-MM-DD` calendar dates in the admin UI, so a malformed bound (e.g. `abc` or `2024-02-30`) is rejected at config time instead of silently breaking the rule's comparisons.
|
|
362
|
+
|
|
3
363
|
## 0.1.0-beta.2
|
|
4
364
|
|
|
5
365
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# @10x-media/form-builder
|
|
2
4
|
|
|
3
5
|
An end-to-end forms platform for Payload v3: author forms in the admin, validate server-side, render headless on your frontend, collect typed submissions, aggregate results, and act on them. Simple by default for editors, with a definition seam (`defineFormField`, `defineValidationRule`, `defineAction`, and friends) wherever developers need depth, and 100% native to Payload.
|
|
@@ -8,8 +10,8 @@ Part of the [@10x-media Payload plugins](https://github.com/10x-media/payload-pl
|
|
|
8
10
|
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
|
-
- **Fields**: text, textarea, email, number, select, checkbox, file, consent, calculation,
|
|
12
|
-
- **Validation**: declarative per-field rules, custom messages
|
|
13
|
+
- **Fields**: text, textarea, email, number, date, select, checkbox, file, consent, calculation, repeater (dynamic row lists with per-row sub-field validation), and message, authored as Payload blocks; `country` and `state` are opt-in, and custom types via `defineFormField` are never second-class.
|
|
14
|
+
- **Validation**: declarative per-field rules with plain-language descriptions, custom messages, cross-field and async server-only rules, a Standard Schema escape hatch (zod, valibot, ...), one server-authoritative engine.
|
|
13
15
|
- **Conditional logic**: `visibleWhen` / `validateWhen` in Payload's `Where` shape, one isomorphic engine on client and server.
|
|
14
16
|
- **Multi-step flows** with conditional branching, authored in the admin flow builder.
|
|
15
17
|
- **Headless rendering**: `<Form>` with progressive validation and lifecycle events, accessible primitives, an optional layout grid; style via CSS hooks, a shadcn registry, or your own renderers.
|
|
@@ -17,10 +19,10 @@ Part of the [@10x-media Payload plugins](https://github.com/10x-media/payload-pl
|
|
|
17
19
|
- **Recall and prefill**: pipe answers into labels and messages; URL prefill; hidden context fields.
|
|
18
20
|
- **Calculations**: a safe no-eval expression engine for totals and quiz scores, recomputed on the server.
|
|
19
21
|
- **Post-submit actions**: email, confirmation, signed webhook, and custom actions, queued via Payload jobs with a bounded inline fallback.
|
|
20
|
-
- **Consent** with proof
|
|
21
|
-
- **Spam protection** on by default: honeypot, per-identity rate limiting,
|
|
22
|
+
- **Consent** referencing statements you own, resolved live per request, with proof stored as document ids and published versions; **file uploads** into your own upload collection with server-enforced MIME/size; **polls** with a gated public results endpoint.
|
|
23
|
+
- **Spam protection** on by default: honeypot, per-identity rate limiting, bundled captcha adapters (Turnstile, reCAPTCHA, hCaptcha), upload-ownership scoping, privacy-first metadata.
|
|
22
24
|
- **Accessibility** verified by automated axe checks; **typed translations** via `@10x-media/form-builder/i18n`.
|
|
23
|
-
- **Collection overrides**: extend `forms
|
|
25
|
+
- **Collection overrides**: extend `forms` and `form-submissions` with extra fields, hooks, and access rules using an explicit spread API that guarantees plugin-critical hooks always run.
|
|
24
26
|
|
|
25
27
|
## Quick start
|
|
26
28
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/actions/assertNoBlockCollision.ts
|
|
2
|
+
/**
|
|
3
|
+
* Fail fast at boot when a registered action type collides with a host block slug. Payload resolves
|
|
4
|
+
* blocks globally by slug, so a shared slug makes a saved action document read back with the content
|
|
5
|
+
* block's fields merged in, silently polluting the stored shape and admin tabs. Rather than let that
|
|
6
|
+
* happen quietly, throw a clear error naming the collision so the author renames one side. Action block
|
|
7
|
+
* slugs stay the raw type (renaming them would break every already-stored action), so this detection is
|
|
8
|
+
* the non-breaking guard.
|
|
9
|
+
*/
|
|
10
|
+
const assertNoActionBlockCollision = (config, registry) => {
|
|
11
|
+
const hostSlugs = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const block of Array.isArray(config.blocks) ? config.blocks : []) if (block && typeof block === "object" && typeof block.slug === "string") hostSlugs.add(block.slug);
|
|
13
|
+
for (const type of registry.keys()) if (hostSlugs.has(type)) throw new Error(`@10x-media/form-builder: action type "${type}" collides with an existing Payload block slug "${type}". Rename the action type or the block.`);
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { assertNoActionBlockCollision };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=assertNoBlockCollision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertNoBlockCollision.js","names":[],"sources":["../../src/actions/assertNoBlockCollision.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport type { ActionRegistry } from './registry'\n\n/**\n * Fail fast at boot when a registered action type collides with a host block slug. Payload resolves\n * blocks globally by slug, so a shared slug makes a saved action document read back with the content\n * block's fields merged in, silently polluting the stored shape and admin tabs. Rather than let that\n * happen quietly, throw a clear error naming the collision so the author renames one side. Action block\n * slugs stay the raw type (renaming them would break every already-stored action), so this detection is\n * the non-breaking guard.\n */\nexport const assertNoActionBlockCollision = (config: Config, registry: ActionRegistry): void => {\n\tconst hostSlugs = new Set<string>()\n\tfor (const block of Array.isArray(config.blocks) ? config.blocks : []) {\n\t\tif (block && typeof block === 'object' && typeof block.slug === 'string') {\n\t\t\thostSlugs.add(block.slug)\n\t\t}\n\t}\n\tfor (const type of registry.keys()) {\n\t\tif (hostSlugs.has(type)) {\n\t\t\tthrow new Error(\n\t\t\t\t`@10x-media/form-builder: action type \"${type}\" collides with an existing Payload block slug \"${type}\". Rename the action type or the block.`\n\t\t\t)\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;AAWA,MAAa,gCAAgC,QAAgB,aAAmC;CAC/F,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,SAAS,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,SAAS,CAAC,GACnE,IAAI,SAAS,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,UAC/D,UAAU,IAAI,MAAM,IAAI;CAG1B,KAAK,MAAM,QAAQ,SAAS,KAAK,GAChC,IAAI,UAAU,IAAI,IAAI,GACrB,MAAM,IAAI,MACT,yCAAyC,KAAK,kDAAkD,KAAK,wCACtG;AAGH"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/actions/body/converters.d.ts
|
|
2
|
+
/** Text pipelines a converter can apply to author-typed content. */
|
|
3
|
+
type BodyRender = {
|
|
4
|
+
/** Full text pipeline: HTML-escape, then expand `{{*}}`/`{{*:table}}` and recall tokens with escaped values. */text: (raw: string) => string; /** Recall-token interpolation only, unescaped; for URL-like values that get escaped separately. */
|
|
5
|
+
interpolate: (raw: string) => string;
|
|
6
|
+
};
|
|
7
|
+
type BodyConverterArgs = {
|
|
8
|
+
node: Record<string, unknown>; /** The node's already-serialized children. */
|
|
9
|
+
children: string;
|
|
10
|
+
render: BodyRender;
|
|
11
|
+
};
|
|
12
|
+
/** Serializes one Lexical node type to an HTML string. Keyed by `node.type` in the registry. */
|
|
13
|
+
type BodyConverter = (args: BodyConverterArgs) => string;
|
|
14
|
+
/** Allow http(s)/mailto/tel and scheme-less (relative) URLs; anything else becomes `#`. */
|
|
15
|
+
declare const sanitizeUrl: (url: string) => string;
|
|
16
|
+
/** Default Lexical-to-HTML converter registry; consumers spread overrides per node type. */
|
|
17
|
+
declare const defaultBodyConverters: Record<string, BodyConverter>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { BodyConverter, BodyConverterArgs, BodyRender, defaultBodyConverters, sanitizeUrl };
|
|
20
|
+
//# sourceMappingURL=converters.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { escapeHtml } from "./escapeHtml.js";
|
|
2
|
+
//#region src/actions/body/converters.ts
|
|
3
|
+
const SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
4
|
+
const SAFE_SCHEME = /^(https?|mailto|tel):/i;
|
|
5
|
+
/** Allow http(s)/mailto/tel and scheme-less (relative) URLs; anything else becomes `#`. */
|
|
6
|
+
const sanitizeUrl = (url) => {
|
|
7
|
+
const probe = url.replace(/\s/g, "");
|
|
8
|
+
if (probe === "") return "#";
|
|
9
|
+
if (SCHEME.test(probe)) return SAFE_SCHEME.test(probe) ? url.trim() : "#";
|
|
10
|
+
return url.trim();
|
|
11
|
+
};
|
|
12
|
+
const BOLD = 1;
|
|
13
|
+
const ITALIC = 2;
|
|
14
|
+
const STRIKETHROUGH = 4;
|
|
15
|
+
const UNDERLINE = 8;
|
|
16
|
+
const CODE = 16;
|
|
17
|
+
const text = ({ node, render }) => {
|
|
18
|
+
let html = render.text(typeof node.text === "string" ? node.text : "");
|
|
19
|
+
const format = typeof node.format === "number" ? node.format : 0;
|
|
20
|
+
if (format & CODE) html = `<code>${html}</code>`;
|
|
21
|
+
if (format & BOLD) html = `<strong>${html}</strong>`;
|
|
22
|
+
if (format & ITALIC) html = `<em>${html}</em>`;
|
|
23
|
+
if (format & STRIKETHROUGH) html = `<s>${html}</s>`;
|
|
24
|
+
if (format & UNDERLINE) html = `<u>${html}</u>`;
|
|
25
|
+
return html;
|
|
26
|
+
};
|
|
27
|
+
const link = ({ node, children, render }) => {
|
|
28
|
+
const fields = node.fields ?? {};
|
|
29
|
+
const raw = typeof fields.url === "string" ? fields.url : typeof node.url === "string" ? node.url : "";
|
|
30
|
+
return `<a href="${escapeHtml(sanitizeUrl(render.interpolate(raw)))}"${fields.newTab === true ? " rel=\"noopener noreferrer\" target=\"_blank\"" : ""}>${children}</a>`;
|
|
31
|
+
};
|
|
32
|
+
const HEADING_TAG = /^h[1-6]$/;
|
|
33
|
+
const heading = ({ node, children }) => {
|
|
34
|
+
const tag = typeof node.tag === "string" && HEADING_TAG.test(node.tag) ? node.tag : "h2";
|
|
35
|
+
return `<${tag}>${children}</${tag}>`;
|
|
36
|
+
};
|
|
37
|
+
const list = ({ node, children }) => {
|
|
38
|
+
const tag = node.tag === "ol" || node.listType === "number" ? "ol" : "ul";
|
|
39
|
+
return `<${tag}>${children}</${tag}>`;
|
|
40
|
+
};
|
|
41
|
+
/** Default Lexical-to-HTML converter registry; consumers spread overrides per node type. */
|
|
42
|
+
const defaultBodyConverters = {
|
|
43
|
+
autolink: link,
|
|
44
|
+
heading,
|
|
45
|
+
horizontalrule: () => "<hr />",
|
|
46
|
+
linebreak: () => "<br />",
|
|
47
|
+
link,
|
|
48
|
+
list,
|
|
49
|
+
listitem: ({ children }) => `<li>${children}</li>`,
|
|
50
|
+
paragraph: ({ children }) => `<p>${children}</p>`,
|
|
51
|
+
quote: ({ children }) => `<blockquote>${children}</blockquote>`,
|
|
52
|
+
text
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { defaultBodyConverters, sanitizeUrl };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=converters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converters.js","names":[],"sources":["../../../src/actions/body/converters.ts"],"sourcesContent":["import { escapeHtml } from './escapeHtml'\n\n/** Text pipelines a converter can apply to author-typed content. */\nexport type BodyRender = {\n\t/** Full text pipeline: HTML-escape, then expand `{{*}}`/`{{*:table}}` and recall tokens with escaped values. */\n\ttext: (raw: string) => string\n\t/** Recall-token interpolation only, unescaped; for URL-like values that get escaped separately. */\n\tinterpolate: (raw: string) => string\n}\n\nexport type BodyConverterArgs = {\n\tnode: Record<string, unknown>\n\t/** The node's already-serialized children. */\n\tchildren: string\n\trender: BodyRender\n}\n\n/** Serializes one Lexical node type to an HTML string. Keyed by `node.type` in the registry. */\nexport type BodyConverter = (args: BodyConverterArgs) => string\n\nconst SCHEME = /^[a-z][a-z0-9+.-]*:/i\nconst SAFE_SCHEME = /^(https?|mailto|tel):/i\n\n/** Allow http(s)/mailto/tel and scheme-less (relative) URLs; anything else becomes `#`. */\nexport const sanitizeUrl = (url: string): string => {\n\tconst probe = url.replace(/\\s/g, '')\n\tif (probe === '') {\n\t\treturn '#'\n\t}\n\tif (SCHEME.test(probe)) {\n\t\treturn SAFE_SCHEME.test(probe) ? url.trim() : '#'\n\t}\n\treturn url.trim()\n}\n\nconst BOLD = 1\nconst ITALIC = 2\nconst STRIKETHROUGH = 4\nconst UNDERLINE = 8\nconst CODE = 16\n\nconst text: BodyConverter = ({ node, render }) => {\n\tlet html = render.text(typeof node.text === 'string' ? node.text : '')\n\tconst format = typeof node.format === 'number' ? node.format : 0\n\tif (format & CODE) {\n\t\thtml = `<code>${html}</code>`\n\t}\n\tif (format & BOLD) {\n\t\thtml = `<strong>${html}</strong>`\n\t}\n\tif (format & ITALIC) {\n\t\thtml = `<em>${html}</em>`\n\t}\n\tif (format & STRIKETHROUGH) {\n\t\thtml = `<s>${html}</s>`\n\t}\n\tif (format & UNDERLINE) {\n\t\thtml = `<u>${html}</u>`\n\t}\n\treturn html\n}\n\nconst link: BodyConverter = ({ node, children, render }) => {\n\tconst fields = (node.fields ?? {}) as Record<string, unknown>\n\tconst raw =\n\t\ttypeof fields.url === 'string' ? fields.url : typeof node.url === 'string' ? node.url : ''\n\tconst href = escapeHtml(sanitizeUrl(render.interpolate(raw)))\n\tconst newTab = fields.newTab === true\n\treturn `<a href=\"${href}\"${newTab ? ' rel=\"noopener noreferrer\" target=\"_blank\"' : ''}>${children}</a>`\n}\n\nconst HEADING_TAG = /^h[1-6]$/\n\nconst heading: BodyConverter = ({ node, children }) => {\n\tconst tag = typeof node.tag === 'string' && HEADING_TAG.test(node.tag) ? node.tag : 'h2'\n\treturn `<${tag}>${children}</${tag}>`\n}\n\nconst list: BodyConverter = ({ node, children }) => {\n\tconst tag = node.tag === 'ol' || node.listType === 'number' ? 'ol' : 'ul'\n\treturn `<${tag}>${children}</${tag}>`\n}\n\n/** Default Lexical-to-HTML converter registry; consumers spread overrides per node type. */\nexport const defaultBodyConverters: Record<string, BodyConverter> = {\n\tautolink: link,\n\theading,\n\thorizontalrule: () => '<hr />',\n\tlinebreak: () => '<br />',\n\tlink,\n\tlist,\n\tlistitem: ({ children }) => `<li>${children}</li>`,\n\tparagraph: ({ children }) => `<p>${children}</p>`,\n\tquote: ({ children }) => `<blockquote>${children}</blockquote>`,\n\ttext,\n}\n"],"mappings":";;AAoBA,MAAM,SAAS;AACf,MAAM,cAAc;;AAGpB,MAAa,eAAe,QAAwB;CACnD,MAAM,QAAQ,IAAI,QAAQ,OAAO,EAAE;CACnC,IAAI,UAAU,IACb,OAAO;CAER,IAAI,OAAO,KAAK,KAAK,GACpB,OAAO,YAAY,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI;CAE/C,OAAO,IAAI,KAAK;AACjB;AAEA,MAAM,OAAO;AACb,MAAM,SAAS;AACf,MAAM,gBAAgB;AACtB,MAAM,YAAY;AAClB,MAAM,OAAO;AAEb,MAAM,QAAuB,EAAE,MAAM,aAAa;CACjD,IAAI,OAAO,OAAO,KAAK,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,EAAE;CACrE,MAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;CAC/D,IAAI,SAAS,MACZ,OAAO,SAAS,KAAK;CAEtB,IAAI,SAAS,MACZ,OAAO,WAAW,KAAK;CAExB,IAAI,SAAS,QACZ,OAAO,OAAO,KAAK;CAEpB,IAAI,SAAS,eACZ,OAAO,MAAM,KAAK;CAEnB,IAAI,SAAS,WACZ,OAAO,MAAM,KAAK;CAEnB,OAAO;AACR;AAEA,MAAM,QAAuB,EAAE,MAAM,UAAU,aAAa;CAC3D,MAAM,SAAU,KAAK,UAAU,CAAC;CAChC,MAAM,MACL,OAAO,OAAO,QAAQ,WAAW,OAAO,MAAM,OAAO,KAAK,QAAQ,WAAW,KAAK,MAAM;CAGzF,OAAO,YAFM,WAAW,YAAY,OAAO,YAAY,GAAG,CAAC,CAErC,EAAE,GADT,OAAO,WAAW,OACG,mDAA+C,GAAG,GAAG,SAAS;AACnG;AAEA,MAAM,cAAc;AAEpB,MAAM,WAA0B,EAAE,MAAM,eAAe;CACtD,MAAM,MAAM,OAAO,KAAK,QAAQ,YAAY,YAAY,KAAK,KAAK,GAAG,IAAI,KAAK,MAAM;CACpF,OAAO,IAAI,IAAI,GAAG,SAAS,IAAI,IAAI;AACpC;AAEA,MAAM,QAAuB,EAAE,MAAM,eAAe;CACnD,MAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK,aAAa,WAAW,OAAO;CACrE,OAAO,IAAI,IAAI,GAAG,SAAS,IAAI,IAAI;AACpC;;AAGA,MAAa,wBAAuD;CACnE,UAAU;CACV;CACA,sBAAsB;CACtB,iBAAiB;CACjB;CACA;CACA,WAAW,EAAE,eAAe,OAAO,SAAS;CAC5C,YAAY,EAAE,eAAe,MAAM,SAAS;CAC5C,QAAQ,EAAE,eAAe,eAAe,SAAS;CACjD;AACD"}
|