@10x-media/form-builder 0.1.0-beta.2 → 0.1.0-beta.3
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 +18 -0
- package/README.md +2 -2
- 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 +35 -0
- package/dist/actions/body/serializeBody.js +76 -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/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/builtin/confirmation.js +28 -12
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailTeam.js +7 -9
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/signedWebhook.js +21 -3
- package/dist/actions/builtin/signedWebhook.js.map +1 -1
- package/dist/actions/defineAction.d.ts +2 -1
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/dispatch.js +2 -1
- package/dist/actions/dispatch.js.map +1 -1
- package/dist/actions/runActions.js +8 -1
- package/dist/actions/runActions.js.map +1 -1
- package/dist/actions/task.js +7 -5
- package/dist/actions/task.js.map +1 -1
- package/dist/calc/normalizeCalc.js +5 -17
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/types.d.ts +8 -2
- package/dist/calc/types.js +22 -0
- package/dist/calc/types.js.map +1 -0
- package/dist/client/ByteSizeField.d.ts +21 -0
- package/dist/client/ByteSizeField.js +56 -0
- package/dist/client/ByteSizeField.js.map +1 -0
- package/dist/client/ConditionBuilder.js +2 -1
- package/dist/client/ConditionBuilder.js.map +1 -1
- package/dist/client/FieldNameSelect.d.ts +20 -0
- package/dist/client/FieldNameSelect.js +58 -0
- package/dist/client/FieldNameSelect.js.map +1 -0
- package/dist/client/FlowBuilder.d.ts +0 -1
- package/dist/client/FlowBuilder.js +4 -6
- 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/admin.css +192 -0
- package/dist/client/toStaticLabel.js +13 -0
- package/dist/client/toStaticLabel.js.map +1 -0
- package/dist/collections/formSubmissions.js +5 -3
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +74 -57
- package/dist/collections/forms.js.map +1 -1
- package/dist/exports/client.d.ts +3 -1
- package/dist/exports/client.js +3 -1
- package/dist/exports/react.d.ts +6 -1
- package/dist/exports/react.js +5 -1
- package/dist/fields/buildFieldBlocks.js +14 -18
- package/dist/fields/buildFieldBlocks.js.map +1 -1
- package/dist/fields/builtin/calculation.js +115 -2
- package/dist/fields/builtin/calculation.js.map +1 -1
- package/dist/fields/builtin/date.js +29 -0
- package/dist/fields/builtin/date.js.map +1 -0
- package/dist/fields/builtin/file.d.ts +15 -0
- package/dist/fields/builtin/file.js +79 -3
- package/dist/fields/builtin/file.js.map +1 -1
- package/dist/fields/builtin/index.d.ts +14 -0
- package/dist/fields/builtin/index.js +11 -2
- package/dist/fields/builtin/index.js.map +1 -1
- package/dist/fields/fieldNamesOfType.js +25 -0
- package/dist/fields/fieldNamesOfType.js.map +1 -0
- package/dist/fields/sharedConfig.js +30 -18
- package/dist/fields/sharedConfig.js.map +1 -1
- package/dist/index.d.ts +19 -2
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/dist/plugin/registerCollections.js +3 -2
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/react/Form.js +3 -3
- package/dist/react/Form.js.map +1 -1
- package/dist/react/Poll.js +1 -1
- package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -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 +41 -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 +28 -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 +45 -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/primitives/Input.d.ts +1 -1
- package/dist/react/primitives/Input.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -1
- package/dist/react/renderers/checkbox.js +1 -1
- package/dist/react/renderers/consent.js +1 -1
- package/dist/react/renderers/date.js +37 -0
- package/dist/react/renderers/date.js.map +1 -0
- package/dist/react/renderers/email.js +1 -1
- package/dist/react/renderers/file.js +20 -7
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/index.js +2 -0
- package/dist/react/renderers/index.js.map +1 -1
- package/dist/react/renderers/number.js +1 -1
- package/dist/react/renderers/repeater.js +1 -1
- package/dist/react/renderers/select.js +1 -1
- package/dist/react/renderers/text.js +1 -1
- package/dist/react/renderers/textarea.js +1 -1
- package/dist/spam/captcha.d.ts +3 -2
- package/dist/spam/captcha.js +3 -2
- 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/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/spamGuard.js +2 -5
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +2 -2
- package/dist/translations/en.js +26 -4
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +26 -4
- package/dist/translations/keys.js +26 -4
- package/dist/translations/keys.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 +1 -1
- package/dist/uploads/resolveFileRef.js.map +1 -1
- package/dist/validation/builtin/index.d.ts +14 -0
- package/dist/validation/builtin/index.js +12 -1
- package/dist/validation/builtin/index.js.map +1 -1
- package/dist/validation/builtin/maxDate.js +23 -0
- package/dist/validation/builtin/maxDate.js.map +1 -0
- package/dist/validation/builtin/minDate.js +23 -0
- package/dist/validation/builtin/minDate.js.map +1 -0
- package/dist/validation/builtin/validateDateBound.js +18 -0
- package/dist/validation/builtin/validateDateBound.js.map +1 -0
- package/package.json +3 -2
- package/styles/form-builder.css +0 -190
package/dist/actions/dispatch.js
CHANGED
|
@@ -36,7 +36,8 @@ const dispatchActions = async (args) => {
|
|
|
36
36
|
},
|
|
37
37
|
registry,
|
|
38
38
|
payload,
|
|
39
|
-
req
|
|
39
|
+
req,
|
|
40
|
+
richText: args.richText
|
|
40
41
|
}).catch((error) => {
|
|
41
42
|
payload.logger?.error(`@10x-media/form-builder: inline action dispatch for submission ${String(submissionId)} threw: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
43
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","names":[],"sources":["../../src/actions/dispatch.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { ACTIONS_TASK_SLUG, runActionsForSubmission } from './task'\n\n/** Default cap on inline action work so a missing worker never hangs the submission response. */\nexport const INLINE_DISPATCH_DEADLINE_MS = 5_000\n\nexport type DispatchActionsArgs = {\n\tactions: ActionInstance[] | null | undefined\n\tformId: number | string\n\tsubmissionId: number | string\n\tregistry: ActionRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** Whether a job runner is likely present (queued path); otherwise the bounded-inline fallback runs. */\n\thasRunner: boolean\n\tdeadlineMs?: number\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\nconst deadline = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, ms)\n\t\ttimer.unref?.()\n\t})\n\n/**\n * Dispatch a submission's post-submit actions without throwing and without blocking the response on slow\n * action work. With no actions, returns immediately. When a job runner is present, enqueues the native\n * `form-builder-actions` task and returns (action work happens out of band). Otherwise runs the actions\n * inline but bounded by a deadline so a missing worker still delivers without hanging the request; any\n * error is swallowed (logged via `payload.logger`). Never rejects.\n */\nexport const dispatchActions = async (args: DispatchActionsArgs): Promise<void> => {\n\tconst { payload, registry, req, formId, submissionId } = args\n\tconst actions = args.actions ?? []\n\tif (actions.length === 0) {\n\t\treturn\n\t}\n\n\tif (args.hasRunner && canQueue(payload)) {\n\t\ttry {\n\t\t\tawait payload.jobs.queue({\n\t\t\t\ttask: ACTIONS_TASK_SLUG,\n\t\t\t\tinput: { formId: String(formId), submissionId: String(submissionId) },\n\t\t\t\treq,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: failed to enqueue actions for submission ${String(submissionId)}: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t)\n\t\t}\n\t\treturn\n\t}\n\n\tconst ms = args.deadlineMs ?? INLINE_DISPATCH_DEADLINE_MS\n\t// Guard the action arm itself (not just the race) so a rejection AFTER the deadline wins is never an unhandled rejection.\n\tconst work = runActionsForSubmission({\n\t\tinput: { formId, submissionId },\n\t\tregistry,\n\t\tpayload,\n\t\treq,\n\t}).catch((error) => {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: inline action dispatch for submission ${String(submissionId)} threw: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t})\n\tawait Promise.race([work, deadline(ms)])\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"dispatch.js","names":[],"sources":["../../src/actions/dispatch.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\nimport type { RichTextBodyOption } from './body/serializeBody'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { ACTIONS_TASK_SLUG, runActionsForSubmission } from './task'\n\n/** Default cap on inline action work so a missing worker never hangs the submission response. */\nexport const INLINE_DISPATCH_DEADLINE_MS = 5_000\n\nexport type DispatchActionsArgs = {\n\tactions: ActionInstance[] | null | undefined\n\tformId: number | string\n\tsubmissionId: number | string\n\tregistry: ActionRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n\t/** Whether a job runner is likely present (queued path); otherwise the bounded-inline fallback runs. */\n\thasRunner: boolean\n\tdeadlineMs?: number\n\trichText?: RichTextBodyOption\n}\n\nconst canQueue = (payload: Payload): boolean => typeof payload.jobs?.queue === 'function'\n\nconst deadline = (ms: number): Promise<void> =>\n\tnew Promise((resolve) => {\n\t\tconst timer = setTimeout(resolve, ms)\n\t\ttimer.unref?.()\n\t})\n\n/**\n * Dispatch a submission's post-submit actions without throwing and without blocking the response on slow\n * action work. With no actions, returns immediately. When a job runner is present, enqueues the native\n * `form-builder-actions` task and returns (action work happens out of band). Otherwise runs the actions\n * inline but bounded by a deadline so a missing worker still delivers without hanging the request; any\n * error is swallowed (logged via `payload.logger`). Never rejects.\n */\nexport const dispatchActions = async (args: DispatchActionsArgs): Promise<void> => {\n\tconst { payload, registry, req, formId, submissionId } = args\n\tconst actions = args.actions ?? []\n\tif (actions.length === 0) {\n\t\treturn\n\t}\n\n\tif (args.hasRunner && canQueue(payload)) {\n\t\ttry {\n\t\t\tawait payload.jobs.queue({\n\t\t\t\ttask: ACTIONS_TASK_SLUG,\n\t\t\t\tinput: { formId: String(formId), submissionId: String(submissionId) },\n\t\t\t\treq,\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: failed to enqueue actions for submission ${String(submissionId)}: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`\n\t\t\t)\n\t\t}\n\t\treturn\n\t}\n\n\tconst ms = args.deadlineMs ?? INLINE_DISPATCH_DEADLINE_MS\n\t// Guard the action arm itself (not just the race) so a rejection AFTER the deadline wins is never an unhandled rejection.\n\tconst work = runActionsForSubmission({\n\t\tinput: { formId, submissionId },\n\t\tregistry,\n\t\tpayload,\n\t\treq,\n\t\trichText: args.richText,\n\t}).catch((error) => {\n\t\tpayload.logger?.error(\n\t\t\t`@10x-media/form-builder: inline action dispatch for submission ${String(submissionId)} threw: ${\n\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t}`\n\t\t)\n\t})\n\tawait Promise.race([work, deadline(ms)])\n}\n"],"mappings":";AAsBA,MAAM,YAAY,YAA8B,OAAO,QAAQ,MAAM,UAAU;AAE/E,MAAM,YAAY,OACjB,IAAI,SAAS,YAAY;CAExB,WADyB,SAAS,EAC9B,EAAE,QAAQ;AACf,CAAC;;;;;;;;AASF,MAAa,kBAAkB,OAAO,SAA6C;CAClF,MAAM,EAAE,SAAS,UAAU,KAAK,QAAQ,iBAAiB;CAEzD,KADgB,KAAK,WAAW,CAAC,GACrB,WAAW,GACtB;CAGD,IAAI,KAAK,aAAa,SAAS,OAAO,GAAG;EACxC,IAAI;GACH,MAAM,QAAQ,KAAK,MAAM;IACxB,MAAM;IACN,OAAO;KAAE,QAAQ,OAAO,MAAM;KAAG,cAAc,OAAO,YAAY;IAAE;IACpE;GACD,CAAC;EACF,SAAS,OAAO;GACf,QAAQ,QAAQ,MACf,qEAAqE,OAAO,YAAY,EAAE,IACzF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;EACD;EACA;CACD;CAEA,MAAM,KAAK,KAAK,cAAA;CAEhB,MAAM,OAAO,wBAAwB;EACpC,OAAO;GAAE;GAAQ;EAAa;EAC9B;EACA;EACA;EACA,UAAU,KAAK;CAChB,CAAC,EAAE,OAAO,UAAU;EACnB,QAAQ,QAAQ,MACf,kEAAkE,OAAO,YAAY,EAAE,UACtF,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAEvD;CACD,CAAC;CACD,MAAM,QAAQ,KAAK,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;AACxC"}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import { makeRenderBody } from "./body/serializeBody.js";
|
|
1
2
|
//#region src/actions/runActions.ts
|
|
2
3
|
/**
|
|
3
4
|
* Run each configured action, isolating failures: a throwing action is captured as a failed result
|
|
4
5
|
* and never breaks the others or the caller.
|
|
5
6
|
*/
|
|
6
7
|
const runActions = async (args) => {
|
|
7
|
-
const { actions, registry, ...ctx } = args;
|
|
8
|
+
const { actions, registry, richText, ...ctx } = args;
|
|
9
|
+
const renderBody = makeRenderBody({
|
|
10
|
+
values: ctx.values,
|
|
11
|
+
descriptors: ctx.descriptors,
|
|
12
|
+
richText
|
|
13
|
+
});
|
|
8
14
|
const results = [];
|
|
9
15
|
for (const instance of actions) {
|
|
10
16
|
const definition = registry.get(instance.blockType);
|
|
@@ -12,6 +18,7 @@ const runActions = async (args) => {
|
|
|
12
18
|
try {
|
|
13
19
|
await definition.run({
|
|
14
20
|
...ctx,
|
|
21
|
+
renderBody,
|
|
15
22
|
config: instance
|
|
16
23
|
});
|
|
17
24
|
results.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\nexport type ActionResult = { type: string; ok: boolean; error?: string }\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, ...ctx } = args\n\tconst results: ActionResult[] = []\n\tfor (const instance of actions) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tawait definition.run({ ...ctx, config: instance })\n\t\t\tresults.push({ type: instance.blockType, ok: true })\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\ttype: instance.blockType,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t}\n\t}\n\treturn results\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"runActions.js","names":[],"sources":["../../src/actions/runActions.ts"],"sourcesContent":["import type { RichTextBodyOption } from './body/serializeBody'\nimport { makeRenderBody } from './body/serializeBody'\nimport type { ActionRunArgs } from './defineAction'\nimport type { ActionRegistry } from './registry'\n\n/** A stored action instance from the form's `actions` blocks array. */\nexport type ActionInstance = { blockType: string; [key: string]: unknown }\n\nexport type ActionResult = { type: string; ok: boolean; error?: string }\n\nexport type RunActionsArgs = Omit<ActionRunArgs, 'config' | 'renderBody'> & {\n\tactions: ActionInstance[]\n\tregistry: ActionRegistry\n\trichText?: RichTextBodyOption\n}\n\n/**\n * Run each configured action, isolating failures: a throwing action is captured as a failed result\n * and never breaks the others or the caller.\n */\nexport const runActions = async (args: RunActionsArgs): Promise<ActionResult[]> => {\n\tconst { actions, registry, richText, ...ctx } = args\n\tconst renderBody = makeRenderBody({ values: ctx.values, descriptors: ctx.descriptors, richText })\n\tconst results: ActionResult[] = []\n\tfor (const instance of actions) {\n\t\tconst definition = registry.get(instance.blockType)\n\t\tif (!definition) {\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tawait definition.run({ ...ctx, renderBody, config: instance })\n\t\t\tresults.push({ type: instance.blockType, ok: true })\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\ttype: instance.blockType,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t})\n\t\t}\n\t}\n\treturn results\n}\n"],"mappings":";;;;;;AAoBA,MAAa,aAAa,OAAO,SAAkD;CAClF,MAAM,EAAE,SAAS,UAAU,UAAU,GAAG,QAAQ;CAChD,MAAM,aAAa,eAAe;EAAE,QAAQ,IAAI;EAAQ,aAAa,IAAI;EAAa;CAAS,CAAC;CAChG,MAAM,UAA0B,CAAC;CACjC,KAAK,MAAM,YAAY,SAAS;EAC/B,MAAM,aAAa,SAAS,IAAI,SAAS,SAAS;EAClD,IAAI,CAAC,YACJ;EAED,IAAI;GACH,MAAM,WAAW,IAAI;IAAE,GAAG;IAAK;IAAY,QAAQ;GAAS,CAAC;GAC7D,QAAQ,KAAK;IAAE,MAAM,SAAS;IAAW,IAAI;GAAK,CAAC;EACpD,SAAS,OAAO;GACf,QAAQ,KAAK;IACZ,MAAM,SAAS;IACf,IAAI;IACJ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC7D,CAAC;EACF;CACD;CACA,OAAO;AACR"}
|
package/dist/actions/task.js
CHANGED
|
@@ -13,7 +13,7 @@ const asDescriptors = (value) => Array.isArray(value) ? value : [];
|
|
|
13
13
|
* run) by returning early. Used by both the queued task handler and the inline fallback.
|
|
14
14
|
*/
|
|
15
15
|
const runActionsForSubmission = async (args) => {
|
|
16
|
-
const { input, registry, payload, req } = args;
|
|
16
|
+
const { input, registry, payload, req, richText } = args;
|
|
17
17
|
const form = await payload.findByID({
|
|
18
18
|
collection: FORMS_SLUG,
|
|
19
19
|
id: input.formId,
|
|
@@ -35,6 +35,7 @@ const runActionsForSubmission = async (args) => {
|
|
|
35
35
|
await runActions({
|
|
36
36
|
actions: asActions(form.actions),
|
|
37
37
|
registry,
|
|
38
|
+
richText,
|
|
38
39
|
form: {
|
|
39
40
|
id: form.id,
|
|
40
41
|
title: typeof form.title === "string" ? form.title : void 0
|
|
@@ -49,7 +50,7 @@ const runActionsForSubmission = async (args) => {
|
|
|
49
50
|
});
|
|
50
51
|
};
|
|
51
52
|
/** Native Payload jobs task that runs a submission's post-submit actions out of band. */
|
|
52
|
-
const buildActionsTask = (registry) => ({
|
|
53
|
+
const buildActionsTask = (registry, richText) => ({
|
|
53
54
|
slug: ACTIONS_TASK_SLUG,
|
|
54
55
|
inputSchema: [{
|
|
55
56
|
name: "formId",
|
|
@@ -65,16 +66,17 @@ const buildActionsTask = (registry) => ({
|
|
|
65
66
|
input,
|
|
66
67
|
registry,
|
|
67
68
|
payload: req.payload,
|
|
68
|
-
req
|
|
69
|
+
req,
|
|
70
|
+
richText
|
|
69
71
|
});
|
|
70
72
|
return { output: {} };
|
|
71
73
|
}
|
|
72
74
|
});
|
|
73
75
|
/** Register the actions task on `config.jobs.tasks`, creating the jobs config if absent. */
|
|
74
|
-
const registerActionsTask = (config, registry) => {
|
|
76
|
+
const registerActionsTask = (config, registry, richText) => {
|
|
75
77
|
config.jobs ??= {};
|
|
76
78
|
config.jobs.tasks ??= [];
|
|
77
|
-
config.jobs.tasks.push(buildActionsTask(registry));
|
|
79
|
+
config.jobs.tasks.push(buildActionsTask(registry, richText));
|
|
78
80
|
};
|
|
79
81
|
//#endregion
|
|
80
82
|
export { ACTIONS_TASK_SLUG, registerActionsTask, runActionsForSubmission };
|
package/dist/actions/task.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.js","names":[],"sources":["../../src/actions/task.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { Translate } from '../fields/types'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\nimport { asFieldTranslate } from '../translations/server'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { runActions } from './runActions'\n\nexport const ACTIONS_TASK_SLUG = 'form-builder-actions'\n\n/** Input the dispatch path enqueues; the handler re-loads everything else from the DB. */\nexport type ActionsTaskInput = { formId: number | string; submissionId: number | string }\n\nconst asActions = (value: unknown): ActionInstance[] =>\n\tArray.isArray(value) ? (value as ActionInstance[]) : []\n\nconst asValues = (value: unknown): SubmissionValue[] =>\n\tArray.isArray(value) ? (value as SubmissionValue[]) : []\n\nconst asDescriptors = (value: unknown): SubmissionDescriptor[] =>\n\tArray.isArray(value) ? (value as SubmissionDescriptor[]) : []\n\n/**\n * Load the form and submission by id and run the form's actions through the shared, failure-isolating\n * `runActions`. Tolerates a missing form or submission (the row may have been deleted between enqueue and\n * run) by returning early. Used by both the queued task handler and the inline fallback.\n */\nexport const runActionsForSubmission = async (args: {\n\tinput: ActionsTaskInput\n\tregistry: ActionRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n}): Promise<void> => {\n\tconst { input, registry, payload, req } = args\n\tconst form = await payload\n\t\t.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: input.formId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn\n\t}\n\tconst submission = await payload\n\t\t.findByID({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\tid: input.submissionId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!submission) {\n\t\treturn\n\t}\n\n\tconst locale = typeof submission.locale === 'string' ? submission.locale : (req?.locale ?? 'en')\n\tconst t: Translate = asFieldTranslate(req?.i18n?.t ?? ((key: string) => key))\n\n\tawait runActions({\n\t\tactions: asActions(form.actions),\n\t\tregistry,\n\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\tsubmissionId: submission.id,\n\t\tvalues: asValues(submission.values),\n\t\tdescriptors: asDescriptors(submission.descriptors),\n\t\tpayload,\n\t\treq,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\n/** Native Payload jobs task that runs a submission's post-submit actions out of band. */\nexport const buildActionsTask = (
|
|
1
|
+
{"version":3,"file":"task.js","names":[],"sources":["../../src/actions/task.ts"],"sourcesContent":["import type { Config, Payload, PayloadRequest, TaskConfig } from 'payload'\nimport { FORM_SUBMISSIONS_SLUG } from '../collections/formSubmissions'\nimport { FORMS_SLUG } from '../collections/forms'\nimport type { Translate } from '../fields/types'\nimport type { SubmissionDescriptor, SubmissionValue } from '../submissions/types'\nimport { asFieldTranslate } from '../translations/server'\nimport type { RichTextBodyOption } from './body/serializeBody'\nimport type { ActionRegistry } from './registry'\nimport type { ActionInstance } from './runActions'\nimport { runActions } from './runActions'\n\nexport const ACTIONS_TASK_SLUG = 'form-builder-actions'\n\n/** Input the dispatch path enqueues; the handler re-loads everything else from the DB. */\nexport type ActionsTaskInput = { formId: number | string; submissionId: number | string }\n\nconst asActions = (value: unknown): ActionInstance[] =>\n\tArray.isArray(value) ? (value as ActionInstance[]) : []\n\nconst asValues = (value: unknown): SubmissionValue[] =>\n\tArray.isArray(value) ? (value as SubmissionValue[]) : []\n\nconst asDescriptors = (value: unknown): SubmissionDescriptor[] =>\n\tArray.isArray(value) ? (value as SubmissionDescriptor[]) : []\n\n/**\n * Load the form and submission by id and run the form's actions through the shared, failure-isolating\n * `runActions`. Tolerates a missing form or submission (the row may have been deleted between enqueue and\n * run) by returning early. Used by both the queued task handler and the inline fallback.\n */\nexport const runActionsForSubmission = async (args: {\n\tinput: ActionsTaskInput\n\tregistry: ActionRegistry\n\tpayload: Payload\n\treq?: PayloadRequest\n\trichText?: RichTextBodyOption\n}): Promise<void> => {\n\tconst { input, registry, payload, req, richText } = args\n\tconst form = await payload\n\t\t.findByID({\n\t\t\tcollection: FORMS_SLUG,\n\t\t\tid: input.formId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!form) {\n\t\treturn\n\t}\n\tconst submission = await payload\n\t\t.findByID({\n\t\t\tcollection: FORM_SUBMISSIONS_SLUG,\n\t\t\tid: input.submissionId,\n\t\t\tdepth: 0,\n\t\t\toverrideAccess: true,\n\t\t\treq,\n\t\t})\n\t\t.catch(() => null)\n\tif (!submission) {\n\t\treturn\n\t}\n\n\tconst locale = typeof submission.locale === 'string' ? submission.locale : (req?.locale ?? 'en')\n\tconst t: Translate = asFieldTranslate(req?.i18n?.t ?? ((key: string) => key))\n\n\tawait runActions({\n\t\tactions: asActions(form.actions),\n\t\tregistry,\n\t\trichText,\n\t\tform: { id: form.id, title: typeof form.title === 'string' ? form.title : undefined },\n\t\tsubmissionId: submission.id,\n\t\tvalues: asValues(submission.values),\n\t\tdescriptors: asDescriptors(submission.descriptors),\n\t\tpayload,\n\t\treq,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\n/** Native Payload jobs task that runs a submission's post-submit actions out of band. */\nexport const buildActionsTask = (\n\tregistry: ActionRegistry,\n\trichText?: RichTextBodyOption\n): TaskConfig =>\n\t({\n\t\tslug: ACTIONS_TASK_SLUG,\n\t\tinputSchema: [\n\t\t\t{ name: 'formId', type: 'text', required: true },\n\t\t\t{ name: 'submissionId', type: 'text', required: true },\n\t\t],\n\t\thandler: async ({ input, req }) => {\n\t\t\tawait runActionsForSubmission({\n\t\t\t\tinput: input as ActionsTaskInput,\n\t\t\t\tregistry,\n\t\t\t\tpayload: req.payload,\n\t\t\t\treq,\n\t\t\t\trichText,\n\t\t\t})\n\t\t\treturn { output: {} }\n\t\t},\n\t}) as TaskConfig\n\n/** Register the actions task on `config.jobs.tasks`, creating the jobs config if absent. */\nexport const registerActionsTask = (\n\tconfig: Config,\n\tregistry: ActionRegistry,\n\trichText?: RichTextBodyOption\n): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildActionsTask(registry, richText))\n}\n"],"mappings":";;;;;AAWA,MAAa,oBAAoB;AAKjC,MAAM,aAAa,UAClB,MAAM,QAAQ,KAAK,IAAK,QAA6B,CAAC;AAEvD,MAAM,YAAY,UACjB,MAAM,QAAQ,KAAK,IAAK,QAA8B,CAAC;AAExD,MAAM,iBAAiB,UACtB,MAAM,QAAQ,KAAK,IAAK,QAAmC,CAAC;;;;;;AAO7D,MAAa,0BAA0B,OAAO,SAMzB;CACpB,MAAM,EAAE,OAAO,UAAU,SAAS,KAAK,aAAa;CACpD,MAAM,OAAO,MAAM,QACjB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,MACJ;CAED,MAAM,aAAa,MAAM,QACvB,SAAS;EACT,YAAY;EACZ,IAAI,MAAM;EACV,OAAO;EACP,gBAAgB;EAChB;CACD,CAAC,EACA,YAAY,IAAI;CAClB,IAAI,CAAC,YACJ;CAGD,MAAM,SAAS,OAAO,WAAW,WAAW,WAAW,WAAW,SAAU,KAAK,UAAU;CAC3F,MAAM,IAAe,iBAAiB,KAAK,MAAM,OAAO,QAAgB,IAAI;CAE5E,MAAM,WAAW;EAChB,SAAS,UAAU,KAAK,OAAO;EAC/B;EACA;EACA,MAAM;GAAE,IAAI,KAAK;GAAI,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;EAAU;EACpF,cAAc,WAAW;EACzB,QAAQ,SAAS,WAAW,MAAM;EAClC,aAAa,cAAc,WAAW,WAAW;EACjD;EACA;EACA;EACA;CACD,CAAC;AACF;;AAGA,MAAa,oBACZ,UACA,cAEC;CACA,MAAM;CACN,aAAa,CACZ;EAAE,MAAM;EAAU,MAAM;EAAQ,UAAU;CAAK,GAC/C;EAAE,MAAM;EAAgB,MAAM;EAAQ,UAAU;CAAK,CACtD;CACA,SAAS,OAAO,EAAE,OAAO,UAAU;EAClC,MAAM,wBAAwB;GACtB;GACP;GACA,SAAS,IAAI;GACb;GACA;EACD,CAAC;EACD,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,uBACZ,QACA,UACA,aACU;CACV,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,iBAAiB,UAAU,QAAQ,CAAC;AAC5D"}
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
+
import { CALC_FNS, CALC_OPS } from "./types.js";
|
|
1
2
|
//#region src/calc/normalizeCalc.ts
|
|
2
3
|
const MAX_DEPTH = 64;
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
"-",
|
|
6
|
-
"*",
|
|
7
|
-
"/",
|
|
8
|
-
"%"
|
|
9
|
-
]);
|
|
10
|
-
const FN_SET = new Set([
|
|
11
|
-
"min",
|
|
12
|
-
"max",
|
|
13
|
-
"round",
|
|
14
|
-
"abs",
|
|
15
|
-
"ceil",
|
|
16
|
-
"floor"
|
|
17
|
-
]);
|
|
4
|
+
const isOp = (v) => CALC_OPS.includes(v);
|
|
5
|
+
const isFn = (v) => CALC_FNS.includes(v);
|
|
18
6
|
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
19
7
|
const normalizeNode = (value, depth) => {
|
|
20
8
|
if (depth > MAX_DEPTH) return void 0;
|
|
@@ -38,7 +26,7 @@ const normalizeNode = (value, depth) => {
|
|
|
38
26
|
}
|
|
39
27
|
case "op": {
|
|
40
28
|
const { op, left, right } = value;
|
|
41
|
-
if (
|
|
29
|
+
if (!isOp(op)) return void 0;
|
|
42
30
|
const l = normalizeNode(left, depth + 1);
|
|
43
31
|
if (!l) return void 0;
|
|
44
32
|
const r = normalizeNode(right, depth + 1);
|
|
@@ -60,7 +48,7 @@ const normalizeNode = (value, depth) => {
|
|
|
60
48
|
}
|
|
61
49
|
case "fn": {
|
|
62
50
|
const { fn, args } = value;
|
|
63
|
-
if (
|
|
51
|
+
if (!isFn(fn)) return void 0;
|
|
64
52
|
if (!Array.isArray(args)) return void 0;
|
|
65
53
|
const normalizedArgs = [];
|
|
66
54
|
for (const arg of args) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalizeCalc.js","names":[],"sources":["../../src/calc/normalizeCalc.ts"],"sourcesContent":["import type
|
|
1
|
+
{"version":3,"file":"normalizeCalc.js","names":[],"sources":["../../src/calc/normalizeCalc.ts"],"sourcesContent":["import { CALC_FNS, CALC_OPS, type CalcExpression, type CalcFn, type CalcOp } from './types'\n\nconst MAX_DEPTH = 64\n\nconst isOp = (v: unknown): v is CalcOp => (CALC_OPS as readonly unknown[]).includes(v)\nconst isFn = (v: unknown): v is CalcFn => (CALC_FNS as readonly unknown[]).includes(v)\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n\tv !== null && typeof v === 'object' && !Array.isArray(v)\n\nconst normalizeNode = (value: unknown, depth: number): CalcExpression | undefined => {\n\tif (depth > MAX_DEPTH) return undefined\n\tif (!isRecord(value)) return undefined\n\n\tswitch (value.type) {\n\t\tcase 'lit': {\n\t\t\tconst { value: v } = value\n\t\t\tif (typeof v !== 'number' || !Number.isFinite(v)) return undefined\n\t\t\treturn { type: 'lit', value: v }\n\t\t}\n\t\tcase 'ref': {\n\t\t\tconst { field } = value\n\t\t\tif (typeof field !== 'string') return undefined\n\t\t\treturn { type: 'ref', field }\n\t\t}\n\t\tcase 'op': {\n\t\t\tconst { op, left, right } = value\n\t\t\tif (!isOp(op)) return undefined\n\t\t\tconst l = normalizeNode(left, depth + 1)\n\t\t\tif (!l) return undefined\n\t\t\tconst r = normalizeNode(right, depth + 1)\n\t\t\tif (!r) return undefined\n\t\t\treturn { type: 'op', op, left: l, right: r }\n\t\t}\n\t\tcase 'neg': {\n\t\t\tconst operand = normalizeNode(value.operand, depth + 1)\n\t\t\tif (!operand) return undefined\n\t\t\treturn { type: 'neg', operand }\n\t\t}\n\t\tcase 'fn': {\n\t\t\tconst { fn, args } = value\n\t\t\tif (!isFn(fn)) return undefined\n\t\t\tif (!Array.isArray(args)) return undefined\n\t\t\tconst normalizedArgs: CalcExpression[] = []\n\t\t\tfor (const arg of args) {\n\t\t\t\tconst a = normalizeNode(arg, depth + 1)\n\t\t\t\tif (!a) return undefined\n\t\t\t\tnormalizedArgs.push(a)\n\t\t\t}\n\t\t\treturn { type: 'fn', fn, args: normalizedArgs }\n\t\t}\n\t\tcase 'weight': {\n\t\t\tconst { field, weights } = value\n\t\t\tif (typeof field !== 'string') return undefined\n\t\t\tif (!isRecord(weights)) return undefined\n\t\t\tconst normalizedWeights: Record<string, number> = {}\n\t\t\tfor (const [k, v] of Object.entries(weights)) {\n\t\t\t\tif (typeof v !== 'number' || !Number.isFinite(v)) return undefined\n\t\t\t\tnormalizedWeights[k] = v\n\t\t\t}\n\t\t\treturn { type: 'weight', field, weights: normalizedWeights }\n\t\t}\n\t\tdefault:\n\t\t\treturn undefined\n\t}\n}\n\n/** Structural guard: returns a valid CalcExpression if `value` is structurally sound (depth-guarded at 64), otherwise undefined. Never throws. */\nexport const normalizeCalc = (value: unknown): CalcExpression | undefined => normalizeNode(value, 0)\n"],"mappings":";;AAEA,MAAM,YAAY;AAElB,MAAM,QAAQ,MAA6B,SAAgC,SAAS,CAAC;AACrF,MAAM,QAAQ,MAA6B,SAAgC,SAAS,CAAC;AAErF,MAAM,YAAY,MACjB,MAAM,QAAQ,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAExD,MAAM,iBAAiB,OAAgB,UAA8C;CACpF,IAAI,QAAQ,WAAW,OAAO,KAAA;CAC9B,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO,KAAA;CAE7B,QAAQ,MAAM,MAAd;EACC,KAAK,OAAO;GACX,MAAM,EAAE,OAAO,MAAM;GACrB,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,KAAA;GACzD,OAAO;IAAE,MAAM;IAAO,OAAO;GAAE;EAChC;EACA,KAAK,OAAO;GACX,MAAM,EAAE,UAAU;GAClB,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;GACtC,OAAO;IAAE,MAAM;IAAO;GAAM;EAC7B;EACA,KAAK,MAAM;GACV,MAAM,EAAE,IAAI,MAAM,UAAU;GAC5B,IAAI,CAAC,KAAK,EAAE,GAAG,OAAO,KAAA;GACtB,MAAM,IAAI,cAAc,MAAM,QAAQ,CAAC;GACvC,IAAI,CAAC,GAAG,OAAO,KAAA;GACf,MAAM,IAAI,cAAc,OAAO,QAAQ,CAAC;GACxC,IAAI,CAAC,GAAG,OAAO,KAAA;GACf,OAAO;IAAE,MAAM;IAAM;IAAI,MAAM;IAAG,OAAO;GAAE;EAC5C;EACA,KAAK,OAAO;GACX,MAAM,UAAU,cAAc,MAAM,SAAS,QAAQ,CAAC;GACtD,IAAI,CAAC,SAAS,OAAO,KAAA;GACrB,OAAO;IAAE,MAAM;IAAO;GAAQ;EAC/B;EACA,KAAK,MAAM;GACV,MAAM,EAAE,IAAI,SAAS;GACrB,IAAI,CAAC,KAAK,EAAE,GAAG,OAAO,KAAA;GACtB,IAAI,CAAC,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAA;GACjC,MAAM,iBAAmC,CAAC;GAC1C,KAAK,MAAM,OAAO,MAAM;IACvB,MAAM,IAAI,cAAc,KAAK,QAAQ,CAAC;IACtC,IAAI,CAAC,GAAG,OAAO,KAAA;IACf,eAAe,KAAK,CAAC;GACtB;GACA,OAAO;IAAE,MAAM;IAAM;IAAI,MAAM;GAAe;EAC/C;EACA,KAAK,UAAU;GACd,MAAM,EAAE,OAAO,YAAY;GAC3B,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;GACtC,IAAI,CAAC,SAAS,OAAO,GAAG,OAAO,KAAA;GAC/B,MAAM,oBAA4C,CAAC;GACnD,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,GAAG;IAC7C,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,KAAA;IACzD,kBAAkB,KAAK;GACxB;GACA,OAAO;IAAE,MAAM;IAAU;IAAO,SAAS;GAAkB;EAC5D;EACA,SACC;CACF;AACD;;AAGA,MAAa,iBAAiB,UAA+C,cAAc,OAAO,CAAC"}
|
package/dist/calc/types.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/calc/types.d.ts
|
|
2
|
+
/** Canonical operator grammar; `CalcExpression`, `normalizeCalc`, and the admin editor's jsonSchema all derive from this list. */
|
|
3
|
+
declare const CALC_OPS: readonly ["+", "-", "*", "/", "%"];
|
|
4
|
+
type CalcOp = (typeof CALC_OPS)[number];
|
|
5
|
+
/** Canonical function grammar; same single source of truth as `CALC_OPS`. */
|
|
6
|
+
declare const CALC_FNS: readonly ["min", "max", "round", "abs", "ceil", "floor"];
|
|
7
|
+
type CalcFn = (typeof CALC_FNS)[number];
|
|
2
8
|
/** A serializable, safe-by-construction calculation expression. No strings are parsed; the evaluator tree-walks this closed node set (never `eval`). */
|
|
3
9
|
type CalcExpression = {
|
|
4
10
|
type: 'lit';
|
|
@@ -8,7 +14,7 @@ type CalcExpression = {
|
|
|
8
14
|
field: string;
|
|
9
15
|
} | {
|
|
10
16
|
type: 'op';
|
|
11
|
-
op:
|
|
17
|
+
op: CalcOp;
|
|
12
18
|
left: CalcExpression;
|
|
13
19
|
right: CalcExpression;
|
|
14
20
|
} | {
|
|
@@ -16,7 +22,7 @@ type CalcExpression = {
|
|
|
16
22
|
operand: CalcExpression;
|
|
17
23
|
} | {
|
|
18
24
|
type: 'fn';
|
|
19
|
-
fn:
|
|
25
|
+
fn: CalcFn;
|
|
20
26
|
args: CalcExpression[];
|
|
21
27
|
} | {
|
|
22
28
|
type: 'weight';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/calc/types.ts
|
|
2
|
+
/** Canonical operator grammar; `CalcExpression`, `normalizeCalc`, and the admin editor's jsonSchema all derive from this list. */
|
|
3
|
+
const CALC_OPS = [
|
|
4
|
+
"+",
|
|
5
|
+
"-",
|
|
6
|
+
"*",
|
|
7
|
+
"/",
|
|
8
|
+
"%"
|
|
9
|
+
];
|
|
10
|
+
/** Canonical function grammar; same single source of truth as `CALC_OPS`. */
|
|
11
|
+
const CALC_FNS = [
|
|
12
|
+
"min",
|
|
13
|
+
"max",
|
|
14
|
+
"round",
|
|
15
|
+
"abs",
|
|
16
|
+
"ceil",
|
|
17
|
+
"floor"
|
|
18
|
+
];
|
|
19
|
+
//#endregion
|
|
20
|
+
export { CALC_FNS, CALC_OPS };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/calc/types.ts"],"sourcesContent":["/** Canonical operator grammar; `CalcExpression`, `normalizeCalc`, and the admin editor's jsonSchema all derive from this list. */\nexport const CALC_OPS = ['+', '-', '*', '/', '%'] as const\nexport type CalcOp = (typeof CALC_OPS)[number]\n\n/** Canonical function grammar; same single source of truth as `CALC_OPS`. */\nexport const CALC_FNS = ['min', 'max', 'round', 'abs', 'ceil', 'floor'] as const\nexport type CalcFn = (typeof CALC_FNS)[number]\n\n/** A serializable, safe-by-construction calculation expression. No strings are parsed; the evaluator tree-walks this closed node set (never `eval`). */\nexport type CalcExpression =\n\t| { type: 'lit'; value: number }\n\t| { type: 'ref'; field: string }\n\t| { type: 'op'; op: CalcOp; left: CalcExpression; right: CalcExpression }\n\t| { type: 'neg'; operand: CalcExpression }\n\t| { type: 'fn'; fn: CalcFn; args: CalcExpression[] }\n\t| { type: 'weight'; field: string; weights: Record<string, number> }\n"],"mappings":";;AACA,MAAa,WAAW;CAAC;CAAK;CAAK;CAAK;CAAK;AAAG;;AAIhD,MAAa,WAAW;CAAC;CAAO;CAAO;CAAS;CAAO;CAAQ;AAAO"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/client/ByteSizeField.d.ts
|
|
2
|
+
/** Props: standard number field client props; label and description arrive resolved on `field`. */
|
|
3
|
+
type ByteSizeFieldProps = {
|
|
4
|
+
path?: string;
|
|
5
|
+
field?: {
|
|
6
|
+
label?: unknown;
|
|
7
|
+
admin?: {
|
|
8
|
+
description?: unknown;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
label?: unknown;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* The `Field` mounted on a byte-count number config field (the file field's `maxSize`): a plain
|
|
15
|
+
* number input that live-previews the value as a human-readable size ('= 2.5 MB') so authors are
|
|
16
|
+
* not left decoding raw bytes. Stores the raw byte number unchanged.
|
|
17
|
+
*/
|
|
18
|
+
declare const ByteSizeField: (props: ByteSizeFieldProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ByteSizeField };
|
|
21
|
+
//# sourceMappingURL=ByteSizeField.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { formatBytes } from "../uploads/formatBytes.js";
|
|
3
|
+
import { toStaticLabel } from "./toStaticLabel.js";
|
|
4
|
+
import { FieldDescription, FieldLabel, useField } from "@payloadcms/ui";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/client/ByteSizeField.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The `Field` mounted on a byte-count number config field (the file field's `maxSize`): a plain
|
|
9
|
+
* number input that live-previews the value as a human-readable size ('= 2.5 MB') so authors are
|
|
10
|
+
* not left decoding raw bytes. Stores the raw byte number unchanged.
|
|
11
|
+
*/
|
|
12
|
+
const ByteSizeField = (props) => {
|
|
13
|
+
const { path, setValue, value } = useField({ path: props.path });
|
|
14
|
+
const label = toStaticLabel(props.field?.label ?? props.label);
|
|
15
|
+
const description = toStaticLabel(props.field?.admin?.description);
|
|
16
|
+
const handleChange = (event) => {
|
|
17
|
+
if (event.target.value === "") {
|
|
18
|
+
setValue(null);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const parsed = Number(event.target.value);
|
|
22
|
+
if (Number.isFinite(parsed)) setValue(parsed);
|
|
23
|
+
};
|
|
24
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: "field-type number",
|
|
26
|
+
style: { marginBlockEnd: "1rem" },
|
|
27
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
28
|
+
label,
|
|
29
|
+
path
|
|
30
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
31
|
+
className: "field-type__wrap",
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ jsx("input", {
|
|
34
|
+
id: `field-${path.replace(/\./g, "__")}`,
|
|
35
|
+
min: 0,
|
|
36
|
+
name: path,
|
|
37
|
+
onChange: handleChange,
|
|
38
|
+
type: "number",
|
|
39
|
+
value: typeof value === "number" ? value : ""
|
|
40
|
+
}),
|
|
41
|
+
typeof value === "number" && value > 0 ? /* @__PURE__ */ jsx("div", {
|
|
42
|
+
className: "field-description",
|
|
43
|
+
children: `= ${formatBytes(value)}`
|
|
44
|
+
}) : null,
|
|
45
|
+
/* @__PURE__ */ jsx(FieldDescription, {
|
|
46
|
+
description,
|
|
47
|
+
path
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
})]
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { ByteSizeField };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=ByteSizeField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ByteSizeField.js","names":[],"sources":["../../src/client/ByteSizeField.tsx"],"sourcesContent":["'use client'\n\nimport { FieldDescription, FieldLabel, useField } from '@payloadcms/ui'\nimport type { ChangeEvent } from 'react'\nimport { formatBytes } from '../uploads/formatBytes'\nimport { toStaticLabel } from './toStaticLabel'\n\n/** Props: standard number field client props; label and description arrive resolved on `field`. */\nexport type ByteSizeFieldProps = {\n\tpath?: string\n\tfield?: { label?: unknown; admin?: { description?: unknown } }\n\tlabel?: unknown\n}\n\n/**\n * The `Field` mounted on a byte-count number config field (the file field's `maxSize`): a plain\n * number input that live-previews the value as a human-readable size ('= 2.5 MB') so authors are\n * not left decoding raw bytes. Stores the raw byte number unchanged.\n */\nexport const ByteSizeField = (props: ByteSizeFieldProps) => {\n\tconst { path, setValue, value } = useField<number>({ path: props.path })\n\tconst label = toStaticLabel(props.field?.label ?? props.label)\n\tconst description = toStaticLabel(props.field?.admin?.description)\n\n\tconst handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n\t\tif (event.target.value === '') {\n\t\t\tsetValue(null)\n\t\t\treturn\n\t\t}\n\t\tconst parsed = Number(event.target.value)\n\t\tif (Number.isFinite(parsed)) {\n\t\t\tsetValue(parsed)\n\t\t}\n\t}\n\n\treturn (\n\t\t<div className=\"field-type number\" style={{ marginBlockEnd: '1rem' }}>\n\t\t\t<FieldLabel label={label} path={path} />\n\t\t\t<div className=\"field-type__wrap\">\n\t\t\t\t<input\n\t\t\t\t\tid={`field-${path.replace(/\\./g, '__')}`}\n\t\t\t\t\tmin={0}\n\t\t\t\t\tname={path}\n\t\t\t\t\tonChange={handleChange}\n\t\t\t\t\ttype=\"number\"\n\t\t\t\t\tvalue={typeof value === 'number' ? value : ''}\n\t\t\t\t/>\n\t\t\t\t{typeof value === 'number' && value > 0 ? (\n\t\t\t\t\t<div className=\"field-description\">{`= ${formatBytes(value)}`}</div>\n\t\t\t\t) : null}\n\t\t\t\t<FieldDescription description={description} path={path} />\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;;;;AAmBA,MAAa,iBAAiB,UAA8B;CAC3D,MAAM,EAAE,MAAM,UAAU,UAAU,SAAiB,EAAE,MAAM,MAAM,KAAK,CAAC;CACvE,MAAM,QAAQ,cAAc,MAAM,OAAO,SAAS,MAAM,KAAK;CAC7D,MAAM,cAAc,cAAc,MAAM,OAAO,OAAO,WAAW;CAEjE,MAAM,gBAAgB,UAAyC;EAC9D,IAAI,MAAM,OAAO,UAAU,IAAI;GAC9B,SAAS,IAAI;GACb;EACD;EACA,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK;EACxC,IAAI,OAAO,SAAS,MAAM,GACzB,SAAS,MAAM;CAEjB;CAEA,OACC,qBAAC,OAAD;EAAK,WAAU;EAAoB,OAAO,EAAE,gBAAgB,OAAO;YAAnE,CACC,oBAAC,YAAD;GAAmB;GAAa;EAAO,CAAA,GACvC,qBAAC,OAAD;GAAK,WAAU;aAAf;IACC,oBAAC,SAAD;KACC,IAAI,SAAS,KAAK,QAAQ,OAAO,IAAI;KACrC,KAAK;KACL,MAAM;KACN,UAAU;KACV,MAAK;KACL,OAAO,OAAO,UAAU,WAAW,QAAQ;IAC3C,CAAA;IACA,OAAO,UAAU,YAAY,QAAQ,IACrC,oBAAC,OAAD;KAAK,WAAU;eAAqB,KAAK,YAAY,KAAK;IAAS,CAAA,IAChE;IACJ,oBAAC,kBAAD;KAA+B;KAAmB;IAAO,CAAA;GACrD;IACD;;AAEP"}
|
|
@@ -6,8 +6,9 @@ import { operandFromRow } from "./synthesizeClientField.js";
|
|
|
6
6
|
import { ConditionRow } from "./ConditionRow.js";
|
|
7
7
|
import { reduceFieldsToValues, transformWhereQuery } from "payload/shared";
|
|
8
8
|
import { Button, useFormFields } from "@payloadcms/ui";
|
|
9
|
-
import { useMemo } from "react";
|
|
10
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { useMemo } from "react";
|
|
11
|
+
import "./admin.css";
|
|
11
12
|
//#region src/client/ConditionBuilder.tsx
|
|
12
13
|
const toRow = (constraint) => {
|
|
13
14
|
const field = Object.keys(constraint)[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConditionBuilder.js","names":["useTranslation"],"sources":["../../src/client/ConditionBuilder.tsx"],"sourcesContent":["'use client'\n\nimport { Button, useFormFields } from '@payloadcms/ui'\nimport type { Operator, Where } from 'payload'\nimport { reduceFieldsToValues, transformWhereQuery } from 'payload/shared'\nimport { useMemo } from 'react'\nimport { type ConditionFieldType, firstOperatorFor } from '../conditions/fieldTypes'\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { ConditionRow, type RowCondition } from './ConditionRow'\nimport { type ConditionOperand, type FieldRow, operandFromRow } from './synthesizeClientField'\n\nexport type ConditionBuilderProps = {\n\tvalue?: Where\n\tonChange: (next: Where) => void\n\tconditionTypes: Record<string, ConditionFieldType>\n\t/** This field's own machine name, excluded from the operand list (a field cannot condition on itself). */\n\tselfName?: string\n}\n\nconst toRow = (constraint: Record<string, unknown>): RowCondition | null => {\n\tconst field = Object.keys(constraint)[0]\n\tif (!field) {\n\t\treturn null\n\t}\n\tconst ops = constraint[field] as Record<string, unknown>\n\tconst operator = Object.keys(ops)[0] as Operator | undefined\n\tif (!operator) {\n\t\treturn null\n\t}\n\treturn { field, operator, value: ops[operator] }\n}\n\nconst fromRow = (row: RowCondition): Where => ({ [row.field]: { [row.operator]: row.value } })\n\nconst readGroups = (value: Where | undefined): RowCondition[][] => {\n\tif (!value || Object.keys(value).length === 0) {\n\t\treturn []\n\t}\n\tconst canonical = transformWhereQuery(value)\n\tconst or = Array.isArray(canonical.or) ? canonical.or : []\n\treturn or.map((group) => {\n\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as Where[]) : []\n\t\treturn and\n\t\t\t.map((constraint) => toRow(constraint as Record<string, unknown>))\n\t\t\t.filter((row): row is RowCondition => row !== null)\n\t})\n}\n\nconst writeGroups = (groups: RowCondition[][]): Where => ({\n\tor: groups.filter((group) => group.length > 0).map((group) => ({ and: group.map(fromRow) })),\n})\n\nconst operandsFromData = (\n\tdata: Record<string, unknown>,\n\tconditionTypes: Record<string, ConditionFieldType>,\n\tselfName: string | undefined\n): ConditionOperand[] => {\n\tconst rows = Array.isArray(data.fields) ? (data.fields as FieldRow[]) : []\n\treturn rows\n\t\t.map((row) => operandFromRow(row, conditionTypes))\n\t\t.filter((operand): operand is ConditionOperand => operand !== null && operand.name !== selfName)\n}\n\nexport const ConditionBuilder = ({\n\tvalue,\n\tonChange,\n\tconditionTypes,\n\tselfName,\n}: ConditionBuilderProps) => {\n\tconst { t } = useTranslation()\n\t// Serialize the operand list so the builder re-renders only when operands actually change (a field\n\t// added, renamed, retyped, or its options edited), not on every unrelated form-state update.\n\tconst operandsJson = useFormFields(([fields]) =>\n\t\tJSON.stringify(operandsFromData(reduceFieldsToValues(fields, true), conditionTypes, selfName))\n\t)\n\tconst operands = useMemo(() => JSON.parse(operandsJson) as ConditionOperand[], [operandsJson])\n\n\tconst groups = readGroups(value)\n\tconst emit = (next: RowCondition[][]) => onChange(writeGroups(next))\n\n\tconst addCondition = (groupIndex: number) => {\n\t\tconst first = operands[0]\n\t\tif (!first) {\n\t\t\treturn\n\t\t}\n\t\tconst row: RowCondition = {\n\t\t\tfield: first.name,\n\t\t\toperator: firstOperatorFor(first.conditionType),\n\t\t\tvalue: undefined,\n\t\t}\n\t\temit(\n\t\t\tgroups.length === 0\n\t\t\t\t? [[row]]\n\t\t\t\t: groups.map((group, index) => (index === groupIndex ? [...group, row] : group))\n\t\t)\n\t}\n\n\tconst addOrGroup = () => {\n\t\tconst first = operands[0]\n\t\tif (!first) {\n\t\t\treturn\n\t\t}\n\t\temit([\n\t\t\t...groups,\n\t\t\t[{ field: first.name, operator: firstOperatorFor(first.conditionType), value: undefined }],\n\t\t])\n\t}\n\n\tconst updateRow = (groupIndex: number, rowIndex: number, row: RowCondition) =>\n\t\temit(\n\t\t\tgroups.map((group, index) =>\n\t\t\t\tindex === groupIndex\n\t\t\t\t\t? group.map((existing, position) => (position === rowIndex ? row : existing))\n\t\t\t\t\t: group\n\t\t\t)\n\t\t)\n\n\tconst removeRow = (groupIndex: number, rowIndex: number) =>\n\t\temit(\n\t\t\tgroups\n\t\t\t\t.map((group, index) =>\n\t\t\t\t\tindex === groupIndex ? group.filter((_, position) => position !== rowIndex) : group\n\t\t\t\t)\n\t\t\t\t.filter((group) => group.length > 0)\n\t\t)\n\n\tif (operands.length === 0) {\n\t\treturn <p className=\"fb-condition-builder__hint\">{t(keys.conditionNoFields)}</p>\n\t}\n\n\treturn (\n\t\t<div className=\"fb-condition-builder\">\n\t\t\t{groups.length === 0 ? (\n\t\t\t\t<p className=\"fb-condition-builder__hint\">{t(keys.conditionEmpty)}</p>\n\t\t\t) : (\n\t\t\t\tgroups.map((group, groupIndex) => (\n\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: groups are positional and never reordered\n\t\t\t\t\t<div key={groupIndex} className=\"fb-condition-builder__group\">\n\t\t\t\t\t\t{groupIndex > 0 ? (\n\t\t\t\t\t\t\t<div className=\"fb-condition-builder__or-label\">{t(keys.conditionOr)}</div>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{group.map((row, rowIndex) => (\n\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: rows are positional and never reordered\n\t\t\t\t\t\t\t<div key={rowIndex} className=\"fb-condition-builder__and-row\">\n\t\t\t\t\t\t\t\t{rowIndex > 0 ? (\n\t\t\t\t\t\t\t\t\t<span className=\"fb-condition-builder__and-label\">{t(keys.conditionAnd)}</span>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t<ConditionRow\n\t\t\t\t\t\t\t\t\tcondition={row}\n\t\t\t\t\t\t\t\t\toperands={operands}\n\t\t\t\t\t\t\t\t\tonChange={(next) => updateRow(groupIndex, rowIndex, next)}\n\t\t\t\t\t\t\t\t\tonRemove={() => removeRow(groupIndex, rowIndex)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t<div className=\"fb-condition-builder__add-condition\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\t\t\t\tonClick={() => addCondition(groupIndex)}\n\t\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{t(keys.conditionAddCondition)}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))\n\t\t\t)}\n\t\t\t<div className=\"fb-condition-builder__actions\">\n\t\t\t\t<Button\n\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\tonClick={() => (groups.length === 0 ? addCondition(0) : addOrGroup())}\n\t\t\t\t\tmargin={false}\n\t\t\t\t>\n\t\t\t\t\t{groups.length === 0 ? t(keys.conditionAddCondition) : t(keys.conditionAddOr)}\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConditionBuilder.js","names":["useTranslation"],"sources":["../../src/client/ConditionBuilder.tsx"],"sourcesContent":["'use client'\n\nimport { Button, useFormFields } from '@payloadcms/ui'\nimport type { Operator, Where } from 'payload'\nimport { reduceFieldsToValues, transformWhereQuery } from 'payload/shared'\nimport { useMemo } from 'react'\nimport { type ConditionFieldType, firstOperatorFor } from '../conditions/fieldTypes'\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { ConditionRow, type RowCondition } from './ConditionRow'\nimport { type ConditionOperand, type FieldRow, operandFromRow } from './synthesizeClientField'\nimport './admin.css'\n\nexport type ConditionBuilderProps = {\n\tvalue?: Where\n\tonChange: (next: Where) => void\n\tconditionTypes: Record<string, ConditionFieldType>\n\t/** This field's own machine name, excluded from the operand list (a field cannot condition on itself). */\n\tselfName?: string\n}\n\nconst toRow = (constraint: Record<string, unknown>): RowCondition | null => {\n\tconst field = Object.keys(constraint)[0]\n\tif (!field) {\n\t\treturn null\n\t}\n\tconst ops = constraint[field] as Record<string, unknown>\n\tconst operator = Object.keys(ops)[0] as Operator | undefined\n\tif (!operator) {\n\t\treturn null\n\t}\n\treturn { field, operator, value: ops[operator] }\n}\n\nconst fromRow = (row: RowCondition): Where => ({ [row.field]: { [row.operator]: row.value } })\n\nconst readGroups = (value: Where | undefined): RowCondition[][] => {\n\tif (!value || Object.keys(value).length === 0) {\n\t\treturn []\n\t}\n\tconst canonical = transformWhereQuery(value)\n\tconst or = Array.isArray(canonical.or) ? canonical.or : []\n\treturn or.map((group) => {\n\t\tconst and = Array.isArray((group as Where).and) ? ((group as Where).and as Where[]) : []\n\t\treturn and\n\t\t\t.map((constraint) => toRow(constraint as Record<string, unknown>))\n\t\t\t.filter((row): row is RowCondition => row !== null)\n\t})\n}\n\nconst writeGroups = (groups: RowCondition[][]): Where => ({\n\tor: groups.filter((group) => group.length > 0).map((group) => ({ and: group.map(fromRow) })),\n})\n\nconst operandsFromData = (\n\tdata: Record<string, unknown>,\n\tconditionTypes: Record<string, ConditionFieldType>,\n\tselfName: string | undefined\n): ConditionOperand[] => {\n\tconst rows = Array.isArray(data.fields) ? (data.fields as FieldRow[]) : []\n\treturn rows\n\t\t.map((row) => operandFromRow(row, conditionTypes))\n\t\t.filter((operand): operand is ConditionOperand => operand !== null && operand.name !== selfName)\n}\n\nexport const ConditionBuilder = ({\n\tvalue,\n\tonChange,\n\tconditionTypes,\n\tselfName,\n}: ConditionBuilderProps) => {\n\tconst { t } = useTranslation()\n\t// Serialize the operand list so the builder re-renders only when operands actually change (a field\n\t// added, renamed, retyped, or its options edited), not on every unrelated form-state update.\n\tconst operandsJson = useFormFields(([fields]) =>\n\t\tJSON.stringify(operandsFromData(reduceFieldsToValues(fields, true), conditionTypes, selfName))\n\t)\n\tconst operands = useMemo(() => JSON.parse(operandsJson) as ConditionOperand[], [operandsJson])\n\n\tconst groups = readGroups(value)\n\tconst emit = (next: RowCondition[][]) => onChange(writeGroups(next))\n\n\tconst addCondition = (groupIndex: number) => {\n\t\tconst first = operands[0]\n\t\tif (!first) {\n\t\t\treturn\n\t\t}\n\t\tconst row: RowCondition = {\n\t\t\tfield: first.name,\n\t\t\toperator: firstOperatorFor(first.conditionType),\n\t\t\tvalue: undefined,\n\t\t}\n\t\temit(\n\t\t\tgroups.length === 0\n\t\t\t\t? [[row]]\n\t\t\t\t: groups.map((group, index) => (index === groupIndex ? [...group, row] : group))\n\t\t)\n\t}\n\n\tconst addOrGroup = () => {\n\t\tconst first = operands[0]\n\t\tif (!first) {\n\t\t\treturn\n\t\t}\n\t\temit([\n\t\t\t...groups,\n\t\t\t[{ field: first.name, operator: firstOperatorFor(first.conditionType), value: undefined }],\n\t\t])\n\t}\n\n\tconst updateRow = (groupIndex: number, rowIndex: number, row: RowCondition) =>\n\t\temit(\n\t\t\tgroups.map((group, index) =>\n\t\t\t\tindex === groupIndex\n\t\t\t\t\t? group.map((existing, position) => (position === rowIndex ? row : existing))\n\t\t\t\t\t: group\n\t\t\t)\n\t\t)\n\n\tconst removeRow = (groupIndex: number, rowIndex: number) =>\n\t\temit(\n\t\t\tgroups\n\t\t\t\t.map((group, index) =>\n\t\t\t\t\tindex === groupIndex ? group.filter((_, position) => position !== rowIndex) : group\n\t\t\t\t)\n\t\t\t\t.filter((group) => group.length > 0)\n\t\t)\n\n\tif (operands.length === 0) {\n\t\treturn <p className=\"fb-condition-builder__hint\">{t(keys.conditionNoFields)}</p>\n\t}\n\n\treturn (\n\t\t<div className=\"fb-condition-builder\">\n\t\t\t{groups.length === 0 ? (\n\t\t\t\t<p className=\"fb-condition-builder__hint\">{t(keys.conditionEmpty)}</p>\n\t\t\t) : (\n\t\t\t\tgroups.map((group, groupIndex) => (\n\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: groups are positional and never reordered\n\t\t\t\t\t<div key={groupIndex} className=\"fb-condition-builder__group\">\n\t\t\t\t\t\t{groupIndex > 0 ? (\n\t\t\t\t\t\t\t<div className=\"fb-condition-builder__or-label\">{t(keys.conditionOr)}</div>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{group.map((row, rowIndex) => (\n\t\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: rows are positional and never reordered\n\t\t\t\t\t\t\t<div key={rowIndex} className=\"fb-condition-builder__and-row\">\n\t\t\t\t\t\t\t\t{rowIndex > 0 ? (\n\t\t\t\t\t\t\t\t\t<span className=\"fb-condition-builder__and-label\">{t(keys.conditionAnd)}</span>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t<ConditionRow\n\t\t\t\t\t\t\t\t\tcondition={row}\n\t\t\t\t\t\t\t\t\toperands={operands}\n\t\t\t\t\t\t\t\t\tonChange={(next) => updateRow(groupIndex, rowIndex, next)}\n\t\t\t\t\t\t\t\t\tonRemove={() => removeRow(groupIndex, rowIndex)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t<div className=\"fb-condition-builder__add-condition\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\t\t\t\tonClick={() => addCondition(groupIndex)}\n\t\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{t(keys.conditionAddCondition)}\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))\n\t\t\t)}\n\t\t\t<div className=\"fb-condition-builder__actions\">\n\t\t\t\t<Button\n\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\tonClick={() => (groups.length === 0 ? addCondition(0) : addOrGroup())}\n\t\t\t\t\tmargin={false}\n\t\t\t\t>\n\t\t\t\t\t{groups.length === 0 ? t(keys.conditionAddCondition) : t(keys.conditionAddOr)}\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAM,SAAS,eAA6D;CAC3E,MAAM,QAAQ,OAAO,KAAK,UAAU,EAAE;CACtC,IAAI,CAAC,OACJ,OAAO;CAER,MAAM,MAAM,WAAW;CACvB,MAAM,WAAW,OAAO,KAAK,GAAG,EAAE;CAClC,IAAI,CAAC,UACJ,OAAO;CAER,OAAO;EAAE;EAAO;EAAU,OAAO,IAAI;CAAU;AAChD;AAEA,MAAM,WAAW,SAA8B,GAAG,IAAI,QAAQ,GAAG,IAAI,WAAW,IAAI,MAAM,EAAE;AAE5F,MAAM,cAAc,UAA+C;CAClE,IAAI,CAAC,SAAS,OAAO,KAAK,KAAK,EAAE,WAAW,GAC3C,OAAO,CAAC;CAET,MAAM,YAAY,oBAAoB,KAAK;CAE3C,QADW,MAAM,QAAQ,UAAU,EAAE,IAAI,UAAU,KAAK,CAAC,GAC/C,KAAK,UAAU;EAExB,QADY,MAAM,QAAS,MAAgB,GAAG,IAAM,MAAgB,MAAkB,CAAC,GAErF,KAAK,eAAe,MAAM,UAAqC,CAAC,EAChE,QAAQ,QAA6B,QAAQ,IAAI;CACpD,CAAC;AACF;AAEA,MAAM,eAAe,YAAqC,EACzD,IAAI,OAAO,QAAQ,UAAU,MAAM,SAAS,CAAC,EAAE,KAAK,WAAW,EAAE,KAAK,MAAM,IAAI,OAAO,EAAE,EAAE,EAC5F;AAEA,MAAM,oBACL,MACA,gBACA,aACwB;CAExB,QADa,MAAM,QAAQ,KAAK,MAAM,IAAK,KAAK,SAAwB,CAAC,GAEvE,KAAK,QAAQ,eAAe,KAAK,cAAc,CAAC,EAChD,QAAQ,YAAyC,YAAY,QAAQ,QAAQ,SAAS,QAAQ;AACjG;AAEA,MAAa,oBAAoB,EAChC,OACA,UACA,gBACA,eAC4B;CAC5B,MAAM,EAAE,MAAMA,iBAAe;CAG7B,MAAM,eAAe,eAAe,CAAC,YACpC,KAAK,UAAU,iBAAiB,qBAAqB,QAAQ,IAAI,GAAG,gBAAgB,QAAQ,CAAC,CAC9F;CACA,MAAM,WAAW,cAAc,KAAK,MAAM,YAAY,GAAyB,CAAC,YAAY,CAAC;CAE7F,MAAM,SAAS,WAAW,KAAK;CAC/B,MAAM,QAAQ,SAA2B,SAAS,YAAY,IAAI,CAAC;CAEnE,MAAM,gBAAgB,eAAuB;EAC5C,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,OACJ;EAED,MAAM,MAAoB;GACzB,OAAO,MAAM;GACb,UAAU,iBAAiB,MAAM,aAAa;GAC9C,OAAO,KAAA;EACR;EACA,KACC,OAAO,WAAW,IACf,CAAC,CAAC,GAAG,CAAC,IACN,OAAO,KAAK,OAAO,UAAW,UAAU,aAAa,CAAC,GAAG,OAAO,GAAG,IAAI,KAAM,CACjF;CACD;CAEA,MAAM,mBAAmB;EACxB,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,OACJ;EAED,KAAK,CACJ,GAAG,QACH,CAAC;GAAE,OAAO,MAAM;GAAM,UAAU,iBAAiB,MAAM,aAAa;GAAG,OAAO,KAAA;EAAU,CAAC,CAC1F,CAAC;CACF;CAEA,MAAM,aAAa,YAAoB,UAAkB,QACxD,KACC,OAAO,KAAK,OAAO,UAClB,UAAU,aACP,MAAM,KAAK,UAAU,aAAc,aAAa,WAAW,MAAM,QAAS,IAC1E,KACJ,CACD;CAED,MAAM,aAAa,YAAoB,aACtC,KACC,OACE,KAAK,OAAO,UACZ,UAAU,aAAa,MAAM,QAAQ,GAAG,aAAa,aAAa,QAAQ,IAAI,KAC/E,EACC,QAAQ,UAAU,MAAM,SAAS,CAAC,CACrC;CAED,IAAI,SAAS,WAAW,GACvB,OAAO,oBAAC,KAAD;EAAG,WAAU;YAA8B,EAAE,KAAK,iBAAiB;CAAK,CAAA;CAGhF,OACC,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,OAAO,WAAW,IAClB,oBAAC,KAAD;GAAG,WAAU;aAA8B,EAAE,KAAK,cAAc;EAAK,CAAA,IAErE,OAAO,KAAK,OAAO,eAElB,qBAAC,OAAD;GAAsB,WAAU;aAAhC;IACE,aAAa,IACb,oBAAC,OAAD;KAAK,WAAU;eAAkC,EAAE,KAAK,WAAW;IAAO,CAAA,IACvE;IACH,MAAM,KAAK,KAAK,aAEhB,qBAAC,OAAD;KAAoB,WAAU;eAA9B,CACE,WAAW,IACX,oBAAC,QAAD;MAAM,WAAU;gBAAmC,EAAE,KAAK,YAAY;KAAQ,CAAA,IAC3E,MACJ,oBAAC,cAAD;MACC,WAAW;MACD;MACV,WAAW,SAAS,UAAU,YAAY,UAAU,IAAI;MACxD,gBAAgB,UAAU,YAAY,QAAQ;KAC9C,CAAA,CACG;OAVK,QAUL,CACL;IACD,oBAAC,OAAD;KAAK,WAAU;eACd,oBAAC,QAAD;MACC,aAAY;MACZ,MAAK;MACL,WAAU;MACV,cAAa;MACb,eAAe,aAAa,UAAU;MACtC,QAAQ;gBAEP,EAAE,KAAK,qBAAqB;KACtB,CAAA;IACJ,CAAA;GACD;KA9BK,UA8BL,CACL,GAEF,oBAAC,OAAD;GAAK,WAAU;aACd,oBAAC,QAAD;IACC,aAAY;IACZ,MAAK;IACL,WAAU;IACV,cAAa;IACb,eAAgB,OAAO,WAAW,IAAI,aAAa,CAAC,IAAI,WAAW;IACnE,QAAQ;cAEP,OAAO,WAAW,IAAI,EAAE,KAAK,qBAAqB,IAAI,EAAE,KAAK,cAAc;GACrE,CAAA;EACJ,CAAA,CACD;;AAEP"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/client/FieldNameSelect.d.ts
|
|
2
|
+
/** Props: standard JSON/text field client props plus the `types` allow-list we pass via clientProps. */
|
|
3
|
+
type FieldNameSelectProps = {
|
|
4
|
+
path?: string;
|
|
5
|
+
field?: {
|
|
6
|
+
label?: unknown;
|
|
7
|
+
};
|
|
8
|
+
label?: unknown; /** Sibling `fields` blocks whose `blockType` is in this list populate the select's options. */
|
|
9
|
+
types: string[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The `Field` mounted on a text config field (e.g. an action's `toField`) that should be authored
|
|
13
|
+
* 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`; the stored value is kept selectable even if
|
|
15
|
+
* it no longer matches any field, so switching to this component never silently drops data.
|
|
16
|
+
*/
|
|
17
|
+
declare const FieldNameSelect: (props: FieldNameSelectProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { FieldNameSelect };
|
|
20
|
+
//# sourceMappingURL=FieldNameSelect.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { fieldNamesOfType } from "../fields/fieldNamesOfType.js";
|
|
3
|
+
import { toStaticLabel } from "./toStaticLabel.js";
|
|
4
|
+
import { reduceFieldsToValues } from "payload/shared";
|
|
5
|
+
import { FieldLabel, ReactSelect, useField, useFormFields } from "@payloadcms/ui";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
//#region src/client/FieldNameSelect.tsx
|
|
9
|
+
const optionsFromData = (data, types) => {
|
|
10
|
+
const rows = Array.isArray(data.fields) ? data.fields : [];
|
|
11
|
+
const labels = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
if (!row || typeof row !== "object") continue;
|
|
14
|
+
const { name, label } = row;
|
|
15
|
+
if (typeof name === "string" && typeof label === "string" && label.length > 0) labels.set(name.trim(), label);
|
|
16
|
+
}
|
|
17
|
+
return fieldNamesOfType(data.fields, types).map((name) => ({
|
|
18
|
+
label: labels.get(name) ?? name,
|
|
19
|
+
value: name
|
|
20
|
+
}));
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The `Field` mounted on a text config field (e.g. an action's `toField`) that should be authored
|
|
24
|
+
* 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`; the stored value is kept selectable even if
|
|
26
|
+
* it no longer matches any field, so switching to this component never silently drops data.
|
|
27
|
+
*/
|
|
28
|
+
const FieldNameSelect = (props) => {
|
|
29
|
+
const { path, setValue, value } = useField({ path: props.path });
|
|
30
|
+
const label = toStaticLabel(props.field?.label ?? props.label);
|
|
31
|
+
const optionsJson = useFormFields(([fields]) => JSON.stringify(optionsFromData(reduceFieldsToValues(fields, true), props.types)));
|
|
32
|
+
const options = useMemo(() => JSON.parse(optionsJson), [optionsJson]);
|
|
33
|
+
const allOptions = value && !options.some((option) => option.value === value) ? [...options, {
|
|
34
|
+
label: value,
|
|
35
|
+
value
|
|
36
|
+
}] : options;
|
|
37
|
+
const handleChange = (selected) => {
|
|
38
|
+
const chosen = Array.isArray(selected) ? selected[0] : selected;
|
|
39
|
+
setValue(chosen ? chosen.value : "");
|
|
40
|
+
};
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
42
|
+
className: "field-type",
|
|
43
|
+
style: { marginBlockEnd: "1rem" },
|
|
44
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
45
|
+
label,
|
|
46
|
+
path
|
|
47
|
+
}), /* @__PURE__ */ jsx(ReactSelect, {
|
|
48
|
+
options: allOptions,
|
|
49
|
+
value: allOptions.find((option) => option.value === value) ?? void 0,
|
|
50
|
+
isClearable: true,
|
|
51
|
+
onChange: handleChange
|
|
52
|
+
})]
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
export { FieldNameSelect };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=FieldNameSelect.js.map
|
|
@@ -0,0 +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/** Sibling `fields` blocks whose `blockType` is in this list populate the select's options. */\n\ttypes: string[]\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\treturn fieldNamesOfType(data.fields, types).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`; the stored value is kept selectable even if\n * it no longer matches any field, so switching to this 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\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</div>\n\t)\n}\n"],"mappings":";;;;;;;;AAwBA,MAAM,mBAAmB,MAA+B,UAAyC;CAChG,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;CACA,OAAO,iBAAiB,KAAK,QAAQ,KAAK,EAAE,KAAK,UAAU;EAC1D,OAAO,OAAO,IAAI,IAAI,KAAK;EAC3B,OAAO;CACR,EAAE;AACH;;;;;;;AAQA,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;CAE7D,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,CACC,oBAAC,YAAD;GAAmB;GAAa;EAAO,CAAA,GACvC,oBAAC,aAAD;GACC,SAAS;GACT,OAAO,WAAW,MAAM,WAAW,OAAO,UAAU,KAAK,KAAK,KAAA;GAC9D,aAAA;GACA,UAAU;EACV,CAAA,CACG;;AAEP"}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { toStaticLabel } from "./toStaticLabel.js";
|
|
2
3
|
import { ConditionBuilder } from "./ConditionBuilder.js";
|
|
3
4
|
import { reduceFieldsToValues } from "payload/shared";
|
|
4
5
|
import { Button, CheckboxInput, Collapsible, FieldLabel, ReactSelect, TextInput, useField, useFormFields } from "@payloadcms/ui";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useMemo, useRef, useState } from "react";
|
|
5
8
|
import { Popup, PopupList } from "@payloadcms/ui/elements/Popup";
|
|
6
9
|
import { MoreIcon } from "@payloadcms/ui/icons/More";
|
|
7
|
-
import
|
|
8
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import "./admin.css";
|
|
9
11
|
//#region src/client/FlowBuilder.tsx
|
|
10
|
-
const toStaticLabel = (label) => {
|
|
11
|
-
if (typeof label === "string") return label;
|
|
12
|
-
if (label && typeof label === "object") return label;
|
|
13
|
-
};
|
|
14
12
|
const extractFieldNames = (data) => {
|
|
15
13
|
return (Array.isArray(data.fields) ? data.fields : []).map((row) => typeof row.name === "string" ? row.name.trim() : null).filter((name) => name !== null && name.length > 0);
|
|
16
14
|
};
|