Skip to content

Commit 1d3d77a

Browse files
artem-harbourArtem Nistuleycaio-pizzol
authored
feat: add presetContent for template builder (#2856)
Co-authored-by: Artem Nistuley <artem@superdoc.dev> Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com>
1 parent 8e2fcf1 commit 1d3d77a

8 files changed

Lines changed: 357 additions & 2 deletions

File tree

apps/docs/solutions/template-builder/api-reference.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ interface FieldDefinition {
178178
id: string; // Unique identifier
179179
label: string; // Display name
180180
defaultValue?: string; // Default value for new instances
181+
presetContent?: {
182+
html?: string; // Block preset as HTML
183+
json?: unknown; // Block preset as ProseMirror JSON
184+
};
181185
metadata?: Record<string, any>; // Custom metadata stored in the SDT tag
182186
mode?: "inline" | "block"; // Insertion mode (default: "inline")
183187
group?: string; // Group ID for linked fields
@@ -186,6 +190,8 @@ interface FieldDefinition {
186190
}
187191
```
188192

193+
`presetContent` is applied only for `mode: "block"` fields. Inline fields ignore it.
194+
189195
### TemplateField
190196

191197
Fields that exist in the template document:

apps/docs/solutions/template-builder/configuration.mdx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,37 @@ Each color controls the field's border and label in the document. The sidebar ba
100100

101101
The `fieldType` value flows through all callbacks (`onFieldInsert`, `onFieldsChange`, `onExport`, etc.) and is stored in the SDT tag metadata.
102102

103+
### Preset block content
104+
105+
Use `presetContent` to prefill block fields with a structure such as a table or list:
106+
107+
```tsx
108+
<SuperDocTemplateBuilder
109+
fields={{
110+
available: [
111+
{
112+
id: "sample_table",
113+
label: "Sample Table",
114+
mode: "block",
115+
fieldType: "data",
116+
lockMode: "contentLocked",
117+
presetContent: {
118+
html: "<table style=\"border-collapse: collapse; width: 100%;\"><tr><th style=\"border: 1px solid #000;\">Column A</th><th style=\"border: 1px solid #000;\">Column B</th></tr><tr><td style=\"border: 1px solid #000;\"></td><td style=\"border: 1px solid #000;\"></td></tr></table>",
119+
// or json: { ...ProseMirror JSON... }
120+
},
121+
},
122+
],
123+
}}
124+
/>
125+
```
126+
127+
`presetContent` is passed into the existing block insertion command. For `mode: "inline"` fields, `presetContent` is ignored.
128+
129+
<Note>
130+
- `presetContent.json` is recommended when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
131+
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.
132+
</Note>
133+
103134
### Field creation
104135

105136
Allow users to create new fields while building templates:

apps/docs/solutions/template-builder/quickstart.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,35 @@ Distinguish between owner-filled and signer-filled fields:
7575

7676
Fields default to `'owner'` when no `fieldType` is specified.
7777

78+
### Add preset block content
79+
80+
If a field should insert a predefined structure (for example, a table), add `presetContent` on a block field:
81+
82+
```jsx
83+
<SuperDocTemplateBuilder
84+
fields={{
85+
available: [
86+
{
87+
id: "sample_table",
88+
label: "Sample Table",
89+
mode: "block",
90+
fieldType: "data",
91+
presetContent: {
92+
html: "<table style=\"border-collapse: collapse; width: 100%;\"><tr><th style=\"border: 1px solid #000;\">Column A</th><th style=\"border: 1px solid #000;\">Column B</th></tr><tr><td style=\"border: 1px solid #000;\"></td><td style=\"border: 1px solid #000;\"></td></tr></table>",
93+
},
94+
},
95+
],
96+
}}
97+
/>
98+
```
99+
100+
`presetContent` applies only to block fields. Inline fields ignore it.
101+
102+
<Note>
103+
- `presetContent.json` is recommended when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
104+
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.
105+
</Note>
106+
78107
### Color-code fields in the editor
79108

80109
Import the optional CSS to visually distinguish field types:

packages/template-builder/README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,16 @@ function TemplateEditor() {
2525
available: [
2626
{ id: '1324567890', label: 'Customer Name' },
2727
{ id: '1324567891', label: 'Invoice Date' },
28-
{ id: '1324567892', label: 'Signature', mode: 'block', fieldType: 'signer' },
28+
{
29+
id: '1324567892',
30+
label: 'Sample Table',
31+
mode: 'block',
32+
fieldType: 'signer',
33+
presetContent: {
34+
html: '<table style="border-collapse: collapse; width: 100%;"><tr><th style="border: 1px solid #000;">Column A</th><th style="border: 1px solid #000;">Column B</th></tr><tr><td style="border: 1px solid #000;"></td><td style="border: 1px solid #000;"></td></tr></table>',
35+
},
36+
},
37+
{ id: '1324567893', label: 'Signature', mode: 'block', fieldType: 'signer' },
2938
],
3039
}}
3140
onFieldInsert={(field) => {
@@ -179,6 +188,32 @@ onFieldsChange={(fields) => {
179188
}}
180189
```
181190

191+
## Preset Block Content
192+
193+
Block fields can include `presetContent` so insertion starts with a predefined structure (for example, a table or list).
194+
195+
```jsx
196+
const availableFields = [
197+
{
198+
id: 'sample_table',
199+
label: 'Sample Table',
200+
mode: 'block',
201+
fieldType: 'data',
202+
lockMode: 'contentLocked',
203+
presetContent: {
204+
html: '<table style="border-collapse: collapse; width: 100%;"><tr><th style="border: 1px solid #000;">Column A</th><th style="border: 1px solid #000;">Column B</th></tr><tr><td style="border: 1px solid #000;"></td><td style="border: 1px solid #000;"></td></tr></table>',
205+
// or json: { ...ProseMirror JSON... }
206+
},
207+
},
208+
];
209+
```
210+
211+
Notes:
212+
- `presetContent` is used only for `mode: 'block'`.
213+
- Inline fields ignore `presetContent`.
214+
- Prefer `presetContent.json` when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
215+
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.
216+
182217
## Custom Field Creation
183218

184219
Enable inline field creation in the dropdown menu:

packages/template-builder/demo/src/App.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import type {
1010
import 'superdoc/style.css';
1111
import './App.css';
1212

13+
const cellBorders = {
14+
top: { val: 'single', size: 1, color: '#000000', style: 'solid' },
15+
right: { val: 'single', size: 1, color: '#000000', style: 'solid' },
16+
bottom: { val: 'single', size: 1, color: '#000000', style: 'solid' },
17+
left: { val: 'single', size: 1, color: '#000000', style: 'solid' },
18+
} as const;
19+
1320
const availableFields: FieldDefinition[] = [
1421
{ id: '1242142770', label: 'Agreement Date' },
1522
{ id: '1242142771', label: 'User Name', defaultValue: 'John Doe' },
@@ -20,6 +27,77 @@ const availableFields: FieldDefinition[] = [
2027
{ id: '1242142776', label: 'Signature', mode: 'block' },
2128
{ id: '1242142777', label: 'Signer Name', fieldType: 'signer' },
2229
{ id: '1242142778', label: 'Signer Table', mode: 'block', fieldType: 'signer' },
30+
{
31+
id: '1242142779',
32+
label: 'Sample Table',
33+
mode: 'block',
34+
fieldType: 'signer',
35+
presetContent: {
36+
html: '<table style="border-collapse: collapse; width: 100%;"><tr><th style="border: 1px solid #000;">Column A</th><th style="border: 1px solid #000;">Column B</th></tr><tr><td style="border: 1px solid #000;"></td><td style="border: 1px solid #000;"></td></tr></table>',
37+
},
38+
},
39+
{
40+
id: '1242142780',
41+
label: 'Sample List',
42+
mode: 'block',
43+
fieldType: 'signer',
44+
presetContent: {
45+
html: '<ul><li>First item</li><li>Second item</li><li>Third item</li></ul>',
46+
},
47+
},
48+
{
49+
id: '1242142781',
50+
label: 'Sample Table (JSON)',
51+
mode: 'block',
52+
fieldType: 'signer',
53+
presetContent: {
54+
json: {
55+
type: 'table',
56+
content: [
57+
{
58+
type: 'tableRow',
59+
content: [
60+
{
61+
type: 'tableHeader',
62+
attrs: { borders: cellBorders },
63+
content: [
64+
{
65+
type: 'paragraph',
66+
content: [{ type: 'run', content: [{ type: 'text', text: 'Column A' }] }],
67+
},
68+
],
69+
},
70+
{
71+
type: 'tableHeader',
72+
attrs: { borders: cellBorders },
73+
content: [
74+
{
75+
type: 'paragraph',
76+
content: [{ type: 'run', content: [{ type: 'text', text: 'Column B' }] }],
77+
},
78+
],
79+
},
80+
],
81+
},
82+
{
83+
type: 'tableRow',
84+
content: [
85+
{
86+
type: 'tableCell',
87+
attrs: { borders: cellBorders },
88+
content: [{ type: 'paragraph' }],
89+
},
90+
{
91+
type: 'tableCell',
92+
attrs: { borders: cellBorders },
93+
content: [{ type: 'paragraph' }],
94+
},
95+
],
96+
},
97+
],
98+
},
99+
},
100+
},
23101
];
24102

25103
export function App() {

packages/template-builder/src/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,13 @@ const SuperDocTemplateBuilder = forwardRef<Types.SuperDocTemplateBuilderHandle,
183183
const success = (
184184
mode === 'inline'
185185
? editor.commands.insertStructuredContentInline?.({ attrs, text: field.defaultValue || field.alias })
186-
: editor.commands.insertStructuredContentBlock?.({ attrs, text: field.defaultValue || field.alias })
186+
: field.presetContent?.json || field.presetContent?.html
187+
? editor.commands.insertStructuredContentBlock?.({
188+
attrs,
189+
...(field.presetContent.html ? { html: field.presetContent.html } : {}),
190+
...(field.presetContent.json ? { json: field.presetContent.json } : {}),
191+
})
192+
: editor.commands.insertStructuredContentBlock?.({ attrs, text: field.defaultValue || field.alias })
187193
) as boolean | undefined;
188194

189195
if (success) {
@@ -531,6 +537,7 @@ const SuperDocTemplateBuilder = forwardRef<Types.SuperDocTemplateBuilderHandle,
531537
alias: createdField.label,
532538
metadata: createdField.metadata,
533539
defaultValue: createdField.defaultValue,
540+
presetContent: createdField.presetContent,
534541
fieldType: createdField.fieldType,
535542
lockMode: createdField.lockMode,
536543
});
@@ -543,6 +550,7 @@ const SuperDocTemplateBuilder = forwardRef<Types.SuperDocTemplateBuilderHandle,
543550
alias: field.label,
544551
metadata: field.metadata,
545552
defaultValue: field.defaultValue,
553+
presetContent: field.presetContent,
546554
fieldType: field.fieldType,
547555
lockMode: field.lockMode,
548556
});

0 commit comments

Comments
 (0)