@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcomeBeforeChange.js","names":[],"sources":["../../src/poll/outcomeBeforeChange.ts"],"sourcesContent":["import { type CollectionBeforeChangeHook, ValidationError } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { pollConfigOf } from '../form/pollState'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nconst WINNING_VALUES_PATH = 'poll.outcome.winningValues'\n\n/** The winning set as a deduped array of non-empty strings, ready to store and to compare. */\nconst normalizeWinning = (value: unknown): string[] => {\n\tif (!Array.isArray(value)) {\n\t\treturn []\n\t}\n\tconst seen = new Set<string>()\n\tfor (const entry of value) {\n\t\tif (typeof entry === 'string' && entry.length > 0) {\n\t\t\tseen.add(entry)\n\t\t}\n\t}\n\treturn [...seen]\n}\n\n/** Set equality on two already-deduped winner arrays: order-insensitive membership. */\nconst sameWinners = (a: string[], b: string[]): boolean => {\n\tif (a.length !== b.length) {\n\t\treturn false\n\t}\n\tconst set = new Set(a)\n\treturn b.every((value) => set.has(value))\n}\n\n/**\n * Owns the poll outcome lifecycle for every create and update, admin and API alike. When the save\n * writes `poll.outcome.winningValues` it validates a set or changed winner set against the poll's\n * effective options (source-resolved or authored, over a merged view of the doc so partial updates\n * validate against stored config) and requires the poll to be enabled; clearing is always allowed.\n * A tie is a set with more than one winner; every value must be a member of the effective options.\n *\n * It is also the single `resolvedAt` stamping point: stamped when the winner set is set or changed,\n * cleared with the set, untouched when the set is unchanged (order-insensitive). Callers can never\n * write `resolvedAt` themselves (field-level access strips it during the beforeValidate traversal,\n * before this hook runs), while values this hook sets survive to the database, so the stamp always\n * reflects the actual transition regardless of the write path.\n */\nexport const pollOutcomeBeforeChange: CollectionBeforeChangeHook = async ({\n\tdata,\n\toriginalDoc,\n\treq,\n}) => {\n\tconst incomingPoll =\n\t\tdata?.poll != null && typeof data.poll === 'object'\n\t\t\t? (data.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst outcome =\n\t\tincomingPoll?.outcome != null && typeof incomingPoll.outcome === 'object'\n\t\t\t? (incomingPoll.outcome as Record<string, unknown>)\n\t\t\t: undefined\n\tif (!outcome || !('winningValues' in outcome)) {\n\t\treturn data\n\t}\n\tconst next = normalizeWinning(outcome.winningValues)\n\toutcome.winningValues = next\n\tconst previous = normalizeWinning(pollConfigOf(originalDoc?.poll)?.outcome?.winningValues)\n\tif (sameWinners(next, previous)) {\n\t\treturn data\n\t}\n\tif (next.length === 0) {\n\t\toutcome.resolvedAt = null\n\t\treturn data\n\t}\n\n\tconst t = asTranslate(req.t)\n\tconst fail = (message: string): never => {\n\t\tthrow new ValidationError(\n\t\t\t{ collection: FORMS_SLUG, errors: [{ path: WINNING_VALUES_PATH, message }] },\n\t\t\treq.t\n\t\t)\n\t}\n\tconst originalPoll =\n\t\toriginalDoc?.poll != null && typeof originalDoc.poll === 'object'\n\t\t\t? (originalDoc.poll as Record<string, unknown>)\n\t\t\t: undefined\n\tconst mergedPoll = { ...(originalPoll ?? {}), ...incomingPoll }\n\t// The poll-enable flag lives at the document root now; merge the incoming partial over the stored\n\t// doc so a partial update that omits it still validates against the persisted value.\n\tconst mergedPollEnabled = { ...(originalDoc ?? {}), ...data }.pollEnabled === true\n\tif (!mergedPollEnabled) {\n\t\tfail(t(keys.validationWinningValueDisabled))\n\t}\n\tconst id = (originalDoc?.id ?? data?.id) as number | string | undefined\n\tconst form = {\n\t\tid: id ?? '',\n\t\ttitle:\n\t\t\ttypeof data?.title === 'string' ? data.title : (originalDoc?.title as string | undefined),\n\t\tpollEnabled: mergedPollEnabled,\n\t\tpoll: mergedPoll,\n\t\tfields: data?.fields ?? originalDoc?.fields,\n\t}\n\tlet options: PollOption[]\n\ttry {\n\t\t// Without a doc id (create) the per-request options cache would key on '', so skip it.\n\t\toptions = await resolveEffectivePollOptions({\n\t\t\tpayload: req.payload,\n\t\t\treq: id != null ? req : undefined,\n\t\t\tform,\n\t\t})\n\t} catch {\n\t\treturn fail(t(keys.pollOptionsUnavailable))\n\t}\n\tconst allowed = new Set(options.map((option) => option.value))\n\tif (!next.every((value) => allowed.has(value))) {\n\t\tfail(t(keys.validationWinningValueUnknown))\n\t}\n\toutcome.resolvedAt = new Date().toISOString()\n\treturn data\n}\n"],"mappings":";;;;;;;AAQA,MAAM,sBAAsB;;AAG5B,MAAM,oBAAoB,UAA6B;CACtD,IAAI,CAAC,MAAM,QAAQ,KAAK,GACvB,OAAO,CAAC;CAET,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OACnB,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC/C,KAAK,IAAI,KAAK;CAGhB,OAAO,CAAC,GAAG,IAAI;AAChB;;AAGA,MAAM,eAAe,GAAa,MAAyB;CAC1D,IAAI,EAAE,WAAW,EAAE,QAClB,OAAO;CAER,MAAM,MAAM,IAAI,IAAI,CAAC;CACrB,OAAO,EAAE,OAAO,UAAU,IAAI,IAAI,KAAK,CAAC;AACzC;;;;;;;;;;;;;;AAeA,MAAa,0BAAsD,OAAO,EACzE,MACA,aACA,UACK;CACL,MAAM,eACL,MAAM,QAAQ,QAAQ,OAAO,KAAK,SAAS,WACvC,KAAK,OACN,KAAA;CACJ,MAAM,UACL,cAAc,WAAW,QAAQ,OAAO,aAAa,YAAY,WAC7D,aAAa,UACd,KAAA;CACJ,IAAI,CAAC,WAAW,EAAE,mBAAmB,UACpC,OAAO;CAER,MAAM,OAAO,iBAAiB,QAAQ,aAAa;CACnD,QAAQ,gBAAgB;CAExB,IAAI,YAAY,MADC,iBAAiB,aAAa,aAAa,IAAI,GAAG,SAAS,aAC/C,CAAC,GAC7B,OAAO;CAER,IAAI,KAAK,WAAW,GAAG;EACtB,QAAQ,aAAa;EACrB,OAAO;CACR;CAEA,MAAM,IAAI,YAAY,IAAI,CAAC;CAC3B,MAAM,QAAQ,YAA2B;EACxC,MAAM,IAAI,gBACT;GAAE,YAAY;GAAY,QAAQ,CAAC;IAAE,MAAM;IAAqB;GAAQ,CAAC;EAAE,GAC3E,IAAI,CACL;CACD;CAKA,MAAM,aAAa;EAAE,IAHpB,aAAa,QAAQ,QAAQ,OAAO,YAAY,SAAS,WACrD,YAAY,OACb,KAAA,MACqC,CAAC;EAAI,GAAG;CAAa;CAG9D,MAAM,oBAAoB;EAAE,GAAI,eAAe,CAAC;EAAI,GAAG;CAAK,EAAE,gBAAgB;CAC9E,IAAI,CAAC,mBACJ,KAAK,EAAE,KAAK,8BAA8B,CAAC;CAE5C,MAAM,KAAM,aAAa,MAAM,MAAM;CACrC,MAAM,OAAO;EACZ,IAAI,MAAM;EACV,OACC,OAAO,MAAM,UAAU,WAAW,KAAK,QAAS,aAAa;EAC9D,aAAa;EACb,MAAM;EACN,QAAQ,MAAM,UAAU,aAAa;CACtC;CACA,IAAI;CACJ,IAAI;EAEH,UAAU,MAAM,4BAA4B;GAC3C,SAAS,IAAI;GACb,KAAK,MAAM,OAAO,MAAM,KAAA;GACxB;EACD,CAAC;CACF,QAAQ;EACP,OAAO,KAAK,EAAE,KAAK,sBAAsB,CAAC;CAC3C;CACA,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CAC7D,IAAI,CAAC,KAAK,OAAO,UAAU,QAAQ,IAAI,KAAK,CAAC,GAC5C,KAAK,EAAE,KAAK,6BAA6B,CAAC;CAE3C,QAAQ,8BAAa,IAAI,KAAK,GAAE,YAAY;CAC5C,OAAO;AACR"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Field } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/outcomeFields.d.ts
|
|
4
|
+
/** The default poll-outcome fields, keyed by field. What the `poll.outcomeFields` seam receives. */
|
|
5
|
+
type DefaultOutcomeFields = {
|
|
6
|
+
winningValues: Field;
|
|
7
|
+
resolvedAt: Field;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Composes the forms poll `outcome` group. Receives the two default fields; the returned array
|
|
11
|
+
* becomes the group's fields verbatim, so swapping `winningValues` for a host component (e.g. a
|
|
12
|
+
* relationship picker over the voteable records), reordering, or adding a field is explicit.
|
|
13
|
+
* `pollOutcomeBeforeChange` validates every stored winning value against the poll's effective
|
|
14
|
+
* options server-side regardless of which component renders it, so a swap cannot bypass the
|
|
15
|
+
* membership check.
|
|
16
|
+
*/
|
|
17
|
+
type OutcomeFieldsOverride = (args: {
|
|
18
|
+
defaultFields: DefaultOutcomeFields;
|
|
19
|
+
}) => Field[];
|
|
20
|
+
/**
|
|
21
|
+
* The winners field (`poll.outcome.winningValues`): a `hasMany` text field whose default admin
|
|
22
|
+
* component is the `poll-options` endpoint select rendered as a multi-select, so a tie records
|
|
23
|
+
* every winner. Each stored value is matched against the poll's effective options by
|
|
24
|
+
* `pollOutcomeBeforeChange`; a host may swap the component, but the stored contract stays an array
|
|
25
|
+
* of `PollOption.value` strings so membership validation keeps working.
|
|
26
|
+
*/
|
|
27
|
+
declare const buildWinningValuesField: () => Field;
|
|
28
|
+
/**
|
|
29
|
+
* The resolved-at stamp field (`poll.outcome.resolvedAt`): read-only and locked against every
|
|
30
|
+
* caller write via field-level access, so only `pollOutcomeBeforeChange` (which runs after access
|
|
31
|
+
* filtering) can stamp it. Kept in the defaults so a host override that preserves it keeps the lock.
|
|
32
|
+
*/
|
|
33
|
+
declare const buildResolvedAtField: () => Field;
|
|
34
|
+
/** Both defaults, exactly what the `poll.outcomeFields` seam is handed. */
|
|
35
|
+
declare const buildDefaultOutcomeFields: () => DefaultOutcomeFields;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { DefaultOutcomeFields, OutcomeFieldsOverride, buildDefaultOutcomeFields, buildResolvedAtField, buildWinningValuesField };
|
|
38
|
+
//# sourceMappingURL=outcomeFields.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { labelForKey } from "../translations/server.js";
|
|
3
|
+
//#region src/poll/outcomeFields.ts
|
|
4
|
+
const ENDPOINT_OPTIONS_SELECT_REF = "@10x-media/form-builder/client#EndpointOptionsSelect";
|
|
5
|
+
/**
|
|
6
|
+
* The winners field (`poll.outcome.winningValues`): a `hasMany` text field whose default admin
|
|
7
|
+
* component is the `poll-options` endpoint select rendered as a multi-select, so a tie records
|
|
8
|
+
* every winner. Each stored value is matched against the poll's effective options by
|
|
9
|
+
* `pollOutcomeBeforeChange`; a host may swap the component, but the stored contract stays an array
|
|
10
|
+
* of `PollOption.value` strings so membership validation keeps working.
|
|
11
|
+
*/
|
|
12
|
+
const buildWinningValuesField = () => ({
|
|
13
|
+
name: "winningValues",
|
|
14
|
+
type: "text",
|
|
15
|
+
hasMany: true,
|
|
16
|
+
label: labelForKey(keys.pollWinningValue),
|
|
17
|
+
admin: {
|
|
18
|
+
condition: (data) => data?.poll?.type === "manual",
|
|
19
|
+
components: { Field: {
|
|
20
|
+
path: ENDPOINT_OPTIONS_SELECT_REF,
|
|
21
|
+
clientProps: {
|
|
22
|
+
endpoint: "poll-options",
|
|
23
|
+
descriptionKey: keys.pollWinningValueDescription,
|
|
24
|
+
isMulti: true
|
|
25
|
+
}
|
|
26
|
+
} }
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* The resolved-at stamp field (`poll.outcome.resolvedAt`): read-only and locked against every
|
|
31
|
+
* caller write via field-level access, so only `pollOutcomeBeforeChange` (which runs after access
|
|
32
|
+
* filtering) can stamp it. Kept in the defaults so a host override that preserves it keeps the lock.
|
|
33
|
+
*/
|
|
34
|
+
const buildResolvedAtField = () => ({
|
|
35
|
+
name: "resolvedAt",
|
|
36
|
+
type: "date",
|
|
37
|
+
label: labelForKey(keys.pollResolvedAt),
|
|
38
|
+
admin: { readOnly: true },
|
|
39
|
+
access: {
|
|
40
|
+
create: () => false,
|
|
41
|
+
update: () => false
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
/** Both defaults, exactly what the `poll.outcomeFields` seam is handed. */
|
|
45
|
+
const buildDefaultOutcomeFields = () => ({
|
|
46
|
+
winningValues: buildWinningValuesField(),
|
|
47
|
+
resolvedAt: buildResolvedAtField()
|
|
48
|
+
});
|
|
49
|
+
//#endregion
|
|
50
|
+
export { buildDefaultOutcomeFields, buildResolvedAtField, buildWinningValuesField };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=outcomeFields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcomeFields.js","names":[],"sources":["../../src/poll/outcomeFields.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\n\nconst ENDPOINT_OPTIONS_SELECT_REF = '@10x-media/form-builder/client#EndpointOptionsSelect'\n\n/** The default poll-outcome fields, keyed by field. What the `poll.outcomeFields` seam receives. */\nexport type DefaultOutcomeFields = {\n\twinningValues: Field\n\tresolvedAt: Field\n}\n\n/**\n * Composes the forms poll `outcome` group. Receives the two default fields; the returned array\n * becomes the group's fields verbatim, so swapping `winningValues` for a host component (e.g. a\n * relationship picker over the voteable records), reordering, or adding a field is explicit.\n * `pollOutcomeBeforeChange` validates every stored winning value against the poll's effective\n * options server-side regardless of which component renders it, so a swap cannot bypass the\n * membership check.\n */\nexport type OutcomeFieldsOverride = (args: { defaultFields: DefaultOutcomeFields }) => Field[]\n\n/**\n * The winners field (`poll.outcome.winningValues`): a `hasMany` text field whose default admin\n * component is the `poll-options` endpoint select rendered as a multi-select, so a tie records\n * every winner. Each stored value is matched against the poll's effective options by\n * `pollOutcomeBeforeChange`; a host may swap the component, but the stored contract stays an array\n * of `PollOption.value` strings so membership validation keeps working.\n */\nexport const buildWinningValuesField = (): Field => ({\n\tname: 'winningValues',\n\ttype: 'text',\n\thasMany: true,\n\tlabel: labelForKey(keys.pollWinningValue),\n\tadmin: {\n\t\t// Only a `manual` poll has a winner to hand-pick; `mostVoted` and `source` auto-resolve on close,\n\t\t// so a hand-picked value would just be discarded. Show the field for `manual` alone. A stored value\n\t\t// survives a save because Payload keeps a condition-hidden field's value in form state and resubmits\n\t\t// it, and the outcome hook's early-return leaves winningValues untouched when a save omits it. The\n\t\t// first Condition arg is the whole document (the field sits at poll.outcome.winningValues), so read\n\t\t// poll.type off it.\n\t\tcondition: (data) => (data as { poll?: { type?: string } })?.poll?.type === 'manual',\n\t\tcomponents: {\n\t\t\tField: {\n\t\t\t\tpath: ENDPOINT_OPTIONS_SELECT_REF,\n\t\t\t\tclientProps: {\n\t\t\t\t\tendpoint: 'poll-options',\n\t\t\t\t\tdescriptionKey: keys.pollWinningValueDescription,\n\t\t\t\t\tisMulti: true,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n})\n\n/**\n * The resolved-at stamp field (`poll.outcome.resolvedAt`): read-only and locked against every\n * caller write via field-level access, so only `pollOutcomeBeforeChange` (which runs after access\n * filtering) can stamp it. Kept in the defaults so a host override that preserves it keeps the lock.\n */\nexport const buildResolvedAtField = (): Field => ({\n\tname: 'resolvedAt',\n\ttype: 'date',\n\tlabel: labelForKey(keys.pollResolvedAt),\n\tadmin: { readOnly: true },\n\taccess: { create: () => false, update: () => false },\n})\n\n/** Both defaults, exactly what the `poll.outcomeFields` seam is handed. */\nexport const buildDefaultOutcomeFields = (): DefaultOutcomeFields => ({\n\twinningValues: buildWinningValuesField(),\n\tresolvedAt: buildResolvedAtField(),\n})\n"],"mappings":";;;AAIA,MAAM,8BAA8B;;;;;;;;AAyBpC,MAAa,iCAAwC;CACpD,MAAM;CACN,MAAM;CACN,SAAS;CACT,OAAO,YAAY,KAAK,gBAAgB;CACxC,OAAO;EAON,YAAY,SAAU,MAAuC,MAAM,SAAS;EAC5E,YAAY,EACX,OAAO;GACN,MAAM;GACN,aAAa;IACZ,UAAU;IACV,gBAAgB,KAAK;IACrB,SAAS;GACV;EACD,EACD;CACD;AACD;;;;;;AAOA,MAAa,8BAAqC;CACjD,MAAM;CACN,MAAM;CACN,OAAO,YAAY,KAAK,cAAc;CACtC,OAAO,EAAE,UAAU,KAAK;CACxB,QAAQ;EAAE,cAAc;EAAO,cAAc;CAAM;AACpD;;AAGA,MAAa,mCAAyD;CACrE,eAAe,wBAAwB;CACvC,YAAY,qBAAqB;AAClC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PollOutcomeStrategy } from "./definePollType.js";
|
|
2
|
+
import { Payload } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/poll/pollTypeRegistry.d.ts
|
|
5
|
+
type PollTypeRegistry = Map<string, PollOutcomeStrategy>;
|
|
6
|
+
/**
|
|
7
|
+
* Host-supplied outcome strategies: a keyed record (the key forces `type`, like the field-type
|
|
8
|
+
* registry) or a plain array. There are no boolean toggles because the three built-ins are always
|
|
9
|
+
* registered; a host entry with a built-in's `type` replaces it.
|
|
10
|
+
*/
|
|
11
|
+
type PollTypesConfig = Record<string, PollOutcomeStrategy> | PollOutcomeStrategy[];
|
|
12
|
+
/** The default: an admin records `winningValues` by hand, so there is nothing to auto-resolve. */
|
|
13
|
+
declare const manualStrategy: PollOutcomeStrategy;
|
|
14
|
+
/**
|
|
15
|
+
* Delegate to the poll's configured option source `resolveOutcome` (the same domain resolver that
|
|
16
|
+
* feeds explicit `resolvePollOutcome` auto mode before this task shipped). Returns `undefined` when
|
|
17
|
+
* the poll has no source, the source is unregistered, or it does not implement `resolveOutcome`, so a
|
|
18
|
+
* misconfigured poll never throws from the close job or the read fallback; a source that resolves to
|
|
19
|
+
* no decision (its `resolveOutcome` returns `undefined`) also yields `undefined`.
|
|
20
|
+
*/
|
|
21
|
+
declare const sourceStrategy: PollOutcomeStrategy;
|
|
22
|
+
/**
|
|
23
|
+
* The active poll-type registry: the three always-registered built-ins (`manual`, `mostVoted`,
|
|
24
|
+
* `source`) plus any host strategies from the plugin `poll.types` option. A host entry keyed by (or
|
|
25
|
+
* carrying a `type` of) a built-in slug replaces that built-in. Mirrors the field-type registry: a
|
|
26
|
+
* record key forces the strategy's `type` so a keyed override cannot rename the slot.
|
|
27
|
+
*/
|
|
28
|
+
declare const resolvePollTypes: (config?: PollTypesConfig) => PollTypeRegistry;
|
|
29
|
+
/** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */
|
|
30
|
+
declare const stashPollTypes: (custom: Record<string, unknown> | undefined, registry: PollTypeRegistry) => Record<string, unknown>;
|
|
31
|
+
declare const pollTypesOf: (payload: Payload) => PollTypeRegistry;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PollTypeRegistry, PollTypesConfig, manualStrategy, pollTypesOf, resolvePollTypes, sourceStrategy, stashPollTypes };
|
|
34
|
+
//# sourceMappingURL=pollTypeRegistry.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
3
|
+
import { customStateOf, stashCustomState } from "../plugin/customState.js";
|
|
4
|
+
import { pollOptionSourcesOf } from "./resolvePollOptions.js";
|
|
5
|
+
import { definePollType } from "./definePollType.js";
|
|
6
|
+
import { mostVotedStrategy } from "./mostVoted.js";
|
|
7
|
+
//#region src/poll/pollTypeRegistry.ts
|
|
8
|
+
/** The default: an admin records `winningValues` by hand, so there is nothing to auto-resolve. */
|
|
9
|
+
const manualStrategy = definePollType({
|
|
10
|
+
type: "manual",
|
|
11
|
+
label: keys.pollTypeManual,
|
|
12
|
+
resolveOutcome: () => void 0
|
|
13
|
+
});
|
|
14
|
+
/** One winner set as a deduped array of non-empty strings, or `undefined` when the source yields none. */
|
|
15
|
+
const normalizeSourceOutcome = (value) => {
|
|
16
|
+
if (value === void 0) return;
|
|
17
|
+
const cleaned = [...new Set((Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === "string" && entry.length > 0))];
|
|
18
|
+
return cleaned.length > 0 ? cleaned : void 0;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Delegate to the poll's configured option source `resolveOutcome` (the same domain resolver that
|
|
22
|
+
* feeds explicit `resolvePollOutcome` auto mode before this task shipped). Returns `undefined` when
|
|
23
|
+
* the poll has no source, the source is unregistered, or it does not implement `resolveOutcome`, so a
|
|
24
|
+
* misconfigured poll never throws from the close job or the read fallback; a source that resolves to
|
|
25
|
+
* no decision (its `resolveOutcome` returns `undefined`) also yields `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
const sourceStrategy = definePollType({
|
|
28
|
+
type: "source",
|
|
29
|
+
label: keys.pollTypeSource,
|
|
30
|
+
resolveOutcome: async ({ payload, req, form, poll }) => {
|
|
31
|
+
const optionSource = typeof poll.optionSource === "string" && poll.optionSource.length > 0 ? poll.optionSource : void 0;
|
|
32
|
+
if (!optionSource) return;
|
|
33
|
+
const source = pollOptionSourcesOf(payload).get(optionSource);
|
|
34
|
+
if (!source?.resolveOutcome) return;
|
|
35
|
+
const config = poll.sourceConfig != null && typeof poll.sourceConfig === "object" ? poll.sourceConfig : {};
|
|
36
|
+
return normalizeSourceOutcome(await source.resolveOutcome({
|
|
37
|
+
config,
|
|
38
|
+
form: {
|
|
39
|
+
id: form.id,
|
|
40
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
41
|
+
},
|
|
42
|
+
payload,
|
|
43
|
+
req
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* The active poll-type registry: the three always-registered built-ins (`manual`, `mostVoted`,
|
|
49
|
+
* `source`) plus any host strategies from the plugin `poll.types` option. A host entry keyed by (or
|
|
50
|
+
* carrying a `type` of) a built-in slug replaces that built-in. Mirrors the field-type registry: a
|
|
51
|
+
* record key forces the strategy's `type` so a keyed override cannot rename the slot.
|
|
52
|
+
*/
|
|
53
|
+
const resolvePollTypes = (config) => {
|
|
54
|
+
const registry = new Map([
|
|
55
|
+
[manualStrategy.type, manualStrategy],
|
|
56
|
+
[mostVotedStrategy.type, mostVotedStrategy],
|
|
57
|
+
[sourceStrategy.type, sourceStrategy]
|
|
58
|
+
]);
|
|
59
|
+
if (Array.isArray(config)) for (const strategy of config) registry.set(strategy.type, strategy);
|
|
60
|
+
else if (config) applyRegistryConfig(registry, config);
|
|
61
|
+
return registry;
|
|
62
|
+
};
|
|
63
|
+
/** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */
|
|
64
|
+
const stashPollTypes = (custom, registry) => stashCustomState(custom, { pollTypes: registry });
|
|
65
|
+
const pollTypesOf = (payload) => customStateOf(payload).pollTypes ?? /* @__PURE__ */ new Map();
|
|
66
|
+
//#endregion
|
|
67
|
+
export { manualStrategy, pollTypesOf, resolvePollTypes, sourceStrategy, stashPollTypes };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=pollTypeRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pollTypeRegistry.js","names":[],"sources":["../../src/poll/pollTypeRegistry.ts"],"sourcesContent":["import type { Payload } from 'payload'\nimport { applyRegistryConfig } from '../plugin/applyRegistryConfig'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\nimport { keys } from '../translations/keys'\nimport { definePollType, type PollOutcomeStrategy } from './definePollType'\nimport { mostVotedStrategy } from './mostVoted'\nimport { pollOptionSourcesOf } from './resolvePollOptions'\n\nexport type PollTypeRegistry = Map<string, PollOutcomeStrategy>\n\n/**\n * Host-supplied outcome strategies: a keyed record (the key forces `type`, like the field-type\n * registry) or a plain array. There are no boolean toggles because the three built-ins are always\n * registered; a host entry with a built-in's `type` replaces it.\n */\nexport type PollTypesConfig = Record<string, PollOutcomeStrategy> | PollOutcomeStrategy[]\n\n/** The default: an admin records `winningValues` by hand, so there is nothing to auto-resolve. */\nexport const manualStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'manual',\n\tlabel: keys.pollTypeManual,\n\tresolveOutcome: () => undefined,\n})\n\n/** One winner set as a deduped array of non-empty strings, or `undefined` when the source yields none. */\nconst normalizeSourceOutcome = (value: string | string[] | undefined): string[] | undefined => {\n\tif (value === undefined) {\n\t\treturn undefined\n\t}\n\tconst cleaned = [\n\t\t...new Set(\n\t\t\t(Array.isArray(value) ? value : [value]).filter(\n\t\t\t\t(entry): entry is string => typeof entry === 'string' && entry.length > 0\n\t\t\t)\n\t\t),\n\t]\n\treturn cleaned.length > 0 ? cleaned : undefined\n}\n\n/**\n * Delegate to the poll's configured option source `resolveOutcome` (the same domain resolver that\n * feeds explicit `resolvePollOutcome` auto mode before this task shipped). Returns `undefined` when\n * the poll has no source, the source is unregistered, or it does not implement `resolveOutcome`, so a\n * misconfigured poll never throws from the close job or the read fallback; a source that resolves to\n * no decision (its `resolveOutcome` returns `undefined`) also yields `undefined`.\n */\nexport const sourceStrategy: PollOutcomeStrategy = definePollType({\n\ttype: 'source',\n\tlabel: keys.pollTypeSource,\n\tresolveOutcome: async ({ payload, req, form, poll }) => {\n\t\tconst optionSource =\n\t\t\ttypeof poll.optionSource === 'string' && poll.optionSource.length > 0\n\t\t\t\t? poll.optionSource\n\t\t\t\t: undefined\n\t\tif (!optionSource) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst source = pollOptionSourcesOf(payload).get(optionSource)\n\t\tif (!source?.resolveOutcome) {\n\t\t\treturn undefined\n\t\t}\n\t\tconst config =\n\t\t\tpoll.sourceConfig != null && typeof poll.sourceConfig === 'object'\n\t\t\t\t? (poll.sourceConfig as Record<string, unknown>)\n\t\t\t\t: {}\n\t\treturn normalizeSourceOutcome(\n\t\t\tawait source.resolveOutcome({\n\t\t\t\tconfig,\n\t\t\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t})\n\t\t)\n\t},\n})\n\n/**\n * The active poll-type registry: the three always-registered built-ins (`manual`, `mostVoted`,\n * `source`) plus any host strategies from the plugin `poll.types` option. A host entry keyed by (or\n * carrying a `type` of) a built-in slug replaces that built-in. Mirrors the field-type registry: a\n * record key forces the strategy's `type` so a keyed override cannot rename the slot.\n */\nexport const resolvePollTypes = (config?: PollTypesConfig): PollTypeRegistry => {\n\tconst registry: PollTypeRegistry = new Map([\n\t\t[manualStrategy.type, manualStrategy],\n\t\t[mostVotedStrategy.type, mostVotedStrategy],\n\t\t[sourceStrategy.type, sourceStrategy],\n\t])\n\tif (Array.isArray(config)) {\n\t\tfor (const strategy of config) {\n\t\t\tregistry.set(strategy.type, strategy)\n\t\t}\n\t} else if (config) {\n\t\t// A keyed record shares the sibling registries' merge (the key forces `type`). There are no\n\t\t// boolean toggles here because the three built-ins are always registered, so those branches\n\t\t// are inert for this always-strategy config.\n\t\tapplyRegistryConfig(registry, config)\n\t}\n\treturn registry\n}\n\ntype PollTypesCustomState = { pollTypes: PollTypeRegistry }\n\n/** Store the resolved registry on the config so `resolvePollOutcome` can reach it via `payload.config`. */\nexport const stashPollTypes = (\n\tcustom: Record<string, unknown> | undefined,\n\tregistry: PollTypeRegistry\n): Record<string, unknown> =>\n\tstashCustomState<PollTypesCustomState>(custom, { pollTypes: registry })\n\nexport const pollTypesOf = (payload: Payload): PollTypeRegistry =>\n\tcustomStateOf<PollTypesCustomState>(payload).pollTypes ?? new Map()\n"],"mappings":";;;;;;;;AAkBA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,sBAAsB,KAAA;AACvB,CAAC;;AAGD,MAAM,0BAA0B,UAA+D;CAC9F,IAAI,UAAU,KAAA,GACb;CAED,MAAM,UAAU,CACf,GAAG,IAAI,KACL,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAAG,QACvC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CACzE,CACD,CACD;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACvC;;;;;;;;AASA,MAAa,iBAAsC,eAAe;CACjE,MAAM;CACN,OAAO,KAAK;CACZ,gBAAgB,OAAO,EAAE,SAAS,KAAK,MAAM,WAAW;EACvD,MAAM,eACL,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IACjE,KAAK,eACL,KAAA;EACJ,IAAI,CAAC,cACJ;EAED,MAAM,SAAS,oBAAoB,OAAO,EAAE,IAAI,YAAY;EAC5D,IAAI,CAAC,QAAQ,gBACZ;EAED,MAAM,SACL,KAAK,gBAAgB,QAAQ,OAAO,KAAK,iBAAiB,WACtD,KAAK,eACN,CAAC;EACL,OAAO,uBACN,MAAM,OAAO,eAAe;GAC3B;GACA,MAAM;IAAE,IAAI,KAAK;IAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;GAAU;GACpF;GACA;EACD,CAAC,CACF;CACD;AACD,CAAC;;;;;;;AAQD,MAAa,oBAAoB,WAA+C;CAC/E,MAAM,WAA6B,IAAI,IAAI;EAC1C,CAAC,eAAe,MAAM,cAAc;EACpC,CAAC,kBAAkB,MAAM,iBAAiB;EAC1C,CAAC,eAAe,MAAM,cAAc;CACrC,CAAC;CACD,IAAI,MAAM,QAAQ,MAAM,GACvB,KAAK,MAAM,YAAY,QACtB,SAAS,IAAI,SAAS,MAAM,QAAQ;MAE/B,IAAI,QAIV,oBAAoB,UAAU,MAAM;CAErC,OAAO;AACR;;AAKA,MAAa,kBACZ,QACA,aAEA,iBAAuC,QAAQ,EAAE,WAAW,SAAS,CAAC;AAEvE,MAAa,eAAe,YAC3B,cAAoC,OAAO,EAAE,6BAAa,IAAI,IAAI"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AnyPollOptionSource } from "./definePollOptionSource.js";
|
|
2
|
+
import { RegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
3
|
+
|
|
4
|
+
//#region src/poll/registry.d.ts
|
|
5
|
+
type PollOptionSourceRegistry = Map<string, AnyPollOptionSource>;
|
|
6
|
+
/** `false` removes a source, `true` keeps it (no-op: there are no built-ins), a definition adds or replaces one. */
|
|
7
|
+
type PollOptionSourceOption = boolean | AnyPollOptionSource;
|
|
8
|
+
type PollOptionSourcesConfig = RegistryConfig<AnyPollOptionSource>;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the active poll option-source registry from a consumer map. Unlike the consent and
|
|
11
|
+
* action registries there are no built-in defaults: the registry starts empty and only host
|
|
12
|
+
* definitions populate it, so `true`/`false` entries are accepted but change nothing.
|
|
13
|
+
*/
|
|
14
|
+
declare const resolvePollOptionSources: (config?: PollOptionSourcesConfig) => PollOptionSourceRegistry;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PollOptionSourceOption, PollOptionSourceRegistry, PollOptionSourcesConfig, resolvePollOptionSources };
|
|
17
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { applyRegistryConfig } from "../plugin/applyRegistryConfig.js";
|
|
2
|
+
//#region src/poll/registry.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the active poll option-source registry from a consumer map. Unlike the consent and
|
|
5
|
+
* action registries there are no built-in defaults: the registry starts empty and only host
|
|
6
|
+
* definitions populate it, so `true`/`false` entries are accepted but change nothing.
|
|
7
|
+
*/
|
|
8
|
+
const resolvePollOptionSources = (config = {}) => applyRegistryConfig(/* @__PURE__ */ new Map(), config);
|
|
9
|
+
//#endregion
|
|
10
|
+
export { resolvePollOptionSources };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../src/poll/registry.ts"],"sourcesContent":["import { applyRegistryConfig, type RegistryConfig } from '../plugin/applyRegistryConfig'\nimport type { AnyPollOptionSource } from './definePollOptionSource'\n\nexport type PollOptionSourceRegistry = Map<string, AnyPollOptionSource>\n\n/** `false` removes a source, `true` keeps it (no-op: there are no built-ins), a definition adds or replaces one. */\nexport type PollOptionSourceOption = boolean | AnyPollOptionSource\n\nexport type PollOptionSourcesConfig = RegistryConfig<AnyPollOptionSource>\n\n/**\n * Resolve the active poll option-source registry from a consumer map. Unlike the consent and\n * action registries there are no built-in defaults: the registry starts empty and only host\n * definitions populate it, so `true`/`false` entries are accepted but change nothing.\n */\nexport const resolvePollOptionSources = (\n\tconfig: PollOptionSourcesConfig = {}\n): PollOptionSourceRegistry => applyRegistryConfig(new Map<string, AnyPollOptionSource>(), config)\n"],"mappings":";;;;;;;AAeA,MAAa,4BACZ,SAAkC,CAAC,MACL,oCAAoB,IAAI,IAAiC,GAAG,MAAM"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { keys } from "../translations/keys.js";
|
|
2
|
+
import { asTranslate } from "../translations/server.js";
|
|
3
|
+
import { isPollClosed, pollConfigOf } from "../form/pollState.js";
|
|
4
|
+
import { resolvePollOutcome } from "./resolvePollOutcome.js";
|
|
5
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
6
|
+
//#region src/poll/resolvePollCloseRequest.ts
|
|
7
|
+
/** The recorded winning set as non-empty strings; empty means no winner has been set yet. */
|
|
8
|
+
const winnersOf = (poll) => {
|
|
9
|
+
const winners = poll.outcome?.winningValues;
|
|
10
|
+
if (!Array.isArray(winners)) return [];
|
|
11
|
+
return winners.filter((value) => typeof value === "string" && value.length > 0);
|
|
12
|
+
};
|
|
13
|
+
/** The selected outcome strategy slug; an absent or empty `type` is `manual`. */
|
|
14
|
+
const strategyOf = (poll) => typeof poll.type === "string" && poll.type.length > 0 ? poll.type : "manual";
|
|
15
|
+
/**
|
|
16
|
+
* Decide what closing a poll now should do, purely from a loaded form so the branch logic is testable
|
|
17
|
+
* without a boot. `manual` requires a winner to already be recorded (closing only stamps the time);
|
|
18
|
+
* `mostVoted`, `source`, and any host strategy auto-resolve on close. Refuses a non-poll form and an
|
|
19
|
+
* already-closed poll (idempotency), so the request helper performs no write unless this returns `ok`.
|
|
20
|
+
*/
|
|
21
|
+
const planPollClose = (form) => {
|
|
22
|
+
const poll = pollConfigOf(form.poll);
|
|
23
|
+
if (form.pollEnabled !== true || !poll) return {
|
|
24
|
+
ok: false,
|
|
25
|
+
reason: "not-poll"
|
|
26
|
+
};
|
|
27
|
+
if (isPollClosed(poll)) return {
|
|
28
|
+
ok: false,
|
|
29
|
+
reason: "already-closed"
|
|
30
|
+
};
|
|
31
|
+
if (strategyOf(poll) === "manual" && winnersOf(poll).length === 0) return {
|
|
32
|
+
ok: false,
|
|
33
|
+
reason: "manual-no-winner"
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
resolveOutcome: strategyOf(poll) !== "manual"
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Authorize and perform a manual poll close backing `POST /:id/close`. Only an authenticated caller
|
|
42
|
+
* may close (anonymous is always 403, mirroring the other resolve* helpers); the route is a trusted
|
|
43
|
+
* admin action, so the load and write run with `overrideAccess`. Sets `closesAt` to now through
|
|
44
|
+
* `payload.update` so the collection hooks run, then for a non-manual strategy resolves the winners via
|
|
45
|
+
* `resolvePollOutcome` (auto mode; a non-decidable poll simply records none and the read fallback heals
|
|
46
|
+
* it later). A `manual` poll with no winner recorded is refused (400) so closing never strands a poll
|
|
47
|
+
* with no result. Idempotent: an already-closed poll returns 400 rather than re-closing.
|
|
48
|
+
*/
|
|
49
|
+
const resolvePollCloseRequest = async (args) => {
|
|
50
|
+
const { payload, formId, isAuthed, pollVotesEnabled, req } = args;
|
|
51
|
+
if (!isAuthed) return {
|
|
52
|
+
status: 403,
|
|
53
|
+
body: { errors: [{ message: "Forbidden" }] }
|
|
54
|
+
};
|
|
55
|
+
if (formId == null) return {
|
|
56
|
+
status: 400,
|
|
57
|
+
body: { errors: [{ message: "Missing form id" }] }
|
|
58
|
+
};
|
|
59
|
+
const form = await payload.findByID({
|
|
60
|
+
collection: FORMS_SLUG,
|
|
61
|
+
id: formId,
|
|
62
|
+
depth: 0,
|
|
63
|
+
overrideAccess: true,
|
|
64
|
+
req
|
|
65
|
+
}).catch(() => null);
|
|
66
|
+
if (!form) return {
|
|
67
|
+
status: 404,
|
|
68
|
+
body: { errors: [{ message: "Not found" }] }
|
|
69
|
+
};
|
|
70
|
+
const plan = planPollClose(form);
|
|
71
|
+
if (!plan.ok) {
|
|
72
|
+
if (plan.reason === "manual-no-winner") return {
|
|
73
|
+
status: 400,
|
|
74
|
+
body: { errors: [{ message: asTranslate(req.t)(keys.pollCloseManualNoWinner) }] }
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
status: 400,
|
|
78
|
+
body: { errors: [{ message: plan.reason === "already-closed" ? "Poll already closed" : "Not a poll" }] }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const closesAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
82
|
+
await payload.update({
|
|
83
|
+
collection: FORMS_SLUG,
|
|
84
|
+
id: formId,
|
|
85
|
+
data: { poll: { closesAt } },
|
|
86
|
+
depth: 0,
|
|
87
|
+
overrideAccess: true,
|
|
88
|
+
req
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
status: 200,
|
|
92
|
+
body: {
|
|
93
|
+
closesAt,
|
|
94
|
+
winningValues: plan.resolveOutcome ? await resolvePollOutcome({
|
|
95
|
+
payload,
|
|
96
|
+
formId,
|
|
97
|
+
pollVotesEnabled,
|
|
98
|
+
req
|
|
99
|
+
}) : winnersOf(pollConfigOf(form.poll) ?? {})
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { resolvePollCloseRequest };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=resolvePollCloseRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolvePollCloseRequest.js","names":[],"sources":["../../src/poll/resolvePollCloseRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport { isPollClosed, type PollConfigLike, pollConfigOf } from '../form/pollState'\nimport { keys } from '../translations/keys'\nimport { asTranslate } from '../translations/server'\nimport { resolvePollOutcome } from './resolvePollOutcome'\n\n/** The recorded winning set as non-empty strings; empty means no winner has been set yet. */\nconst winnersOf = (poll: PollConfigLike): string[] => {\n\tconst winners = poll.outcome?.winningValues\n\tif (!Array.isArray(winners)) {\n\t\treturn []\n\t}\n\treturn winners.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\n/** The selected outcome strategy slug; an absent or empty `type` is `manual`. */\nconst strategyOf = (poll: PollConfigLike): string =>\n\ttypeof poll.type === 'string' && poll.type.length > 0 ? poll.type : 'manual'\n\n/** The slice of a loaded form the close planner reads. */\nexport type PollCloseFormLike = { pollEnabled?: boolean | null; poll?: unknown }\n\nexport type PollClosePlan =\n\t| { ok: false; reason: 'already-closed' | 'manual-no-winner' | 'not-poll' }\n\t| { ok: true; resolveOutcome: boolean }\n\n/**\n * Decide what closing a poll now should do, purely from a loaded form so the branch logic is testable\n * without a boot. `manual` requires a winner to already be recorded (closing only stamps the time);\n * `mostVoted`, `source`, and any host strategy auto-resolve on close. Refuses a non-poll form and an\n * already-closed poll (idempotency), so the request helper performs no write unless this returns `ok`.\n */\nexport const planPollClose = (form: PollCloseFormLike): PollClosePlan => {\n\tconst poll = pollConfigOf(form.poll)\n\tif (form.pollEnabled !== true || !poll) {\n\t\treturn { ok: false, reason: 'not-poll' }\n\t}\n\tif (isPollClosed(poll)) {\n\t\treturn { ok: false, reason: 'already-closed' }\n\t}\n\tif (strategyOf(poll) === 'manual' && winnersOf(poll).length === 0) {\n\t\treturn { ok: false, reason: 'manual-no-winner' }\n\t}\n\treturn { ok: true, resolveOutcome: strategyOf(poll) !== 'manual' }\n}\n\nexport type ResolvePollCloseRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\t/** Whether the hidden tally store backs the outcome aggregation (see `resolvePollOutcome`). */\n\tpollVotesEnabled?: boolean\n\treq: PayloadRequest\n}\n\nexport type ResolvePollCloseRequestResult = {\n\tstatus: number\n\tbody: { closesAt: string; winningValues: string[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and perform a manual poll close backing `POST /:id/close`. Only an authenticated caller\n * may close (anonymous is always 403, mirroring the other resolve* helpers); the route is a trusted\n * admin action, so the load and write run with `overrideAccess`. Sets `closesAt` to now through\n * `payload.update` so the collection hooks run, then for a non-manual strategy resolves the winners via\n * `resolvePollOutcome` (auto mode; a non-decidable poll simply records none and the read fallback heals\n * it later). A `manual` poll with no winner recorded is refused (400) so closing never strands a poll\n * with no result. Idempotent: an already-closed poll returns 400 rather than re-closing.\n */\nexport const resolvePollCloseRequest = async (\n\targs: ResolvePollCloseRequestArgs\n): Promise<ResolvePollCloseRequestResult> => {\n\tconst { payload, formId, isAuthed, pollVotesEnabled, req } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\n\tconst plan = planPollClose(form as PollCloseFormLike)\n\tif (!plan.ok) {\n\t\tif (plan.reason === 'manual-no-winner') {\n\t\t\treturn {\n\t\t\t\tstatus: 400,\n\t\t\t\tbody: { errors: [{ message: asTranslate(req.t)(keys.pollCloseManualNoWinner) }] },\n\t\t\t}\n\t\t}\n\t\tconst message = plan.reason === 'already-closed' ? 'Poll already closed' : 'Not a poll'\n\t\treturn { status: 400, body: { errors: [{ message }] } }\n\t}\n\n\tconst closesAt = new Date().toISOString()\n\tawait payload.update({\n\t\tcollection: FORMS_SLUG,\n\t\tid: formId,\n\t\tdata: { poll: { closesAt } },\n\t\tdepth: 0,\n\t\toverrideAccess: true,\n\t\treq,\n\t})\n\n\tconst winningValues = plan.resolveOutcome\n\t\t? await resolvePollOutcome({ payload, formId, pollVotesEnabled, req })\n\t\t: winnersOf(pollConfigOf(form.poll) ?? {})\n\treturn { status: 200, body: { closesAt, winningValues } }\n}\n"],"mappings":";;;;;;;AAQA,MAAM,aAAa,SAAmC;CACrD,MAAM,UAAU,KAAK,SAAS;CAC9B,IAAI,CAAC,MAAM,QAAQ,OAAO,GACzB,OAAO,CAAC;CAET,OAAO,QAAQ,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAChG;;AAGA,MAAM,cAAc,SACnB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;;;;;;;AAerE,MAAa,iBAAiB,SAA2C;CACxE,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,IAAI,KAAK,gBAAgB,QAAQ,CAAC,MACjC,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAW;CAExC,IAAI,aAAa,IAAI,GACpB,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAiB;CAE9C,IAAI,WAAW,IAAI,MAAM,YAAY,UAAU,IAAI,EAAE,WAAW,GAC/D,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAmB;CAEhD,OAAO;EAAE,IAAI;EAAM,gBAAgB,WAAW,IAAI,MAAM;CAAS;AAClE;;;;;;;;;;AA0BA,MAAa,0BAA0B,OACtC,SAC4C;CAC5C,MAAM,EAAE,SAAS,QAAQ,UAAU,kBAAkB,QAAQ;CAC7D,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAE1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG,gBAAgB;EAAM;CAAI,CAAC,EACpF,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAGpE,MAAM,OAAO,cAAc,IAAyB;CACpD,IAAI,CAAC,KAAK,IAAI;EACb,IAAI,KAAK,WAAW,oBACnB,OAAO;GACN,QAAQ;GACR,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,IAAI,CAAC,EAAE,KAAK,uBAAuB,EAAE,CAAC,EAAE;EACjF;EAGD,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SADzB,KAAK,WAAW,mBAAmB,wBAAwB,aAC1B,CAAC,EAAE;EAAE;CACvD;CAEA,MAAM,4BAAW,IAAI,KAAK,GAAE,YAAY;CACxC,MAAM,QAAQ,OAAO;EACpB,YAAY;EACZ,IAAI;EACJ,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE;EAC3B,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC;CAKD,OAAO;EAAE,QAAQ;EAAK,MAAM;GAAE;GAAU,eAHlB,KAAK,iBACxB,MAAM,mBAAmB;IAAE;IAAS;IAAQ;IAAkB;GAAI,CAAC,IACnE,UAAU,aAAa,KAAK,IAAI,KAAK,CAAC,CAAC;EACY;CAAE;AACzD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PollOption } from "./definePollOptionSource.js";
|
|
2
|
+
import { PollOptionSourceRegistry } from "./registry.js";
|
|
3
|
+
import { Payload, PayloadRequest } from "payload";
|
|
4
|
+
|
|
5
|
+
//#region src/poll/resolvePollOptions.d.ts
|
|
6
|
+
type ResolvePollOptionsArgs = {
|
|
7
|
+
payload: Payload;
|
|
8
|
+
req?: PayloadRequest; /** A loaded forms document; its top-level `pollEnabled` flag and `poll` config drive resolution. */
|
|
9
|
+
form: {
|
|
10
|
+
id: number | string;
|
|
11
|
+
title?: string | null;
|
|
12
|
+
pollEnabled?: boolean | null;
|
|
13
|
+
poll?: unknown;
|
|
14
|
+
}; /** Registry override for plugin-internal callers; defaults to the registry the plugin stashed on the config. */
|
|
15
|
+
sources?: PollOptionSourceRegistry;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a poll-enabled form's choice options from its configured option source (plugin option
|
|
19
|
+
* `poll.sources`). Returns `undefined` when the form is not poll-enabled or has no `optionSource`
|
|
20
|
+
* set, so hand-authored options render unchanged. Hosts call this server-side and hand the result
|
|
21
|
+
* to `toFormDocument(doc, { pollOptions })`; submission validation calls it too, making the
|
|
22
|
+
* resolved values the only accepted answers. Throws when the configured source type is not
|
|
23
|
+
* registered or its `resolve` fails; results are cached per request (keyed by form id) when a
|
|
24
|
+
* `req` is provided.
|
|
25
|
+
*/
|
|
26
|
+
declare const resolvePollOptions: (args: ResolvePollOptionsArgs) => Promise<PollOption[] | undefined>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ResolvePollOptionsArgs, resolvePollOptions };
|
|
29
|
+
//# sourceMappingURL=resolvePollOptions.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { customStateOf, stashCustomState } from "../plugin/customState.js";
|
|
2
|
+
import { pollConfigOf } from "../form/pollState.js";
|
|
3
|
+
//#region src/poll/resolvePollOptions.ts
|
|
4
|
+
/** Store the resolved registry on the config so `resolvePollOptions` can find it via `payload.config`. */
|
|
5
|
+
const stashPollOptionSources = (custom, registry) => stashCustomState(custom, { pollOptionSources: registry });
|
|
6
|
+
const pollOptionSourcesOf = (payload) => customStateOf(payload).pollOptionSources ?? /* @__PURE__ */ new Map();
|
|
7
|
+
const CACHE_KEY = "formBuilderPollOptionsCache";
|
|
8
|
+
const cacheOf = (req) => {
|
|
9
|
+
if (!req?.context) return;
|
|
10
|
+
const existing = req.context[CACHE_KEY];
|
|
11
|
+
if (existing instanceof Map) return existing;
|
|
12
|
+
const created = /* @__PURE__ */ new Map();
|
|
13
|
+
req.context[CACHE_KEY] = created;
|
|
14
|
+
return created;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a poll-enabled form's choice options from its configured option source (plugin option
|
|
18
|
+
* `poll.sources`). Returns `undefined` when the form is not poll-enabled or has no `optionSource`
|
|
19
|
+
* set, so hand-authored options render unchanged. Hosts call this server-side and hand the result
|
|
20
|
+
* to `toFormDocument(doc, { pollOptions })`; submission validation calls it too, making the
|
|
21
|
+
* resolved values the only accepted answers. Throws when the configured source type is not
|
|
22
|
+
* registered or its `resolve` fails; results are cached per request (keyed by form id) when a
|
|
23
|
+
* `req` is provided.
|
|
24
|
+
*/
|
|
25
|
+
const resolvePollOptions = async (args) => {
|
|
26
|
+
const { payload, req, form, sources } = args;
|
|
27
|
+
const poll = pollConfigOf(form.poll);
|
|
28
|
+
const optionSource = typeof poll?.optionSource === "string" ? poll.optionSource : void 0;
|
|
29
|
+
if (form.pollEnabled !== true || !poll || !optionSource) return;
|
|
30
|
+
const source = (sources ?? pollOptionSourcesOf(payload)).get(optionSource);
|
|
31
|
+
if (!source) throw new Error(`Poll option source "${optionSource}" is not registered. Register it via the plugin's poll.sources option.`);
|
|
32
|
+
const cache = cacheOf(req);
|
|
33
|
+
const cacheKey = String(form.id);
|
|
34
|
+
const cached = cache?.get(cacheKey);
|
|
35
|
+
if (cached) return cached;
|
|
36
|
+
const config = poll.sourceConfig != null && typeof poll.sourceConfig === "object" ? poll.sourceConfig : {};
|
|
37
|
+
const options = await source.resolve({
|
|
38
|
+
config,
|
|
39
|
+
form: {
|
|
40
|
+
id: form.id,
|
|
41
|
+
title: typeof form.title === "string" ? form.title : void 0
|
|
42
|
+
},
|
|
43
|
+
payload,
|
|
44
|
+
req
|
|
45
|
+
});
|
|
46
|
+
cache?.set(cacheKey, options);
|
|
47
|
+
return options;
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { pollOptionSourcesOf, resolvePollOptions, stashPollOptionSources };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=resolvePollOptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolvePollOptions.js","names":[],"sources":["../../src/poll/resolvePollOptions.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { pollConfigOf } from '../form/pollState'\nimport { customStateOf, stashCustomState } from '../plugin/customState'\nimport type { PollOption } from './definePollOptionSource'\nimport type { PollOptionSourceRegistry } from './registry'\n\ntype PollCustomState = { pollOptionSources: PollOptionSourceRegistry }\n\n/** Store the resolved registry on the config so `resolvePollOptions` can find it via `payload.config`. */\nexport const stashPollOptionSources = (\n\tcustom: Record<string, unknown> | undefined,\n\tregistry: PollOptionSourceRegistry\n): Record<string, unknown> =>\n\tstashCustomState<PollCustomState>(custom, { pollOptionSources: registry })\n\nexport const pollOptionSourcesOf = (payload: Payload): PollOptionSourceRegistry =>\n\tcustomStateOf<PollCustomState>(payload).pollOptionSources ?? new Map()\n\nconst CACHE_KEY = 'formBuilderPollOptionsCache'\n\ntype OptionsCache = Map<string, PollOption[]>\n\nconst cacheOf = (req: PayloadRequest | undefined): OptionsCache | undefined => {\n\tif (!req?.context) {\n\t\treturn undefined\n\t}\n\tconst existing = req.context[CACHE_KEY]\n\tif (existing instanceof Map) {\n\t\treturn existing as OptionsCache\n\t}\n\tconst created: OptionsCache = new Map()\n\treq.context[CACHE_KEY] = created\n\treturn created\n}\n\nexport type ResolvePollOptionsArgs = {\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** A loaded forms document; its top-level `pollEnabled` flag and `poll` config drive resolution. */\n\tform: { id: number | string; title?: string | null; pollEnabled?: boolean | null; poll?: unknown }\n\t/** Registry override for plugin-internal callers; defaults to the registry the plugin stashed on the config. */\n\tsources?: PollOptionSourceRegistry\n}\n\n/**\n * Resolve a poll-enabled form's choice options from its configured option source (plugin option\n * `poll.sources`). Returns `undefined` when the form is not poll-enabled or has no `optionSource`\n * set, so hand-authored options render unchanged. Hosts call this server-side and hand the result\n * to `toFormDocument(doc, { pollOptions })`; submission validation calls it too, making the\n * resolved values the only accepted answers. Throws when the configured source type is not\n * registered or its `resolve` fails; results are cached per request (keyed by form id) when a\n * `req` is provided.\n */\nexport const resolvePollOptions = async (\n\targs: ResolvePollOptionsArgs\n): Promise<PollOption[] | undefined> => {\n\tconst { payload, req, form, sources } = args\n\tconst poll = pollConfigOf(form.poll)\n\tconst optionSource = typeof poll?.optionSource === 'string' ? poll.optionSource : undefined\n\tif (form.pollEnabled !== true || !poll || !optionSource) {\n\t\treturn undefined\n\t}\n\tconst source = (sources ?? pollOptionSourcesOf(payload)).get(optionSource)\n\tif (!source) {\n\t\tthrow new Error(\n\t\t\t`Poll option source \"${optionSource}\" is not registered. Register it via the plugin's poll.sources option.`\n\t\t)\n\t}\n\tconst cache = cacheOf(req)\n\tconst cacheKey = String(form.id)\n\tconst cached = cache?.get(cacheKey)\n\tif (cached) {\n\t\treturn cached\n\t}\n\tconst config =\n\t\tpoll.sourceConfig != null && typeof poll.sourceConfig === 'object'\n\t\t\t? (poll.sourceConfig as Record<string, unknown>)\n\t\t\t: {}\n\tconst options = await source.resolve({\n\t\tconfig,\n\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\tpayload,\n\t\treq,\n\t})\n\tcache?.set(cacheKey, options)\n\treturn options\n}\n"],"mappings":";;;;AASA,MAAa,0BACZ,QACA,aAEA,iBAAkC,QAAQ,EAAE,mBAAmB,SAAS,CAAC;AAE1E,MAAa,uBAAuB,YACnC,cAA+B,OAAO,EAAE,qCAAqB,IAAI,IAAI;AAEtE,MAAM,YAAY;AAIlB,MAAM,WAAW,QAA8D;CAC9E,IAAI,CAAC,KAAK,SACT;CAED,MAAM,WAAW,IAAI,QAAQ;CAC7B,IAAI,oBAAoB,KACvB,OAAO;CAER,MAAM,0BAAwB,IAAI,IAAI;CACtC,IAAI,QAAQ,aAAa;CACzB,OAAO;AACR;;;;;;;;;;AAoBA,MAAa,qBAAqB,OACjC,SACuC;CACvC,MAAM,EAAE,SAAS,KAAK,MAAM,YAAY;CACxC,MAAM,OAAO,aAAa,KAAK,IAAI;CACnC,MAAM,eAAe,OAAO,MAAM,iBAAiB,WAAW,KAAK,eAAe,KAAA;CAClF,IAAI,KAAK,gBAAgB,QAAQ,CAAC,QAAQ,CAAC,cAC1C;CAED,MAAM,UAAU,WAAW,oBAAoB,OAAO,GAAG,IAAI,YAAY;CACzE,IAAI,CAAC,QACJ,MAAM,IAAI,MACT,uBAAuB,aAAa,uEACrC;CAED,MAAM,QAAQ,QAAQ,GAAG;CACzB,MAAM,WAAW,OAAO,KAAK,EAAE;CAC/B,MAAM,SAAS,OAAO,IAAI,QAAQ;CAClC,IAAI,QACH,OAAO;CAER,MAAM,SACL,KAAK,gBAAgB,QAAQ,OAAO,KAAK,iBAAiB,WACtD,KAAK,eACN,CAAC;CACL,MAAM,UAAU,MAAM,OAAO,QAAQ;EACpC;EACA,MAAM;GAAE,IAAI,KAAK;GAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAAU;EACpF;EACA;CACD,CAAC;CACD,OAAO,IAAI,UAAU,OAAO;CAC5B,OAAO;AACR"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { resolveEffectivePollOptions } from "./effectivePollOptions.js";
|
|
2
|
+
import { FORMS_SLUG } from "../collections/forms.js";
|
|
3
|
+
//#region src/poll/resolvePollOptionsRequest.ts
|
|
4
|
+
/**
|
|
5
|
+
* Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:
|
|
6
|
+
* authenticated callers get the poll's effective options (source-resolved or authored, exactly the
|
|
7
|
+
* set outcome validation accepts), anonymous callers are always refused. Statuses mirror the
|
|
8
|
+
* results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option
|
|
9
|
+
* source fails (fail closed).
|
|
10
|
+
*/
|
|
11
|
+
const resolvePollOptionsRequest = async (args) => {
|
|
12
|
+
const { payload, formId, isAuthed, req } = args;
|
|
13
|
+
if (!isAuthed) return {
|
|
14
|
+
status: 403,
|
|
15
|
+
body: { errors: [{ message: "Forbidden" }] }
|
|
16
|
+
};
|
|
17
|
+
if (formId == null) return {
|
|
18
|
+
status: 400,
|
|
19
|
+
body: { errors: [{ message: "Missing form id" }] }
|
|
20
|
+
};
|
|
21
|
+
const form = await payload.findByID({
|
|
22
|
+
collection: FORMS_SLUG,
|
|
23
|
+
id: formId,
|
|
24
|
+
depth: 0,
|
|
25
|
+
req
|
|
26
|
+
}).catch(() => null);
|
|
27
|
+
if (!form) return {
|
|
28
|
+
status: 404,
|
|
29
|
+
body: { errors: [{ message: "Not found" }] }
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
return {
|
|
33
|
+
status: 200,
|
|
34
|
+
body: { options: await resolveEffectivePollOptions({
|
|
35
|
+
payload,
|
|
36
|
+
req,
|
|
37
|
+
form
|
|
38
|
+
}) }
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
status: 503,
|
|
43
|
+
body: { errors: [{ message: "Poll options unavailable" }] }
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { resolvePollOptionsRequest };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=resolvePollOptionsRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolvePollOptionsRequest.js","names":[],"sources":["../../src/poll/resolvePollOptionsRequest.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { PollOption } from './definePollOptionSource'\nimport { resolveEffectivePollOptions } from './effectivePollOptions'\n\nexport type ResolvePollOptionsRequestArgs = {\n\tpayload: Payload\n\tformId: number | string | undefined\n\t/** Whether the caller is authenticated (an admin/user). */\n\tisAuthed: boolean\n\treq?: PayloadRequest\n}\n\nexport type ResolvePollOptionsRequestResult = {\n\tstatus: number\n\tbody: { options: PollOption[] } | { errors: { message: string }[] }\n}\n\n/**\n * Authorize and resolve the `GET /:id/poll-options` request backing the admin winner select:\n * authenticated callers get the poll's effective options (source-resolved or authored, exactly the\n * set outcome validation accepts), anonymous callers are always refused. Statuses mirror the\n * results endpoint: 400 missing id, 403 unauthenticated, 404 unknown form, 503 when the option\n * source fails (fail closed).\n */\nexport const resolvePollOptionsRequest = async (\n\targs: ResolvePollOptionsRequestArgs\n): Promise<ResolvePollOptionsRequestResult> => {\n\tconst { payload, formId, isAuthed, req } = args\n\tif (!isAuthed) {\n\t\treturn { status: 403, body: { errors: [{ message: 'Forbidden' }] } }\n\t}\n\tif (formId == null) {\n\t\treturn { status: 400, body: { errors: [{ message: 'Missing form id' }] } }\n\t}\n\t// Load under the caller's own read access (no overrideAccess): a tenant-scoped `forms.access.read`\n\t// then refuses a cross-tenant form id (null -> 404), so an authed user cannot read another tenant's\n\t// poll options.\n\tconst form = await payload\n\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, req })\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn { status: 404, body: { errors: [{ message: 'Not found' }] } }\n\t}\n\ttry {\n\t\tconst options = await resolveEffectivePollOptions({ payload, req, form })\n\t\treturn { status: 200, body: { options } }\n\t} catch {\n\t\treturn { status: 503, body: { errors: [{ message: 'Poll options unavailable' }] } }\n\t}\n}\n"],"mappings":";;;;;;;;;;AAyBA,MAAa,4BAA4B,OACxC,SAC8C;CAC9C,MAAM,EAAE,SAAS,QAAQ,UAAU,QAAQ;CAC3C,IAAI,CAAC,UACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI,UAAU,MACb,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,kBAAkB,CAAC,EAAE;CAAE;CAK1E,MAAM,OAAO,MAAM,QACjB,SAAS;EAAE,YAAY;EAAY,IAAI;EAAQ,OAAO;EAAG;CAAI,CAAC,EAC9D,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ,OAAO;EAAE,QAAQ;EAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY,CAAC,EAAE;CAAE;CAEpE,IAAI;EAEH,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,SAAA,MADR,4BAA4B;IAAE;IAAS;IAAK;GAAK,CAAC,EAClC;EAAE;CACzC,QAAQ;EACP,OAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,QAAQ,CAAC,EAAE,SAAS,2BAA2B,CAAC,EAAE;EAAE;CACnF;AACD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Payload, PayloadRequest } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/poll/resolvePollOutcome.d.ts
|
|
4
|
+
type ResolvePollOutcomeArgs = {
|
|
5
|
+
payload: Payload;
|
|
6
|
+
formId: number | string;
|
|
7
|
+
/**
|
|
8
|
+
* The winning set: stable option values, each matching one of the poll's choice values (the
|
|
9
|
+
* host's contract with `PollOption.value`). Pass more than one for a tie, an empty array to clear
|
|
10
|
+
* the outcome. Omit to auto-resolve via the poll's selected outcome strategy (`poll.type`).
|
|
11
|
+
*/
|
|
12
|
+
winningValues?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Whether the hidden tally store backs the auto-resolve aggregation (`mostVoted` and any host
|
|
15
|
+
* strategy that calls `aggregate()`). False keeps the submission scan, so persist-off forms
|
|
16
|
+
* only resolve correctly with the store enabled.
|
|
17
|
+
*/
|
|
18
|
+
pollVotesEnabled?: boolean;
|
|
19
|
+
req?: PayloadRequest;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Record a poll's final outcome server-side, in one of two modes:
|
|
23
|
+
* - explicit: pass `winningValues` when host code already knows the winner(s); more than one is a
|
|
24
|
+
* tie, and an empty array clears the outcome;
|
|
25
|
+
* - auto: omit it and the poll's selected outcome strategy (`poll.type`, from the `poll.types`
|
|
26
|
+
* registry, defaulting to `manual`) resolves the winners. `manual` is a no-op; `mostVoted` reads
|
|
27
|
+
* the aggregated results field; `source` delegates to the option source. A strategy that is not
|
|
28
|
+
* yet decidable (or `manual`) writes nothing and this returns an empty set, so auto mode is safe
|
|
29
|
+
* to call repeatedly from the close job and the read fallback.
|
|
30
|
+
*
|
|
31
|
+
* Every write runs through `payload.update` with `overrideAccess`, so the forms collection hook
|
|
32
|
+
* validates each value against the poll's effective options and owns the `poll.outcome.resolvedAt`
|
|
33
|
+
* stamp in both modes (auto-resolved winners are validated exactly as hand-picked ones). Throws only
|
|
34
|
+
* when the form does not exist or is not poll-enabled. Returns the recorded winning set (empty when
|
|
35
|
+
* auto mode did not decide).
|
|
36
|
+
*/
|
|
37
|
+
declare const resolvePollOutcome: (args: ResolvePollOutcomeArgs) => Promise<string[]>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ResolvePollOutcomeArgs, resolvePollOutcome };
|
|
40
|
+
//# sourceMappingURL=resolvePollOutcome.d.ts.map
|