@unlev/exeq 0.2.1 → 0.3.0

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