@drvillo/react-browser-e-signing 0.1.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/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/index.d.ts +192 -0
- package/dist/index.js +808 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# React Browser E-Signing
|
|
2
|
+
|
|
3
|
+
`@drvillo/react-browser-e-signing` — browser-only PDF e-signing for React: field placement, typed or drawn signatures, `pdf-lib` embedding.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Browser-only PDF signing flow with no server upload
|
|
8
|
+
- Drag + resize field placement for signature, full name, title, and date
|
|
9
|
+
- Typed signature rendering with handwriting fonts or freehand drawing
|
|
10
|
+
- PDF modification with `pdf-lib`
|
|
11
|
+
- SHA-256 hash generation for integrity checks
|
|
12
|
+
- React components, hooks, and pure utility functions
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @drvillo/react-browser-e-signing
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { defaults, modifyPdf, sha256, useFieldPlacement } from '@drvillo/react-browser-e-signing'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use the demo app for a complete end-to-end integration:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm install
|
|
30
|
+
pnpm demo
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Public API
|
|
34
|
+
|
|
35
|
+
### Components
|
|
36
|
+
|
|
37
|
+
- `PdfViewer`
|
|
38
|
+
- `FieldOverlay`
|
|
39
|
+
- `SignatureField`
|
|
40
|
+
- `FieldPalette`
|
|
41
|
+
- `SignerDetailsPanel`
|
|
42
|
+
- `SignaturePreview`
|
|
43
|
+
- `SignaturePad`
|
|
44
|
+
- `SigningComplete`
|
|
45
|
+
|
|
46
|
+
### Hooks
|
|
47
|
+
|
|
48
|
+
- `usePdfDocument`
|
|
49
|
+
- `useFieldPlacement`
|
|
50
|
+
- `useSignatureRenderer`
|
|
51
|
+
|
|
52
|
+
### Pure Utilities
|
|
53
|
+
|
|
54
|
+
- `modifyPdf`
|
|
55
|
+
- `mapToPoints`
|
|
56
|
+
- `mapFromPoints`
|
|
57
|
+
- `loadSignatureFont`
|
|
58
|
+
- `SIGNATURE_FONTS`
|
|
59
|
+
- `sha256`
|
|
60
|
+
|
|
61
|
+
### Types
|
|
62
|
+
|
|
63
|
+
- `FieldPlacement`
|
|
64
|
+
- `FieldType`
|
|
65
|
+
- `SignerInfo`
|
|
66
|
+
- `SignatureStyle`
|
|
67
|
+
- `SigningResult`
|
|
68
|
+
- `PdfPageDimensions`
|
|
69
|
+
|
|
70
|
+
## Tailwind Consumer Setup
|
|
71
|
+
|
|
72
|
+
This package ships Tailwind classes in components. Add the package path to your Tailwind content scan:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// tailwind.config.js
|
|
76
|
+
export default {
|
|
77
|
+
content: [
|
|
78
|
+
'./src/**/*.{ts,tsx}',
|
|
79
|
+
'./node_modules/@drvillo/react-browser-e-signing/src/**/*.{ts,tsx}',
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Development Scripts
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm build
|
|
88
|
+
pnpm dev
|
|
89
|
+
pnpm demo
|
|
90
|
+
pnpm demo:build
|
|
91
|
+
pnpm test
|
|
92
|
+
pnpm typecheck
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- `PdfViewer` sets `react-pdf` worker from CDN by default using the runtime PDF.js version.
|
|
98
|
+
- Browser test config skips execution when Playwright Chromium is not available in the environment.
|
|
99
|
+
|
|
100
|
+
## Limitations
|
|
101
|
+
|
|
102
|
+
- Single signer workflow in v0.1
|
|
103
|
+
- Visual e-signature annotation + hash integrity, not cryptographic PAdES signatures
|
|
104
|
+
- Desktop-first drag/resize experience
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface PdfViewerProps {
|
|
6
|
+
pdfData: ArrayBuffer | null;
|
|
7
|
+
numPages: number;
|
|
8
|
+
scale: number;
|
|
9
|
+
onScaleChange: (nextScale: number) => void;
|
|
10
|
+
onDocumentLoadSuccess: (numPages: number) => void;
|
|
11
|
+
onPageDimensions: (input: {
|
|
12
|
+
pageIndex: number;
|
|
13
|
+
widthPt: number;
|
|
14
|
+
heightPt: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
renderOverlay?: (pageIndex: number) => ReactNode;
|
|
17
|
+
}
|
|
18
|
+
declare function PdfViewer({ pdfData, numPages, scale, onScaleChange, onDocumentLoadSuccess, onPageDimensions, renderOverlay, }: PdfViewerProps): react_jsx_runtime.JSX.Element;
|
|
19
|
+
|
|
20
|
+
type FieldType = 'signature' | 'fullName' | 'title' | 'date';
|
|
21
|
+
interface FieldPlacement {
|
|
22
|
+
id: string;
|
|
23
|
+
type: FieldType;
|
|
24
|
+
pageIndex: number;
|
|
25
|
+
xPercent: number;
|
|
26
|
+
yPercent: number;
|
|
27
|
+
widthPercent: number;
|
|
28
|
+
heightPercent: number;
|
|
29
|
+
}
|
|
30
|
+
interface SignerInfo {
|
|
31
|
+
firstName: string;
|
|
32
|
+
lastName: string;
|
|
33
|
+
title: string;
|
|
34
|
+
}
|
|
35
|
+
interface SignatureStyleTyped {
|
|
36
|
+
mode: 'typed';
|
|
37
|
+
fontFamily: string;
|
|
38
|
+
}
|
|
39
|
+
interface SignatureStyleDrawn {
|
|
40
|
+
mode: 'drawn';
|
|
41
|
+
dataUrl: string;
|
|
42
|
+
}
|
|
43
|
+
type SignatureStyle = SignatureStyleTyped | SignatureStyleDrawn;
|
|
44
|
+
interface SigningResult {
|
|
45
|
+
pdfBytes: Uint8Array;
|
|
46
|
+
sha256: string;
|
|
47
|
+
}
|
|
48
|
+
interface PdfPageDimensions {
|
|
49
|
+
pageIndex: number;
|
|
50
|
+
widthPt: number;
|
|
51
|
+
heightPt: number;
|
|
52
|
+
}
|
|
53
|
+
interface PointRect {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}
|
|
59
|
+
interface SignatureFieldPreview {
|
|
60
|
+
signatureDataUrl: string | null;
|
|
61
|
+
fullName: string;
|
|
62
|
+
title: string;
|
|
63
|
+
dateText: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface FieldOverlayProps {
|
|
67
|
+
pageIndex: number;
|
|
68
|
+
fields: FieldPlacement[];
|
|
69
|
+
selectedFieldType: FieldType | null;
|
|
70
|
+
onAddField: (input: {
|
|
71
|
+
pageIndex: number;
|
|
72
|
+
type: FieldType;
|
|
73
|
+
xPercent: number;
|
|
74
|
+
yPercent: number;
|
|
75
|
+
}) => void;
|
|
76
|
+
onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void;
|
|
77
|
+
onRemoveField: (fieldId: string) => void;
|
|
78
|
+
preview: SignatureFieldPreview;
|
|
79
|
+
}
|
|
80
|
+
declare function FieldOverlay({ pageIndex, fields, selectedFieldType, onAddField, onUpdateField, onRemoveField, preview, }: FieldOverlayProps): react_jsx_runtime.JSX.Element;
|
|
81
|
+
|
|
82
|
+
interface SignatureFieldProps {
|
|
83
|
+
field: FieldPlacement;
|
|
84
|
+
onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void;
|
|
85
|
+
onRemoveField: (fieldId: string) => void;
|
|
86
|
+
preview: SignatureFieldPreview;
|
|
87
|
+
}
|
|
88
|
+
declare function SignatureField({ field, onUpdateField, onRemoveField, preview }: SignatureFieldProps): react_jsx_runtime.JSX.Element;
|
|
89
|
+
|
|
90
|
+
interface FieldPaletteProps {
|
|
91
|
+
selectedFieldType: FieldType | null;
|
|
92
|
+
onSelectFieldType: (fieldType: FieldType | null) => void;
|
|
93
|
+
}
|
|
94
|
+
declare function FieldPalette({ selectedFieldType, onSelectFieldType }: FieldPaletteProps): react_jsx_runtime.JSX.Element;
|
|
95
|
+
|
|
96
|
+
interface SignerDetailsPanelProps {
|
|
97
|
+
signerInfo: SignerInfo;
|
|
98
|
+
onSignerInfoChange: (nextSignerInfo: SignerInfo) => void;
|
|
99
|
+
}
|
|
100
|
+
declare function SignerDetailsPanel({ signerInfo, onSignerInfoChange }: SignerDetailsPanelProps): react_jsx_runtime.JSX.Element;
|
|
101
|
+
|
|
102
|
+
interface SignaturePreviewProps {
|
|
103
|
+
signerName: string;
|
|
104
|
+
style: SignatureStyle;
|
|
105
|
+
signatureDataUrl: string | null;
|
|
106
|
+
isRendering: boolean;
|
|
107
|
+
onStyleChange: (nextStyle: SignatureStyle) => void;
|
|
108
|
+
}
|
|
109
|
+
declare function SignaturePreview({ signerName, style, signatureDataUrl, isRendering, onStyleChange, }: SignaturePreviewProps): react_jsx_runtime.JSX.Element;
|
|
110
|
+
|
|
111
|
+
interface SignaturePadProps {
|
|
112
|
+
onDrawn: (signatureDataUrl: string) => void;
|
|
113
|
+
}
|
|
114
|
+
declare function SignaturePad({ onDrawn }: SignaturePadProps): react_jsx_runtime.JSX.Element;
|
|
115
|
+
|
|
116
|
+
interface SigningCompleteProps {
|
|
117
|
+
signerName: string;
|
|
118
|
+
fieldCount: number;
|
|
119
|
+
signedAt: string;
|
|
120
|
+
documentHash: string;
|
|
121
|
+
downloadUrl: string;
|
|
122
|
+
fileName?: string;
|
|
123
|
+
onReset: () => void;
|
|
124
|
+
}
|
|
125
|
+
declare function SigningComplete({ signerName, fieldCount, signedAt, documentHash, downloadUrl, fileName, onReset, }: SigningCompleteProps): react_jsx_runtime.JSX.Element;
|
|
126
|
+
|
|
127
|
+
type PdfInput = File | Blob | ArrayBuffer | Uint8Array | null;
|
|
128
|
+
declare function usePdfDocument(pdfInput: PdfInput): {
|
|
129
|
+
pdfData: ArrayBuffer | null;
|
|
130
|
+
numPages: number;
|
|
131
|
+
scale: number;
|
|
132
|
+
setScale: react.Dispatch<react.SetStateAction<number>>;
|
|
133
|
+
pageDimensions: PdfPageDimensions[];
|
|
134
|
+
setPageDimension: (pageIndex: number, widthPt: number, heightPt: number) => void;
|
|
135
|
+
handleDocumentLoadSuccess: (loadedPages: number) => void;
|
|
136
|
+
hasPdf: boolean;
|
|
137
|
+
isLoading: boolean;
|
|
138
|
+
errorMessage: string | null;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
interface UseFieldPlacementOptions {
|
|
142
|
+
defaultWidthPercent?: number;
|
|
143
|
+
defaultHeightPercent?: number;
|
|
144
|
+
}
|
|
145
|
+
interface AddFieldInput {
|
|
146
|
+
pageIndex: number;
|
|
147
|
+
type: FieldType;
|
|
148
|
+
xPercent: number;
|
|
149
|
+
yPercent: number;
|
|
150
|
+
}
|
|
151
|
+
declare function useFieldPlacement(options?: UseFieldPlacementOptions): {
|
|
152
|
+
addField: ({ pageIndex, type, xPercent, yPercent }: AddFieldInput) => FieldPlacement;
|
|
153
|
+
updateField: (id: string, partial: Partial<FieldPlacement>) => void;
|
|
154
|
+
removeField: (id: string) => void;
|
|
155
|
+
clearFields: () => void;
|
|
156
|
+
fields: FieldPlacement[];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
interface UseSignatureRendererInput {
|
|
160
|
+
signerName: string;
|
|
161
|
+
style: SignatureStyle;
|
|
162
|
+
}
|
|
163
|
+
declare function useSignatureRenderer({ signerName, style }: UseSignatureRendererInput): {
|
|
164
|
+
signatureDataUrl: string | null;
|
|
165
|
+
isRendering: boolean;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
interface ModifyPdfInput {
|
|
169
|
+
pdfBytes: Uint8Array;
|
|
170
|
+
fields: FieldPlacement[];
|
|
171
|
+
signer: SignerInfo;
|
|
172
|
+
signatureDataUrl: string;
|
|
173
|
+
pageDimensions: PdfPageDimensions[];
|
|
174
|
+
dateText?: string;
|
|
175
|
+
}
|
|
176
|
+
declare function modifyPdf({ pdfBytes, fields, signer, signatureDataUrl, pageDimensions, dateText, }: ModifyPdfInput): Promise<Uint8Array>;
|
|
177
|
+
|
|
178
|
+
declare function mapToPoints(field: FieldPlacement, page: PdfPageDimensions): PointRect;
|
|
179
|
+
declare function mapFromPoints(rect: PointRect, page: PdfPageDimensions): Pick<FieldPlacement, 'xPercent' | 'yPercent' | 'widthPercent' | 'heightPercent'>;
|
|
180
|
+
|
|
181
|
+
declare const SIGNATURE_FONTS: readonly ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
|
|
182
|
+
declare function loadSignatureFont(fontFamily: string): Promise<void>;
|
|
183
|
+
|
|
184
|
+
declare function sha256(data: Uint8Array): Promise<string>;
|
|
185
|
+
|
|
186
|
+
declare const defaults: {
|
|
187
|
+
readonly SIGNATURE_FONTS: readonly ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
|
|
188
|
+
readonly DEFAULT_FIELD_WIDTH_PERCENT: 25;
|
|
189
|
+
readonly DEFAULT_FIELD_HEIGHT_PERCENT: 5;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export { FieldOverlay, FieldPalette, type FieldPlacement, type FieldType, type PdfPageDimensions, PdfViewer, SIGNATURE_FONTS, SignatureField, type SignatureFieldPreview, SignaturePad, SignaturePreview, type SignatureStyle, SignerDetailsPanel, type SignerInfo, SigningComplete, type SigningResult, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
import { pdfjs, Document, Page } from 'react-pdf';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
|
|
4
|
+
import SignaturePadLibrary from 'signature_pad';
|
|
5
|
+
import fontkit from '@pdf-lib/fontkit';
|
|
6
|
+
import { PDFDocument, StandardFonts } from 'pdf-lib';
|
|
7
|
+
|
|
8
|
+
// src/components/pdf-viewer.tsx
|
|
9
|
+
if (typeof window !== "undefined") {
|
|
10
|
+
pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
11
|
+
}
|
|
12
|
+
var MIN_SCALE = 0.5;
|
|
13
|
+
var MAX_SCALE = 2;
|
|
14
|
+
var SCALE_STEP = 0.1;
|
|
15
|
+
function PdfViewer({
|
|
16
|
+
pdfData,
|
|
17
|
+
numPages,
|
|
18
|
+
scale,
|
|
19
|
+
onScaleChange,
|
|
20
|
+
onDocumentLoadSuccess,
|
|
21
|
+
onPageDimensions,
|
|
22
|
+
renderOverlay
|
|
23
|
+
}) {
|
|
24
|
+
if (!pdfData)
|
|
25
|
+
return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-slate-300 bg-slate-50 p-8 text-center text-sm text-slate-500", children: "Upload a PDF to begin" });
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border border-slate-300 bg-white p-3", children: [
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm text-slate-700", children: [
|
|
29
|
+
"Pages: ",
|
|
30
|
+
numPages || "\u2014"
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
33
|
+
/* @__PURE__ */ jsx(
|
|
34
|
+
"button",
|
|
35
|
+
{
|
|
36
|
+
type: "button",
|
|
37
|
+
className: "rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50",
|
|
38
|
+
onClick: () => onScaleChange(Math.max(MIN_SCALE, Number((scale - SCALE_STEP).toFixed(2)))),
|
|
39
|
+
children: "-"
|
|
40
|
+
}
|
|
41
|
+
),
|
|
42
|
+
/* @__PURE__ */ jsxs("span", { className: "w-16 text-center text-sm text-slate-700", children: [
|
|
43
|
+
Math.round(scale * 100),
|
|
44
|
+
"%"
|
|
45
|
+
] }),
|
|
46
|
+
/* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
type: "button",
|
|
50
|
+
className: "rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50",
|
|
51
|
+
onClick: () => onScaleChange(Math.min(MAX_SCALE, Number((scale + SCALE_STEP).toFixed(2)))),
|
|
52
|
+
children: "+"
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
] })
|
|
56
|
+
] }),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
Document,
|
|
59
|
+
{
|
|
60
|
+
file: pdfData,
|
|
61
|
+
onLoadSuccess: (loadedPdf) => onDocumentLoadSuccess(loadedPdf.numPages),
|
|
62
|
+
loading: /* @__PURE__ */ jsx("div", { className: "text-sm text-slate-500", children: "Loading PDF..." }),
|
|
63
|
+
error: /* @__PURE__ */ jsx("div", { className: "text-sm text-red-600", children: "Unable to render this PDF." }),
|
|
64
|
+
children: /* @__PURE__ */ jsx("div", { className: "space-y-6", children: Array.from({ length: numPages }, (_, pageIndex) => /* @__PURE__ */ jsxs("div", { className: "relative mx-auto w-fit rounded bg-white p-2 shadow", children: [
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
Page,
|
|
67
|
+
{
|
|
68
|
+
pageNumber: pageIndex + 1,
|
|
69
|
+
scale,
|
|
70
|
+
renderTextLayer: false,
|
|
71
|
+
renderAnnotationLayer: false,
|
|
72
|
+
onLoadSuccess: (page) => onPageDimensions({
|
|
73
|
+
pageIndex,
|
|
74
|
+
widthPt: page.view[2],
|
|
75
|
+
heightPt: page.view[3]
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
renderOverlay?.(pageIndex)
|
|
80
|
+
] }, `pdf-page-${pageIndex}`)) })
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
] });
|
|
84
|
+
}
|
|
85
|
+
var MIN_WIDTH_PERCENT = 8;
|
|
86
|
+
var MIN_HEIGHT_PERCENT = 3;
|
|
87
|
+
function clampPercent(value) {
|
|
88
|
+
if (value < 0) return 0;
|
|
89
|
+
if (value > 100) return 100;
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function getFieldPreviewText(field, preview) {
|
|
93
|
+
if (field.type === "fullName") return preview.fullName;
|
|
94
|
+
if (field.type === "title") return preview.title;
|
|
95
|
+
if (field.type === "date") return preview.dateText;
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
function SignatureField({ field, onUpdateField, onRemoveField, preview }) {
|
|
99
|
+
const rootRef = useRef(null);
|
|
100
|
+
const dragStateRef = useRef(null);
|
|
101
|
+
const resizeStateRef = useRef(null);
|
|
102
|
+
function handleDragPointerDown(event) {
|
|
103
|
+
event.stopPropagation();
|
|
104
|
+
if (event.button !== 0) return;
|
|
105
|
+
if (!rootRef.current?.parentElement) return;
|
|
106
|
+
dragStateRef.current = {
|
|
107
|
+
startClientX: event.clientX,
|
|
108
|
+
startClientY: event.clientY,
|
|
109
|
+
startXPercent: field.xPercent,
|
|
110
|
+
startYPercent: field.yPercent
|
|
111
|
+
};
|
|
112
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
113
|
+
}
|
|
114
|
+
function handleDragPointerMove(event) {
|
|
115
|
+
if (!dragStateRef.current) return;
|
|
116
|
+
const parentElement = rootRef.current?.parentElement;
|
|
117
|
+
if (!parentElement) return;
|
|
118
|
+
const deltaX = event.clientX - dragStateRef.current.startClientX;
|
|
119
|
+
const deltaY = event.clientY - dragStateRef.current.startClientY;
|
|
120
|
+
const deltaXPercent = deltaX / parentElement.clientWidth * 100;
|
|
121
|
+
const deltaYPercent = deltaY / parentElement.clientHeight * 100;
|
|
122
|
+
const maxX = 100 - field.widthPercent;
|
|
123
|
+
const maxY = 100 - field.heightPercent;
|
|
124
|
+
onUpdateField(field.id, {
|
|
125
|
+
xPercent: clampPercent(Math.min(maxX, dragStateRef.current.startXPercent + deltaXPercent)),
|
|
126
|
+
yPercent: clampPercent(Math.min(maxY, dragStateRef.current.startYPercent + deltaYPercent))
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function handleDragPointerUp(event) {
|
|
130
|
+
dragStateRef.current = null;
|
|
131
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
132
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
133
|
+
}
|
|
134
|
+
function handleResizePointerDown(event) {
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
if (event.button !== 0) return;
|
|
137
|
+
resizeStateRef.current = {
|
|
138
|
+
startClientX: event.clientX,
|
|
139
|
+
startClientY: event.clientY,
|
|
140
|
+
startWidthPercent: field.widthPercent,
|
|
141
|
+
startHeightPercent: field.heightPercent
|
|
142
|
+
};
|
|
143
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
144
|
+
}
|
|
145
|
+
function handleResizePointerMove(event) {
|
|
146
|
+
if (!resizeStateRef.current) return;
|
|
147
|
+
const parentElement = rootRef.current?.parentElement;
|
|
148
|
+
if (!parentElement) return;
|
|
149
|
+
const deltaX = event.clientX - resizeStateRef.current.startClientX;
|
|
150
|
+
const deltaY = event.clientY - resizeStateRef.current.startClientY;
|
|
151
|
+
const deltaWidthPercent = deltaX / parentElement.clientWidth * 100;
|
|
152
|
+
const deltaHeightPercent = deltaY / parentElement.clientHeight * 100;
|
|
153
|
+
const maxWidth = 100 - field.xPercent;
|
|
154
|
+
const maxHeight = 100 - field.yPercent;
|
|
155
|
+
const widthPercent = clampPercent(
|
|
156
|
+
Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))
|
|
157
|
+
);
|
|
158
|
+
const heightPercent = clampPercent(
|
|
159
|
+
Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent))
|
|
160
|
+
);
|
|
161
|
+
onUpdateField(field.id, { widthPercent, heightPercent });
|
|
162
|
+
}
|
|
163
|
+
function handleResizePointerUp(event) {
|
|
164
|
+
resizeStateRef.current = null;
|
|
165
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
166
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
167
|
+
}
|
|
168
|
+
const previewText = getFieldPreviewText(field, preview);
|
|
169
|
+
return /* @__PURE__ */ jsxs(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
ref: rootRef,
|
|
173
|
+
className: "absolute rounded border-2 border-blue-500 bg-blue-50/80 shadow-sm select-none",
|
|
174
|
+
style: {
|
|
175
|
+
left: `${field.xPercent}%`,
|
|
176
|
+
top: `${field.yPercent}%`,
|
|
177
|
+
width: `${field.widthPercent}%`,
|
|
178
|
+
height: `${field.heightPercent}%`
|
|
179
|
+
},
|
|
180
|
+
onPointerDown: handleDragPointerDown,
|
|
181
|
+
onPointerMove: handleDragPointerMove,
|
|
182
|
+
onPointerUp: handleDragPointerUp,
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-full w-full items-start justify-between gap-2 p-1.5 text-[11px] text-slate-800", children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 overflow-hidden", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-semibold capitalize", children: field.type }),
|
|
187
|
+
field.type === "signature" && preview.signatureDataUrl ? /* @__PURE__ */ jsx(
|
|
188
|
+
"img",
|
|
189
|
+
{
|
|
190
|
+
src: preview.signatureDataUrl,
|
|
191
|
+
alt: "signature preview",
|
|
192
|
+
className: "mt-1 h-[calc(100%-18px)] max-h-full w-full object-contain",
|
|
193
|
+
draggable: false
|
|
194
|
+
}
|
|
195
|
+
) : /* @__PURE__ */ jsx("div", { className: "truncate text-slate-600", children: previewText || "\u2014" })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"button",
|
|
199
|
+
{
|
|
200
|
+
type: "button",
|
|
201
|
+
className: "rounded bg-white/80 px-1 text-xs text-red-600 hover:bg-white",
|
|
202
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
203
|
+
onClick: (event) => {
|
|
204
|
+
event.stopPropagation();
|
|
205
|
+
onRemoveField(field.id);
|
|
206
|
+
},
|
|
207
|
+
"aria-label": "Remove field",
|
|
208
|
+
children: "\xD7"
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
className: "absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-nwse-resize rounded-full bg-blue-600",
|
|
216
|
+
onPointerDown: handleResizePointerDown,
|
|
217
|
+
onPointerMove: handleResizePointerMove,
|
|
218
|
+
onPointerUp: handleResizePointerUp
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
function FieldOverlay({
|
|
226
|
+
pageIndex,
|
|
227
|
+
fields,
|
|
228
|
+
selectedFieldType,
|
|
229
|
+
onAddField,
|
|
230
|
+
onUpdateField,
|
|
231
|
+
onRemoveField,
|
|
232
|
+
preview
|
|
233
|
+
}) {
|
|
234
|
+
function handleOverlayPointerDown(event) {
|
|
235
|
+
if (!selectedFieldType) return;
|
|
236
|
+
if (event.button !== 0) return;
|
|
237
|
+
if (event.target !== event.currentTarget) return;
|
|
238
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
239
|
+
const xPercent = (event.clientX - rect.left) / rect.width * 100;
|
|
240
|
+
const yPercent = (event.clientY - rect.top) / rect.height * 100;
|
|
241
|
+
onAddField({
|
|
242
|
+
pageIndex,
|
|
243
|
+
type: selectedFieldType,
|
|
244
|
+
xPercent,
|
|
245
|
+
yPercent
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const pageFields = fields.filter((field) => field.pageIndex === pageIndex);
|
|
249
|
+
return /* @__PURE__ */ jsx(
|
|
250
|
+
"div",
|
|
251
|
+
{
|
|
252
|
+
className: `absolute inset-0 z-20 rounded ${selectedFieldType ? "cursor-crosshair" : "cursor-default"}`,
|
|
253
|
+
onPointerDown: handleOverlayPointerDown,
|
|
254
|
+
"aria-label": `Field overlay page ${pageIndex + 1}`,
|
|
255
|
+
children: pageFields.map((field) => /* @__PURE__ */ jsx(
|
|
256
|
+
SignatureField,
|
|
257
|
+
{
|
|
258
|
+
field,
|
|
259
|
+
onUpdateField,
|
|
260
|
+
onRemoveField,
|
|
261
|
+
preview
|
|
262
|
+
},
|
|
263
|
+
field.id
|
|
264
|
+
))
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
var FIELD_LABELS = {
|
|
269
|
+
signature: "Signature",
|
|
270
|
+
fullName: "Full Name",
|
|
271
|
+
title: "Title",
|
|
272
|
+
date: "Date"
|
|
273
|
+
};
|
|
274
|
+
var FIELD_TYPES = ["signature", "fullName", "title", "date"];
|
|
275
|
+
function FieldPalette({ selectedFieldType, onSelectFieldType }) {
|
|
276
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-slate-300 bg-white p-2", children: FIELD_TYPES.map((fieldType) => {
|
|
277
|
+
const isSelected = selectedFieldType === fieldType;
|
|
278
|
+
return /* @__PURE__ */ jsx(
|
|
279
|
+
"button",
|
|
280
|
+
{
|
|
281
|
+
type: "button",
|
|
282
|
+
className: `rounded-md border px-3 py-1.5 text-sm font-medium transition ${isSelected ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700 hover:bg-slate-100"}`,
|
|
283
|
+
onClick: () => onSelectFieldType(isSelected ? null : fieldType),
|
|
284
|
+
"aria-pressed": isSelected,
|
|
285
|
+
children: FIELD_LABELS[fieldType]
|
|
286
|
+
},
|
|
287
|
+
fieldType
|
|
288
|
+
);
|
|
289
|
+
}) });
|
|
290
|
+
}
|
|
291
|
+
function SignerDetailsPanel({ signerInfo, onSignerInfoChange }) {
|
|
292
|
+
function handleInputChange(fieldName, fieldValue) {
|
|
293
|
+
onSignerInfoChange({
|
|
294
|
+
...signerInfo,
|
|
295
|
+
[fieldName]: fieldValue
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-slate-300 bg-white p-4", children: [
|
|
299
|
+
/* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-slate-800", children: "Signer Details" }),
|
|
300
|
+
/* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
|
|
301
|
+
"First Name",
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
"input",
|
|
304
|
+
{
|
|
305
|
+
className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
|
|
306
|
+
value: signerInfo.firstName,
|
|
307
|
+
onChange: (event) => handleInputChange("firstName", event.target.value)
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
] }),
|
|
311
|
+
/* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
|
|
312
|
+
"Last Name",
|
|
313
|
+
/* @__PURE__ */ jsx(
|
|
314
|
+
"input",
|
|
315
|
+
{
|
|
316
|
+
className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
|
|
317
|
+
value: signerInfo.lastName,
|
|
318
|
+
onChange: (event) => handleInputChange("lastName", event.target.value)
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
|
|
323
|
+
"Title",
|
|
324
|
+
/* @__PURE__ */ jsx(
|
|
325
|
+
"input",
|
|
326
|
+
{
|
|
327
|
+
className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
|
|
328
|
+
value: signerInfo.title,
|
|
329
|
+
onChange: (event) => handleInputChange("title", event.target.value)
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
] })
|
|
333
|
+
] });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/lib/signature-fonts.ts
|
|
337
|
+
var loadedFonts = /* @__PURE__ */ new Set();
|
|
338
|
+
var SIGNATURE_FONTS = ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"];
|
|
339
|
+
function buildGoogleFontsCssUrl(family) {
|
|
340
|
+
const encoded = family.trim().replace(/\s+/g, "+");
|
|
341
|
+
return `https://fonts.googleapis.com/css2?family=${encoded}&display=swap`;
|
|
342
|
+
}
|
|
343
|
+
async function loadCssFromGoogleFonts(url) {
|
|
344
|
+
const response = await fetch(url);
|
|
345
|
+
if (!response.ok) throw new Error(`Unable to load font css from ${url}`);
|
|
346
|
+
return response.text();
|
|
347
|
+
}
|
|
348
|
+
function extractFontSource(cssText) {
|
|
349
|
+
const sourceMatch = cssText.match(/src:\s*url\(([^)]+)\)\s*format\(['"]?([^'")]+)['"]?\)/i);
|
|
350
|
+
if (!sourceMatch) return null;
|
|
351
|
+
return sourceMatch[1].replace(/['"]/g, "");
|
|
352
|
+
}
|
|
353
|
+
async function loadSignatureFont(fontFamily) {
|
|
354
|
+
if (loadedFonts.has(fontFamily)) return;
|
|
355
|
+
if (typeof document === "undefined") return;
|
|
356
|
+
if (typeof FontFace === "undefined") return;
|
|
357
|
+
const cssUrl = buildGoogleFontsCssUrl(fontFamily);
|
|
358
|
+
const cssText = await loadCssFromGoogleFonts(cssUrl);
|
|
359
|
+
const fontSource = extractFontSource(cssText);
|
|
360
|
+
if (!fontSource) throw new Error(`Unable to extract font source for ${fontFamily}`);
|
|
361
|
+
const fontFace = new FontFace(fontFamily, `url(${fontSource})`);
|
|
362
|
+
await fontFace.load();
|
|
363
|
+
const fontSet = document.fonts;
|
|
364
|
+
fontSet.add(fontFace);
|
|
365
|
+
loadedFonts.add(fontFamily);
|
|
366
|
+
}
|
|
367
|
+
function SignaturePad({ onDrawn }) {
|
|
368
|
+
const canvasRef = useRef(null);
|
|
369
|
+
const signaturePadRef = useRef(null);
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
if (!canvasRef.current) return;
|
|
372
|
+
const signaturePad = new SignaturePadLibrary(canvasRef.current, {
|
|
373
|
+
minWidth: 1,
|
|
374
|
+
maxWidth: 2.5,
|
|
375
|
+
penColor: "#111827",
|
|
376
|
+
backgroundColor: "rgba(255,255,255,0)"
|
|
377
|
+
});
|
|
378
|
+
signaturePadRef.current = signaturePad;
|
|
379
|
+
function emitSignatureIfNotEmpty() {
|
|
380
|
+
if (signaturePad.isEmpty()) return;
|
|
381
|
+
onDrawn(signaturePad.toDataURL("image/png"));
|
|
382
|
+
}
|
|
383
|
+
signaturePad.addEventListener("endStroke", emitSignatureIfNotEmpty);
|
|
384
|
+
return () => {
|
|
385
|
+
signaturePad.removeEventListener("endStroke", emitSignatureIfNotEmpty);
|
|
386
|
+
signaturePad.off();
|
|
387
|
+
signaturePadRef.current = null;
|
|
388
|
+
};
|
|
389
|
+
}, [onDrawn]);
|
|
390
|
+
function handleClear() {
|
|
391
|
+
signaturePadRef.current?.clear();
|
|
392
|
+
onDrawn("");
|
|
393
|
+
}
|
|
394
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
395
|
+
/* @__PURE__ */ jsx("canvas", { ref: canvasRef, width: 420, height: 140, className: "w-full rounded border border-slate-300 bg-white" }),
|
|
396
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
397
|
+
"button",
|
|
398
|
+
{
|
|
399
|
+
type: "button",
|
|
400
|
+
className: "rounded border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50",
|
|
401
|
+
onClick: handleClear,
|
|
402
|
+
children: "Clear"
|
|
403
|
+
}
|
|
404
|
+
) })
|
|
405
|
+
] });
|
|
406
|
+
}
|
|
407
|
+
function SignaturePreview({
|
|
408
|
+
signerName,
|
|
409
|
+
style,
|
|
410
|
+
signatureDataUrl,
|
|
411
|
+
isRendering,
|
|
412
|
+
onStyleChange
|
|
413
|
+
}) {
|
|
414
|
+
const canShowPreview = useMemo(() => signerName.trim().length > 0, [signerName]);
|
|
415
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-slate-300 bg-white p-4", children: [
|
|
416
|
+
/* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-slate-800", children: "Signature" }),
|
|
417
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
"button",
|
|
420
|
+
{
|
|
421
|
+
type: "button",
|
|
422
|
+
className: `rounded border px-2 py-1 text-xs ${style.mode === "typed" ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700"}`,
|
|
423
|
+
onClick: () => onStyleChange({ mode: "typed", fontFamily: style.mode === "typed" ? style.fontFamily : SIGNATURE_FONTS[0] }),
|
|
424
|
+
children: "Typed"
|
|
425
|
+
}
|
|
426
|
+
),
|
|
427
|
+
/* @__PURE__ */ jsx(
|
|
428
|
+
"button",
|
|
429
|
+
{
|
|
430
|
+
type: "button",
|
|
431
|
+
className: `rounded border px-2 py-1 text-xs ${style.mode === "drawn" ? "border-blue-600 bg-blue-600 text-white" : "border-slate-300 bg-slate-50 text-slate-700"}`,
|
|
432
|
+
onClick: () => onStyleChange({ mode: "drawn", dataUrl: style.mode === "drawn" ? style.dataUrl : "" }),
|
|
433
|
+
children: "Drawn"
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
] }),
|
|
437
|
+
style.mode === "typed" ? /* @__PURE__ */ jsxs("label", { className: "block text-sm text-slate-700", children: [
|
|
438
|
+
"Font",
|
|
439
|
+
/* @__PURE__ */ jsx(
|
|
440
|
+
"select",
|
|
441
|
+
{
|
|
442
|
+
className: "mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm",
|
|
443
|
+
value: style.fontFamily,
|
|
444
|
+
onChange: (event) => onStyleChange({ mode: "typed", fontFamily: event.target.value }),
|
|
445
|
+
children: SIGNATURE_FONTS.map((fontFamily) => /* @__PURE__ */ jsx("option", { value: fontFamily, children: fontFamily }, fontFamily))
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
] }) : /* @__PURE__ */ jsx(SignaturePad, { onDrawn: (dataUrl) => onStyleChange({ mode: "drawn", dataUrl }) }),
|
|
449
|
+
/* @__PURE__ */ jsx("div", { className: "rounded border border-dashed border-slate-300 bg-slate-50 p-3", children: !canShowPreview ? /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Enter signer first and last name to render a signature." }) : isRendering ? /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Rendering signature..." }) : signatureDataUrl ? /* @__PURE__ */ jsx("img", { src: signatureDataUrl, alt: "Signature preview", className: "h-24 w-full object-contain" }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "No signature available yet." }) })
|
|
450
|
+
] });
|
|
451
|
+
}
|
|
452
|
+
function SigningComplete({
|
|
453
|
+
signerName,
|
|
454
|
+
fieldCount,
|
|
455
|
+
signedAt,
|
|
456
|
+
documentHash,
|
|
457
|
+
downloadUrl,
|
|
458
|
+
fileName = "signed-document.pdf",
|
|
459
|
+
onReset
|
|
460
|
+
}) {
|
|
461
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border border-emerald-300 bg-emerald-50 p-4", children: [
|
|
462
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-emerald-900", children: "Document Signed" }),
|
|
463
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 text-sm text-emerald-900", children: [
|
|
464
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
465
|
+
"Signer: ",
|
|
466
|
+
signerName || "Unknown"
|
|
467
|
+
] }),
|
|
468
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
469
|
+
"Fields applied: ",
|
|
470
|
+
fieldCount
|
|
471
|
+
] }),
|
|
472
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
473
|
+
"Signed at: ",
|
|
474
|
+
signedAt
|
|
475
|
+
] })
|
|
476
|
+
] }),
|
|
477
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded border border-emerald-200 bg-white p-3", children: [
|
|
478
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium uppercase tracking-wide text-slate-500", children: "SHA-256" }),
|
|
479
|
+
/* @__PURE__ */ jsx("p", { className: "break-all font-mono text-xs text-slate-800", children: documentHash })
|
|
480
|
+
] }),
|
|
481
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
482
|
+
/* @__PURE__ */ jsx(
|
|
483
|
+
"a",
|
|
484
|
+
{
|
|
485
|
+
href: downloadUrl,
|
|
486
|
+
download: fileName,
|
|
487
|
+
className: "rounded bg-emerald-700 px-3 py-1.5 text-sm text-white hover:bg-emerald-800",
|
|
488
|
+
children: "Download Signed PDF"
|
|
489
|
+
}
|
|
490
|
+
),
|
|
491
|
+
/* @__PURE__ */ jsx(
|
|
492
|
+
"button",
|
|
493
|
+
{
|
|
494
|
+
type: "button",
|
|
495
|
+
className: "rounded border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-50",
|
|
496
|
+
onClick: onReset,
|
|
497
|
+
children: "Sign Another"
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
] })
|
|
501
|
+
] });
|
|
502
|
+
}
|
|
503
|
+
function isArrayBuffer(value) {
|
|
504
|
+
return value instanceof ArrayBuffer;
|
|
505
|
+
}
|
|
506
|
+
function isUint8Array(value) {
|
|
507
|
+
return value instanceof Uint8Array;
|
|
508
|
+
}
|
|
509
|
+
async function convertPdfInputToArrayBuffer(pdfInput) {
|
|
510
|
+
if (isArrayBuffer(pdfInput)) return pdfInput;
|
|
511
|
+
if (isUint8Array(pdfInput)) {
|
|
512
|
+
const copy = new Uint8Array(pdfInput.byteLength);
|
|
513
|
+
copy.set(pdfInput);
|
|
514
|
+
return copy.buffer;
|
|
515
|
+
}
|
|
516
|
+
return pdfInput.arrayBuffer();
|
|
517
|
+
}
|
|
518
|
+
function usePdfDocument(pdfInput) {
|
|
519
|
+
const [pdfData, setPdfData] = useState(null);
|
|
520
|
+
const [numPages, setNumPages] = useState(0);
|
|
521
|
+
const [scale, setScale] = useState(1);
|
|
522
|
+
const [pageDimensions, setPageDimensions] = useState([]);
|
|
523
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
524
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
525
|
+
useEffect(() => {
|
|
526
|
+
if (!pdfInput) {
|
|
527
|
+
setPdfData(null);
|
|
528
|
+
setNumPages(0);
|
|
529
|
+
setPageDimensions([]);
|
|
530
|
+
setErrorMessage(null);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
let isMounted = true;
|
|
534
|
+
setIsLoading(true);
|
|
535
|
+
setErrorMessage(null);
|
|
536
|
+
convertPdfInputToArrayBuffer(pdfInput).then((arrayBuffer) => {
|
|
537
|
+
if (!isMounted) return;
|
|
538
|
+
setPdfData(arrayBuffer);
|
|
539
|
+
}).catch((error) => {
|
|
540
|
+
if (!isMounted) return;
|
|
541
|
+
const message = error instanceof Error ? error.message : "Unable to read PDF data";
|
|
542
|
+
setErrorMessage(message);
|
|
543
|
+
setPdfData(null);
|
|
544
|
+
}).finally(() => {
|
|
545
|
+
if (!isMounted) return;
|
|
546
|
+
setIsLoading(false);
|
|
547
|
+
});
|
|
548
|
+
return () => {
|
|
549
|
+
isMounted = false;
|
|
550
|
+
};
|
|
551
|
+
}, [pdfInput]);
|
|
552
|
+
function handleDocumentLoadSuccess(loadedPages) {
|
|
553
|
+
setNumPages(loadedPages);
|
|
554
|
+
setPageDimensions(
|
|
555
|
+
(previousDimensions) => previousDimensions.filter((dimension) => dimension.pageIndex < loadedPages).sort((left, right) => left.pageIndex - right.pageIndex)
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
function setPageDimension(pageIndex, widthPt, heightPt) {
|
|
559
|
+
setPageDimensions((previousDimensions) => {
|
|
560
|
+
const nextDimensions = previousDimensions.filter((entry) => entry.pageIndex !== pageIndex);
|
|
561
|
+
nextDimensions.push({ pageIndex, widthPt, heightPt });
|
|
562
|
+
nextDimensions.sort((left, right) => left.pageIndex - right.pageIndex);
|
|
563
|
+
return nextDimensions;
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
const hasPdf = useMemo(() => pdfData !== null, [pdfData]);
|
|
567
|
+
return {
|
|
568
|
+
pdfData,
|
|
569
|
+
numPages,
|
|
570
|
+
scale,
|
|
571
|
+
setScale,
|
|
572
|
+
pageDimensions,
|
|
573
|
+
setPageDimension,
|
|
574
|
+
handleDocumentLoadSuccess,
|
|
575
|
+
hasPdf,
|
|
576
|
+
isLoading,
|
|
577
|
+
errorMessage
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function clampPercent2(value) {
|
|
581
|
+
if (Number.isNaN(value)) return 0;
|
|
582
|
+
if (value < 0) return 0;
|
|
583
|
+
if (value > 100) return 100;
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
function buildFieldId() {
|
|
587
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
588
|
+
return crypto.randomUUID();
|
|
589
|
+
}
|
|
590
|
+
return `field-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
591
|
+
}
|
|
592
|
+
function useFieldPlacement(options = {}) {
|
|
593
|
+
const defaultWidthPercent = options.defaultWidthPercent ?? 25;
|
|
594
|
+
const defaultHeightPercent = options.defaultHeightPercent ?? 5;
|
|
595
|
+
const [fields, setFields] = useState([]);
|
|
596
|
+
const addField = useCallback(
|
|
597
|
+
({ pageIndex, type, xPercent, yPercent }) => {
|
|
598
|
+
const field = {
|
|
599
|
+
id: buildFieldId(),
|
|
600
|
+
pageIndex,
|
|
601
|
+
type,
|
|
602
|
+
xPercent: clampPercent2(xPercent),
|
|
603
|
+
yPercent: clampPercent2(yPercent),
|
|
604
|
+
widthPercent: clampPercent2(defaultWidthPercent),
|
|
605
|
+
heightPercent: clampPercent2(defaultHeightPercent)
|
|
606
|
+
};
|
|
607
|
+
setFields((previousFields) => [...previousFields, field]);
|
|
608
|
+
return field;
|
|
609
|
+
},
|
|
610
|
+
[defaultHeightPercent, defaultWidthPercent]
|
|
611
|
+
);
|
|
612
|
+
const updateField = useCallback((id, partial) => {
|
|
613
|
+
setFields(
|
|
614
|
+
(previousFields) => previousFields.map((field) => {
|
|
615
|
+
if (field.id !== id) return field;
|
|
616
|
+
return {
|
|
617
|
+
...field,
|
|
618
|
+
...partial,
|
|
619
|
+
xPercent: partial.xPercent === void 0 ? field.xPercent : clampPercent2(partial.xPercent),
|
|
620
|
+
yPercent: partial.yPercent === void 0 ? field.yPercent : clampPercent2(partial.yPercent),
|
|
621
|
+
widthPercent: partial.widthPercent === void 0 ? field.widthPercent : clampPercent2(partial.widthPercent),
|
|
622
|
+
heightPercent: partial.heightPercent === void 0 ? field.heightPercent : clampPercent2(partial.heightPercent)
|
|
623
|
+
};
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
}, []);
|
|
627
|
+
const removeField = useCallback((id) => {
|
|
628
|
+
setFields((previousFields) => previousFields.filter((field) => field.id !== id));
|
|
629
|
+
}, []);
|
|
630
|
+
const clearFields = useCallback(() => {
|
|
631
|
+
setFields([]);
|
|
632
|
+
}, []);
|
|
633
|
+
const fieldActions = useMemo(
|
|
634
|
+
() => ({
|
|
635
|
+
addField,
|
|
636
|
+
updateField,
|
|
637
|
+
removeField,
|
|
638
|
+
clearFields
|
|
639
|
+
}),
|
|
640
|
+
[addField, clearFields, removeField, updateField]
|
|
641
|
+
);
|
|
642
|
+
return {
|
|
643
|
+
fields,
|
|
644
|
+
...fieldActions
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function buildCanvas() {
|
|
648
|
+
return document.createElement("canvas");
|
|
649
|
+
}
|
|
650
|
+
function drawTypedSignature({
|
|
651
|
+
signerName,
|
|
652
|
+
fontFamily
|
|
653
|
+
}) {
|
|
654
|
+
const canvas = buildCanvas();
|
|
655
|
+
const context = canvas.getContext("2d");
|
|
656
|
+
if (!context) return "";
|
|
657
|
+
const padding = 16;
|
|
658
|
+
const fontSize = 56;
|
|
659
|
+
context.font = `${fontSize}px "${fontFamily}"`;
|
|
660
|
+
const metrics = context.measureText(signerName);
|
|
661
|
+
canvas.width = Math.max(240, Math.ceil(metrics.width + padding * 2));
|
|
662
|
+
canvas.height = 100;
|
|
663
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
664
|
+
context.font = `${fontSize}px "${fontFamily}"`;
|
|
665
|
+
context.fillStyle = "#111827";
|
|
666
|
+
context.textBaseline = "middle";
|
|
667
|
+
context.fillText(signerName, padding, canvas.height / 2);
|
|
668
|
+
return canvas.toDataURL("image/png");
|
|
669
|
+
}
|
|
670
|
+
function useSignatureRenderer({ signerName, style }) {
|
|
671
|
+
const [signatureDataUrl, setSignatureDataUrl] = useState(null);
|
|
672
|
+
const [isRendering, setIsRendering] = useState(false);
|
|
673
|
+
const normalizedName = useMemo(() => signerName.trim(), [signerName]);
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
if (!normalizedName) {
|
|
676
|
+
setSignatureDataUrl(null);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (style.mode === "drawn") {
|
|
680
|
+
setSignatureDataUrl(style.dataUrl || null);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
let isActive = true;
|
|
684
|
+
setIsRendering(true);
|
|
685
|
+
loadSignatureFont(style.fontFamily).then(() => {
|
|
686
|
+
if (!isActive) return;
|
|
687
|
+
const dataUrl = drawTypedSignature({ signerName: normalizedName, fontFamily: style.fontFamily });
|
|
688
|
+
setSignatureDataUrl(dataUrl || null);
|
|
689
|
+
}).catch(() => {
|
|
690
|
+
if (!isActive) return;
|
|
691
|
+
setSignatureDataUrl(null);
|
|
692
|
+
}).finally(() => {
|
|
693
|
+
if (!isActive) return;
|
|
694
|
+
setIsRendering(false);
|
|
695
|
+
});
|
|
696
|
+
return () => {
|
|
697
|
+
isActive = false;
|
|
698
|
+
};
|
|
699
|
+
}, [normalizedName, style]);
|
|
700
|
+
return {
|
|
701
|
+
signatureDataUrl,
|
|
702
|
+
isRendering
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/lib/coordinate-mapper.ts
|
|
707
|
+
function clampPercent3(value) {
|
|
708
|
+
if (Number.isNaN(value)) return 0;
|
|
709
|
+
if (value < 0) return 0;
|
|
710
|
+
if (value > 100) return 100;
|
|
711
|
+
return value;
|
|
712
|
+
}
|
|
713
|
+
function mapToPoints(field, page) {
|
|
714
|
+
const xPercent = clampPercent3(field.xPercent);
|
|
715
|
+
const yPercent = clampPercent3(field.yPercent);
|
|
716
|
+
const widthPercent = clampPercent3(field.widthPercent);
|
|
717
|
+
const heightPercent = clampPercent3(field.heightPercent);
|
|
718
|
+
const width = widthPercent / 100 * page.widthPt;
|
|
719
|
+
const height = heightPercent / 100 * page.heightPt;
|
|
720
|
+
const x = xPercent / 100 * page.widthPt;
|
|
721
|
+
const y = page.heightPt - yPercent / 100 * page.heightPt - height;
|
|
722
|
+
return { x, y, width, height };
|
|
723
|
+
}
|
|
724
|
+
function mapFromPoints(rect, page) {
|
|
725
|
+
const widthPercent = rect.width / page.widthPt * 100;
|
|
726
|
+
const heightPercent = rect.height / page.heightPt * 100;
|
|
727
|
+
const xPercent = rect.x / page.widthPt * 100;
|
|
728
|
+
const yPercent = (page.heightPt - rect.y - rect.height) / page.heightPt * 100;
|
|
729
|
+
return {
|
|
730
|
+
xPercent: clampPercent3(xPercent),
|
|
731
|
+
yPercent: clampPercent3(yPercent),
|
|
732
|
+
widthPercent: clampPercent3(widthPercent),
|
|
733
|
+
heightPercent: clampPercent3(heightPercent)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// src/lib/pdf-modifier.ts
|
|
738
|
+
function fullNameFromSigner(signer) {
|
|
739
|
+
return [signer.firstName, signer.lastName].filter(Boolean).join(" ").trim();
|
|
740
|
+
}
|
|
741
|
+
function dataUrlToBytes(dataUrl) {
|
|
742
|
+
const base64 = dataUrl.split(",")[1];
|
|
743
|
+
if (!base64) throw new Error("Invalid signature data URL");
|
|
744
|
+
const binary = typeof atob === "function" ? atob(base64) : Buffer.from(base64, "base64").toString("binary");
|
|
745
|
+
const bytes = new Uint8Array(binary.length);
|
|
746
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
747
|
+
return bytes;
|
|
748
|
+
}
|
|
749
|
+
async function modifyPdf({
|
|
750
|
+
pdfBytes,
|
|
751
|
+
fields,
|
|
752
|
+
signer,
|
|
753
|
+
signatureDataUrl,
|
|
754
|
+
pageDimensions,
|
|
755
|
+
dateText
|
|
756
|
+
}) {
|
|
757
|
+
const pdfDocument = await PDFDocument.load(pdfBytes);
|
|
758
|
+
pdfDocument.registerFontkit(fontkit);
|
|
759
|
+
const pages = pdfDocument.getPages();
|
|
760
|
+
const helveticaFont = await pdfDocument.embedFont(StandardFonts.Helvetica);
|
|
761
|
+
const signatureImageBytes = signatureDataUrl ? dataUrlToBytes(signatureDataUrl) : null;
|
|
762
|
+
const signatureImage = signatureImageBytes ? await pdfDocument.embedPng(signatureImageBytes) : null;
|
|
763
|
+
const resolvedDateText = dateText || (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
764
|
+
for (const field of fields) {
|
|
765
|
+
const page = pages[field.pageIndex];
|
|
766
|
+
const pageDimension = pageDimensions.find((entry) => entry.pageIndex === field.pageIndex);
|
|
767
|
+
if (!page || !pageDimension) continue;
|
|
768
|
+
const pointRect = mapToPoints(field, pageDimension);
|
|
769
|
+
if (field.type === "signature" && signatureImage) {
|
|
770
|
+
page.drawImage(signatureImage, {
|
|
771
|
+
x: pointRect.x,
|
|
772
|
+
y: pointRect.y,
|
|
773
|
+
width: pointRect.width,
|
|
774
|
+
height: pointRect.height
|
|
775
|
+
});
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const textValue = field.type === "fullName" ? fullNameFromSigner(signer) : field.type === "title" ? signer.title : field.type === "date" ? resolvedDateText : "";
|
|
779
|
+
if (!textValue) continue;
|
|
780
|
+
const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6));
|
|
781
|
+
page.drawText(textValue, {
|
|
782
|
+
x: pointRect.x + 2,
|
|
783
|
+
y: pointRect.y + Math.max(0, (pointRect.height - textSize) / 2),
|
|
784
|
+
size: textSize,
|
|
785
|
+
font: helveticaFont
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return pdfDocument.save();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/lib/hash.ts
|
|
792
|
+
async function sha256(data) {
|
|
793
|
+
const dataBuffer = new Uint8Array(data.byteLength);
|
|
794
|
+
dataBuffer.set(data);
|
|
795
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
|
|
796
|
+
return Array.from(new Uint8Array(hashBuffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/index.ts
|
|
800
|
+
var defaults = {
|
|
801
|
+
SIGNATURE_FONTS: ["Dancing Script", "Great Vibes", "Sacramento", "Alex Brush"],
|
|
802
|
+
DEFAULT_FIELD_WIDTH_PERCENT: 25,
|
|
803
|
+
DEFAULT_FIELD_HEIGHT_PERCENT: 5
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
export { FieldOverlay, FieldPalette, PdfViewer, SIGNATURE_FONTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, useSignatureRenderer };
|
|
807
|
+
//# sourceMappingURL=index.js.map
|
|
808
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/pdf-viewer.tsx","../src/components/signature-field.tsx","../src/components/field-overlay.tsx","../src/components/field-palette.tsx","../src/components/signer-details-panel.tsx","../src/lib/signature-fonts.ts","../src/components/signature-pad.tsx","../src/components/signature-preview.tsx","../src/components/signing-complete.tsx","../src/hooks/use-pdf-document.ts","../src/hooks/use-field-placement.ts","../src/hooks/use-signature-renderer.ts","../src/lib/coordinate-mapper.ts","../src/lib/pdf-modifier.ts","../src/lib/hash.ts","../src/index.ts"],"names":["jsxs","jsx","useRef","useEffect","useMemo","clampPercent","useState"],"mappings":"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","file":"index.js","sourcesContent":["import { Document, Page, pdfjs } from 'react-pdf'\nimport type { ReactNode } from 'react'\n\nif (typeof window !== 'undefined') {\n pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n}\n\ninterface PdfViewerProps {\n pdfData: ArrayBuffer | null\n numPages: number\n scale: number\n onScaleChange: (nextScale: number) => void\n onDocumentLoadSuccess: (numPages: number) => void\n onPageDimensions: (input: { pageIndex: number; widthPt: number; heightPt: number }) => void\n renderOverlay?: (pageIndex: number) => ReactNode\n}\n\nconst MIN_SCALE = 0.5\nconst MAX_SCALE = 2\nconst SCALE_STEP = 0.1\n\nexport function PdfViewer({\n pdfData,\n numPages,\n scale,\n onScaleChange,\n onDocumentLoadSuccess,\n onPageDimensions,\n renderOverlay,\n}: PdfViewerProps) {\n if (!pdfData)\n return (\n <div className=\"rounded-lg border border-dashed border-slate-300 bg-slate-50 p-8 text-center text-sm text-slate-500\">\n Upload a PDF to begin\n </div>\n )\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between rounded-lg border border-slate-300 bg-white p-3\">\n <div className=\"text-sm text-slate-700\">Pages: {numPages || '—'}</div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50\"\n onClick={() => onScaleChange(Math.max(MIN_SCALE, Number((scale - SCALE_STEP).toFixed(2))))}\n >\n -\n </button>\n <span className=\"w-16 text-center text-sm text-slate-700\">{Math.round(scale * 100)}%</span>\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-sm hover:bg-slate-50\"\n onClick={() => onScaleChange(Math.min(MAX_SCALE, Number((scale + SCALE_STEP).toFixed(2))))}\n >\n +\n </button>\n </div>\n </div>\n\n <Document\n file={pdfData}\n onLoadSuccess={(loadedPdf) => onDocumentLoadSuccess(loadedPdf.numPages)}\n loading={<div className=\"text-sm text-slate-500\">Loading PDF...</div>}\n error={<div className=\"text-sm text-red-600\">Unable to render this PDF.</div>}\n >\n <div className=\"space-y-6\">\n {Array.from({ length: numPages }, (_, pageIndex) => (\n <div key={`pdf-page-${pageIndex}`} className=\"relative mx-auto w-fit rounded bg-white p-2 shadow\">\n <Page\n pageNumber={pageIndex + 1}\n scale={scale}\n renderTextLayer={false}\n renderAnnotationLayer={false}\n onLoadSuccess={(page) =>\n onPageDimensions({\n pageIndex,\n widthPt: page.view[2],\n heightPt: page.view[3],\n })\n }\n />\n {renderOverlay?.(pageIndex)}\n </div>\n ))}\n </div>\n </Document>\n </div>\n )\n}\n","import { useRef } from 'react'\nimport type { FieldPlacement, SignatureFieldPreview } from '../types'\nimport type { PointerEvent } from 'react'\n\ninterface SignatureFieldProps {\n field: FieldPlacement\n onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void\n onRemoveField: (fieldId: string) => void\n preview: SignatureFieldPreview\n}\n\ninterface DragState {\n startClientX: number\n startClientY: number\n startXPercent: number\n startYPercent: number\n}\n\ninterface ResizeState {\n startClientX: number\n startClientY: number\n startWidthPercent: number\n startHeightPercent: number\n}\n\nconst MIN_WIDTH_PERCENT = 8\nconst MIN_HEIGHT_PERCENT = 3\n\nfunction clampPercent(value: number): number {\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nfunction getFieldPreviewText(field: FieldPlacement, preview: SignatureFieldPreview): string {\n if (field.type === 'fullName') return preview.fullName\n if (field.type === 'title') return preview.title\n if (field.type === 'date') return preview.dateText\n return ''\n}\n\nexport function SignatureField({ field, onUpdateField, onRemoveField, preview }: SignatureFieldProps) {\n const rootRef = useRef<HTMLDivElement | null>(null)\n const dragStateRef = useRef<DragState | null>(null)\n const resizeStateRef = useRef<ResizeState | null>(null)\n\n function handleDragPointerDown(event: PointerEvent<HTMLDivElement>): void {\n event.stopPropagation()\n if (event.button !== 0) return\n if (!rootRef.current?.parentElement) return\n\n dragStateRef.current = {\n startClientX: event.clientX,\n startClientY: event.clientY,\n startXPercent: field.xPercent,\n startYPercent: field.yPercent,\n }\n\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n function handleDragPointerMove(event: PointerEvent<HTMLDivElement>): void {\n if (!dragStateRef.current) return\n const parentElement = rootRef.current?.parentElement\n if (!parentElement) return\n\n const deltaX = event.clientX - dragStateRef.current.startClientX\n const deltaY = event.clientY - dragStateRef.current.startClientY\n const deltaXPercent = (deltaX / parentElement.clientWidth) * 100\n const deltaYPercent = (deltaY / parentElement.clientHeight) * 100\n const maxX = 100 - field.widthPercent\n const maxY = 100 - field.heightPercent\n\n onUpdateField(field.id, {\n xPercent: clampPercent(Math.min(maxX, dragStateRef.current.startXPercent + deltaXPercent)),\n yPercent: clampPercent(Math.min(maxY, dragStateRef.current.startYPercent + deltaYPercent)),\n })\n }\n\n function handleDragPointerUp(event: PointerEvent<HTMLDivElement>): void {\n dragStateRef.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId))\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n\n function handleResizePointerDown(event: PointerEvent<HTMLDivElement>): void {\n event.stopPropagation()\n if (event.button !== 0) return\n\n resizeStateRef.current = {\n startClientX: event.clientX,\n startClientY: event.clientY,\n startWidthPercent: field.widthPercent,\n startHeightPercent: field.heightPercent,\n }\n\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n function handleResizePointerMove(event: PointerEvent<HTMLDivElement>): void {\n if (!resizeStateRef.current) return\n const parentElement = rootRef.current?.parentElement\n if (!parentElement) return\n\n const deltaX = event.clientX - resizeStateRef.current.startClientX\n const deltaY = event.clientY - resizeStateRef.current.startClientY\n const deltaWidthPercent = (deltaX / parentElement.clientWidth) * 100\n const deltaHeightPercent = (deltaY / parentElement.clientHeight) * 100\n\n const maxWidth = 100 - field.xPercent\n const maxHeight = 100 - field.yPercent\n const widthPercent = clampPercent(\n Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))\n )\n const heightPercent = clampPercent(\n Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent))\n )\n\n onUpdateField(field.id, { widthPercent, heightPercent })\n }\n\n function handleResizePointerUp(event: PointerEvent<HTMLDivElement>): void {\n resizeStateRef.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId))\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n\n const previewText = getFieldPreviewText(field, preview)\n\n return (\n <div\n ref={rootRef}\n className=\"absolute rounded border-2 border-blue-500 bg-blue-50/80 shadow-sm select-none\"\n style={{\n left: `${field.xPercent}%`,\n top: `${field.yPercent}%`,\n width: `${field.widthPercent}%`,\n height: `${field.heightPercent}%`,\n }}\n onPointerDown={handleDragPointerDown}\n onPointerMove={handleDragPointerMove}\n onPointerUp={handleDragPointerUp}\n >\n <div className=\"flex h-full w-full items-start justify-between gap-2 p-1.5 text-[11px] text-slate-800\">\n <div className=\"min-w-0 flex-1 overflow-hidden\">\n <div className=\"truncate font-semibold capitalize\">{field.type}</div>\n {field.type === 'signature' && preview.signatureDataUrl ? (\n <img\n src={preview.signatureDataUrl}\n alt=\"signature preview\"\n className=\"mt-1 h-[calc(100%-18px)] max-h-full w-full object-contain\"\n draggable={false}\n />\n ) : (\n <div className=\"truncate text-slate-600\">{previewText || '—'}</div>\n )}\n </div>\n <button\n type=\"button\"\n className=\"rounded bg-white/80 px-1 text-xs text-red-600 hover:bg-white\"\n onPointerDown={(event) => event.stopPropagation()}\n onClick={(event) => {\n event.stopPropagation()\n onRemoveField(field.id)\n }}\n aria-label=\"Remove field\"\n >\n ×\n </button>\n </div>\n\n <div\n className=\"absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-nwse-resize rounded-full bg-blue-600\"\n onPointerDown={handleResizePointerDown}\n onPointerMove={handleResizePointerMove}\n onPointerUp={handleResizePointerUp}\n />\n </div>\n )\n}\n","import type { FieldPlacement, FieldType, SignatureFieldPreview } from '../types'\nimport { SignatureField } from './signature-field'\nimport type { PointerEvent } from 'react'\n\ninterface FieldOverlayProps {\n pageIndex: number\n fields: FieldPlacement[]\n selectedFieldType: FieldType | null\n onAddField: (input: { pageIndex: number; type: FieldType; xPercent: number; yPercent: number }) => void\n onUpdateField: (fieldId: string, partial: Partial<FieldPlacement>) => void\n onRemoveField: (fieldId: string) => void\n preview: SignatureFieldPreview\n}\n\nexport function FieldOverlay({\n pageIndex,\n fields,\n selectedFieldType,\n onAddField,\n onUpdateField,\n onRemoveField,\n preview,\n}: FieldOverlayProps) {\n function handleOverlayPointerDown(event: PointerEvent<HTMLDivElement>): void {\n if (!selectedFieldType) return\n if (event.button !== 0) return\n if (event.target !== event.currentTarget) return\n const rect = event.currentTarget.getBoundingClientRect()\n const xPercent = ((event.clientX - rect.left) / rect.width) * 100\n const yPercent = ((event.clientY - rect.top) / rect.height) * 100\n onAddField({\n pageIndex,\n type: selectedFieldType,\n xPercent,\n yPercent,\n })\n }\n\n const pageFields = fields.filter((field) => field.pageIndex === pageIndex)\n\n return (\n <div\n className={`absolute inset-0 z-20 rounded ${selectedFieldType ? 'cursor-crosshair' : 'cursor-default'}`}\n onPointerDown={handleOverlayPointerDown}\n aria-label={`Field overlay page ${pageIndex + 1}`}\n >\n {pageFields.map((field) => (\n <SignatureField\n key={field.id}\n field={field}\n onUpdateField={onUpdateField}\n onRemoveField={onRemoveField}\n preview={preview}\n />\n ))}\n </div>\n )\n}\n","import type { FieldType } from '../types'\n\ninterface FieldPaletteProps {\n selectedFieldType: FieldType | null\n onSelectFieldType: (fieldType: FieldType | null) => void\n}\n\nconst FIELD_LABELS: Record<FieldType, string> = {\n signature: 'Signature',\n fullName: 'Full Name',\n title: 'Title',\n date: 'Date',\n}\n\nconst FIELD_TYPES: FieldType[] = ['signature', 'fullName', 'title', 'date']\n\nexport function FieldPalette({ selectedFieldType, onSelectFieldType }: FieldPaletteProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-2 rounded-lg border border-slate-300 bg-white p-2\">\n {FIELD_TYPES.map((fieldType) => {\n const isSelected = selectedFieldType === fieldType\n return (\n <button\n key={fieldType}\n type=\"button\"\n className={`rounded-md border px-3 py-1.5 text-sm font-medium transition ${\n isSelected\n ? 'border-blue-600 bg-blue-600 text-white'\n : 'border-slate-300 bg-slate-50 text-slate-700 hover:bg-slate-100'\n }`}\n onClick={() => onSelectFieldType(isSelected ? null : fieldType)}\n aria-pressed={isSelected}\n >\n {FIELD_LABELS[fieldType]}\n </button>\n )\n })}\n </div>\n )\n}\n","import type { SignerInfo } from '../types'\n\ninterface SignerDetailsPanelProps {\n signerInfo: SignerInfo\n onSignerInfoChange: (nextSignerInfo: SignerInfo) => void\n}\n\nexport function SignerDetailsPanel({ signerInfo, onSignerInfoChange }: SignerDetailsPanelProps) {\n function handleInputChange(fieldName: keyof SignerInfo, fieldValue: string): void {\n onSignerInfoChange({\n ...signerInfo,\n [fieldName]: fieldValue,\n })\n }\n\n return (\n <div className=\"space-y-3 rounded-lg border border-slate-300 bg-white p-4\">\n <h2 className=\"text-sm font-semibold text-slate-800\">Signer Details</h2>\n\n <label className=\"block text-sm text-slate-700\">\n First Name\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.firstName}\n onChange={(event) => handleInputChange('firstName', event.target.value)}\n />\n </label>\n\n <label className=\"block text-sm text-slate-700\">\n Last Name\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.lastName}\n onChange={(event) => handleInputChange('lastName', event.target.value)}\n />\n </label>\n\n <label className=\"block text-sm text-slate-700\">\n Title\n <input\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={signerInfo.title}\n onChange={(event) => handleInputChange('title', event.target.value)}\n />\n </label>\n </div>\n )\n}\n","const loadedFonts = new Set<string>()\n\nexport const SIGNATURE_FONTS = ['Dancing Script', 'Great Vibes', 'Sacramento', 'Alex Brush'] as const\n\nfunction buildGoogleFontsCssUrl(family: string): string {\n const encoded = family.trim().replace(/\\s+/g, '+')\n return `https://fonts.googleapis.com/css2?family=${encoded}&display=swap`\n}\n\nasync function loadCssFromGoogleFonts(url: string): Promise<string> {\n const response = await fetch(url)\n if (!response.ok) throw new Error(`Unable to load font css from ${url}`)\n return response.text()\n}\n\nfunction extractFontSource(cssText: string): string | null {\n const sourceMatch = cssText.match(/src:\\s*url\\(([^)]+)\\)\\s*format\\(['\"]?([^'\")]+)['\"]?\\)/i)\n if (!sourceMatch) return null\n return sourceMatch[1].replace(/['\"]/g, '')\n}\n\nexport async function loadSignatureFont(fontFamily: string): Promise<void> {\n if (loadedFonts.has(fontFamily)) return\n if (typeof document === 'undefined') return\n if (typeof FontFace === 'undefined') return\n\n const cssUrl = buildGoogleFontsCssUrl(fontFamily)\n const cssText = await loadCssFromGoogleFonts(cssUrl)\n const fontSource = extractFontSource(cssText)\n if (!fontSource) throw new Error(`Unable to extract font source for ${fontFamily}`)\n\n const fontFace = new FontFace(fontFamily, `url(${fontSource})`)\n await fontFace.load()\n const fontSet = document.fonts as unknown as { add: (font: FontFace) => void }\n fontSet.add(fontFace)\n loadedFonts.add(fontFamily)\n}\n\nexport function buildSignatureFontCssUrl(fontFamily: string): string {\n return buildGoogleFontsCssUrl(fontFamily)\n}\n","import SignaturePadLibrary from 'signature_pad'\nimport { useEffect, useRef } from 'react'\n\ninterface SignaturePadProps {\n onDrawn: (signatureDataUrl: string) => void\n}\n\nexport function SignaturePad({ onDrawn }: SignaturePadProps) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const signaturePadRef = useRef<SignaturePadLibrary | null>(null)\n\n useEffect(() => {\n if (!canvasRef.current) return\n\n const signaturePad = new SignaturePadLibrary(canvasRef.current, {\n minWidth: 1,\n maxWidth: 2.5,\n penColor: '#111827',\n backgroundColor: 'rgba(255,255,255,0)',\n })\n\n signaturePadRef.current = signaturePad\n\n function emitSignatureIfNotEmpty(): void {\n if (signaturePad.isEmpty()) return\n onDrawn(signaturePad.toDataURL('image/png'))\n }\n\n signaturePad.addEventListener('endStroke', emitSignatureIfNotEmpty)\n\n return () => {\n signaturePad.removeEventListener('endStroke', emitSignatureIfNotEmpty)\n signaturePad.off()\n signaturePadRef.current = null\n }\n }, [onDrawn])\n\n function handleClear(): void {\n signaturePadRef.current?.clear()\n onDrawn('')\n }\n\n return (\n <div className=\"space-y-2\">\n <canvas ref={canvasRef} width={420} height={140} className=\"w-full rounded border border-slate-300 bg-white\" />\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50\"\n onClick={handleClear}\n >\n Clear\n </button>\n </div>\n </div>\n )\n}\n","import { useMemo } from 'react'\nimport { SIGNATURE_FONTS } from '../lib/signature-fonts'\nimport type { SignatureStyle } from '../types'\nimport { SignaturePad } from './signature-pad'\n\ninterface SignaturePreviewProps {\n signerName: string\n style: SignatureStyle\n signatureDataUrl: string | null\n isRendering: boolean\n onStyleChange: (nextStyle: SignatureStyle) => void\n}\n\nexport function SignaturePreview({\n signerName,\n style,\n signatureDataUrl,\n isRendering,\n onStyleChange,\n}: SignaturePreviewProps) {\n const canShowPreview = useMemo(() => signerName.trim().length > 0, [signerName])\n\n return (\n <div className=\"space-y-3 rounded-lg border border-slate-300 bg-white p-4\">\n <h2 className=\"text-sm font-semibold text-slate-800\">Signature</h2>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n className={`rounded border px-2 py-1 text-xs ${style.mode === 'typed' ? 'border-blue-600 bg-blue-600 text-white' : 'border-slate-300 bg-slate-50 text-slate-700'}`}\n onClick={() => onStyleChange({ mode: 'typed', fontFamily: style.mode === 'typed' ? style.fontFamily : SIGNATURE_FONTS[0] })}\n >\n Typed\n </button>\n <button\n type=\"button\"\n className={`rounded border px-2 py-1 text-xs ${style.mode === 'drawn' ? 'border-blue-600 bg-blue-600 text-white' : 'border-slate-300 bg-slate-50 text-slate-700'}`}\n onClick={() => onStyleChange({ mode: 'drawn', dataUrl: style.mode === 'drawn' ? style.dataUrl : '' })}\n >\n Drawn\n </button>\n </div>\n\n {style.mode === 'typed' ? (\n <label className=\"block text-sm text-slate-700\">\n Font\n <select\n className=\"mt-1 w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"\n value={style.fontFamily}\n onChange={(event) => onStyleChange({ mode: 'typed', fontFamily: event.target.value })}\n >\n {SIGNATURE_FONTS.map((fontFamily) => (\n <option key={fontFamily} value={fontFamily}>\n {fontFamily}\n </option>\n ))}\n </select>\n </label>\n ) : (\n <SignaturePad onDrawn={(dataUrl) => onStyleChange({ mode: 'drawn', dataUrl })} />\n )}\n\n <div className=\"rounded border border-dashed border-slate-300 bg-slate-50 p-3\">\n {!canShowPreview ? (\n <p className=\"text-xs text-slate-500\">Enter signer first and last name to render a signature.</p>\n ) : isRendering ? (\n <p className=\"text-xs text-slate-500\">Rendering signature...</p>\n ) : signatureDataUrl ? (\n <img src={signatureDataUrl} alt=\"Signature preview\" className=\"h-24 w-full object-contain\" />\n ) : (\n <p className=\"text-xs text-slate-500\">No signature available yet.</p>\n )}\n </div>\n </div>\n )\n}\n","interface SigningCompleteProps {\n signerName: string\n fieldCount: number\n signedAt: string\n documentHash: string\n downloadUrl: string\n fileName?: string\n onReset: () => void\n}\n\nexport function SigningComplete({\n signerName,\n fieldCount,\n signedAt,\n documentHash,\n downloadUrl,\n fileName = 'signed-document.pdf',\n onReset,\n}: SigningCompleteProps) {\n return (\n <div className=\"space-y-4 rounded-lg border border-emerald-300 bg-emerald-50 p-4\">\n <h2 className=\"text-base font-semibold text-emerald-900\">Document Signed</h2>\n <div className=\"space-y-1 text-sm text-emerald-900\">\n <p>Signer: {signerName || 'Unknown'}</p>\n <p>Fields applied: {fieldCount}</p>\n <p>Signed at: {signedAt}</p>\n </div>\n\n <div className=\"rounded border border-emerald-200 bg-white p-3\">\n <p className=\"mb-1 text-xs font-medium uppercase tracking-wide text-slate-500\">SHA-256</p>\n <p className=\"break-all font-mono text-xs text-slate-800\">{documentHash}</p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <a\n href={downloadUrl}\n download={fileName}\n className=\"rounded bg-emerald-700 px-3 py-1.5 text-sm text-white hover:bg-emerald-800\"\n >\n Download Signed PDF\n </a>\n <button\n type=\"button\"\n className=\"rounded border border-slate-300 bg-white px-3 py-1.5 text-sm text-slate-700 hover:bg-slate-50\"\n onClick={onReset}\n >\n Sign Another\n </button>\n </div>\n </div>\n )\n}\n","import { useEffect, useMemo, useState } from 'react'\nimport type { PdfPageDimensions } from '../types'\n\nexport type PdfInput = File | Blob | ArrayBuffer | Uint8Array | null\n\nfunction isArrayBuffer(value: unknown): value is ArrayBuffer {\n return value instanceof ArrayBuffer\n}\n\nfunction isUint8Array(value: unknown): value is Uint8Array {\n return value instanceof Uint8Array\n}\n\nasync function convertPdfInputToArrayBuffer(pdfInput: Exclude<PdfInput, null>): Promise<ArrayBuffer> {\n if (isArrayBuffer(pdfInput)) return pdfInput\n if (isUint8Array(pdfInput)) {\n const copy = new Uint8Array(pdfInput.byteLength)\n copy.set(pdfInput)\n return copy.buffer\n }\n return pdfInput.arrayBuffer()\n}\n\nexport function usePdfDocument(pdfInput: PdfInput) {\n const [pdfData, setPdfData] = useState<ArrayBuffer | null>(null)\n const [numPages, setNumPages] = useState(0)\n const [scale, setScale] = useState(1)\n const [pageDimensions, setPageDimensions] = useState<PdfPageDimensions[]>([])\n const [errorMessage, setErrorMessage] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n\n useEffect(() => {\n if (!pdfInput) {\n setPdfData(null)\n setNumPages(0)\n setPageDimensions([])\n setErrorMessage(null)\n return\n }\n\n let isMounted = true\n\n setIsLoading(true)\n setErrorMessage(null)\n convertPdfInputToArrayBuffer(pdfInput)\n .then((arrayBuffer) => {\n if (!isMounted) return\n setPdfData(arrayBuffer)\n })\n .catch((error: unknown) => {\n if (!isMounted) return\n const message = error instanceof Error ? error.message : 'Unable to read PDF data'\n setErrorMessage(message)\n setPdfData(null)\n })\n .finally(() => {\n if (!isMounted) return\n setIsLoading(false)\n })\n\n return () => {\n isMounted = false\n }\n }, [pdfInput])\n\n function handleDocumentLoadSuccess(loadedPages: number): void {\n setNumPages(loadedPages)\n setPageDimensions((previousDimensions) =>\n previousDimensions\n .filter((dimension) => dimension.pageIndex < loadedPages)\n .sort((left, right) => left.pageIndex - right.pageIndex)\n )\n }\n\n function setPageDimension(pageIndex: number, widthPt: number, heightPt: number): void {\n setPageDimensions((previousDimensions) => {\n const nextDimensions = previousDimensions.filter((entry) => entry.pageIndex !== pageIndex)\n nextDimensions.push({ pageIndex, widthPt, heightPt })\n nextDimensions.sort((left, right) => left.pageIndex - right.pageIndex)\n return nextDimensions\n })\n }\n\n const hasPdf = useMemo(() => pdfData !== null, [pdfData])\n\n return {\n pdfData,\n numPages,\n scale,\n setScale,\n pageDimensions,\n setPageDimension,\n handleDocumentLoadSuccess,\n hasPdf,\n isLoading,\n errorMessage,\n }\n}\n","import { useCallback, useMemo, useState } from 'react'\nimport type { FieldPlacement, FieldType } from '../types'\n\ninterface UseFieldPlacementOptions {\n defaultWidthPercent?: number\n defaultHeightPercent?: number\n}\n\ninterface AddFieldInput {\n pageIndex: number\n type: FieldType\n xPercent: number\n yPercent: number\n}\n\nfunction clampPercent(value: number): number {\n if (Number.isNaN(value)) return 0\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nfunction buildFieldId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n\n return `field-${Date.now()}-${Math.random().toString(16).slice(2)}`\n}\n\nexport function useFieldPlacement(options: UseFieldPlacementOptions = {}) {\n const defaultWidthPercent = options.defaultWidthPercent ?? 25\n const defaultHeightPercent = options.defaultHeightPercent ?? 5\n const [fields, setFields] = useState<FieldPlacement[]>([])\n\n const addField = useCallback(\n ({ pageIndex, type, xPercent, yPercent }: AddFieldInput) => {\n const field: FieldPlacement = {\n id: buildFieldId(),\n pageIndex,\n type,\n xPercent: clampPercent(xPercent),\n yPercent: clampPercent(yPercent),\n widthPercent: clampPercent(defaultWidthPercent),\n heightPercent: clampPercent(defaultHeightPercent),\n }\n setFields((previousFields) => [...previousFields, field])\n return field\n },\n [defaultHeightPercent, defaultWidthPercent]\n )\n\n const updateField = useCallback((id: string, partial: Partial<FieldPlacement>) => {\n setFields((previousFields) =>\n previousFields.map((field) => {\n if (field.id !== id) return field\n return {\n ...field,\n ...partial,\n xPercent: partial.xPercent === undefined ? field.xPercent : clampPercent(partial.xPercent),\n yPercent: partial.yPercent === undefined ? field.yPercent : clampPercent(partial.yPercent),\n widthPercent:\n partial.widthPercent === undefined ? field.widthPercent : clampPercent(partial.widthPercent),\n heightPercent:\n partial.heightPercent === undefined ? field.heightPercent : clampPercent(partial.heightPercent),\n }\n })\n )\n }, [])\n\n const removeField = useCallback((id: string) => {\n setFields((previousFields) => previousFields.filter((field) => field.id !== id))\n }, [])\n\n const clearFields = useCallback(() => {\n setFields([])\n }, [])\n\n const fieldActions = useMemo(\n () => ({\n addField,\n updateField,\n removeField,\n clearFields,\n }),\n [addField, clearFields, removeField, updateField]\n )\n\n return {\n fields,\n ...fieldActions,\n }\n}\n","import { useEffect, useMemo, useState } from 'react'\nimport { loadSignatureFont } from '../lib/signature-fonts'\nimport type { SignatureStyle } from '../types'\n\ninterface UseSignatureRendererInput {\n signerName: string\n style: SignatureStyle\n}\n\nfunction buildCanvas(): HTMLCanvasElement {\n return document.createElement('canvas')\n}\n\nfunction drawTypedSignature({\n signerName,\n fontFamily,\n}: {\n signerName: string\n fontFamily: string\n}): string {\n const canvas = buildCanvas()\n const context = canvas.getContext('2d')\n if (!context) return ''\n\n const padding = 16\n const fontSize = 56\n context.font = `${fontSize}px \"${fontFamily}\"`\n const metrics = context.measureText(signerName)\n\n canvas.width = Math.max(240, Math.ceil(metrics.width + padding * 2))\n canvas.height = 100\n\n context.clearRect(0, 0, canvas.width, canvas.height)\n context.font = `${fontSize}px \"${fontFamily}\"`\n context.fillStyle = '#111827'\n context.textBaseline = 'middle'\n context.fillText(signerName, padding, canvas.height / 2)\n\n return canvas.toDataURL('image/png')\n}\n\nexport function useSignatureRenderer({ signerName, style }: UseSignatureRendererInput) {\n const [signatureDataUrl, setSignatureDataUrl] = useState<string | null>(null)\n const [isRendering, setIsRendering] = useState(false)\n\n const normalizedName = useMemo(() => signerName.trim(), [signerName])\n\n useEffect(() => {\n if (!normalizedName) {\n setSignatureDataUrl(null)\n return\n }\n\n if (style.mode === 'drawn') {\n setSignatureDataUrl(style.dataUrl || null)\n return\n }\n\n let isActive = true\n setIsRendering(true)\n\n loadSignatureFont(style.fontFamily)\n .then(() => {\n if (!isActive) return\n const dataUrl = drawTypedSignature({ signerName: normalizedName, fontFamily: style.fontFamily })\n setSignatureDataUrl(dataUrl || null)\n })\n .catch(() => {\n if (!isActive) return\n setSignatureDataUrl(null)\n })\n .finally(() => {\n if (!isActive) return\n setIsRendering(false)\n })\n\n return () => {\n isActive = false\n }\n }, [normalizedName, style])\n\n return {\n signatureDataUrl,\n isRendering,\n }\n}\n","import type { FieldPlacement, PdfPageDimensions, PointRect } from '../types'\n\nfunction clampPercent(value: number): number {\n if (Number.isNaN(value)) return 0\n if (value < 0) return 0\n if (value > 100) return 100\n return value\n}\n\nexport function mapToPoints(field: FieldPlacement, page: PdfPageDimensions): PointRect {\n const xPercent = clampPercent(field.xPercent)\n const yPercent = clampPercent(field.yPercent)\n const widthPercent = clampPercent(field.widthPercent)\n const heightPercent = clampPercent(field.heightPercent)\n\n const width = (widthPercent / 100) * page.widthPt\n const height = (heightPercent / 100) * page.heightPt\n const x = (xPercent / 100) * page.widthPt\n const y = page.heightPt - (yPercent / 100) * page.heightPt - height\n\n return { x, y, width, height }\n}\n\nexport function mapFromPoints(\n rect: PointRect,\n page: PdfPageDimensions\n): Pick<FieldPlacement, 'xPercent' | 'yPercent' | 'widthPercent' | 'heightPercent'> {\n const widthPercent = (rect.width / page.widthPt) * 100\n const heightPercent = (rect.height / page.heightPt) * 100\n const xPercent = (rect.x / page.widthPt) * 100\n const yPercent = ((page.heightPt - rect.y - rect.height) / page.heightPt) * 100\n\n return {\n xPercent: clampPercent(xPercent),\n yPercent: clampPercent(yPercent),\n widthPercent: clampPercent(widthPercent),\n heightPercent: clampPercent(heightPercent),\n }\n}\n","import fontkit from '@pdf-lib/fontkit'\nimport { PDFDocument, StandardFonts } from 'pdf-lib'\nimport { mapToPoints } from './coordinate-mapper'\nimport type { FieldPlacement, PdfPageDimensions, SignerInfo } from '../types'\n\ninterface ModifyPdfInput {\n pdfBytes: Uint8Array\n fields: FieldPlacement[]\n signer: SignerInfo\n signatureDataUrl: string\n pageDimensions: PdfPageDimensions[]\n dateText?: string\n}\n\nfunction fullNameFromSigner(signer: SignerInfo): string {\n return [signer.firstName, signer.lastName].filter(Boolean).join(' ').trim()\n}\n\nfunction dataUrlToBytes(dataUrl: string): Uint8Array {\n const base64 = dataUrl.split(',')[1]\n if (!base64) throw new Error('Invalid signature data URL')\n const binary =\n typeof atob === 'function'\n ? atob(base64)\n : Buffer.from(base64, 'base64').toString('binary')\n const bytes = new Uint8Array(binary.length)\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index)\n return bytes\n}\n\nexport async function modifyPdf({\n pdfBytes,\n fields,\n signer,\n signatureDataUrl,\n pageDimensions,\n dateText,\n}: ModifyPdfInput): Promise<Uint8Array> {\n const pdfDocument = await PDFDocument.load(pdfBytes)\n pdfDocument.registerFontkit(fontkit)\n const pages = pdfDocument.getPages()\n const helveticaFont = await pdfDocument.embedFont(StandardFonts.Helvetica)\n\n const signatureImageBytes = signatureDataUrl ? dataUrlToBytes(signatureDataUrl) : null\n const signatureImage = signatureImageBytes ? await pdfDocument.embedPng(signatureImageBytes) : null\n const resolvedDateText = dateText || new Date().toLocaleDateString()\n\n for (const field of fields) {\n const page = pages[field.pageIndex]\n const pageDimension = pageDimensions.find((entry) => entry.pageIndex === field.pageIndex)\n if (!page || !pageDimension) continue\n\n const pointRect = mapToPoints(field, pageDimension)\n if (field.type === 'signature' && signatureImage) {\n page.drawImage(signatureImage, {\n x: pointRect.x,\n y: pointRect.y,\n width: pointRect.width,\n height: pointRect.height,\n })\n continue\n }\n\n const textValue =\n field.type === 'fullName'\n ? fullNameFromSigner(signer)\n : field.type === 'title'\n ? signer.title\n : field.type === 'date'\n ? resolvedDateText\n : ''\n if (!textValue) continue\n\n const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6))\n page.drawText(textValue, {\n x: pointRect.x + 2,\n y: pointRect.y + Math.max(0, (pointRect.height - textSize) / 2),\n size: textSize,\n font: helveticaFont,\n })\n }\n\n return pdfDocument.save()\n}\n","export async function sha256(data: Uint8Array): Promise<string> {\n const dataBuffer = new Uint8Array(data.byteLength)\n dataBuffer.set(data)\n const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer)\n return Array.from(new Uint8Array(hashBuffer))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n","export { PdfViewer } from './components/pdf-viewer'\nexport { FieldOverlay } from './components/field-overlay'\nexport { SignatureField } from './components/signature-field'\nexport { FieldPalette } from './components/field-palette'\nexport { SignerDetailsPanel } from './components/signer-details-panel'\nexport { SignaturePreview } from './components/signature-preview'\nexport { SignaturePad } from './components/signature-pad'\nexport { SigningComplete } from './components/signing-complete'\n\nexport { usePdfDocument } from './hooks/use-pdf-document'\nexport { useFieldPlacement } from './hooks/use-field-placement'\nexport { useSignatureRenderer } from './hooks/use-signature-renderer'\n\nexport { modifyPdf } from './lib/pdf-modifier'\nexport { mapToPoints, mapFromPoints } from './lib/coordinate-mapper'\nexport { loadSignatureFont, SIGNATURE_FONTS } from './lib/signature-fonts'\nexport { sha256 } from './lib/hash'\n\nexport type {\n FieldPlacement,\n FieldType,\n SignerInfo,\n SignatureStyle,\n SigningResult,\n PdfPageDimensions,\n SignatureFieldPreview,\n} from './types'\n\nexport const defaults = {\n SIGNATURE_FONTS: ['Dancing Script', 'Great Vibes', 'Sacramento', 'Alex Brush'],\n DEFAULT_FIELD_WIDTH_PERCENT: 25,\n DEFAULT_FIELD_HEIGHT_PERCENT: 5,\n} as const\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drvillo/react-browser-e-signing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React PDF e-signing in the browser — fields, handwriting, pdf-lib",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/drvillo/react-browser-e-signing.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/drvillo/react-browser-e-signing#readme",
|
|
10
|
+
"bugs": "https://github.com/drvillo/react-browser-e-signing/issues",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"postinstall": "pnpm run install:browsers",
|
|
28
|
+
"install:browsers": "playwright install chromium",
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"demo": "vite",
|
|
32
|
+
"demo:build": "vite build",
|
|
33
|
+
"test": "pnpm run test:unit && pnpm run test:browser",
|
|
34
|
+
"test:unit": "vitest run --config vitest.config.ts",
|
|
35
|
+
"test:browser": "vitest run --config vitest.browser.config.ts",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"prepublishOnly": "npm run build",
|
|
38
|
+
"publish:npm": "npm publish"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"e-signature",
|
|
42
|
+
"pdf",
|
|
43
|
+
"sign",
|
|
44
|
+
"browser",
|
|
45
|
+
"react"
|
|
46
|
+
],
|
|
47
|
+
"author": "",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@pdf-lib/fontkit": "^1.1.1",
|
|
51
|
+
"pdf-lib": "^1.17.1",
|
|
52
|
+
"react-pdf": "^9.0.0",
|
|
53
|
+
"signature_pad": "^5.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
57
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
61
|
+
"@testing-library/react": "^16.3.0",
|
|
62
|
+
"@types/node": "^24.6.0",
|
|
63
|
+
"@types/react": "^19.1.13",
|
|
64
|
+
"@types/react-dom": "^19.1.9",
|
|
65
|
+
"@vitejs/plugin-react": "^5.0.2",
|
|
66
|
+
"@vitest/browser-playwright": "^4.0.0",
|
|
67
|
+
"happy-dom": "^20.0.0",
|
|
68
|
+
"playwright": "^1.55.0",
|
|
69
|
+
"react": "^19.1.1",
|
|
70
|
+
"react-dom": "^19.1.1",
|
|
71
|
+
"tailwindcss": "^4.1.13",
|
|
72
|
+
"tsup": "^8.5.0",
|
|
73
|
+
"typescript": "^5.9.2",
|
|
74
|
+
"vite": "^7.1.5",
|
|
75
|
+
"vitest": "^4.0.0"
|
|
76
|
+
}
|
|
77
|
+
}
|