@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.js
CHANGED
|
@@ -56,7 +56,7 @@ __export(lib_exports, {
|
|
|
56
56
|
module.exports = __toCommonJS(lib_exports);
|
|
57
57
|
|
|
58
58
|
// src/components/pdf-builder/DesignerView.tsx
|
|
59
|
-
var
|
|
59
|
+
var import_react5 = require("react");
|
|
60
60
|
var import_react_dom = require("react-dom");
|
|
61
61
|
|
|
62
62
|
// src/types/pdf-builder.ts
|
|
@@ -196,11 +196,14 @@ async function renderPdfPages(source, scale = 2) {
|
|
|
196
196
|
canvas.height = viewport.height;
|
|
197
197
|
const ctx = canvas.getContext("2d");
|
|
198
198
|
await page.render({ canvasContext: ctx, viewport }).promise;
|
|
199
|
+
const unscaledViewport = page.getViewport({ scale: 1 });
|
|
199
200
|
pages.push({
|
|
200
201
|
pageNumber: i,
|
|
201
202
|
dataUrl: canvas.toDataURL("image/png"),
|
|
202
203
|
width: viewport.width,
|
|
203
|
-
height: viewport.height
|
|
204
|
+
height: viewport.height,
|
|
205
|
+
pdfWidth: unscaledViewport.width,
|
|
206
|
+
pdfHeight: unscaledViewport.height
|
|
204
207
|
});
|
|
205
208
|
}
|
|
206
209
|
return pages;
|
|
@@ -219,23 +222,69 @@ function PdfViewer({
|
|
|
219
222
|
onPageClick,
|
|
220
223
|
onDropField,
|
|
221
224
|
onGroupMove,
|
|
225
|
+
onMoveEnd,
|
|
222
226
|
mode,
|
|
223
227
|
currentSigner,
|
|
224
|
-
renderFieldContent
|
|
228
|
+
renderFieldContent,
|
|
229
|
+
zoom = 1,
|
|
230
|
+
onMarqueeSelect
|
|
225
231
|
}) {
|
|
226
232
|
const containerRef = (0, import_react.useRef)(null);
|
|
227
|
-
const
|
|
233
|
+
const [guides, setGuides] = (0, import_react.useState)([]);
|
|
234
|
+
const [marquee, setMarquee] = (0, import_react.useState)(null);
|
|
235
|
+
const marqueeRef = (0, import_react.useRef)(null);
|
|
236
|
+
const handlePageMouseDown = (0, import_react.useCallback)((e, pageIndex) => {
|
|
228
237
|
const target = e.target;
|
|
229
238
|
if (target.closest(".field-overlay")) return;
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
if (mode !== "designer") return;
|
|
240
|
+
e.preventDefault();
|
|
241
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
242
|
+
const x = (e.clientX - rect.left) / rect.width * 100;
|
|
243
|
+
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
244
|
+
marqueeRef.current = { startX: x, startY: y, page: pageIndex, didDrag: false };
|
|
245
|
+
const handleMouseMove = (ev) => {
|
|
246
|
+
if (!marqueeRef.current) return;
|
|
247
|
+
const mx = (ev.clientX - rect.left) / rect.width * 100;
|
|
248
|
+
const my = (ev.clientY - rect.top) / rect.height * 100;
|
|
249
|
+
const dx = Math.abs(mx - marqueeRef.current.startX);
|
|
250
|
+
const dy = Math.abs(my - marqueeRef.current.startY);
|
|
251
|
+
if (dx > 1 || dy > 1) {
|
|
252
|
+
marqueeRef.current.didDrag = true;
|
|
253
|
+
setMarquee({
|
|
254
|
+
page: pageIndex,
|
|
255
|
+
x1: Math.min(marqueeRef.current.startX, mx),
|
|
256
|
+
y1: Math.min(marqueeRef.current.startY, my),
|
|
257
|
+
x2: Math.max(marqueeRef.current.startX, mx),
|
|
258
|
+
y2: Math.max(marqueeRef.current.startY, my)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const handleMouseUp = (ev) => {
|
|
263
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
264
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
265
|
+
if (marqueeRef.current?.didDrag && onMarqueeSelect) {
|
|
266
|
+
const mx1 = Math.min(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
267
|
+
const my1 = Math.min(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
268
|
+
const mx2 = Math.max(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
269
|
+
const my2 = Math.max(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
270
|
+
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
271
|
+
const hits = pageFields.filter((f) => {
|
|
272
|
+
return f.x < mx2 && f.x + f.width > mx1 && f.y < my2 && f.y + f.height > my1;
|
|
273
|
+
}).map((f) => f.id);
|
|
274
|
+
onMarqueeSelect(hits);
|
|
275
|
+
} else if (!marqueeRef.current?.didDrag) {
|
|
276
|
+
if (onPageClick) {
|
|
277
|
+
onPageClick(pageIndex, x, y);
|
|
278
|
+
} else {
|
|
279
|
+
onSelectField(null);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
marqueeRef.current = null;
|
|
283
|
+
setMarquee(null);
|
|
284
|
+
};
|
|
285
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
286
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
287
|
+
}, [mode, fields, onPageClick, onSelectField, onMarqueeSelect]);
|
|
239
288
|
const handleDragOver = (0, import_react.useCallback)((e) => {
|
|
240
289
|
if (e.dataTransfer.types.includes("application/exeq-field-type")) {
|
|
241
290
|
e.preventDefault();
|
|
@@ -251,14 +300,14 @@ function PdfViewer({
|
|
|
251
300
|
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
252
301
|
onDropField(pageIndex, x, y, fieldType);
|
|
253
302
|
}, [onDropField]);
|
|
254
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
|
|
303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer-zoom-wrapper", ref: containerRef, style: zoom !== 1 ? { width: `${zoom * 100}%`, minHeight: "min-content" } : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer", style: zoom !== 1 ? { transform: `scale(${zoom})`, transformOrigin: "top left", width: `${100 / zoom}%` } : void 0, children: pages.map((page, pageIndex) => {
|
|
255
304
|
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
256
305
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
257
306
|
"div",
|
|
258
307
|
{
|
|
259
308
|
className: "pdf-page",
|
|
260
309
|
style: { aspectRatio: `${page.width} / ${page.height}` },
|
|
261
|
-
|
|
310
|
+
onMouseDown: (e) => handlePageMouseDown(e, pageIndex),
|
|
262
311
|
onDragOver: handleDragOver,
|
|
263
312
|
onDrop: (e) => handleDrop(e, pageIndex),
|
|
264
313
|
"data-page": pageIndex,
|
|
@@ -282,18 +331,86 @@ function PdfViewer({
|
|
|
282
331
|
onMove: onFieldMove,
|
|
283
332
|
onResize: onFieldResize,
|
|
284
333
|
onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
|
|
334
|
+
onMoveEnd,
|
|
335
|
+
otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
|
|
336
|
+
setGuides: mode === "designer" ? setGuides : void 0,
|
|
337
|
+
pageIndex,
|
|
285
338
|
selectedIds: selectedFieldIds,
|
|
286
339
|
mode,
|
|
287
340
|
currentSigner,
|
|
288
341
|
renderContent: renderFieldContent
|
|
289
342
|
},
|
|
290
343
|
field.id
|
|
291
|
-
))
|
|
344
|
+
)),
|
|
345
|
+
guides.filter((g) => g.page === pageIndex).map((g, i) => g.x !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "snap-guide snap-guide-v", style: { left: `${g.x}%` } }, `gx${i}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "snap-guide snap-guide-h", style: { top: `${g.y}%` } }, `gy${i}`)),
|
|
346
|
+
marquee && marquee.page === pageIndex && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "marquee-rect", style: {
|
|
347
|
+
left: `${marquee.x1}%`,
|
|
348
|
+
top: `${marquee.y1}%`,
|
|
349
|
+
width: `${marquee.x2 - marquee.x1}%`,
|
|
350
|
+
height: `${marquee.y2 - marquee.y1}%`
|
|
351
|
+
} })
|
|
292
352
|
]
|
|
293
353
|
},
|
|
294
354
|
pageIndex
|
|
295
355
|
);
|
|
296
|
-
}) });
|
|
356
|
+
}) }) });
|
|
357
|
+
}
|
|
358
|
+
var SNAP_THRESHOLD = 1.2;
|
|
359
|
+
function snapAndGuide(field, others, page) {
|
|
360
|
+
const fx = field.x, fy = field.y, fw = field.width, fh = field.height;
|
|
361
|
+
const fcx = fx + fw / 2, fcy = fy + fh / 2;
|
|
362
|
+
let bestSnapX = null;
|
|
363
|
+
let bestSnapY = null;
|
|
364
|
+
for (const o of others) {
|
|
365
|
+
const ox = o.x, oy = o.y, ow = o.width, oh = o.height;
|
|
366
|
+
const ocx = ox + ow / 2, ocy = oy + oh / 2;
|
|
367
|
+
const xCandidates = [
|
|
368
|
+
[fx, ox, ox],
|
|
369
|
+
// left ↔ left
|
|
370
|
+
[fx + fw, ox + ow, ox + ow],
|
|
371
|
+
// right ↔ right
|
|
372
|
+
[fcx, ocx, ocx],
|
|
373
|
+
// center ↔ center
|
|
374
|
+
[fx, ox + ow, ox + ow],
|
|
375
|
+
// left ↔ right
|
|
376
|
+
[fx + fw, ox, ox]
|
|
377
|
+
// right ↔ left
|
|
378
|
+
];
|
|
379
|
+
for (const [fe, te, gx] of xCandidates) {
|
|
380
|
+
const d = Math.abs(fe - te);
|
|
381
|
+
if (d < SNAP_THRESHOLD && (!bestSnapX || d < bestSnapX.dist)) {
|
|
382
|
+
bestSnapX = { offset: te - fe, guideX: gx, dist: d };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const yCandidates = [
|
|
386
|
+
[fy, oy, oy],
|
|
387
|
+
// top ↔ top
|
|
388
|
+
[fy + fh, oy + oh, oy + oh],
|
|
389
|
+
// bottom ↔ bottom
|
|
390
|
+
[fcy, ocy, ocy],
|
|
391
|
+
// center ↔ center
|
|
392
|
+
[fy, oy + oh, oy + oh],
|
|
393
|
+
// top ↔ bottom
|
|
394
|
+
[fy + fh, oy, oy]
|
|
395
|
+
// bottom ↔ top
|
|
396
|
+
];
|
|
397
|
+
for (const [fe, te, gy] of yCandidates) {
|
|
398
|
+
const d = Math.abs(fe - te);
|
|
399
|
+
if (d < SNAP_THRESHOLD && (!bestSnapY || d < bestSnapY.dist)) {
|
|
400
|
+
bestSnapY = { offset: te - fe, guideY: gy, dist: d };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const snappedX = bestSnapX ? fx + bestSnapX.offset : fx;
|
|
405
|
+
const snappedY = bestSnapY ? fy + bestSnapY.offset : fy;
|
|
406
|
+
const guides = [];
|
|
407
|
+
if (bestSnapX) guides.push({ page, x: bestSnapX.guideX });
|
|
408
|
+
if (bestSnapY) guides.push({ page, y: bestSnapY.guideY });
|
|
409
|
+
return {
|
|
410
|
+
x: Math.max(0, Math.min(100 - fw, snappedX)),
|
|
411
|
+
y: Math.max(0, Math.min(100 - fh, snappedY)),
|
|
412
|
+
guides
|
|
413
|
+
};
|
|
297
414
|
}
|
|
298
415
|
function FieldOverlayItem({
|
|
299
416
|
field,
|
|
@@ -303,6 +420,10 @@ function FieldOverlayItem({
|
|
|
303
420
|
onMove,
|
|
304
421
|
onResize,
|
|
305
422
|
onGroupMove,
|
|
423
|
+
onMoveEnd,
|
|
424
|
+
otherFields,
|
|
425
|
+
setGuides,
|
|
426
|
+
pageIndex,
|
|
306
427
|
selectedIds,
|
|
307
428
|
mode,
|
|
308
429
|
currentSigner,
|
|
@@ -310,19 +431,27 @@ function FieldOverlayItem({
|
|
|
310
431
|
}) {
|
|
311
432
|
const overlayRef = (0, import_react.useRef)(null);
|
|
312
433
|
const dragStartRef = (0, import_react.useRef)(null);
|
|
434
|
+
const dragPosRef = (0, import_react.useRef)({ x: field.x, y: field.y });
|
|
435
|
+
const didDragRef = (0, import_react.useRef)(false);
|
|
313
436
|
const resizeStartRef = (0, import_react.useRef)(null);
|
|
314
437
|
const isRedact = field.type === "blackout" || field.type === "whiteout";
|
|
315
438
|
const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
|
|
316
439
|
const isInactiveSigner = mode === "signer" && !isRedact && field.assignee !== currentSigner;
|
|
317
|
-
const
|
|
440
|
+
const isFilled = mode === "signer" && isEditable && !isRedact && (field.type === "checkbox" ? true : field.formula ? true : !!field.value);
|
|
441
|
+
const color = isInactiveSigner ? "#cccccc" : isFilled ? "#22c55e" : getSignerColor(field.assignee);
|
|
318
442
|
const isPreFilled = !isEditable && !!field.value;
|
|
319
443
|
const handleMouseDown = (0, import_react.useCallback)((e) => {
|
|
320
444
|
if (mode !== "designer" || !onMove) return;
|
|
321
445
|
e.preventDefault();
|
|
322
446
|
e.stopPropagation();
|
|
323
|
-
|
|
447
|
+
const alreadyInSelection = selectedIds.has(field.id) && selectedIds.size > 1;
|
|
448
|
+
if (!alreadyInSelection) {
|
|
449
|
+
onSelect(e);
|
|
450
|
+
}
|
|
451
|
+
if (field.locked) return;
|
|
324
452
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
325
453
|
if (!pageEl) return;
|
|
454
|
+
didDragRef.current = false;
|
|
326
455
|
dragStartRef.current = {
|
|
327
456
|
startX: e.clientX,
|
|
328
457
|
startY: e.clientY,
|
|
@@ -331,6 +460,7 @@ function FieldOverlayItem({
|
|
|
331
460
|
};
|
|
332
461
|
const handleMouseMove = (ev) => {
|
|
333
462
|
if (!dragStartRef.current) return;
|
|
463
|
+
didDragRef.current = true;
|
|
334
464
|
const rect = pageEl.getBoundingClientRect();
|
|
335
465
|
const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
|
|
336
466
|
const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
|
|
@@ -341,19 +471,35 @@ function FieldOverlayItem({
|
|
|
341
471
|
} else {
|
|
342
472
|
const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
|
|
343
473
|
const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
|
|
474
|
+
dragPosRef.current = { x: newX, y: newY };
|
|
344
475
|
onMove(field.id, field.page, newX, newY);
|
|
476
|
+
if (ev.shiftKey && otherFields && setGuides && pageIndex !== void 0) {
|
|
477
|
+
const { guides: g } = snapAndGuide({ x: newX, y: newY, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
478
|
+
setGuides(g);
|
|
479
|
+
} else {
|
|
480
|
+
setGuides?.([]);
|
|
481
|
+
}
|
|
345
482
|
}
|
|
346
483
|
};
|
|
347
|
-
const handleMouseUp = () => {
|
|
484
|
+
const handleMouseUp = (ev) => {
|
|
485
|
+
if (ev.shiftKey && didDragRef.current && otherFields && onMove && pageIndex !== void 0 && !isMultiSelected) {
|
|
486
|
+
const pos = dragPosRef.current;
|
|
487
|
+
const snap = snapAndGuide({ x: pos.x, y: pos.y, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
488
|
+
if (snap.x !== pos.x || snap.y !== pos.y) {
|
|
489
|
+
onMove(field.id, field.page, snap.x, snap.y);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
348
492
|
dragStartRef.current = null;
|
|
349
493
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
350
494
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
495
|
+
setGuides?.([]);
|
|
496
|
+
onMoveEnd?.();
|
|
351
497
|
};
|
|
352
498
|
window.addEventListener("mousemove", handleMouseMove);
|
|
353
499
|
window.addEventListener("mouseup", handleMouseUp);
|
|
354
|
-
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
|
|
500
|
+
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
355
501
|
const handleResizeMouseDown = (0, import_react.useCallback)((e) => {
|
|
356
|
-
if (mode !== "designer" || !onResize) return;
|
|
502
|
+
if (mode !== "designer" || !onResize || field.locked) return;
|
|
357
503
|
e.preventDefault();
|
|
358
504
|
e.stopPropagation();
|
|
359
505
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
@@ -377,15 +523,16 @@ function FieldOverlayItem({
|
|
|
377
523
|
resizeStartRef.current = null;
|
|
378
524
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
379
525
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
526
|
+
onMoveEnd?.();
|
|
380
527
|
};
|
|
381
528
|
window.addEventListener("mousemove", handleMouseMove);
|
|
382
529
|
window.addEventListener("mouseup", handleMouseUp);
|
|
383
|
-
}, [field, mode, onResize]);
|
|
530
|
+
}, [field, mode, onResize, onMoveEnd]);
|
|
384
531
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
385
532
|
"div",
|
|
386
533
|
{
|
|
387
534
|
ref: overlayRef,
|
|
388
|
-
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""}`,
|
|
535
|
+
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""} ${field.locked ? "locked" : ""}`,
|
|
389
536
|
style: {
|
|
390
537
|
left: `${field.x}%`,
|
|
391
538
|
top: `${field.y}%`,
|
|
@@ -394,18 +541,18 @@ function FieldOverlayItem({
|
|
|
394
541
|
borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
|
|
395
542
|
borderStyle: isRedact ? "dashed" : "solid",
|
|
396
543
|
backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isInactiveSigner ? "#f5f5f5" : isSelected ? `${color}22` : `${color}11`,
|
|
397
|
-
cursor: mode === "designer" ? "
|
|
544
|
+
cursor: mode === "designer" ? field.locked ? "not-allowed" : "move" : "default",
|
|
398
545
|
pointerEvents: mode === "signer" && (isRedact || isInactiveSigner) ? "none" : void 0
|
|
399
546
|
},
|
|
400
547
|
onClick: (e) => {
|
|
401
548
|
e.stopPropagation();
|
|
402
|
-
onSelect(e);
|
|
549
|
+
if (!didDragRef.current) onSelect(e);
|
|
403
550
|
},
|
|
404
551
|
onMouseDown: handleMouseDown,
|
|
405
552
|
children: [
|
|
406
553
|
mode === "designer" && !isRedact && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
407
554
|
renderContent ? renderContent(field) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
|
|
408
|
-
mode === "designer" && isSelected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
555
|
+
mode === "designer" && isSelected && !field.locked && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
409
556
|
"div",
|
|
410
557
|
{
|
|
411
558
|
className: "field-resize-handle",
|
|
@@ -424,7 +571,7 @@ var INK_COLORS = [
|
|
|
424
571
|
{ value: "#000000", label: "Black" },
|
|
425
572
|
{ value: "#1a56db", label: "Blue" }
|
|
426
573
|
];
|
|
427
|
-
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
574
|
+
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillContent, pageSize }) {
|
|
428
575
|
const color = getSignerColor(field.assignee);
|
|
429
576
|
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
430
577
|
const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
|
|
@@ -432,113 +579,306 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
432
579
|
const [newOption, setNewOption] = (0, import_react2.useState)("");
|
|
433
580
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
434
581
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
|
|
435
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: { color }, children: field.label }),
|
|
436
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onDelete(field.id), className: "panel-delete-btn", children: "Delete" })
|
|
437
|
-
] }),
|
|
438
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
439
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Label" }),
|
|
440
582
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
441
583
|
"input",
|
|
442
584
|
{
|
|
443
|
-
|
|
585
|
+
className: "panel-header-label",
|
|
586
|
+
style: { color },
|
|
444
587
|
value: field.label,
|
|
445
588
|
onChange: (e) => onUpdate(field.id, { label: e.target.value })
|
|
446
589
|
}
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
)
|
|
466
|
-
] }),
|
|
467
|
-
field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
468
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Text Type" }),
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
470
|
-
"select",
|
|
471
|
-
{
|
|
472
|
-
value: field.textSubtype || "freeform",
|
|
473
|
-
onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }),
|
|
474
|
-
children: [
|
|
475
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "freeform", children: "Freeform" }),
|
|
476
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "number", children: "Number" }),
|
|
477
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "date", children: "Date" }),
|
|
478
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "email", children: "Email" }),
|
|
479
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "phone", children: "Phone" })
|
|
480
|
-
]
|
|
481
|
-
}
|
|
482
|
-
)
|
|
590
|
+
),
|
|
591
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header-actions", children: [
|
|
592
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
593
|
+
"button",
|
|
594
|
+
{
|
|
595
|
+
onClick: () => onUpdate(field.id, { locked: !field.locked }),
|
|
596
|
+
className: `panel-icon-btn ${field.locked ? "active" : ""}`,
|
|
597
|
+
title: field.locked ? "Unlock" : "Lock",
|
|
598
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: field.locked ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0V6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
|
|
601
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4.5 6V4.5a2.5 2.5 0 015 0", fill: "none", stroke: "currentColor", strokeWidth: "1.5" })
|
|
604
|
+
] }) })
|
|
605
|
+
}
|
|
606
|
+
),
|
|
607
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onDelete(field.id), className: "panel-icon-btn panel-icon-btn-danger", title: "Delete", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) }) })
|
|
608
|
+
] })
|
|
483
609
|
] }),
|
|
484
|
-
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-
|
|
485
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
610
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Assigned To" }),
|
|
486
612
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
487
613
|
"select",
|
|
488
614
|
{
|
|
615
|
+
className: "panel-assignee-select",
|
|
489
616
|
value: field.assignee,
|
|
490
617
|
onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
|
|
618
|
+
style: { borderColor: color, color },
|
|
491
619
|
children: signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
492
620
|
}
|
|
493
621
|
)
|
|
494
622
|
] }),
|
|
495
|
-
|
|
496
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
497
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
498
|
-
"
|
|
499
|
-
{
|
|
500
|
-
|
|
501
|
-
value:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
623
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Content" }),
|
|
625
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
626
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Field Type" }),
|
|
627
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: field.type, onChange: (e) => onUpdate(field.id, { type: e.target.value }), children: [
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "text", children: "Text" }),
|
|
629
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "dropdown", children: "Dropdown" }),
|
|
630
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signature", children: "Signature" }),
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signed-date", children: "Signed Date" }),
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "checkbox", children: "Checkbox" }),
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "initials", children: "Initials" })
|
|
634
|
+
] })
|
|
635
|
+
] }),
|
|
636
|
+
field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Text Type" }),
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: field.textSubtype || "freeform", onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }), children: [
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "freeform", children: "Freeform" }),
|
|
640
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "number", children: "Number" }),
|
|
641
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "date", children: "Date" }),
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "email", children: "Email" }),
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "phone", children: "Phone" })
|
|
644
|
+
] })
|
|
645
|
+
] }),
|
|
646
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Placeholder" }),
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: field.placeholder, onChange: (e) => onUpdate(field.id, { placeholder: e.target.value }) })
|
|
649
|
+
] }),
|
|
650
|
+
!isRedactField && field.type !== "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: field.required, onChange: (e) => onUpdate(field.id, { required: e.target.checked }) }),
|
|
652
|
+
"Required"
|
|
653
|
+
] }) }),
|
|
654
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
655
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Formula" }),
|
|
656
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: field.formula || "", onChange: (e) => onUpdate(field.id, { formula: e.target.value || void 0 }), placeholder: "e.g. {{Date Field | month}}" }),
|
|
657
|
+
field.formula && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Auto-computed. Signer cannot edit." })
|
|
658
|
+
] }),
|
|
659
|
+
field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Min Chars" }),
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "0", max: "9999", value: field.minLength || 0, onChange: (e) => onUpdate(field.id, { minLength: Number(e.target.value) }) })
|
|
663
|
+
] }),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Max Chars" }),
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "0", max: "9999", value: field.maxLength || 0, onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) }) })
|
|
667
|
+
] })
|
|
668
|
+
] }),
|
|
669
|
+
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
670
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
671
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-options-list", children: [
|
|
672
|
+
(field.options || []).map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-item", children: [
|
|
673
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: opt }),
|
|
674
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "panel-option-remove", onClick: () => {
|
|
675
|
+
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
676
|
+
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
677
|
+
}, children: "\xD7" })
|
|
678
|
+
] }, i)),
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-add", children: [
|
|
680
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: newOption, onChange: (e) => setNewOption(e.target.value), onKeyDown: (e) => {
|
|
681
|
+
if (e.key === "Enter" && newOption.trim()) {
|
|
682
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
683
|
+
setNewOption("");
|
|
684
|
+
}
|
|
685
|
+
}, placeholder: "Add option..." }),
|
|
686
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => {
|
|
687
|
+
if (newOption.trim()) {
|
|
688
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
689
|
+
setNewOption("");
|
|
690
|
+
}
|
|
691
|
+
}, children: "+" })
|
|
692
|
+
] })
|
|
693
|
+
] }),
|
|
694
|
+
field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
|
|
695
|
+
field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
|
|
696
|
+
] }),
|
|
697
|
+
prefillContent
|
|
505
698
|
] }),
|
|
506
|
-
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-
|
|
507
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
508
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
509
|
-
"
|
|
699
|
+
(isTextField || showInkColor) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Style" }),
|
|
701
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font" }),
|
|
703
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { value: field.fontFamily || "Helvetica", onChange: (e) => onUpdate(field.id, { fontFamily: e.target.value }), children: FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: f.value, children: f.label }, f.value)) })
|
|
704
|
+
] }),
|
|
705
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
706
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
707
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "6", max: "72", value: field.fontSize, onChange: (e) => onUpdate(field.id, { fontSize: Number(e.target.value) }) })
|
|
708
|
+
] }),
|
|
709
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
710
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
|
|
712
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "number", min: "0", max: "20", step: "0.5", value: field.letterSpacing || 0, onChange: (e) => onUpdate(field.id, { letterSpacing: Number(e.target.value) }) })
|
|
713
|
+
] }),
|
|
714
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
715
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Line Height" }),
|
|
716
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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) }) })
|
|
717
|
+
] })
|
|
718
|
+
] }),
|
|
719
|
+
showInkColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
721
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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)) })
|
|
722
|
+
] })
|
|
723
|
+
] }),
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Position & Size" }),
|
|
726
|
+
(() => {
|
|
727
|
+
const pw = pageSize?.width || 612;
|
|
728
|
+
const ph = pageSize?.height || 792;
|
|
729
|
+
const toPt = (pct, dim) => Math.round(pct / 100 * dim * 10) / 10;
|
|
730
|
+
const fromPt = (pt, dim) => pt / dim * 100;
|
|
731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "X (pt)" }),
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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) }) })
|
|
736
|
+
] }),
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Y (pt)" }),
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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) }) })
|
|
740
|
+
] })
|
|
741
|
+
] }),
|
|
742
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Width (pt)" }),
|
|
745
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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) }) })
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
748
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Height (pt)" }),
|
|
749
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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) }) })
|
|
750
|
+
] })
|
|
751
|
+
] }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-hint", children: [
|
|
753
|
+
pw,
|
|
754
|
+
" \xD7 ",
|
|
755
|
+
ph,
|
|
756
|
+
" pt \xB7 Page ",
|
|
757
|
+
field.page + 1,
|
|
758
|
+
" \xB7 ",
|
|
759
|
+
field.id.slice(0, 8)
|
|
760
|
+
] })
|
|
761
|
+
] });
|
|
762
|
+
})()
|
|
763
|
+
] })
|
|
764
|
+
] });
|
|
765
|
+
}
|
|
766
|
+
function sharedValue(items) {
|
|
767
|
+
if (items.length === 0) return void 0;
|
|
768
|
+
return items.every((v) => v === items[0]) ? items[0] : void 0;
|
|
769
|
+
}
|
|
770
|
+
function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete }) {
|
|
771
|
+
const count = fields.length;
|
|
772
|
+
const hasText = fields.some((f) => f.type === "text" || f.type === "dropdown" || f.type === "signed-date");
|
|
773
|
+
const allNonRedact = fields.every((f) => f.type !== "blackout" && f.type !== "whiteout");
|
|
774
|
+
const sharedAssignee = sharedValue(fields.map((f) => f.assignee));
|
|
775
|
+
const sharedRequired = sharedValue(fields.map((f) => f.required));
|
|
776
|
+
const sharedFontSize = sharedValue(fields.map((f) => f.fontSize));
|
|
777
|
+
const sharedFontFamily = sharedValue(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
778
|
+
const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
|
|
779
|
+
const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
|
|
780
|
+
const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
|
|
781
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
782
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
|
|
783
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
|
|
784
|
+
count,
|
|
785
|
+
" fields selected"
|
|
786
|
+
] }),
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: onDelete, className: "panel-delete-btn", children: "Delete All" })
|
|
788
|
+
] }),
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Align" }),
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "align-buttons", children: [
|
|
792
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("left"), title: "Align left edges", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "1", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
796
|
+
] }) }),
|
|
797
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("center-h"), title: "Align centers horizontally", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "6.25", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "10", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3.5", y: "7", width: "7", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
801
|
+
] }) }),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("right"), title: "Align right edges", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "11.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "5", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
806
|
+
] }) }),
|
|
807
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("top"), title: "Align top edges", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "1", width: "14", height: "1.5", fill: "currentColor" }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "4", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
810
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "4", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
811
|
+
] }) }),
|
|
812
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("center-v"), title: "Align centers vertically", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
813
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "6.25", width: "14", height: "1.5", fill: "currentColor" }),
|
|
814
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "1", width: "3", height: "12", fill: "currentColor", opacity: "0.5" }),
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "3", width: "3", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
816
|
+
] }) }),
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("bottom"), title: "Align bottom edges", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "11.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "5", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
821
|
+
] }) })
|
|
822
|
+
] }),
|
|
823
|
+
count > 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { style: { marginTop: "0.4rem" }, children: "Distribute" }),
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "align-buttons", children: [
|
|
826
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("distribute-h"), title: "Distribute horizontally", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "12.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "5", y: "3", width: "4", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
830
|
+
] }) }),
|
|
831
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => onAlign("distribute-v"), title: "Distribute vertically", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", children: [
|
|
832
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "0", width: "14", height: "1.5", fill: "currentColor" }),
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "12.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "5", width: "8", height: "4", fill: "currentColor", opacity: "0.5" })
|
|
835
|
+
] }) })
|
|
836
|
+
] })
|
|
837
|
+
] })
|
|
838
|
+
] }),
|
|
839
|
+
allNonRedact && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Assigned To" }),
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
842
|
+
"select",
|
|
510
843
|
{
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
844
|
+
value: sharedAssignee || "",
|
|
845
|
+
onChange: (e) => onUpdate({ assignee: e.target.value }),
|
|
846
|
+
children: [
|
|
847
|
+
!sharedAssignee && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", disabled: true, children: "Mixed" }),
|
|
848
|
+
signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
849
|
+
]
|
|
515
850
|
}
|
|
516
|
-
)
|
|
517
|
-
field.formula && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "This field auto-computes from other fields. Signer cannot edit it." })
|
|
851
|
+
)
|
|
518
852
|
] }),
|
|
519
|
-
|
|
853
|
+
allNonRedact && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
520
854
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
521
855
|
"input",
|
|
522
856
|
{
|
|
523
857
|
type: "checkbox",
|
|
524
|
-
checked:
|
|
525
|
-
|
|
858
|
+
checked: sharedRequired ?? false,
|
|
859
|
+
ref: (el) => {
|
|
860
|
+
if (el) el.indeterminate = sharedRequired === void 0;
|
|
861
|
+
},
|
|
862
|
+
onChange: (e) => onUpdate({ required: e.target.checked })
|
|
526
863
|
}
|
|
527
864
|
),
|
|
528
865
|
"Required"
|
|
529
866
|
] }) }),
|
|
530
|
-
|
|
867
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
531
868
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font" }),
|
|
532
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
533
870
|
"select",
|
|
534
871
|
{
|
|
535
|
-
value:
|
|
536
|
-
onChange: (e) => onUpdate(
|
|
537
|
-
children:
|
|
872
|
+
value: sharedFontFamily || "",
|
|
873
|
+
onChange: (e) => onUpdate({ fontFamily: e.target.value }),
|
|
874
|
+
children: [
|
|
875
|
+
!sharedFontFamily && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", disabled: true, children: "Mixed" }),
|
|
876
|
+
FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: f.value, children: f.label }, f.value))
|
|
877
|
+
]
|
|
538
878
|
}
|
|
539
879
|
)
|
|
540
880
|
] }),
|
|
541
|
-
|
|
881
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
542
882
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
543
883
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
544
884
|
"input",
|
|
@@ -546,12 +886,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
546
886
|
type: "number",
|
|
547
887
|
min: "6",
|
|
548
888
|
max: "72",
|
|
549
|
-
value:
|
|
550
|
-
|
|
889
|
+
value: sharedFontSize ?? "",
|
|
890
|
+
placeholder: "Mixed",
|
|
891
|
+
onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
|
|
551
892
|
}
|
|
552
893
|
)
|
|
553
894
|
] }),
|
|
554
|
-
|
|
895
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
555
896
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
556
897
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
|
|
557
898
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -561,8 +902,9 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
561
902
|
min: "0",
|
|
562
903
|
max: "20",
|
|
563
904
|
step: "0.5",
|
|
564
|
-
value:
|
|
565
|
-
|
|
905
|
+
value: sharedLetterSpacing ?? "",
|
|
906
|
+
placeholder: "Mixed",
|
|
907
|
+
onChange: (e) => onUpdate({ letterSpacing: Number(e.target.value) })
|
|
566
908
|
}
|
|
567
909
|
)
|
|
568
910
|
] }),
|
|
@@ -575,87 +917,25 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
575
917
|
min: "0.8",
|
|
576
918
|
max: "3",
|
|
577
919
|
step: "0.1",
|
|
578
|
-
value:
|
|
579
|
-
|
|
920
|
+
value: sharedLineHeight ?? "",
|
|
921
|
+
placeholder: "Mixed",
|
|
922
|
+
onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
|
|
580
923
|
}
|
|
581
924
|
)
|
|
582
925
|
] })
|
|
583
926
|
] }),
|
|
584
|
-
|
|
585
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Max Characters (0 = unlimited)" }),
|
|
586
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
587
|
-
"input",
|
|
588
|
-
{
|
|
589
|
-
type: "number",
|
|
590
|
-
min: "0",
|
|
591
|
-
max: "9999",
|
|
592
|
-
value: field.maxLength || 0,
|
|
593
|
-
onChange: (e) => onUpdate(field.id, { maxLength: Number(e.target.value) })
|
|
594
|
-
}
|
|
595
|
-
)
|
|
596
|
-
] }),
|
|
597
|
-
showInkColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
927
|
+
allNonRedact && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
598
928
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
599
929
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
600
930
|
"button",
|
|
601
931
|
{
|
|
602
|
-
className: `ink-color-swatch ${
|
|
932
|
+
className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
|
|
603
933
|
style: { backgroundColor: c.value },
|
|
604
|
-
onClick: () => onUpdate(
|
|
934
|
+
onClick: () => onUpdate({ inkColor: c.value }),
|
|
605
935
|
title: c.label
|
|
606
936
|
},
|
|
607
937
|
c.value
|
|
608
938
|
)) })
|
|
609
|
-
] }),
|
|
610
|
-
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
611
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
612
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-options-list", children: [
|
|
613
|
-
(field.options || []).map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-item", children: [
|
|
614
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: opt }),
|
|
615
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
616
|
-
"button",
|
|
617
|
-
{
|
|
618
|
-
className: "panel-option-remove",
|
|
619
|
-
onClick: () => {
|
|
620
|
-
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
621
|
-
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
622
|
-
},
|
|
623
|
-
children: "\xD7"
|
|
624
|
-
}
|
|
625
|
-
)
|
|
626
|
-
] }, i)),
|
|
627
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-add", children: [
|
|
628
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
629
|
-
"input",
|
|
630
|
-
{
|
|
631
|
-
type: "text",
|
|
632
|
-
value: newOption,
|
|
633
|
-
onChange: (e) => setNewOption(e.target.value),
|
|
634
|
-
onKeyDown: (e) => {
|
|
635
|
-
if (e.key === "Enter" && newOption.trim()) {
|
|
636
|
-
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
637
|
-
setNewOption("");
|
|
638
|
-
}
|
|
639
|
-
},
|
|
640
|
-
placeholder: "Add option..."
|
|
641
|
-
}
|
|
642
|
-
),
|
|
643
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
644
|
-
"button",
|
|
645
|
-
{
|
|
646
|
-
onClick: () => {
|
|
647
|
-
if (newOption.trim()) {
|
|
648
|
-
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
649
|
-
setNewOption("");
|
|
650
|
-
}
|
|
651
|
-
},
|
|
652
|
-
children: "+"
|
|
653
|
-
}
|
|
654
|
-
)
|
|
655
|
-
] })
|
|
656
|
-
] }),
|
|
657
|
-
field.type === "dropdown" && (field.options?.length ?? 0) === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", style: { color: "#c44" }, children: "Add at least one option" }),
|
|
658
|
-
field.type === "text" && (field.options?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Signer will choose from these options via dropdown" })
|
|
659
939
|
] })
|
|
660
940
|
] });
|
|
661
941
|
}
|
|
@@ -800,6 +1080,75 @@ function isValidApiKey(key) {
|
|
|
800
1080
|
return VALID_KEYS.has(key);
|
|
801
1081
|
}
|
|
802
1082
|
|
|
1083
|
+
// src/hooks/useHistory.ts
|
|
1084
|
+
var import_react4 = require("react");
|
|
1085
|
+
function useHistory(initialState, maxHistory = 50) {
|
|
1086
|
+
const [state, setState] = (0, import_react4.useState)({
|
|
1087
|
+
past: [],
|
|
1088
|
+
present: initialState,
|
|
1089
|
+
future: []
|
|
1090
|
+
});
|
|
1091
|
+
const skipRef = (0, import_react4.useRef)(false);
|
|
1092
|
+
const set = (0, import_react4.useCallback)((updater) => {
|
|
1093
|
+
setState((prev) => {
|
|
1094
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1095
|
+
if (newPresent === prev.present) return prev;
|
|
1096
|
+
if (skipRef.current) {
|
|
1097
|
+
skipRef.current = false;
|
|
1098
|
+
return { ...prev, present: newPresent };
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
past: [...prev.past.slice(-maxHistory), prev.present],
|
|
1102
|
+
present: newPresent,
|
|
1103
|
+
future: []
|
|
1104
|
+
};
|
|
1105
|
+
});
|
|
1106
|
+
}, [maxHistory]);
|
|
1107
|
+
const setWithoutHistory = (0, import_react4.useCallback)((updater) => {
|
|
1108
|
+
skipRef.current = true;
|
|
1109
|
+
set(updater);
|
|
1110
|
+
}, [set]);
|
|
1111
|
+
const snapshot = (0, import_react4.useCallback)(() => {
|
|
1112
|
+
setState((prev) => ({
|
|
1113
|
+
past: [...prev.past.slice(-maxHistory), prev.present],
|
|
1114
|
+
present: prev.present,
|
|
1115
|
+
future: []
|
|
1116
|
+
}));
|
|
1117
|
+
}, [maxHistory]);
|
|
1118
|
+
const undo = (0, import_react4.useCallback)(() => {
|
|
1119
|
+
setState((prev) => {
|
|
1120
|
+
if (prev.past.length === 0) return prev;
|
|
1121
|
+
const previous = prev.past[prev.past.length - 1];
|
|
1122
|
+
return {
|
|
1123
|
+
past: prev.past.slice(0, -1),
|
|
1124
|
+
present: previous,
|
|
1125
|
+
future: [prev.present, ...prev.future]
|
|
1126
|
+
};
|
|
1127
|
+
});
|
|
1128
|
+
}, []);
|
|
1129
|
+
const redo = (0, import_react4.useCallback)(() => {
|
|
1130
|
+
setState((prev) => {
|
|
1131
|
+
if (prev.future.length === 0) return prev;
|
|
1132
|
+
const next = prev.future[0];
|
|
1133
|
+
return {
|
|
1134
|
+
past: [...prev.past, prev.present],
|
|
1135
|
+
present: next,
|
|
1136
|
+
future: prev.future.slice(1)
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
}, []);
|
|
1140
|
+
return {
|
|
1141
|
+
state: state.present,
|
|
1142
|
+
set,
|
|
1143
|
+
setWithoutHistory,
|
|
1144
|
+
snapshot,
|
|
1145
|
+
undo,
|
|
1146
|
+
redo,
|
|
1147
|
+
canUndo: state.past.length > 0,
|
|
1148
|
+
canRedo: state.future.length > 0
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
803
1152
|
// src/components/pdf-builder/DesignerView.tsx
|
|
804
1153
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
805
1154
|
var FIELD_TYPE_META = [
|
|
@@ -830,24 +1179,28 @@ function DesignerView({
|
|
|
830
1179
|
] })
|
|
831
1180
|
] }) });
|
|
832
1181
|
}
|
|
833
|
-
const [pages, setPages] = (0,
|
|
834
|
-
const
|
|
835
|
-
const [selectedFieldIds, setSelectedFieldIds] = (0,
|
|
836
|
-
const [signerRoles, setSignerRoles] = (0,
|
|
837
|
-
const [activeRole, setActiveRole] = (0,
|
|
838
|
-
const [activeFieldType, setActiveFieldType] = (0,
|
|
839
|
-
const [loading, setLoading] = (0,
|
|
840
|
-
const [pdfSource, setPdfSource] = (0,
|
|
841
|
-
const [rightTab, setRightTab] = (0,
|
|
842
|
-
const [isAddingRole, setIsAddingRole] = (0,
|
|
843
|
-
const [newRoleName, setNewRoleName] = (0,
|
|
844
|
-
const [draggingFieldType, setDraggingFieldType] = (0,
|
|
845
|
-
const [panelWidth, setPanelWidth] = (0,
|
|
846
|
-
const [
|
|
847
|
-
const
|
|
848
|
-
const
|
|
849
|
-
const
|
|
850
|
-
(0,
|
|
1182
|
+
const [pages, setPages] = (0, import_react5.useState)([]);
|
|
1183
|
+
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, snapshot: snapshotFields, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
|
|
1184
|
+
const [selectedFieldIds, setSelectedFieldIds] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
1185
|
+
const [signerRoles, setSignerRoles] = (0, import_react5.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
1186
|
+
const [activeRole, setActiveRole] = (0, import_react5.useState)("Sender");
|
|
1187
|
+
const [activeFieldType, setActiveFieldType] = (0, import_react5.useState)("text");
|
|
1188
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
1189
|
+
const [pdfSource, setPdfSource] = (0, import_react5.useState)(null);
|
|
1190
|
+
const [rightTab, setRightTab] = (0, import_react5.useState)("properties");
|
|
1191
|
+
const [isAddingRole, setIsAddingRole] = (0, import_react5.useState)(false);
|
|
1192
|
+
const [newRoleName, setNewRoleName] = (0, import_react5.useState)("");
|
|
1193
|
+
const [draggingFieldType, setDraggingFieldType] = (0, import_react5.useState)(null);
|
|
1194
|
+
const [panelWidth, setPanelWidth] = (0, import_react5.useState)(380);
|
|
1195
|
+
const [zoom, setZoom] = (0, import_react5.useState)(1);
|
|
1196
|
+
const [isPanning, setIsPanning] = (0, import_react5.useState)(false);
|
|
1197
|
+
const panRef = (0, import_react5.useRef)(null);
|
|
1198
|
+
const pdfAreaRef = (0, import_react5.useRef)(null);
|
|
1199
|
+
const [clipboardFields, setClipboardFields] = (0, import_react5.useState)([]);
|
|
1200
|
+
const dragGhostRef = (0, import_react5.useRef)(null);
|
|
1201
|
+
const resizingRef = (0, import_react5.useRef)(false);
|
|
1202
|
+
const lastStylesRef = (0, import_react5.useRef)({});
|
|
1203
|
+
(0, import_react5.useEffect)(() => {
|
|
851
1204
|
const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
|
|
852
1205
|
if (pdfUrl) {
|
|
853
1206
|
loadPdf(pdfUrl);
|
|
@@ -871,7 +1224,7 @@ function DesignerView({
|
|
|
871
1224
|
window.addEventListener("message", handleMessage);
|
|
872
1225
|
return () => window.removeEventListener("message", handleMessage);
|
|
873
1226
|
}, [initialPdfUrl, initialTemplate]);
|
|
874
|
-
const loadPdf = (0,
|
|
1227
|
+
const loadPdf = (0, import_react5.useCallback)(async (source) => {
|
|
875
1228
|
setLoading(true);
|
|
876
1229
|
try {
|
|
877
1230
|
const rendered = await renderPdfPages(source);
|
|
@@ -883,7 +1236,7 @@ function DesignerView({
|
|
|
883
1236
|
setLoading(false);
|
|
884
1237
|
}
|
|
885
1238
|
}, []);
|
|
886
|
-
const handleFileUpload = (0,
|
|
1239
|
+
const handleFileUpload = (0, import_react5.useCallback)((e) => {
|
|
887
1240
|
const file = e.target.files?.[0];
|
|
888
1241
|
if (!file) return;
|
|
889
1242
|
const reader = new FileReader();
|
|
@@ -892,7 +1245,7 @@ function DesignerView({
|
|
|
892
1245
|
};
|
|
893
1246
|
reader.readAsArrayBuffer(file);
|
|
894
1247
|
}, [loadPdf]);
|
|
895
|
-
const handlePageClick = (0,
|
|
1248
|
+
const handlePageClick = (0, import_react5.useCallback)((page, x, y) => {
|
|
896
1249
|
const field = createField(activeFieldType, activeRole, page, x, y, fields);
|
|
897
1250
|
const sticky = lastStylesRef.current[activeFieldType];
|
|
898
1251
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -900,11 +1253,14 @@ function DesignerView({
|
|
|
900
1253
|
setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
|
|
901
1254
|
setRightTab("properties");
|
|
902
1255
|
}, [activeFieldType, activeRole, fields]);
|
|
903
|
-
const handleFieldMove = (0,
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1256
|
+
const handleFieldMove = (0, import_react5.useCallback)((id, page, x, y) => {
|
|
1257
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1258
|
+
if (f.id !== id || f.locked) return f;
|
|
1259
|
+
return { ...f, page, x, y };
|
|
1260
|
+
}));
|
|
1261
|
+
}, [setFieldsSilent]);
|
|
1262
|
+
const handleFieldResize = (0, import_react5.useCallback)((id, width, height) => {
|
|
1263
|
+
setFieldsSilent((prev) => {
|
|
908
1264
|
const field = prev.find((f) => f.id === id);
|
|
909
1265
|
if (field) {
|
|
910
1266
|
const existing = lastStylesRef.current[field.type] || {};
|
|
@@ -912,8 +1268,8 @@ function DesignerView({
|
|
|
912
1268
|
}
|
|
913
1269
|
return prev.map((f) => f.id === id ? { ...f, width, height } : f);
|
|
914
1270
|
});
|
|
915
|
-
}, []);
|
|
916
|
-
const handleFieldUpdate = (0,
|
|
1271
|
+
}, [setFieldsSilent]);
|
|
1272
|
+
const handleFieldUpdate = (0, import_react5.useCallback)((id, updates) => {
|
|
917
1273
|
setFields((prev) => {
|
|
918
1274
|
if (updates.label !== void 0) {
|
|
919
1275
|
const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
|
|
@@ -933,7 +1289,75 @@ function DesignerView({
|
|
|
933
1289
|
return updated;
|
|
934
1290
|
});
|
|
935
1291
|
}, []);
|
|
936
|
-
const
|
|
1292
|
+
const handleBulkUpdate = (0, import_react5.useCallback)((updates) => {
|
|
1293
|
+
setFields((prev) => prev.map((f) => selectedFieldIds.has(f.id) ? { ...f, ...updates } : f));
|
|
1294
|
+
}, [selectedFieldIds]);
|
|
1295
|
+
const handleAlign = (0, import_react5.useCallback)((type) => {
|
|
1296
|
+
setFields((prev) => {
|
|
1297
|
+
const selected = prev.filter((f) => selectedFieldIds.has(f.id));
|
|
1298
|
+
if (selected.length < 2) return prev;
|
|
1299
|
+
let updates = {};
|
|
1300
|
+
if (type === "left") {
|
|
1301
|
+
const minX = Math.min(...selected.map((f) => f.x));
|
|
1302
|
+
selected.forEach((f) => {
|
|
1303
|
+
updates[f.id] = { x: minX };
|
|
1304
|
+
});
|
|
1305
|
+
} else if (type === "right") {
|
|
1306
|
+
const maxRight = Math.max(...selected.map((f) => f.x + f.width));
|
|
1307
|
+
selected.forEach((f) => {
|
|
1308
|
+
updates[f.id] = { x: maxRight - f.width };
|
|
1309
|
+
});
|
|
1310
|
+
} else if (type === "top") {
|
|
1311
|
+
const minY = Math.min(...selected.map((f) => f.y));
|
|
1312
|
+
selected.forEach((f) => {
|
|
1313
|
+
updates[f.id] = { y: minY };
|
|
1314
|
+
});
|
|
1315
|
+
} else if (type === "bottom") {
|
|
1316
|
+
const maxBottom = Math.max(...selected.map((f) => f.y + f.height));
|
|
1317
|
+
selected.forEach((f) => {
|
|
1318
|
+
updates[f.id] = { y: maxBottom - f.height };
|
|
1319
|
+
});
|
|
1320
|
+
} else if (type === "center-h") {
|
|
1321
|
+
const centers = selected.map((f) => f.x + f.width / 2);
|
|
1322
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1323
|
+
selected.forEach((f) => {
|
|
1324
|
+
updates[f.id] = { x: avgCenter - f.width / 2 };
|
|
1325
|
+
});
|
|
1326
|
+
} else if (type === "center-v") {
|
|
1327
|
+
const centers = selected.map((f) => f.y + f.height / 2);
|
|
1328
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1329
|
+
selected.forEach((f) => {
|
|
1330
|
+
updates[f.id] = { y: avgCenter - f.height / 2 };
|
|
1331
|
+
});
|
|
1332
|
+
} else if (type === "distribute-h") {
|
|
1333
|
+
const sorted = [...selected].sort((a, b) => a.x - b.x);
|
|
1334
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1335
|
+
const totalSpan = last.x + last.width - first.x;
|
|
1336
|
+
const totalFieldWidth = sorted.reduce((s, f) => s + f.width, 0);
|
|
1337
|
+
const gap = (totalSpan - totalFieldWidth) / (sorted.length - 1);
|
|
1338
|
+
let cx = first.x;
|
|
1339
|
+
sorted.forEach((f) => {
|
|
1340
|
+
updates[f.id] = { x: cx };
|
|
1341
|
+
cx += f.width + gap;
|
|
1342
|
+
});
|
|
1343
|
+
} else if (type === "distribute-v") {
|
|
1344
|
+
const sorted = [...selected].sort((a, b) => a.y - b.y);
|
|
1345
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1346
|
+
const totalSpan = last.y + last.height - first.y;
|
|
1347
|
+
const totalFieldHeight = sorted.reduce((s, f) => s + f.height, 0);
|
|
1348
|
+
const gap = (totalSpan - totalFieldHeight) / (sorted.length - 1);
|
|
1349
|
+
let cy = first.y;
|
|
1350
|
+
sorted.forEach((f) => {
|
|
1351
|
+
updates[f.id] = { y: cy };
|
|
1352
|
+
cy += f.height + gap;
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
return prev.map((f) => updates[f.id] ? { ...f, ...updates[f.id] } : f);
|
|
1356
|
+
});
|
|
1357
|
+
}, [selectedFieldIds]);
|
|
1358
|
+
const handleFieldDelete = (0, import_react5.useCallback)((id) => {
|
|
1359
|
+
const field = fields.find((f) => f.id === id);
|
|
1360
|
+
if (field?.locked) return;
|
|
937
1361
|
setFields((prev) => prev.filter((f) => f.id !== id));
|
|
938
1362
|
setSelectedFieldIds((prev) => {
|
|
939
1363
|
const next = new Set(prev);
|
|
@@ -941,7 +1365,7 @@ function DesignerView({
|
|
|
941
1365
|
return next;
|
|
942
1366
|
});
|
|
943
1367
|
}, []);
|
|
944
|
-
const handleSelectField = (0,
|
|
1368
|
+
const handleSelectField = (0, import_react5.useCallback)((id, e) => {
|
|
945
1369
|
if (!id) {
|
|
946
1370
|
setSelectedFieldIds(/* @__PURE__ */ new Set());
|
|
947
1371
|
return;
|
|
@@ -975,15 +1399,15 @@ function DesignerView({
|
|
|
975
1399
|
}
|
|
976
1400
|
setRightTab("properties");
|
|
977
1401
|
}, [selectedFieldIds]);
|
|
978
|
-
const handleGroupMove = (0,
|
|
979
|
-
|
|
980
|
-
if (!ids.includes(f.id)) return f;
|
|
1402
|
+
const handleGroupMove = (0, import_react5.useCallback)((ids, dx, dy) => {
|
|
1403
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1404
|
+
if (!ids.includes(f.id) || f.locked) return f;
|
|
981
1405
|
const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
|
|
982
1406
|
const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
|
|
983
1407
|
return { ...f, x: newX, y: newY };
|
|
984
1408
|
}));
|
|
985
|
-
}, []);
|
|
986
|
-
const handleAddRole = (0,
|
|
1409
|
+
}, [setFieldsSilent]);
|
|
1410
|
+
const handleAddRole = (0, import_react5.useCallback)(() => {
|
|
987
1411
|
const name = newRoleName.trim();
|
|
988
1412
|
if (name && !signerRoles.includes(name)) {
|
|
989
1413
|
setSignerRoles((prev) => [...prev, name]);
|
|
@@ -991,12 +1415,12 @@ function DesignerView({
|
|
|
991
1415
|
setNewRoleName("");
|
|
992
1416
|
setIsAddingRole(false);
|
|
993
1417
|
}, [newRoleName, signerRoles]);
|
|
994
|
-
const handleRemoveRole = (0,
|
|
1418
|
+
const handleRemoveRole = (0, import_react5.useCallback)((role) => {
|
|
995
1419
|
setSignerRoles((prev) => prev.filter((r) => r !== role));
|
|
996
1420
|
setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
|
|
997
1421
|
if (activeRole === role) setActiveRole(signerRoles[0]);
|
|
998
1422
|
}, [signerRoles, activeRole]);
|
|
999
|
-
const handleExport = (0,
|
|
1423
|
+
const handleExport = (0, import_react5.useCallback)(() => {
|
|
1000
1424
|
const template = {
|
|
1001
1425
|
fields,
|
|
1002
1426
|
signerRoles,
|
|
@@ -1016,7 +1440,7 @@ function DesignerView({
|
|
|
1016
1440
|
}
|
|
1017
1441
|
window.parent?.postMessage({ type: "template-saved", template }, "*");
|
|
1018
1442
|
}, [fields, signerRoles, pdfSource, onSave]);
|
|
1019
|
-
const handlePaletteDragStart = (0,
|
|
1443
|
+
const handlePaletteDragStart = (0, import_react5.useCallback)((e, type) => {
|
|
1020
1444
|
setDraggingFieldType(type);
|
|
1021
1445
|
e.dataTransfer.setData("application/exeq-field-type", type);
|
|
1022
1446
|
e.dataTransfer.effectAllowed = "copy";
|
|
@@ -1033,14 +1457,14 @@ function DesignerView({
|
|
|
1033
1457
|
e.dataTransfer.setDragImage(ghost, 40, 16);
|
|
1034
1458
|
dragGhostRef.current = ghost;
|
|
1035
1459
|
}, [activeRole]);
|
|
1036
|
-
const handlePaletteDragEnd = (0,
|
|
1460
|
+
const handlePaletteDragEnd = (0, import_react5.useCallback)(() => {
|
|
1037
1461
|
setDraggingFieldType(null);
|
|
1038
1462
|
if (dragGhostRef.current) {
|
|
1039
1463
|
document.body.removeChild(dragGhostRef.current);
|
|
1040
1464
|
dragGhostRef.current = null;
|
|
1041
1465
|
}
|
|
1042
1466
|
}, []);
|
|
1043
|
-
const handleDropOnPage = (0,
|
|
1467
|
+
const handleDropOnPage = (0, import_react5.useCallback)((page, x, y, fieldType) => {
|
|
1044
1468
|
const field = createField(fieldType, activeRole, page, x, y, fields);
|
|
1045
1469
|
const sticky = lastStylesRef.current[fieldType];
|
|
1046
1470
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -1048,7 +1472,7 @@ function DesignerView({
|
|
|
1048
1472
|
setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
|
|
1049
1473
|
setRightTab("properties");
|
|
1050
1474
|
}, [activeRole, fields]);
|
|
1051
|
-
(0,
|
|
1475
|
+
(0, import_react5.useEffect)(() => {
|
|
1052
1476
|
const handleKeyDown = (e) => {
|
|
1053
1477
|
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
|
1054
1478
|
if (selectedFieldIds.size === 0) return;
|
|
@@ -1056,21 +1480,39 @@ function DesignerView({
|
|
|
1056
1480
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1057
1481
|
if (document.activeElement?.isContentEditable) return;
|
|
1058
1482
|
e.preventDefault();
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1483
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1484
|
+
setSelectedFieldIds((prev) => {
|
|
1485
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1486
|
+
fields.forEach((f) => {
|
|
1487
|
+
if (selectedFieldIds.has(f.id) && f.locked) remaining.add(f.id);
|
|
1488
|
+
});
|
|
1489
|
+
return remaining;
|
|
1490
|
+
});
|
|
1064
1491
|
};
|
|
1065
1492
|
window.addEventListener("keydown", handleKeyDown);
|
|
1066
1493
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1067
|
-
}, [selectedFieldIds]);
|
|
1068
|
-
(0,
|
|
1494
|
+
}, [selectedFieldIds, fields]);
|
|
1495
|
+
(0, import_react5.useEffect)(() => {
|
|
1069
1496
|
const handleKeyDown = (e) => {
|
|
1070
1497
|
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1071
1498
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1072
1499
|
if (document.activeElement?.isContentEditable) return;
|
|
1073
1500
|
const isMod = e.metaKey || e.ctrlKey;
|
|
1501
|
+
if (isMod && e.key === "a") {
|
|
1502
|
+
e.preventDefault();
|
|
1503
|
+
setSelectedFieldIds(new Set(fields.map((f) => f.id)));
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
if (isMod && e.key === "z" && !e.shiftKey) {
|
|
1507
|
+
e.preventDefault();
|
|
1508
|
+
undoFields();
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
if (isMod && (e.key === "y" || e.key === "z" && e.shiftKey)) {
|
|
1512
|
+
e.preventDefault();
|
|
1513
|
+
redoFields();
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1074
1516
|
if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
|
|
1075
1517
|
const selected = fields.filter((f) => selectedFieldIds.has(f.id));
|
|
1076
1518
|
if (selected.length > 0) {
|
|
@@ -1097,7 +1539,75 @@ function DesignerView({
|
|
|
1097
1539
|
window.addEventListener("keydown", handleKeyDown);
|
|
1098
1540
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1099
1541
|
}, [selectedFieldIds, fields, clipboardFields]);
|
|
1100
|
-
|
|
1542
|
+
(0, import_react5.useEffect)(() => {
|
|
1543
|
+
const handleKeyDown = (e) => {
|
|
1544
|
+
const isMod = e.metaKey || e.ctrlKey;
|
|
1545
|
+
if (isMod && (e.key === "=" || e.key === "+")) {
|
|
1546
|
+
e.preventDefault();
|
|
1547
|
+
setZoom((z) => Math.min(3, z + 0.25));
|
|
1548
|
+
}
|
|
1549
|
+
if (isMod && e.key === "-") {
|
|
1550
|
+
e.preventDefault();
|
|
1551
|
+
setZoom((z) => Math.max(0.75, z - 0.25));
|
|
1552
|
+
}
|
|
1553
|
+
if (isMod && e.key === "0") {
|
|
1554
|
+
e.preventDefault();
|
|
1555
|
+
setZoom(1);
|
|
1556
|
+
}
|
|
1557
|
+
if (e.key === " ") {
|
|
1558
|
+
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1559
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1560
|
+
e.preventDefault();
|
|
1561
|
+
e.stopPropagation();
|
|
1562
|
+
setIsPanning(true);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
const handleKeyUp = (e) => {
|
|
1566
|
+
if (e.key === " ") {
|
|
1567
|
+
e.preventDefault();
|
|
1568
|
+
setIsPanning(false);
|
|
1569
|
+
panRef.current = null;
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
window.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
1573
|
+
window.addEventListener("keyup", handleKeyUp, { capture: true });
|
|
1574
|
+
return () => {
|
|
1575
|
+
window.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
1576
|
+
window.removeEventListener("keyup", handleKeyUp, { capture: true });
|
|
1577
|
+
};
|
|
1578
|
+
}, []);
|
|
1579
|
+
(0, import_react5.useEffect)(() => {
|
|
1580
|
+
const el = pdfAreaRef.current;
|
|
1581
|
+
if (!el) return;
|
|
1582
|
+
const handleWheel = (e) => {
|
|
1583
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1584
|
+
e.preventDefault();
|
|
1585
|
+
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
1586
|
+
setZoom((z) => Math.max(0.75, Math.min(3, z + delta)));
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
el.addEventListener("wheel", handleWheel, { passive: false });
|
|
1590
|
+
return () => el.removeEventListener("wheel", handleWheel);
|
|
1591
|
+
}, [pages.length]);
|
|
1592
|
+
const handlePanMouseDown = (0, import_react5.useCallback)((e) => {
|
|
1593
|
+
if (!isPanning || !pdfAreaRef.current) return;
|
|
1594
|
+
e.preventDefault();
|
|
1595
|
+
panRef.current = {
|
|
1596
|
+
startX: e.clientX,
|
|
1597
|
+
startY: e.clientY,
|
|
1598
|
+
scrollLeft: pdfAreaRef.current.scrollLeft,
|
|
1599
|
+
scrollTop: pdfAreaRef.current.scrollTop
|
|
1600
|
+
};
|
|
1601
|
+
}, [isPanning]);
|
|
1602
|
+
const handlePanMouseMove = (0, import_react5.useCallback)((e) => {
|
|
1603
|
+
if (!panRef.current || !pdfAreaRef.current) return;
|
|
1604
|
+
pdfAreaRef.current.scrollLeft = panRef.current.scrollLeft - (e.clientX - panRef.current.startX);
|
|
1605
|
+
pdfAreaRef.current.scrollTop = panRef.current.scrollTop - (e.clientY - panRef.current.startY);
|
|
1606
|
+
}, []);
|
|
1607
|
+
const handlePanMouseUp = (0, import_react5.useCallback)(() => {
|
|
1608
|
+
panRef.current = null;
|
|
1609
|
+
}, []);
|
|
1610
|
+
const handleResizeStart = (0, import_react5.useCallback)((e) => {
|
|
1101
1611
|
e.preventDefault();
|
|
1102
1612
|
resizingRef.current = true;
|
|
1103
1613
|
const startX = e.clientX;
|
|
@@ -1122,7 +1632,7 @@ function DesignerView({
|
|
|
1122
1632
|
return a.x - b.x;
|
|
1123
1633
|
});
|
|
1124
1634
|
const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
|
|
1125
|
-
const renderFieldContent = (0,
|
|
1635
|
+
const renderFieldContent = (0, import_react5.useCallback)((field) => {
|
|
1126
1636
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1127
1637
|
return null;
|
|
1128
1638
|
}
|
|
@@ -1226,39 +1736,91 @@ function DesignerView({
|
|
|
1226
1736
|
type
|
|
1227
1737
|
))
|
|
1228
1738
|
] }),
|
|
1229
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1740
|
+
"div",
|
|
1741
|
+
{
|
|
1742
|
+
className: `designer-pdf-area ${isPanning ? "panning" : ""}`,
|
|
1743
|
+
ref: pdfAreaRef,
|
|
1744
|
+
onMouseDown: isPanning ? handlePanMouseDown : void 0,
|
|
1745
|
+
onMouseMove: isPanning ? handlePanMouseMove : void 0,
|
|
1746
|
+
onMouseUp: isPanning ? handlePanMouseUp : void 0,
|
|
1747
|
+
onMouseLeave: isPanning ? handlePanMouseUp : void 0,
|
|
1748
|
+
children: [
|
|
1749
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
1750
|
+
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
1751
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Open a PDF to get started" }),
|
|
1752
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
1754
|
+
"Select PDF",
|
|
1755
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
1756
|
+
] })
|
|
1757
|
+
] }),
|
|
1758
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1759
|
+
PdfViewer,
|
|
1760
|
+
{
|
|
1761
|
+
pages,
|
|
1762
|
+
fields,
|
|
1763
|
+
selectedFieldIds,
|
|
1764
|
+
onSelectField: handleSelectField,
|
|
1765
|
+
onFieldMove: handleFieldMove,
|
|
1766
|
+
onFieldResize: handleFieldResize,
|
|
1767
|
+
onGroupMove: handleGroupMove,
|
|
1768
|
+
onMoveEnd: snapshotFields,
|
|
1769
|
+
onPageClick: handlePageClick,
|
|
1770
|
+
onDropField: handleDropOnPage,
|
|
1771
|
+
onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
|
|
1772
|
+
mode: "designer",
|
|
1773
|
+
renderFieldContent,
|
|
1774
|
+
zoom
|
|
1775
|
+
}
|
|
1776
|
+
)
|
|
1777
|
+
]
|
|
1778
|
+
}
|
|
1779
|
+
),
|
|
1256
1780
|
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1257
1781
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
|
|
1258
1782
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-panel", style: { width: panelWidth }, children: [
|
|
1259
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "
|
|
1260
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.
|
|
1261
|
-
|
|
1783
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-panel-header", children: [
|
|
1784
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "signer-role-indicator", children: [
|
|
1785
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "signer-role-indicator-label", children: "Editing as" }),
|
|
1786
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: activeRole })
|
|
1787
|
+
] }),
|
|
1788
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "zoom-slider", children: [
|
|
1789
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setZoom((z) => Math.max(0.75, z - 0.25)), disabled: zoom <= 0.75, children: "\u2212" }),
|
|
1790
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1791
|
+
"input",
|
|
1792
|
+
{
|
|
1793
|
+
type: "range",
|
|
1794
|
+
min: "75",
|
|
1795
|
+
max: "300",
|
|
1796
|
+
step: "25",
|
|
1797
|
+
value: Math.round(zoom * 100),
|
|
1798
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100)
|
|
1799
|
+
}
|
|
1800
|
+
),
|
|
1801
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setZoom((z) => Math.min(3, z + 0.25)), disabled: zoom >= 3, children: "+" }),
|
|
1802
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "zoom-dropdown-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1803
|
+
"select",
|
|
1804
|
+
{
|
|
1805
|
+
className: "zoom-dropdown",
|
|
1806
|
+
value: Math.round(zoom * 100),
|
|
1807
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100),
|
|
1808
|
+
children: [
|
|
1809
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "75", children: "75%" }),
|
|
1810
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "100", children: "100%" }),
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "125", children: "125%" }),
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "150", children: "150%" }),
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "200", children: "200%" }),
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "250", children: "250%" }),
|
|
1815
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "300", children: "300%" }),
|
|
1816
|
+
![75, 100, 125, 150, 200, 250, 300].includes(Math.round(zoom * 100)) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: Math.round(zoom * 100), children: [
|
|
1817
|
+
Math.round(zoom * 100),
|
|
1818
|
+
"%"
|
|
1819
|
+
] })
|
|
1820
|
+
]
|
|
1821
|
+
}
|
|
1822
|
+
) })
|
|
1823
|
+
] })
|
|
1262
1824
|
] }),
|
|
1263
1825
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tabs", children: [
|
|
1264
1826
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -1282,82 +1844,163 @@ function DesignerView({
|
|
|
1282
1844
|
)
|
|
1283
1845
|
] }),
|
|
1284
1846
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tab-content", children: [
|
|
1285
|
-
rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1847
|
+
rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1848
|
+
BulkPropertyPanel,
|
|
1849
|
+
{
|
|
1850
|
+
fields: fields.filter((f) => selectedFieldIds.has(f.id)),
|
|
1851
|
+
signerRoles,
|
|
1852
|
+
onUpdate: handleBulkUpdate,
|
|
1853
|
+
onAlign: handleAlign,
|
|
1854
|
+
onDelete: () => {
|
|
1855
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1856
|
+
setSelectedFieldIds((prev) => {
|
|
1857
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1858
|
+
fields.forEach((f) => {
|
|
1859
|
+
if (prev.has(f.id) && f.locked) remaining.add(f.id);
|
|
1860
|
+
});
|
|
1861
|
+
return remaining;
|
|
1862
|
+
});
|
|
1299
1863
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1314
|
-
"input",
|
|
1864
|
+
}
|
|
1865
|
+
) : selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1866
|
+
FieldPropertyPanel,
|
|
1867
|
+
{
|
|
1868
|
+
field: selectedField,
|
|
1869
|
+
signerRoles,
|
|
1870
|
+
onUpdate: handleFieldUpdate,
|
|
1871
|
+
onDelete: handleFieldDelete,
|
|
1872
|
+
pageSize: pages[selectedField.page] ? { width: pages[selectedField.page].pdfWidth, height: pages[selectedField.page].pdfHeight } : void 0,
|
|
1873
|
+
prefillContent: selectedField.type !== "blackout" && selectedField.type !== "whiteout" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "prefill-section", children: [
|
|
1874
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Pre-fill Value" }),
|
|
1875
|
+
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1876
|
+
SignatureCanvas,
|
|
1315
1877
|
{
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1878
|
+
width: panelWidth - 40,
|
|
1879
|
+
height: selectedField.type === "initials" ? 100 : 150,
|
|
1880
|
+
onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
|
|
1881
|
+
initialValue: selectedField.value,
|
|
1882
|
+
inkColor: selectedField.inkColor
|
|
1319
1883
|
}
|
|
1320
|
-
),
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
)
|
|
1333
|
-
] })
|
|
1334
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1335
|
-
rightTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1336
|
-
"div",
|
|
1337
|
-
{
|
|
1338
|
-
className: `field-list-item ${selectedFieldIds.has(f.id) ? "active" : ""}`,
|
|
1339
|
-
onClick: () => {
|
|
1340
|
-
setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
|
|
1341
|
-
setRightTab("properties");
|
|
1342
|
-
},
|
|
1343
|
-
children: [
|
|
1344
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1345
|
-
"span",
|
|
1884
|
+
) : selectedField.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
1885
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1886
|
+
"input",
|
|
1887
|
+
{
|
|
1888
|
+
type: "checkbox",
|
|
1889
|
+
checked: selectedField.value === "true",
|
|
1890
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
1891
|
+
}
|
|
1892
|
+
),
|
|
1893
|
+
"Checked"
|
|
1894
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1895
|
+
"input",
|
|
1346
1896
|
{
|
|
1347
|
-
|
|
1348
|
-
|
|
1897
|
+
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1898
|
+
value: selectedField.value,
|
|
1899
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
|
|
1900
|
+
placeholder: `Pre-fill ${selectedField.label}`,
|
|
1901
|
+
className: "prefill-input",
|
|
1902
|
+
style: { color: selectedField.inkColor || "#000000" }
|
|
1349
1903
|
}
|
|
1350
|
-
)
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1904
|
+
)
|
|
1905
|
+
] }) : void 0
|
|
1906
|
+
}
|
|
1907
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1908
|
+
rightTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("div", { className: "field-list", children: sortedFields.map((f, idx) => {
|
|
1909
|
+
const isSel = selectedFieldIds.has(f.id);
|
|
1910
|
+
const isSingleSel = isSel && selectedFieldIds.size === 1;
|
|
1911
|
+
const isTextLike = f.type === "text" || f.type === "dropdown" || f.type === "signed-date";
|
|
1912
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1913
|
+
"div",
|
|
1914
|
+
{
|
|
1915
|
+
className: `field-list-item ${isSel ? "active" : ""}`,
|
|
1916
|
+
onClick: (e) => {
|
|
1917
|
+
if (e.target.tagName === "INPUT") return;
|
|
1918
|
+
if (e.metaKey || e.ctrlKey) {
|
|
1919
|
+
setSelectedFieldIds((prev) => {
|
|
1920
|
+
const next = new Set(prev);
|
|
1921
|
+
if (next.has(f.id)) next.delete(f.id);
|
|
1922
|
+
else next.add(f.id);
|
|
1923
|
+
return next;
|
|
1924
|
+
});
|
|
1925
|
+
} else if (e.shiftKey && selectedFieldIds.size > 0) {
|
|
1926
|
+
const ids = sortedFields.map((sf) => sf.id);
|
|
1927
|
+
const lastId = Array.from(selectedFieldIds).pop();
|
|
1928
|
+
const a = ids.indexOf(lastId);
|
|
1929
|
+
const b = ids.indexOf(f.id);
|
|
1930
|
+
if (a >= 0 && b >= 0) {
|
|
1931
|
+
const start = Math.min(a, b);
|
|
1932
|
+
const end = Math.max(a, b);
|
|
1933
|
+
setSelectedFieldIds((prev) => {
|
|
1934
|
+
const next = new Set(prev);
|
|
1935
|
+
ids.slice(start, end + 1).forEach((id) => next.add(id));
|
|
1936
|
+
return next;
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
} else {
|
|
1940
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
children: [
|
|
1944
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1945
|
+
"span",
|
|
1946
|
+
{
|
|
1947
|
+
className: "field-list-dot",
|
|
1948
|
+
style: { backgroundColor: getSignerColor(f.assignee) }
|
|
1949
|
+
}
|
|
1950
|
+
),
|
|
1951
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-name", children: f.label }),
|
|
1952
|
+
f.locked && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-lock", title: "Locked", children: "\u{1F512}" }),
|
|
1953
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "field-list-page", children: [
|
|
1954
|
+
"p",
|
|
1955
|
+
f.page + 1
|
|
1956
|
+
] }),
|
|
1957
|
+
f.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-required", children: "*" }),
|
|
1958
|
+
isSingleSel && isTextLike && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1959
|
+
"input",
|
|
1960
|
+
{
|
|
1961
|
+
className: "field-list-input",
|
|
1962
|
+
type: "text",
|
|
1963
|
+
value: f.value,
|
|
1964
|
+
placeholder: f.placeholder,
|
|
1965
|
+
maxLength: f.maxLength || void 0,
|
|
1966
|
+
autoFocus: true,
|
|
1967
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.value }),
|
|
1968
|
+
onKeyDown: (e) => {
|
|
1969
|
+
if (e.key === "Tab") {
|
|
1970
|
+
e.preventDefault();
|
|
1971
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
1972
|
+
const nextIdx = idx + dir;
|
|
1973
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
1974
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
},
|
|
1978
|
+
onClick: (e) => e.stopPropagation()
|
|
1979
|
+
}
|
|
1980
|
+
),
|
|
1981
|
+
isSingleSel && f.type === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "field-list-checkbox", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1982
|
+
"input",
|
|
1983
|
+
{
|
|
1984
|
+
type: "checkbox",
|
|
1985
|
+
checked: f.value === "true",
|
|
1986
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.checked ? "true" : "" }),
|
|
1987
|
+
onKeyDown: (e) => {
|
|
1988
|
+
if (e.key === "Tab") {
|
|
1989
|
+
e.preventDefault();
|
|
1990
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
1991
|
+
const nextIdx = idx + dir;
|
|
1992
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
1993
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
) })
|
|
1999
|
+
]
|
|
2000
|
+
},
|
|
2001
|
+
f.id
|
|
2002
|
+
);
|
|
2003
|
+
}) }) })
|
|
1361
2004
|
] }),
|
|
1362
2005
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "powered-by", children: [
|
|
1363
2006
|
"Powered by ",
|
|
@@ -1370,7 +2013,7 @@ function DesignerView({
|
|
|
1370
2013
|
}
|
|
1371
2014
|
|
|
1372
2015
|
// src/components/pdf-builder/SignerView.tsx
|
|
1373
|
-
var
|
|
2016
|
+
var import_react7 = require("react");
|
|
1374
2017
|
|
|
1375
2018
|
// src/utils/pdfFiller.ts
|
|
1376
2019
|
var import_pdf_lib = require("pdf-lib");
|
|
@@ -1507,7 +2150,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1507
2150
|
}
|
|
1508
2151
|
|
|
1509
2152
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1510
|
-
var
|
|
2153
|
+
var import_react6 = require("react");
|
|
1511
2154
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1512
2155
|
function isFieldFilled(f) {
|
|
1513
2156
|
if (!f.required) return true;
|
|
@@ -1522,7 +2165,7 @@ function FieldNavigator({
|
|
|
1522
2165
|
onComplete,
|
|
1523
2166
|
completeLabel = "Complete"
|
|
1524
2167
|
}) {
|
|
1525
|
-
const [showIncomplete, setShowIncomplete] = (0,
|
|
2168
|
+
const [showIncomplete, setShowIncomplete] = (0, import_react6.useState)(false);
|
|
1526
2169
|
const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
|
|
1527
2170
|
const hasPrev = currentIndex > 0;
|
|
1528
2171
|
const hasNext = currentIndex < fields.length - 1;
|
|
@@ -1698,7 +2341,12 @@ function SignerView({
|
|
|
1698
2341
|
initialValues,
|
|
1699
2342
|
submitLabel,
|
|
1700
2343
|
signerOrder: signerOrderProp,
|
|
1701
|
-
transforms
|
|
2344
|
+
transforms,
|
|
2345
|
+
onChange,
|
|
2346
|
+
onSignerComplete,
|
|
2347
|
+
includeAuditTrail,
|
|
2348
|
+
exportFormat,
|
|
2349
|
+
onExport
|
|
1702
2350
|
} = {}) {
|
|
1703
2351
|
if (!isValidApiKey(apiKey)) {
|
|
1704
2352
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "empty-state", children: [
|
|
@@ -1710,28 +2358,29 @@ function SignerView({
|
|
|
1710
2358
|
] })
|
|
1711
2359
|
] }) });
|
|
1712
2360
|
}
|
|
1713
|
-
const [pages, setPages] = (0,
|
|
1714
|
-
const [fields, setFields] = (0,
|
|
1715
|
-
const [selectedFieldId, setSelectedFieldId] = (0,
|
|
1716
|
-
const [signerRoles, setSignerRoles] = (0,
|
|
1717
|
-
const [currentSignerIndex, setCurrentSignerIndex] = (0,
|
|
2361
|
+
const [pages, setPages] = (0, import_react7.useState)([]);
|
|
2362
|
+
const [fields, setFields] = (0, import_react7.useState)([]);
|
|
2363
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react7.useState)(null);
|
|
2364
|
+
const [signerRoles, setSignerRoles] = (0, import_react7.useState)([]);
|
|
2365
|
+
const [currentSignerIndex, setCurrentSignerIndex] = (0, import_react7.useState)(() => {
|
|
1718
2366
|
if (initialSigner && signerOrderProp) {
|
|
1719
2367
|
const idx = signerOrderProp.indexOf(initialSigner);
|
|
1720
2368
|
return idx >= 0 ? idx : 0;
|
|
1721
2369
|
}
|
|
1722
2370
|
return 0;
|
|
1723
2371
|
});
|
|
1724
|
-
const initializedRef = (0,
|
|
1725
|
-
const
|
|
1726
|
-
const [
|
|
1727
|
-
const [
|
|
1728
|
-
const [
|
|
1729
|
-
const
|
|
2372
|
+
const initializedRef = (0, import_react7.useRef)(false);
|
|
2373
|
+
const auditLogRef = (0, import_react7.useRef)([]);
|
|
2374
|
+
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
2375
|
+
const [submitting, setSubmitting] = (0, import_react7.useState)(false);
|
|
2376
|
+
const [pdfSource, setPdfSource] = (0, import_react7.useState)(null);
|
|
2377
|
+
const [callbackUrl, setCallbackUrl] = (0, import_react7.useState)(initialCallbackUrl || "");
|
|
2378
|
+
const containerRef = (0, import_react7.useRef)(null);
|
|
1730
2379
|
const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
|
|
1731
2380
|
const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
|
|
1732
2381
|
const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
|
|
1733
2382
|
const isMultiSigner = signerOrder.length > 1;
|
|
1734
|
-
(0,
|
|
2383
|
+
(0, import_react7.useEffect)(() => {
|
|
1735
2384
|
if (fields.length === 0 || !isMultiSigner) return;
|
|
1736
2385
|
const signerFields = fields.filter(
|
|
1737
2386
|
(f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout"
|
|
@@ -1746,7 +2395,7 @@ function SignerView({
|
|
|
1746
2395
|
setSelectedFieldId(null);
|
|
1747
2396
|
}
|
|
1748
2397
|
}, [currentSignerIndex, fields, signer, isLastSigner, isMultiSigner]);
|
|
1749
|
-
(0,
|
|
2398
|
+
(0, import_react7.useEffect)(() => {
|
|
1750
2399
|
if (initialTemplate) {
|
|
1751
2400
|
let templateFields = initialTemplate.fields;
|
|
1752
2401
|
if (initialTemplate.signerRoles) setSignerRoles(initialTemplate.signerRoles);
|
|
@@ -1816,7 +2465,7 @@ function SignerView({
|
|
|
1816
2465
|
window.addEventListener("message", handleMessage);
|
|
1817
2466
|
return () => window.removeEventListener("message", handleMessage);
|
|
1818
2467
|
}, [initialTemplate, initialPdfUrl]);
|
|
1819
|
-
const loadPdf = (0,
|
|
2468
|
+
const loadPdf = (0, import_react7.useCallback)(async (source) => {
|
|
1820
2469
|
setLoading(true);
|
|
1821
2470
|
try {
|
|
1822
2471
|
const rendered = await renderPdfPages(source);
|
|
@@ -1836,13 +2485,22 @@ function SignerView({
|
|
|
1836
2485
|
});
|
|
1837
2486
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1838
2487
|
const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
|
|
1839
|
-
const handleFieldUpdate = (0,
|
|
2488
|
+
const handleFieldUpdate = (0, import_react7.useCallback)((id, value) => {
|
|
1840
2489
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1841
2490
|
}, []);
|
|
1842
|
-
const handleFieldPropertyUpdate = (0,
|
|
2491
|
+
const handleFieldPropertyUpdate = (0, import_react7.useCallback)((id, updates) => {
|
|
1843
2492
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
|
|
1844
2493
|
}, []);
|
|
1845
|
-
const
|
|
2494
|
+
const fieldValuesKey = fields.map((f) => `${f.id}:${f.value}`).join("|");
|
|
2495
|
+
(0, import_react7.useEffect)(() => {
|
|
2496
|
+
if (!onChange) return;
|
|
2497
|
+
const values = {};
|
|
2498
|
+
fields.forEach((f) => {
|
|
2499
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2500
|
+
});
|
|
2501
|
+
onChange(values);
|
|
2502
|
+
}, [fieldValuesKey]);
|
|
2503
|
+
const handleNavigate = (0, import_react7.useCallback)((fieldId) => {
|
|
1846
2504
|
setSelectedFieldId(fieldId);
|
|
1847
2505
|
const field = fields.find((f) => f.id === fieldId);
|
|
1848
2506
|
if (field && containerRef.current) {
|
|
@@ -1855,10 +2513,23 @@ function SignerView({
|
|
|
1855
2513
|
const allRequiredFilled = editableFields.every((f) => {
|
|
1856
2514
|
if (!f.required) return true;
|
|
1857
2515
|
if (f.type === "checkbox") return true;
|
|
1858
|
-
|
|
2516
|
+
if (!f.value) return false;
|
|
2517
|
+
if (f.minLength && f.value.length < f.minLength) return false;
|
|
2518
|
+
return true;
|
|
1859
2519
|
});
|
|
1860
|
-
const
|
|
2520
|
+
const getFieldValues = (0, import_react7.useCallback)(() => {
|
|
2521
|
+
const values = {};
|
|
2522
|
+
fields.forEach((f) => {
|
|
2523
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2524
|
+
});
|
|
2525
|
+
return values;
|
|
2526
|
+
}, [fields]);
|
|
2527
|
+
const handleAdvanceOrSubmit = (0, import_react7.useCallback)(async () => {
|
|
1861
2528
|
if (!allRequiredFilled) return;
|
|
2529
|
+
auditLogRef.current.push({ signer, completedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2530
|
+
if (onSignerComplete) {
|
|
2531
|
+
onSignerComplete(signer, getFieldValues());
|
|
2532
|
+
}
|
|
1862
2533
|
if (!isLastSigner) {
|
|
1863
2534
|
setCurrentSignerIndex((prev) => prev + 1);
|
|
1864
2535
|
setSelectedFieldId(null);
|
|
@@ -1872,8 +2543,40 @@ function SignerView({
|
|
|
1872
2543
|
setSubmitting(true);
|
|
1873
2544
|
try {
|
|
1874
2545
|
const finalFields = resolveAllFormulas(fields, transforms);
|
|
1875
|
-
|
|
2546
|
+
let pdfBytes;
|
|
2547
|
+
if (includeAuditTrail) {
|
|
2548
|
+
const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
|
|
2549
|
+
const basePdf = await generateFilledPdf(pdfSource, finalFields);
|
|
2550
|
+
const pdfDoc = await PDFDocument2.load(basePdf);
|
|
2551
|
+
const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
|
|
2552
|
+
const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
|
|
2553
|
+
const auditPage = pdfDoc.addPage([612, 792]);
|
|
2554
|
+
let cy = 742;
|
|
2555
|
+
auditPage.drawText("Signing Audit Trail", { x: 50, y: cy, size: 18, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2556
|
+
cy -= 30;
|
|
2557
|
+
auditPage.drawText(`Document completed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, { x: 50, y: cy, size: 10, font, color: rgb2(0.4, 0.4, 0.4) });
|
|
2558
|
+
cy -= 30;
|
|
2559
|
+
for (const entry of auditLogRef.current) {
|
|
2560
|
+
auditPage.drawText(`${entry.signer}`, { x: 50, y: cy, size: 12, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2561
|
+
auditPage.drawText(`Completed: ${new Date(entry.completedAt).toLocaleString()}`, { x: 200, y: cy, size: 10, font, color: rgb2(0.3, 0.3, 0.3) });
|
|
2562
|
+
cy -= 22;
|
|
2563
|
+
}
|
|
2564
|
+
pdfBytes = await pdfDoc.save();
|
|
2565
|
+
} else {
|
|
2566
|
+
pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
2567
|
+
}
|
|
1876
2568
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
2569
|
+
if (onExport && exportFormat) {
|
|
2570
|
+
const values = getFieldValues();
|
|
2571
|
+
if (exportFormat === "json") {
|
|
2572
|
+
onExport(JSON.stringify(values, null, 2), "json");
|
|
2573
|
+
} else {
|
|
2574
|
+
const headers = Object.keys(values);
|
|
2575
|
+
const row = headers.map((h) => `"${(values[h] || "").replace(/"/g, '""')}"`);
|
|
2576
|
+
onExport(`${headers.join(",")}
|
|
2577
|
+
${row.join(",")}`, "csv");
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
1877
2580
|
if (callbackUrl) {
|
|
1878
2581
|
await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
|
|
1879
2582
|
}
|
|
@@ -1889,8 +2592,8 @@ function SignerView({
|
|
|
1889
2592
|
} finally {
|
|
1890
2593
|
setSubmitting(false);
|
|
1891
2594
|
}
|
|
1892
|
-
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner]);
|
|
1893
|
-
const renderFieldContent = (0,
|
|
2595
|
+
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner, signer, onSignerComplete, includeAuditTrail, exportFormat, onExport, getFieldValues, transforms]);
|
|
2596
|
+
const renderFieldContent = (0, import_react7.useCallback)((field) => {
|
|
1894
2597
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1895
2598
|
return null;
|
|
1896
2599
|
}
|
|
@@ -1967,7 +2670,7 @@ function SignerView({
|
|
|
1967
2670
|
}
|
|
1968
2671
|
);
|
|
1969
2672
|
}, [signer, handleFieldUpdate, setSelectedFieldId]);
|
|
1970
|
-
(0,
|
|
2673
|
+
(0, import_react7.useEffect)(() => {
|
|
1971
2674
|
const sigFields = fields.filter((f) => f.assignee === signer && f.type === "signature" && f.value);
|
|
1972
2675
|
if (sigFields.length > 0) {
|
|
1973
2676
|
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
@@ -1979,7 +2682,7 @@ function SignerView({
|
|
|
1979
2682
|
}));
|
|
1980
2683
|
}
|
|
1981
2684
|
}, [fields.filter((f) => f.type === "signature" && f.value).length]);
|
|
1982
|
-
(0,
|
|
2685
|
+
(0, import_react7.useEffect)(() => {
|
|
1983
2686
|
const hasFormulas = fields.some((f) => f.formula);
|
|
1984
2687
|
if (!hasFormulas) return;
|
|
1985
2688
|
const resolved = resolveAllFormulas(fields, transforms);
|
|
@@ -2108,7 +2811,7 @@ function SignerView({
|
|
|
2108
2811
|
}
|
|
2109
2812
|
|
|
2110
2813
|
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
2111
|
-
var
|
|
2814
|
+
var import_react8 = require("react");
|
|
2112
2815
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2113
2816
|
function SignerRoleSelector({
|
|
2114
2817
|
roles,
|
|
@@ -2117,8 +2820,8 @@ function SignerRoleSelector({
|
|
|
2117
2820
|
onAddRole,
|
|
2118
2821
|
onRemoveRole
|
|
2119
2822
|
}) {
|
|
2120
|
-
const [isAdding, setIsAdding] = (0,
|
|
2121
|
-
const [newRoleName, setNewRoleName] = (0,
|
|
2823
|
+
const [isAdding, setIsAdding] = (0, import_react8.useState)(false);
|
|
2824
|
+
const [newRoleName, setNewRoleName] = (0, import_react8.useState)("");
|
|
2122
2825
|
const handleAdd = () => {
|
|
2123
2826
|
if (newRoleName.trim()) {
|
|
2124
2827
|
onAddRole(newRoleName.trim());
|