@signiphi/pdf-signer 0.2.0-beta.26 → 0.2.0-beta.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -0
- package/assets/viewer-annotations.html +1444 -0
- package/dist/components/AnnotationChatPanel.d.ts +43 -0
- package/dist/components/AnnotationChatPanel.d.ts.map +1 -0
- package/dist/components/AnnotationLayer.d.ts +36 -0
- package/dist/components/AnnotationLayer.d.ts.map +1 -0
- package/dist/components/AnnotationToolProperties.d.ts +13 -0
- package/dist/components/AnnotationToolProperties.d.ts.map +1 -0
- package/dist/components/AnnotationToolbar.d.ts +24 -0
- package/dist/components/AnnotationToolbar.d.ts.map +1 -0
- package/dist/components/PdfDocument.d.ts +69 -0
- package/dist/components/PdfDocument.d.ts.map +1 -0
- package/dist/components/PdfJsProvider.d.ts +43 -0
- package/dist/components/PdfJsProvider.d.ts.map +1 -0
- package/dist/components/PdfViewerStyled.d.ts +2 -0
- package/dist/components/PdfViewerStyled.d.ts.map +1 -1
- package/dist/components/SignatureTypeInput.d.ts.map +1 -1
- package/dist/components/SubmissionForm.d.ts +63 -3
- package/dist/components/SubmissionForm.d.ts.map +1 -1
- package/dist/components/form-fields/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/form-fields/InitialsFieldRenderer.d.ts.map +1 -1
- package/dist/components/form-fields/SignatureFieldRenderer.d.ts.map +1 -1
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3899 -831
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3895 -834
- package/dist/components/index.mjs.map +1 -1
- package/dist/core/PdfViewerCore.d.ts +2 -0
- package/dist/core/PdfViewerCore.d.ts.map +1 -1
- package/dist/core/index.js +126 -48
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +127 -49
- package/dist/core/index.mjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1507 -23
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +1507 -24
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/hooks/useAnnotationChat.d.ts +81 -0
- package/dist/hooks/useAnnotationChat.d.ts.map +1 -0
- package/dist/hooks/usePdfViewer.d.ts +1 -1
- package/dist/hooks/usePdfViewer.d.ts.map +1 -1
- package/dist/index.css +452 -6
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3862 -843
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3855 -846
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +392 -18
- package/dist/types/annotations.d.ts +212 -0
- package/dist/types/annotations.d.ts.map +1 -0
- package/dist/types/index.d.ts +27 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs +20 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/annotation-bridge.d.ts +185 -0
- package/dist/utils/annotation-bridge.d.ts.map +1 -0
- package/dist/utils/annotation-export.d.ts +30 -0
- package/dist/utils/annotation-export.d.ts.map +1 -0
- package/dist/utils/annotation-pdf-save.d.ts +66 -0
- package/dist/utils/annotation-pdf-save.d.ts.map +1 -0
- package/dist/utils/attachment-validators.d.ts +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +262 -23
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +256 -24
- package/dist/utils/index.mjs.map +1 -1
- package/dist/utils/pdf-metadata.d.ts.map +1 -1
- package/dist/utils/pdf-validators.d.ts +4 -4
- package/dist/utils/pdfjs-config.d.ts +12 -0
- package/dist/utils/pdfjs-config.d.ts.map +1 -1
- package/dist/utils/pdfjs-version-check.d.ts.map +1 -1
- package/dist/utils/performance-monitor.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/copy-utils.js +8 -0
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Flexible React components for PDF viewing, form filling, and signature capture.
|
|
|
7
7
|
- 📄 **PDF Viewing** - Render PDFs with PDF.js viewer
|
|
8
8
|
- ✍️ **Signature Capture** - Draw or upload signatures
|
|
9
9
|
- 📝 **Form Filling** - Fill PDF form fields
|
|
10
|
+
- 💬 **Annotation Chat** - Threaded highlights, ink, and free-text with sender/receiver review round-trips
|
|
10
11
|
- 🎨 **Flexible Styling** - Use headless components or pre-styled components
|
|
11
12
|
- 📱 **Responsive** - Works on desktop and mobile devices
|
|
12
13
|
- 🔧 **TypeScript** - Fully typed API
|
|
@@ -1138,6 +1139,259 @@ For implementation details, troubleshooting, and advanced topics, see:
|
|
|
1138
1139
|
|
|
1139
1140
|
---
|
|
1140
1141
|
|
|
1142
|
+
## Annotation Chat & Review Round-Trips
|
|
1143
|
+
|
|
1144
|
+
`SubmissionForm` can host an interactive annotation panel on top of the PDF
|
|
1145
|
+
viewer. Receivers can highlight, free-draw, drop free-text notes, and reply
|
|
1146
|
+
to threads; senders can review submissions, reply, add their own
|
|
1147
|
+
annotations, and volley the document back. The flow is opt-in and the
|
|
1148
|
+
state is the consumer's responsibility — the form emits change events,
|
|
1149
|
+
the host app persists.
|
|
1150
|
+
|
|
1151
|
+
### Two roles, one component
|
|
1152
|
+
|
|
1153
|
+
The same `SubmissionForm` renders for both sides of a review round-trip.
|
|
1154
|
+
The role is gated by the **`role`** prop (route-driven, NOT identity-driven):
|
|
1155
|
+
|
|
1156
|
+
| `role` | Sign tab? | Can annotate? | Submit-for-Review? |
|
|
1157
|
+
|--------------|-----------|---------------|--------------------|
|
|
1158
|
+
| `'signer'` | yes | yes | yes (volley back) |
|
|
1159
|
+
| `'reviewer'` | no | yes | yes (volley back) |
|
|
1160
|
+
|
|
1161
|
+
Set `role` per-route in your host app — e.g. the receiver's signing
|
|
1162
|
+
URL renders with `role="signer"`, the sender's review URL renders with
|
|
1163
|
+
`role="reviewer"`. Don't drive it from `currentAuthor.name`; that would
|
|
1164
|
+
couple the role to a display string.
|
|
1165
|
+
|
|
1166
|
+
> **Note:** the deprecated `reviewMode` boolean is still accepted —
|
|
1167
|
+
> `reviewMode={true}` maps to `role="reviewer"`. Prefer `role` in new
|
|
1168
|
+
> code; `reviewMode` will be removed in a future major.
|
|
1169
|
+
|
|
1170
|
+
### The `pdfUrl` / `pdfUrlForSigning` contract
|
|
1171
|
+
|
|
1172
|
+
Annotations are baked into the PDF on submit (`Submit for Review`). The
|
|
1173
|
+
receiver's *signing* operation must run against a clean copy — otherwise
|
|
1174
|
+
the signed PDF carries the review marks burned into page content.
|
|
1175
|
+
|
|
1176
|
+
- `pdfUrl` — what the user sees in the viewer.
|
|
1177
|
+
- `pdfUrlForSigning` — the original/prepared PDF the final fill runs against.
|
|
1178
|
+
|
|
1179
|
+
When `enableAnnotationChat` or `reviewMode` is true, **always pass both**.
|
|
1180
|
+
The form logs an error (and throws in development) when `pdfUrlForSigning`
|
|
1181
|
+
is missing, so the misuse surfaces immediately rather than producing a
|
|
1182
|
+
corrupted signed PDF later.
|
|
1183
|
+
|
|
1184
|
+
### Round-trip example
|
|
1185
|
+
|
|
1186
|
+
```tsx
|
|
1187
|
+
import {
|
|
1188
|
+
SubmissionForm,
|
|
1189
|
+
type AnnotationThread,
|
|
1190
|
+
type AnnotationChatConfig,
|
|
1191
|
+
} from '@signiphi/pdf-signer';
|
|
1192
|
+
|
|
1193
|
+
function ReceiverSigningPage({ document, currentUser, savedAnnotations }) {
|
|
1194
|
+
const [annotations, setAnnotations] = useState<AnnotationThread[]>(
|
|
1195
|
+
savedAnnotations ?? []
|
|
1196
|
+
);
|
|
1197
|
+
|
|
1198
|
+
const annotationChatConfig: AnnotationChatConfig = {
|
|
1199
|
+
currentAuthor: {
|
|
1200
|
+
id: currentUser.id,
|
|
1201
|
+
name: currentUser.name,
|
|
1202
|
+
email: currentUser.email,
|
|
1203
|
+
},
|
|
1204
|
+
initialAnnotations: annotations,
|
|
1205
|
+
onAnnotationsChange: setAnnotations,
|
|
1206
|
+
enabledTools: ['highlight', 'freehand_highlight', 'ink', 'freetext'],
|
|
1207
|
+
enableReplies: true,
|
|
1208
|
+
enableResolve: true,
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
return (
|
|
1212
|
+
<SubmissionForm
|
|
1213
|
+
pdfUrl={document.bakedReviewPdfUrl ?? document.preparedPdfUrl}
|
|
1214
|
+
pdfUrlForSigning={document.preparedPdfUrl} // clean source
|
|
1215
|
+
enableAnnotationChat
|
|
1216
|
+
annotationChatConfig={annotationChatConfig}
|
|
1217
|
+
role="signer" // receiver = signer
|
|
1218
|
+
onRequestReview={async (data) => {
|
|
1219
|
+
await api.submitForReview(document.id, data);
|
|
1220
|
+
}}
|
|
1221
|
+
onSubmit={async (data) => {
|
|
1222
|
+
await api.submitSigned(document.id, data);
|
|
1223
|
+
}}
|
|
1224
|
+
// ...other props
|
|
1225
|
+
/>
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function SenderReviewPage({ document, currentUser, latestAnnotations }) {
|
|
1230
|
+
const [annotations, setAnnotations] = useState<AnnotationThread[]>(
|
|
1231
|
+
latestAnnotations
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
return (
|
|
1235
|
+
<SubmissionForm
|
|
1236
|
+
pdfUrl={document.bakedReviewPdfUrl}
|
|
1237
|
+
pdfUrlForSigning={document.preparedPdfUrl}
|
|
1238
|
+
enableAnnotationChat
|
|
1239
|
+
role="reviewer" // sender = reviewer
|
|
1240
|
+
annotationChatConfig={{
|
|
1241
|
+
currentAuthor: { id: currentUser.id, name: currentUser.name, email: currentUser.email },
|
|
1242
|
+
initialAnnotations: annotations,
|
|
1243
|
+
onAnnotationsChange: setAnnotations,
|
|
1244
|
+
enabledTools: ['highlight', 'ink', 'freetext'],
|
|
1245
|
+
enableReplies: true,
|
|
1246
|
+
}}
|
|
1247
|
+
requestReviewButtonLabel="Send back to receiver"
|
|
1248
|
+
onRequestReview={async (data) => {
|
|
1249
|
+
await api.respondToReview(document.id, data);
|
|
1250
|
+
}}
|
|
1251
|
+
onSubmit={async () => {}} // unused in reviewMode
|
|
1252
|
+
/>
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
### `initialAnnotations` shape
|
|
1258
|
+
|
|
1259
|
+
`AnnotationChatConfig.initialAnnotations` accepts `AnnotationThread[]`.
|
|
1260
|
+
Round-trip the same shape via `onAnnotationsChange`:
|
|
1261
|
+
|
|
1262
|
+
```ts
|
|
1263
|
+
type AnnotationThread = {
|
|
1264
|
+
annotation: Annotation; // primary annotation (highlight, ink, etc.)
|
|
1265
|
+
replies: AnnotationReply[]; // chat replies, threaded via PDF /IRT entries
|
|
1266
|
+
};
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
Each `Annotation` carries a `pdfJsSerializedData` blob — populated by the
|
|
1270
|
+
bridge when the editor is created/modified. **Persist that blob with the
|
|
1271
|
+
annotation.** It contains the PDF user-space rect/inkList/quadPoints used
|
|
1272
|
+
when baking. If you store annotations through a non-PDF.js path (e.g.
|
|
1273
|
+
imported JSON without serialized data) `embedAnnotationsInPdf` will skip
|
|
1274
|
+
those threads on save — see "Surfacing dropped annotations" below.
|
|
1275
|
+
|
|
1276
|
+
### Surfacing dropped annotations
|
|
1277
|
+
|
|
1278
|
+
When the host calls `embedAnnotationsInPdf` directly (e.g. to render a
|
|
1279
|
+
final reviewable PDF), threads missing PDF user-space data are returned
|
|
1280
|
+
in the `skipped` array:
|
|
1281
|
+
|
|
1282
|
+
```ts
|
|
1283
|
+
import { embedAnnotationsInPdf } from '@signiphi/pdf-signer';
|
|
1284
|
+
|
|
1285
|
+
const { pdfBytes, skipped } = await embedAnnotationsInPdf(rawPdfBytes, threads, {
|
|
1286
|
+
// Either inspect skipped after the fact:
|
|
1287
|
+
onSkipped: (s) => toast.warn(`Dropped ${s.length} annotation(s)`),
|
|
1288
|
+
// ...or hard-fail:
|
|
1289
|
+
// throwOnSkip: true,
|
|
1290
|
+
});
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Pre-flight check: every annotation that should be persisted must carry
|
|
1294
|
+
`annotation.pdfJsSerializedData` by the time the user clicks submit.
|
|
1295
|
+
|
|
1296
|
+
### Stamp (image) tool
|
|
1297
|
+
|
|
1298
|
+
The `'stamp'` tool opens a native file picker. Uploads are validated
|
|
1299
|
+
client-side before they reach PDF.js: only `image/*` MIME types are
|
|
1300
|
+
accepted, and files larger than **10 MB** are rejected. Oversized or
|
|
1301
|
+
non-image selections are dropped silently with a console warning — the
|
|
1302
|
+
consumer doesn't need to gate the picker.
|
|
1303
|
+
|
|
1304
|
+
### Composable building blocks (greenfield consumers)
|
|
1305
|
+
|
|
1306
|
+
`<SubmissionForm>` is a back-compat preset bundling viewer + signing +
|
|
1307
|
+
annotations. New consumers who want their own UI can compose smaller
|
|
1308
|
+
primitives instead:
|
|
1309
|
+
|
|
1310
|
+
| Component / hook | Purpose |
|
|
1311
|
+
|------------------------|----------------------------------------------------------------------|
|
|
1312
|
+
| `<PdfJsProvider>` | Supplies PDF.js config (worker / viewer paths) to the subtree. |
|
|
1313
|
+
| `<PdfDocument>` | Renders the viewer and exposes its ref + load state via context. |
|
|
1314
|
+
| `<AnnotationLayer>` | Opt-in annotation chat; render-prop API on top of `useAnnotationChat`.|
|
|
1315
|
+
| `usePdfDocument()` | Read the surrounding `<PdfDocument>` context (viewer ref, URLs). |
|
|
1316
|
+
| `usePdfJsConfig()` | Read the active PDF.js config from a provider or the global default. |
|
|
1317
|
+
|
|
1318
|
+
```tsx
|
|
1319
|
+
import {
|
|
1320
|
+
PdfJsProvider,
|
|
1321
|
+
PdfDocument,
|
|
1322
|
+
AnnotationLayer,
|
|
1323
|
+
AnnotationChatPanel,
|
|
1324
|
+
} from '@signiphi/pdf-signer';
|
|
1325
|
+
|
|
1326
|
+
function MyReviewer({ pdfUrl, originalUrl, currentAuthor, savedAnnotations }) {
|
|
1327
|
+
return (
|
|
1328
|
+
<PdfJsProvider config={{ viewerBasePath: '/static/pdfjs' }}>
|
|
1329
|
+
<PdfDocument pdfUrl={pdfUrl} pdfUrlForSigning={originalUrl} enableAnnotationEditor>
|
|
1330
|
+
<AnnotationLayer
|
|
1331
|
+
role="reviewer"
|
|
1332
|
+
config={{
|
|
1333
|
+
currentAuthor,
|
|
1334
|
+
initialAnnotations: savedAnnotations,
|
|
1335
|
+
onAnnotationsChange: (anns) => persist(anns),
|
|
1336
|
+
}}
|
|
1337
|
+
>
|
|
1338
|
+
{(api) => (
|
|
1339
|
+
<AnnotationChatPanel
|
|
1340
|
+
annotations={api.annotations}
|
|
1341
|
+
activeAnnotationId={api.activeAnnotationId}
|
|
1342
|
+
onAddReply={api.addReply}
|
|
1343
|
+
onResolveThread={api.resolveThread}
|
|
1344
|
+
onUnresolveThread={api.unresolveThread}
|
|
1345
|
+
onSelectAnnotation={api.setActiveAnnotation}
|
|
1346
|
+
onDeleteAnnotation={api.removeAnnotation}
|
|
1347
|
+
currentAuthor={currentAuthor}
|
|
1348
|
+
enabledTools={['highlight', 'ink', 'freetext']}
|
|
1349
|
+
enableReplies
|
|
1350
|
+
enableResolve
|
|
1351
|
+
/>
|
|
1352
|
+
)}
|
|
1353
|
+
</AnnotationLayer>
|
|
1354
|
+
</PdfDocument>
|
|
1355
|
+
</PdfJsProvider>
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
For the all-in-one experience (signing + annotations + multi-signer +
|
|
1361
|
+
attachments), continue to use `<SubmissionForm>` — it's the supported
|
|
1362
|
+
preset for the full review/signing flow.
|
|
1363
|
+
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
### Headless usage (advanced)
|
|
1367
|
+
|
|
1368
|
+
`useAnnotationChat`, `AnnotationBridge`, and `embedAnnotationsInPdf` are
|
|
1369
|
+
exported as building blocks, but the hook layer ships with one tight
|
|
1370
|
+
coupling that consumers must respect:
|
|
1371
|
+
|
|
1372
|
+
**Iframe contract.** `useAnnotationChat` drives an `AnnotationBridge`
|
|
1373
|
+
that reaches into a PDF.js viewer iframe to deserialize/inject editors.
|
|
1374
|
+
The discovery routine looks for an `<iframe title="PDF Viewer">` in the
|
|
1375
|
+
DOM and a `viewerRef` whose `current.getPDFViewerApplication()` returns
|
|
1376
|
+
the same `PDFViewerApplication` instance running inside that iframe. If
|
|
1377
|
+
you frame your own viewer (custom shell, embedded `<embed>`, etc.) the
|
|
1378
|
+
hook will not find the iframe and the bridge will silently noop.
|
|
1379
|
+
|
|
1380
|
+
If your custom UI cannot satisfy that contract, the supported path is to
|
|
1381
|
+
keep `<SubmissionForm>` (or `<PdfViewerStyled>`) at the framing layer and
|
|
1382
|
+
build your own panel UI on top — they expose `viewerRef` and the
|
|
1383
|
+
underlying `pdfViewerApplication` for that purpose. Truly headless
|
|
1384
|
+
support (custom iframe sources, explicit `iframeRef` prop) is on the
|
|
1385
|
+
roadmap; until then treat `useAnnotationChat` as an implementation detail
|
|
1386
|
+
of the framed components.
|
|
1387
|
+
|
|
1388
|
+
The other building blocks are framework-agnostic:
|
|
1389
|
+
- `embedAnnotationsInPdf(bytes, threads, options?)` — pure function.
|
|
1390
|
+
- `AnnotationBridge` — accepts an `iframeRef` directly; safe to drive
|
|
1391
|
+
from any UI as long as the iframe runs the bundled PDF.js viewer.
|
|
1392
|
+
|
|
1393
|
+
---
|
|
1394
|
+
|
|
1141
1395
|
## Multi-Signer Support
|
|
1142
1396
|
|
|
1143
1397
|
The package supports multi-signer workflows where multiple people sign the same document in sequence. Each signer only sees and fills their assigned fields.
|