@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
@@ -1 +1 @@
1
- {"version":3,"file":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype Field,\n\ttype PayloadRequest,\n\tValidationError,\n} from 'payload'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { ActionRegistry } from '../actions/registry'\nimport { resolveFormResultsRequest } from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\n\nexport const FORMS_SLUG = 'forms'\n\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry?: ConsentSourceRegistry\n\tpresentationRegistry?: PresentationDescriptorRegistry\n\tactionRegistry?: ActionRegistry\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry = new Map(Object.entries(defaultPresentationDescriptors)),\n\tactionRegistry = new Map(),\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (\n\t\t\tdata &&\n\t\t\ttypeof data.defaultPresentation === 'string' &&\n\t\t\t!presentationRegistry.has(data.defaultPresentation)\n\t\t) {\n\t\t\tdata.defaultPresentation = DEFAULT_PRESENTATION_NAME\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\tconst fieldNames = normalized\n\t\t\t\t.map((field: FieldRow) => (typeof field.name === 'string' ? field.name : undefined))\n\t\t\t\t.filter((name): name is string => name !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldNames)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t'A flow needs at least two steps with unique, non-empty IDs. Add another step or remove the flow.',\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\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t}\n\t\treturn data\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'title', type: 'text', required: true, label: labelForKey(keys.fieldTitle) },\n\t\t{\n\t\t\tname: 'fields',\n\t\t\ttype: 'blocks',\n\t\t\tblocks: buildFieldBlocks(registry, ruleRegistry, consentRegistry),\n\t\t},\n\t\t{\n\t\t\tname: 'flow',\n\t\t\ttype: 'json',\n\t\t\tvalidate: validateFlow,\n\t\t\tadmin: {\n\t\t\t\tcomponents: {\n\t\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes } },\n\t\t\t\t},\n\t\t\t},\n\t\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\t\ttypescriptSchema: [\n\t\t\t\t() => ({\n\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\trequired: ['steps'],\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tsteps: {\n\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\t\tnext: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\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},\n\t\t\t\t}),\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: 'actions',\n\t\t\ttype: 'blocks',\n\t\t\tblocks: buildActionBlocks(actionRegistry),\n\t\t\tlabel: labelForKey(keys.configActions),\n\t\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t\t// action config must never be exposed to anonymous callers.\n\t\t\taccess: { read: isLoggedIn },\n\t\t},\n\t\t{\n\t\t\tname: 'defaultPresentation',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: DEFAULT_PRESENTATION_NAME,\n\t\t\toptions: [...presentationRegistry.values()].map((descriptor) => ({\n\t\t\t\tlabel: labelFor(descriptor.label),\n\t\t\t\tvalue: descriptor.name,\n\t\t\t})),\n\t\t\tlabel: labelForKey(keys.configDefaultPresentation),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'showResults',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: false,\n\t\t\tlabel: labelForKey(keys.configShowResults),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'resultsField',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.configResultsField),\n\t\t\tadmin: {\n\t\t\t\tposition: 'sidebar',\n\t\t\t\tdescription:\n\t\t\t\t\t'Field whose aggregate results are public when \"Show results publicly\" is on. Use a choice field, never a free-text or PII field.',\n\t\t\t\tcondition: (data) => Boolean(data?.showResults),\n\t\t\t},\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: { group: 'Forms', useAsTitle: 'title', ...(overrides?.admin ?? {}) },\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\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":"forms.js","names":[],"sources":["../../src/collections/forms.ts"],"sourcesContent":["import {\n\ttype CollectionBeforeValidateHook,\n\ttype CollectionConfig,\n\ttype Field,\n\ttype PayloadRequest,\n\tValidationError,\n} from 'payload'\nimport { buildActionBlocks } from '../actions/buildActionBlocks'\nimport type { ActionRegistry } from '../actions/registry'\nimport { resolveFormResultsRequest } from '../aggregation/resolveResultsRequest'\nimport { normalizeCalc } from '../calc/normalizeCalc'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { type FieldRow, normalizeFormConditions } from '../conditions/normalizeConditions'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { buildFieldBlocks } from '../fields/buildFieldBlocks'\nimport type { FieldTypeRegistry } from '../fields/registry'\nimport { normalizeFlow } from '../flow/normalizeFlow'\nimport { isLoggedIn } from '../plugin/access'\nimport type { CollectionOverrides } from '../plugin/collectionOverrides'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport type { PresentationDescriptorRegistry } from '../presentations/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor, labelForKey } from '../translations/server'\nimport type { ValidationRuleRegistry } from '../validation/registry'\n\nexport const FORMS_SLUG = 'forms'\n\nconst validateFlow = (raw: unknown): string | true => {\n\tif (raw === null || raw === undefined) return true\n\tconst r = raw as Record<string, unknown>\n\tif (!Array.isArray(r.steps)) return true\n\tconst steps = r.steps as Array<Record<string, unknown>>\n\tconst emptyIdStep = steps.find((s) => typeof s?.id !== 'string' || s.id.length === 0)\n\tif (emptyIdStep) return 'Flow: every step must have a non-empty ID'\n\tconst ids = steps.map((s) => s.id as string)\n\tif (new Set(ids).size !== ids.length) {\n\t\treturn 'Flow: duplicate step IDs found'\n\t}\n\tconst idSet = new Set(ids)\n\tfor (const step of steps) {\n\t\tconst id = step.id as string\n\t\tif (typeof step.next === 'string' && step.next.length > 0 && !idSet.has(step.next)) {\n\t\t\treturn `Flow: step \"${id}\" references unknown next step \"${step.next}\"`\n\t\t}\n\t\tif (Array.isArray(step.transitions)) {\n\t\t\tfor (const t of step.transitions as Array<Record<string, unknown>>) {\n\t\t\t\tif (typeof t?.to === 'string' && t.to.length > 0 && !idSet.has(t.to)) {\n\t\t\t\t\treturn `Flow: step \"${id}\" has a transition to unknown step \"${t.to}\"`\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n/** How many steps the caller actually submitted, before normalization strips/collapses the flow. */\nconst providedFlowStepCount = (raw: unknown): number => {\n\tif (raw === null || typeof raw !== 'object') return 0\n\tconst steps = (raw as { steps?: unknown }).steps\n\treturn Array.isArray(steps) ? steps.length : 0\n}\n\ntype BuildFormsCollectionArgs = {\n\tregistry: FieldTypeRegistry\n\truleRegistry: ValidationRuleRegistry\n\tconsentRegistry?: ConsentSourceRegistry\n\tpresentationRegistry?: PresentationDescriptorRegistry\n\tactionRegistry?: ActionRegistry\n\toverrides?: CollectionOverrides\n}\n\nexport const buildFormsCollection = ({\n\toverrides,\n\tregistry,\n\truleRegistry,\n\tconsentRegistry,\n\tpresentationRegistry = new Map(Object.entries(defaultPresentationDescriptors)),\n\tactionRegistry = new Map(),\n}: BuildFormsCollectionArgs): CollectionConfig => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst FLOW_BUILDER_REF = '@10x-media/form-builder/client#FlowBuilder'\n\n\tconst beforeValidate: CollectionBeforeValidateHook = ({ data, req }) => {\n\t\tif (\n\t\t\tdata &&\n\t\t\ttypeof data.defaultPresentation === 'string' &&\n\t\t\t!presentationRegistry.has(data.defaultPresentation)\n\t\t) {\n\t\t\tdata.defaultPresentation = DEFAULT_PRESENTATION_NAME\n\t\t}\n\t\tif (data && Array.isArray(data.fields)) {\n\t\t\tconst normalized: FieldRow[] = normalizeFormConditions(\n\t\t\t\tdata.fields as FieldRow[],\n\t\t\t\tconditionTypes\n\t\t\t)\n\t\t\tfor (const field of normalized) {\n\t\t\t\tif ('expression' in field) {\n\t\t\t\t\tfield.expression = normalizeCalc(field.expression)\n\t\t\t\t}\n\t\t\t}\n\t\t\tdata.fields = normalized\n\t\t\tconst fieldNames = normalized\n\t\t\t\t.map((field: FieldRow) => (typeof field.name === 'string' ? field.name : undefined))\n\t\t\t\t.filter((name): name is string => name !== undefined)\n\t\t\tconst normalizedFlow = normalizeFlow(data.flow, fieldNames)\n\t\t\t// A flow the author built but that collapses to fewer than two valid steps would\n\t\t\t// otherwise vanish silently. Surface it instead of discarding their work.\n\t\t\tif (providedFlowStepCount(data.flow) > 0 && normalizedFlow === undefined) {\n\t\t\t\tthrow new ValidationError(\n\t\t\t\t\t{\n\t\t\t\t\t\tcollection: FORMS_SLUG,\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpath: 'flow',\n\t\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\t'A flow needs at least two steps with unique, non-empty IDs. Add another step or remove the flow.',\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\treq.t\n\t\t\t\t)\n\t\t\t}\n\t\t\tdata.flow = normalizedFlow\n\t\t}\n\t\treturn data\n\t}\n\n\tconst fieldsField: Field = {\n\t\tname: 'fields',\n\t\ttype: 'blocks',\n\t\tblocks: buildFieldBlocks(registry, ruleRegistry, consentRegistry),\n\t}\n\n\tconst flowField: Field = {\n\t\tname: 'flow',\n\t\ttype: 'json',\n\t\tvalidate: validateFlow,\n\t\tadmin: {\n\t\t\tcomponents: {\n\t\t\t\tField: { path: FLOW_BUILDER_REF, clientProps: { conditionTypes } },\n\t\t\t},\n\t\t},\n\t\t// Narrows the generated TypeScript type from opaque JSON to FormFlow so callers\n\t\t// don't need a cast. Keep this in sync with src/flow/types.ts.\n\t\ttypescriptSchema: [\n\t\t\t() => ({\n\t\t\t\ttype: 'object' as const,\n\t\t\t\trequired: ['steps'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t\tproperties: {\n\t\t\t\t\tsteps: {\n\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\trequired: ['id'],\n\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\tid: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttitle: { type: 'string' as const },\n\t\t\t\t\t\t\t\tfields: { type: 'array' as const, items: { type: 'string' as const } },\n\t\t\t\t\t\t\t\tnext: { type: 'string' as const },\n\t\t\t\t\t\t\t\ttransitions: {\n\t\t\t\t\t\t\t\t\ttype: 'array' as const,\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\ttype: 'object' as const,\n\t\t\t\t\t\t\t\t\t\trequired: ['to'],\n\t\t\t\t\t\t\t\t\t\tadditionalProperties: true,\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tto: { type: 'string' as const },\n\t\t\t\t\t\t\t\t\t\t\twhen: { type: 'object' as const, additionalProperties: true },\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},\n\t\t\t}),\n\t\t],\n\t}\n\n\tconst actionsField: Field = {\n\t\tname: 'actions',\n\t\ttype: 'blocks',\n\t\tblocks: buildActionBlocks(actionRegistry),\n\t\tlabel: labelForKey(keys.configActions),\n\t\t// Action config can contain secrets (e.g. signedWebhook.secret). The collection\n\t\t// itself is publicly readable so forms can be rendered by anonymous clients, but\n\t\t// action config must never be exposed to anonymous callers.\n\t\taccess: { read: isLoggedIn },\n\t}\n\n\tconst defaultFields: Field[] = [\n\t\t{ name: 'title', type: 'text', required: true, label: labelForKey(keys.fieldTitle) },\n\t\t// Unnamed tabs are presentational only: fields/flow/actions stay at the document root.\n\t\t{\n\t\t\ttype: 'tabs',\n\t\t\ttabs: [\n\t\t\t\t{ label: labelForKey(keys.tabFields), fields: [fieldsField] },\n\t\t\t\t{ label: labelForKey(keys.tabFlow), fields: [flowField] },\n\t\t\t\t{ label: labelForKey(keys.tabActions), fields: [actionsField] },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tname: 'defaultPresentation',\n\t\t\ttype: 'select',\n\t\t\tdefaultValue: DEFAULT_PRESENTATION_NAME,\n\t\t\toptions: [...presentationRegistry.values()].map((descriptor) => ({\n\t\t\t\tlabel: labelFor(descriptor.label),\n\t\t\t\tvalue: descriptor.name,\n\t\t\t})),\n\t\t\tlabel: labelForKey(keys.configDefaultPresentation),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'showResults',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: false,\n\t\t\tlabel: labelForKey(keys.configShowResults),\n\t\t\tadmin: { position: 'sidebar' },\n\t\t},\n\t\t{\n\t\t\tname: 'resultsField',\n\t\t\ttype: 'text',\n\t\t\tlabel: labelForKey(keys.configResultsField),\n\t\t\tadmin: {\n\t\t\t\tposition: 'sidebar',\n\t\t\t\tdescription:\n\t\t\t\t\t'Field whose aggregate results are public when \"Show results publicly\" is on. Use a choice field, never a free-text or PII field.',\n\t\t\t\tcondition: (data) => Boolean(data?.showResults),\n\t\t\t},\n\t\t},\n\t]\n\n\tconst defaultEndpoints: CollectionConfig['endpoints'] = [\n\t\t{\n\t\t\tpath: '/:id/results',\n\t\t\tmethod: 'get',\n\t\t\thandler: async (req: PayloadRequest) => {\n\t\t\t\tconst field = typeof req.query?.field === 'string' ? req.query.field : undefined\n\t\t\t\tconst { status, body } = await resolveFormResultsRequest({\n\t\t\t\t\tpayload: req.payload,\n\t\t\t\t\tformId: req.routeParams?.id as number | string | undefined,\n\t\t\t\t\tfield,\n\t\t\t\t\tisAuthed: Boolean(req.user),\n\t\t\t\t\treq,\n\t\t\t\t})\n\t\t\t\treturn Response.json(body, { status })\n\t\t\t},\n\t\t},\n\t]\n\n\treturn {\n\t\t...(overrides ?? {}),\n\t\tslug: FORMS_SLUG,\n\t\tlabels: {\n\t\t\tsingular: labelForKey(keys.collectionFormSingular),\n\t\t\tplural: labelForKey(keys.collectionFormPlural),\n\t\t\t...(overrides?.labels ?? {}),\n\t\t},\n\t\tadmin: { group: 'Forms', useAsTitle: 'title', ...(overrides?.admin ?? {}) },\n\t\taccess: { read: () => true, ...(overrides?.access ?? {}) },\n\t\thooks: {\n\t\t\t...(overrides?.hooks ?? {}),\n\t\t\t// beforeValidate normalizes conditions and flow; consumer hooks run after\n\t\t\tbeforeValidate: [beforeValidate, ...(overrides?.hooks?.beforeValidate ?? [])],\n\t\t},\n\t\tendpoints: [\n\t\t\t...defaultEndpoints,\n\t\t\t...(Array.isArray(overrides?.endpoints) ? overrides.endpoints : []),\n\t\t],\n\t\tfields: overrides?.fields ? overrides.fields({ defaultFields }) : defaultFields,\n\t}\n}\n"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { ByteSizeField } from "../client/ByteSizeField.js";
2
+ import { FieldNameSelect } from "../client/FieldNameSelect.js";
1
3
  import { FlowBuilder } from "../client/FlowBuilder.js";
2
4
  import { FormConditionField } from "../client/FormConditionField.js";
3
- export { FlowBuilder, FormConditionField };
5
+ export { ByteSizeField, FieldNameSelect, FlowBuilder, FormConditionField };
@@ -1,4 +1,6 @@
1
1
  "use client";
2
+ import { ByteSizeField } from "../client/ByteSizeField.js";
3
+ import { FieldNameSelect } from "../client/FieldNameSelect.js";
2
4
  import { FlowBuilder } from "../client/FlowBuilder.js";
3
5
  import { FormConditionField } from "../client/FormConditionField.js";
4
- export { FlowBuilder, FormConditionField };
6
+ export { ByteSizeField, FieldNameSelect, FlowBuilder, FormConditionField };
@@ -6,6 +6,10 @@ import { evaluateCalc } from "../calc/evaluate.js";
6
6
  import { evaluateCondition } from "../conditions/evaluate.js";
7
7
  import { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from "../flow/engine.js";
8
8
  import { PrefillOptions, valuesFromSearchParams } from "../prefill/valuesFromSearchParams.js";
9
+ import { CaptchaWidgetHandle } from "../react/captcha/types.js";
10
+ import { HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, HcaptchaCaptchaProps } from "../react/captcha/HcaptchaCaptcha.js";
11
+ import { RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha, RecaptchaCaptchaHandle, RecaptchaCaptchaProps } from "../react/captcha/RecaptchaCaptcha.js";
12
+ import { TURNSTILE_SCRIPT_URL, TurnstileCaptcha, TurnstileCaptchaProps } from "../react/captcha/TurnstileCaptcha.js";
9
13
  import { cn } from "../react/cn.js";
10
14
  import { FieldRenderer, FieldRendererProps, RendererTranslate, defineFieldRenderer } from "../react/contract.js";
11
15
  import { FormPresentation, PresentationWrapperProps } from "../react/presentation/types.js";
@@ -42,4 +46,5 @@ import { RecallResolver, buildRecallResolver } from "../recall/resolver.js";
42
46
  import { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from "../spam/constants.js";
43
47
  import { en } from "../translations/en.js";
44
48
  import { makeTranslate } from "../translations/makeTranslate.js";
45
- export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, CAPTCHA_TOKEN_KEY, type CalcExpression, Checkbox, type CheckboxProps, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormDocument, type FormFieldInstance, FormLayout, type FormLayoutProps, type FormPresentation, type FormProps, FormResults, type FormResultsProps, type FormState, type FormStepInfo, Honeypot, type HoneypotProps, Input, type InputProps, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, type RecallResolver, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, Textarea, type TextareaProps, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
49
+ import { formatBytes } from "../uploads/formatBytes.js";
50
+ export { type AggregationBucket, type BackButtonRenderProps, Backdrop, type BackdropProps, CAPTCHA_TOKEN_KEY, type CalcExpression, type CaptchaWidgetHandle, Checkbox, type CheckboxProps, DEFAULT_HONEYPOT_FIELD, DialogSurface, type DialogSurfaceProps, type FetchResultsInput, type FetchResultsResult, type FieldAggregation, type FieldRenderer, type FieldRendererProps, FieldShell, type FieldShellProps, type FieldWidth, Form, type FormDocument, type FormFieldInstance, FormLayout, type FormLayoutProps, type FormPresentation, type FormProps, FormResults, type FormResultsProps, type FormState, type FormStepInfo, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, type HcaptchaCaptchaProps, Honeypot, type HoneypotProps, Input, type InputProps, type NextButtonRenderProps, Poll, type PollProps, type PrefillOptions, type PresentationOption, type PresentationRegistry, type PresentationWrapperProps, type PresentationsConfig, RECAPTCHA_SCRIPT_URL, type RecallResolver, RecaptchaCaptcha, type RecaptchaCaptchaHandle, type RecaptchaCaptchaProps, type RendererOption, type RendererRegistry, type RendererTranslate, type RenderersConfig, Select, type SelectOption, type SelectProps, type SubmitButtonRenderProps, type SubmitFormResult, type SubmitHandler, TURNSTILE_SCRIPT_URL, Textarea, type TextareaProps, TurnstileCaptcha, type TurnstileCaptchaProps, type UploadFileInput, type UploadFileResult, type UseDismissOptions, type UseFieldResult, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
@@ -7,7 +7,11 @@ import { evaluateCondition } from "../conditions/evaluate.js";
7
7
  import { en } from "../translations/en.js";
8
8
  import { valuesFromSearchParams } from "../prefill/valuesFromSearchParams.js";
9
9
  import { buildRecallResolver } from "../recall/resolver.js";
10
+ import { formatBytes } from "../uploads/formatBytes.js";
10
11
  import { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from "../flow/engine.js";
12
+ import { HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha } from "../react/captcha/HcaptchaCaptcha.js";
13
+ import { RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha } from "../react/captcha/RecaptchaCaptcha.js";
14
+ import { TURNSTILE_SCRIPT_URL, TurnstileCaptcha } from "../react/captcha/TurnstileCaptcha.js";
11
15
  import { cn } from "../react/cn.js";
12
16
  import { defineFieldRenderer } from "../react/contract.js";
13
17
  import { makeTranslate } from "../translations/makeTranslate.js";
@@ -37,4 +41,4 @@ import { Poll } from "../react/Poll.js";
37
41
  import { toFormDocument } from "../react/toFormDocument.js";
38
42
  import { useFormState } from "../react/useFormState.js";
39
43
  import { useFormStep } from "../react/useFormStep.js";
40
- export { Backdrop, CAPTCHA_TOKEN_KEY, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormLayout, FormResults, Honeypot, Input, Poll, Select, Textarea, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
44
+ export { Backdrop, CAPTCHA_TOKEN_KEY, Checkbox, DEFAULT_HONEYPOT_FIELD, DialogSurface, FieldShell, Form, FormLayout, FormResults, HCAPTCHA_SCRIPT_URL, HcaptchaCaptcha, Honeypot, Input, Poll, RECAPTCHA_SCRIPT_URL, RecaptchaCaptcha, Select, TURNSTILE_SCRIPT_URL, Textarea, TurnstileCaptcha, buildRecallResolver, cn, computeCalcFields, defaultPresentations, defaultRenderers, defineFieldRenderer, en, evaluateCalc, evaluateCondition, fetchFormResults, firstStepId, formatBytes, interpolate, isTerminalStepId, makeTranslate, resolveNextStepId, resolvePresentations, resolveRenderers, stepFieldNames, submitForm, toFormDocument, uploadFile, useDismiss, useField, useFocusTrap, useFormState, useFormStep, useScrollLock, valuesFromSearchParams, widthProps };
@@ -3,9 +3,13 @@ import { labelFor } from "../translations/server.js";
3
3
  import { buildConditionTypeMap } from "../conditions/conditionType.js";
4
4
  import { buildConsentSourceConfig } from "../consent/buildConsentSourceConfig.js";
5
5
  import { buildRuleBlocks } from "../validation/buildRuleBlocks.js";
6
- import { sharedFieldConfig } from "./sharedConfig.js";
6
+ import { fieldBlockTabs } from "./sharedConfig.js";
7
7
  //#region src/fields/buildFieldBlocks.ts
8
- /** One add-field block per registered type: shared config, the type's own config, then its validations. */
8
+ /** The tab whose fields hold the shared basics and type config, found by the `name` field it carries. */
9
+ const fieldTabOf = (block) => {
10
+ return block.fields.find((f) => f.type === "tabs")?.tabs.find((tab) => tab.fields.some((f) => "name" in f && f.name === "name"))?.fields;
11
+ };
12
+ /** One add-field block per registered type: tabs holding shared config, type config, and validations. */
9
13
  const buildFieldBlocks = (registry, ruleRegistry, consentRegistry) => {
10
14
  const conditionTypes = buildConditionTypeMap(registry);
11
15
  const blocks = [];
@@ -26,31 +30,23 @@ const buildFieldBlocks = (registry, ruleRegistry, consentRegistry) => {
26
30
  singular: labelFor(definition.label),
27
31
  plural: labelFor(definition.label)
28
32
  },
29
- fields: [
30
- ...sharedFieldConfig(conditionTypes),
31
- ...typeConfig,
32
- {
33
- name: "validations",
34
- type: "blocks",
35
- label: labelFor(keys.validationsLabel),
36
- blocks: buildRuleBlocks(ruleRegistry, definition.type)
37
- }
38
- ]
33
+ fields: [fieldBlockTabs(conditionTypes, typeConfig, {
34
+ name: "validations",
35
+ type: "blocks",
36
+ label: labelFor(keys.validationsLabel),
37
+ blocks: buildRuleBlocks(ruleRegistry, definition.type)
38
+ })]
39
39
  });
40
40
  }
41
41
  const repeaterBlock = blocks.find((b) => b.slug === "repeater");
42
42
  if (repeaterBlock) {
43
- const subFieldBlocks = blocks.filter((b) => b.slug !== "repeater");
44
- const fieldsArr = repeaterBlock.fields;
45
- const validationsIdx = fieldsArr.findIndex((f) => f.name === "validations");
46
43
  const subFieldsField = {
47
44
  name: "subFields",
48
45
  type: "blocks",
49
46
  label: labelFor(keys.configSubFields),
50
- blocks: subFieldBlocks
47
+ blocks: blocks.filter((b) => b.slug !== "repeater")
51
48
  };
52
- if (validationsIdx >= 0) fieldsArr.splice(validationsIdx, 0, subFieldsField);
53
- else fieldsArr.push(subFieldsField);
49
+ (fieldTabOf(repeaterBlock) ?? repeaterBlock.fields).push(subFieldsField);
54
50
  }
55
51
  return blocks;
56
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { buildConsentSourceConfig } from '../consent/buildConsentSourceConfig'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\nimport { buildRuleBlocks } from '../validation/buildRuleBlocks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { FieldTypeRegistry } from './registry'\nimport { sharedFieldConfig } from './sharedConfig'\n\n/** One add-field block per registered type: shared config, the type's own config, then its validations. */\nexport const buildFieldBlocks = (\n\tregistry: FieldTypeRegistry,\n\truleRegistry: ValidationRuleRegistry,\n\tconsentRegistry?: ConsentSourceRegistry\n): Block[] => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst blocks: Block[] = []\n\tfor (const definition of registry.values()) {\n\t\tlet typeConfig: Field[] = definition.config ?? []\n\n\t\t// Inject dynamic source select + conditional sourceConfig group for the consent field.\n\t\t// consent.ts intentionally omits source/sourceConfig; the live registry drives the\n\t\t// select options and per-source field visibility (admin.condition).\n\t\tif (definition.type === 'consent' && consentRegistry) {\n\t\t\t// statement is first; optional is last; source/sourceConfig go in between.\n\t\t\tconst statement = typeConfig.find((f) => (f as { name?: string }).name === 'statement')\n\t\t\tconst optional = typeConfig.find((f) => (f as { name?: string }).name === 'optional')\n\t\t\ttypeConfig = [\n\t\t\t\t...(statement ? [statement] : []),\n\t\t\t\t...buildConsentSourceConfig(consentRegistry),\n\t\t\t\t...(optional ? [optional] : []),\n\t\t\t]\n\t\t}\n\n\t\tblocks.push({\n\t\t\tslug: definition.type,\n\t\t\tlabels: { singular: labelFor(definition.label), plural: labelFor(definition.label) },\n\t\t\tfields: [\n\t\t\t\t...sharedFieldConfig(conditionTypes),\n\t\t\t\t...typeConfig,\n\t\t\t\t{\n\t\t\t\t\tname: 'validations',\n\t\t\t\t\ttype: 'blocks',\n\t\t\t\t\tlabel: labelFor(keys.validationsLabel),\n\t\t\t\t\tblocks: buildRuleBlocks(ruleRegistry, definition.type),\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t}\n\n\t// Second pass: inject subFields into the repeater block using all non-repeater blocks.\n\t// Done after the main loop so every sibling block is already built.\n\t// Repeater-in-repeater is not supported in v1; the repeater block is excluded from subFields.\n\tconst repeaterBlock = blocks.find((b) => b.slug === 'repeater')\n\tif (repeaterBlock) {\n\t\tconst subFieldBlocks = blocks.filter((b) => b.slug !== 'repeater')\n\t\tconst fieldsArr = repeaterBlock.fields as Field[]\n\t\tconst validationsIdx = fieldsArr.findIndex(\n\t\t\t(f) => (f as { name?: string }).name === 'validations'\n\t\t)\n\t\tconst subFieldsField: Field = {\n\t\t\tname: 'subFields',\n\t\t\ttype: 'blocks',\n\t\t\tlabel: labelFor(keys.configSubFields),\n\t\t\tblocks: subFieldBlocks,\n\t\t}\n\t\tif (validationsIdx >= 0) {\n\t\t\tfieldsArr.splice(validationsIdx, 0, subFieldsField)\n\t\t} else {\n\t\t\tfieldsArr.push(subFieldsField)\n\t\t}\n\t}\n\n\treturn blocks\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,oBACZ,UACA,cACA,oBACa;CACb,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,SAAkB,CAAC;CACzB,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,aAAsB,WAAW,UAAU,CAAC;EAKhD,IAAI,WAAW,SAAS,aAAa,iBAAiB;GAErD,MAAM,YAAY,WAAW,MAAM,MAAO,EAAwB,SAAS,WAAW;GACtF,MAAM,WAAW,WAAW,MAAM,MAAO,EAAwB,SAAS,UAAU;GACpF,aAAa;IACZ,GAAI,YAAY,CAAC,SAAS,IAAI,CAAC;IAC/B,GAAG,yBAAyB,eAAe;IAC3C,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;GAC9B;EACD;EAEA,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU,SAAS,WAAW,KAAK;IAAG,QAAQ,SAAS,WAAW,KAAK;GAAE;GACnF,QAAQ;IACP,GAAG,kBAAkB,cAAc;IACnC,GAAG;IACH;KACC,MAAM;KACN,MAAM;KACN,OAAO,SAAS,KAAK,gBAAgB;KACrC,QAAQ,gBAAgB,cAAc,WAAW,IAAI;IACtD;GACD;EACD,CAAC;CACF;CAKA,MAAM,gBAAgB,OAAO,MAAM,MAAM,EAAE,SAAS,UAAU;CAC9D,IAAI,eAAe;EAClB,MAAM,iBAAiB,OAAO,QAAQ,MAAM,EAAE,SAAS,UAAU;EACjE,MAAM,YAAY,cAAc;EAChC,MAAM,iBAAiB,UAAU,WAC/B,MAAO,EAAwB,SAAS,aAC1C;EACA,MAAM,iBAAwB;GAC7B,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,eAAe;GACpC,QAAQ;EACT;EACA,IAAI,kBAAkB,GACrB,UAAU,OAAO,gBAAgB,GAAG,cAAc;OAElD,UAAU,KAAK,cAAc;CAE/B;CAEA,OAAO;AACR"}
1
+ {"version":3,"file":"buildFieldBlocks.js","names":[],"sources":["../../src/fields/buildFieldBlocks.ts"],"sourcesContent":["import type { Block, Field, TabsField } from 'payload'\nimport { buildConditionTypeMap } from '../conditions/conditionType'\nimport { buildConsentSourceConfig } from '../consent/buildConsentSourceConfig'\nimport type { ConsentSourceRegistry } from '../consent/registry'\nimport { keys } from '../translations/keys'\nimport { labelFor } from '../translations/server'\nimport { buildRuleBlocks } from '../validation/buildRuleBlocks'\nimport type { ValidationRuleRegistry } from '../validation/registry'\nimport type { FieldTypeRegistry } from './registry'\nimport { fieldBlockTabs } from './sharedConfig'\n\n/** The tab whose fields hold the shared basics and type config, found by the `name` field it carries. */\nconst fieldTabOf = (block: Block): Field[] | undefined => {\n\tconst tabsField = block.fields.find((f): f is TabsField => f.type === 'tabs')\n\treturn tabsField?.tabs.find((tab) => tab.fields.some((f) => 'name' in f && f.name === 'name'))\n\t\t?.fields\n}\n\n/** One add-field block per registered type: tabs holding shared config, type config, and validations. */\nexport const buildFieldBlocks = (\n\tregistry: FieldTypeRegistry,\n\truleRegistry: ValidationRuleRegistry,\n\tconsentRegistry?: ConsentSourceRegistry\n): Block[] => {\n\tconst conditionTypes = buildConditionTypeMap(registry)\n\tconst blocks: Block[] = []\n\tfor (const definition of registry.values()) {\n\t\tlet typeConfig: Field[] = definition.config ?? []\n\n\t\t// Inject dynamic source select + conditional sourceConfig group for the consent field.\n\t\t// consent.ts intentionally omits source/sourceConfig; the live registry drives the\n\t\t// select options and per-source field visibility (admin.condition).\n\t\tif (definition.type === 'consent' && consentRegistry) {\n\t\t\t// statement is first; optional is last; source/sourceConfig go in between.\n\t\t\tconst statement = typeConfig.find((f) => (f as { name?: string }).name === 'statement')\n\t\t\tconst optional = typeConfig.find((f) => (f as { name?: string }).name === 'optional')\n\t\t\ttypeConfig = [\n\t\t\t\t...(statement ? [statement] : []),\n\t\t\t\t...buildConsentSourceConfig(consentRegistry),\n\t\t\t\t...(optional ? [optional] : []),\n\t\t\t]\n\t\t}\n\n\t\tblocks.push({\n\t\t\tslug: definition.type,\n\t\t\tlabels: { singular: labelFor(definition.label), plural: labelFor(definition.label) },\n\t\t\tfields: [\n\t\t\t\tfieldBlockTabs(conditionTypes, typeConfig, {\n\t\t\t\t\tname: 'validations',\n\t\t\t\t\ttype: 'blocks',\n\t\t\t\t\tlabel: labelFor(keys.validationsLabel),\n\t\t\t\t\tblocks: buildRuleBlocks(ruleRegistry, definition.type),\n\t\t\t\t}),\n\t\t\t],\n\t\t})\n\t}\n\n\t// Second pass: inject subFields into the repeater block using all non-repeater blocks.\n\t// Done after the main loop so every sibling block is already built.\n\t// Repeater-in-repeater is not supported in v1; the repeater block is excluded from subFields.\n\tconst repeaterBlock = blocks.find((b) => b.slug === 'repeater')\n\tif (repeaterBlock) {\n\t\tconst subFieldsField: Field = {\n\t\t\tname: 'subFields',\n\t\t\ttype: 'blocks',\n\t\t\tlabel: labelFor(keys.configSubFields),\n\t\t\tblocks: blocks.filter((b) => b.slug !== 'repeater'),\n\t\t}\n\t\tconst fieldTabFields = fieldTabOf(repeaterBlock)\n\t\t;(fieldTabFields ?? (repeaterBlock.fields as Field[])).push(subFieldsField)\n\t}\n\n\treturn blocks\n}\n"],"mappings":";;;;;;;;AAYA,MAAM,cAAc,UAAsC;CAEzD,OADkB,MAAM,OAAO,MAAM,MAAsB,EAAE,SAAS,MACvD,GAAG,KAAK,MAAM,QAAQ,IAAI,OAAO,MAAM,MAAM,UAAU,KAAK,EAAE,SAAS,MAAM,CAAC,GAC1F;AACJ;;AAGA,MAAa,oBACZ,UACA,cACA,oBACa;CACb,MAAM,iBAAiB,sBAAsB,QAAQ;CACrD,MAAM,SAAkB,CAAC;CACzB,KAAK,MAAM,cAAc,SAAS,OAAO,GAAG;EAC3C,IAAI,aAAsB,WAAW,UAAU,CAAC;EAKhD,IAAI,WAAW,SAAS,aAAa,iBAAiB;GAErD,MAAM,YAAY,WAAW,MAAM,MAAO,EAAwB,SAAS,WAAW;GACtF,MAAM,WAAW,WAAW,MAAM,MAAO,EAAwB,SAAS,UAAU;GACpF,aAAa;IACZ,GAAI,YAAY,CAAC,SAAS,IAAI,CAAC;IAC/B,GAAG,yBAAyB,eAAe;IAC3C,GAAI,WAAW,CAAC,QAAQ,IAAI,CAAC;GAC9B;EACD;EAEA,OAAO,KAAK;GACX,MAAM,WAAW;GACjB,QAAQ;IAAE,UAAU,SAAS,WAAW,KAAK;IAAG,QAAQ,SAAS,WAAW,KAAK;GAAE;GACnF,QAAQ,CACP,eAAe,gBAAgB,YAAY;IAC1C,MAAM;IACN,MAAM;IACN,OAAO,SAAS,KAAK,gBAAgB;IACrC,QAAQ,gBAAgB,cAAc,WAAW,IAAI;GACtD,CAAC,CACF;EACD,CAAC;CACF;CAKA,MAAM,gBAAgB,OAAO,MAAM,MAAM,EAAE,SAAS,UAAU;CAC9D,IAAI,eAAe;EAClB,MAAM,iBAAwB;GAC7B,MAAM;GACN,MAAM;GACN,OAAO,SAAS,KAAK,eAAe;GACpC,QAAQ,OAAO,QAAQ,MAAM,EAAE,SAAS,UAAU;EACnD;EAEC,CADsB,WAAW,aACnB,KAAM,cAAc,QAAoB,KAAK,cAAc;CAC3E;CAEA,OAAO;AACR"}
@@ -1,7 +1,116 @@
1
1
  import { keys } from "../../translations/keys.js";
2
- import { labelFor } from "../../translations/server.js";
2
+ import { asTranslate, labelFor } from "../../translations/server.js";
3
+ import { CALC_FNS, CALC_OPS } from "../../calc/types.js";
4
+ import { normalizeCalc } from "../../calc/normalizeCalc.js";
3
5
  import { defineFormField } from "../defineFormField.js";
4
6
  //#region src/fields/builtin/calculation.ts
7
+ /**
8
+ * Editor-facing JSON schema for `CalcExpression` (src/calc/types.ts), used by the admin JSON
9
+ * field's Monaco editor for autocomplete and inline diagnostics. The real validity gate is
10
+ * `validateExpression`/`normalizeCalc`; this schema is a convenience, not the source of truth.
11
+ */
12
+ const calcExpressionSchema = {
13
+ uri: "https://10xmedia.de/schemas/form-builder/calc-expression.json",
14
+ fileMatch: ["https://10xmedia.de/schemas/form-builder/calc-expression.json"],
15
+ schema: {
16
+ $ref: "#/definitions/expr",
17
+ definitions: {
18
+ expr: { oneOf: [
19
+ { $ref: "#/definitions/lit" },
20
+ { $ref: "#/definitions/ref" },
21
+ { $ref: "#/definitions/op" },
22
+ { $ref: "#/definitions/neg" },
23
+ { $ref: "#/definitions/fn" },
24
+ { $ref: "#/definitions/weight" }
25
+ ] },
26
+ lit: {
27
+ type: "object",
28
+ properties: {
29
+ type: { enum: ["lit"] },
30
+ value: { type: "number" }
31
+ },
32
+ required: ["type", "value"],
33
+ additionalProperties: false
34
+ },
35
+ ref: {
36
+ type: "object",
37
+ properties: {
38
+ type: { enum: ["ref"] },
39
+ field: { type: "string" }
40
+ },
41
+ required: ["type", "field"],
42
+ additionalProperties: false
43
+ },
44
+ op: {
45
+ type: "object",
46
+ properties: {
47
+ type: { enum: ["op"] },
48
+ op: { enum: [...CALC_OPS] },
49
+ left: { $ref: "#/definitions/expr" },
50
+ right: { $ref: "#/definitions/expr" }
51
+ },
52
+ required: [
53
+ "type",
54
+ "op",
55
+ "left",
56
+ "right"
57
+ ],
58
+ additionalProperties: false
59
+ },
60
+ neg: {
61
+ type: "object",
62
+ properties: {
63
+ type: { enum: ["neg"] },
64
+ operand: { $ref: "#/definitions/expr" }
65
+ },
66
+ required: ["type", "operand"],
67
+ additionalProperties: false
68
+ },
69
+ fn: {
70
+ type: "object",
71
+ properties: {
72
+ type: { enum: ["fn"] },
73
+ fn: { enum: [...CALC_FNS] },
74
+ args: {
75
+ type: "array",
76
+ items: { $ref: "#/definitions/expr" }
77
+ }
78
+ },
79
+ required: [
80
+ "type",
81
+ "fn",
82
+ "args"
83
+ ],
84
+ additionalProperties: false
85
+ },
86
+ weight: {
87
+ type: "object",
88
+ properties: {
89
+ type: { enum: ["weight"] },
90
+ field: { type: "string" },
91
+ weights: {
92
+ type: "object",
93
+ additionalProperties: { type: "number" }
94
+ }
95
+ },
96
+ required: [
97
+ "type",
98
+ "field",
99
+ "weights"
100
+ ],
101
+ additionalProperties: false
102
+ }
103
+ }
104
+ }
105
+ };
106
+ /**
107
+ * Validates the calculation field's `expression`: unset/empty is fine, otherwise it must
108
+ * normalize into a well-formed `CalcExpression` tree. Exported for unit testing.
109
+ */
110
+ const validateExpression = (value, { req }) => {
111
+ if (value == null || value === "") return true;
112
+ return normalizeCalc(value) !== void 0 ? true : asTranslate(req.t)(keys.validationCalcExpressionInvalid);
113
+ };
5
114
  const calculationField = defineFormField({
6
115
  type: "calculation",
7
116
  label: keys.fieldTypeCalculation,
@@ -9,7 +118,11 @@ const calculationField = defineFormField({
9
118
  config: [{
10
119
  name: "expression",
11
120
  type: "json",
12
- label: labelFor(keys.configExpression)
121
+ label: labelFor(keys.configExpression),
122
+ admin: { description: labelFor(keys.configExpressionDescription) },
123
+ jsonSchema: calcExpressionSchema,
124
+ typescriptSchema: [() => ({ type: "object" })],
125
+ validate: validateExpression
13
126
  }, {
14
127
  name: "calcDisplay",
15
128
  type: "checkbox",
@@ -1 +1 @@
1
- {"version":3,"file":"calculation.js","names":[],"sources":["../../../src/fields/builtin/calculation.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport { defineFormField } from '../defineFormField'\n\nexport const calculationField = defineFormField<'number'>({\n\ttype: 'calculation',\n\tlabel: keys.fieldTypeCalculation,\n\tvalue: 'number',\n\tconfig: [\n\t\t{ name: 'expression', type: 'json', label: labelFor(keys.configExpression) },\n\t\t{\n\t\t\tname: 'calcDisplay',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: true,\n\t\t\tlabel: labelFor(keys.configCalcDisplay),\n\t\t},\n\t],\n\tformat: ({ value }) => (value == null ? '' : String(value)),\n})\n"],"mappings":";;;;AAIA,MAAa,mBAAmB,gBAA0B;CACzD,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,QAAQ,CACP;EAAE,MAAM;EAAc,MAAM;EAAQ,OAAO,SAAS,KAAK,gBAAgB;CAAE,GAC3E;EACC,MAAM;EACN,MAAM;EACN,cAAc;EACd,OAAO,SAAS,KAAK,iBAAiB;CACvC,CACD;CACA,SAAS,EAAE,YAAa,SAAS,OAAO,KAAK,OAAO,KAAK;AAC1D,CAAC"}
1
+ {"version":3,"file":"calculation.js","names":[],"sources":["../../../src/fields/builtin/calculation.ts"],"sourcesContent":["import type { JSONField, PayloadRequest } from 'payload'\nimport { normalizeCalc } from '../../calc/normalizeCalc'\nimport { CALC_FNS, CALC_OPS } from '../../calc/types'\nimport { keys } from '../../translations/keys'\nimport { asTranslate, labelFor } from '../../translations/server'\nimport { defineFormField } from '../defineFormField'\n\ntype CalcExpressionSchema = NonNullable<JSONField['jsonSchema']>\n\n/**\n * Editor-facing JSON schema for `CalcExpression` (src/calc/types.ts), used by the admin JSON\n * field's Monaco editor for autocomplete and inline diagnostics. The real validity gate is\n * `validateExpression`/`normalizeCalc`; this schema is a convenience, not the source of truth.\n */\nconst calcExpressionSchema: CalcExpressionSchema = {\n\turi: 'https://10xmedia.de/schemas/form-builder/calc-expression.json',\n\tfileMatch: ['https://10xmedia.de/schemas/form-builder/calc-expression.json'],\n\tschema: {\n\t\t$ref: '#/definitions/expr',\n\t\tdefinitions: {\n\t\t\texpr: {\n\t\t\t\toneOf: [\n\t\t\t\t\t{ $ref: '#/definitions/lit' },\n\t\t\t\t\t{ $ref: '#/definitions/ref' },\n\t\t\t\t\t{ $ref: '#/definitions/op' },\n\t\t\t\t\t{ $ref: '#/definitions/neg' },\n\t\t\t\t\t{ $ref: '#/definitions/fn' },\n\t\t\t\t\t{ $ref: '#/definitions/weight' },\n\t\t\t\t],\n\t\t\t},\n\t\t\tlit: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { type: { enum: ['lit'] }, value: { type: 'number' } },\n\t\t\t\trequired: ['type', 'value'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t\tref: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { type: { enum: ['ref'] }, field: { type: 'string' } },\n\t\t\t\trequired: ['type', 'field'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t\top: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: { enum: ['op'] },\n\t\t\t\t\top: { enum: [...CALC_OPS] },\n\t\t\t\t\tleft: { $ref: '#/definitions/expr' },\n\t\t\t\t\tright: { $ref: '#/definitions/expr' },\n\t\t\t\t},\n\t\t\t\trequired: ['type', 'op', 'left', 'right'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t\tneg: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: { type: { enum: ['neg'] }, operand: { $ref: '#/definitions/expr' } },\n\t\t\t\trequired: ['type', 'operand'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t\tfn: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: { enum: ['fn'] },\n\t\t\t\t\tfn: { enum: [...CALC_FNS] },\n\t\t\t\t\targs: { type: 'array', items: { $ref: '#/definitions/expr' } },\n\t\t\t\t},\n\t\t\t\trequired: ['type', 'fn', 'args'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t\tweight: {\n\t\t\t\ttype: 'object',\n\t\t\t\tproperties: {\n\t\t\t\t\ttype: { enum: ['weight'] },\n\t\t\t\t\tfield: { type: 'string' },\n\t\t\t\t\tweights: { type: 'object', additionalProperties: { type: 'number' } },\n\t\t\t\t},\n\t\t\t\trequired: ['type', 'field', 'weights'],\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t},\n\t},\n}\n\n/**\n * Validates the calculation field's `expression`: unset/empty is fine, otherwise it must\n * normalize into a well-formed `CalcExpression` tree. Exported for unit testing.\n */\nexport const validateExpression = (\n\tvalue: unknown,\n\t{ req }: { data?: unknown; req: PayloadRequest }\n): string | true => {\n\tif (value == null || value === '') {\n\t\treturn true\n\t}\n\treturn normalizeCalc(value) !== undefined\n\t\t? true\n\t\t: asTranslate(req.t)(keys.validationCalcExpressionInvalid)\n}\n\nexport const calculationField = defineFormField<'number'>({\n\ttype: 'calculation',\n\tlabel: keys.fieldTypeCalculation,\n\tvalue: 'number',\n\tconfig: [\n\t\t{\n\t\t\tname: 'expression',\n\t\t\ttype: 'json',\n\t\t\tlabel: labelFor(keys.configExpression),\n\t\t\tadmin: { description: labelFor(keys.configExpressionDescription) },\n\t\t\tjsonSchema: calcExpressionSchema,\n\t\t\t// generate:types embeds jsonSchema.schema into the config-level schema, where its\n\t\t\t// document-root-relative $refs cannot resolve; hand type generation a flat shape instead.\n\t\t\ttypescriptSchema: [() => ({ type: 'object' })],\n\t\t\tvalidate: validateExpression,\n\t\t},\n\t\t{\n\t\t\tname: 'calcDisplay',\n\t\t\ttype: 'checkbox',\n\t\t\tdefaultValue: true,\n\t\t\tlabel: labelFor(keys.configCalcDisplay),\n\t\t},\n\t],\n\tformat: ({ value }) => (value == null ? '' : String(value)),\n})\n"],"mappings":";;;;;;;;;;;AAcA,MAAM,uBAA6C;CAClD,KAAK;CACL,WAAW,CAAC,+DAA+D;CAC3E,QAAQ;EACP,MAAM;EACN,aAAa;GACZ,MAAM,EACL,OAAO;IACN,EAAE,MAAM,oBAAoB;IAC5B,EAAE,MAAM,oBAAoB;IAC5B,EAAE,MAAM,mBAAmB;IAC3B,EAAE,MAAM,oBAAoB;IAC5B,EAAE,MAAM,mBAAmB;IAC3B,EAAE,MAAM,uBAAuB;GAChC,EACD;GACA,KAAK;IACJ,MAAM;IACN,YAAY;KAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;KAAG,OAAO,EAAE,MAAM,SAAS;IAAE;IACjE,UAAU,CAAC,QAAQ,OAAO;IAC1B,sBAAsB;GACvB;GACA,KAAK;IACJ,MAAM;IACN,YAAY;KAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;KAAG,OAAO,EAAE,MAAM,SAAS;IAAE;IACjE,UAAU,CAAC,QAAQ,OAAO;IAC1B,sBAAsB;GACvB;GACA,IAAI;IACH,MAAM;IACN,YAAY;KACX,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KACrB,IAAI,EAAE,MAAM,CAAC,GAAG,QAAQ,EAAE;KAC1B,MAAM,EAAE,MAAM,qBAAqB;KACnC,OAAO,EAAE,MAAM,qBAAqB;IACrC;IACA,UAAU;KAAC;KAAQ;KAAM;KAAQ;IAAO;IACxC,sBAAsB;GACvB;GACA,KAAK;IACJ,MAAM;IACN,YAAY;KAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;KAAG,SAAS,EAAE,MAAM,qBAAqB;IAAE;IAC/E,UAAU,CAAC,QAAQ,SAAS;IAC5B,sBAAsB;GACvB;GACA,IAAI;IACH,MAAM;IACN,YAAY;KACX,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KACrB,IAAI,EAAE,MAAM,CAAC,GAAG,QAAQ,EAAE;KAC1B,MAAM;MAAE,MAAM;MAAS,OAAO,EAAE,MAAM,qBAAqB;KAAE;IAC9D;IACA,UAAU;KAAC;KAAQ;KAAM;IAAM;IAC/B,sBAAsB;GACvB;GACA,QAAQ;IACP,MAAM;IACN,YAAY;KACX,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE;KACzB,OAAO,EAAE,MAAM,SAAS;KACxB,SAAS;MAAE,MAAM;MAAU,sBAAsB,EAAE,MAAM,SAAS;KAAE;IACrE;IACA,UAAU;KAAC;KAAQ;KAAS;IAAS;IACrC,sBAAsB;GACvB;EACD;CACD;AACD;;;;;AAMA,MAAa,sBACZ,OACA,EAAE,UACiB;CACnB,IAAI,SAAS,QAAQ,UAAU,IAC9B,OAAO;CAER,OAAO,cAAc,KAAK,MAAM,KAAA,IAC7B,OACA,YAAY,IAAI,CAAC,EAAE,KAAK,+BAA+B;AAC3D;AAEA,MAAa,mBAAmB,gBAA0B;CACzD,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,QAAQ,CACP;EACC,MAAM;EACN,MAAM;EACN,OAAO,SAAS,KAAK,gBAAgB;EACrC,OAAO,EAAE,aAAa,SAAS,KAAK,2BAA2B,EAAE;EACjE,YAAY;EAGZ,kBAAkB,QAAQ,EAAE,MAAM,SAAS,EAAE;EAC7C,UAAU;CACX,GACA;EACC,MAAM;EACN,MAAM;EACN,cAAc;EACd,OAAO,SAAS,KAAK,iBAAiB;CACvC,CACD;CACA,SAAS,EAAE,YAAa,SAAS,OAAO,KAAK,OAAO,KAAK;AAC1D,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { keys } from "../../translations/keys.js";
2
+ import { defineFormField } from "../defineFormField.js";
3
+ //#region src/fields/builtin/date.ts
4
+ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
5
+ /** True calendar-date check for `YYYY-MM-DD` strings (rejects e.g. `2024-02-30`). */
6
+ const isValidCalendarDate = (value) => {
7
+ const match = DATE_PATTERN.exec(value);
8
+ if (!match) return false;
9
+ const [, yearStr, monthStr, dayStr] = match;
10
+ const year = Number(yearStr);
11
+ const month = Number(monthStr);
12
+ const day = Number(dayStr);
13
+ const date = new Date(Date.UTC(year, month - 1, day));
14
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
15
+ };
16
+ const dateField = defineFormField({
17
+ type: "date",
18
+ label: keys.fieldTypeDate,
19
+ value: "date",
20
+ validate: ({ value, t }) => {
21
+ if (value == null || value === "") return true;
22
+ return isValidCalendarDate(value) ? true : t(keys.validationDate);
23
+ },
24
+ format: ({ value }) => value == null ? "" : String(value)
25
+ });
26
+ //#endregion
27
+ export { dateField, isValidCalendarDate };
28
+
29
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.js","names":[],"sources":["../../../src/fields/builtin/date.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { defineFormField } from '../defineFormField'\n\nconst DATE_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\n/** True calendar-date check for `YYYY-MM-DD` strings (rejects e.g. `2024-02-30`). */\nexport const isValidCalendarDate = (value: string): boolean => {\n\tconst match = DATE_PATTERN.exec(value)\n\tif (!match) {\n\t\treturn false\n\t}\n\tconst [, yearStr, monthStr, dayStr] = match\n\tconst year = Number(yearStr)\n\tconst month = Number(monthStr)\n\tconst day = Number(dayStr)\n\tconst date = new Date(Date.UTC(year, month - 1, day))\n\treturn (\n\t\tdate.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n\t)\n}\n\nexport const dateField = defineFormField<'date'>({\n\ttype: 'date',\n\tlabel: keys.fieldTypeDate,\n\tvalue: 'date',\n\tvalidate: ({ value, t }) => {\n\t\tif (value == null || value === '') {\n\t\t\treturn true\n\t\t}\n\t\treturn isValidCalendarDate(value) ? true : t(keys.validationDate)\n\t},\n\tformat: ({ value }) => (value == null ? '' : String(value)),\n})\n"],"mappings":";;;AAGA,MAAM,eAAe;;AAGrB,MAAa,uBAAuB,UAA2B;CAC9D,MAAM,QAAQ,aAAa,KAAK,KAAK;CACrC,IAAI,CAAC,OACJ,OAAO;CAER,MAAM,GAAG,SAAS,UAAU,UAAU;CACtC,MAAM,OAAO,OAAO,OAAO;CAC3B,MAAM,QAAQ,OAAO,QAAQ;CAC7B,MAAM,MAAM,OAAO,MAAM;CACzB,MAAM,OAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;CACpD,OACC,KAAK,eAAe,MAAM,QAAQ,KAAK,YAAY,MAAM,QAAQ,KAAK,KAAK,WAAW,MAAM;AAE9F;AAEA,MAAa,YAAY,gBAAwB;CAChD,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,WAAW,EAAE,OAAO,QAAQ;EAC3B,IAAI,SAAS,QAAQ,UAAU,IAC9B,OAAO;EAER,OAAO,oBAAoB,KAAK,IAAI,OAAO,EAAE,KAAK,cAAc;CACjE;CACA,SAAS,EAAE,YAAa,SAAS,OAAO,KAAK,OAAO,KAAK;AAC1D,CAAC"}
@@ -0,0 +1,15 @@
1
+ //#region src/fields/builtin/file.d.ts
2
+ /**
3
+ * Curated MIME type choices for the file field's `mimeTypes` select. Values are raw MIME strings or
4
+ * trailing-`*` prefix wildcards, exactly the patterns `resolveFileRef` matches server-side; `type/*`
5
+ * values double as the file input's `accept` attribute (browsers ignore other prefix patterns, which
6
+ * is fine since the server stays authoritative). Every value must be 63 bytes or less: Payload's
7
+ * db-postgres adapter stores select options as a pg enum, and pg enum labels cap at 63 bytes.
8
+ */
9
+ declare const fileMimeTypeOptions: {
10
+ label: string;
11
+ value: string;
12
+ }[];
13
+ //#endregion
14
+ export { fileMimeTypeOptions };
15
+ //# sourceMappingURL=file.d.ts.map
@@ -3,6 +3,76 @@ import { labelFor } from "../../translations/server.js";
3
3
  import { defineFormField } from "../defineFormField.js";
4
4
  //#region src/fields/builtin/file.ts
5
5
  const isFileRef = (value) => typeof value === "object" && value !== null && "filename" in value;
6
+ const MAX_SIZE_REF = "@10x-media/form-builder/client#ByteSizeField";
7
+ /**
8
+ * Curated MIME type choices for the file field's `mimeTypes` select. Values are raw MIME strings or
9
+ * trailing-`*` prefix wildcards, exactly the patterns `resolveFileRef` matches server-side; `type/*`
10
+ * values double as the file input's `accept` attribute (browsers ignore other prefix patterns, which
11
+ * is fine since the server stays authoritative). Every value must be 63 bytes or less: Payload's
12
+ * db-postgres adapter stores select options as a pg enum, and pg enum labels cap at 63 bytes.
13
+ */
14
+ const fileMimeTypeOptions = [
15
+ {
16
+ label: "Any image",
17
+ value: "image/*"
18
+ },
19
+ {
20
+ label: "PNG image",
21
+ value: "image/png"
22
+ },
23
+ {
24
+ label: "JPEG image",
25
+ value: "image/jpeg"
26
+ },
27
+ {
28
+ label: "WebP image",
29
+ value: "image/webp"
30
+ },
31
+ {
32
+ label: "GIF image",
33
+ value: "image/gif"
34
+ },
35
+ {
36
+ label: "SVG image",
37
+ value: "image/svg+xml"
38
+ },
39
+ {
40
+ label: "PDF",
41
+ value: "application/pdf"
42
+ },
43
+ {
44
+ label: "Plain text",
45
+ value: "text/plain"
46
+ },
47
+ {
48
+ label: "CSV",
49
+ value: "text/csv"
50
+ },
51
+ {
52
+ label: "ZIP archive",
53
+ value: "application/zip"
54
+ },
55
+ {
56
+ label: "Word document (.doc)",
57
+ value: "application/msword"
58
+ },
59
+ {
60
+ label: "Excel spreadsheet (.xls)",
61
+ value: "application/vnd.ms-excel"
62
+ },
63
+ {
64
+ label: "Office documents (docx, xlsx, pptx)",
65
+ value: "application/vnd.openxmlformats-officedocument.*"
66
+ },
67
+ {
68
+ label: "Any audio",
69
+ value: "audio/*"
70
+ },
71
+ {
72
+ label: "Any video",
73
+ value: "video/*"
74
+ }
75
+ ];
6
76
  /**
7
77
  * The `file` field type. Its stored value is a server-captured {@link FileRef}; the client only ever submits
8
78
  * the upload id, and `runSubmission` re-reads filename/mimeType/filesize from the upload doc at the trust
@@ -22,19 +92,25 @@ const fileField = defineFormField({
22
92
  },
23
93
  {
24
94
  name: "mimeTypes",
25
- type: "text",
95
+ type: "select",
26
96
  hasMany: true,
97
+ options: fileMimeTypeOptions,
27
98
  label: labelFor(keys.fileConfigMimeTypes)
28
99
  },
29
100
  {
30
101
  name: "maxSize",
31
102
  type: "number",
32
- label: labelFor(keys.fileConfigMaxSize)
103
+ min: 0,
104
+ label: labelFor(keys.fileConfigMaxSize),
105
+ admin: {
106
+ description: labelFor(keys.fileConfigMaxSizeDescription),
107
+ components: { Field: MAX_SIZE_REF }
108
+ }
33
109
  }
34
110
  ],
35
111
  format: ({ value }) => isFileRef(value) ? value.filename : ""
36
112
  });
37
113
  //#endregion
38
- export { fileField };
114
+ export { fileField, fileMimeTypeOptions };
39
115
 
40
116
  //# sourceMappingURL=file.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","names":[],"sources":["../../../src/fields/builtin/file.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport type { FileRef } from '../../uploads/types'\nimport { defineFormField } from '../defineFormField'\n\ntype FileConfig = {\n\trelationTo?: string\n\tmimeTypes?: string[]\n\tmaxSize?: number\n}\n\nconst isFileRef = (value: unknown): value is FileRef =>\n\ttypeof value === 'object' && value !== null && 'filename' in value\n\n/**\n * The `file` field type. Its stored value is a server-captured {@link FileRef}; the client only ever submits\n * the upload id, and `runSubmission` re-reads filename/mimeType/filesize from the upload doc at the trust\n * boundary (see `captureFileRef`). The intrinsic validator is intentionally a no-op: MIME/size/existence are\n * enforced authoritatively server-side against the stored doc, never against client metadata. `relationTo`,\n * `mimeTypes`, and `maxSize` persist on the block so the capture can enforce them.\n */\nexport const fileField = defineFormField<'file', FileConfig>({\n\ttype: 'file',\n\tlabel: keys.fieldTypeFile,\n\tvalue: 'file',\n\tconfig: [\n\t\t{ name: 'relationTo', type: 'text', label: labelFor(keys.fileConfigRelationTo) },\n\t\t{ name: 'mimeTypes', type: 'text', hasMany: true, label: labelFor(keys.fileConfigMimeTypes) },\n\t\t{ name: 'maxSize', type: 'number', label: labelFor(keys.fileConfigMaxSize) },\n\t],\n\tformat: ({ value }) => (isFileRef(value) ? value.filename : ''),\n})\n"],"mappings":";;;;AAWA,MAAM,aAAa,UAClB,OAAO,UAAU,YAAY,UAAU,QAAQ,cAAc;;;;;;;;AAS9D,MAAa,YAAY,gBAAoC;CAC5D,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,QAAQ;EACP;GAAE,MAAM;GAAc,MAAM;GAAQ,OAAO,SAAS,KAAK,oBAAoB;EAAE;EAC/E;GAAE,MAAM;GAAa,MAAM;GAAQ,SAAS;GAAM,OAAO,SAAS,KAAK,mBAAmB;EAAE;EAC5F;GAAE,MAAM;GAAW,MAAM;GAAU,OAAO,SAAS,KAAK,iBAAiB;EAAE;CAC5E;CACA,SAAS,EAAE,YAAa,UAAU,KAAK,IAAI,MAAM,WAAW;AAC7D,CAAC"}
1
+ {"version":3,"file":"file.js","names":[],"sources":["../../../src/fields/builtin/file.ts"],"sourcesContent":["import { keys } from '../../translations/keys'\nimport { labelFor } from '../../translations/server'\nimport type { FileRef } from '../../uploads/types'\nimport { defineFormField } from '../defineFormField'\n\ntype FileConfig = {\n\trelationTo?: string\n\tmimeTypes?: string[]\n\tmaxSize?: number\n}\n\nconst isFileRef = (value: unknown): value is FileRef =>\n\ttypeof value === 'object' && value !== null && 'filename' in value\n\nconst MAX_SIZE_REF = '@10x-media/form-builder/client#ByteSizeField'\n\n/**\n * Curated MIME type choices for the file field's `mimeTypes` select. Values are raw MIME strings or\n * trailing-`*` prefix wildcards, exactly the patterns `resolveFileRef` matches server-side; `type/*`\n * values double as the file input's `accept` attribute (browsers ignore other prefix patterns, which\n * is fine since the server stays authoritative). Every value must be 63 bytes or less: Payload's\n * db-postgres adapter stores select options as a pg enum, and pg enum labels cap at 63 bytes.\n */\nexport const fileMimeTypeOptions = [\n\t{ label: 'Any image', value: 'image/*' },\n\t{ label: 'PNG image', value: 'image/png' },\n\t{ label: 'JPEG image', value: 'image/jpeg' },\n\t{ label: 'WebP image', value: 'image/webp' },\n\t{ label: 'GIF image', value: 'image/gif' },\n\t{ label: 'SVG image', value: 'image/svg+xml' },\n\t{ label: 'PDF', value: 'application/pdf' },\n\t{ label: 'Plain text', value: 'text/plain' },\n\t{ label: 'CSV', value: 'text/csv' },\n\t{ label: 'ZIP archive', value: 'application/zip' },\n\t{ label: 'Word document (.doc)', value: 'application/msword' },\n\t{ label: 'Excel spreadsheet (.xls)', value: 'application/vnd.ms-excel' },\n\t{\n\t\tlabel: 'Office documents (docx, xlsx, pptx)',\n\t\tvalue: 'application/vnd.openxmlformats-officedocument.*',\n\t},\n\t{ label: 'Any audio', value: 'audio/*' },\n\t{ label: 'Any video', value: 'video/*' },\n]\n\n/**\n * The `file` field type. Its stored value is a server-captured {@link FileRef}; the client only ever submits\n * the upload id, and `runSubmission` re-reads filename/mimeType/filesize from the upload doc at the trust\n * boundary (see `captureFileRef`). The intrinsic validator is intentionally a no-op: MIME/size/existence are\n * enforced authoritatively server-side against the stored doc, never against client metadata. `relationTo`,\n * `mimeTypes`, and `maxSize` persist on the block so the capture can enforce them.\n */\nexport const fileField = defineFormField<'file', FileConfig>({\n\ttype: 'file',\n\tlabel: keys.fieldTypeFile,\n\tvalue: 'file',\n\tconfig: [\n\t\t{ name: 'relationTo', type: 'text', label: labelFor(keys.fileConfigRelationTo) },\n\t\t{\n\t\t\tname: 'mimeTypes',\n\t\t\ttype: 'select',\n\t\t\thasMany: true,\n\t\t\toptions: fileMimeTypeOptions,\n\t\t\tlabel: labelFor(keys.fileConfigMimeTypes),\n\t\t},\n\t\t{\n\t\t\tname: 'maxSize',\n\t\t\ttype: 'number',\n\t\t\tmin: 0,\n\t\t\tlabel: labelFor(keys.fileConfigMaxSize),\n\t\t\tadmin: {\n\t\t\t\tdescription: labelFor(keys.fileConfigMaxSizeDescription),\n\t\t\t\tcomponents: { Field: MAX_SIZE_REF },\n\t\t\t},\n\t\t},\n\t],\n\tformat: ({ value }) => (isFileRef(value) ? value.filename : ''),\n})\n"],"mappings":";;;;AAWA,MAAM,aAAa,UAClB,OAAO,UAAU,YAAY,UAAU,QAAQ,cAAc;AAE9D,MAAM,eAAe;;;;;;;;AASrB,MAAa,sBAAsB;CAClC;EAAE,OAAO;EAAa,OAAO;CAAU;CACvC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAgB;CAC7C;EAAE,OAAO;EAAO,OAAO;CAAkB;CACzC;EAAE,OAAO;EAAc,OAAO;CAAa;CAC3C;EAAE,OAAO;EAAO,OAAO;CAAW;CAClC;EAAE,OAAO;EAAe,OAAO;CAAkB;CACjD;EAAE,OAAO;EAAwB,OAAO;CAAqB;CAC7D;EAAE,OAAO;EAA4B,OAAO;CAA2B;CACvE;EACC,OAAO;EACP,OAAO;CACR;CACA;EAAE,OAAO;EAAa,OAAO;CAAU;CACvC;EAAE,OAAO;EAAa,OAAO;CAAU;AACxC;;;;;;;;AASA,MAAa,YAAY,gBAAoC;CAC5D,MAAM;CACN,OAAO,KAAK;CACZ,OAAO;CACP,QAAQ;EACP;GAAE,MAAM;GAAc,MAAM;GAAQ,OAAO,SAAS,KAAK,oBAAoB;EAAE;EAC/E;GACC,MAAM;GACN,MAAM;GACN,SAAS;GACT,SAAS;GACT,OAAO,SAAS,KAAK,mBAAmB;EACzC;EACA;GACC,MAAM;GACN,MAAM;GACN,KAAK;GACL,OAAO,SAAS,KAAK,iBAAiB;GACtC,OAAO;IACN,aAAa,SAAS,KAAK,4BAA4B;IACvD,YAAY,EAAE,OAAO,aAAa;GACnC;EACD;CACD;CACA,SAAS,EAAE,YAAa,UAAU,KAAK,IAAI,MAAM,WAAW;AAC7D,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { AnyFormFieldDefinition } from "../types.js";
2
+
3
+ //#region src/fields/builtin/index.d.ts
4
+ declare const defaultFieldDefinitions: AnyFormFieldDefinition[];
5
+ /**
6
+ * `defaultFieldDefinitions` indexed by `type`, so a single built-in can be spread and tweaked
7
+ * without hand-rolling a `.find()`, e.g.
8
+ * `fields: { text: { ...defaultFieldDefinitionsByType.text, validate: myValidate } }`. The
9
+ * `fields` plugin option replaces a built-in of the same `type`; `false` removes it, `true` keeps it.
10
+ */
11
+ declare const defaultFieldDefinitionsByType: Record<string, AnyFormFieldDefinition>;
12
+ //#endregion
13
+ export { defaultFieldDefinitions, defaultFieldDefinitionsByType };
14
+ //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { calculationField } from "./calculation.js";
2
2
  import { checkboxField } from "./checkbox.js";
3
3
  import { consentField } from "./consent.js";
4
+ import { dateField } from "./date.js";
4
5
  import { emailField } from "./email.js";
5
6
  import { fileField } from "./file.js";
6
7
  import { numberField } from "./number.js";
@@ -19,9 +20,17 @@ const defaultFieldDefinitions = [
19
20
  calculationField,
20
21
  consentField,
21
22
  fileField,
22
- repeaterField
23
+ repeaterField,
24
+ dateField
23
25
  ];
26
+ /**
27
+ * `defaultFieldDefinitions` indexed by `type`, so a single built-in can be spread and tweaked
28
+ * without hand-rolling a `.find()`, e.g.
29
+ * `fields: { text: { ...defaultFieldDefinitionsByType.text, validate: myValidate } }`. The
30
+ * `fields` plugin option replaces a built-in of the same `type`; `false` removes it, `true` keeps it.
31
+ */
32
+ const defaultFieldDefinitionsByType = Object.fromEntries(defaultFieldDefinitions.map((definition) => [definition.type, definition]));
24
33
  //#endregion
25
- export { defaultFieldDefinitions };
34
+ export { defaultFieldDefinitions, defaultFieldDefinitionsByType };
26
35
 
27
36
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { emailField } from './email'\nimport { fileField } from './file'\nimport { numberField } from './number'\nimport { repeaterField } from './repeater'\nimport { selectField } from './select'\nimport { textField } from './text'\nimport { textareaField } from './textarea'\n\n// Field types are authored with precise value/config generics; the registry stores them erased\n// (config is re-narrowed per matched type at execution). One cast per built-in, no `any`.\nexport const defaultFieldDefinitions: AnyFormFieldDefinition[] = [\n\ttextField as AnyFormFieldDefinition,\n\ttextareaField as AnyFormFieldDefinition,\n\temailField as AnyFormFieldDefinition,\n\tnumberField as AnyFormFieldDefinition,\n\tselectField as AnyFormFieldDefinition,\n\tcheckboxField as AnyFormFieldDefinition,\n\tcalculationField as AnyFormFieldDefinition,\n\tconsentField as AnyFormFieldDefinition,\n\tfileField as AnyFormFieldDefinition,\n\trepeaterField as AnyFormFieldDefinition,\n]\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,0BAAoD;CAChE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/fields/builtin/index.ts"],"sourcesContent":["import type { AnyFormFieldDefinition } from '../types'\nimport { calculationField } from './calculation'\nimport { checkboxField } from './checkbox'\nimport { consentField } from './consent'\nimport { dateField } from './date'\nimport { emailField } from './email'\nimport { fileField } from './file'\nimport { numberField } from './number'\nimport { repeaterField } from './repeater'\nimport { selectField } from './select'\nimport { textField } from './text'\nimport { textareaField } from './textarea'\n\n// Field types are authored with precise value/config generics; the registry stores them erased\n// (config is re-narrowed per matched type at execution). One cast per built-in, no `any`.\nexport const defaultFieldDefinitions: AnyFormFieldDefinition[] = [\n\ttextField as AnyFormFieldDefinition,\n\ttextareaField as AnyFormFieldDefinition,\n\temailField as AnyFormFieldDefinition,\n\tnumberField as AnyFormFieldDefinition,\n\tselectField as AnyFormFieldDefinition,\n\tcheckboxField as AnyFormFieldDefinition,\n\tcalculationField as AnyFormFieldDefinition,\n\tconsentField as AnyFormFieldDefinition,\n\tfileField as AnyFormFieldDefinition,\n\trepeaterField as AnyFormFieldDefinition,\n\tdateField as AnyFormFieldDefinition,\n]\n\n/**\n * `defaultFieldDefinitions` indexed by `type`, so a single built-in can be spread and tweaked\n * without hand-rolling a `.find()`, e.g.\n * `fields: { text: { ...defaultFieldDefinitionsByType.text, validate: myValidate } }`. The\n * `fields` plugin option replaces a built-in of the same `type`; `false` removes it, `true` keeps it.\n */\nexport const defaultFieldDefinitionsByType: Record<string, AnyFormFieldDefinition> =\n\tObject.fromEntries(defaultFieldDefinitions.map((definition) => [definition.type, definition]))\n"],"mappings":";;;;;;;;;;;;AAeA,MAAa,0BAAoD;CAChE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;;;AAQA,MAAa,gCACZ,OAAO,YAAY,wBAAwB,KAAK,eAAe,CAAC,WAAW,MAAM,UAAU,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ //#region src/fields/fieldNamesOfType.ts
2
+ /**
3
+ * Names of form `fields` blocks whose `blockType` is in `types`, trimmed and non-empty.
4
+ * Server-safe and defensive: `fields` may be missing, non-array, or contain garbled rows
5
+ * mid-edit. The single source of truth for which field names are selectable and valid as
6
+ * a field reference (e.g. the confirmation action's `toField`), so client options and
7
+ * server validation cannot drift apart.
8
+ */
9
+ const fieldNamesOfType = (fields, types) => {
10
+ if (!Array.isArray(fields)) return [];
11
+ const names = [];
12
+ for (const row of fields) {
13
+ if (!row || typeof row !== "object") continue;
14
+ const { blockType, name } = row;
15
+ if (typeof blockType !== "string" || !types.includes(blockType)) continue;
16
+ if (typeof name !== "string") continue;
17
+ const trimmed = name.trim();
18
+ if (trimmed.length > 0) names.push(trimmed);
19
+ }
20
+ return names;
21
+ };
22
+ //#endregion
23
+ export { fieldNamesOfType };
24
+
25
+ //# sourceMappingURL=fieldNamesOfType.js.map