@unlev/exeq 0.2.2 → 0.3.1

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