@signiphi/pdf-signer 0.2.0-beta.2 → 0.2.0-beta.21
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/assets/viewer.html +1 -5
- package/dist/components/index.js +2746 -901
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +2513 -669
- package/dist/components/index.mjs.map +1 -1
- package/dist/core/index.js +420 -20
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +420 -20
- package/dist/core/index.mjs.map +1 -1
- package/dist/hooks/index.js +506 -211
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +507 -212
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.css +214 -191
- package/dist/index.css.map +1 -1
- package/dist/index.js +3019 -893
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2762 -653
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +202 -172
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/index.js +792 -147
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +777 -148
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +2 -2
- package/scripts/copy-utils.js +14 -3
- package/src/styles/index.css +33 -3
- package/dist/__tests__/helpers/fixtures.d.ts +0 -43
- package/dist/__tests__/helpers/fixtures.d.ts.map +0 -1
- package/dist/__tests__/helpers/mocks.d.ts +0 -333
- package/dist/__tests__/helpers/mocks.d.ts.map +0 -1
- package/dist/__tests__/setup.d.ts +0 -6
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/components/AcknowledgementModal.d.ts +0 -21
- package/dist/components/AcknowledgementModal.d.ts.map +0 -1
- package/dist/components/AcknowledgementsSidebar.d.ts +0 -22
- package/dist/components/AcknowledgementsSidebar.d.ts.map +0 -1
- package/dist/components/AttachmentUpload.d.ts +0 -17
- package/dist/components/AttachmentUpload.d.ts.map +0 -1
- package/dist/components/EditableFieldsPanel.d.ts +0 -30
- package/dist/components/EditableFieldsPanel.d.ts.map +0 -1
- package/dist/components/ErrorBoundary.d.ts +0 -67
- package/dist/components/ErrorBoundary.d.ts.map +0 -1
- package/dist/components/FormFieldsView.d.ts +0 -46
- package/dist/components/FormFieldsView.d.ts.map +0 -1
- package/dist/components/InitialsModal.d.ts +0 -16
- package/dist/components/InitialsModal.d.ts.map +0 -1
- package/dist/components/PdfViewerStyled.d.ts +0 -16
- package/dist/components/PdfViewerStyled.d.ts.map +0 -1
- package/dist/components/PoweredBySigniphi.d.ts +0 -11
- package/dist/components/PoweredBySigniphi.d.ts.map +0 -1
- package/dist/components/RequiredFieldNavigation.d.ts +0 -18
- package/dist/components/RequiredFieldNavigation.d.ts.map +0 -1
- package/dist/components/SignatureCanvas.d.ts +0 -12
- package/dist/components/SignatureCanvas.d.ts.map +0 -1
- package/dist/components/SignatureInitialsBox.d.ts +0 -25
- package/dist/components/SignatureInitialsBox.d.ts.map +0 -1
- package/dist/components/SignatureModal.d.ts +0 -21
- package/dist/components/SignatureModal.d.ts.map +0 -1
- package/dist/components/SigningInstructions.d.ts +0 -12
- package/dist/components/SigningInstructions.d.ts.map +0 -1
- package/dist/components/SubmissionForm.d.ts +0 -52
- package/dist/components/SubmissionForm.d.ts.map +0 -1
- package/dist/components/UnacknowledgedFieldsModal.d.ts +0 -23
- package/dist/components/UnacknowledgedFieldsModal.d.ts.map +0 -1
- package/dist/components/ViewToggleToolbar.d.ts +0 -38
- package/dist/components/ViewToggleToolbar.d.ts.map +0 -1
- package/dist/components/form-fields/CheckboxRenderer.d.ts +0 -10
- package/dist/components/form-fields/CheckboxRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/DateFieldRenderer.d.ts +0 -14
- package/dist/components/form-fields/DateFieldRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/DropdownRenderer.d.ts +0 -14
- package/dist/components/form-fields/DropdownRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/FormFieldRenderer.d.ts +0 -22
- package/dist/components/form-fields/FormFieldRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/InitialsFieldRenderer.d.ts +0 -16
- package/dist/components/form-fields/InitialsFieldRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/RadioGroupRenderer.d.ts +0 -10
- package/dist/components/form-fields/RadioGroupRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/SignatureFieldRenderer.d.ts +0 -16
- package/dist/components/form-fields/SignatureFieldRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/TextFieldRenderer.d.ts +0 -14
- package/dist/components/form-fields/TextFieldRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/TextLabelRenderer.d.ts +0 -14
- package/dist/components/form-fields/TextLabelRenderer.d.ts.map +0 -1
- package/dist/components/form-fields/index.d.ts +0 -14
- package/dist/components/form-fields/index.d.ts.map +0 -1
- package/dist/components/index.d.ts +0 -17
- package/dist/components/index.d.ts.map +0 -1
- package/dist/core/PdfViewerCore.d.ts +0 -19
- package/dist/core/PdfViewerCore.d.ts.map +0 -1
- package/dist/core/SignatureCaptureCore.d.ts +0 -37
- package/dist/core/SignatureCaptureCore.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.d.ts.map +0 -1
- package/dist/hooks/index.d.ts +0 -9
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/useAcknowledgements.d.ts +0 -50
- package/dist/hooks/useAcknowledgements.d.ts.map +0 -1
- package/dist/hooks/useAttachments.d.ts +0 -25
- package/dist/hooks/useAttachments.d.ts.map +0 -1
- package/dist/hooks/useFieldFiltering.d.ts +0 -29
- package/dist/hooks/useFieldFiltering.d.ts.map +0 -1
- package/dist/hooks/useFormFields.d.ts +0 -23
- package/dist/hooks/useFormFields.d.ts.map +0 -1
- package/dist/hooks/useMultiSignerContext.d.ts +0 -25
- package/dist/hooks/useMultiSignerContext.d.ts.map +0 -1
- package/dist/hooks/usePdfViewer.d.ts +0 -52
- package/dist/hooks/usePdfViewer.d.ts.map +0 -1
- package/dist/hooks/useRequiredFieldNavigation.d.ts +0 -16
- package/dist/hooks/useRequiredFieldNavigation.d.ts.map +0 -1
- package/dist/hooks/useSignatureCapture.d.ts +0 -17
- package/dist/hooks/useSignatureCapture.d.ts.map +0 -1
- package/dist/hooks/useSignatures.d.ts +0 -29
- package/dist/hooks/useSignatures.d.ts.map +0 -1
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +0 -1
- package/dist/integrations/index.d.ts +0 -6
- package/dist/integrations/index.d.ts.map +0 -1
- package/dist/integrations/next-config.d.ts +0 -46
- package/dist/integrations/next-config.d.ts.map +0 -1
- package/dist/integrations/vite-plugin.d.ts +0 -48
- package/dist/integrations/vite-plugin.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -3
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/ui/accordion.d.ts +0 -8
- package/dist/lib/ui/accordion.d.ts.map +0 -1
- package/dist/lib/ui/alert.d.ts +0 -9
- package/dist/lib/ui/alert.d.ts.map +0 -1
- package/dist/lib/ui/button.d.ts +0 -12
- package/dist/lib/ui/button.d.ts.map +0 -1
- package/dist/lib/ui/calendar.d.ts +0 -10
- package/dist/lib/ui/calendar.d.ts.map +0 -1
- package/dist/lib/ui/card.d.ts +0 -9
- package/dist/lib/ui/card.d.ts.map +0 -1
- package/dist/lib/ui/checkbox.d.ts +0 -5
- package/dist/lib/ui/checkbox.d.ts.map +0 -1
- package/dist/lib/ui/dialog.d.ts +0 -20
- package/dist/lib/ui/dialog.d.ts.map +0 -1
- package/dist/lib/ui/index.d.ts +0 -13
- package/dist/lib/ui/index.d.ts.map +0 -1
- package/dist/lib/ui/input.d.ts +0 -6
- package/dist/lib/ui/input.d.ts.map +0 -1
- package/dist/lib/ui/label.d.ts +0 -6
- package/dist/lib/ui/label.d.ts.map +0 -1
- package/dist/lib/ui/popover.d.ts +0 -7
- package/dist/lib/ui/popover.d.ts.map +0 -1
- package/dist/lib/ui/radio-group.d.ts +0 -6
- package/dist/lib/ui/radio-group.d.ts.map +0 -1
- package/dist/lib/ui/select.d.ts +0 -14
- package/dist/lib/ui/select.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -278
- package/dist/types/index.d.ts.map +0 -1
- package/dist/utils/attachment-validators.d.ts +0 -118
- package/dist/utils/attachment-validators.d.ts.map +0 -1
- package/dist/utils/audit-trail.d.ts +0 -27
- package/dist/utils/audit-trail.d.ts.map +0 -1
- package/dist/utils/date-validation.d.ts +0 -30
- package/dist/utils/date-validation.d.ts.map +0 -1
- package/dist/utils/errors.d.ts +0 -106
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/field-extraction.d.ts +0 -36
- package/dist/utils/field-extraction.d.ts.map +0 -1
- package/dist/utils/field-visibility.d.ts +0 -104
- package/dist/utils/field-visibility.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -18
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -16
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/pdf-field-type-helpers.d.ts +0 -78
- package/dist/utils/pdf-field-type-helpers.d.ts.map +0 -1
- package/dist/utils/pdf-helpers.d.ts +0 -38
- package/dist/utils/pdf-helpers.d.ts.map +0 -1
- package/dist/utils/pdf-lib-loader.d.ts +0 -45
- package/dist/utils/pdf-lib-loader.d.ts.map +0 -1
- package/dist/utils/pdf-manipulation.d.ts +0 -93
- package/dist/utils/pdf-manipulation.d.ts.map +0 -1
- package/dist/utils/pdf-metadata.d.ts +0 -41
- package/dist/utils/pdf-metadata.d.ts.map +0 -1
- package/dist/utils/pdf-validators.d.ts +0 -149
- package/dist/utils/pdf-validators.d.ts.map +0 -1
- package/dist/utils/pdf-viewer-filter.d.ts +0 -35
- package/dist/utils/pdf-viewer-filter.d.ts.map +0 -1
- package/dist/utils/pdf-widget-helpers.d.ts +0 -98
- package/dist/utils/pdf-widget-helpers.d.ts.map +0 -1
- package/dist/utils/pdfjs-config.d.ts +0 -56
- package/dist/utils/pdfjs-config.d.ts.map +0 -1
- package/dist/utils/pdfjs-version-check.d.ts +0 -28
- package/dist/utils/pdfjs-version-check.d.ts.map +0 -1
- package/dist/utils/performance-monitor.d.ts +0 -172
- package/dist/utils/performance-monitor.d.ts.map +0 -1
- package/dist/utils/tracking.d.ts +0 -89
- package/dist/utils/tracking.d.ts.map +0 -1
package/dist/hooks/index.js
CHANGED
|
@@ -77,6 +77,7 @@ function validatePdfBytes(pdfBytes) {
|
|
|
77
77
|
}
|
|
78
78
|
function isAutoGeneratedLabel(label) {
|
|
79
79
|
if (!label || !label.trim()) return true;
|
|
80
|
+
const trimmed = label.trim();
|
|
80
81
|
const autoLabels = [
|
|
81
82
|
"Signature",
|
|
82
83
|
"Initials",
|
|
@@ -87,7 +88,65 @@ function isAutoGeneratedLabel(label) {
|
|
|
87
88
|
"Option",
|
|
88
89
|
"Radio"
|
|
89
90
|
];
|
|
90
|
-
|
|
91
|
+
if (autoLabels.includes(trimmed)) return true;
|
|
92
|
+
if (/^\d{10,}$/.test(trimmed)) return true;
|
|
93
|
+
const typeTimestampPattern = new RegExp(
|
|
94
|
+
`^(${autoLabels.join("|")})[\\s_-]?\\d{10,}$`,
|
|
95
|
+
"i"
|
|
96
|
+
);
|
|
97
|
+
if (typeTimestampPattern.test(trimmed)) return true;
|
|
98
|
+
const typeNumberPattern = new RegExp(
|
|
99
|
+
`^(${autoLabels.join("|")})[\\s_-]?\\d{1,3}$`,
|
|
100
|
+
"i"
|
|
101
|
+
);
|
|
102
|
+
if (typeNumberPattern.test(trimmed)) return true;
|
|
103
|
+
const typeWordsJoined = autoLabels.join("|");
|
|
104
|
+
const corruptedPattern = new RegExp(
|
|
105
|
+
`^(${typeWordsJoined})[\\s_-]?\\d{10,}[\\s_-]?(${typeWordsJoined})+`,
|
|
106
|
+
"i"
|
|
107
|
+
);
|
|
108
|
+
if (corruptedPattern.test(trimmed)) return true;
|
|
109
|
+
const repeatedTypePattern = new RegExp(
|
|
110
|
+
`^((${typeWordsJoined})[\\s_-]+)+(${typeWordsJoined})$`,
|
|
111
|
+
"i"
|
|
112
|
+
);
|
|
113
|
+
if (repeatedTypePattern.test(trimmed)) return true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
function hasDrawableLabel(field) {
|
|
117
|
+
if (!field.label || !field.label.trim()) return false;
|
|
118
|
+
if (field.isLabelAutoGenerated) return false;
|
|
119
|
+
if (isAutoGeneratedLabel(field.label)) return false;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
function getFieldDisplayName(field) {
|
|
123
|
+
if (field.label && !isAutoGeneratedLabel(field.label)) {
|
|
124
|
+
return field.label;
|
|
125
|
+
}
|
|
126
|
+
const typeNames = {
|
|
127
|
+
"text": "Text field",
|
|
128
|
+
"signature": "Signature",
|
|
129
|
+
"initials": "Initials",
|
|
130
|
+
"date": "Date field",
|
|
131
|
+
"checkbox": "Checkbox",
|
|
132
|
+
"dropdown": "Dropdown",
|
|
133
|
+
"radio": "Radio selection",
|
|
134
|
+
"radiogroup": "Radio selection",
|
|
135
|
+
"text_label": "Text label"
|
|
136
|
+
};
|
|
137
|
+
if (field.type) {
|
|
138
|
+
const typeName = typeNames[field.type.toLowerCase()];
|
|
139
|
+
if (typeName) {
|
|
140
|
+
return typeName;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (field.name) {
|
|
144
|
+
let cleaned = field.name.replace(/_?(signature|initials|date)$/i, "").replace(/[_-]\d{10,}$/, "").replace(/[_-]/g, " ").trim();
|
|
145
|
+
if (cleaned && !isAutoGeneratedLabel(cleaned)) {
|
|
146
|
+
return cleaned.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return "This field";
|
|
91
150
|
}
|
|
92
151
|
function validateFieldValues(values) {
|
|
93
152
|
const errors = [];
|
|
@@ -234,18 +293,15 @@ function isFieldVisibleToSigner(field, multiSignerContext) {
|
|
|
234
293
|
if (!multiSignerContext.isMultiSigner) {
|
|
235
294
|
return true;
|
|
236
295
|
}
|
|
237
|
-
const { currentSignerEmail
|
|
296
|
+
const { currentSignerEmail } = multiSignerContext;
|
|
238
297
|
if (!field.assignedSignerEmail) {
|
|
239
|
-
return
|
|
298
|
+
return true;
|
|
240
299
|
}
|
|
241
300
|
if (field.assignedSignerEmail === currentSignerEmail) {
|
|
242
301
|
return true;
|
|
243
302
|
}
|
|
244
|
-
if (field.assignedSignerEmail
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
if (field.assignedSignerEmail.includes("signers")) {
|
|
248
|
-
return isFinalSigner;
|
|
303
|
+
if (field.assignedSignerEmail === "to-recipients" || field.assignedSignerEmail === "additional-signers") {
|
|
304
|
+
return true;
|
|
249
305
|
}
|
|
250
306
|
return false;
|
|
251
307
|
}
|
|
@@ -259,18 +315,15 @@ function shouldFlattenField(field, multiSignerContext) {
|
|
|
259
315
|
if (!multiSignerContext.isMultiSigner) {
|
|
260
316
|
return true;
|
|
261
317
|
}
|
|
262
|
-
const { currentSignerEmail
|
|
318
|
+
const { currentSignerEmail } = multiSignerContext;
|
|
263
319
|
if (!field.assignedSignerEmail) {
|
|
264
|
-
return
|
|
320
|
+
return true;
|
|
265
321
|
}
|
|
266
322
|
if (field.assignedSignerEmail === currentSignerEmail) {
|
|
267
323
|
return true;
|
|
268
324
|
}
|
|
269
|
-
if (field.assignedSignerEmail
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
if (field.assignedSignerEmail.includes("signers")) {
|
|
273
|
-
return isFinalSigner;
|
|
325
|
+
if (field.assignedSignerEmail === "to-recipients" || field.assignedSignerEmail === "additional-signers") {
|
|
326
|
+
return true;
|
|
274
327
|
}
|
|
275
328
|
return false;
|
|
276
329
|
}
|
|
@@ -310,18 +363,23 @@ function findPageIndexWithFallback(pages, pageRef) {
|
|
|
310
363
|
|
|
311
364
|
// src/utils/pdf-field-type-helpers.ts
|
|
312
365
|
function detectFieldType(field) {
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
return "text";
|
|
316
|
-
} else if (typeName === "PDFCheckBox") {
|
|
366
|
+
const f = field;
|
|
367
|
+
if (typeof f.check === "function" && typeof f.uncheck === "function") {
|
|
317
368
|
return "checkbox";
|
|
318
|
-
}
|
|
369
|
+
}
|
|
370
|
+
if (typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function") {
|
|
371
|
+
return "radiogroup";
|
|
372
|
+
}
|
|
373
|
+
if (typeof f.select === "function" && typeof f.getOptions === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function")) {
|
|
319
374
|
return "dropdown";
|
|
320
|
-
}
|
|
375
|
+
}
|
|
376
|
+
if (typeof f.getOptions === "function" && typeof f.setOptions === "function" && typeof f.select !== "function") {
|
|
321
377
|
return "optionlist";
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
378
|
+
}
|
|
379
|
+
if (typeof f.getText === "function" && typeof f.setText === "function") {
|
|
380
|
+
return "text";
|
|
381
|
+
}
|
|
382
|
+
if (typeof f.getText !== "function" && typeof f.check !== "function" && typeof f.select !== "function") {
|
|
325
383
|
return "signature";
|
|
326
384
|
}
|
|
327
385
|
return "unknown";
|
|
@@ -330,13 +388,20 @@ function extractFieldValue(field, fieldType) {
|
|
|
330
388
|
try {
|
|
331
389
|
switch (fieldType) {
|
|
332
390
|
case "text":
|
|
391
|
+
case "date":
|
|
333
392
|
return field.getText?.() || "";
|
|
334
393
|
case "checkbox":
|
|
335
394
|
return field.isChecked?.() ? "true" : "false";
|
|
336
395
|
case "dropdown":
|
|
337
|
-
case "optionlist":
|
|
396
|
+
case "optionlist": {
|
|
397
|
+
const selected = field.getSelected?.();
|
|
398
|
+
return Array.isArray(selected) ? selected[0] || "" : String(selected || "");
|
|
399
|
+
}
|
|
338
400
|
case "radiogroup":
|
|
339
|
-
|
|
401
|
+
case "radio": {
|
|
402
|
+
const radioSelected = field.getSelected?.();
|
|
403
|
+
return radioSelected ? String(radioSelected) : "";
|
|
404
|
+
}
|
|
340
405
|
default:
|
|
341
406
|
return "";
|
|
342
407
|
}
|
|
@@ -408,12 +473,16 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
408
473
|
const errors = [];
|
|
409
474
|
for (const field of pdfFormFields) {
|
|
410
475
|
if (field.required) {
|
|
476
|
+
const extractedField = extractedFields?.find((f) => f.name === field.name);
|
|
411
477
|
if (multiSignerContext?.isMultiSigner && extractedFields) {
|
|
412
|
-
const extractedField = extractedFields.find((f) => f.name === field.name);
|
|
413
478
|
if (!extractedField) {
|
|
414
479
|
continue;
|
|
415
480
|
}
|
|
416
481
|
}
|
|
482
|
+
const logicalType = extractedField?.type || field.type;
|
|
483
|
+
if (logicalType === "date" || logicalType === "signature" || logicalType === "initials") {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
417
486
|
let hasValue = false;
|
|
418
487
|
const fieldValue = fieldValues[field.name];
|
|
419
488
|
const signatureValue = signatures[field.name];
|
|
@@ -423,16 +492,17 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
423
492
|
hasValue = !!(signatureValue || fieldValue || mainSignature);
|
|
424
493
|
} else if (field.name.includes("initials")) {
|
|
425
494
|
hasValue = !!(signatureValue || fieldValue || mainInitials);
|
|
495
|
+
} else if (field.type === "checkbox" || logicalType === "checkbox") {
|
|
496
|
+
hasValue = fieldValue === "true" || field.value === "true";
|
|
426
497
|
} else {
|
|
427
498
|
hasValue = !!(fieldValue || field.value);
|
|
428
499
|
}
|
|
429
500
|
if (!hasValue) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
501
|
+
const friendlyName = getFieldDisplayName({
|
|
502
|
+
label: extractedField?.label,
|
|
503
|
+
name: field.name,
|
|
504
|
+
type: logicalType
|
|
505
|
+
});
|
|
436
506
|
errors.push(`${friendlyName} is required`);
|
|
437
507
|
}
|
|
438
508
|
}
|
|
@@ -443,7 +513,80 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
443
513
|
return ["Unable to validate form fields. Please ensure all required fields are completed."];
|
|
444
514
|
}
|
|
445
515
|
}
|
|
446
|
-
|
|
516
|
+
function extractFieldFontSize(fieldName, field, extractedFormFields) {
|
|
517
|
+
const DEFAULT_FONT_SIZE = 10;
|
|
518
|
+
const MIN_FONT_SIZE = 8;
|
|
519
|
+
const MAX_FONT_SIZE = 72;
|
|
520
|
+
const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName);
|
|
521
|
+
if (fieldInfo?.fontSize && fieldInfo.fontSize >= MIN_FONT_SIZE && fieldInfo.fontSize <= MAX_FONT_SIZE) {
|
|
522
|
+
return fieldInfo.fontSize;
|
|
523
|
+
}
|
|
524
|
+
const daFontSize = extractFromDAField(field);
|
|
525
|
+
if (daFontSize !== null) {
|
|
526
|
+
return daFontSize;
|
|
527
|
+
}
|
|
528
|
+
const tuFontSize = extractFromTUField(field);
|
|
529
|
+
if (tuFontSize !== null) {
|
|
530
|
+
return tuFontSize;
|
|
531
|
+
}
|
|
532
|
+
return DEFAULT_FONT_SIZE;
|
|
533
|
+
}
|
|
534
|
+
function extractFromDAField(field) {
|
|
535
|
+
try {
|
|
536
|
+
const fieldWithAcro = field;
|
|
537
|
+
const dict = fieldWithAcro.acroField?.dict;
|
|
538
|
+
if (!dict) return null;
|
|
539
|
+
const daKey = dict.context?.obj?.("DA") || "DA";
|
|
540
|
+
const daEntry = dict.lookup?.(daKey) || dict.get?.(daKey);
|
|
541
|
+
if (!daEntry) return null;
|
|
542
|
+
let daValue = "";
|
|
543
|
+
if (typeof daEntry.decodeText === "function") {
|
|
544
|
+
daValue = daEntry.decodeText();
|
|
545
|
+
} else if (typeof daEntry.asString === "function") {
|
|
546
|
+
daValue = daEntry.asString();
|
|
547
|
+
} else {
|
|
548
|
+
daValue = String(daEntry);
|
|
549
|
+
}
|
|
550
|
+
const daMatch = daValue.match(/\s+(\d+)\s+Tf/i);
|
|
551
|
+
if (daMatch?.[1]) {
|
|
552
|
+
const fontSize = parseInt(daMatch[1], 10);
|
|
553
|
+
if (fontSize >= 8 && fontSize <= 72) {
|
|
554
|
+
return fontSize;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
function extractFromTUField(field) {
|
|
562
|
+
try {
|
|
563
|
+
const fieldWithAcro = field;
|
|
564
|
+
const dict = fieldWithAcro.acroField?.dict;
|
|
565
|
+
if (!dict) return null;
|
|
566
|
+
const tuKey = dict.context?.obj?.("TU") || "TU";
|
|
567
|
+
const tuEntry = dict.lookup?.(tuKey) || dict.get?.(tuKey);
|
|
568
|
+
if (!tuEntry) return null;
|
|
569
|
+
let tuValue = "";
|
|
570
|
+
if (typeof tuEntry.decodeText === "function") {
|
|
571
|
+
tuValue = tuEntry.decodeText();
|
|
572
|
+
} else if (typeof tuEntry.asString === "function") {
|
|
573
|
+
tuValue = tuEntry.asString();
|
|
574
|
+
} else {
|
|
575
|
+
tuValue = String(tuEntry);
|
|
576
|
+
}
|
|
577
|
+
tuValue = tuValue.replace(/^\(|\)$|^<|>$/g, "");
|
|
578
|
+
const match = tuValue.match(/\|fontSize:(\d+)$/);
|
|
579
|
+
if (match?.[1]) {
|
|
580
|
+
const fontSize = parseInt(match[1], 10);
|
|
581
|
+
if (fontSize >= 8 && fontSize <= 72) {
|
|
582
|
+
return fontSize;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {}, _currentSignerEmail, extractedFormFields, metadata, auditTrail, multiSignerContext) {
|
|
447
590
|
try {
|
|
448
591
|
const { PDFDocument, rgb, StandardFonts } = await loadPdfLib();
|
|
449
592
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
@@ -460,39 +603,39 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
460
603
|
continue;
|
|
461
604
|
}
|
|
462
605
|
try {
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
606
|
+
const f = field;
|
|
607
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
608
|
+
const isCheckbox = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
609
|
+
const isRadio = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
610
|
+
const isDropdown = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
611
|
+
if (isTextField) {
|
|
612
|
+
f.setText?.(fieldValue);
|
|
613
|
+
} else if (isCheckbox) {
|
|
469
614
|
if (fieldValue === "true" || fieldValue === "Yes" || fieldValue === "checked" || fieldValue === "On") {
|
|
470
|
-
|
|
615
|
+
f.check?.();
|
|
471
616
|
} else {
|
|
472
|
-
|
|
617
|
+
f.uncheck?.();
|
|
473
618
|
}
|
|
474
|
-
} else if (
|
|
475
|
-
const radioGroup = field;
|
|
619
|
+
} else if (isRadio) {
|
|
476
620
|
if (fieldValue === "true" || fieldValue === "false") {
|
|
477
621
|
continue;
|
|
478
622
|
}
|
|
479
623
|
const idxMatch = fieldValue.match(/__RADIO_OPTION_INDEX_(\d+)__/);
|
|
480
624
|
if (idxMatch && idxMatch[1]) {
|
|
481
625
|
const selectedIndex = parseInt(idxMatch[1], 10);
|
|
482
|
-
const options =
|
|
626
|
+
const options = f.getOptions?.() || [];
|
|
483
627
|
if (selectedIndex >= 0 && selectedIndex < options.length) {
|
|
484
|
-
|
|
628
|
+
f.select?.(options[selectedIndex]);
|
|
485
629
|
}
|
|
486
630
|
} else {
|
|
487
|
-
const options =
|
|
631
|
+
const options = f.getOptions?.() || [];
|
|
488
632
|
if (options.includes(fieldValue)) {
|
|
489
|
-
|
|
633
|
+
f.select?.(fieldValue);
|
|
490
634
|
} else {
|
|
491
635
|
}
|
|
492
636
|
}
|
|
493
|
-
} else if (
|
|
494
|
-
|
|
495
|
-
dropdown.select?.(fieldValue);
|
|
637
|
+
} else if (isDropdown) {
|
|
638
|
+
f.select?.(fieldValue);
|
|
496
639
|
}
|
|
497
640
|
} catch (fieldError) {
|
|
498
641
|
logger.error(`Error setting field "${fieldName}":`, fieldError);
|
|
@@ -500,11 +643,6 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
500
643
|
}
|
|
501
644
|
try {
|
|
502
645
|
form.updateFieldAppearances();
|
|
503
|
-
const PDFName3 = pdfLib.PDFName;
|
|
504
|
-
const acroForm = pdfDoc.catalog.lookup(PDFName3.of("AcroForm"));
|
|
505
|
-
if (acroForm && typeof acroForm === "object" && "set" in acroForm) {
|
|
506
|
-
acroForm.set(PDFName3.of("NeedAppearances"), false);
|
|
507
|
-
}
|
|
508
646
|
} catch (appearanceError) {
|
|
509
647
|
logger.warn("Could not update field appearances:", appearanceError);
|
|
510
648
|
}
|
|
@@ -573,6 +711,20 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
573
711
|
const y = fieldPosition.y;
|
|
574
712
|
const width = Math.max(fieldPosition.width, 80);
|
|
575
713
|
const height = Math.max(fieldPosition.height, 30);
|
|
714
|
+
const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName);
|
|
715
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
716
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
717
|
+
const labelFontSize = Math.min(10, height * 0.4);
|
|
718
|
+
const labelX = x;
|
|
719
|
+
const labelY = y + height + 5;
|
|
720
|
+
page.drawText(fieldInfo.label, {
|
|
721
|
+
x: labelX,
|
|
722
|
+
y: labelY,
|
|
723
|
+
size: labelFontSize,
|
|
724
|
+
font: labelFont,
|
|
725
|
+
color: rgb(0, 0, 0)
|
|
726
|
+
});
|
|
727
|
+
}
|
|
576
728
|
const signatureDims = signatureImage.scaleToFit(width - 4, height - 4);
|
|
577
729
|
const finalX = x;
|
|
578
730
|
const finalY = y;
|
|
@@ -607,7 +759,7 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
607
759
|
console.log("[FLATTEN] After pattern matching in fieldPageMap:", foundInitialsFields);
|
|
608
760
|
if (extractedFormFields && extractedFormFields.length > 0) {
|
|
609
761
|
foundInitialsFields = foundInitialsFields.filter((fieldName) => {
|
|
610
|
-
const fieldInfo = extractedFormFields.find((f) => f.name === fieldName);
|
|
762
|
+
const fieldInfo = extractedFormFields.find((f) => f.name === fieldName) || extractedFormFields.find((f) => f.name === fieldName.replace(/_initials$/i, "")) || extractedFormFields.find((f) => fieldName.startsWith(f.name));
|
|
611
763
|
if (!fieldInfo) return false;
|
|
612
764
|
const isActualInitialsField = fieldInfo.type === "initials" || fieldName.toLowerCase().includes("initials");
|
|
613
765
|
if (!isActualInitialsField) return false;
|
|
@@ -636,6 +788,19 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
636
788
|
const y = fieldPosition.y;
|
|
637
789
|
const height = Math.max(fieldPosition.height, 20);
|
|
638
790
|
const fontSize = Math.min(height * 0.7, 14);
|
|
791
|
+
const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName) || extractedFormFields?.find((f) => f.name === fieldName.replace(/_initials$/i, "")) || extractedFormFields?.find((f) => fieldName.startsWith(f.name)) || extractedFormFields?.find((f) => f.fieldId && fieldName.includes(f.fieldId)) || extractedFormFields?.find((f) => f.type === "initials" && f.name !== "initials_field_main");
|
|
792
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
793
|
+
const labelFontSize = Math.min(10, height * 0.4);
|
|
794
|
+
const labelX = x;
|
|
795
|
+
const labelY = y + height + 5;
|
|
796
|
+
page.drawText(fieldInfo.label, {
|
|
797
|
+
x: labelX,
|
|
798
|
+
y: labelY,
|
|
799
|
+
size: labelFontSize,
|
|
800
|
+
font,
|
|
801
|
+
color: rgb(0, 0, 0)
|
|
802
|
+
});
|
|
803
|
+
}
|
|
639
804
|
const finalX = x + 2;
|
|
640
805
|
const finalY = y + (height - fontSize) / 2;
|
|
641
806
|
console.log("[FLATTEN] \u2705 Drawing initials at:", { x: finalX, y: finalY, fontSize, text: mainInitialsData });
|
|
@@ -678,11 +843,16 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
678
843
|
let flattenedCount = 0;
|
|
679
844
|
for (const field of fieldsToFlatten) {
|
|
680
845
|
const fieldName = field.getName();
|
|
681
|
-
const
|
|
846
|
+
const f = field;
|
|
847
|
+
const isCheckboxField = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
848
|
+
const isRadioField = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
849
|
+
const isDropdownField = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
850
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
851
|
+
const isSignatureType = !isCheckboxField && !isRadioField && !isDropdownField && !isTextField;
|
|
682
852
|
try {
|
|
683
853
|
const userEnteredValue = formFieldValues[fieldName];
|
|
684
|
-
const isSignatureField = fieldName.toLowerCase().includes("signature") ||
|
|
685
|
-
const isInitialsField = fieldName.toLowerCase().includes("initials")
|
|
854
|
+
const isSignatureField = fieldName.toLowerCase().includes("signature") || isSignatureType;
|
|
855
|
+
const isInitialsField = fieldName.toLowerCase().includes("initials");
|
|
686
856
|
if (isSignatureField || isInitialsField) {
|
|
687
857
|
form.removeField(field);
|
|
688
858
|
flattenedCount++;
|
|
@@ -690,15 +860,29 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
690
860
|
}
|
|
691
861
|
const fieldWithWidgets = field;
|
|
692
862
|
const widgets = fieldWithWidgets.acroField?.getWidgets?.() || [];
|
|
693
|
-
if (
|
|
863
|
+
if (isCheckboxField) {
|
|
694
864
|
const stringValue = String(userEnteredValue || "");
|
|
695
865
|
const isChecked = stringValue === "true" || stringValue === "Yes" || stringValue === "On" || stringValue === "checked";
|
|
866
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
867
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
696
868
|
if (isChecked) {
|
|
697
869
|
for (const widget of widgets) {
|
|
698
870
|
const result = getWidgetRectangleAndPage(widget, pages);
|
|
699
871
|
if (!result) continue;
|
|
700
872
|
const { rect, page } = result;
|
|
701
|
-
|
|
873
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
874
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
875
|
+
const labelX = rect.x + rect.width + 5;
|
|
876
|
+
const labelY = rect.y + (rect.height - labelFontSize) / 2;
|
|
877
|
+
page.drawText(fieldInfo.label, {
|
|
878
|
+
x: labelX,
|
|
879
|
+
y: labelY,
|
|
880
|
+
size: labelFontSize,
|
|
881
|
+
font: labelFont,
|
|
882
|
+
color: rgb(0, 0, 0)
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
const checkboxSize = Math.min(rect.width, rect.height);
|
|
702
886
|
const checkboxX = rect.x + (rect.width - checkboxSize) / 2;
|
|
703
887
|
const checkboxY = rect.y + (rect.height - checkboxSize) / 2;
|
|
704
888
|
page.drawRectangle({
|
|
@@ -709,21 +893,51 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
709
893
|
borderColor: rgb(0, 0, 0),
|
|
710
894
|
borderWidth: 1
|
|
711
895
|
});
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const
|
|
896
|
+
const checkFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
897
|
+
const checkFontSize = checkboxSize * 0.7;
|
|
898
|
+
const textWidth = checkFont.widthOfTextAtSize("X", checkFontSize);
|
|
899
|
+
const textHeight = checkFont.heightAtSize(checkFontSize);
|
|
715
900
|
page.drawText("X", {
|
|
716
901
|
x: checkboxX + (checkboxSize - textWidth) / 2,
|
|
717
|
-
y: checkboxY + (checkboxSize - textHeight) / 2 + textHeight * 0.
|
|
718
|
-
size:
|
|
719
|
-
font:
|
|
902
|
+
y: checkboxY + (checkboxSize - textHeight) / 2 + textHeight * 0.15,
|
|
903
|
+
size: checkFontSize,
|
|
904
|
+
font: checkFont,
|
|
720
905
|
color: rgb(0, 0, 0)
|
|
721
906
|
});
|
|
722
907
|
}
|
|
908
|
+
} else {
|
|
909
|
+
for (const widget of widgets) {
|
|
910
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
911
|
+
if (!result) continue;
|
|
912
|
+
const { rect, page } = result;
|
|
913
|
+
const checkboxSize = Math.min(rect.width, rect.height);
|
|
914
|
+
const checkboxX = rect.x + (rect.width - checkboxSize) / 2;
|
|
915
|
+
const checkboxY = rect.y + (rect.height - checkboxSize) / 2;
|
|
916
|
+
page.drawRectangle({
|
|
917
|
+
x: checkboxX,
|
|
918
|
+
y: checkboxY,
|
|
919
|
+
width: checkboxSize,
|
|
920
|
+
height: checkboxSize,
|
|
921
|
+
borderColor: rgb(0, 0, 0),
|
|
922
|
+
borderWidth: 1
|
|
923
|
+
});
|
|
924
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
925
|
+
const labelFontSize = Math.min(12, rect.height * 0.6);
|
|
926
|
+
const labelX = rect.x + rect.width + 5;
|
|
927
|
+
const labelY = rect.y + (rect.height - labelFontSize) / 2;
|
|
928
|
+
page.drawText(fieldInfo.label, {
|
|
929
|
+
x: labelX,
|
|
930
|
+
y: labelY,
|
|
931
|
+
size: labelFontSize,
|
|
932
|
+
font: labelFont,
|
|
933
|
+
color: rgb(0, 0, 0)
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
723
937
|
}
|
|
724
|
-
} else if (
|
|
725
|
-
const radioGroup =
|
|
726
|
-
const fieldInfo = extractedFormFields?.find((
|
|
938
|
+
} else if (isRadioField) {
|
|
939
|
+
const radioGroup = f;
|
|
940
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
727
941
|
let actualSelectedValue = "";
|
|
728
942
|
try {
|
|
729
943
|
actualSelectedValue = radioGroup.getSelected?.() || "";
|
|
@@ -768,14 +982,15 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
768
982
|
const result = getWidgetRectangleAndPage(widget, pages);
|
|
769
983
|
if (!result) continue;
|
|
770
984
|
const { rect, page, pageIndex } = result;
|
|
771
|
-
if (i === 0 && fieldInfo
|
|
985
|
+
if (i === 0 && fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
772
986
|
const groupLabelKey = `${pageIndex}-${fieldName}-grouplabel`;
|
|
773
987
|
if (!drawnGroupLabels.has(groupLabelKey)) {
|
|
988
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
774
989
|
const labelY = rect.y + rect.height + 3;
|
|
775
990
|
page.drawText(fieldInfo.label, {
|
|
776
991
|
x: rect.x,
|
|
777
992
|
y: labelY,
|
|
778
|
-
size:
|
|
993
|
+
size: labelFontSize,
|
|
779
994
|
font: labelFont,
|
|
780
995
|
color: rgb(0, 0, 0)
|
|
781
996
|
});
|
|
@@ -783,22 +998,21 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
783
998
|
}
|
|
784
999
|
} else if (i === 0 && isAutoGeneratedLabel(fieldInfo?.label || "")) {
|
|
785
1000
|
}
|
|
786
|
-
const radioSize = Math.min(rect.width, rect.height)
|
|
787
|
-
const
|
|
788
|
-
const
|
|
1001
|
+
const radioSize = Math.min(rect.width, rect.height);
|
|
1002
|
+
const centerX = rect.x + rect.width / 2;
|
|
1003
|
+
const centerY = rect.y + rect.height / 2;
|
|
789
1004
|
page.drawCircle({
|
|
790
|
-
x:
|
|
791
|
-
y:
|
|
792
|
-
size: radioSize,
|
|
1005
|
+
x: centerX,
|
|
1006
|
+
y: centerY,
|
|
1007
|
+
size: radioSize / 2,
|
|
793
1008
|
borderColor: rgb(0, 0, 0),
|
|
794
1009
|
borderWidth: 1
|
|
795
1010
|
});
|
|
796
1011
|
if (i === selectedIndex) {
|
|
797
|
-
const circleSize = radioSize * 0.5;
|
|
798
1012
|
page.drawCircle({
|
|
799
|
-
x:
|
|
800
|
-
y:
|
|
801
|
-
size:
|
|
1013
|
+
x: centerX,
|
|
1014
|
+
y: centerY,
|
|
1015
|
+
size: radioSize / 4,
|
|
802
1016
|
color: rgb(0, 0, 0)
|
|
803
1017
|
});
|
|
804
1018
|
}
|
|
@@ -808,8 +1022,8 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
808
1022
|
if (optionText && !drawnOptionLabels.has(optionKey)) {
|
|
809
1023
|
page.drawText(optionText, {
|
|
810
1024
|
x: rect.x + rect.width + 4,
|
|
811
|
-
y: rect.y + (rect.height -
|
|
812
|
-
size:
|
|
1025
|
+
y: rect.y + (rect.height - 7) / 2,
|
|
1026
|
+
size: 7,
|
|
813
1027
|
font: labelFont,
|
|
814
1028
|
color: rgb(0, 0, 0)
|
|
815
1029
|
});
|
|
@@ -819,17 +1033,31 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
819
1033
|
}
|
|
820
1034
|
if (fieldInfo?.options) {
|
|
821
1035
|
}
|
|
822
|
-
} else if (
|
|
1036
|
+
} else if (isDropdownField) {
|
|
823
1037
|
const selectedValue = userEnteredValue ? String(userEnteredValue) : "";
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1038
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
1039
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
1040
|
+
for (const widget of widgets) {
|
|
1041
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
1042
|
+
if (!result) continue;
|
|
1043
|
+
const { rect, page } = result;
|
|
1044
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
1045
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1046
|
+
const labelX = rect.x;
|
|
1047
|
+
const labelY = rect.y + rect.height + 5;
|
|
1048
|
+
page.drawText(fieldInfo.label, {
|
|
1049
|
+
x: labelX,
|
|
1050
|
+
y: labelY,
|
|
1051
|
+
size: labelFontSize,
|
|
1052
|
+
font: labelFont,
|
|
1053
|
+
color: rgb(0, 0, 0)
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
if (selectedValue && selectedValue.trim()) {
|
|
829
1057
|
page.drawText(selectedValue, {
|
|
830
1058
|
x: rect.x + 2,
|
|
831
1059
|
y: rect.y + 2,
|
|
832
|
-
size:
|
|
1060
|
+
size: 14,
|
|
833
1061
|
font: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
834
1062
|
color: rgb(0, 0, 0)
|
|
835
1063
|
});
|
|
@@ -837,15 +1065,31 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
837
1065
|
}
|
|
838
1066
|
} else {
|
|
839
1067
|
const fieldValue = userEnteredValue ? String(userEnteredValue) : "";
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1068
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
1069
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
1070
|
+
const isDateField = fieldName.toLowerCase().includes("date") || fieldName.toLowerCase().includes("_date");
|
|
1071
|
+
const fontSize = isDateField ? 14 : extractFieldFontSize(fieldName, field, extractedFormFields);
|
|
1072
|
+
for (const widget of widgets) {
|
|
1073
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
1074
|
+
if (!result) continue;
|
|
1075
|
+
const { rect, page } = result;
|
|
1076
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
1077
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1078
|
+
const labelX = rect.x;
|
|
1079
|
+
const labelY = rect.y + rect.height + 5;
|
|
1080
|
+
page.drawText(fieldInfo.label, {
|
|
1081
|
+
x: labelX,
|
|
1082
|
+
y: labelY,
|
|
1083
|
+
size: labelFontSize,
|
|
1084
|
+
font: labelFont,
|
|
1085
|
+
color: rgb(0, 0, 0)
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
if (fieldValue && fieldValue.trim()) {
|
|
845
1089
|
page.drawText(fieldValue, {
|
|
846
1090
|
x: rect.x + 2,
|
|
847
1091
|
y: rect.y + 2,
|
|
848
|
-
size:
|
|
1092
|
+
size: fontSize,
|
|
849
1093
|
font: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
850
1094
|
color: rgb(0, 0, 0)
|
|
851
1095
|
});
|
|
@@ -1003,7 +1247,6 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1003
1247
|
const fields = form.getFields();
|
|
1004
1248
|
const visibleFields = [];
|
|
1005
1249
|
let hasAnyInitialsFields = false;
|
|
1006
|
-
let hasInitialsFieldsForCurrentSigner = false;
|
|
1007
1250
|
for (const field of fields) {
|
|
1008
1251
|
const fieldName = field.getName();
|
|
1009
1252
|
const fieldWithExtensions = field;
|
|
@@ -1029,28 +1272,31 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1029
1272
|
};
|
|
1030
1273
|
})();
|
|
1031
1274
|
let fieldType = "text" /* TEXT */;
|
|
1032
|
-
const
|
|
1275
|
+
const f = field;
|
|
1276
|
+
const isCheckboxField = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
1277
|
+
const isRadioField = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
1278
|
+
const isDropdownField = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
1279
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
1280
|
+
const isSignatureType = !isCheckboxField && !isRadioField && !isDropdownField && !isTextField;
|
|
1033
1281
|
const cleanFieldName = fieldName.replace(/_signature$|_initials$|_date$/i, "");
|
|
1034
|
-
const isActualSignatureField =
|
|
1035
|
-
const isActualInitialsField = cleanFieldName.toLowerCase().includes("initials") && !
|
|
1282
|
+
const isActualSignatureField = isSignatureType || cleanFieldName.toLowerCase().includes("signature") && !isCheckboxField && !isRadioField;
|
|
1283
|
+
const isActualInitialsField = cleanFieldName.toLowerCase().includes("initials") && !isCheckboxField && !isRadioField;
|
|
1036
1284
|
if (isActualSignatureField) {
|
|
1037
1285
|
fieldType = "signature" /* SIGNATURE */;
|
|
1038
1286
|
} else if (isActualInitialsField) {
|
|
1039
1287
|
fieldType = "initials" /* INITIALS */;
|
|
1040
1288
|
hasAnyInitialsFields = true;
|
|
1041
|
-
if (currentSignerEmail && fieldMetadata.signer === currentSignerEmail) {
|
|
1042
|
-
hasInitialsFieldsForCurrentSigner = true;
|
|
1043
|
-
}
|
|
1044
1289
|
} else if (cleanFieldName.toLowerCase().includes("date")) {
|
|
1045
1290
|
fieldType = "date" /* DATE */;
|
|
1046
|
-
} else if (
|
|
1291
|
+
} else if (isCheckboxField) {
|
|
1047
1292
|
fieldType = "checkbox" /* CHECKBOX */;
|
|
1048
|
-
} else if (
|
|
1293
|
+
} else if (isDropdownField) {
|
|
1049
1294
|
fieldType = "dropdown" /* DROPDOWN */;
|
|
1050
|
-
} else if (
|
|
1295
|
+
} else if (isRadioField) {
|
|
1051
1296
|
fieldType = "radio" /* RADIO */;
|
|
1052
1297
|
}
|
|
1053
1298
|
let displayLabel = fieldMetadata.label || "";
|
|
1299
|
+
let isLabelAutoGenerated = false;
|
|
1054
1300
|
if (!displayLabel || !displayLabel.trim()) {
|
|
1055
1301
|
try {
|
|
1056
1302
|
const tuLabel = extractTULabel(fieldWithExtensions);
|
|
@@ -1062,6 +1308,7 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1062
1308
|
}
|
|
1063
1309
|
if (!displayLabel || !displayLabel.trim()) {
|
|
1064
1310
|
displayLabel = generateFallbackLabel(cleanFieldName, fieldType);
|
|
1311
|
+
isLabelAutoGenerated = true;
|
|
1065
1312
|
}
|
|
1066
1313
|
const esignField = {
|
|
1067
1314
|
id: fieldName,
|
|
@@ -1073,6 +1320,8 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1073
1320
|
type: fieldType,
|
|
1074
1321
|
label: displayLabel,
|
|
1075
1322
|
// Use friendly label for display
|
|
1323
|
+
isLabelAutoGenerated,
|
|
1324
|
+
// True when label was generated from field name (should not be drawn on PDF)
|
|
1076
1325
|
position: { x: 0, y: 0, width: 100, height: 30, page: 1 },
|
|
1077
1326
|
// Default position
|
|
1078
1327
|
required: fieldWithExtensions.isRequired?.() ?? false,
|
|
@@ -1093,6 +1342,13 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1093
1342
|
logger.warn("Error extracting options for field:", error);
|
|
1094
1343
|
}
|
|
1095
1344
|
}
|
|
1345
|
+
try {
|
|
1346
|
+
const currentValue = extractFieldValue(f, esignField.type);
|
|
1347
|
+
if (currentValue) {
|
|
1348
|
+
esignField.defaultValue = currentValue;
|
|
1349
|
+
}
|
|
1350
|
+
} catch {
|
|
1351
|
+
}
|
|
1096
1352
|
visibleFields.push(esignField);
|
|
1097
1353
|
}
|
|
1098
1354
|
}
|
|
@@ -1109,11 +1365,8 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1109
1365
|
placeholder: "Draw or upload your signature",
|
|
1110
1366
|
assignedSignerEmail: currentSignerEmail
|
|
1111
1367
|
});
|
|
1112
|
-
const shouldCreateInitialsField =
|
|
1368
|
+
const shouldCreateInitialsField = hasAnyInitialsFields;
|
|
1113
1369
|
if (shouldCreateInitialsField) {
|
|
1114
|
-
if (hasInitialsFieldsForCurrentSigner) {
|
|
1115
|
-
} else {
|
|
1116
|
-
}
|
|
1117
1370
|
mainFields.push({
|
|
1118
1371
|
id: "initials_field_main",
|
|
1119
1372
|
fieldId: "initials_field_main",
|
|
@@ -1200,8 +1453,12 @@ function decodeFieldName(fieldName) {
|
|
|
1200
1453
|
}
|
|
1201
1454
|
function generateFallbackLabel(decodedFieldName, fieldType) {
|
|
1202
1455
|
let displayLabel = decodedFieldName;
|
|
1203
|
-
|
|
1204
|
-
|
|
1456
|
+
let prevLabel = "";
|
|
1457
|
+
while (displayLabel !== prevLabel) {
|
|
1458
|
+
prevLabel = displayLabel;
|
|
1459
|
+
displayLabel = displayLabel.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1460
|
+
}
|
|
1461
|
+
if (/^(text|signature|initials|date|checkbox|radio|dropdown)_\d+/i.test(displayLabel)) {
|
|
1205
1462
|
if (fieldType === "signature" /* SIGNATURE */) return "Signature";
|
|
1206
1463
|
if (fieldType === "initials" /* INITIALS */) return "Initials";
|
|
1207
1464
|
if (fieldType === "date" /* DATE */) return "Date";
|
|
@@ -1209,9 +1466,9 @@ function generateFallbackLabel(decodedFieldName, fieldType) {
|
|
|
1209
1466
|
if (fieldType === "checkbox" /* CHECKBOX */) return "Checkbox";
|
|
1210
1467
|
if (fieldType === "dropdown" /* DROPDOWN */) return "Dropdown";
|
|
1211
1468
|
if (fieldType === "radio" /* RADIO */) return "Option";
|
|
1212
|
-
|
|
1213
|
-
displayLabel = displayLabel.replace(/_/g, " ").replace(/\s+\d+$/g, "").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1469
|
+
if (fieldType === "text_label" /* TEXT_LABEL */) return "Text Label";
|
|
1214
1470
|
}
|
|
1471
|
+
displayLabel = displayLabel.replace(/_/g, " ").replace(/\s+\d+$/g, "").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1215
1472
|
return displayLabel;
|
|
1216
1473
|
}
|
|
1217
1474
|
function extractTULabel(field) {
|
|
@@ -1306,13 +1563,13 @@ var PdfProcessingError = class _PdfProcessingError extends Error {
|
|
|
1306
1563
|
|
|
1307
1564
|
// src/utils/attachment-validators.ts
|
|
1308
1565
|
var DEFAULT_ATTACHMENT_CONSTRAINTS = {
|
|
1309
|
-
maxFileSize:
|
|
1310
|
-
//
|
|
1566
|
+
maxFileSize: 25 * 1024 * 1024,
|
|
1567
|
+
// 25MB
|
|
1311
1568
|
maxTotalSize: 50 * 1024 * 1024,
|
|
1312
1569
|
// 50MB
|
|
1313
1570
|
maxFiles: 10,
|
|
1314
1571
|
allowedTypes: ["image/*", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
|
|
1315
|
-
allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".doc", ".docx"]
|
|
1572
|
+
allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".doc", ".docx"]
|
|
1316
1573
|
};
|
|
1317
1574
|
function validateFile(file, constraints = DEFAULT_ATTACHMENT_CONSTRAINTS) {
|
|
1318
1575
|
const errors = [];
|
|
@@ -1376,6 +1633,35 @@ function isValidISODate(value) {
|
|
|
1376
1633
|
}
|
|
1377
1634
|
|
|
1378
1635
|
// src/utils/pdf-viewer-filter.ts
|
|
1636
|
+
function robustlyRemoveField(form, field) {
|
|
1637
|
+
try {
|
|
1638
|
+
const fieldWithAcro = field;
|
|
1639
|
+
if (fieldWithAcro.acroField && typeof fieldWithAcro.acroField.getWidgets === "function") {
|
|
1640
|
+
let widgets = fieldWithAcro.acroField.getWidgets() || [];
|
|
1641
|
+
let safetyCounter = 0;
|
|
1642
|
+
const maxIterations = widgets.length + 5;
|
|
1643
|
+
while (widgets.length > 0 && safetyCounter < maxIterations) {
|
|
1644
|
+
try {
|
|
1645
|
+
const widgetIndex = widgets.length - 1;
|
|
1646
|
+
fieldWithAcro.acroField.removeWidget?.(widgetIndex);
|
|
1647
|
+
widgets = fieldWithAcro.acroField.getWidgets() || [];
|
|
1648
|
+
} catch {
|
|
1649
|
+
break;
|
|
1650
|
+
}
|
|
1651
|
+
safetyCounter++;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
form.removeField(field);
|
|
1655
|
+
return true;
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
try {
|
|
1658
|
+
form.removeField(field);
|
|
1659
|
+
return true;
|
|
1660
|
+
} catch {
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1379
1665
|
async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
1380
1666
|
try {
|
|
1381
1667
|
const labelFont = await pdfDoc.embedFont("Helvetica-Bold");
|
|
@@ -1385,8 +1671,7 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1385
1671
|
for (const field of fieldsToLabel) {
|
|
1386
1672
|
const isRadioField = field.type === "radio" /* RADIO */;
|
|
1387
1673
|
const hasRadioOptions = isRadioField && field.options && field.options.length > 0;
|
|
1388
|
-
|
|
1389
|
-
if (!hasCustomLabel && !hasRadioOptions) {
|
|
1674
|
+
if (!hasDrawableLabel(field) && !hasRadioOptions) {
|
|
1390
1675
|
continue;
|
|
1391
1676
|
}
|
|
1392
1677
|
const pdfField = form.getFieldMaybe(field.name);
|
|
@@ -1397,27 +1682,30 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1397
1682
|
const widgets = fieldWithExtensions.acroField?.getWidgets?.() || [];
|
|
1398
1683
|
if (widgets.length === 0) continue;
|
|
1399
1684
|
if (isRadioField) {
|
|
1400
|
-
if (
|
|
1685
|
+
if (hasDrawableLabel(field)) {
|
|
1401
1686
|
const firstWidget = widgets[0];
|
|
1402
|
-
if (
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1687
|
+
if (firstWidget) {
|
|
1688
|
+
const firstRect = firstWidget.getRectangle?.();
|
|
1689
|
+
if (firstRect) {
|
|
1690
|
+
const pageRef = firstWidget.P?.();
|
|
1691
|
+
const pageIndex = findPageIndexWithFallback(pages, pageRef);
|
|
1692
|
+
if (pageIndex >= 0) {
|
|
1693
|
+
const page = pages[pageIndex];
|
|
1694
|
+
if (page) {
|
|
1695
|
+
const groupLabelKey = `${pageIndex}-${field.name}-grouplabel`;
|
|
1696
|
+
if (!drawnOnce.has(groupLabelKey)) {
|
|
1697
|
+
const radioLabelFontSize = Math.min(10, firstRect.height * 0.4);
|
|
1698
|
+
const labelY = firstRect.y + firstRect.height + 5;
|
|
1699
|
+
page.drawText(field.label, {
|
|
1700
|
+
x: firstRect.x,
|
|
1701
|
+
y: labelY,
|
|
1702
|
+
size: radioLabelFontSize,
|
|
1703
|
+
font: labelFont,
|
|
1704
|
+
color: rgb(0, 0, 0)
|
|
1705
|
+
});
|
|
1706
|
+
drawnOnce.add(groupLabelKey);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1421
1709
|
}
|
|
1422
1710
|
}
|
|
1423
1711
|
}
|
|
@@ -1430,17 +1718,16 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1430
1718
|
if (!rect) continue;
|
|
1431
1719
|
const pageRef = widget.P?.();
|
|
1432
1720
|
const pageIndex = findPageIndexWithFallback(pages, pageRef);
|
|
1433
|
-
if (pageIndex >= 0) {
|
|
1721
|
+
if (pageIndex >= 0 && pageIndex < pages.length) {
|
|
1434
1722
|
const page = pages[pageIndex];
|
|
1435
1723
|
if (!page) continue;
|
|
1436
|
-
const
|
|
1437
|
-
const optionText = optionTexts[i] || optionTexts[0];
|
|
1724
|
+
const optionText = field.options[i];
|
|
1438
1725
|
const optionKey = `${pageIndex}-${field.name}-opt-${i}`;
|
|
1439
1726
|
if (optionText && !drawnOnce.has(optionKey)) {
|
|
1440
1727
|
page.drawText(optionText, {
|
|
1441
|
-
x: rect.x + rect.width +
|
|
1442
|
-
y: rect.y + (rect.height -
|
|
1443
|
-
size:
|
|
1728
|
+
x: rect.x + rect.width + 4,
|
|
1729
|
+
y: rect.y + (rect.height - 7) / 2,
|
|
1730
|
+
size: 7,
|
|
1444
1731
|
font: labelFont,
|
|
1445
1732
|
color: rgb(0, 0, 0)
|
|
1446
1733
|
});
|
|
@@ -1461,12 +1748,13 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1461
1748
|
if (!page) continue;
|
|
1462
1749
|
const key = `${pageIndex}-${field.name}`;
|
|
1463
1750
|
if (!drawnOnce.has(key)) {
|
|
1464
|
-
if (
|
|
1751
|
+
if (hasDrawableLabel(field)) {
|
|
1752
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1465
1753
|
if (field.type === "checkbox" /* CHECKBOX */) {
|
|
1466
1754
|
page.drawText(field.label, {
|
|
1467
1755
|
x: rect.x + rect.width + 5,
|
|
1468
|
-
y: rect.y + rect.height
|
|
1469
|
-
size:
|
|
1756
|
+
y: rect.y + (rect.height - labelFontSize) / 2,
|
|
1757
|
+
size: labelFontSize,
|
|
1470
1758
|
font: labelFont,
|
|
1471
1759
|
color: rgb(0, 0, 0)
|
|
1472
1760
|
});
|
|
@@ -1475,9 +1763,9 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1475
1763
|
page.drawText(field.label, {
|
|
1476
1764
|
x: rect.x,
|
|
1477
1765
|
y: labelY,
|
|
1478
|
-
size:
|
|
1766
|
+
size: labelFontSize,
|
|
1479
1767
|
font: labelFont,
|
|
1480
|
-
color: rgb(0
|
|
1768
|
+
color: rgb(0, 0, 0)
|
|
1481
1769
|
});
|
|
1482
1770
|
}
|
|
1483
1771
|
drawnOnce.add(key);
|
|
@@ -1538,7 +1826,7 @@ async function filterPdfForCurrentSigner(pdfBytes, allFields, multiSignerContext
|
|
|
1538
1826
|
try {
|
|
1539
1827
|
const pdfField = form.getFieldMaybe(field.name);
|
|
1540
1828
|
if (pdfField) {
|
|
1541
|
-
form
|
|
1829
|
+
robustlyRemoveField(form, pdfField);
|
|
1542
1830
|
removedCount++;
|
|
1543
1831
|
} else {
|
|
1544
1832
|
notFoundCount++;
|
|
@@ -1572,7 +1860,7 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1572
1860
|
const [originalPdfBytes, setOriginalPdfBytes] = react.useState(null);
|
|
1573
1861
|
const [_viewerPdfBytes, setViewerPdfBytes] = react.useState(null);
|
|
1574
1862
|
const [extractedFields, setExtractedFields] = react.useState([]);
|
|
1575
|
-
const loadPdf = react.useCallback(async (url) => {
|
|
1863
|
+
const loadPdf = react.useCallback(async (url, signerEmailForExtraction) => {
|
|
1576
1864
|
setIsLoading(true);
|
|
1577
1865
|
setError(null);
|
|
1578
1866
|
setIsLoaded(false);
|
|
@@ -1585,8 +1873,22 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1585
1873
|
}
|
|
1586
1874
|
const bytes = await urlToPdfBytes(url);
|
|
1587
1875
|
setOriginalPdfBytes(bytes);
|
|
1588
|
-
|
|
1589
|
-
|
|
1876
|
+
const fields = await extractVisibleFormFields(bytes, signerEmailForExtraction);
|
|
1877
|
+
setExtractedFields(fields);
|
|
1878
|
+
const filteredBytes = await filterPdfForCurrentSigner(
|
|
1879
|
+
bytes,
|
|
1880
|
+
fields,
|
|
1881
|
+
multiSignerContext || {
|
|
1882
|
+
isMultiSigner: false,
|
|
1883
|
+
currentSigner: null,
|
|
1884
|
+
currentSignerEmail: "",
|
|
1885
|
+
isPrimarySigner: true,
|
|
1886
|
+
isFinalSigner: true
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
setViewerPdfBytes(filteredBytes);
|
|
1890
|
+
const blobUrl = createPdfBlobUrl(filteredBytes);
|
|
1891
|
+
await viewerRef.current?.loadPdf(blobUrl);
|
|
1590
1892
|
} catch (err) {
|
|
1591
1893
|
const errorMessage = err instanceof Error ? err.message : "Failed to load PDF";
|
|
1592
1894
|
logger.error("Error loading PDF:", err);
|
|
@@ -1594,7 +1896,7 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1594
1896
|
setOriginalPdfBytes(null);
|
|
1595
1897
|
setViewerPdfBytes(null);
|
|
1596
1898
|
}
|
|
1597
|
-
}, []);
|
|
1899
|
+
}, [multiSignerContext]);
|
|
1598
1900
|
const handleLoad = react.useCallback(() => {
|
|
1599
1901
|
setIsLoading(false);
|
|
1600
1902
|
setIsLoaded(true);
|
|
@@ -1630,52 +1932,15 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1630
1932
|
return await viewerRef.current.saveDocument();
|
|
1631
1933
|
}, []);
|
|
1632
1934
|
const extractFormFields = react.useCallback(
|
|
1633
|
-
async (
|
|
1634
|
-
if (
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
const fields = await extractVisibleFormFields(
|
|
1639
|
-
originalPdfBytes,
|
|
1640
|
-
currentSignerEmail
|
|
1641
|
-
);
|
|
1642
|
-
setExtractedFields(fields);
|
|
1643
|
-
if (multiSignerContext?.isMultiSigner) {
|
|
1644
|
-
const filteredBytes = await filterPdfForCurrentSigner(
|
|
1645
|
-
originalPdfBytes,
|
|
1646
|
-
fields,
|
|
1647
|
-
multiSignerContext
|
|
1648
|
-
);
|
|
1649
|
-
setViewerPdfBytes(filteredBytes);
|
|
1650
|
-
const filteredBlobUrl = createPdfBlobUrl(filteredBytes);
|
|
1651
|
-
await viewerRef.current?.loadPdf(filteredBlobUrl);
|
|
1652
|
-
} else {
|
|
1653
|
-
const labeledBytes = await filterPdfForCurrentSigner(
|
|
1654
|
-
originalPdfBytes,
|
|
1655
|
-
fields,
|
|
1656
|
-
multiSignerContext || {
|
|
1657
|
-
isMultiSigner: false,
|
|
1658
|
-
currentSigner: null,
|
|
1659
|
-
currentSignerEmail: "",
|
|
1660
|
-
isPrimarySigner: true,
|
|
1661
|
-
isFinalSigner: true
|
|
1662
|
-
}
|
|
1663
|
-
);
|
|
1664
|
-
setViewerPdfBytes(labeledBytes);
|
|
1665
|
-
const labeledBlobUrl = createPdfBlobUrl(labeledBytes);
|
|
1666
|
-
await viewerRef.current?.loadPdf(labeledBlobUrl);
|
|
1667
|
-
}
|
|
1668
|
-
if (viewerRef.current && fields.length > 0) {
|
|
1669
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1670
|
-
viewerRef.current.injectPlaceholders(fields);
|
|
1671
|
-
}
|
|
1672
|
-
return fields;
|
|
1673
|
-
} catch (err) {
|
|
1674
|
-
const errorMessage = err instanceof Error ? err.message : "Failed to extract form fields";
|
|
1675
|
-
throw new Error(errorMessage);
|
|
1935
|
+
async (_currentSignerEmail) => {
|
|
1936
|
+
if (viewerRef.current && extractedFields.length > 0) {
|
|
1937
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1938
|
+
viewerRef.current.injectPlaceholders(extractedFields);
|
|
1939
|
+
viewerRef.current.injectRadioLabels(extractedFields);
|
|
1676
1940
|
}
|
|
1941
|
+
return extractedFields;
|
|
1677
1942
|
},
|
|
1678
|
-
[
|
|
1943
|
+
[extractedFields]
|
|
1679
1944
|
);
|
|
1680
1945
|
const fillPdf = react.useCallback(
|
|
1681
1946
|
async (fieldValues, signatures, currentSignerEmail, metadata, auditTrail) => {
|
|
@@ -1818,6 +2083,20 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1818
2083
|
}
|
|
1819
2084
|
function useFormFields(fields = []) {
|
|
1820
2085
|
const [fieldValues, setFieldValues] = react.useState({});
|
|
2086
|
+
const seededFieldsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2087
|
+
react.useEffect(() => {
|
|
2088
|
+
if (fields.length === 0) return;
|
|
2089
|
+
const defaults = {};
|
|
2090
|
+
for (const field of fields) {
|
|
2091
|
+
if (field.defaultValue && field.defaultValue.trim() && !seededFieldsRef.current.has(field.id)) {
|
|
2092
|
+
defaults[field.id] = field.defaultValue;
|
|
2093
|
+
seededFieldsRef.current.add(field.id);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
if (Object.keys(defaults).length > 0) {
|
|
2097
|
+
setFieldValues((prev) => ({ ...defaults, ...prev }));
|
|
2098
|
+
}
|
|
2099
|
+
}, [fields]);
|
|
1821
2100
|
const [errors, setErrors] = react.useState([]);
|
|
1822
2101
|
const [touched, setTouched] = react.useState({});
|
|
1823
2102
|
const updateField = react.useCallback((fieldId, value) => {
|
|
@@ -1857,14 +2136,14 @@ function useFormFields(fields = []) {
|
|
|
1857
2136
|
if (!value || value.trim() === "") {
|
|
1858
2137
|
newErrors.push({
|
|
1859
2138
|
field: fieldId,
|
|
1860
|
-
message: `${field
|
|
2139
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1861
2140
|
});
|
|
1862
2141
|
}
|
|
1863
2142
|
}
|
|
1864
2143
|
if (value && field.maxLength && value.length > field.maxLength) {
|
|
1865
2144
|
newErrors.push({
|
|
1866
2145
|
field: fieldId,
|
|
1867
|
-
message: `${field
|
|
2146
|
+
message: `${getFieldDisplayName(field)} must be at most ${field.maxLength} characters`
|
|
1868
2147
|
});
|
|
1869
2148
|
}
|
|
1870
2149
|
if (value && field.type === "date") {
|
|
@@ -1890,13 +2169,13 @@ function useFormFields(fields = []) {
|
|
|
1890
2169
|
if (!signatures[field.id]) {
|
|
1891
2170
|
newErrors.push({
|
|
1892
2171
|
field: field.id,
|
|
1893
|
-
message: `${field
|
|
2172
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1894
2173
|
});
|
|
1895
2174
|
}
|
|
1896
2175
|
} else if (!value2 || value2.trim() === "") {
|
|
1897
2176
|
newErrors.push({
|
|
1898
2177
|
field: field.id,
|
|
1899
|
-
message: `${field
|
|
2178
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1900
2179
|
});
|
|
1901
2180
|
}
|
|
1902
2181
|
}
|
|
@@ -1904,7 +2183,7 @@ function useFormFields(fields = []) {
|
|
|
1904
2183
|
if (value && field.maxLength && value.length > field.maxLength) {
|
|
1905
2184
|
newErrors.push({
|
|
1906
2185
|
field: field.id,
|
|
1907
|
-
message: `${field
|
|
2186
|
+
message: `${getFieldDisplayName(field)} must be at most ${field.maxLength} characters`
|
|
1908
2187
|
});
|
|
1909
2188
|
}
|
|
1910
2189
|
if (value && field.type === "date") {
|
|
@@ -2078,7 +2357,7 @@ function useSignatures() {
|
|
|
2078
2357
|
for (const field of fields) {
|
|
2079
2358
|
if (field.required && (field.type === "signature" || field.type === "initials")) {
|
|
2080
2359
|
if (!signaturesToCheck[field.id]) {
|
|
2081
|
-
errors.push(`${field
|
|
2360
|
+
errors.push(`${getFieldDisplayName(field)} is required`);
|
|
2082
2361
|
}
|
|
2083
2362
|
}
|
|
2084
2363
|
}
|
|
@@ -2196,6 +2475,13 @@ function useAttachments(options = {}) {
|
|
|
2196
2475
|
errors.push(...validation.errors);
|
|
2197
2476
|
continue;
|
|
2198
2477
|
}
|
|
2478
|
+
const isDuplicate = [...attachments, ...newAttachments].some(
|
|
2479
|
+
(att) => att.name.toLowerCase() === file.name.toLowerCase()
|
|
2480
|
+
);
|
|
2481
|
+
if (isDuplicate) {
|
|
2482
|
+
errors.push(`"${file.name}" is already attached`);
|
|
2483
|
+
continue;
|
|
2484
|
+
}
|
|
2199
2485
|
const currentTotalSize = [...attachments, ...newAttachments].reduce((sum, att) => sum + att.size, 0);
|
|
2200
2486
|
if (currentTotalSize + file.size > constraints.maxTotalSize) {
|
|
2201
2487
|
const maxTotalMB = (constraints.maxTotalSize / 1024 / 1024).toFixed(1);
|
|
@@ -2246,6 +2532,9 @@ function useAttachments(options = {}) {
|
|
|
2246
2532
|
setAttachments([]);
|
|
2247
2533
|
setValidationErrors([]);
|
|
2248
2534
|
}, []);
|
|
2535
|
+
const clearValidationErrors = react.useCallback(() => {
|
|
2536
|
+
setValidationErrors([]);
|
|
2537
|
+
}, []);
|
|
2249
2538
|
const getTotalSize = react.useCallback(() => {
|
|
2250
2539
|
return attachments.reduce((sum, att) => sum + att.size, 0);
|
|
2251
2540
|
}, [attachments]);
|
|
@@ -2277,6 +2566,7 @@ function useAttachments(options = {}) {
|
|
|
2277
2566
|
addFiles,
|
|
2278
2567
|
removeAttachment,
|
|
2279
2568
|
clearAttachments,
|
|
2569
|
+
clearValidationErrors,
|
|
2280
2570
|
// Utilities
|
|
2281
2571
|
getTotalSize,
|
|
2282
2572
|
formatSize,
|
|
@@ -2309,6 +2599,10 @@ function useAcknowledgements(fields) {
|
|
|
2309
2599
|
const [acknowledgedMap, setAcknowledgedMap] = react.useState(
|
|
2310
2600
|
/* @__PURE__ */ new Map()
|
|
2311
2601
|
);
|
|
2602
|
+
const acknowledgedMapRef = react.useRef(acknowledgedMap);
|
|
2603
|
+
react.useEffect(() => {
|
|
2604
|
+
acknowledgedMapRef.current = acknowledgedMap;
|
|
2605
|
+
}, [acknowledgedMap]);
|
|
2312
2606
|
const getFieldsWithAcknowledgements = react.useCallback((fieldsToFilter) => {
|
|
2313
2607
|
return fieldsToFilter.filter(
|
|
2314
2608
|
(field) => field.acknowledgements && field.acknowledgements.length > 0
|
|
@@ -2320,26 +2614,27 @@ function useAcknowledgements(fields) {
|
|
|
2320
2614
|
const fieldAcks = newMap.get(fieldId) || /* @__PURE__ */ new Set();
|
|
2321
2615
|
fieldAcks.add(ackId);
|
|
2322
2616
|
newMap.set(fieldId, fieldAcks);
|
|
2617
|
+
acknowledgedMapRef.current = newMap;
|
|
2323
2618
|
return newMap;
|
|
2324
2619
|
});
|
|
2325
2620
|
}, []);
|
|
2326
2621
|
const isAcknowledged = react.useCallback((fieldId, ackId) => {
|
|
2327
|
-
const fieldAcks =
|
|
2622
|
+
const fieldAcks = acknowledgedMapRef.current.get(fieldId);
|
|
2328
2623
|
if (!fieldAcks) return false;
|
|
2329
2624
|
if (ackId) {
|
|
2330
2625
|
return fieldAcks.has(ackId);
|
|
2331
2626
|
}
|
|
2332
2627
|
return fieldAcks.size > 0;
|
|
2333
|
-
}, [
|
|
2628
|
+
}, []);
|
|
2334
2629
|
const isFieldFullyAcknowledged = react.useCallback((fieldId) => {
|
|
2335
2630
|
const field = fields.find((f) => f.id === fieldId);
|
|
2336
2631
|
if (!field || !field.acknowledgements || field.acknowledgements.length === 0) {
|
|
2337
2632
|
return true;
|
|
2338
2633
|
}
|
|
2339
|
-
const fieldAcks =
|
|
2634
|
+
const fieldAcks = acknowledgedMapRef.current.get(fieldId);
|
|
2340
2635
|
if (!fieldAcks) return false;
|
|
2341
2636
|
return field.acknowledgements.every((ack) => fieldAcks.has(ack.id));
|
|
2342
|
-
}, [fields
|
|
2637
|
+
}, [fields]);
|
|
2343
2638
|
const getFieldAcknowledgementProgress = react.useCallback((fieldId) => {
|
|
2344
2639
|
const field = fields.find((f) => f.id === fieldId);
|
|
2345
2640
|
if (!field || !field.acknowledgements || field.acknowledgements.length === 0) {
|