@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.
Files changed (156) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +2 -2
  3. package/dist/actions/body/converters.d.ts +20 -0
  4. package/dist/actions/body/converters.js +57 -0
  5. package/dist/actions/body/converters.js.map +1 -0
  6. package/dist/actions/body/escapeHtml.d.ts +6 -0
  7. package/dist/actions/body/escapeHtml.js +14 -0
  8. package/dist/actions/body/escapeHtml.js.map +1 -0
  9. package/dist/actions/body/serializeBody.d.ts +35 -0
  10. package/dist/actions/body/serializeBody.js +76 -0
  11. package/dist/actions/body/serializeBody.js.map +1 -0
  12. package/dist/actions/body/serializeSlate.js +31 -0
  13. package/dist/actions/body/serializeSlate.js.map +1 -0
  14. package/dist/actions/body/wildcards.d.ts +10 -0
  15. package/dist/actions/body/wildcards.js +33 -0
  16. package/dist/actions/body/wildcards.js.map +1 -0
  17. package/dist/actions/builtin/confirmation.js +28 -12
  18. package/dist/actions/builtin/confirmation.js.map +1 -1
  19. package/dist/actions/builtin/emailTeam.js +7 -9
  20. package/dist/actions/builtin/emailTeam.js.map +1 -1
  21. package/dist/actions/builtin/signedWebhook.js +21 -3
  22. package/dist/actions/builtin/signedWebhook.js.map +1 -1
  23. package/dist/actions/defineAction.d.ts +2 -1
  24. package/dist/actions/defineAction.js.map +1 -1
  25. package/dist/actions/dispatch.js +2 -1
  26. package/dist/actions/dispatch.js.map +1 -1
  27. package/dist/actions/runActions.js +8 -1
  28. package/dist/actions/runActions.js.map +1 -1
  29. package/dist/actions/task.js +7 -5
  30. package/dist/actions/task.js.map +1 -1
  31. package/dist/calc/normalizeCalc.js +5 -17
  32. package/dist/calc/normalizeCalc.js.map +1 -1
  33. package/dist/calc/types.d.ts +8 -2
  34. package/dist/calc/types.js +22 -0
  35. package/dist/calc/types.js.map +1 -0
  36. package/dist/client/ByteSizeField.d.ts +21 -0
  37. package/dist/client/ByteSizeField.js +56 -0
  38. package/dist/client/ByteSizeField.js.map +1 -0
  39. package/dist/client/ConditionBuilder.js +2 -1
  40. package/dist/client/ConditionBuilder.js.map +1 -1
  41. package/dist/client/FieldNameSelect.d.ts +20 -0
  42. package/dist/client/FieldNameSelect.js +58 -0
  43. package/dist/client/FieldNameSelect.js.map +1 -0
  44. package/dist/client/FlowBuilder.d.ts +0 -1
  45. package/dist/client/FlowBuilder.js +4 -6
  46. package/dist/client/FlowBuilder.js.map +1 -1
  47. package/dist/client/FormConditionField.js +1 -4
  48. package/dist/client/FormConditionField.js.map +1 -1
  49. package/dist/client/admin.css +192 -0
  50. package/dist/client/toStaticLabel.js +13 -0
  51. package/dist/client/toStaticLabel.js.map +1 -0
  52. package/dist/collections/formSubmissions.js +5 -3
  53. package/dist/collections/formSubmissions.js.map +1 -1
  54. package/dist/collections/forms.js +74 -57
  55. package/dist/collections/forms.js.map +1 -1
  56. package/dist/exports/client.d.ts +3 -1
  57. package/dist/exports/client.js +3 -1
  58. package/dist/exports/react.d.ts +6 -1
  59. package/dist/exports/react.js +5 -1
  60. package/dist/fields/buildFieldBlocks.js +14 -18
  61. package/dist/fields/buildFieldBlocks.js.map +1 -1
  62. package/dist/fields/builtin/calculation.js +115 -2
  63. package/dist/fields/builtin/calculation.js.map +1 -1
  64. package/dist/fields/builtin/date.js +29 -0
  65. package/dist/fields/builtin/date.js.map +1 -0
  66. package/dist/fields/builtin/file.d.ts +15 -0
  67. package/dist/fields/builtin/file.js +79 -3
  68. package/dist/fields/builtin/file.js.map +1 -1
  69. package/dist/fields/builtin/index.d.ts +14 -0
  70. package/dist/fields/builtin/index.js +11 -2
  71. package/dist/fields/builtin/index.js.map +1 -1
  72. package/dist/fields/fieldNamesOfType.js +25 -0
  73. package/dist/fields/fieldNamesOfType.js.map +1 -0
  74. package/dist/fields/sharedConfig.js +30 -18
  75. package/dist/fields/sharedConfig.js.map +1 -1
  76. package/dist/index.d.ts +19 -2
  77. package/dist/index.js +14 -4
  78. package/dist/index.js.map +1 -1
  79. package/dist/plugin/registerCollections.js +3 -2
  80. package/dist/plugin/registerCollections.js.map +1 -1
  81. package/dist/react/Form.js +3 -3
  82. package/dist/react/Form.js.map +1 -1
  83. package/dist/react/Poll.js +1 -1
  84. package/dist/react/captcha/HcaptchaCaptcha.d.ts +28 -0
  85. package/dist/react/captcha/HcaptchaCaptcha.js +53 -0
  86. package/dist/react/captcha/HcaptchaCaptcha.js.map +1 -0
  87. package/dist/react/captcha/RecaptchaCaptcha.d.ts +41 -0
  88. package/dist/react/captcha/RecaptchaCaptcha.js +93 -0
  89. package/dist/react/captcha/RecaptchaCaptcha.js.map +1 -0
  90. package/dist/react/captcha/TurnstileCaptcha.d.ts +28 -0
  91. package/dist/react/captcha/TurnstileCaptcha.js +53 -0
  92. package/dist/react/captcha/TurnstileCaptcha.js.map +1 -0
  93. package/dist/react/captcha/types.d.ts +8 -0
  94. package/dist/react/captcha/useCaptchaScript.js +45 -0
  95. package/dist/react/captcha/useCaptchaScript.js.map +1 -0
  96. package/dist/react/captcha/vendors.js +8 -0
  97. package/dist/react/captcha/vendors.js.map +1 -0
  98. package/dist/react/primitives/Input.d.ts +1 -1
  99. package/dist/react/primitives/Input.js.map +1 -1
  100. package/dist/react/renderers/calculation.js +1 -1
  101. package/dist/react/renderers/checkbox.js +1 -1
  102. package/dist/react/renderers/consent.js +1 -1
  103. package/dist/react/renderers/date.js +37 -0
  104. package/dist/react/renderers/date.js.map +1 -0
  105. package/dist/react/renderers/email.js +1 -1
  106. package/dist/react/renderers/file.js +20 -7
  107. package/dist/react/renderers/file.js.map +1 -1
  108. package/dist/react/renderers/index.js +2 -0
  109. package/dist/react/renderers/index.js.map +1 -1
  110. package/dist/react/renderers/number.js +1 -1
  111. package/dist/react/renderers/repeater.js +1 -1
  112. package/dist/react/renderers/select.js +1 -1
  113. package/dist/react/renderers/text.js +1 -1
  114. package/dist/react/renderers/textarea.js +1 -1
  115. package/dist/spam/captcha.d.ts +3 -2
  116. package/dist/spam/captcha.js +3 -2
  117. package/dist/spam/captcha.js.map +1 -1
  118. package/dist/spam/clientIp.js +9 -0
  119. package/dist/spam/clientIp.js.map +1 -0
  120. package/dist/spam/identify.js +3 -6
  121. package/dist/spam/identify.js.map +1 -1
  122. package/dist/spam/providers/hcaptcha.d.ts +17 -0
  123. package/dist/spam/providers/hcaptcha.js +18 -0
  124. package/dist/spam/providers/hcaptcha.js.map +1 -0
  125. package/dist/spam/providers/recaptcha.d.ts +23 -0
  126. package/dist/spam/providers/recaptcha.js +23 -0
  127. package/dist/spam/providers/recaptcha.js.map +1 -0
  128. package/dist/spam/providers/siteverify.js +34 -0
  129. package/dist/spam/providers/siteverify.js.map +1 -0
  130. package/dist/spam/providers/turnstile.d.ts +17 -0
  131. package/dist/spam/providers/turnstile.js +18 -0
  132. package/dist/spam/providers/turnstile.js.map +1 -0
  133. package/dist/spam/spamGuard.js +2 -5
  134. package/dist/spam/spamGuard.js.map +1 -1
  135. package/dist/spam/types.d.ts +2 -2
  136. package/dist/translations/en.js +26 -4
  137. package/dist/translations/en.js.map +1 -1
  138. package/dist/translations/keys.d.ts +26 -4
  139. package/dist/translations/keys.js +26 -4
  140. package/dist/translations/keys.js.map +1 -1
  141. package/dist/uploads/formatBytes.d.ts +9 -0
  142. package/dist/uploads/formatBytes.js +25 -0
  143. package/dist/uploads/formatBytes.js.map +1 -0
  144. package/dist/uploads/resolveFileRef.js +1 -1
  145. package/dist/uploads/resolveFileRef.js.map +1 -1
  146. package/dist/validation/builtin/index.d.ts +14 -0
  147. package/dist/validation/builtin/index.js +12 -1
  148. package/dist/validation/builtin/index.js.map +1 -1
  149. package/dist/validation/builtin/maxDate.js +23 -0
  150. package/dist/validation/builtin/maxDate.js.map +1 -0
  151. package/dist/validation/builtin/minDate.js +23 -0
  152. package/dist/validation/builtin/minDate.js.map +1 -0
  153. package/dist/validation/builtin/validateDateBound.js +18 -0
  154. package/dist/validation/builtin/validateDateBound.js.map +1 -0
  155. package/package.json +3 -2
  156. package/styles/form-builder.css +0 -190
@@ -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":";AAoBA,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;CACD,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
+ {"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":";;;;;AAiBA,MAAa,aAAa,OAAO,SAAkD;CAClF,MAAM,EAAE,SAAS,UAAU,GAAG,QAAQ;CACtC,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,QAAQ;GAAS,CAAC;GACjD,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"}
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"}
@@ -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 };
@@ -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 = (registry: ActionRegistry): 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})\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 = (config: Config, registry: ActionRegistry): void => {\n\tconfig.jobs ??= {}\n\tconfig.jobs.tasks ??= []\n\tconfig.jobs.tasks.push(buildActionsTask(registry))\n}\n"],"mappings":";;;;;AAUA,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,SAKzB;CACpB,MAAM,EAAE,OAAO,UAAU,SAAS,QAAQ;CAC1C,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,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,oBAAoB,cAC/B;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;EACD,CAAC;EACD,OAAO,EAAE,QAAQ,CAAC,EAAE;CACrB;AACD;;AAGD,MAAa,uBAAuB,QAAgB,aAAmC;CACtF,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,UAAU,CAAC;CACvB,OAAO,KAAK,MAAM,KAAK,iBAAiB,QAAQ,CAAC;AAClD"}
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 OP_SET = new Set([
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 (typeof op !== "string" || !OP_SET.has(op)) return void 0;
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 (typeof fn !== "string" || !FN_SET.has(fn)) return void 0;
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 { CalcExpression } from './types'\n\nconst MAX_DEPTH = 64\n\nconst OP_SET = new Set(['+', '-', '*', '/', '%'])\nconst FN_SET = new Set(['min', 'max', 'round', 'abs', 'ceil', 'floor'])\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 (typeof op !== 'string' || !OP_SET.has(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: op as '+' | '-' | '*' | '/' | '%', 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 (typeof fn !== 'string' || !FN_SET.has(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 {\n\t\t\t\ttype: 'fn',\n\t\t\t\tfn: fn as 'min' | 'max' | 'round' | 'abs' | 'ceil' | 'floor',\n\t\t\t\targs: normalizedArgs,\n\t\t\t}\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,SAAS,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;AAChD,MAAM,SAAS,IAAI,IAAI;CAAC;CAAO;CAAO;CAAS;CAAO;CAAQ;AAAO,CAAC;AAEtE,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,OAAO,OAAO,YAAY,CAAC,OAAO,IAAI,EAAE,GAAG,OAAO,KAAA;GACtD,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;IAAU;IAAmC,MAAM;IAAG,OAAO;GAAE;EAC/E;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,OAAO,OAAO,YAAY,CAAC,OAAO,IAAI,EAAE,GAAG,OAAO,KAAA;GACtD,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;IACN,MAAM;IACF;IACJ,MAAM;GACP;EACD;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"}
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"}
@@ -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: 'min' | 'max' | 'round' | 'abs' | 'ceil' | 'floor';
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":"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"}
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":"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"}
@@ -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,5 +1,4 @@
1
1
  import { ConditionFieldType } from "../conditions/fieldTypes.js";
2
-
3
2
  //#region src/client/FlowBuilder.d.ts
4
3
  type FlowBuilderProps = {
5
4
  path?: string;
@@ -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 { useMemo, useRef, useState } from "react";
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
  };