@kopjra/pdf-sentinel 1.8.0 â 1.9.1
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.
- package/README.md +367 -191
- package/dist/preact/pdf-sentinel.js +142 -132
- package/dist/preact/pdf-sentinel.js.map +1 -1
- package/dist/preact/src/hooks/usePdfSentinel.d.ts +1 -1
- package/dist/react/pdf-sentinel.js +192 -182
- package/dist/react/pdf-sentinel.js.map +1 -1
- package/dist/react/src/hooks/usePdfSentinel.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pdf-sentinel Documentation
|
|
2
2
|
|
|
3
|
-
A powerful React/Preact library for viewing and interacting with PDF forms. Built on top of [embed-pdf-viewer](https://github.com/embedpdf/embed-pdf-viewer), pdf-sentinel provides a complete solution for rendering PDF documents with form fields, custom toolbars, internationalization, and signature
|
|
3
|
+
A powerful React/Preact library for viewing and interacting with PDF forms. Built on top of [embed-pdf-viewer](https://github.com/embedpdf/embed-pdf-viewer), pdf-sentinel provides a complete solution for rendering PDF documents with form fields, custom toolbars, internationalization, annotation management, and signature handling.
|
|
4
4
|
|
|
5
5
|
<img src="pdf-sentinel.png" alt="logo" height="200"/>
|
|
6
6
|
|
|
@@ -11,7 +11,11 @@ A powerful React/Preact library for viewing and interacting with PDF forms. Buil
|
|
|
11
11
|
- [Features](#features)
|
|
12
12
|
- [API Reference](#api-reference)
|
|
13
13
|
- [usePdfSentinel Hook](#usepdfsentinel-hook)
|
|
14
|
+
- [Usage Modes](#usage-modes)
|
|
15
|
+
- [Return Value](#return-value)
|
|
14
16
|
- [Types](#types)
|
|
17
|
+
- [Form API](#form-api)
|
|
18
|
+
- [Annotation API](#annotation-api)
|
|
15
19
|
- [Toolbar Customization](#toolbar-customization)
|
|
16
20
|
- [Internationalization](#internationalization)
|
|
17
21
|
- [Examples](#examples)
|
|
@@ -33,6 +37,7 @@ function MyPdfViewer() {
|
|
|
33
37
|
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
34
38
|
pdf: "/path/to/document.pdf",
|
|
35
39
|
name: "My Document",
|
|
40
|
+
usage: { type: "plain" },
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
return (
|
|
@@ -50,32 +55,22 @@ function MyPdfViewer() {
|
|
|
50
55
|
|
|
51
56
|
### Core Functionality
|
|
52
57
|
|
|
53
|
-
- đ **PDF Viewing**
|
|
54
|
-
- đ **Form Fields**
|
|
55
|
-
- âī¸ **Form Validation**
|
|
56
|
-
- đ¨ **Toolbar**
|
|
57
|
-
- đ **Internationalization**
|
|
58
|
-
- đ§ **
|
|
59
|
-
- đ **Signatures**
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- PDF
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
### Form Features
|
|
71
|
-
|
|
72
|
-
- Text field rendering with readonly support
|
|
73
|
-
- Checkbox field rendering with custom styling
|
|
74
|
-
- Radio button field rendering with custom styling
|
|
75
|
-
- Signature field rendering with conditional group coloring
|
|
76
|
-
- Form state management
|
|
77
|
-
- Field validation with error styling
|
|
78
|
-
- Initial values and readonly properties
|
|
58
|
+
- đ **PDF Viewing** â Render PDF documents with zoom, scroll, and page navigation
|
|
59
|
+
- đ **Form Fields** â Support for text inputs, checkboxes, radio buttons, and signature fields
|
|
60
|
+
- âī¸ **Form Validation** â Built-in validation with error highlighting and auto-scroll to first error
|
|
61
|
+
- đ¨ **Toolbar** â Customizable toolbar with zoom, page navigation, undo/redo, and download
|
|
62
|
+
- đ **Internationalization** â Multi-language support (en, it, es, fr, de) with custom overrides
|
|
63
|
+
- đ§ **Annotations** â Full annotation management (add, edit, remove, select) with multiple field types
|
|
64
|
+
- đ **Signatures** â Native conditional signature support with automatic group coloring
|
|
65
|
+
- âŠī¸ **Undo/Redo** â History plugin support for annotation operations
|
|
66
|
+
- đą **Pan Support** â Touch device pan support with automatic detection
|
|
67
|
+
- âŦī¸ **PDF Download** â Built-in download capability
|
|
68
|
+
|
|
69
|
+
### Three Usage Modes
|
|
70
|
+
|
|
71
|
+
1. **`plain`** â View-only PDF rendering
|
|
72
|
+
2. **`form`** â Interactive form filling with initial values, readonly fields, conditional signatures
|
|
73
|
+
3. **`annotation`** â Freeform annotation placement (email, fullname, phone, date, signature, custom)
|
|
79
74
|
|
|
80
75
|
## API Reference
|
|
81
76
|
|
|
@@ -95,74 +90,124 @@ function usePdfSentinel(options: PdfSentinelOptions): PdfSentinelResult
|
|
|
95
90
|
interface PdfSentinelOptions {
|
|
96
91
|
/** PDF document URL or ArrayBuffer */
|
|
97
92
|
pdf: string | ArrayBuffer;
|
|
98
|
-
|
|
93
|
+
|
|
99
94
|
/** Document name/identifier */
|
|
100
95
|
name: string;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
96
|
+
|
|
97
|
+
/** Locale code (en, it, es, fr, de) */
|
|
98
|
+
locale?: LocaleType;
|
|
99
|
+
|
|
100
|
+
/** Custom translations for any locale */
|
|
101
|
+
translations?: Record<string, Record<string, string>>;
|
|
102
|
+
|
|
105
103
|
/** Hide the default toolbar */
|
|
106
104
|
hideToolbar?: boolean;
|
|
107
|
-
|
|
105
|
+
|
|
108
106
|
/** Callback when document finishes loading */
|
|
109
107
|
onDocumentLoaded?: () => void;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
|
|
109
|
+
/** Usage mode â determines how the PDF is rendered and interacted with */
|
|
110
|
+
usage: PlainUsage | FormUsage | AnnotationUsage;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Usage Modes
|
|
115
|
+
|
|
116
|
+
#### PlainUsage
|
|
117
|
+
|
|
118
|
+
View-only rendering with no form or annotation interactivity.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
interface PlainUsage {
|
|
122
|
+
type: "plain";
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### FormUsage
|
|
127
|
+
|
|
128
|
+
Interactive form filling mode.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface FormUsage {
|
|
132
|
+
type: "form";
|
|
133
|
+
/** Initial form field values keyed by field name */
|
|
134
|
+
initialValues?: InitialValuesType;
|
|
135
|
+
/** Conditional signature group configurations */
|
|
121
136
|
conditionalSignatures?: ConditionalSignature[];
|
|
137
|
+
/** Field names to treat as signature fields */
|
|
138
|
+
signatureFieldNames?: string[];
|
|
122
139
|
}
|
|
123
140
|
```
|
|
124
141
|
|
|
125
|
-
####
|
|
142
|
+
#### AnnotationUsage
|
|
143
|
+
|
|
144
|
+
Freeform annotation placement mode.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface AnnotationUsage {
|
|
148
|
+
type: "annotation";
|
|
149
|
+
/** Initial annotations to place, or "readFromDocument" to read existing ones */
|
|
150
|
+
initialAnnotations?: AnnotationField[] | "readFromDocument";
|
|
151
|
+
/** Whether to assign the user's email to created annotations */
|
|
152
|
+
assignEmailToAnnotations?: boolean;
|
|
153
|
+
/** Default size for newly created annotations */
|
|
154
|
+
defaultSize?: { width: number; height: number };
|
|
155
|
+
/** Default annotation field type for new annotations */
|
|
156
|
+
defaultType?: AnnotationFieldType;
|
|
157
|
+
/** Whether to auto-select annotations on creation */
|
|
158
|
+
selectOnCreation?: boolean;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Return Value
|
|
126
163
|
|
|
127
164
|
```typescript
|
|
128
165
|
interface PdfSentinelResult {
|
|
129
166
|
/** Container component that wraps the PDF viewer and toolbar */
|
|
130
|
-
PdfSentinelContainer: React.ComponentType<{children: React.ReactNode}>;
|
|
131
|
-
|
|
132
|
-
/** Toolbar component
|
|
133
|
-
Toolbar: React.ComponentType<{
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
PdfSentinelContainer: React.ComponentType<{ children: React.ReactNode }>;
|
|
168
|
+
|
|
169
|
+
/** Toolbar component â accepts optional toolbar customization props */
|
|
170
|
+
Toolbar: React.ComponentType<{
|
|
171
|
+
toolbarButtons?: ToolbarItem[];
|
|
172
|
+
removeButtons?: DefaultButtons[];
|
|
173
|
+
}>;
|
|
174
|
+
|
|
175
|
+
/** Main PDF viewer component */
|
|
136
176
|
PdfSentinel: React.ComponentType<{}>;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
177
|
+
|
|
178
|
+
/** API access for form and annotation operations */
|
|
179
|
+
api: {
|
|
180
|
+
form: UsePdfFormsResult | null;
|
|
181
|
+
annotation: AnnotationsApi | null;
|
|
182
|
+
};
|
|
183
|
+
|
|
141
184
|
/** Current page number (1-indexed) */
|
|
142
185
|
currentPage: number | null;
|
|
143
|
-
|
|
186
|
+
|
|
144
187
|
/** Total number of pages */
|
|
145
188
|
totalPages: number | null;
|
|
146
|
-
|
|
189
|
+
|
|
147
190
|
/** Whether PDF is loading */
|
|
148
191
|
loadingPdf: boolean;
|
|
149
|
-
|
|
192
|
+
|
|
150
193
|
/** Whether there was a loading error */
|
|
151
194
|
loadingError: boolean;
|
|
152
|
-
|
|
195
|
+
|
|
196
|
+
/** The initialized PDF document buffer (available after load) */
|
|
197
|
+
initializedDocument: ArrayBuffer | null;
|
|
198
|
+
|
|
153
199
|
/** PDF engine instance */
|
|
154
200
|
engine: PdfEngine<Blob> | null;
|
|
155
|
-
|
|
201
|
+
|
|
156
202
|
/** Whether PDF has been closed */
|
|
157
203
|
closedPdf: boolean;
|
|
158
|
-
|
|
159
|
-
/** Helper methods */
|
|
204
|
+
|
|
205
|
+
/** Helper methods for controlling the viewer programmatically */
|
|
160
206
|
helpers: {
|
|
161
207
|
zoomIn: () => void;
|
|
162
208
|
zoomOut: () => void;
|
|
163
209
|
resetZoom: () => void;
|
|
164
210
|
scrollToPage: (pageNumber: number) => void;
|
|
165
|
-
scrollToPosition: (position: { x: number; y: number; behavior?: ScrollBehavior }) => void;
|
|
166
211
|
download: () => void;
|
|
167
212
|
};
|
|
168
213
|
}
|
|
@@ -174,7 +219,7 @@ interface PdfSentinelResult {
|
|
|
174
219
|
|
|
175
220
|
```typescript
|
|
176
221
|
type InitialValuesType = Record<string, {
|
|
177
|
-
value
|
|
222
|
+
value?: string | boolean;
|
|
178
223
|
readonly?: boolean;
|
|
179
224
|
}>;
|
|
180
225
|
```
|
|
@@ -199,7 +244,7 @@ interface ConditionalSignature {
|
|
|
199
244
|
}
|
|
200
245
|
```
|
|
201
246
|
|
|
202
|
-
Defines a group of signature fields that are conditional on the same
|
|
247
|
+
Defines a group of signature fields that are conditional on the same clause.
|
|
203
248
|
|
|
204
249
|
**Example:**
|
|
205
250
|
```typescript
|
|
@@ -209,23 +254,76 @@ const conditionalSignatures: ConditionalSignature[] = [
|
|
|
209
254
|
];
|
|
210
255
|
```
|
|
211
256
|
|
|
212
|
-
####
|
|
257
|
+
#### AnnotationFieldType
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
enum AnnotationFieldType {
|
|
261
|
+
EMAIL = "EMAIL",
|
|
262
|
+
FULLNAME = "FULLNAME",
|
|
263
|
+
PHONE = "PHONE",
|
|
264
|
+
DATE = "DATE",
|
|
265
|
+
SIGNATURE = "SIGNATURE",
|
|
266
|
+
CONDITIONAL_SIGNATURE = "CONDITIONAL_SIGNATURE",
|
|
267
|
+
CUSTOM = "CUSTOM",
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### AnnotationField
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
interface AnnotationField {
|
|
275
|
+
type: AnnotationFieldType;
|
|
276
|
+
/** Unique annotation identifier */
|
|
277
|
+
id: string;
|
|
278
|
+
originalId?: string;
|
|
279
|
+
/** Display name */
|
|
280
|
+
name?: string;
|
|
281
|
+
/** Description (used for conditional signatures) */
|
|
282
|
+
description?: string;
|
|
283
|
+
/** Whether the annotation is required */
|
|
284
|
+
required?: boolean;
|
|
285
|
+
/** Page number (1-based) */
|
|
286
|
+
page: number;
|
|
287
|
+
/** Bounding rectangle in pixels */
|
|
288
|
+
rect: { x: number; y: number; width: number; height: number };
|
|
289
|
+
/** Assigned email address (if applicable) */
|
|
290
|
+
assignedEmail?: string;
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### DefaultButtons
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
type DefaultButtons = "zoomIn" | "zoomOut" | "fitWidth" | "download" | "undo" | "redo";
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Used to selectively remove built-in toolbar buttons.
|
|
301
|
+
|
|
302
|
+
### Form API
|
|
303
|
+
|
|
304
|
+
Returned via `api.form` when using `FormUsage`.
|
|
213
305
|
|
|
214
306
|
```typescript
|
|
215
307
|
interface UsePdfFormsResult {
|
|
308
|
+
/** All form fields in the document */
|
|
216
309
|
fields: Field[];
|
|
310
|
+
/** Current field values keyed by field name */
|
|
217
311
|
values: Record<string, string | boolean>;
|
|
312
|
+
/** Set a field value */
|
|
218
313
|
setValue: (name: string, value: string | boolean) => void;
|
|
314
|
+
/** Validate all required fields â returns validity and errors */
|
|
219
315
|
validate: () => { valid: boolean; errors: Record<string, string> };
|
|
316
|
+
/** Reload form fields from the document */
|
|
220
317
|
reload: () => void;
|
|
318
|
+
/** Current validation errors (if any) */
|
|
221
319
|
errors?: Record<string, string>;
|
|
320
|
+
/** Whether errors should be displayed */
|
|
222
321
|
showErrors: boolean;
|
|
322
|
+
/** Scroll to a conditional signature group by index */
|
|
223
323
|
scrollToConditionalSignature: (index: string) => Generator;
|
|
224
324
|
}
|
|
225
325
|
```
|
|
226
326
|
|
|
227
|
-
Represents the current form state.
|
|
228
|
-
|
|
229
327
|
#### Field
|
|
230
328
|
|
|
231
329
|
```typescript
|
|
@@ -241,10 +339,58 @@ interface Field {
|
|
|
241
339
|
}
|
|
242
340
|
```
|
|
243
341
|
|
|
244
|
-
|
|
342
|
+
### Annotation API
|
|
343
|
+
|
|
344
|
+
Returned via `api.annotation` when using `AnnotationUsage`.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
interface AnnotationsApi {
|
|
348
|
+
/** Get all current annotations */
|
|
349
|
+
getAnnotations: () => Promise<AnnotationField[]>;
|
|
350
|
+
/** Get annotation by ID */
|
|
351
|
+
getAnnotationById: (id: string) => AnnotationField | undefined;
|
|
352
|
+
/** Add a new annotation */
|
|
353
|
+
addAnnotation: (annotation: Partial<AnnotationField>) => void;
|
|
354
|
+
/** Update an existing annotation */
|
|
355
|
+
updateAnnotation: (annotation: AnnotationField) => void;
|
|
356
|
+
/** Remove annotations */
|
|
357
|
+
removeAnnotations: (annotations: AnnotationField[]) => void;
|
|
358
|
+
/** Get annotations for a specific page (1-based) */
|
|
359
|
+
getAnnotationsByPage: (page: number) => Promise<AnnotationField[]>;
|
|
360
|
+
/** Subscribe to annotation changes â returns unsubscribe function */
|
|
361
|
+
subscribe: (listener: (event: SubscribeEvent) => void) => () => void;
|
|
362
|
+
/** Select annotations by ID (null to deselect all) */
|
|
363
|
+
selectAnnotations: (ids: string[] | null) => void;
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Subscribe events:**
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Fired on create, update, or delete
|
|
371
|
+
interface SubscribeEventModificationsOptions {
|
|
372
|
+
type: "update" | "create" | "delete";
|
|
373
|
+
annotations: AnnotationField[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Fired on selection change
|
|
377
|
+
interface SubscribeEventSelectionOptions {
|
|
378
|
+
type: "selection";
|
|
379
|
+
selectedAnnotationsId: string[] | null;
|
|
380
|
+
}
|
|
381
|
+
```
|
|
245
382
|
|
|
246
383
|
### Toolbar Customization
|
|
247
384
|
|
|
385
|
+
The `Toolbar` component accepts optional props for extending or trimming the default toolbar.
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
<Toolbar
|
|
389
|
+
toolbarButtons={customButtons}
|
|
390
|
+
removeButtons={["download", "redo"]}
|
|
391
|
+
/>
|
|
392
|
+
```
|
|
393
|
+
|
|
248
394
|
#### ToolbarItem
|
|
249
395
|
|
|
250
396
|
```typescript
|
|
@@ -256,13 +402,14 @@ type ToolbarItem = ToolbarButton | ToolbarSpacer;
|
|
|
256
402
|
```typescript
|
|
257
403
|
interface ToolbarButton {
|
|
258
404
|
id: string;
|
|
259
|
-
label
|
|
405
|
+
label?: string;
|
|
260
406
|
icon?: string; // Font Awesome icon class
|
|
261
407
|
title?: string; // Tooltip text
|
|
262
408
|
onClick: () => void;
|
|
263
409
|
area: "left" | "right";
|
|
264
410
|
disabled?: boolean;
|
|
265
411
|
type: "button";
|
|
412
|
+
primary?: boolean;
|
|
266
413
|
}
|
|
267
414
|
```
|
|
268
415
|
|
|
@@ -280,11 +427,11 @@ interface ToolbarSpacer {
|
|
|
280
427
|
|
|
281
428
|
#### Supported Locales
|
|
282
429
|
|
|
283
|
-
- `en`
|
|
284
|
-
- `it`
|
|
285
|
-
- `es`
|
|
286
|
-
- `fr`
|
|
287
|
-
- `de`
|
|
430
|
+
- `en` â English (default)
|
|
431
|
+
- `it` â Italian
|
|
432
|
+
- `es` â Spanish
|
|
433
|
+
- `fr` â French
|
|
434
|
+
- `de` â German
|
|
288
435
|
|
|
289
436
|
#### Default Translation Keys
|
|
290
437
|
|
|
@@ -296,6 +443,8 @@ interface ToolbarSpacer {
|
|
|
296
443
|
download: string;
|
|
297
444
|
pageNumberTitle: string;
|
|
298
445
|
signature: string;
|
|
446
|
+
conditional1: string; // "By accepting clause %s"
|
|
447
|
+
conditional2: string; // "your signature will be placed here"
|
|
299
448
|
}
|
|
300
449
|
```
|
|
301
450
|
|
|
@@ -307,7 +456,7 @@ Override or add translations for any locale:
|
|
|
307
456
|
const translations = {
|
|
308
457
|
en: {
|
|
309
458
|
zoomIn: "Enlarge",
|
|
310
|
-
|
|
459
|
+
download: "Download PDF",
|
|
311
460
|
customKey: "Custom value",
|
|
312
461
|
},
|
|
313
462
|
it: {
|
|
@@ -318,15 +467,17 @@ const translations = {
|
|
|
318
467
|
|
|
319
468
|
## Examples
|
|
320
469
|
|
|
321
|
-
### Basic PDF Viewer
|
|
470
|
+
### Basic PDF Viewer (Plain Mode)
|
|
322
471
|
|
|
323
472
|
```tsx
|
|
324
473
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
474
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
325
475
|
|
|
326
476
|
export function BasicViewer() {
|
|
327
477
|
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
328
478
|
pdf: "https://example.com/document.pdf",
|
|
329
479
|
name: "Document",
|
|
480
|
+
usage: { type: "plain" },
|
|
330
481
|
});
|
|
331
482
|
|
|
332
483
|
return (
|
|
@@ -344,20 +495,27 @@ export function BasicViewer() {
|
|
|
344
495
|
|
|
345
496
|
```tsx
|
|
346
497
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
498
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
347
499
|
|
|
348
500
|
export function FormViewer() {
|
|
349
|
-
const { PdfSentinelContainer, Toolbar, PdfSentinel,
|
|
501
|
+
const { PdfSentinelContainer, Toolbar, PdfSentinel, api } = usePdfSentinel({
|
|
350
502
|
pdf: "/document.pdf",
|
|
351
503
|
name: "Form Document",
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
504
|
+
usage: {
|
|
505
|
+
type: "form",
|
|
506
|
+
initialValues: {
|
|
507
|
+
"firstName": { value: "John" },
|
|
508
|
+
"lastName": { value: "Doe" },
|
|
509
|
+
},
|
|
355
510
|
},
|
|
356
511
|
});
|
|
357
512
|
|
|
358
513
|
const handleValidate = () => {
|
|
359
|
-
|
|
360
|
-
|
|
514
|
+
const result = api.form?.validate();
|
|
515
|
+
if (result?.valid) {
|
|
516
|
+
console.log("Form is valid, values:", api.form?.values);
|
|
517
|
+
} else {
|
|
518
|
+
console.log("Errors:", result?.errors);
|
|
361
519
|
}
|
|
362
520
|
};
|
|
363
521
|
|
|
@@ -375,10 +533,86 @@ export function FormViewer() {
|
|
|
375
533
|
}
|
|
376
534
|
```
|
|
377
535
|
|
|
536
|
+
### Conditional Signatures
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
540
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
541
|
+
|
|
542
|
+
export function SignatureViewer() {
|
|
543
|
+
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
544
|
+
pdf: "/contract.pdf",
|
|
545
|
+
name: "Contract",
|
|
546
|
+
usage: {
|
|
547
|
+
type: "form",
|
|
548
|
+
conditionalSignatures: [
|
|
549
|
+
{ index: "1", fieldNames: ["authorizedBy", "authorizedDate"] },
|
|
550
|
+
{ index: "2", fieldNames: ["approvedBy", "approvedDate"] },
|
|
551
|
+
],
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
return (
|
|
556
|
+
<div style={{ height: "600px" }}>
|
|
557
|
+
<PdfSentinelContainer>
|
|
558
|
+
<Toolbar />
|
|
559
|
+
<PdfSentinel />
|
|
560
|
+
</PdfSentinelContainer>
|
|
561
|
+
</div>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Annotation Mode
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
import { usePdfSentinel, AnnotationFieldType } from "@kopjra/pdf-sentinel";
|
|
570
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
571
|
+
|
|
572
|
+
export function AnnotationEditor() {
|
|
573
|
+
const { PdfSentinelContainer, Toolbar, PdfSentinel, api } = usePdfSentinel({
|
|
574
|
+
pdf: "/document.pdf",
|
|
575
|
+
name: "Annotate",
|
|
576
|
+
usage: {
|
|
577
|
+
type: "annotation",
|
|
578
|
+
initialAnnotations: "readFromDocument",
|
|
579
|
+
defaultType: AnnotationFieldType.SIGNATURE,
|
|
580
|
+
defaultSize: { width: 200, height: 60 },
|
|
581
|
+
selectOnCreation: true,
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const handleExport = async () => {
|
|
586
|
+
const annotations = await api.annotation?.getAnnotations();
|
|
587
|
+
console.log("Annotations:", annotations);
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// Subscribe to annotation changes
|
|
591
|
+
api.annotation?.subscribe((event) => {
|
|
592
|
+
if (event.type === "create") {
|
|
593
|
+
console.log("New annotations:", event.annotations);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
return (
|
|
598
|
+
<>
|
|
599
|
+
<div style={{ height: "600px" }}>
|
|
600
|
+
<PdfSentinelContainer>
|
|
601
|
+
<Toolbar />
|
|
602
|
+
<PdfSentinel />
|
|
603
|
+
</PdfSentinelContainer>
|
|
604
|
+
</div>
|
|
605
|
+
<button onClick={handleExport}>Export Annotations</button>
|
|
606
|
+
</>
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
378
611
|
### Custom Toolbar Buttons
|
|
379
612
|
|
|
380
613
|
```tsx
|
|
381
614
|
import { usePdfSentinel, ToolbarItem } from "@kopjra/pdf-sentinel";
|
|
615
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
382
616
|
|
|
383
617
|
export function CustomToolbar() {
|
|
384
618
|
const customButtons: ToolbarItem[] = [
|
|
@@ -392,7 +626,7 @@ export function CustomToolbar() {
|
|
|
392
626
|
onClick: () => window.print(),
|
|
393
627
|
},
|
|
394
628
|
{
|
|
395
|
-
id: "spacer",
|
|
629
|
+
id: "spacer-1",
|
|
396
630
|
type: "spacer",
|
|
397
631
|
area: "left",
|
|
398
632
|
},
|
|
@@ -403,22 +637,23 @@ export function CustomToolbar() {
|
|
|
403
637
|
title: "Share document",
|
|
404
638
|
area: "right",
|
|
405
639
|
type: "button",
|
|
406
|
-
onClick: () =>
|
|
407
|
-
console.log("Share clicked");
|
|
408
|
-
},
|
|
640
|
+
onClick: () => console.log("Share clicked"),
|
|
409
641
|
},
|
|
410
642
|
];
|
|
411
643
|
|
|
412
644
|
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
413
645
|
pdf: "/document.pdf",
|
|
414
646
|
name: "Document",
|
|
415
|
-
|
|
647
|
+
usage: { type: "plain" },
|
|
416
648
|
});
|
|
417
649
|
|
|
418
650
|
return (
|
|
419
651
|
<div style={{ height: "600px" }}>
|
|
420
652
|
<PdfSentinelContainer>
|
|
421
|
-
<Toolbar
|
|
653
|
+
<Toolbar
|
|
654
|
+
toolbarButtons={customButtons}
|
|
655
|
+
removeButtons={["undo", "redo"]}
|
|
656
|
+
/>
|
|
422
657
|
<PdfSentinel />
|
|
423
658
|
</PdfSentinelContainer>
|
|
424
659
|
</div>
|
|
@@ -430,34 +665,33 @@ export function CustomToolbar() {
|
|
|
430
665
|
|
|
431
666
|
```tsx
|
|
432
667
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
668
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
433
669
|
import { useState } from "react";
|
|
434
670
|
|
|
435
671
|
export function MultiLanguageViewer() {
|
|
436
|
-
const [locale, setLocale] = useState("en");
|
|
672
|
+
const [locale, setLocale] = useState<"en" | "it" | "es" | "fr" | "de">("en");
|
|
437
673
|
|
|
438
674
|
const customTranslations = {
|
|
439
|
-
en: {
|
|
440
|
-
|
|
441
|
-
download: "Download PDF",
|
|
442
|
-
},
|
|
443
|
-
it: {
|
|
444
|
-
zoomIn: "Ingrandisci",
|
|
445
|
-
download: "Scarica PDF",
|
|
446
|
-
},
|
|
675
|
+
en: { download: "Download PDF" },
|
|
676
|
+
it: { download: "Scarica PDF" },
|
|
447
677
|
};
|
|
448
678
|
|
|
449
679
|
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
450
680
|
pdf: "/document.pdf",
|
|
451
681
|
name: "Document",
|
|
452
|
-
locale
|
|
682
|
+
locale,
|
|
453
683
|
translations: customTranslations,
|
|
684
|
+
usage: { type: "plain" },
|
|
454
685
|
});
|
|
455
686
|
|
|
456
687
|
return (
|
|
457
688
|
<div>
|
|
458
|
-
<select onChange={(e) => setLocale(e.target.value)} value={locale}>
|
|
689
|
+
<select onChange={(e) => setLocale(e.target.value as any)} value={locale}>
|
|
459
690
|
<option value="en">English</option>
|
|
460
691
|
<option value="it">Italiano</option>
|
|
692
|
+
<option value="es">EspaÃąol</option>
|
|
693
|
+
<option value="fr">Français</option>
|
|
694
|
+
<option value="de">Deutsch</option>
|
|
461
695
|
</select>
|
|
462
696
|
<div style={{ height: "600px", marginTop: "10px" }}>
|
|
463
697
|
<PdfSentinelContainer>
|
|
@@ -470,120 +704,64 @@ export function MultiLanguageViewer() {
|
|
|
470
704
|
}
|
|
471
705
|
```
|
|
472
706
|
|
|
473
|
-
###
|
|
474
|
-
|
|
475
|
-
```tsx
|
|
476
|
-
import { usePdfSentinel, ConditionalSignature } from "@kopjra/pdf-sentinel";
|
|
477
|
-
|
|
478
|
-
export function SignatureViewer() {
|
|
479
|
-
const conditionalSignatures: ConditionalSignature[] = [
|
|
480
|
-
{ index: "1", fieldNames: ["authorizedBy", "authorizedDate"] },
|
|
481
|
-
{ index: "2", fieldNames: ["approvedBy", "approvedDate"] },
|
|
482
|
-
];
|
|
483
|
-
|
|
484
|
-
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
485
|
-
pdf: "/contract.pdf",
|
|
486
|
-
name: "Contract",
|
|
487
|
-
conditionalSignatures: conditionalSignatures,
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
return (
|
|
491
|
-
<div style={{ height: "600px" }}>
|
|
492
|
-
<PdfSentinelContainer>
|
|
493
|
-
<Toolbar />
|
|
494
|
-
<PdfSentinel />
|
|
495
|
-
</PdfSentinelContainer>
|
|
496
|
-
</div>
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### Form Validation with Error Handling
|
|
707
|
+
### External Toolbar (Custom UI)
|
|
502
708
|
|
|
503
709
|
```tsx
|
|
504
710
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
711
|
+
import "@kopjra/pdf-sentinel/style.css";
|
|
505
712
|
|
|
506
|
-
export function
|
|
507
|
-
const {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
713
|
+
export function ExternalToolbarViewer() {
|
|
714
|
+
const {
|
|
715
|
+
PdfSentinelContainer,
|
|
716
|
+
PdfSentinel,
|
|
717
|
+
currentPage,
|
|
718
|
+
totalPages,
|
|
719
|
+
helpers,
|
|
720
|
+
} = usePdfSentinel({
|
|
721
|
+
pdf: "/document.pdf",
|
|
722
|
+
name: "Document",
|
|
723
|
+
hideToolbar: true,
|
|
724
|
+
usage: { type: "plain" },
|
|
514
725
|
});
|
|
515
726
|
|
|
516
|
-
const handleSubmit = () => {
|
|
517
|
-
if (!form) return;
|
|
518
|
-
|
|
519
|
-
// Validate required fields
|
|
520
|
-
let isValid = true;
|
|
521
|
-
const errors: Record<string, string> = {};
|
|
522
|
-
|
|
523
|
-
if (!form.values["email"]) {
|
|
524
|
-
errors["email"] = "Email is required";
|
|
525
|
-
isValid = false;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (!form.values["phone"]) {
|
|
529
|
-
errors["phone"] = "Phone is required";
|
|
530
|
-
isValid = false;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (!isValid) {
|
|
534
|
-
// Set errors and show them
|
|
535
|
-
Object.entries(errors).forEach(([field, error]) => {
|
|
536
|
-
form.setFieldError(field, error);
|
|
537
|
-
});
|
|
538
|
-
} else {
|
|
539
|
-
console.log("Form submitted:", form.values);
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
|
|
543
727
|
return (
|
|
544
|
-
|
|
728
|
+
<div>
|
|
729
|
+
{/* Custom toolbar outside the container */}
|
|
730
|
+
<div style={{ display: "flex", gap: "8px", padding: "8px" }}>
|
|
731
|
+
<button onClick={helpers.zoomIn}>+</button>
|
|
732
|
+
<button onClick={helpers.zoomOut}>â</button>
|
|
733
|
+
<button onClick={helpers.resetZoom}>Fit</button>
|
|
734
|
+
<span>{currentPage} / {totalPages}</span>
|
|
735
|
+
<button onClick={helpers.download}>Download</button>
|
|
736
|
+
</div>
|
|
737
|
+
|
|
545
738
|
<div style={{ height: "600px" }}>
|
|
546
739
|
<PdfSentinelContainer>
|
|
547
|
-
<Toolbar />
|
|
548
740
|
<PdfSentinel />
|
|
549
741
|
</PdfSentinelContainer>
|
|
550
742
|
</div>
|
|
551
|
-
|
|
552
|
-
</>
|
|
743
|
+
</div>
|
|
553
744
|
);
|
|
554
745
|
}
|
|
555
746
|
```
|
|
556
747
|
|
|
557
748
|
### Using React and Preact Variants
|
|
558
749
|
|
|
559
|
-
To use the Preact variant
|
|
560
|
-
The React is the default
|
|
750
|
+
To use the Preact variant, import from `@kopjra/pdf-sentinel/preact` instead of `@kopjra/pdf-sentinel`.
|
|
751
|
+
The React variant is the default; you can also use `@kopjra/pdf-sentinel/react` for clarity.
|
|
561
752
|
|
|
562
753
|
```tsx
|
|
563
|
-
// React variant
|
|
754
|
+
// React variant (default)
|
|
755
|
+
import { usePdfSentinel } from "@kopjra/pdf-sentinel";
|
|
756
|
+
|
|
757
|
+
// React variant (explicit)
|
|
564
758
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel/react";
|
|
565
759
|
|
|
566
|
-
// Preact variant
|
|
760
|
+
// Preact variant
|
|
567
761
|
import { usePdfSentinel } from "@kopjra/pdf-sentinel/preact";
|
|
568
|
-
|
|
569
|
-
export function Viewer() {
|
|
570
|
-
const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
|
|
571
|
-
pdf: "/document.pdf",
|
|
572
|
-
name: "Document",
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
return (
|
|
576
|
-
<div style={{ height: "600px" }}>
|
|
577
|
-
<PdfSentinelContainer>
|
|
578
|
-
<Toolbar />
|
|
579
|
-
<PdfSentinel />
|
|
580
|
-
</PdfSentinelContainer>
|
|
581
|
-
</div>
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
762
|
```
|
|
585
763
|
|
|
586
|
-
The same code works for both React and Preact
|
|
764
|
+
The same code works for both React and Preact â the library automatically uses the correct framework variant based on the import path.
|
|
587
765
|
|
|
588
766
|
## Browser Support
|
|
589
767
|
|
|
@@ -604,5 +782,3 @@ Contributions are welcome! Please ensure:
|
|
|
604
782
|
## License
|
|
605
783
|
|
|
606
784
|
ISC
|
|
607
|
-
|
|
608
|
-
2
|