@unlev/exeq 0.2.2 → 0.3.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.css +246 -3
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +1059 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1065 -362
- 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 useState5, useCallback as useCallback4, useEffect as useEffect2, useRef as useRef4 } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
|
|
5
5
|
// src/types/pdf-builder.ts
|
|
@@ -139,18 +139,21 @@ async function renderPdfPages(source, scale = 2) {
|
|
|
139
139
|
canvas.height = viewport.height;
|
|
140
140
|
const ctx = canvas.getContext("2d");
|
|
141
141
|
await page.render({ canvasContext: ctx, viewport }).promise;
|
|
142
|
+
const unscaledViewport = page.getViewport({ scale: 1 });
|
|
142
143
|
pages.push({
|
|
143
144
|
pageNumber: i,
|
|
144
145
|
dataUrl: canvas.toDataURL("image/png"),
|
|
145
146
|
width: viewport.width,
|
|
146
|
-
height: viewport.height
|
|
147
|
+
height: viewport.height,
|
|
148
|
+
pdfWidth: unscaledViewport.width,
|
|
149
|
+
pdfHeight: unscaledViewport.height
|
|
147
150
|
});
|
|
148
151
|
}
|
|
149
152
|
return pages;
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
// src/components/pdf-builder/PdfViewer.tsx
|
|
153
|
-
import { useRef, useCallback } from "react";
|
|
156
|
+
import { useRef, useCallback, useState } from "react";
|
|
154
157
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
155
158
|
function PdfViewer({
|
|
156
159
|
pages,
|
|
@@ -162,23 +165,69 @@ function PdfViewer({
|
|
|
162
165
|
onPageClick,
|
|
163
166
|
onDropField,
|
|
164
167
|
onGroupMove,
|
|
168
|
+
onMoveEnd,
|
|
165
169
|
mode,
|
|
166
170
|
currentSigner,
|
|
167
|
-
renderFieldContent
|
|
171
|
+
renderFieldContent,
|
|
172
|
+
zoom = 1,
|
|
173
|
+
onMarqueeSelect
|
|
168
174
|
}) {
|
|
169
175
|
const containerRef = useRef(null);
|
|
170
|
-
const
|
|
176
|
+
const [guides, setGuides] = useState([]);
|
|
177
|
+
const [marquee, setMarquee] = useState(null);
|
|
178
|
+
const marqueeRef = useRef(null);
|
|
179
|
+
const handlePageMouseDown = useCallback((e, pageIndex) => {
|
|
171
180
|
const target = e.target;
|
|
172
181
|
if (target.closest(".field-overlay")) return;
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
if (mode !== "designer") return;
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
185
|
+
const x = (e.clientX - rect.left) / rect.width * 100;
|
|
186
|
+
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
187
|
+
marqueeRef.current = { startX: x, startY: y, page: pageIndex, didDrag: false };
|
|
188
|
+
const handleMouseMove = (ev) => {
|
|
189
|
+
if (!marqueeRef.current) return;
|
|
190
|
+
const mx = (ev.clientX - rect.left) / rect.width * 100;
|
|
191
|
+
const my = (ev.clientY - rect.top) / rect.height * 100;
|
|
192
|
+
const dx = Math.abs(mx - marqueeRef.current.startX);
|
|
193
|
+
const dy = Math.abs(my - marqueeRef.current.startY);
|
|
194
|
+
if (dx > 1 || dy > 1) {
|
|
195
|
+
marqueeRef.current.didDrag = true;
|
|
196
|
+
setMarquee({
|
|
197
|
+
page: pageIndex,
|
|
198
|
+
x1: Math.min(marqueeRef.current.startX, mx),
|
|
199
|
+
y1: Math.min(marqueeRef.current.startY, my),
|
|
200
|
+
x2: Math.max(marqueeRef.current.startX, mx),
|
|
201
|
+
y2: Math.max(marqueeRef.current.startY, my)
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const handleMouseUp = (ev) => {
|
|
206
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
207
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
208
|
+
if (marqueeRef.current?.didDrag && onMarqueeSelect) {
|
|
209
|
+
const mx1 = Math.min(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
210
|
+
const my1 = Math.min(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
211
|
+
const mx2 = Math.max(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
212
|
+
const my2 = Math.max(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
213
|
+
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
214
|
+
const hits = pageFields.filter((f) => {
|
|
215
|
+
return f.x < mx2 && f.x + f.width > mx1 && f.y < my2 && f.y + f.height > my1;
|
|
216
|
+
}).map((f) => f.id);
|
|
217
|
+
onMarqueeSelect(hits);
|
|
218
|
+
} else if (!marqueeRef.current?.didDrag) {
|
|
219
|
+
if (onPageClick) {
|
|
220
|
+
onPageClick(pageIndex, x, y);
|
|
221
|
+
} else {
|
|
222
|
+
onSelectField(null);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
marqueeRef.current = null;
|
|
226
|
+
setMarquee(null);
|
|
227
|
+
};
|
|
228
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
229
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
230
|
+
}, [mode, fields, onPageClick, onSelectField, onMarqueeSelect]);
|
|
182
231
|
const handleDragOver = useCallback((e) => {
|
|
183
232
|
if (e.dataTransfer.types.includes("application/exeq-field-type")) {
|
|
184
233
|
e.preventDefault();
|
|
@@ -194,14 +243,14 @@ function PdfViewer({
|
|
|
194
243
|
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
195
244
|
onDropField(pageIndex, x, y, fieldType);
|
|
196
245
|
}, [onDropField]);
|
|
197
|
-
return /* @__PURE__ */ jsx("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
|
|
246
|
+
return /* @__PURE__ */ jsx("div", { className: "pdf-viewer-zoom-wrapper", ref: containerRef, style: zoom !== 1 ? { width: `${zoom * 100}%`, minHeight: "min-content" } : void 0, children: /* @__PURE__ */ jsx("div", { className: "pdf-viewer", style: zoom !== 1 ? { transform: `scale(${zoom})`, transformOrigin: "top left", width: `${100 / zoom}%` } : void 0, children: pages.map((page, pageIndex) => {
|
|
198
247
|
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
199
248
|
return /* @__PURE__ */ jsxs(
|
|
200
249
|
"div",
|
|
201
250
|
{
|
|
202
251
|
className: "pdf-page",
|
|
203
252
|
style: { aspectRatio: `${page.width} / ${page.height}` },
|
|
204
|
-
|
|
253
|
+
onMouseDown: (e) => handlePageMouseDown(e, pageIndex),
|
|
205
254
|
onDragOver: handleDragOver,
|
|
206
255
|
onDrop: (e) => handleDrop(e, pageIndex),
|
|
207
256
|
"data-page": pageIndex,
|
|
@@ -225,18 +274,86 @@ function PdfViewer({
|
|
|
225
274
|
onMove: onFieldMove,
|
|
226
275
|
onResize: onFieldResize,
|
|
227
276
|
onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
|
|
277
|
+
onMoveEnd,
|
|
278
|
+
otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
|
|
279
|
+
setGuides: mode === "designer" ? setGuides : void 0,
|
|
280
|
+
pageIndex,
|
|
228
281
|
selectedIds: selectedFieldIds,
|
|
229
282
|
mode,
|
|
230
283
|
currentSigner,
|
|
231
284
|
renderContent: renderFieldContent
|
|
232
285
|
},
|
|
233
286
|
field.id
|
|
234
|
-
))
|
|
287
|
+
)),
|
|
288
|
+
guides.filter((g) => g.page === pageIndex).map((g, i) => g.x !== void 0 ? /* @__PURE__ */ jsx("div", { className: "snap-guide snap-guide-v", style: { left: `${g.x}%` } }, `gx${i}`) : /* @__PURE__ */ jsx("div", { className: "snap-guide snap-guide-h", style: { top: `${g.y}%` } }, `gy${i}`)),
|
|
289
|
+
marquee && marquee.page === pageIndex && /* @__PURE__ */ jsx("div", { className: "marquee-rect", style: {
|
|
290
|
+
left: `${marquee.x1}%`,
|
|
291
|
+
top: `${marquee.y1}%`,
|
|
292
|
+
width: `${marquee.x2 - marquee.x1}%`,
|
|
293
|
+
height: `${marquee.y2 - marquee.y1}%`
|
|
294
|
+
} })
|
|
235
295
|
]
|
|
236
296
|
},
|
|
237
297
|
pageIndex
|
|
238
298
|
);
|
|
239
|
-
}) });
|
|
299
|
+
}) }) });
|
|
300
|
+
}
|
|
301
|
+
var SNAP_THRESHOLD = 1.2;
|
|
302
|
+
function snapAndGuide(field, others, page) {
|
|
303
|
+
const fx = field.x, fy = field.y, fw = field.width, fh = field.height;
|
|
304
|
+
const fcx = fx + fw / 2, fcy = fy + fh / 2;
|
|
305
|
+
let bestSnapX = null;
|
|
306
|
+
let bestSnapY = null;
|
|
307
|
+
for (const o of others) {
|
|
308
|
+
const ox = o.x, oy = o.y, ow = o.width, oh = o.height;
|
|
309
|
+
const ocx = ox + ow / 2, ocy = oy + oh / 2;
|
|
310
|
+
const xCandidates = [
|
|
311
|
+
[fx, ox, ox],
|
|
312
|
+
// left ↔ left
|
|
313
|
+
[fx + fw, ox + ow, ox + ow],
|
|
314
|
+
// right ↔ right
|
|
315
|
+
[fcx, ocx, ocx],
|
|
316
|
+
// center ↔ center
|
|
317
|
+
[fx, ox + ow, ox + ow],
|
|
318
|
+
// left ↔ right
|
|
319
|
+
[fx + fw, ox, ox]
|
|
320
|
+
// right ↔ left
|
|
321
|
+
];
|
|
322
|
+
for (const [fe, te, gx] of xCandidates) {
|
|
323
|
+
const d = Math.abs(fe - te);
|
|
324
|
+
if (d < SNAP_THRESHOLD && (!bestSnapX || d < bestSnapX.dist)) {
|
|
325
|
+
bestSnapX = { offset: te - fe, guideX: gx, dist: d };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const yCandidates = [
|
|
329
|
+
[fy, oy, oy],
|
|
330
|
+
// top ↔ top
|
|
331
|
+
[fy + fh, oy + oh, oy + oh],
|
|
332
|
+
// bottom ↔ bottom
|
|
333
|
+
[fcy, ocy, ocy],
|
|
334
|
+
// center ↔ center
|
|
335
|
+
[fy, oy + oh, oy + oh],
|
|
336
|
+
// top ↔ bottom
|
|
337
|
+
[fy + fh, oy, oy]
|
|
338
|
+
// bottom ↔ top
|
|
339
|
+
];
|
|
340
|
+
for (const [fe, te, gy] of yCandidates) {
|
|
341
|
+
const d = Math.abs(fe - te);
|
|
342
|
+
if (d < SNAP_THRESHOLD && (!bestSnapY || d < bestSnapY.dist)) {
|
|
343
|
+
bestSnapY = { offset: te - fe, guideY: gy, dist: d };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const snappedX = bestSnapX ? fx + bestSnapX.offset : fx;
|
|
348
|
+
const snappedY = bestSnapY ? fy + bestSnapY.offset : fy;
|
|
349
|
+
const guides = [];
|
|
350
|
+
if (bestSnapX) guides.push({ page, x: bestSnapX.guideX });
|
|
351
|
+
if (bestSnapY) guides.push({ page, y: bestSnapY.guideY });
|
|
352
|
+
return {
|
|
353
|
+
x: Math.max(0, Math.min(100 - fw, snappedX)),
|
|
354
|
+
y: Math.max(0, Math.min(100 - fh, snappedY)),
|
|
355
|
+
guides
|
|
356
|
+
};
|
|
240
357
|
}
|
|
241
358
|
function FieldOverlayItem({
|
|
242
359
|
field,
|
|
@@ -246,6 +363,10 @@ function FieldOverlayItem({
|
|
|
246
363
|
onMove,
|
|
247
364
|
onResize,
|
|
248
365
|
onGroupMove,
|
|
366
|
+
onMoveEnd,
|
|
367
|
+
otherFields,
|
|
368
|
+
setGuides,
|
|
369
|
+
pageIndex,
|
|
249
370
|
selectedIds,
|
|
250
371
|
mode,
|
|
251
372
|
currentSigner,
|
|
@@ -253,19 +374,27 @@ function FieldOverlayItem({
|
|
|
253
374
|
}) {
|
|
254
375
|
const overlayRef = useRef(null);
|
|
255
376
|
const dragStartRef = useRef(null);
|
|
377
|
+
const dragPosRef = useRef({ x: field.x, y: field.y });
|
|
378
|
+
const didDragRef = useRef(false);
|
|
256
379
|
const resizeStartRef = useRef(null);
|
|
257
380
|
const isRedact = field.type === "blackout" || field.type === "whiteout";
|
|
258
381
|
const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
|
|
259
382
|
const isInactiveSigner = mode === "signer" && !isRedact && field.assignee !== currentSigner;
|
|
260
|
-
const
|
|
383
|
+
const isFilled = mode === "signer" && isEditable && !isRedact && (field.type === "checkbox" ? true : field.formula ? true : !!field.value);
|
|
384
|
+
const color = isInactiveSigner ? "#cccccc" : isFilled ? "#22c55e" : getSignerColor(field.assignee);
|
|
261
385
|
const isPreFilled = !isEditable && !!field.value;
|
|
262
386
|
const handleMouseDown = useCallback((e) => {
|
|
263
387
|
if (mode !== "designer" || !onMove) return;
|
|
264
388
|
e.preventDefault();
|
|
265
389
|
e.stopPropagation();
|
|
266
|
-
|
|
390
|
+
const alreadyInSelection = selectedIds.has(field.id) && selectedIds.size > 1;
|
|
391
|
+
if (!alreadyInSelection) {
|
|
392
|
+
onSelect(e);
|
|
393
|
+
}
|
|
394
|
+
if (field.locked) return;
|
|
267
395
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
268
396
|
if (!pageEl) return;
|
|
397
|
+
didDragRef.current = false;
|
|
269
398
|
dragStartRef.current = {
|
|
270
399
|
startX: e.clientX,
|
|
271
400
|
startY: e.clientY,
|
|
@@ -274,6 +403,7 @@ function FieldOverlayItem({
|
|
|
274
403
|
};
|
|
275
404
|
const handleMouseMove = (ev) => {
|
|
276
405
|
if (!dragStartRef.current) return;
|
|
406
|
+
didDragRef.current = true;
|
|
277
407
|
const rect = pageEl.getBoundingClientRect();
|
|
278
408
|
const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
|
|
279
409
|
const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
|
|
@@ -284,19 +414,35 @@ function FieldOverlayItem({
|
|
|
284
414
|
} else {
|
|
285
415
|
const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
|
|
286
416
|
const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
|
|
417
|
+
dragPosRef.current = { x: newX, y: newY };
|
|
287
418
|
onMove(field.id, field.page, newX, newY);
|
|
419
|
+
if (ev.shiftKey && otherFields && setGuides && pageIndex !== void 0) {
|
|
420
|
+
const { guides: g } = snapAndGuide({ x: newX, y: newY, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
421
|
+
setGuides(g);
|
|
422
|
+
} else {
|
|
423
|
+
setGuides?.([]);
|
|
424
|
+
}
|
|
288
425
|
}
|
|
289
426
|
};
|
|
290
|
-
const handleMouseUp = () => {
|
|
427
|
+
const handleMouseUp = (ev) => {
|
|
428
|
+
if (ev.shiftKey && didDragRef.current && otherFields && onMove && pageIndex !== void 0 && !isMultiSelected) {
|
|
429
|
+
const pos = dragPosRef.current;
|
|
430
|
+
const snap = snapAndGuide({ x: pos.x, y: pos.y, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
431
|
+
if (snap.x !== pos.x || snap.y !== pos.y) {
|
|
432
|
+
onMove(field.id, field.page, snap.x, snap.y);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
291
435
|
dragStartRef.current = null;
|
|
292
436
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
293
437
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
438
|
+
setGuides?.([]);
|
|
439
|
+
onMoveEnd?.();
|
|
294
440
|
};
|
|
295
441
|
window.addEventListener("mousemove", handleMouseMove);
|
|
296
442
|
window.addEventListener("mouseup", handleMouseUp);
|
|
297
|
-
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
|
|
443
|
+
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
298
444
|
const handleResizeMouseDown = useCallback((e) => {
|
|
299
|
-
if (mode !== "designer" || !onResize) return;
|
|
445
|
+
if (mode !== "designer" || !onResize || field.locked) return;
|
|
300
446
|
e.preventDefault();
|
|
301
447
|
e.stopPropagation();
|
|
302
448
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
@@ -320,15 +466,16 @@ function FieldOverlayItem({
|
|
|
320
466
|
resizeStartRef.current = null;
|
|
321
467
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
322
468
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
469
|
+
onMoveEnd?.();
|
|
323
470
|
};
|
|
324
471
|
window.addEventListener("mousemove", handleMouseMove);
|
|
325
472
|
window.addEventListener("mouseup", handleMouseUp);
|
|
326
|
-
}, [field, mode, onResize]);
|
|
473
|
+
}, [field, mode, onResize, onMoveEnd]);
|
|
327
474
|
return /* @__PURE__ */ jsxs(
|
|
328
475
|
"div",
|
|
329
476
|
{
|
|
330
477
|
ref: overlayRef,
|
|
331
|
-
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""}`,
|
|
478
|
+
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""} ${field.locked ? "locked" : ""}`,
|
|
332
479
|
style: {
|
|
333
480
|
left: `${field.x}%`,
|
|
334
481
|
top: `${field.y}%`,
|
|
@@ -337,18 +484,18 @@ function FieldOverlayItem({
|
|
|
337
484
|
borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
|
|
338
485
|
borderStyle: isRedact ? "dashed" : "solid",
|
|
339
486
|
backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isInactiveSigner ? "#f5f5f5" : isSelected ? `${color}22` : `${color}11`,
|
|
340
|
-
cursor: mode === "designer" ? "
|
|
487
|
+
cursor: mode === "designer" ? field.locked ? "not-allowed" : "move" : "default",
|
|
341
488
|
pointerEvents: mode === "signer" && (isRedact || isInactiveSigner) ? "none" : void 0
|
|
342
489
|
},
|
|
343
490
|
onClick: (e) => {
|
|
344
491
|
e.stopPropagation();
|
|
345
|
-
onSelect(e);
|
|
492
|
+
if (!didDragRef.current) onSelect(e);
|
|
346
493
|
},
|
|
347
494
|
onMouseDown: handleMouseDown,
|
|
348
495
|
children: [
|
|
349
496
|
mode === "designer" && !isRedact && /* @__PURE__ */ jsx("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
350
497
|
renderContent ? renderContent(field) : /* @__PURE__ */ jsx("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
|
|
351
|
-
mode === "designer" && isSelected && /* @__PURE__ */ jsx(
|
|
498
|
+
mode === "designer" && isSelected && !field.locked && /* @__PURE__ */ jsx(
|
|
352
499
|
"div",
|
|
353
500
|
{
|
|
354
501
|
className: "field-resize-handle",
|
|
@@ -361,127 +508,320 @@ function FieldOverlayItem({
|
|
|
361
508
|
}
|
|
362
509
|
|
|
363
510
|
// src/components/pdf-builder/FieldPropertyPanel.tsx
|
|
364
|
-
import { useState } from "react";
|
|
365
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
511
|
+
import { useState as useState2 } from "react";
|
|
512
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
366
513
|
var INK_COLORS = [
|
|
367
514
|
{ value: "#000000", label: "Black" },
|
|
368
515
|
{ value: "#1a56db", label: "Blue" }
|
|
369
516
|
];
|
|
370
|
-
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
517
|
+
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillContent, pageSize }) {
|
|
371
518
|
const color = getSignerColor(field.assignee);
|
|
372
519
|
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
373
520
|
const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
|
|
374
521
|
const showInkColor = field.type === "text" || field.type === "dropdown" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
|
|
375
|
-
const [newOption, setNewOption] =
|
|
522
|
+
const [newOption, setNewOption] = useState2("");
|
|
376
523
|
return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
|
|
377
524
|
/* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
|
|
378
|
-
/* @__PURE__ */ jsx2("h3", { style: { color }, children: field.label }),
|
|
379
|
-
/* @__PURE__ */ jsx2("button", { onClick: () => onDelete(field.id), className: "panel-delete-btn", children: "Delete" })
|
|
380
|
-
] }),
|
|
381
|
-
/* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
382
|
-
/* @__PURE__ */ jsx2("label", { children: "Label" }),
|
|
383
525
|
/* @__PURE__ */ jsx2(
|
|
384
526
|
"input",
|
|
385
527
|
{
|
|
386
|
-
|
|
528
|
+
className: "panel-header-label",
|
|
529
|
+
style: { color },
|
|
387
530
|
value: field.label,
|
|
388
531
|
onChange: (e) => onUpdate(field.id, { label: e.target.value })
|
|
389
532
|
}
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
/* @__PURE__ */
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
)
|
|
409
|
-
] }),
|
|
410
|
-
field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
411
|
-
/* @__PURE__ */ jsx2("label", { children: "Text Type" }),
|
|
412
|
-
/* @__PURE__ */ jsxs2(
|
|
413
|
-
"select",
|
|
414
|
-
{
|
|
415
|
-
value: field.textSubtype || "freeform",
|
|
416
|
-
onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }),
|
|
417
|
-
children: [
|
|
418
|
-
/* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
|
|
419
|
-
/* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
|
|
420
|
-
/* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
|
|
421
|
-
/* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
|
|
422
|
-
/* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
|
|
423
|
-
]
|
|
424
|
-
}
|
|
425
|
-
)
|
|
533
|
+
),
|
|
534
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-header-actions", children: [
|
|
535
|
+
/* @__PURE__ */ jsx2(
|
|
536
|
+
"button",
|
|
537
|
+
{
|
|
538
|
+
onClick: () => onUpdate(field.id, { locked: !field.locked }),
|
|
539
|
+
className: `panel-icon-btn ${field.locked ? "active" : ""}`,
|
|
540
|
+
title: field.locked ? "Unlock" : "Lock",
|
|
541
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: field.locked ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
542
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
543
|
+
/* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0V6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
|
|
544
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
545
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
546
|
+
/* @__PURE__ */ jsx2("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
|
|
547
|
+
] }) })
|
|
548
|
+
}
|
|
549
|
+
),
|
|
550
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onDelete(field.id), className: "panel-icon-btn panel-icon-btn-danger", title: "Delete", children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M5 1h4v1H5V1zM2 3v1h1v8.5A1.5 1.5 0 004.5 14h5a1.5 1.5 0 001.5-1.5V4h1V3H2zm2 1h6v8.5a.5.5 0 01-.5.5h-5a.5.5 0 01-.5-.5V4z" }) }) })
|
|
551
|
+
] })
|
|
426
552
|
] }),
|
|
427
|
-
!isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-
|
|
428
|
-
/* @__PURE__ */ jsx2("
|
|
553
|
+
!isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
|
|
554
|
+
/* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Assigned To" }),
|
|
429
555
|
/* @__PURE__ */ jsx2(
|
|
430
556
|
"select",
|
|
431
557
|
{
|
|
558
|
+
className: "panel-assignee-select",
|
|
432
559
|
value: field.assignee,
|
|
433
560
|
onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
|
|
561
|
+
style: { borderColor: color, color },
|
|
434
562
|
children: signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
435
563
|
}
|
|
436
564
|
)
|
|
437
565
|
] }),
|
|
438
|
-
|
|
439
|
-
/* @__PURE__ */ jsx2("
|
|
440
|
-
/* @__PURE__ */
|
|
441
|
-
"
|
|
442
|
-
{
|
|
443
|
-
|
|
444
|
-
value:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
566
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
|
|
567
|
+
/* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Content" }),
|
|
568
|
+
!isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
569
|
+
/* @__PURE__ */ jsx2("label", { children: "Field Type" }),
|
|
570
|
+
/* @__PURE__ */ jsxs2("select", { value: field.type, onChange: (e) => onUpdate(field.id, { type: e.target.value }), children: [
|
|
571
|
+
/* @__PURE__ */ jsx2("option", { value: "text", children: "Text" }),
|
|
572
|
+
/* @__PURE__ */ jsx2("option", { value: "dropdown", children: "Dropdown" }),
|
|
573
|
+
/* @__PURE__ */ jsx2("option", { value: "signature", children: "Signature" }),
|
|
574
|
+
/* @__PURE__ */ jsx2("option", { value: "signed-date", children: "Signed Date" }),
|
|
575
|
+
/* @__PURE__ */ jsx2("option", { value: "checkbox", children: "Checkbox" }),
|
|
576
|
+
/* @__PURE__ */ jsx2("option", { value: "initials", children: "Initials" })
|
|
577
|
+
] })
|
|
578
|
+
] }),
|
|
579
|
+
field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
580
|
+
/* @__PURE__ */ jsx2("label", { children: "Text Type" }),
|
|
581
|
+
/* @__PURE__ */ jsxs2("select", { value: field.textSubtype || "freeform", onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }), children: [
|
|
582
|
+
/* @__PURE__ */ jsx2("option", { value: "freeform", children: "Freeform" }),
|
|
583
|
+
/* @__PURE__ */ jsx2("option", { value: "number", children: "Number" }),
|
|
584
|
+
/* @__PURE__ */ jsx2("option", { value: "date", children: "Date" }),
|
|
585
|
+
/* @__PURE__ */ jsx2("option", { value: "email", children: "Email" }),
|
|
586
|
+
/* @__PURE__ */ jsx2("option", { value: "phone", children: "Phone" })
|
|
587
|
+
] })
|
|
588
|
+
] }),
|
|
589
|
+
!isRedactField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
590
|
+
/* @__PURE__ */ jsx2("label", { children: "Placeholder" }),
|
|
591
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: field.placeholder, onChange: (e) => onUpdate(field.id, { placeholder: e.target.value }) })
|
|
592
|
+
] }),
|
|
593
|
+
!isRedactField && field.type !== "checkbox" && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
|
|
594
|
+
/* @__PURE__ */ jsx2("input", { type: "checkbox", checked: field.required, onChange: (e) => onUpdate(field.id, { required: e.target.checked }) }),
|
|
595
|
+
"Required"
|
|
596
|
+
] }) }),
|
|
597
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
598
|
+
/* @__PURE__ */ jsx2("label", { children: "Formula" }),
|
|
599
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: field.formula || "", onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }), placeholder: "e.g. {{Date Field | month}}" }),
|
|
600
|
+
field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Auto-computed. Signer cannot edit." })
|
|
601
|
+
] }),
|
|
602
|
+
field.type === "text" && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
603
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
604
|
+
/* @__PURE__ */ jsx2("label", { children: "Min Chars" }),
|
|
605
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.minLength || 0, onChange: (e) => onUpdate(field.id, { minLength: Number(e.target.value) }) })
|
|
606
|
+
] }),
|
|
607
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
608
|
+
/* @__PURE__ */ jsx2("label", { children: "Max Chars" }),
|
|
609
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
|
|
610
|
+
] })
|
|
611
|
+
] }),
|
|
612
|
+
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
613
|
+
/* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
614
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
|
|
615
|
+
(field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
|
|
616
|
+
/* @__PURE__ */ jsx2("span", { children: opt }),
|
|
617
|
+
/* @__PURE__ */ jsx2("button", { className: "panel-option-remove", onClick: () => {
|
|
618
|
+
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
619
|
+
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
620
|
+
}, children: "\xD7" })
|
|
621
|
+
] }, i)),
|
|
622
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
|
|
623
|
+
/* @__PURE__ */ jsx2("input", { type: "text", value: newOption, onChange: (e) => setNewOption(e.target.value), onKeyDown: (e) => {
|
|
624
|
+
if (e.key === "Enter" && newOption.trim()) {
|
|
625
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
626
|
+
setNewOption("");
|
|
627
|
+
}
|
|
628
|
+
}, placeholder: "Add option..." }),
|
|
629
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => {
|
|
630
|
+
if (newOption.trim()) {
|
|
631
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
632
|
+
setNewOption("");
|
|
633
|
+
}
|
|
634
|
+
}, children: "+" })
|
|
635
|
+
] })
|
|
636
|
+
] }),
|
|
637
|
+
field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
|
|
638
|
+
field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
|
|
639
|
+
] }),
|
|
640
|
+
prefillContent
|
|
448
641
|
] }),
|
|
449
|
-
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-
|
|
450
|
-
/* @__PURE__ */ jsx2("
|
|
451
|
-
/* @__PURE__ */
|
|
452
|
-
"
|
|
642
|
+
(isTextField || showInkColor) && /* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
|
|
643
|
+
/* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Style" }),
|
|
644
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
645
|
+
/* @__PURE__ */ jsx2("label", { children: "Font" }),
|
|
646
|
+
/* @__PURE__ */ jsx2("select", { value: field.fontFamily || "Helvetica", onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }), children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value)) })
|
|
647
|
+
] }),
|
|
648
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
649
|
+
/* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
|
|
650
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
|
|
651
|
+
] }),
|
|
652
|
+
isTextField && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
653
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
654
|
+
/* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
|
|
655
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", max: "20", step: "0.5", value: field.letterSpacing || 0, onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) }) })
|
|
656
|
+
] }),
|
|
657
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
658
|
+
/* @__PURE__ */ jsx2("label", { children: "Line Height" }),
|
|
659
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0.8", max: "3", step: "0.1", value: field.lineHeight || 1.2, onChange: (e) => onUpdate(field.id, { lineHeight: Number(e.target.value) }) })
|
|
660
|
+
] })
|
|
661
|
+
] }),
|
|
662
|
+
showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
663
|
+
/* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
|
|
664
|
+
/* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2("button", { className: `ink-color-swatch ${(field.inkColor || "#000000") === c.value ? "active" : ""}`, style: { backgroundColor: c.value }, onClick: () => onUpdate(field.id, { inkColor: c.value }), title: c.label }, c.value)) })
|
|
665
|
+
] })
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-section", children: [
|
|
668
|
+
/* @__PURE__ */ jsx2("div", { className: "panel-section-heading", children: "Position & Size" }),
|
|
669
|
+
(() => {
|
|
670
|
+
const pw = pageSize?.width || 612;
|
|
671
|
+
const ph = pageSize?.height || 792;
|
|
672
|
+
const toPt = (pct, dim) => Math.round(pct / 100 * dim * 10) / 10;
|
|
673
|
+
const fromPt = (pt, dim) => pt / dim * 100;
|
|
674
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
675
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
676
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
677
|
+
/* @__PURE__ */ jsx2("label", { children: "X (pt)" }),
|
|
678
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", step: "0.5", value: toPt(field.x, pw), onChange: (e) => onUpdate(field.id, { x: fromPt(Number(e.target.value), pw) }) })
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
681
|
+
/* @__PURE__ */ jsx2("label", { children: "Y (pt)" }),
|
|
682
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "0", step: "0.5", value: toPt(field.y, ph), onChange: (e) => onUpdate(field.id, { y: fromPt(Number(e.target.value), ph) }) })
|
|
683
|
+
] })
|
|
684
|
+
] }),
|
|
685
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
686
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
687
|
+
/* @__PURE__ */ jsx2("label", { children: "Width (pt)" }),
|
|
688
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "1", step: "0.5", value: toPt(field.width, pw), onChange: (e) => onUpdate(field.id, { width: fromPt(Number(e.target.value), pw) }) })
|
|
689
|
+
] }),
|
|
690
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
691
|
+
/* @__PURE__ */ jsx2("label", { children: "Height (pt)" }),
|
|
692
|
+
/* @__PURE__ */ jsx2("input", { type: "number", min: "1", step: "0.5", value: toPt(field.height, ph), onChange: (e) => onUpdate(field.id, { height: fromPt(Number(e.target.value), ph) }) })
|
|
693
|
+
] })
|
|
694
|
+
] }),
|
|
695
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-hint", children: [
|
|
696
|
+
pw,
|
|
697
|
+
" \xD7 ",
|
|
698
|
+
ph,
|
|
699
|
+
" pt \xB7 Page ",
|
|
700
|
+
field.page + 1,
|
|
701
|
+
" \xB7 ",
|
|
702
|
+
field.id.slice(0, 8)
|
|
703
|
+
] })
|
|
704
|
+
] });
|
|
705
|
+
})()
|
|
706
|
+
] })
|
|
707
|
+
] });
|
|
708
|
+
}
|
|
709
|
+
function sharedValue(items) {
|
|
710
|
+
if (items.length === 0) return void 0;
|
|
711
|
+
return items.every((v) => v === items[0]) ? items[0] : void 0;
|
|
712
|
+
}
|
|
713
|
+
function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete }) {
|
|
714
|
+
const count = fields.length;
|
|
715
|
+
const hasText = fields.some((f) => f.type === "text" || f.type === "dropdown" || f.type === "signed-date");
|
|
716
|
+
const allNonRedact = fields.every((f) => f.type !== "blackout" && f.type !== "whiteout");
|
|
717
|
+
const sharedAssignee = sharedValue(fields.map((f) => f.assignee));
|
|
718
|
+
const sharedRequired = sharedValue(fields.map((f) => f.required));
|
|
719
|
+
const sharedFontSize = sharedValue(fields.map((f) => f.fontSize));
|
|
720
|
+
const sharedFontFamily = sharedValue(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
721
|
+
const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
|
|
722
|
+
const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
|
|
723
|
+
const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
|
|
724
|
+
return /* @__PURE__ */ jsxs2("div", { className: "field-property-panel", children: [
|
|
725
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-header", children: [
|
|
726
|
+
/* @__PURE__ */ jsxs2("h3", { children: [
|
|
727
|
+
count,
|
|
728
|
+
" fields selected"
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ jsx2("button", { onClick: onDelete, className: "panel-delete-btn", children: "Delete All" })
|
|
731
|
+
] }),
|
|
732
|
+
/* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
733
|
+
/* @__PURE__ */ jsx2("label", { children: "Align" }),
|
|
734
|
+
/* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
|
|
735
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("left"), title: "Align left edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
736
|
+
/* @__PURE__ */ jsx2("rect", { x: "1", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
737
|
+
/* @__PURE__ */ jsx2("rect", { x: "4", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
738
|
+
/* @__PURE__ */ jsx2("rect", { x: "4", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
739
|
+
] }) }),
|
|
740
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("center-h"), title: "Align centers horizontally", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
741
|
+
/* @__PURE__ */ jsx2("rect", { x: "6.25", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
742
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "10", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
743
|
+
/* @__PURE__ */ jsx2("rect", { x: "3.5", y: "7", width: "7", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
744
|
+
] }) }),
|
|
745
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("right"), title: "Align right edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
746
|
+
/* @__PURE__ */ jsx2("rect", { x: "11.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
747
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
748
|
+
/* @__PURE__ */ jsx2("rect", { x: "5", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
749
|
+
] }) }),
|
|
750
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("top"), title: "Align top edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
751
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "1", width: "14", height: "1.5", fill: "currentColor" }),
|
|
752
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "4", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
753
|
+
/* @__PURE__ */ jsx2("rect", { x: "7", y: "4", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
754
|
+
] }) }),
|
|
755
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("center-v"), title: "Align centers vertically", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
756
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "6.25", width: "14", height: "1.5", fill: "currentColor" }),
|
|
757
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "1", width: "3", height: "12", fill: "currentColor", opacity: "0.5" }),
|
|
758
|
+
/* @__PURE__ */ jsx2("rect", { x: "7", y: "3", width: "3", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
759
|
+
] }) }),
|
|
760
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("bottom"), title: "Align bottom edges", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
761
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "11.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
762
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "2", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
763
|
+
/* @__PURE__ */ jsx2("rect", { x: "7", y: "5", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
764
|
+
] }) })
|
|
765
|
+
] }),
|
|
766
|
+
count > 2 && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
767
|
+
/* @__PURE__ */ jsx2("label", { style: { marginTop: "0.4rem" }, children: "Distribute" }),
|
|
768
|
+
/* @__PURE__ */ jsxs2("div", { className: "align-buttons", children: [
|
|
769
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-h"), title: "Distribute horizontally", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
770
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
771
|
+
/* @__PURE__ */ jsx2("rect", { x: "12.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
772
|
+
/* @__PURE__ */ jsx2("rect", { x: "5", y: "3", width: "4", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
773
|
+
] }) }),
|
|
774
|
+
/* @__PURE__ */ jsx2("button", { onClick: () => onAlign("distribute-v"), title: "Distribute vertically", children: /* @__PURE__ */ jsxs2("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
775
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "0", width: "14", height: "1.5", fill: "currentColor" }),
|
|
776
|
+
/* @__PURE__ */ jsx2("rect", { x: "0", y: "12.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
777
|
+
/* @__PURE__ */ jsx2("rect", { x: "3", y: "5", width: "8", height: "4", fill: "currentColor", opacity: "0.5" })
|
|
778
|
+
] }) })
|
|
779
|
+
] })
|
|
780
|
+
] })
|
|
781
|
+
] }),
|
|
782
|
+
allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
783
|
+
/* @__PURE__ */ jsx2("label", { children: "Assigned To" }),
|
|
784
|
+
/* @__PURE__ */ jsxs2(
|
|
785
|
+
"select",
|
|
453
786
|
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
787
|
+
value: sharedAssignee || "",
|
|
788
|
+
onChange: (e) => onUpdate({ assignee: e.target.value }),
|
|
789
|
+
children: [
|
|
790
|
+
!sharedAssignee && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
|
|
791
|
+
signerRoles.map((role) => /* @__PURE__ */ jsx2("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
792
|
+
]
|
|
458
793
|
}
|
|
459
|
-
)
|
|
460
|
-
field.formula && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
|
|
794
|
+
)
|
|
461
795
|
] }),
|
|
462
|
-
|
|
796
|
+
allNonRedact && /* @__PURE__ */ jsx2("div", { className: "panel-field", children: /* @__PURE__ */ jsxs2("label", { className: "panel-checkbox-label", children: [
|
|
463
797
|
/* @__PURE__ */ jsx2(
|
|
464
798
|
"input",
|
|
465
799
|
{
|
|
466
800
|
type: "checkbox",
|
|
467
|
-
checked:
|
|
468
|
-
|
|
801
|
+
checked: sharedRequired ?? false,
|
|
802
|
+
ref: (el) => {
|
|
803
|
+
if (el) el.indeterminate = sharedRequired === void 0;
|
|
804
|
+
},
|
|
805
|
+
onChange: (e) => onUpdate({ required: e.target.checked })
|
|
469
806
|
}
|
|
470
807
|
),
|
|
471
808
|
"Required"
|
|
472
809
|
] }) }),
|
|
473
|
-
|
|
810
|
+
hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
474
811
|
/* @__PURE__ */ jsx2("label", { children: "Font" }),
|
|
475
|
-
/* @__PURE__ */
|
|
812
|
+
/* @__PURE__ */ jsxs2(
|
|
476
813
|
"select",
|
|
477
814
|
{
|
|
478
|
-
value:
|
|
479
|
-
onChange: (e) => onUpdate(
|
|
480
|
-
children:
|
|
815
|
+
value: sharedFontFamily || "",
|
|
816
|
+
onChange: (e) => onUpdate({ fontFamily: e.target.value }),
|
|
817
|
+
children: [
|
|
818
|
+
!sharedFontFamily && /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "Mixed" }),
|
|
819
|
+
FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx2("option", { value: f.value, children: f.label }, f.value))
|
|
820
|
+
]
|
|
481
821
|
}
|
|
482
822
|
)
|
|
483
823
|
] }),
|
|
484
|
-
|
|
824
|
+
hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
485
825
|
/* @__PURE__ */ jsx2("label", { children: "Font Size (pt)" }),
|
|
486
826
|
/* @__PURE__ */ jsx2(
|
|
487
827
|
"input",
|
|
@@ -489,12 +829,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
489
829
|
type: "number",
|
|
490
830
|
min: "6",
|
|
491
831
|
max: "72",
|
|
492
|
-
value:
|
|
493
|
-
|
|
832
|
+
value: sharedFontSize ?? "",
|
|
833
|
+
placeholder: "Mixed",
|
|
834
|
+
onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
|
|
494
835
|
}
|
|
495
836
|
)
|
|
496
837
|
] }),
|
|
497
|
-
|
|
838
|
+
hasText && /* @__PURE__ */ jsxs2("div", { className: "panel-field-row", children: [
|
|
498
839
|
/* @__PURE__ */ jsxs2("div", { className: "panel-field panel-field-half", children: [
|
|
499
840
|
/* @__PURE__ */ jsx2("label", { children: "Letter Spacing" }),
|
|
500
841
|
/* @__PURE__ */ jsx2(
|
|
@@ -504,8 +845,9 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
504
845
|
min: "0",
|
|
505
846
|
max: "20",
|
|
506
847
|
step: "0.5",
|
|
507
|
-
value:
|
|
508
|
-
|
|
848
|
+
value: sharedLetterSpacing ?? "",
|
|
849
|
+
placeholder: "Mixed",
|
|
850
|
+
onChange: (e) => onUpdate({ letterSpacing: Number(e.target.value) })
|
|
509
851
|
}
|
|
510
852
|
)
|
|
511
853
|
] }),
|
|
@@ -518,93 +860,31 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
518
860
|
min: "0.8",
|
|
519
861
|
max: "3",
|
|
520
862
|
step: "0.1",
|
|
521
|
-
value:
|
|
522
|
-
|
|
863
|
+
value: sharedLineHeight ?? "",
|
|
864
|
+
placeholder: "Mixed",
|
|
865
|
+
onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
|
|
523
866
|
}
|
|
524
867
|
)
|
|
525
868
|
] })
|
|
526
869
|
] }),
|
|
527
|
-
|
|
528
|
-
/* @__PURE__ */ jsx2("label", { children: "Max Characters (0 = unlimited)" }),
|
|
529
|
-
/* @__PURE__ */ jsx2(
|
|
530
|
-
"input",
|
|
531
|
-
{
|
|
532
|
-
type: "number",
|
|
533
|
-
min: "0",
|
|
534
|
-
max: "9999",
|
|
535
|
-
value: field.maxLength || 0,
|
|
536
|
-
onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) })
|
|
537
|
-
}
|
|
538
|
-
)
|
|
539
|
-
] }),
|
|
540
|
-
showInkColor && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
870
|
+
allNonRedact && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
541
871
|
/* @__PURE__ */ jsx2("label", { children: "Ink Color" }),
|
|
542
872
|
/* @__PURE__ */ jsx2("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ jsx2(
|
|
543
873
|
"button",
|
|
544
874
|
{
|
|
545
|
-
className: `ink-color-swatch ${
|
|
875
|
+
className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
|
|
546
876
|
style: { backgroundColor: c.value },
|
|
547
|
-
onClick: () => onUpdate(
|
|
877
|
+
onClick: () => onUpdate({ inkColor: c.value }),
|
|
548
878
|
title: c.label
|
|
549
879
|
},
|
|
550
880
|
c.value
|
|
551
881
|
)) })
|
|
552
|
-
] }),
|
|
553
|
-
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ jsxs2("div", { className: "panel-field", children: [
|
|
554
|
-
/* @__PURE__ */ jsx2("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
555
|
-
/* @__PURE__ */ jsxs2("div", { className: "panel-options-list", children: [
|
|
556
|
-
(field.options || []).map((opt, i) => /* @__PURE__ */ jsxs2("div", { className: "panel-option-item", children: [
|
|
557
|
-
/* @__PURE__ */ jsx2("span", { children: opt }),
|
|
558
|
-
/* @__PURE__ */ jsx2(
|
|
559
|
-
"button",
|
|
560
|
-
{
|
|
561
|
-
className: "panel-option-remove",
|
|
562
|
-
onClick: () => {
|
|
563
|
-
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
564
|
-
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
565
|
-
},
|
|
566
|
-
children: "\xD7"
|
|
567
|
-
}
|
|
568
|
-
)
|
|
569
|
-
] }, i)),
|
|
570
|
-
/* @__PURE__ */ jsxs2("div", { className: "panel-option-add", children: [
|
|
571
|
-
/* @__PURE__ */ jsx2(
|
|
572
|
-
"input",
|
|
573
|
-
{
|
|
574
|
-
type: "text",
|
|
575
|
-
value: newOption,
|
|
576
|
-
onChange: (e) => setNewOption(e.target.value),
|
|
577
|
-
onKeyDown: (e) => {
|
|
578
|
-
if (e.key === "Enter" && newOption.trim()) {
|
|
579
|
-
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
580
|
-
setNewOption("");
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
placeholder: "Add option..."
|
|
584
|
-
}
|
|
585
|
-
),
|
|
586
|
-
/* @__PURE__ */ jsx2(
|
|
587
|
-
"button",
|
|
588
|
-
{
|
|
589
|
-
onClick: () => {
|
|
590
|
-
if (newOption.trim()) {
|
|
591
|
-
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
592
|
-
setNewOption("");
|
|
593
|
-
}
|
|
594
|
-
},
|
|
595
|
-
children: "+"
|
|
596
|
-
}
|
|
597
|
-
)
|
|
598
|
-
] })
|
|
599
|
-
] }),
|
|
600
|
-
field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
|
|
601
|
-
field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ jsx2("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
|
|
602
882
|
] })
|
|
603
883
|
] });
|
|
604
884
|
}
|
|
605
885
|
|
|
606
886
|
// src/components/pdf-builder/SignatureCanvas.tsx
|
|
607
|
-
import { useRef as useRef2, useState as
|
|
887
|
+
import { useRef as useRef2, useState as useState3, useCallback as useCallback2, useEffect } from "react";
|
|
608
888
|
import { getStroke } from "perfect-freehand";
|
|
609
889
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
610
890
|
function getSvgPathFromStroke(stroke) {
|
|
@@ -629,9 +909,9 @@ function SignatureCanvas({
|
|
|
629
909
|
inkColor = "#000000"
|
|
630
910
|
}) {
|
|
631
911
|
const canvasRef = useRef2(null);
|
|
632
|
-
const [paths, setPaths] =
|
|
633
|
-
const [currentPath, setCurrentPath] =
|
|
634
|
-
const [isEmpty, setIsEmpty] =
|
|
912
|
+
const [paths, setPaths] = useState3([]);
|
|
913
|
+
const [currentPath, setCurrentPath] = useState3(null);
|
|
914
|
+
const [isEmpty, setIsEmpty] = useState3(!initialValue);
|
|
635
915
|
useEffect(() => {
|
|
636
916
|
if (initialValue && canvasRef.current) {
|
|
637
917
|
const ctx = canvasRef.current.getContext("2d");
|
|
@@ -743,8 +1023,77 @@ function isValidApiKey(key) {
|
|
|
743
1023
|
return VALID_KEYS.has(key);
|
|
744
1024
|
}
|
|
745
1025
|
|
|
1026
|
+
// src/hooks/useHistory.ts
|
|
1027
|
+
import { useState as useState4, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
1028
|
+
function useHistory(initialState, maxHistory = 50) {
|
|
1029
|
+
const [state, setState] = useState4({
|
|
1030
|
+
past: [],
|
|
1031
|
+
present: initialState,
|
|
1032
|
+
future: []
|
|
1033
|
+
});
|
|
1034
|
+
const skipRef = useRef3(false);
|
|
1035
|
+
const set = useCallback3((updater) => {
|
|
1036
|
+
setState((prev) => {
|
|
1037
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1038
|
+
if (newPresent === prev.present) return prev;
|
|
1039
|
+
if (skipRef.current) {
|
|
1040
|
+
skipRef.current = false;
|
|
1041
|
+
return { ...prev, present: newPresent };
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
past: [...prev.past.slice(-maxHistory), prev.present],
|
|
1045
|
+
present: newPresent,
|
|
1046
|
+
future: []
|
|
1047
|
+
};
|
|
1048
|
+
});
|
|
1049
|
+
}, [maxHistory]);
|
|
1050
|
+
const setWithoutHistory = useCallback3((updater) => {
|
|
1051
|
+
skipRef.current = true;
|
|
1052
|
+
set(updater);
|
|
1053
|
+
}, [set]);
|
|
1054
|
+
const snapshot = useCallback3(() => {
|
|
1055
|
+
setState((prev) => ({
|
|
1056
|
+
past: [...prev.past.slice(-maxHistory), prev.present],
|
|
1057
|
+
present: prev.present,
|
|
1058
|
+
future: []
|
|
1059
|
+
}));
|
|
1060
|
+
}, [maxHistory]);
|
|
1061
|
+
const undo = useCallback3(() => {
|
|
1062
|
+
setState((prev) => {
|
|
1063
|
+
if (prev.past.length === 0) return prev;
|
|
1064
|
+
const previous = prev.past[prev.past.length - 1];
|
|
1065
|
+
return {
|
|
1066
|
+
past: prev.past.slice(0, -1),
|
|
1067
|
+
present: previous,
|
|
1068
|
+
future: [prev.present, ...prev.future]
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
1071
|
+
}, []);
|
|
1072
|
+
const redo = useCallback3(() => {
|
|
1073
|
+
setState((prev) => {
|
|
1074
|
+
if (prev.future.length === 0) return prev;
|
|
1075
|
+
const next = prev.future[0];
|
|
1076
|
+
return {
|
|
1077
|
+
past: [...prev.past, prev.present],
|
|
1078
|
+
present: next,
|
|
1079
|
+
future: prev.future.slice(1)
|
|
1080
|
+
};
|
|
1081
|
+
});
|
|
1082
|
+
}, []);
|
|
1083
|
+
return {
|
|
1084
|
+
state: state.present,
|
|
1085
|
+
set,
|
|
1086
|
+
setWithoutHistory,
|
|
1087
|
+
snapshot,
|
|
1088
|
+
undo,
|
|
1089
|
+
redo,
|
|
1090
|
+
canUndo: state.past.length > 0,
|
|
1091
|
+
canRedo: state.future.length > 0
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
746
1095
|
// src/components/pdf-builder/DesignerView.tsx
|
|
747
|
-
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1096
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
748
1097
|
var FIELD_TYPE_META = [
|
|
749
1098
|
{ type: "text", label: "Text", icon: "T" },
|
|
750
1099
|
{ type: "dropdown", label: "Dropdown", icon: "\u25BE" },
|
|
@@ -773,23 +1122,27 @@ function DesignerView({
|
|
|
773
1122
|
] })
|
|
774
1123
|
] }) });
|
|
775
1124
|
}
|
|
776
|
-
const [pages, setPages] =
|
|
777
|
-
const
|
|
778
|
-
const [selectedFieldIds, setSelectedFieldIds] =
|
|
779
|
-
const [signerRoles, setSignerRoles] =
|
|
780
|
-
const [activeRole, setActiveRole] =
|
|
781
|
-
const [activeFieldType, setActiveFieldType] =
|
|
782
|
-
const [loading, setLoading] =
|
|
783
|
-
const [pdfSource, setPdfSource] =
|
|
784
|
-
const [rightTab, setRightTab] =
|
|
785
|
-
const [isAddingRole, setIsAddingRole] =
|
|
786
|
-
const [newRoleName, setNewRoleName] =
|
|
787
|
-
const [draggingFieldType, setDraggingFieldType] =
|
|
788
|
-
const [panelWidth, setPanelWidth] =
|
|
789
|
-
const [
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
const
|
|
1125
|
+
const [pages, setPages] = useState5([]);
|
|
1126
|
+
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, snapshot: snapshotFields, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
|
|
1127
|
+
const [selectedFieldIds, setSelectedFieldIds] = useState5(/* @__PURE__ */ new Set());
|
|
1128
|
+
const [signerRoles, setSignerRoles] = useState5(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
1129
|
+
const [activeRole, setActiveRole] = useState5("Sender");
|
|
1130
|
+
const [activeFieldType, setActiveFieldType] = useState5("text");
|
|
1131
|
+
const [loading, setLoading] = useState5(false);
|
|
1132
|
+
const [pdfSource, setPdfSource] = useState5(null);
|
|
1133
|
+
const [rightTab, setRightTab] = useState5("properties");
|
|
1134
|
+
const [isAddingRole, setIsAddingRole] = useState5(false);
|
|
1135
|
+
const [newRoleName, setNewRoleName] = useState5("");
|
|
1136
|
+
const [draggingFieldType, setDraggingFieldType] = useState5(null);
|
|
1137
|
+
const [panelWidth, setPanelWidth] = useState5(380);
|
|
1138
|
+
const [zoom, setZoom] = useState5(1);
|
|
1139
|
+
const [isPanning, setIsPanning] = useState5(false);
|
|
1140
|
+
const panRef = useRef4(null);
|
|
1141
|
+
const pdfAreaRef = useRef4(null);
|
|
1142
|
+
const [clipboardFields, setClipboardFields] = useState5([]);
|
|
1143
|
+
const dragGhostRef = useRef4(null);
|
|
1144
|
+
const resizingRef = useRef4(false);
|
|
1145
|
+
const lastStylesRef = useRef4({});
|
|
793
1146
|
useEffect2(() => {
|
|
794
1147
|
const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
|
|
795
1148
|
if (pdfUrl) {
|
|
@@ -814,7 +1167,7 @@ function DesignerView({
|
|
|
814
1167
|
window.addEventListener("message", handleMessage);
|
|
815
1168
|
return () => window.removeEventListener("message", handleMessage);
|
|
816
1169
|
}, [initialPdfUrl, initialTemplate]);
|
|
817
|
-
const loadPdf =
|
|
1170
|
+
const loadPdf = useCallback4(async (source) => {
|
|
818
1171
|
setLoading(true);
|
|
819
1172
|
try {
|
|
820
1173
|
const rendered = await renderPdfPages(source);
|
|
@@ -826,7 +1179,7 @@ function DesignerView({
|
|
|
826
1179
|
setLoading(false);
|
|
827
1180
|
}
|
|
828
1181
|
}, []);
|
|
829
|
-
const handleFileUpload =
|
|
1182
|
+
const handleFileUpload = useCallback4((e) => {
|
|
830
1183
|
const file = e.target.files?.[0];
|
|
831
1184
|
if (!file) return;
|
|
832
1185
|
const reader = new FileReader();
|
|
@@ -835,7 +1188,7 @@ function DesignerView({
|
|
|
835
1188
|
};
|
|
836
1189
|
reader.readAsArrayBuffer(file);
|
|
837
1190
|
}, [loadPdf]);
|
|
838
|
-
const handlePageClick =
|
|
1191
|
+
const handlePageClick = useCallback4((page, x, y) => {
|
|
839
1192
|
const field = createField(activeFieldType, activeRole, page, x, y, fields);
|
|
840
1193
|
const sticky = lastStylesRef.current[activeFieldType];
|
|
841
1194
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -843,11 +1196,14 @@ function DesignerView({
|
|
|
843
1196
|
setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
|
|
844
1197
|
setRightTab("properties");
|
|
845
1198
|
}, [activeFieldType, activeRole, fields]);
|
|
846
|
-
const handleFieldMove =
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1199
|
+
const handleFieldMove = useCallback4((id, page, x, y) => {
|
|
1200
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1201
|
+
if (f.id !== id || f.locked) return f;
|
|
1202
|
+
return { ...f, page, x, y };
|
|
1203
|
+
}));
|
|
1204
|
+
}, [setFieldsSilent]);
|
|
1205
|
+
const handleFieldResize = useCallback4((id, width, height) => {
|
|
1206
|
+
setFieldsSilent((prev) => {
|
|
851
1207
|
const field = prev.find((f) => f.id === id);
|
|
852
1208
|
if (field) {
|
|
853
1209
|
const existing = lastStylesRef.current[field.type] || {};
|
|
@@ -855,8 +1211,8 @@ function DesignerView({
|
|
|
855
1211
|
}
|
|
856
1212
|
return prev.map((f) => f.id === id ? { ...f, width, height } : f);
|
|
857
1213
|
});
|
|
858
|
-
}, []);
|
|
859
|
-
const handleFieldUpdate =
|
|
1214
|
+
}, [setFieldsSilent]);
|
|
1215
|
+
const handleFieldUpdate = useCallback4((id, updates) => {
|
|
860
1216
|
setFields((prev) => {
|
|
861
1217
|
if (updates.label !== void 0) {
|
|
862
1218
|
const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
|
|
@@ -876,7 +1232,75 @@ function DesignerView({
|
|
|
876
1232
|
return updated;
|
|
877
1233
|
});
|
|
878
1234
|
}, []);
|
|
879
|
-
const
|
|
1235
|
+
const handleBulkUpdate = useCallback4((updates) => {
|
|
1236
|
+
setFields((prev) => prev.map((f) => selectedFieldIds.has(f.id) ? { ...f, ...updates } : f));
|
|
1237
|
+
}, [selectedFieldIds]);
|
|
1238
|
+
const handleAlign = useCallback4((type) => {
|
|
1239
|
+
setFields((prev) => {
|
|
1240
|
+
const selected = prev.filter((f) => selectedFieldIds.has(f.id));
|
|
1241
|
+
if (selected.length < 2) return prev;
|
|
1242
|
+
let updates = {};
|
|
1243
|
+
if (type === "left") {
|
|
1244
|
+
const minX = Math.min(...selected.map((f) => f.x));
|
|
1245
|
+
selected.forEach((f) => {
|
|
1246
|
+
updates[f.id] = { x: minX };
|
|
1247
|
+
});
|
|
1248
|
+
} else if (type === "right") {
|
|
1249
|
+
const maxRight = Math.max(...selected.map((f) => f.x + f.width));
|
|
1250
|
+
selected.forEach((f) => {
|
|
1251
|
+
updates[f.id] = { x: maxRight - f.width };
|
|
1252
|
+
});
|
|
1253
|
+
} else if (type === "top") {
|
|
1254
|
+
const minY = Math.min(...selected.map((f) => f.y));
|
|
1255
|
+
selected.forEach((f) => {
|
|
1256
|
+
updates[f.id] = { y: minY };
|
|
1257
|
+
});
|
|
1258
|
+
} else if (type === "bottom") {
|
|
1259
|
+
const maxBottom = Math.max(...selected.map((f) => f.y + f.height));
|
|
1260
|
+
selected.forEach((f) => {
|
|
1261
|
+
updates[f.id] = { y: maxBottom - f.height };
|
|
1262
|
+
});
|
|
1263
|
+
} else if (type === "center-h") {
|
|
1264
|
+
const centers = selected.map((f) => f.x + f.width / 2);
|
|
1265
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1266
|
+
selected.forEach((f) => {
|
|
1267
|
+
updates[f.id] = { x: avgCenter - f.width / 2 };
|
|
1268
|
+
});
|
|
1269
|
+
} else if (type === "center-v") {
|
|
1270
|
+
const centers = selected.map((f) => f.y + f.height / 2);
|
|
1271
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1272
|
+
selected.forEach((f) => {
|
|
1273
|
+
updates[f.id] = { y: avgCenter - f.height / 2 };
|
|
1274
|
+
});
|
|
1275
|
+
} else if (type === "distribute-h") {
|
|
1276
|
+
const sorted = [...selected].sort((a, b) => a.x - b.x);
|
|
1277
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1278
|
+
const totalSpan = last.x + last.width - first.x;
|
|
1279
|
+
const totalFieldWidth = sorted.reduce((s, f) => s + f.width, 0);
|
|
1280
|
+
const gap = (totalSpan - totalFieldWidth) / (sorted.length - 1);
|
|
1281
|
+
let cx = first.x;
|
|
1282
|
+
sorted.forEach((f) => {
|
|
1283
|
+
updates[f.id] = { x: cx };
|
|
1284
|
+
cx += f.width + gap;
|
|
1285
|
+
});
|
|
1286
|
+
} else if (type === "distribute-v") {
|
|
1287
|
+
const sorted = [...selected].sort((a, b) => a.y - b.y);
|
|
1288
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1289
|
+
const totalSpan = last.y + last.height - first.y;
|
|
1290
|
+
const totalFieldHeight = sorted.reduce((s, f) => s + f.height, 0);
|
|
1291
|
+
const gap = (totalSpan - totalFieldHeight) / (sorted.length - 1);
|
|
1292
|
+
let cy = first.y;
|
|
1293
|
+
sorted.forEach((f) => {
|
|
1294
|
+
updates[f.id] = { y: cy };
|
|
1295
|
+
cy += f.height + gap;
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
return prev.map((f) => updates[f.id] ? { ...f, ...updates[f.id] } : f);
|
|
1299
|
+
});
|
|
1300
|
+
}, [selectedFieldIds]);
|
|
1301
|
+
const handleFieldDelete = useCallback4((id) => {
|
|
1302
|
+
const field = fields.find((f) => f.id === id);
|
|
1303
|
+
if (field?.locked) return;
|
|
880
1304
|
setFields((prev) => prev.filter((f) => f.id !== id));
|
|
881
1305
|
setSelectedFieldIds((prev) => {
|
|
882
1306
|
const next = new Set(prev);
|
|
@@ -884,7 +1308,7 @@ function DesignerView({
|
|
|
884
1308
|
return next;
|
|
885
1309
|
});
|
|
886
1310
|
}, []);
|
|
887
|
-
const handleSelectField =
|
|
1311
|
+
const handleSelectField = useCallback4((id, e) => {
|
|
888
1312
|
if (!id) {
|
|
889
1313
|
setSelectedFieldIds(/* @__PURE__ */ new Set());
|
|
890
1314
|
return;
|
|
@@ -918,15 +1342,15 @@ function DesignerView({
|
|
|
918
1342
|
}
|
|
919
1343
|
setRightTab("properties");
|
|
920
1344
|
}, [selectedFieldIds]);
|
|
921
|
-
const handleGroupMove =
|
|
922
|
-
|
|
923
|
-
if (!ids.includes(f.id)) return f;
|
|
1345
|
+
const handleGroupMove = useCallback4((ids, dx, dy) => {
|
|
1346
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1347
|
+
if (!ids.includes(f.id) || f.locked) return f;
|
|
924
1348
|
const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
|
|
925
1349
|
const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
|
|
926
1350
|
return { ...f, x: newX, y: newY };
|
|
927
1351
|
}));
|
|
928
|
-
}, []);
|
|
929
|
-
const handleAddRole =
|
|
1352
|
+
}, [setFieldsSilent]);
|
|
1353
|
+
const handleAddRole = useCallback4(() => {
|
|
930
1354
|
const name = newRoleName.trim();
|
|
931
1355
|
if (name && !signerRoles.includes(name)) {
|
|
932
1356
|
setSignerRoles((prev) => [...prev, name]);
|
|
@@ -934,12 +1358,12 @@ function DesignerView({
|
|
|
934
1358
|
setNewRoleName("");
|
|
935
1359
|
setIsAddingRole(false);
|
|
936
1360
|
}, [newRoleName, signerRoles]);
|
|
937
|
-
const handleRemoveRole =
|
|
1361
|
+
const handleRemoveRole = useCallback4((role) => {
|
|
938
1362
|
setSignerRoles((prev) => prev.filter((r) => r !== role));
|
|
939
1363
|
setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
|
|
940
1364
|
if (activeRole === role) setActiveRole(signerRoles[0]);
|
|
941
1365
|
}, [signerRoles, activeRole]);
|
|
942
|
-
const handleExport =
|
|
1366
|
+
const handleExport = useCallback4(() => {
|
|
943
1367
|
const template = {
|
|
944
1368
|
fields,
|
|
945
1369
|
signerRoles,
|
|
@@ -959,7 +1383,7 @@ function DesignerView({
|
|
|
959
1383
|
}
|
|
960
1384
|
window.parent?.postMessage({ type: "template-saved", template }, "*");
|
|
961
1385
|
}, [fields, signerRoles, pdfSource, onSave]);
|
|
962
|
-
const handlePaletteDragStart =
|
|
1386
|
+
const handlePaletteDragStart = useCallback4((e, type) => {
|
|
963
1387
|
setDraggingFieldType(type);
|
|
964
1388
|
e.dataTransfer.setData("application/exeq-field-type", type);
|
|
965
1389
|
e.dataTransfer.effectAllowed = "copy";
|
|
@@ -976,14 +1400,14 @@ function DesignerView({
|
|
|
976
1400
|
e.dataTransfer.setDragImage(ghost, 40, 16);
|
|
977
1401
|
dragGhostRef.current = ghost;
|
|
978
1402
|
}, [activeRole]);
|
|
979
|
-
const handlePaletteDragEnd =
|
|
1403
|
+
const handlePaletteDragEnd = useCallback4(() => {
|
|
980
1404
|
setDraggingFieldType(null);
|
|
981
1405
|
if (dragGhostRef.current) {
|
|
982
1406
|
document.body.removeChild(dragGhostRef.current);
|
|
983
1407
|
dragGhostRef.current = null;
|
|
984
1408
|
}
|
|
985
1409
|
}, []);
|
|
986
|
-
const handleDropOnPage =
|
|
1410
|
+
const handleDropOnPage = useCallback4((page, x, y, fieldType) => {
|
|
987
1411
|
const field = createField(fieldType, activeRole, page, x, y, fields);
|
|
988
1412
|
const sticky = lastStylesRef.current[fieldType];
|
|
989
1413
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -999,21 +1423,39 @@ function DesignerView({
|
|
|
999
1423
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1000
1424
|
if (document.activeElement?.isContentEditable) return;
|
|
1001
1425
|
e.preventDefault();
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1426
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1427
|
+
setSelectedFieldIds((prev) => {
|
|
1428
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1429
|
+
fields.forEach((f) => {
|
|
1430
|
+
if (selectedFieldIds.has(f.id) && f.locked) remaining.add(f.id);
|
|
1431
|
+
});
|
|
1432
|
+
return remaining;
|
|
1433
|
+
});
|
|
1007
1434
|
};
|
|
1008
1435
|
window.addEventListener("keydown", handleKeyDown);
|
|
1009
1436
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1010
|
-
}, [selectedFieldIds]);
|
|
1437
|
+
}, [selectedFieldIds, fields]);
|
|
1011
1438
|
useEffect2(() => {
|
|
1012
1439
|
const handleKeyDown = (e) => {
|
|
1013
1440
|
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1014
1441
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1015
1442
|
if (document.activeElement?.isContentEditable) return;
|
|
1016
1443
|
const isMod = e.metaKey || e.ctrlKey;
|
|
1444
|
+
if (isMod && e.key === "a") {
|
|
1445
|
+
e.preventDefault();
|
|
1446
|
+
setSelectedFieldIds(new Set(fields.map((f) => f.id)));
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
if (isMod && e.key === "z" && !e.shiftKey) {
|
|
1450
|
+
e.preventDefault();
|
|
1451
|
+
undoFields();
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (isMod && (e.key === "y" || e.key === "z" && e.shiftKey)) {
|
|
1455
|
+
e.preventDefault();
|
|
1456
|
+
redoFields();
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1017
1459
|
if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
|
|
1018
1460
|
const selected = fields.filter((f) => selectedFieldIds.has(f.id));
|
|
1019
1461
|
if (selected.length > 0) {
|
|
@@ -1040,7 +1482,75 @@ function DesignerView({
|
|
|
1040
1482
|
window.addEventListener("keydown", handleKeyDown);
|
|
1041
1483
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1042
1484
|
}, [selectedFieldIds, fields, clipboardFields]);
|
|
1043
|
-
|
|
1485
|
+
useEffect2(() => {
|
|
1486
|
+
const handleKeyDown = (e) => {
|
|
1487
|
+
const isMod = e.metaKey || e.ctrlKey;
|
|
1488
|
+
if (isMod && (e.key === "=" || e.key === "+")) {
|
|
1489
|
+
e.preventDefault();
|
|
1490
|
+
setZoom((z) => Math.min(3, z + 0.25));
|
|
1491
|
+
}
|
|
1492
|
+
if (isMod && e.key === "-") {
|
|
1493
|
+
e.preventDefault();
|
|
1494
|
+
setZoom((z) => Math.max(0.75, z - 0.25));
|
|
1495
|
+
}
|
|
1496
|
+
if (isMod && e.key === "0") {
|
|
1497
|
+
e.preventDefault();
|
|
1498
|
+
setZoom(1);
|
|
1499
|
+
}
|
|
1500
|
+
if (e.key === " ") {
|
|
1501
|
+
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1502
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1503
|
+
e.preventDefault();
|
|
1504
|
+
e.stopPropagation();
|
|
1505
|
+
setIsPanning(true);
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
const handleKeyUp = (e) => {
|
|
1509
|
+
if (e.key === " ") {
|
|
1510
|
+
e.preventDefault();
|
|
1511
|
+
setIsPanning(false);
|
|
1512
|
+
panRef.current = null;
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
window.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
1516
|
+
window.addEventListener("keyup", handleKeyUp, { capture: true });
|
|
1517
|
+
return () => {
|
|
1518
|
+
window.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
1519
|
+
window.removeEventListener("keyup", handleKeyUp, { capture: true });
|
|
1520
|
+
};
|
|
1521
|
+
}, []);
|
|
1522
|
+
useEffect2(() => {
|
|
1523
|
+
const el = pdfAreaRef.current;
|
|
1524
|
+
if (!el) return;
|
|
1525
|
+
const handleWheel = (e) => {
|
|
1526
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1527
|
+
e.preventDefault();
|
|
1528
|
+
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
1529
|
+
setZoom((z) => Math.max(0.75, Math.min(3, z + delta)));
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
el.addEventListener("wheel", handleWheel, { passive: false });
|
|
1533
|
+
return () => el.removeEventListener("wheel", handleWheel);
|
|
1534
|
+
}, [pages.length]);
|
|
1535
|
+
const handlePanMouseDown = useCallback4((e) => {
|
|
1536
|
+
if (!isPanning || !pdfAreaRef.current) return;
|
|
1537
|
+
e.preventDefault();
|
|
1538
|
+
panRef.current = {
|
|
1539
|
+
startX: e.clientX,
|
|
1540
|
+
startY: e.clientY,
|
|
1541
|
+
scrollLeft: pdfAreaRef.current.scrollLeft,
|
|
1542
|
+
scrollTop: pdfAreaRef.current.scrollTop
|
|
1543
|
+
};
|
|
1544
|
+
}, [isPanning]);
|
|
1545
|
+
const handlePanMouseMove = useCallback4((e) => {
|
|
1546
|
+
if (!panRef.current || !pdfAreaRef.current) return;
|
|
1547
|
+
pdfAreaRef.current.scrollLeft = panRef.current.scrollLeft - (e.clientX - panRef.current.startX);
|
|
1548
|
+
pdfAreaRef.current.scrollTop = panRef.current.scrollTop - (e.clientY - panRef.current.startY);
|
|
1549
|
+
}, []);
|
|
1550
|
+
const handlePanMouseUp = useCallback4(() => {
|
|
1551
|
+
panRef.current = null;
|
|
1552
|
+
}, []);
|
|
1553
|
+
const handleResizeStart = useCallback4((e) => {
|
|
1044
1554
|
e.preventDefault();
|
|
1045
1555
|
resizingRef.current = true;
|
|
1046
1556
|
const startX = e.clientX;
|
|
@@ -1065,7 +1575,7 @@ function DesignerView({
|
|
|
1065
1575
|
return a.x - b.x;
|
|
1066
1576
|
});
|
|
1067
1577
|
const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
|
|
1068
|
-
const renderFieldContent =
|
|
1578
|
+
const renderFieldContent = useCallback4((field) => {
|
|
1069
1579
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1070
1580
|
return null;
|
|
1071
1581
|
}
|
|
@@ -1091,7 +1601,7 @@ function DesignerView({
|
|
|
1091
1601
|
}
|
|
1092
1602
|
);
|
|
1093
1603
|
}, [fields]);
|
|
1094
|
-
const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(
|
|
1604
|
+
const headerButtons = pages.length > 0 ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1095
1605
|
/* @__PURE__ */ jsxs4("label", { className: "header-btn header-btn-outline", children: [
|
|
1096
1606
|
"Change PDF",
|
|
1097
1607
|
/* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
@@ -1169,39 +1679,91 @@ function DesignerView({
|
|
|
1169
1679
|
type
|
|
1170
1680
|
))
|
|
1171
1681
|
] }),
|
|
1172
|
-
/* @__PURE__ */ jsxs4(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1682
|
+
/* @__PURE__ */ jsxs4(
|
|
1683
|
+
"div",
|
|
1684
|
+
{
|
|
1685
|
+
className: `designer-pdf-area ${isPanning ? "panning" : ""}`,
|
|
1686
|
+
ref: pdfAreaRef,
|
|
1687
|
+
onMouseDown: isPanning ? handlePanMouseDown : void 0,
|
|
1688
|
+
onMouseMove: isPanning ? handlePanMouseMove : void 0,
|
|
1689
|
+
onMouseUp: isPanning ? handlePanMouseUp : void 0,
|
|
1690
|
+
onMouseLeave: isPanning ? handlePanMouseUp : void 0,
|
|
1691
|
+
children: [
|
|
1692
|
+
loading && /* @__PURE__ */ jsx4("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
1693
|
+
!pages.length && !loading && /* @__PURE__ */ jsxs4("div", { className: "empty-state", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx4("h2", { children: "Open a PDF to get started" }),
|
|
1695
|
+
/* @__PURE__ */ jsx4("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
|
|
1696
|
+
/* @__PURE__ */ jsxs4("label", { className: "upload-btn upload-btn-large", children: [
|
|
1697
|
+
"Select PDF",
|
|
1698
|
+
/* @__PURE__ */ jsx4("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
1699
|
+
] })
|
|
1700
|
+
] }),
|
|
1701
|
+
pages.length > 0 && /* @__PURE__ */ jsx4(
|
|
1702
|
+
PdfViewer,
|
|
1703
|
+
{
|
|
1704
|
+
pages,
|
|
1705
|
+
fields,
|
|
1706
|
+
selectedFieldIds,
|
|
1707
|
+
onSelectField: handleSelectField,
|
|
1708
|
+
onFieldMove: handleFieldMove,
|
|
1709
|
+
onFieldResize: handleFieldResize,
|
|
1710
|
+
onGroupMove: handleGroupMove,
|
|
1711
|
+
onMoveEnd: snapshotFields,
|
|
1712
|
+
onPageClick: handlePageClick,
|
|
1713
|
+
onDropField: handleDropOnPage,
|
|
1714
|
+
onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
|
|
1715
|
+
mode: "designer",
|
|
1716
|
+
renderFieldContent,
|
|
1717
|
+
zoom
|
|
1718
|
+
}
|
|
1719
|
+
)
|
|
1720
|
+
]
|
|
1721
|
+
}
|
|
1722
|
+
),
|
|
1723
|
+
pages.length > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1200
1724
|
/* @__PURE__ */ jsx4("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
|
|
1201
1725
|
/* @__PURE__ */ jsxs4("div", { className: "designer-panel", style: { width: panelWidth }, children: [
|
|
1202
|
-
/* @__PURE__ */ jsxs4("div", { className: "
|
|
1203
|
-
/* @__PURE__ */
|
|
1204
|
-
|
|
1726
|
+
/* @__PURE__ */ jsxs4("div", { className: "designer-panel-header", children: [
|
|
1727
|
+
/* @__PURE__ */ jsxs4("div", { className: "signer-role-indicator", children: [
|
|
1728
|
+
/* @__PURE__ */ jsx4("span", { className: "signer-role-indicator-label", children: "Editing as" }),
|
|
1729
|
+
/* @__PURE__ */ jsx4("strong", { children: activeRole })
|
|
1730
|
+
] }),
|
|
1731
|
+
/* @__PURE__ */ jsxs4("div", { className: "zoom-slider", children: [
|
|
1732
|
+
/* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.max(0.75, z - 0.25)), disabled: zoom <= 0.75, children: "\u2212" }),
|
|
1733
|
+
/* @__PURE__ */ jsx4(
|
|
1734
|
+
"input",
|
|
1735
|
+
{
|
|
1736
|
+
type: "range",
|
|
1737
|
+
min: "75",
|
|
1738
|
+
max: "300",
|
|
1739
|
+
step: "25",
|
|
1740
|
+
value: Math.round(zoom * 100),
|
|
1741
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100)
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
/* @__PURE__ */ jsx4("button", { onClick: () => setZoom((z) => Math.min(3, z + 0.25)), disabled: zoom >= 3, children: "+" }),
|
|
1745
|
+
/* @__PURE__ */ jsx4("div", { className: "zoom-dropdown-wrapper", children: /* @__PURE__ */ jsxs4(
|
|
1746
|
+
"select",
|
|
1747
|
+
{
|
|
1748
|
+
className: "zoom-dropdown",
|
|
1749
|
+
value: Math.round(zoom * 100),
|
|
1750
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100),
|
|
1751
|
+
children: [
|
|
1752
|
+
/* @__PURE__ */ jsx4("option", { value: "75", children: "75%" }),
|
|
1753
|
+
/* @__PURE__ */ jsx4("option", { value: "100", children: "100%" }),
|
|
1754
|
+
/* @__PURE__ */ jsx4("option", { value: "125", children: "125%" }),
|
|
1755
|
+
/* @__PURE__ */ jsx4("option", { value: "150", children: "150%" }),
|
|
1756
|
+
/* @__PURE__ */ jsx4("option", { value: "200", children: "200%" }),
|
|
1757
|
+
/* @__PURE__ */ jsx4("option", { value: "250", children: "250%" }),
|
|
1758
|
+
/* @__PURE__ */ jsx4("option", { value: "300", children: "300%" }),
|
|
1759
|
+
![75, 100, 125, 150, 200, 250, 300].includes(Math.round(zoom * 100)) && /* @__PURE__ */ jsxs4("option", { value: Math.round(zoom * 100), children: [
|
|
1760
|
+
Math.round(zoom * 100),
|
|
1761
|
+
"%"
|
|
1762
|
+
] })
|
|
1763
|
+
]
|
|
1764
|
+
}
|
|
1765
|
+
) })
|
|
1766
|
+
] })
|
|
1205
1767
|
] }),
|
|
1206
1768
|
/* @__PURE__ */ jsxs4("div", { className: "panel-tabs", children: [
|
|
1207
1769
|
/* @__PURE__ */ jsx4(
|
|
@@ -1225,82 +1787,163 @@ function DesignerView({
|
|
|
1225
1787
|
)
|
|
1226
1788
|
] }),
|
|
1227
1789
|
/* @__PURE__ */ jsxs4("div", { className: "panel-tab-content", children: [
|
|
1228
|
-
rightTab === "properties" && /* @__PURE__ */ jsx4(
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1790
|
+
rightTab === "properties" && /* @__PURE__ */ jsx4(Fragment2, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ jsx4(
|
|
1791
|
+
BulkPropertyPanel,
|
|
1792
|
+
{
|
|
1793
|
+
fields: fields.filter((f) => selectedFieldIds.has(f.id)),
|
|
1794
|
+
signerRoles,
|
|
1795
|
+
onUpdate: handleBulkUpdate,
|
|
1796
|
+
onAlign: handleAlign,
|
|
1797
|
+
onDelete: () => {
|
|
1798
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1799
|
+
setSelectedFieldIds((prev) => {
|
|
1800
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1801
|
+
fields.forEach((f) => {
|
|
1802
|
+
if (prev.has(f.id) && f.locked) remaining.add(f.id);
|
|
1803
|
+
});
|
|
1804
|
+
return remaining;
|
|
1805
|
+
});
|
|
1242
1806
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
/* @__PURE__ */ jsx4(
|
|
1257
|
-
"input",
|
|
1807
|
+
}
|
|
1808
|
+
) : selectedField ? /* @__PURE__ */ jsx4(Fragment2, { children: /* @__PURE__ */ jsx4(
|
|
1809
|
+
FieldPropertyPanel,
|
|
1810
|
+
{
|
|
1811
|
+
field: selectedField,
|
|
1812
|
+
signerRoles,
|
|
1813
|
+
onUpdate: handleFieldUpdate,
|
|
1814
|
+
onDelete: handleFieldDelete,
|
|
1815
|
+
pageSize: pages[selectedField.page] ? { width: pages[selectedField.page].pdfWidth, height: pages[selectedField.page].pdfHeight } : void 0,
|
|
1816
|
+
prefillContent: selectedField.type !== "blackout" && selectedField.type !== "whiteout" ? /* @__PURE__ */ jsxs4("div", { className: "prefill-section", children: [
|
|
1817
|
+
/* @__PURE__ */ jsx4("label", { children: "Pre-fill Value" }),
|
|
1818
|
+
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ jsx4(
|
|
1819
|
+
SignatureCanvas,
|
|
1258
1820
|
{
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1821
|
+
width: panelWidth - 40,
|
|
1822
|
+
height: selectedField.type === "initials" ? 100 : 150,
|
|
1823
|
+
onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
|
|
1824
|
+
initialValue: selectedField.value,
|
|
1825
|
+
inkColor: selectedField.inkColor
|
|
1262
1826
|
}
|
|
1263
|
-
),
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
)
|
|
1276
|
-
] })
|
|
1277
|
-
] }) : /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1278
|
-
rightTab === "fields" && /* @__PURE__ */ jsx4(Fragment, { children: fields.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "No fields yet. Drag a field from the left palette onto the PDF, or click on the PDF to place one." }) : /* @__PURE__ */ jsx4("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ jsxs4(
|
|
1279
|
-
"div",
|
|
1280
|
-
{
|
|
1281
|
-
className: `field-list-item ${selectedFieldIds.has(f.id) ? "active" : ""}`,
|
|
1282
|
-
onClick: () => {
|
|
1283
|
-
setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
|
|
1284
|
-
setRightTab("properties");
|
|
1285
|
-
},
|
|
1286
|
-
children: [
|
|
1287
|
-
/* @__PURE__ */ jsx4(
|
|
1288
|
-
"span",
|
|
1827
|
+
) : selectedField.type === "checkbox" ? /* @__PURE__ */ jsxs4("label", { className: "panel-checkbox-label", children: [
|
|
1828
|
+
/* @__PURE__ */ jsx4(
|
|
1829
|
+
"input",
|
|
1830
|
+
{
|
|
1831
|
+
type: "checkbox",
|
|
1832
|
+
checked: selectedField.value === "true",
|
|
1833
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
1834
|
+
}
|
|
1835
|
+
),
|
|
1836
|
+
"Checked"
|
|
1837
|
+
] }) : /* @__PURE__ */ jsx4(
|
|
1838
|
+
"input",
|
|
1289
1839
|
{
|
|
1290
|
-
|
|
1291
|
-
|
|
1840
|
+
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1841
|
+
value: selectedField.value,
|
|
1842
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
|
|
1843
|
+
placeholder: `Pre-fill ${selectedField.label}`,
|
|
1844
|
+
className: "prefill-input",
|
|
1845
|
+
style: { color: selectedField.inkColor || "#000000" }
|
|
1292
1846
|
}
|
|
1293
|
-
)
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1847
|
+
)
|
|
1848
|
+
] }) : void 0
|
|
1849
|
+
}
|
|
1850
|
+
) }) : /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1851
|
+
rightTab === "fields" && /* @__PURE__ */ jsx4(Fragment2, { children: fields.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "panel-empty", children: "No fields yet. Drag a field from the left palette onto the PDF, or click on the PDF to place one." }) : /* @__PURE__ */ jsx4("div", { className: "field-list", children: sortedFields.map((f, idx) => {
|
|
1852
|
+
const isSel = selectedFieldIds.has(f.id);
|
|
1853
|
+
const isSingleSel = isSel && selectedFieldIds.size === 1;
|
|
1854
|
+
const isTextLike = f.type === "text" || f.type === "dropdown" || f.type === "signed-date";
|
|
1855
|
+
return /* @__PURE__ */ jsxs4(
|
|
1856
|
+
"div",
|
|
1857
|
+
{
|
|
1858
|
+
className: `field-list-item ${isSel ? "active" : ""}`,
|
|
1859
|
+
onClick: (e) => {
|
|
1860
|
+
if (e.target.tagName === "INPUT") return;
|
|
1861
|
+
if (e.metaKey || e.ctrlKey) {
|
|
1862
|
+
setSelectedFieldIds((prev) => {
|
|
1863
|
+
const next = new Set(prev);
|
|
1864
|
+
if (next.has(f.id)) next.delete(f.id);
|
|
1865
|
+
else next.add(f.id);
|
|
1866
|
+
return next;
|
|
1867
|
+
});
|
|
1868
|
+
} else if (e.shiftKey && selectedFieldIds.size > 0) {
|
|
1869
|
+
const ids = sortedFields.map((sf) => sf.id);
|
|
1870
|
+
const lastId = Array.from(selectedFieldIds).pop();
|
|
1871
|
+
const a = ids.indexOf(lastId);
|
|
1872
|
+
const b = ids.indexOf(f.id);
|
|
1873
|
+
if (a >= 0 && b >= 0) {
|
|
1874
|
+
const start = Math.min(a, b);
|
|
1875
|
+
const end = Math.max(a, b);
|
|
1876
|
+
setSelectedFieldIds((prev) => {
|
|
1877
|
+
const next = new Set(prev);
|
|
1878
|
+
ids.slice(start, end + 1).forEach((id) => next.add(id));
|
|
1879
|
+
return next;
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
} else {
|
|
1883
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
|
|
1884
|
+
}
|
|
1885
|
+
},
|
|
1886
|
+
children: [
|
|
1887
|
+
/* @__PURE__ */ jsx4(
|
|
1888
|
+
"span",
|
|
1889
|
+
{
|
|
1890
|
+
className: "field-list-dot",
|
|
1891
|
+
style: { backgroundColor: getSignerColor(f.assignee) }
|
|
1892
|
+
}
|
|
1893
|
+
),
|
|
1894
|
+
/* @__PURE__ */ jsx4("span", { className: "field-list-name", children: f.label }),
|
|
1895
|
+
f.locked && /* @__PURE__ */ jsx4("span", { className: "field-list-lock", title: "Locked", children: "\u{1F512}" }),
|
|
1896
|
+
/* @__PURE__ */ jsxs4("span", { className: "field-list-page", children: [
|
|
1897
|
+
"p",
|
|
1898
|
+
f.page + 1
|
|
1899
|
+
] }),
|
|
1900
|
+
f.required && /* @__PURE__ */ jsx4("span", { className: "field-list-required", children: "*" }),
|
|
1901
|
+
isSingleSel && isTextLike && /* @__PURE__ */ jsx4(
|
|
1902
|
+
"input",
|
|
1903
|
+
{
|
|
1904
|
+
className: "field-list-input",
|
|
1905
|
+
type: "text",
|
|
1906
|
+
value: f.value,
|
|
1907
|
+
placeholder: f.placeholder,
|
|
1908
|
+
maxLength: f.maxLength || void 0,
|
|
1909
|
+
autoFocus: true,
|
|
1910
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.value }),
|
|
1911
|
+
onKeyDown: (e) => {
|
|
1912
|
+
if (e.key === "Tab") {
|
|
1913
|
+
e.preventDefault();
|
|
1914
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
1915
|
+
const nextIdx = idx + dir;
|
|
1916
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
1917
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
onClick: (e) => e.stopPropagation()
|
|
1922
|
+
}
|
|
1923
|
+
),
|
|
1924
|
+
isSingleSel && f.type === "checkbox" && /* @__PURE__ */ jsx4("label", { className: "field-list-checkbox", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx4(
|
|
1925
|
+
"input",
|
|
1926
|
+
{
|
|
1927
|
+
type: "checkbox",
|
|
1928
|
+
checked: f.value === "true",
|
|
1929
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.checked ? "true" : "" }),
|
|
1930
|
+
onKeyDown: (e) => {
|
|
1931
|
+
if (e.key === "Tab") {
|
|
1932
|
+
e.preventDefault();
|
|
1933
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
1934
|
+
const nextIdx = idx + dir;
|
|
1935
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
1936
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
) })
|
|
1942
|
+
]
|
|
1943
|
+
},
|
|
1944
|
+
f.id
|
|
1945
|
+
);
|
|
1946
|
+
}) }) })
|
|
1304
1947
|
] }),
|
|
1305
1948
|
/* @__PURE__ */ jsxs4("div", { className: "powered-by", children: [
|
|
1306
1949
|
"Powered by ",
|
|
@@ -1313,7 +1956,7 @@ function DesignerView({
|
|
|
1313
1956
|
}
|
|
1314
1957
|
|
|
1315
1958
|
// src/components/pdf-builder/SignerView.tsx
|
|
1316
|
-
import { useState as
|
|
1959
|
+
import { useState as useState7, useCallback as useCallback5, useEffect as useEffect3, useRef as useRef5 } from "react";
|
|
1317
1960
|
|
|
1318
1961
|
// src/utils/pdfFiller.ts
|
|
1319
1962
|
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
@@ -1450,7 +2093,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1450
2093
|
}
|
|
1451
2094
|
|
|
1452
2095
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1453
|
-
import { useState as
|
|
2096
|
+
import { useState as useState6 } from "react";
|
|
1454
2097
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1455
2098
|
function isFieldFilled(f) {
|
|
1456
2099
|
if (!f.required) return true;
|
|
@@ -1465,7 +2108,7 @@ function FieldNavigator({
|
|
|
1465
2108
|
onComplete,
|
|
1466
2109
|
completeLabel = "Complete"
|
|
1467
2110
|
}) {
|
|
1468
|
-
const [showIncomplete, setShowIncomplete] =
|
|
2111
|
+
const [showIncomplete, setShowIncomplete] = useState6(false);
|
|
1469
2112
|
const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
|
|
1470
2113
|
const hasPrev = currentIndex > 0;
|
|
1471
2114
|
const hasNext = currentIndex < fields.length - 1;
|
|
@@ -1630,7 +2273,7 @@ function resolveAllFormulas(fields, customTransforms) {
|
|
|
1630
2273
|
}
|
|
1631
2274
|
|
|
1632
2275
|
// src/components/pdf-builder/SignerView.tsx
|
|
1633
|
-
import { Fragment as
|
|
2276
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1634
2277
|
function SignerView({
|
|
1635
2278
|
apiKey,
|
|
1636
2279
|
initialPdfUrl,
|
|
@@ -1641,7 +2284,12 @@ function SignerView({
|
|
|
1641
2284
|
initialValues,
|
|
1642
2285
|
submitLabel,
|
|
1643
2286
|
signerOrder: signerOrderProp,
|
|
1644
|
-
transforms
|
|
2287
|
+
transforms,
|
|
2288
|
+
onChange,
|
|
2289
|
+
onSignerComplete,
|
|
2290
|
+
includeAuditTrail,
|
|
2291
|
+
exportFormat,
|
|
2292
|
+
onExport
|
|
1645
2293
|
} = {}) {
|
|
1646
2294
|
if (!isValidApiKey(apiKey)) {
|
|
1647
2295
|
return /* @__PURE__ */ jsx6("div", { className: "signer-layout", children: /* @__PURE__ */ jsxs6("div", { className: "empty-state", children: [
|
|
@@ -1653,23 +2301,24 @@ function SignerView({
|
|
|
1653
2301
|
] })
|
|
1654
2302
|
] }) });
|
|
1655
2303
|
}
|
|
1656
|
-
const [pages, setPages] =
|
|
1657
|
-
const [fields, setFields] =
|
|
1658
|
-
const [selectedFieldId, setSelectedFieldId] =
|
|
1659
|
-
const [signerRoles, setSignerRoles] =
|
|
1660
|
-
const [currentSignerIndex, setCurrentSignerIndex] =
|
|
2304
|
+
const [pages, setPages] = useState7([]);
|
|
2305
|
+
const [fields, setFields] = useState7([]);
|
|
2306
|
+
const [selectedFieldId, setSelectedFieldId] = useState7(null);
|
|
2307
|
+
const [signerRoles, setSignerRoles] = useState7([]);
|
|
2308
|
+
const [currentSignerIndex, setCurrentSignerIndex] = useState7(() => {
|
|
1661
2309
|
if (initialSigner && signerOrderProp) {
|
|
1662
2310
|
const idx = signerOrderProp.indexOf(initialSigner);
|
|
1663
2311
|
return idx >= 0 ? idx : 0;
|
|
1664
2312
|
}
|
|
1665
2313
|
return 0;
|
|
1666
2314
|
});
|
|
1667
|
-
const initializedRef =
|
|
1668
|
-
const
|
|
1669
|
-
const [
|
|
1670
|
-
const [
|
|
1671
|
-
const [
|
|
1672
|
-
const
|
|
2315
|
+
const initializedRef = useRef5(false);
|
|
2316
|
+
const auditLogRef = useRef5([]);
|
|
2317
|
+
const [loading, setLoading] = useState7(false);
|
|
2318
|
+
const [submitting, setSubmitting] = useState7(false);
|
|
2319
|
+
const [pdfSource, setPdfSource] = useState7(null);
|
|
2320
|
+
const [callbackUrl, setCallbackUrl] = useState7(initialCallbackUrl || "");
|
|
2321
|
+
const containerRef = useRef5(null);
|
|
1673
2322
|
const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
|
|
1674
2323
|
const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
|
|
1675
2324
|
const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
|
|
@@ -1759,7 +2408,7 @@ function SignerView({
|
|
|
1759
2408
|
window.addEventListener("message", handleMessage);
|
|
1760
2409
|
return () => window.removeEventListener("message", handleMessage);
|
|
1761
2410
|
}, [initialTemplate, initialPdfUrl]);
|
|
1762
|
-
const loadPdf =
|
|
2411
|
+
const loadPdf = useCallback5(async (source) => {
|
|
1763
2412
|
setLoading(true);
|
|
1764
2413
|
try {
|
|
1765
2414
|
const rendered = await renderPdfPages(source);
|
|
@@ -1779,13 +2428,22 @@ function SignerView({
|
|
|
1779
2428
|
});
|
|
1780
2429
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1781
2430
|
const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
|
|
1782
|
-
const handleFieldUpdate =
|
|
2431
|
+
const handleFieldUpdate = useCallback5((id, value) => {
|
|
1783
2432
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1784
2433
|
}, []);
|
|
1785
|
-
const handleFieldPropertyUpdate =
|
|
2434
|
+
const handleFieldPropertyUpdate = useCallback5((id, updates) => {
|
|
1786
2435
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
|
|
1787
2436
|
}, []);
|
|
1788
|
-
const
|
|
2437
|
+
const fieldValuesKey = fields.map((f) => `${f.id}:${f.value}`).join("|");
|
|
2438
|
+
useEffect3(() => {
|
|
2439
|
+
if (!onChange) return;
|
|
2440
|
+
const values = {};
|
|
2441
|
+
fields.forEach((f) => {
|
|
2442
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2443
|
+
});
|
|
2444
|
+
onChange(values);
|
|
2445
|
+
}, [fieldValuesKey]);
|
|
2446
|
+
const handleNavigate = useCallback5((fieldId) => {
|
|
1789
2447
|
setSelectedFieldId(fieldId);
|
|
1790
2448
|
const field = fields.find((f) => f.id === fieldId);
|
|
1791
2449
|
if (field && containerRef.current) {
|
|
@@ -1798,10 +2456,23 @@ function SignerView({
|
|
|
1798
2456
|
const allRequiredFilled = editableFields.every((f) => {
|
|
1799
2457
|
if (!f.required) return true;
|
|
1800
2458
|
if (f.type === "checkbox") return true;
|
|
1801
|
-
|
|
2459
|
+
if (!f.value) return false;
|
|
2460
|
+
if (f.minLength && f.value.length < f.minLength) return false;
|
|
2461
|
+
return true;
|
|
1802
2462
|
});
|
|
1803
|
-
const
|
|
2463
|
+
const getFieldValues = useCallback5(() => {
|
|
2464
|
+
const values = {};
|
|
2465
|
+
fields.forEach((f) => {
|
|
2466
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2467
|
+
});
|
|
2468
|
+
return values;
|
|
2469
|
+
}, [fields]);
|
|
2470
|
+
const handleAdvanceOrSubmit = useCallback5(async () => {
|
|
1804
2471
|
if (!allRequiredFilled) return;
|
|
2472
|
+
auditLogRef.current.push({ signer, completedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2473
|
+
if (onSignerComplete) {
|
|
2474
|
+
onSignerComplete(signer, getFieldValues());
|
|
2475
|
+
}
|
|
1805
2476
|
if (!isLastSigner) {
|
|
1806
2477
|
setCurrentSignerIndex((prev) => prev + 1);
|
|
1807
2478
|
setSelectedFieldId(null);
|
|
@@ -1815,8 +2486,40 @@ function SignerView({
|
|
|
1815
2486
|
setSubmitting(true);
|
|
1816
2487
|
try {
|
|
1817
2488
|
const finalFields = resolveAllFormulas(fields, transforms);
|
|
1818
|
-
|
|
2489
|
+
let pdfBytes;
|
|
2490
|
+
if (includeAuditTrail) {
|
|
2491
|
+
const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
|
|
2492
|
+
const basePdf = await generateFilledPdf(pdfSource, finalFields);
|
|
2493
|
+
const pdfDoc = await PDFDocument2.load(basePdf);
|
|
2494
|
+
const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
|
|
2495
|
+
const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
|
|
2496
|
+
const auditPage = pdfDoc.addPage([612, 792]);
|
|
2497
|
+
let cy = 742;
|
|
2498
|
+
auditPage.drawText("Signing Audit Trail", { x: 50, y: cy, size: 18, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2499
|
+
cy -= 30;
|
|
2500
|
+
auditPage.drawText(`Document completed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, { x: 50, y: cy, size: 10, font, color: rgb2(0.4, 0.4, 0.4) });
|
|
2501
|
+
cy -= 30;
|
|
2502
|
+
for (const entry of auditLogRef.current) {
|
|
2503
|
+
auditPage.drawText(`${entry.signer}`, { x: 50, y: cy, size: 12, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2504
|
+
auditPage.drawText(`Completed: ${new Date(entry.completedAt).toLocaleString()}`, { x: 200, y: cy, size: 10, font, color: rgb2(0.3, 0.3, 0.3) });
|
|
2505
|
+
cy -= 22;
|
|
2506
|
+
}
|
|
2507
|
+
pdfBytes = await pdfDoc.save();
|
|
2508
|
+
} else {
|
|
2509
|
+
pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
2510
|
+
}
|
|
1819
2511
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
2512
|
+
if (onExport && exportFormat) {
|
|
2513
|
+
const values = getFieldValues();
|
|
2514
|
+
if (exportFormat === "json") {
|
|
2515
|
+
onExport(JSON.stringify(values, null, 2), "json");
|
|
2516
|
+
} else {
|
|
2517
|
+
const headers = Object.keys(values);
|
|
2518
|
+
const row = headers.map((h) => `"${(values[h] || "").replace(/"/g, '""')}"`);
|
|
2519
|
+
onExport(`${headers.join(",")}
|
|
2520
|
+
${row.join(",")}`, "csv");
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
1820
2523
|
if (callbackUrl) {
|
|
1821
2524
|
await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
|
|
1822
2525
|
}
|
|
@@ -1832,8 +2535,8 @@ function SignerView({
|
|
|
1832
2535
|
} finally {
|
|
1833
2536
|
setSubmitting(false);
|
|
1834
2537
|
}
|
|
1835
|
-
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner]);
|
|
1836
|
-
const renderFieldContent =
|
|
2538
|
+
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner, signer, onSignerComplete, includeAuditTrail, exportFormat, onExport, getFieldValues, transforms]);
|
|
2539
|
+
const renderFieldContent = useCallback5((field) => {
|
|
1837
2540
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1838
2541
|
return null;
|
|
1839
2542
|
}
|
|
@@ -1970,7 +2673,7 @@ function SignerView({
|
|
|
1970
2673
|
initialValue: selectedField.value
|
|
1971
2674
|
}
|
|
1972
2675
|
),
|
|
1973
|
-
(selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(
|
|
2676
|
+
(selectedField.type === "text" || selectedField.type === "dropdown") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1974
2677
|
selectedField.type === "dropdown" || selectedField.options && selectedField.options.length > 0 ? /* @__PURE__ */ jsxs6(
|
|
1975
2678
|
"select",
|
|
1976
2679
|
{
|
|
@@ -2051,7 +2754,7 @@ function SignerView({
|
|
|
2051
2754
|
}
|
|
2052
2755
|
|
|
2053
2756
|
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
2054
|
-
import { useState as
|
|
2757
|
+
import { useState as useState8 } from "react";
|
|
2055
2758
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2056
2759
|
function SignerRoleSelector({
|
|
2057
2760
|
roles,
|
|
@@ -2060,8 +2763,8 @@ function SignerRoleSelector({
|
|
|
2060
2763
|
onAddRole,
|
|
2061
2764
|
onRemoveRole
|
|
2062
2765
|
}) {
|
|
2063
|
-
const [isAdding, setIsAdding] =
|
|
2064
|
-
const [newRoleName, setNewRoleName] =
|
|
2766
|
+
const [isAdding, setIsAdding] = useState8(false);
|
|
2767
|
+
const [newRoleName, setNewRoleName] = useState8("");
|
|
2065
2768
|
const handleAdd = () => {
|
|
2066
2769
|
if (newRoleName.trim()) {
|
|
2067
2770
|
onAddRole(newRoleName.trim());
|