@josephomills/esign 0.8.0 → 0.9.1

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/ui/index.js CHANGED
@@ -313,7 +313,7 @@ function PdfViewer({ url, placements, workerSrc, primaryColor = "#4f46e5" }) {
313
313
  opacity: 0.85,
314
314
  whiteSpace: "nowrap"
315
315
  },
316
- children: "\u270D Sign here"
316
+ children: pl.type === "date" ? "\u{1F4C5} Select a date" : "\u270D Sign here"
317
317
  }
318
318
  )
319
319
  },
@@ -324,6 +324,27 @@ function PdfViewer({ url, placements, workerSrc, primaryColor = "#4f46e5" }) {
324
324
  )
325
325
  ] });
326
326
  }
327
+
328
+ // src/shared/fields.ts
329
+ var DATE_FORMATS = [
330
+ "DD/MM/YYYY",
331
+ "MM/DD/YYYY",
332
+ "YYYY-MM-DD",
333
+ "D MMM YYYY",
334
+ "Do MMM YYYY",
335
+ "Do MMMM YYYY",
336
+ "MMMM D, YYYY"
337
+ ];
338
+ var DEFAULT_DATE_FORMAT = "Do MMMM YYYY";
339
+ var DATE_FORMAT_LABELS = {
340
+ "DD/MM/YYYY": "DD/MM/YYYY",
341
+ "MM/DD/YYYY": "MM/DD/YYYY",
342
+ "YYYY-MM-DD": "YYYY-MM-DD",
343
+ "D MMM YYYY": "D Mon YYYY",
344
+ "Do MMM YYYY": "Do Mon YYYY",
345
+ "Do MMMM YYYY": "Do Month YYYY",
346
+ "MMMM D, YYYY": "Month D, YYYY"
347
+ };
327
348
  var clamp = (v, min, max) => Math.min(Math.max(v, min), max);
328
349
  var MIN_W = 0.04;
329
350
  var MIN_H = 0.02;
@@ -341,10 +362,13 @@ function FieldDesigner({
341
362
  const [failed, setFailed] = useState(false);
342
363
  const [selectedId, setSelectedId] = useState(value[0]?.id ?? null);
343
364
  const [drawMode, setDrawMode] = useState(false);
365
+ const [pendingType, setPendingType] = useState("signature");
366
+ const [configId, setConfigId] = useState(null);
344
367
  const [drag, setDrag] = useState(null);
345
368
  const [live, setLive] = useState(null);
346
369
  const stageRef = useRef(null);
347
370
  const canvasRef = useRef(null);
371
+ const minDateRef = useRef(null);
348
372
  const docRef = useRef(null);
349
373
  useEffect(() => {
350
374
  let cancelled = false;
@@ -433,15 +457,21 @@ function FieldDesigner({
433
457
  function onPointerUp() {
434
458
  if (drag && live && live.w >= MIN_W && live.h >= MIN_H) {
435
459
  if (drag.mode === "new") {
460
+ const isDate = pendingType === "date";
461
+ const n = value.filter((v) => v.type === pendingType).length + 1;
462
+ const base = isDate ? "Date" : "Signature";
436
463
  const f = {
437
464
  id: newId(),
438
- label: value.length === 0 ? "Signature" : `Signature ${value.length + 1}`,
465
+ label: n === 1 ? base : `${base} ${n}`,
466
+ type: pendingType,
467
+ ...isDate ? { dateFormat: DEFAULT_DATE_FORMAT, minDate: null } : {},
439
468
  page,
440
469
  ...live
441
470
  };
442
471
  onChange([...value, f]);
443
472
  setSelectedId(f.id);
444
473
  setDrawMode(false);
474
+ if (isDate) setConfigId(f.id);
445
475
  } else {
446
476
  onChange(value.map((x) => x.id === drag.id ? { ...x, ...live } : x));
447
477
  }
@@ -452,10 +482,15 @@ function FieldDesigner({
452
482
  function removeField(id) {
453
483
  onChange(value.filter((f) => f.id !== id));
454
484
  if (selectedId === id) setSelectedId(null);
485
+ if (configId === id) setConfigId(null);
455
486
  }
456
487
  function rename(id, label) {
457
488
  onChange(value.map((f) => f.id === id ? { ...f, label } : f));
458
489
  }
490
+ function setConfig(id, patch) {
491
+ onChange(value.map((f) => f.id === id ? { ...f, ...patch } : f));
492
+ }
493
+ const configField = value.find((f) => f.id === configId && f.type === "date") ?? null;
459
494
  const handle = (id, geom, c) => {
460
495
  const cx = c[1] === "w" ? geom.x : geom.x + geom.w;
461
496
  const cy = c[0] === "n" ? geom.y : geom.y + geom.h;
@@ -501,25 +536,35 @@ function FieldDesigner({
501
536
  flexWrap: "wrap"
502
537
  },
503
538
  children: [
504
- /* @__PURE__ */ jsx(
505
- "button",
506
- {
507
- type: "button",
508
- onClick: () => setDrawMode((d) => !d),
509
- style: {
510
- fontSize: 13,
511
- fontWeight: 600,
512
- padding: "5px 12px",
513
- borderRadius: 6,
514
- border: `1px solid ${primaryColor}`,
515
- background: drawMode ? primaryColor : "#fff",
516
- color: drawMode ? "#fff" : primaryColor,
517
- cursor: "pointer"
539
+ ["signature", "date"].map((t) => {
540
+ const active = drawMode && pendingType === t;
541
+ return /* @__PURE__ */ jsx(
542
+ "button",
543
+ {
544
+ type: "button",
545
+ onClick: () => {
546
+ if (active) setDrawMode(false);
547
+ else {
548
+ setPendingType(t);
549
+ setDrawMode(true);
550
+ }
551
+ },
552
+ style: {
553
+ fontSize: 13,
554
+ fontWeight: 600,
555
+ padding: "5px 12px",
556
+ borderRadius: 6,
557
+ border: `1px solid ${primaryColor}`,
558
+ background: active ? primaryColor : "#fff",
559
+ color: active ? "#fff" : primaryColor,
560
+ cursor: "pointer"
561
+ },
562
+ children: t === "date" ? "+ Date" : "+ Signature"
518
563
  },
519
- children: drawMode ? "Now drag on the page\u2026" : "+ Add field"
520
- }
521
- ),
522
- /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#6b7280" }, children: "or drag on the page (desktop)." }),
564
+ t
565
+ );
566
+ }),
567
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#6b7280" }, children: drawMode ? `Drag a ${pendingType} box on the page\u2026` : "or drag on the page." }),
523
568
  numPages > 1 ? /* @__PURE__ */ jsxs("label", { style: { marginLeft: "auto", fontSize: 13, color: "#374151" }, children: [
524
569
  "Page",
525
570
  " ",
@@ -590,8 +635,9 @@ function FieldDesigner({
590
635
  padding: "0 4px"
591
636
  },
592
637
  children: [
593
- "\u270D ",
594
- f.label || "Signature"
638
+ f.type === "date" ? "\u{1F4C5}" : "\u270D",
639
+ " ",
640
+ f.label || (f.type === "date" ? "Date" : "Signature")
595
641
  ]
596
642
  }
597
643
  )
@@ -630,7 +676,39 @@ function FieldDesigner({
630
676
  },
631
677
  children: "\xD7"
632
678
  }
633
- )
679
+ ),
680
+ f.type === "date" ? /* @__PURE__ */ jsx(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ "aria-label": `Configure ${f.label}`,
685
+ onPointerDown: (e) => e.stopPropagation(),
686
+ onClick: (e) => {
687
+ e.stopPropagation();
688
+ setConfigId(f.id);
689
+ },
690
+ style: {
691
+ position: "absolute",
692
+ left: `${geom.x * 100}%`,
693
+ top: `${geom.y * 100}%`,
694
+ transform: "translate(-50%, -50%)",
695
+ marginTop: -18,
696
+ width: 22,
697
+ height: 22,
698
+ borderRadius: "50%",
699
+ background: "#fff",
700
+ color: primaryColor,
701
+ border: `2px solid ${primaryColor}`,
702
+ boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
703
+ fontSize: 12,
704
+ lineHeight: "14px",
705
+ cursor: "pointer",
706
+ padding: 0,
707
+ touchAction: "none"
708
+ },
709
+ children: "\u2699"
710
+ }
711
+ ) : null
634
712
  ] }) : null
635
713
  ] }, f.id);
636
714
  }),
@@ -652,11 +730,161 @@ function FieldDesigner({
652
730
  ) : null
653
731
  ]
654
732
  }
655
- )
733
+ ),
734
+ configField ? /* @__PURE__ */ jsxs(
735
+ "div",
736
+ {
737
+ style: {
738
+ marginTop: 10,
739
+ border: `1px solid ${primaryColor}66`,
740
+ borderRadius: 8,
741
+ padding: 12,
742
+ background: "#fff",
743
+ fontSize: 13,
744
+ color: "#374151"
745
+ },
746
+ children: [
747
+ /* @__PURE__ */ jsxs(
748
+ "div",
749
+ {
750
+ style: {
751
+ display: "flex",
752
+ alignItems: "center",
753
+ justifyContent: "space-between",
754
+ marginBottom: 8
755
+ },
756
+ children: [
757
+ /* @__PURE__ */ jsxs("strong", { children: [
758
+ "\u{1F4C5} ",
759
+ configField.label || "Date"
760
+ ] }),
761
+ /* @__PURE__ */ jsx(
762
+ "button",
763
+ {
764
+ type: "button",
765
+ onClick: () => setConfigId(null),
766
+ style: {
767
+ fontSize: 12,
768
+ padding: "3px 10px",
769
+ borderRadius: 6,
770
+ border: `1px solid ${primaryColor}`,
771
+ background: primaryColor,
772
+ color: "#fff",
773
+ cursor: "pointer"
774
+ },
775
+ children: "Done"
776
+ }
777
+ )
778
+ ]
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 10 }, children: [
782
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "Display format" }),
783
+ /* @__PURE__ */ jsx(
784
+ "div",
785
+ {
786
+ style: {
787
+ display: "flex",
788
+ flexDirection: "column",
789
+ border: "1px solid #e5e7eb",
790
+ borderRadius: 8,
791
+ overflow: "hidden"
792
+ },
793
+ children: DATE_FORMATS.map((fmt) => {
794
+ const active = (configField.dateFormat ?? DEFAULT_DATE_FORMAT) === fmt;
795
+ return /* @__PURE__ */ jsxs(
796
+ "button",
797
+ {
798
+ type: "button",
799
+ onClick: () => setConfig(configField.id, { dateFormat: fmt }),
800
+ style: {
801
+ display: "flex",
802
+ alignItems: "center",
803
+ justifyContent: "space-between",
804
+ padding: "7px 10px",
805
+ border: "none",
806
+ borderTop: "1px solid #f3f4f6",
807
+ textAlign: "left",
808
+ background: active ? `${primaryColor}14` : "#fff",
809
+ color: active ? primaryColor : "#374151",
810
+ fontWeight: active ? 600 : 400,
811
+ fontSize: 13,
812
+ cursor: "pointer"
813
+ },
814
+ children: [
815
+ /* @__PURE__ */ jsx("span", { children: DATE_FORMAT_LABELS[fmt] }),
816
+ active ? /* @__PURE__ */ jsx("span", { children: "\u2713" }) : null
817
+ ]
818
+ },
819
+ fmt
820
+ );
821
+ })
822
+ }
823
+ )
824
+ ] }),
825
+ /* @__PURE__ */ jsxs("div", { children: [
826
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "Earliest date" }),
827
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
828
+ /* @__PURE__ */ jsx(
829
+ "input",
830
+ {
831
+ ref: minDateRef,
832
+ type: "date",
833
+ value: configField.minDate ?? "",
834
+ onChange: (e) => setConfig(configField.id, { minDate: e.target.value || null }),
835
+ style: {
836
+ padding: "5px 8px",
837
+ borderRadius: 6,
838
+ border: "1px solid #d1d5db",
839
+ fontSize: 13
840
+ }
841
+ }
842
+ ),
843
+ /* @__PURE__ */ jsx(
844
+ "button",
845
+ {
846
+ type: "button",
847
+ "aria-label": "Open calendar",
848
+ onClick: () => minDateRef.current?.showPicker?.(),
849
+ style: {
850
+ padding: "5px 8px",
851
+ borderRadius: 6,
852
+ border: `1px solid ${primaryColor}`,
853
+ background: "#fff",
854
+ cursor: "pointer",
855
+ fontSize: 14,
856
+ lineHeight: 1
857
+ },
858
+ children: "\u{1F4C5}"
859
+ }
860
+ ),
861
+ configField.minDate ? /* @__PURE__ */ jsx(
862
+ "button",
863
+ {
864
+ type: "button",
865
+ onClick: () => setConfig(configField.id, { minDate: null }),
866
+ style: {
867
+ padding: "5px 8px",
868
+ borderRadius: 6,
869
+ border: "1px solid #d1d5db",
870
+ background: "#fff",
871
+ color: "#6b7280",
872
+ cursor: "pointer",
873
+ fontSize: 12
874
+ },
875
+ children: "Clear"
876
+ }
877
+ ) : null
878
+ ] }),
879
+ /* @__PURE__ */ jsx("span", { style: { color: "#9ca3af", fontSize: 11 }, children: "Leave blank to use the document's creation date." })
880
+ ] })
881
+ ]
882
+ }
883
+ ) : null
656
884
  ] }),
657
885
  hideFieldList ? null : /* @__PURE__ */ jsxs("div", { style: { flex: "1 1 200px", minWidth: 180, maxWidth: 300 }, children: [
658
886
  /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, fontWeight: 600, color: "#374151", margin: "0 0 8px" }, children: [
659
- "Signature fields (",
887
+ "Fields (",
660
888
  value.length,
661
889
  ")"
662
890
  ] }),
@@ -678,6 +906,7 @@ function FieldDesigner({
678
906
  cursor: "pointer"
679
907
  },
680
908
  children: [
909
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13 }, children: f.type === "date" ? "\u{1F4C5}" : "\u270D" }),
681
910
  /* @__PURE__ */ jsx(
682
911
  "input",
683
912
  {
@@ -699,6 +928,27 @@ function FieldDesigner({
699
928
  "p",
700
929
  f.page
701
930
  ] }),
931
+ f.type === "date" ? /* @__PURE__ */ jsx(
932
+ "button",
933
+ {
934
+ type: "button",
935
+ "aria-label": `Configure ${f.label}`,
936
+ onClick: (e) => {
937
+ e.stopPropagation();
938
+ setConfigId(f.id);
939
+ },
940
+ style: {
941
+ border: "none",
942
+ background: "transparent",
943
+ color: "#6b7280",
944
+ cursor: "pointer",
945
+ fontSize: 13,
946
+ lineHeight: 1,
947
+ padding: 0
948
+ },
949
+ children: "\u2699"
950
+ }
951
+ ) : null,
702
952
  /* @__PURE__ */ jsx(
703
953
  "button",
704
954
  {
@@ -731,9 +981,12 @@ function SigningExperience(props) {
731
981
  const primary = props.branding?.primaryColor ?? "#4f46e5";
732
982
  const [signaturePng, setSignaturePng] = useState(null);
733
983
  const [consent, setConsent] = useState({ signerName: "", consent: false });
984
+ const [dateValues, setDateValues] = useState({});
734
985
  const [submitting, setSubmitting] = useState(false);
735
986
  const [error, setError] = useState(null);
736
- const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && !submitting;
987
+ const dateFields = (props.placements ?? []).filter((p) => p.type === "date" && p.id);
988
+ const allDatesFilled = dateFields.every((f) => dateValues[f.id]);
989
+ const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && allDatesFilled && !submitting;
737
990
  async function submit() {
738
991
  if (!canSubmit) return;
739
992
  setSubmitting(true);
@@ -745,7 +998,8 @@ function SigningExperience(props) {
745
998
  body: JSON.stringify({
746
999
  signaturePng,
747
1000
  signerName: consent.signerName.trim(),
748
- consent: true
1001
+ consent: true,
1002
+ dateValues
749
1003
  })
750
1004
  });
751
1005
  if (!res.ok) {
@@ -790,6 +1044,31 @@ function SigningExperience(props) {
790
1044
  /* @__PURE__ */ jsx("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: "Your signature" }),
791
1045
  /* @__PURE__ */ jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsx(SignaturePad, { primaryColor: primary, onChange: setSignaturePng }) })
792
1046
  ] }),
1047
+ dateFields.length > 0 ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: dateFields.map((f) => /* @__PURE__ */ jsxs("div", { children: [
1048
+ /* @__PURE__ */ jsxs("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: [
1049
+ "\u{1F4C5} ",
1050
+ f.label || "Date"
1051
+ ] }),
1052
+ /* @__PURE__ */ jsx(
1053
+ "input",
1054
+ {
1055
+ type: "date",
1056
+ value: dateValues[f.id] ?? "",
1057
+ min: f.minDate ?? void 0,
1058
+ onChange: (e) => setDateValues((prev) => ({ ...prev, [f.id]: e.target.value })),
1059
+ style: {
1060
+ display: "block",
1061
+ marginTop: 6,
1062
+ padding: "10px 12px",
1063
+ borderRadius: 8,
1064
+ border: "1px solid #d1d5db",
1065
+ fontSize: 15,
1066
+ width: "100%",
1067
+ maxWidth: 280
1068
+ }
1069
+ }
1070
+ )
1071
+ ] }, f.id)) }) : null,
793
1072
  /* @__PURE__ */ jsx(ConsentBlock, { value: consent, onChange: setConsent, primaryColor: primary }),
794
1073
  error ? /* @__PURE__ */ jsx(
795
1074
  "div",