@drvillo/react-browser-e-signing 0.3.1 → 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,14 +380,13 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
313
380
  event.currentTarget.releasePointerCapture(event.pointerId);
314
381
  }
315
382
  const previewText = getFieldPreviewText(field, preview);
316
- const isLocked = field.locked === true;
383
+ const fieldLabel = isCustom && field.label ? field.label : field.type;
317
384
  return /* @__PURE__ */ jsxs(
318
385
  "div",
319
386
  {
320
387
  ref: rootRef,
321
388
  "data-slot": "signature-field",
322
389
  "data-field-type": field.type,
323
- "data-locked": isLocked ? true : void 0,
324
390
  className: cn(className),
325
391
  style: {
326
392
  position: "absolute",
@@ -328,18 +394,11 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
328
394
  top: `${field.yPercent}%`,
329
395
  width: `${field.widthPercent}%`,
330
396
  height: `${field.heightPercent}%`,
331
- userSelect: "none",
332
- cursor: isLocked ? "default" : void 0,
333
- ...isLocked && {
334
- borderStyle: "dashed",
335
- borderWidth: 1,
336
- borderColor: "rgba(100, 116, 139, 0.55)",
337
- boxSizing: "border-box"
338
- }
397
+ userSelect: "none"
339
398
  },
340
- onPointerDown: isLocked ? void 0 : handleDragPointerDown,
341
- onPointerMove: isLocked ? void 0 : handleDragPointerMove,
342
- onPointerUp: isLocked ? void 0 : handleDragPointerUp,
399
+ onPointerDown: handleDragPointerDown,
400
+ onPointerMove: handleDragPointerMove,
401
+ onPointerUp: handleDragPointerUp,
343
402
  children: [
344
403
  /* @__PURE__ */ jsxs(
345
404
  "div",
@@ -350,11 +409,37 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
350
409
  height: "100%",
351
410
  width: "100%",
352
411
  alignItems: "stretch",
353
- justifyContent: "space-between"
412
+ justifyContent: "space-between",
413
+ boxSizing: "border-box",
414
+ overflow: "hidden"
354
415
  },
355
416
  children: [
356
417
  /* @__PURE__ */ jsxs("div", { "data-slot": "signature-field-preview", children: [
357
- /* @__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
+ ),
358
443
  field.type === "signature" && preview.signatureDataUrl ? /* @__PURE__ */ jsx(
359
444
  "img",
360
445
  {
@@ -363,41 +448,32 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
363
448
  alt: "signature preview",
364
449
  draggable: false
365
450
  }
366
- ) : /* @__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
+ )
367
475
  ] }),
368
- isLocked ? /* @__PURE__ */ jsx(
369
- "div",
370
- {
371
- "data-slot": "signature-field-lock",
372
- "aria-label": "Locked field",
373
- style: {
374
- flexShrink: 0,
375
- display: "flex",
376
- alignItems: "flex-start",
377
- justifyContent: "center",
378
- padding: "2px",
379
- color: "rgba(71, 85, 105, 0.9)"
380
- },
381
- children: /* @__PURE__ */ jsxs(
382
- "svg",
383
- {
384
- width: "14",
385
- height: "14",
386
- viewBox: "0 0 24 24",
387
- fill: "none",
388
- stroke: "currentColor",
389
- strokeWidth: "2",
390
- strokeLinecap: "round",
391
- strokeLinejoin: "round",
392
- "aria-hidden": true,
393
- children: [
394
- /* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
395
- /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
396
- ]
397
- }
398
- )
399
- }
400
- ) : /* @__PURE__ */ jsx(
476
+ /* @__PURE__ */ jsx(
401
477
  "button",
402
478
  {
403
479
  type: "button",
@@ -414,7 +490,7 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
414
490
  ]
415
491
  }
416
492
  ),
417
- !isLocked ? /* @__PURE__ */ jsx(
493
+ /* @__PURE__ */ jsx(
418
494
  "div",
419
495
  {
420
496
  "data-slot": "signature-field-resize",
@@ -430,7 +506,7 @@ function SignatureField({ field, onUpdateField, onRemoveField, preview, classNam
430
506
  onPointerMove: handleResizePointerMove,
431
507
  onPointerUp: handleResizePointerUp
432
508
  }
433
- ) : null
509
+ )
434
510
  ]
435
511
  }
436
512
  );
@@ -442,6 +518,8 @@ function FieldOverlay({
442
518
  onAddField,
443
519
  onUpdateField,
444
520
  onRemoveField,
521
+ onUpdateCustomValue,
522
+ onCustomFieldRenamed,
445
523
  preview,
446
524
  className
447
525
  }) {
@@ -480,6 +558,8 @@ function FieldOverlay({
480
558
  field,
481
559
  onUpdateField,
482
560
  onRemoveField,
561
+ onUpdateCustomValue,
562
+ onCustomFieldRenamed,
483
563
  preview
484
564
  },
485
565
  field.id
@@ -491,11 +571,12 @@ var FIELD_LABELS = {
491
571
  signature: "Signature",
492
572
  fullName: "Full Name",
493
573
  title: "Title",
494
- date: "Date"
574
+ date: "Date",
575
+ custom: "Custom"
495
576
  };
496
- var FIELD_TYPES = ["signature", "fullName", "title", "date"];
497
- function FieldPalette({ selectedFieldType, onSelectFieldType, className }) {
498
- 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) => {
499
580
  const isSelected = selectedFieldType === fieldType;
500
581
  return /* @__PURE__ */ jsx(
501
582
  "button",
@@ -704,6 +785,74 @@ function SigningComplete({
704
785
  ] })
705
786
  ] });
706
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
+ }
707
856
  function isArrayBuffer(value) {
708
857
  return value instanceof ArrayBuffer;
709
858
  }
@@ -879,7 +1028,7 @@ function useFieldPlacement(options = {}) {
879
1028
  const defaultHeightPercent = options.defaultHeightPercent ?? 5;
880
1029
  const [fields, setFields] = useState(options.initialFields ?? []);
881
1030
  const addField = useCallback(
882
- ({ pageIndex, type, xPercent, yPercent }) => {
1031
+ ({ pageIndex, type, xPercent, yPercent, label }) => {
883
1032
  const field = {
884
1033
  id: buildFieldId(),
885
1034
  pageIndex,
@@ -887,13 +1036,23 @@ function useFieldPlacement(options = {}) {
887
1036
  xPercent: clampPercent2(xPercent),
888
1037
  yPercent: clampPercent2(yPercent),
889
1038
  widthPercent: clampPercent2(defaultWidthPercent),
890
- heightPercent: clampPercent2(defaultHeightPercent)
1039
+ heightPercent: clampPercent2(defaultHeightPercent),
1040
+ ...label !== void 0 && { label }
891
1041
  };
892
1042
  setFields((previousFields) => [...previousFields, field]);
893
1043
  return field;
894
1044
  },
895
1045
  [defaultHeightPercent, defaultWidthPercent]
896
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
+ }, []);
897
1056
  const updateField = useCallback((id, partial) => {
898
1057
  setFields(
899
1058
  (previousFields) => previousFields.map((field) => {
@@ -918,11 +1077,12 @@ function useFieldPlacement(options = {}) {
918
1077
  const fieldActions = useMemo(
919
1078
  () => ({
920
1079
  addField,
1080
+ appendFields,
921
1081
  updateField,
922
1082
  removeField,
923
1083
  clearFields
924
1084
  }),
925
- [addField, clearFields, removeField, updateField]
1085
+ [addField, appendFields, clearFields, removeField, updateField]
926
1086
  );
927
1087
  return {
928
1088
  fields,
@@ -1108,6 +1268,27 @@ async function modifyPdf({
1108
1268
  });
1109
1269
  continue;
1110
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
+ }
1111
1292
  const textValue = field.type === "fullName" ? fullNameFromSigner(signer) : field.type === "title" ? signer.title : field.type === "date" ? resolvedDateText : "";
1112
1293
  if (!textValue) continue;
1113
1294
  const textSize = Math.max(9, Math.min(16, pointRect.height * 0.6));
@@ -1155,7 +1336,13 @@ var SLOTS = {
1155
1336
  signatureFieldPreviewText: "signature-field-preview-text",
1156
1337
  signatureFieldRemove: "signature-field-remove",
1157
1338
  signatureFieldResize: "signature-field-resize",
1158
- signatureFieldLock: "signature-field-lock",
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",
1159
1346
  fieldPalette: "field-palette",
1160
1347
  fieldPaletteButton: "field-palette-button",
1161
1348
  signerPanel: "signer-panel",
@@ -1193,6 +1380,6 @@ var defaults = {
1193
1380
  DEFAULT_FIELD_HEIGHT_PERCENT: 5
1194
1381
  };
1195
1382
 
1196
- 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 };
1197
1384
  //# sourceMappingURL=index.js.map
1198
1385
  //# sourceMappingURL=index.js.map