@unlev/exeq 0.1.12 → 0.2.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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/pdf-builder/DesignerView.tsx
2
- import { useState as useState2, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef3 } from "react";
2
+ import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2, useRef as useRef3 } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
5
  // src/types/pdf-builder.ts
@@ -25,6 +25,11 @@ var SIGNER_ROLE_COLORS = {
25
25
  function getSignerColor(role) {
26
26
  return SIGNER_ROLE_COLORS[role] || "#888888";
27
27
  }
28
+ var FONT_FAMILIES = [
29
+ { value: "Helvetica", label: "Helvetica" },
30
+ { value: "Courier", label: "Courier New" },
31
+ { value: "TimesRoman", label: "Times New Roman" }
32
+ ];
28
33
  var FIELD_DEFAULTS = {
29
34
  text: {
30
35
  width: 20,
@@ -33,6 +38,12 @@ var FIELD_DEFAULTS = {
33
38
  placeholder: "Enter text",
34
39
  textSubtype: "freeform"
35
40
  },
41
+ dropdown: {
42
+ width: 20,
43
+ height: 3,
44
+ fontSize: 12,
45
+ placeholder: "Select..."
46
+ },
36
47
  signature: {
37
48
  width: 20,
38
49
  height: 6,
@@ -72,6 +83,7 @@ var FIELD_DEFAULTS = {
72
83
  };
73
84
  var TYPE_LABELS = {
74
85
  text: "Text Field",
86
+ dropdown: "Dropdown",
75
87
  signature: "Signature",
76
88
  "signed-date": "Signed Date",
77
89
  checkbox: "Checkbox",
@@ -336,6 +348,7 @@ function FieldOverlayItem({
336
348
  }
337
349
 
338
350
  // src/components/pdf-builder/FieldPropertyPanel.tsx
351
+ import { useState } from "react";
339
352
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
340
353
  var INK_COLORS = [
341
354
  { value: "#000000", label: "Black" },
@@ -344,8 +357,9 @@ var INK_COLORS = [
344
357
  function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
345
358
  const color = getSignerColor(field.assignee);
346
359
  const isRedactField = field.type === "blackout" || field.type === "whiteout";
347
- const showFontSize = field.type === "text" || field.type === "signed-date";
348
- const showInkColor = field.type === "text" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
360
+ const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
361
+ const showInkColor = field.type === "text" || field.type === "dropdown" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
362
+ const [newOption, setNewOption] = useState("");
349
363
  return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
350
364
  /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
351
365
  /* @__PURE__ */ jsx2("h3", { style: { color }, children: field.label }),
@@ -371,6 +385,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
371
385
  onChange: (e) => onUpdate(field.id, { type: e.target.value }),
372
386
  children: [
373
387
  /* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
388
+ /* @__PURE__ */ jsx2("option", { value: "dropdown", children: "Dropdown" }),
374
389
  /* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
375
390
  /* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
376
391
  /* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
@@ -418,6 +433,19 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
418
433
  }
419
434
  )
420
435
  ] }),
436
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
437
+ /* @__PURE__ */ jsx2("label", { children: "Formula" }),
438
+ /* @__PURE__ */ jsx2(
439
+ "input",
440
+ {
441
+ type: "text",
442
+ value: field.formula || "",
443
+ onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }),
444
+ placeholder: "e.g. {{Date Field | month}}"
445
+ }
446
+ ),
447
+ field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
448
+ ] }),
421
449
  !isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
422
450
  /* @__PURE__ */ jsx2(
423
451
  "input",
@@ -429,7 +457,18 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
429
457
  ),
430
458
  "Required"
431
459
  ] }) }),
432
- showFontSize && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
460
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
461
+ /* @__PURE__ */ jsx2("label", { children: "Font" }),
462
+ /* @__PURE__ */ jsx2(
463
+ "select",
464
+ {
465
+ value: field.fontFamily || "Helvetica",
466
+ onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }),
467
+ children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
468
+ }
469
+ )
470
+ ] }),
471
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
433
472
  /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
434
473
  /* @__PURE__ */ jsx2(
435
474
  "input",
@@ -442,6 +481,49 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
442
481
  }
443
482
  )
444
483
  ] }),
484
+ isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
485
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
486
+ /* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
487
+ /* @__PURE__ */ jsx2(
488
+ "input",
489
+ {
490
+ type: "number",
491
+ min: "0",
492
+ max: "20",
493
+ step: "0.5",
494
+ value: field.letterSpacing || 0,
495
+ onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) })
496
+ }
497
+ )
498
+ ] }),
499
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
500
+ /* @__PURE__ */ jsx2("label", { children: "Line Height" }),
501
+ /* @__PURE__ */ jsx2(
502
+ "input",
503
+ {
504
+ type: "number",
505
+ min: "0.8",
506
+ max: "3",
507
+ step: "0.1",
508
+ value: field.lineHeight || 1.2,
509
+ onChange: (e) => onUpdate(field.id, { lineHeight: Number(e.target.value) })
510
+ }
511
+ )
512
+ ] })
513
+ ] }),
514
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
515
+ /* @__PURE__ */ jsx2("label", { children: "Max Characters (0 = unlimited)" }),
516
+ /* @__PURE__ */ jsx2(
517
+ "input",
518
+ {
519
+ type: "number",
520
+ min: "0",
521
+ max: "9999",
522
+ value: field.maxLength || 0,
523
+ onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) })
524
+ }
525
+ )
526
+ ] }),
445
527
  showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
446
528
  /* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
447
529
  /* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2(
@@ -454,12 +536,62 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
454
536
  },
455
537
  c.value
456
538
  )) })
539
+ ] }),
540
+ (field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
541
+ /* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
542
+ /* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
543
+ (field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
544
+ /* @__PURE__ */ jsx2("span", { children: opt }),
545
+ /* @__PURE__ */ jsx2(
546
+ "button",
547
+ {
548
+ className: "panel-option-remove",
549
+ onClick: () => {
550
+ const updated = (field.options || []).filter((_, j) => j !== i);
551
+ onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
552
+ },
553
+ children: "\xD7"
554
+ }
555
+ )
556
+ ] }, i)),
557
+ /* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
558
+ /* @__PURE__ */ jsx2(
559
+ "input",
560
+ {
561
+ type: "text",
562
+ value: newOption,
563
+ onChange: (e) => setNewOption(e.target.value),
564
+ onKeyDown: (e) => {
565
+ if (e.key === "Enter" && newOption.trim()) {
566
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
567
+ setNewOption("");
568
+ }
569
+ },
570
+ placeholder: "Add option..."
571
+ }
572
+ ),
573
+ /* @__PURE__ */ jsx2(
574
+ "button",
575
+ {
576
+ onClick: () => {
577
+ if (newOption.trim()) {
578
+ onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
579
+ setNewOption("");
580
+ }
581
+ },
582
+ children: "+"
583
+ }
584
+ )
585
+ ] })
586
+ ] }),
587
+ field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
588
+ field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
457
589
  ] })
458
590
  ] });
459
591
  }
460
592
 
461
593
  // src/components/pdf-builder/SignatureCanvas.tsx
462
- import { useRef as useRef2, useState, useCallback as useCallback2, useEffect } from "react";
594
+ import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect } from "react";
463
595
  import { getStroke } from "perfect-freehand";
464
596
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
465
597
  function getSvgPathFromStroke(stroke) {
@@ -484,9 +616,9 @@ function SignatureCanvas({
484
616
  inkColor = "#000000"
485
617
  }) {
486
618
  const canvasRef = useRef2(null);
487
- const [paths, setPaths] = useState([]);
488
- const [currentPath, setCurrentPath] = useState(null);
489
- const [isEmpty, setIsEmpty] = useState(!initialValue);
619
+ const [paths, setPaths] = useState2([]);
620
+ const [currentPath, setCurrentPath] = useState2(null);
621
+ const [isEmpty, setIsEmpty] = useState2(!initialValue);
490
622
  useEffect(() => {
491
623
  if (initialValue && canvasRef.current) {
492
624
  const ctx = canvasRef.current.getContext("2d");
@@ -602,6 +734,7 @@ function isValidApiKey(key) {
602
734
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
603
735
  var FIELD_TYPE_META = [
604
736
  { type: "text", label: "Text", icon: "T" },
737
+ { type: "dropdown", label: "Dropdown", icon: "\u25BE" },
605
738
  { type: "signature", label: "Signature", icon: "\u270D" },
606
739
  { type: "signed-date", label: "Date", icon: "\u{1F4C5}" },
607
740
  { type: "checkbox", label: "Checkbox", icon: "\u2611" },
@@ -627,20 +760,20 @@ function DesignerView({
627
760
  ] })
628
761
  ] }) });
629
762
  }
630
- const [pages, setPages] = useState2([]);
631
- const [fields, setFields] = useState2(initialTemplate?.fields ?? []);
632
- const [selectedFieldId, setSelectedFieldId] = useState2(null);
633
- const [signerRoles, setSignerRoles] = useState2(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
634
- const [activeRole, setActiveRole] = useState2("Sender");
635
- const [activeFieldType, setActiveFieldType] = useState2("text");
636
- const [loading, setLoading] = useState2(false);
637
- const [pdfSource, setPdfSource] = useState2(null);
638
- const [rightTab, setRightTab] = useState2("properties");
639
- const [isAddingRole, setIsAddingRole] = useState2(false);
640
- const [newRoleName, setNewRoleName] = useState2("");
641
- const [draggingFieldType, setDraggingFieldType] = useState2(null);
642
- const [panelWidth, setPanelWidth] = useState2(380);
643
- const [clipboardField, setClipboardField] = useState2(null);
763
+ const [pages, setPages] = useState3([]);
764
+ const [fields, setFields] = useState3(initialTemplate?.fields ?? []);
765
+ const [selectedFieldId, setSelectedFieldId] = useState3(null);
766
+ const [signerRoles, setSignerRoles] = useState3(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
767
+ const [activeRole, setActiveRole] = useState3("Sender");
768
+ const [activeFieldType, setActiveFieldType] = useState3("text");
769
+ const [loading, setLoading] = useState3(false);
770
+ const [pdfSource, setPdfSource] = useState3(null);
771
+ const [rightTab, setRightTab] = useState3("properties");
772
+ const [isAddingRole, setIsAddingRole] = useState3(false);
773
+ const [newRoleName, setNewRoleName] = useState3("");
774
+ const [draggingFieldType, setDraggingFieldType] = useState3(null);
775
+ const [panelWidth, setPanelWidth] = useState3(380);
776
+ const [clipboardField, setClipboardField] = useState3(null);
644
777
  const dragGhostRef = useRef3(null);
645
778
  const resizingRef = useRef3(false);
646
779
  const lastStylesRef = useRef3({});
@@ -881,15 +1014,22 @@ function DesignerView({
881
1014
  }
882
1015
  }
883
1016
  const inkColor = field.inkColor || "#000000";
1017
+ const cssFontFamily = field.fontFamily === "Courier" ? '"Courier New", Courier, monospace' : field.fontFamily === "TimesRoman" ? '"Times New Roman", Times, serif' : field.fontFamily === "Helvetica" ? "Helvetica, Arial, sans-serif" : void 0;
884
1018
  return /* @__PURE__ */ jsx4(
885
1019
  "div",
886
1020
  {
887
1021
  className: "field-overlay-placeholder",
888
- style: { color: field.value ? inkColor : void 0, fontSize: `${field.fontSize * 0.6}px` },
1022
+ style: {
1023
+ color: field.value ? inkColor : void 0,
1024
+ fontSize: `${field.fontSize * 0.6}px`,
1025
+ fontFamily: cssFontFamily,
1026
+ letterSpacing: field.letterSpacing ? `${field.letterSpacing * 0.6}px` : void 0,
1027
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0
1028
+ },
889
1029
  children: field.value || field.placeholder
890
1030
  }
891
1031
  );
892
- }, []);
1032
+ }, [fields]);
893
1033
  const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
894
1034
  /* @__PURE__ */ jsxs4("label", { className: "header-btn header-btn-outline", children: [
895
1035
  "Change PDF",
@@ -1108,10 +1248,15 @@ function DesignerView({
1108
1248
  }
1109
1249
 
1110
1250
  // src/components/pdf-builder/SignerView.tsx
1111
- import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
1251
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
1112
1252
 
1113
1253
  // src/utils/pdfFiller.ts
1114
1254
  import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
1255
+ var FONT_MAP = {
1256
+ "Helvetica": StandardFonts.Helvetica,
1257
+ "Courier": StandardFonts.Courier,
1258
+ "TimesRoman": StandardFonts.TimesRoman
1259
+ };
1115
1260
  async function generateFilledPdf(pdfSource, fields) {
1116
1261
  let pdfBytes;
1117
1262
  if (typeof pdfSource === "string") {
@@ -1121,7 +1266,12 @@ async function generateFilledPdf(pdfSource, fields) {
1121
1266
  pdfBytes = pdfSource;
1122
1267
  }
1123
1268
  const pdfDoc = await PDFDocument.load(pdfBytes);
1124
- const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
1269
+ const fontCache = /* @__PURE__ */ new Map();
1270
+ const usedFontKeys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
1271
+ for (const key of usedFontKeys) {
1272
+ const stdFont = FONT_MAP[key] || StandardFonts.Helvetica;
1273
+ fontCache.set(key, await pdfDoc.embedFont(stdFont));
1274
+ }
1125
1275
  const pages = pdfDoc.getPages();
1126
1276
  function hexToRgb(hex) {
1127
1277
  const r = parseInt(hex.slice(1, 3), 16) / 255;
@@ -1182,21 +1332,35 @@ async function generateFilledPdf(pdfSource, fields) {
1182
1332
  });
1183
1333
  }
1184
1334
  } else {
1335
+ const font = fontCache.get(field.fontFamily || "Helvetica");
1336
+ const spacing = field.letterSpacing || 0;
1337
+ const textWidthAtSize = (text, size) => {
1338
+ const baseWidth = font.widthOfTextAtSize(text, size);
1339
+ return baseWidth + spacing * (text.length - 1);
1340
+ };
1185
1341
  const maxFontSize = Math.min(field.fontSize, h * 0.7);
1186
1342
  let fontSize = maxFontSize;
1187
1343
  const padding = 4;
1188
1344
  while (fontSize > 4) {
1189
- const textWidth = font.widthOfTextAtSize(field.value, fontSize);
1190
- if (textWidth <= w - padding) break;
1345
+ if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
1191
1346
  fontSize -= 0.5;
1192
1347
  }
1193
- page.drawText(field.value, {
1194
- x: x + 2,
1195
- y: y + h * 0.3,
1196
- size: fontSize,
1197
- font,
1198
- color: inkColor
1199
- });
1348
+ if (spacing > 0) {
1349
+ let cx = x + 2;
1350
+ const cy = y + h * 0.3;
1351
+ for (const char of field.value) {
1352
+ page.drawText(char, { x: cx, y: cy, size: fontSize, font, color: inkColor });
1353
+ cx += font.widthOfTextAtSize(char, fontSize) + spacing;
1354
+ }
1355
+ } else {
1356
+ page.drawText(field.value, {
1357
+ x: x + 2,
1358
+ y: y + h * 0.3,
1359
+ size: fontSize,
1360
+ font,
1361
+ color: inkColor
1362
+ });
1363
+ }
1200
1364
  }
1201
1365
  }
1202
1366
  return pdfDoc.save();
@@ -1221,7 +1385,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
1221
1385
  }
1222
1386
 
1223
1387
  // src/components/pdf-builder/FieldNavigator.tsx
1224
- import { useState as useState3 } from "react";
1388
+ import { useState as useState4 } from "react";
1225
1389
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1226
1390
  function isFieldFilled(f) {
1227
1391
  if (!f.required) return true;
@@ -1236,7 +1400,7 @@ function FieldNavigator({
1236
1400
  onComplete,
1237
1401
  completeLabel = "Complete"
1238
1402
  }) {
1239
- const [showIncomplete, setShowIncomplete] = useState3(false);
1403
+ const [showIncomplete, setShowIncomplete] = useState4(false);
1240
1404
  const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
1241
1405
  const hasPrev = currentIndex > 0;
1242
1406
  const hasNext = currentIndex < fields.length - 1;
@@ -1311,6 +1475,95 @@ function FieldNavigator({
1311
1475
  ] });
1312
1476
  }
1313
1477
 
1478
+ // src/utils/formulaResolver.ts
1479
+ var BUILTIN_TRANSFORMS = {
1480
+ // Date transforms (expects a parseable date string)
1481
+ month: (v) => {
1482
+ const d = new Date(v);
1483
+ return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
1484
+ },
1485
+ month2: (v) => {
1486
+ const d = new Date(v);
1487
+ return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
1488
+ },
1489
+ monthname: (v) => {
1490
+ const d = new Date(v);
1491
+ return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
1492
+ },
1493
+ monthshort: (v) => {
1494
+ const d = new Date(v);
1495
+ return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
1496
+ },
1497
+ day: (v) => {
1498
+ const d = new Date(v);
1499
+ return isNaN(d.getTime()) ? "" : String(d.getDate());
1500
+ },
1501
+ day2: (v) => {
1502
+ const d = new Date(v);
1503
+ return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
1504
+ },
1505
+ year: (v) => {
1506
+ const d = new Date(v);
1507
+ return isNaN(d.getTime()) ? "" : String(d.getFullYear());
1508
+ },
1509
+ year2: (v) => {
1510
+ const d = new Date(v);
1511
+ return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
1512
+ },
1513
+ // String transforms
1514
+ upper: (v) => v.toUpperCase(),
1515
+ lower: (v) => v.toLowerCase(),
1516
+ trim: (v) => v.trim(),
1517
+ first: (v) => v.split(/\s+/)[0] || "",
1518
+ last: (v) => {
1519
+ const parts = v.split(/\s+/);
1520
+ return parts[parts.length - 1] || "";
1521
+ },
1522
+ initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
1523
+ // Numeric / substring
1524
+ last4: (v) => v.slice(-4),
1525
+ last2: (v) => v.slice(-2),
1526
+ first4: (v) => v.slice(0, 4),
1527
+ first2: (v) => v.slice(0, 2),
1528
+ digits: (v) => v.replace(/\D/g, ""),
1529
+ number: (v) => {
1530
+ const n = parseFloat(v);
1531
+ return isNaN(n) ? "" : String(n);
1532
+ },
1533
+ currency: (v) => {
1534
+ const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
1535
+ return isNaN(n) ? "" : `$${n.toFixed(2)}`;
1536
+ }
1537
+ };
1538
+ var FORMULA_RE = /\{\{(.+?)\}\}/g;
1539
+ var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
1540
+ function resolveFormula(formula, fields, customTransforms) {
1541
+ const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
1542
+ return formula.replace(FORMULA_RE, (_match, expr) => {
1543
+ const pipeMatch = expr.match(PIPE_RE);
1544
+ const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
1545
+ const transformName = pipeMatch ? pipeMatch[2].trim() : null;
1546
+ const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
1547
+ if (!sourceField) return "";
1548
+ const rawValue = sourceField.value || "";
1549
+ if (!transformName) return rawValue;
1550
+ const fn = transforms[transformName];
1551
+ if (!fn) return rawValue;
1552
+ try {
1553
+ return fn(rawValue);
1554
+ } catch {
1555
+ return rawValue;
1556
+ }
1557
+ });
1558
+ }
1559
+ function resolveAllFormulas(fields, customTransforms) {
1560
+ return fields.map((f) => {
1561
+ if (!f.formula) return f;
1562
+ const computed = resolveFormula(f.formula, fields, customTransforms);
1563
+ return computed !== f.value ? { ...f, value: computed } : f;
1564
+ });
1565
+ }
1566
+
1314
1567
  // src/components/pdf-builder/SignerView.tsx
1315
1568
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1316
1569
  function SignerView({
@@ -1322,7 +1575,8 @@ function SignerView({
1322
1575
  onComplete,
1323
1576
  initialValues,
1324
1577
  submitLabel,
1325
- signerOrder: signerOrderProp
1578
+ signerOrder: signerOrderProp,
1579
+ transforms
1326
1580
  } = {}) {
1327
1581
  if (!isValidApiKey(apiKey)) {
1328
1582
  return /* @__PURE__ */ jsx6("div", { className: "signer-layout", children: /* @__PURE__ */ jsxs6("div", { className: "empty-state", children: [
@@ -1334,11 +1588,11 @@ function SignerView({
1334
1588
  ] })
1335
1589
  ] }) });
1336
1590
  }
1337
- const [pages, setPages] = useState4([]);
1338
- const [fields, setFields] = useState4([]);
1339
- const [selectedFieldId, setSelectedFieldId] = useState4(null);
1340
- const [signerRoles, setSignerRoles] = useState4([]);
1341
- const [currentSignerIndex, setCurrentSignerIndex] = useState4(() => {
1591
+ const [pages, setPages] = useState5([]);
1592
+ const [fields, setFields] = useState5([]);
1593
+ const [selectedFieldId, setSelectedFieldId] = useState5(null);
1594
+ const [signerRoles, setSignerRoles] = useState5([]);
1595
+ const [currentSignerIndex, setCurrentSignerIndex] = useState5(() => {
1342
1596
  if (initialSigner && signerOrderProp) {
1343
1597
  const idx = signerOrderProp.indexOf(initialSigner);
1344
1598
  return idx >= 0 ? idx : 0;
@@ -1346,15 +1600,30 @@ function SignerView({
1346
1600
  return 0;
1347
1601
  });
1348
1602
  const initializedRef = useRef4(false);
1349
- const [loading, setLoading] = useState4(false);
1350
- const [submitting, setSubmitting] = useState4(false);
1351
- const [pdfSource, setPdfSource] = useState4(null);
1352
- const [callbackUrl, setCallbackUrl] = useState4(initialCallbackUrl || "");
1603
+ const [loading, setLoading] = useState5(false);
1604
+ const [submitting, setSubmitting] = useState5(false);
1605
+ const [pdfSource, setPdfSource] = useState5(null);
1606
+ const [callbackUrl, setCallbackUrl] = useState5(initialCallbackUrl || "");
1353
1607
  const containerRef = useRef4(null);
1354
1608
  const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
1355
1609
  const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
1356
1610
  const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
1357
1611
  const isMultiSigner = signerOrder.length > 1;
1612
+ useEffect3(() => {
1613
+ if (fields.length === 0 || !isMultiSigner) return;
1614
+ const signerFields = fields.filter(
1615
+ (f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout"
1616
+ );
1617
+ const allFilled = signerFields.length > 0 && signerFields.every((f) => {
1618
+ if (!f.required) return true;
1619
+ if (f.type === "checkbox") return true;
1620
+ return !!f.value;
1621
+ });
1622
+ if (allFilled && !isLastSigner) {
1623
+ setCurrentSignerIndex((prev) => prev + 1);
1624
+ setSelectedFieldId(null);
1625
+ }
1626
+ }, [currentSignerIndex, fields, signer, isLastSigner, isMultiSigner]);
1358
1627
  useEffect3(() => {
1359
1628
  if (initialTemplate) {
1360
1629
  let templateFields = initialTemplate.fields;
@@ -1437,14 +1706,14 @@ function SignerView({
1437
1706
  setLoading(false);
1438
1707
  }
1439
1708
  }, []);
1440
- const editableFields = fields.filter((f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout").sort((a, b) => {
1709
+ const editableFields = fields.filter((f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout" && !f.formula).sort((a, b) => {
1441
1710
  if (a.page !== b.page) return a.page - b.page;
1442
1711
  const bandThreshold = 2;
1443
1712
  if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
1444
1713
  return a.x - b.x;
1445
1714
  });
1446
1715
  const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
1447
- const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" : false;
1716
+ const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
1448
1717
  const handleFieldUpdate = useCallback4((id, value) => {
1449
1718
  setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
1450
1719
  }, []);
@@ -1480,7 +1749,8 @@ function SignerView({
1480
1749
  if (!pdfSource) return;
1481
1750
  setSubmitting(true);
1482
1751
  try {
1483
- const pdfBytes = await generateFilledPdf(pdfSource, fields);
1752
+ const finalFields = resolveAllFormulas(fields, transforms);
1753
+ const pdfBytes = await generateFilledPdf(pdfSource, finalFields);
1484
1754
  const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
1485
1755
  if (callbackUrl) {
1486
1756
  await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
@@ -1502,6 +1772,9 @@ function SignerView({
1502
1772
  if (field.type === "blackout" || field.type === "whiteout") {
1503
1773
  return null;
1504
1774
  }
1775
+ if (field.formula) {
1776
+ return /* @__PURE__ */ jsx6("div", { className: "field-overlay-value formula", children: field.value || "..." });
1777
+ }
1505
1778
  const editable = field.assignee === signer;
1506
1779
  if (!editable) {
1507
1780
  if (field.type === "signature" || field.type === "initials") {
@@ -1534,6 +1807,29 @@ function SignerView({
1534
1807
  if (field.type === "signed-date") {
1535
1808
  return /* @__PURE__ */ jsx6("div", { className: "field-overlay-value", children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
1536
1809
  }
1810
+ const fontStyle = {
1811
+ fontSize: `${field.fontSize * 0.6}px`,
1812
+ letterSpacing: field.letterSpacing ? `${field.letterSpacing * 0.6}px` : void 0,
1813
+ lineHeight: field.lineHeight ? `${field.lineHeight}` : void 0,
1814
+ fontFamily: field.fontFamily === "Courier" ? '"Courier New", Courier, monospace' : field.fontFamily === "TimesRoman" ? '"Times New Roman", Times, serif' : field.fontFamily === "Helvetica" ? "Helvetica, Arial, sans-serif" : void 0
1815
+ };
1816
+ if (field.type === "dropdown" || field.options && field.options.length > 0) {
1817
+ return /* @__PURE__ */ jsxs6(
1818
+ "select",
1819
+ {
1820
+ className: "field-inline-input",
1821
+ value: field.value,
1822
+ onChange: (e) => handleFieldUpdate(field.id, e.target.value),
1823
+ onFocus: () => setSelectedFieldId(field.id),
1824
+ onClick: (e) => e.stopPropagation(),
1825
+ style: fontStyle,
1826
+ children: [
1827
+ /* @__PURE__ */ jsx6("option", { value: "", children: field.placeholder || "Select..." }),
1828
+ (field.options || []).map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
1829
+ ]
1830
+ }
1831
+ );
1832
+ }
1537
1833
  return /* @__PURE__ */ jsx6(
1538
1834
  "input",
1539
1835
  {
@@ -1541,10 +1837,11 @@ function SignerView({
1541
1837
  className: "field-inline-input",
1542
1838
  value: field.value,
1543
1839
  placeholder: field.placeholder,
1840
+ maxLength: field.maxLength || void 0,
1544
1841
  onChange: (e) => handleFieldUpdate(field.id, e.target.value),
1545
1842
  onFocus: () => setSelectedFieldId(field.id),
1546
1843
  onClick: (e) => e.stopPropagation(),
1547
- style: { fontSize: `${field.fontSize * 0.6}px` }
1844
+ style: fontStyle
1548
1845
  }
1549
1846
  );
1550
1847
  }, [signer, handleFieldUpdate, setSelectedFieldId]);
@@ -1560,6 +1857,13 @@ function SignerView({
1560
1857
  }));
1561
1858
  }
1562
1859
  }, [fields.filter((f) => f.type === "signature" && f.value).length]);
1860
+ useEffect3(() => {
1861
+ const hasFormulas = fields.some((f) => f.formula);
1862
+ if (!hasFormulas) return;
1863
+ const resolved = resolveAllFormulas(fields, transforms);
1864
+ const changed = resolved.some((f, i) => f.value !== fields[i].value);
1865
+ if (changed) setFields(resolved);
1866
+ }, [fields, transforms]);
1563
1867
  return /* @__PURE__ */ jsxs6("div", { className: "signer-layout", ref: containerRef, children: [
1564
1868
  loading && /* @__PURE__ */ jsx6("div", { className: "loading-indicator", children: "Loading document..." }),
1565
1869
  /* @__PURE__ */ jsxs6("div", { className: "signer-content", children: [
@@ -1601,14 +1905,26 @@ function SignerView({
1601
1905
  initialValue: selectedField.value
1602
1906
  }
1603
1907
  ),
1604
- selectedField.type === "text" && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1605
- /* @__PURE__ */ jsx6(
1908
+ (selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1909
+ selectedField.type === "dropdown" || selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ jsxs6(
1910
+ "select",
1911
+ {
1912
+ value: selectedField.value,
1913
+ onChange: (e) => handleFieldUpdate(selectedField.id, e.target.value),
1914
+ className: "signer-text-input",
1915
+ children: [
1916
+ /* @__PURE__ */ jsx6("option", { value: "", children: selectedField.placeholder || "Select..." }),
1917
+ (selectedField.options || []).map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
1918
+ ]
1919
+ }
1920
+ ) : /* @__PURE__ */ jsx6(
1606
1921
  "input",
1607
1922
  {
1608
1923
  type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
1609
1924
  value: selectedField.value,
1610
1925
  onChange: (e) => handleFieldUpdate(selectedField.id, e.target.value),
1611
1926
  placeholder: selectedField.placeholder,
1927
+ maxLength: selectedField.maxLength || void 0,
1612
1928
  className: "signer-text-input"
1613
1929
  }
1614
1930
  ),
@@ -1670,7 +1986,7 @@ function SignerView({
1670
1986
  }
1671
1987
 
1672
1988
  // src/components/pdf-builder/SignerRoleSelector.tsx
1673
- import { useState as useState5 } from "react";
1989
+ import { useState as useState6 } from "react";
1674
1990
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1675
1991
  function SignerRoleSelector({
1676
1992
  roles,
@@ -1679,8 +1995,8 @@ function SignerRoleSelector({
1679
1995
  onAddRole,
1680
1996
  onRemoveRole
1681
1997
  }) {
1682
- const [isAdding, setIsAdding] = useState5(false);
1683
- const [newRoleName, setNewRoleName] = useState5("");
1998
+ const [isAdding, setIsAdding] = useState6(false);
1999
+ const [newRoleName, setNewRoleName] = useState6("");
1684
2000
  const handleAdd = () => {
1685
2001
  if (newRoleName.trim()) {
1686
2002
  onAddRole(newRoleName.trim());
@@ -1744,9 +2060,11 @@ function SignerRoleSelector({
1744
2060
  ] });
1745
2061
  }
1746
2062
  export {
2063
+ BUILTIN_TRANSFORMS,
1747
2064
  DEFAULT_SIGNER_ROLES,
1748
2065
  DesignerView,
1749
2066
  FIELD_DEFAULTS,
2067
+ FONT_FAMILIES,
1750
2068
  FieldNavigator,
1751
2069
  FieldPropertyPanel,
1752
2070
  PdfViewer,
@@ -1757,9 +2075,12 @@ export {
1757
2075
  createField,
1758
2076
  downloadPdf,
1759
2077
  generateFilledPdf,
2078
+ generateId,
1760
2079
  getSignerColor,
1761
2080
  postPdfToCallback,
1762
2081
  renderPdfPages,
2082
+ resolveAllFormulas,
2083
+ resolveFormula,
1763
2084
  uniqueLabel
1764
2085
  };
1765
2086
  //# sourceMappingURL=index.mjs.map