@drvillo/react-browser-e-signing 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { pdfjs, Document, Page } from 'react-pdf';
2
- import { useEffect, useRef, useMemo, useState, useCallback } from 'react';
2
+ import { useEffect, useRef, useState, useMemo, useCallback } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import SignaturePadLibrary from 'signature_pad';
5
5
  import fontkit from '@pdf-lib/fontkit';
6
- import { PDFDocument, StandardFonts } from 'pdf-lib';
6
+ import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
7
7
 
8
8
  // src/lib/config.ts
9
9
  var _config = {};
@@ -240,12 +240,82 @@ function getFieldPreviewText(field, preview) {
240
240
  if (field.type === "fullName") return preview.fullName;
241
241
  if (field.type === "title") return preview.title;
242
242
  if (field.type === "date") return preview.dateText;
243
+ if (field.type === "custom" && field.label) return preview.customFields?.[field.label] ?? "";
243
244
  return "";
244
245
  }
245
- function SignatureField({ field, onUpdateField, onRemoveField, preview, className }) {
246
+ function SignatureField({
247
+ field,
248
+ onUpdateField,
249
+ onRemoveField,
250
+ preview,
251
+ onUpdateCustomValue,
252
+ onCustomFieldRenamed,
253
+ className
254
+ }) {
246
255
  const rootRef = useRef(null);
247
256
  const dragStateRef = useRef(null);
248
257
  const resizeStateRef = useRef(null);
258
+ const labelInputRef = useRef(null);
259
+ const valueInputRef = useRef(null);
260
+ const nextLabelModeRef = useRef("idle");
261
+ const isCustom = field.type === "custom";
262
+ const [editMode, setEditMode] = useState(
263
+ isCustom && !field.label ? "label" : "idle"
264
+ );
265
+ const [labelDraft, setLabelDraft] = useState(field.label ?? "");
266
+ const [valueDraft, setValueDraft] = useState("");
267
+ useEffect(() => {
268
+ if (editMode !== "label") return;
269
+ const id = setTimeout(() => labelInputRef.current?.focus(), 0);
270
+ return () => clearTimeout(id);
271
+ }, [editMode]);
272
+ useEffect(() => {
273
+ if (editMode !== "value") return;
274
+ const id = setTimeout(() => valueInputRef.current?.focus(), 0);
275
+ return () => clearTimeout(id);
276
+ }, [editMode]);
277
+ useEffect(() => {
278
+ if (editMode === "idle") setLabelDraft(field.label ?? "");
279
+ }, [field.label, editMode]);
280
+ function handleLabelKeyDown(e) {
281
+ if (e.key === "Enter" || e.key === "Tab") {
282
+ e.preventDefault();
283
+ nextLabelModeRef.current = onUpdateCustomValue ? "value" : "idle";
284
+ labelInputRef.current?.blur();
285
+ }
286
+ if (e.key === "Escape") {
287
+ nextLabelModeRef.current = "idle";
288
+ setLabelDraft(field.label ?? "");
289
+ labelInputRef.current?.blur();
290
+ }
291
+ }
292
+ function handleLabelBlur() {
293
+ const trimmed = labelDraft.trim();
294
+ if (trimmed) {
295
+ onUpdateField(field.id, { label: trimmed });
296
+ if (field.label && trimmed !== field.label) {
297
+ onCustomFieldRenamed?.(field.label, trimmed);
298
+ }
299
+ } else {
300
+ setLabelDraft(field.label ?? "");
301
+ }
302
+ const next = nextLabelModeRef.current;
303
+ nextLabelModeRef.current = "idle";
304
+ if (next === "value") {
305
+ setValueDraft(preview.customFields?.[trimmed || field.label || ""] ?? "");
306
+ }
307
+ setEditMode(next);
308
+ }
309
+ function handleValueKeyDown(e) {
310
+ if (e.key === "Enter" || e.key === "Tab" || e.key === "Escape") {
311
+ e.preventDefault();
312
+ valueInputRef.current?.blur();
313
+ }
314
+ }
315
+ function handleValueBlur() {
316
+ if (field.label && onUpdateCustomValue) onUpdateCustomValue(field.label, valueDraft);
317
+ setEditMode("idle");
318
+ }
249
319
  function handleDragPointerDown(event) {
250
320
  event.stopPropagation();
251
321
  if (event.button !== 0) return;
@@ -299,13 +369,10 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
299
369
  const deltaHeightPercent = deltaY / parentElement.clientHeight * 100;
300
370
  const maxWidth = 100 - field.xPercent;
301
371
  const maxHeight = 100 - field.yPercent;
302
- const widthPercent = clampPercent(
303
- Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))
304
- );
305
- const heightPercent = clampPercent(
306
- Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent))
307
- );
308
- onUpdateField(field.id, { widthPercent, heightPercent });
372
+ onUpdateField(field.id, {
373
+ widthPercent: clampPercent(Math.min(maxWidth, Math.max(MIN_WIDTH_PERCENT, resizeStateRef.current.startWidthPercent + deltaWidthPercent))),
374
+ heightPercent: clampPercent(Math.min(maxHeight, Math.max(MIN_HEIGHT_PERCENT, resizeStateRef.current.startHeightPercent + deltaHeightPercent)))
375
+ });
309
376
  }
310
377
  function handleResizePointerUp(event) {
311
378
  resizeStateRef.current = null;
@@ -313,6 +380,7 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
313
380
  event.currentTarget.releasePointerCapture(event.pointerId);
314
381
  }
315
382
  const previewText = getFieldPreviewText(field, preview);
383
+ const fieldLabel = isCustom && field.label ? field.label : field.type;
316
384
  return /* @__PURE__ */ jsxs(
317
385
  "div",
318
386
  {
@@ -341,11 +409,37 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
341
409
  height: "100%",
342
410
  width: "100%",
343
411
  alignItems: "stretch",
344
- justifyContent: "space-between"
412
+ justifyContent: "space-between",
413
+ boxSizing: "border-box",
414
+ overflow: "hidden"
345
415
  },
346
416
  children: [
347
417
  /* @__PURE__ */ jsxs("div", { "data-slot": "signature-field-preview", children: [
348
- /* @__PURE__ */ jsx("div", { "data-slot": "signature-field-label", children: field.type }),
418
+ isCustom && editMode === "label" ? /* @__PURE__ */ jsx(
419
+ "input",
420
+ {
421
+ ref: labelInputRef,
422
+ "data-slot": "signature-field-label-input",
423
+ value: labelDraft,
424
+ placeholder: "Field name\u2026",
425
+ onChange: (e) => setLabelDraft(e.target.value),
426
+ onBlur: handleLabelBlur,
427
+ onKeyDown: handleLabelKeyDown,
428
+ onPointerDown: (e) => e.stopPropagation()
429
+ }
430
+ ) : /* @__PURE__ */ jsx(
431
+ "div",
432
+ {
433
+ "data-slot": "signature-field-label",
434
+ title: isCustom ? "Click to rename" : void 0,
435
+ style: { cursor: isCustom ? "text" : void 0 },
436
+ onClick: isCustom ? () => {
437
+ setLabelDraft(field.label ?? "");
438
+ setEditMode("label");
439
+ } : void 0,
440
+ children: fieldLabel
441
+ }
442
+ ),
349
443
  field.type === "signature" && preview.signatureDataUrl ? /* @__PURE__ */ jsx(
350
444
  "img",
351
445
  {
@@ -354,7 +448,30 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
354
448
  alt: "signature preview",
355
449
  draggable: false
356
450
  }
357
- ) : /* @__PURE__ */ jsx("div", { "data-slot": "signature-field-preview-text", children: previewText || "\u2014" })
451
+ ) : isCustom && editMode === "value" ? /* @__PURE__ */ jsx(
452
+ "input",
453
+ {
454
+ ref: valueInputRef,
455
+ "data-slot": "signature-field-value-input",
456
+ value: valueDraft,
457
+ placeholder: "Value\u2026",
458
+ onChange: (e) => setValueDraft(e.target.value),
459
+ onBlur: handleValueBlur,
460
+ onKeyDown: handleValueKeyDown,
461
+ onPointerDown: (e) => e.stopPropagation()
462
+ }
463
+ ) : /* @__PURE__ */ jsx(
464
+ "div",
465
+ {
466
+ "data-slot": "signature-field-preview-text",
467
+ style: { cursor: isCustom && editMode === "idle" ? "text" : void 0 },
468
+ onClick: isCustom && editMode === "idle" ? () => {
469
+ setValueDraft(preview.customFields?.[field.label ?? ""] ?? "");
470
+ setEditMode("value");
471
+ } : void 0,
472
+ children: previewText || "\u2014"
473
+ }
474
+ )
358
475
  ] }),
359
476
  /* @__PURE__ */ jsx(
360
477
  "button",
@@ -401,6 +518,8 @@ function FieldOverlay({
401
518
  onAddField,
402
519
  onUpdateField,
403
520
  onRemoveField,
521
+ onUpdateCustomValue,
522
+ onCustomFieldRenamed,
404
523
  preview,
405
524
  className
406
525
  }) {
@@ -439,6 +558,8 @@ function FieldOverlay({
439
558
  field,
440
559
  onUpdateField,
441
560
  onRemoveField,
561
+ onUpdateCustomValue,
562
+ onCustomFieldRenamed,
442
563
  preview
443
564
  },
444
565
  field.id
@@ -450,11 +571,12 @@ var FIELD_LABELS = {
450
571
  signature: "Signature",
451
572
  fullName: "Full Name",
452
573
  title: "Title",
453
- date: "Date"
574
+ date: "Date",
575
+ custom: "Custom"
454
576
  };
455
- var FIELD_TYPES = ["signature", "fullName", "title", "date"];
456
- function FieldPalette({ selectedFieldType, onSelectFieldType, className }) {
457
- return /* @__PURE__ */ jsx("div", { "data-slot": "field-palette", className: cn(className), children: FIELD_TYPES.map((fieldType) => {
577
+ var DEFAULT_FIELD_TYPES = ["signature", "fullName", "title", "date"];
578
+ function FieldPalette({ selectedFieldType, onSelectFieldType, fieldTypes = DEFAULT_FIELD_TYPES, className }) {
579
+ return /* @__PURE__ */ jsx("div", { "data-slot": "field-palette", className: cn(className), children: fieldTypes.map((fieldType) => {
458
580
  const isSelected = selectedFieldType === fieldType;
459
581
  return /* @__PURE__ */ jsx(
460
582
  "button",
@@ -663,6 +785,74 @@ function SigningComplete({
663
785
  ] })
664
786
  ] });
665
787
  }
788
+ function CustomFieldInputs({ labels, values, onValuesChange, className }) {
789
+ if (labels.length === 0) return null;
790
+ function handleChange(label, value) {
791
+ onValuesChange({ ...values, [label]: value });
792
+ }
793
+ function toInputId(label) {
794
+ const normalized = label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
795
+ return normalized ? `custom-field-${normalized}` : "custom-field";
796
+ }
797
+ const uniqueLabels = [...new Set(labels)];
798
+ return /* @__PURE__ */ jsx("div", { "data-slot": "custom-field-inputs", className: cn(className), children: uniqueLabels.map((label) => /* @__PURE__ */ jsxs("div", { "data-slot": "custom-field-inputs-field", children: [
799
+ /* @__PURE__ */ jsx("label", { "data-slot": "custom-field-inputs-label", htmlFor: toInputId(label), children: label }),
800
+ /* @__PURE__ */ jsx(
801
+ "input",
802
+ {
803
+ "data-slot": "custom-field-inputs-input",
804
+ id: toInputId(label),
805
+ type: "text",
806
+ value: values[label] ?? "",
807
+ onChange: (event) => handleChange(label, event.target.value)
808
+ }
809
+ )
810
+ ] }, label)) });
811
+ }
812
+ function CustomFieldsPanel({
813
+ fields,
814
+ values,
815
+ onValuesChange,
816
+ isPlacingField,
817
+ onTogglePlacing,
818
+ className
819
+ }) {
820
+ const seen = /* @__PURE__ */ new Set();
821
+ const uniqueLabels = [];
822
+ for (const f of fields) {
823
+ if (f.type === "custom" && f.label && !seen.has(f.label)) {
824
+ seen.add(f.label);
825
+ uniqueLabels.push(f.label);
826
+ }
827
+ }
828
+ function handleChange(label, value) {
829
+ onValuesChange({ ...values, [label]: value });
830
+ }
831
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "custom-fields-panel", className: cn(className), children: [
832
+ /* @__PURE__ */ jsx("h2", { "data-slot": "custom-fields-panel-heading", children: "Custom Fields" }),
833
+ uniqueLabels.map((label) => /* @__PURE__ */ jsxs("label", { "data-slot": "custom-fields-panel-label", children: [
834
+ label,
835
+ /* @__PURE__ */ jsx(
836
+ "input",
837
+ {
838
+ "data-slot": "custom-fields-panel-input",
839
+ value: values[label] ?? "",
840
+ onChange: (e) => handleChange(label, e.target.value)
841
+ }
842
+ )
843
+ ] }, label)),
844
+ /* @__PURE__ */ jsx(
845
+ "button",
846
+ {
847
+ type: "button",
848
+ "data-slot": "custom-fields-panel-add-button",
849
+ "data-state": isPlacingField ? "placing" : "idle",
850
+ onClick: onTogglePlacing,
851
+ children: isPlacingField ? "Click on the PDF to place\u2026" : "+ New field"
852
+ }
853
+ )
854
+ ] });
855
+ }
666
856
  function isArrayBuffer(value) {
667
857
  return value instanceof ArrayBuffer;
668
858
  }
@@ -836,9 +1026,9 @@ function buildFieldId() {
836
1026
  function useFieldPlacement(options = {}) {
837
1027
  const defaultWidthPercent = options.defaultWidthPercent ?? 25;
838
1028
  const defaultHeightPercent = options.defaultHeightPercent ?? 5;
839
- const [fields, setFields] = useState([]);
1029
+ const [fields, setFields] = useState(options.initialFields ?? []);
840
1030
  const addField = useCallback(
841
- ({ pageIndex, type, xPercent, yPercent }) => {
1031
+ ({ pageIndex, type, xPercent, yPercent, label }) => {
842
1032
  const field = {
843
1033
  id: buildFieldId(),
844
1034
  pageIndex,
@@ -846,13 +1036,23 @@ function useFieldPlacement(options = {}) {
846
1036
  xPercent: clampPercent2(xPercent),
847
1037
  yPercent: clampPercent2(yPercent),
848
1038
  widthPercent: clampPercent2(defaultWidthPercent),
849
- heightPercent: clampPercent2(defaultHeightPercent)
1039
+ heightPercent: clampPercent2(defaultHeightPercent),
1040
+ ...label !== void 0 && { label }
850
1041
  };
851
1042
  setFields((previousFields) => [...previousFields, field]);
852
1043
  return field;
853
1044
  },
854
1045
  [defaultHeightPercent, defaultWidthPercent]
855
1046
  );
1047
+ const appendFields = useCallback((nextFields) => {
1048
+ if (!nextFields.length) return;
1049
+ setFields((previousFields) => {
1050
+ const existingIds = new Set(previousFields.map((field) => field.id));
1051
+ const uniqueNextFields = nextFields.filter((field) => !existingIds.has(field.id));
1052
+ if (!uniqueNextFields.length) return previousFields;
1053
+ return [...previousFields, ...uniqueNextFields];
1054
+ });
1055
+ }, []);
856
1056
  const updateField = useCallback((id, partial) => {
857
1057
  setFields(
858
1058
  (previousFields) => previousFields.map((field) => {
@@ -877,11 +1077,12 @@ function useFieldPlacement(options = {}) {
877
1077
  const fieldActions = useMemo(
878
1078
  () => ({
879
1079
  addField,
1080
+ appendFields,
880
1081
  updateField,
881
1082
  removeField,
882
1083
  clearFields
883
1084
  }),
884
- [addField, clearFields, removeField, updateField]
1085
+ [addField, appendFields, clearFields, removeField, updateField]
885
1086
  );
886
1087
  return {
887
1088
  fields,
@@ -1067,6 +1268,27 @@ async function modifyPdf({
1067
1268
  });
1068
1269
  continue;
1069
1270
  }
1271
+ if (field.type === "custom") {
1272
+ const pad = 2;
1273
+ page.drawRectangle({
1274
+ x: pointRect.x - pad,
1275
+ y: pointRect.y - pad,
1276
+ width: pointRect.width + pad * 2,
1277
+ height: pointRect.height + pad * 2,
1278
+ color: rgb(1, 1, 1)
1279
+ });
1280
+ const customValue = field.label ? signer.customFields?.[field.label] ?? "" : "";
1281
+ if (customValue) {
1282
+ const textSize2 = Math.max(9, Math.min(16, pointRect.height * 0.6));
1283
+ page.drawText(customValue, {
1284
+ x: pointRect.x + 2,
1285
+ y: pointRect.y + Math.max(0, (pointRect.height - textSize2) / 2),
1286
+ size: textSize2,
1287
+ font: helveticaFont
1288
+ });
1289
+ }
1290
+ continue;
1291
+ }
1070
1292
  const textValue = field.type === "fullName" ? fullNameFromSigner(signer) : field.type === "title" ? signer.title : field.type === "date" ? resolvedDateText : "";
1071
1293
  if (!textValue) continue;
1072
1294
  const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6));
@@ -1114,6 +1336,13 @@ var SLOTS = {
1114
1336
  signatureFieldPreviewText: "signature-field-preview-text",
1115
1337
  signatureFieldRemove: "signature-field-remove",
1116
1338
  signatureFieldResize: "signature-field-resize",
1339
+ signatureFieldLabelInput: "signature-field-label-input",
1340
+ signatureFieldValueInput: "signature-field-value-input",
1341
+ customFieldsPanel: "custom-fields-panel",
1342
+ customFieldsPanelHeading: "custom-fields-panel-heading",
1343
+ customFieldsPanelLabel: "custom-fields-panel-label",
1344
+ customFieldsPanelInput: "custom-fields-panel-input",
1345
+ customFieldsPanelAddButton: "custom-fields-panel-add-button",
1117
1346
  fieldPalette: "field-palette",
1118
1347
  fieldPaletteButton: "field-palette-button",
1119
1348
  signerPanel: "signer-panel",
@@ -1151,6 +1380,6 @@ var defaults = {
1151
1380
  DEFAULT_FIELD_HEIGHT_PERCENT: 5
1152
1381
  };
1153
1382
 
1154
- export { FieldOverlay, FieldPalette, PdfPageNavigator, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, usePdfPageVisibility, useSignatureRenderer };
1383
+ export { CustomFieldInputs, CustomFieldsPanel, FieldOverlay, FieldPalette, PdfPageNavigator, PdfViewer, SIGNATURE_FONTS, SLOTS, SignatureField, SignaturePad, SignaturePreview, SignerDetailsPanel, SigningComplete, configure, defaults, loadSignatureFont, mapFromPoints, mapToPoints, modifyPdf, sha256, useFieldPlacement, usePdfDocument, usePdfPageVisibility, useSignatureRenderer };
1155
1384
  //# sourceMappingURL=index.js.map
1156
1385
  //# sourceMappingURL=index.js.map