@drvillo/react-browser-e-signing 0.1.3 → 0.3.0
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/INTEGRATION_GUIDELINES.md +471 -0
- package/README.md +44 -12
- package/dist/index.d.ts +38 -3
- package/dist/index.js +138 -3
- package/dist/index.js.map +1 -1
- package/dist/pdf.worker.min.mjs +21 -0
- package/dist/styles.css +41 -0
- package/dist/worker.d.mts +1 -0
- package/dist/worker.mjs +3 -0
- package/package.json +25 -17
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# INTEGRATION_GUIDELINES
|
|
2
|
+
|
|
3
|
+
## 1) Metadata
|
|
4
|
+
|
|
5
|
+
- packageName: `@drvillo/react-browser-e-signing`
|
|
6
|
+
- runtime: browser-only (React)
|
|
7
|
+
- peerDependencies: `react`, `react-dom`
|
|
8
|
+
- requiredCssImport: `@drvillo/react-browser-e-signing/styles.css`
|
|
9
|
+
- recommendedWorkerSetup:
|
|
10
|
+
- import `configure` from package
|
|
11
|
+
- import `getPdfWorkerSrc` from `@drvillo/react-browser-e-signing/worker`
|
|
12
|
+
- call `configure({ pdfWorkerSrc: getPdfWorkerSrc() })`
|
|
13
|
+
|
|
14
|
+
## 2) Full Public API (post-change)
|
|
15
|
+
|
|
16
|
+
### 2.1 Components
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface PdfViewerProps {
|
|
20
|
+
pdfData: ArrayBuffer | null
|
|
21
|
+
numPages: number
|
|
22
|
+
scale: number
|
|
23
|
+
onScaleChange: (nextScale: number) => void
|
|
24
|
+
onDocumentLoadSuccess: (numPages: number) => void
|
|
25
|
+
onPageDimensions: (input: { pageIndex: number; widthPt: number; heightPt: number }) => void
|
|
26
|
+
renderOverlay?: (pageIndex: number) => React.ReactNode
|
|
27
|
+
renderToolbarContent?: () => React.ReactNode
|
|
28
|
+
className?: string
|
|
29
|
+
workerSrc?: string
|
|
30
|
+
pageMode?: 'scroll' | 'single'
|
|
31
|
+
currentPageIndex?: number
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
interface PdfPageNavigatorProps {
|
|
37
|
+
currentPageIndex: number
|
|
38
|
+
numPages: number
|
|
39
|
+
onPageChange: (pageIndex: number) => void
|
|
40
|
+
className?: string
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
interface FieldOverlayProps {
|
|
46
|
+
pageIndex: number
|
|
47
|
+
fields: FieldPlacement[]
|
|
48
|
+
selectedFieldType: FieldType | null
|
|
49
|
+
onAddField: (input: { pageIndex: number; type: FieldType; xPercent: number; yPercent: number }) => void
|
|
50
|
+
onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void
|
|
51
|
+
onRemoveField: (fieldId: string) => void
|
|
52
|
+
preview: SignatureFieldPreview
|
|
53
|
+
className?: string
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
interface SignatureFieldProps {
|
|
59
|
+
field: FieldPlacement
|
|
60
|
+
onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void
|
|
61
|
+
onRemoveField: (fieldId: string) => void
|
|
62
|
+
preview: SignatureFieldPreview
|
|
63
|
+
className?: string
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface FieldPaletteProps {
|
|
69
|
+
selectedFieldType: FieldType | null
|
|
70
|
+
onSelectFieldType: (fieldType: FieldType | null) => void
|
|
71
|
+
className?: string
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
interface SignerDetailsPanelProps {
|
|
77
|
+
signerInfo: SignerInfo
|
|
78
|
+
onSignerInfoChange: (nextSignerInfo: SignerInfo) => void
|
|
79
|
+
className?: string
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
interface SignaturePreviewProps {
|
|
85
|
+
signerName: string
|
|
86
|
+
style: SignatureStyle
|
|
87
|
+
signatureDataUrl: string | null
|
|
88
|
+
isRendering: boolean
|
|
89
|
+
onStyleChange: (nextStyle: SignatureStyle) => void
|
|
90
|
+
className?: string
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
interface SignaturePadProps {
|
|
96
|
+
onDrawn: (signatureDataUrl: string) => void
|
|
97
|
+
className?: string
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
interface SigningCompleteProps {
|
|
103
|
+
signerName: string
|
|
104
|
+
fieldCount: number
|
|
105
|
+
signedAt: string
|
|
106
|
+
documentHash: string
|
|
107
|
+
downloadUrl: string
|
|
108
|
+
fileName?: string
|
|
109
|
+
onReset: () => void
|
|
110
|
+
className?: string
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2.2 Hooks
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
type PdfInput = File | Blob | ArrayBuffer | Uint8Array | null
|
|
118
|
+
|
|
119
|
+
function usePdfDocument(pdfInput: PdfInput): {
|
|
120
|
+
pdfData: ArrayBuffer | null
|
|
121
|
+
numPages: number
|
|
122
|
+
scale: number
|
|
123
|
+
setScale: React.Dispatch<React.SetStateAction<number>>
|
|
124
|
+
pageDimensions: PdfPageDimensions[]
|
|
125
|
+
setPageDimension: (pageIndex: number, widthPt: number, heightPt: number) => void
|
|
126
|
+
handleDocumentLoadSuccess: (loadedPages: number) => void
|
|
127
|
+
hasPdf: boolean
|
|
128
|
+
isLoading: boolean
|
|
129
|
+
errorMessage: string | null
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
function useFieldPlacement(options?: {
|
|
135
|
+
defaultWidthPercent?: number
|
|
136
|
+
defaultHeightPercent?: number
|
|
137
|
+
}): {
|
|
138
|
+
fields: FieldPlacement[]
|
|
139
|
+
addField: (input: { pageIndex: number; type: FieldType; xPercent: number; yPercent: number }) => FieldPlacement
|
|
140
|
+
updateField: (id: string, partial: Partial<FieldPlacement>) => void
|
|
141
|
+
removeField: (id: string) => void
|
|
142
|
+
clearFields: () => void
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
function useSignatureRenderer(input: {
|
|
148
|
+
signerName: string
|
|
149
|
+
style: SignatureStyle
|
|
150
|
+
}): {
|
|
151
|
+
signatureDataUrl: string | null
|
|
152
|
+
isRendering: boolean
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
interface UsePdfPageVisibilityOptions {
|
|
158
|
+
containerRef: React.RefObject<HTMLElement | null>
|
|
159
|
+
numPages: number
|
|
160
|
+
threshold?: number
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface UsePdfPageVisibilityReturn {
|
|
164
|
+
currentPageIndex: number
|
|
165
|
+
visiblePageIndices: number[]
|
|
166
|
+
scrollToPage: (pageIndex: number) => void
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function usePdfPageVisibility(options: UsePdfPageVisibilityOptions): UsePdfPageVisibilityReturn
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2.3 Types
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
type FieldType = 'signature' | 'fullName' | 'title' | 'date'
|
|
176
|
+
|
|
177
|
+
interface FieldPlacement {
|
|
178
|
+
id: string
|
|
179
|
+
type: FieldType
|
|
180
|
+
pageIndex: number
|
|
181
|
+
xPercent: number
|
|
182
|
+
yPercent: number
|
|
183
|
+
widthPercent: number
|
|
184
|
+
heightPercent: number
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface SignerInfo {
|
|
188
|
+
firstName: string
|
|
189
|
+
lastName: string
|
|
190
|
+
title: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type SignatureStyle =
|
|
194
|
+
| { mode: 'typed'; fontFamily: string }
|
|
195
|
+
| { mode: 'drawn'; dataUrl: string }
|
|
196
|
+
|
|
197
|
+
interface SigningResult {
|
|
198
|
+
pdfBytes: Uint8Array
|
|
199
|
+
sha256: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface PdfPageDimensions {
|
|
203
|
+
pageIndex: number
|
|
204
|
+
widthPt: number
|
|
205
|
+
heightPt: number
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface SignatureFieldPreview {
|
|
209
|
+
signatureDataUrl: string | null
|
|
210
|
+
fullName: string
|
|
211
|
+
title: string
|
|
212
|
+
dateText: string
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 2.4 Utilities and config
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
function configure(options: {
|
|
220
|
+
pdfWorkerSrc?: string
|
|
221
|
+
fontMode?: 'network' | 'local-only'
|
|
222
|
+
fontUrlResolver?: (fontFamily: string) => string | null
|
|
223
|
+
onWarning?: (warning: { code: string; message: string }) => void
|
|
224
|
+
}): void
|
|
225
|
+
|
|
226
|
+
function modifyPdf(input: {
|
|
227
|
+
pdfBytes: Uint8Array
|
|
228
|
+
fields: FieldPlacement[]
|
|
229
|
+
signer: SignerInfo
|
|
230
|
+
signatureDataUrl: string
|
|
231
|
+
pageDimensions: PdfPageDimensions[]
|
|
232
|
+
dateText?: string
|
|
233
|
+
}): Promise<Uint8Array>
|
|
234
|
+
|
|
235
|
+
function mapToPoints(field: FieldPlacement, page: PdfPageDimensions): {
|
|
236
|
+
x: number
|
|
237
|
+
y: number
|
|
238
|
+
width: number
|
|
239
|
+
height: number
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function mapFromPoints(
|
|
243
|
+
rect: { x: number; y: number; width: number; height: number },
|
|
244
|
+
page: PdfPageDimensions
|
|
245
|
+
): Pick<FieldPlacement, 'xPercent' | 'yPercent' | 'widthPercent' | 'heightPercent'>
|
|
246
|
+
|
|
247
|
+
function loadSignatureFont(fontFamily: string): Promise<void>
|
|
248
|
+
function sha256(data: Uint8Array): Promise<string>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 2.5 Constants
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const defaults: {
|
|
255
|
+
SIGNATURE_FONTS: readonly string[]
|
|
256
|
+
DEFAULT_FIELD_WIDTH_PERCENT: 25
|
|
257
|
+
DEFAULT_FIELD_HEIGHT_PERCENT: 5
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const SLOTS = {
|
|
263
|
+
pdfViewer: 'pdf-viewer',
|
|
264
|
+
pdfViewerEmpty: 'pdf-viewer-empty',
|
|
265
|
+
pdfViewerToolbar: 'pdf-viewer-toolbar',
|
|
266
|
+
pdfViewerToolbarContent: 'pdf-viewer-toolbar-content',
|
|
267
|
+
pdfViewerPageCount: 'pdf-viewer-page-count',
|
|
268
|
+
pdfViewerZoom: 'pdf-viewer-zoom',
|
|
269
|
+
pdfViewerZoomButton: 'pdf-viewer-zoom-button',
|
|
270
|
+
pdfViewerZoomValue: 'pdf-viewer-zoom-value',
|
|
271
|
+
pdfViewerPages: 'pdf-viewer-pages',
|
|
272
|
+
pdfViewerPage: 'pdf-viewer-page',
|
|
273
|
+
pdfViewerLoading: 'pdf-viewer-loading',
|
|
274
|
+
pdfViewerError: 'pdf-viewer-error',
|
|
275
|
+
pdfPageNavigator: 'pdf-page-navigator',
|
|
276
|
+
pdfPageNavigatorButton: 'pdf-page-navigator-button',
|
|
277
|
+
pdfPageNavigatorLabel: 'pdf-page-navigator-label',
|
|
278
|
+
fieldOverlay: 'field-overlay',
|
|
279
|
+
signatureField: 'signature-field',
|
|
280
|
+
signatureFieldContent: 'signature-field-content',
|
|
281
|
+
signatureFieldLabel: 'signature-field-label',
|
|
282
|
+
signatureFieldPreview: 'signature-field-preview',
|
|
283
|
+
signatureFieldPreviewImage: 'signature-field-preview-image',
|
|
284
|
+
signatureFieldPreviewText: 'signature-field-preview-text',
|
|
285
|
+
signatureFieldRemove: 'signature-field-remove',
|
|
286
|
+
signatureFieldResize: 'signature-field-resize',
|
|
287
|
+
fieldPalette: 'field-palette',
|
|
288
|
+
fieldPaletteButton: 'field-palette-button',
|
|
289
|
+
signerPanel: 'signer-panel',
|
|
290
|
+
signerPanelHeading: 'signer-panel-heading',
|
|
291
|
+
signerPanelLabel: 'signer-panel-label',
|
|
292
|
+
signerPanelInput: 'signer-panel-input',
|
|
293
|
+
signaturePreview: 'signature-preview',
|
|
294
|
+
signaturePreviewHeading: 'signature-preview-heading',
|
|
295
|
+
signaturePreviewModeToggle: 'signature-preview-mode-toggle',
|
|
296
|
+
signaturePreviewModeButton: 'signature-preview-mode-button',
|
|
297
|
+
signaturePreviewFontLabel: 'signature-preview-font-label',
|
|
298
|
+
signaturePreviewFontSelect: 'signature-preview-font-select',
|
|
299
|
+
signaturePreviewDisplay: 'signature-preview-display',
|
|
300
|
+
signaturePreviewImage: 'signature-preview-image',
|
|
301
|
+
signaturePreviewPlaceholder: 'signature-preview-placeholder',
|
|
302
|
+
signaturePad: 'signature-pad',
|
|
303
|
+
signaturePadCanvas: 'signature-pad-canvas',
|
|
304
|
+
signaturePadActions: 'signature-pad-actions',
|
|
305
|
+
signaturePadClear: 'signature-pad-clear',
|
|
306
|
+
signingComplete: 'signing-complete',
|
|
307
|
+
signingCompleteHeading: 'signing-complete-heading',
|
|
308
|
+
signingCompleteDetails: 'signing-complete-details',
|
|
309
|
+
signingCompleteHash: 'signing-complete-hash',
|
|
310
|
+
signingCompleteHashLabel: 'signing-complete-hash-label',
|
|
311
|
+
signingCompleteHashValue: 'signing-complete-hash-value',
|
|
312
|
+
signingCompleteActions: 'signing-complete-actions',
|
|
313
|
+
signingCompleteDownload: 'signing-complete-download',
|
|
314
|
+
signingCompleteReset: 'signing-complete-reset',
|
|
315
|
+
} as const
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## 3) Multi-Page behavior (post-change)
|
|
319
|
+
|
|
320
|
+
- `pageMode='scroll'`:
|
|
321
|
+
- all pages rendered in vertical stack
|
|
322
|
+
- best for desktop reading/review
|
|
323
|
+
- use `usePdfPageVisibility` + `PdfPageNavigator` for quick jumps
|
|
324
|
+
- `pageMode='single'`:
|
|
325
|
+
- only one page rendered at a time
|
|
326
|
+
- controlled by `currentPageIndex`
|
|
327
|
+
- best for mobile/focused workflows
|
|
328
|
+
- `renderToolbarContent`:
|
|
329
|
+
- add `FieldPalette`, `PdfPageNavigator`, or custom controls directly in viewer toolbar
|
|
330
|
+
- avoids losing field placement controls while moving across pages
|
|
331
|
+
|
|
332
|
+
## 4) Required wiring for page-aware UI
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
const viewerContainerRef = useRef<HTMLDivElement | null>(null)
|
|
336
|
+
const { currentPageIndex, scrollToPage } = usePdfPageVisibility({
|
|
337
|
+
containerRef: viewerContainerRef,
|
|
338
|
+
numPages,
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
<div ref={viewerContainerRef}>
|
|
344
|
+
<PdfViewer
|
|
345
|
+
pdfData={pdfData}
|
|
346
|
+
numPages={numPages}
|
|
347
|
+
scale={scale}
|
|
348
|
+
onScaleChange={setScale}
|
|
349
|
+
onDocumentLoadSuccess={handleDocumentLoadSuccess}
|
|
350
|
+
onPageDimensions={({ pageIndex, widthPt, heightPt }) => setPageDimension(pageIndex, widthPt, heightPt)}
|
|
351
|
+
pageMode="scroll"
|
|
352
|
+
currentPageIndex={currentPageIndex}
|
|
353
|
+
renderToolbarContent={() => (
|
|
354
|
+
<>
|
|
355
|
+
<PdfPageNavigator
|
|
356
|
+
currentPageIndex={currentPageIndex}
|
|
357
|
+
numPages={numPages}
|
|
358
|
+
onPageChange={scrollToPage}
|
|
359
|
+
/>
|
|
360
|
+
<FieldPalette selectedFieldType={selectedFieldType} onSelectFieldType={setSelectedFieldType} />
|
|
361
|
+
</>
|
|
362
|
+
)}
|
|
363
|
+
renderOverlay={(pageIndex) => (
|
|
364
|
+
<FieldOverlay
|
|
365
|
+
pageIndex={pageIndex}
|
|
366
|
+
fields={fields}
|
|
367
|
+
selectedFieldType={selectedFieldType}
|
|
368
|
+
onAddField={handleAddField}
|
|
369
|
+
onUpdateField={updateField}
|
|
370
|
+
onRemoveField={removeField}
|
|
371
|
+
preview={fieldPreview}
|
|
372
|
+
/>
|
|
373
|
+
)}
|
|
374
|
+
/>
|
|
375
|
+
</div>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## 5) Composition patterns
|
|
379
|
+
|
|
380
|
+
### PatternA_desktopStickySidebar
|
|
381
|
+
|
|
382
|
+
- use `pageMode='scroll'`
|
|
383
|
+
- put `PdfPageNavigator + FieldPalette` in `renderToolbarContent`
|
|
384
|
+
- keep signer controls sticky:
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
<aside className="space-y-4 lg:sticky lg:top-6 lg:self-start">
|
|
388
|
+
<SignerDetailsPanel signerInfo={signerInfo} onSignerInfoChange={setSignerInfo} />
|
|
389
|
+
<SignaturePreview
|
|
390
|
+
signerName={displayName}
|
|
391
|
+
style={signatureStyle}
|
|
392
|
+
signatureDataUrl={signatureDataUrl}
|
|
393
|
+
isRendering={isRendering}
|
|
394
|
+
onStyleChange={setSignatureStyle}
|
|
395
|
+
/>
|
|
396
|
+
</aside>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### PatternB_mobileSinglePage
|
|
400
|
+
|
|
401
|
+
- use `pageMode='single'`
|
|
402
|
+
- keep local `singlePageIndex` state
|
|
403
|
+
- use `PdfPageNavigator.onPageChange -> setSinglePageIndex`
|
|
404
|
+
|
|
405
|
+
### PatternC_responsiveSwitching
|
|
406
|
+
|
|
407
|
+
- desktop: `scroll`
|
|
408
|
+
- narrow viewport: `single`
|
|
409
|
+
- keep one toolbar composition in both modes
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
const [isMobile, setIsMobile] = useState(false)
|
|
413
|
+
const [singlePageIndex, setSinglePageIndex] = useState(0)
|
|
414
|
+
const isSinglePageMode = isMobile
|
|
415
|
+
|
|
416
|
+
const activePageIndex = isSinglePageMode ? singlePageIndex : currentPageIndex
|
|
417
|
+
const handlePageChange = (pageIndex: number) =>
|
|
418
|
+
isSinglePageMode ? setSinglePageIndex(pageIndex) : scrollToPage(pageIndex)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### PatternD_mobileWizard
|
|
422
|
+
|
|
423
|
+
- step1: upload + signer details
|
|
424
|
+
- step2: `PdfViewer(pageMode='single')` + `FieldPalette` + `PdfPageNavigator`
|
|
425
|
+
- step3: review + sign
|
|
426
|
+
|
|
427
|
+
### PatternE_embeddedMinimal
|
|
428
|
+
|
|
429
|
+
- only `PdfViewer` + overlays + toolbar controls
|
|
430
|
+
- no sidebar
|
|
431
|
+
- recommended for constrained panes/modals
|
|
432
|
+
|
|
433
|
+
## 6) Field placement data flow
|
|
434
|
+
|
|
435
|
+
1. user toggles a field in `FieldPalette`
|
|
436
|
+
2. `FieldOverlay` enters placing mode (`data-state='placing'`, crosshair cursor)
|
|
437
|
+
3. user clicks overlay on target page
|
|
438
|
+
4. overlay computes `xPercent/yPercent` from click position
|
|
439
|
+
5. `useFieldPlacement.addField` adds a `FieldPlacement` with `pageIndex`
|
|
440
|
+
6. `SignatureField` supports drag move + corner resize
|
|
441
|
+
7. `modifyPdf` maps percentages to PDF points using `mapToPoints` for each page
|
|
442
|
+
|
|
443
|
+
## 7) Decision tree (agent-optimized)
|
|
444
|
+
|
|
445
|
+
```txt
|
|
446
|
+
IF desktop-first:
|
|
447
|
+
use PatternA_desktopStickySidebar
|
|
448
|
+
|
|
449
|
+
IF mobile-first:
|
|
450
|
+
use PatternB_mobileSinglePage
|
|
451
|
+
|
|
452
|
+
IF responsive:
|
|
453
|
+
use PatternC_responsiveSwitching
|
|
454
|
+
|
|
455
|
+
IF multi-step signing flow:
|
|
456
|
+
use PatternD_mobileWizard
|
|
457
|
+
|
|
458
|
+
IF embedded in modal or constrained panel:
|
|
459
|
+
use PatternE_embeddedMinimal
|
|
460
|
+
|
|
461
|
+
IF documents are usually 1-2 pages:
|
|
462
|
+
default scroll mode is enough
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## 8) Consumer responsibilities
|
|
466
|
+
|
|
467
|
+
- manage layout (sticky/fixed/sidebar/wizard)
|
|
468
|
+
- manage app-level validation and confirmation UX
|
|
469
|
+
- manage final download flow and success state
|
|
470
|
+
- choose mobile/desktop strategy (`scroll` vs `single`)
|
|
471
|
+
- style components via default `styles.css` or custom `[data-slot]` selectors
|
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ pnpm demo
|
|
|
62
62
|
### Configuration
|
|
63
63
|
|
|
64
64
|
- `configure(options)` — PDF worker URL, signature font mode (`network` | `local-only`), optional `fontUrlResolver`, optional `onWarning` callback
|
|
65
|
+
- `getPdfWorkerSrc()` — from `@drvillo/react-browser-e-signing/worker`; returns a bundler-resolved URL for the packaged `pdf.worker.min.mjs` (recommended default for `pdfWorkerSrc`)
|
|
65
66
|
- Types: `ESigningConfig`, `SignatureFontWarning`
|
|
66
67
|
|
|
67
68
|
### Types
|
|
@@ -126,9 +127,11 @@ pnpm test
|
|
|
126
127
|
pnpm typecheck
|
|
127
128
|
```
|
|
128
129
|
|
|
130
|
+
After a fresh clone, run **`pnpm build` once** so `dist/` includes the bundled worker, `worker.mjs`, and types (the copy step runs after tsup’s type emit).
|
|
131
|
+
|
|
129
132
|
## Notes
|
|
130
133
|
|
|
131
|
-
- **PDF worker:** the package does **not** inject a
|
|
134
|
+
- **PDF worker:** PDF.js **must** load its worker from a URL. This package ships `pdf.worker.min.mjs` built from the same `pdfjs-dist` version as `react-pdf` (see **Bundled PDF.js worker** below). The package does **not** inject a CDN URL by default; call `configure({ pdfWorkerSrc: getPdfWorkerSrc() })` or set `workerSrc` on `PdfViewer` so the worker loads (recommended for production). See **Production hardening** below.
|
|
132
135
|
- Browser test config skips execution when Playwright Chromium is not available in the environment.
|
|
133
136
|
- Demo theme switcher (`default` / `custom`) shows how a container app can fully re-theme the same components.
|
|
134
137
|
|
|
@@ -136,30 +139,59 @@ pnpm typecheck
|
|
|
136
139
|
|
|
137
140
|
Runtime calls to external CDNs (PDF.js worker, Google Fonts) often fail in real apps: **CSP** (`worker-src`, `connect-src`, `font-src`), ad blockers, corporate proxies, or offline users. They also add noisy console errors (`Failed to fetch`) even when the rest of the UI works. This library defaults to **no injected worker URL** and lets you control loading explicitly.
|
|
138
141
|
|
|
139
|
-
###
|
|
142
|
+
### Bundled PDF.js worker (recommended)
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
The npm package includes `pdf.worker.min.mjs` at the same path layout as `react-pdf`’s `pdfjs-dist` dependency, so you do **not** need unpkg or a manual copy script for the default flow.
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Then configure once (e.g. in your app entry or root layout client component):
|
|
146
|
+
Configure once (e.g. app entry or a client-only module):
|
|
148
147
|
|
|
149
148
|
```tsx
|
|
150
149
|
import { configure } from '@drvillo/react-browser-e-signing'
|
|
150
|
+
import { getPdfWorkerSrc } from '@drvillo/react-browser-e-signing/worker'
|
|
151
151
|
|
|
152
|
-
configure({ pdfWorkerSrc:
|
|
152
|
+
configure({ pdfWorkerSrc: getPdfWorkerSrc() })
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
`getPdfWorkerSrc()` uses `new URL('./pdf.worker.min.mjs', import.meta.url)` so Vite, webpack 5, and similar bundlers emit the worker as an asset and rewrite the URL. **Do not** point at a third-party CDN in production if you can avoid it.
|
|
156
|
+
|
|
155
157
|
Or per viewer:
|
|
156
158
|
|
|
157
159
|
```tsx
|
|
158
|
-
|
|
160
|
+
import { getPdfWorkerSrc } from '@drvillo/react-browser-e-signing/worker'
|
|
161
|
+
|
|
162
|
+
<PdfViewer workerSrc={getPdfWorkerSrc()} {...pdfViewerProps} />
|
|
159
163
|
```
|
|
160
164
|
|
|
161
165
|
`workerSrc` on `PdfViewer` overrides `configure({ pdfWorkerSrc })`.
|
|
162
166
|
|
|
167
|
+
**Advanced:** import the raw file (e.g. Vite):
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import workerUrl from '@drvillo/react-browser-e-signing/pdf.worker.min.mjs?url'
|
|
171
|
+
|
|
172
|
+
configure({ pdfWorkerSrc: workerUrl })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Versioning:** when this library upgrades `react-pdf` / `pdfjs-dist`, the published worker file is regenerated from that `pdfjs-dist` version. Keep your app on the published package version you intend; do not mix a different `pdfjs-dist` worker binary with another API version.
|
|
176
|
+
|
|
177
|
+
**SSR / Next.js App Router:** PDF.js runs in the browser. Set `pdfWorkerSrc` only on the client — e.g. in `useEffect`, or in a file loaded via `dynamic(..., { ssr: false })`, or in a client-only entry — so `getPdfWorkerSrc()` and worker loading are not evaluated during SSR.
|
|
178
|
+
|
|
179
|
+
### Manual self-host (fallback)
|
|
180
|
+
|
|
181
|
+
If you prefer serving the worker from your own `public/` folder, copy the file that matches the `pdfjs-dist` version used by `react-pdf` (see `node_modules/react-pdf/package.json`):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Then:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { configure } from '@drvillo/react-browser-e-signing'
|
|
191
|
+
|
|
192
|
+
configure({ pdfWorkerSrc: '/pdf.worker.min.mjs' })
|
|
193
|
+
```
|
|
194
|
+
|
|
163
195
|
### Typed signatures: local-only fonts (no network)
|
|
164
196
|
|
|
165
197
|
Skip all font fetches (handwriting fonts won’t load from Google; the browser uses whatever faces are already available, with sensible fallback):
|
|
@@ -194,13 +226,13 @@ Warnings are non-throwing; signing flow should remain usable.
|
|
|
194
226
|
|
|
195
227
|
### CSP-oriented example
|
|
196
228
|
|
|
197
|
-
If everything is same-origin:
|
|
229
|
+
If everything is same-origin (including the worker URL after your bundler emits it):
|
|
198
230
|
|
|
199
231
|
```
|
|
200
232
|
Content-Security-Policy: worker-src 'self'; script-src 'self'; connect-src 'self'; font-src 'self';
|
|
201
233
|
```
|
|
202
234
|
|
|
203
|
-
Adjust `connect-src` / `font-src` if you still use Google Fonts or
|
|
235
|
+
A self-hosted worker loaded from the same origin as your app typically satisfies `worker-src 'self'`. Adjust `connect-src` / `font-src` if you still use Google Fonts or load scripts from elsewhere.
|
|
204
236
|
|
|
205
237
|
### Migration from v0.1.2 and earlier
|
|
206
238
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
|
-
import { ReactNode } from 'react';
|
|
3
|
+
import { ReactNode, RefObject } from 'react';
|
|
4
4
|
|
|
5
5
|
declare const SIGNATURE_FONTS: readonly ["Caveat", "Homemade Apple", "Reenie Beanie", "Mr Dafoe", "Pacifico", "Qwitcher Grypen"];
|
|
6
6
|
declare function loadSignatureFont(fontFamily: string): Promise<void>;
|
|
@@ -33,11 +33,25 @@ interface PdfViewerProps {
|
|
|
33
33
|
heightPt: number;
|
|
34
34
|
}) => void;
|
|
35
35
|
renderOverlay?: (pageIndex: number) => ReactNode;
|
|
36
|
+
/** Render extra controls in the toolbar between page count and zoom controls. */
|
|
37
|
+
renderToolbarContent?: () => ReactNode;
|
|
36
38
|
className?: string;
|
|
37
39
|
/** PDF.js worker script URL. Overrides `configure({ pdfWorkerSrc })`. When neither is set, worker URL is left unset (no CDN injection). */
|
|
38
40
|
workerSrc?: string;
|
|
41
|
+
/** 'scroll' renders all pages. 'single' renders only currentPageIndex. */
|
|
42
|
+
pageMode?: 'scroll' | 'single';
|
|
43
|
+
/** Visible page index in single mode (0-based). */
|
|
44
|
+
currentPageIndex?: number;
|
|
39
45
|
}
|
|
40
|
-
declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, className, workerSrc, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
|
|
46
|
+
declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, renderToolbarContent, className, workerSrc, pageMode, currentPageIndex, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
|
|
47
|
+
|
|
48
|
+
interface PdfPageNavigatorProps {
|
|
49
|
+
currentPageIndex: number;
|
|
50
|
+
numPages: number;
|
|
51
|
+
onPageChange: (pageIndex: number) => void;
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
declare function PdfPageNavigator({ currentPageIndex, numPages, onPageChange, className }: PdfPageNavigatorProps): react_jsx_runtime.JSX.Element;
|
|
41
55
|
|
|
42
56
|
type FieldType = 'signature' | 'fullName' | 'title' | 'date';
|
|
43
57
|
interface FieldPlacement {
|
|
@@ -167,6 +181,23 @@ declare function usePdfDocument(pdfInput: PdfInput): {
|
|
|
167
181
|
errorMessage: string | null;
|
|
168
182
|
};
|
|
169
183
|
|
|
184
|
+
interface UsePdfPageVisibilityOptions {
|
|
185
|
+
/** Ref to the element containing the pdf-viewer pages stack or its parent container. */
|
|
186
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
187
|
+
numPages: number;
|
|
188
|
+
/** IntersectionObserver threshold (0-1). Defaults to 0.5. */
|
|
189
|
+
threshold?: number;
|
|
190
|
+
}
|
|
191
|
+
interface UsePdfPageVisibilityReturn {
|
|
192
|
+
/** 0-based index of the most visible page. */
|
|
193
|
+
currentPageIndex: number;
|
|
194
|
+
/** All currently visible page indices, sorted ascending. */
|
|
195
|
+
visiblePageIndices: number[];
|
|
196
|
+
/** Scroll a page element into view. */
|
|
197
|
+
scrollToPage: (pageIndex: number) => void;
|
|
198
|
+
}
|
|
199
|
+
declare function usePdfPageVisibility({ containerRef, numPages, threshold, }: UsePdfPageVisibilityOptions): UsePdfPageVisibilityReturn;
|
|
200
|
+
|
|
170
201
|
interface UseFieldPlacementOptions {
|
|
171
202
|
defaultWidthPercent?: number;
|
|
172
203
|
defaultHeightPercent?: number;
|
|
@@ -213,6 +244,7 @@ declare const SLOTS: {
|
|
|
213
244
|
readonly pdfViewer: "pdf-viewer";
|
|
214
245
|
readonly pdfViewerEmpty: "pdf-viewer-empty";
|
|
215
246
|
readonly pdfViewerToolbar: "pdf-viewer-toolbar";
|
|
247
|
+
readonly pdfViewerToolbarContent: "pdf-viewer-toolbar-content";
|
|
216
248
|
readonly pdfViewerPageCount: "pdf-viewer-page-count";
|
|
217
249
|
readonly pdfViewerZoom: "pdf-viewer-zoom";
|
|
218
250
|
readonly pdfViewerZoomButton: "pdf-viewer-zoom-button";
|
|
@@ -221,6 +253,9 @@ declare const SLOTS: {
|
|
|
221
253
|
readonly pdfViewerPage: "pdf-viewer-page";
|
|
222
254
|
readonly pdfViewerLoading: "pdf-viewer-loading";
|
|
223
255
|
readonly pdfViewerError: "pdf-viewer-error";
|
|
256
|
+
readonly pdfPageNavigator: "pdf-page-navigator";
|
|
257
|
+
readonly pdfPageNavigatorButton: "pdf-page-navigator-button";
|
|
258
|
+
readonly pdfPageNavigatorLabel: "pdf-page-navigator-label";
|
|
224
259
|
readonly fieldOverlay: "field-overlay";
|
|
225
260
|
readonly signatureField: "signature-field";
|
|
226
261
|
readonly signatureFieldContent: "signature-field-content";
|
|
@@ -266,4 +301,4 @@ declare const defaults: {
|
|
|
266
301
|
readonly DEFAULT_FIELD_HEIGHT_PERCENT: 5;
|
|
267
302
|
};
|
|
268
303
|
|
|
269
|
-
export { type ESigningConfig, FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, type SignatureFieldPreview, type SignatureFontWarning, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
|
304
|
+
export { type ESigningConfig, FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfPageNavigator, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, type SignatureFieldPreview, type SignatureFontWarning, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, type UsePdfPageVisibilityOptions, type UsePdfPageVisibilityReturn, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, usePdfPageVisibility, useSignatureRenderer };
|