@10x-media/form-builder 0.1.0-beta.4 → 0.1.0-beta.5
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 +83 -0
- package/README.md +3 -3
- package/dist/actions/body/serializeBody.d.ts +8 -5
- package/dist/actions/body/serializeBody.js.map +1 -1
- 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/builtin/confirmation.js +41 -11
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +77 -38
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +8 -3
- package/dist/actions/builtin/index.js +8 -5
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +1 -1
- package/dist/actions/fromAddresses.d.ts +21 -0
- package/dist/actions/fromAddresses.js +74 -0
- package/dist/actions/fromAddresses.js.map +1 -0
- package/dist/actions/task.js +9 -8
- 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 +19 -6
- package/dist/aggregation/resolveResultsRequest.js +58 -12
- package/dist/aggregation/resolveResultsRequest.js.map +1 -1
- package/dist/calc/computeCalcFields.js +2 -1
- package/dist/calc/computeCalcFields.js.map +1 -1
- package/dist/client/ByteSizeField.d.ts +8 -0
- package/dist/client/ByteSizeField.js +3 -1
- package/dist/client/ByteSizeField.js.map +1 -1
- 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/EndpointOptionsSelect.d.ts +45 -0
- package/dist/client/EndpointOptionsSelect.js +103 -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 +20 -4
- package/dist/client/FieldNameSelect.js +25 -14
- package/dist/client/FieldNameSelect.js.map +1 -1
- package/dist/client/FlowBuilder.d.ts +2 -1
- package/dist/client/FlowBuilder.js +63 -57
- package/dist/client/FlowBuilder.js.map +1 -1
- 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 +36 -0
- package/dist/client/endpointOptions.js +29 -0
- package/dist/client/endpointOptions.js.map +1 -0
- package/dist/client/flowAuthoring.js +114 -7
- package/dist/client/flowAuthoring.js.map +1 -1
- package/dist/client/synthesizeClientField.js +15 -8
- package/dist/client/synthesizeClientField.js.map +1 -1
- 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 +22 -17
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +310 -137
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/redirectFields.d.ts +29 -0
- 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 +19 -8
- package/dist/consent/captureConsent.js +34 -11
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +58 -0
- package/dist/consent/consentSourcesField.js +79 -0
- package/dist/consent/consentSourcesField.js.map +1 -0
- package/dist/consent/resolveConsentEntries.d.ts +24 -0
- package/dist/consent/resolveConsentEntries.js +43 -0
- package/dist/consent/resolveConsentEntries.js.map +1 -0
- package/dist/consent/resolveConsentSourcesRequest.d.ts +37 -0
- package/dist/consent/resolveConsentSourcesRequest.js +55 -0
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -0
- package/dist/consent/resolveConsentStatements.d.ts +45 -0
- package/dist/consent/resolveConsentStatements.js +44 -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 +49 -0
- package/dist/email/departments.d.ts +55 -0
- package/dist/email/departments.js +130 -0
- package/dist/email/departments.js.map +1 -0
- package/dist/email/departmentsField.d.ts +49 -0
- package/dist/email/departmentsField.js +69 -0
- package/dist/email/departmentsField.js.map +1 -0
- package/dist/exports/client.d.ts +8 -1
- package/dist/exports/client.js +8 -1
- package/dist/exports/react.d.ts +8 -3
- package/dist/exports/react.js +6 -2
- package/dist/exports/rsc.d.ts +3 -1
- package/dist/exports/rsc.js +4 -2
- package/dist/exports/types.d.ts +16 -8
- package/dist/fields/buildFieldBlocks.js +28 -13
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.js +7 -0
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/checkbox.js +8 -0
- package/dist/fields/builtin/checkbox.js.map +1 -1
- package/dist/fields/builtin/consent.js +27 -21
- 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 +2 -0
- package/dist/fields/builtin/date.js.map +1 -1
- package/dist/fields/builtin/file.js +5 -4
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +8 -6
- package/dist/fields/builtin/index.js +10 -9
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/builtin/message.js +8 -4
- package/dist/fields/builtin/message.js.map +1 -1
- package/dist/fields/builtin/repeater.js +1 -0
- package/dist/fields/builtin/repeater.js.map +1 -1
- package/dist/fields/builtin/select.js +1 -0
- 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/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 +13 -8
- package/dist/fields/fieldNamesOfType.js.map +1 -1
- package/dist/fields/instanceOptions.js +20 -0
- package/dist/fields/instanceOptions.js.map +1 -0
- package/dist/fields/registry.d.ts +1 -0
- package/dist/fields/registry.js +21 -2
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +46 -9
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/fields/types.d.ts +54 -3
- 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 +16 -7
- package/dist/flow/normalizeFlow.js.map +1 -1
- 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 +4 -4
- package/dist/form/pollState.js.map +1 -1
- package/dist/form/toFormDocument.d.ts +37 -12
- package/dist/form/toFormDocument.js +43 -7
- package/dist/form/toFormDocument.js.map +1 -1
- package/dist/form/types.d.ts +31 -14
- package/dist/index.d.ts +143 -28
- package/dist/index.js +43 -14
- package/dist/index.js.map +1 -1
- package/dist/plugin/customState.js +26 -0
- package/dist/plugin/customState.js.map +1 -0
- package/dist/plugin/registerCollections.js +23 -13
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/plugin/uploadsCollection.d.ts +2 -0
- package/dist/poll/applyPollOptions.js +3 -2
- package/dist/poll/applyPollOptions.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -5
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- package/dist/poll/closeJob.d.ts +52 -0
- package/dist/poll/closeJob.js +119 -0
- package/dist/poll/closeJob.js.map +1 -0
- package/dist/poll/definePollOptionSource.d.ts +10 -1
- package/dist/poll/definePollOptionSource.js.map +1 -1
- 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 +71 -0
- package/dist/poll/pollTypeRegistry.js.map +1 -0
- package/dist/poll/resolvePollCloseRequest.js +105 -0
- package/dist/poll/resolvePollCloseRequest.js.map +1 -0
- package/dist/poll/resolvePollOptions.d.ts +2 -1
- package/dist/poll/resolvePollOptions.js +5 -17
- package/dist/poll/resolvePollOptions.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +51 -0
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -0
- package/dist/poll/resolvePollOutcome.d.ts +22 -9
- package/dist/poll/resolvePollOutcome.js +54 -14
- package/dist/poll/resolvePollOutcome.js.map +1 -1
- package/dist/poll/resultsField.js +21 -0
- package/dist/poll/resultsField.js.map +1 -0
- package/dist/prefill/valuesFromSearchParams.js +2 -1
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +8 -8
- package/dist/react/Form.js +74 -62
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +6 -4
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormControls.d.ts +3 -3
- package/dist/react/FormControls.js +3 -3
- package/dist/react/FormControls.js.map +1 -1
- package/dist/react/FormResults.d.ts +5 -5
- package/dist/react/FormResults.js +6 -6
- package/dist/react/FormResults.js.map +1 -1
- package/dist/react/Poll.d.ts +2 -2
- package/dist/react/Poll.js +5 -5
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/contract.d.ts +2 -3
- package/dist/react/contract.js.map +1 -1
- package/dist/react/primitives/Checkbox.d.ts +4 -2
- package/dist/react/primitives/Checkbox.js +2 -1
- package/dist/react/primitives/Checkbox.js.map +1 -1
- package/dist/react/primitives/FieldShell.d.ts +2 -4
- package/dist/react/primitives/FieldShell.js +12 -19
- package/dist/react/primitives/FieldShell.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -2
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -2
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +78 -34
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +1 -3
- package/dist/react/renderers/date.js.map +1 -1
- package/dist/react/renderers/email.js +1 -2
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +1 -2
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +3 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/number.js +1 -2
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +45 -0
- package/dist/react/renderers/region.js.map +1 -0
- package/dist/react/renderers/repeater.js +7 -10
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -2
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -2
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -2
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/react/state.d.ts +0 -3
- package/dist/react/state.js +7 -11
- package/dist/react/state.js.map +1 -1
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +0 -1
- 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 +14 -2
- package/dist/recall/resolver.js.map +1 -1
- package/dist/spam/captcha.d.ts +1 -1
- package/dist/spam/captcha.js +1 -1
- package/dist/spam/captcha.js.map +1 -1
- package/dist/submissions/SubmissionAnswers.js +6 -2
- package/dist/submissions/SubmissionAnswers.js.map +1 -1
- package/dist/submissions/SubmissionAnswersClient.js +91 -62
- package/dist/submissions/SubmissionAnswersClient.js.map +1 -1
- 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/runSubmission.d.ts +10 -0
- package/dist/submissions/runSubmission.js +29 -18
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/types.d.ts +11 -4
- package/dist/submissions/validateSubmission.js +38 -23
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/submissions/votedCookie.js +3 -3
- package/dist/submissions/votedCookie.js.map +1 -1
- package/dist/translations/de.js +102 -26
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +103 -27
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +97 -21
- package/dist/translations/keys.js +98 -22
- package/dist/translations/keys.js.map +1 -1
- package/dist/validation/buildRuleBlocks.js +18 -17
- package/dist/validation/buildRuleBlocks.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -0
- package/dist/validation/builtin/email.js.map +1 -1
- 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 +1 -0
- package/dist/validation/builtin/maxDate.js.map +1 -1
- 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 +1 -0
- package/dist/validation/builtin/minDate.js.map +1 -1
- package/dist/validation/builtin/minLength.js +1 -0
- package/dist/validation/builtin/minLength.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- 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/validateRegexParams.js +44 -0
- package/dist/validation/builtin/validateRegexParams.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/runValidation.js +8 -29
- package/dist/validation/runValidation.js.map +1 -1
- package/dist/validation/types.d.ts +5 -11
- package/package.json +6 -6
- package/dist/consent/buildConsentSourceConfig.js +0 -49
- package/dist/consent/buildConsentSourceConfig.js.map +0 -1
- package/dist/consent/builtin/index.d.ts +0 -17
- package/dist/consent/builtin/index.js +0 -21
- 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 -48
- 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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { Button, useDocumentInfo, useForm, useFormFields } from "@payloadcms/ui";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
//#region src/client/ClosePollButton.tsx
|
|
8
|
+
/** The help line under the button when closing, tailored to the strategy that closing will apply. */
|
|
9
|
+
const closeHintKeyFor = (type) => {
|
|
10
|
+
if (type === "mostVoted") return keys.pollCloseHintMostVoted;
|
|
11
|
+
if (type === "source") return keys.pollCloseHintSource;
|
|
12
|
+
return keys.pollCloseHintManual;
|
|
13
|
+
};
|
|
14
|
+
const strOf = (value) => typeof value === "string" && value.length > 0 ? value : "manual";
|
|
15
|
+
/** True once a `closesAt` is set (scheduled or past): the toggle treats that as closed/finalized. */
|
|
16
|
+
const isClosed = (value) => typeof value === "string" && value.length > 0;
|
|
17
|
+
/** True when the winner set holds at least one non-empty value. */
|
|
18
|
+
const hasWinner = (value) => Array.isArray(value) && value.some((entry) => typeof entry === "string" && entry.length > 0);
|
|
19
|
+
const asRecord = (value) => value != null && typeof value === "object" ? value : {};
|
|
20
|
+
/**
|
|
21
|
+
* The `poll.closePoll` UI field: one button that closes the poll and, once closed, reopens it, by
|
|
22
|
+
* saving the whole document rather than calling the `/:id/close` endpoint. Closing stamps
|
|
23
|
+
* `poll.closesAt` now while keeping the admin's just-picked winner, so both persist together through
|
|
24
|
+
* the normal save (`pollOutcomeBeforeChange` validates the winner and stamps `resolvedAt`; the
|
|
25
|
+
* `afterChange` hook enqueues the close for a `mostVoted`/`source` strategy). Reopening clears both
|
|
26
|
+
* `closesAt` and `winningValues`, since either one keeps a poll finalized. A `manual` poll with no
|
|
27
|
+
* winner selected disables the button (there is nothing to record); `mostVoted`/`source` stay enabled
|
|
28
|
+
* because the winner is server-computed. Disabled on an unsaved document (no id yet).
|
|
29
|
+
*/
|
|
30
|
+
const ClosePollButton = () => {
|
|
31
|
+
const { t } = useTranslation$1();
|
|
32
|
+
const { id } = useDocumentInfo();
|
|
33
|
+
const { getDataByPath, submit } = useForm();
|
|
34
|
+
const type = useFormFields(([fields]) => strOf(fields?.["poll.type"]?.value));
|
|
35
|
+
const closed = useFormFields(([fields]) => isClosed(fields?.["poll.closesAt"]?.value));
|
|
36
|
+
const winnerSet = useFormFields(([fields]) => hasWinner(fields?.["poll.outcome.winningValues"]?.value));
|
|
37
|
+
const [submitting, setSubmitting] = useState(false);
|
|
38
|
+
const manualNeedsWinner = !closed && type === "manual" && !winnerSet;
|
|
39
|
+
const disabled = id == null || submitting || manualNeedsWinner;
|
|
40
|
+
const save = async (nextPoll) => {
|
|
41
|
+
if (id == null) return;
|
|
42
|
+
setSubmitting(true);
|
|
43
|
+
try {
|
|
44
|
+
await submit({ overrides: { poll: nextPoll(asRecord(getDataByPath("poll"))) } });
|
|
45
|
+
} finally {
|
|
46
|
+
setSubmitting(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const close = () => save((poll) => ({
|
|
50
|
+
...poll,
|
|
51
|
+
closesAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
52
|
+
}));
|
|
53
|
+
const reopen = () => save((poll) => ({
|
|
54
|
+
...poll,
|
|
55
|
+
closesAt: null,
|
|
56
|
+
outcome: {
|
|
57
|
+
...asRecord(poll.outcome),
|
|
58
|
+
winningValues: []
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
const hintKey = closed ? keys.pollReopenHint : manualNeedsWinner ? keys.pollCloseNeedsWinner : closeHintKeyFor(type);
|
|
62
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
63
|
+
className: "field-type",
|
|
64
|
+
style: { marginBlockEnd: "1rem" },
|
|
65
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
66
|
+
buttonStyle: "secondary",
|
|
67
|
+
onClick: closed ? reopen : close,
|
|
68
|
+
disabled,
|
|
69
|
+
children: t(closed ? keys.pollReopenButton : keys.pollCloseButton)
|
|
70
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
71
|
+
className: "field-description",
|
|
72
|
+
style: { marginBlockStart: "0.5rem" },
|
|
73
|
+
children: t(hintKey)
|
|
74
|
+
})]
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ClosePollButton };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=ClosePollButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClosePollButton.js","names":["useTranslation"],"sources":["../../src/client/ClosePollButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, useDocumentInfo, useForm, useFormFields } from '@payloadcms/ui'\nimport { useState } from 'react'\nimport { keys, type TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/** The help line under the button when closing, tailored to the strategy that closing will apply. */\nconst closeHintKeyFor = (type: string): TranslationKey => {\n\tif (type === 'mostVoted') {\n\t\treturn keys.pollCloseHintMostVoted\n\t}\n\tif (type === 'source') {\n\t\treturn keys.pollCloseHintSource\n\t}\n\treturn keys.pollCloseHintManual\n}\n\nconst strOf = (value: unknown): string =>\n\ttypeof value === 'string' && value.length > 0 ? value : 'manual'\n\n/** True once a `closesAt` is set (scheduled or past): the toggle treats that as closed/finalized. */\nconst isClosed = (value: unknown): boolean => typeof value === 'string' && value.length > 0\n\n/** True when the winner set holds at least one non-empty value. */\nconst hasWinner = (value: unknown): boolean =>\n\tArray.isArray(value) && value.some((entry) => typeof entry === 'string' && entry.length > 0)\n\nconst asRecord = (value: unknown): Record<string, unknown> =>\n\tvalue != null && typeof value === 'object' ? (value as Record<string, unknown>) : {}\n\n/**\n * The `poll.closePoll` UI field: one button that closes the poll and, once closed, reopens it, by\n * saving the whole document rather than calling the `/:id/close` endpoint. Closing stamps\n * `poll.closesAt` now while keeping the admin's just-picked winner, so both persist together through\n * the normal save (`pollOutcomeBeforeChange` validates the winner and stamps `resolvedAt`; the\n * `afterChange` hook enqueues the close for a `mostVoted`/`source` strategy). Reopening clears both\n * `closesAt` and `winningValues`, since either one keeps a poll finalized. A `manual` poll with no\n * winner selected disables the button (there is nothing to record); `mostVoted`/`source` stay enabled\n * because the winner is server-computed. Disabled on an unsaved document (no id yet).\n */\nexport const ClosePollButton = () => {\n\tconst { t } = useTranslation()\n\tconst { id } = useDocumentInfo()\n\tconst { getDataByPath, submit } = useForm()\n\tconst type = useFormFields(([fields]) => strOf(fields?.['poll.type']?.value))\n\tconst closed = useFormFields(([fields]) => isClosed(fields?.['poll.closesAt']?.value))\n\tconst winnerSet = useFormFields(([fields]) =>\n\t\thasWinner(fields?.['poll.outcome.winningValues']?.value)\n\t)\n\tconst [submitting, setSubmitting] = useState(false)\n\n\tconst manualNeedsWinner = !closed && type === 'manual' && !winnerSet\n\tconst disabled = id == null || submitting || manualNeedsWinner\n\n\tconst save = async (nextPoll: (poll: Record<string, unknown>) => Record<string, unknown>) => {\n\t\tif (id == null) {\n\t\t\treturn\n\t\t}\n\t\tsetSubmitting(true)\n\t\ttry {\n\t\t\t// Rebuild the whole poll group from live form state and override it in one shot: a shallow\n\t\t\t// top-level override, so the winner and every other poll field are preserved (a dotted\n\t\t\t// `poll.closesAt` override key never reaches the nested field on the server).\n\t\t\tconst poll = asRecord(getDataByPath('poll'))\n\t\t\tawait submit({ overrides: { poll: nextPoll(poll) } })\n\t\t} finally {\n\t\t\tsetSubmitting(false)\n\t\t}\n\t}\n\n\tconst close = () => save((poll) => ({ ...poll, closesAt: new Date().toISOString() }))\n\tconst reopen = () =>\n\t\tsave((poll) => ({\n\t\t\t...poll,\n\t\t\tclosesAt: null,\n\t\t\toutcome: { ...asRecord(poll.outcome), winningValues: [] },\n\t\t}))\n\n\tconst hintKey: TranslationKey = closed\n\t\t? keys.pollReopenHint\n\t\t: manualNeedsWinner\n\t\t\t? keys.pollCloseNeedsWinner\n\t\t\t: closeHintKeyFor(type)\n\n\treturn (\n\t\t<div className=\"field-type\" style={{ marginBlockEnd: '1rem' }}>\n\t\t\t<Button buttonStyle=\"secondary\" onClick={closed ? reopen : close} disabled={disabled}>\n\t\t\t\t{t(closed ? keys.pollReopenButton : keys.pollCloseButton)}\n\t\t\t</Button>\n\t\t\t<p className=\"field-description\" style={{ marginBlockStart: '0.5rem' }}>\n\t\t\t\t{t(hintKey)}\n\t\t\t</p>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,mBAAmB,SAAiC;CACzD,IAAI,SAAS,aACZ,OAAO,KAAK;CAEb,IAAI,SAAS,UACZ,OAAO,KAAK;CAEb,OAAO,KAAK;AACb;AAEA,MAAM,SAAS,UACd,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;;AAGzD,MAAM,YAAY,UAA4B,OAAO,UAAU,YAAY,MAAM,SAAS;;AAG1F,MAAM,aAAa,UAClB,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAE5F,MAAM,YAAY,UACjB,SAAS,QAAQ,OAAO,UAAU,WAAY,QAAoC,CAAC;;;;;;;;;;;AAYpF,MAAa,wBAAwB;CACpC,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,EAAE,OAAO,gBAAgB;CAC/B,MAAM,EAAE,eAAe,WAAW,QAAQ;CAC1C,MAAM,OAAO,eAAe,CAAC,YAAY,MAAM,SAAS,cAAc,KAAK,CAAC;CAC5E,MAAM,SAAS,eAAe,CAAC,YAAY,SAAS,SAAS,kBAAkB,KAAK,CAAC;CACrF,MAAM,YAAY,eAAe,CAAC,YACjC,UAAU,SAAS,+BAA+B,KAAK,CACxD;CACA,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAElD,MAAM,oBAAoB,CAAC,UAAU,SAAS,YAAY,CAAC;CAC3D,MAAM,WAAW,MAAM,QAAQ,cAAc;CAE7C,MAAM,OAAO,OAAO,aAAyE;EAC5F,IAAI,MAAM,MACT;EAED,cAAc,IAAI;EAClB,IAAI;GAKH,MAAM,OAAO,EAAE,WAAW,EAAE,MAAM,SADrB,SAAS,cAAc,MAAM,CACI,CAAC,EAAE,EAAE,CAAC;EACrD,UAAU;GACT,cAAc,KAAK;EACpB;CACD;CAEA,MAAM,cAAc,MAAM,UAAU;EAAE,GAAG;EAAM,2BAAU,IAAI,KAAK,GAAE,YAAY;CAAE,EAAE;CACpF,MAAM,eACL,MAAM,UAAU;EACf,GAAG;EACH,UAAU;EACV,SAAS;GAAE,GAAG,SAAS,KAAK,OAAO;GAAG,eAAe,CAAC;EAAE;CACzD,EAAE;CAEH,MAAM,UAA0B,SAC7B,KAAK,iBACL,oBACC,KAAK,uBACL,gBAAgB,IAAI;CAExB,OACC,qBAAC,OAAD;EAAK,WAAU;EAAa,OAAO,EAAE,gBAAgB,OAAO;YAA5D,CACC,oBAAC,QAAD;GAAQ,aAAY;GAAY,SAAS,SAAS,SAAS;GAAiB;aAC1E,EAAE,SAAS,KAAK,mBAAmB,KAAK,eAAe;EACjD,CAAA,GACR,oBAAC,KAAD;GAAG,WAAU;GAAoB,OAAO,EAAE,kBAAkB,SAAS;aACnE,EAAE,OAAO;EACR,CAAA,CACC;;AAEP"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ArrayFieldClientComponent } from "payload";
|
|
2
|
+
//#region src/client/CondensedArray.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A custom `Field` for a real Payload `array` that renders its rows flat: each row is `[label, email]`
|
|
5
|
+
* laid out at 50% width with a remove control, added by a footer button, and never wrapped in the
|
|
6
|
+
* per-row `Collapsible` that Payload's native array applies unconditionally. Row ids and per-locale
|
|
7
|
+
* storage stay Payload's: rows come from `useField({ hasRows: true })`, mutations go through
|
|
8
|
+
* `useForm`'s `addFieldRow`/`removeFieldRow`, and each row's subfields render through `RenderFields`
|
|
9
|
+
* at `{path}.{index}` exactly as the native array does, so localization, validation, and descriptions
|
|
10
|
+
* work natively. Placed by {@link departmentsField}.
|
|
11
|
+
*/
|
|
12
|
+
declare const CondensedArray: ArrayFieldClientComponent;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { CondensedArray };
|
|
15
|
+
//# sourceMappingURL=CondensedArray.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { Button, FieldDescription, FieldLabel, RenderCustomComponent, RenderFields, useField, useForm } from "@payloadcms/ui";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import "./admin.css";
|
|
7
|
+
//#region src/client/CondensedArray.tsx
|
|
8
|
+
const baseClass = "fb-condensed-array";
|
|
9
|
+
/**
|
|
10
|
+
* A custom `Field` for a real Payload `array` that renders its rows flat: each row is `[label, email]`
|
|
11
|
+
* laid out at 50% width with a remove control, added by a footer button, and never wrapped in the
|
|
12
|
+
* per-row `Collapsible` that Payload's native array applies unconditionally. Row ids and per-locale
|
|
13
|
+
* storage stay Payload's: rows come from `useField({ hasRows: true })`, mutations go through
|
|
14
|
+
* `useForm`'s `addFieldRow`/`removeFieldRow`, and each row's subfields render through `RenderFields`
|
|
15
|
+
* at `{path}.{index}` exactly as the native array does, so localization, validation, and descriptions
|
|
16
|
+
* work natively. Placed by {@link departmentsField}.
|
|
17
|
+
*/
|
|
18
|
+
const CondensedArray = (props) => {
|
|
19
|
+
const { field: { name, admin: { description } = {}, fields, label, localized }, forceRender = false, path: pathFromProps, permissions, readOnly, schemaPath: schemaPathFromProps } = props;
|
|
20
|
+
const schemaPath = schemaPathFromProps ?? name;
|
|
21
|
+
const { t } = useTranslation$1();
|
|
22
|
+
const { addFieldRow, removeFieldRow } = useForm();
|
|
23
|
+
const { customComponents: { Description, Label } = {}, path, rows = [] } = useField({
|
|
24
|
+
hasRows: true,
|
|
25
|
+
potentiallyStalePath: pathFromProps
|
|
26
|
+
});
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
className: `field-type ${baseClass}`,
|
|
29
|
+
id: `field-${path.replace(/\./g, "__")}`,
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsx(RenderCustomComponent, {
|
|
32
|
+
CustomComponent: Label,
|
|
33
|
+
Fallback: /* @__PURE__ */ jsx(FieldLabel, {
|
|
34
|
+
label,
|
|
35
|
+
localized,
|
|
36
|
+
path
|
|
37
|
+
})
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ jsx(RenderCustomComponent, {
|
|
40
|
+
CustomComponent: Description,
|
|
41
|
+
Fallback: /* @__PURE__ */ jsx(FieldDescription, {
|
|
42
|
+
description,
|
|
43
|
+
path
|
|
44
|
+
})
|
|
45
|
+
}),
|
|
46
|
+
rows.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
47
|
+
className: `${baseClass}__rows`,
|
|
48
|
+
children: rows.map((row, index) => /* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: `${baseClass}__row`,
|
|
50
|
+
children: [/* @__PURE__ */ jsx(RenderFields, {
|
|
51
|
+
className: `${baseClass}__fields`,
|
|
52
|
+
fields,
|
|
53
|
+
forceRender,
|
|
54
|
+
margins: false,
|
|
55
|
+
parentIndexPath: "",
|
|
56
|
+
parentPath: `${path}.${index}`,
|
|
57
|
+
parentSchemaPath: schemaPath,
|
|
58
|
+
permissions: permissions === true ? permissions : permissions?.fields ?? {},
|
|
59
|
+
readOnly
|
|
60
|
+
}), !readOnly ? /* @__PURE__ */ jsx("div", {
|
|
61
|
+
className: `${baseClass}__remove`,
|
|
62
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
63
|
+
buttonStyle: "icon-label",
|
|
64
|
+
icon: "x",
|
|
65
|
+
onClick: () => removeFieldRow({
|
|
66
|
+
path,
|
|
67
|
+
rowIndex: index
|
|
68
|
+
}),
|
|
69
|
+
"aria-label": t(keys.departmentRemoveRow),
|
|
70
|
+
margin: false
|
|
71
|
+
})
|
|
72
|
+
}) : null]
|
|
73
|
+
}, row.id))
|
|
74
|
+
}) : null,
|
|
75
|
+
!readOnly ? /* @__PURE__ */ jsx("div", {
|
|
76
|
+
className: `${baseClass}__actions`,
|
|
77
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
78
|
+
buttonStyle: "icon-label",
|
|
79
|
+
icon: "plus",
|
|
80
|
+
iconStyle: "with-border",
|
|
81
|
+
iconPosition: "left",
|
|
82
|
+
onClick: () => addFieldRow({
|
|
83
|
+
path,
|
|
84
|
+
rowIndex: rows.length,
|
|
85
|
+
schemaPath
|
|
86
|
+
}),
|
|
87
|
+
margin: false,
|
|
88
|
+
children: t(keys.departmentAddRow)
|
|
89
|
+
})
|
|
90
|
+
}) : null
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { CondensedArray };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=CondensedArray.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CondensedArray.js","names":["useTranslation"],"sources":["../../src/client/CondensedArray.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tButton,\n\tFieldDescription,\n\tFieldLabel,\n\tRenderCustomComponent,\n\tRenderFields,\n\tuseField,\n\tuseForm,\n} from '@payloadcms/ui'\nimport type { ArrayFieldClientComponent } from 'payload'\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport './admin.css'\n\nconst baseClass = 'fb-condensed-array'\n\n/**\n * A custom `Field` for a real Payload `array` that renders its rows flat: each row is `[label, email]`\n * laid out at 50% width with a remove control, added by a footer button, and never wrapped in the\n * per-row `Collapsible` that Payload's native array applies unconditionally. Row ids and per-locale\n * storage stay Payload's: rows come from `useField({ hasRows: true })`, mutations go through\n * `useForm`'s `addFieldRow`/`removeFieldRow`, and each row's subfields render through `RenderFields`\n * at `{path}.{index}` exactly as the native array does, so localization, validation, and descriptions\n * work natively. Placed by {@link departmentsField}.\n */\nexport const CondensedArray: ArrayFieldClientComponent = (props) => {\n\tconst {\n\t\tfield: { name, admin: { description } = {}, fields, label, localized },\n\t\tforceRender = false,\n\t\tpath: pathFromProps,\n\t\tpermissions,\n\t\treadOnly,\n\t\tschemaPath: schemaPathFromProps,\n\t} = props\n\tconst schemaPath = schemaPathFromProps ?? name\n\n\tconst { t } = useTranslation()\n\tconst { addFieldRow, removeFieldRow } = useForm()\n\tconst {\n\t\tcustomComponents: { Description, Label } = {},\n\t\tpath,\n\t\trows = [],\n\t} = useField<number>({ hasRows: true, potentiallyStalePath: pathFromProps })\n\n\treturn (\n\t\t<div className={`field-type ${baseClass}`} id={`field-${path.replace(/\\./g, '__')}`}>\n\t\t\t<RenderCustomComponent\n\t\t\t\tCustomComponent={Label}\n\t\t\t\tFallback={<FieldLabel label={label} localized={localized} path={path} />}\n\t\t\t/>\n\t\t\t<RenderCustomComponent\n\t\t\t\tCustomComponent={Description}\n\t\t\t\tFallback={<FieldDescription description={description} path={path} />}\n\t\t\t/>\n\t\t\t{rows.length > 0 ? (\n\t\t\t\t<div className={`${baseClass}__rows`}>\n\t\t\t\t\t{rows.map((row, index) => (\n\t\t\t\t\t\t<div key={row.id} className={`${baseClass}__row`}>\n\t\t\t\t\t\t\t<RenderFields\n\t\t\t\t\t\t\t\tclassName={`${baseClass}__fields`}\n\t\t\t\t\t\t\t\tfields={fields}\n\t\t\t\t\t\t\t\tforceRender={forceRender}\n\t\t\t\t\t\t\t\tmargins={false}\n\t\t\t\t\t\t\t\tparentIndexPath=\"\"\n\t\t\t\t\t\t\t\tparentPath={`${path}.${index}`}\n\t\t\t\t\t\t\t\tparentSchemaPath={schemaPath}\n\t\t\t\t\t\t\t\tpermissions={permissions === true ? permissions : (permissions?.fields ?? {})}\n\t\t\t\t\t\t\t\treadOnly={readOnly}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t{!readOnly ? (\n\t\t\t\t\t\t\t\t<div className={`${baseClass}__remove`}>\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\t\t\t\t\ticon=\"x\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => removeFieldRow({ path, rowIndex: index })}\n\t\t\t\t\t\t\t\t\t\taria-label={t(keys.departmentRemoveRow)}\n\t\t\t\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{!readOnly ? (\n\t\t\t\t<div className={`${baseClass}__actions`}>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\t\tonClick={() => addFieldRow({ path, rowIndex: rows.length, schemaPath })}\n\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t>\n\t\t\t\t\t\t{t(keys.departmentAddRow)}\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;AAgBA,MAAM,YAAY;;;;;;;;;;AAWlB,MAAa,kBAA6C,UAAU;CACnE,MAAM,EACL,OAAO,EAAE,MAAM,OAAO,EAAE,gBAAgB,CAAC,GAAG,QAAQ,OAAO,aAC3D,cAAc,OACd,MAAM,eACN,aACA,UACA,YAAY,wBACT;CACJ,MAAM,aAAa,uBAAuB;CAE1C,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,EAAE,aAAa,mBAAmB,QAAQ;CAChD,MAAM,EACL,kBAAkB,EAAE,aAAa,UAAU,CAAC,GAC5C,MACA,OAAO,CAAC,MACL,SAAiB;EAAE,SAAS;EAAM,sBAAsB;CAAc,CAAC;CAE3E,OACC,qBAAC,OAAD;EAAK,WAAW,cAAc;EAAa,IAAI,SAAS,KAAK,QAAQ,OAAO,IAAI;YAAhF;GACC,oBAAC,uBAAD;IACC,iBAAiB;IACjB,UAAU,oBAAC,YAAD;KAAmB;KAAkB;KAAiB;IAAO,CAAA;GACvE,CAAA;GACD,oBAAC,uBAAD;IACC,iBAAiB;IACjB,UAAU,oBAAC,kBAAD;KAA+B;KAAmB;IAAO,CAAA;GACnE,CAAA;GACA,KAAK,SAAS,IACd,oBAAC,OAAD;IAAK,WAAW,GAAG,UAAU;cAC3B,KAAK,KAAK,KAAK,UACf,qBAAC,OAAD;KAAkB,WAAW,GAAG,UAAU;eAA1C,CACC,oBAAC,cAAD;MACC,WAAW,GAAG,UAAU;MAChB;MACK;MACb,SAAS;MACT,iBAAgB;MAChB,YAAY,GAAG,KAAK,GAAG;MACvB,kBAAkB;MAClB,aAAa,gBAAgB,OAAO,cAAe,aAAa,UAAU,CAAC;MACjE;KACV,CAAA,GACA,CAAC,WACD,oBAAC,OAAD;MAAK,WAAW,GAAG,UAAU;gBAC5B,oBAAC,QAAD;OACC,aAAY;OACZ,MAAK;OACL,eAAe,eAAe;QAAE;QAAM,UAAU;OAAM,CAAC;OACvD,cAAY,EAAE,KAAK,mBAAmB;OACtC,QAAQ;MACR,CAAA;KACG,CAAA,IACF,IACA;OAvBK,IAAI,EAuBT,CACL;GACG,CAAA,IACF;GACH,CAAC,WACD,oBAAC,OAAD;IAAK,WAAW,GAAG,UAAU;cAC5B,oBAAC,QAAD;KACC,aAAY;KACZ,MAAK;KACL,WAAU;KACV,cAAa;KACb,eAAe,YAAY;MAAE;MAAM,UAAU,KAAK;MAAQ;KAAW,CAAC;KACtE,QAAQ;eAEP,EAAE,KAAK,gBAAgB;IACjB,CAAA;GACJ,CAAA,IACF;EACA;;AAEP"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TranslationKey } from "../translations/keys.js";
|
|
2
|
+
|
|
3
|
+
//#region src/client/EndpointOptionsSelect.d.ts
|
|
4
|
+
/** Standard text-field client props plus this component's `clientProps`. */
|
|
5
|
+
type EndpointOptionsSelectProps = {
|
|
6
|
+
/** The field path within the document (Payload-injected, not the endpoint path). */path?: string;
|
|
7
|
+
field?: {
|
|
8
|
+
label?: unknown;
|
|
9
|
+
admin?: {
|
|
10
|
+
description?: unknown;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
label?: unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Endpoint subpath under the current document's collection API route; `'poll-options'` fetches
|
|
16
|
+
* `GET {routes.api}/{collectionSlug}/{id}/poll-options`. The endpoint must return
|
|
17
|
+
* `{ options: { label, value }[] }`.
|
|
18
|
+
*/
|
|
19
|
+
endpoint: string;
|
|
20
|
+
/**
|
|
21
|
+
* Field description as a translation key, resolved client-side. Payload drops `admin.description`
|
|
22
|
+
* functions from client fields, so a translated description must travel as a key; a static
|
|
23
|
+
* `admin.description` string still renders when this is unset.
|
|
24
|
+
*/
|
|
25
|
+
descriptionKey?: TranslationKey; /** Mirrors ReactSelect's `isClearable`; defaults to true. */
|
|
26
|
+
isClearable?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Render a multi-value select bound to a `string[]`, for a `hasMany` field (e.g. a poll outcome
|
|
29
|
+
* with tied winners). Default false: a single-value select bound to a `string`.
|
|
30
|
+
*/
|
|
31
|
+
isMulti?: boolean;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A select whose options load from a document-scoped plugin endpoint, for stored values whose
|
|
35
|
+
* valid choices only the server knows (e.g. a poll's source-resolved options). The pattern: pass
|
|
36
|
+
* `endpoint` via `clientProps`, the component reads the document id from `useDocumentInfo` and the
|
|
37
|
+
* API route from `useConfig`, fetches once per document with the admin cookie, and renders
|
|
38
|
+
* translated loading/error states. The stored value stays selectable even when it is missing from
|
|
39
|
+
* the fetched options (or the fetch failed), so opening an old document never silently drops data;
|
|
40
|
+
* unsaved documents skip the fetch since the server cannot resolve options for them yet.
|
|
41
|
+
*/
|
|
42
|
+
declare const EndpointOptionsSelect: (props: EndpointOptionsSelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { EndpointOptionsSelect };
|
|
45
|
+
//# sourceMappingURL=EndpointOptionsSelect.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { toStaticLabel } from "./toStaticLabel.js";
|
|
5
|
+
import { buildEndpointOptionsUrl, parseEndpointOptions } from "./endpointOptions.js";
|
|
6
|
+
import { FieldDescription, FieldLabel, ReactSelect, useConfig, useDocumentInfo, useField } from "@payloadcms/ui";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
//#region src/client/EndpointOptionsSelect.tsx
|
|
10
|
+
/**
|
|
11
|
+
* A select whose options load from a document-scoped plugin endpoint, for stored values whose
|
|
12
|
+
* valid choices only the server knows (e.g. a poll's source-resolved options). The pattern: pass
|
|
13
|
+
* `endpoint` via `clientProps`, the component reads the document id from `useDocumentInfo` and the
|
|
14
|
+
* API route from `useConfig`, fetches once per document with the admin cookie, and renders
|
|
15
|
+
* translated loading/error states. The stored value stays selectable even when it is missing from
|
|
16
|
+
* the fetched options (or the fetch failed), so opening an old document never silently drops data;
|
|
17
|
+
* unsaved documents skip the fetch since the server cannot resolve options for them yet.
|
|
18
|
+
*/
|
|
19
|
+
const EndpointOptionsSelect = (props) => {
|
|
20
|
+
const isMulti = props.isMulti === true;
|
|
21
|
+
const { path, setValue, value } = useField({ path: props.path });
|
|
22
|
+
const label = toStaticLabel(props.field?.label ?? props.label);
|
|
23
|
+
const { t } = useTranslation$1();
|
|
24
|
+
const description = props.descriptionKey ? t(props.descriptionKey) : toStaticLabel(props.field?.admin?.description);
|
|
25
|
+
const { id, collectionSlug } = useDocumentInfo();
|
|
26
|
+
const { config } = useConfig();
|
|
27
|
+
const apiRoute = config.routes.api;
|
|
28
|
+
const [state, setState] = useState({ status: "idle" });
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (id == null || !collectionSlug) return;
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const url = buildEndpointOptionsUrl({
|
|
33
|
+
apiRoute,
|
|
34
|
+
collectionSlug,
|
|
35
|
+
id,
|
|
36
|
+
endpoint: props.endpoint
|
|
37
|
+
});
|
|
38
|
+
setState({ status: "loading" });
|
|
39
|
+
fetch(url, {
|
|
40
|
+
credentials: "include",
|
|
41
|
+
headers: { Accept: "application/json" },
|
|
42
|
+
signal: controller.signal
|
|
43
|
+
}).then(async (response) => {
|
|
44
|
+
if (!response.ok) throw new Error(`Options request failed with ${response.status}`);
|
|
45
|
+
setState({
|
|
46
|
+
status: "loaded",
|
|
47
|
+
options: parseEndpointOptions(await response.json())
|
|
48
|
+
});
|
|
49
|
+
}).catch(() => {
|
|
50
|
+
if (!controller.signal.aborted) setState({ status: "error" });
|
|
51
|
+
});
|
|
52
|
+
return () => controller.abort();
|
|
53
|
+
}, [
|
|
54
|
+
apiRoute,
|
|
55
|
+
collectionSlug,
|
|
56
|
+
id,
|
|
57
|
+
props.endpoint
|
|
58
|
+
]);
|
|
59
|
+
const selectedValues = isMulti ? Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [] : typeof value === "string" && value.length > 0 ? [value] : [];
|
|
60
|
+
const options = state.status === "loaded" ? state.options : [];
|
|
61
|
+
const missing = selectedValues.filter((selected) => !options.some((option) => option.value === selected)).map((selected) => ({
|
|
62
|
+
label: selected,
|
|
63
|
+
value: selected
|
|
64
|
+
}));
|
|
65
|
+
const allOptions = missing.length > 0 ? [...options, ...missing] : options;
|
|
66
|
+
const selectedOptions = selectedValues.map((selected) => allOptions.find((option) => option.value === selected)).filter((option) => option !== void 0);
|
|
67
|
+
const handleChange = (selected) => {
|
|
68
|
+
if (isMulti) {
|
|
69
|
+
setValue((Array.isArray(selected) ? selected : selected ? [selected] : []).map((option) => option.value));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const chosen = Array.isArray(selected) ? selected[0] : selected;
|
|
73
|
+
setValue(chosen ? chosen.value : "");
|
|
74
|
+
};
|
|
75
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: "field-type",
|
|
77
|
+
style: { marginBlockEnd: "1rem" },
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
80
|
+
label,
|
|
81
|
+
path
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ jsx(ReactSelect, {
|
|
84
|
+
options: allOptions,
|
|
85
|
+
value: isMulti ? selectedOptions : selectedOptions[0] ?? void 0,
|
|
86
|
+
isMulti,
|
|
87
|
+
isClearable: props.isClearable !== false,
|
|
88
|
+
isLoading: state.status === "loading",
|
|
89
|
+
placeholder: state.status === "loading" ? t(keys.endpointOptionsLoading) : void 0,
|
|
90
|
+
showError: state.status === "error",
|
|
91
|
+
onChange: handleChange
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsx(FieldDescription, {
|
|
94
|
+
description: state.status === "error" ? t(keys.endpointOptionsError) : description,
|
|
95
|
+
path
|
|
96
|
+
})
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
export { EndpointOptionsSelect };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=EndpointOptionsSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EndpointOptionsSelect.js","names":["useTranslation"],"sources":["../../src/client/EndpointOptionsSelect.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tFieldDescription,\n\tFieldLabel,\n\tReactSelect,\n\ttype ReactSelectOption,\n\tuseConfig,\n\tuseDocumentInfo,\n\tuseField,\n} from '@payloadcms/ui'\nimport { useEffect, useState } from 'react'\nimport { keys, type TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport {\n\tbuildEndpointOptionsUrl,\n\ttype EndpointOption,\n\tparseEndpointOptions,\n} from './endpointOptions'\nimport { toStaticLabel } from './toStaticLabel'\n\n/** Standard text-field client props plus this component's `clientProps`. */\nexport type EndpointOptionsSelectProps = {\n\t/** The field path within the document (Payload-injected, not the endpoint path). */\n\tpath?: string\n\tfield?: { label?: unknown; admin?: { description?: unknown } }\n\tlabel?: unknown\n\t/**\n\t * Endpoint subpath under the current document's collection API route; `'poll-options'` fetches\n\t * `GET {routes.api}/{collectionSlug}/{id}/poll-options`. The endpoint must return\n\t * `{ options: { label, value }[] }`.\n\t */\n\tendpoint: string\n\t/**\n\t * Field description as a translation key, resolved client-side. Payload drops `admin.description`\n\t * functions from client fields, so a translated description must travel as a key; a static\n\t * `admin.description` string still renders when this is unset.\n\t */\n\tdescriptionKey?: TranslationKey\n\t/** Mirrors ReactSelect's `isClearable`; defaults to true. */\n\tisClearable?: boolean\n\t/**\n\t * Render a multi-value select bound to a `string[]`, for a `hasMany` field (e.g. a poll outcome\n\t * with tied winners). Default false: a single-value select bound to a `string`.\n\t */\n\tisMulti?: boolean\n}\n\ntype FetchState =\n\t| { status: 'error' | 'idle' | 'loading'; options?: never }\n\t| { status: 'loaded'; options: EndpointOption[] }\n\n/**\n * A select whose options load from a document-scoped plugin endpoint, for stored values whose\n * valid choices only the server knows (e.g. a poll's source-resolved options). The pattern: pass\n * `endpoint` via `clientProps`, the component reads the document id from `useDocumentInfo` and the\n * API route from `useConfig`, fetches once per document with the admin cookie, and renders\n * translated loading/error states. The stored value stays selectable even when it is missing from\n * the fetched options (or the fetch failed), so opening an old document never silently drops data;\n * unsaved documents skip the fetch since the server cannot resolve options for them yet.\n */\nexport const EndpointOptionsSelect = (props: EndpointOptionsSelectProps) => {\n\tconst isMulti = props.isMulti === true\n\tconst { path, setValue, value } = useField<string | string[]>({ path: props.path })\n\tconst label = toStaticLabel(props.field?.label ?? props.label)\n\tconst { t } = useTranslation()\n\tconst description = props.descriptionKey\n\t\t? t(props.descriptionKey)\n\t\t: toStaticLabel(props.field?.admin?.description)\n\tconst { id, collectionSlug } = useDocumentInfo()\n\tconst { config } = useConfig()\n\tconst apiRoute = config.routes.api\n\tconst [state, setState] = useState<FetchState>({ status: 'idle' })\n\n\tuseEffect(() => {\n\t\tif (id == null || !collectionSlug) {\n\t\t\treturn\n\t\t}\n\t\tconst controller = new AbortController()\n\t\tconst url = buildEndpointOptionsUrl({\n\t\t\tapiRoute,\n\t\t\tcollectionSlug,\n\t\t\tid,\n\t\t\tendpoint: props.endpoint,\n\t\t})\n\t\tsetState({ status: 'loading' })\n\t\tfetch(url, {\n\t\t\tcredentials: 'include',\n\t\t\theaders: { Accept: 'application/json' },\n\t\t\tsignal: controller.signal,\n\t\t})\n\t\t\t.then(async (response) => {\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Options request failed with ${response.status}`)\n\t\t\t\t}\n\t\t\t\tsetState({ status: 'loaded', options: parseEndpointOptions(await response.json()) })\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tif (!controller.signal.aborted) {\n\t\t\t\t\tsetState({ status: 'error' })\n\t\t\t\t}\n\t\t\t})\n\t\treturn () => controller.abort()\n\t}, [apiRoute, collectionSlug, id, props.endpoint])\n\n\t// A missing fetched option (or a failed fetch) must never silently drop a stored value, so every\n\t// selected value the options don't cover is kept selectable by its own label.\n\tconst selectedValues: string[] = isMulti\n\t\t? Array.isArray(value)\n\t\t\t? value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0)\n\t\t\t: []\n\t\t: typeof value === 'string' && value.length > 0\n\t\t\t? [value]\n\t\t\t: []\n\tconst options: ReactSelectOption[] = state.status === 'loaded' ? state.options : []\n\tconst missing = selectedValues\n\t\t.filter((selected) => !options.some((option) => option.value === selected))\n\t\t.map((selected) => ({ label: selected, value: selected }))\n\tconst allOptions = missing.length > 0 ? [...options, ...missing] : options\n\tconst selectedOptions = selectedValues\n\t\t.map((selected) => allOptions.find((option) => option.value === selected))\n\t\t.filter((option): option is ReactSelectOption => option !== undefined)\n\n\tconst handleChange = (selected: ReactSelectOption | ReactSelectOption[] | null) => {\n\t\tif (isMulti) {\n\t\t\tconst chosen = Array.isArray(selected) ? selected : selected ? [selected] : []\n\t\t\tsetValue(chosen.map((option) => option.value as string))\n\t\t\treturn\n\t\t}\n\t\tconst chosen = Array.isArray(selected) ? selected[0] : selected\n\t\tsetValue(chosen ? (chosen.value as string) : '')\n\t}\n\n\treturn (\n\t\t<div className=\"field-type\" style={{ marginBlockEnd: '1rem' }}>\n\t\t\t<FieldLabel label={label} path={path} />\n\t\t\t<ReactSelect\n\t\t\t\toptions={allOptions}\n\t\t\t\tvalue={isMulti ? selectedOptions : (selectedOptions[0] ?? undefined)}\n\t\t\t\tisMulti={isMulti}\n\t\t\t\tisClearable={props.isClearable !== false}\n\t\t\t\tisLoading={state.status === 'loading'}\n\t\t\t\tplaceholder={state.status === 'loading' ? t(keys.endpointOptionsLoading) : undefined}\n\t\t\t\tshowError={state.status === 'error'}\n\t\t\t\tonChange={handleChange}\n\t\t\t/>\n\t\t\t<FieldDescription\n\t\t\t\tdescription={state.status === 'error' ? t(keys.endpointOptionsError) : description}\n\t\t\t\tpath={path}\n\t\t\t/>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6DA,MAAa,yBAAyB,UAAsC;CAC3E,MAAM,UAAU,MAAM,YAAY;CAClC,MAAM,EAAE,MAAM,UAAU,UAAU,SAA4B,EAAE,MAAM,MAAM,KAAK,CAAC;CAClF,MAAM,QAAQ,cAAc,MAAM,OAAO,SAAS,MAAM,KAAK;CAC7D,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,cAAc,MAAM,iBACvB,EAAE,MAAM,cAAc,IACtB,cAAc,MAAM,OAAO,OAAO,WAAW;CAChD,MAAM,EAAE,IAAI,mBAAmB,gBAAgB;CAC/C,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,WAAW,OAAO,OAAO;CAC/B,MAAM,CAAC,OAAO,YAAY,SAAqB,EAAE,QAAQ,OAAO,CAAC;CAEjE,gBAAgB;EACf,IAAI,MAAM,QAAQ,CAAC,gBAClB;EAED,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,MAAM,wBAAwB;GACnC;GACA;GACA;GACA,UAAU,MAAM;EACjB,CAAC;EACD,SAAS,EAAE,QAAQ,UAAU,CAAC;EAC9B,MAAM,KAAK;GACV,aAAa;GACb,SAAS,EAAE,QAAQ,mBAAmB;GACtC,QAAQ,WAAW;EACpB,CAAC,EACC,KAAK,OAAO,aAAa;GACzB,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MAAM,+BAA+B,SAAS,QAAQ;GAEjE,SAAS;IAAE,QAAQ;IAAU,SAAS,qBAAqB,MAAM,SAAS,KAAK,CAAC;GAAE,CAAC;EACpF,CAAC,EACA,YAAY;GACZ,IAAI,CAAC,WAAW,OAAO,SACtB,SAAS,EAAE,QAAQ,QAAQ,CAAC;EAE9B,CAAC;EACF,aAAa,WAAW,MAAM;CAC/B,GAAG;EAAC;EAAU;EAAgB;EAAI,MAAM;CAAQ,CAAC;CAIjD,MAAM,iBAA2B,UAC9B,MAAM,QAAQ,KAAK,IAClB,MAAM,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC,IACtF,CAAC,IACF,OAAO,UAAU,YAAY,MAAM,SAAS,IAC3C,CAAC,KAAK,IACN,CAAC;CACL,MAAM,UAA+B,MAAM,WAAW,WAAW,MAAM,UAAU,CAAC;CAClF,MAAM,UAAU,eACd,QAAQ,aAAa,CAAC,QAAQ,MAAM,WAAW,OAAO,UAAU,QAAQ,CAAC,EACzE,KAAK,cAAc;EAAE,OAAO;EAAU,OAAO;CAAS,EAAE;CAC1D,MAAM,aAAa,QAAQ,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,OAAO,IAAI;CACnE,MAAM,kBAAkB,eACtB,KAAK,aAAa,WAAW,MAAM,WAAW,OAAO,UAAU,QAAQ,CAAC,EACxE,QAAQ,WAAwC,WAAW,KAAA,CAAS;CAEtE,MAAM,gBAAgB,aAA6D;EAClF,IAAI,SAAS;GAEZ,UADe,MAAM,QAAQ,QAAQ,IAAI,WAAW,WAAW,CAAC,QAAQ,IAAI,CAAC,GAC7D,KAAK,WAAW,OAAO,KAAe,CAAC;GACvD;EACD;EACA,MAAM,SAAS,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK;EACvD,SAAS,SAAU,OAAO,QAAmB,EAAE;CAChD;CAEA,OACC,qBAAC,OAAD;EAAK,WAAU;EAAa,OAAO,EAAE,gBAAgB,OAAO;YAA5D;GACC,oBAAC,YAAD;IAAmB;IAAa;GAAO,CAAA;GACvC,oBAAC,aAAD;IACC,SAAS;IACT,OAAO,UAAU,kBAAmB,gBAAgB,MAAM,KAAA;IACjD;IACT,aAAa,MAAM,gBAAgB;IACnC,WAAW,MAAM,WAAW;IAC5B,aAAa,MAAM,WAAW,YAAY,EAAE,KAAK,sBAAsB,IAAI,KAAA;IAC3E,WAAW,MAAM,WAAW;IAC5B,UAAU;GACV,CAAA;GACD,oBAAC,kBAAD;IACC,aAAa,MAAM,WAAW,UAAU,EAAE,KAAK,oBAAoB,IAAI;IACjE;GACN,CAAA;EACG;;AAEP"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/client/FieldBlockLabel.d.ts
|
|
2
|
+
/** Props threaded from each field block's `admin.components.Label` clientProps. */
|
|
3
|
+
type FieldBlockLabelProps = {
|
|
4
|
+
/** The block's type-label key (e.g. `keys.fieldTypeText`), shown while the field has no label. */typeLabelKey?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Row header for a field block. Payload's native collapsed-row header ends in the editable
|
|
8
|
+
* `blockName`, which reads "Untitled" until someone fills it; a field already carries a `label`, so
|
|
9
|
+
* this shows that instead, giving each row a "01 Text Full name" heading. Falls back to the block's
|
|
10
|
+
* type label while the field is still unnamed. Because a custom block label replaces the whole native
|
|
11
|
+
* header, it re-emits the row number and type the default would otherwise render.
|
|
12
|
+
*/
|
|
13
|
+
declare const FieldBlockLabel: ({
|
|
14
|
+
typeLabelKey
|
|
15
|
+
}: FieldBlockLabelProps) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { FieldBlockLabel };
|
|
18
|
+
//# sourceMappingURL=FieldBlockLabel.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
3
|
+
import { useRowLabel } from "@payloadcms/ui";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/client/FieldBlockLabel.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Row header for a field block. Payload's native collapsed-row header ends in the editable
|
|
8
|
+
* `blockName`, which reads "Untitled" until someone fills it; a field already carries a `label`, so
|
|
9
|
+
* this shows that instead, giving each row a "01 Text Full name" heading. Falls back to the block's
|
|
10
|
+
* type label while the field is still unnamed. Because a custom block label replaces the whole native
|
|
11
|
+
* header, it re-emits the row number and type the default would otherwise render.
|
|
12
|
+
*/
|
|
13
|
+
const FieldBlockLabel = ({ typeLabelKey }) => {
|
|
14
|
+
const { data, rowNumber } = useRowLabel();
|
|
15
|
+
const { t } = useTranslation$1();
|
|
16
|
+
const typeLabel = typeLabelKey ? t(typeLabelKey) : "";
|
|
17
|
+
const fieldLabel = typeof data?.label === "string" && data.label.trim().length > 0 ? data.label.trim() : "";
|
|
18
|
+
const number = String((rowNumber ?? 0) + 1).padStart(2, "0");
|
|
19
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
20
|
+
style: {
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
display: "inline-flex",
|
|
23
|
+
gap: "0.5rem"
|
|
24
|
+
},
|
|
25
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
26
|
+
style: { color: "var(--theme-elevation-500)" },
|
|
27
|
+
children: typeLabel ? `${number} ${typeLabel}` : number
|
|
28
|
+
}), fieldLabel ? /* @__PURE__ */ jsx("span", { children: fieldLabel }) : null]
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { FieldBlockLabel };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=FieldBlockLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FieldBlockLabel.js","names":["useTranslation"],"sources":["../../src/client/FieldBlockLabel.tsx"],"sourcesContent":["'use client'\n\nimport { useRowLabel } from '@payloadcms/ui'\nimport type { TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/** Props threaded from each field block's `admin.components.Label` clientProps. */\nexport type FieldBlockLabelProps = {\n\t/** The block's type-label key (e.g. `keys.fieldTypeText`), shown while the field has no label. */\n\ttypeLabelKey?: string\n}\n\ntype RowData = { label?: unknown }\n\n/**\n * Row header for a field block. Payload's native collapsed-row header ends in the editable\n * `blockName`, which reads \"Untitled\" until someone fills it; a field already carries a `label`, so\n * this shows that instead, giving each row a \"01 Text Full name\" heading. Falls back to the block's\n * type label while the field is still unnamed. Because a custom block label replaces the whole native\n * header, it re-emits the row number and type the default would otherwise render.\n */\nexport const FieldBlockLabel = ({ typeLabelKey }: FieldBlockLabelProps) => {\n\tconst { data, rowNumber } = useRowLabel<RowData>()\n\tconst { t } = useTranslation()\n\tconst typeLabel = typeLabelKey ? t(typeLabelKey as TranslationKey) : ''\n\tconst fieldLabel =\n\t\ttypeof data?.label === 'string' && data.label.trim().length > 0 ? data.label.trim() : ''\n\tconst number = String((rowNumber ?? 0) + 1).padStart(2, '0')\n\treturn (\n\t\t<span style={{ alignItems: 'center', display: 'inline-flex', gap: '0.5rem' }}>\n\t\t\t<span style={{ color: 'var(--theme-elevation-500)' }}>\n\t\t\t\t{typeLabel ? `${number} ${typeLabel}` : number}\n\t\t\t</span>\n\t\t\t{fieldLabel ? <span>{fieldLabel}</span> : null}\n\t\t</span>\n\t)\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAa,mBAAmB,EAAE,mBAAyC;CAC1E,MAAM,EAAE,MAAM,cAAc,YAAqB;CACjD,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,YAAY,eAAe,EAAE,YAA8B,IAAI;CACrE,MAAM,aACL,OAAO,MAAM,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,IAAI,KAAK,MAAM,KAAK,IAAI;CACvF,MAAM,SAAS,QAAQ,aAAa,KAAK,CAAC,EAAE,SAAS,GAAG,GAAG;CAC3D,OACC,qBAAC,QAAD;EAAM,OAAO;GAAE,YAAY;GAAU,SAAS;GAAe,KAAK;EAAS;YAA3E,CACC,oBAAC,QAAD;GAAM,OAAO,EAAE,OAAO,6BAA6B;aACjD,YAAY,GAAG,OAAO,GAAG,cAAc;EACnC,CAAA,GACL,aAAa,oBAAC,QAAD,EAAA,UAAO,WAAiB,CAAA,IAAI,IACrC;;AAER"}
|
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import { TranslationKey } from "../translations/keys.js";
|
|
2
|
+
|
|
1
3
|
//#region src/client/FieldNameSelect.d.ts
|
|
2
4
|
/** Props: standard JSON/text field client props plus the `types` allow-list we pass via clientProps. */
|
|
3
5
|
type FieldNameSelectProps = {
|
|
4
6
|
path?: string;
|
|
5
7
|
field?: {
|
|
6
8
|
label?: unknown;
|
|
9
|
+
admin?: {
|
|
10
|
+
description?: unknown;
|
|
11
|
+
};
|
|
7
12
|
};
|
|
8
|
-
label?: unknown;
|
|
9
|
-
|
|
13
|
+
label?: unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Sibling `fields` blocks whose `blockType` is in this list populate the select's options. Omit to
|
|
16
|
+
* offer every named field regardless of type (a general field-target picker).
|
|
17
|
+
*/
|
|
18
|
+
types?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Field description as a translation key, resolved client-side. Payload drops `admin.description`
|
|
21
|
+
* functions from client fields, so a translated description must travel as a key; a static
|
|
22
|
+
* `admin.description` string still renders when this is unset.
|
|
23
|
+
*/
|
|
24
|
+
descriptionKey?: TranslationKey;
|
|
10
25
|
};
|
|
11
26
|
/**
|
|
12
27
|
* The `Field` mounted on a text config field (e.g. an action's `toField`) that should be authored
|
|
13
28
|
* by picking from the form's own fields rather than free-typing a name. Options come from the
|
|
14
|
-
* sibling `fields` blocks array, filtered to `types
|
|
15
|
-
* it no longer matches any field, so switching to this
|
|
29
|
+
* sibling `fields` blocks array, filtered to `types` when given (every named field otherwise); the
|
|
30
|
+
* stored value is kept selectable even if it no longer matches any field, so switching to this
|
|
31
|
+
* component never silently drops data.
|
|
16
32
|
*/
|
|
17
33
|
declare const FieldNameSelect: (props: FieldNameSelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
34
|
//#endregion
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { fieldNamesOfType } from "../fields/fieldNamesOfType.js";
|
|
2
|
+
import { fieldNames, fieldNamesOfType } from "../fields/fieldNamesOfType.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
3
4
|
import { toStaticLabel } from "./toStaticLabel.js";
|
|
4
5
|
import { reduceFieldsToValues } from "payload/shared";
|
|
5
|
-
import { FieldLabel, ReactSelect, useField, useFormFields } from "@payloadcms/ui";
|
|
6
|
+
import { FieldDescription, FieldLabel, ReactSelect, useField, useFormFields } from "@payloadcms/ui";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { useMemo } from "react";
|
|
8
9
|
//#region src/client/FieldNameSelect.tsx
|
|
@@ -14,7 +15,7 @@ const optionsFromData = (data, types) => {
|
|
|
14
15
|
const { name, label } = row;
|
|
15
16
|
if (typeof name === "string" && typeof label === "string" && label.length > 0) labels.set(name.trim(), label);
|
|
16
17
|
}
|
|
17
|
-
return fieldNamesOfType(data.fields, types).map((name) => ({
|
|
18
|
+
return (types ? fieldNamesOfType(data.fields, types) : fieldNames(data.fields)).map((name) => ({
|
|
18
19
|
label: labels.get(name) ?? name,
|
|
19
20
|
value: name
|
|
20
21
|
}));
|
|
@@ -22,12 +23,15 @@ const optionsFromData = (data, types) => {
|
|
|
22
23
|
/**
|
|
23
24
|
* The `Field` mounted on a text config field (e.g. an action's `toField`) that should be authored
|
|
24
25
|
* by picking from the form's own fields rather than free-typing a name. Options come from the
|
|
25
|
-
* sibling `fields` blocks array, filtered to `types
|
|
26
|
-
* it no longer matches any field, so switching to this
|
|
26
|
+
* sibling `fields` blocks array, filtered to `types` when given (every named field otherwise); the
|
|
27
|
+
* stored value is kept selectable even if it no longer matches any field, so switching to this
|
|
28
|
+
* component never silently drops data.
|
|
27
29
|
*/
|
|
28
30
|
const FieldNameSelect = (props) => {
|
|
29
31
|
const { path, setValue, value } = useField({ path: props.path });
|
|
30
32
|
const label = toStaticLabel(props.field?.label ?? props.label);
|
|
33
|
+
const { t } = useTranslation$1();
|
|
34
|
+
const description = props.descriptionKey ? t(props.descriptionKey) : toStaticLabel(props.field?.admin?.description);
|
|
31
35
|
const optionsJson = useFormFields(([fields]) => JSON.stringify(optionsFromData(reduceFieldsToValues(fields, true), props.types)));
|
|
32
36
|
const options = useMemo(() => JSON.parse(optionsJson), [optionsJson]);
|
|
33
37
|
const allOptions = value && !options.some((option) => option.value === value) ? [...options, {
|
|
@@ -41,15 +45,22 @@ const FieldNameSelect = (props) => {
|
|
|
41
45
|
return /* @__PURE__ */ jsxs("div", {
|
|
42
46
|
className: "field-type",
|
|
43
47
|
style: { marginBlockEnd: "1rem" },
|
|
44
|
-
children: [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
50
|
+
label,
|
|
51
|
+
path
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsx(ReactSelect, {
|
|
54
|
+
options: allOptions,
|
|
55
|
+
value: allOptions.find((option) => option.value === value) ?? void 0,
|
|
56
|
+
isClearable: true,
|
|
57
|
+
onChange: handleChange
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ jsx(FieldDescription, {
|
|
60
|
+
description,
|
|
61
|
+
path
|
|
62
|
+
})
|
|
63
|
+
]
|
|
53
64
|
});
|
|
54
65
|
};
|
|
55
66
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldNameSelect.js","names":[],"sources":["../../src/client/FieldNameSelect.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tFieldLabel,\n\tReactSelect,\n\ttype ReactSelectOption,\n\tuseField,\n\tuseFormFields,\n} from '@payloadcms/ui'\nimport { reduceFieldsToValues } from 'payload/shared'\nimport { useMemo } from 'react'\nimport { fieldNamesOfType } from '../fields/fieldNamesOfType'\nimport type { FieldRow } from './synthesizeClientField'\nimport { toStaticLabel } from './toStaticLabel'\n\n/** Props: standard JSON/text field client props plus the `types` allow-list we pass via clientProps. */\nexport type FieldNameSelectProps = {\n\tpath?: string\n\tfield?: { label?: unknown }\n\tlabel?: unknown\n\t
|
|
1
|
+
{"version":3,"file":"FieldNameSelect.js","names":["useTranslation"],"sources":["../../src/client/FieldNameSelect.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tFieldDescription,\n\tFieldLabel,\n\tReactSelect,\n\ttype ReactSelectOption,\n\tuseField,\n\tuseFormFields,\n} from '@payloadcms/ui'\nimport { reduceFieldsToValues } from 'payload/shared'\nimport { useMemo } from 'react'\nimport { fieldNames, fieldNamesOfType } from '../fields/fieldNamesOfType'\nimport type { TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport type { FieldRow } from './synthesizeClientField'\nimport { toStaticLabel } from './toStaticLabel'\n\n/** Props: standard JSON/text field client props plus the `types` allow-list we pass via clientProps. */\nexport type FieldNameSelectProps = {\n\tpath?: string\n\tfield?: { label?: unknown; admin?: { description?: unknown } }\n\tlabel?: unknown\n\t/**\n\t * Sibling `fields` blocks whose `blockType` is in this list populate the select's options. Omit to\n\t * offer every named field regardless of type (a general field-target picker).\n\t */\n\ttypes?: string[]\n\t/**\n\t * Field description as a translation key, resolved client-side. Payload drops `admin.description`\n\t * functions from client fields, so a translated description must travel as a key; a static\n\t * `admin.description` string still renders when this is unset.\n\t */\n\tdescriptionKey?: TranslationKey\n}\n\nconst optionsFromData = (data: Record<string, unknown>, types?: string[]): ReactSelectOption[] => {\n\tconst rows = Array.isArray(data.fields) ? (data.fields as unknown[]) : []\n\tconst labels = new Map<string, string>()\n\tfor (const row of rows) {\n\t\tif (!row || typeof row !== 'object') {\n\t\t\tcontinue\n\t\t}\n\t\tconst { name, label } = row as FieldRow\n\t\tif (typeof name === 'string' && typeof label === 'string' && label.length > 0) {\n\t\t\tlabels.set(name.trim(), label)\n\t\t}\n\t}\n\tconst names = types ? fieldNamesOfType(data.fields, types) : fieldNames(data.fields)\n\treturn names.map((name) => ({\n\t\tlabel: labels.get(name) ?? name,\n\t\tvalue: name,\n\t}))\n}\n\n/**\n * The `Field` mounted on a text config field (e.g. an action's `toField`) that should be authored\n * by picking from the form's own fields rather than free-typing a name. Options come from the\n * sibling `fields` blocks array, filtered to `types` when given (every named field otherwise); the\n * stored value is kept selectable even if it no longer matches any field, so switching to this\n * component never silently drops data.\n */\nexport const FieldNameSelect = (props: FieldNameSelectProps) => {\n\tconst { path, setValue, value } = useField<string>({ path: props.path })\n\tconst label = toStaticLabel(props.field?.label ?? props.label)\n\tconst { t } = useTranslation()\n\tconst description = props.descriptionKey\n\t\t? t(props.descriptionKey)\n\t\t: toStaticLabel(props.field?.admin?.description)\n\n\tconst optionsJson = useFormFields(([fields]) =>\n\t\tJSON.stringify(optionsFromData(reduceFieldsToValues(fields, true), props.types))\n\t)\n\tconst options = useMemo(() => JSON.parse(optionsJson) as ReactSelectOption[], [optionsJson])\n\n\tconst allOptions =\n\t\tvalue && !options.some((option) => option.value === value)\n\t\t\t? [...options, { label: value, value }]\n\t\t\t: options\n\n\tconst handleChange = (selected: ReactSelectOption | ReactSelectOption[] | null) => {\n\t\tconst chosen = Array.isArray(selected) ? selected[0] : selected\n\t\tsetValue(chosen ? (chosen.value as string) : '')\n\t}\n\n\treturn (\n\t\t<div className=\"field-type\" style={{ marginBlockEnd: '1rem' }}>\n\t\t\t<FieldLabel label={label} path={path} />\n\t\t\t<ReactSelect\n\t\t\t\toptions={allOptions}\n\t\t\t\tvalue={allOptions.find((option) => option.value === value) ?? undefined}\n\t\t\t\tisClearable\n\t\t\t\tonChange={handleChange}\n\t\t\t/>\n\t\t\t<FieldDescription description={description} path={path} />\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;;AAoCA,MAAM,mBAAmB,MAA+B,UAA0C;CACjG,MAAM,OAAO,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAuB,CAAC;CACxE,MAAM,yBAAS,IAAI,IAAoB;CACvC,KAAK,MAAM,OAAO,MAAM;EACvB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAC1B;EAED,MAAM,EAAE,MAAM,UAAU;EACxB,IAAI,OAAO,SAAS,YAAY,OAAO,UAAU,YAAY,MAAM,SAAS,GAC3E,OAAO,IAAI,KAAK,KAAK,GAAG,KAAK;CAE/B;CAEA,QADc,QAAQ,iBAAiB,KAAK,QAAQ,KAAK,IAAI,WAAW,KAAK,MAAM,GACtE,KAAK,UAAU;EAC3B,OAAO,OAAO,IAAI,IAAI,KAAK;EAC3B,OAAO;CACR,EAAE;AACH;;;;;;;;AASA,MAAa,mBAAmB,UAAgC;CAC/D,MAAM,EAAE,MAAM,UAAU,UAAU,SAAiB,EAAE,MAAM,MAAM,KAAK,CAAC;CACvE,MAAM,QAAQ,cAAc,MAAM,OAAO,SAAS,MAAM,KAAK;CAC7D,MAAM,EAAE,MAAMA,iBAAe;CAC7B,MAAM,cAAc,MAAM,iBACvB,EAAE,MAAM,cAAc,IACtB,cAAc,MAAM,OAAO,OAAO,WAAW;CAEhD,MAAM,cAAc,eAAe,CAAC,YACnC,KAAK,UAAU,gBAAgB,qBAAqB,QAAQ,IAAI,GAAG,MAAM,KAAK,CAAC,CAChF;CACA,MAAM,UAAU,cAAc,KAAK,MAAM,WAAW,GAA0B,CAAC,WAAW,CAAC;CAE3F,MAAM,aACL,SAAS,CAAC,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,IACtD,CAAC,GAAG,SAAS;EAAE,OAAO;EAAO;CAAM,CAAC,IACpC;CAEJ,MAAM,gBAAgB,aAA6D;EAClF,MAAM,SAAS,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK;EACvD,SAAS,SAAU,OAAO,QAAmB,EAAE;CAChD;CAEA,OACC,qBAAC,OAAD;EAAK,WAAU;EAAa,OAAO,EAAE,gBAAgB,OAAO;YAA5D;GACC,oBAAC,YAAD;IAAmB;IAAa;GAAO,CAAA;GACvC,oBAAC,aAAD;IACC,SAAS;IACT,OAAO,WAAW,MAAM,WAAW,OAAO,UAAU,KAAK,KAAK,KAAA;IAC9D,aAAA;IACA,UAAU;GACV,CAAA;GACD,oBAAC,kBAAD;IAA+B;IAAmB;GAAO,CAAA;EACrD;;AAEP"}
|
|
@@ -6,7 +6,8 @@ type FlowBuilderProps = {
|
|
|
6
6
|
label?: unknown;
|
|
7
7
|
};
|
|
8
8
|
label?: unknown;
|
|
9
|
-
conditionTypes: Record<string, ConditionFieldType>;
|
|
9
|
+
conditionTypes: Record<string, ConditionFieldType>; /** Bare (nameless) block slugs to their display label: an i18n key or a literal. */
|
|
10
|
+
bareTypeLabels?: Record<string, string>;
|
|
10
11
|
};
|
|
11
12
|
declare const FlowBuilder: (props: FlowBuilderProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
13
|
//#endregion
|