Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/docs/solutions/template-builder/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ interface FieldDefinition {
id: string; // Unique identifier
label: string; // Display name
defaultValue?: string; // Default value for new instances
presetContent?: {
html?: string; // Block preset as HTML
json?: unknown; // Block preset as ProseMirror JSON
};
metadata?: Record<string, any>; // Custom metadata stored in the SDT tag
mode?: "inline" | "block"; // Insertion mode (default: "inline")
group?: string; // Group ID for linked fields
Expand All @@ -186,6 +190,8 @@ interface FieldDefinition {
}
```

`presetContent` is applied only for `mode: "block"` fields. Inline fields ignore it.

### TemplateField

Fields that exist in the template document:
Expand Down
31 changes: 31 additions & 0 deletions apps/docs/solutions/template-builder/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,37 @@ Each color controls the field's border and label in the document. The sidebar ba

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

### Preset block content

Use `presetContent` to prefill block fields with a structure such as a table or list:

```tsx
<SuperDocTemplateBuilder
fields={{
available: [
{
id: "sample_table",
label: "Sample Table",
mode: "block",
fieldType: "data",
lockMode: "contentLocked",
presetContent: {
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>",
// or json: { ...ProseMirror JSON... }
},
},
],
}}
/>
```

`presetContent` is passed into the existing block insertion command. For `mode: "inline"` fields, `presetContent` is ignored.

<Note>
- `presetContent.json` is recommended when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.
</Note>

### Field creation

Allow users to create new fields while building templates:
Expand Down
29 changes: 29 additions & 0 deletions apps/docs/solutions/template-builder/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,35 @@ Distinguish between owner-filled and signer-filled fields:

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

### Add preset block content

If a field should insert a predefined structure (for example, a table), add `presetContent` on a block field:

```jsx
<SuperDocTemplateBuilder
fields={{
available: [
{
id: "sample_table",
label: "Sample Table",
mode: "block",
fieldType: "data",
presetContent: {
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>",
},
},
],
}}
/>
```

`presetContent` applies only to block fields. Inline fields ignore it.

<Note>
- `presetContent.json` is recommended when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.
</Note>

### Color-code fields in the editor

Import the optional CSS to visually distinguish field types:
Expand Down
37 changes: 36 additions & 1 deletion packages/template-builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ function TemplateEditor() {
available: [
{ id: '1324567890', label: 'Customer Name' },
{ id: '1324567891', label: 'Invoice Date' },
{ id: '1324567892', label: 'Signature', mode: 'block', fieldType: 'signer' },
{
id: '1324567892',
label: 'Sample Table',
mode: 'block',
fieldType: 'signer',
presetContent: {
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>',
},
},
{ id: '1324567893', label: 'Signature', mode: 'block', fieldType: 'signer' },
],
}}
onFieldInsert={(field) => {
Expand Down Expand Up @@ -179,6 +188,32 @@ onFieldsChange={(fields) => {
}}
```

## Preset Block Content

Block fields can include `presetContent` so insertion starts with a predefined structure (for example, a table or list).

```jsx
const availableFields = [
{
id: 'sample_table',
label: 'Sample Table',
mode: 'block',
fieldType: 'data',
lockMode: 'contentLocked',
presetContent: {
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>',
// or json: { ...ProseMirror JSON... }
},
},
];
```

Notes:
- `presetContent` is used only for `mode: 'block'`.
- Inline fields ignore `presetContent`.
- Prefer `presetContent.json` when you need the most reliable preservation of structure, semantics, and editor-compatible styling.
- With `presetContent.html`, include required attributes and inline styles explicitly so the HTML parser can map them into node attributes.

## Custom Field Creation

Enable inline field creation in the dropdown menu:
Expand Down
78 changes: 78 additions & 0 deletions packages/template-builder/demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import type {
import 'superdoc/style.css';
import './App.css';

const cellBorders = {
top: { val: 'single', size: 1, color: '#000000', style: 'solid' },
right: { val: 'single', size: 1, color: '#000000', style: 'solid' },
bottom: { val: 'single', size: 1, color: '#000000', style: 'solid' },
left: { val: 'single', size: 1, color: '#000000', style: 'solid' },
} as const;

const availableFields: FieldDefinition[] = [
{ id: '1242142770', label: 'Agreement Date' },
{ id: '1242142771', label: 'User Name', defaultValue: 'John Doe' },
Expand All @@ -20,6 +27,77 @@ const availableFields: FieldDefinition[] = [
{ id: '1242142776', label: 'Signature', mode: 'block' },
{ id: '1242142777', label: 'Signer Name', fieldType: 'signer' },
{ id: '1242142778', label: 'Signer Table', mode: 'block', fieldType: 'signer' },
{
id: '1242142779',
label: 'Sample Table',
mode: 'block',
fieldType: 'signer',
presetContent: {
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>',
},
},
{
id: '1242142780',
label: 'Sample List',
mode: 'block',
fieldType: 'signer',
presetContent: {
html: '<ul><li>First item</li><li>Second item</li><li>Third item</li></ul>',
},
},
{
id: '1242142781',
label: 'Sample Table (JSON)',
mode: 'block',
fieldType: 'signer',
presetContent: {
json: {
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableHeader',
attrs: { borders: cellBorders },
content: [
{
type: 'paragraph',
content: [{ type: 'run', content: [{ type: 'text', text: 'Column A' }] }],
},
],
},
{
type: 'tableHeader',
attrs: { borders: cellBorders },
content: [
{
type: 'paragraph',
content: [{ type: 'run', content: [{ type: 'text', text: 'Column B' }] }],
},
],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: { borders: cellBorders },
content: [{ type: 'paragraph' }],
},
{
type: 'tableCell',
attrs: { borders: cellBorders },
content: [{ type: 'paragraph' }],
},
],
},
],
},
},
},
];

export function App() {
Expand Down
10 changes: 9 additions & 1 deletion packages/template-builder/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
type Editor = NonNullable<SuperDoc['activeEditor']>;

const applyDocumentMode = (instance: SuperDoc, mode: string) => {
(instance as any).setDocumentMode(mode);

Check warning on line 19 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
};

const getTemplateFieldsFromEditor = (editor: Editor): Types.TemplateField[] => {
const structuredContentHelpers = (editor.helpers as any)?.structuredContentCommands;

Check warning on line 23 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type

if (!structuredContentHelpers?.getStructuredContentTags) {
return [];
Expand All @@ -28,7 +28,7 @@

const tags = structuredContentHelpers.getStructuredContentTags(editor.state) || [];

return tags.map((entry: any) => {

Check warning on line 31 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
const node = entry?.node ?? entry;
const attrs = node?.attrs ?? {};
const nodeType = node?.type?.name || '';
Expand Down Expand Up @@ -183,7 +183,13 @@
const success = (
mode === 'inline'
? editor.commands.insertStructuredContentInline?.({ attrs, text: field.defaultValue || field.alias })
: editor.commands.insertStructuredContentBlock?.({ attrs, text: field.defaultValue || field.alias })
: field.presetContent?.json || field.presetContent?.html
? editor.commands.insertStructuredContentBlock?.({
attrs,
...(field.presetContent.html ? { html: field.presetContent.html } : {}),
...(field.presetContent.json ? { json: field.presetContent.json } : {}),
})
: editor.commands.insertStructuredContentBlock?.({ attrs, text: field.defaultValue || field.alias })
) as boolean | undefined;

if (success) {
Expand Down Expand Up @@ -379,7 +385,7 @@
const editor = instance.activeEditor;
const pe = getPresentationEditor(instance);

editor.on('update', ({ editor: e }: any) => {

Check warning on line 388 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
const { state } = e;
const { from } = state.selection;

Expand All @@ -396,7 +402,7 @@
if (!editor) return;
const currentPos = editor.state.selection.from;
const tr = editor.state.tr.delete(triggerStart, currentPos);
(editor as any).view.dispatch(tr);

Check warning on line 405 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
};

triggerCleanupRef.current = cleanup;
Expand Down Expand Up @@ -531,6 +537,7 @@
alias: createdField.label,
metadata: createdField.metadata,
defaultValue: createdField.defaultValue,
presetContent: createdField.presetContent,
fieldType: createdField.fieldType,
lockMode: createdField.lockMode,
});
Expand All @@ -543,6 +550,7 @@
alias: field.label,
metadata: field.metadata,
defaultValue: field.defaultValue,
presetContent: field.presetContent,
fieldType: field.fieldType,
lockMode: field.lockMode,
});
Expand All @@ -563,7 +571,7 @@
const editor = superdocRef.current?.activeEditor;
if (!editor) return;

const structuredContentHelpers = (editor.helpers as any)?.structuredContentCommands;

Check warning on line 574 in packages/template-builder/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type

if (!structuredContentHelpers) return;

Expand Down
Loading
Loading