@unlev/exeq 0.2.2 → 0.3.0

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