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