@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.
Files changed (82) hide show
  1. package/README.md +254 -0
  2. package/assets/viewer-annotations.html +1444 -0
  3. package/dist/components/AnnotationChatPanel.d.ts +43 -0
  4. package/dist/components/AnnotationChatPanel.d.ts.map +1 -0
  5. package/dist/components/AnnotationLayer.d.ts +36 -0
  6. package/dist/components/AnnotationLayer.d.ts.map +1 -0
  7. package/dist/components/AnnotationToolProperties.d.ts +13 -0
  8. package/dist/components/AnnotationToolProperties.d.ts.map +1 -0
  9. package/dist/components/AnnotationToolbar.d.ts +24 -0
  10. package/dist/components/AnnotationToolbar.d.ts.map +1 -0
  11. package/dist/components/PdfDocument.d.ts +69 -0
  12. package/dist/components/PdfDocument.d.ts.map +1 -0
  13. package/dist/components/PdfJsProvider.d.ts +43 -0
  14. package/dist/components/PdfJsProvider.d.ts.map +1 -0
  15. package/dist/components/PdfViewerStyled.d.ts +2 -0
  16. package/dist/components/PdfViewerStyled.d.ts.map +1 -1
  17. package/dist/components/SignatureTypeInput.d.ts.map +1 -1
  18. package/dist/components/SubmissionForm.d.ts +63 -3
  19. package/dist/components/SubmissionForm.d.ts.map +1 -1
  20. package/dist/components/form-fields/FormFieldRenderer.d.ts.map +1 -1
  21. package/dist/components/form-fields/InitialsFieldRenderer.d.ts.map +1 -1
  22. package/dist/components/form-fields/SignatureFieldRenderer.d.ts.map +1 -1
  23. package/dist/components/index.d.ts +5 -0
  24. package/dist/components/index.d.ts.map +1 -1
  25. package/dist/components/index.js +3899 -831
  26. package/dist/components/index.js.map +1 -1
  27. package/dist/components/index.mjs +3895 -834
  28. package/dist/components/index.mjs.map +1 -1
  29. package/dist/core/PdfViewerCore.d.ts +2 -0
  30. package/dist/core/PdfViewerCore.d.ts.map +1 -1
  31. package/dist/core/index.js +126 -48
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/index.mjs +127 -49
  34. package/dist/core/index.mjs.map +1 -1
  35. package/dist/hooks/index.d.ts +1 -0
  36. package/dist/hooks/index.d.ts.map +1 -1
  37. package/dist/hooks/index.js +1507 -23
  38. package/dist/hooks/index.js.map +1 -1
  39. package/dist/hooks/index.mjs +1507 -24
  40. package/dist/hooks/index.mjs.map +1 -1
  41. package/dist/hooks/useAnnotationChat.d.ts +81 -0
  42. package/dist/hooks/useAnnotationChat.d.ts.map +1 -0
  43. package/dist/hooks/usePdfViewer.d.ts +1 -1
  44. package/dist/hooks/usePdfViewer.d.ts.map +1 -1
  45. package/dist/index.css +452 -6
  46. package/dist/index.css.map +1 -1
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3862 -843
  50. package/dist/index.js.map +1 -1
  51. package/dist/index.mjs +3855 -846
  52. package/dist/index.mjs.map +1 -1
  53. package/dist/styles/index.css +392 -18
  54. package/dist/types/annotations.d.ts +212 -0
  55. package/dist/types/annotations.d.ts.map +1 -0
  56. package/dist/types/index.d.ts +27 -2
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/dist/types/index.js +21 -0
  59. package/dist/types/index.js.map +1 -1
  60. package/dist/types/index.mjs +20 -1
  61. package/dist/types/index.mjs.map +1 -1
  62. package/dist/utils/annotation-bridge.d.ts +185 -0
  63. package/dist/utils/annotation-bridge.d.ts.map +1 -0
  64. package/dist/utils/annotation-export.d.ts +30 -0
  65. package/dist/utils/annotation-export.d.ts.map +1 -0
  66. package/dist/utils/annotation-pdf-save.d.ts +66 -0
  67. package/dist/utils/annotation-pdf-save.d.ts.map +1 -0
  68. package/dist/utils/attachment-validators.d.ts +1 -1
  69. package/dist/utils/index.d.ts +2 -0
  70. package/dist/utils/index.d.ts.map +1 -1
  71. package/dist/utils/index.js +262 -23
  72. package/dist/utils/index.js.map +1 -1
  73. package/dist/utils/index.mjs +256 -24
  74. package/dist/utils/index.mjs.map +1 -1
  75. package/dist/utils/pdf-metadata.d.ts.map +1 -1
  76. package/dist/utils/pdf-validators.d.ts +4 -4
  77. package/dist/utils/pdfjs-config.d.ts +12 -0
  78. package/dist/utils/pdfjs-config.d.ts.map +1 -1
  79. package/dist/utils/pdfjs-version-check.d.ts.map +1 -1
  80. package/dist/utils/performance-monitor.d.ts +1 -1
  81. package/package.json +2 -2
  82. 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.