@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.
@@ -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 third-party worker URL. Set `configure({ pdfWorkerSrc })` and/or `<PdfViewer workerSrc="..." />` so PDF.js can load a worker (recommended for production). See **Production hardening** below.
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
- ### Self-hosted PDF.js worker (recommended)
142
+ ### Bundled PDF.js worker (recommended)
140
143
 
141
- Copy the worker file that matches your installed `pdfjs-dist` (same major/minor as `react-pdf` / PDF.js):
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
- ```bash
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: '/pdf.worker.min.mjs' })
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
- <PdfViewer workerSrc="/pdf.worker.min.mjs" {...pdfViewerProps} />
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 a CDN for the worker.
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 };