@10x-media/form-builder 0.1.0-beta.11 → 0.1.0-beta.13

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 (151) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/aggregation/resolveResultsRequest.d.ts +6 -0
  3. package/dist/aggregation/resolveResultsRequest.js +32 -3
  4. package/dist/aggregation/resolveResultsRequest.js.map +1 -1
  5. package/dist/calc/computeCalcFields.d.ts +5 -3
  6. package/dist/calc/computeCalcFields.js +4 -3
  7. package/dist/calc/computeCalcFields.js.map +1 -1
  8. package/dist/calc/evaluate.d.ts +26 -3
  9. package/dist/calc/evaluate.js +33 -11
  10. package/dist/calc/evaluate.js.map +1 -1
  11. package/dist/calc/formatCalc.d.ts +11 -0
  12. package/dist/calc/formatCalc.js +35 -0
  13. package/dist/calc/formatCalc.js.map +1 -0
  14. package/dist/calc/formatCalcValue.d.ts +17 -0
  15. package/dist/calc/formatCalcValue.js +17 -0
  16. package/dist/calc/formatCalcValue.js.map +1 -0
  17. package/dist/calc/normalizeCalc.d.ts +17 -3
  18. package/dist/calc/normalizeCalc.js +39 -17
  19. package/dist/calc/normalizeCalc.js.map +1 -1
  20. package/dist/calc/registry.d.ts +43 -0
  21. package/dist/calc/registry.js +27 -0
  22. package/dist/calc/registry.js.map +1 -0
  23. package/dist/calc/resolveCalcContext.d.ts +36 -0
  24. package/dist/calc/resolveCalcContext.js +87 -0
  25. package/dist/calc/resolveCalcContext.js.map +1 -0
  26. package/dist/calc/types.d.ts +18 -3
  27. package/dist/calc/types.js +2 -1
  28. package/dist/calc/types.js.map +1 -1
  29. package/dist/client/CalcExpressionBuilder.d.ts +20 -0
  30. package/dist/client/CalcExpressionBuilder.js +890 -0
  31. package/dist/client/CalcExpressionBuilder.js.map +1 -0
  32. package/dist/client/admin.css +282 -0
  33. package/dist/collections/formSubmissions.js +7 -9
  34. package/dist/collections/formSubmissions.js.map +1 -1
  35. package/dist/collections/forms.js +59 -34
  36. package/dist/collections/forms.js.map +1 -1
  37. package/dist/collections/formsEndpoints.js +4 -2
  38. package/dist/collections/formsEndpoints.js.map +1 -1
  39. package/dist/collections/settingsFields.d.ts +31 -0
  40. package/dist/collections/settingsFields.js +45 -0
  41. package/dist/collections/settingsFields.js.map +1 -0
  42. package/dist/exports/client.d.ts +2 -1
  43. package/dist/exports/client.js +2 -1
  44. package/dist/exports/react.d.ts +7 -3
  45. package/dist/exports/react.js +6 -2
  46. package/dist/exports/rsc.js +1 -1
  47. package/dist/exports/types.d.ts +1 -1
  48. package/dist/fields/buildFieldBlocks.js +2 -1
  49. package/dist/fields/buildFieldBlocks.js.map +1 -1
  50. package/dist/fields/builtin/calculation.d.ts +17 -0
  51. package/dist/fields/builtin/calculation.js +170 -115
  52. package/dist/fields/builtin/calculation.js.map +1 -1
  53. package/dist/fields/builtin/checkbox.js +15 -0
  54. package/dist/fields/builtin/checkbox.js.map +1 -1
  55. package/dist/fields/builtin/email.js +7 -0
  56. package/dist/fields/builtin/email.js.map +1 -1
  57. package/dist/fields/builtin/index.d.ts +6 -2
  58. package/dist/fields/builtin/index.js +9 -4
  59. package/dist/fields/builtin/index.js.map +1 -1
  60. package/dist/fields/builtin/select.js +20 -1
  61. package/dist/fields/builtin/select.js.map +1 -1
  62. package/dist/fields/builtin/text.js +7 -0
  63. package/dist/fields/builtin/text.js.map +1 -1
  64. package/dist/fields/instanceOptions.js +1 -1
  65. package/dist/fields/instanceOptions.js.map +1 -1
  66. package/dist/fields/sharedConfig.js +15 -11
  67. package/dist/fields/sharedConfig.js.map +1 -1
  68. package/dist/fields/types.d.ts +8 -2
  69. package/dist/form/toFormDocument.d.ts +7 -0
  70. package/dist/form/toFormDocument.js +2 -1
  71. package/dist/form/toFormDocument.js.map +1 -1
  72. package/dist/form/types.d.ts +7 -0
  73. package/dist/index.d.ts +12 -4
  74. package/dist/index.js +35 -8
  75. package/dist/index.js.map +1 -1
  76. package/dist/options.d.ts +35 -0
  77. package/dist/plugin/registerCollections.js +29 -19
  78. package/dist/plugin/registerCollections.js.map +1 -1
  79. package/dist/poll/closeJob.d.ts +4 -3
  80. package/dist/poll/closeJob.js +6 -4
  81. package/dist/poll/closeJob.js.map +1 -1
  82. package/dist/poll/pollTypeRegistry.js +1 -1
  83. package/dist/poll/resolvePollCloseRequest.js +2 -1
  84. package/dist/poll/resolvePollCloseRequest.js.map +1 -1
  85. package/dist/poll/resolvePollOutcome.d.ts +6 -0
  86. package/dist/poll/resolvePollOutcome.js +17 -3
  87. package/dist/poll/resolvePollOutcome.js.map +1 -1
  88. package/dist/poll/votes/aggregateFromVotes.d.ts +27 -0
  89. package/dist/poll/votes/aggregateFromVotes.js +53 -0
  90. package/dist/poll/votes/aggregateFromVotes.js.map +1 -0
  91. package/dist/poll/votes/bumpPollVote.js +88 -0
  92. package/dist/poll/votes/bumpPollVote.js.map +1 -0
  93. package/dist/poll/votes/recountPollVotes.d.ts +20 -0
  94. package/dist/poll/votes/recountPollVotes.js +63 -0
  95. package/dist/poll/votes/recountPollVotes.js.map +1 -0
  96. package/dist/poll/votes/voteTallyHook.js +58 -0
  97. package/dist/poll/votes/voteTallyHook.js.map +1 -0
  98. package/dist/poll/votes/votesCollection.d.ts +19 -0
  99. package/dist/poll/votes/votesCollection.js +89 -0
  100. package/dist/poll/votes/votesCollection.js.map +1 -0
  101. package/dist/react/Form.d.ts +7 -0
  102. package/dist/react/Form.js +20 -3
  103. package/dist/react/Form.js.map +1 -1
  104. package/dist/react/FormContext.d.ts +8 -1
  105. package/dist/react/FormContext.js.map +1 -1
  106. package/dist/react/primitives/Checkbox.d.ts +4 -2
  107. package/dist/react/primitives/Checkbox.js +3 -2
  108. package/dist/react/primitives/Checkbox.js.map +1 -1
  109. package/dist/react/primitives/ChoiceGroup.d.ts +40 -0
  110. package/dist/react/primitives/ChoiceGroup.js +33 -0
  111. package/dist/react/primitives/ChoiceGroup.js.map +1 -0
  112. package/dist/react/primitives/FieldShell.d.ts +11 -1
  113. package/dist/react/primitives/FieldShell.js +43 -33
  114. package/dist/react/primitives/FieldShell.js.map +1 -1
  115. package/dist/react/primitives/Input.d.ts +3 -1
  116. package/dist/react/primitives/Input.js +2 -1
  117. package/dist/react/primitives/Input.js.map +1 -1
  118. package/dist/react/renderers/calculation.js +6 -1
  119. package/dist/react/renderers/calculation.js.map +1 -1
  120. package/dist/react/renderers/checkbox.js +2 -1
  121. package/dist/react/renderers/checkbox.js.map +1 -1
  122. package/dist/react/renderers/email.js +3 -1
  123. package/dist/react/renderers/email.js.map +1 -1
  124. package/dist/react/renderers/select.js +20 -2
  125. package/dist/react/renderers/select.js.map +1 -1
  126. package/dist/react/renderers/text.js +3 -1
  127. package/dist/react/renderers/text.js.map +1 -1
  128. package/dist/react/useCalcValues.d.ts +10 -0
  129. package/dist/react/useCalcValues.js +13 -0
  130. package/dist/react/useCalcValues.js.map +1 -0
  131. package/dist/recall/resolver.js +1 -1
  132. package/dist/recall/resolver.js.map +1 -1
  133. package/dist/submissions/formIdOf.js +13 -0
  134. package/dist/submissions/formIdOf.js.map +1 -0
  135. package/dist/submissions/runSubmission.js +3 -3
  136. package/dist/submissions/runSubmission.js.map +1 -1
  137. package/dist/submissions/validateSubmission.js +26 -2
  138. package/dist/submissions/validateSubmission.js.map +1 -1
  139. package/dist/translations/de.js +58 -3
  140. package/dist/translations/de.js.map +1 -1
  141. package/dist/translations/en.js +58 -3
  142. package/dist/translations/en.js.map +1 -1
  143. package/dist/translations/keys.d.ts +56 -1
  144. package/dist/translations/keys.js +56 -1
  145. package/dist/translations/keys.js.map +1 -1
  146. package/dist/validation/builtin/index.js +2 -0
  147. package/dist/validation/builtin/index.js.map +1 -1
  148. package/dist/validation/builtin/integer.js +15 -0
  149. package/dist/validation/builtin/integer.js.map +1 -0
  150. package/package.json +10 -4
  151. package/styles/form-builder.css +84 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CalcExpressionBuilder.js","names":["useTranslation"],"sources":["../../src/client/CalcExpressionBuilder.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tButton,\n\tFieldError,\n\tFieldLabel,\n\tReactSelect,\n\ttype ReactSelectOption,\n\tRenderCustomComponent,\n\tuseField,\n\tuseFormFields,\n} from '@payloadcms/ui'\nimport { reduceFieldsToValues } from 'payload/shared'\nimport { useMemo, useState } from 'react'\nimport { formatCalc } from '../calc/formatCalc'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport {\n\tCALC_FNS,\n\tCALC_OPS,\n\ttype CalcExpression,\n\ttype CalcFn,\n\ttype CalcFnName,\n\ttype CalcOp,\n\tisCalcFn,\n} from '../calc/types'\nimport type { CalcSourceClientMeta } from '../fields/builtin/calculation'\nimport { keys, type TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport type { FieldRow } from './synthesizeClientField'\nimport { toStaticLabel } from './toStaticLabel'\nimport './admin.css'\n\nexport type CalcExpressionBuilderProps = {\n\tpath?: string\n\tfield?: { label?: unknown }\n\treadOnly?: boolean\n\t/** Registered calc-source metadata (key, label, modes), threaded as clientProps by `buildCalculationField`. */\n\tsources?: CalcSourceClientMeta[]\n\t/** Registered custom calc function names, so client-side normalization accepts what the server accepts. */\n\tfunctions?: string[]\n}\n\n/**\n * The editing view model: authors read and build expressions left-to-right, so the UI edits a\n * chain (first operand, then op+operand steps, then an optional wrapping unary function) and the\n * stored `CalcExpression` AST is derived by left-folding. The AST contract is untouched.\n */\nexport type CalcChain = { first: CalcOperand; steps: CalcChainStep[]; finish?: CalcFn }\nexport type CalcChainStep = { op: CalcOp; operand: CalcOperand }\nexport type CalcOperand =\n\t| { kind: 'ref'; field: string }\n\t| { kind: 'lit'; value: number }\n\t| { kind: 'weight'; field: string; weights?: Record<string, number>; source?: string }\n\t| { kind: 'source'; source: string }\n\t| { kind: 'group'; chain: CalcChain }\n\t| { kind: 'fn'; fn: CalcFnName; args: CalcChain[] }\n\t| { kind: 'neg'; operand: CalcOperand }\n\n/** Kinds a leaf operand card can switch between in place; containers come from Start with, loading, or JSON. */\ntype LeafKind = 'lit' | 'ref' | 'weight'\ntype StartKind = 'fn' | LeafKind\n\n/** The single-argument functions offered by the \"Then apply\" finisher; min/max are variadic and stay Function operands. */\nconst UNARY_FNS = CALC_FNS.filter((fn) => fn !== 'min' && fn !== 'max')\n\nconst seedOperand = (kind: LeafKind): CalcOperand => {\n\tswitch (kind) {\n\t\tcase 'ref':\n\t\t\treturn { kind: 'ref', field: '' }\n\t\tcase 'lit':\n\t\t\treturn { kind: 'lit', value: 0 }\n\t\tcase 'weight':\n\t\t\treturn { kind: 'weight', field: '', weights: {} }\n\t}\n}\n\nconst seedAst = (kind: StartKind): CalcExpression => {\n\tswitch (kind) {\n\t\tcase 'ref':\n\t\t\treturn { type: 'ref', field: '' }\n\t\tcase 'lit':\n\t\t\treturn { type: 'lit', value: 0 }\n\t\tcase 'weight':\n\t\t\treturn { type: 'weight', field: '', weights: {} }\n\t\tcase 'fn':\n\t\t\treturn { type: 'fn', fn: 'min', args: [{ type: 'lit', value: 0 }] }\n\t}\n}\n\nconst astToOperand = (node: CalcExpression): CalcOperand => {\n\tswitch (node.type) {\n\t\tcase 'ref':\n\t\t\treturn { kind: 'ref', field: node.field }\n\t\tcase 'lit':\n\t\t\treturn { kind: 'lit', value: node.value }\n\t\tcase 'source':\n\t\t\treturn { kind: 'source', source: node.source }\n\t\tcase 'weight':\n\t\t\treturn {\n\t\t\t\tkind: 'weight',\n\t\t\t\tfield: node.field,\n\t\t\t\t...(node.weights !== undefined ? { weights: node.weights } : {}),\n\t\t\t\t...(node.source !== undefined ? { source: node.source } : {}),\n\t\t\t}\n\t\tcase 'neg':\n\t\t\treturn { kind: 'neg', operand: astToOperand(node.operand) }\n\t\tcase 'fn':\n\t\t\treturn { kind: 'fn', fn: node.fn, args: node.args.map((arg) => toChain(arg, false)) }\n\t\tcase 'op':\n\t\t\treturn { kind: 'group', chain: toChain(node, false) }\n\t}\n}\n\nconst toChain = (expr: CalcExpression, allowFinish: boolean): CalcChain => {\n\tconst arg = expr.type === 'fn' ? expr.args[0] : undefined\n\tif (\n\t\tallowFinish &&\n\t\texpr.type === 'fn' &&\n\t\texpr.args.length === 1 &&\n\t\targ !== undefined &&\n\t\tisCalcFn(expr.fn) &&\n\t\t(UNARY_FNS as readonly CalcFn[]).includes(expr.fn)\n\t) {\n\t\treturn { ...toChain(arg, false), finish: expr.fn }\n\t}\n\tconst steps: CalcChainStep[] = []\n\tlet node = expr\n\twhile (node.type === 'op') {\n\t\tsteps.unshift({ op: node.op, operand: astToOperand(node.right) })\n\t\tnode = node.left\n\t}\n\treturn { first: astToOperand(node), steps }\n}\n\n/**\n * AST -> chain: the left spine of binary ops flattens into first + steps (a nested op on the right\n * becomes a `group`); a root unary fn wrapping the whole expression loads as `finish` (min/max stay\n * fn operands so the finisher select never holds a value it cannot offer). Total: every valid\n * `CalcExpression` loads without loss, and `chainToAst(astToChain(x))` is structurally `x`.\n */\nexport const astToChain = (expr: CalcExpression): CalcChain => toChain(expr, true)\n\nconst operandToAst = (operand: CalcOperand): CalcExpression => {\n\tswitch (operand.kind) {\n\t\tcase 'ref':\n\t\t\treturn { type: 'ref', field: operand.field }\n\t\tcase 'lit':\n\t\t\treturn { type: 'lit', value: operand.value }\n\t\tcase 'weight':\n\t\t\treturn {\n\t\t\t\ttype: 'weight',\n\t\t\t\tfield: operand.field,\n\t\t\t\t...(operand.weights !== undefined ? { weights: operand.weights } : {}),\n\t\t\t\t...(operand.source !== undefined ? { source: operand.source } : {}),\n\t\t\t}\n\t\tcase 'source':\n\t\t\treturn { type: 'source', source: operand.source }\n\t\tcase 'neg':\n\t\t\treturn { type: 'neg', operand: operandToAst(operand.operand) }\n\t\tcase 'fn':\n\t\t\treturn { type: 'fn', fn: operand.fn, args: operand.args.map(chainBodyToAst) }\n\t\tcase 'group':\n\t\t\treturn chainBodyToAst(operand.chain)\n\t}\n}\n\n/** Folds first + steps only. `finish` is root-only by design: nested chains (groups, fn args) never carry one, so it is deliberately ignored here. */\nconst chainBodyToAst = (chain: CalcChain): CalcExpression =>\n\tchain.steps.reduce<CalcExpression>(\n\t\t(left, step) => ({ type: 'op', op: step.op, left, right: operandToAst(step.operand) }),\n\t\toperandToAst(chain.first)\n\t)\n\n/** Chain -> AST: left-fold first + steps into the binary tree (left-assoc matches `-`/`/`/`%`), then wrap in `finish` when set. */\nexport const chainToAst = (chain: CalcChain): CalcExpression => {\n\tconst body = chainBodyToAst(chain)\n\treturn chain.finish ? { type: 'fn', fn: chain.finish, args: [body] } : body\n}\n\ntype ChoiceOption = { label: string; value: string }\ntype ChoiceSibling = { name: string; label: string; options: ChoiceOption[] }\ntype Siblings = {\n\tnumeric: ChoiceOption[]\n\tchoices: ChoiceSibling[]\n\tlabels: Record<string, string>\n}\n\nconst optionRows = (options: unknown): ChoiceOption[] => {\n\tif (!Array.isArray(options)) {\n\t\treturn []\n\t}\n\treturn options.flatMap((option) => {\n\t\tif (typeof option === 'string') {\n\t\t\treturn option.length > 0 ? [{ label: option, value: option }] : []\n\t\t}\n\t\tif (!option || typeof option !== 'object') {\n\t\t\treturn []\n\t\t}\n\t\tconst { label, value } = option as { label?: unknown; value?: unknown }\n\t\tif (typeof value !== 'string' || value.length === 0) {\n\t\t\treturn []\n\t\t}\n\t\treturn [{ label: typeof label === 'string' && label.length > 0 ? label : value, value }]\n\t})\n}\n\nconst siblingsFromData = (data: Record<string, unknown>, selfIndex: number): Siblings => {\n\tconst rows = Array.isArray(data.fields) ? (data.fields as unknown[]) : []\n\tconst siblings: Siblings = { numeric: [], choices: [], labels: {} }\n\trows.forEach((row, index) => {\n\t\tif (!row || typeof row !== 'object') {\n\t\t\treturn\n\t\t}\n\t\tconst { blockType, name, label, options } = row as FieldRow\n\t\tconst trimmed = typeof name === 'string' ? name.trim() : ''\n\t\tif (trimmed.length === 0) {\n\t\t\treturn\n\t\t}\n\t\tconst display = typeof label === 'string' && label.length > 0 ? label : trimmed\n\t\tsiblings.labels[trimmed] = display\n\t\t// Calculations fold in declaration order, so a calculation is only referenceable when it is\n\t\t// declared before this one; a forward (or self) reference would always evaluate to 0.\n\t\tconst referenceable =\n\t\t\tblockType === 'number' || (blockType === 'calculation' && index < selfIndex)\n\t\tif (referenceable) {\n\t\t\tsiblings.numeric.push({ label: display, value: trimmed })\n\t\t}\n\t\tif (blockType === 'select') {\n\t\t\tsiblings.choices.push({ name: trimmed, label: display, options: optionRows(options) })\n\t\t}\n\t})\n\treturn siblings\n}\n\nconst singleOption = (\n\tselected: ReactSelectOption | ReactSelectOption[] | null\n): ReactSelectOption | undefined => {\n\tconst chosen = Array.isArray(selected) ? selected[0] : selected\n\treturn chosen ?? undefined\n}\n\n/** Keeps a stored reference selectable even when it no longer matches any option, like FieldNameSelect. */\nconst withStored = (options: ChoiceOption[], stored: string): ChoiceOption[] =>\n\tstored.length > 0 && !options.some((option) => option.value === stored)\n\t\t? [...options, { label: stored, value: stored }]\n\t\t: options\n\ntype NumberInputProps = {\n\tvalue: number | undefined\n\tonCommit: (value: number) => void\n\tclassName: string\n\tid?: string\n\tdisabled?: boolean\n\t'aria-label'?: string\n}\n\n/**\n * Number input with a local draft: keystrokes commit only when the text parses to a finite number,\n * an invalid draft reverts to the committed value on blur, and an external value change resyncs\n * the draft.\n */\nconst NumberInput = ({\n\tvalue,\n\tonCommit,\n\tclassName,\n\tid,\n\tdisabled,\n\t'aria-label': ariaLabel,\n}: NumberInputProps) => {\n\tconst [draft, setDraft] = useState(value === undefined ? '' : String(value))\n\tconst [lastValue, setLastValue] = useState(value)\n\tif (value !== lastValue) {\n\t\tsetLastValue(value)\n\t\tif (draft.trim() === '' || Number(draft) !== value) {\n\t\t\tsetDraft(value === undefined ? '' : String(value))\n\t\t}\n\t}\n\treturn (\n\t\t<input\n\t\t\ttype=\"number\"\n\t\t\tclassName={className}\n\t\t\tid={id}\n\t\t\tdisabled={disabled}\n\t\t\taria-label={ariaLabel}\n\t\t\tvalue={draft}\n\t\t\tonChange={(event) => {\n\t\t\t\tconst text = event.target.value\n\t\t\t\tsetDraft(text)\n\t\t\t\tconst parsed = Number(text)\n\t\t\t\tif (text.trim() !== '' && Number.isFinite(parsed)) {\n\t\t\t\t\tonCommit(parsed)\n\t\t\t\t}\n\t\t\t}}\n\t\t\tonBlur={() => {\n\t\t\t\tconst parsed = Number(draft)\n\t\t\t\tif (draft.trim() === '' || !Number.isFinite(parsed)) {\n\t\t\t\t\tsetDraft(value === undefined ? '' : String(value))\n\t\t\t\t}\n\t\t\t}}\n\t\t/>\n\t)\n}\n\ntype BuilderCtx = {\n\tt: (key: TranslationKey) => string\n\tsiblings: Siblings\n\treadOnly: boolean\n\t/** Resolves a calc source key to its admin-locale display label (raw key when unregistered). */\n\tsourceLabelOf: (key: string) => string\n\t/** Scalar-capable sources as select options (`source:`-prefixed values). */\n\tscalarSourceOptions: ChoiceOption[]\n\t/** Weights-capable sources as select options (`source:`-prefixed values). */\n\tweightSourceOptions: ChoiceOption[]\n\t/** Registered custom calc function names, offered by the fn select after the built-ins. */\n\tcustomFnOptions: ChoiceOption[]\n}\n\nconst OP_OPTIONS: ChoiceOption[] = CALC_OPS.map((op) => ({ label: op, value: op }))\nconst FN_OPTIONS: ChoiceOption[] = CALC_FNS.map((fn) => ({ label: fn, value: fn }))\nconst UNARY_OPTIONS: ChoiceOption[] = UNARY_FNS.map((fn) => ({ label: fn, value: fn }))\n\n/** Source entries share kind/start selects with the fixed kinds, namespaced to avoid key collisions. */\nconst SOURCE_PREFIX = 'source:'\nconst sourceOptionValue = (key: string): string => `${SOURCE_PREFIX}${key}`\nconst sourceKeyOf = (value: string): string | undefined =>\n\tvalue.startsWith(SOURCE_PREFIX) ? value.slice(SOURCE_PREFIX.length) : undefined\n\n/** A source label is a plain string or a per-locale record; resolve it for the admin UI language. */\nconst resolveSourceLabel = (label: string | Record<string, string>, language: string): string =>\n\ttypeof label === 'string' ? label : (label[language] ?? label.en ?? Object.values(label)[0] ?? '')\n\ntype SelectOptionOrGroup = ChoiceOption | { label: string; options: ChoiceOption[] }\n\n/** Fixed kinds stay flat; sources join as a labeled group when any exist (react-select mixes both). */\nconst withSourceGroup = (\n\tbase: ChoiceOption[],\n\tsourceOptions: ChoiceOption[],\n\tgroupLabel: string\n): SelectOptionOrGroup[] =>\n\tsourceOptions.length > 0 ? [...base, { label: groupLabel, options: sourceOptions }] : base\n\ntype OperandCardProps = {\n\toperand: CalcOperand\n\tonChange: (next: CalcOperand) => void\n\tctx: BuilderCtx\n\tidPrefix: string\n}\n\nconst OperandCard = ({ operand, onChange, ctx, idPrefix }: OperandCardProps) => {\n\tconst { t, siblings, readOnly } = ctx\n\n\tif (operand.kind === 'group') {\n\t\treturn (\n\t\t\t<div className=\"fb-calc-operand fb-calc-operand--group\">\n\t\t\t\t<div className=\"fb-calc-operand__title\">{t(keys.calcBuilderGroup)}</div>\n\t\t\t\t<ChainEditor\n\t\t\t\t\tchain={operand.chain}\n\t\t\t\t\tonChange={(chain) => onChange({ ...operand, chain })}\n\t\t\t\t\tctx={ctx}\n\t\t\t\t\tidPrefix={`${idPrefix}-g`}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (operand.kind === 'fn') {\n\t\tconst fnOptions = withStored([...FN_OPTIONS, ...ctx.customFnOptions], operand.fn)\n\t\treturn (\n\t\t\t<div className=\"fb-calc-operand fb-calc-operand--fn\">\n\t\t\t\t<div className=\"fb-calc-operand__header\">\n\t\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idPrefix}-fn`}>\n\t\t\t\t\t\t{t(keys.calcBuilderFunction)}\n\t\t\t\t\t</label>\n\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\tclassName=\"fb-calc-operand__fn\"\n\t\t\t\t\t\tinputId={`${idPrefix}-fn`}\n\t\t\t\t\t\toptions={fnOptions}\n\t\t\t\t\t\tvalue={fnOptions.find((option) => option.value === operand.fn)}\n\t\t\t\t\t\tisClearable={false}\n\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\tif (chosen) {\n\t\t\t\t\t\t\t\tonChange({ ...operand, fn: String(chosen.value) })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"fb-calc-fn__args\">\n\t\t\t\t\t{operand.args.map((arg, index) => (\n\t\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: args are positional and never reordered\n\t\t\t\t\t\t<div key={index} className=\"fb-calc-fn__arg\">\n\t\t\t\t\t\t\t<div className=\"fb-calc-fn__arg-chain\">\n\t\t\t\t\t\t\t\t<ChainEditor\n\t\t\t\t\t\t\t\t\tchain={arg}\n\t\t\t\t\t\t\t\t\tonChange={(next) =>\n\t\t\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t\t\t...operand,\n\t\t\t\t\t\t\t\t\t\t\targs: operand.args.map((a, i) => (i === index ? next : a)),\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tctx={ctx}\n\t\t\t\t\t\t\t\t\tidPrefix={`${idPrefix}-a${index}`}\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\t{operand.args.length > 1 ? (\n\t\t\t\t\t\t\t\t<div className=\"fb-calc-fn__arg-remove\">\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\t\t\t\t\ticon=\"x\"\n\t\t\t\t\t\t\t\t\t\taria-label={t(keys.calcBuilderRemove)}\n\t\t\t\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t\t\t\t...operand,\n\t\t\t\t\t\t\t\t\t\t\t\targs: operand.args.filter((_, i) => i !== index),\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t\t<div className=\"fb-calc-fn__add\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tbuttonStyle=\"icon-label\"\n\t\t\t\t\t\t\ticon=\"plus\"\n\t\t\t\t\t\t\ticonStyle=\"with-border\"\n\t\t\t\t\t\t\ticonPosition=\"left\"\n\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t...operand,\n\t\t\t\t\t\t\t\t\targs: [...operand.args, { first: { kind: 'lit', value: 0 }, steps: [] }],\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{t(keys.calcBuilderAddArgument)}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t)\n\t}\n\n\tif (operand.kind === 'neg') {\n\t\treturn (\n\t\t\t<div className=\"fb-calc-operand fb-calc-operand--neg\">\n\t\t\t\t<div className=\"fb-calc-operand__title\">{t(keys.calcBuilderNegate)}</div>\n\t\t\t\t<OperandCard\n\t\t\t\t\toperand={operand.operand}\n\t\t\t\t\tonChange={(next) => onChange({ kind: 'neg', operand: next })}\n\t\t\t\t\tctx={ctx}\n\t\t\t\t\tidPrefix={`${idPrefix}-n`}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t)\n\t}\n\n\tconst leafKindOptions: ChoiceOption[] = [\n\t\t{ label: t(keys.calcBuilderAnswer), value: 'ref' },\n\t\t{ label: t(keys.calcBuilderNumber), value: 'lit' },\n\t\t{ label: t(keys.calcBuilderWeights), value: 'weight' },\n\t]\n\tconst kindValue = operand.kind === 'source' ? sourceOptionValue(operand.source) : operand.kind\n\tconst kindSourceOptions =\n\t\toperand.kind === 'source' &&\n\t\t!ctx.scalarSourceOptions.some((option) => option.value === sourceOptionValue(operand.source))\n\t\t\t? [...ctx.scalarSourceOptions, { label: operand.source, value: kindValue }]\n\t\t\t: ctx.scalarSourceOptions\n\tconst kindFlat = [...leafKindOptions, ...kindSourceOptions]\n\tconst refOptions = operand.kind === 'ref' ? withStored(siblings.numeric, operand.field) : []\n\tconst choiceOptions =\n\t\toperand.kind === 'weight'\n\t\t\t? withStored(\n\t\t\t\t\tsiblings.choices.map((c) => ({ label: c.label, value: c.name })),\n\t\t\t\t\toperand.field\n\t\t\t\t)\n\t\t\t: []\n\tconst choice =\n\t\toperand.kind === 'weight' ? siblings.choices.find((c) => c.name === operand.field) : undefined\n\tconst weightSource = operand.kind === 'weight' ? operand.source : undefined\n\tconst weightValuesOptions: ChoiceOption[] = [\n\t\t{ label: t(keys.calcBuilderWeightManual), value: 'manual' },\n\t\t...(weightSource !== undefined &&\n\t\t!ctx.weightSourceOptions.some((option) => option.value === sourceOptionValue(weightSource))\n\t\t\t? [\n\t\t\t\t\t...ctx.weightSourceOptions,\n\t\t\t\t\t{ label: weightSource, value: sourceOptionValue(weightSource) },\n\t\t\t\t]\n\t\t\t: ctx.weightSourceOptions),\n\t]\n\n\treturn (\n\t\t<div className=\"fb-calc-operand\">\n\t\t\t<div className=\"fb-calc-operand__header\">\n\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idPrefix}-kind`}>\n\t\t\t\t\t{t(keys.calcBuilderKind)}\n\t\t\t\t</label>\n\t\t\t\t<ReactSelect\n\t\t\t\t\tclassName=\"fb-calc-operand__kind\"\n\t\t\t\t\tinputId={`${idPrefix}-kind`}\n\t\t\t\t\toptions={\n\t\t\t\t\t\twithSourceGroup(\n\t\t\t\t\t\t\tleafKindOptions,\n\t\t\t\t\t\t\tkindSourceOptions,\n\t\t\t\t\t\t\tt(keys.calcBuilderSourcesGroup)\n\t\t\t\t\t\t) as ReactSelectOption[]\n\t\t\t\t\t}\n\t\t\t\t\tvalue={kindFlat.find((option) => option.value === kindValue)}\n\t\t\t\t\tisClearable={false}\n\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\tif (!chosen || chosen.value === kindValue) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst sourceKey = sourceKeyOf(String(chosen.value))\n\t\t\t\t\t\tonChange(\n\t\t\t\t\t\t\tsourceKey !== undefined\n\t\t\t\t\t\t\t\t? { kind: 'source', source: sourceKey }\n\t\t\t\t\t\t\t\t: seedOperand(chosen.value as LeafKind)\n\t\t\t\t\t\t)\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t\t{operand.kind === 'source' ? (\n\t\t\t\t\t<span className=\"fb-calc-operand__source\">{ctx.sourceLabelOf(operand.source)}</span>\n\t\t\t\t) : null}\n\t\t\t\t{operand.kind === 'ref' ? (\n\t\t\t\t\trefOptions.length === 0 ? (\n\t\t\t\t\t\t<p className=\"fb-calc-hint\">{t(keys.calcBuilderNoNumericFields)}</p>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idPrefix}-ref`}>\n\t\t\t\t\t\t\t\t{t(keys.calcBuilderPickField)}\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-operand__ref\"\n\t\t\t\t\t\t\t\tinputId={`${idPrefix}-ref`}\n\t\t\t\t\t\t\t\toptions={refOptions}\n\t\t\t\t\t\t\t\tvalue={refOptions.find((option) => option.value === operand.field)}\n\t\t\t\t\t\t\t\tisClearable={!readOnly}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tplaceholder={t(keys.calcBuilderPickField)}\n\t\t\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\t\t\tonChange({ kind: 'ref', field: chosen ? String(chosen.value) : '' })\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)\n\t\t\t\t) : null}\n\t\t\t\t{operand.kind === 'lit' ? (\n\t\t\t\t\t<NumberInput\n\t\t\t\t\t\tclassName=\"fb-calc-operand__number\"\n\t\t\t\t\t\tid={`${idPrefix}-number`}\n\t\t\t\t\t\taria-label={t(keys.calcBuilderNumber)}\n\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\tvalue={operand.value}\n\t\t\t\t\t\tonCommit={(value) => onChange({ kind: 'lit', value })}\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t\t{operand.kind === 'weight' ? (\n\t\t\t\t\tchoiceOptions.length === 0 ? (\n\t\t\t\t\t\t<p className=\"fb-calc-hint\">{t(keys.calcBuilderNoChoiceFields)}</p>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idPrefix}-weight-field`}>\n\t\t\t\t\t\t\t\t{t(keys.calcBuilderPickField)}\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-operand__weight-field\"\n\t\t\t\t\t\t\t\tinputId={`${idPrefix}-weight-field`}\n\t\t\t\t\t\t\t\toptions={choiceOptions}\n\t\t\t\t\t\t\t\tvalue={choiceOptions.find((option) => option.value === operand.field)}\n\t\t\t\t\t\t\t\tisClearable={!readOnly}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tplaceholder={t(keys.calcBuilderPickField)}\n\t\t\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\t\t\tconst field = chosen ? String(chosen.value) : ''\n\t\t\t\t\t\t\t\t\tif (field !== operand.field) {\n\t\t\t\t\t\t\t\t\t\t// Inline weights are per-option and reset with the field; a source keeps\n\t\t\t\t\t\t\t\t\t\t// resolving (its resolver receives the newly chosen field).\n\t\t\t\t\t\t\t\t\t\tonChange(\n\t\t\t\t\t\t\t\t\t\t\toperand.source !== undefined\n\t\t\t\t\t\t\t\t\t\t\t\t? { kind: 'weight', field, source: operand.source }\n\t\t\t\t\t\t\t\t\t\t\t\t: { kind: 'weight', field, weights: {} }\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</>\n\t\t\t\t\t)\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t{operand.kind === 'weight' && ctx.weightSourceOptions.length > 0 ? (\n\t\t\t\t<div className=\"fb-calc-operand__weight-values-row\">\n\t\t\t\t\t<label className=\"fb-calc-operand__weight-values-label\" htmlFor={`${idPrefix}-wv`}>\n\t\t\t\t\t\t{t(keys.calcBuilderWeightValues)}\n\t\t\t\t\t</label>\n\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\tclassName=\"fb-calc-operand__weight-values\"\n\t\t\t\t\t\tinputId={`${idPrefix}-wv`}\n\t\t\t\t\t\toptions={weightValuesOptions}\n\t\t\t\t\t\tvalue={weightValuesOptions.find(\n\t\t\t\t\t\t\t(option) =>\n\t\t\t\t\t\t\t\toption.value ===\n\t\t\t\t\t\t\t\t(operand.source !== undefined ? sourceOptionValue(operand.source) : 'manual')\n\t\t\t\t\t\t)}\n\t\t\t\t\t\tisClearable={false}\n\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\tif (!chosen) {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst sourceKey = sourceKeyOf(String(chosen.value))\n\t\t\t\t\t\t\tif (sourceKey === undefined) {\n\t\t\t\t\t\t\t\tif (operand.source !== undefined) {\n\t\t\t\t\t\t\t\t\t// Back to Manual: drop the source, keep any inline weights (they were\n\t\t\t\t\t\t\t\t\t// preserved untouched while sourced) so the author's numbers return.\n\t\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t\tkind: 'weight',\n\t\t\t\t\t\t\t\t\t\tfield: operand.field,\n\t\t\t\t\t\t\t\t\t\tweights: operand.weights ?? {},\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (sourceKey !== operand.source) {\n\t\t\t\t\t\t\t\tonChange({ ...operand, source: sourceKey })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{operand.kind === 'weight' && operand.source !== undefined ? (\n\t\t\t\t<p className=\"fb-calc-hint\">{t(keys.calcBuilderWeightsFromSource)}</p>\n\t\t\t) : null}\n\t\t\t{operand.kind === 'weight' && operand.source === undefined && choice ? (\n\t\t\t\t<div className=\"fb-calc-operand__weights\">\n\t\t\t\t\t{choice.options.map((option) => (\n\t\t\t\t\t\t<div key={option.value} className=\"fb-calc-operand__weight-row\">\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-operand__weight-label\"\n\t\t\t\t\t\t\t\thtmlFor={`${idPrefix}-weight-${option.value}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{option.label}\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<NumberInput\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-operand__weight\"\n\t\t\t\t\t\t\t\tid={`${idPrefix}-weight-${option.value}`}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tvalue={operand.weights?.[option.value]}\n\t\t\t\t\t\t\t\tonCommit={(value) =>\n\t\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t\t...operand,\n\t\t\t\t\t\t\t\t\t\tweights: { ...(operand.weights ?? {}), [option.value]: value },\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t)\n}\n\ntype ChainEditorProps = {\n\tchain: CalcChain\n\tonChange: (next: CalcChain) => void\n\t/** Root only: called when the only row is removed, clearing the expression back to Start with. */\n\tonEmptied?: () => void\n\t/**\n\t * Honest-affordance rule: an X must actually remove. The root's only-row X clears the whole\n\t * expression, so it stays; a nested chain's only row (fn arg, group) would merely reseed, so\n\t * its X is not rendered at all.\n\t */\n\tisRoot?: boolean\n\tctx: BuilderCtx\n\tidPrefix: string\n}\n\nconst ChainEditor = ({ chain, onChange, onEmptied, isRoot, ctx, idPrefix }: ChainEditorProps) => {\n\tconst { t, readOnly } = ctx\n\n\tconst removeRow = (row: number) => {\n\t\tif (row === 0) {\n\t\t\tconst [promoted, ...rest] = chain.steps\n\t\t\tif (!promoted) {\n\t\t\t\tonEmptied?.()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tonChange({ ...chain, first: promoted.operand, steps: rest })\n\t\t\treturn\n\t\t}\n\t\tonChange({ ...chain, steps: chain.steps.filter((_, i) => i !== row - 1) })\n\t}\n\n\tconst rows: { op?: CalcOp; operand: CalcOperand }[] = [\n\t\t{ operand: chain.first },\n\t\t...chain.steps.map((step) => ({ op: step.op, operand: step.operand })),\n\t]\n\n\treturn (\n\t\t<div className=\"fb-calc-chain\">\n\t\t\t{rows.map((row, index) => (\n\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: rows are positional and never reordered\n\t\t\t\t<div key={index} className={index > 0 ? 'fb-calc-row fb-calc-row--divided' : 'fb-calc-row'}>\n\t\t\t\t\t{index === 0 ? <div className=\"fb-calc-row__op-spacer\" aria-hidden=\"true\" /> : null}\n\t\t\t\t\t{index > 0 ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idPrefix}-r${index}-op`}>\n\t\t\t\t\t\t\t\t{t(keys.calcBuilderMath)}\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-row__op\"\n\t\t\t\t\t\t\t\tinputId={`${idPrefix}-r${index}-op`}\n\t\t\t\t\t\t\t\toptions={OP_OPTIONS}\n\t\t\t\t\t\t\t\tvalue={OP_OPTIONS.find((option) => option.value === row.op)}\n\t\t\t\t\t\t\t\tisClearable={false}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\t\t\tif (chosen) {\n\t\t\t\t\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t\t\t\t\t...chain,\n\t\t\t\t\t\t\t\t\t\t\tsteps: chain.steps.map((step, i) =>\n\t\t\t\t\t\t\t\t\t\t\t\ti === index - 1 ? { ...step, op: chosen.value as CalcOp } : step\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div className=\"fb-calc-row__operand\">\n\t\t\t\t\t\t<OperandCard\n\t\t\t\t\t\t\toperand={row.operand}\n\t\t\t\t\t\t\tonChange={(operand) =>\n\t\t\t\t\t\t\t\tindex === 0\n\t\t\t\t\t\t\t\t\t? onChange({ ...chain, first: operand })\n\t\t\t\t\t\t\t\t\t: onChange({\n\t\t\t\t\t\t\t\t\t\t\t...chain,\n\t\t\t\t\t\t\t\t\t\t\tsteps: chain.steps.map((step, i) =>\n\t\t\t\t\t\t\t\t\t\t\t\ti === index - 1 ? { ...step, operand } : step\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tctx={ctx}\n\t\t\t\t\t\t\tidPrefix={`${idPrefix}-r${index}`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t{rows.length > 1 || isRoot ? (\n\t\t\t\t\t\t<div className=\"fb-calc-row__remove\">\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=\"x\"\n\t\t\t\t\t\t\t\taria-label={t(keys.calcBuilderRemove)}\n\t\t\t\t\t\t\t\tmargin={false}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tonClick={() => removeRow(index)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t\t<div className=\"fb-calc-chain__add\">\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\tmargin={false}\n\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\tonChange({\n\t\t\t\t\t\t\t...chain,\n\t\t\t\t\t\t\tsteps: [...chain.steps, { op: '*', operand: seedOperand('ref') }],\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t>\n\t\t\t\t\t{t(keys.calcBuilderAddStep)}\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/**\n * Visual editor for the calculation field's `expression` AST, presented as a left-to-right chain.\n * Storage shape is unchanged and `validateExpression` stays the server gate; the AST remains\n * authorable via the API.\n */\nexport const CalcExpressionBuilder = (props: CalcExpressionBuilderProps) => {\n\tconst {\n\t\tcustomComponents: { Error: ErrorComponent, Label } = {},\n\t\tdisabled,\n\t\tpath,\n\t\tsetValue,\n\t\tshowError,\n\t\tvalue,\n\t} = useField<unknown>({ path: props.path })\n\tconst { t, i18n } = useTranslation()\n\tconst label = toStaticLabel(props.field?.label)\n\tconst readOnly = props.readOnly === true || disabled === true\n\n\tconst sources = useMemo(() => props.sources ?? [], [props.sources])\n\t// Mirrors the server-side allowed sets, so the builder accepts exactly the expressions\n\t// `buildValidateExpression` accepts (a sourced expression must not read as \"stored invalid\").\n\tconst allowed = useMemo(\n\t\t() => ({\n\t\t\tsources: new Set(sources.map((source) => source.key)),\n\t\t\tfunctions: new Set(props.functions ?? []),\n\t\t}),\n\t\t[sources, props.functions]\n\t)\n\tconst sourceLabelOf = (key: string): string => {\n\t\tconst meta = sources.find((source) => source.key === key)\n\t\treturn meta ? resolveSourceLabel(meta.label, i18n.language) : key\n\t}\n\tconst scalarSourceOptions: ChoiceOption[] = sources\n\t\t.filter((source) => source.scalar)\n\t\t.map((source) => ({ label: sourceLabelOf(source.key), value: sourceOptionValue(source.key) }))\n\tconst weightSourceOptions: ChoiceOption[] = sources\n\t\t.filter((source) => source.weights)\n\t\t.map((source) => ({ label: sourceLabelOf(source.key), value: sourceOptionValue(source.key) }))\n\tconst customFnOptions: ChoiceOption[] = (props.functions ?? []).map((fn) => ({\n\t\tlabel: fn,\n\t\tvalue: fn,\n\t}))\n\n\tconst expression = useMemo(() => normalizeCalc(value, allowed), [value, allowed])\n\tconst chain = useMemo(() => (expression ? astToChain(expression) : undefined), [expression])\n\tconst storedInvalid = expression === undefined && value != null && value !== ''\n\n\t// The expression path is `fields.<row>.expression`; that row is the calculation block being\n\t// edited, so it bounds which calculation siblings are referenceable (see siblingsFromData).\n\t// An unparseable path (never expected) offers every calculation rather than none.\n\tconst selfIndex = useMemo(() => {\n\t\tconst match = /^fields\\.(\\d+)\\./.exec(path)\n\t\treturn match ? Number(match[1]) : Number.MAX_SAFE_INTEGER\n\t}, [path])\n\n\tconst siblingsJson = useFormFields(([fields]) =>\n\t\tJSON.stringify(siblingsFromData(reduceFieldsToValues(fields, true), selfIndex))\n\t)\n\tconst siblings = useMemo(() => JSON.parse(siblingsJson) as Siblings, [siblingsJson])\n\n\tconst startOptions: ChoiceOption[] = [\n\t\t{ label: t(keys.calcBuilderAnswer), value: 'ref' },\n\t\t{ label: t(keys.calcBuilderNumber), value: 'lit' },\n\t\t{ label: t(keys.calcBuilderWeights), value: 'weight' },\n\t\t{ label: t(keys.calcBuilderFunction), value: 'fn' },\n\t]\n\tconst startDescriptions: { key: string; label: string; description: string }[] = [\n\t\t{\n\t\t\tkey: 'ref',\n\t\t\tlabel: t(keys.calcBuilderAnswer),\n\t\t\tdescription: t(keys.calcBuilderFieldDescription),\n\t\t},\n\t\t{\n\t\t\tkey: 'lit',\n\t\t\tlabel: t(keys.calcBuilderNumber),\n\t\t\tdescription: t(keys.calcBuilderNumberDescription),\n\t\t},\n\t\t{\n\t\t\tkey: 'weight',\n\t\t\tlabel: t(keys.calcBuilderWeights),\n\t\t\tdescription: t(keys.calcBuilderWeightsDescription),\n\t\t},\n\t\t{\n\t\t\tkey: 'fn',\n\t\t\tlabel: t(keys.calcBuilderFunction),\n\t\t\tdescription: t(keys.calcBuilderFunctionDescription),\n\t\t},\n\t]\n\n\tconst commitChain = (next: CalcChain) => {\n\t\tif (!readOnly) {\n\t\t\tsetValue(chainToAst(next))\n\t\t}\n\t}\n\n\tconst clear = () => {\n\t\tif (!readOnly) {\n\t\t\tsetValue(null)\n\t\t}\n\t}\n\n\t// An unfilled slot (blank ref) previews as \"?\" so a partial chain reads \"Price × ?\" instead of\n\t// trailing off; formatCalc itself stays pure.\n\tconst labelOf = (field: string) => (field.trim() === '' ? '?' : (siblings.labels[field] ?? field))\n\n\tconst idBase = `calc-${path.replace(/\\./g, '__')}`\n\tconst ctx: BuilderCtx = {\n\t\tt,\n\t\tsiblings,\n\t\treadOnly,\n\t\tsourceLabelOf,\n\t\tscalarSourceOptions,\n\t\tweightSourceOptions,\n\t\tcustomFnOptions,\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName={['field-type', 'fb-calc-builder', showError && 'error', readOnly && 'read-only']\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' ')}\n\t\t\tid={path ? `field-${path.replace(/\\./g, '__')}` : undefined}\n\t\t>\n\t\t\t<RenderCustomComponent\n\t\t\t\tCustomComponent={Label}\n\t\t\t\tFallback={<FieldLabel label={label} path={path} />}\n\t\t\t/>\n\t\t\t<div className=\"field-type__wrap\">\n\t\t\t\t<RenderCustomComponent\n\t\t\t\t\tCustomComponent={ErrorComponent}\n\t\t\t\t\tFallback={<FieldError path={path} showError={showError} />}\n\t\t\t\t/>\n\t\t\t\t{chain ? (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<ChainEditor\n\t\t\t\t\t\t\tchain={chain}\n\t\t\t\t\t\t\tonChange={commitChain}\n\t\t\t\t\t\t\tonEmptied={clear}\n\t\t\t\t\t\t\tisRoot\n\t\t\t\t\t\t\tctx={ctx}\n\t\t\t\t\t\t\tidPrefix={idBase}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div className=\"fb-calc-finish\">\n\t\t\t\t\t\t\t<label\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-finish__label\"\n\t\t\t\t\t\t\t\thtmlFor={`${idBase}-finish`}\n\t\t\t\t\t\t\t\ttitle={t(keys.calcBuilderThenApply)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{t(keys.calcBuilderThenApply)}\n\t\t\t\t\t\t\t</label>\n\t\t\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\t\t\tclassName=\"fb-calc-finish__select\"\n\t\t\t\t\t\t\t\tinputId={`${idBase}-finish`}\n\t\t\t\t\t\t\t\toptions={UNARY_OPTIONS}\n\t\t\t\t\t\t\t\tvalue={UNARY_OPTIONS.find((option) => option.value === chain.finish)}\n\t\t\t\t\t\t\t\tisClearable={!readOnly}\n\t\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\t\t\tcommitChain({\n\t\t\t\t\t\t\t\t\t\t...chain,\n\t\t\t\t\t\t\t\t\t\tfinish: chosen ? (chosen.value as CalcFn) : undefined,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"fb-calc-builder__empty\">\n\t\t\t\t\t\t{storedInvalid ? (\n\t\t\t\t\t\t\t<p className=\"fb-calc-builder__warning\">{t(keys.calcBuilderStoredInvalid)}</p>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t<p className=\"fb-calc-builder__hint\">{t(keys.calcBuilderAddExpression)}</p>\n\t\t\t\t\t\t<label className=\"fb-visually-hidden\" htmlFor={`${idBase}-seed`}>\n\t\t\t\t\t\t\t{t(keys.calcBuilderStartWith)}\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<ReactSelect\n\t\t\t\t\t\t\tclassName=\"fb-calc-builder__seed\"\n\t\t\t\t\t\t\tinputId={`${idBase}-seed`}\n\t\t\t\t\t\t\toptions={\n\t\t\t\t\t\t\t\twithSourceGroup(\n\t\t\t\t\t\t\t\t\tstartOptions,\n\t\t\t\t\t\t\t\t\tscalarSourceOptions,\n\t\t\t\t\t\t\t\t\tt(keys.calcBuilderSourcesGroup)\n\t\t\t\t\t\t\t\t) as ReactSelectOption[]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={readOnly}\n\t\t\t\t\t\t\tplaceholder={t(keys.calcBuilderStartWith)}\n\t\t\t\t\t\t\tonChange={(selected) => {\n\t\t\t\t\t\t\t\tconst chosen = singleOption(selected)\n\t\t\t\t\t\t\t\tif (!chosen || readOnly) {\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst sourceKey = sourceKeyOf(String(chosen.value))\n\t\t\t\t\t\t\t\tsetValue(\n\t\t\t\t\t\t\t\t\tsourceKey !== undefined\n\t\t\t\t\t\t\t\t\t\t? { type: 'source', source: sourceKey }\n\t\t\t\t\t\t\t\t\t\t: seedAst(chosen.value as StartKind)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ul className=\"fb-calc-builder__kinds\">\n\t\t\t\t\t\t\t{startDescriptions.map((entry) => (\n\t\t\t\t\t\t\t\t<li key={entry.key}>\n\t\t\t\t\t\t\t\t\t<span className=\"fb-calc-builder__kind-label\">{entry.label}</span>{' '}\n\t\t\t\t\t\t\t\t\t{entry.description}\n\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<p className=\"fb-calc-builder__preview\" aria-live=\"polite\">\n\t\t\t\t\t{expression ? formatCalc(expression, labelOf, sourceLabelOf) : ''}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"],"mappings":"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"}
@@ -273,3 +273,285 @@
273
273
  .fb-condensed-array__actions {
274
274
  margin-block-start: 0.25rem;
275
275
  }
276
+
277
+ /* Calculation expression builder */
278
+ .fb-calc-builder {
279
+ margin-block-end: 1rem;
280
+ }
281
+ .fb-calc-builder > .field-type__wrap {
282
+ display: flex;
283
+ flex-direction: column;
284
+ gap: 0.5rem;
285
+ }
286
+ .fb-visually-hidden {
287
+ position: absolute;
288
+ width: 1px;
289
+ height: 1px;
290
+ padding: 0;
291
+ margin: -1px;
292
+ overflow: hidden;
293
+ clip: rect(0, 0, 0, 0);
294
+ white-space: nowrap;
295
+ border: 0;
296
+ }
297
+ .fb-calc-builder__hint {
298
+ color: var(--theme-elevation-400);
299
+ font-size: 0.875rem;
300
+ margin: 0;
301
+ }
302
+ .fb-calc-builder__empty {
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: 0.375rem;
306
+ }
307
+ .fb-calc-builder__seed {
308
+ max-width: 16rem;
309
+ }
310
+ .fb-calc-builder__preview {
311
+ color: var(--theme-elevation-400);
312
+ font-size: 0.875rem;
313
+ margin: 0;
314
+ }
315
+ .fb-calc-builder__actions {
316
+ align-self: flex-start;
317
+ }
318
+ .fb-calc-builder__warning {
319
+ color: var(--theme-elevation-400);
320
+ font-size: 0.8125rem;
321
+ margin: 0;
322
+ }
323
+ .fb-calc-builder__kinds {
324
+ margin: 0;
325
+ margin-inline-start: 0.75rem;
326
+ padding-inline-start: 1rem;
327
+ color: var(--theme-elevation-400);
328
+ font-size: 0.8125rem;
329
+ display: flex;
330
+ flex-direction: column;
331
+ gap: 0.125rem;
332
+ }
333
+ .fb-calc-builder__kind-label {
334
+ color: var(--theme-elevation-600);
335
+ font-weight: 600;
336
+ }
337
+ .fb-calc-chain {
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: 0.5rem;
341
+ min-width: 0;
342
+ }
343
+ /* Flat three-column grid, identical at every depth: [op select | spacer, fixed 90px]
344
+ * [operand controls, flexing] [remove, fixed]. The first row reserves the op column with a
345
+ * spacer so every kind select starts at the same left margin; nothing wraps, flexing
346
+ * controls shrink and ellipsize instead. */
347
+ .fb-calc-row {
348
+ display: flex;
349
+ align-items: flex-start;
350
+ gap: 0.5rem;
351
+ }
352
+ .fb-calc-row--divided {
353
+ border-block-start: 1px solid var(--theme-elevation-100);
354
+ padding-block-start: 0.5rem;
355
+ }
356
+ .fb-calc-row__op,
357
+ .fb-calc-row__op-spacer {
358
+ flex: 0 0 90px;
359
+ min-width: 0;
360
+ }
361
+ .fb-calc-row__operand {
362
+ flex: 1;
363
+ min-width: 0;
364
+ }
365
+ /* A fixed 40px centering box pins the remove level with the first control line even when
366
+ * secondary content (weight rows, nested chains) extends the row downward. */
367
+ .fb-calc-row__remove {
368
+ flex: 0 0 auto;
369
+ align-self: flex-start;
370
+ height: 40px;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: flex-end;
374
+ }
375
+ .fb-calc-chain__add {
376
+ align-self: flex-start;
377
+ /* Column 1 width + row gap: aligns the button with the operand column. */
378
+ margin-inline-start: calc(90px + 0.5rem);
379
+ }
380
+ .fb-calc-operand {
381
+ display: flex;
382
+ flex-direction: column;
383
+ gap: 0.5rem;
384
+ min-width: 0;
385
+ }
386
+ .fb-calc-operand--group > .fb-calc-chain {
387
+ padding-inline-start: 0.75rem;
388
+ border-inline-start: 2px solid var(--theme-elevation-100);
389
+ }
390
+ .fb-calc-operand__title {
391
+ font-size: 0.6875rem;
392
+ font-weight: 600;
393
+ text-transform: uppercase;
394
+ letter-spacing: 0.04em;
395
+ color: var(--theme-elevation-400);
396
+ }
397
+ .fb-calc-operand__header {
398
+ display: flex;
399
+ align-items: center;
400
+ gap: 0.5rem;
401
+ }
402
+ .fb-calc-hint {
403
+ color: var(--theme-elevation-400);
404
+ font-size: 0.875rem;
405
+ margin: 0;
406
+ flex: 1 1 auto;
407
+ min-width: 0;
408
+ overflow: hidden;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ }
412
+ /* 200px fits the longest kind label in both locales ("Gewichtetes Feld" / "Weighted field":
413
+ * ~110px at the 13px admin body size) plus the react-select control chrome (2 x base(0.6)
414
+ * padding + indicator), with room for zoomed fonts. */
415
+ .fb-calc-operand__header > .fb-calc-operand__kind {
416
+ flex: 0 0 200px;
417
+ min-width: 0;
418
+ }
419
+ .fb-calc-operand__header > .fb-calc-operand__fn {
420
+ flex: 0 0 110px;
421
+ min-width: 0;
422
+ }
423
+ .fb-calc-operand__header > .fb-calc-operand__ref,
424
+ .fb-calc-operand__header > .fb-calc-operand__weight-field {
425
+ flex: 1 1 auto;
426
+ min-width: 0;
427
+ }
428
+ .fb-calc-builder .rs__single-value {
429
+ white-space: nowrap;
430
+ overflow: hidden;
431
+ text-overflow: ellipsis;
432
+ }
433
+ .fb-calc-operand__number,
434
+ .fb-calc-operand__weight {
435
+ height: 40px;
436
+ padding: 0 15px;
437
+ font-size: 1rem;
438
+ font-family: inherit;
439
+ color: var(--theme-elevation-800);
440
+ background: var(--theme-input-bg);
441
+ border: 1px solid var(--theme-elevation-150);
442
+ border-radius: var(--style-radius-s);
443
+ transition:
444
+ border-color 100ms ease,
445
+ background-color 100ms ease,
446
+ box-shadow 100ms ease;
447
+ }
448
+ .fb-calc-operand__number:hover,
449
+ .fb-calc-operand__weight:hover {
450
+ border-color: var(--theme-elevation-250);
451
+ }
452
+ .fb-calc-operand__number:focus,
453
+ .fb-calc-operand__weight:focus {
454
+ border-color: var(--theme-elevation-400);
455
+ outline: none;
456
+ }
457
+ .fb-calc-operand__header > .fb-calc-operand__number {
458
+ flex: 1 1 auto;
459
+ min-width: 60px;
460
+ width: 0;
461
+ }
462
+ .fb-calc-fn__args {
463
+ display: flex;
464
+ flex-direction: column;
465
+ gap: 0.5rem;
466
+ padding-inline-start: 0.75rem;
467
+ border-inline-start: 2px solid var(--theme-elevation-100);
468
+ }
469
+ .fb-calc-fn__arg {
470
+ display: flex;
471
+ align-items: flex-start;
472
+ gap: 0.5rem;
473
+ }
474
+ .fb-calc-fn__arg-chain {
475
+ flex: 1;
476
+ min-width: 0;
477
+ }
478
+ .fb-calc-fn__arg-remove {
479
+ flex: 0 0 auto;
480
+ align-self: flex-start;
481
+ height: 40px;
482
+ display: flex;
483
+ align-items: center;
484
+ }
485
+ .fb-calc-fn__add {
486
+ align-self: flex-start;
487
+ }
488
+ .fb-calc-operand__source {
489
+ flex: 1 1 auto;
490
+ min-width: 0;
491
+ line-height: 40px;
492
+ font-size: 0.875rem;
493
+ overflow: hidden;
494
+ text-overflow: ellipsis;
495
+ white-space: nowrap;
496
+ }
497
+ .fb-calc-operand__weight-values-row {
498
+ display: flex;
499
+ align-items: center;
500
+ gap: 0.5rem;
501
+ }
502
+ .fb-calc-operand__weight-values-label {
503
+ flex: 0 0 90px;
504
+ min-width: 0;
505
+ line-height: 40px;
506
+ font-size: 0.875rem;
507
+ color: var(--theme-elevation-600);
508
+ white-space: nowrap;
509
+ overflow: hidden;
510
+ text-overflow: ellipsis;
511
+ }
512
+ .fb-calc-operand__weight-values-row > .fb-calc-operand__weight-values {
513
+ flex: 0 0 200px;
514
+ min-width: 0;
515
+ }
516
+ .fb-calc-operand__weights {
517
+ display: flex;
518
+ flex-direction: column;
519
+ gap: 0.375rem;
520
+ }
521
+ .fb-calc-operand__weight-row {
522
+ display: flex;
523
+ align-items: center;
524
+ gap: 0.5rem;
525
+ }
526
+ .fb-calc-operand__weight-label {
527
+ flex: 1;
528
+ min-width: 0;
529
+ font-size: 0.875rem;
530
+ overflow: hidden;
531
+ text-overflow: ellipsis;
532
+ white-space: nowrap;
533
+ }
534
+ .fb-calc-operand__weight-row > .fb-calc-operand__weight {
535
+ flex: 0 0 8rem;
536
+ }
537
+ .fb-calc-finish {
538
+ display: flex;
539
+ align-items: flex-start;
540
+ gap: 0.5rem;
541
+ }
542
+ /* The wording occupies column 1 (the 90px op column), centered on the 40px control line, so the
543
+ * select below aligns exactly with the operand column's kind selects. */
544
+ .fb-calc-finish__label {
545
+ flex: 0 0 90px;
546
+ min-width: 0;
547
+ line-height: 40px;
548
+ font-size: 0.875rem;
549
+ color: var(--theme-elevation-600);
550
+ white-space: nowrap;
551
+ overflow: hidden;
552
+ text-overflow: ellipsis;
553
+ }
554
+ .fb-calc-finish__select {
555
+ flex: 0 0 200px;
556
+ min-width: 0;
557
+ }
@@ -3,20 +3,15 @@ import { labelForKey } from "../translations/server.js";
3
3
  import { dispatchActions } from "../actions/dispatch.js";
4
4
  import { resolveEventSink } from "../events/resolveEventSink.js";
5
5
  import { isLoggedIn } from "../plugin/access.js";
6
- import { buildSpamGuard } from "../spam/spamGuard.js";
7
6
  import { FORMS_SLUG } from "./forms.js";
7
+ import { formIdOf } from "../submissions/formIdOf.js";
8
+ import { makeVoteTallyHook } from "../poll/votes/voteTallyHook.js";
9
+ import { buildSpamGuard } from "../spam/spamGuard.js";
8
10
  import { POLL_CONTEXT_KEY, votedCookieName } from "../submissions/votedCookie.js";
9
11
  import { validateSubmission } from "../submissions/validateSubmission.js";
10
12
  import { verifyContext } from "../submissions/verifyContext.js";
11
13
  //#region src/collections/formSubmissions.ts
12
14
  const FORM_SUBMISSIONS_SLUG = "form-submissions";
13
- const formIdOf = (form) => {
14
- if (typeof form === "number" || typeof form === "string") return form;
15
- if (form && typeof form === "object" && "id" in form) {
16
- const id = form.id;
17
- if (typeof id === "number" || typeof id === "string") return id;
18
- }
19
- };
20
15
  /**
21
16
  * On a completed submission create, dispatch the form's post-submit actions and emit `submission.created`.
22
17
  * Both are side effects on an already-written row, so the whole body is wrapped: nothing it does can throw
@@ -92,7 +87,7 @@ const makeVotedCookieHook = () => {
92
87
  return doc;
93
88
  };
94
89
  };
95
- const buildSubmissionsCollection = ({ registry, ruleRegistry, consentSources, consentSnapshot, actionRegistry = /* @__PURE__ */ new Map(), events, hasRunner = false, richText, uploadSlug, spam, votedCookie = false, pollSourceRegistry, showRawFields = false, overrides }) => {
90
+ const buildSubmissionsCollection = ({ registry, ruleRegistry, calcSources, calcFunctions, consentSources, consentSnapshot, actionRegistry = /* @__PURE__ */ new Map(), events, hasRunner = false, richText, uploadSlug, spam, votedCookie = false, pollSourceRegistry, pollVotes = false, showRawFields = false, overrides }) => {
96
91
  const defaultFields = [
97
92
  {
98
93
  name: "form",
@@ -196,6 +191,8 @@ const buildSubmissionsCollection = ({ registry, ruleRegistry, consentSources, co
196
191
  validateSubmission({
197
192
  registry,
198
193
  ruleRegistry,
194
+ calcSources,
195
+ calcFunctions,
199
196
  consentSources,
200
197
  consentSnapshot,
201
198
  uploadSlug,
@@ -205,6 +202,7 @@ const buildSubmissionsCollection = ({ registry, ruleRegistry, consentSources, co
205
202
  ...overrides?.hooks?.beforeValidate ?? []
206
203
  ],
207
204
  afterChange: [
205
+ ...pollVotes !== false ? [makeVoteTallyHook()] : [],
208
206
  makeAfterChange({
209
207
  actionRegistry,
210
208
  events,
@@ -1 +1 @@
1
- {"version":3,"file":"formSubmissions.js","names":[],"sources":["../../src/collections/formSubmissions.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, Field } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { dispatchActions } from '../actions/dispatch'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport { resolveEventSink } from '../events/resolveEventSink'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { buildSpamGuard } from '../spam/spamGuard'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { validateSubmission } from '../submissions/validateSubmission'\nimport { verifyContext } from '../submissions/verifyContext'\nimport { POLL_CONTEXT_KEY, votedCookieName } from '../submissions/votedCookie'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { FORMS_SLUG } from './forms'\n\nexport const FORM_SUBMISSIONS_SLUG = 'form-submissions'\n\ntype BuildSubmissionsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry?: ActionRegistry\n\tevents?: FormEventSink\n\t/** Whether a job runner is likely present; gates the queued vs bounded-inline dispatch path. */\n\thasRunner?: boolean\n\t/** Body serialization customization forwarded to the inline action dispatch path. */\n\trichText?: RichTextBodyOption\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Resolved spam config; when active, prepends the spam guard before validation. `false` disables it. */\n\tspam?: ResolvedSpamConfig | false\n\t/** Opt-in (`poll.votedCookie`): set an httpOnly `fb-voted-{formId}` cookie on poll submission creates. */\n\tvotedCookie?: boolean\n\t/** Registered poll option sources; submission validation resolves allowed values through them. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/**\n\t * When `true`, reveals the standalone `locale`, `values`, `descriptors`, `consent`, and `meta`\n\t * fields in the admin UI. Default `false`, because the `SubmissionAnswers` UI component already\n\t * represents them fully (locale and meta are folded into its Submission-details section).\n\t */\n\tshowRawFields?: boolean\n\toverrides?: CollectionOverrides\n}\n\nconst formIdOf = (form: unknown): number | string | undefined => {\n\tif (typeof form === 'number' || typeof form === 'string') {\n\t\treturn form\n\t}\n\tif (form && typeof form === 'object' && 'id' in form) {\n\t\tconst id = (form as { id: unknown }).id\n\t\tif (typeof id === 'number' || typeof id === 'string') {\n\t\t\treturn id\n\t\t}\n\t}\n\treturn undefined\n}\n\n/**\n * On a completed submission create, dispatch the form's post-submit actions and emit `submission.created`.\n * Both are side effects on an already-written row, so the whole body is wrapped: nothing it does can throw\n * past the hook (a failed action or sink must never fail the submission write). Dispatch is itself bounded\n * and non-throwing; the inline fallback is awaited so the bound caps the added latency, while the queued\n * path returns immediately. The form's `actions` are null-guarded for legacy rows created before the field\n * existed.\n */\nconst makeAfterChange =\n\t(args: {\n\t\tactionRegistry: ActionRegistry\n\t\tevents?: FormEventSink\n\t\thasRunner: boolean\n\t\trichText?: RichTextBodyOption\n\t}): CollectionAfterChangeHook =>\n\tasync ({ doc, operation, req }) => {\n\t\tif (operation !== 'create' || (doc.status != null && doc.status !== 'complete')) {\n\t\t\treturn doc\n\t\t}\n\t\tconst { payload } = req\n\t\ttry {\n\t\t\tconst formId = formIdOf(doc.form)\n\t\t\tif (formId == null) {\n\t\t\t\treturn doc\n\t\t\t}\n\t\t\tconst form = await payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\n\t\t\tawait dispatchActions({\n\t\t\t\tactions: (form?.actions ?? null) as ActionInstance[] | null,\n\t\t\t\tformId,\n\t\t\t\tsubmissionId: doc.id as number | string,\n\t\t\t\tregistry: args.actionRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\thasRunner: args.hasRunner,\n\t\t\t\tpersistSubmissions: form?.persistSubmissions as boolean | undefined,\n\t\t\t\trichText: args.richText,\n\t\t\t})\n\n\t\t\ttry {\n\t\t\t\tawait resolveEventSink(args.events).emit({\n\t\t\t\t\ttype: 'submission.created',\n\t\t\t\t\tformId: String(formId),\n\t\t\t\t\tsubmissionId: String(doc.id),\n\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\tpayload.logger?.error(\n\t\t\t\t\t`@10x-media/form-builder: submission.created sink threw: ${\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t}`\n\t\t\t\t)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: afterChange dispatch failed for submission ${String(doc.id)}: ${\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 doc\n\t}\n\n/**\n * Opt-in (`poll.votedCookie`) hook: after a submission to a poll-enabled form is created, appends\n * an httpOnly `fb-voted-{formId}=1` cookie via `req.responseHeaders`, Payload's supported channel\n * for hook-set response headers (its own auth strategies use it; `handleEndpoints` merges it into\n * every REST response). Reads the poll config `validateSubmission` stashed on `req.context`,\n * falling back to a form fetch when absent. httpOnly keeps the marker readable only server-side\n * (`hasVotedCookie`); the client `<Poll>` keeps its localStorage guard.\n */\nconst makeVotedCookieHook = (): CollectionAfterChangeHook => {\n\treturn async ({ doc, operation, req }) => {\n\t\tif (operation !== 'create') {\n\t\t\treturn doc\n\t\t}\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) {\n\t\t\treturn doc\n\t\t}\n\t\tconst stashed = req.context?.[POLL_CONTEXT_KEY]\n\t\tlet pollEnabled = typeof stashed === 'boolean' ? stashed : undefined\n\t\tif (pollEnabled === undefined) {\n\t\t\tconst form = await req.payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\t\t\tpollEnabled = form?.pollEnabled === true\n\t\t}\n\t\tif (!pollEnabled) {\n\t\t\treturn doc\n\t\t}\n\t\treq.responseHeaders ??= new Headers()\n\t\treq.responseHeaders.append(\n\t\t\t'Set-Cookie',\n\t\t\t`${votedCookieName(formId)}=1; Path=/; Max-Age=31536000; HttpOnly; SameSite=Lax`\n\t\t)\n\t\treturn doc\n\t}\n}\n\nexport const buildSubmissionsCollection = ({\n\tregistry,\n\truleRegistry,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry = new Map(),\n\tevents,\n\thasRunner = false,\n\trichText,\n\tuploadSlug,\n\tspam,\n\tvotedCookie = false,\n\tpollSourceRegistry,\n\tshowRawFields = false,\n\toverrides,\n}: BuildSubmissionsCollectionArgs): CollectionConfig => {\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'form', type: 'relationship', relationTo: FORMS_SLUG, required: true },\n\t\t{\n\t\t\tname: 'status',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: 'complete',\n\t\t\toptions: [\n\t\t\t\t{ label: labelForKey(keys.statusComplete), value: 'complete' },\n\t\t\t\t{ label: labelForKey(keys.statusPartial), value: 'partial' },\n\t\t\t],\n\t\t\t// Not clearable: aggregation counts `status: complete` submissions, so a cleared status\n\t\t\t// would drop the submission out of every poll result with nothing in the UI saying so.\n\t\t\tadmin: { isClearable: false },\n\t\t\t// Defense-in-depth at the REST layer: anonymous clients cannot set status via the API.\n\t\t\t// The validateSubmission hook also forces 'complete' server-side, so this covers both paths.\n\t\t\taccess: { create: isLoggedIn, update: isLoggedIn },\n\t\t},\n\t\t// answers UI appears first so it is the dominant view when opening a submission document.\n\t\t{\n\t\t\tname: 'answers',\n\t\t\ttype: 'ui',\n\t\t\tadmin: {\n\t\t\t\tcomponents: { Field: '@10x-media/form-builder/rsc#SubmissionAnswers' },\n\t\t\t},\n\t\t},\n\t\t{ name: 'locale', type: 'text', admin: { hidden: !showRawFields } },\n\t\t{ name: 'values', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'descriptors', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'consent', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'meta', type: 'json', admin: { hidden: !showRawFields } },\n\t\t// The verified reference to the document this form was rendered for (see verifyContext). Only forms\n\t\t// rendered with a signed context carry one, so the group is shown only when present. Readable, so it\n\t\t// is a legible audit record of which document a submission came through.\n\t\t{\n\t\t\tname: 'context',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.submissionContext),\n\t\t\t// A Payload group always materializes as an object, so gate on a populated `relationTo`\n\t\t\t// (not the group itself) to show it only for submissions made through a signed context.\n\t\t\tadmin: { readOnly: true, condition: (data) => Boolean(data?.context?.relationTo) },\n\t\t\tfields: [\n\t\t\t\t{ name: 'relationTo', type: 'text' },\n\t\t\t\t{ name: 'value', type: 'text' },\n\t\t\t],\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORM_SUBMISSIONS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionSubmissionSingular),\n\t\t\tplural: labelForKey(keys.collectionSubmissionPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tdefaultColumns: ['form', 'status', 'locale', 'createdAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: {\n\t\t\tcreate: () => true,\n\t\t\tread: isLoggedIn,\n\t\t\tupdate: () => false,\n\t\t\t...(overrides?.access ?? {}),\n\t\t},\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// Spam guard + validateSubmission must remain first: they enforce the security invariant\n\t\t\t// that anonymous callers cannot bypass post-submit actions or supply a forged status.\n\t\t\t// Consumer beforeValidate hooks are appended after so they run on already-validated data.\n\t\t\tbeforeValidate: [\n\t\t\t\t...(spam ? [buildSpamGuard(spam)] : []),\n\t\t\t\t// Verify + store the signed form context before validation stores the answers, and independently\n\t\t\t\t// of spam so it runs even with spam off.\n\t\t\t\tverifyContext(),\n\t\t\t\tvalidateSubmission({\n\t\t\t\t\tregistry,\n\t\t\t\t\truleRegistry,\n\t\t\t\t\tconsentSources,\n\t\t\t\t\tconsentSnapshot,\n\t\t\t\t\tuploadSlug,\n\t\t\t\t\tpollSourceRegistry,\n\t\t\t\t\tstrictUploadOwnership: spam ? spam.uploadOwnership === 'strict' : false,\n\t\t\t\t}),\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tafterChange: [\n\t\t\t\tmakeAfterChange({ actionRegistry, events, hasRunner, richText }),\n\t\t\t\t...(votedCookie ? [makeVotedCookieHook()] : []),\n\t\t\t\t...(overrides?.hooks?.afterChange ?? []),\n\t\t\t],\n\t\t},\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"formSubmissions.js","names":[],"sources":["../../src/collections/formSubmissions.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionConfig, Field } from 'payload'\nimport type { RichTextBodyOption } from '../actions/body/serializeBody'\nimport { dispatchActions } from '../actions/dispatch'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { ActionInstance } from '../actions/runActions'\nimport type { CalcFunction, CalcSource } from '../calc/registry'\nimport type { ConsentSnapshotMode } from '../consent/captureConsent'\nimport type { ConsentSourcesResolver } from '../consent/types'\nimport { resolveEventSink } from '../events/resolveEventSink'\nimport type { FormEventSink } from '../events/types'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport type { PollOptionSourceRegistry } from '../poll/registry'\nimport { makeVoteTallyHook } from '../poll/votes/voteTallyHook'\nimport { buildSpamGuard } from '../spam/spamGuard'\nimport type { ResolvedSpamConfig } from '../spam/types'\nimport { formIdOf } from '../submissions/formIdOf'\nimport { validateSubmission } from '../submissions/validateSubmission'\nimport { verifyContext } from '../submissions/verifyContext'\nimport { POLL_CONTEXT_KEY, votedCookieName } from '../submissions/votedCookie'\nimport { keys } from '../translations/keys'\nimport { labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport { FORMS_SLUG } from './forms'\n\nexport const FORM_SUBMISSIONS_SLUG = 'form-submissions'\n\ntype BuildSubmissionsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\t/** Registered calc sources; submission validation re-resolves them fresh (a failure rejects the submission). */\n\tcalcSources?: Record<string, CalcSource>\n\t/** Registered calc functions; threaded into submit-time calc evaluation. */\n\tcalcFunctions?: Record<string, CalcFunction>\n\t/** The host's consent sources resolver (plugin option `consent.sources`); absent when no sources are configured. */\n\tconsentSources?: ConsentSourcesResolver\n\t/** What each consent proof snapshots of the agreed wording (plugin option `consent.snapshot`). */\n\tconsentSnapshot?: ConsentSnapshotMode\n\tactionRegistry?: ActionRegistry\n\tevents?: FormEventSink\n\t/** Whether a job runner is likely present; gates the queued vs bounded-inline dispatch path. */\n\thasRunner?: boolean\n\t/** Body serialization customization forwarded to the inline action dispatch path. */\n\trichText?: RichTextBodyOption\n\t/** The plugin-configured uploads collection slug; absent when uploads are disabled. */\n\tuploadSlug?: string\n\t/** Resolved spam config; when active, prepends the spam guard before validation. `false` disables it. */\n\tspam?: ResolvedSpamConfig | false\n\t/** Opt-in (`poll.votedCookie`): set an httpOnly `fb-voted-{formId}` cookie on poll submission creates. */\n\tvotedCookie?: boolean\n\t/** Registered poll option sources; submission validation resolves allowed values through them. */\n\tpollSourceRegistry?: PollOptionSourceRegistry\n\t/** Resolved `poll.votes` option; `false` skips registering the append-only vote tally hook. */\n\tpollVotes?: false | { overrides?: CollectionOverrides }\n\t/**\n\t * When `true`, reveals the standalone `locale`, `values`, `descriptors`, `consent`, and `meta`\n\t * fields in the admin UI. Default `false`, because the `SubmissionAnswers` UI component already\n\t * represents them fully (locale and meta are folded into its Submission-details section).\n\t */\n\tshowRawFields?: boolean\n\toverrides?: CollectionOverrides\n}\n\n/**\n * On a completed submission create, dispatch the form's post-submit actions and emit `submission.created`.\n * Both are side effects on an already-written row, so the whole body is wrapped: nothing it does can throw\n * past the hook (a failed action or sink must never fail the submission write). Dispatch is itself bounded\n * and non-throwing; the inline fallback is awaited so the bound caps the added latency, while the queued\n * path returns immediately. The form's `actions` are null-guarded for legacy rows created before the field\n * existed.\n */\nconst makeAfterChange =\n\t(args: {\n\t\tactionRegistry: ActionRegistry\n\t\tevents?: FormEventSink\n\t\thasRunner: boolean\n\t\trichText?: RichTextBodyOption\n\t}): CollectionAfterChangeHook =>\n\tasync ({ doc, operation, req }) => {\n\t\tif (operation !== 'create' || (doc.status != null && doc.status !== 'complete')) {\n\t\t\treturn doc\n\t\t}\n\t\tconst { payload } = req\n\t\ttry {\n\t\t\tconst formId = formIdOf(doc.form)\n\t\t\tif (formId == null) {\n\t\t\t\treturn doc\n\t\t\t}\n\t\t\tconst form = await payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\n\t\t\tawait dispatchActions({\n\t\t\t\tactions: (form?.actions ?? null) as ActionInstance[] | null,\n\t\t\t\tformId,\n\t\t\t\tsubmissionId: doc.id as number | string,\n\t\t\t\tregistry: args.actionRegistry,\n\t\t\t\tpayload,\n\t\t\t\treq,\n\t\t\t\thasRunner: args.hasRunner,\n\t\t\t\tpersistSubmissions: form?.persistSubmissions as boolean | undefined,\n\t\t\t\trichText: args.richText,\n\t\t\t})\n\n\t\t\ttry {\n\t\t\t\tawait resolveEventSink(args.events).emit({\n\t\t\t\t\ttype: 'submission.created',\n\t\t\t\t\tformId: String(formId),\n\t\t\t\t\tsubmissionId: String(doc.id),\n\t\t\t\t\tat: new Date().toISOString(),\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\tpayload.logger?.error(\n\t\t\t\t\t`@10x-media/form-builder: submission.created sink threw: ${\n\t\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t\t}`\n\t\t\t\t)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tpayload.logger?.error(\n\t\t\t\t`@10x-media/form-builder: afterChange dispatch failed for submission ${String(doc.id)}: ${\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 doc\n\t}\n\n/**\n * Opt-in (`poll.votedCookie`) hook: after a submission to a poll-enabled form is created, appends\n * an httpOnly `fb-voted-{formId}=1` cookie via `req.responseHeaders`, Payload's supported channel\n * for hook-set response headers (its own auth strategies use it; `handleEndpoints` merges it into\n * every REST response). Reads the poll config `validateSubmission` stashed on `req.context`,\n * falling back to a form fetch when absent. httpOnly keeps the marker readable only server-side\n * (`hasVotedCookie`); the client `<Poll>` keeps its localStorage guard.\n */\nconst makeVotedCookieHook = (): CollectionAfterChangeHook => {\n\treturn async ({ doc, operation, req }) => {\n\t\tif (operation !== 'create') {\n\t\t\treturn doc\n\t\t}\n\t\tconst formId = formIdOf(doc.form)\n\t\tif (formId == null) {\n\t\t\treturn doc\n\t\t}\n\t\tconst stashed = req.context?.[POLL_CONTEXT_KEY]\n\t\tlet pollEnabled = typeof stashed === 'boolean' ? stashed : undefined\n\t\tif (pollEnabled === undefined) {\n\t\t\tconst form = await req.payload\n\t\t\t\t.findByID({ collection: FORMS_SLUG, id: formId, depth: 0, overrideAccess: true, req })\n\t\t\t\t.catch(() => null)\n\t\t\tpollEnabled = form?.pollEnabled === true\n\t\t}\n\t\tif (!pollEnabled) {\n\t\t\treturn doc\n\t\t}\n\t\treq.responseHeaders ??= new Headers()\n\t\treq.responseHeaders.append(\n\t\t\t'Set-Cookie',\n\t\t\t`${votedCookieName(formId)}=1; Path=/; Max-Age=31536000; HttpOnly; SameSite=Lax`\n\t\t)\n\t\treturn doc\n\t}\n}\n\nexport const buildSubmissionsCollection = ({\n\tregistry,\n\truleRegistry,\n\tcalcSources,\n\tcalcFunctions,\n\tconsentSources,\n\tconsentSnapshot,\n\tactionRegistry = new Map(),\n\tevents,\n\thasRunner = false,\n\trichText,\n\tuploadSlug,\n\tspam,\n\tvotedCookie = false,\n\tpollSourceRegistry,\n\tpollVotes = false,\n\tshowRawFields = false,\n\toverrides,\n}: BuildSubmissionsCollectionArgs): CollectionConfig => {\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'form', type: 'relationship', relationTo: FORMS_SLUG, required: true },\n\t\t{\n\t\t\tname: 'status',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: 'complete',\n\t\t\toptions: [\n\t\t\t\t{ label: labelForKey(keys.statusComplete), value: 'complete' },\n\t\t\t\t{ label: labelForKey(keys.statusPartial), value: 'partial' },\n\t\t\t],\n\t\t\t// Not clearable: aggregation counts `status: complete` submissions, so a cleared status\n\t\t\t// would drop the submission out of every poll result with nothing in the UI saying so.\n\t\t\tadmin: { isClearable: false },\n\t\t\t// Defense-in-depth at the REST layer: anonymous clients cannot set status via the API.\n\t\t\t// The validateSubmission hook also forces 'complete' server-side, so this covers both paths.\n\t\t\taccess: { create: isLoggedIn, update: isLoggedIn },\n\t\t},\n\t\t// answers UI appears first so it is the dominant view when opening a submission document.\n\t\t{\n\t\t\tname: 'answers',\n\t\t\ttype: 'ui',\n\t\t\tadmin: {\n\t\t\t\tcomponents: { Field: '@10x-media/form-builder/rsc#SubmissionAnswers' },\n\t\t\t},\n\t\t},\n\t\t{ name: 'locale', type: 'text', admin: { hidden: !showRawFields } },\n\t\t{ name: 'values', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'descriptors', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'consent', type: 'json', admin: { hidden: !showRawFields } },\n\t\t{ name: 'meta', type: 'json', admin: { hidden: !showRawFields } },\n\t\t// The verified reference to the document this form was rendered for (see verifyContext). Only forms\n\t\t// rendered with a signed context carry one, so the group is shown only when present. Readable, so it\n\t\t// is a legible audit record of which document a submission came through.\n\t\t{\n\t\t\tname: 'context',\n\t\t\ttype: 'group',\n\t\t\tlabel: labelForKey(keys.submissionContext),\n\t\t\t// A Payload group always materializes as an object, so gate on a populated `relationTo`\n\t\t\t// (not the group itself) to show it only for submissions made through a signed context.\n\t\t\tadmin: { readOnly: true, condition: (data) => Boolean(data?.context?.relationTo) },\n\t\t\tfields: [\n\t\t\t\t{ name: 'relationTo', type: 'text' },\n\t\t\t\t{ name: 'value', type: 'text' },\n\t\t\t],\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORM_SUBMISSIONS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionSubmissionSingular),\n\t\t\tplural: labelForKey(keys.collectionSubmissionPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: {\n\t\t\tgroup: 'Forms',\n\t\t\tdefaultColumns: ['form', 'status', 'locale', 'createdAt'],\n\t\t\t...(overrides?.admin ?? {}),\n\t\t},\n\t\taccess: {\n\t\t\tcreate: () => true,\n\t\t\tread: isLoggedIn,\n\t\t\tupdate: () => false,\n\t\t\t...(overrides?.access ?? {}),\n\t\t},\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// Spam guard + validateSubmission must remain first: they enforce the security invariant\n\t\t\t// that anonymous callers cannot bypass post-submit actions or supply a forged status.\n\t\t\t// Consumer beforeValidate hooks are appended after so they run on already-validated data.\n\t\t\tbeforeValidate: [\n\t\t\t\t...(spam ? [buildSpamGuard(spam)] : []),\n\t\t\t\t// Verify + store the signed form context before validation stores the answers, and independently\n\t\t\t\t// of spam so it runs even with spam off.\n\t\t\t\tverifyContext(),\n\t\t\t\tvalidateSubmission({\n\t\t\t\t\tregistry,\n\t\t\t\t\truleRegistry,\n\t\t\t\t\tcalcSources,\n\t\t\t\t\tcalcFunctions,\n\t\t\t\t\tconsentSources,\n\t\t\t\t\tconsentSnapshot,\n\t\t\t\t\tuploadSlug,\n\t\t\t\t\tpollSourceRegistry,\n\t\t\t\t\tstrictUploadOwnership: spam ? spam.uploadOwnership === 'strict' : false,\n\t\t\t\t}),\n\t\t\t\t...(overrides?.hooks?.beforeValidate ?? []),\n\t\t\t],\n\t\t\tafterChange: [\n\t\t\t\t// The tally hook runs first and does not swallow errors: a bump failure must throw so\n\t\t\t\t// Payload rolls the submission create/update back inside the operation transaction,\n\t\t\t\t// rather than being absorbed by the dispatch hook's swallow-all error boundary below.\n\t\t\t\t...(pollVotes !== false ? [makeVoteTallyHook()] : []),\n\t\t\t\tmakeAfterChange({ actionRegistry, events, hasRunner, richText }),\n\t\t\t\t...(votedCookie ? [makeVotedCookieHook()] : []),\n\t\t\t\t...(overrides?.hooks?.afterChange ?? []),\n\t\t\t],\n\t\t},\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}