@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 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 management.
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** - Render PDF documents with zoom, scroll, and navigation
54
- - 📋 **Form Fields** - Support for text inputs, checkboxes, radio buttons, and signature fields
55
- - âœī¸ **Form Validation** - Built-in validation with error highlighting and auto-scroll to errors
56
- - 🎨 **Toolbar** - Customizable toolbar with zoom, page navigation, and download
57
- - 🌍 **Internationalization** - Multi-language support with custom translation overrides
58
- - 🔧 **Custom Buttons** - Extend the toolbar with custom buttons in left/center/right areas
59
- - 📝 **Signatures** - Native conditional signature support with automatic group coloring
60
- - đŸŽ¯ **Responsive** - Adaptive layout that responds to zoom and scroll events
61
-
62
- ### Toolbar Features
63
-
64
- - Zoom in/out and fit-to-width
65
- - Page navigation and page count display
66
- - PDF download capability
67
- - Custom button support with spacers
68
- - Multi-language tooltips and labels
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
- /** Initial form field values */
103
- initialValues?: InitialValuesType;
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
- /** Locale code (en, it, es, fr, de) */
112
- locale?: string;
113
-
114
- /** Custom translations for any locale */
115
- translations?: Record<string, Record<string, string>>;
116
-
117
- /** Custom toolbar buttons */
118
- toolbarButtons?: ToolbarItem[];
119
-
120
- /** Conditional signature configurations */
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
- #### Return Value
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 - should be rendered inside PdfSentinelContainer */
133
- Toolbar: React.ComponentType<{}>;
134
-
135
- /** Main PDF viewer component - should be rendered inside PdfSentinelContainer */
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
- /** Current form state */
139
- form: UsePdfFormsResult | null;
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: string | boolean;
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 value.
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
- #### UsePdfFormsResult
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
- Represents a single form field.
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: string;
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` - English (default)
284
- - `it` - Italian
285
- - `es` - Spanish
286
- - `fr` - French
287
- - `de` - German
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
- zoomOut: "Shrink",
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, form } = usePdfSentinel({
501
+ const { PdfSentinelContainer, Toolbar, PdfSentinel, api } = usePdfSentinel({
350
502
  pdf: "/document.pdf",
351
503
  name: "Form Document",
352
- initialValues: {
353
- "firstName": { value: "John" },
354
- "lastName": { value: "Doe" },
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
- if (form?.validate()) {
360
- console.log("Form is valid, values:", form.values);
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
- toolbarButtons: customButtons,
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
- zoomIn: "Zoom In",
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: 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
- ### Conditional Signatures with Groups
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 FormWithValidation() {
507
- const { PdfSentinelContainer, Toolbar, PdfSentinel, form } = usePdfSentinel({
508
- pdf: "/form.pdf",
509
- name: "Application Form",
510
- initialValues: {
511
- "email": { value: "" },
512
- "phone": { value: "" },
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
- <button onClick={handleSubmit}>Submit</button>
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 use import from `@kopjra/pdf-sentinel/preact` instead of `@kopjra/pdf-sentinel`.
560
- The React is the default one, however you can also use the import `@kopjra/pdf-sentinel/react` for clarity.
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 (same import, different package)
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 - the library automatically uses the correct variant based on your project configuration.
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