@unlev/exeq 0.1.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/index.mjs ADDED
@@ -0,0 +1,1266 @@
1
+ // src/components/pdf-builder/DesignerView.tsx
2
+ import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2 } from "react";
3
+
4
+ // src/types/pdf-builder.ts
5
+ function generateId() {
6
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
7
+ return crypto.randomUUID();
8
+ }
9
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
10
+ const r = Math.random() * 16 | 0;
11
+ const v = c === "x" ? r : r & 3 | 8;
12
+ return v.toString(16);
13
+ });
14
+ }
15
+ var DEFAULT_SIGNER_ROLES = ["Sender", "Signer 1"];
16
+ var SIGNER_ROLE_COLORS = {
17
+ "Sender": "#6366f1",
18
+ "Signer 1": "#22c55e",
19
+ "Signer 2": "#f59e0b",
20
+ "Signer 3": "#ef4444",
21
+ "Signer 4": "#06b6d4",
22
+ "Signer 5": "#d946ef"
23
+ };
24
+ function getSignerColor(role) {
25
+ return SIGNER_ROLE_COLORS[role] || "#888888";
26
+ }
27
+ var FIELD_DEFAULTS = {
28
+ text: {
29
+ width: 20,
30
+ height: 3,
31
+ fontSize: 12,
32
+ placeholder: "Enter text",
33
+ textSubtype: "freeform"
34
+ },
35
+ signature: {
36
+ width: 20,
37
+ height: 6,
38
+ fontSize: 12,
39
+ placeholder: "Sign here"
40
+ },
41
+ "signed-date": {
42
+ width: 15,
43
+ height: 3,
44
+ fontSize: 12,
45
+ placeholder: "Date"
46
+ },
47
+ checkbox: {
48
+ width: 2.5,
49
+ height: 2.5,
50
+ fontSize: 12,
51
+ placeholder: ""
52
+ },
53
+ initials: {
54
+ width: 8,
55
+ height: 5,
56
+ fontSize: 12,
57
+ placeholder: "Initials"
58
+ }
59
+ };
60
+ function createField(type, assignee, page, x, y) {
61
+ const defaults = FIELD_DEFAULTS[type];
62
+ return {
63
+ id: generateId(),
64
+ type,
65
+ label: type === "text" ? "Text Field" : type === "signature" ? "Signature" : type === "signed-date" ? "Signed Date" : type === "checkbox" ? "Checkbox" : "Initials",
66
+ placeholder: defaults.placeholder || "",
67
+ required: true,
68
+ assignee,
69
+ page,
70
+ x,
71
+ y,
72
+ width: defaults.width || 20,
73
+ height: defaults.height || 3,
74
+ fontSize: defaults.fontSize || 12,
75
+ value: "",
76
+ ...type === "text" ? { textSubtype: defaults.textSubtype } : {}
77
+ };
78
+ }
79
+
80
+ // src/utils/pdfRenderer.ts
81
+ import * as pdfjsLib from "pdfjs-dist";
82
+ if (typeof window !== "undefined") {
83
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.mjs`;
84
+ }
85
+ async function renderPdfPages(source, scale = 2) {
86
+ const loadingTask = pdfjsLib.getDocument(
87
+ typeof source === "string" ? { url: source } : { data: source }
88
+ );
89
+ const pdf = await loadingTask.promise;
90
+ const pages = [];
91
+ for (let i = 1; i <= pdf.numPages; i++) {
92
+ const page = await pdf.getPage(i);
93
+ const viewport = page.getViewport({ scale });
94
+ const canvas = document.createElement("canvas");
95
+ canvas.width = viewport.width;
96
+ canvas.height = viewport.height;
97
+ const ctx = canvas.getContext("2d");
98
+ await page.render({ canvasContext: ctx, viewport }).promise;
99
+ pages.push({
100
+ pageNumber: i,
101
+ dataUrl: canvas.toDataURL("image/png"),
102
+ width: viewport.width,
103
+ height: viewport.height
104
+ });
105
+ }
106
+ return pages;
107
+ }
108
+
109
+ // src/components/pdf-builder/PdfViewer.tsx
110
+ import { useRef, useCallback } from "react";
111
+ import { jsx, jsxs } from "react/jsx-runtime";
112
+ function PdfViewer({
113
+ pages,
114
+ fields,
115
+ selectedFieldId,
116
+ onSelectField,
117
+ onFieldMove,
118
+ onFieldResize,
119
+ onPageClick,
120
+ mode,
121
+ currentSigner,
122
+ renderFieldContent
123
+ }) {
124
+ const containerRef = useRef(null);
125
+ const handlePageClick = useCallback((e, pageIndex) => {
126
+ const target = e.target;
127
+ if (target.closest(".field-overlay")) return;
128
+ if (onPageClick) {
129
+ const rect = e.currentTarget.getBoundingClientRect();
130
+ const x = (e.clientX - rect.left) / rect.width * 100;
131
+ const y = (e.clientY - rect.top) / rect.height * 100;
132
+ onPageClick(pageIndex, x, y);
133
+ } else {
134
+ onSelectField(null);
135
+ }
136
+ }, [onPageClick, onSelectField]);
137
+ return /* @__PURE__ */ jsx("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
138
+ const pageFields = fields.filter((f) => f.page === pageIndex);
139
+ return /* @__PURE__ */ jsxs(
140
+ "div",
141
+ {
142
+ className: "pdf-page",
143
+ style: { aspectRatio: `${page.width} / ${page.height}` },
144
+ onClick: (e) => handlePageClick(e, pageIndex),
145
+ "data-page": pageIndex,
146
+ children: [
147
+ /* @__PURE__ */ jsx(
148
+ "img",
149
+ {
150
+ src: page.dataUrl,
151
+ alt: `Page ${pageIndex + 1}`,
152
+ className: "pdf-page-image",
153
+ draggable: false
154
+ }
155
+ ),
156
+ pageFields.map((field) => /* @__PURE__ */ jsx(
157
+ FieldOverlayItem,
158
+ {
159
+ field,
160
+ isSelected: field.id === selectedFieldId,
161
+ onSelect: () => onSelectField(field.id),
162
+ onMove: onFieldMove,
163
+ onResize: onFieldResize,
164
+ mode,
165
+ currentSigner,
166
+ renderContent: renderFieldContent
167
+ },
168
+ field.id
169
+ ))
170
+ ]
171
+ },
172
+ pageIndex
173
+ );
174
+ }) });
175
+ }
176
+ function FieldOverlayItem({
177
+ field,
178
+ isSelected,
179
+ onSelect,
180
+ onMove,
181
+ onResize,
182
+ mode,
183
+ currentSigner,
184
+ renderContent
185
+ }) {
186
+ const overlayRef = useRef(null);
187
+ const dragStartRef = useRef(null);
188
+ const resizeStartRef = useRef(null);
189
+ const color = getSignerColor(field.assignee);
190
+ const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
191
+ const isPreFilled = !isEditable && !!field.value;
192
+ const handleMouseDown = useCallback((e) => {
193
+ if (mode !== "designer" || !onMove) return;
194
+ e.preventDefault();
195
+ e.stopPropagation();
196
+ onSelect();
197
+ const pageEl = overlayRef.current?.closest(".pdf-page");
198
+ if (!pageEl) return;
199
+ dragStartRef.current = {
200
+ startX: e.clientX,
201
+ startY: e.clientY,
202
+ fieldX: field.x,
203
+ fieldY: field.y
204
+ };
205
+ const handleMouseMove = (e2) => {
206
+ if (!dragStartRef.current) return;
207
+ const rect = pageEl.getBoundingClientRect();
208
+ const dx = (e2.clientX - dragStartRef.current.startX) / rect.width * 100;
209
+ const dy = (e2.clientY - dragStartRef.current.startY) / rect.height * 100;
210
+ const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
211
+ const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
212
+ onMove(field.id, field.page, newX, newY);
213
+ };
214
+ const handleMouseUp = () => {
215
+ dragStartRef.current = null;
216
+ window.removeEventListener("mousemove", handleMouseMove);
217
+ window.removeEventListener("mouseup", handleMouseUp);
218
+ };
219
+ window.addEventListener("mousemove", handleMouseMove);
220
+ window.addEventListener("mouseup", handleMouseUp);
221
+ }, [field, mode, onMove, onSelect]);
222
+ const handleResizeMouseDown = useCallback((e) => {
223
+ if (mode !== "designer" || !onResize) return;
224
+ e.preventDefault();
225
+ e.stopPropagation();
226
+ const pageEl = overlayRef.current?.closest(".pdf-page");
227
+ if (!pageEl) return;
228
+ resizeStartRef.current = {
229
+ startX: e.clientX,
230
+ startY: e.clientY,
231
+ fieldW: field.width,
232
+ fieldH: field.height
233
+ };
234
+ const handleMouseMove = (e2) => {
235
+ if (!resizeStartRef.current) return;
236
+ const rect = pageEl.getBoundingClientRect();
237
+ const dw = (e2.clientX - resizeStartRef.current.startX) / rect.width * 100;
238
+ const dh = (e2.clientY - resizeStartRef.current.startY) / rect.height * 100;
239
+ const newW = Math.max(2, Math.min(100 - field.x, resizeStartRef.current.fieldW + dw));
240
+ const newH = Math.max(1.5, Math.min(100 - field.y, resizeStartRef.current.fieldH + dh));
241
+ onResize(field.id, newW, newH);
242
+ };
243
+ const handleMouseUp = () => {
244
+ resizeStartRef.current = null;
245
+ window.removeEventListener("mousemove", handleMouseMove);
246
+ window.removeEventListener("mouseup", handleMouseUp);
247
+ };
248
+ window.addEventListener("mousemove", handleMouseMove);
249
+ window.addEventListener("mouseup", handleMouseUp);
250
+ }, [field, mode, onResize]);
251
+ return /* @__PURE__ */ jsxs(
252
+ "div",
253
+ {
254
+ ref: overlayRef,
255
+ className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""}`,
256
+ style: {
257
+ left: `${field.x}%`,
258
+ top: `${field.y}%`,
259
+ width: `${field.width}%`,
260
+ height: `${field.height}%`,
261
+ borderColor: color,
262
+ backgroundColor: isSelected ? `${color}22` : `${color}11`,
263
+ cursor: mode === "designer" ? "move" : "default"
264
+ },
265
+ onClick: (e) => {
266
+ e.stopPropagation();
267
+ onSelect();
268
+ },
269
+ onMouseDown: handleMouseDown,
270
+ children: [
271
+ mode === "designer" && /* @__PURE__ */ jsx("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
272
+ renderContent ? renderContent(field) : /* @__PURE__ */ jsx("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
273
+ mode === "designer" && isSelected && /* @__PURE__ */ jsx(
274
+ "div",
275
+ {
276
+ className: "field-resize-handle",
277
+ onMouseDown: handleResizeMouseDown
278
+ }
279
+ )
280
+ ]
281
+ }
282
+ );
283
+ }
284
+
285
+ // src/components/pdf-builder/FieldPropertyPanel.tsx
286
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
287
+ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
288
+ const color = getSignerColor(field.assignee);
289
+ return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
290
+ /* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
291
+ /* @__PURE__ */ jsx2("h3", { style: { color }, children: field.label }),
292
+ /* @__PURE__ */ jsx2("button", { onClick: () => onDelete(field.id), className: "panel-delete-btn", children: "Delete" })
293
+ ] }),
294
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
295
+ /* @__PURE__ */ jsx2("label", { children: "Label" }),
296
+ /* @__PURE__ */ jsx2(
297
+ "input",
298
+ {
299
+ type: "text",
300
+ value: field.label,
301
+ onChange: (e) => onUpdate(field.id, { label: e.target.value })
302
+ }
303
+ )
304
+ ] }),
305
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
306
+ /* @__PURE__ */ jsx2("label", { children: "Field Type" }),
307
+ /* @__PURE__ */ jsxs2(
308
+ "select",
309
+ {
310
+ value: field.type,
311
+ onChange: (e) => onUpdate(field.id, { type: e.target.value }),
312
+ children: [
313
+ /* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
314
+ /* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
315
+ /* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
316
+ /* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
317
+ /* @__PURE__ */ jsx2("option", { value: "initials", children: "Initials" })
318
+ ]
319
+ }
320
+ )
321
+ ] }),
322
+ field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
323
+ /* @__PURE__ */ jsx2("label", { children: "Text Type" }),
324
+ /* @__PURE__ */ jsxs2(
325
+ "select",
326
+ {
327
+ value: field.textSubtype || "freeform",
328
+ onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }),
329
+ children: [
330
+ /* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
331
+ /* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
332
+ /* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
333
+ /* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
334
+ /* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
335
+ ]
336
+ }
337
+ )
338
+ ] }),
339
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
340
+ /* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
341
+ /* @__PURE__ */ jsx2(
342
+ "select",
343
+ {
344
+ value: field.assignee,
345
+ onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
346
+ children: signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
347
+ }
348
+ )
349
+ ] }),
350
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
351
+ /* @__PURE__ */ jsx2("label", { children: "Placeholder" }),
352
+ /* @__PURE__ */ jsx2(
353
+ "input",
354
+ {
355
+ type: "text",
356
+ value: field.placeholder,
357
+ onChange: (e) => onUpdate(field.id, { placeholder: e.target.value })
358
+ }
359
+ )
360
+ ] }),
361
+ /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
362
+ /* @__PURE__ */ jsx2(
363
+ "input",
364
+ {
365
+ type: "checkbox",
366
+ checked: field.required,
367
+ onChange: (e) => onUpdate(field.id, { required: e.target.checked })
368
+ }
369
+ ),
370
+ "Required"
371
+ ] }) }),
372
+ field.type !== "checkbox" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
373
+ /* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
374
+ /* @__PURE__ */ jsx2(
375
+ "input",
376
+ {
377
+ type: "number",
378
+ min: "6",
379
+ max: "72",
380
+ value: field.fontSize,
381
+ onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) })
382
+ }
383
+ )
384
+ ] }),
385
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field-group", children: [
386
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
387
+ /* @__PURE__ */ jsx2("label", { children: "Width (%)" }),
388
+ /* @__PURE__ */ jsx2(
389
+ "input",
390
+ {
391
+ type: "number",
392
+ min: "1",
393
+ max: "100",
394
+ step: "0.5",
395
+ value: field.width,
396
+ onChange: (e) => onUpdate(field.id, { width: Number(e.target.value) })
397
+ }
398
+ )
399
+ ] }),
400
+ /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
401
+ /* @__PURE__ */ jsx2("label", { children: "Height (%)" }),
402
+ /* @__PURE__ */ jsx2(
403
+ "input",
404
+ {
405
+ type: "number",
406
+ min: "1",
407
+ max: "100",
408
+ step: "0.5",
409
+ value: field.height,
410
+ onChange: (e) => onUpdate(field.id, { height: Number(e.target.value) })
411
+ }
412
+ )
413
+ ] })
414
+ ] })
415
+ ] });
416
+ }
417
+
418
+ // src/components/pdf-builder/SignerRoleSelector.tsx
419
+ import { useState } from "react";
420
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
421
+ function SignerRoleSelector({
422
+ roles,
423
+ activeRole,
424
+ onSetActiveRole,
425
+ onAddRole,
426
+ onRemoveRole
427
+ }) {
428
+ const [isAdding, setIsAdding] = useState(false);
429
+ const [newRoleName, setNewRoleName] = useState("");
430
+ const handleAdd = () => {
431
+ if (newRoleName.trim()) {
432
+ onAddRole(newRoleName.trim());
433
+ setNewRoleName("");
434
+ setIsAdding(false);
435
+ }
436
+ };
437
+ return /* @__PURE__ */ jsxs3("div", { className: "signer-role-selector", children: [
438
+ /* @__PURE__ */ jsx3("div", { className: "signer-role-label", children: "Signer Roles" }),
439
+ /* @__PURE__ */ jsxs3("div", { className: "signer-role-list", children: [
440
+ roles.map((role) => /* @__PURE__ */ jsxs3(
441
+ "button",
442
+ {
443
+ className: `signer-role-chip ${role === activeRole ? "active" : ""}`,
444
+ style: {
445
+ borderColor: getSignerColor(role),
446
+ backgroundColor: role === activeRole ? getSignerColor(role) : "transparent",
447
+ color: role === activeRole ? "#fff" : getSignerColor(role)
448
+ },
449
+ onClick: () => onSetActiveRole(role),
450
+ children: [
451
+ role,
452
+ roles.length > 1 && role !== "Sender" && /* @__PURE__ */ jsx3(
453
+ "span",
454
+ {
455
+ className: "signer-role-remove",
456
+ onClick: (e) => {
457
+ e.stopPropagation();
458
+ onRemoveRole(role);
459
+ },
460
+ children: "\xD7"
461
+ }
462
+ )
463
+ ]
464
+ },
465
+ role
466
+ )),
467
+ isAdding ? /* @__PURE__ */ jsxs3("div", { className: "signer-role-add-input", children: [
468
+ /* @__PURE__ */ jsx3(
469
+ "input",
470
+ {
471
+ type: "text",
472
+ value: newRoleName,
473
+ onChange: (e) => setNewRoleName(e.target.value),
474
+ onKeyDown: (e) => e.key === "Enter" && handleAdd(),
475
+ placeholder: "Role name",
476
+ autoFocus: true
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsx3("button", { onClick: handleAdd, children: "Add" }),
480
+ /* @__PURE__ */ jsx3("button", { onClick: () => setIsAdding(false), children: "Cancel" })
481
+ ] }) : /* @__PURE__ */ jsx3(
482
+ "button",
483
+ {
484
+ className: "signer-role-add-btn",
485
+ onClick: () => setIsAdding(true),
486
+ children: "+ Add Role"
487
+ }
488
+ )
489
+ ] })
490
+ ] });
491
+ }
492
+
493
+ // src/components/pdf-builder/SignatureCanvas.tsx
494
+ import { useRef as useRef2, useState as useState2, useCallback as useCallback2, useEffect } from "react";
495
+ import { getStroke } from "perfect-freehand";
496
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
497
+ function getSvgPathFromStroke(stroke) {
498
+ if (!stroke.length) return "";
499
+ const d = stroke.reduce(
500
+ (acc, [x0, y0], i, arr) => {
501
+ const [x1, y1] = arr[(i + 1) % arr.length];
502
+ acc.push(x0, y0, (x0 + x1) / 2, (y0 + y1) / 2);
503
+ return acc;
504
+ },
505
+ ["M", ...stroke[0], "Q"]
506
+ );
507
+ d.push("Z");
508
+ return d.join(" ");
509
+ }
510
+ function SignatureCanvas({
511
+ width = 400,
512
+ height = 150,
513
+ onSign,
514
+ initialValue,
515
+ className
516
+ }) {
517
+ const canvasRef = useRef2(null);
518
+ const [paths, setPaths] = useState2([]);
519
+ const [currentPath, setCurrentPath] = useState2(null);
520
+ const [isEmpty, setIsEmpty] = useState2(!initialValue);
521
+ useEffect(() => {
522
+ if (initialValue && canvasRef.current) {
523
+ const ctx = canvasRef.current.getContext("2d");
524
+ const img = new Image();
525
+ img.onload = () => {
526
+ ctx.clearRect(0, 0, width, height);
527
+ ctx.drawImage(img, 0, 0, width, height);
528
+ };
529
+ img.src = initialValue;
530
+ }
531
+ }, [initialValue, width, height]);
532
+ useEffect(() => {
533
+ const canvas = canvasRef.current;
534
+ if (!canvas) return;
535
+ const ctx = canvas.getContext("2d");
536
+ ctx.clearRect(0, 0, width, height);
537
+ const allPaths = currentPath ? [...paths, currentPath] : paths;
538
+ for (const path of allPaths) {
539
+ const stroke = getStroke(path, {
540
+ size: 3,
541
+ thinning: 0.5,
542
+ smoothing: 0.5,
543
+ streamline: 0.5
544
+ });
545
+ const pathStr = getSvgPathFromStroke(stroke);
546
+ const path2d = new Path2D(pathStr);
547
+ ctx.fillStyle = "#000";
548
+ ctx.fill(path2d);
549
+ }
550
+ }, [paths, currentPath, width, height]);
551
+ const getPoint = useCallback2((e) => {
552
+ const rect = canvasRef.current.getBoundingClientRect();
553
+ return [
554
+ e.clientX - rect.left,
555
+ e.clientY - rect.top,
556
+ e.pressure
557
+ ];
558
+ }, []);
559
+ const handlePointerDown = useCallback2((e) => {
560
+ e.preventDefault();
561
+ e.target.setPointerCapture(e.pointerId);
562
+ setCurrentPath([getPoint(e)]);
563
+ }, [getPoint]);
564
+ const handlePointerMove = useCallback2((e) => {
565
+ if (!currentPath) return;
566
+ e.preventDefault();
567
+ setCurrentPath([...currentPath, getPoint(e)]);
568
+ }, [currentPath, getPoint]);
569
+ const handlePointerUp = useCallback2(() => {
570
+ if (!currentPath) return;
571
+ setPaths((prev) => [...prev, currentPath]);
572
+ setCurrentPath(null);
573
+ setIsEmpty(false);
574
+ requestAnimationFrame(() => {
575
+ if (canvasRef.current) {
576
+ onSign(canvasRef.current.toDataURL("image/png"));
577
+ }
578
+ });
579
+ }, [currentPath, onSign]);
580
+ const handleClear = useCallback2(() => {
581
+ setPaths([]);
582
+ setCurrentPath(null);
583
+ setIsEmpty(true);
584
+ const canvas = canvasRef.current;
585
+ if (canvas) {
586
+ const ctx = canvas.getContext("2d");
587
+ ctx.clearRect(0, 0, width, height);
588
+ onSign("");
589
+ }
590
+ }, [width, height, onSign]);
591
+ return /* @__PURE__ */ jsxs4("div", { className: `signature-canvas-wrapper ${className || ""}`, children: [
592
+ /* @__PURE__ */ jsx4(
593
+ "canvas",
594
+ {
595
+ ref: canvasRef,
596
+ width,
597
+ height,
598
+ className: "signature-canvas",
599
+ onPointerDown: handlePointerDown,
600
+ onPointerMove: handlePointerMove,
601
+ onPointerUp: handlePointerUp,
602
+ style: { touchAction: "none" }
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsxs4("div", { className: "signature-canvas-actions", children: [
606
+ !isEmpty && /* @__PURE__ */ jsx4("button", { type: "button", onClick: handleClear, className: "signature-clear-btn", children: "Clear" }),
607
+ isEmpty && /* @__PURE__ */ jsx4("span", { className: "signature-hint", children: "Draw your signature above" })
608
+ ] })
609
+ ] });
610
+ }
611
+
612
+ // src/components/pdf-builder/DesignerView.tsx
613
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
614
+ function DesignerView() {
615
+ const [pages, setPages] = useState3([]);
616
+ const [fields, setFields] = useState3([]);
617
+ const [selectedFieldId, setSelectedFieldId] = useState3(null);
618
+ const [signerRoles, setSignerRoles] = useState3([...DEFAULT_SIGNER_ROLES]);
619
+ const [activeRole, setActiveRole] = useState3("Sender");
620
+ const [activeFieldType, setActiveFieldType] = useState3("text");
621
+ const [loading, setLoading] = useState3(false);
622
+ const [pdfSource, setPdfSource] = useState3(null);
623
+ useEffect2(() => {
624
+ const params = new URLSearchParams(window.location.search);
625
+ const pdfUrl = params.get("pdf");
626
+ if (pdfUrl) {
627
+ loadPdf(pdfUrl);
628
+ }
629
+ const handleMessage = (e) => {
630
+ if (e.data?.type === "load-pdf" && e.data.url) {
631
+ loadPdf(e.data.url);
632
+ }
633
+ if (e.data?.type === "load-template") {
634
+ if (e.data.fields) setFields(e.data.fields);
635
+ if (e.data.signerRoles) setSignerRoles(e.data.signerRoles);
636
+ if (e.data.pdfUrl) loadPdf(e.data.pdfUrl);
637
+ }
638
+ };
639
+ window.addEventListener("message", handleMessage);
640
+ return () => window.removeEventListener("message", handleMessage);
641
+ }, []);
642
+ const loadPdf = useCallback3(async (source) => {
643
+ setLoading(true);
644
+ try {
645
+ const rendered = await renderPdfPages(source);
646
+ setPages(rendered);
647
+ setPdfSource(source);
648
+ } catch (err) {
649
+ console.error("Failed to load PDF:", err);
650
+ } finally {
651
+ setLoading(false);
652
+ }
653
+ }, []);
654
+ const handleFileUpload = useCallback3((e) => {
655
+ const file = e.target.files?.[0];
656
+ if (!file) return;
657
+ const reader = new FileReader();
658
+ reader.onload = () => {
659
+ if (reader.result) loadPdf(reader.result);
660
+ };
661
+ reader.readAsArrayBuffer(file);
662
+ }, [loadPdf]);
663
+ const handlePageClick = useCallback3((page, x, y) => {
664
+ const field = createField(activeFieldType, activeRole, page, x, y);
665
+ setFields((prev) => [...prev, field]);
666
+ setSelectedFieldId(field.id);
667
+ }, [activeFieldType, activeRole]);
668
+ const handleFieldMove = useCallback3((id, page, x, y) => {
669
+ setFields((prev) => prev.map((f) => f.id === id ? { ...f, page, x, y } : f));
670
+ }, []);
671
+ const handleFieldResize = useCallback3((id, width, height) => {
672
+ setFields((prev) => prev.map((f) => f.id === id ? { ...f, width, height } : f));
673
+ }, []);
674
+ const handleFieldUpdate = useCallback3((id, updates) => {
675
+ setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
676
+ }, []);
677
+ const handleFieldDelete = useCallback3((id) => {
678
+ setFields((prev) => prev.filter((f) => f.id !== id));
679
+ if (selectedFieldId === id) setSelectedFieldId(null);
680
+ }, [selectedFieldId]);
681
+ const handleAddRole = useCallback3((role) => {
682
+ if (!signerRoles.includes(role)) {
683
+ setSignerRoles((prev) => [...prev, role]);
684
+ }
685
+ }, [signerRoles]);
686
+ const handleRemoveRole = useCallback3((role) => {
687
+ setSignerRoles((prev) => prev.filter((r) => r !== role));
688
+ setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
689
+ if (activeRole === role) setActiveRole(signerRoles[0]);
690
+ }, [signerRoles, activeRole]);
691
+ const handleExport = useCallback3(() => {
692
+ const template = {
693
+ fields,
694
+ signerRoles,
695
+ pdfUrl: typeof pdfSource === "string" ? pdfSource : ""
696
+ };
697
+ const json = JSON.stringify(template, null, 2);
698
+ const blob = new Blob([json], { type: "application/json" });
699
+ const url = URL.createObjectURL(blob);
700
+ const a = document.createElement("a");
701
+ a.href = url;
702
+ a.download = "template.json";
703
+ a.click();
704
+ URL.revokeObjectURL(url);
705
+ window.parent?.postMessage({ type: "template-saved", template }, "*");
706
+ }, [fields, signerRoles, pdfSource]);
707
+ const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
708
+ const renderFieldContent = useCallback3((field) => {
709
+ if (field.type === "signature" || field.type === "initials") {
710
+ if (field.value) {
711
+ return /* @__PURE__ */ jsx5("img", { src: field.value, alt: field.label, className: "field-signature-preview" });
712
+ }
713
+ }
714
+ return /* @__PURE__ */ jsx5("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder });
715
+ }, []);
716
+ return /* @__PURE__ */ jsxs5("div", { className: "designer-layout", children: [
717
+ /* @__PURE__ */ jsxs5("div", { className: "designer-toolbar", children: [
718
+ /* @__PURE__ */ jsxs5("div", { className: "toolbar-left", children: [
719
+ !pages.length && /* @__PURE__ */ jsxs5("label", { className: "upload-btn", children: [
720
+ "Upload PDF",
721
+ /* @__PURE__ */ jsx5("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
722
+ ] }),
723
+ pages.length > 0 && /* @__PURE__ */ jsxs5(Fragment, { children: [
724
+ /* @__PURE__ */ jsxs5("label", { className: "upload-btn upload-btn-small", children: [
725
+ "Change PDF",
726
+ /* @__PURE__ */ jsx5("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
727
+ ] }),
728
+ /* @__PURE__ */ jsx5("div", { className: "field-type-selector", children: ["text", "signature", "signed-date", "checkbox", "initials"].map((type) => /* @__PURE__ */ jsx5(
729
+ "button",
730
+ {
731
+ className: `field-type-btn ${activeFieldType === type ? "active" : ""}`,
732
+ onClick: () => setActiveFieldType(type),
733
+ children: type === "text" ? "Text" : type === "signature" ? "Signature" : type === "signed-date" ? "Date" : type === "checkbox" ? "Checkbox" : "Initials"
734
+ },
735
+ type
736
+ )) })
737
+ ] })
738
+ ] }),
739
+ /* @__PURE__ */ jsx5("div", { className: "toolbar-right", children: pages.length > 0 && /* @__PURE__ */ jsx5("button", { onClick: handleExport, className: "export-btn", children: "Export Template" }) })
740
+ ] }),
741
+ pages.length > 0 && /* @__PURE__ */ jsx5(
742
+ SignerRoleSelector,
743
+ {
744
+ roles: signerRoles,
745
+ activeRole,
746
+ onSetActiveRole: setActiveRole,
747
+ onAddRole: handleAddRole,
748
+ onRemoveRole: handleRemoveRole
749
+ }
750
+ ),
751
+ /* @__PURE__ */ jsxs5("div", { className: "designer-content", children: [
752
+ /* @__PURE__ */ jsxs5("div", { className: "designer-pdf-area", children: [
753
+ loading && /* @__PURE__ */ jsx5("div", { className: "loading-indicator", children: "Loading PDF..." }),
754
+ !pages.length && !loading && /* @__PURE__ */ jsxs5("div", { className: "empty-state", children: [
755
+ /* @__PURE__ */ jsx5("h2", { children: "Upload a PDF to get started" }),
756
+ /* @__PURE__ */ jsx5("p", { children: "Upload a template PDF, then click on the page to place form fields." }),
757
+ /* @__PURE__ */ jsxs5("label", { className: "upload-btn upload-btn-large", children: [
758
+ "Choose PDF File",
759
+ /* @__PURE__ */ jsx5("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
760
+ ] })
761
+ ] }),
762
+ pages.length > 0 && /* @__PURE__ */ jsx5(
763
+ PdfViewer,
764
+ {
765
+ pages,
766
+ fields,
767
+ selectedFieldId,
768
+ onSelectField: setSelectedFieldId,
769
+ onFieldMove: handleFieldMove,
770
+ onFieldResize: handleFieldResize,
771
+ onPageClick: handlePageClick,
772
+ mode: "designer",
773
+ renderFieldContent
774
+ }
775
+ )
776
+ ] }),
777
+ /* @__PURE__ */ jsxs5("div", { className: "designer-panel", children: [
778
+ selectedField ? /* @__PURE__ */ jsxs5(Fragment, { children: [
779
+ /* @__PURE__ */ jsx5(
780
+ FieldPropertyPanel,
781
+ {
782
+ field: selectedField,
783
+ signerRoles,
784
+ onUpdate: handleFieldUpdate,
785
+ onDelete: handleFieldDelete
786
+ }
787
+ ),
788
+ selectedField.assignee === activeRole && /* @__PURE__ */ jsxs5("div", { className: "prefill-section", children: [
789
+ /* @__PURE__ */ jsx5("h4", { children: "Pre-fill Value" }),
790
+ selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ jsx5(
791
+ SignatureCanvas,
792
+ {
793
+ width: 300,
794
+ height: selectedField.type === "initials" ? 100 : 150,
795
+ onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
796
+ initialValue: selectedField.value
797
+ }
798
+ ) : selectedField.type === "checkbox" ? /* @__PURE__ */ jsxs5("label", { className: "panel-checkbox-label", children: [
799
+ /* @__PURE__ */ jsx5(
800
+ "input",
801
+ {
802
+ type: "checkbox",
803
+ checked: selectedField.value === "true",
804
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
805
+ }
806
+ ),
807
+ "Checked"
808
+ ] }) : /* @__PURE__ */ jsx5(
809
+ "input",
810
+ {
811
+ type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
812
+ value: selectedField.value,
813
+ onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
814
+ placeholder: `Pre-fill ${selectedField.label}`,
815
+ className: "prefill-input"
816
+ }
817
+ )
818
+ ] })
819
+ ] }) : /* @__PURE__ */ jsx5("div", { className: "panel-empty", children: pages.length > 0 ? "Click on the PDF to place a field, or select an existing field to edit its properties." : "Upload a PDF to begin designing your template." }),
820
+ fields.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "field-list", children: [
821
+ /* @__PURE__ */ jsxs5("h4", { children: [
822
+ "All Fields (",
823
+ fields.length,
824
+ ")"
825
+ ] }),
826
+ fields.map((f, i) => /* @__PURE__ */ jsxs5(
827
+ "div",
828
+ {
829
+ className: `field-list-item ${f.id === selectedFieldId ? "active" : ""}`,
830
+ onClick: () => setSelectedFieldId(f.id),
831
+ children: [
832
+ /* @__PURE__ */ jsx5(
833
+ "span",
834
+ {
835
+ className: "field-list-dot",
836
+ style: { backgroundColor: getSignerColor(f.assignee) }
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx5("span", { className: "field-list-name", children: f.label }),
840
+ /* @__PURE__ */ jsxs5("span", { className: "field-list-page", children: [
841
+ "p",
842
+ f.page + 1
843
+ ] }),
844
+ f.required && /* @__PURE__ */ jsx5("span", { className: "field-list-required", children: "*" })
845
+ ]
846
+ },
847
+ f.id
848
+ ))
849
+ ] })
850
+ ] })
851
+ ] }),
852
+ /* @__PURE__ */ jsxs5("div", { className: "powered-by", children: [
853
+ "Powered by ",
854
+ /* @__PURE__ */ jsx5("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
855
+ ] })
856
+ ] });
857
+ }
858
+
859
+ // src/components/pdf-builder/SignerView.tsx
860
+ import { useState as useState4, useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3 } from "react";
861
+
862
+ // src/utils/pdfFiller.ts
863
+ import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
864
+ async function generateFilledPdf(pdfSource, fields) {
865
+ let pdfBytes;
866
+ if (typeof pdfSource === "string") {
867
+ const res = await fetch(pdfSource);
868
+ pdfBytes = await res.arrayBuffer();
869
+ } else {
870
+ pdfBytes = pdfSource;
871
+ }
872
+ const pdfDoc = await PDFDocument.load(pdfBytes);
873
+ const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
874
+ const pages = pdfDoc.getPages();
875
+ for (const field of fields) {
876
+ if (!field.value) continue;
877
+ const page = pages[field.page];
878
+ if (!page) continue;
879
+ const pageWidth = page.getWidth();
880
+ const pageHeight = page.getHeight();
881
+ const x = field.x / 100 * pageWidth;
882
+ const y = pageHeight - field.y / 100 * pageHeight - field.height / 100 * pageHeight;
883
+ const w = field.width / 100 * pageWidth;
884
+ const h = field.height / 100 * pageHeight;
885
+ if (field.type === "checkbox") {
886
+ if (field.value === "true") {
887
+ const inset = Math.min(w, h) * 0.2;
888
+ const lineWidth = Math.min(w, h) * 0.08;
889
+ page.drawLine({
890
+ start: { x: x + inset, y: y + inset },
891
+ end: { x: x + w - inset, y: y + h - inset },
892
+ thickness: lineWidth,
893
+ color: rgb(0, 0, 0)
894
+ });
895
+ page.drawLine({
896
+ start: { x: x + w - inset, y: y + inset },
897
+ end: { x: x + inset, y: y + h - inset },
898
+ thickness: lineWidth,
899
+ color: rgb(0, 0, 0)
900
+ });
901
+ }
902
+ } else if (field.type === "signature" || field.type === "initials") {
903
+ if (field.value.startsWith("data:image/png")) {
904
+ const pngBytes = Uint8Array.from(
905
+ atob(field.value.split(",")[1]),
906
+ (c) => c.charCodeAt(0)
907
+ );
908
+ const pngImage = await pdfDoc.embedPng(pngBytes);
909
+ page.drawImage(pngImage, {
910
+ x,
911
+ y,
912
+ width: w,
913
+ height: h
914
+ });
915
+ }
916
+ } else {
917
+ const fontSize = Math.min(field.fontSize, h * 0.7);
918
+ page.drawText(field.value, {
919
+ x: x + 2,
920
+ y: y + h * 0.3,
921
+ size: fontSize,
922
+ font,
923
+ color: rgb(0, 0, 0),
924
+ maxWidth: w - 4
925
+ });
926
+ }
927
+ }
928
+ return pdfDoc.save();
929
+ }
930
+ function downloadPdf(bytes, filename) {
931
+ const blob = new Blob([bytes.slice().buffer], { type: "application/pdf" });
932
+ const url = URL.createObjectURL(blob);
933
+ const a = document.createElement("a");
934
+ a.href = url;
935
+ a.download = filename;
936
+ a.click();
937
+ URL.revokeObjectURL(url);
938
+ }
939
+ async function postPdfToCallback(bytes, callbackUrl, filename) {
940
+ const blob = new Blob([bytes.slice().buffer], { type: "application/pdf" });
941
+ const formData = new FormData();
942
+ formData.append("file", blob, filename);
943
+ await fetch(callbackUrl, {
944
+ method: "POST",
945
+ body: formData
946
+ });
947
+ }
948
+
949
+ // src/components/pdf-builder/FieldNavigator.tsx
950
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
951
+ function FieldNavigator({
952
+ fields,
953
+ currentFieldId,
954
+ onNavigate,
955
+ allRequiredFilled,
956
+ onSubmit
957
+ }) {
958
+ const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
959
+ const hasPrev = currentIndex > 0;
960
+ const hasNext = currentIndex < fields.length - 1;
961
+ const handlePrev = () => {
962
+ if (hasPrev) {
963
+ onNavigate(fields[currentIndex - 1].id);
964
+ }
965
+ };
966
+ const handleNext = () => {
967
+ if (hasNext) {
968
+ onNavigate(fields[currentIndex + 1].id);
969
+ }
970
+ };
971
+ const filledCount = fields.filter((f) => {
972
+ if (!f.required) return true;
973
+ if (f.type === "checkbox") return true;
974
+ return !!f.value;
975
+ }).length;
976
+ return /* @__PURE__ */ jsxs6("div", { className: "field-navigator", children: [
977
+ /* @__PURE__ */ jsxs6("div", { className: "field-navigator-progress", children: [
978
+ filledCount,
979
+ " of ",
980
+ fields.length,
981
+ " fields completed"
982
+ ] }),
983
+ /* @__PURE__ */ jsxs6("div", { className: "field-navigator-controls", children: [
984
+ /* @__PURE__ */ jsx6(
985
+ "button",
986
+ {
987
+ onClick: handlePrev,
988
+ disabled: !hasPrev,
989
+ className: "nav-btn",
990
+ children: "Prev"
991
+ }
992
+ ),
993
+ /* @__PURE__ */ jsx6("span", { className: "field-navigator-position", children: currentIndex >= 0 ? `${currentIndex + 1} / ${fields.length}` : "-" }),
994
+ /* @__PURE__ */ jsx6(
995
+ "button",
996
+ {
997
+ onClick: handleNext,
998
+ disabled: !hasNext,
999
+ className: "nav-btn",
1000
+ children: "Next"
1001
+ }
1002
+ )
1003
+ ] }),
1004
+ /* @__PURE__ */ jsx6(
1005
+ "button",
1006
+ {
1007
+ onClick: onSubmit,
1008
+ disabled: !allRequiredFilled,
1009
+ className: "submit-btn",
1010
+ children: "Complete & Download"
1011
+ }
1012
+ )
1013
+ ] });
1014
+ }
1015
+
1016
+ // src/components/pdf-builder/SignerView.tsx
1017
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1018
+ function SignerView({
1019
+ initialPdfUrl,
1020
+ initialTemplate,
1021
+ initialSigner,
1022
+ callbackUrl: initialCallbackUrl,
1023
+ onComplete
1024
+ } = {}) {
1025
+ const [pages, setPages] = useState4([]);
1026
+ const [fields, setFields] = useState4([]);
1027
+ const [selectedFieldId, setSelectedFieldId] = useState4(null);
1028
+ const [signer, setSigner] = useState4(initialSigner || "Signer 1");
1029
+ const [loading, setLoading] = useState4(false);
1030
+ const [submitting, setSubmitting] = useState4(false);
1031
+ const [pdfSource, setPdfSource] = useState4(null);
1032
+ const [callbackUrl, setCallbackUrl] = useState4(initialCallbackUrl || "");
1033
+ const containerRef = useRef3(null);
1034
+ useEffect3(() => {
1035
+ if (initialTemplate) {
1036
+ setFields(initialTemplate.fields);
1037
+ if (initialPdfUrl) loadPdf(initialPdfUrl);
1038
+ return;
1039
+ }
1040
+ const params = new URLSearchParams(window.location.search);
1041
+ const pdfUrl = params.get("pdf");
1042
+ const fieldsUrl = params.get("fields");
1043
+ const signerParam = params.get("signer");
1044
+ const cbUrl = params.get("callbackUrl");
1045
+ if (signerParam) setSigner(signerParam);
1046
+ if (cbUrl) setCallbackUrl(cbUrl);
1047
+ if (pdfUrl) loadPdf(pdfUrl);
1048
+ if (fieldsUrl) {
1049
+ fetch(fieldsUrl).then((r) => r.json()).then((template) => setFields(template.fields)).catch((err) => console.error("Failed to load fields:", err));
1050
+ }
1051
+ const handleMessage = (e) => {
1052
+ if (e.data?.type === "load-signing") {
1053
+ if (e.data.fields) setFields(e.data.fields);
1054
+ if (e.data.pdfUrl) loadPdf(e.data.pdfUrl);
1055
+ if (e.data.signer) setSigner(e.data.signer);
1056
+ if (e.data.callbackUrl) setCallbackUrl(e.data.callbackUrl);
1057
+ }
1058
+ };
1059
+ window.addEventListener("message", handleMessage);
1060
+ return () => window.removeEventListener("message", handleMessage);
1061
+ }, [initialTemplate, initialPdfUrl]);
1062
+ const loadPdf = useCallback4(async (source) => {
1063
+ setLoading(true);
1064
+ try {
1065
+ const rendered = await renderPdfPages(source);
1066
+ setPages(rendered);
1067
+ setPdfSource(source);
1068
+ } catch (err) {
1069
+ console.error("Failed to load PDF:", err);
1070
+ } finally {
1071
+ setLoading(false);
1072
+ }
1073
+ }, []);
1074
+ const editableFields = fields.filter((f) => f.assignee === signer);
1075
+ const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
1076
+ const isFieldEditable = selectedField ? selectedField.assignee === signer : false;
1077
+ const handleFieldUpdate = useCallback4((id, value) => {
1078
+ setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
1079
+ }, []);
1080
+ const handleNavigate = useCallback4((fieldId) => {
1081
+ setSelectedFieldId(fieldId);
1082
+ const field = fields.find((f) => f.id === fieldId);
1083
+ if (field && containerRef.current) {
1084
+ const pageEl = containerRef.current.querySelector(`[data-page="${field.page}"]`);
1085
+ if (pageEl) {
1086
+ pageEl.scrollIntoView({ behavior: "smooth", block: "center" });
1087
+ }
1088
+ }
1089
+ }, [fields]);
1090
+ const allRequiredFilled = editableFields.every((f) => {
1091
+ if (!f.required) return true;
1092
+ if (f.type === "checkbox") return true;
1093
+ return !!f.value;
1094
+ });
1095
+ const handleSubmit = useCallback4(async () => {
1096
+ if (!pdfSource || !allRequiredFilled) return;
1097
+ setSubmitting(true);
1098
+ try {
1099
+ const pdfBytes = await generateFilledPdf(pdfSource, fields);
1100
+ const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
1101
+ downloadPdf(pdfBytes, "signed-document.pdf");
1102
+ if (callbackUrl) {
1103
+ await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
1104
+ }
1105
+ if (onComplete) {
1106
+ onComplete(blob);
1107
+ }
1108
+ window.parent?.postMessage({ type: "signing-complete" }, "*");
1109
+ } catch (err) {
1110
+ console.error("Failed to generate PDF:", err);
1111
+ } finally {
1112
+ setSubmitting(false);
1113
+ }
1114
+ }, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete]);
1115
+ const renderFieldContent = useCallback4((field) => {
1116
+ const editable = field.assignee === signer;
1117
+ if (!editable) {
1118
+ if (field.type === "signature" || field.type === "initials") {
1119
+ return field.value ? /* @__PURE__ */ jsx7("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) : /* @__PURE__ */ jsx7("div", { className: "field-overlay-placeholder readonly", children: field.placeholder });
1120
+ }
1121
+ if (field.type === "checkbox") {
1122
+ return /* @__PURE__ */ jsx7("div", { className: "field-checkbox-display readonly", children: field.value === "true" ? "\u2713" : "" });
1123
+ }
1124
+ return /* @__PURE__ */ jsx7("div", { className: "field-overlay-placeholder readonly", children: field.value || field.placeholder });
1125
+ }
1126
+ if (field.type === "signature" || field.type === "initials") {
1127
+ if (field.value) {
1128
+ return /* @__PURE__ */ jsx7("div", { className: "field-signature-filled", onClick: () => handleFieldUpdate(field.id, ""), children: /* @__PURE__ */ jsx7("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) });
1129
+ }
1130
+ return /* @__PURE__ */ jsx7("div", { className: "field-overlay-placeholder editable", children: field.placeholder });
1131
+ }
1132
+ if (field.type === "checkbox") {
1133
+ return /* @__PURE__ */ jsx7(
1134
+ "div",
1135
+ {
1136
+ className: "field-checkbox-display editable",
1137
+ onClick: (e) => {
1138
+ e.stopPropagation();
1139
+ handleFieldUpdate(field.id, field.value === "true" ? "" : "true");
1140
+ },
1141
+ children: field.value === "true" ? "\u2713" : ""
1142
+ }
1143
+ );
1144
+ }
1145
+ if (field.type === "signed-date") {
1146
+ return /* @__PURE__ */ jsx7("div", { className: "field-overlay-value", children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
1147
+ }
1148
+ return /* @__PURE__ */ jsx7(
1149
+ "input",
1150
+ {
1151
+ type: field.textSubtype === "email" ? "email" : field.textSubtype === "number" ? "number" : field.textSubtype === "phone" ? "tel" : field.textSubtype === "date" ? "date" : "text",
1152
+ className: "field-inline-input",
1153
+ value: field.value,
1154
+ placeholder: field.placeholder,
1155
+ onChange: (e) => handleFieldUpdate(field.id, e.target.value),
1156
+ onClick: (e) => e.stopPropagation(),
1157
+ style: { fontSize: `${field.fontSize * 0.6}px` }
1158
+ }
1159
+ );
1160
+ }, [signer, handleFieldUpdate]);
1161
+ useEffect3(() => {
1162
+ const sigFields = fields.filter((f) => f.assignee === signer && f.type === "signature" && f.value);
1163
+ if (sigFields.length > 0) {
1164
+ const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString();
1165
+ setFields((prev) => prev.map((f) => {
1166
+ if (f.assignee === signer && f.type === "signed-date" && !f.value) {
1167
+ return { ...f, value: dateStr };
1168
+ }
1169
+ return f;
1170
+ }));
1171
+ }
1172
+ }, [fields.filter((f) => f.type === "signature" && f.value).length]);
1173
+ return /* @__PURE__ */ jsxs7("div", { className: "signer-layout", ref: containerRef, children: [
1174
+ loading && /* @__PURE__ */ jsx7("div", { className: "loading-indicator", children: "Loading document..." }),
1175
+ /* @__PURE__ */ jsxs7("div", { className: "signer-content", children: [
1176
+ /* @__PURE__ */ jsx7("div", { className: "signer-pdf-area", children: pages.length > 0 && /* @__PURE__ */ jsx7(
1177
+ PdfViewer,
1178
+ {
1179
+ pages,
1180
+ fields,
1181
+ selectedFieldId,
1182
+ onSelectField: setSelectedFieldId,
1183
+ mode: "signer",
1184
+ currentSigner: signer,
1185
+ renderFieldContent
1186
+ }
1187
+ ) }),
1188
+ /* @__PURE__ */ jsxs7("div", { className: "signer-panel", children: [
1189
+ selectedField && isFieldEditable && /* @__PURE__ */ jsxs7("div", { className: "signer-field-input", children: [
1190
+ /* @__PURE__ */ jsx7("h3", { children: selectedField.label }),
1191
+ selectedField.required && /* @__PURE__ */ jsx7("span", { className: "required-badge", children: "Required" }),
1192
+ (selectedField.type === "signature" || selectedField.type === "initials") && /* @__PURE__ */ jsx7(
1193
+ SignatureCanvas,
1194
+ {
1195
+ width: 280,
1196
+ height: selectedField.type === "initials" ? 80 : 120,
1197
+ onSign: (dataUrl) => handleFieldUpdate(selectedField.id, dataUrl),
1198
+ initialValue: selectedField.value
1199
+ }
1200
+ ),
1201
+ selectedField.type === "text" && /* @__PURE__ */ jsx7(
1202
+ "input",
1203
+ {
1204
+ type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
1205
+ value: selectedField.value,
1206
+ onChange: (e) => handleFieldUpdate(selectedField.id, e.target.value),
1207
+ placeholder: selectedField.placeholder,
1208
+ className: "signer-text-input"
1209
+ }
1210
+ ),
1211
+ selectedField.type === "checkbox" && /* @__PURE__ */ jsxs7("label", { className: "signer-checkbox-label", children: [
1212
+ /* @__PURE__ */ jsx7(
1213
+ "input",
1214
+ {
1215
+ type: "checkbox",
1216
+ checked: selectedField.value === "true",
1217
+ onChange: (e) => handleFieldUpdate(selectedField.id, e.target.checked ? "true" : "")
1218
+ }
1219
+ ),
1220
+ selectedField.placeholder || "Check this box"
1221
+ ] }),
1222
+ selectedField.type === "signed-date" && /* @__PURE__ */ jsx7("div", { className: "signer-date-display", children: selectedField.value || "Will auto-fill when you sign" })
1223
+ ] }),
1224
+ selectedField && !isFieldEditable && /* @__PURE__ */ jsxs7("div", { className: "signer-field-readonly", children: [
1225
+ /* @__PURE__ */ jsx7("h3", { children: selectedField.label }),
1226
+ /* @__PURE__ */ jsx7("p", { children: "This field belongs to another signer." })
1227
+ ] }),
1228
+ !selectedField && editableFields.length > 0 && /* @__PURE__ */ jsx7("div", { className: "panel-empty", children: "Select a field to fill it in, or use the navigation below." }),
1229
+ /* @__PURE__ */ jsx7(
1230
+ FieldNavigator,
1231
+ {
1232
+ fields: editableFields,
1233
+ currentFieldId: selectedFieldId,
1234
+ onNavigate: handleNavigate,
1235
+ allRequiredFilled,
1236
+ onSubmit: handleSubmit
1237
+ }
1238
+ ),
1239
+ submitting && /* @__PURE__ */ jsx7("div", { className: "loading-indicator", children: "Generating PDF..." })
1240
+ ] })
1241
+ ] }),
1242
+ /* @__PURE__ */ jsxs7("div", { className: "powered-by", children: [
1243
+ "Powered by ",
1244
+ /* @__PURE__ */ jsx7("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
1245
+ ] })
1246
+ ] });
1247
+ }
1248
+ export {
1249
+ DEFAULT_SIGNER_ROLES,
1250
+ DesignerView,
1251
+ FIELD_DEFAULTS,
1252
+ FieldNavigator,
1253
+ FieldPropertyPanel,
1254
+ PdfViewer,
1255
+ SIGNER_ROLE_COLORS,
1256
+ SignatureCanvas,
1257
+ SignerRoleSelector,
1258
+ SignerView,
1259
+ createField,
1260
+ downloadPdf,
1261
+ generateFilledPdf,
1262
+ getSignerColor,
1263
+ postPdfToCallback,
1264
+ renderPdfPages
1265
+ };
1266
+ //# sourceMappingURL=index.mjs.map