@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.cjs CHANGED
@@ -319,7 +319,7 @@ function PdfViewer({ url, placements, workerSrc, primaryColor = "#4f46e5" }) {
319
319
  opacity: 0.85,
320
320
  whiteSpace: "nowrap"
321
321
  },
322
- children: "\u270D Sign here"
322
+ children: pl.type === "date" ? "\u{1F4C5} Select a date" : "\u270D Sign here"
323
323
  }
324
324
  )
325
325
  },
@@ -330,6 +330,27 @@ function PdfViewer({ url, placements, workerSrc, primaryColor = "#4f46e5" }) {
330
330
  )
331
331
  ] });
332
332
  }
333
+
334
+ // src/shared/fields.ts
335
+ var DATE_FORMATS = [
336
+ "DD/MM/YYYY",
337
+ "MM/DD/YYYY",
338
+ "YYYY-MM-DD",
339
+ "D MMM YYYY",
340
+ "Do MMM YYYY",
341
+ "Do MMMM YYYY",
342
+ "MMMM D, YYYY"
343
+ ];
344
+ var DEFAULT_DATE_FORMAT = "Do MMMM YYYY";
345
+ var DATE_FORMAT_LABELS = {
346
+ "DD/MM/YYYY": "DD/MM/YYYY",
347
+ "MM/DD/YYYY": "MM/DD/YYYY",
348
+ "YYYY-MM-DD": "YYYY-MM-DD",
349
+ "D MMM YYYY": "D Mon YYYY",
350
+ "Do MMM YYYY": "Do Mon YYYY",
351
+ "Do MMMM YYYY": "Do Month YYYY",
352
+ "MMMM D, YYYY": "Month D, YYYY"
353
+ };
333
354
  var clamp = (v, min, max) => Math.min(Math.max(v, min), max);
334
355
  var MIN_W = 0.04;
335
356
  var MIN_H = 0.02;
@@ -347,10 +368,13 @@ function FieldDesigner({
347
368
  const [failed, setFailed] = react.useState(false);
348
369
  const [selectedId, setSelectedId] = react.useState(value[0]?.id ?? null);
349
370
  const [drawMode, setDrawMode] = react.useState(false);
371
+ const [pendingType, setPendingType] = react.useState("signature");
372
+ const [configId, setConfigId] = react.useState(null);
350
373
  const [drag, setDrag] = react.useState(null);
351
374
  const [live, setLive] = react.useState(null);
352
375
  const stageRef = react.useRef(null);
353
376
  const canvasRef = react.useRef(null);
377
+ const minDateRef = react.useRef(null);
354
378
  const docRef = react.useRef(null);
355
379
  react.useEffect(() => {
356
380
  let cancelled = false;
@@ -439,15 +463,21 @@ function FieldDesigner({
439
463
  function onPointerUp() {
440
464
  if (drag && live && live.w >= MIN_W && live.h >= MIN_H) {
441
465
  if (drag.mode === "new") {
466
+ const isDate = pendingType === "date";
467
+ const n = value.filter((v) => v.type === pendingType).length + 1;
468
+ const base = isDate ? "Date" : "Signature";
442
469
  const f = {
443
470
  id: newId(),
444
- label: value.length === 0 ? "Signature" : `Signature ${value.length + 1}`,
471
+ label: n === 1 ? base : `${base} ${n}`,
472
+ type: pendingType,
473
+ ...isDate ? { dateFormat: DEFAULT_DATE_FORMAT, minDate: null } : {},
445
474
  page,
446
475
  ...live
447
476
  };
448
477
  onChange([...value, f]);
449
478
  setSelectedId(f.id);
450
479
  setDrawMode(false);
480
+ if (isDate) setConfigId(f.id);
451
481
  } else {
452
482
  onChange(value.map((x) => x.id === drag.id ? { ...x, ...live } : x));
453
483
  }
@@ -458,10 +488,15 @@ function FieldDesigner({
458
488
  function removeField(id) {
459
489
  onChange(value.filter((f) => f.id !== id));
460
490
  if (selectedId === id) setSelectedId(null);
491
+ if (configId === id) setConfigId(null);
461
492
  }
462
493
  function rename(id, label) {
463
494
  onChange(value.map((f) => f.id === id ? { ...f, label } : f));
464
495
  }
496
+ function setConfig(id, patch) {
497
+ onChange(value.map((f) => f.id === id ? { ...f, ...patch } : f));
498
+ }
499
+ const configField = value.find((f) => f.id === configId && f.type === "date") ?? null;
465
500
  const handle = (id, geom, c) => {
466
501
  const cx = c[1] === "w" ? geom.x : geom.x + geom.w;
467
502
  const cy = c[0] === "n" ? geom.y : geom.y + geom.h;
@@ -507,25 +542,35 @@ function FieldDesigner({
507
542
  flexWrap: "wrap"
508
543
  },
509
544
  children: [
510
- /* @__PURE__ */ jsxRuntime.jsx(
511
- "button",
512
- {
513
- type: "button",
514
- onClick: () => setDrawMode((d) => !d),
515
- style: {
516
- fontSize: 13,
517
- fontWeight: 600,
518
- padding: "5px 12px",
519
- borderRadius: 6,
520
- border: `1px solid ${primaryColor}`,
521
- background: drawMode ? primaryColor : "#fff",
522
- color: drawMode ? "#fff" : primaryColor,
523
- cursor: "pointer"
545
+ ["signature", "date"].map((t) => {
546
+ const active = drawMode && pendingType === t;
547
+ return /* @__PURE__ */ jsxRuntime.jsx(
548
+ "button",
549
+ {
550
+ type: "button",
551
+ onClick: () => {
552
+ if (active) setDrawMode(false);
553
+ else {
554
+ setPendingType(t);
555
+ setDrawMode(true);
556
+ }
557
+ },
558
+ style: {
559
+ fontSize: 13,
560
+ fontWeight: 600,
561
+ padding: "5px 12px",
562
+ borderRadius: 6,
563
+ border: `1px solid ${primaryColor}`,
564
+ background: active ? primaryColor : "#fff",
565
+ color: active ? "#fff" : primaryColor,
566
+ cursor: "pointer"
567
+ },
568
+ children: t === "date" ? "+ Date" : "+ Signature"
524
569
  },
525
- children: drawMode ? "Now drag on the page\u2026" : "+ Add field"
526
- }
527
- ),
528
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#6b7280" }, children: "or drag on the page (desktop)." }),
570
+ t
571
+ );
572
+ }),
573
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#6b7280" }, children: drawMode ? `Drag a ${pendingType} box on the page\u2026` : "or drag on the page." }),
529
574
  numPages > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { marginLeft: "auto", fontSize: 13, color: "#374151" }, children: [
530
575
  "Page",
531
576
  " ",
@@ -596,8 +641,9 @@ function FieldDesigner({
596
641
  padding: "0 4px"
597
642
  },
598
643
  children: [
599
- "\u270D ",
600
- f.label || "Signature"
644
+ f.type === "date" ? "\u{1F4C5}" : "\u270D",
645
+ " ",
646
+ f.label || (f.type === "date" ? "Date" : "Signature")
601
647
  ]
602
648
  }
603
649
  )
@@ -636,7 +682,39 @@ function FieldDesigner({
636
682
  },
637
683
  children: "\xD7"
638
684
  }
639
- )
685
+ ),
686
+ f.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ "aria-label": `Configure ${f.label}`,
691
+ onPointerDown: (e) => e.stopPropagation(),
692
+ onClick: (e) => {
693
+ e.stopPropagation();
694
+ setConfigId(f.id);
695
+ },
696
+ style: {
697
+ position: "absolute",
698
+ left: `${geom.x * 100}%`,
699
+ top: `${geom.y * 100}%`,
700
+ transform: "translate(-50%, -50%)",
701
+ marginTop: -18,
702
+ width: 22,
703
+ height: 22,
704
+ borderRadius: "50%",
705
+ background: "#fff",
706
+ color: primaryColor,
707
+ border: `2px solid ${primaryColor}`,
708
+ boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
709
+ fontSize: 12,
710
+ lineHeight: "14px",
711
+ cursor: "pointer",
712
+ padding: 0,
713
+ touchAction: "none"
714
+ },
715
+ children: "\u2699"
716
+ }
717
+ ) : null
640
718
  ] }) : null
641
719
  ] }, f.id);
642
720
  }),
@@ -658,11 +736,161 @@ function FieldDesigner({
658
736
  ) : null
659
737
  ]
660
738
  }
661
- )
739
+ ),
740
+ configField ? /* @__PURE__ */ jsxRuntime.jsxs(
741
+ "div",
742
+ {
743
+ style: {
744
+ marginTop: 10,
745
+ border: `1px solid ${primaryColor}66`,
746
+ borderRadius: 8,
747
+ padding: 12,
748
+ background: "#fff",
749
+ fontSize: 13,
750
+ color: "#374151"
751
+ },
752
+ children: [
753
+ /* @__PURE__ */ jsxRuntime.jsxs(
754
+ "div",
755
+ {
756
+ style: {
757
+ display: "flex",
758
+ alignItems: "center",
759
+ justifyContent: "space-between",
760
+ marginBottom: 8
761
+ },
762
+ children: [
763
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
764
+ "\u{1F4C5} ",
765
+ configField.label || "Date"
766
+ ] }),
767
+ /* @__PURE__ */ jsxRuntime.jsx(
768
+ "button",
769
+ {
770
+ type: "button",
771
+ onClick: () => setConfigId(null),
772
+ style: {
773
+ fontSize: 12,
774
+ padding: "3px 10px",
775
+ borderRadius: 6,
776
+ border: `1px solid ${primaryColor}`,
777
+ background: primaryColor,
778
+ color: "#fff",
779
+ cursor: "pointer"
780
+ },
781
+ children: "Done"
782
+ }
783
+ )
784
+ ]
785
+ }
786
+ ),
787
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
788
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "Display format" }),
789
+ /* @__PURE__ */ jsxRuntime.jsx(
790
+ "div",
791
+ {
792
+ style: {
793
+ display: "flex",
794
+ flexDirection: "column",
795
+ border: "1px solid #e5e7eb",
796
+ borderRadius: 8,
797
+ overflow: "hidden"
798
+ },
799
+ children: DATE_FORMATS.map((fmt) => {
800
+ const active = (configField.dateFormat ?? DEFAULT_DATE_FORMAT) === fmt;
801
+ return /* @__PURE__ */ jsxRuntime.jsxs(
802
+ "button",
803
+ {
804
+ type: "button",
805
+ onClick: () => setConfig(configField.id, { dateFormat: fmt }),
806
+ style: {
807
+ display: "flex",
808
+ alignItems: "center",
809
+ justifyContent: "space-between",
810
+ padding: "7px 10px",
811
+ border: "none",
812
+ borderTop: "1px solid #f3f4f6",
813
+ textAlign: "left",
814
+ background: active ? `${primaryColor}14` : "#fff",
815
+ color: active ? primaryColor : "#374151",
816
+ fontWeight: active ? 600 : 400,
817
+ fontSize: 13,
818
+ cursor: "pointer"
819
+ },
820
+ children: [
821
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: DATE_FORMAT_LABELS[fmt] }),
822
+ active ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2713" }) : null
823
+ ]
824
+ },
825
+ fmt
826
+ );
827
+ })
828
+ }
829
+ )
830
+ ] }),
831
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
832
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 4 }, children: "Earliest date" }),
833
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
834
+ /* @__PURE__ */ jsxRuntime.jsx(
835
+ "input",
836
+ {
837
+ ref: minDateRef,
838
+ type: "date",
839
+ value: configField.minDate ?? "",
840
+ onChange: (e) => setConfig(configField.id, { minDate: e.target.value || null }),
841
+ style: {
842
+ padding: "5px 8px",
843
+ borderRadius: 6,
844
+ border: "1px solid #d1d5db",
845
+ fontSize: 13
846
+ }
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsxRuntime.jsx(
850
+ "button",
851
+ {
852
+ type: "button",
853
+ "aria-label": "Open calendar",
854
+ onClick: () => minDateRef.current?.showPicker?.(),
855
+ style: {
856
+ padding: "5px 8px",
857
+ borderRadius: 6,
858
+ border: `1px solid ${primaryColor}`,
859
+ background: "#fff",
860
+ cursor: "pointer",
861
+ fontSize: 14,
862
+ lineHeight: 1
863
+ },
864
+ children: "\u{1F4C5}"
865
+ }
866
+ ),
867
+ configField.minDate ? /* @__PURE__ */ jsxRuntime.jsx(
868
+ "button",
869
+ {
870
+ type: "button",
871
+ onClick: () => setConfig(configField.id, { minDate: null }),
872
+ style: {
873
+ padding: "5px 8px",
874
+ borderRadius: 6,
875
+ border: "1px solid #d1d5db",
876
+ background: "#fff",
877
+ color: "#6b7280",
878
+ cursor: "pointer",
879
+ fontSize: 12
880
+ },
881
+ children: "Clear"
882
+ }
883
+ ) : null
884
+ ] }),
885
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#9ca3af", fontSize: 11 }, children: "Leave blank to use the document's creation date." })
886
+ ] })
887
+ ]
888
+ }
889
+ ) : null
662
890
  ] }),
663
891
  hideFieldList ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: "1 1 200px", minWidth: 180, maxWidth: 300 }, children: [
664
892
  /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 13, fontWeight: 600, color: "#374151", margin: "0 0 8px" }, children: [
665
- "Signature fields (",
893
+ "Fields (",
666
894
  value.length,
667
895
  ")"
668
896
  ] }),
@@ -684,6 +912,7 @@ function FieldDesigner({
684
912
  cursor: "pointer"
685
913
  },
686
914
  children: [
915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: f.type === "date" ? "\u{1F4C5}" : "\u270D" }),
687
916
  /* @__PURE__ */ jsxRuntime.jsx(
688
917
  "input",
689
918
  {
@@ -705,6 +934,27 @@ function FieldDesigner({
705
934
  "p",
706
935
  f.page
707
936
  ] }),
937
+ f.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
938
+ "button",
939
+ {
940
+ type: "button",
941
+ "aria-label": `Configure ${f.label}`,
942
+ onClick: (e) => {
943
+ e.stopPropagation();
944
+ setConfigId(f.id);
945
+ },
946
+ style: {
947
+ border: "none",
948
+ background: "transparent",
949
+ color: "#6b7280",
950
+ cursor: "pointer",
951
+ fontSize: 13,
952
+ lineHeight: 1,
953
+ padding: 0
954
+ },
955
+ children: "\u2699"
956
+ }
957
+ ) : null,
708
958
  /* @__PURE__ */ jsxRuntime.jsx(
709
959
  "button",
710
960
  {
@@ -737,9 +987,12 @@ function SigningExperience(props) {
737
987
  const primary = props.branding?.primaryColor ?? "#4f46e5";
738
988
  const [signaturePng, setSignaturePng] = react.useState(null);
739
989
  const [consent, setConsent] = react.useState({ signerName: "", consent: false });
990
+ const [dateValues, setDateValues] = react.useState({});
740
991
  const [submitting, setSubmitting] = react.useState(false);
741
992
  const [error, setError] = react.useState(null);
742
- const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && !submitting;
993
+ const dateFields = (props.placements ?? []).filter((p) => p.type === "date" && p.id);
994
+ const allDatesFilled = dateFields.every((f) => dateValues[f.id]);
995
+ const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && allDatesFilled && !submitting;
743
996
  async function submit() {
744
997
  if (!canSubmit) return;
745
998
  setSubmitting(true);
@@ -751,7 +1004,8 @@ function SigningExperience(props) {
751
1004
  body: JSON.stringify({
752
1005
  signaturePng,
753
1006
  signerName: consent.signerName.trim(),
754
- consent: true
1007
+ consent: true,
1008
+ dateValues
755
1009
  })
756
1010
  });
757
1011
  if (!res.ok) {
@@ -796,6 +1050,31 @@ function SigningExperience(props) {
796
1050
  /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: "Your signature" }),
797
1051
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(SignaturePad, { primaryColor: primary, onChange: setSignaturePng }) })
798
1052
  ] }),
1053
+ dateFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: dateFields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1054
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: [
1055
+ "\u{1F4C5} ",
1056
+ f.label || "Date"
1057
+ ] }),
1058
+ /* @__PURE__ */ jsxRuntime.jsx(
1059
+ "input",
1060
+ {
1061
+ type: "date",
1062
+ value: dateValues[f.id] ?? "",
1063
+ min: f.minDate ?? void 0,
1064
+ onChange: (e) => setDateValues((prev) => ({ ...prev, [f.id]: e.target.value })),
1065
+ style: {
1066
+ display: "block",
1067
+ marginTop: 6,
1068
+ padding: "10px 12px",
1069
+ borderRadius: 8,
1070
+ border: "1px solid #d1d5db",
1071
+ fontSize: 15,
1072
+ width: "100%",
1073
+ maxWidth: 280
1074
+ }
1075
+ }
1076
+ )
1077
+ ] }, f.id)) }) : null,
799
1078
  /* @__PURE__ */ jsxRuntime.jsx(ConsentBlock, { value: consent, onChange: setConsent, primaryColor: primary }),
800
1079
  error ? /* @__PURE__ */ jsxRuntime.jsx(
801
1080
  "div",