@unlev/exeq 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +246 -3
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +1082 -356
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1088 -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,70 @@ function PdfViewer({
|
|
|
219
222
|
onPageClick,
|
|
220
223
|
onDropField,
|
|
221
224
|
onGroupMove,
|
|
225
|
+
onMoveStart,
|
|
226
|
+
onMoveEnd,
|
|
222
227
|
mode,
|
|
223
228
|
currentSigner,
|
|
224
|
-
renderFieldContent
|
|
229
|
+
renderFieldContent,
|
|
230
|
+
zoom = 1,
|
|
231
|
+
onMarqueeSelect
|
|
225
232
|
}) {
|
|
226
233
|
const containerRef = (0, import_react.useRef)(null);
|
|
227
|
-
const
|
|
234
|
+
const [guides, setGuides] = (0, import_react.useState)([]);
|
|
235
|
+
const [marquee, setMarquee] = (0, import_react.useState)(null);
|
|
236
|
+
const marqueeRef = (0, import_react.useRef)(null);
|
|
237
|
+
const handlePageMouseDown = (0, import_react.useCallback)((e, pageIndex) => {
|
|
228
238
|
const target = e.target;
|
|
229
239
|
if (target.closest(".field-overlay")) return;
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
if (mode !== "designer") return;
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
243
|
+
const x = (e.clientX - rect.left) / rect.width * 100;
|
|
244
|
+
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
245
|
+
marqueeRef.current = { startX: x, startY: y, page: pageIndex, didDrag: false };
|
|
246
|
+
const handleMouseMove = (ev) => {
|
|
247
|
+
if (!marqueeRef.current) return;
|
|
248
|
+
const mx = (ev.clientX - rect.left) / rect.width * 100;
|
|
249
|
+
const my = (ev.clientY - rect.top) / rect.height * 100;
|
|
250
|
+
const dx = Math.abs(mx - marqueeRef.current.startX);
|
|
251
|
+
const dy = Math.abs(my - marqueeRef.current.startY);
|
|
252
|
+
if (dx > 1 || dy > 1) {
|
|
253
|
+
marqueeRef.current.didDrag = true;
|
|
254
|
+
setMarquee({
|
|
255
|
+
page: pageIndex,
|
|
256
|
+
x1: Math.min(marqueeRef.current.startX, mx),
|
|
257
|
+
y1: Math.min(marqueeRef.current.startY, my),
|
|
258
|
+
x2: Math.max(marqueeRef.current.startX, mx),
|
|
259
|
+
y2: Math.max(marqueeRef.current.startY, my)
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const handleMouseUp = (ev) => {
|
|
264
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
265
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
266
|
+
if (marqueeRef.current?.didDrag && onMarqueeSelect) {
|
|
267
|
+
const mx1 = Math.min(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
268
|
+
const my1 = Math.min(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
269
|
+
const mx2 = Math.max(marqueeRef.current.startX, (ev.clientX - rect.left) / rect.width * 100);
|
|
270
|
+
const my2 = Math.max(marqueeRef.current.startY, (ev.clientY - rect.top) / rect.height * 100);
|
|
271
|
+
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
272
|
+
const hits = pageFields.filter((f) => {
|
|
273
|
+
return f.x < mx2 && f.x + f.width > mx1 && f.y < my2 && f.y + f.height > my1;
|
|
274
|
+
}).map((f) => f.id);
|
|
275
|
+
onMarqueeSelect(hits);
|
|
276
|
+
} else if (!marqueeRef.current?.didDrag) {
|
|
277
|
+
if (onPageClick) {
|
|
278
|
+
onPageClick(pageIndex, x, y);
|
|
279
|
+
} else {
|
|
280
|
+
onSelectField(null);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
marqueeRef.current = null;
|
|
284
|
+
setMarquee(null);
|
|
285
|
+
};
|
|
286
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
287
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
288
|
+
}, [mode, fields, onPageClick, onSelectField, onMarqueeSelect]);
|
|
239
289
|
const handleDragOver = (0, import_react.useCallback)((e) => {
|
|
240
290
|
if (e.dataTransfer.types.includes("application/exeq-field-type")) {
|
|
241
291
|
e.preventDefault();
|
|
@@ -251,14 +301,14 @@ function PdfViewer({
|
|
|
251
301
|
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
252
302
|
onDropField(pageIndex, x, y, fieldType);
|
|
253
303
|
}, [onDropField]);
|
|
254
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
|
|
304
|
+
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
305
|
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
256
306
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
257
307
|
"div",
|
|
258
308
|
{
|
|
259
309
|
className: "pdf-page",
|
|
260
310
|
style: { aspectRatio: `${page.width} / ${page.height}` },
|
|
261
|
-
|
|
311
|
+
onMouseDown: (e) => handlePageMouseDown(e, pageIndex),
|
|
262
312
|
onDragOver: handleDragOver,
|
|
263
313
|
onDrop: (e) => handleDrop(e, pageIndex),
|
|
264
314
|
"data-page": pageIndex,
|
|
@@ -282,18 +332,87 @@ function PdfViewer({
|
|
|
282
332
|
onMove: onFieldMove,
|
|
283
333
|
onResize: onFieldResize,
|
|
284
334
|
onGroupMove: selectedFieldIds.size > 1 && selectedFieldIds.has(field.id) ? onGroupMove : void 0,
|
|
335
|
+
onMoveStart,
|
|
336
|
+
onMoveEnd,
|
|
337
|
+
otherFields: mode === "designer" ? pageFields.filter((f) => f.id !== field.id && !selectedFieldIds.has(f.id)) : void 0,
|
|
338
|
+
setGuides: mode === "designer" ? setGuides : void 0,
|
|
339
|
+
pageIndex,
|
|
285
340
|
selectedIds: selectedFieldIds,
|
|
286
341
|
mode,
|
|
287
342
|
currentSigner,
|
|
288
343
|
renderContent: renderFieldContent
|
|
289
344
|
},
|
|
290
345
|
field.id
|
|
291
|
-
))
|
|
346
|
+
)),
|
|
347
|
+
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}`)),
|
|
348
|
+
marquee && marquee.page === pageIndex && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "marquee-rect", style: {
|
|
349
|
+
left: `${marquee.x1}%`,
|
|
350
|
+
top: `${marquee.y1}%`,
|
|
351
|
+
width: `${marquee.x2 - marquee.x1}%`,
|
|
352
|
+
height: `${marquee.y2 - marquee.y1}%`
|
|
353
|
+
} })
|
|
292
354
|
]
|
|
293
355
|
},
|
|
294
356
|
pageIndex
|
|
295
357
|
);
|
|
296
|
-
}) });
|
|
358
|
+
}) }) });
|
|
359
|
+
}
|
|
360
|
+
var SNAP_THRESHOLD = 1.2;
|
|
361
|
+
function snapAndGuide(field, others, page) {
|
|
362
|
+
const fx = field.x, fy = field.y, fw = field.width, fh = field.height;
|
|
363
|
+
const fcx = fx + fw / 2, fcy = fy + fh / 2;
|
|
364
|
+
let bestSnapX = null;
|
|
365
|
+
let bestSnapY = null;
|
|
366
|
+
for (const o of others) {
|
|
367
|
+
const ox = o.x, oy = o.y, ow = o.width, oh = o.height;
|
|
368
|
+
const ocx = ox + ow / 2, ocy = oy + oh / 2;
|
|
369
|
+
const xCandidates = [
|
|
370
|
+
[fx, ox, ox],
|
|
371
|
+
// left ↔ left
|
|
372
|
+
[fx + fw, ox + ow, ox + ow],
|
|
373
|
+
// right ↔ right
|
|
374
|
+
[fcx, ocx, ocx],
|
|
375
|
+
// center ↔ center
|
|
376
|
+
[fx, ox + ow, ox + ow],
|
|
377
|
+
// left ↔ right
|
|
378
|
+
[fx + fw, ox, ox]
|
|
379
|
+
// right ↔ left
|
|
380
|
+
];
|
|
381
|
+
for (const [fe, te, gx] of xCandidates) {
|
|
382
|
+
const d = Math.abs(fe - te);
|
|
383
|
+
if (d < SNAP_THRESHOLD && (!bestSnapX || d < bestSnapX.dist)) {
|
|
384
|
+
bestSnapX = { offset: te - fe, guideX: gx, dist: d };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const yCandidates = [
|
|
388
|
+
[fy, oy, oy],
|
|
389
|
+
// top ↔ top
|
|
390
|
+
[fy + fh, oy + oh, oy + oh],
|
|
391
|
+
// bottom ↔ bottom
|
|
392
|
+
[fcy, ocy, ocy],
|
|
393
|
+
// center ↔ center
|
|
394
|
+
[fy, oy + oh, oy + oh],
|
|
395
|
+
// top ↔ bottom
|
|
396
|
+
[fy + fh, oy, oy]
|
|
397
|
+
// bottom ↔ top
|
|
398
|
+
];
|
|
399
|
+
for (const [fe, te, gy] of yCandidates) {
|
|
400
|
+
const d = Math.abs(fe - te);
|
|
401
|
+
if (d < SNAP_THRESHOLD && (!bestSnapY || d < bestSnapY.dist)) {
|
|
402
|
+
bestSnapY = { offset: te - fe, guideY: gy, dist: d };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const snappedX = bestSnapX ? fx + bestSnapX.offset : fx;
|
|
407
|
+
const snappedY = bestSnapY ? fy + bestSnapY.offset : fy;
|
|
408
|
+
const guides = [];
|
|
409
|
+
if (bestSnapX) guides.push({ page, x: bestSnapX.guideX });
|
|
410
|
+
if (bestSnapY) guides.push({ page, y: bestSnapY.guideY });
|
|
411
|
+
return {
|
|
412
|
+
x: Math.max(0, Math.min(100 - fw, snappedX)),
|
|
413
|
+
y: Math.max(0, Math.min(100 - fh, snappedY)),
|
|
414
|
+
guides
|
|
415
|
+
};
|
|
297
416
|
}
|
|
298
417
|
function FieldOverlayItem({
|
|
299
418
|
field,
|
|
@@ -303,6 +422,11 @@ function FieldOverlayItem({
|
|
|
303
422
|
onMove,
|
|
304
423
|
onResize,
|
|
305
424
|
onGroupMove,
|
|
425
|
+
onMoveStart,
|
|
426
|
+
onMoveEnd,
|
|
427
|
+
otherFields,
|
|
428
|
+
setGuides,
|
|
429
|
+
pageIndex,
|
|
306
430
|
selectedIds,
|
|
307
431
|
mode,
|
|
308
432
|
currentSigner,
|
|
@@ -310,19 +434,28 @@ function FieldOverlayItem({
|
|
|
310
434
|
}) {
|
|
311
435
|
const overlayRef = (0, import_react.useRef)(null);
|
|
312
436
|
const dragStartRef = (0, import_react.useRef)(null);
|
|
437
|
+
const dragPosRef = (0, import_react.useRef)({ x: field.x, y: field.y });
|
|
438
|
+
const didDragRef = (0, import_react.useRef)(false);
|
|
313
439
|
const resizeStartRef = (0, import_react.useRef)(null);
|
|
314
440
|
const isRedact = field.type === "blackout" || field.type === "whiteout";
|
|
315
441
|
const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
|
|
316
442
|
const isInactiveSigner = mode === "signer" && !isRedact && field.assignee !== currentSigner;
|
|
317
|
-
const
|
|
443
|
+
const isFilled = mode === "signer" && isEditable && !isRedact && (field.type === "checkbox" ? true : field.formula ? true : !!field.value);
|
|
444
|
+
const color = isInactiveSigner ? "#cccccc" : isFilled ? "#22c55e" : getSignerColor(field.assignee);
|
|
318
445
|
const isPreFilled = !isEditable && !!field.value;
|
|
319
446
|
const handleMouseDown = (0, import_react.useCallback)((e) => {
|
|
320
447
|
if (mode !== "designer" || !onMove) return;
|
|
321
448
|
e.preventDefault();
|
|
322
449
|
e.stopPropagation();
|
|
323
|
-
|
|
450
|
+
const alreadyInSelection = selectedIds.has(field.id) && selectedIds.size > 1;
|
|
451
|
+
if (!alreadyInSelection) {
|
|
452
|
+
onSelect(e);
|
|
453
|
+
}
|
|
454
|
+
if (field.locked) return;
|
|
455
|
+
onMoveStart?.();
|
|
324
456
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
325
457
|
if (!pageEl) return;
|
|
458
|
+
didDragRef.current = false;
|
|
326
459
|
dragStartRef.current = {
|
|
327
460
|
startX: e.clientX,
|
|
328
461
|
startY: e.clientY,
|
|
@@ -331,6 +464,7 @@ function FieldOverlayItem({
|
|
|
331
464
|
};
|
|
332
465
|
const handleMouseMove = (ev) => {
|
|
333
466
|
if (!dragStartRef.current) return;
|
|
467
|
+
didDragRef.current = true;
|
|
334
468
|
const rect = pageEl.getBoundingClientRect();
|
|
335
469
|
const dx = (ev.clientX - dragStartRef.current.startX) / rect.width * 100;
|
|
336
470
|
const dy = (ev.clientY - dragStartRef.current.startY) / rect.height * 100;
|
|
@@ -341,21 +475,38 @@ function FieldOverlayItem({
|
|
|
341
475
|
} else {
|
|
342
476
|
const newX = Math.max(0, Math.min(100 - field.width, dragStartRef.current.fieldX + dx));
|
|
343
477
|
const newY = Math.max(0, Math.min(100 - field.height, dragStartRef.current.fieldY + dy));
|
|
478
|
+
dragPosRef.current = { x: newX, y: newY };
|
|
344
479
|
onMove(field.id, field.page, newX, newY);
|
|
480
|
+
if (ev.shiftKey && otherFields && setGuides && pageIndex !== void 0) {
|
|
481
|
+
const { guides: g } = snapAndGuide({ x: newX, y: newY, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
482
|
+
setGuides(g);
|
|
483
|
+
} else {
|
|
484
|
+
setGuides?.([]);
|
|
485
|
+
}
|
|
345
486
|
}
|
|
346
487
|
};
|
|
347
|
-
const handleMouseUp = () => {
|
|
488
|
+
const handleMouseUp = (ev) => {
|
|
489
|
+
if (ev.shiftKey && didDragRef.current && otherFields && onMove && pageIndex !== void 0 && !isMultiSelected) {
|
|
490
|
+
const pos = dragPosRef.current;
|
|
491
|
+
const snap = snapAndGuide({ x: pos.x, y: pos.y, width: field.width, height: field.height }, otherFields, pageIndex);
|
|
492
|
+
if (snap.x !== pos.x || snap.y !== pos.y) {
|
|
493
|
+
onMove(field.id, field.page, snap.x, snap.y);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
348
496
|
dragStartRef.current = null;
|
|
349
497
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
350
498
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
499
|
+
setGuides?.([]);
|
|
500
|
+
onMoveEnd?.();
|
|
351
501
|
};
|
|
352
502
|
window.addEventListener("mousemove", handleMouseMove);
|
|
353
503
|
window.addEventListener("mouseup", handleMouseUp);
|
|
354
|
-
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, selectedIds]);
|
|
504
|
+
}, [field, mode, onMove, onSelect, isMultiSelected, onGroupMove, onMoveStart, onMoveEnd, selectedIds, otherFields, setGuides, pageIndex]);
|
|
355
505
|
const handleResizeMouseDown = (0, import_react.useCallback)((e) => {
|
|
356
|
-
if (mode !== "designer" || !onResize) return;
|
|
506
|
+
if (mode !== "designer" || !onResize || field.locked) return;
|
|
357
507
|
e.preventDefault();
|
|
358
508
|
e.stopPropagation();
|
|
509
|
+
onMoveStart?.();
|
|
359
510
|
const pageEl = overlayRef.current?.closest(".pdf-page");
|
|
360
511
|
if (!pageEl) return;
|
|
361
512
|
resizeStartRef.current = {
|
|
@@ -377,15 +528,16 @@ function FieldOverlayItem({
|
|
|
377
528
|
resizeStartRef.current = null;
|
|
378
529
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
379
530
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
531
|
+
onMoveEnd?.();
|
|
380
532
|
};
|
|
381
533
|
window.addEventListener("mousemove", handleMouseMove);
|
|
382
534
|
window.addEventListener("mouseup", handleMouseUp);
|
|
383
|
-
}, [field, mode, onResize]);
|
|
535
|
+
}, [field, mode, onResize, onMoveStart, onMoveEnd]);
|
|
384
536
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
385
537
|
"div",
|
|
386
538
|
{
|
|
387
539
|
ref: overlayRef,
|
|
388
|
-
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""}`,
|
|
540
|
+
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""} ${isInactiveSigner ? "inactive-signer" : ""} ${field.locked ? "locked" : ""}`,
|
|
389
541
|
style: {
|
|
390
542
|
left: `${field.x}%`,
|
|
391
543
|
top: `${field.y}%`,
|
|
@@ -394,18 +546,18 @@ function FieldOverlayItem({
|
|
|
394
546
|
borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
|
|
395
547
|
borderStyle: isRedact ? "dashed" : "solid",
|
|
396
548
|
backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isInactiveSigner ? "#f5f5f5" : isSelected ? `${color}22` : `${color}11`,
|
|
397
|
-
cursor: mode === "designer" ? "
|
|
549
|
+
cursor: mode === "designer" ? field.locked ? "not-allowed" : "move" : "default",
|
|
398
550
|
pointerEvents: mode === "signer" && (isRedact || isInactiveSigner) ? "none" : void 0
|
|
399
551
|
},
|
|
400
552
|
onClick: (e) => {
|
|
401
553
|
e.stopPropagation();
|
|
402
|
-
onSelect(e);
|
|
554
|
+
if (!didDragRef.current) onSelect(e);
|
|
403
555
|
},
|
|
404
556
|
onMouseDown: handleMouseDown,
|
|
405
557
|
children: [
|
|
406
558
|
mode === "designer" && !isRedact && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
407
559
|
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)(
|
|
560
|
+
mode === "designer" && isSelected && !field.locked && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
409
561
|
"div",
|
|
410
562
|
{
|
|
411
563
|
className: "field-resize-handle",
|
|
@@ -424,7 +576,7 @@ var INK_COLORS = [
|
|
|
424
576
|
{ value: "#000000", label: "Black" },
|
|
425
577
|
{ value: "#1a56db", label: "Blue" }
|
|
426
578
|
];
|
|
427
|
-
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
579
|
+
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete, prefillContent, pageSize }) {
|
|
428
580
|
const color = getSignerColor(field.assignee);
|
|
429
581
|
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
430
582
|
const isTextField = field.type === "text" || field.type === "signed-date" || field.type === "dropdown";
|
|
@@ -432,113 +584,306 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
432
584
|
const [newOption, setNewOption] = (0, import_react2.useState)("");
|
|
433
585
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
434
586
|
/* @__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
587
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
441
588
|
"input",
|
|
442
589
|
{
|
|
443
|
-
|
|
590
|
+
className: "panel-header-label",
|
|
591
|
+
style: { color },
|
|
444
592
|
value: field.label,
|
|
445
593
|
onChange: (e) => onUpdate(field.id, { label: e.target.value })
|
|
446
594
|
}
|
|
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
|
-
)
|
|
595
|
+
),
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header-actions", children: [
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
598
|
+
"button",
|
|
599
|
+
{
|
|
600
|
+
onClick: () => onUpdate(field.id, { locked: !field.locked }),
|
|
601
|
+
className: `panel-icon-btn ${field.locked ? "active" : ""}`,
|
|
602
|
+
title: field.locked ? "Unlock" : "Lock",
|
|
603
|
+
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: [
|
|
604
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
605
|
+
/* @__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" })
|
|
606
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
607
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "10", height: "7", rx: "1.5" }),
|
|
608
|
+
/* @__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" })
|
|
609
|
+
] }) })
|
|
610
|
+
}
|
|
611
|
+
),
|
|
612
|
+
/* @__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" }) }) })
|
|
613
|
+
] })
|
|
483
614
|
] }),
|
|
484
|
-
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-
|
|
485
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
615
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
616
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Assigned To" }),
|
|
486
617
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
487
618
|
"select",
|
|
488
619
|
{
|
|
620
|
+
className: "panel-assignee-select",
|
|
489
621
|
value: field.assignee,
|
|
490
622
|
onChange: (e) => onUpdate(field.id, { assignee: e.target.value }),
|
|
623
|
+
style: { borderColor: color, color },
|
|
491
624
|
children: signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
492
625
|
}
|
|
493
626
|
)
|
|
494
627
|
] }),
|
|
495
|
-
|
|
496
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
497
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
498
|
-
"
|
|
499
|
-
{
|
|
500
|
-
|
|
501
|
-
value:
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
629
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Content" }),
|
|
630
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Field Type" }),
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: field.type, onChange: (e) => onUpdate(field.id, { type: e.target.value }), children: [
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "text", children: "Text" }),
|
|
634
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "dropdown", children: "Dropdown" }),
|
|
635
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signature", children: "Signature" }),
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "signed-date", children: "Signed Date" }),
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "checkbox", children: "Checkbox" }),
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "initials", children: "Initials" })
|
|
639
|
+
] })
|
|
640
|
+
] }),
|
|
641
|
+
field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Text Type" }),
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: field.textSubtype || "freeform", onChange: (e) => onUpdate(field.id, { textSubtype: e.target.value }), children: [
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "freeform", children: "Freeform" }),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "number", children: "Number" }),
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "date", children: "Date" }),
|
|
647
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "email", children: "Email" }),
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "phone", children: "Phone" })
|
|
649
|
+
] })
|
|
650
|
+
] }),
|
|
651
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
652
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Placeholder" }),
|
|
653
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: field.placeholder, onChange: (e) => onUpdate(field.id, { placeholder: e.target.value }) })
|
|
654
|
+
] }),
|
|
655
|
+
!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: [
|
|
656
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: field.required, onChange: (e) => onUpdate(field.id, { required: e.target.checked }) }),
|
|
657
|
+
"Required"
|
|
658
|
+
] }) }),
|
|
659
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Formula" }),
|
|
661
|
+
/* @__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}}" }),
|
|
662
|
+
field.formula && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "panel-hint", children: "Auto-computed. Signer cannot edit." })
|
|
663
|
+
] }),
|
|
664
|
+
field.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Min Chars" }),
|
|
667
|
+
/* @__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) }) })
|
|
668
|
+
] }),
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
670
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Max Chars" }),
|
|
671
|
+
/* @__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) }) })
|
|
672
|
+
] })
|
|
673
|
+
] }),
|
|
674
|
+
(field.type === "text" || field.type === "dropdown") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: field.type === "dropdown" ? "Options" : "Predefined Options" }),
|
|
676
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-options-list", children: [
|
|
677
|
+
(field.options || []).map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-item", children: [
|
|
678
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: opt }),
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "panel-option-remove", onClick: () => {
|
|
680
|
+
const updated = (field.options || []).filter((_, j) => j !== i);
|
|
681
|
+
onUpdate(field.id, { options: updated.length > 0 ? updated : void 0 });
|
|
682
|
+
}, children: "\xD7" })
|
|
683
|
+
] }, i)),
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-option-add", children: [
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "text", value: newOption, onChange: (e) => setNewOption(e.target.value), onKeyDown: (e) => {
|
|
686
|
+
if (e.key === "Enter" && newOption.trim()) {
|
|
687
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
688
|
+
setNewOption("");
|
|
689
|
+
}
|
|
690
|
+
}, placeholder: "Add option..." }),
|
|
691
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => {
|
|
692
|
+
if (newOption.trim()) {
|
|
693
|
+
onUpdate(field.id, { options: [...field.options || [], newOption.trim()] });
|
|
694
|
+
setNewOption("");
|
|
695
|
+
}
|
|
696
|
+
}, children: "+" })
|
|
697
|
+
] })
|
|
698
|
+
] }),
|
|
699
|
+
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" }),
|
|
700
|
+
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" })
|
|
701
|
+
] }),
|
|
702
|
+
prefillContent
|
|
505
703
|
] }),
|
|
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
|
-
"
|
|
704
|
+
(isTextField || showInkColor) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
705
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Style" }),
|
|
706
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
707
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font" }),
|
|
708
|
+
/* @__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)) })
|
|
709
|
+
] }),
|
|
710
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
712
|
+
/* @__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) }) })
|
|
713
|
+
] }),
|
|
714
|
+
isTextField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
715
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
716
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
|
|
717
|
+
/* @__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) }) })
|
|
718
|
+
] }),
|
|
719
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
720
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Line Height" }),
|
|
721
|
+
/* @__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) }) })
|
|
722
|
+
] })
|
|
723
|
+
] }),
|
|
724
|
+
showInkColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
726
|
+
/* @__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)) })
|
|
727
|
+
] })
|
|
728
|
+
] }),
|
|
729
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-section", children: [
|
|
730
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-section-heading", children: "Position & Size" }),
|
|
731
|
+
(() => {
|
|
732
|
+
const pw = pageSize?.width || 612;
|
|
733
|
+
const ph = pageSize?.height || 792;
|
|
734
|
+
const toPt = (pct, dim) => Math.round(pct / 100 * dim * 10) / 10;
|
|
735
|
+
const fromPt = (pt, dim) => pt / dim * 100;
|
|
736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
738
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "X (pt)" }),
|
|
740
|
+
/* @__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) }) })
|
|
741
|
+
] }),
|
|
742
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Y (pt)" }),
|
|
744
|
+
/* @__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) }) })
|
|
745
|
+
] })
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
748
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
749
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Width (pt)" }),
|
|
750
|
+
/* @__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) }) })
|
|
751
|
+
] }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Height (pt)" }),
|
|
754
|
+
/* @__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) }) })
|
|
755
|
+
] })
|
|
756
|
+
] }),
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-hint", children: [
|
|
758
|
+
pw,
|
|
759
|
+
" \xD7 ",
|
|
760
|
+
ph,
|
|
761
|
+
" pt \xB7 Page ",
|
|
762
|
+
field.page + 1,
|
|
763
|
+
" \xB7 ",
|
|
764
|
+
field.id.slice(0, 8)
|
|
765
|
+
] })
|
|
766
|
+
] });
|
|
767
|
+
})()
|
|
768
|
+
] })
|
|
769
|
+
] });
|
|
770
|
+
}
|
|
771
|
+
function sharedValue(items) {
|
|
772
|
+
if (items.length === 0) return void 0;
|
|
773
|
+
return items.every((v) => v === items[0]) ? items[0] : void 0;
|
|
774
|
+
}
|
|
775
|
+
function BulkPropertyPanel({ fields, signerRoles, onUpdate, onAlign, onDelete }) {
|
|
776
|
+
const count = fields.length;
|
|
777
|
+
const hasText = fields.some((f) => f.type === "text" || f.type === "dropdown" || f.type === "signed-date");
|
|
778
|
+
const allNonRedact = fields.every((f) => f.type !== "blackout" && f.type !== "whiteout");
|
|
779
|
+
const sharedAssignee = sharedValue(fields.map((f) => f.assignee));
|
|
780
|
+
const sharedRequired = sharedValue(fields.map((f) => f.required));
|
|
781
|
+
const sharedFontSize = sharedValue(fields.map((f) => f.fontSize));
|
|
782
|
+
const sharedFontFamily = sharedValue(fields.map((f) => f.fontFamily || "Helvetica"));
|
|
783
|
+
const sharedInkColor = sharedValue(fields.map((f) => f.inkColor || "#000000"));
|
|
784
|
+
const sharedLetterSpacing = sharedValue(fields.map((f) => f.letterSpacing || 0));
|
|
785
|
+
const sharedLineHeight = sharedValue(fields.map((f) => f.lineHeight || 1.2));
|
|
786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
|
|
788
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h3", { children: [
|
|
789
|
+
count,
|
|
790
|
+
" fields selected"
|
|
791
|
+
] }),
|
|
792
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: onDelete, className: "panel-delete-btn", children: "Delete All" })
|
|
793
|
+
] }),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Align" }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "align-buttons", children: [
|
|
797
|
+
/* @__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: [
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "1", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
800
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "4", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
801
|
+
] }) }),
|
|
802
|
+
/* @__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: [
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "6.25", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "10", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3.5", y: "7", width: "7", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
806
|
+
] }) }),
|
|
807
|
+
/* @__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: [
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "11.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "8", height: "3", fill: "currentColor", opacity: "0.5" }),
|
|
810
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "5", y: "7", width: "5", height: "3", fill: "currentColor", opacity: "0.5" })
|
|
811
|
+
] }) }),
|
|
812
|
+
/* @__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: [
|
|
813
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "1", width: "14", height: "1.5", fill: "currentColor" }),
|
|
814
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "4", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "4", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
816
|
+
] }) }),
|
|
817
|
+
/* @__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: [
|
|
818
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "6.25", width: "14", height: "1.5", fill: "currentColor" }),
|
|
819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "1", width: "3", height: "12", fill: "currentColor", opacity: "0.5" }),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "3", width: "3", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
821
|
+
] }) }),
|
|
822
|
+
/* @__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: [
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "11.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "2", width: "3", height: "8", fill: "currentColor", opacity: "0.5" }),
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "7", y: "5", width: "3", height: "5", fill: "currentColor", opacity: "0.5" })
|
|
826
|
+
] }) })
|
|
827
|
+
] }),
|
|
828
|
+
count > 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { style: { marginTop: "0.4rem" }, children: "Distribute" }),
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "align-buttons", children: [
|
|
831
|
+
/* @__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: [
|
|
832
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "12.5", y: "0", width: "1.5", height: "14", fill: "currentColor" }),
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "5", y: "3", width: "4", height: "8", fill: "currentColor", opacity: "0.5" })
|
|
835
|
+
] }) }),
|
|
836
|
+
/* @__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: [
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "0", width: "14", height: "1.5", fill: "currentColor" }),
|
|
838
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "0", y: "12.5", width: "14", height: "1.5", fill: "currentColor" }),
|
|
839
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "5", width: "8", height: "4", fill: "currentColor", opacity: "0.5" })
|
|
840
|
+
] }) })
|
|
841
|
+
] })
|
|
842
|
+
] })
|
|
843
|
+
] }),
|
|
844
|
+
allNonRedact && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
845
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Assigned To" }),
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
847
|
+
"select",
|
|
510
848
|
{
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
849
|
+
value: sharedAssignee || "",
|
|
850
|
+
onChange: (e) => onUpdate({ assignee: e.target.value }),
|
|
851
|
+
children: [
|
|
852
|
+
!sharedAssignee && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", disabled: true, children: "Mixed" }),
|
|
853
|
+
signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: role, style: { color: getSignerColor(role) }, children: role }, role))
|
|
854
|
+
]
|
|
515
855
|
}
|
|
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." })
|
|
856
|
+
)
|
|
518
857
|
] }),
|
|
519
|
-
|
|
858
|
+
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
859
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
521
860
|
"input",
|
|
522
861
|
{
|
|
523
862
|
type: "checkbox",
|
|
524
|
-
checked:
|
|
525
|
-
|
|
863
|
+
checked: sharedRequired ?? false,
|
|
864
|
+
ref: (el) => {
|
|
865
|
+
if (el) el.indeterminate = sharedRequired === void 0;
|
|
866
|
+
},
|
|
867
|
+
onChange: (e) => onUpdate({ required: e.target.checked })
|
|
526
868
|
}
|
|
527
869
|
),
|
|
528
870
|
"Required"
|
|
529
871
|
] }) }),
|
|
530
|
-
|
|
872
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
531
873
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font" }),
|
|
532
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
874
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
533
875
|
"select",
|
|
534
876
|
{
|
|
535
|
-
value:
|
|
536
|
-
onChange: (e) => onUpdate(
|
|
537
|
-
children:
|
|
877
|
+
value: sharedFontFamily || "",
|
|
878
|
+
onChange: (e) => onUpdate({ fontFamily: e.target.value }),
|
|
879
|
+
children: [
|
|
880
|
+
!sharedFontFamily && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", disabled: true, children: "Mixed" }),
|
|
881
|
+
FONT_FAMILIES.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: f.value, children: f.label }, f.value))
|
|
882
|
+
]
|
|
538
883
|
}
|
|
539
884
|
)
|
|
540
885
|
] }),
|
|
541
|
-
|
|
886
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
542
887
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
543
888
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
544
889
|
"input",
|
|
@@ -546,12 +891,13 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
546
891
|
type: "number",
|
|
547
892
|
min: "6",
|
|
548
893
|
max: "72",
|
|
549
|
-
value:
|
|
550
|
-
|
|
894
|
+
value: sharedFontSize ?? "",
|
|
895
|
+
placeholder: "Mixed",
|
|
896
|
+
onChange: (e) => onUpdate({ fontSize: Number(e.target.value) })
|
|
551
897
|
}
|
|
552
898
|
)
|
|
553
899
|
] }),
|
|
554
|
-
|
|
900
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field-row", children: [
|
|
555
901
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field panel-field-half", children: [
|
|
556
902
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Letter Spacing" }),
|
|
557
903
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
@@ -561,8 +907,9 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
561
907
|
min: "0",
|
|
562
908
|
max: "20",
|
|
563
909
|
step: "0.5",
|
|
564
|
-
value:
|
|
565
|
-
|
|
910
|
+
value: sharedLetterSpacing ?? "",
|
|
911
|
+
placeholder: "Mixed",
|
|
912
|
+
onChange: (e) => onUpdate({ letterSpacing: Number(e.target.value) })
|
|
566
913
|
}
|
|
567
914
|
)
|
|
568
915
|
] }),
|
|
@@ -575,87 +922,25 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
575
922
|
min: "0.8",
|
|
576
923
|
max: "3",
|
|
577
924
|
step: "0.1",
|
|
578
|
-
value:
|
|
579
|
-
|
|
925
|
+
value: sharedLineHeight ?? "",
|
|
926
|
+
placeholder: "Mixed",
|
|
927
|
+
onChange: (e) => onUpdate({ lineHeight: Number(e.target.value) })
|
|
580
928
|
}
|
|
581
929
|
)
|
|
582
930
|
] })
|
|
583
931
|
] }),
|
|
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: [
|
|
932
|
+
allNonRedact && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
598
933
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
599
934
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
600
935
|
"button",
|
|
601
936
|
{
|
|
602
|
-
className: `ink-color-swatch ${
|
|
937
|
+
className: `ink-color-swatch ${sharedInkColor === c.value ? "active" : ""}`,
|
|
603
938
|
style: { backgroundColor: c.value },
|
|
604
|
-
onClick: () => onUpdate(
|
|
939
|
+
onClick: () => onUpdate({ inkColor: c.value }),
|
|
605
940
|
title: c.label
|
|
606
941
|
},
|
|
607
942
|
c.value
|
|
608
943
|
)) })
|
|
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
944
|
] })
|
|
660
945
|
] });
|
|
661
946
|
}
|
|
@@ -800,6 +1085,92 @@ function isValidApiKey(key) {
|
|
|
800
1085
|
return VALID_KEYS.has(key);
|
|
801
1086
|
}
|
|
802
1087
|
|
|
1088
|
+
// src/hooks/useHistory.ts
|
|
1089
|
+
var import_react4 = require("react");
|
|
1090
|
+
function useHistory(initialState, maxHistory = 50) {
|
|
1091
|
+
const [state, setState] = (0, import_react4.useState)({
|
|
1092
|
+
past: [],
|
|
1093
|
+
present: initialState,
|
|
1094
|
+
future: []
|
|
1095
|
+
});
|
|
1096
|
+
const batchRef = (0, import_react4.useRef)(false);
|
|
1097
|
+
const batchStartRef = (0, import_react4.useRef)(null);
|
|
1098
|
+
const set = (0, import_react4.useCallback)((updater) => {
|
|
1099
|
+
setState((prev) => {
|
|
1100
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1101
|
+
if (newPresent === prev.present) return prev;
|
|
1102
|
+
if (batchRef.current) {
|
|
1103
|
+
return { ...prev, present: newPresent };
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
past: [...prev.past.slice(-maxHistory), prev.present],
|
|
1107
|
+
present: newPresent,
|
|
1108
|
+
future: []
|
|
1109
|
+
};
|
|
1110
|
+
});
|
|
1111
|
+
}, [maxHistory]);
|
|
1112
|
+
const setWithoutHistory = (0, import_react4.useCallback)((updater) => {
|
|
1113
|
+
setState((prev) => {
|
|
1114
|
+
const newPresent = typeof updater === "function" ? updater(prev.present) : updater;
|
|
1115
|
+
if (newPresent === prev.present) return prev;
|
|
1116
|
+
return { ...prev, present: newPresent };
|
|
1117
|
+
});
|
|
1118
|
+
}, []);
|
|
1119
|
+
const beginBatch = (0, import_react4.useCallback)(() => {
|
|
1120
|
+
batchRef.current = true;
|
|
1121
|
+
setState((prev) => {
|
|
1122
|
+
batchStartRef.current = prev.present;
|
|
1123
|
+
return prev;
|
|
1124
|
+
});
|
|
1125
|
+
}, []);
|
|
1126
|
+
const commitBatch = (0, import_react4.useCallback)(() => {
|
|
1127
|
+
batchRef.current = false;
|
|
1128
|
+
setState((prev) => {
|
|
1129
|
+
const startState = batchStartRef.current;
|
|
1130
|
+
batchStartRef.current = null;
|
|
1131
|
+
if (!startState || startState === prev.present) return prev;
|
|
1132
|
+
return {
|
|
1133
|
+
past: [...prev.past.slice(-maxHistory), startState],
|
|
1134
|
+
present: prev.present,
|
|
1135
|
+
future: []
|
|
1136
|
+
};
|
|
1137
|
+
});
|
|
1138
|
+
}, [maxHistory]);
|
|
1139
|
+
const undo = (0, import_react4.useCallback)(() => {
|
|
1140
|
+
setState((prev) => {
|
|
1141
|
+
if (prev.past.length === 0) return prev;
|
|
1142
|
+
const previous = prev.past[prev.past.length - 1];
|
|
1143
|
+
return {
|
|
1144
|
+
past: prev.past.slice(0, -1),
|
|
1145
|
+
present: previous,
|
|
1146
|
+
future: [prev.present, ...prev.future]
|
|
1147
|
+
};
|
|
1148
|
+
});
|
|
1149
|
+
}, []);
|
|
1150
|
+
const redo = (0, import_react4.useCallback)(() => {
|
|
1151
|
+
setState((prev) => {
|
|
1152
|
+
if (prev.future.length === 0) return prev;
|
|
1153
|
+
const next = prev.future[0];
|
|
1154
|
+
return {
|
|
1155
|
+
past: [...prev.past, prev.present],
|
|
1156
|
+
present: next,
|
|
1157
|
+
future: prev.future.slice(1)
|
|
1158
|
+
};
|
|
1159
|
+
});
|
|
1160
|
+
}, []);
|
|
1161
|
+
return {
|
|
1162
|
+
state: state.present,
|
|
1163
|
+
set,
|
|
1164
|
+
setWithoutHistory,
|
|
1165
|
+
beginBatch,
|
|
1166
|
+
commitBatch,
|
|
1167
|
+
undo,
|
|
1168
|
+
redo,
|
|
1169
|
+
canUndo: state.past.length > 0,
|
|
1170
|
+
canRedo: state.future.length > 0
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
803
1174
|
// src/components/pdf-builder/DesignerView.tsx
|
|
804
1175
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
805
1176
|
var FIELD_TYPE_META = [
|
|
@@ -830,24 +1201,28 @@ function DesignerView({
|
|
|
830
1201
|
] })
|
|
831
1202
|
] }) });
|
|
832
1203
|
}
|
|
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,
|
|
1204
|
+
const [pages, setPages] = (0, import_react5.useState)([]);
|
|
1205
|
+
const { state: fields, set: setFields, setWithoutHistory: setFieldsSilent, beginBatch, commitBatch, undo: undoFields, redo: redoFields, canUndo, canRedo } = useHistory(initialTemplate?.fields ?? []);
|
|
1206
|
+
const [selectedFieldIds, setSelectedFieldIds] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
1207
|
+
const [signerRoles, setSignerRoles] = (0, import_react5.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
1208
|
+
const [activeRole, setActiveRole] = (0, import_react5.useState)("Sender");
|
|
1209
|
+
const [activeFieldType, setActiveFieldType] = (0, import_react5.useState)("text");
|
|
1210
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
1211
|
+
const [pdfSource, setPdfSource] = (0, import_react5.useState)(null);
|
|
1212
|
+
const [rightTab, setRightTab] = (0, import_react5.useState)("properties");
|
|
1213
|
+
const [isAddingRole, setIsAddingRole] = (0, import_react5.useState)(false);
|
|
1214
|
+
const [newRoleName, setNewRoleName] = (0, import_react5.useState)("");
|
|
1215
|
+
const [draggingFieldType, setDraggingFieldType] = (0, import_react5.useState)(null);
|
|
1216
|
+
const [panelWidth, setPanelWidth] = (0, import_react5.useState)(380);
|
|
1217
|
+
const [zoom, setZoom] = (0, import_react5.useState)(1);
|
|
1218
|
+
const [isPanning, setIsPanning] = (0, import_react5.useState)(false);
|
|
1219
|
+
const panRef = (0, import_react5.useRef)(null);
|
|
1220
|
+
const pdfAreaRef = (0, import_react5.useRef)(null);
|
|
1221
|
+
const [clipboardFields, setClipboardFields] = (0, import_react5.useState)([]);
|
|
1222
|
+
const dragGhostRef = (0, import_react5.useRef)(null);
|
|
1223
|
+
const resizingRef = (0, import_react5.useRef)(false);
|
|
1224
|
+
const lastStylesRef = (0, import_react5.useRef)({});
|
|
1225
|
+
(0, import_react5.useEffect)(() => {
|
|
851
1226
|
const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
|
|
852
1227
|
if (pdfUrl) {
|
|
853
1228
|
loadPdf(pdfUrl);
|
|
@@ -871,7 +1246,7 @@ function DesignerView({
|
|
|
871
1246
|
window.addEventListener("message", handleMessage);
|
|
872
1247
|
return () => window.removeEventListener("message", handleMessage);
|
|
873
1248
|
}, [initialPdfUrl, initialTemplate]);
|
|
874
|
-
const loadPdf = (0,
|
|
1249
|
+
const loadPdf = (0, import_react5.useCallback)(async (source) => {
|
|
875
1250
|
setLoading(true);
|
|
876
1251
|
try {
|
|
877
1252
|
const rendered = await renderPdfPages(source);
|
|
@@ -883,7 +1258,7 @@ function DesignerView({
|
|
|
883
1258
|
setLoading(false);
|
|
884
1259
|
}
|
|
885
1260
|
}, []);
|
|
886
|
-
const handleFileUpload = (0,
|
|
1261
|
+
const handleFileUpload = (0, import_react5.useCallback)((e) => {
|
|
887
1262
|
const file = e.target.files?.[0];
|
|
888
1263
|
if (!file) return;
|
|
889
1264
|
const reader = new FileReader();
|
|
@@ -892,7 +1267,7 @@ function DesignerView({
|
|
|
892
1267
|
};
|
|
893
1268
|
reader.readAsArrayBuffer(file);
|
|
894
1269
|
}, [loadPdf]);
|
|
895
|
-
const handlePageClick = (0,
|
|
1270
|
+
const handlePageClick = (0, import_react5.useCallback)((page, x, y) => {
|
|
896
1271
|
const field = createField(activeFieldType, activeRole, page, x, y, fields);
|
|
897
1272
|
const sticky = lastStylesRef.current[activeFieldType];
|
|
898
1273
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -900,11 +1275,14 @@ function DesignerView({
|
|
|
900
1275
|
setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
|
|
901
1276
|
setRightTab("properties");
|
|
902
1277
|
}, [activeFieldType, activeRole, fields]);
|
|
903
|
-
const handleFieldMove = (0,
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1278
|
+
const handleFieldMove = (0, import_react5.useCallback)((id, page, x, y) => {
|
|
1279
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1280
|
+
if (f.id !== id || f.locked) return f;
|
|
1281
|
+
return { ...f, page, x, y };
|
|
1282
|
+
}));
|
|
1283
|
+
}, [setFieldsSilent]);
|
|
1284
|
+
const handleFieldResize = (0, import_react5.useCallback)((id, width, height) => {
|
|
1285
|
+
setFieldsSilent((prev) => {
|
|
908
1286
|
const field = prev.find((f) => f.id === id);
|
|
909
1287
|
if (field) {
|
|
910
1288
|
const existing = lastStylesRef.current[field.type] || {};
|
|
@@ -912,8 +1290,8 @@ function DesignerView({
|
|
|
912
1290
|
}
|
|
913
1291
|
return prev.map((f) => f.id === id ? { ...f, width, height } : f);
|
|
914
1292
|
});
|
|
915
|
-
}, []);
|
|
916
|
-
const handleFieldUpdate = (0,
|
|
1293
|
+
}, [setFieldsSilent]);
|
|
1294
|
+
const handleFieldUpdate = (0, import_react5.useCallback)((id, updates) => {
|
|
917
1295
|
setFields((prev) => {
|
|
918
1296
|
if (updates.label !== void 0) {
|
|
919
1297
|
const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
|
|
@@ -933,7 +1311,75 @@ function DesignerView({
|
|
|
933
1311
|
return updated;
|
|
934
1312
|
});
|
|
935
1313
|
}, []);
|
|
936
|
-
const
|
|
1314
|
+
const handleBulkUpdate = (0, import_react5.useCallback)((updates) => {
|
|
1315
|
+
setFields((prev) => prev.map((f) => selectedFieldIds.has(f.id) ? { ...f, ...updates } : f));
|
|
1316
|
+
}, [selectedFieldIds]);
|
|
1317
|
+
const handleAlign = (0, import_react5.useCallback)((type) => {
|
|
1318
|
+
setFields((prev) => {
|
|
1319
|
+
const selected = prev.filter((f) => selectedFieldIds.has(f.id));
|
|
1320
|
+
if (selected.length < 2) return prev;
|
|
1321
|
+
let updates = {};
|
|
1322
|
+
if (type === "left") {
|
|
1323
|
+
const minX = Math.min(...selected.map((f) => f.x));
|
|
1324
|
+
selected.forEach((f) => {
|
|
1325
|
+
updates[f.id] = { x: minX };
|
|
1326
|
+
});
|
|
1327
|
+
} else if (type === "right") {
|
|
1328
|
+
const maxRight = Math.max(...selected.map((f) => f.x + f.width));
|
|
1329
|
+
selected.forEach((f) => {
|
|
1330
|
+
updates[f.id] = { x: maxRight - f.width };
|
|
1331
|
+
});
|
|
1332
|
+
} else if (type === "top") {
|
|
1333
|
+
const minY = Math.min(...selected.map((f) => f.y));
|
|
1334
|
+
selected.forEach((f) => {
|
|
1335
|
+
updates[f.id] = { y: minY };
|
|
1336
|
+
});
|
|
1337
|
+
} else if (type === "bottom") {
|
|
1338
|
+
const maxBottom = Math.max(...selected.map((f) => f.y + f.height));
|
|
1339
|
+
selected.forEach((f) => {
|
|
1340
|
+
updates[f.id] = { y: maxBottom - f.height };
|
|
1341
|
+
});
|
|
1342
|
+
} else if (type === "center-h") {
|
|
1343
|
+
const centers = selected.map((f) => f.x + f.width / 2);
|
|
1344
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1345
|
+
selected.forEach((f) => {
|
|
1346
|
+
updates[f.id] = { x: avgCenter - f.width / 2 };
|
|
1347
|
+
});
|
|
1348
|
+
} else if (type === "center-v") {
|
|
1349
|
+
const centers = selected.map((f) => f.y + f.height / 2);
|
|
1350
|
+
const avgCenter = centers.reduce((a, b) => a + b, 0) / centers.length;
|
|
1351
|
+
selected.forEach((f) => {
|
|
1352
|
+
updates[f.id] = { y: avgCenter - f.height / 2 };
|
|
1353
|
+
});
|
|
1354
|
+
} else if (type === "distribute-h") {
|
|
1355
|
+
const sorted = [...selected].sort((a, b) => a.x - b.x);
|
|
1356
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1357
|
+
const totalSpan = last.x + last.width - first.x;
|
|
1358
|
+
const totalFieldWidth = sorted.reduce((s, f) => s + f.width, 0);
|
|
1359
|
+
const gap = (totalSpan - totalFieldWidth) / (sorted.length - 1);
|
|
1360
|
+
let cx = first.x;
|
|
1361
|
+
sorted.forEach((f) => {
|
|
1362
|
+
updates[f.id] = { x: cx };
|
|
1363
|
+
cx += f.width + gap;
|
|
1364
|
+
});
|
|
1365
|
+
} else if (type === "distribute-v") {
|
|
1366
|
+
const sorted = [...selected].sort((a, b) => a.y - b.y);
|
|
1367
|
+
const first = sorted[0], last = sorted[sorted.length - 1];
|
|
1368
|
+
const totalSpan = last.y + last.height - first.y;
|
|
1369
|
+
const totalFieldHeight = sorted.reduce((s, f) => s + f.height, 0);
|
|
1370
|
+
const gap = (totalSpan - totalFieldHeight) / (sorted.length - 1);
|
|
1371
|
+
let cy = first.y;
|
|
1372
|
+
sorted.forEach((f) => {
|
|
1373
|
+
updates[f.id] = { y: cy };
|
|
1374
|
+
cy += f.height + gap;
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
return prev.map((f) => updates[f.id] ? { ...f, ...updates[f.id] } : f);
|
|
1378
|
+
});
|
|
1379
|
+
}, [selectedFieldIds]);
|
|
1380
|
+
const handleFieldDelete = (0, import_react5.useCallback)((id) => {
|
|
1381
|
+
const field = fields.find((f) => f.id === id);
|
|
1382
|
+
if (field?.locked) return;
|
|
937
1383
|
setFields((prev) => prev.filter((f) => f.id !== id));
|
|
938
1384
|
setSelectedFieldIds((prev) => {
|
|
939
1385
|
const next = new Set(prev);
|
|
@@ -941,7 +1387,7 @@ function DesignerView({
|
|
|
941
1387
|
return next;
|
|
942
1388
|
});
|
|
943
1389
|
}, []);
|
|
944
|
-
const handleSelectField = (0,
|
|
1390
|
+
const handleSelectField = (0, import_react5.useCallback)((id, e) => {
|
|
945
1391
|
if (!id) {
|
|
946
1392
|
setSelectedFieldIds(/* @__PURE__ */ new Set());
|
|
947
1393
|
return;
|
|
@@ -975,15 +1421,15 @@ function DesignerView({
|
|
|
975
1421
|
}
|
|
976
1422
|
setRightTab("properties");
|
|
977
1423
|
}, [selectedFieldIds]);
|
|
978
|
-
const handleGroupMove = (0,
|
|
979
|
-
|
|
980
|
-
if (!ids.includes(f.id)) return f;
|
|
1424
|
+
const handleGroupMove = (0, import_react5.useCallback)((ids, dx, dy) => {
|
|
1425
|
+
setFieldsSilent((prev) => prev.map((f) => {
|
|
1426
|
+
if (!ids.includes(f.id) || f.locked) return f;
|
|
981
1427
|
const newX = Math.max(0, Math.min(100 - f.width, f.x + dx));
|
|
982
1428
|
const newY = Math.max(0, Math.min(100 - f.height, f.y + dy));
|
|
983
1429
|
return { ...f, x: newX, y: newY };
|
|
984
1430
|
}));
|
|
985
|
-
}, []);
|
|
986
|
-
const handleAddRole = (0,
|
|
1431
|
+
}, [setFieldsSilent]);
|
|
1432
|
+
const handleAddRole = (0, import_react5.useCallback)(() => {
|
|
987
1433
|
const name = newRoleName.trim();
|
|
988
1434
|
if (name && !signerRoles.includes(name)) {
|
|
989
1435
|
setSignerRoles((prev) => [...prev, name]);
|
|
@@ -991,12 +1437,12 @@ function DesignerView({
|
|
|
991
1437
|
setNewRoleName("");
|
|
992
1438
|
setIsAddingRole(false);
|
|
993
1439
|
}, [newRoleName, signerRoles]);
|
|
994
|
-
const handleRemoveRole = (0,
|
|
1440
|
+
const handleRemoveRole = (0, import_react5.useCallback)((role) => {
|
|
995
1441
|
setSignerRoles((prev) => prev.filter((r) => r !== role));
|
|
996
1442
|
setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
|
|
997
1443
|
if (activeRole === role) setActiveRole(signerRoles[0]);
|
|
998
1444
|
}, [signerRoles, activeRole]);
|
|
999
|
-
const handleExport = (0,
|
|
1445
|
+
const handleExport = (0, import_react5.useCallback)(() => {
|
|
1000
1446
|
const template = {
|
|
1001
1447
|
fields,
|
|
1002
1448
|
signerRoles,
|
|
@@ -1016,7 +1462,7 @@ function DesignerView({
|
|
|
1016
1462
|
}
|
|
1017
1463
|
window.parent?.postMessage({ type: "template-saved", template }, "*");
|
|
1018
1464
|
}, [fields, signerRoles, pdfSource, onSave]);
|
|
1019
|
-
const handlePaletteDragStart = (0,
|
|
1465
|
+
const handlePaletteDragStart = (0, import_react5.useCallback)((e, type) => {
|
|
1020
1466
|
setDraggingFieldType(type);
|
|
1021
1467
|
e.dataTransfer.setData("application/exeq-field-type", type);
|
|
1022
1468
|
e.dataTransfer.effectAllowed = "copy";
|
|
@@ -1033,14 +1479,14 @@ function DesignerView({
|
|
|
1033
1479
|
e.dataTransfer.setDragImage(ghost, 40, 16);
|
|
1034
1480
|
dragGhostRef.current = ghost;
|
|
1035
1481
|
}, [activeRole]);
|
|
1036
|
-
const handlePaletteDragEnd = (0,
|
|
1482
|
+
const handlePaletteDragEnd = (0, import_react5.useCallback)(() => {
|
|
1037
1483
|
setDraggingFieldType(null);
|
|
1038
1484
|
if (dragGhostRef.current) {
|
|
1039
1485
|
document.body.removeChild(dragGhostRef.current);
|
|
1040
1486
|
dragGhostRef.current = null;
|
|
1041
1487
|
}
|
|
1042
1488
|
}, []);
|
|
1043
|
-
const handleDropOnPage = (0,
|
|
1489
|
+
const handleDropOnPage = (0, import_react5.useCallback)((page, x, y, fieldType) => {
|
|
1044
1490
|
const field = createField(fieldType, activeRole, page, x, y, fields);
|
|
1045
1491
|
const sticky = lastStylesRef.current[fieldType];
|
|
1046
1492
|
const styledField = sticky ? { ...field, ...sticky } : field;
|
|
@@ -1048,7 +1494,7 @@ function DesignerView({
|
|
|
1048
1494
|
setSelectedFieldIds(/* @__PURE__ */ new Set([styledField.id]));
|
|
1049
1495
|
setRightTab("properties");
|
|
1050
1496
|
}, [activeRole, fields]);
|
|
1051
|
-
(0,
|
|
1497
|
+
(0, import_react5.useEffect)(() => {
|
|
1052
1498
|
const handleKeyDown = (e) => {
|
|
1053
1499
|
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
|
1054
1500
|
if (selectedFieldIds.size === 0) return;
|
|
@@ -1056,21 +1502,39 @@ function DesignerView({
|
|
|
1056
1502
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1057
1503
|
if (document.activeElement?.isContentEditable) return;
|
|
1058
1504
|
e.preventDefault();
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1505
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1506
|
+
setSelectedFieldIds((prev) => {
|
|
1507
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1508
|
+
fields.forEach((f) => {
|
|
1509
|
+
if (selectedFieldIds.has(f.id) && f.locked) remaining.add(f.id);
|
|
1510
|
+
});
|
|
1511
|
+
return remaining;
|
|
1512
|
+
});
|
|
1064
1513
|
};
|
|
1065
1514
|
window.addEventListener("keydown", handleKeyDown);
|
|
1066
1515
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1067
|
-
}, [selectedFieldIds]);
|
|
1068
|
-
(0,
|
|
1516
|
+
}, [selectedFieldIds, fields]);
|
|
1517
|
+
(0, import_react5.useEffect)(() => {
|
|
1069
1518
|
const handleKeyDown = (e) => {
|
|
1070
1519
|
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1071
1520
|
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1072
1521
|
if (document.activeElement?.isContentEditable) return;
|
|
1073
1522
|
const isMod = e.metaKey || e.ctrlKey;
|
|
1523
|
+
if (isMod && e.key === "a") {
|
|
1524
|
+
e.preventDefault();
|
|
1525
|
+
setSelectedFieldIds(new Set(fields.map((f) => f.id)));
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (isMod && e.key === "z" && !e.shiftKey) {
|
|
1529
|
+
e.preventDefault();
|
|
1530
|
+
undoFields();
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
if (isMod && (e.key === "y" || e.key === "z" && e.shiftKey)) {
|
|
1534
|
+
e.preventDefault();
|
|
1535
|
+
redoFields();
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1074
1538
|
if (isMod && e.key === "c" && selectedFieldIds.size > 0) {
|
|
1075
1539
|
const selected = fields.filter((f) => selectedFieldIds.has(f.id));
|
|
1076
1540
|
if (selected.length > 0) {
|
|
@@ -1097,7 +1561,75 @@ function DesignerView({
|
|
|
1097
1561
|
window.addEventListener("keydown", handleKeyDown);
|
|
1098
1562
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1099
1563
|
}, [selectedFieldIds, fields, clipboardFields]);
|
|
1100
|
-
|
|
1564
|
+
(0, import_react5.useEffect)(() => {
|
|
1565
|
+
const handleKeyDown = (e) => {
|
|
1566
|
+
const isMod = e.metaKey || e.ctrlKey;
|
|
1567
|
+
if (isMod && (e.key === "=" || e.key === "+")) {
|
|
1568
|
+
e.preventDefault();
|
|
1569
|
+
setZoom((z) => Math.min(3, z + 0.25));
|
|
1570
|
+
}
|
|
1571
|
+
if (isMod && e.key === "-") {
|
|
1572
|
+
e.preventDefault();
|
|
1573
|
+
setZoom((z) => Math.max(0.75, z - 0.25));
|
|
1574
|
+
}
|
|
1575
|
+
if (isMod && e.key === "0") {
|
|
1576
|
+
e.preventDefault();
|
|
1577
|
+
setZoom(1);
|
|
1578
|
+
}
|
|
1579
|
+
if (e.key === " ") {
|
|
1580
|
+
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
1581
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
1582
|
+
e.preventDefault();
|
|
1583
|
+
e.stopPropagation();
|
|
1584
|
+
setIsPanning(true);
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
const handleKeyUp = (e) => {
|
|
1588
|
+
if (e.key === " ") {
|
|
1589
|
+
e.preventDefault();
|
|
1590
|
+
setIsPanning(false);
|
|
1591
|
+
panRef.current = null;
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
window.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
1595
|
+
window.addEventListener("keyup", handleKeyUp, { capture: true });
|
|
1596
|
+
return () => {
|
|
1597
|
+
window.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
1598
|
+
window.removeEventListener("keyup", handleKeyUp, { capture: true });
|
|
1599
|
+
};
|
|
1600
|
+
}, []);
|
|
1601
|
+
(0, import_react5.useEffect)(() => {
|
|
1602
|
+
const el = pdfAreaRef.current;
|
|
1603
|
+
if (!el) return;
|
|
1604
|
+
const handleWheel = (e) => {
|
|
1605
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1606
|
+
e.preventDefault();
|
|
1607
|
+
const delta = e.deltaY > 0 ? -0.1 : 0.1;
|
|
1608
|
+
setZoom((z) => Math.max(0.75, Math.min(3, z + delta)));
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
el.addEventListener("wheel", handleWheel, { passive: false });
|
|
1612
|
+
return () => el.removeEventListener("wheel", handleWheel);
|
|
1613
|
+
}, [pages.length]);
|
|
1614
|
+
const handlePanMouseDown = (0, import_react5.useCallback)((e) => {
|
|
1615
|
+
if (!isPanning || !pdfAreaRef.current) return;
|
|
1616
|
+
e.preventDefault();
|
|
1617
|
+
panRef.current = {
|
|
1618
|
+
startX: e.clientX,
|
|
1619
|
+
startY: e.clientY,
|
|
1620
|
+
scrollLeft: pdfAreaRef.current.scrollLeft,
|
|
1621
|
+
scrollTop: pdfAreaRef.current.scrollTop
|
|
1622
|
+
};
|
|
1623
|
+
}, [isPanning]);
|
|
1624
|
+
const handlePanMouseMove = (0, import_react5.useCallback)((e) => {
|
|
1625
|
+
if (!panRef.current || !pdfAreaRef.current) return;
|
|
1626
|
+
pdfAreaRef.current.scrollLeft = panRef.current.scrollLeft - (e.clientX - panRef.current.startX);
|
|
1627
|
+
pdfAreaRef.current.scrollTop = panRef.current.scrollTop - (e.clientY - panRef.current.startY);
|
|
1628
|
+
}, []);
|
|
1629
|
+
const handlePanMouseUp = (0, import_react5.useCallback)(() => {
|
|
1630
|
+
panRef.current = null;
|
|
1631
|
+
}, []);
|
|
1632
|
+
const handleResizeStart = (0, import_react5.useCallback)((e) => {
|
|
1101
1633
|
e.preventDefault();
|
|
1102
1634
|
resizingRef.current = true;
|
|
1103
1635
|
const startX = e.clientX;
|
|
@@ -1122,7 +1654,7 @@ function DesignerView({
|
|
|
1122
1654
|
return a.x - b.x;
|
|
1123
1655
|
});
|
|
1124
1656
|
const selectedField = selectedFieldIds.size === 1 ? fields.find((f) => f.id === Array.from(selectedFieldIds)[0]) || null : null;
|
|
1125
|
-
const renderFieldContent = (0,
|
|
1657
|
+
const renderFieldContent = (0, import_react5.useCallback)((field) => {
|
|
1126
1658
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1127
1659
|
return null;
|
|
1128
1660
|
}
|
|
@@ -1226,39 +1758,92 @@ function DesignerView({
|
|
|
1226
1758
|
type
|
|
1227
1759
|
))
|
|
1228
1760
|
] }),
|
|
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
|
-
|
|
1761
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1762
|
+
"div",
|
|
1763
|
+
{
|
|
1764
|
+
className: `designer-pdf-area ${isPanning ? "panning" : ""}`,
|
|
1765
|
+
ref: pdfAreaRef,
|
|
1766
|
+
onMouseDown: isPanning ? handlePanMouseDown : void 0,
|
|
1767
|
+
onMouseMove: isPanning ? handlePanMouseMove : void 0,
|
|
1768
|
+
onMouseUp: isPanning ? handlePanMouseUp : void 0,
|
|
1769
|
+
onMouseLeave: isPanning ? handlePanMouseUp : void 0,
|
|
1770
|
+
children: [
|
|
1771
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
1772
|
+
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
1773
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Open a PDF to get started" }),
|
|
1774
|
+
/* @__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." }),
|
|
1775
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
1776
|
+
"Select PDF",
|
|
1777
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
1778
|
+
] })
|
|
1779
|
+
] }),
|
|
1780
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1781
|
+
PdfViewer,
|
|
1782
|
+
{
|
|
1783
|
+
pages,
|
|
1784
|
+
fields,
|
|
1785
|
+
selectedFieldIds,
|
|
1786
|
+
onSelectField: handleSelectField,
|
|
1787
|
+
onFieldMove: handleFieldMove,
|
|
1788
|
+
onFieldResize: handleFieldResize,
|
|
1789
|
+
onGroupMove: handleGroupMove,
|
|
1790
|
+
onMoveStart: beginBatch,
|
|
1791
|
+
onMoveEnd: commitBatch,
|
|
1792
|
+
onPageClick: handlePageClick,
|
|
1793
|
+
onDropField: handleDropOnPage,
|
|
1794
|
+
onMarqueeSelect: (ids) => setSelectedFieldIds(new Set(ids)),
|
|
1795
|
+
mode: "designer",
|
|
1796
|
+
renderFieldContent,
|
|
1797
|
+
zoom
|
|
1798
|
+
}
|
|
1799
|
+
)
|
|
1800
|
+
]
|
|
1801
|
+
}
|
|
1802
|
+
),
|
|
1256
1803
|
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1257
1804
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
|
|
1258
1805
|
/* @__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
|
-
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-panel-header", children: [
|
|
1807
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "signer-role-indicator", children: [
|
|
1808
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "signer-role-indicator-label", children: "Editing as" }),
|
|
1809
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: activeRole })
|
|
1810
|
+
] }),
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "zoom-slider", children: [
|
|
1812
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setZoom((z) => Math.max(0.75, z - 0.25)), disabled: zoom <= 0.75, children: "\u2212" }),
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1814
|
+
"input",
|
|
1815
|
+
{
|
|
1816
|
+
type: "range",
|
|
1817
|
+
min: "75",
|
|
1818
|
+
max: "300",
|
|
1819
|
+
step: "25",
|
|
1820
|
+
value: Math.round(zoom * 100),
|
|
1821
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100)
|
|
1822
|
+
}
|
|
1823
|
+
),
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => setZoom((z) => Math.min(3, z + 0.25)), disabled: zoom >= 3, children: "+" }),
|
|
1825
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "zoom-dropdown-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1826
|
+
"select",
|
|
1827
|
+
{
|
|
1828
|
+
className: "zoom-dropdown",
|
|
1829
|
+
value: Math.round(zoom * 100),
|
|
1830
|
+
onChange: (e) => setZoom(Number(e.target.value) / 100),
|
|
1831
|
+
children: [
|
|
1832
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "75", children: "75%" }),
|
|
1833
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "100", children: "100%" }),
|
|
1834
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "125", children: "125%" }),
|
|
1835
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "150", children: "150%" }),
|
|
1836
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "200", children: "200%" }),
|
|
1837
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "250", children: "250%" }),
|
|
1838
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "300", children: "300%" }),
|
|
1839
|
+
![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: [
|
|
1840
|
+
Math.round(zoom * 100),
|
|
1841
|
+
"%"
|
|
1842
|
+
] })
|
|
1843
|
+
]
|
|
1844
|
+
}
|
|
1845
|
+
) })
|
|
1846
|
+
] })
|
|
1262
1847
|
] }),
|
|
1263
1848
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tabs", children: [
|
|
1264
1849
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -1282,82 +1867,163 @@ function DesignerView({
|
|
|
1282
1867
|
)
|
|
1283
1868
|
] }),
|
|
1284
1869
|
/* @__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
|
-
|
|
1870
|
+
rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedFieldIds.size > 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1871
|
+
BulkPropertyPanel,
|
|
1872
|
+
{
|
|
1873
|
+
fields: fields.filter((f) => selectedFieldIds.has(f.id)),
|
|
1874
|
+
signerRoles,
|
|
1875
|
+
onUpdate: handleBulkUpdate,
|
|
1876
|
+
onAlign: handleAlign,
|
|
1877
|
+
onDelete: () => {
|
|
1878
|
+
setFields((prev) => prev.filter((f) => !selectedFieldIds.has(f.id) || f.locked));
|
|
1879
|
+
setSelectedFieldIds((prev) => {
|
|
1880
|
+
const remaining = /* @__PURE__ */ new Set();
|
|
1881
|
+
fields.forEach((f) => {
|
|
1882
|
+
if (prev.has(f.id) && f.locked) remaining.add(f.id);
|
|
1883
|
+
});
|
|
1884
|
+
return remaining;
|
|
1885
|
+
});
|
|
1299
1886
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1314
|
-
"input",
|
|
1887
|
+
}
|
|
1888
|
+
) : selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1889
|
+
FieldPropertyPanel,
|
|
1890
|
+
{
|
|
1891
|
+
field: selectedField,
|
|
1892
|
+
signerRoles,
|
|
1893
|
+
onUpdate: handleFieldUpdate,
|
|
1894
|
+
onDelete: handleFieldDelete,
|
|
1895
|
+
pageSize: pages[selectedField.page] ? { width: pages[selectedField.page].pdfWidth, height: pages[selectedField.page].pdfHeight } : void 0,
|
|
1896
|
+
prefillContent: selectedField.type !== "blackout" && selectedField.type !== "whiteout" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "prefill-section", children: [
|
|
1897
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "Pre-fill Value" }),
|
|
1898
|
+
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1899
|
+
SignatureCanvas,
|
|
1315
1900
|
{
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1901
|
+
width: panelWidth - 40,
|
|
1902
|
+
height: selectedField.type === "initials" ? 100 : 150,
|
|
1903
|
+
onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
|
|
1904
|
+
initialValue: selectedField.value,
|
|
1905
|
+
inkColor: selectedField.inkColor
|
|
1319
1906
|
}
|
|
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",
|
|
1907
|
+
) : selectedField.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
1908
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1909
|
+
"input",
|
|
1910
|
+
{
|
|
1911
|
+
type: "checkbox",
|
|
1912
|
+
checked: selectedField.value === "true",
|
|
1913
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
1914
|
+
}
|
|
1915
|
+
),
|
|
1916
|
+
"Checked"
|
|
1917
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1918
|
+
"input",
|
|
1346
1919
|
{
|
|
1347
|
-
|
|
1348
|
-
|
|
1920
|
+
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1921
|
+
value: selectedField.value,
|
|
1922
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
|
|
1923
|
+
placeholder: `Pre-fill ${selectedField.label}`,
|
|
1924
|
+
className: "prefill-input",
|
|
1925
|
+
style: { color: selectedField.inkColor || "#000000" }
|
|
1349
1926
|
}
|
|
1350
|
-
)
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1927
|
+
)
|
|
1928
|
+
] }) : void 0
|
|
1929
|
+
}
|
|
1930
|
+
) }) : /* @__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." }) }),
|
|
1931
|
+
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) => {
|
|
1932
|
+
const isSel = selectedFieldIds.has(f.id);
|
|
1933
|
+
const isSingleSel = isSel && selectedFieldIds.size === 1;
|
|
1934
|
+
const isTextLike = f.type === "text" || f.type === "dropdown" || f.type === "signed-date";
|
|
1935
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1936
|
+
"div",
|
|
1937
|
+
{
|
|
1938
|
+
className: `field-list-item ${isSel ? "active" : ""}`,
|
|
1939
|
+
onClick: (e) => {
|
|
1940
|
+
if (e.target.tagName === "INPUT") return;
|
|
1941
|
+
if (e.metaKey || e.ctrlKey) {
|
|
1942
|
+
setSelectedFieldIds((prev) => {
|
|
1943
|
+
const next = new Set(prev);
|
|
1944
|
+
if (next.has(f.id)) next.delete(f.id);
|
|
1945
|
+
else next.add(f.id);
|
|
1946
|
+
return next;
|
|
1947
|
+
});
|
|
1948
|
+
} else if (e.shiftKey && selectedFieldIds.size > 0) {
|
|
1949
|
+
const ids = sortedFields.map((sf) => sf.id);
|
|
1950
|
+
const lastId = Array.from(selectedFieldIds).pop();
|
|
1951
|
+
const a = ids.indexOf(lastId);
|
|
1952
|
+
const b = ids.indexOf(f.id);
|
|
1953
|
+
if (a >= 0 && b >= 0) {
|
|
1954
|
+
const start = Math.min(a, b);
|
|
1955
|
+
const end = Math.max(a, b);
|
|
1956
|
+
setSelectedFieldIds((prev) => {
|
|
1957
|
+
const next = new Set(prev);
|
|
1958
|
+
ids.slice(start, end + 1).forEach((id) => next.add(id));
|
|
1959
|
+
return next;
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
} else {
|
|
1963
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([f.id]));
|
|
1964
|
+
}
|
|
1965
|
+
},
|
|
1966
|
+
children: [
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1968
|
+
"span",
|
|
1969
|
+
{
|
|
1970
|
+
className: "field-list-dot",
|
|
1971
|
+
style: { backgroundColor: getSignerColor(f.assignee) }
|
|
1972
|
+
}
|
|
1973
|
+
),
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-name", children: f.label }),
|
|
1975
|
+
f.locked && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-lock", title: "Locked", children: "\u{1F512}" }),
|
|
1976
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "field-list-page", children: [
|
|
1977
|
+
"p",
|
|
1978
|
+
f.page + 1
|
|
1979
|
+
] }),
|
|
1980
|
+
f.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-required", children: "*" }),
|
|
1981
|
+
isSingleSel && isTextLike && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1982
|
+
"input",
|
|
1983
|
+
{
|
|
1984
|
+
className: "field-list-input",
|
|
1985
|
+
type: "text",
|
|
1986
|
+
value: f.value,
|
|
1987
|
+
placeholder: f.placeholder,
|
|
1988
|
+
maxLength: f.maxLength || void 0,
|
|
1989
|
+
autoFocus: true,
|
|
1990
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.value }),
|
|
1991
|
+
onKeyDown: (e) => {
|
|
1992
|
+
if (e.key === "Tab") {
|
|
1993
|
+
e.preventDefault();
|
|
1994
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
1995
|
+
const nextIdx = idx + dir;
|
|
1996
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
1997
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
onClick: (e) => e.stopPropagation()
|
|
2002
|
+
}
|
|
2003
|
+
),
|
|
2004
|
+
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)(
|
|
2005
|
+
"input",
|
|
2006
|
+
{
|
|
2007
|
+
type: "checkbox",
|
|
2008
|
+
checked: f.value === "true",
|
|
2009
|
+
onChange: (e) => handleFieldUpdate(f.id, { value: e.target.checked ? "true" : "" }),
|
|
2010
|
+
onKeyDown: (e) => {
|
|
2011
|
+
if (e.key === "Tab") {
|
|
2012
|
+
e.preventDefault();
|
|
2013
|
+
const dir = e.shiftKey ? -1 : 1;
|
|
2014
|
+
const nextIdx = idx + dir;
|
|
2015
|
+
if (nextIdx >= 0 && nextIdx < sortedFields.length) {
|
|
2016
|
+
setSelectedFieldIds(/* @__PURE__ */ new Set([sortedFields[nextIdx].id]));
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
) })
|
|
2022
|
+
]
|
|
2023
|
+
},
|
|
2024
|
+
f.id
|
|
2025
|
+
);
|
|
2026
|
+
}) }) })
|
|
1361
2027
|
] }),
|
|
1362
2028
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "powered-by", children: [
|
|
1363
2029
|
"Powered by ",
|
|
@@ -1370,7 +2036,7 @@ function DesignerView({
|
|
|
1370
2036
|
}
|
|
1371
2037
|
|
|
1372
2038
|
// src/components/pdf-builder/SignerView.tsx
|
|
1373
|
-
var
|
|
2039
|
+
var import_react7 = require("react");
|
|
1374
2040
|
|
|
1375
2041
|
// src/utils/pdfFiller.ts
|
|
1376
2042
|
var import_pdf_lib = require("pdf-lib");
|
|
@@ -1507,7 +2173,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1507
2173
|
}
|
|
1508
2174
|
|
|
1509
2175
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1510
|
-
var
|
|
2176
|
+
var import_react6 = require("react");
|
|
1511
2177
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1512
2178
|
function isFieldFilled(f) {
|
|
1513
2179
|
if (!f.required) return true;
|
|
@@ -1522,7 +2188,7 @@ function FieldNavigator({
|
|
|
1522
2188
|
onComplete,
|
|
1523
2189
|
completeLabel = "Complete"
|
|
1524
2190
|
}) {
|
|
1525
|
-
const [showIncomplete, setShowIncomplete] = (0,
|
|
2191
|
+
const [showIncomplete, setShowIncomplete] = (0, import_react6.useState)(false);
|
|
1526
2192
|
const currentIndex = fields.findIndex((f) => f.id === currentFieldId);
|
|
1527
2193
|
const hasPrev = currentIndex > 0;
|
|
1528
2194
|
const hasNext = currentIndex < fields.length - 1;
|
|
@@ -1698,7 +2364,12 @@ function SignerView({
|
|
|
1698
2364
|
initialValues,
|
|
1699
2365
|
submitLabel,
|
|
1700
2366
|
signerOrder: signerOrderProp,
|
|
1701
|
-
transforms
|
|
2367
|
+
transforms,
|
|
2368
|
+
onChange,
|
|
2369
|
+
onSignerComplete,
|
|
2370
|
+
includeAuditTrail,
|
|
2371
|
+
exportFormat,
|
|
2372
|
+
onExport
|
|
1702
2373
|
} = {}) {
|
|
1703
2374
|
if (!isValidApiKey(apiKey)) {
|
|
1704
2375
|
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 +2381,29 @@ function SignerView({
|
|
|
1710
2381
|
] })
|
|
1711
2382
|
] }) });
|
|
1712
2383
|
}
|
|
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,
|
|
2384
|
+
const [pages, setPages] = (0, import_react7.useState)([]);
|
|
2385
|
+
const [fields, setFields] = (0, import_react7.useState)([]);
|
|
2386
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react7.useState)(null);
|
|
2387
|
+
const [signerRoles, setSignerRoles] = (0, import_react7.useState)([]);
|
|
2388
|
+
const [currentSignerIndex, setCurrentSignerIndex] = (0, import_react7.useState)(() => {
|
|
1718
2389
|
if (initialSigner && signerOrderProp) {
|
|
1719
2390
|
const idx = signerOrderProp.indexOf(initialSigner);
|
|
1720
2391
|
return idx >= 0 ? idx : 0;
|
|
1721
2392
|
}
|
|
1722
2393
|
return 0;
|
|
1723
2394
|
});
|
|
1724
|
-
const initializedRef = (0,
|
|
1725
|
-
const
|
|
1726
|
-
const [
|
|
1727
|
-
const [
|
|
1728
|
-
const [
|
|
1729
|
-
const
|
|
2395
|
+
const initializedRef = (0, import_react7.useRef)(false);
|
|
2396
|
+
const auditLogRef = (0, import_react7.useRef)([]);
|
|
2397
|
+
const [loading, setLoading] = (0, import_react7.useState)(false);
|
|
2398
|
+
const [submitting, setSubmitting] = (0, import_react7.useState)(false);
|
|
2399
|
+
const [pdfSource, setPdfSource] = (0, import_react7.useState)(null);
|
|
2400
|
+
const [callbackUrl, setCallbackUrl] = (0, import_react7.useState)(initialCallbackUrl || "");
|
|
2401
|
+
const containerRef = (0, import_react7.useRef)(null);
|
|
1730
2402
|
const signerOrder = signerOrderProp || (signerRoles.length > 0 ? [...signerRoles.filter((r) => r !== "Sender"), ...signerRoles.filter((r) => r === "Sender")] : [initialSigner || "Signer 1"]);
|
|
1731
2403
|
const signer = signerOrder[currentSignerIndex] || signerOrder[0] || "Signer 1";
|
|
1732
2404
|
const isLastSigner = currentSignerIndex >= signerOrder.length - 1;
|
|
1733
2405
|
const isMultiSigner = signerOrder.length > 1;
|
|
1734
|
-
(0,
|
|
2406
|
+
(0, import_react7.useEffect)(() => {
|
|
1735
2407
|
if (fields.length === 0 || !isMultiSigner) return;
|
|
1736
2408
|
const signerFields = fields.filter(
|
|
1737
2409
|
(f) => f.assignee === signer && f.type !== "blackout" && f.type !== "whiteout"
|
|
@@ -1746,7 +2418,7 @@ function SignerView({
|
|
|
1746
2418
|
setSelectedFieldId(null);
|
|
1747
2419
|
}
|
|
1748
2420
|
}, [currentSignerIndex, fields, signer, isLastSigner, isMultiSigner]);
|
|
1749
|
-
(0,
|
|
2421
|
+
(0, import_react7.useEffect)(() => {
|
|
1750
2422
|
if (initialTemplate) {
|
|
1751
2423
|
let templateFields = initialTemplate.fields;
|
|
1752
2424
|
if (initialTemplate.signerRoles) setSignerRoles(initialTemplate.signerRoles);
|
|
@@ -1816,7 +2488,7 @@ function SignerView({
|
|
|
1816
2488
|
window.addEventListener("message", handleMessage);
|
|
1817
2489
|
return () => window.removeEventListener("message", handleMessage);
|
|
1818
2490
|
}, [initialTemplate, initialPdfUrl]);
|
|
1819
|
-
const loadPdf = (0,
|
|
2491
|
+
const loadPdf = (0, import_react7.useCallback)(async (source) => {
|
|
1820
2492
|
setLoading(true);
|
|
1821
2493
|
try {
|
|
1822
2494
|
const rendered = await renderPdfPages(source);
|
|
@@ -1836,13 +2508,22 @@ function SignerView({
|
|
|
1836
2508
|
});
|
|
1837
2509
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1838
2510
|
const isFieldEditable = selectedField ? selectedField.assignee === signer && selectedField.type !== "blackout" && selectedField.type !== "whiteout" && !selectedField.formula : false;
|
|
1839
|
-
const handleFieldUpdate = (0,
|
|
2511
|
+
const handleFieldUpdate = (0, import_react7.useCallback)((id, value) => {
|
|
1840
2512
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1841
2513
|
}, []);
|
|
1842
|
-
const handleFieldPropertyUpdate = (0,
|
|
2514
|
+
const handleFieldPropertyUpdate = (0, import_react7.useCallback)((id, updates) => {
|
|
1843
2515
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, ...updates } : f));
|
|
1844
2516
|
}, []);
|
|
1845
|
-
const
|
|
2517
|
+
const fieldValuesKey = fields.map((f) => `${f.id}:${f.value}`).join("|");
|
|
2518
|
+
(0, import_react7.useEffect)(() => {
|
|
2519
|
+
if (!onChange) return;
|
|
2520
|
+
const values = {};
|
|
2521
|
+
fields.forEach((f) => {
|
|
2522
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2523
|
+
});
|
|
2524
|
+
onChange(values);
|
|
2525
|
+
}, [fieldValuesKey]);
|
|
2526
|
+
const handleNavigate = (0, import_react7.useCallback)((fieldId) => {
|
|
1846
2527
|
setSelectedFieldId(fieldId);
|
|
1847
2528
|
const field = fields.find((f) => f.id === fieldId);
|
|
1848
2529
|
if (field && containerRef.current) {
|
|
@@ -1855,10 +2536,23 @@ function SignerView({
|
|
|
1855
2536
|
const allRequiredFilled = editableFields.every((f) => {
|
|
1856
2537
|
if (!f.required) return true;
|
|
1857
2538
|
if (f.type === "checkbox") return true;
|
|
1858
|
-
|
|
2539
|
+
if (!f.value) return false;
|
|
2540
|
+
if (f.minLength && f.value.length < f.minLength) return false;
|
|
2541
|
+
return true;
|
|
1859
2542
|
});
|
|
1860
|
-
const
|
|
2543
|
+
const getFieldValues = (0, import_react7.useCallback)(() => {
|
|
2544
|
+
const values = {};
|
|
2545
|
+
fields.forEach((f) => {
|
|
2546
|
+
if (f.type !== "blackout" && f.type !== "whiteout") values[f.label] = f.value || "";
|
|
2547
|
+
});
|
|
2548
|
+
return values;
|
|
2549
|
+
}, [fields]);
|
|
2550
|
+
const handleAdvanceOrSubmit = (0, import_react7.useCallback)(async () => {
|
|
1861
2551
|
if (!allRequiredFilled) return;
|
|
2552
|
+
auditLogRef.current.push({ signer, completedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2553
|
+
if (onSignerComplete) {
|
|
2554
|
+
onSignerComplete(signer, getFieldValues());
|
|
2555
|
+
}
|
|
1862
2556
|
if (!isLastSigner) {
|
|
1863
2557
|
setCurrentSignerIndex((prev) => prev + 1);
|
|
1864
2558
|
setSelectedFieldId(null);
|
|
@@ -1872,8 +2566,40 @@ function SignerView({
|
|
|
1872
2566
|
setSubmitting(true);
|
|
1873
2567
|
try {
|
|
1874
2568
|
const finalFields = resolveAllFormulas(fields, transforms);
|
|
1875
|
-
|
|
2569
|
+
let pdfBytes;
|
|
2570
|
+
if (includeAuditTrail) {
|
|
2571
|
+
const { PDFDocument: PDFDocument2, StandardFonts: StandardFonts2, rgb: rgb2 } = await import("pdf-lib");
|
|
2572
|
+
const basePdf = await generateFilledPdf(pdfSource, finalFields);
|
|
2573
|
+
const pdfDoc = await PDFDocument2.load(basePdf);
|
|
2574
|
+
const font = await pdfDoc.embedFont(StandardFonts2.Helvetica);
|
|
2575
|
+
const boldFont = await pdfDoc.embedFont(StandardFonts2.HelveticaBold);
|
|
2576
|
+
const auditPage = pdfDoc.addPage([612, 792]);
|
|
2577
|
+
let cy = 742;
|
|
2578
|
+
auditPage.drawText("Signing Audit Trail", { x: 50, y: cy, size: 18, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2579
|
+
cy -= 30;
|
|
2580
|
+
auditPage.drawText(`Document completed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, { x: 50, y: cy, size: 10, font, color: rgb2(0.4, 0.4, 0.4) });
|
|
2581
|
+
cy -= 30;
|
|
2582
|
+
for (const entry of auditLogRef.current) {
|
|
2583
|
+
auditPage.drawText(`${entry.signer}`, { x: 50, y: cy, size: 12, font: boldFont, color: rgb2(0, 0, 0) });
|
|
2584
|
+
auditPage.drawText(`Completed: ${new Date(entry.completedAt).toLocaleString()}`, { x: 200, y: cy, size: 10, font, color: rgb2(0.3, 0.3, 0.3) });
|
|
2585
|
+
cy -= 22;
|
|
2586
|
+
}
|
|
2587
|
+
pdfBytes = await pdfDoc.save();
|
|
2588
|
+
} else {
|
|
2589
|
+
pdfBytes = await generateFilledPdf(pdfSource, finalFields);
|
|
2590
|
+
}
|
|
1876
2591
|
const blob = new Blob([pdfBytes.slice().buffer], { type: "application/pdf" });
|
|
2592
|
+
if (onExport && exportFormat) {
|
|
2593
|
+
const values = getFieldValues();
|
|
2594
|
+
if (exportFormat === "json") {
|
|
2595
|
+
onExport(JSON.stringify(values, null, 2), "json");
|
|
2596
|
+
} else {
|
|
2597
|
+
const headers = Object.keys(values);
|
|
2598
|
+
const row = headers.map((h) => `"${(values[h] || "").replace(/"/g, '""')}"`);
|
|
2599
|
+
onExport(`${headers.join(",")}
|
|
2600
|
+
${row.join(",")}`, "csv");
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
1877
2603
|
if (callbackUrl) {
|
|
1878
2604
|
await postPdfToCallback(pdfBytes, callbackUrl, "signed-document.pdf");
|
|
1879
2605
|
}
|
|
@@ -1889,8 +2615,8 @@ function SignerView({
|
|
|
1889
2615
|
} finally {
|
|
1890
2616
|
setSubmitting(false);
|
|
1891
2617
|
}
|
|
1892
|
-
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner]);
|
|
1893
|
-
const renderFieldContent = (0,
|
|
2618
|
+
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete, isLastSigner, signer, onSignerComplete, includeAuditTrail, exportFormat, onExport, getFieldValues, transforms]);
|
|
2619
|
+
const renderFieldContent = (0, import_react7.useCallback)((field) => {
|
|
1894
2620
|
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1895
2621
|
return null;
|
|
1896
2622
|
}
|
|
@@ -1967,7 +2693,7 @@ function SignerView({
|
|
|
1967
2693
|
}
|
|
1968
2694
|
);
|
|
1969
2695
|
}, [signer, handleFieldUpdate, setSelectedFieldId]);
|
|
1970
|
-
(0,
|
|
2696
|
+
(0, import_react7.useEffect)(() => {
|
|
1971
2697
|
const sigFields = fields.filter((f) => f.assignee === signer && f.type === "signature" && f.value);
|
|
1972
2698
|
if (sigFields.length > 0) {
|
|
1973
2699
|
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
@@ -1979,7 +2705,7 @@ function SignerView({
|
|
|
1979
2705
|
}));
|
|
1980
2706
|
}
|
|
1981
2707
|
}, [fields.filter((f) => f.type === "signature" && f.value).length]);
|
|
1982
|
-
(0,
|
|
2708
|
+
(0, import_react7.useEffect)(() => {
|
|
1983
2709
|
const hasFormulas = fields.some((f) => f.formula);
|
|
1984
2710
|
if (!hasFormulas) return;
|
|
1985
2711
|
const resolved = resolveAllFormulas(fields, transforms);
|
|
@@ -2108,7 +2834,7 @@ function SignerView({
|
|
|
2108
2834
|
}
|
|
2109
2835
|
|
|
2110
2836
|
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
2111
|
-
var
|
|
2837
|
+
var import_react8 = require("react");
|
|
2112
2838
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2113
2839
|
function SignerRoleSelector({
|
|
2114
2840
|
roles,
|
|
@@ -2117,8 +2843,8 @@ function SignerRoleSelector({
|
|
|
2117
2843
|
onAddRole,
|
|
2118
2844
|
onRemoveRole
|
|
2119
2845
|
}) {
|
|
2120
|
-
const [isAdding, setIsAdding] = (0,
|
|
2121
|
-
const [newRoleName, setNewRoleName] = (0,
|
|
2846
|
+
const [isAdding, setIsAdding] = (0, import_react8.useState)(false);
|
|
2847
|
+
const [newRoleName, setNewRoleName] = (0, import_react8.useState)("");
|
|
2122
2848
|
const handleAdd = () => {
|
|
2123
2849
|
if (newRoleName.trim()) {
|
|
2124
2850
|
onAddRole(newRoleName.trim());
|