mirror of
https://github.com/documenso/documenso.git
synced 2026-08-26 00:02:43 +10:00
223 lines
6.4 KiB
Markdown
223 lines
6.4 KiB
Markdown
---
|
|
date: 2026-05-07
|
|
title: Pdf Placeholder Selection Fields
|
|
---
|
|
|
|
## Summary
|
|
|
|
Extend PDF placeholders so radio and dropdown fields can be configured from the existing Documenso placeholder syntax:
|
|
|
|
```text
|
|
{{FIELD_TYPE, RECIPIENT, key=value, key=value}}
|
|
```
|
|
|
|
Do not introduce a new delimiter style. Existing applications may already generate placeholders in this format, so the new selection-field behavior should fit into it.
|
|
|
|
## Goals
|
|
|
|
- Keep the current placeholder grammar unchanged.
|
|
- Support checkbox placeholders with option lists, checked values, validation, direction, required, read-only, and font size.
|
|
- Support radio placeholders with option lists, default/preselected values, direction, required, read-only, and font size.
|
|
- Support dropdown placeholders with option lists, default value, required, read-only, and font size.
|
|
- Use `options` as the only public list key in PDF placeholders.
|
|
- Convert `options` into internal `fieldMeta.values` during parsing.
|
|
- Make generated fields usable immediately in the editor, signing UI, preview renderer, and final PDF export.
|
|
|
|
## Non-Goals
|
|
|
|
- No semicolon placeholder syntax.
|
|
- No `values` alias in PDF placeholder syntax.
|
|
- No database migration.
|
|
- No behavior change for existing placeholders such as `{{text, r1, required=true}}`.
|
|
|
|
## Placeholder Syntax
|
|
|
|
Use the existing comma-separated placeholder format:
|
|
|
|
```text
|
|
{{checkbox, r1, options=Email|SMS|Phone, checked=Email|Phone, validationRule=atLeast, validationLength=1}}
|
|
{{radio, r1, options=Card|Bank transfer|Check, defaultValue=Check}}
|
|
{{radio, r1, options=Basic|Pro|Enterprise, selected=Pro, direction=horizontal}}
|
|
{{dropdown, r1, options=United States|Canada|United Kingdom}}
|
|
{{dropdown, r2, options=Sales|Legal|Finance, defaultValue=Legal}}
|
|
```
|
|
|
|
Use `|` inside `options` because `,` is already the top-level placeholder delimiter.
|
|
|
|
Parsing rules:
|
|
|
|
- Split top-level placeholder tokens on unescaped commas.
|
|
- Split metadata tokens on the first unescaped equals sign.
|
|
- Split `options` on unescaped pipes.
|
|
- Trim option values and drop empty values.
|
|
- Preserve option order.
|
|
- Support escaped delimiters: `\,`, `\=`, and `\|`.
|
|
- Treat field type values case-insensitively.
|
|
|
|
## Field Type Mapping
|
|
|
|
- `checkbox` maps to `FieldType.CHECKBOX`.
|
|
- `radio` maps to `FieldType.RADIO`.
|
|
- `dropdown` maps to `FieldType.DROPDOWN`.
|
|
|
|
## Metadata Mapping
|
|
|
|
### Checkbox
|
|
|
|
Example:
|
|
|
|
```text
|
|
{{checkbox, r1, options=Email|SMS|Phone, checked=Email|Phone, validationRule=atLeast, validationLength=1}}
|
|
```
|
|
|
|
Normalize to:
|
|
|
|
```ts
|
|
{
|
|
type: FieldType.CHECKBOX,
|
|
fieldMeta: {
|
|
type: 'checkbox',
|
|
validationRule: 'Select at least',
|
|
validationLength: 1,
|
|
values: [
|
|
{ id: 1, value: 'Email', checked: true },
|
|
{ id: 2, value: 'SMS', checked: false },
|
|
{ id: 3, value: 'Phone', checked: true },
|
|
],
|
|
},
|
|
}
|
|
```
|
|
|
|
Accepted keys:
|
|
|
|
- `options`
|
|
- `checked`
|
|
- `direction=vertical|horizontal`
|
|
- `validationRule=atLeast|exactly|atMost`
|
|
- `validationLength=1`
|
|
- `required=true|false`
|
|
- `readOnly=true|false`
|
|
- `fontSize=12`
|
|
|
|
Map checkbox validation aliases internally: `atLeast` -> `Select at least`, `exactly` -> `Select exactly`, `atMost` -> `Select at most`.
|
|
|
|
Checkbox placeholders do not support `label` or `placeholder` metadata.
|
|
|
|
### Radio
|
|
|
|
Example:
|
|
|
|
```text
|
|
{{radio, r1, options=Card|Bank transfer|Check, selected=Bank transfer}}
|
|
```
|
|
|
|
Normalize to:
|
|
|
|
```ts
|
|
{
|
|
type: FieldType.RADIO,
|
|
fieldMeta: {
|
|
type: 'radio',
|
|
values: [
|
|
{ id: 1, value: 'Card', checked: false },
|
|
{ id: 2, value: 'Bank transfer', checked: true },
|
|
{ id: 3, value: 'Check', checked: false },
|
|
],
|
|
},
|
|
}
|
|
```
|
|
|
|
Accepted keys:
|
|
|
|
- `options`
|
|
- `selected`, `default`, or `defaultValue`
|
|
- `direction=vertical|horizontal`
|
|
- `required=true|false`
|
|
- `readOnly=true|false`
|
|
- `fontSize=12`
|
|
|
|
Radio placeholders do not support `label` or `placeholder` metadata.
|
|
|
|
### Dropdown
|
|
|
|
Example:
|
|
|
|
```text
|
|
{{dropdown, r1, options=Sales|Legal|Finance, defaultValue=Legal}}
|
|
```
|
|
|
|
Normalize to:
|
|
|
|
```ts
|
|
{
|
|
type: FieldType.DROPDOWN,
|
|
fieldMeta: {
|
|
type: 'dropdown',
|
|
values: [{ value: 'Sales' }, { value: 'Legal' }, { value: 'Finance' }],
|
|
defaultValue: 'Legal',
|
|
},
|
|
}
|
|
```
|
|
|
|
Accepted keys:
|
|
|
|
- `options`
|
|
- `selected`, `default`, or `defaultValue`
|
|
- `required=true|false`
|
|
- `readOnly=true|false`
|
|
- `fontSize=12`
|
|
|
|
`defaultValue` should only be set if it matches one parsed option.
|
|
|
|
Dropdown placeholders do not support `label` or `placeholder` metadata.
|
|
|
|
## Code Touchpoints
|
|
|
|
- `packages/lib/server-only/pdf/helpers.ts`
|
|
- Extend `parseFieldMetaFromPlaceholder` so `options` normalizes into checkbox/radio/dropdown `fieldMeta.values`.
|
|
- Add delimiter-aware helpers for commas, equals signs, and pipes.
|
|
- `packages/lib/server-only/pdf/auto-place-fields.ts`
|
|
- Replace plain comma splitting with delimiter-aware splitting.
|
|
- Preserve the existing positional structure: field type, recipient, metadata.
|
|
- `packages/lib/types/field-meta.ts`
|
|
- Keep current internal schemas: checkbox/radio/dropdown still store options as `fieldMeta.values`.
|
|
- `packages/ui/primitives/document-flow/field-content.tsx`
|
|
- Display a radio fallback when a placeholder-created radio has no options.
|
|
- Docs:
|
|
- `apps/docs/content/docs/users/documents/advanced/pdf-placeholders.mdx`
|
|
- `apps/docs/content/docs/developers/api/fields.mdx`
|
|
|
|
## Test Plan
|
|
|
|
Unit tests:
|
|
|
|
- `options=Yes|No|Maybe` becomes stable radio values.
|
|
- `selected=No` marks only the matching radio option checked.
|
|
- Checkbox `options`, `checked`, `validationRule`, and `validationLength` normalize correctly.
|
|
- Dropdown `options` and `defaultValue` normalize correctly.
|
|
- Escaped delimiters parse correctly, for example `options=Sales\|Ops|Legal\, Compliance|A\=B`.
|
|
|
|
E2E/API tests:
|
|
|
|
- Add a PDF fixture with checkbox, radio, and dropdown placeholders using the current syntax.
|
|
- Verify created fields have schema-compatible metadata and expected options/defaults.
|
|
|
|
Suggested verification:
|
|
|
|
```bash
|
|
npm run test -w @documenso/lib -- server-only/pdf/helpers.test.ts
|
|
npm run test:dev -w @documenso/app-tests -- e2e/auto-placing-fields/auto-place-fields-document.spec.ts
|
|
npm run test:dev -w @documenso/app-tests -- e2e/envelope-editor-v2/envelope-fields.spec.ts
|
|
npx tsc --noEmit -p packages/lib/tsconfig.json
|
|
npx tsc --noEmit -p apps/remix/tsconfig.json
|
|
```
|
|
|
|
Do not use `npm run build` for routine verification unless explicitly requested.
|
|
|
|
## Decisions
|
|
|
|
- Keep the existing placeholder format.
|
|
- Use only `options` publicly.
|
|
- Keep `values` as an internal metadata field only.
|
|
- Use `|` as the option delimiter inside `options`.
|