@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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useState, useCallback, useMemo } from 'react';
|
|
1
|
+
import { useRef, useState, useCallback, useEffect, useMemo } from 'react';
|
|
2
2
|
import * as pdfjsLib2 from 'pdfjs-dist';
|
|
3
3
|
import { parseISO, isValid } from 'date-fns';
|
|
4
4
|
|
|
@@ -55,6 +55,7 @@ function validatePdfBytes(pdfBytes) {
|
|
|
55
55
|
}
|
|
56
56
|
function isAutoGeneratedLabel(label) {
|
|
57
57
|
if (!label || !label.trim()) return true;
|
|
58
|
+
const trimmed = label.trim();
|
|
58
59
|
const autoLabels = [
|
|
59
60
|
"Signature",
|
|
60
61
|
"Initials",
|
|
@@ -65,7 +66,65 @@ function isAutoGeneratedLabel(label) {
|
|
|
65
66
|
"Option",
|
|
66
67
|
"Radio"
|
|
67
68
|
];
|
|
68
|
-
|
|
69
|
+
if (autoLabels.includes(trimmed)) return true;
|
|
70
|
+
if (/^\d{10,}$/.test(trimmed)) return true;
|
|
71
|
+
const typeTimestampPattern = new RegExp(
|
|
72
|
+
`^(${autoLabels.join("|")})[\\s_-]?\\d{10,}$`,
|
|
73
|
+
"i"
|
|
74
|
+
);
|
|
75
|
+
if (typeTimestampPattern.test(trimmed)) return true;
|
|
76
|
+
const typeNumberPattern = new RegExp(
|
|
77
|
+
`^(${autoLabels.join("|")})[\\s_-]?\\d{1,3}$`,
|
|
78
|
+
"i"
|
|
79
|
+
);
|
|
80
|
+
if (typeNumberPattern.test(trimmed)) return true;
|
|
81
|
+
const typeWordsJoined = autoLabels.join("|");
|
|
82
|
+
const corruptedPattern = new RegExp(
|
|
83
|
+
`^(${typeWordsJoined})[\\s_-]?\\d{10,}[\\s_-]?(${typeWordsJoined})+`,
|
|
84
|
+
"i"
|
|
85
|
+
);
|
|
86
|
+
if (corruptedPattern.test(trimmed)) return true;
|
|
87
|
+
const repeatedTypePattern = new RegExp(
|
|
88
|
+
`^((${typeWordsJoined})[\\s_-]+)+(${typeWordsJoined})$`,
|
|
89
|
+
"i"
|
|
90
|
+
);
|
|
91
|
+
if (repeatedTypePattern.test(trimmed)) return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function hasDrawableLabel(field) {
|
|
95
|
+
if (!field.label || !field.label.trim()) return false;
|
|
96
|
+
if (field.isLabelAutoGenerated) return false;
|
|
97
|
+
if (isAutoGeneratedLabel(field.label)) return false;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function getFieldDisplayName(field) {
|
|
101
|
+
if (field.label && !isAutoGeneratedLabel(field.label)) {
|
|
102
|
+
return field.label;
|
|
103
|
+
}
|
|
104
|
+
const typeNames = {
|
|
105
|
+
"text": "Text field",
|
|
106
|
+
"signature": "Signature",
|
|
107
|
+
"initials": "Initials",
|
|
108
|
+
"date": "Date field",
|
|
109
|
+
"checkbox": "Checkbox",
|
|
110
|
+
"dropdown": "Dropdown",
|
|
111
|
+
"radio": "Radio selection",
|
|
112
|
+
"radiogroup": "Radio selection",
|
|
113
|
+
"text_label": "Text label"
|
|
114
|
+
};
|
|
115
|
+
if (field.type) {
|
|
116
|
+
const typeName = typeNames[field.type.toLowerCase()];
|
|
117
|
+
if (typeName) {
|
|
118
|
+
return typeName;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (field.name) {
|
|
122
|
+
let cleaned = field.name.replace(/_?(signature|initials|date)$/i, "").replace(/[_-]\d{10,}$/, "").replace(/[_-]/g, " ").trim();
|
|
123
|
+
if (cleaned && !isAutoGeneratedLabel(cleaned)) {
|
|
124
|
+
return cleaned.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return "This field";
|
|
69
128
|
}
|
|
70
129
|
function validateFieldValues(values) {
|
|
71
130
|
const errors = [];
|
|
@@ -212,18 +271,15 @@ function isFieldVisibleToSigner(field, multiSignerContext) {
|
|
|
212
271
|
if (!multiSignerContext.isMultiSigner) {
|
|
213
272
|
return true;
|
|
214
273
|
}
|
|
215
|
-
const { currentSignerEmail
|
|
274
|
+
const { currentSignerEmail } = multiSignerContext;
|
|
216
275
|
if (!field.assignedSignerEmail) {
|
|
217
|
-
return
|
|
276
|
+
return true;
|
|
218
277
|
}
|
|
219
278
|
if (field.assignedSignerEmail === currentSignerEmail) {
|
|
220
279
|
return true;
|
|
221
280
|
}
|
|
222
|
-
if (field.assignedSignerEmail
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
if (field.assignedSignerEmail.includes("signers")) {
|
|
226
|
-
return isFinalSigner;
|
|
281
|
+
if (field.assignedSignerEmail === "to-recipients" || field.assignedSignerEmail === "additional-signers") {
|
|
282
|
+
return true;
|
|
227
283
|
}
|
|
228
284
|
return false;
|
|
229
285
|
}
|
|
@@ -237,18 +293,15 @@ function shouldFlattenField(field, multiSignerContext) {
|
|
|
237
293
|
if (!multiSignerContext.isMultiSigner) {
|
|
238
294
|
return true;
|
|
239
295
|
}
|
|
240
|
-
const { currentSignerEmail
|
|
296
|
+
const { currentSignerEmail } = multiSignerContext;
|
|
241
297
|
if (!field.assignedSignerEmail) {
|
|
242
|
-
return
|
|
298
|
+
return true;
|
|
243
299
|
}
|
|
244
300
|
if (field.assignedSignerEmail === currentSignerEmail) {
|
|
245
301
|
return true;
|
|
246
302
|
}
|
|
247
|
-
if (field.assignedSignerEmail
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
if (field.assignedSignerEmail.includes("signers")) {
|
|
251
|
-
return isFinalSigner;
|
|
303
|
+
if (field.assignedSignerEmail === "to-recipients" || field.assignedSignerEmail === "additional-signers") {
|
|
304
|
+
return true;
|
|
252
305
|
}
|
|
253
306
|
return false;
|
|
254
307
|
}
|
|
@@ -288,18 +341,23 @@ function findPageIndexWithFallback(pages, pageRef) {
|
|
|
288
341
|
|
|
289
342
|
// src/utils/pdf-field-type-helpers.ts
|
|
290
343
|
function detectFieldType(field) {
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
return "text";
|
|
294
|
-
} else if (typeName === "PDFCheckBox") {
|
|
344
|
+
const f = field;
|
|
345
|
+
if (typeof f.check === "function" && typeof f.uncheck === "function") {
|
|
295
346
|
return "checkbox";
|
|
296
|
-
}
|
|
347
|
+
}
|
|
348
|
+
if (typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function") {
|
|
349
|
+
return "radiogroup";
|
|
350
|
+
}
|
|
351
|
+
if (typeof f.select === "function" && typeof f.getOptions === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function")) {
|
|
297
352
|
return "dropdown";
|
|
298
|
-
}
|
|
353
|
+
}
|
|
354
|
+
if (typeof f.getOptions === "function" && typeof f.setOptions === "function" && typeof f.select !== "function") {
|
|
299
355
|
return "optionlist";
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
356
|
+
}
|
|
357
|
+
if (typeof f.getText === "function" && typeof f.setText === "function") {
|
|
358
|
+
return "text";
|
|
359
|
+
}
|
|
360
|
+
if (typeof f.getText !== "function" && typeof f.check !== "function" && typeof f.select !== "function") {
|
|
303
361
|
return "signature";
|
|
304
362
|
}
|
|
305
363
|
return "unknown";
|
|
@@ -308,13 +366,20 @@ function extractFieldValue(field, fieldType) {
|
|
|
308
366
|
try {
|
|
309
367
|
switch (fieldType) {
|
|
310
368
|
case "text":
|
|
369
|
+
case "date":
|
|
311
370
|
return field.getText?.() || "";
|
|
312
371
|
case "checkbox":
|
|
313
372
|
return field.isChecked?.() ? "true" : "false";
|
|
314
373
|
case "dropdown":
|
|
315
|
-
case "optionlist":
|
|
374
|
+
case "optionlist": {
|
|
375
|
+
const selected = field.getSelected?.();
|
|
376
|
+
return Array.isArray(selected) ? selected[0] || "" : String(selected || "");
|
|
377
|
+
}
|
|
316
378
|
case "radiogroup":
|
|
317
|
-
|
|
379
|
+
case "radio": {
|
|
380
|
+
const radioSelected = field.getSelected?.();
|
|
381
|
+
return radioSelected ? String(radioSelected) : "";
|
|
382
|
+
}
|
|
318
383
|
default:
|
|
319
384
|
return "";
|
|
320
385
|
}
|
|
@@ -386,12 +451,16 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
386
451
|
const errors = [];
|
|
387
452
|
for (const field of pdfFormFields) {
|
|
388
453
|
if (field.required) {
|
|
454
|
+
const extractedField = extractedFields?.find((f) => f.name === field.name);
|
|
389
455
|
if (multiSignerContext?.isMultiSigner && extractedFields) {
|
|
390
|
-
const extractedField = extractedFields.find((f) => f.name === field.name);
|
|
391
456
|
if (!extractedField) {
|
|
392
457
|
continue;
|
|
393
458
|
}
|
|
394
459
|
}
|
|
460
|
+
const logicalType = extractedField?.type || field.type;
|
|
461
|
+
if (logicalType === "date" || logicalType === "signature" || logicalType === "initials") {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
395
464
|
let hasValue = false;
|
|
396
465
|
const fieldValue = fieldValues[field.name];
|
|
397
466
|
const signatureValue = signatures[field.name];
|
|
@@ -401,16 +470,17 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
401
470
|
hasValue = !!(signatureValue || fieldValue || mainSignature);
|
|
402
471
|
} else if (field.name.includes("initials")) {
|
|
403
472
|
hasValue = !!(signatureValue || fieldValue || mainInitials);
|
|
473
|
+
} else if (field.type === "checkbox" || logicalType === "checkbox") {
|
|
474
|
+
hasValue = fieldValue === "true" || field.value === "true";
|
|
404
475
|
} else {
|
|
405
476
|
hasValue = !!(fieldValue || field.value);
|
|
406
477
|
}
|
|
407
478
|
if (!hasValue) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
479
|
+
const friendlyName = getFieldDisplayName({
|
|
480
|
+
label: extractedField?.label,
|
|
481
|
+
name: field.name,
|
|
482
|
+
type: logicalType
|
|
483
|
+
});
|
|
414
484
|
errors.push(`${friendlyName} is required`);
|
|
415
485
|
}
|
|
416
486
|
}
|
|
@@ -421,7 +491,80 @@ async function validatePdfFormFields(pdfBytes, fieldValues, signatures, extracte
|
|
|
421
491
|
return ["Unable to validate form fields. Please ensure all required fields are completed."];
|
|
422
492
|
}
|
|
423
493
|
}
|
|
424
|
-
|
|
494
|
+
function extractFieldFontSize(fieldName, field, extractedFormFields) {
|
|
495
|
+
const DEFAULT_FONT_SIZE = 10;
|
|
496
|
+
const MIN_FONT_SIZE = 8;
|
|
497
|
+
const MAX_FONT_SIZE = 72;
|
|
498
|
+
const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName);
|
|
499
|
+
if (fieldInfo?.fontSize && fieldInfo.fontSize >= MIN_FONT_SIZE && fieldInfo.fontSize <= MAX_FONT_SIZE) {
|
|
500
|
+
return fieldInfo.fontSize;
|
|
501
|
+
}
|
|
502
|
+
const daFontSize = extractFromDAField(field);
|
|
503
|
+
if (daFontSize !== null) {
|
|
504
|
+
return daFontSize;
|
|
505
|
+
}
|
|
506
|
+
const tuFontSize = extractFromTUField(field);
|
|
507
|
+
if (tuFontSize !== null) {
|
|
508
|
+
return tuFontSize;
|
|
509
|
+
}
|
|
510
|
+
return DEFAULT_FONT_SIZE;
|
|
511
|
+
}
|
|
512
|
+
function extractFromDAField(field) {
|
|
513
|
+
try {
|
|
514
|
+
const fieldWithAcro = field;
|
|
515
|
+
const dict = fieldWithAcro.acroField?.dict;
|
|
516
|
+
if (!dict) return null;
|
|
517
|
+
const daKey = dict.context?.obj?.("DA") || "DA";
|
|
518
|
+
const daEntry = dict.lookup?.(daKey) || dict.get?.(daKey);
|
|
519
|
+
if (!daEntry) return null;
|
|
520
|
+
let daValue = "";
|
|
521
|
+
if (typeof daEntry.decodeText === "function") {
|
|
522
|
+
daValue = daEntry.decodeText();
|
|
523
|
+
} else if (typeof daEntry.asString === "function") {
|
|
524
|
+
daValue = daEntry.asString();
|
|
525
|
+
} else {
|
|
526
|
+
daValue = String(daEntry);
|
|
527
|
+
}
|
|
528
|
+
const daMatch = daValue.match(/\s+(\d+)\s+Tf/i);
|
|
529
|
+
if (daMatch?.[1]) {
|
|
530
|
+
const fontSize = parseInt(daMatch[1], 10);
|
|
531
|
+
if (fontSize >= 8 && fontSize <= 72) {
|
|
532
|
+
return fontSize;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
function extractFromTUField(field) {
|
|
540
|
+
try {
|
|
541
|
+
const fieldWithAcro = field;
|
|
542
|
+
const dict = fieldWithAcro.acroField?.dict;
|
|
543
|
+
if (!dict) return null;
|
|
544
|
+
const tuKey = dict.context?.obj?.("TU") || "TU";
|
|
545
|
+
const tuEntry = dict.lookup?.(tuKey) || dict.get?.(tuKey);
|
|
546
|
+
if (!tuEntry) return null;
|
|
547
|
+
let tuValue = "";
|
|
548
|
+
if (typeof tuEntry.decodeText === "function") {
|
|
549
|
+
tuValue = tuEntry.decodeText();
|
|
550
|
+
} else if (typeof tuEntry.asString === "function") {
|
|
551
|
+
tuValue = tuEntry.asString();
|
|
552
|
+
} else {
|
|
553
|
+
tuValue = String(tuEntry);
|
|
554
|
+
}
|
|
555
|
+
tuValue = tuValue.replace(/^\(|\)$|^<|>$/g, "");
|
|
556
|
+
const match = tuValue.match(/\|fontSize:(\d+)$/);
|
|
557
|
+
if (match?.[1]) {
|
|
558
|
+
const fontSize = parseInt(match[1], 10);
|
|
559
|
+
if (fontSize >= 8 && fontSize <= 72) {
|
|
560
|
+
return fontSize;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {}, _currentSignerEmail, extractedFormFields, metadata, auditTrail, multiSignerContext) {
|
|
425
568
|
try {
|
|
426
569
|
const { PDFDocument, rgb, StandardFonts } = await loadPdfLib();
|
|
427
570
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
@@ -438,39 +581,39 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
438
581
|
continue;
|
|
439
582
|
}
|
|
440
583
|
try {
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
584
|
+
const f = field;
|
|
585
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
586
|
+
const isCheckbox = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
587
|
+
const isRadio = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
588
|
+
const isDropdown = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
589
|
+
if (isTextField) {
|
|
590
|
+
f.setText?.(fieldValue);
|
|
591
|
+
} else if (isCheckbox) {
|
|
447
592
|
if (fieldValue === "true" || fieldValue === "Yes" || fieldValue === "checked" || fieldValue === "On") {
|
|
448
|
-
|
|
593
|
+
f.check?.();
|
|
449
594
|
} else {
|
|
450
|
-
|
|
595
|
+
f.uncheck?.();
|
|
451
596
|
}
|
|
452
|
-
} else if (
|
|
453
|
-
const radioGroup = field;
|
|
597
|
+
} else if (isRadio) {
|
|
454
598
|
if (fieldValue === "true" || fieldValue === "false") {
|
|
455
599
|
continue;
|
|
456
600
|
}
|
|
457
601
|
const idxMatch = fieldValue.match(/__RADIO_OPTION_INDEX_(\d+)__/);
|
|
458
602
|
if (idxMatch && idxMatch[1]) {
|
|
459
603
|
const selectedIndex = parseInt(idxMatch[1], 10);
|
|
460
|
-
const options =
|
|
604
|
+
const options = f.getOptions?.() || [];
|
|
461
605
|
if (selectedIndex >= 0 && selectedIndex < options.length) {
|
|
462
|
-
|
|
606
|
+
f.select?.(options[selectedIndex]);
|
|
463
607
|
}
|
|
464
608
|
} else {
|
|
465
|
-
const options =
|
|
609
|
+
const options = f.getOptions?.() || [];
|
|
466
610
|
if (options.includes(fieldValue)) {
|
|
467
|
-
|
|
611
|
+
f.select?.(fieldValue);
|
|
468
612
|
} else {
|
|
469
613
|
}
|
|
470
614
|
}
|
|
471
|
-
} else if (
|
|
472
|
-
|
|
473
|
-
dropdown.select?.(fieldValue);
|
|
615
|
+
} else if (isDropdown) {
|
|
616
|
+
f.select?.(fieldValue);
|
|
474
617
|
}
|
|
475
618
|
} catch (fieldError) {
|
|
476
619
|
logger.error(`Error setting field "${fieldName}":`, fieldError);
|
|
@@ -478,11 +621,6 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
478
621
|
}
|
|
479
622
|
try {
|
|
480
623
|
form.updateFieldAppearances();
|
|
481
|
-
const PDFName3 = pdfLib.PDFName;
|
|
482
|
-
const acroForm = pdfDoc.catalog.lookup(PDFName3.of("AcroForm"));
|
|
483
|
-
if (acroForm && typeof acroForm === "object" && "set" in acroForm) {
|
|
484
|
-
acroForm.set(PDFName3.of("NeedAppearances"), false);
|
|
485
|
-
}
|
|
486
624
|
} catch (appearanceError) {
|
|
487
625
|
logger.warn("Could not update field appearances:", appearanceError);
|
|
488
626
|
}
|
|
@@ -551,6 +689,20 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
551
689
|
const y = fieldPosition.y;
|
|
552
690
|
const width = Math.max(fieldPosition.width, 80);
|
|
553
691
|
const height = Math.max(fieldPosition.height, 30);
|
|
692
|
+
const fieldInfo = extractedFormFields?.find((f) => f.name === fieldName);
|
|
693
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
694
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
695
|
+
const labelFontSize = Math.min(10, height * 0.4);
|
|
696
|
+
const labelX = x;
|
|
697
|
+
const labelY = y + height + 5;
|
|
698
|
+
page.drawText(fieldInfo.label, {
|
|
699
|
+
x: labelX,
|
|
700
|
+
y: labelY,
|
|
701
|
+
size: labelFontSize,
|
|
702
|
+
font: labelFont,
|
|
703
|
+
color: rgb(0, 0, 0)
|
|
704
|
+
});
|
|
705
|
+
}
|
|
554
706
|
const signatureDims = signatureImage.scaleToFit(width - 4, height - 4);
|
|
555
707
|
const finalX = x;
|
|
556
708
|
const finalY = y;
|
|
@@ -585,7 +737,7 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
585
737
|
console.log("[FLATTEN] After pattern matching in fieldPageMap:", foundInitialsFields);
|
|
586
738
|
if (extractedFormFields && extractedFormFields.length > 0) {
|
|
587
739
|
foundInitialsFields = foundInitialsFields.filter((fieldName) => {
|
|
588
|
-
const fieldInfo = extractedFormFields.find((f) => f.name === fieldName);
|
|
740
|
+
const fieldInfo = extractedFormFields.find((f) => f.name === fieldName) || extractedFormFields.find((f) => f.name === fieldName.replace(/_initials$/i, "")) || extractedFormFields.find((f) => fieldName.startsWith(f.name));
|
|
589
741
|
if (!fieldInfo) return false;
|
|
590
742
|
const isActualInitialsField = fieldInfo.type === "initials" || fieldName.toLowerCase().includes("initials");
|
|
591
743
|
if (!isActualInitialsField) return false;
|
|
@@ -614,6 +766,19 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
614
766
|
const y = fieldPosition.y;
|
|
615
767
|
const height = Math.max(fieldPosition.height, 20);
|
|
616
768
|
const fontSize = Math.min(height * 0.7, 14);
|
|
769
|
+
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");
|
|
770
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
771
|
+
const labelFontSize = Math.min(10, height * 0.4);
|
|
772
|
+
const labelX = x;
|
|
773
|
+
const labelY = y + height + 5;
|
|
774
|
+
page.drawText(fieldInfo.label, {
|
|
775
|
+
x: labelX,
|
|
776
|
+
y: labelY,
|
|
777
|
+
size: labelFontSize,
|
|
778
|
+
font,
|
|
779
|
+
color: rgb(0, 0, 0)
|
|
780
|
+
});
|
|
781
|
+
}
|
|
617
782
|
const finalX = x + 2;
|
|
618
783
|
const finalY = y + (height - fontSize) / 2;
|
|
619
784
|
console.log("[FLATTEN] \u2705 Drawing initials at:", { x: finalX, y: finalY, fontSize, text: mainInitialsData });
|
|
@@ -656,11 +821,16 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
656
821
|
let flattenedCount = 0;
|
|
657
822
|
for (const field of fieldsToFlatten) {
|
|
658
823
|
const fieldName = field.getName();
|
|
659
|
-
const
|
|
824
|
+
const f = field;
|
|
825
|
+
const isCheckboxField = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
826
|
+
const isRadioField = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
827
|
+
const isDropdownField = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
828
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
829
|
+
const isSignatureType = !isCheckboxField && !isRadioField && !isDropdownField && !isTextField;
|
|
660
830
|
try {
|
|
661
831
|
const userEnteredValue = formFieldValues[fieldName];
|
|
662
|
-
const isSignatureField = fieldName.toLowerCase().includes("signature") ||
|
|
663
|
-
const isInitialsField = fieldName.toLowerCase().includes("initials")
|
|
832
|
+
const isSignatureField = fieldName.toLowerCase().includes("signature") || isSignatureType;
|
|
833
|
+
const isInitialsField = fieldName.toLowerCase().includes("initials");
|
|
664
834
|
if (isSignatureField || isInitialsField) {
|
|
665
835
|
form.removeField(field);
|
|
666
836
|
flattenedCount++;
|
|
@@ -668,15 +838,29 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
668
838
|
}
|
|
669
839
|
const fieldWithWidgets = field;
|
|
670
840
|
const widgets = fieldWithWidgets.acroField?.getWidgets?.() || [];
|
|
671
|
-
if (
|
|
841
|
+
if (isCheckboxField) {
|
|
672
842
|
const stringValue = String(userEnteredValue || "");
|
|
673
843
|
const isChecked = stringValue === "true" || stringValue === "Yes" || stringValue === "On" || stringValue === "checked";
|
|
844
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
845
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
674
846
|
if (isChecked) {
|
|
675
847
|
for (const widget of widgets) {
|
|
676
848
|
const result = getWidgetRectangleAndPage(widget, pages);
|
|
677
849
|
if (!result) continue;
|
|
678
850
|
const { rect, page } = result;
|
|
679
|
-
|
|
851
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
852
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
853
|
+
const labelX = rect.x + rect.width + 5;
|
|
854
|
+
const labelY = rect.y + (rect.height - labelFontSize) / 2;
|
|
855
|
+
page.drawText(fieldInfo.label, {
|
|
856
|
+
x: labelX,
|
|
857
|
+
y: labelY,
|
|
858
|
+
size: labelFontSize,
|
|
859
|
+
font: labelFont,
|
|
860
|
+
color: rgb(0, 0, 0)
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
const checkboxSize = Math.min(rect.width, rect.height);
|
|
680
864
|
const checkboxX = rect.x + (rect.width - checkboxSize) / 2;
|
|
681
865
|
const checkboxY = rect.y + (rect.height - checkboxSize) / 2;
|
|
682
866
|
page.drawRectangle({
|
|
@@ -687,21 +871,51 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
687
871
|
borderColor: rgb(0, 0, 0),
|
|
688
872
|
borderWidth: 1
|
|
689
873
|
});
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
const
|
|
874
|
+
const checkFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
875
|
+
const checkFontSize = checkboxSize * 0.7;
|
|
876
|
+
const textWidth = checkFont.widthOfTextAtSize("X", checkFontSize);
|
|
877
|
+
const textHeight = checkFont.heightAtSize(checkFontSize);
|
|
693
878
|
page.drawText("X", {
|
|
694
879
|
x: checkboxX + (checkboxSize - textWidth) / 2,
|
|
695
|
-
y: checkboxY + (checkboxSize - textHeight) / 2 + textHeight * 0.
|
|
696
|
-
size:
|
|
697
|
-
font:
|
|
880
|
+
y: checkboxY + (checkboxSize - textHeight) / 2 + textHeight * 0.15,
|
|
881
|
+
size: checkFontSize,
|
|
882
|
+
font: checkFont,
|
|
698
883
|
color: rgb(0, 0, 0)
|
|
699
884
|
});
|
|
700
885
|
}
|
|
886
|
+
} else {
|
|
887
|
+
for (const widget of widgets) {
|
|
888
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
889
|
+
if (!result) continue;
|
|
890
|
+
const { rect, page } = result;
|
|
891
|
+
const checkboxSize = Math.min(rect.width, rect.height);
|
|
892
|
+
const checkboxX = rect.x + (rect.width - checkboxSize) / 2;
|
|
893
|
+
const checkboxY = rect.y + (rect.height - checkboxSize) / 2;
|
|
894
|
+
page.drawRectangle({
|
|
895
|
+
x: checkboxX,
|
|
896
|
+
y: checkboxY,
|
|
897
|
+
width: checkboxSize,
|
|
898
|
+
height: checkboxSize,
|
|
899
|
+
borderColor: rgb(0, 0, 0),
|
|
900
|
+
borderWidth: 1
|
|
901
|
+
});
|
|
902
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
903
|
+
const labelFontSize = Math.min(12, rect.height * 0.6);
|
|
904
|
+
const labelX = rect.x + rect.width + 5;
|
|
905
|
+
const labelY = rect.y + (rect.height - labelFontSize) / 2;
|
|
906
|
+
page.drawText(fieldInfo.label, {
|
|
907
|
+
x: labelX,
|
|
908
|
+
y: labelY,
|
|
909
|
+
size: labelFontSize,
|
|
910
|
+
font: labelFont,
|
|
911
|
+
color: rgb(0, 0, 0)
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
701
915
|
}
|
|
702
|
-
} else if (
|
|
703
|
-
const radioGroup =
|
|
704
|
-
const fieldInfo = extractedFormFields?.find((
|
|
916
|
+
} else if (isRadioField) {
|
|
917
|
+
const radioGroup = f;
|
|
918
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
705
919
|
let actualSelectedValue = "";
|
|
706
920
|
try {
|
|
707
921
|
actualSelectedValue = radioGroup.getSelected?.() || "";
|
|
@@ -746,14 +960,15 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
746
960
|
const result = getWidgetRectangleAndPage(widget, pages);
|
|
747
961
|
if (!result) continue;
|
|
748
962
|
const { rect, page, pageIndex } = result;
|
|
749
|
-
if (i === 0 && fieldInfo
|
|
963
|
+
if (i === 0 && fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
750
964
|
const groupLabelKey = `${pageIndex}-${fieldName}-grouplabel`;
|
|
751
965
|
if (!drawnGroupLabels.has(groupLabelKey)) {
|
|
966
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
752
967
|
const labelY = rect.y + rect.height + 3;
|
|
753
968
|
page.drawText(fieldInfo.label, {
|
|
754
969
|
x: rect.x,
|
|
755
970
|
y: labelY,
|
|
756
|
-
size:
|
|
971
|
+
size: labelFontSize,
|
|
757
972
|
font: labelFont,
|
|
758
973
|
color: rgb(0, 0, 0)
|
|
759
974
|
});
|
|
@@ -761,22 +976,21 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
761
976
|
}
|
|
762
977
|
} else if (i === 0 && isAutoGeneratedLabel(fieldInfo?.label || "")) {
|
|
763
978
|
}
|
|
764
|
-
const radioSize = Math.min(rect.width, rect.height)
|
|
765
|
-
const
|
|
766
|
-
const
|
|
979
|
+
const radioSize = Math.min(rect.width, rect.height);
|
|
980
|
+
const centerX = rect.x + rect.width / 2;
|
|
981
|
+
const centerY = rect.y + rect.height / 2;
|
|
767
982
|
page.drawCircle({
|
|
768
|
-
x:
|
|
769
|
-
y:
|
|
770
|
-
size: radioSize,
|
|
983
|
+
x: centerX,
|
|
984
|
+
y: centerY,
|
|
985
|
+
size: radioSize / 2,
|
|
771
986
|
borderColor: rgb(0, 0, 0),
|
|
772
987
|
borderWidth: 1
|
|
773
988
|
});
|
|
774
989
|
if (i === selectedIndex) {
|
|
775
|
-
const circleSize = radioSize * 0.5;
|
|
776
990
|
page.drawCircle({
|
|
777
|
-
x:
|
|
778
|
-
y:
|
|
779
|
-
size:
|
|
991
|
+
x: centerX,
|
|
992
|
+
y: centerY,
|
|
993
|
+
size: radioSize / 4,
|
|
780
994
|
color: rgb(0, 0, 0)
|
|
781
995
|
});
|
|
782
996
|
}
|
|
@@ -786,8 +1000,8 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
786
1000
|
if (optionText && !drawnOptionLabels.has(optionKey)) {
|
|
787
1001
|
page.drawText(optionText, {
|
|
788
1002
|
x: rect.x + rect.width + 4,
|
|
789
|
-
y: rect.y + (rect.height -
|
|
790
|
-
size:
|
|
1003
|
+
y: rect.y + (rect.height - 7) / 2,
|
|
1004
|
+
size: 7,
|
|
791
1005
|
font: labelFont,
|
|
792
1006
|
color: rgb(0, 0, 0)
|
|
793
1007
|
});
|
|
@@ -797,17 +1011,31 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
797
1011
|
}
|
|
798
1012
|
if (fieldInfo?.options) {
|
|
799
1013
|
}
|
|
800
|
-
} else if (
|
|
1014
|
+
} else if (isDropdownField) {
|
|
801
1015
|
const selectedValue = userEnteredValue ? String(userEnteredValue) : "";
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1016
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
1017
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
1018
|
+
for (const widget of widgets) {
|
|
1019
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
1020
|
+
if (!result) continue;
|
|
1021
|
+
const { rect, page } = result;
|
|
1022
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
1023
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1024
|
+
const labelX = rect.x;
|
|
1025
|
+
const labelY = rect.y + rect.height + 5;
|
|
1026
|
+
page.drawText(fieldInfo.label, {
|
|
1027
|
+
x: labelX,
|
|
1028
|
+
y: labelY,
|
|
1029
|
+
size: labelFontSize,
|
|
1030
|
+
font: labelFont,
|
|
1031
|
+
color: rgb(0, 0, 0)
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
if (selectedValue && selectedValue.trim()) {
|
|
807
1035
|
page.drawText(selectedValue, {
|
|
808
1036
|
x: rect.x + 2,
|
|
809
1037
|
y: rect.y + 2,
|
|
810
|
-
size:
|
|
1038
|
+
size: 14,
|
|
811
1039
|
font: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
812
1040
|
color: rgb(0, 0, 0)
|
|
813
1041
|
});
|
|
@@ -815,15 +1043,31 @@ async function fillPdfWithSignatures(pdfBytes, signatures, formFieldValues = {},
|
|
|
815
1043
|
}
|
|
816
1044
|
} else {
|
|
817
1045
|
const fieldValue = userEnteredValue ? String(userEnteredValue) : "";
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1046
|
+
const fieldInfo = extractedFormFields?.find((f2) => f2.name === fieldName);
|
|
1047
|
+
const labelFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
|
1048
|
+
const isDateField = fieldName.toLowerCase().includes("date") || fieldName.toLowerCase().includes("_date");
|
|
1049
|
+
const fontSize = isDateField ? 14 : extractFieldFontSize(fieldName, field, extractedFormFields);
|
|
1050
|
+
for (const widget of widgets) {
|
|
1051
|
+
const result = getWidgetRectangleAndPage(widget, pages);
|
|
1052
|
+
if (!result) continue;
|
|
1053
|
+
const { rect, page } = result;
|
|
1054
|
+
if (fieldInfo && hasDrawableLabel(fieldInfo)) {
|
|
1055
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1056
|
+
const labelX = rect.x;
|
|
1057
|
+
const labelY = rect.y + rect.height + 5;
|
|
1058
|
+
page.drawText(fieldInfo.label, {
|
|
1059
|
+
x: labelX,
|
|
1060
|
+
y: labelY,
|
|
1061
|
+
size: labelFontSize,
|
|
1062
|
+
font: labelFont,
|
|
1063
|
+
color: rgb(0, 0, 0)
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (fieldValue && fieldValue.trim()) {
|
|
823
1067
|
page.drawText(fieldValue, {
|
|
824
1068
|
x: rect.x + 2,
|
|
825
1069
|
y: rect.y + 2,
|
|
826
|
-
size:
|
|
1070
|
+
size: fontSize,
|
|
827
1071
|
font: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
828
1072
|
color: rgb(0, 0, 0)
|
|
829
1073
|
});
|
|
@@ -981,7 +1225,6 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
981
1225
|
const fields = form.getFields();
|
|
982
1226
|
const visibleFields = [];
|
|
983
1227
|
let hasAnyInitialsFields = false;
|
|
984
|
-
let hasInitialsFieldsForCurrentSigner = false;
|
|
985
1228
|
for (const field of fields) {
|
|
986
1229
|
const fieldName = field.getName();
|
|
987
1230
|
const fieldWithExtensions = field;
|
|
@@ -1007,28 +1250,31 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1007
1250
|
};
|
|
1008
1251
|
})();
|
|
1009
1252
|
let fieldType = "text" /* TEXT */;
|
|
1010
|
-
const
|
|
1253
|
+
const f = field;
|
|
1254
|
+
const isCheckboxField = typeof f.check === "function" && typeof f.uncheck === "function";
|
|
1255
|
+
const isRadioField = typeof f.select === "function" && typeof f.getOptions === "function" && typeof f.check !== "function" && typeof f.setOptions !== "function";
|
|
1256
|
+
const isDropdownField = typeof f.select === "function" && (typeof f.setOptions === "function" || typeof f.addOptions === "function");
|
|
1257
|
+
const isTextField = typeof f.getText === "function" && typeof f.setText === "function";
|
|
1258
|
+
const isSignatureType = !isCheckboxField && !isRadioField && !isDropdownField && !isTextField;
|
|
1011
1259
|
const cleanFieldName = fieldName.replace(/_signature$|_initials$|_date$/i, "");
|
|
1012
|
-
const isActualSignatureField =
|
|
1013
|
-
const isActualInitialsField = cleanFieldName.toLowerCase().includes("initials") && !
|
|
1260
|
+
const isActualSignatureField = isSignatureType || cleanFieldName.toLowerCase().includes("signature") && !isCheckboxField && !isRadioField;
|
|
1261
|
+
const isActualInitialsField = cleanFieldName.toLowerCase().includes("initials") && !isCheckboxField && !isRadioField;
|
|
1014
1262
|
if (isActualSignatureField) {
|
|
1015
1263
|
fieldType = "signature" /* SIGNATURE */;
|
|
1016
1264
|
} else if (isActualInitialsField) {
|
|
1017
1265
|
fieldType = "initials" /* INITIALS */;
|
|
1018
1266
|
hasAnyInitialsFields = true;
|
|
1019
|
-
if (currentSignerEmail && fieldMetadata.signer === currentSignerEmail) {
|
|
1020
|
-
hasInitialsFieldsForCurrentSigner = true;
|
|
1021
|
-
}
|
|
1022
1267
|
} else if (cleanFieldName.toLowerCase().includes("date")) {
|
|
1023
1268
|
fieldType = "date" /* DATE */;
|
|
1024
|
-
} else if (
|
|
1269
|
+
} else if (isCheckboxField) {
|
|
1025
1270
|
fieldType = "checkbox" /* CHECKBOX */;
|
|
1026
|
-
} else if (
|
|
1271
|
+
} else if (isDropdownField) {
|
|
1027
1272
|
fieldType = "dropdown" /* DROPDOWN */;
|
|
1028
|
-
} else if (
|
|
1273
|
+
} else if (isRadioField) {
|
|
1029
1274
|
fieldType = "radio" /* RADIO */;
|
|
1030
1275
|
}
|
|
1031
1276
|
let displayLabel = fieldMetadata.label || "";
|
|
1277
|
+
let isLabelAutoGenerated = false;
|
|
1032
1278
|
if (!displayLabel || !displayLabel.trim()) {
|
|
1033
1279
|
try {
|
|
1034
1280
|
const tuLabel = extractTULabel(fieldWithExtensions);
|
|
@@ -1040,6 +1286,7 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1040
1286
|
}
|
|
1041
1287
|
if (!displayLabel || !displayLabel.trim()) {
|
|
1042
1288
|
displayLabel = generateFallbackLabel(cleanFieldName, fieldType);
|
|
1289
|
+
isLabelAutoGenerated = true;
|
|
1043
1290
|
}
|
|
1044
1291
|
const esignField = {
|
|
1045
1292
|
id: fieldName,
|
|
@@ -1051,6 +1298,8 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1051
1298
|
type: fieldType,
|
|
1052
1299
|
label: displayLabel,
|
|
1053
1300
|
// Use friendly label for display
|
|
1301
|
+
isLabelAutoGenerated,
|
|
1302
|
+
// True when label was generated from field name (should not be drawn on PDF)
|
|
1054
1303
|
position: { x: 0, y: 0, width: 100, height: 30, page: 1 },
|
|
1055
1304
|
// Default position
|
|
1056
1305
|
required: fieldWithExtensions.isRequired?.() ?? false,
|
|
@@ -1071,6 +1320,13 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1071
1320
|
logger.warn("Error extracting options for field:", error);
|
|
1072
1321
|
}
|
|
1073
1322
|
}
|
|
1323
|
+
try {
|
|
1324
|
+
const currentValue = extractFieldValue(f, esignField.type);
|
|
1325
|
+
if (currentValue) {
|
|
1326
|
+
esignField.defaultValue = currentValue;
|
|
1327
|
+
}
|
|
1328
|
+
} catch {
|
|
1329
|
+
}
|
|
1074
1330
|
visibleFields.push(esignField);
|
|
1075
1331
|
}
|
|
1076
1332
|
}
|
|
@@ -1087,11 +1343,8 @@ async function extractVisibleFormFields(pdfBytes, currentSignerEmail) {
|
|
|
1087
1343
|
placeholder: "Draw or upload your signature",
|
|
1088
1344
|
assignedSignerEmail: currentSignerEmail
|
|
1089
1345
|
});
|
|
1090
|
-
const shouldCreateInitialsField =
|
|
1346
|
+
const shouldCreateInitialsField = hasAnyInitialsFields;
|
|
1091
1347
|
if (shouldCreateInitialsField) {
|
|
1092
|
-
if (hasInitialsFieldsForCurrentSigner) {
|
|
1093
|
-
} else {
|
|
1094
|
-
}
|
|
1095
1348
|
mainFields.push({
|
|
1096
1349
|
id: "initials_field_main",
|
|
1097
1350
|
fieldId: "initials_field_main",
|
|
@@ -1178,8 +1431,12 @@ function decodeFieldName(fieldName) {
|
|
|
1178
1431
|
}
|
|
1179
1432
|
function generateFallbackLabel(decodedFieldName, fieldType) {
|
|
1180
1433
|
let displayLabel = decodedFieldName;
|
|
1181
|
-
|
|
1182
|
-
|
|
1434
|
+
let prevLabel = "";
|
|
1435
|
+
while (displayLabel !== prevLabel) {
|
|
1436
|
+
prevLabel = displayLabel;
|
|
1437
|
+
displayLabel = displayLabel.replace(/_signature$/i, "").replace(/_initials$/i, "").replace(/_date$/i, "");
|
|
1438
|
+
}
|
|
1439
|
+
if (/^(text|signature|initials|date|checkbox|radio|dropdown)_\d+/i.test(displayLabel)) {
|
|
1183
1440
|
if (fieldType === "signature" /* SIGNATURE */) return "Signature";
|
|
1184
1441
|
if (fieldType === "initials" /* INITIALS */) return "Initials";
|
|
1185
1442
|
if (fieldType === "date" /* DATE */) return "Date";
|
|
@@ -1187,9 +1444,9 @@ function generateFallbackLabel(decodedFieldName, fieldType) {
|
|
|
1187
1444
|
if (fieldType === "checkbox" /* CHECKBOX */) return "Checkbox";
|
|
1188
1445
|
if (fieldType === "dropdown" /* DROPDOWN */) return "Dropdown";
|
|
1189
1446
|
if (fieldType === "radio" /* RADIO */) return "Option";
|
|
1190
|
-
|
|
1191
|
-
displayLabel = displayLabel.replace(/_/g, " ").replace(/\s+\d+$/g, "").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1447
|
+
if (fieldType === "text_label" /* TEXT_LABEL */) return "Text Label";
|
|
1192
1448
|
}
|
|
1449
|
+
displayLabel = displayLabel.replace(/_/g, " ").replace(/\s+\d+$/g, "").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1193
1450
|
return displayLabel;
|
|
1194
1451
|
}
|
|
1195
1452
|
function extractTULabel(field) {
|
|
@@ -1284,13 +1541,13 @@ var PdfProcessingError = class _PdfProcessingError extends Error {
|
|
|
1284
1541
|
|
|
1285
1542
|
// src/utils/attachment-validators.ts
|
|
1286
1543
|
var DEFAULT_ATTACHMENT_CONSTRAINTS = {
|
|
1287
|
-
maxFileSize:
|
|
1288
|
-
//
|
|
1544
|
+
maxFileSize: 25 * 1024 * 1024,
|
|
1545
|
+
// 25MB
|
|
1289
1546
|
maxTotalSize: 50 * 1024 * 1024,
|
|
1290
1547
|
// 50MB
|
|
1291
1548
|
maxFiles: 10,
|
|
1292
1549
|
allowedTypes: ["image/*", "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
|
|
1293
|
-
allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".doc", ".docx"]
|
|
1550
|
+
allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".doc", ".docx"]
|
|
1294
1551
|
};
|
|
1295
1552
|
function validateFile(file, constraints = DEFAULT_ATTACHMENT_CONSTRAINTS) {
|
|
1296
1553
|
const errors = [];
|
|
@@ -1354,6 +1611,35 @@ function isValidISODate(value) {
|
|
|
1354
1611
|
}
|
|
1355
1612
|
|
|
1356
1613
|
// src/utils/pdf-viewer-filter.ts
|
|
1614
|
+
function robustlyRemoveField(form, field) {
|
|
1615
|
+
try {
|
|
1616
|
+
const fieldWithAcro = field;
|
|
1617
|
+
if (fieldWithAcro.acroField && typeof fieldWithAcro.acroField.getWidgets === "function") {
|
|
1618
|
+
let widgets = fieldWithAcro.acroField.getWidgets() || [];
|
|
1619
|
+
let safetyCounter = 0;
|
|
1620
|
+
const maxIterations = widgets.length + 5;
|
|
1621
|
+
while (widgets.length > 0 && safetyCounter < maxIterations) {
|
|
1622
|
+
try {
|
|
1623
|
+
const widgetIndex = widgets.length - 1;
|
|
1624
|
+
fieldWithAcro.acroField.removeWidget?.(widgetIndex);
|
|
1625
|
+
widgets = fieldWithAcro.acroField.getWidgets() || [];
|
|
1626
|
+
} catch {
|
|
1627
|
+
break;
|
|
1628
|
+
}
|
|
1629
|
+
safetyCounter++;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
form.removeField(field);
|
|
1633
|
+
return true;
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
try {
|
|
1636
|
+
form.removeField(field);
|
|
1637
|
+
return true;
|
|
1638
|
+
} catch {
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1357
1643
|
async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
1358
1644
|
try {
|
|
1359
1645
|
const labelFont = await pdfDoc.embedFont("Helvetica-Bold");
|
|
@@ -1363,8 +1649,7 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1363
1649
|
for (const field of fieldsToLabel) {
|
|
1364
1650
|
const isRadioField = field.type === "radio" /* RADIO */;
|
|
1365
1651
|
const hasRadioOptions = isRadioField && field.options && field.options.length > 0;
|
|
1366
|
-
|
|
1367
|
-
if (!hasCustomLabel && !hasRadioOptions) {
|
|
1652
|
+
if (!hasDrawableLabel(field) && !hasRadioOptions) {
|
|
1368
1653
|
continue;
|
|
1369
1654
|
}
|
|
1370
1655
|
const pdfField = form.getFieldMaybe(field.name);
|
|
@@ -1375,27 +1660,30 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1375
1660
|
const widgets = fieldWithExtensions.acroField?.getWidgets?.() || [];
|
|
1376
1661
|
if (widgets.length === 0) continue;
|
|
1377
1662
|
if (isRadioField) {
|
|
1378
|
-
if (
|
|
1663
|
+
if (hasDrawableLabel(field)) {
|
|
1379
1664
|
const firstWidget = widgets[0];
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1665
|
+
if (firstWidget) {
|
|
1666
|
+
const firstRect = firstWidget.getRectangle?.();
|
|
1667
|
+
if (firstRect) {
|
|
1668
|
+
const pageRef = firstWidget.P?.();
|
|
1669
|
+
const pageIndex = findPageIndexWithFallback(pages, pageRef);
|
|
1670
|
+
if (pageIndex >= 0) {
|
|
1671
|
+
const page = pages[pageIndex];
|
|
1672
|
+
if (page) {
|
|
1673
|
+
const groupLabelKey = `${pageIndex}-${field.name}-grouplabel`;
|
|
1674
|
+
if (!drawnOnce.has(groupLabelKey)) {
|
|
1675
|
+
const radioLabelFontSize = Math.min(10, firstRect.height * 0.4);
|
|
1676
|
+
const labelY = firstRect.y + firstRect.height + 5;
|
|
1677
|
+
page.drawText(field.label, {
|
|
1678
|
+
x: firstRect.x,
|
|
1679
|
+
y: labelY,
|
|
1680
|
+
size: radioLabelFontSize,
|
|
1681
|
+
font: labelFont,
|
|
1682
|
+
color: rgb(0, 0, 0)
|
|
1683
|
+
});
|
|
1684
|
+
drawnOnce.add(groupLabelKey);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1399
1687
|
}
|
|
1400
1688
|
}
|
|
1401
1689
|
}
|
|
@@ -1408,17 +1696,16 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1408
1696
|
if (!rect) continue;
|
|
1409
1697
|
const pageRef = widget.P?.();
|
|
1410
1698
|
const pageIndex = findPageIndexWithFallback(pages, pageRef);
|
|
1411
|
-
if (pageIndex >= 0) {
|
|
1699
|
+
if (pageIndex >= 0 && pageIndex < pages.length) {
|
|
1412
1700
|
const page = pages[pageIndex];
|
|
1413
1701
|
if (!page) continue;
|
|
1414
|
-
const
|
|
1415
|
-
const optionText = optionTexts[i] || optionTexts[0];
|
|
1702
|
+
const optionText = field.options[i];
|
|
1416
1703
|
const optionKey = `${pageIndex}-${field.name}-opt-${i}`;
|
|
1417
1704
|
if (optionText && !drawnOnce.has(optionKey)) {
|
|
1418
1705
|
page.drawText(optionText, {
|
|
1419
|
-
x: rect.x + rect.width +
|
|
1420
|
-
y: rect.y + (rect.height -
|
|
1421
|
-
size:
|
|
1706
|
+
x: rect.x + rect.width + 4,
|
|
1707
|
+
y: rect.y + (rect.height - 7) / 2,
|
|
1708
|
+
size: 7,
|
|
1422
1709
|
font: labelFont,
|
|
1423
1710
|
color: rgb(0, 0, 0)
|
|
1424
1711
|
});
|
|
@@ -1439,12 +1726,13 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1439
1726
|
if (!page) continue;
|
|
1440
1727
|
const key = `${pageIndex}-${field.name}`;
|
|
1441
1728
|
if (!drawnOnce.has(key)) {
|
|
1442
|
-
if (
|
|
1729
|
+
if (hasDrawableLabel(field)) {
|
|
1730
|
+
const labelFontSize = Math.min(10, rect.height * 0.4);
|
|
1443
1731
|
if (field.type === "checkbox" /* CHECKBOX */) {
|
|
1444
1732
|
page.drawText(field.label, {
|
|
1445
1733
|
x: rect.x + rect.width + 5,
|
|
1446
|
-
y: rect.y + rect.height
|
|
1447
|
-
size:
|
|
1734
|
+
y: rect.y + (rect.height - labelFontSize) / 2,
|
|
1735
|
+
size: labelFontSize,
|
|
1448
1736
|
font: labelFont,
|
|
1449
1737
|
color: rgb(0, 0, 0)
|
|
1450
1738
|
});
|
|
@@ -1453,9 +1741,9 @@ async function drawFieldLabelsOnPdf(pdfDoc, fieldsToLabel, rgb) {
|
|
|
1453
1741
|
page.drawText(field.label, {
|
|
1454
1742
|
x: rect.x,
|
|
1455
1743
|
y: labelY,
|
|
1456
|
-
size:
|
|
1744
|
+
size: labelFontSize,
|
|
1457
1745
|
font: labelFont,
|
|
1458
|
-
color: rgb(0
|
|
1746
|
+
color: rgb(0, 0, 0)
|
|
1459
1747
|
});
|
|
1460
1748
|
}
|
|
1461
1749
|
drawnOnce.add(key);
|
|
@@ -1516,7 +1804,7 @@ async function filterPdfForCurrentSigner(pdfBytes, allFields, multiSignerContext
|
|
|
1516
1804
|
try {
|
|
1517
1805
|
const pdfField = form.getFieldMaybe(field.name);
|
|
1518
1806
|
if (pdfField) {
|
|
1519
|
-
form
|
|
1807
|
+
robustlyRemoveField(form, pdfField);
|
|
1520
1808
|
removedCount++;
|
|
1521
1809
|
} else {
|
|
1522
1810
|
notFoundCount++;
|
|
@@ -1550,7 +1838,7 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1550
1838
|
const [originalPdfBytes, setOriginalPdfBytes] = useState(null);
|
|
1551
1839
|
const [_viewerPdfBytes, setViewerPdfBytes] = useState(null);
|
|
1552
1840
|
const [extractedFields, setExtractedFields] = useState([]);
|
|
1553
|
-
const loadPdf = useCallback(async (url) => {
|
|
1841
|
+
const loadPdf = useCallback(async (url, signerEmailForExtraction) => {
|
|
1554
1842
|
setIsLoading(true);
|
|
1555
1843
|
setError(null);
|
|
1556
1844
|
setIsLoaded(false);
|
|
@@ -1563,8 +1851,22 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1563
1851
|
}
|
|
1564
1852
|
const bytes = await urlToPdfBytes(url);
|
|
1565
1853
|
setOriginalPdfBytes(bytes);
|
|
1566
|
-
|
|
1567
|
-
|
|
1854
|
+
const fields = await extractVisibleFormFields(bytes, signerEmailForExtraction);
|
|
1855
|
+
setExtractedFields(fields);
|
|
1856
|
+
const filteredBytes = await filterPdfForCurrentSigner(
|
|
1857
|
+
bytes,
|
|
1858
|
+
fields,
|
|
1859
|
+
multiSignerContext || {
|
|
1860
|
+
isMultiSigner: false,
|
|
1861
|
+
currentSigner: null,
|
|
1862
|
+
currentSignerEmail: "",
|
|
1863
|
+
isPrimarySigner: true,
|
|
1864
|
+
isFinalSigner: true
|
|
1865
|
+
}
|
|
1866
|
+
);
|
|
1867
|
+
setViewerPdfBytes(filteredBytes);
|
|
1868
|
+
const blobUrl = createPdfBlobUrl(filteredBytes);
|
|
1869
|
+
await viewerRef.current?.loadPdf(blobUrl);
|
|
1568
1870
|
} catch (err) {
|
|
1569
1871
|
const errorMessage = err instanceof Error ? err.message : "Failed to load PDF";
|
|
1570
1872
|
logger.error("Error loading PDF:", err);
|
|
@@ -1572,7 +1874,7 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1572
1874
|
setOriginalPdfBytes(null);
|
|
1573
1875
|
setViewerPdfBytes(null);
|
|
1574
1876
|
}
|
|
1575
|
-
}, []);
|
|
1877
|
+
}, [multiSignerContext]);
|
|
1576
1878
|
const handleLoad = useCallback(() => {
|
|
1577
1879
|
setIsLoading(false);
|
|
1578
1880
|
setIsLoaded(true);
|
|
@@ -1608,52 +1910,15 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1608
1910
|
return await viewerRef.current.saveDocument();
|
|
1609
1911
|
}, []);
|
|
1610
1912
|
const extractFormFields = useCallback(
|
|
1611
|
-
async (
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
const fields = await extractVisibleFormFields(
|
|
1617
|
-
originalPdfBytes,
|
|
1618
|
-
currentSignerEmail
|
|
1619
|
-
);
|
|
1620
|
-
setExtractedFields(fields);
|
|
1621
|
-
if (multiSignerContext?.isMultiSigner) {
|
|
1622
|
-
const filteredBytes = await filterPdfForCurrentSigner(
|
|
1623
|
-
originalPdfBytes,
|
|
1624
|
-
fields,
|
|
1625
|
-
multiSignerContext
|
|
1626
|
-
);
|
|
1627
|
-
setViewerPdfBytes(filteredBytes);
|
|
1628
|
-
const filteredBlobUrl = createPdfBlobUrl(filteredBytes);
|
|
1629
|
-
await viewerRef.current?.loadPdf(filteredBlobUrl);
|
|
1630
|
-
} else {
|
|
1631
|
-
const labeledBytes = await filterPdfForCurrentSigner(
|
|
1632
|
-
originalPdfBytes,
|
|
1633
|
-
fields,
|
|
1634
|
-
multiSignerContext || {
|
|
1635
|
-
isMultiSigner: false,
|
|
1636
|
-
currentSigner: null,
|
|
1637
|
-
currentSignerEmail: "",
|
|
1638
|
-
isPrimarySigner: true,
|
|
1639
|
-
isFinalSigner: true
|
|
1640
|
-
}
|
|
1641
|
-
);
|
|
1642
|
-
setViewerPdfBytes(labeledBytes);
|
|
1643
|
-
const labeledBlobUrl = createPdfBlobUrl(labeledBytes);
|
|
1644
|
-
await viewerRef.current?.loadPdf(labeledBlobUrl);
|
|
1645
|
-
}
|
|
1646
|
-
if (viewerRef.current && fields.length > 0) {
|
|
1647
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1648
|
-
viewerRef.current.injectPlaceholders(fields);
|
|
1649
|
-
}
|
|
1650
|
-
return fields;
|
|
1651
|
-
} catch (err) {
|
|
1652
|
-
const errorMessage = err instanceof Error ? err.message : "Failed to extract form fields";
|
|
1653
|
-
throw new Error(errorMessage);
|
|
1913
|
+
async (_currentSignerEmail) => {
|
|
1914
|
+
if (viewerRef.current && extractedFields.length > 0) {
|
|
1915
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1916
|
+
viewerRef.current.injectPlaceholders(extractedFields);
|
|
1917
|
+
viewerRef.current.injectRadioLabels(extractedFields);
|
|
1654
1918
|
}
|
|
1919
|
+
return extractedFields;
|
|
1655
1920
|
},
|
|
1656
|
-
[
|
|
1921
|
+
[extractedFields]
|
|
1657
1922
|
);
|
|
1658
1923
|
const fillPdf = useCallback(
|
|
1659
1924
|
async (fieldValues, signatures, currentSignerEmail, metadata, auditTrail) => {
|
|
@@ -1796,6 +2061,20 @@ function usePdfViewer(multiSignerContext) {
|
|
|
1796
2061
|
}
|
|
1797
2062
|
function useFormFields(fields = []) {
|
|
1798
2063
|
const [fieldValues, setFieldValues] = useState({});
|
|
2064
|
+
const seededFieldsRef = useRef(/* @__PURE__ */ new Set());
|
|
2065
|
+
useEffect(() => {
|
|
2066
|
+
if (fields.length === 0) return;
|
|
2067
|
+
const defaults = {};
|
|
2068
|
+
for (const field of fields) {
|
|
2069
|
+
if (field.defaultValue && field.defaultValue.trim() && !seededFieldsRef.current.has(field.id)) {
|
|
2070
|
+
defaults[field.id] = field.defaultValue;
|
|
2071
|
+
seededFieldsRef.current.add(field.id);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (Object.keys(defaults).length > 0) {
|
|
2075
|
+
setFieldValues((prev) => ({ ...defaults, ...prev }));
|
|
2076
|
+
}
|
|
2077
|
+
}, [fields]);
|
|
1799
2078
|
const [errors, setErrors] = useState([]);
|
|
1800
2079
|
const [touched, setTouched] = useState({});
|
|
1801
2080
|
const updateField = useCallback((fieldId, value) => {
|
|
@@ -1835,14 +2114,14 @@ function useFormFields(fields = []) {
|
|
|
1835
2114
|
if (!value || value.trim() === "") {
|
|
1836
2115
|
newErrors.push({
|
|
1837
2116
|
field: fieldId,
|
|
1838
|
-
message: `${field
|
|
2117
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1839
2118
|
});
|
|
1840
2119
|
}
|
|
1841
2120
|
}
|
|
1842
2121
|
if (value && field.maxLength && value.length > field.maxLength) {
|
|
1843
2122
|
newErrors.push({
|
|
1844
2123
|
field: fieldId,
|
|
1845
|
-
message: `${field
|
|
2124
|
+
message: `${getFieldDisplayName(field)} must be at most ${field.maxLength} characters`
|
|
1846
2125
|
});
|
|
1847
2126
|
}
|
|
1848
2127
|
if (value && field.type === "date") {
|
|
@@ -1868,13 +2147,13 @@ function useFormFields(fields = []) {
|
|
|
1868
2147
|
if (!signatures[field.id]) {
|
|
1869
2148
|
newErrors.push({
|
|
1870
2149
|
field: field.id,
|
|
1871
|
-
message: `${field
|
|
2150
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1872
2151
|
});
|
|
1873
2152
|
}
|
|
1874
2153
|
} else if (!value2 || value2.trim() === "") {
|
|
1875
2154
|
newErrors.push({
|
|
1876
2155
|
field: field.id,
|
|
1877
|
-
message: `${field
|
|
2156
|
+
message: `${getFieldDisplayName(field)} is required`
|
|
1878
2157
|
});
|
|
1879
2158
|
}
|
|
1880
2159
|
}
|
|
@@ -1882,7 +2161,7 @@ function useFormFields(fields = []) {
|
|
|
1882
2161
|
if (value && field.maxLength && value.length > field.maxLength) {
|
|
1883
2162
|
newErrors.push({
|
|
1884
2163
|
field: field.id,
|
|
1885
|
-
message: `${field
|
|
2164
|
+
message: `${getFieldDisplayName(field)} must be at most ${field.maxLength} characters`
|
|
1886
2165
|
});
|
|
1887
2166
|
}
|
|
1888
2167
|
if (value && field.type === "date") {
|
|
@@ -2056,7 +2335,7 @@ function useSignatures() {
|
|
|
2056
2335
|
for (const field of fields) {
|
|
2057
2336
|
if (field.required && (field.type === "signature" || field.type === "initials")) {
|
|
2058
2337
|
if (!signaturesToCheck[field.id]) {
|
|
2059
|
-
errors.push(`${field
|
|
2338
|
+
errors.push(`${getFieldDisplayName(field)} is required`);
|
|
2060
2339
|
}
|
|
2061
2340
|
}
|
|
2062
2341
|
}
|
|
@@ -2174,6 +2453,13 @@ function useAttachments(options = {}) {
|
|
|
2174
2453
|
errors.push(...validation.errors);
|
|
2175
2454
|
continue;
|
|
2176
2455
|
}
|
|
2456
|
+
const isDuplicate = [...attachments, ...newAttachments].some(
|
|
2457
|
+
(att) => att.name.toLowerCase() === file.name.toLowerCase()
|
|
2458
|
+
);
|
|
2459
|
+
if (isDuplicate) {
|
|
2460
|
+
errors.push(`"${file.name}" is already attached`);
|
|
2461
|
+
continue;
|
|
2462
|
+
}
|
|
2177
2463
|
const currentTotalSize = [...attachments, ...newAttachments].reduce((sum, att) => sum + att.size, 0);
|
|
2178
2464
|
if (currentTotalSize + file.size > constraints.maxTotalSize) {
|
|
2179
2465
|
const maxTotalMB = (constraints.maxTotalSize / 1024 / 1024).toFixed(1);
|
|
@@ -2224,6 +2510,9 @@ function useAttachments(options = {}) {
|
|
|
2224
2510
|
setAttachments([]);
|
|
2225
2511
|
setValidationErrors([]);
|
|
2226
2512
|
}, []);
|
|
2513
|
+
const clearValidationErrors = useCallback(() => {
|
|
2514
|
+
setValidationErrors([]);
|
|
2515
|
+
}, []);
|
|
2227
2516
|
const getTotalSize = useCallback(() => {
|
|
2228
2517
|
return attachments.reduce((sum, att) => sum + att.size, 0);
|
|
2229
2518
|
}, [attachments]);
|
|
@@ -2255,6 +2544,7 @@ function useAttachments(options = {}) {
|
|
|
2255
2544
|
addFiles,
|
|
2256
2545
|
removeAttachment,
|
|
2257
2546
|
clearAttachments,
|
|
2547
|
+
clearValidationErrors,
|
|
2258
2548
|
// Utilities
|
|
2259
2549
|
getTotalSize,
|
|
2260
2550
|
formatSize,
|
|
@@ -2287,6 +2577,10 @@ function useAcknowledgements(fields) {
|
|
|
2287
2577
|
const [acknowledgedMap, setAcknowledgedMap] = useState(
|
|
2288
2578
|
/* @__PURE__ */ new Map()
|
|
2289
2579
|
);
|
|
2580
|
+
const acknowledgedMapRef = useRef(acknowledgedMap);
|
|
2581
|
+
useEffect(() => {
|
|
2582
|
+
acknowledgedMapRef.current = acknowledgedMap;
|
|
2583
|
+
}, [acknowledgedMap]);
|
|
2290
2584
|
const getFieldsWithAcknowledgements = useCallback((fieldsToFilter) => {
|
|
2291
2585
|
return fieldsToFilter.filter(
|
|
2292
2586
|
(field) => field.acknowledgements && field.acknowledgements.length > 0
|
|
@@ -2298,26 +2592,27 @@ function useAcknowledgements(fields) {
|
|
|
2298
2592
|
const fieldAcks = newMap.get(fieldId) || /* @__PURE__ */ new Set();
|
|
2299
2593
|
fieldAcks.add(ackId);
|
|
2300
2594
|
newMap.set(fieldId, fieldAcks);
|
|
2595
|
+
acknowledgedMapRef.current = newMap;
|
|
2301
2596
|
return newMap;
|
|
2302
2597
|
});
|
|
2303
2598
|
}, []);
|
|
2304
2599
|
const isAcknowledged = useCallback((fieldId, ackId) => {
|
|
2305
|
-
const fieldAcks =
|
|
2600
|
+
const fieldAcks = acknowledgedMapRef.current.get(fieldId);
|
|
2306
2601
|
if (!fieldAcks) return false;
|
|
2307
2602
|
if (ackId) {
|
|
2308
2603
|
return fieldAcks.has(ackId);
|
|
2309
2604
|
}
|
|
2310
2605
|
return fieldAcks.size > 0;
|
|
2311
|
-
}, [
|
|
2606
|
+
}, []);
|
|
2312
2607
|
const isFieldFullyAcknowledged = useCallback((fieldId) => {
|
|
2313
2608
|
const field = fields.find((f) => f.id === fieldId);
|
|
2314
2609
|
if (!field || !field.acknowledgements || field.acknowledgements.length === 0) {
|
|
2315
2610
|
return true;
|
|
2316
2611
|
}
|
|
2317
|
-
const fieldAcks =
|
|
2612
|
+
const fieldAcks = acknowledgedMapRef.current.get(fieldId);
|
|
2318
2613
|
if (!fieldAcks) return false;
|
|
2319
2614
|
return field.acknowledgements.every((ack) => fieldAcks.has(ack.id));
|
|
2320
|
-
}, [fields
|
|
2615
|
+
}, [fields]);
|
|
2321
2616
|
const getFieldAcknowledgementProgress = useCallback((fieldId) => {
|
|
2322
2617
|
const field = fields.find((f) => f.id === fieldId);
|
|
2323
2618
|
if (!field || !field.acknowledgements || field.acknowledgements.length === 0) {
|