@josephomills/esign 0.7.1 → 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;
@@ -339,13 +360,16 @@ function FieldDesigner({
339
360
  value,
340
361
  onChange,
341
362
  workerSrc,
342
- primaryColor = "#4f46e5"
363
+ primaryColor = "#4f46e5",
364
+ hideFieldList = false
343
365
  }) {
344
366
  const [numPages, setNumPages] = react.useState(0);
345
367
  const [page, setPage] = react.useState(value[0]?.page ?? 1);
346
368
  const [failed, setFailed] = react.useState(false);
347
369
  const [selectedId, setSelectedId] = react.useState(value[0]?.id ?? null);
348
370
  const [drawMode, setDrawMode] = react.useState(false);
371
+ const [pendingType, setPendingType] = react.useState("signature");
372
+ const [configId, setConfigId] = react.useState(null);
349
373
  const [drag, setDrag] = react.useState(null);
350
374
  const [live, setLive] = react.useState(null);
351
375
  const stageRef = react.useRef(null);
@@ -438,15 +462,21 @@ function FieldDesigner({
438
462
  function onPointerUp() {
439
463
  if (drag && live && live.w >= MIN_W && live.h >= MIN_H) {
440
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";
441
468
  const f = {
442
469
  id: newId(),
443
- 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 } : {},
444
473
  page,
445
474
  ...live
446
475
  };
447
476
  onChange([...value, f]);
448
477
  setSelectedId(f.id);
449
478
  setDrawMode(false);
479
+ if (isDate) setConfigId(f.id);
450
480
  } else {
451
481
  onChange(value.map((x) => x.id === drag.id ? { ...x, ...live } : x));
452
482
  }
@@ -457,10 +487,15 @@ function FieldDesigner({
457
487
  function removeField(id) {
458
488
  onChange(value.filter((f) => f.id !== id));
459
489
  if (selectedId === id) setSelectedId(null);
490
+ if (configId === id) setConfigId(null);
460
491
  }
461
492
  function rename(id, label) {
462
493
  onChange(value.map((f) => f.id === id ? { ...f, label } : f));
463
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;
464
499
  const handle = (id, geom, c) => {
465
500
  const cx = c[1] === "w" ? geom.x : geom.x + geom.w;
466
501
  const cy = c[0] === "n" ? geom.y : geom.y + geom.h;
@@ -506,25 +541,35 @@ function FieldDesigner({
506
541
  flexWrap: "wrap"
507
542
  },
508
543
  children: [
509
- /* @__PURE__ */ jsxRuntime.jsx(
510
- "button",
511
- {
512
- type: "button",
513
- onClick: () => setDrawMode((d) => !d),
514
- style: {
515
- fontSize: 13,
516
- fontWeight: 600,
517
- padding: "5px 12px",
518
- borderRadius: 6,
519
- border: `1px solid ${primaryColor}`,
520
- background: drawMode ? primaryColor : "#fff",
521
- color: drawMode ? "#fff" : primaryColor,
522
- 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"
523
568
  },
524
- children: drawMode ? "Now drag on the page\u2026" : "+ Add field"
525
- }
526
- ),
527
- /* @__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." }),
528
573
  numPages > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { marginLeft: "auto", fontSize: 13, color: "#374151" }, children: [
529
574
  "Page",
530
575
  " ",
@@ -595,8 +640,9 @@ function FieldDesigner({
595
640
  padding: "0 4px"
596
641
  },
597
642
  children: [
598
- "\u270D ",
599
- f.label || "Signature"
643
+ f.type === "date" ? "\u{1F4C5}" : "\u270D",
644
+ " ",
645
+ f.label || (f.type === "date" ? "Date" : "Signature")
600
646
  ]
601
647
  }
602
648
  )
@@ -635,7 +681,39 @@ function FieldDesigner({
635
681
  },
636
682
  children: "\xD7"
637
683
  }
638
- )
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
639
717
  ] }) : null
640
718
  ] }, f.id);
641
719
  }),
@@ -657,11 +735,99 @@ function FieldDesigner({
657
735
  ) : null
658
736
  ]
659
737
  }
660
- )
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
661
827
  ] }),
662
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: "1 1 200px", minWidth: 180, maxWidth: 300 }, children: [
828
+ hideFieldList ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: "1 1 200px", minWidth: 180, maxWidth: 300 }, children: [
663
829
  /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 13, fontWeight: 600, color: "#374151", margin: "0 0 8px" }, children: [
664
- "Signature fields (",
830
+ "Fields (",
665
831
  value.length,
666
832
  ")"
667
833
  ] }),
@@ -683,6 +849,7 @@ function FieldDesigner({
683
849
  cursor: "pointer"
684
850
  },
685
851
  children: [
852
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 13 }, children: f.type === "date" ? "\u{1F4C5}" : "\u270D" }),
686
853
  /* @__PURE__ */ jsxRuntime.jsx(
687
854
  "input",
688
855
  {
@@ -704,6 +871,27 @@ function FieldDesigner({
704
871
  "p",
705
872
  f.page
706
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,
707
895
  /* @__PURE__ */ jsxRuntime.jsx(
708
896
  "button",
709
897
  {
@@ -736,9 +924,12 @@ function SigningExperience(props) {
736
924
  const primary = props.branding?.primaryColor ?? "#4f46e5";
737
925
  const [signaturePng, setSignaturePng] = react.useState(null);
738
926
  const [consent, setConsent] = react.useState({ signerName: "", consent: false });
927
+ const [dateValues, setDateValues] = react.useState({});
739
928
  const [submitting, setSubmitting] = react.useState(false);
740
929
  const [error, setError] = react.useState(null);
741
- 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;
742
933
  async function submit() {
743
934
  if (!canSubmit) return;
744
935
  setSubmitting(true);
@@ -750,7 +941,8 @@ function SigningExperience(props) {
750
941
  body: JSON.stringify({
751
942
  signaturePng,
752
943
  signerName: consent.signerName.trim(),
753
- consent: true
944
+ consent: true,
945
+ dateValues
754
946
  })
755
947
  });
756
948
  if (!res.ok) {
@@ -795,6 +987,31 @@ function SigningExperience(props) {
795
987
  /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 13, fontWeight: 600, color: "#374151" }, children: "Your signature" }),
796
988
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(SignaturePad, { primaryColor: primary, onChange: setSignaturePng }) })
797
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,
798
1015
  /* @__PURE__ */ jsxRuntime.jsx(ConsentBlock, { value: consent, onChange: setConsent, primaryColor: primary }),
799
1016
  error ? /* @__PURE__ */ jsxRuntime.jsx(
800
1017
  "div",