@kopjra/pdf-sentinel 1.0.1 → 1.1.2

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 ADDED
@@ -0,0 +1,607 @@
1
+ # pdf-sentinel Documentation
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.
4
+
5
+ <img src="pdf-sentinel.png" alt="logo" height="200"/>
6
+
7
+ ## Table of Contents
8
+
9
+ - [Installation](#installation)
10
+ - [Quick Start](#quick-start)
11
+ - [Features](#features)
12
+ - [API Reference](#api-reference)
13
+ - [usePdfSentinel Hook](#usepdfsentinel-hook)
14
+ - [Types](#types)
15
+ - [Toolbar Customization](#toolbar-customization)
16
+ - [Internationalization](#internationalization)
17
+ - [Examples](#examples)
18
+ - [Browser Support](#browser-support)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @kopjra/pdf-sentinel
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel";
30
+ import "@kopjra/pdf-sentinel/style.css";
31
+
32
+ function MyPdfViewer() {
33
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
34
+ pdf: "/path/to/document.pdf",
35
+ name: "My Document",
36
+ });
37
+
38
+ return (
39
+ <div style={{ height: "100vh" }}>
40
+ <PdfSentinelContainer>
41
+ <Toolbar />
42
+ <PdfSentinel />
43
+ </PdfSentinelContainer>
44
+ </div>
45
+ );
46
+ }
47
+ ```
48
+
49
+ ## Features
50
+
51
+ ### Core Functionality
52
+
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
79
+
80
+ ## API Reference
81
+
82
+ ### usePdfSentinel Hook
83
+
84
+ The main hook for initializing and controlling the PDF viewer.
85
+
86
+ #### Signature
87
+
88
+ ```typescript
89
+ function usePdfSentinel(options: PdfSentinelOptions): PdfSentinelResult
90
+ ```
91
+
92
+ #### Options
93
+
94
+ ```typescript
95
+ interface PdfSentinelOptions {
96
+ /** PDF document URL or ArrayBuffer */
97
+ pdf: string | ArrayBuffer;
98
+
99
+ /** Document name/identifier */
100
+ name: string;
101
+
102
+ /** Initial form field values */
103
+ initialValues?: InitialValuesType;
104
+
105
+ /** Hide the default toolbar */
106
+ hideToolbar?: boolean;
107
+
108
+ /** Callback when document finishes loading */
109
+ 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 */
121
+ conditionalSignatures?: ConditionalSignature[];
122
+ }
123
+ ```
124
+
125
+ #### Return Value
126
+
127
+ ```typescript
128
+ interface PdfSentinelResult {
129
+ /** 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 */
136
+ PdfSentinel: React.ComponentType<{}>;
137
+
138
+ /** Current form state */
139
+ form: UsePdfFormsResult | null;
140
+
141
+ /** Current page number (1-indexed) */
142
+ currentPage: number | null;
143
+
144
+ /** Total number of pages */
145
+ totalPages: number | null;
146
+
147
+ /** Whether PDF is loading */
148
+ loadingPdf: boolean;
149
+
150
+ /** Whether there was a loading error */
151
+ loadingError: boolean;
152
+
153
+ /** PDF engine instance */
154
+ engine: PdfEngine<Blob> | null;
155
+
156
+ /** Whether PDF has been closed */
157
+ closedPdf: boolean;
158
+
159
+ /** Helper methods */
160
+ helpers: {
161
+ zoomIn: () => void;
162
+ zoomOut: () => void;
163
+ resetZoom: () => void;
164
+ scrollToPage: (pageNumber: number) => void;
165
+ download: () => void;
166
+ };
167
+ }
168
+ ```
169
+
170
+ ### Types
171
+
172
+ #### InitialValuesType
173
+
174
+ ```typescript
175
+ type InitialValuesType = Record<string, {
176
+ value: string | boolean;
177
+ readonly?: boolean;
178
+ }>;
179
+ ```
180
+
181
+ Initial form field values. Each key is a field name.
182
+
183
+ **Example:**
184
+ ```typescript
185
+ const initialValues: InitialValuesType = {
186
+ "fullName": { value: "John Doe", readonly: false },
187
+ "signature": { value: true, readonly: true },
188
+ "agreeToTerms": { value: true },
189
+ };
190
+ ```
191
+
192
+ #### ConditionalSignature
193
+
194
+ ```typescript
195
+ interface ConditionalSignature {
196
+ index: string;
197
+ fieldNames: string[];
198
+ }
199
+ ```
200
+
201
+ Defines a group of signature fields that are conditional on the same value.
202
+
203
+ **Example:**
204
+ ```typescript
205
+ const conditionalSignatures: ConditionalSignature[] = [
206
+ { index: "1", fieldNames: ["signature1", "signature2"] },
207
+ { index: "2", fieldNames: ["signature3", "signature4"] },
208
+ ];
209
+ ```
210
+
211
+ #### UsePdfFormsResult
212
+
213
+ ```typescript
214
+ interface UsePdfFormsResult {
215
+ fields: Field[];
216
+ values: Record<string, string | boolean>;
217
+ setValue: (name: string, value: string | boolean) => void;
218
+ validate: () => { valid: boolean; errors: Record<string, string> };
219
+ reload: () => void;
220
+ errors?: Record<string, string>;
221
+ showErrors: boolean;
222
+ scrollToConditionalSignature: (index: string) => Generator;
223
+ }
224
+ ```
225
+
226
+ Represents the current form state.
227
+
228
+ #### Field
229
+
230
+ ```typescript
231
+ interface Field {
232
+ name: string;
233
+ type: "text" | "checkbox" | "signature" | "radiobutton";
234
+ pageIndex: number;
235
+ bbox: { x: number; y: number; width: number; height: number };
236
+ radioValue?: string;
237
+ readonly?: boolean;
238
+ required?: boolean;
239
+ conditionalIndex?: string;
240
+ }
241
+ ```
242
+
243
+ Represents a single form field.
244
+
245
+ ### Toolbar Customization
246
+
247
+ #### ToolbarItem
248
+
249
+ ```typescript
250
+ type ToolbarItem = ToolbarButton | ToolbarSpacer;
251
+ ```
252
+
253
+ #### ToolbarButton
254
+
255
+ ```typescript
256
+ interface ToolbarButton {
257
+ id: string;
258
+ label: string;
259
+ icon?: string; // Font Awesome icon class
260
+ title?: string; // Tooltip text
261
+ onClick: () => void;
262
+ area: "left" | "right";
263
+ disabled?: boolean;
264
+ type: "button";
265
+ }
266
+ ```
267
+
268
+ #### ToolbarSpacer
269
+
270
+ ```typescript
271
+ interface ToolbarSpacer {
272
+ id: string;
273
+ type: "spacer";
274
+ area: "left" | "right";
275
+ }
276
+ ```
277
+
278
+ ### Internationalization
279
+
280
+ #### Supported Locales
281
+
282
+ - `en` - English (default)
283
+ - `it` - Italian
284
+ - `es` - Spanish
285
+ - `fr` - French
286
+ - `de` - German
287
+
288
+ #### Default Translation Keys
289
+
290
+ ```typescript
291
+ {
292
+ zoomIn: string;
293
+ zoomOut: string;
294
+ resetZoom: string;
295
+ download: string;
296
+ pageNumberTitle: string;
297
+ signature: string;
298
+ }
299
+ ```
300
+
301
+ #### Custom Translations
302
+
303
+ Override or add translations for any locale:
304
+
305
+ ```typescript
306
+ const translations = {
307
+ en: {
308
+ zoomIn: "Enlarge",
309
+ zoomOut: "Shrink",
310
+ customKey: "Custom value",
311
+ },
312
+ it: {
313
+ customKey: "Valore personalizzato",
314
+ },
315
+ };
316
+ ```
317
+
318
+ ## Examples
319
+
320
+ ### Basic PDF Viewer
321
+
322
+ ```tsx
323
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel";
324
+
325
+ export function BasicViewer() {
326
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
327
+ pdf: "https://example.com/document.pdf",
328
+ name: "Document",
329
+ });
330
+
331
+ return (
332
+ <div style={{ height: "600px" }}>
333
+ <PdfSentinelContainer>
334
+ <Toolbar />
335
+ <PdfSentinel />
336
+ </PdfSentinelContainer>
337
+ </div>
338
+ );
339
+ }
340
+ ```
341
+
342
+ ### PDF with Form Fields
343
+
344
+ ```tsx
345
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel";
346
+
347
+ export function FormViewer() {
348
+ const { PdfSentinelContainer, Toolbar, PdfSentinel, form } = usePdfSentinel({
349
+ pdf: "/document.pdf",
350
+ name: "Form Document",
351
+ initialValues: {
352
+ "firstName": { value: "John" },
353
+ "lastName": { value: "Doe" },
354
+ },
355
+ });
356
+
357
+ const handleValidate = () => {
358
+ if (form?.validate()) {
359
+ console.log("Form is valid, values:", form.values);
360
+ }
361
+ };
362
+
363
+ return (
364
+ <>
365
+ <div style={{ height: "600px" }}>
366
+ <PdfSentinelContainer>
367
+ <Toolbar />
368
+ <PdfSentinel />
369
+ </PdfSentinelContainer>
370
+ </div>
371
+ <button onClick={handleValidate}>Validate</button>
372
+ </>
373
+ );
374
+ }
375
+ ```
376
+
377
+ ### Custom Toolbar Buttons
378
+
379
+ ```tsx
380
+ import { usePdfSentinel, ToolbarItem } from "@kopjra/pdf-sentinel";
381
+
382
+ export function CustomToolbar() {
383
+ const customButtons: ToolbarItem[] = [
384
+ {
385
+ id: "print-btn",
386
+ label: "Print",
387
+ icon: "fal fa-print",
388
+ title: "Print document",
389
+ area: "left",
390
+ type: "button",
391
+ onClick: () => window.print(),
392
+ },
393
+ {
394
+ id: "spacer",
395
+ type: "spacer",
396
+ area: "left",
397
+ },
398
+ {
399
+ id: "share-btn",
400
+ label: "Share",
401
+ icon: "fal fa-share",
402
+ title: "Share document",
403
+ area: "right",
404
+ type: "button",
405
+ onClick: () => {
406
+ console.log("Share clicked");
407
+ },
408
+ },
409
+ ];
410
+
411
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
412
+ pdf: "/document.pdf",
413
+ name: "Document",
414
+ toolbarButtons: customButtons,
415
+ });
416
+
417
+ return (
418
+ <div style={{ height: "600px" }}>
419
+ <PdfSentinelContainer>
420
+ <Toolbar />
421
+ <PdfSentinel />
422
+ </PdfSentinelContainer>
423
+ </div>
424
+ );
425
+ }
426
+ ```
427
+
428
+ ### Multi-Language Support
429
+
430
+ ```tsx
431
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel";
432
+ import { useState } from "react";
433
+
434
+ export function MultiLanguageViewer() {
435
+ const [locale, setLocale] = useState("en");
436
+
437
+ const customTranslations = {
438
+ en: {
439
+ zoomIn: "Zoom In",
440
+ download: "Download PDF",
441
+ },
442
+ it: {
443
+ zoomIn: "Ingrandisci",
444
+ download: "Scarica PDF",
445
+ },
446
+ };
447
+
448
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
449
+ pdf: "/document.pdf",
450
+ name: "Document",
451
+ locale: locale,
452
+ translations: customTranslations,
453
+ });
454
+
455
+ return (
456
+ <div>
457
+ <select onChange={(e) => setLocale(e.target.value)} value={locale}>
458
+ <option value="en">English</option>
459
+ <option value="it">Italiano</option>
460
+ </select>
461
+ <div style={{ height: "600px", marginTop: "10px" }}>
462
+ <PdfSentinelContainer>
463
+ <Toolbar />
464
+ <PdfSentinel />
465
+ </PdfSentinelContainer>
466
+ </div>
467
+ </div>
468
+ );
469
+ }
470
+ ```
471
+
472
+ ### Conditional Signatures with Groups
473
+
474
+ ```tsx
475
+ import { usePdfSentinel, ConditionalSignature } from "@kopjra/pdf-sentinel";
476
+
477
+ export function SignatureViewer() {
478
+ const conditionalSignatures: ConditionalSignature[] = [
479
+ { index: "1", fieldNames: ["authorizedBy", "authorizedDate"] },
480
+ { index: "2", fieldNames: ["approvedBy", "approvedDate"] },
481
+ ];
482
+
483
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
484
+ pdf: "/contract.pdf",
485
+ name: "Contract",
486
+ conditionalSignatures: conditionalSignatures,
487
+ });
488
+
489
+ return (
490
+ <div style={{ height: "600px" }}>
491
+ <PdfSentinelContainer>
492
+ <Toolbar />
493
+ <PdfSentinel />
494
+ </PdfSentinelContainer>
495
+ </div>
496
+ );
497
+ }
498
+ ```
499
+
500
+ ### Form Validation with Error Handling
501
+
502
+ ```tsx
503
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel";
504
+
505
+ export function FormWithValidation() {
506
+ const { PdfSentinelContainer, Toolbar, PdfSentinel, form } = usePdfSentinel({
507
+ pdf: "/form.pdf",
508
+ name: "Application Form",
509
+ initialValues: {
510
+ "email": { value: "" },
511
+ "phone": { value: "" },
512
+ },
513
+ });
514
+
515
+ const handleSubmit = () => {
516
+ if (!form) return;
517
+
518
+ // Validate required fields
519
+ let isValid = true;
520
+ const errors: Record<string, string> = {};
521
+
522
+ if (!form.values["email"]) {
523
+ errors["email"] = "Email is required";
524
+ isValid = false;
525
+ }
526
+
527
+ if (!form.values["phone"]) {
528
+ errors["phone"] = "Phone is required";
529
+ isValid = false;
530
+ }
531
+
532
+ if (!isValid) {
533
+ // Set errors and show them
534
+ Object.entries(errors).forEach(([field, error]) => {
535
+ form.setFieldError(field, error);
536
+ });
537
+ } else {
538
+ console.log("Form submitted:", form.values);
539
+ }
540
+ };
541
+
542
+ return (
543
+ <>
544
+ <div style={{ height: "600px" }}>
545
+ <PdfSentinelContainer>
546
+ <Toolbar />
547
+ <PdfSentinel />
548
+ </PdfSentinelContainer>
549
+ </div>
550
+ <button onClick={handleSubmit}>Submit</button>
551
+ </>
552
+ );
553
+ }
554
+ ```
555
+
556
+ ### Using React and Preact Variants
557
+
558
+ To use the Preact variant use import from `@kopjra/pdf-sentinel/preact` instead of `@kopjra/pdf-sentinel`.
559
+ The React is the default one, however you can also use the import `@kopjra/pdf-sentinel/react` for clarity.
560
+
561
+ ```tsx
562
+ // React variant
563
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel/react";
564
+
565
+ // Preact variant (same import, different package)
566
+ import { usePdfSentinel } from "@kopjra/pdf-sentinel/preact";
567
+
568
+ export function Viewer() {
569
+ const { PdfSentinelContainer, Toolbar, PdfSentinel } = usePdfSentinel({
570
+ pdf: "/document.pdf",
571
+ name: "Document",
572
+ });
573
+
574
+ return (
575
+ <div style={{ height: "600px" }}>
576
+ <PdfSentinelContainer>
577
+ <Toolbar />
578
+ <PdfSentinel />
579
+ </PdfSentinelContainer>
580
+ </div>
581
+ );
582
+ }
583
+ ```
584
+
585
+ The same code works for both React and Preact - the library automatically uses the correct variant based on your project configuration.
586
+
587
+ ## Browser Support
588
+
589
+ - Chrome/Chromium (latest)
590
+ - Firefox (latest)
591
+ - Safari (latest)
592
+ - Edge (latest)
593
+
594
+ ## Contributing
595
+
596
+ Contributions are welcome! Please ensure:
597
+
598
+ 1. TypeScript types are correctly defined
599
+ 2. All code follows the project style guide
600
+ 3. `npm run typecheck` passes
601
+ 4. Changes are tested in both React and Preact variants
602
+
603
+ ## License
604
+
605
+ ISC
606
+
607
+ 1