@josephomills/esign 0.8.0 → 0.9.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/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": "02/10/2026",
347
+ "MM/DD/YYYY": "10/02/2026",
348
+ "YYYY-MM-DD": "2026-10-02",
349
+ "D MMM YYYY": "2 Oct 2026",
350
+ "Do MMM YYYY": "2nd Oct 2026",
351
+ "Do MMMM YYYY": "2nd October 2026",
352
+ "MMMM D, YYYY": "October 2, 2026"
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,6 +368,8 @@ 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);
@@ -439,15 +462,21 @@ function FieldDesigner({
439
462
  function onPointerUp() {
440
463
  if (drag && live && live.w >= MIN_W && live.h >= MIN_H) {
441
464
  if (drag.mode === "new") {
465
+ const isDate = pendingType === "date";
466
+ const n = value.filter((v) => v.type === pendingType).length + 1;
467
+ const base = isDate ? "Date" : "Signature";
442
468
  const f = {
443
469
  id: newId(),
444
- label: value.length === 0 ? "Signature" : `Signature ${value.length + 1}`,
470
+ label: n === 1 ? base : `${base} ${n}`,
471
+ type: pendingType,
472
+ ...isDate ? { dateFormat: DEFAULT_DATE_FORMAT, minDate: null } : {},
445
473
  page,
446
474
  ...live
447
475
  };
448
476
  onChange([...value, f]);
449
477
  setSelectedId(f.id);
450
478
  setDrawMode(false);
479
+ if (isDate) setConfigId(f.id);
451
480
  } else {
452
481
  onChange(value.map((x) => x.id === drag.id ? { ...x, ...live } : x));
453
482
  }
@@ -458,10 +487,15 @@ function FieldDesigner({
458
487
  function removeField(id) {
459
488
  onChange(value.filter((f) => f.id !== id));
460
489
  if (selectedId === id) setSelectedId(null);
490
+ if (configId === id) setConfigId(null);
461
491
  }
462
492
  function rename(id, label) {
463
493
  onChange(value.map((f) => f.id === id ? { ...f, label } : f));
464
494
  }
495
+ function setConfig(id, patch) {
496
+ onChange(value.map((f) => f.id === id ? { ...f, ...patch } : f));
497
+ }
498
+ const configField = value.find((f) => f.id === configId && f.type === "date") ?? null;
465
499
  const handle = (id, geom, c) => {
466
500
  const cx = c[1] === "w" ? geom.x : geom.x + geom.w;
467
501
  const cy = c[0] === "n" ? geom.y : geom.y + geom.h;
@@ -507,25 +541,35 @@ function FieldDesigner({
507
541
  flexWrap: "wrap"
508
542
  },
509
543
  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"
544
+ ["signature", "date"].map((t) => {
545
+ const active = drawMode && pendingType === t;
546
+ return /* @__PURE__ */ jsxRuntime.jsx(
547
+ "button",
548
+ {
549
+ type: "button",
550
+ onClick: () => {
551
+ if (active) setDrawMode(false);
552
+ else {
553
+ setPendingType(t);
554
+ setDrawMode(true);
555
+ }
556
+ },
557
+ style: {
558
+ fontSize: 13,
559
+ fontWeight: 600,
560
+ padding: "5px 12px",
561
+ borderRadius: 6,
562
+ border: `1px solid ${primaryColor}`,
563
+ background: active ? primaryColor : "#fff",
564
+ color: active ? "#fff" : primaryColor,
565
+ cursor: "pointer"
566
+ },
567
+ children: t === "date" ? "+ Date" : "+ Signature"
524
568
  },
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)." }),
569
+ t
570
+ );
571
+ }),
572
+ /* @__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
573
  numPages > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { marginLeft: "auto", fontSize: 13, color: "#374151" }, children: [
530
574
  "Page",
531
575
  " ",
@@ -596,8 +640,9 @@ function FieldDesigner({
596
640
  padding: "0 4px"
597
641
  },
598
642
  children: [
599
- "\u270D ",
600
- f.label || "Signature"
643
+ f.type === "date" ? "\u{1F4C5}" : "\u270D",
644
+ " ",
645
+ f.label || (f.type === "date" ? "Date" : "Signature")
601
646
  ]
602
647
  }
603
648
  )
@@ -636,7 +681,39 @@ function FieldDesigner({
636
681
  },
637
682
  children: "\xD7"
638
683
  }
639
- )
684
+ ),
685
+ f.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
686
+ "button",
687
+ {
688
+ type: "button",
689
+ "aria-label": `Configure ${f.label}`,
690
+ onPointerDown: (e) => e.stopPropagation(),
691
+ onClick: (e) => {
692
+ e.stopPropagation();
693
+ setConfigId(f.id);
694
+ },
695
+ style: {
696
+ position: "absolute",
697
+ left: `${geom.x * 100}%`,
698
+ top: `${geom.y * 100}%`,
699
+ transform: "translate(-50%, -50%)",
700
+ marginTop: -18,
701
+ width: 22,
702
+ height: 22,
703
+ borderRadius: "50%",
704
+ background: "#fff",
705
+ color: primaryColor,
706
+ border: `2px solid ${primaryColor}`,
707
+ boxShadow: "0 1px 3px rgba(0,0,0,0.3)",
708
+ fontSize: 12,
709
+ lineHeight: "14px",
710
+ cursor: "pointer",
711
+ padding: 0,
712
+ touchAction: "none"
713
+ },
714
+ children: "\u2699"
715
+ }
716
+ ) : null
640
717
  ] }) : null
641
718
  ] }, f.id);
642
719
  }),
@@ -658,11 +735,99 @@ function FieldDesigner({
658
735
  ) : null
659
736
  ]
660
737
  }
661
- )
738
+ ),
739
+ configField ? /* @__PURE__ */ jsxRuntime.jsxs(
740
+ "div",
741
+ {
742
+ style: {
743
+ marginTop: 10,
744
+ border: `1px solid ${primaryColor}66`,
745
+ borderRadius: 8,
746
+ padding: 12,
747
+ background: "#fff",
748
+ fontSize: 13,
749
+ color: "#374151"
750
+ },
751
+ children: [
752
+ /* @__PURE__ */ jsxRuntime.jsxs(
753
+ "div",
754
+ {
755
+ style: {
756
+ display: "flex",
757
+ alignItems: "center",
758
+ justifyContent: "space-between",
759
+ marginBottom: 8
760
+ },
761
+ children: [
762
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
763
+ "\u{1F4C5} ",
764
+ configField.label || "Date"
765
+ ] }),
766
+ /* @__PURE__ */ jsxRuntime.jsx(
767
+ "button",
768
+ {
769
+ type: "button",
770
+ onClick: () => setConfigId(null),
771
+ style: {
772
+ fontSize: 12,
773
+ padding: "3px 10px",
774
+ borderRadius: 6,
775
+ border: `1px solid ${primaryColor}`,
776
+ background: primaryColor,
777
+ color: "#fff",
778
+ cursor: "pointer"
779
+ },
780
+ children: "Done"
781
+ }
782
+ )
783
+ ]
784
+ }
785
+ ),
786
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "block", marginBottom: 8 }, children: [
787
+ "Display format",
788
+ /* @__PURE__ */ jsxRuntime.jsx(
789
+ "select",
790
+ {
791
+ value: configField.dateFormat ?? DEFAULT_DATE_FORMAT,
792
+ onChange: (e) => setConfig(configField.id, { dateFormat: e.target.value }),
793
+ style: {
794
+ display: "block",
795
+ marginTop: 4,
796
+ width: "100%",
797
+ padding: "4px 6px",
798
+ borderRadius: 6,
799
+ border: "1px solid #d1d5db"
800
+ },
801
+ children: DATE_FORMATS.map((fmt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: fmt, children: DATE_FORMAT_LABELS[fmt] }, fmt))
802
+ }
803
+ )
804
+ ] }),
805
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "block" }, children: [
806
+ "Earliest date",
807
+ /* @__PURE__ */ jsxRuntime.jsx(
808
+ "input",
809
+ {
810
+ type: "date",
811
+ value: configField.minDate ?? "",
812
+ onChange: (e) => setConfig(configField.id, { minDate: e.target.value || null }),
813
+ style: {
814
+ display: "block",
815
+ marginTop: 4,
816
+ padding: "4px 6px",
817
+ borderRadius: 6,
818
+ border: "1px solid #d1d5db"
819
+ }
820
+ }
821
+ ),
822
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#9ca3af", fontSize: 11 }, children: "Leave blank to use the document's creation date." })
823
+ ] })
824
+ ]
825
+ }
826
+ ) : null
662
827
  ] }),
663
828
  hideFieldList ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: "1 1 200px", minWidth: 180, maxWidth: 300 }, children: [
664
829
  /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 13, fontWeight: 600, color: "#374151", margin: "0 0 8px" }, children: [
665
- "Signature fields (",
830
+ "Fields (",
666
831
  value.length,
667
832
  ")"
668
833
  ] }),
@@ -684,6 +849,7 @@ function FieldDesigner({
684
849
  cursor: "pointer"
685
850
  },
686
851
  children: [
852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: f.type === "date" ? "\u{1F4C5}" : "\u270D" }),
687
853
  /* @__PURE__ */ jsxRuntime.jsx(
688
854
  "input",
689
855
  {
@@ -705,6 +871,27 @@ function FieldDesigner({
705
871
  "p",
706
872
  f.page
707
873
  ] }),
874
+ f.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
875
+ "button",
876
+ {
877
+ type: "button",
878
+ "aria-label": `Configure ${f.label}`,
879
+ onClick: (e) => {
880
+ e.stopPropagation();
881
+ setConfigId(f.id);
882
+ },
883
+ style: {
884
+ border: "none",
885
+ background: "transparent",
886
+ color: "#6b7280",
887
+ cursor: "pointer",
888
+ fontSize: 13,
889
+ lineHeight: 1,
890
+ padding: 0
891
+ },
892
+ children: "\u2699"
893
+ }
894
+ ) : null,
708
895
  /* @__PURE__ */ jsxRuntime.jsx(
709
896
  "button",
710
897
  {
@@ -737,9 +924,12 @@ function SigningExperience(props) {
737
924
  const primary = props.branding?.primaryColor ?? "#4f46e5";
738
925
  const [signaturePng, setSignaturePng] = react.useState(null);
739
926
  const [consent, setConsent] = react.useState({ signerName: "", consent: false });
927
+ const [dateValues, setDateValues] = react.useState({});
740
928
  const [submitting, setSubmitting] = react.useState(false);
741
929
  const [error, setError] = react.useState(null);
742
- const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && !submitting;
930
+ const dateFields = (props.placements ?? []).filter((p) => p.type === "date" && p.id);
931
+ const allDatesFilled = dateFields.every((f) => dateValues[f.id]);
932
+ const canSubmit = !!signaturePng && consent.consent && consent.signerName.trim().length > 0 && allDatesFilled && !submitting;
743
933
  async function submit() {
744
934
  if (!canSubmit) return;
745
935
  setSubmitting(true);
@@ -751,7 +941,8 @@ function SigningExperience(props) {
751
941
  body: JSON.stringify({
752
942
  signaturePng,
753
943
  signerName: consent.signerName.trim(),
754
- consent: true
944
+ consent: true,
945
+ dateValues
755
946
  })
756
947
  });
757
948
  if (!res.ok) {
@@ -796,6 +987,31 @@ function SigningExperience(props) {
796
987
  /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: "Your signature" }),
797
988
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(SignaturePad, { primaryColor: primary, onChange: setSignaturePng }) })
798
989
  ] }),
990
+ dateFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: dateFields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
991
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: [
992
+ "\u{1F4C5} ",
993
+ f.label || "Date"
994
+ ] }),
995
+ /* @__PURE__ */ jsxRuntime.jsx(
996
+ "input",
997
+ {
998
+ type: "date",
999
+ value: dateValues[f.id] ?? "",
1000
+ min: f.minDate ?? void 0,
1001
+ onChange: (e) => setDateValues((prev) => ({ ...prev, [f.id]: e.target.value })),
1002
+ style: {
1003
+ display: "block",
1004
+ marginTop: 6,
1005
+ padding: "10px 12px",
1006
+ borderRadius: 8,
1007
+ border: "1px solid #d1d5db",
1008
+ fontSize: 15,
1009
+ width: "100%",
1010
+ maxWidth: 280
1011
+ }
1012
+ }
1013
+ )
1014
+ ] }, f.id)) }) : null,
799
1015
  /* @__PURE__ */ jsxRuntime.jsx(ConsentBlock, { value: consent, onChange: setConsent, primaryColor: primary }),
800
1016
  error ? /* @__PURE__ */ jsxRuntime.jsx(
801
1017
  "div",