@unlev/exeq 0.1.11 → 0.2.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/README.md +51 -0
- package/dist/index.css +91 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +438 -100
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +394 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/pdf-builder/DesignerView.tsx
|
|
2
|
-
import { useState as
|
|
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,
|
|
@@ -336,6 +341,7 @@ function FieldOverlayItem({
|
|
|
336
341
|
}
|
|
337
342
|
|
|
338
343
|
// src/components/pdf-builder/FieldPropertyPanel.tsx
|
|
344
|
+
import { useState } from "react";
|
|
339
345
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
340
346
|
var INK_COLORS = [
|
|
341
347
|
{ value: "#000000", label: "Black" },
|
|
@@ -344,8 +350,9 @@ var INK_COLORS = [
|
|
|
344
350
|
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
345
351
|
const color = getSignerColor(field.assignee);
|
|
346
352
|
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
347
|
-
const
|
|
353
|
+
const isTextField = field.type === "text" || field.type === "signed-date";
|
|
348
354
|
const showInkColor = field.type === "text" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
|
|
355
|
+
const [newOption, setNewOption] = useState("");
|
|
349
356
|
return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
|
|
350
357
|
/* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
|
|
351
358
|
/* @__PURE__ */ jsx2("h3", { style: { color }, children: field.label }),
|
|
@@ -418,6 +425,19 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
418
425
|
}
|
|
419
426
|
)
|
|
420
427
|
] }),
|
|
428
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
429
|
+
/* @__PURE__ */ jsx2("label", { children: "Formula" }),
|
|
430
|
+
/* @__PURE__ */ jsx2(
|
|
431
|
+
"input",
|
|
432
|
+
{
|
|
433
|
+
type: "text",
|
|
434
|
+
value: field.formula || "",
|
|
435
|
+
onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }),
|
|
436
|
+
placeholder: "e.g. {{Date Field | month}}"
|
|
437
|
+
}
|
|
438
|
+
),
|
|
439
|
+
field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
|
|
440
|
+
] }),
|
|
421
441
|
!isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
|
|
422
442
|
/* @__PURE__ */ jsx2(
|
|
423
443
|
"input",
|
|
@@ -429,7 +449,18 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
429
449
|
),
|
|
430
450
|
"Required"
|
|
431
451
|
] }) }),
|
|
432
|
-
|
|
452
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
453
|
+
/* @__PURE__ */ jsx2("label", { children: "Font" }),
|
|
454
|
+
/* @__PURE__ */ jsx2(
|
|
455
|
+
"select",
|
|
456
|
+
{
|
|
457
|
+
value: field.fontFamily || "Helvetica",
|
|
458
|
+
onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }),
|
|
459
|
+
children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
] }),
|
|
463
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
433
464
|
/* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
|
|
434
465
|
/* @__PURE__ */ jsx2(
|
|
435
466
|
"input",
|
|
@@ -442,6 +473,49 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
442
473
|
}
|
|
443
474
|
)
|
|
444
475
|
] }),
|
|
476
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
477
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
478
|
+
/* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
|
|
479
|
+
/* @__PURE__ */ jsx2(
|
|
480
|
+
"input",
|
|
481
|
+
{
|
|
482
|
+
type: "number",
|
|
483
|
+
min: "0",
|
|
484
|
+
max: "20",
|
|
485
|
+
step: "0.5",
|
|
486
|
+
value: field.letterSpacing || 0,
|
|
487
|
+
onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) })
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
] }),
|
|
491
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
492
|
+
/* @__PURE__ */ jsx2("label", { children: "Line Height" }),
|
|
493
|
+
/* @__PURE__ */ jsx2(
|
|
494
|
+
"input",
|
|
495
|
+
{
|
|
496
|
+
type: "number",
|
|
497
|
+
min: "0.8",
|
|
498
|
+
max: "3",
|
|
499
|
+
step: "0.1",
|
|
500
|
+
value: field.lineHeight || 1.2,
|
|
501
|
+
onChange: (e) => onUpdate(field.id, { lineHeight: Number(e.target.value) })
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
] })
|
|
505
|
+
] }),
|
|
506
|
+
field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
507
|
+
/* @__PURE__ */ jsx2("label", { children: "Max Characters (0 = unlimited)" }),
|
|
508
|
+
/* @__PURE__ */ jsx2(
|
|
509
|
+
"input",
|
|
510
|
+
{
|
|
511
|
+
type: "number",
|
|
512
|
+
min: "0",
|
|
513
|
+
max: "9999",
|
|
514
|
+
value: field.maxLength || 0,
|
|
515
|
+
onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) })
|
|
516
|
+
}
|
|
517
|
+
)
|
|
518
|
+
] }),
|
|
445
519
|
showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
446
520
|
/* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
|
|
447
521
|
/* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2(
|
|
@@ -454,12 +528,61 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
454
528
|
},
|
|
455
529
|
c.value
|
|
456
530
|
)) })
|
|
531
|
+
] }),
|
|
532
|
+
field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
533
|
+
/* @__PURE__ */ jsx2("label", { children: "Predefined Options" }),
|
|
534
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
|
|
535
|
+
(field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
|
|
536
|
+
/* @__PURE__ */ jsx2("span", { children: opt }),
|
|
537
|
+
/* @__PURE__ */ jsx2(
|
|
538
|
+
"button",
|
|
539
|
+
{
|
|
540
|
+
className: "panel-option-remove",
|
|
541
|
+
onClick: () => {
|
|
542
|
+
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
543
|
+
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
544
|
+
},
|
|
545
|
+
children: "\xD7"
|
|
546
|
+
}
|
|
547
|
+
)
|
|
548
|
+
] }, i)),
|
|
549
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
|
|
550
|
+
/* @__PURE__ */ jsx2(
|
|
551
|
+
"input",
|
|
552
|
+
{
|
|
553
|
+
type: "text",
|
|
554
|
+
value: newOption,
|
|
555
|
+
onChange: (e) => setNewOption(e.target.value),
|
|
556
|
+
onKeyDown: (e) => {
|
|
557
|
+
if (e.key === "Enter" && newOption.trim()) {
|
|
558
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
559
|
+
setNewOption("");
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
placeholder: "Add option..."
|
|
563
|
+
}
|
|
564
|
+
),
|
|
565
|
+
/* @__PURE__ */ jsx2(
|
|
566
|
+
"button",
|
|
567
|
+
{
|
|
568
|
+
onClick: () => {
|
|
569
|
+
if (newOption.trim()) {
|
|
570
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
571
|
+
setNewOption("");
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
children: "+"
|
|
575
|
+
}
|
|
576
|
+
)
|
|
577
|
+
] })
|
|
578
|
+
] }),
|
|
579
|
+
(field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
|
|
457
580
|
] })
|
|
458
581
|
] });
|
|
459
582
|
}
|
|
460
583
|
|
|
461
584
|
// src/components/pdf-builder/SignatureCanvas.tsx
|
|
462
|
-
import { useRef as useRef2, useState, useCallback as useCallback2, useEffect } from "react";
|
|
585
|
+
import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect } from "react";
|
|
463
586
|
import { getStroke } from "perfect-freehand";
|
|
464
587
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
465
588
|
function getSvgPathFromStroke(stroke) {
|
|
@@ -484,9 +607,9 @@ function SignatureCanvas({
|
|
|
484
607
|
inkColor = "#000000"
|
|
485
608
|
}) {
|
|
486
609
|
const canvasRef = useRef2(null);
|
|
487
|
-
const [paths, setPaths] =
|
|
488
|
-
const [currentPath, setCurrentPath] =
|
|
489
|
-
const [isEmpty, setIsEmpty] =
|
|
610
|
+
const [paths, setPaths] = useState2([]);
|
|
611
|
+
const [currentPath, setCurrentPath] = useState2(null);
|
|
612
|
+
const [isEmpty, setIsEmpty] = useState2(!initialValue);
|
|
490
613
|
useEffect(() => {
|
|
491
614
|
if (initialValue && canvasRef.current) {
|
|
492
615
|
const ctx = canvasRef.current.getContext("2d");
|
|
@@ -627,20 +750,20 @@ function DesignerView({
|
|
|
627
750
|
] })
|
|
628
751
|
] }) });
|
|
629
752
|
}
|
|
630
|
-
const [pages, setPages] =
|
|
631
|
-
const [fields, setFields] =
|
|
632
|
-
const [selectedFieldId, setSelectedFieldId] =
|
|
633
|
-
const [signerRoles, setSignerRoles] =
|
|
634
|
-
const [activeRole, setActiveRole] =
|
|
635
|
-
const [activeFieldType, setActiveFieldType] =
|
|
636
|
-
const [loading, setLoading] =
|
|
637
|
-
const [pdfSource, setPdfSource] =
|
|
638
|
-
const [rightTab, setRightTab] =
|
|
639
|
-
const [isAddingRole, setIsAddingRole] =
|
|
640
|
-
const [newRoleName, setNewRoleName] =
|
|
641
|
-
const [draggingFieldType, setDraggingFieldType] =
|
|
642
|
-
const [panelWidth, setPanelWidth] =
|
|
643
|
-
const [clipboardField, setClipboardField] =
|
|
753
|
+
const [pages, setPages] = useState3([]);
|
|
754
|
+
const [fields, setFields] = useState3(initialTemplate?.fields ?? []);
|
|
755
|
+
const [selectedFieldId, setSelectedFieldId] = useState3(null);
|
|
756
|
+
const [signerRoles, setSignerRoles] = useState3(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
757
|
+
const [activeRole, setActiveRole] = useState3("Sender");
|
|
758
|
+
const [activeFieldType, setActiveFieldType] = useState3("text");
|
|
759
|
+
const [loading, setLoading] = useState3(false);
|
|
760
|
+
const [pdfSource, setPdfSource] = useState3(null);
|
|
761
|
+
const [rightTab, setRightTab] = useState3("properties");
|
|
762
|
+
const [isAddingRole, setIsAddingRole] = useState3(false);
|
|
763
|
+
const [newRoleName, setNewRoleName] = useState3("");
|
|
764
|
+
const [draggingFieldType, setDraggingFieldType] = useState3(null);
|
|
765
|
+
const [panelWidth, setPanelWidth] = useState3(380);
|
|
766
|
+
const [clipboardField, setClipboardField] = useState3(null);
|
|
644
767
|
const dragGhostRef = useRef3(null);
|
|
645
768
|
const resizingRef = useRef3(false);
|
|
646
769
|
const lastStylesRef = useRef3({});
|
|
@@ -1108,10 +1231,15 @@ function DesignerView({
|
|
|
1108
1231
|
}
|
|
1109
1232
|
|
|
1110
1233
|
// src/components/pdf-builder/SignerView.tsx
|
|
1111
|
-
import { useState as
|
|
1234
|
+
import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
1112
1235
|
|
|
1113
1236
|
// src/utils/pdfFiller.ts
|
|
1114
1237
|
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
1238
|
+
var FONT_MAP = {
|
|
1239
|
+
"Helvetica": StandardFonts.Helvetica,
|
|
1240
|
+
"Courier": StandardFonts.Courier,
|
|
1241
|
+
"TimesRoman": StandardFonts.TimesRoman
|
|
1242
|
+
};
|
|
1115
1243
|
async function generateFilledPdf(pdfSource, fields) {
|
|
1116
1244
|
let pdfBytes;
|
|
1117
1245
|
if (typeof pdfSource === "string") {
|
|
@@ -1121,7 +1249,12 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
1121
1249
|
pdfBytes = pdfSource;
|
|
1122
1250
|
}
|
|
1123
1251
|
const pdfDoc = await PDFDocument.load(pdfBytes);
|
|
1124
|
-
const
|
|
1252
|
+
const fontCache = /* @__PURE__ */ new Map();
|
|
1253
|
+
const usedFontKeys = new Set(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
1254
|
+
for (const key of usedFontKeys) {
|
|
1255
|
+
const stdFont = FONT_MAP[key] || StandardFonts.Helvetica;
|
|
1256
|
+
fontCache.set(key, await pdfDoc.embedFont(stdFont));
|
|
1257
|
+
}
|
|
1125
1258
|
const pages = pdfDoc.getPages();
|
|
1126
1259
|
function hexToRgb(hex) {
|
|
1127
1260
|
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
@@ -1182,15 +1315,35 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
1182
1315
|
});
|
|
1183
1316
|
}
|
|
1184
1317
|
} else {
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1318
|
+
const font = fontCache.get(field.fontFamily || "Helvetica");
|
|
1319
|
+
const spacing = field.letterSpacing || 0;
|
|
1320
|
+
const textWidthAtSize = (text, size) => {
|
|
1321
|
+
const baseWidth = font.widthOfTextAtSize(text, size);
|
|
1322
|
+
return baseWidth + spacing * (text.length - 1);
|
|
1323
|
+
};
|
|
1324
|
+
const maxFontSize = Math.min(field.fontSize, h * 0.7);
|
|
1325
|
+
let fontSize = maxFontSize;
|
|
1326
|
+
const padding = 4;
|
|
1327
|
+
while (fontSize > 4) {
|
|
1328
|
+
if (textWidthAtSize(field.value, fontSize) <= w - padding) break;
|
|
1329
|
+
fontSize -= 0.5;
|
|
1330
|
+
}
|
|
1331
|
+
if (spacing > 0) {
|
|
1332
|
+
let cx = x + 2;
|
|
1333
|
+
const cy = y + h * 0.3;
|
|
1334
|
+
for (const char of field.value) {
|
|
1335
|
+
page.drawText(char, { x: cx, y: cy, size: fontSize, font, color: inkColor });
|
|
1336
|
+
cx += font.widthOfTextAtSize(char, fontSize) + spacing;
|
|
1337
|
+
}
|
|
1338
|
+
} else {
|
|
1339
|
+
page.drawText(field.value, {
|
|
1340
|
+
x: x + 2,
|
|
1341
|
+
y: y + h * 0.3,
|
|
1342
|
+
size: fontSize,
|
|
1343
|
+
font,
|
|
1344
|
+
color: inkColor
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1194
1347
|
}
|
|
1195
1348
|
}
|
|
1196
1349
|
return pdfDoc.save();
|
|
@@ -1215,7 +1368,13 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1215
1368
|
}
|
|
1216
1369
|
|
|
1217
1370
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1371
|
+
import { useState as useState4 } from "react";
|
|
1218
1372
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1373
|
+
function isFieldFilled(f) {
|
|
1374
|
+
if (!f.required) return true;
|
|
1375
|
+
if (f.type === "checkbox") return true;
|
|
1376
|
+
return !!f.value;
|
|
1377
|
+
}
|
|
1219
1378
|
function FieldNavigator({
|
|
1220
1379
|
fields,
|
|
1221
1380
|
currentFieldId,
|
|
@@ -1224,6 +1383,7 @@ function FieldNavigator({
|
|
|
1224
1383
|
onComplete,
|
|
1225
1384
|
completeLabel = "Complete"
|
|
1226
1385
|
}) {
|
|
1386
|
+
const [showIncomplete, setShowIncomplete] = useState4(false);
|
|
1227
1387
|
const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
|
|
1228
1388
|
const hasPrev = currentIndex > 0;
|
|
1229
1389
|
const hasNext = currentIndex < fields.length - 1;
|
|
@@ -1237,19 +1397,36 @@ function FieldNavigator({
|
|
|
1237
1397
|
onNavigate(fields[currentIndex + 1].id);
|
|
1238
1398
|
}
|
|
1239
1399
|
};
|
|
1240
|
-
const filledCount = fields.filter(
|
|
1241
|
-
|
|
1242
|
-
if (f.type === "checkbox") return true;
|
|
1243
|
-
return !!f.value;
|
|
1244
|
-
}).length;
|
|
1400
|
+
const filledCount = fields.filter(isFieldFilled).length;
|
|
1401
|
+
const incompleteFields = fields.filter((f) => !isFieldFilled(f));
|
|
1245
1402
|
const showCompleteAsNext = !hasNext && allRequiredFilled;
|
|
1246
1403
|
return /* @__PURE__ */ jsxs5("div", { className: "field-navigator", children: [
|
|
1247
|
-
/* @__PURE__ */ jsxs5(
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1404
|
+
/* @__PURE__ */ jsxs5(
|
|
1405
|
+
"div",
|
|
1406
|
+
{
|
|
1407
|
+
className: `field-navigator-progress ${incompleteFields.length > 0 ? "clickable" : ""}`,
|
|
1408
|
+
onClick: () => incompleteFields.length > 0 && setShowIncomplete(!showIncomplete),
|
|
1409
|
+
children: [
|
|
1410
|
+
filledCount,
|
|
1411
|
+
" of ",
|
|
1412
|
+
fields.length,
|
|
1413
|
+
" fields completed",
|
|
1414
|
+
incompleteFields.length > 0 && /* @__PURE__ */ jsx5("span", { className: "field-navigator-toggle", children: showIncomplete ? "\u25B2" : "\u25BC" })
|
|
1415
|
+
]
|
|
1416
|
+
}
|
|
1417
|
+
),
|
|
1418
|
+
showIncomplete && incompleteFields.length > 0 && /* @__PURE__ */ jsx5("div", { className: "incomplete-fields-list", children: incompleteFields.map((f) => /* @__PURE__ */ jsx5(
|
|
1419
|
+
"button",
|
|
1420
|
+
{
|
|
1421
|
+
className: "incomplete-field-item",
|
|
1422
|
+
onClick: () => {
|
|
1423
|
+
onNavigate(f.id);
|
|
1424
|
+
setShowIncomplete(false);
|
|
1425
|
+
},
|
|
1426
|
+
children: f.label
|
|
1427
|
+
},
|
|
1428
|
+
f.id
|
|
1429
|
+
)) }),
|
|
1253
1430
|
/* @__PURE__ */ jsxs5("div", { className: "field-navigator-controls", children: [
|
|
1254
1431
|
/* @__PURE__ */ jsx5(
|
|
1255
1432
|
"button",
|
|
@@ -1281,6 +1458,95 @@ function FieldNavigator({
|
|
|
1281
1458
|
] });
|
|
1282
1459
|
}
|
|
1283
1460
|
|
|
1461
|
+
// src/utils/formulaResolver.ts
|
|
1462
|
+
var BUILTIN_TRANSFORMS = {
|
|
1463
|
+
// Date transforms (expects a parseable date string)
|
|
1464
|
+
month: (v) => {
|
|
1465
|
+
const d = new Date(v);
|
|
1466
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1);
|
|
1467
|
+
},
|
|
1468
|
+
month2: (v) => {
|
|
1469
|
+
const d = new Date(v);
|
|
1470
|
+
return isNaN(d.getTime()) ? "" : String(d.getMonth() + 1).padStart(2, "0");
|
|
1471
|
+
},
|
|
1472
|
+
monthname: (v) => {
|
|
1473
|
+
const d = new Date(v);
|
|
1474
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "long" });
|
|
1475
|
+
},
|
|
1476
|
+
monthshort: (v) => {
|
|
1477
|
+
const d = new Date(v);
|
|
1478
|
+
return isNaN(d.getTime()) ? "" : d.toLocaleString("en", { month: "short" });
|
|
1479
|
+
},
|
|
1480
|
+
day: (v) => {
|
|
1481
|
+
const d = new Date(v);
|
|
1482
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate());
|
|
1483
|
+
},
|
|
1484
|
+
day2: (v) => {
|
|
1485
|
+
const d = new Date(v);
|
|
1486
|
+
return isNaN(d.getTime()) ? "" : String(d.getDate()).padStart(2, "0");
|
|
1487
|
+
},
|
|
1488
|
+
year: (v) => {
|
|
1489
|
+
const d = new Date(v);
|
|
1490
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear());
|
|
1491
|
+
},
|
|
1492
|
+
year2: (v) => {
|
|
1493
|
+
const d = new Date(v);
|
|
1494
|
+
return isNaN(d.getTime()) ? "" : String(d.getFullYear()).slice(-2);
|
|
1495
|
+
},
|
|
1496
|
+
// String transforms
|
|
1497
|
+
upper: (v) => v.toUpperCase(),
|
|
1498
|
+
lower: (v) => v.toLowerCase(),
|
|
1499
|
+
trim: (v) => v.trim(),
|
|
1500
|
+
first: (v) => v.split(/\s+/)[0] || "",
|
|
1501
|
+
last: (v) => {
|
|
1502
|
+
const parts = v.split(/\s+/);
|
|
1503
|
+
return parts[parts.length - 1] || "";
|
|
1504
|
+
},
|
|
1505
|
+
initials: (v) => v.split(/\s+/).map((w) => w[0] || "").join("").toUpperCase(),
|
|
1506
|
+
// Numeric / substring
|
|
1507
|
+
last4: (v) => v.slice(-4),
|
|
1508
|
+
last2: (v) => v.slice(-2),
|
|
1509
|
+
first4: (v) => v.slice(0, 4),
|
|
1510
|
+
first2: (v) => v.slice(0, 2),
|
|
1511
|
+
digits: (v) => v.replace(/\D/g, ""),
|
|
1512
|
+
number: (v) => {
|
|
1513
|
+
const n = parseFloat(v);
|
|
1514
|
+
return isNaN(n) ? "" : String(n);
|
|
1515
|
+
},
|
|
1516
|
+
currency: (v) => {
|
|
1517
|
+
const n = parseFloat(v.replace(/[^0-9.-]/g, ""));
|
|
1518
|
+
return isNaN(n) ? "" : `$${n.toFixed(2)}`;
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
var FORMULA_RE = /\{\{(.+?)\}\}/g;
|
|
1522
|
+
var PIPE_RE = /^(.+?)\s*\|\s*(.+)$/;
|
|
1523
|
+
function resolveFormula(formula, fields, customTransforms) {
|
|
1524
|
+
const transforms = { ...BUILTIN_TRANSFORMS, ...customTransforms };
|
|
1525
|
+
return formula.replace(FORMULA_RE, (_match, expr) => {
|
|
1526
|
+
const pipeMatch = expr.match(PIPE_RE);
|
|
1527
|
+
const sourceLabel = (pipeMatch ? pipeMatch[1] : expr).trim();
|
|
1528
|
+
const transformName = pipeMatch ? pipeMatch[2].trim() : null;
|
|
1529
|
+
const sourceField = fields.find((f) => f.label.toLowerCase() === sourceLabel.toLowerCase()) || fields.find((f) => f.id === sourceLabel);
|
|
1530
|
+
if (!sourceField) return "";
|
|
1531
|
+
const rawValue = sourceField.value || "";
|
|
1532
|
+
if (!transformName) return rawValue;
|
|
1533
|
+
const fn = transforms[transformName];
|
|
1534
|
+
if (!fn) return rawValue;
|
|
1535
|
+
try {
|
|
1536
|
+
return fn(rawValue);
|
|
1537
|
+
} catch {
|
|
1538
|
+
return rawValue;
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
function resolveAllFormulas(fields, customTransforms) {
|
|
1543
|
+
return fields.map((f) => {
|
|
1544
|
+
if (!f.formula) return f;
|
|
1545
|
+
const computed = resolveFormula(f.formula, fields, customTransforms);
|
|
1546
|
+
return computed !== f.value ? { ...f, value: computed } : f;
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1284
1550
|
// src/components/pdf-builder/SignerView.tsx
|
|
1285
1551
|
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1286
1552
|
function SignerView({
|
|
@@ -1292,7 +1558,8 @@ function SignerView({
|
|
|
1292
1558
|
onComplete,
|
|
1293
1559
|
initialValues,
|
|
1294
1560
|
submitLabel,
|
|
1295
|
-
signerOrder: signerOrderProp
|
|
1561
|
+
signerOrder: signerOrderProp,
|
|
1562
|
+
transforms
|
|
1296
1563
|
} = {}) {
|
|
1297
1564
|
if (!isValidApiKey(apiKey)) {
|
|
1298
1565
|
return /* @__PURE__ */ jsx6("div", { className: "signer-layout", children: /* @__PURE__ */ jsxs6("div", { className: "empty-state", children: [
|
|
@@ -1304,11 +1571,11 @@ function SignerView({
|
|
|
1304
1571
|
] })
|
|
1305
1572
|
] }) });
|
|
1306
1573
|
}
|
|
1307
|
-
const [pages, setPages] =
|
|
1308
|
-
const [fields, setFields] =
|
|
1309
|
-
const [selectedFieldId, setSelectedFieldId] =
|
|
1310
|
-
const [signerRoles, setSignerRoles] =
|
|
1311
|
-
const [currentSignerIndex, setCurrentSignerIndex] =
|
|
1574
|
+
const [pages, setPages] = useState5([]);
|
|
1575
|
+
const [fields, setFields] = useState5([]);
|
|
1576
|
+
const [selectedFieldId, setSelectedFieldId] = useState5(null);
|
|
1577
|
+
const [signerRoles, setSignerRoles] = useState5([]);
|
|
1578
|
+
const [currentSignerIndex, setCurrentSignerIndex] = useState5(() => {
|
|
1312
1579
|
if (initialSigner && signerOrderProp) {
|
|
1313
1580
|
const idx = signerOrderProp.indexOf(initialSigner);
|
|
1314
1581
|
return idx >= 0 ? idx : 0;
|
|
@@ -1316,15 +1583,30 @@ function SignerView({
|
|
|
1316
1583
|
return 0;
|
|
1317
1584
|
});
|
|
1318
1585
|
const initializedRef = useRef4(false);
|
|
1319
|
-
const [loading, setLoading] =
|
|
1320
|
-
const [submitting, setSubmitting] =
|
|
1321
|
-
const [pdfSource, setPdfSource] =
|
|
1322
|
-
const [callbackUrl, setCallbackUrl] =
|
|
1586
|
+
const [loading, setLoading] = useState5(false);
|
|
1587
|
+
const [submitting, setSubmitting] = useState5(false);
|
|
1588
|
+
const [pdfSource, setPdfSource] = useState5(null);
|
|
1589
|
+
const [callbackUrl, setCallbackUrl] = useState5(initialCallbackUrl || "");
|
|
1323
1590
|
const containerRef = useRef4(null);
|
|
1324
1591
|
const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
|
|
1325
1592
|
const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
|
|
1326
1593
|
const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
|
|
1327
1594
|
const isMultiSigner = signerOrder.length > 1;
|
|
1595
|
+
useEffect3(() => {
|
|
1596
|
+
if (fields.length === 0 || !isMultiSigner) return;
|
|
1597
|
+
const signerFields = fields.filter(
|
|
1598
|
+
(f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout"
|
|
1599
|
+
);
|
|
1600
|
+
const allFilled = signerFields.length > 0 && signerFields.every((f) => {
|
|
1601
|
+
if (!f.required) return true;
|
|
1602
|
+
if (f.type === "checkbox") return true;
|
|
1603
|
+
return !!f.value;
|
|
1604
|
+
});
|
|
1605
|
+
if (allFilled && !isLastSigner) {
|
|
1606
|
+
setCurrentSignerIndex((prev) => prev + 1);
|
|
1607
|
+
setSelectedFieldId(null);
|
|
1608
|
+
}
|
|
1609
|
+
}, [currentSignerIndex, fields, signer, isLastSigner, isMultiSigner]);
|
|
1328
1610
|
useEffect3(() => {
|
|
1329
1611
|
if (initialTemplate) {
|
|
1330
1612
|
let templateFields = initialTemplate.fields;
|
|
@@ -1407,14 +1689,14 @@ function SignerView({
|
|
|
1407
1689
|
setLoading(false);
|
|
1408
1690
|
}
|
|
1409
1691
|
}, []);
|
|
1410
|
-
const editableFields = fields.filter((f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout").sort((a, b) => {
|
|
1692
|
+
const editableFields = fields.filter((f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout" && !f.formula).sort((a, b) => {
|
|
1411
1693
|
if (a.page !== b.page) return a.page - b.page;
|
|
1412
1694
|
const bandThreshold = 2;
|
|
1413
1695
|
if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
|
|
1414
1696
|
return a.x - b.x;
|
|
1415
1697
|
});
|
|
1416
1698
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1417
|
-
const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" : false;
|
|
1699
|
+
const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
|
|
1418
1700
|
const handleFieldUpdate = useCallback4((id, value) => {
|
|
1419
1701
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1420
1702
|
}, []);
|
|
@@ -1450,7 +1732,8 @@ function SignerView({
|
|
|
1450
1732
|
if (!pdfSource) return;
|
|
1451
1733
|
setSubmitting(true);
|
|
1452
1734
|
try {
|
|
1453
|
-
const
|
|
1735
|
+
const finalFields = resolveAllFormulas(fields, transforms);
|
|
1736
|
+
const pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
1454
1737
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
1455
1738
|
if (callbackUrl) {
|
|
1456
1739
|
await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
|
|
@@ -1472,6 +1755,9 @@ function SignerView({
|
|
|
1472
1755
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1473
1756
|
return null;
|
|
1474
1757
|
}
|
|
1758
|
+
if (field.formula) {
|
|
1759
|
+
return /* @__PURE__ */ jsx6("div", { className: "field-overlay-value formula", children: field.value || "..." });
|
|
1760
|
+
}
|
|
1475
1761
|
const editable = field.assignee === signer;
|
|
1476
1762
|
if (!editable) {
|
|
1477
1763
|
if (field.type === "signature" || field.type === "initials") {
|
|
@@ -1504,6 +1790,28 @@ function SignerView({
|
|
|
1504
1790
|
if (field.type === "signed-date") {
|
|
1505
1791
|
return /* @__PURE__ */ jsx6("div", { className: "field-overlay-value", children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
|
|
1506
1792
|
}
|
|
1793
|
+
const fontStyle = {
|
|
1794
|
+
fontSize: `${field.fontSize * 0.6}px`,
|
|
1795
|
+
letterSpacing: field.letterSpacing ? `${field.letterSpacing * 0.6}px` : void 0,
|
|
1796
|
+
fontFamily: field.fontFamily === "Courier" ? "monospace" : field.fontFamily === "TimesRoman" ? "serif" : void 0
|
|
1797
|
+
};
|
|
1798
|
+
if (field.options && field.options.length > 0) {
|
|
1799
|
+
return /* @__PURE__ */ jsxs6(
|
|
1800
|
+
"select",
|
|
1801
|
+
{
|
|
1802
|
+
className: "field-inline-input",
|
|
1803
|
+
value: field.value,
|
|
1804
|
+
onChange: (e) => handleFieldUpdate(field.id, e.target.value),
|
|
1805
|
+
onFocus: () => setSelectedFieldId(field.id),
|
|
1806
|
+
onClick: (e) => e.stopPropagation(),
|
|
1807
|
+
style: fontStyle,
|
|
1808
|
+
children: [
|
|
1809
|
+
/* @__PURE__ */ jsx6("option", { value: "", children: field.placeholder || "Select..." }),
|
|
1810
|
+
field.options.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
|
|
1811
|
+
]
|
|
1812
|
+
}
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1507
1815
|
return /* @__PURE__ */ jsx6(
|
|
1508
1816
|
"input",
|
|
1509
1817
|
{
|
|
@@ -1511,10 +1819,11 @@ function SignerView({
|
|
|
1511
1819
|
className: "field-inline-input",
|
|
1512
1820
|
value: field.value,
|
|
1513
1821
|
placeholder: field.placeholder,
|
|
1822
|
+
maxLength: field.maxLength || void 0,
|
|
1514
1823
|
onChange: (e) => handleFieldUpdate(field.id, e.target.value),
|
|
1515
1824
|
onFocus: () => setSelectedFieldId(field.id),
|
|
1516
1825
|
onClick: (e) => e.stopPropagation(),
|
|
1517
|
-
style:
|
|
1826
|
+
style: fontStyle
|
|
1518
1827
|
}
|
|
1519
1828
|
);
|
|
1520
1829
|
}, [signer, handleFieldUpdate, setSelectedFieldId]);
|
|
@@ -1530,6 +1839,13 @@ function SignerView({
|
|
|
1530
1839
|
}));
|
|
1531
1840
|
}
|
|
1532
1841
|
}, [fields.filter((f) => f.type === "signature" && f.value).length]);
|
|
1842
|
+
useEffect3(() => {
|
|
1843
|
+
const hasFormulas = fields.some((f) => f.formula);
|
|
1844
|
+
if (!hasFormulas) return;
|
|
1845
|
+
const resolved = resolveAllFormulas(fields, transforms);
|
|
1846
|
+
const changed = resolved.some((f, i) => f.value !== fields[i].value);
|
|
1847
|
+
if (changed) setFields(resolved);
|
|
1848
|
+
}, [fields, transforms]);
|
|
1533
1849
|
return /* @__PURE__ */ jsxs6("div", { className: "signer-layout", ref: containerRef, children: [
|
|
1534
1850
|
loading && /* @__PURE__ */ jsx6("div", { className: "loading-indicator", children: "Loading document..." }),
|
|
1535
1851
|
/* @__PURE__ */ jsxs6("div", { className: "signer-content", children: [
|
|
@@ -1572,13 +1888,25 @@ function SignerView({
|
|
|
1572
1888
|
}
|
|
1573
1889
|
),
|
|
1574
1890
|
selectedField.type === "text" && /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1575
|
-
/* @__PURE__ */
|
|
1891
|
+
selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ jsxs6(
|
|
1892
|
+
"select",
|
|
1893
|
+
{
|
|
1894
|
+
value: selectedField.value,
|
|
1895
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, e.target.value),
|
|
1896
|
+
className: "signer-text-input",
|
|
1897
|
+
children: [
|
|
1898
|
+
/* @__PURE__ */ jsx6("option", { value: "", children: selectedField.placeholder || "Select..." }),
|
|
1899
|
+
selectedField.options.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt, children: opt }, opt))
|
|
1900
|
+
]
|
|
1901
|
+
}
|
|
1902
|
+
) : /* @__PURE__ */ jsx6(
|
|
1576
1903
|
"input",
|
|
1577
1904
|
{
|
|
1578
1905
|
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1579
1906
|
value: selectedField.value,
|
|
1580
1907
|
onChange: (e) => handleFieldUpdate(selectedField.id, e.target.value),
|
|
1581
1908
|
placeholder: selectedField.placeholder,
|
|
1909
|
+
maxLength: selectedField.maxLength || void 0,
|
|
1582
1910
|
className: "signer-text-input"
|
|
1583
1911
|
}
|
|
1584
1912
|
),
|
|
@@ -1640,7 +1968,7 @@ function SignerView({
|
|
|
1640
1968
|
}
|
|
1641
1969
|
|
|
1642
1970
|
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
1643
|
-
import { useState as
|
|
1971
|
+
import { useState as useState6 } from "react";
|
|
1644
1972
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1645
1973
|
function SignerRoleSelector({
|
|
1646
1974
|
roles,
|
|
@@ -1649,8 +1977,8 @@ function SignerRoleSelector({
|
|
|
1649
1977
|
onAddRole,
|
|
1650
1978
|
onRemoveRole
|
|
1651
1979
|
}) {
|
|
1652
|
-
const [isAdding, setIsAdding] =
|
|
1653
|
-
const [newRoleName, setNewRoleName] =
|
|
1980
|
+
const [isAdding, setIsAdding] = useState6(false);
|
|
1981
|
+
const [newRoleName, setNewRoleName] = useState6("");
|
|
1654
1982
|
const handleAdd = () => {
|
|
1655
1983
|
if (newRoleName.trim()) {
|
|
1656
1984
|
onAddRole(newRoleName.trim());
|
|
@@ -1714,9 +2042,11 @@ function SignerRoleSelector({
|
|
|
1714
2042
|
] });
|
|
1715
2043
|
}
|
|
1716
2044
|
export {
|
|
2045
|
+
BUILTIN_TRANSFORMS,
|
|
1717
2046
|
DEFAULT_SIGNER_ROLES,
|
|
1718
2047
|
DesignerView,
|
|
1719
2048
|
FIELD_DEFAULTS,
|
|
2049
|
+
FONT_FAMILIES,
|
|
1720
2050
|
FieldNavigator,
|
|
1721
2051
|
FieldPropertyPanel,
|
|
1722
2052
|
PdfViewer,
|
|
@@ -1727,9 +2057,12 @@ export {
|
|
|
1727
2057
|
createField,
|
|
1728
2058
|
downloadPdf,
|
|
1729
2059
|
generateFilledPdf,
|
|
2060
|
+
generateId,
|
|
1730
2061
|
getSignerColor,
|
|
1731
2062
|
postPdfToCallback,
|
|
1732
2063
|
renderPdfPages,
|
|
2064
|
+
resolveAllFormulas,
|
|
2065
|
+
resolveFormula,
|
|
1733
2066
|
uniqueLabel
|
|
1734
2067
|
};
|
|
1735
2068
|
//# sourceMappingURL=index.mjs.map
|