@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 +607 -0
- package/dist/preact/pdf-sentinel.js +209 -207
- package/dist/preact/pdf-sentinel.js.map +1 -1
- package/dist/react/pdf-sentinel.js +4 -2
- package/dist/react/pdf-sentinel.js.map +1 -1
- package/package.json +1 -1
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
|