@servicetitan/dte-pdf-editor
v1.35.0
Published
A React component library for creating interactive PDF editors with drag-and-drop field placement, data model integration, e-signature support, fillable form fields, and calculated fields with formulas.
Keywords
Readme
@servicetitan/dte-pdf-editor
A React component library for creating interactive PDF editors with drag-and-drop field placement, data model integration, e-signature support, fillable form fields, and calculated fields with formulas.
Features
- 📄 PDF Rendering: Display and interact with PDF documents using
react-pdfandpdfjs-dist - 🎯 Drag & Drop Field Placement: Intuitively place fields on PDF pages by dragging from a sidebar
- 📊 Data Model Integration: Connect fields to structured data models using JSON Schema
- ✍️ E-Signature Support: Add signature, initials, date signed, and full name fields
- 📝 Fillable Fields: Support for text, number, date, checkbox, and radio button inputs
- 🧮 Calculated Fields: Formula-based fields with validation, autosuggest, and advanced formatting (number, currency, percent, rounding, separators)
- 📋 Generic Fields: Custom table and text blocks with configurable rows/cells and inline editing in view mode
- 👥 Multi-Recipient Support: Assign fields to different recipients with color-coded visualization
- ⚙️ Field Configuration: Configure field properties including position, size, label, recipient assignment, formula/format for calculated fields, and table structure (header/body rows and cells) for generic table fields
- 👁️ View Mode: Display PDFs with filled data in a read-only or interactive view mode (including generic fields via
PdfViewGeneric)
Recent changes (vs master) — feature/DTE-3811
- Generic fields: New field type
genericwith subTypestableandtext. Tables support configurable header and body rows with editable cells; text blocks support inline editing. Both are rendered in the overlay and in view mode. - PdfField: Added optional
data?: FieldDataTypefor generic fields (GenericFieldTableDataTypefor tables,GenericFieldTextDataTypefor text). - Field config panel: New Table configs (TableConfigs) for generic table fields—manage header visibility, header/body row heights, and cell values. Inline editable components for text and table content in the overlay.
- Sidebar: Generic field type list (Text, Table) in the field menu for drag-and-drop placement.
- PdfView: Generic fields are rendered in view mode via
PdfViewGenericwith the same inline editing support.
Installation
npm install @servicetitan/dte-pdf-editorPeer Dependencies
This package requires the following peer dependencies:
npm install @servicetitan/anvil2@^1.46.9 react@~18.3.1 react-dom@~18.3.1Quick Start
import { PdfEditor, PdfField, PdfView, SchemaObject } from '@servicetitan/dte-pdf-editor';
import { useState } from 'react';
const pdfUrl = 'https://example.com/document.pdf';
function App() {
const [fields, setFields] = useState<PdfField[]>([]);
const [data, setData] = useState<Record<string, any>>({});
const recipients = [
{ id: 1, name: 'technician', displayName: 'Technician' },
{ id: 2, name: 'customer', displayName: 'Customer' },
];
const dataModel: SchemaObject = {
type: 'object',
properties: {
CustomerName: {
type: 'string',
title: 'Customer Name',
options: { placeholder: 'Enter customer name' },
},
},
};
return (
<PdfEditor
pdfUrl={pdfUrl}
fields={fields}
onFieldsChange={setFields}
dataModel={dataModel}
recipients={recipients}
/>
);
}Components
PdfEditor
The main component for editing PDF documents and placing fields.
Props
| Prop | Type | Required | Description |
|----------------------|--------------------------------|----------|---------------------------------------------------------|
| pdfUrl | string | Yes | URL or path to the PDF file |
| fields | PdfField[] | No | Array of fields placed on the PDF |
| onFieldsChange | (fields: PdfField[]) => void | Yes | Callback when fields are added, modified, or deleted |
| dataModel | SchemaObject | No | JSON Schema object defining available data model fields |
| recipients | RecipientInfo[] | No | Array of recipients that can be assigned to fields |
| loading | boolean | No | Whether the PDF is currently loading |
| loadingPlaceholder | ReactNode | No | Custom component to display while PDF is loading |
| errorPlaceholder | ReactNode | No | Custom component to display if PDF fails to load |
Example
<PdfEditor
pdfUrl="https://example.com/document.pdf"
fields={fields}
onFieldsChange={setFields}
dataModel={dataModel}
recipients={recipients}
loading={!pdfUrl}
loadingPlaceholder={<Spinner />}
errorPlaceholder={<Alert status="danger" title="Failed to load PDF" />}
/>PdfView
Component for displaying PDFs with filled data in view mode.
Props
| Prop | Type | Required | Description |
|----------------------|----------------------------------------------------------------|----------|----------------------------------------------------------|
| pdfUrl | string | Yes | URL or path to the PDF file |
| fields | PdfField[] | Yes | Array of fields to display on the PDF |
| data | DataModelValues | No | Data object containing values for data model fields |
| recipients | RecipientInfo[] | No | Array of recipients for field assignment |
| fillingBy | string[] | No | Array of recipient names that are allowed to fill fields |
| onDataChange | (changedData: { [path: string]: string \| boolean }) => void | No | Callback when fillable field data changes |
| loading | boolean | No | Whether the PDF is currently loading |
| loadingPlaceholder | ReactNode | No | Custom component to display while PDF is loading |
| errorPlaceholder | ReactNode | No | Custom component to display if PDF fails to load |
Example
<PdfView
pdfUrl="https://example.com/document.pdf"
fields={fields}
data={data}
recipients={recipients}
fillingBy={['technician']}
onDataChange={(changedData) => {
setData({ ...data, ...changedData });
}}
/>Types
PdfField
Represents a field placed on a PDF document.
interface PdfField {
id: string;
type: FieldTypeEnum;
subType?: PdfFieldSubType;
x: number;
y: number;
page: number;
label: string;
width: number;
height: number;
required?: boolean;
path?: string;
recipient?: string;
description?: string;
data?: FieldDataType; // for generic fields (table/text)
formula?: StructuredFormula; // for calculated fields
formulaFormat?: CalculatedFieldFormat; // for calculated fields
}FieldTypeEnum
Enumeration of field types:
dataModel: Field connected to a data model schemaeSign: E-signature fieldfillable: User-fillable form fieldcalculated: Formula-based calculated fieldgeneric: Custom table or text block (configurable rows/cells, inline editing)
ESignFieldType
Types of e-signature fields:
signature: Signature fieldinitials: Initials fielddateSigned: Date signed fieldfullName: Full name field
PdfFieldSubType
Union of fillable, e-sign, and generic sub-types: FillableFieldType | ESignFieldType | GenericFieldType. Used for
PdfField.subType and FieldTypeOption.subType.
FillableFieldType
Types of fillable fields:
text: Text inputnumber: Number inputdate: Date pickercheckbox: Checkboxradio: Radio button
GenericFieldType
Types of generic fields:
table: Configurable table with header and body rows and editable cellstext: Text block with inline editing
FieldDataType
For generic fields, PdfField.data can be:
- GenericFieldTableDataType:
showHeader,header(height, cells),body(array of rows with height and cells) - GenericFieldTextDataType:
value(string)
Calculated Fields (formula & format)
Calculated fields use a structured formula and optional display format:
- StructuredFormula: Token list (numbers, operators
+ - * /, parentheses, field references) used for validation and safe editing. - CalculatedFieldFormat: Controls result display:
resultType(number|currency|percent), rounding mode, decimal places, thousands/decimal separators, prefix/postfix text.
Schema fields can opt in via options.useInCalculatedFields and options.useInConditionals for data model extraction
in formula builder and conditionals.
SchemaObject
JSON Schema object defining the data model structure. Supports nested objects, arrays, and various string subtypes ( string, text, html, image).
interface SchemaObject {
type: 'object';
properties: Record<string, SchemaNode>;
title?: string;
options?: SchemaFieldBaseOptions;
}RecipientInfo
Information about a recipient that can be assigned to fields.
interface RecipientInfo {
id: number;
name: string;
displayName: string;
}Usage Examples
Basic Editor with Data Model
import { PdfEditor, PdfField, SchemaObject } from '@servicetitan/dte-pdf-editor';
import { useState } from 'react';
const dataModel: SchemaObject = {
type: 'object',
properties: {
JobInformation: {
type: 'object',
properties: {
JobNumber: {
type: 'string',
title: 'Job Number',
options: { placeholder: 'Enter job number' },
},
JobName: {
type: 'string',
title: 'Job Name',
options: { placeholder: 'Enter job name' },
},
},
},
},
};
function MyEditor() {
const [fields, setFields] = useState<PdfField[]>([]);
return (
<PdfEditor
pdfUrl="https://example.com/document.pdf"
fields={fields}
onFieldsChange={setFields}
dataModel={dataModel}
/>
);
}Editor with Recipients
const recipients = [
{ id: 1, name: 'technician', displayName: 'Technician' },
{ id: 2, name: 'customer', displayName: 'Customer' },
{ id: 3, name: 'accountant', displayName: 'Accountant' },
];
<PdfEditor
pdfUrl={pdfUrl}
fields={fields}
onFieldsChange={setFields}
recipients={recipients}
/>View Mode with Data
const [data, setData] = useState({
'JobInformation.JobNumber': 'JOB-12345',
'JobInformation.JobName': 'HVAC Installation',
});
<PdfView
pdfUrl={pdfUrl}
fields={fields}
data={data}
recipients={recipients}
fillingBy={['technician']}
onDataChange={(changedData) => {
setData({ ...data, ...changedData });
}}
/>Toggle Between Edit and View Modes
function PdfEditorPage() {
const [fields, setFields] = useState<PdfField[]>([]);
const [isView, setIsView] = useState(false);
const [data, setData] = useState<Record<string, any>>({});
return (
<>
<Button onClick={() => setIsView(!isView)}>
{isView ? 'Edit' : 'View'}
</Button>
{!isView ? (
<PdfEditor
pdfUrl={pdfUrl}
fields={fields}
onFieldsChange={setFields}
dataModel={dataModel}
recipients={recipients}
/>
) : (
<PdfView
pdfUrl={pdfUrl}
fields={fields}
data={data}
recipients={recipients}
fillingBy={['technician']}
onDataChange={(changedData) => {
setData({ ...data, ...changedData });
}}
/>
)}
</>
);
}Field Operations
Adding Fields
Fields are added by dragging from the sidebar onto the PDF canvas. The sidebar displays:
- Data Model Fields (Merge Tags): Grouped by schema structure
- E-Sign Fields: Signature, initials, date signed, full name
- Fillable Fields: Text, number, date, checkbox, radio
- Calculated Fields: Formula-based fields (configure formula and format in the config panel)
- Generic Fields: Text and Table (configure table rows/cells in the config panel; inline editing in view mode)
Configuring Fields
Click on a field to open the configuration panel where you can:
- Change the label
- Adjust position and size
- Assign a recipient
- Set required status (fillable fields, except checkbox)
- Update the data model path
- For calculated fields: edit formula (with validation and autosuggest from data model + fillable fields), set result type (number/currency/percent), rounding, decimals, and separators
- For generic table fields: configure header visibility, header/body row heights, and cell values via Table configs; generic text fields support inline editing in the overlay
Moving and Resizing
- Move: Click and drag a field to reposition it
- Resize: Use the resize handles on field corners
Deleting Fields
Select a field and use the delete button in the configuration panel, or press the delete key.
Styling
The package includes default styles. To customize, you can override CSS classes:
.dte-pdf-editor: Main editor container.dte-pdf-view-container: View mode container.dte-pdf-wrapper: PDF document wrapper.dte-pdf-field-overlay: Field overlay container.dte-pdf-editor-sidebar-container: Sidebar container
Browser Support
This package requires modern browsers with support for:
- ES6+
- Canvas API
- Drag and Drop API
License
Copyright © ServiceTitan
